Skip to content

Commit

Permalink
Handle new outgoing payment failures (#563)
Browse files Browse the repository at this point in the history
Outgoing LN parts have a new failure type, which will
help provide more meaningful error messages. The
payments database interface has also changed to
accomodate these new types.

See ACINQ/lightning-kmp#634

---------

Co-authored-by: Robbie Hanson <304604+robbiehanson@users.noreply.github.com>
  • Loading branch information
dpad85 and robbiehanson committed May 28, 2024
1 parent 7720975 commit bd7f5d1
Show file tree
Hide file tree
Showing 24 changed files with 690 additions and 177 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import fr.acinq.bitcoin.ByteVector32
import fr.acinq.bitcoin.TxId
import fr.acinq.bitcoin.utils.Either
import fr.acinq.lightning.blockchain.electrum.ElectrumConnectionStatus
import fr.acinq.lightning.blockchain.electrum.getConfirmations
import fr.acinq.lightning.db.*
import fr.acinq.lightning.payment.FinalFailure
import fr.acinq.lightning.payment.OutgoingPaymentFailure
import fr.acinq.lightning.utils.msat
import fr.acinq.lightning.utils.sum
import fr.acinq.lightning.wire.LiquidityAds
Expand Down Expand Up @@ -119,8 +121,8 @@ fun PaymentDetailsSplashView(
}

if (payment is LightningOutgoingPayment) {
(payment.status as? LightningOutgoingPayment.Status.Completed.Failed)?.let {
PaymentErrorView(status = it)
(payment.status as? LightningOutgoingPayment.Status.Completed.Failed)?.let { status ->
PaymentErrorView(status = status, failedParts = payment.parts.map { it.status }.filterIsInstance<LightningOutgoingPayment.Part.Status.Failed>())
}
}

Expand Down Expand Up @@ -594,8 +596,9 @@ private fun InboundLiquidityLeaseDetails(lease: LiquidityAds.Lease) {
}

@Composable
private fun PaymentErrorView(status: LightningOutgoingPayment.Status.Completed.Failed) {
translatePaymentError(failure = status.reason)?.let {
private fun PaymentErrorView(status: LightningOutgoingPayment.Status.Completed.Failed, failedParts: List<LightningOutgoingPayment.Part.Status.Failed>) {
val failure = remember(status, failedParts) { OutgoingPaymentFailure(status.reason, failedParts) }
translatePaymentError(failure).let {
Spacer(modifier = Modifier.height(8.dp))
SplashLabelRow(label = stringResource(id = R.string.paymentdetails_error_label)) {
Text(text = it)
Expand Down Expand Up @@ -668,9 +671,7 @@ private fun ConfirmationView(

suspend fun getConfirmations(): Int {
val confirmations = electrumClient.getConfirmations(txId)
// log.debug { "retrieved confirmations=$confirmations from electrum for tx=$txId" }
return confirmations ?: run {
// log.debug { "retrying getConfirmations from electrum in 5 sec" }
delay(5_000)
getConfirmations()
}
Expand Down Expand Up @@ -749,20 +750,41 @@ private fun BumpTransactionDialog(
}

@Composable
private fun translatePaymentError(failure: FinalFailure): String? {
return when (failure) {
FinalFailure.RetryExhausted -> stringResource(id = R.string.outgoing_finalfailure_noroutefound)
FinalFailure.NoRouteToRecipient -> stringResource(id = R.string.outgoing_finalfailure_noroutefound)
FinalFailure.RecipientUnreachable -> stringResource(id = R.string.outgoing_finalfailure_noroutefound)

FinalFailure.AlreadyPaid -> stringResource(id = R.string.outgoing_finalfailure_alreadypaid)

FinalFailure.NoAvailableChannels,
FinalFailure.InsufficientBalance,
FinalFailure.FeaturesNotSupported,
FinalFailure.InvalidPaymentAmount,
FinalFailure.InvalidPaymentId,
FinalFailure.UnknownError,
FinalFailure.WalletRestarted -> null
private fun translatePaymentError(paymentFailure: OutgoingPaymentFailure): String {
return when (val result = paymentFailure.explain()) {
is Either.Left -> {
when (val partFailure = result.value) {
is LightningOutgoingPayment.Part.Status.Failure.Uninterpretable -> partFailure.message
LightningOutgoingPayment.Part.Status.Failure.ChannelIsClosing -> stringResource(id = R.string.outgoing_failuremessage_channel_closing)
LightningOutgoingPayment.Part.Status.Failure.ChannelIsSplicing -> stringResource(id = R.string.outgoing_failuremessage_channel_splicing)
LightningOutgoingPayment.Part.Status.Failure.NotEnoughFees -> stringResource(id = R.string.outgoing_failuremessage_not_enough_fee)
LightningOutgoingPayment.Part.Status.Failure.NotEnoughFunds -> stringResource(id = R.string.outgoing_failuremessage_not_enough_balance)
LightningOutgoingPayment.Part.Status.Failure.PaymentAmountTooBig -> stringResource(id = R.string.outgoing_failuremessage_too_big)
LightningOutgoingPayment.Part.Status.Failure.PaymentAmountTooSmall -> stringResource(id = R.string.outgoing_failuremessage_too_small)
LightningOutgoingPayment.Part.Status.Failure.PaymentExpiryTooBig -> stringResource(id = R.string.outgoing_failuremessage_expiry_too_big)
LightningOutgoingPayment.Part.Status.Failure.RecipientRejectedPayment -> stringResource(id = R.string.outgoing_failuremessage_rejected_by_recipient)
LightningOutgoingPayment.Part.Status.Failure.RecipientIsOffline -> stringResource(id = R.string.outgoing_failuremessage_recipient_offline)
LightningOutgoingPayment.Part.Status.Failure.RecipientLiquidityIssue -> stringResource(id = R.string.outgoing_failuremessage_not_enough_liquidity)
LightningOutgoingPayment.Part.Status.Failure.TemporaryRemoteFailure -> stringResource(id = R.string.outgoing_failuremessage_temporary_failure)
LightningOutgoingPayment.Part.Status.Failure.TooManyPendingPayments -> stringResource(id = R.string.outgoing_failuremessage_too_many_pending)
}
}
is Either.Right -> {
when (result.value) {
FinalFailure.InvalidPaymentId -> stringResource(id = R.string.outgoing_failuremessage_invalid_id)
FinalFailure.AlreadyPaid -> stringResource(id = R.string.outgoing_failuremessage_alreadypaid)
FinalFailure.ChannelClosing -> stringResource(id = R.string.outgoing_failuremessage_channel_closing)
FinalFailure.ChannelNotConnected -> stringResource(id = R.string.outgoing_failuremessage_not_connected)
FinalFailure.ChannelOpening -> stringResource(id = R.string.outgoing_failuremessage_channel_opening)
FinalFailure.FeaturesNotSupported -> stringResource(id = R.string.outgoing_failuremessage_unsupported_features)
FinalFailure.InsufficientBalance -> stringResource(id = R.string.outgoing_failuremessage_not_enough_balance)
FinalFailure.InvalidPaymentAmount -> stringResource(id = R.string.outgoing_failuremessage_invalid_amount)
FinalFailure.NoAvailableChannels -> stringResource(id = R.string.outgoing_failuremessage_no_available_channels)
FinalFailure.RecipientUnreachable -> stringResource(id = R.string.outgoing_failuremessage_noroutefound)
FinalFailure.RetryExhausted -> stringResource(id = R.string.outgoing_failuremessage_noroutefound)
FinalFailure.UnknownError -> stringResource(id = R.string.outgoing_failuremessage_unknown)
FinalFailure.WalletRestarted -> stringResource(id = R.string.outgoing_failuremessage_restarted)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -417,8 +417,9 @@ object LegacyMigrationHelper {
amount = part.amount().toLong().msat + 0.msat, // must include the fee!!!
route = listOf(),
status = LightningOutgoingPayment.Part.Status.Failed(
remoteFailureCode = null,
details = JavaConverters.asJavaCollectionConverter(partStatus.failures()).asJavaCollection().toList().lastOrNull()?.failureMessage() ?: "error details unavailable",
failure = LightningOutgoingPayment.Part.Status.Failure.Uninterpretable(
message = JavaConverters.asJavaCollectionConverter(partStatus.failures()).asJavaCollection().toList().lastOrNull()?.failureMessage() ?: "error details unavailable",
),
completedAt = partStatus.completedAt()
),
createdAt = part.createdAt()
Expand Down Expand Up @@ -463,11 +464,11 @@ object LegacyMigrationHelper {
val lastFailure = JavaConverters.asJavaCollectionConverter((it.last().status() as OutgoingPaymentStatus.Failed).failures()).asJavaCollection().toList().lastOrNull()
when {
lastFailure == null -> FinalFailure.UnknownError
lastFailure.failureMessage().contains(TrampolineFeeInsufficient.message(), ignoreCase = true) -> FinalFailure.NoRouteToRecipient
lastFailure.failureMessage().contains(TrampolineFeeInsufficient.message(), ignoreCase = true) -> FinalFailure.RecipientUnreachable
lastFailure.failureMessage().contains(TemporaryNodeFailure.message(), ignoreCase = true)
|| lastFailure.failureMessage().contains(UnknownNextPeer.message(), ignoreCase = true)
|| lastFailure.failureMessage().contains("is currently unavailable", ignoreCase = true) -> FinalFailure.RecipientUnreachable
lastFailure.failureMessage().contains("incorrect payment details or unknown payment hash", ignoreCase = true) -> FinalFailure.NoRouteToRecipient
lastFailure.failureMessage().contains("incorrect payment details or unknown payment hash", ignoreCase = true) -> FinalFailure.RecipientUnreachable
else -> FinalFailure.UnknownError
} to (it.last().status() as OutgoingPaymentStatus.Failed).completedAt()
}
Expand Down
26 changes: 24 additions & 2 deletions phoenix-android/src/main/res/values-b+es+419/important_strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -357,8 +357,30 @@

<!-- Lightning payment errors -->

<string name="outgoing_finalfailure_alreadypaid">Esta factura ya ha sido pagada.</string>
<string name="outgoing_finalfailure_noroutefound">El destinatario no está localizable o no tiene suficiente liquidez entrante.</string>
<string name="outgoing_failuremessage_channel_closing">Los canales se están cerrando.</string>
<string name="outgoing_failuremessage_channel_splicing">Los canales ya están procesando un splice. Inténtelo de nuevo más tarde.</string>
<string name="outgoing_failuremessage_not_enough_fee">La tarifa es insuficiente.</string>
<string name="outgoing_failuremessage_not_enough_balance">Este pago supera su saldo.</string>
<string name="outgoing_failuremessage_too_big">El importe del pago es demasiado grande - intente dividirlo en varias partes.</string>
<string name="outgoing_failuremessage_too_small">El importe del pago es demasiado pequeño.</string>
<string name="outgoing_failuremessage_expiry_too_big">El vencimiento de este pago es demasiado lejano.</string>
<string name="outgoing_failuremessage_rejected_by_recipient">El pago ha sido rechazado por el destinatario. Es posible que esta factura en concreto ya se haya pagado.</string>
<string name="outgoing_failuremessage_recipient_offline">El destinatario no está conectado.</string>
<string name="outgoing_failuremessage_not_enough_liquidity">El pago no se ha podido retransmitir al destinatario (probablemente liquidez entrante insuficiente).</string>
<string name="outgoing_failuremessage_temporary_failure">Se ha producido un error en un nodo de la ruta de pago. El pago puede tener éxito si lo intentas de nuevo.</string>
<string name="outgoing_failuremessage_too_many_pending">Tienes demasiados pagos pendientes. Vuelva a intentarlo una vez que se hayan liquidado.</string>

<string name="outgoing_failuremessage_invalid_id">El ID del pago no es válido. Inténtelo de nuevo.</string>
<string name="outgoing_failuremessage_not_connected">Tu canal aún no está conectado. Espere a tener una conexión estable e inténtelo de nuevo.</string>
<string name="outgoing_failuremessage_channel_opening">Tu canal aún está en proceso de apertura. Espere e inténtelo de nuevo.</string>
<string name="outgoing_failuremessage_unsupported_features">Esta factura utiliza funciones no compatibles. Asegúrate de que estás en la última versión de Phoenix.</string>
<string name="outgoing_failuremessage_invalid_amount">El importe del pago no es válido.</string>
<string name="outgoing_failuremessage_no_available_channels">El pago no se ha podido enviar a través de sus canales existentes.</string>
<string name="outgoing_failuremessage_unknown">Se ha producido un error desconocido y el pago ha fallado.</string>
<string name="outgoing_failuremessage_restarted">La cartera se ha reiniciado mientras se procesaba el pago.</string>

<string name="outgoing_failuremessage_alreadypaid">Esta factura ya ha sido pagada.</string>
<string name="outgoing_failuremessage_noroutefound">El destinatario no está localizable o no tiene suficiente liquidez entrante.</string>

<!-- low feerate disclaimer -->

Expand Down
26 changes: 24 additions & 2 deletions phoenix-android/src/main/res/values-cs/important_strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -368,8 +368,30 @@

<!-- Lightning payment errors -->

<string name="outgoing_finalfailure_alreadypaid">Tato platba již byla uhrazena.</string>
<string name="outgoing_finalfailure_noroutefound">Příjemce není dosažitelný nebo nemá dostatečnou příchozí likviditu.</string>
<string name="outgoing_failuremessage_channel_closing">Kanály se uzavírají.</string>
<string name="outgoing_failuremessage_channel_splicing">Kanály již zpracovávají splicing. Zkuste to později.</string>
<string name="outgoing_failuremessage_not_enough_fee">Poplatek je nedostatečný.</string>
<string name="outgoing_failuremessage_not_enough_balance">Tato platba přesahuje váš zůstatek.</string>
<string name="outgoing_failuremessage_too_big">Suma platby je příliš velká - zkuste ji rozdělit na více částí.</string>
<string name="outgoing_failuremessage_too_small">Suma platby je příliš malá.</string>
<string name="outgoing_failuremessage_expiry_too_big">Ukončení platnosti této platby je příliš daleko v budoucnosti.</string>
<string name="outgoing_failuremessage_rejected_by_recipient">Příjemce platbu odmítl. Tato konkrétní faktura již mohla být uhrazena.</string>
<string name="outgoing_failuremessage_recipient_offline">Příjemce je offline.</string>
<string name="outgoing_failuremessage_not_enough_liquidity">Platbu nebylo možné předat příjemci (pravděpodobně nedostatečná příchozí likvidita).</string>
<string name="outgoing_failuremessage_temporary_failure">Na uzlu v platební trase došlo k chybě. Platba může být úspěšná, pokud se o ni pokusíte znovu.</string>
<string name="outgoing_failuremessage_too_many_pending">Máte příliš mnoho čekajících plateb. Zkuste to znovu, jakmile budou vypořádány.</string>

<string name="outgoing_failuremessage_invalid_id">Identifikátor platby není platný. Zkuste to znovu.</string>
<string name="outgoing_failuremessage_not_connected">Váš kanál ještě není připojen. Počkejte na stabilní připojení a zkuste to znovu.</string>
<string name="outgoing_failuremessage_channel_opening">Váš kanál je stále v procesu otevírání. Počkejte a zkuste to znovu.</string>
<string name="outgoing_failuremessage_unsupported_features">Tato faktura používá nepodporované funkce. Ujistěte se, že používáte nejnovější verzi systému Phoenix.</string>
<string name="outgoing_failuremessage_invalid_amount">Částka platby je neplatná.</string>
<string name="outgoing_failuremessage_no_available_channels">Platba nemohla být odeslána prostřednictvím vašich stávajících kanálů.</string>
<string name="outgoing_failuremessage_unknown">Došlo k neznámé chybě a platba se nezdařila.</string>
<string name="outgoing_failuremessage_restarted">Peněženka byla restartována během zpracování platby.</string>

<string name="outgoing_failuremessage_alreadypaid">Tato platba již byla uhrazena.</string>
<string name="outgoing_failuremessage_noroutefound">Příjemce není dosažitelný nebo nemá dostatečnou příchozí likviditu.</string>

<!-- low feerate disclaimer -->

Expand Down
26 changes: 24 additions & 2 deletions phoenix-android/src/main/res/values-de/important_strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -366,8 +366,30 @@

<!-- Lightning payment errors -->

<string name="outgoing_finalfailure_alreadypaid">Diese Zahlung wurde bereits getätigt.</string>
<string name="outgoing_finalfailure_noroutefound">Der Empfänger ist nicht erreichbar oder hat nicht genügend Liquidität im Eingang.</string>
<string name="outgoing_failuremessage_channel_closing">Kanäle werden geschlossen.</string>
<string name="outgoing_failuremessage_channel_splicing">Kanäle verarbeiten bereits einen Splice. Versuchen Sie es später noch einmal.</string>
<string name="outgoing_failuremessage_not_enough_fee">Gebühr ist nicht ausreichend.</string>
<string name="outgoing_failuremessage_not_enough_balance">Diese Zahlung übersteigt Ihr Guthaben.</string>
<string name="outgoing_failuremessage_too_big">Der Zahlungsbetrag ist zu groß - versuchen Sie, ihn in mehrere Teile aufzuteilen.</string>
<string name="outgoing_failuremessage_too_small">Der Zahlungsbetrag ist zu klein.</string>
<string name="outgoing_failuremessage_expiry_too_big">Der Verfall dieser Zahlung liegt zu weit in der Zukunft.</string>
<string name="outgoing_failuremessage_rejected_by_recipient">Die Zahlung wurde vom Empfänger abgelehnt. Möglicherweise wurde diese Rechnung bereits bezahlt.</string>
<string name="outgoing_failuremessage_recipient_offline">Der Empfänger ist offline.</string>
<string name="outgoing_failuremessage_not_enough_liquidity">Die Zahlung konnte nicht an den Empfänger weitergeleitet werden (wahrscheinlich unzureichende eingehende Liquidität).</string>
<string name="outgoing_failuremessage_temporary_failure">Ein Fehler ist an einem Knoten in der Zahlungsroute aufgetreten. Die Zahlung kann erfolgreich sein, wenn Sie es erneut versuchen.</string>
<string name="outgoing_failuremessage_too_many_pending">Sie haben zu viele ausstehende Zahlungen. Versuchen Sie es erneut, sobald sie abgewickelt sind.</string>

<string name="outgoing_failuremessage_alreadypaid">Diese Zahlung wurde bereits getätigt.</string>
<string name="outgoing_failuremessage_noroutefound">Der Empfänger ist nicht erreichbar oder hat nicht genügend Liquidität im Eingang.</string>

<string name="outgoing_failuremessage_invalid_id">Die ID der Zahlung ist nicht gültig. Versuchen Sie es erneut.</string>
<string name="outgoing_failuremessage_not_connected">Ihr Kanal ist noch nicht verbunden. Warte auf eine stabile Verbindung und versuche es erneut.</string>
<string name="outgoing_failuremessage_channel_opening">Ihr Kanal wird noch geöffnet. Warten Sie und versuchen Sie es erneut.</string>
<string name="outgoing_failuremessage_unsupported_features">Diese Rechnung verwendet nicht unterstützte Funktionen. Stellen Sie sicher, dass Sie die neueste Phoenix-Version verwenden.</string>
<string name="outgoing_failuremessage_invalid_amount">Der Zahlungsbetrag ist ungültig.</string>
<string name="outgoing_failuremessage_no_available_channels">Die Zahlung konnte nicht über Ihre vorhandenen Kanäle gesendet werden.</string>
<string name="outgoing_failuremessage_unknown">Ein unbekannter Fehler ist aufgetreten und die Zahlung ist fehlgeschlagen.</string>
<string name="outgoing_failuremessage_restarted">Die Brieftasche wurde neu gestartet, während die Zahlung bearbeitet wurde.</string>

<!-- low feerate disclaimer -->

Expand Down
Loading

0 comments on commit bd7f5d1

Please sign in to comment.