Skip to content

Commit

Permalink
Prevent repeating payments on same payment hash (#652)
Browse files Browse the repository at this point in the history
The check must include pending payments, not only successfully
sent payments.

---------

Co-authored-by: Robbie Hanson <[email protected]>
  • Loading branch information
dpad85 and robbiehanson authored Nov 19, 2024
1 parent a4269e0 commit a222785
Show file tree
Hide file tree
Showing 18 changed files with 83 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ fun LnurlPayView(
details = when (state) {
is LnurlPayViewState.Error.Generic -> state.cause.localizedMessage
is LnurlPayViewState.Error.PayError -> when (val error = state.error) {
is SendManager.LnurlPayError.PaymentPending -> annotatedStringResource(R.string.lnurl_pay_error_payment_pending, payIntent.callback.host)
is SendManager.LnurlPayError.AlreadyPaidInvoice -> annotatedStringResource(R.string.lnurl_pay_error_already_paid, payIntent.callback.host)
is SendManager.LnurlPayError.ChainMismatch -> annotatedStringResource(R.string.lnurl_pay_error_invalid_chain, payIntent.callback.host)
is SendManager.LnurlPayError.BadResponseError -> when (val errorDetail = error.err) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ fun SendManager.ParseResult.BadRequest.toLocalisedMessage(): String {
return when (val reason = this.reason) {
is SendManager.BadRequestReason.Expired -> stringResource(R.string.send_error_invoice_expired)
is SendManager.BadRequestReason.ChainMismatch -> stringResource(R.string.send_error_invalid_chain)
is SendManager.BadRequestReason.PaymentPending -> stringResource(R.string.send_error_payment_pending)
is SendManager.BadRequestReason.AlreadyPaidInvoice -> stringResource(R.string.send_error_already_paid)
is SendManager.BadRequestReason.ServiceError -> reason.error.toLocalisedMessage().text
is SendManager.BadRequestReason.InvalidLnurl -> stringResource(R.string.send_error_lnurl_invalid)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@
<!-- scanning errors -->

<string name="send_error_invoice_expired">Esta factura está vencida.</string>
<string name="send_error_payment_pending">Este pago ya se está procesando. Por favor, espere a que se complete.</string>
<string name="send_error_already_paid">Este pago ya se realizó.</string>
<string name="send_error_invalid_chain">Este pago no usa la misma cadena de bloques de la billetera.</string>
<string name="send_error_lnurl_invalid">Error al procesar este enlace LNURL. Comprueba que sea válido.</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@
<!-- scanning errors -->

<string name="send_error_invoice_expired">Platnost této faktury vypršela.</string>
<string name="send_error_payment_pending">Tato platba se již zpracovává. Počkejte prosím na její dokončení.</string>
<string name="send_error_already_paid">Tato platba již byla uhrazena.</string>
<string name="send_error_invalid_chain">Tato platba nepoužívá stejný blockchain jako vaše peněženka.</string>
<string name="send_error_lnurl_invalid">Tento LNURL odkaz se nepodařilo zpracovat. Ujistěte se, že je platný.</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@
<!-- scanning errors -->

<string name="send_error_invoice_expired">Die Rechnung ist abgelaufen.</string>
<string name="send_error_payment_pending">Diese Zahlung wird bereits bearbeitet. Bitte warten Sie, bis sie abgeschlossen ist.</string>
<string name="send_error_already_paid">Diese Zahlung wurde bereits getätigt.</string>
<string name="send_error_invalid_chain">Diese Zahlung verwendet nicht die gleiche Blockchain wie Ihre Wallet.</string>
<string name="send_error_lnurl_invalid">Dieser LNURL-Link konnte nicht verarbeitet werden. Stellen Sie sicher, dass er gültig ist.</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
<!-- scanning errors -->

<string name="send_error_invoice_expired">Esta factura ha caducado.</string>
<string name="send_error_payment_pending">Este pago ya se está procesando. Por favor, espere a que se complete.</string>
<string name="send_error_already_paid">Este pago ya ha sido abonado.</string>
<string name="send_error_invalid_chain">Este pago no utiliza la misma blockchain que su cartera.</string>
<string name="send_error_lnurl_invalid">No se ha podido procesar este enlace LNURL. Asegúrese de que es válido.</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
<!-- scanning errors -->

<string name="send_error_invoice_expired">Cette requête a expiré.</string>
<string name="send_error_payment_pending">Ce paiement est déjà en cours de traitement. Veuillez attendre qu\'il soit terminé.</string>
<string name="send_error_already_paid">Ce paiement a déjà été réglé.</string>
<string name="send_error_invalid_chain">Ce paiement n\'utilise pas la même blockchain que votre wallet.</string>
<string name="send_error_lnurl_invalid">Ce lien LNURL n\'a pas pu être traité. Assurez-vous qu\'il soit valide.</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@
<!-- scanning errors -->

<string name="send_error_invoice_expired">Esta fatura está expirada.</string>
<string name="send_error_payment_pending">Este pagamento já está sendo processado. Aguarde a conclusão.</string>
<string name="send_error_already_paid">Este pagamento já foi pago.</string>
<string name="send_error_invalid_chain">Este pagamento não usa o mesmo blockchain que sua carteira.</string>
<string name="send_error_lnurl_invalid">Falha ao processar esse link LNURL. Verifique se ele é válido.</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@
<!-- scanning errors -->

<string name="send_error_invoice_expired">Platnosť tejto faktúry vypršala.</string>
<string name="send_error_payment_pending">Táto platba sa už spracováva. Počkajte, prosím, na jej dokončenie.</string>
<string name="send_error_already_paid">Táto platba už bola uhradená.</string>
<string name="send_error_invalid_chain">Táto platba nepoužíva ten istý blockchain ako vaša peňaženka.</string>
<string name="send_error_lnurl_invalid">Tento LNURL odkaz sa nepodarilo spracovať. Uistite sa, že je platný.</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@
<!-- scanning errors -->

<string name="send_error_invoice_expired">Ankara hii imeisha muda wake.</string>
<string name="send_error_payment_pending">Malipo haya tayari yanachakatwa. Tafadhali subiri ikamilike.</string>
<string name="send_error_already_paid">Malipo haya tayari yamelipwa.</string>
<string name="send_error_invalid_chain">Malipo haya hayatumii mnyororo sawa na pochi yako.</string>
<string name="send_error_lnurl_invalid">Imeshindwa kuchakata kiungo hiki cha LNURL. Hakikisha ni halali.</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@
<!-- scanning errors -->

<string name="send_error_invoice_expired">Hoá đơn này đã hết hạn.</string>
<string name="send_error_payment_pending">Thanh toán này đang được xử lý. Vui lòng đợi để hoàn tất.</string>
<string name="send_error_already_paid">Khoản thanh toán này đã được trả.</string>
<string name="send_error_invalid_chain">Khoản thanh toán này không sử dụng cùng blockchain với ví của bạn.</string>
<string name="send_error_lnurl_invalid">Không thể xử lý liên kết LNURL này. Hãy đảm bảo liên kết này có hiệu lực.</string>
Expand Down
1 change: 1 addition & 0 deletions phoenix-android/src/main/res/values/important_strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@
<!-- send errors -->

<string name="send_error_invoice_expired">This invoice is expired.</string>
<string name="send_error_payment_pending">This payment is already being processed. Please wait for it to complete.</string>
<string name="send_error_already_paid">This payment has already been paid.</string>
<string name="send_error_invalid_chain">This payment does not use the same blockchain as your wallet.</string>
<string name="send_error_lnurl_invalid">Failed to process this LNURL link. Make sure it is valid.</string>
Expand Down
1 change: 1 addition & 0 deletions phoenix-android/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,7 @@

<string name="lnurl_pay_error_header">Payment has failed.</string>
<string name="lnurl_pay_error_invalid_chain">The invoice returned by <b>%1$s</b> does not use the same chain as your wallet.</string>
<string name="lnurl_pay_error_payment_pending">The invoice returned by <b>%1$s</b> is already in progress.</string>
<string name="lnurl_pay_error_already_paid">The invoice returned by <b>%1$s</b> has already been paid.</string>
<string name="lnurl_pay_error_invalid_amount">The invoice returned by <b>%1$s</b> has an incorrect amount.</string>
<string name="lnurl_pay_error_invalid_malformed">The invoice returned by <b>%1$s</b> is malformed.</string>
Expand Down
38 changes: 38 additions & 0 deletions phoenix-ios/phoenix-ios/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -40476,6 +40476,9 @@
}
}
}
},
"The received invoice is already in progress." : {

},
"The recipient is offline." : {
"localizations" : {
Expand Down Expand Up @@ -42088,6 +42091,41 @@
}
}
},
"This payment is already being processed. Please wait for it to complete." : {
"comment" : "Error message - scanning lightning invoice",
"localizations" : {
"cs" : {
"stringUnit" : {
"state" : "translated",
"value" : "Tato platba se již zpracovává. Počkejte prosím na její dokončení."
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Diese Zahlung wird bereits bearbeitet. Bitte warten Sie, bis sie abgeschlossen ist."
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Este pago ya se está procesando. Por favor, espere a que se complete."
}
},
"es-419" : {
"stringUnit" : {
"state" : "translated",
"value" : "Este pago ya se está procesando. Por favor, espere a que se complete."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ce paiement est déjà en cours de traitement. Veuillez attendre qu'il soit terminé."
}
}
}
},
"This screen is a debugging tool that can be used to manually import encrypted channels data.\n\nUse with caution." : {
"localizations" : {
"ar" : {
Expand Down
2 changes: 2 additions & 0 deletions phoenix-ios/phoenix-ios/kotlin/KotlinTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ extension SendManager {
typealias ParseResult_Lnurl_Auth = ParseResultLnurlAuth

typealias BadRequestReason_AlreadyPaidInvoice = BadRequestReasonAlreadyPaidInvoice
typealias BadRequestReason_PaymentPending = BadRequestReasonPaymentPending
typealias BadRequestReason_Bip353InvalidOffer = BadRequestReasonBip353InvalidOffer
typealias BadRequestReason_Bip353InvalidUri = BadRequestReasonBip353InvalidUri
typealias BadRequestReason_Bip353NameNotFound = BadRequestReasonBip353NameNotFound
Expand All @@ -40,6 +41,7 @@ extension SendManager {
typealias LnurlPay_Error_BadResponseError = LnurlPayErrorBadResponseError
typealias LnurlPay_Error_ChainMismatch = LnurlPayErrorChainMismatch
typealias LnurlPay_Error_AlreadyPaidInvoice = LnurlPayErrorAlreadyPaidInvoice
typealias LnurlPay_Error_PaymentPending = LnurlPayErrorPaymentPending

typealias LnurlWithdraw_Error = LnurlWithdrawError
typealias LnurlWithdraw_Error_RemoteError = LnurlWithdrawErrorRemoteError
Expand Down
4 changes: 4 additions & 0 deletions phoenix-ios/phoenix-ios/views/send/LnurlFlowErrorNotice.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ struct LnurlFlowErrorNotice: View {

Text("You have already paid this invoice.")

} else if let _ = payError as? SendManager.LnurlPay_Error_PaymentPending {

Text("The received invoice is already in progress.")

} else {
genericErrorMessage()
}
Expand Down
7 changes: 7 additions & 0 deletions phoenix-ios/phoenix-ios/views/send/ParseResultHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ class ParseResultHelper {
localized: "You've already paid this invoice. Paying it again could result in stolen funds.",
comment: "Error message - scanning lightning invoice"
)

case is SendManager.BadRequestReason_PaymentPending:

msg = String(
localized: "This payment is already being processed. Please wait for it to complete.",
comment: "Error message - scanning lightning invoice"
)

case is SendManager.BadRequestReason_Bip353InvalidOffer:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ class SendManager(
private val log = loggerFactory.newLogger(this::class)

sealed class BadRequestReason : Exception() {
object UnknownFormat : BadRequestReason()
object AlreadyPaidInvoice : BadRequestReason()
data object UnknownFormat : BadRequestReason()
data object AlreadyPaidInvoice : BadRequestReason()
data object PaymentPending : BadRequestReason()
data class Expired(val timestampSeconds: Long, val expirySeconds: Long) : BadRequestReason()
data class ChainMismatch(val expected: Chain) : BadRequestReason()
data class ServiceError(val url: Url, val error: LnurlError.RemoteFailure) : BadRequestReason()
Expand All @@ -86,6 +87,7 @@ class SendManager(
data class BadResponseError(val err: LnurlError.Pay.Invoice) : LnurlPayError()
data class ChainMismatch(val expected: Chain) : LnurlPayError()
data object AlreadyPaidInvoice : LnurlPayError()
data object PaymentPending : LnurlPayError()
}

sealed class LnurlWithdrawError {
Expand Down Expand Up @@ -209,10 +211,16 @@ class SendManager(
}

val db = databaseManager.databases.filterNotNull().first()
return if (db.payments.listLightningOutgoingPayments(invoice.paymentHash).any { it.status is LightningOutgoingPayment.Status.Completed.Succeeded }) {
BadRequestReason.AlreadyPaidInvoice
} else {
null
val similarPayments = db.payments.listLightningOutgoingPayments(invoice.paymentHash)
// we MUST raise an error if this payment hash has already been paid, or is being paid.
// parallel pending payments on the same payment hash can trigger force-closes
// FIXME: this check should be done in lightning-kmp, not in Phoenix
return when {
similarPayments.any { it.status is LightningOutgoingPayment.Status.Completed.Succeeded || it.parts.any { part -> part.status is LightningOutgoingPayment.Part.Status.Succeeded } } ->
BadRequestReason.AlreadyPaidInvoice
similarPayments.any { it.status is LightningOutgoingPayment.Status.Pending || it.parts.any { part -> part.status is LightningOutgoingPayment.Part.Status.Pending } } ->
BadRequestReason.PaymentPending
else -> null
}
}

Expand Down Expand Up @@ -471,22 +479,15 @@ class SendManager(
return try {
val invoice = task.await()
when (checkForBadBolt11Invoice(invoice.invoice)) {
is BadRequestReason.ChainMismatch -> Either.Left(
LnurlPayError.ChainMismatch(expected = chain)
)
is BadRequestReason.AlreadyPaidInvoice -> Either.Left(
LnurlPayError.AlreadyPaidInvoice
)
is BadRequestReason.ChainMismatch -> Either.Left(LnurlPayError.ChainMismatch(expected = chain))
is BadRequestReason.AlreadyPaidInvoice -> Either.Left(LnurlPayError.AlreadyPaidInvoice)
is BadRequestReason.PaymentPending -> Either.Left(LnurlPayError.PaymentPending)
else -> Either.Right(invoice)
}
} catch (err: Throwable) {
when (err) {
is LnurlError.RemoteFailure -> Either.Left(
LnurlPayError.RemoteError(err)
)
is LnurlError.Pay.Invoice -> Either.Left(
LnurlPayError.BadResponseError(err)
)
is LnurlError.RemoteFailure -> Either.Left(LnurlPayError.RemoteError(err))
is LnurlError.Pay.Invoice -> Either.Left(LnurlPayError.BadResponseError(err))
else -> Either.Left(
LnurlPayError.RemoteError(
LnurlError.RemoteFailure.Unreadable(
Expand Down

0 comments on commit a222785

Please sign in to comment.