From 3f0d42ba3e161b3198d3bbe91876c0d2a8a61892 Mon Sep 17 00:00:00 2001 From: pm47 Date: Mon, 16 Dec 2024 15:01:27 +0100 Subject: [PATCH 1/5] simpler and more consistent outgoing db api --- .../fr/acinq/lightning/db/PaymentsDb.kt | 119 +++++++++--------- .../payment/OutgoingPaymentFailure.kt | 94 +++++++------- .../payment/OutgoingPaymentHandler.kt | 32 +++-- .../acinq/lightning/db/InMemoryPaymentsDb.kt | 28 ++--- .../lightning/db/PaymentsDbTestsCommon.kt | 44 +++---- .../OutgoingPaymentFailureTestsCommon.kt | 14 +-- .../OutgoingPaymentHandlerTestsCommon.kt | 24 ++-- 7 files changed, 172 insertions(+), 183 deletions(-) diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt index 5b28f247a..e6338d371 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt @@ -1,7 +1,6 @@ package fr.acinq.lightning.db import fr.acinq.bitcoin.* -import fr.acinq.lightning.Lightning import fr.acinq.lightning.MilliSatoshi import fr.acinq.lightning.ShortChannelId import fr.acinq.lightning.channel.ChannelManagementFees @@ -65,26 +64,20 @@ interface OutgoingPaymentsDb { /** Get information about an outgoing payment (settled or not). */ suspend fun getLightningOutgoingPayment(id: UUID): LightningOutgoingPayment? - /** Get information about a liquidity purchase (for which the funding transaction has been signed). */ - suspend fun getInboundLiquidityPurchase(fundingTxId: TxId): InboundLiquidityOutgoingPayment? - - /** Mark an outgoing payment as completed over Lightning. */ - suspend fun completeOutgoingPaymentOffchain(id: UUID, preimage: ByteVector32, completedAt: Long = currentTimestampMillis()) - - /** Mark an outgoing payment as failed. */ - suspend fun completeOutgoingPaymentOffchain(id: UUID, finalFailure: FinalFailure, completedAt: Long = currentTimestampMillis()) + /** Get information about an outgoing payment from the id of one of its parts. */ + suspend fun getLightningOutgoingPaymentFromPartId(partId: UUID): LightningOutgoingPayment? /** Add new partial payments to a pending outgoing payment. */ suspend fun addOutgoingLightningParts(parentId: UUID, parts: List) - /** Mark an outgoing payment part as failed. */ - suspend fun completeOutgoingLightningPart(partId: UUID, failure: LightningOutgoingPayment.Part.Status.Failure, completedAt: Long = currentTimestampMillis()) + /** Mark a lightning outgoing payment as completed. */ + suspend fun completeLightningOutgoingPayment(id: UUID, status: LightningOutgoingPayment.Status.Completed) - /** Mark an outgoing payment part as succeeded. This should not update the parent payment, since some parts may still be pending. */ - suspend fun completeOutgoingLightningPart(partId: UUID, preimage: ByteVector32, completedAt: Long = currentTimestampMillis()) + /** Mark a lightning outgoing payment part as completed. */ + suspend fun completeLightningOutgoingPaymentPart(parentId: UUID, partId: UUID, status: LightningOutgoingPayment.Part.Status.Completed) - /** Get information about an outgoing payment from the id of one of its parts. */ - suspend fun getLightningOutgoingPaymentFromPartId(partId: UUID): LightningOutgoingPayment? + /** Get information about a liquidity purchase (for which the funding transaction has been signed). */ + suspend fun getInboundLiquidityPurchase(fundingTxId: TxId): InboundLiquidityOutgoingPayment? /** List all the outgoing payment attempts that tried to pay the given payment hash. */ suspend fun listLightningOutgoingPayments(paymentHash: ByteVector32): List @@ -329,8 +322,8 @@ data class LightningOutgoingPayment( /** This is the total fees that have been paid to make the payment work. It includes the LN routing fees, the fee for the swap-out service, the mining fees for closing a channel. */ override val fees: MilliSatoshi = when (status) { is Status.Pending -> 0.msat - is Status.Completed.Failed -> 0.msat - is Status.Completed.Succeeded.OffChain -> { + is Status.Failed -> 0.msat + is Status.Succeeded -> { if (details is Details.SwapOut) { // The swap-out service takes a fee to cover the miner fee. It's the difference between what we paid the service (recipientAmount) and what goes to the address. // We also include the routing fee, in case the swap-service is NOT the trampoline node. @@ -376,15 +369,10 @@ data class LightningOutgoingPayment( data object Pending : Status() sealed class Completed : Status() { abstract val completedAt: Long - - data class Failed(val reason: FinalFailure, override val completedAt: Long = currentTimestampMillis()) : Completed() - sealed class Succeeded : Completed() { - data class OffChain( - val preimage: ByteVector32, - override val completedAt: Long = currentTimestampMillis() - ) : Succeeded() - } } + + data class Succeeded(val preimage: ByteVector32, override val completedAt: Long = currentTimestampMillis()) : Completed() + data class Failed(val reason: FinalFailure, override val completedAt: Long = currentTimestampMillis()) : Completed() } /** @@ -405,44 +393,49 @@ data class LightningOutgoingPayment( ) { sealed class Status { data object Pending : Status() - data class Succeeded(val preimage: ByteVector32, val completedAt: Long = currentTimestampMillis()) : Status() - data class Failed(val failure: Failure, val completedAt: Long = currentTimestampMillis()) : Status() - - /** - * User-friendly payment part failure reason, whenever possible. - * Applications should define their own localized message for each of these failure cases. - */ - sealed class Failure { - // @formatter:off - /** The payment is too small: try sending a larger amount. */ - data object PaymentAmountTooSmall : Failure() { override fun toString(): String = "the payment amount is too small" } - /** The user has sufficient balance, but the payment is too big: try sending a smaller amount. */ - data object PaymentAmountTooBig : Failure() { override fun toString(): String = "the payment amount is too large" } - /** The user doesn't have sufficient balance: try sending a smaller amount. */ - data object NotEnoughFunds : Failure() { override fun toString(): String = "not enough funds" } - /** The payment must be retried with more fees to reach the recipient. */ - data object NotEnoughFees : Failure() { override fun toString(): String = "routing fees are insufficient" } - /** The payment expiry specified by the recipient is too far away in the future. */ - data object PaymentExpiryTooBig : Failure() { override fun toString(): String = "the payment expiry is too far in the future" } - /** There are too many pending payments: wait for them to settle and retry. */ - data object TooManyPendingPayments : Failure() { override fun toString(): String = "too many pending payments" } - /** Payments are temporarily paused while a channel is splicing: the payment can be retried after the splice. */ - data object ChannelIsSplicing : Failure() { override fun toString(): String = "a splicing operation is in progress" } - /** The channel is closing: another channel should be created to send the payment. */ - data object ChannelIsClosing : Failure() { override fun toString(): String = "channel closing is in progress" } - /** Remote failure from an intermediate node in the payment route. */ - sealed class RouteFailure : Failure() - /** A remote node had a temporary failure: the payment may succeed if retried. */ - data object TemporaryRemoteFailure : RouteFailure() { override fun toString(): String = "a node in the route had a temporary failure" } - /** The payment amount could not be relayed to the recipient, most likely because they don't have enough inbound liquidity. */ - data object RecipientLiquidityIssue : RouteFailure() { override fun toString(): String = "liquidity issue at the recipient node" } - /** The payment recipient is offline and could not accept the payment. */ - data object RecipientIsOffline : RouteFailure() { override fun toString(): String = "recipient node is offline or unreachable" } - /** The payment recipient received the payment but rejected it. */ - data object RecipientRejectedPayment : Failure() { override fun toString(): String = "recipient node rejected the payment" } - /** This is an error that cannot be easily interpreted: we don't know what exactly went wrong and cannot correctly inform the user. */ - data class Uninterpretable(val message: String) : Failure() { override fun toString(): String = message } - // @formatter:on + sealed class Completed : Status() { + abstract val completedAt: Long + } + + data class Succeeded(val preimage: ByteVector32, override val completedAt: Long = currentTimestampMillis()) : Completed() + data class Failed(val failure: Failure, override val completedAt: Long = currentTimestampMillis()) : Completed() { + + /** + * User-friendly payment part failure reason, whenever possible. + * Applications should define their own localized message for each of these failure cases. + */ + sealed class Failure { + // @formatter:off + /** The payment is too small: try sending a larger amount. */ + data object PaymentAmountTooSmall : Failure() { override fun toString(): String = "the payment amount is too small" } + /** The user has sufficient balance, but the payment is too big: try sending a smaller amount. */ + data object PaymentAmountTooBig : Failure() { override fun toString(): String = "the payment amount is too large" } + /** The user doesn't have sufficient balance: try sending a smaller amount. */ + data object NotEnoughFunds : Failure() { override fun toString(): String = "not enough funds" } + /** The payment must be retried with more fees to reach the recipient. */ + data object NotEnoughFees : Failure() { override fun toString(): String = "routing fees are insufficient" } + /** The payment expiry specified by the recipient is too far away in the future. */ + data object PaymentExpiryTooBig : Failure() { override fun toString(): String = "the payment expiry is too far in the future" } + /** There are too many pending payments: wait for them to settle and retry. */ + data object TooManyPendingPayments : Failure() { override fun toString(): String = "too many pending payments" } + /** Payments are temporarily paused while a channel is splicing: the payment can be retried after the splice. */ + data object ChannelIsSplicing : Failure() { override fun toString(): String = "a splicing operation is in progress" } + /** The channel is closing: another channel should be created to send the payment. */ + data object ChannelIsClosing : Failure() { override fun toString(): String = "channel closing is in progress" } + /** Remote failure from an intermediate node in the payment route. */ + sealed class RouteFailure : Failure() + /** A remote node had a temporary failure: the payment may succeed if retried. */ + data object TemporaryRemoteFailure : RouteFailure() { override fun toString(): String = "a node in the route had a temporary failure" } + /** The payment amount could not be relayed to the recipient, most likely because they don't have enough inbound liquidity. */ + data object RecipientLiquidityIssue : RouteFailure() { override fun toString(): String = "liquidity issue at the recipient node" } + /** The payment recipient is offline and could not accept the payment. */ + data object RecipientIsOffline : RouteFailure() { override fun toString(): String = "recipient node is offline or unreachable" } + /** The payment recipient received the payment but rejected it. */ + data object RecipientRejectedPayment : Failure() { override fun toString(): String = "recipient node rejected the payment" } + /** This is an error that cannot be easily interpreted: we don't know what exactly went wrong and cannot correctly inform the user. */ + data class Uninterpretable(val message: String) : Failure() { override fun toString(): String = message } + // @formatter:on + } } } } diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentFailure.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentFailure.kt index 77bab3048..5fbec58df 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentFailure.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentFailure.kt @@ -3,6 +3,8 @@ package fr.acinq.lightning.payment import fr.acinq.bitcoin.utils.Either import fr.acinq.lightning.channel.* import fr.acinq.lightning.db.LightningOutgoingPayment +import fr.acinq.lightning.db.OutgoingPaymentsDb +import fr.acinq.lightning.utils.UUID import fr.acinq.lightning.utils.currentTimestampMillis import fr.acinq.lightning.wire.* @@ -39,8 +41,8 @@ data class OutgoingPaymentFailure(val reason: FinalFailure, val failures: List { - val partFailure = failures.map { it.failure }.lastOrNull { it !is LightningOutgoingPayment.Part.Status.Failure.Uninterpretable } ?: failures.lastOrNull()?.failure + fun explain(): Either { + val partFailure = failures.map { it.failure }.lastOrNull { it !is LightningOutgoingPayment.Part.Status.Failed.Failure.Uninterpretable } ?: failures.lastOrNull()?.failure return when (reason) { FinalFailure.NoAvailableChannels, FinalFailure.UnknownError, FinalFailure.RetryExhausted -> partFailure?.let { Either.Left(it) } ?: Either.Right(reason) else -> Either.Right(reason) @@ -54,54 +56,54 @@ data class OutgoingPaymentFailure(val reason: FinalFailure, val failures: List msg + "${index + 1}: ${problem.failure}\n" } companion object { - fun convertFailure(failure: Either): LightningOutgoingPayment.Part.Status.Failure { + fun convertFailure(failure: Either): LightningOutgoingPayment.Part.Status.Failed.Failure { return when (failure) { is Either.Left -> when (failure.value) { - is HtlcValueTooSmall -> LightningOutgoingPayment.Part.Status.Failure.PaymentAmountTooSmall - is CannotAffordFees -> LightningOutgoingPayment.Part.Status.Failure.PaymentAmountTooBig - is RemoteCannotAffordFeesForNewHtlc -> LightningOutgoingPayment.Part.Status.Failure.PaymentAmountTooBig - is HtlcValueTooHighInFlight -> LightningOutgoingPayment.Part.Status.Failure.PaymentAmountTooBig - is InsufficientFunds -> LightningOutgoingPayment.Part.Status.Failure.NotEnoughFunds - is TooManyAcceptedHtlcs -> LightningOutgoingPayment.Part.Status.Failure.TooManyPendingPayments - is TooManyOfferedHtlcs -> LightningOutgoingPayment.Part.Status.Failure.TooManyPendingPayments - is ExpiryTooBig -> LightningOutgoingPayment.Part.Status.Failure.PaymentExpiryTooBig - is ForbiddenDuringSplice -> LightningOutgoingPayment.Part.Status.Failure.ChannelIsSplicing - is ChannelUnavailable -> LightningOutgoingPayment.Part.Status.Failure.ChannelIsClosing - is ClosingAlreadyInProgress -> LightningOutgoingPayment.Part.Status.Failure.ChannelIsClosing - is ForcedLocalCommit -> LightningOutgoingPayment.Part.Status.Failure.ChannelIsClosing - is FundingTxSpent -> LightningOutgoingPayment.Part.Status.Failure.ChannelIsClosing - is HtlcOverriddenByLocalCommit -> LightningOutgoingPayment.Part.Status.Failure.ChannelIsClosing - is HtlcsTimedOutDownstream -> LightningOutgoingPayment.Part.Status.Failure.ChannelIsClosing - is NoMoreHtlcsClosingInProgress -> LightningOutgoingPayment.Part.Status.Failure.ChannelIsClosing - else -> LightningOutgoingPayment.Part.Status.Failure.Uninterpretable(failure.value.message) + is HtlcValueTooSmall -> LightningOutgoingPayment.Part.Status.Failed.Failure.PaymentAmountTooSmall + is CannotAffordFees -> LightningOutgoingPayment.Part.Status.Failed.Failure.PaymentAmountTooBig + is RemoteCannotAffordFeesForNewHtlc -> LightningOutgoingPayment.Part.Status.Failed.Failure.PaymentAmountTooBig + is HtlcValueTooHighInFlight -> LightningOutgoingPayment.Part.Status.Failed.Failure.PaymentAmountTooBig + is InsufficientFunds -> LightningOutgoingPayment.Part.Status.Failed.Failure.NotEnoughFunds + is TooManyAcceptedHtlcs -> LightningOutgoingPayment.Part.Status.Failed.Failure.TooManyPendingPayments + is TooManyOfferedHtlcs -> LightningOutgoingPayment.Part.Status.Failed.Failure.TooManyPendingPayments + is ExpiryTooBig -> LightningOutgoingPayment.Part.Status.Failed.Failure.PaymentExpiryTooBig + is ForbiddenDuringSplice -> LightningOutgoingPayment.Part.Status.Failed.Failure.ChannelIsSplicing + is ChannelUnavailable -> LightningOutgoingPayment.Part.Status.Failed.Failure.ChannelIsClosing + is ClosingAlreadyInProgress -> LightningOutgoingPayment.Part.Status.Failed.Failure.ChannelIsClosing + is ForcedLocalCommit -> LightningOutgoingPayment.Part.Status.Failed.Failure.ChannelIsClosing + is FundingTxSpent -> LightningOutgoingPayment.Part.Status.Failed.Failure.ChannelIsClosing + is HtlcOverriddenByLocalCommit -> LightningOutgoingPayment.Part.Status.Failed.Failure.ChannelIsClosing + is HtlcsTimedOutDownstream -> LightningOutgoingPayment.Part.Status.Failed.Failure.ChannelIsClosing + is NoMoreHtlcsClosingInProgress -> LightningOutgoingPayment.Part.Status.Failed.Failure.ChannelIsClosing + else -> LightningOutgoingPayment.Part.Status.Failed.Failure.Uninterpretable(failure.value.message) } is Either.Right -> when (failure.value) { - is AmountBelowMinimum -> LightningOutgoingPayment.Part.Status.Failure.PaymentAmountTooSmall - is FeeInsufficient -> LightningOutgoingPayment.Part.Status.Failure.NotEnoughFees - TrampolineExpiryTooSoon -> LightningOutgoingPayment.Part.Status.Failure.NotEnoughFees - TrampolineFeeInsufficient -> LightningOutgoingPayment.Part.Status.Failure.NotEnoughFees - is FinalIncorrectCltvExpiry -> LightningOutgoingPayment.Part.Status.Failure.RecipientRejectedPayment - is FinalIncorrectHtlcAmount -> LightningOutgoingPayment.Part.Status.Failure.RecipientRejectedPayment - is IncorrectOrUnknownPaymentDetails -> LightningOutgoingPayment.Part.Status.Failure.RecipientRejectedPayment - PaymentTimeout -> LightningOutgoingPayment.Part.Status.Failure.RecipientLiquidityIssue - UnknownNextPeer -> LightningOutgoingPayment.Part.Status.Failure.RecipientIsOffline - is ExpiryTooSoon -> LightningOutgoingPayment.Part.Status.Failure.TemporaryRemoteFailure - ExpiryTooFar -> LightningOutgoingPayment.Part.Status.Failure.TemporaryRemoteFailure - is ChannelDisabled -> LightningOutgoingPayment.Part.Status.Failure.TemporaryRemoteFailure - is TemporaryChannelFailure -> LightningOutgoingPayment.Part.Status.Failure.TemporaryRemoteFailure - TemporaryNodeFailure -> LightningOutgoingPayment.Part.Status.Failure.TemporaryRemoteFailure - PermanentChannelFailure -> LightningOutgoingPayment.Part.Status.Failure.TemporaryRemoteFailure - PermanentNodeFailure -> LightningOutgoingPayment.Part.Status.Failure.TemporaryRemoteFailure - is InvalidOnionBlinding -> LightningOutgoingPayment.Part.Status.Failure.Uninterpretable(failure.value.message) - is InvalidOnionHmac -> LightningOutgoingPayment.Part.Status.Failure.Uninterpretable(failure.value.message) - is InvalidOnionKey -> LightningOutgoingPayment.Part.Status.Failure.Uninterpretable(failure.value.message) - is InvalidOnionPayload -> LightningOutgoingPayment.Part.Status.Failure.Uninterpretable(failure.value.message) - is InvalidOnionVersion -> LightningOutgoingPayment.Part.Status.Failure.Uninterpretable(failure.value.message) - InvalidRealm -> LightningOutgoingPayment.Part.Status.Failure.Uninterpretable(failure.value.message) - is IncorrectCltvExpiry -> LightningOutgoingPayment.Part.Status.Failure.Uninterpretable(failure.value.message) - RequiredChannelFeatureMissing -> LightningOutgoingPayment.Part.Status.Failure.Uninterpretable(failure.value.message) - RequiredNodeFeatureMissing -> LightningOutgoingPayment.Part.Status.Failure.Uninterpretable(failure.value.message) - is UnknownFailureMessage -> LightningOutgoingPayment.Part.Status.Failure.Uninterpretable(failure.value.message) + is AmountBelowMinimum -> LightningOutgoingPayment.Part.Status.Failed.Failure.PaymentAmountTooSmall + is FeeInsufficient -> LightningOutgoingPayment.Part.Status.Failed.Failure.NotEnoughFees + TrampolineExpiryTooSoon -> LightningOutgoingPayment.Part.Status.Failed.Failure.NotEnoughFees + TrampolineFeeInsufficient -> LightningOutgoingPayment.Part.Status.Failed.Failure.NotEnoughFees + is FinalIncorrectCltvExpiry -> LightningOutgoingPayment.Part.Status.Failed.Failure.RecipientRejectedPayment + is FinalIncorrectHtlcAmount -> LightningOutgoingPayment.Part.Status.Failed.Failure.RecipientRejectedPayment + is IncorrectOrUnknownPaymentDetails -> LightningOutgoingPayment.Part.Status.Failed.Failure.RecipientRejectedPayment + PaymentTimeout -> LightningOutgoingPayment.Part.Status.Failed.Failure.RecipientLiquidityIssue + UnknownNextPeer -> LightningOutgoingPayment.Part.Status.Failed.Failure.RecipientIsOffline + is ExpiryTooSoon -> LightningOutgoingPayment.Part.Status.Failed.Failure.TemporaryRemoteFailure + ExpiryTooFar -> LightningOutgoingPayment.Part.Status.Failed.Failure.TemporaryRemoteFailure + is ChannelDisabled -> LightningOutgoingPayment.Part.Status.Failed.Failure.TemporaryRemoteFailure + is TemporaryChannelFailure -> LightningOutgoingPayment.Part.Status.Failed.Failure.TemporaryRemoteFailure + TemporaryNodeFailure -> LightningOutgoingPayment.Part.Status.Failed.Failure.TemporaryRemoteFailure + PermanentChannelFailure -> LightningOutgoingPayment.Part.Status.Failed.Failure.TemporaryRemoteFailure + PermanentNodeFailure -> LightningOutgoingPayment.Part.Status.Failed.Failure.TemporaryRemoteFailure + is InvalidOnionBlinding -> LightningOutgoingPayment.Part.Status.Failed.Failure.Uninterpretable(failure.value.message) + is InvalidOnionHmac -> LightningOutgoingPayment.Part.Status.Failed.Failure.Uninterpretable(failure.value.message) + is InvalidOnionKey -> LightningOutgoingPayment.Part.Status.Failed.Failure.Uninterpretable(failure.value.message) + is InvalidOnionPayload -> LightningOutgoingPayment.Part.Status.Failed.Failure.Uninterpretable(failure.value.message) + is InvalidOnionVersion -> LightningOutgoingPayment.Part.Status.Failed.Failure.Uninterpretable(failure.value.message) + InvalidRealm -> LightningOutgoingPayment.Part.Status.Failed.Failure.Uninterpretable(failure.value.message) + is IncorrectCltvExpiry -> LightningOutgoingPayment.Part.Status.Failed.Failure.Uninterpretable(failure.value.message) + RequiredChannelFeatureMissing -> LightningOutgoingPayment.Part.Status.Failed.Failure.Uninterpretable(failure.value.message) + RequiredNodeFeatureMissing -> LightningOutgoingPayment.Part.Status.Failed.Failure.Uninterpretable(failure.value.message) + is UnknownFailureMessage -> LightningOutgoingPayment.Part.Status.Failed.Failure.Uninterpretable(failure.value.message) } } } diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandler.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandler.kt index f0213252f..f8c0cae62 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandler.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandler.kt @@ -82,7 +82,7 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle if (attemptNumber == 0) { db.addOutgoingPayment(LightningOutgoingPayment(request.paymentId, request.amount, request.recipient, request.paymentDetails)) } - db.completeOutgoingPaymentOffchain(request.paymentId, result.value) + db.completeLightningOutgoingPayment(request.paymentId, result.value) removeFromState(request.paymentId) Either.Left(Failure(request, OutgoingPaymentFailure(result.value, failures))) } @@ -116,7 +116,7 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle logger.error { "contract violation: caller is recycling uuid's" } return Failure(request, FinalFailure.InvalidPaymentId.toPaymentFailure()) } - if (db.listLightningOutgoingPayments(request.paymentHash).find { it.status is LightningOutgoingPayment.Status.Completed.Succeeded } != null) { + if (db.listLightningOutgoingPayments(request.paymentHash).find { it.status is LightningOutgoingPayment.Status.Succeeded } != null) { logger.error { "invoice has already been paid" } return Failure(request, FinalFailure.AlreadyPaid.toPaymentFailure()) } @@ -137,8 +137,8 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle } logger.info { "could not send HTLC: ${event.error.message}" } - db.completeOutgoingLightningPart(event.cmd.paymentId, OutgoingPaymentFailure.convertFailure(Either.Left(event.error))) - db.completeOutgoingPaymentOffchain(payment.request.paymentId, FinalFailure.NoAvailableChannels) + db.completeLightningOutgoingPaymentPart(payment.request.paymentId, event.cmd.paymentId, Either.Left(event.error)) + db.completeLightningOutgoingPayment(payment.request.paymentId, FinalFailure.NoAvailableChannels) removeFromState(payment.request.paymentId) return Failure(payment.request, OutgoingPaymentFailure(FinalFailure.NoAvailableChannels, payment.failures + Either.Left(event.error))) } @@ -180,7 +180,7 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle } // We update the status in our DB. - db.completeOutgoingLightningPart(event.paymentId, OutgoingPaymentFailure.convertFailure(failure)) + db.completeLightningOutgoingPaymentPart(payment.request.paymentId, event.paymentId, failure) val trampolineFees = payment.request.trampolineFeesOverride ?: walletParams.trampolineFees val finalError = when { @@ -190,7 +190,7 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle else -> null } return if (finalError != null) { - db.completeOutgoingPaymentOffchain(payment.request.paymentId, finalError) + db.completeLightningOutgoingPayment(payment.request.paymentId, finalError) removeFromState(payment.request.paymentId) Failure(payment.request, OutgoingPaymentFailure(finalError, payment.failures + failure)) } else { @@ -209,10 +209,10 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle val logger = MDCLogger(logger, staticMdc = mapOf("childPaymentId" to partId) + payment.mdc()) logger.debug { "could not send HTLC (wallet restart): ${failure.fold({ it.message }, { it.message })}" } val status = LightningOutgoingPayment.Part.Status.Failed(OutgoingPaymentFailure.convertFailure(failure)) - db.completeOutgoingLightningPart(partId, status.failure) + db.completeLightningOutgoingPaymentPart(payment.id, partId, failure) logger.warning { "payment failed: ${FinalFailure.WalletRestarted}" } val request = PayInvoice(payment.id, payment.recipientAmount, payment.details) - db.completeOutgoingPaymentOffchain(payment.id, FinalFailure.WalletRestarted) + db.completeLightningOutgoingPayment(payment.id, FinalFailure.WalletRestarted) removeFromState(payment.id) val failures = payment.parts.map { it.status }.filterIsInstance() + status Failure(request, OutgoingPaymentFailure(FinalFailure.WalletRestarted, failures)) @@ -231,10 +231,10 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle } logger.info { "payment successfully sent (fees=${payment.fees})" } - db.completeOutgoingLightningPart(event.paymentId, preimage) - db.completeOutgoingPaymentOffchain(payment.request.paymentId, preimage) + db.completeLightningOutgoingPaymentPart(payment.request.paymentId, event.paymentId, LightningOutgoingPayment.Part.Status.Succeeded(preimage)) + db.completeLightningOutgoingPayment(payment.request.paymentId, LightningOutgoingPayment.Status.Succeeded(preimage)) removeFromState(payment.request.paymentId) - val status = LightningOutgoingPayment.Status.Completed.Succeeded.OffChain(preimage) + val status = LightningOutgoingPayment.Status.Succeeded(preimage) val part = payment.pending.copy(status = LightningOutgoingPayment.Part.Status.Succeeded(preimage)) val result = LightningOutgoingPayment(payment.request.paymentId, payment.request.amount, payment.request.recipient, payment.request.paymentDetails, listOf(part), status) return Success(payment.request, result, preimage) @@ -248,10 +248,10 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle } else -> { val logger = MDCLogger(logger, staticMdc = mapOf("childPaymentId" to partId) + payment.mdc()) - db.completeOutgoingLightningPart(partId, preimage) + db.completeLightningOutgoingPaymentPart(payment.id, partId, LightningOutgoingPayment.Part.Status.Succeeded(preimage)) logger.info { "payment successfully sent (wallet restart)" } val request = PayInvoice(payment.id, payment.recipientAmount, payment.details) - db.completeOutgoingPaymentOffchain(payment.id, preimage) + db.completeLightningOutgoingPayment(payment.id, LightningOutgoingPayment.Status.Succeeded(preimage)) removeFromState(payment.id) // NB: we reload the payment to ensure all parts status are updated // this payment cannot be null @@ -330,4 +330,10 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle } } + private suspend fun OutgoingPaymentsDb.completeLightningOutgoingPayment(id: UUID, failure: FinalFailure) = + completeLightningOutgoingPayment(id, LightningOutgoingPayment.Status.Failed(failure)) + + private suspend fun OutgoingPaymentsDb.completeLightningOutgoingPaymentPart(id: UUID, partId: UUID, failure: Either) = + completeLightningOutgoingPaymentPart(id, partId, LightningOutgoingPayment.Part.Status.Failed(OutgoingPaymentFailure.convertFailure(failure))) + } diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/InMemoryPaymentsDb.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/InMemoryPaymentsDb.kt index c812f495f..d5504f751 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/InMemoryPaymentsDb.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/InMemoryPaymentsDb.kt @@ -77,7 +77,7 @@ class InMemoryPaymentsDb : PaymentsDb { return outgoing[id]?.let { payment -> val parts = outgoingParts.values.filter { it.first == payment.id }.map { it.second } return when (payment.status) { - is LightningOutgoingPayment.Status.Completed.Succeeded -> payment.copy(parts = parts.filter { it.status is LightningOutgoingPayment.Part.Status.Succeeded }) + is LightningOutgoingPayment.Status.Succeeded -> payment.copy(parts = parts.filter { it.status is LightningOutgoingPayment.Part.Status.Succeeded }) else -> payment.copy(parts = parts) } } @@ -90,34 +90,22 @@ class InMemoryPaymentsDb : PaymentsDb { } } - override suspend fun completeOutgoingPaymentOffchain(id: UUID, preimage: ByteVector32, completedAt: Long) { - require(outgoing.contains(id)) { "outgoing payment with id=$id doesn't exist" } - val payment = outgoing[id]!! - outgoing[id] = payment.copy(status = LightningOutgoingPayment.Status.Completed.Succeeded.OffChain(preimage = preimage, completedAt = completedAt)) - } - - override suspend fun completeOutgoingPaymentOffchain(id: UUID, finalFailure: FinalFailure, completedAt: Long) { - require(outgoing.contains(id)) { "outgoing payment with id=$id doesn't exist" } - val payment = outgoing[id]!! - outgoing[id] = payment.copy(status = LightningOutgoingPayment.Status.Completed.Failed(reason = finalFailure, completedAt = completedAt)) - } - override suspend fun addOutgoingLightningParts(parentId: UUID, parts: List) { require(outgoing.contains(parentId)) { "parent outgoing payment with id=$parentId doesn't exist" } parts.forEach { require(!outgoingParts.contains(it.id)) { "an outgoing payment part with id=${it.id} already exists" } } parts.forEach { outgoingParts[it.id] = Pair(parentId, it) } } - override suspend fun completeOutgoingLightningPart(partId: UUID, failure: LightningOutgoingPayment.Part.Status.Failure, completedAt: Long) { - require(outgoingParts.contains(partId)) { "outgoing payment part with id=$partId doesn't exist" } - val (parentId, part) = outgoingParts[partId]!! - outgoingParts[partId] = Pair(parentId, part.copy(status = LightningOutgoingPayment.Part.Status.Failed(failure, completedAt))) + override suspend fun completeLightningOutgoingPayment(id: UUID, status: LightningOutgoingPayment.Status.Completed) { + require(outgoing.contains(id)) { "outgoing payment with id=$id doesn't exist" } + val payment = outgoing[id]!! + outgoing[id] = payment.copy(status = status) } - override suspend fun completeOutgoingLightningPart(partId: UUID, preimage: ByteVector32, completedAt: Long) { + override suspend fun completeLightningOutgoingPaymentPart(parentId: UUID, partId: UUID, status: LightningOutgoingPayment.Part.Status.Completed) { require(outgoingParts.contains(partId)) { "outgoing payment part with id=$partId doesn't exist" } - val (parentId, part) = outgoingParts[partId]!! - outgoingParts[partId] = Pair(parentId, part.copy(status = LightningOutgoingPayment.Part.Status.Succeeded(preimage, completedAt))) + val (_, part) = outgoingParts[partId]!! + outgoingParts[partId] = Pair(parentId, part.copy(status = status)) } override suspend fun getLightningOutgoingPaymentFromPartId(partId: UUID): LightningOutgoingPayment? { diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/PaymentsDbTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/PaymentsDbTestsCommon.kt index 1c98b08e3..800bd8822 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/PaymentsDbTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/PaymentsDbTestsCommon.kt @@ -148,17 +148,17 @@ class PaymentsDbTestsCommon : LightningTestSuite() { // One of the parts fails. val onePartFailed = initialPayment.copy( parts = listOf( - initialParts[0].copy(status = LightningOutgoingPayment.Part.Status.Failed(LightningOutgoingPayment.Part.Status.Failure.TemporaryRemoteFailure, 110)), + initialParts[0].copy(status = LightningOutgoingPayment.Part.Status.Failed(LightningOutgoingPayment.Part.Status.Failed.Failure.TemporaryRemoteFailure, 110)), initialParts[1] ) ) - db.completeOutgoingLightningPart(initialPayment.parts[0].id, LightningOutgoingPayment.Part.Status.Failure.TemporaryRemoteFailure, 110) + db.completeLightningOutgoingPaymentPart(initialPayment.id, initialPayment.parts[0].id, LightningOutgoingPayment.Part.Status.Failed(LightningOutgoingPayment.Part.Status.Failed.Failure.TemporaryRemoteFailure, 110)) assertEquals(onePartFailed, db.getLightningOutgoingPayment(initialPayment.id)) initialPayment.parts.forEach { assertEquals(onePartFailed, db.getLightningOutgoingPaymentFromPartId(it.id)) } // We should never update non-existing parts. - assertFails { db.completeOutgoingLightningPart(UUID.randomUUID(), LightningOutgoingPayment.Part.Status.Failure.TemporaryRemoteFailure) } - assertFails { db.completeOutgoingLightningPart(UUID.randomUUID(), randomBytes32()) } + assertFails { db.completeLightningOutgoingPaymentPart(initialPayment.id, UUID.randomUUID(), LightningOutgoingPayment.Part.Status.Failed(LightningOutgoingPayment.Part.Status.Failed.Failure.TemporaryRemoteFailure)) } + assertFails { db.completeLightningOutgoingPaymentPart(initialPayment.id, UUID.randomUUID(), LightningOutgoingPayment.Part.Status.Succeeded(randomBytes32())) } // Other payment parts are added. val newParts = listOf( @@ -183,19 +183,19 @@ class PaymentsDbTestsCommon : LightningTestSuite() { ) ) assertEquals(LightningOutgoingPayment.Status.Pending, partsSettled.status) - db.completeOutgoingLightningPart(withMoreParts.parts[1].id, preimage, 125) - db.completeOutgoingLightningPart(withMoreParts.parts[2].id, preimage, 126) - db.completeOutgoingLightningPart(withMoreParts.parts[3].id, preimage, 127) + db.completeLightningOutgoingPaymentPart(initialPayment.id, withMoreParts.parts[1].id, LightningOutgoingPayment.Part.Status.Succeeded(preimage, 125)) + db.completeLightningOutgoingPaymentPart(initialPayment.id, withMoreParts.parts[2].id, LightningOutgoingPayment.Part.Status.Succeeded(preimage, 126)) + db.completeLightningOutgoingPaymentPart(initialPayment.id, withMoreParts.parts[3].id, LightningOutgoingPayment.Part.Status.Succeeded(preimage, 127)) assertEquals(partsSettled, db.getLightningOutgoingPayment(initialPayment.id)) partsSettled.parts.forEach { assertEquals(partsSettled, db.getLightningOutgoingPaymentFromPartId(it.id)) } // Payment succeeds: failed parts will be ignored. val paymentSucceeded = partsSettled.copy( - status = LightningOutgoingPayment.Status.Completed.Succeeded.OffChain(preimage, 130), + status = LightningOutgoingPayment.Status.Succeeded(preimage, 130), parts = partsSettled.parts.drop(1) ) - db.completeOutgoingPaymentOffchain(initialPayment.id, preimage, 130) - assertFails { db.completeOutgoingPaymentOffchain(UUID.randomUUID(), preimage, 130) } + db.completeLightningOutgoingPayment(initialPayment.id, LightningOutgoingPayment.Status.Succeeded(preimage, 130)) + assertFails { db.completeLightningOutgoingPayment(UUID.randomUUID(), LightningOutgoingPayment.Status.Succeeded(preimage, 130)) } assertEquals(paymentSucceeded, db.getLightningOutgoingPayment(initialPayment.id)) partsSettled.parts.forEach { assertEquals(paymentSucceeded, db.getLightningOutgoingPaymentFromPartId(it.id)) } } @@ -222,9 +222,9 @@ class PaymentsDbTestsCommon : LightningTestSuite() { LightningOutgoingPayment.Part(UUID.randomUUID(), amount = 75_000.msat, route = hops2, status = LightningOutgoingPayment.Part.Status.Pending, createdAt = 105) ) db.addOutgoingLightningParts(parentId = normalPayment.id, parts = normalParts) - db.completeOutgoingLightningPart(normalParts[0].id, preimage, 110) - db.completeOutgoingLightningPart(normalParts[1].id, preimage, 115) - db.completeOutgoingPaymentOffchain(normalPayment.id, preimage, 120) + db.completeLightningOutgoingPaymentPart(normalPayment.id, normalParts[0].id, LightningOutgoingPayment.Part.Status.Succeeded(preimage, 110)) + db.completeLightningOutgoingPaymentPart(normalPayment.id, normalParts[1].id, LightningOutgoingPayment.Part.Status.Succeeded(preimage, 115)) + db.completeLightningOutgoingPayment(normalPayment.id, LightningOutgoingPayment.Status.Succeeded(preimage, 120)) val normalPaymentInDb = db.getLightningOutgoingPayment(normalPayment.id) assertNotNull(normalPaymentInDb) @@ -251,8 +251,8 @@ class PaymentsDbTestsCommon : LightningTestSuite() { LightningOutgoingPayment.Part(UUID.randomUUID(), amount = 157_000.msat, route = hops, status = LightningOutgoingPayment.Part.Status.Pending, createdAt = 100), ) db.addOutgoingLightningParts(parentId = swapOutPayment.id, parts = swapOutParts) - db.completeOutgoingLightningPart(swapOutParts[0].id, preimage, 110) - db.completeOutgoingPaymentOffchain(swapOutPayment.id, preimage, 120) + db.completeLightningOutgoingPaymentPart(swapOutPayment.id, swapOutParts[0].id, LightningOutgoingPayment.Part.Status.Succeeded(preimage, 110)) + db.completeLightningOutgoingPayment(swapOutPayment.id, LightningOutgoingPayment.Status.Succeeded(preimage, 120)) val swapOutPaymentInDb = db.getLightningOutgoingPayment(swapOutPayment.id) assertNotNull(swapOutPaymentInDb) @@ -284,18 +284,18 @@ class PaymentsDbTestsCommon : LightningTestSuite() { val partsFailed = initialPayment.copy( parts = listOf( - initialParts[0].copy(status = LightningOutgoingPayment.Part.Status.Failed(LightningOutgoingPayment.Part.Status.Failure.TemporaryRemoteFailure, 110)), - initialParts[1].copy(status = LightningOutgoingPayment.Part.Status.Failed(LightningOutgoingPayment.Part.Status.Failure.TooManyPendingPayments, 111)), + initialParts[0].copy(status = LightningOutgoingPayment.Part.Status.Failed(LightningOutgoingPayment.Part.Status.Failed.Failure.TemporaryRemoteFailure, 110)), + initialParts[1].copy(status = LightningOutgoingPayment.Part.Status.Failed(LightningOutgoingPayment.Part.Status.Failed.Failure.TooManyPendingPayments, 111)), ) ) - db.completeOutgoingLightningPart(initialPayment.parts[0].id, LightningOutgoingPayment.Part.Status.Failure.TemporaryRemoteFailure, 110) - db.completeOutgoingLightningPart(initialPayment.parts[1].id, LightningOutgoingPayment.Part.Status.Failure.TooManyPendingPayments, 111) + db.completeLightningOutgoingPaymentPart(initialPayment.id, initialPayment.parts[0].id, LightningOutgoingPayment.Part.Status.Failed(LightningOutgoingPayment.Part.Status.Failed.Failure.TemporaryRemoteFailure, 110)) + db.completeLightningOutgoingPaymentPart(initialPayment.id, initialPayment.parts[1].id, LightningOutgoingPayment.Part.Status.Failed(LightningOutgoingPayment.Part.Status.Failed.Failure.TooManyPendingPayments, 111)) assertEquals(partsFailed, db.getLightningOutgoingPayment(initialPayment.id)) initialPayment.parts.forEach { assertEquals(partsFailed, db.getLightningOutgoingPaymentFromPartId(it.id)) } - val paymentFailed = partsFailed.copy(status = LightningOutgoingPayment.Status.Completed.Failed(FinalFailure.RetryExhausted, 120)) - db.completeOutgoingPaymentOffchain(initialPayment.id, FinalFailure.RetryExhausted, 120) - assertFails { db.completeOutgoingPaymentOffchain(UUID.randomUUID(), FinalFailure.RetryExhausted, 120) } + val paymentFailed = partsFailed.copy(status = LightningOutgoingPayment.Status.Failed(FinalFailure.RetryExhausted, 120)) + db.completeLightningOutgoingPayment(initialPayment.id, LightningOutgoingPayment.Status.Failed(FinalFailure.RetryExhausted, 120)) + assertFails { db.completeLightningOutgoingPayment(UUID.randomUUID(), LightningOutgoingPayment.Status.Failed(FinalFailure.RetryExhausted, 120)) } assertEquals(paymentFailed, db.getLightningOutgoingPayment(initialPayment.id)) initialPayment.parts.forEach { assertEquals(paymentFailed, db.getLightningOutgoingPaymentFromPartId(it.id)) } } diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/OutgoingPaymentFailureTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/OutgoingPaymentFailureTestsCommon.kt index 68c69b650..78d122e27 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/OutgoingPaymentFailureTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/OutgoingPaymentFailureTestsCommon.kt @@ -27,9 +27,9 @@ class OutgoingPaymentFailureTestsCommon : LightningTestSuite() { Either.Left(TooManyAcceptedHtlcs(ByteVector32.Zeroes, 42)) ) ) - assertIs(failure.failures[0].failure) - assertIs(failure.failures[1].failure) - assertIsNot(failure.failures[2].failure) + assertIs(failure.failures[0].failure) + assertIs(failure.failures[1].failure) + assertIsNot(failure.failures[2].failure) } @Test @@ -42,9 +42,9 @@ class OutgoingPaymentFailureTestsCommon : LightningTestSuite() { Either.Right(IncorrectOrUnknownPaymentDetails(100_000.msat, 150)) ) ) - assertIsNot(failure.failures[0].failure) - assertIsNot(failure.failures[1].failure) - assertIs(failure.failures[2].failure) + assertIsNot(failure.failures[0].failure) + assertIsNot(failure.failures[1].failure) + assertIs(failure.failures[2].failure) } @Test @@ -56,7 +56,7 @@ class OutgoingPaymentFailureTestsCommon : LightningTestSuite() { Either.Right(PaymentTimeout), ) ) - assertEquals(Either.Left(LightningOutgoingPayment.Part.Status.Failure.RecipientLiquidityIssue), failure.explain()) + assertEquals(Either.Left(LightningOutgoingPayment.Part.Status.Failed.Failure.RecipientLiquidityIssue), failure.explain()) } @Test diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandlerTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandlerTestsCommon.kt index 9e555a5bc..8a9247cb6 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandlerTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandlerTestsCommon.kt @@ -99,8 +99,8 @@ class OutgoingPaymentHandlerTestsCommon : LightningTestSuite() { assertNotNull(dbPayment) assertEquals(100_000.msat, dbPayment.recipientAmount) assertEquals(invoice.nodeId, dbPayment.recipient) - assertTrue(dbPayment.status is LightningOutgoingPayment.Status.Completed.Failed) - assertEquals(FinalFailure.ChannelNotConnected, (dbPayment.status as LightningOutgoingPayment.Status.Completed.Failed).reason) + assertTrue(dbPayment.status is LightningOutgoingPayment.Status.Failed) + assertEquals(FinalFailure.ChannelNotConnected, (dbPayment.status as LightningOutgoingPayment.Status.Failed).reason) assertTrue(dbPayment.parts.isEmpty()) } @@ -118,8 +118,8 @@ class OutgoingPaymentHandlerTestsCommon : LightningTestSuite() { val dbPayment = outgoingPaymentHandler.db.getLightningOutgoingPayment(payment.paymentId) assertNotNull(dbPayment) assertEquals(amount, dbPayment.recipientAmount) - assertTrue(dbPayment.status is LightningOutgoingPayment.Status.Completed.Failed) - assertEquals(FinalFailure.InsufficientBalance, (dbPayment.status as LightningOutgoingPayment.Status.Completed.Failed).reason) + assertTrue(dbPayment.status is LightningOutgoingPayment.Status.Failed) + assertEquals(FinalFailure.InsufficientBalance, (dbPayment.status as LightningOutgoingPayment.Status.Failed).reason) assertTrue(dbPayment.parts.isEmpty()) } @@ -303,7 +303,7 @@ class OutgoingPaymentHandlerTestsCommon : LightningTestSuite() { assertEquals(invoice.nodeId, success.payment.recipient) assertEquals(invoice.paymentHash, success.payment.paymentHash) assertEquals(invoice, (success.payment.details as LightningOutgoingPayment.Details.Normal).paymentRequest) - assertEquals(preimage, (success.payment.status as LightningOutgoingPayment.Status.Completed.Succeeded.OffChain).preimage) + assertEquals(preimage, (success.payment.status as LightningOutgoingPayment.Status.Succeeded).preimage) assertEquals(1, success.payment.parts.size) val part = success.payment.parts.first() assertNotEquals(part.id, payment.paymentId) @@ -350,7 +350,7 @@ class OutgoingPaymentHandlerTestsCommon : LightningTestSuite() { assertEquals(invoice.nodeId, success.payment.recipient) assertEquals(invoice.paymentHash, success.payment.paymentHash) assertEquals(invoice, (success.payment.details as LightningOutgoingPayment.Details.Normal).paymentRequest) - assertEquals(preimage, (success.payment.status as LightningOutgoingPayment.Status.Completed.Succeeded.OffChain).preimage) + assertEquals(preimage, (success.payment.status as LightningOutgoingPayment.Status.Succeeded).preimage) assertEquals(1, success.payment.parts.size) assertEquals(310_000.msat, success.payment.parts.first().amount) val status = success.payment.parts.first().status @@ -443,7 +443,7 @@ class OutgoingPaymentHandlerTestsCommon : LightningTestSuite() { assertEquals(invoice.nodeId, success.payment.recipient) assertEquals(invoice.paymentHash, success.payment.paymentHash) assertEquals(invoice, (success.payment.details as LightningOutgoingPayment.Details.Normal).paymentRequest) - assertEquals(preimage, (success.payment.status as LightningOutgoingPayment.Status.Completed.Succeeded.OffChain).preimage) + assertEquals(preimage, (success.payment.status as LightningOutgoingPayment.Status.Succeeded).preimage) assertEquals(1, success.payment.parts.size) assertEquals(301_030.msat, success.payment.parts.first().amount) val status = success.payment.parts.first().status @@ -453,7 +453,7 @@ class OutgoingPaymentHandlerTestsCommon : LightningTestSuite() { assertNull(outgoingPaymentHandler.getPendingPayment(payment.paymentId)) val dbPayment2 = outgoingPaymentHandler.db.getLightningOutgoingPayment(payment.paymentId) assertNotNull(dbPayment2) - assertIs(dbPayment2.status) + assertIs(dbPayment2.status) assertEquals(1, dbPayment2.parts.size) assertTrue(dbPayment2.parts.all { it.status is LightningOutgoingPayment.Part.Status.Succeeded }) } @@ -577,7 +577,7 @@ class OutgoingPaymentHandlerTestsCommon : LightningTestSuite() { assertEquals(FinalFailure.WalletRestarted, fail.failure.reason) assertEquals(1, fail.failure.failures.size) // Since we haven't stored the shared secrets, we can't decrypt remote failure. - assertIs(fail.failure.failures.first().failure) + assertIs(fail.failure.failures.first().failure) } } @@ -605,7 +605,7 @@ class OutgoingPaymentHandlerTestsCommon : LightningTestSuite() { assertEquals(preimage, success.preimage) assertEquals(1, success.payment.parts.size) assertEquals(payment, success.request) - assertEquals(preimage, (success.payment.status as LightningOutgoingPayment.Status.Completed.Succeeded.OffChain).preimage) + assertEquals(preimage, (success.payment.status as LightningOutgoingPayment.Status.Succeeded).preimage) } } @@ -659,7 +659,7 @@ class OutgoingPaymentHandlerTestsCommon : LightningTestSuite() { private suspend fun assertDbPaymentFailed(db: OutgoingPaymentsDb, paymentId: UUID, partsCount: Int) { val dbPayment = db.getLightningOutgoingPayment(paymentId) assertNotNull(dbPayment) - assertTrue(dbPayment.status is LightningOutgoingPayment.Status.Completed.Failed) + assertTrue(dbPayment.status is LightningOutgoingPayment.Status.Failed) assertEquals(partsCount, dbPayment.parts.size) assertTrue(dbPayment.parts.all { it.status is LightningOutgoingPayment.Part.Status.Failed }) } @@ -669,7 +669,7 @@ class OutgoingPaymentHandlerTestsCommon : LightningTestSuite() { assertNotNull(dbPayment) assertEquals(amount, dbPayment.recipientAmount) assertEquals(fees, dbPayment.fees) - assertTrue(dbPayment.status is LightningOutgoingPayment.Status.Completed.Succeeded.OffChain) + assertTrue(dbPayment.status is LightningOutgoingPayment.Status.Succeeded) assertEquals(partsCount, dbPayment.parts.size) assertTrue(dbPayment.parts.all { it.status is LightningOutgoingPayment.Part.Status.Succeeded }) } From 0d78c416c58a72e752774bda224b6a95a20e1584 Mon Sep 17 00:00:00 2001 From: pm47 Date: Mon, 16 Dec 2024 15:05:25 +0100 Subject: [PATCH 2/5] helper methods to update outgoing onchain child classes --- .../fr/acinq/lightning/db/PaymentsDb.kt | 26 ++++++++++++++++--- .../payment/OutgoingPaymentHandler.kt | 2 +- .../acinq/lightning/db/InMemoryPaymentsDb.kt | 12 ++++----- .../lightning/db/PaymentsDbTestsCommon.kt | 10 +++---- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt index e6338d371..ea59030e8 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt @@ -61,15 +61,15 @@ interface OutgoingPaymentsDb { /** Add a new pending outgoing payment (not yet settled). */ suspend fun addOutgoingPayment(outgoingPayment: OutgoingPayment) + /** Add new partial payments to a pending outgoing payment. */ + suspend fun addLightningOutgoingPaymentParts(parentId: UUID, parts: List) + /** Get information about an outgoing payment (settled or not). */ suspend fun getLightningOutgoingPayment(id: UUID): LightningOutgoingPayment? /** Get information about an outgoing payment from the id of one of its parts. */ suspend fun getLightningOutgoingPaymentFromPartId(partId: UUID): LightningOutgoingPayment? - /** Add new partial payments to a pending outgoing payment. */ - suspend fun addOutgoingLightningParts(parentId: UUID, parts: List) - /** Mark a lightning outgoing payment as completed. */ suspend fun completeLightningOutgoingPayment(id: UUID, status: LightningOutgoingPayment.Status.Completed) @@ -449,6 +449,26 @@ sealed class OnChainOutgoingPayment : OutgoingPayment() { abstract override val createdAt: Long abstract val confirmedAt: Long? abstract val lockedAt: Long? + + companion object { + /** Helper method to facilitate updating child classes */ + fun OnChainOutgoingPayment.setLocked(lockedAt: Long): OnChainOutgoingPayment = + when (this) { + is SpliceOutgoingPayment -> copy(lockedAt = lockedAt) + is SpliceCpfpOutgoingPayment -> copy(lockedAt = lockedAt) + is InboundLiquidityOutgoingPayment -> copy(lockedAt = lockedAt) + is ChannelCloseOutgoingPayment -> copy(lockedAt = lockedAt) + } + + /** Helper method to facilitate updating child classes */ + fun OnChainOutgoingPayment.setConfirmed(confirmedAt: Long): OnChainOutgoingPayment = + when (this) { + is SpliceOutgoingPayment -> copy(confirmedAt = confirmedAt) + is SpliceCpfpOutgoingPayment -> copy(confirmedAt = confirmedAt) + is InboundLiquidityOutgoingPayment -> copy(confirmedAt = confirmedAt) + is ChannelCloseOutgoingPayment -> copy(confirmedAt = confirmedAt) + } + } } data class SpliceOutgoingPayment( diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandler.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandler.kt index f8c0cae62..e422272ab 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandler.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandler.kt @@ -92,7 +92,7 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle if (attemptNumber == 0) { db.addOutgoingPayment(LightningOutgoingPayment(request.paymentId, request.amount, request.recipient, request.paymentDetails, listOf(childPayment), LightningOutgoingPayment.Status.Pending)) } else { - db.addOutgoingLightningParts(request.paymentId, listOf(childPayment)) + db.addLightningOutgoingPaymentParts(request.paymentId, listOf(childPayment)) } val payment = PaymentAttempt(request, attemptNumber, childPayment, sharedSecrets, failures) pending[request.paymentId] = payment diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/InMemoryPaymentsDb.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/InMemoryPaymentsDb.kt index d5504f751..8f561228d 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/InMemoryPaymentsDb.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/InMemoryPaymentsDb.kt @@ -73,6 +73,12 @@ class InMemoryPaymentsDb : PaymentsDb { } } + override suspend fun addLightningOutgoingPaymentParts(parentId: UUID, parts: List) { + require(outgoing.contains(parentId)) { "parent outgoing payment with id=$parentId doesn't exist" } + parts.forEach { require(!outgoingParts.contains(it.id)) { "an outgoing payment part with id=${it.id} already exists" } } + parts.forEach { outgoingParts[it.id] = Pair(parentId, it) } + } + override suspend fun getLightningOutgoingPayment(id: UUID): LightningOutgoingPayment? { return outgoing[id]?.let { payment -> val parts = outgoingParts.values.filter { it.first == payment.id }.map { it.second } @@ -90,12 +96,6 @@ class InMemoryPaymentsDb : PaymentsDb { } } - override suspend fun addOutgoingLightningParts(parentId: UUID, parts: List) { - require(outgoing.contains(parentId)) { "parent outgoing payment with id=$parentId doesn't exist" } - parts.forEach { require(!outgoingParts.contains(it.id)) { "an outgoing payment part with id=${it.id} already exists" } } - parts.forEach { outgoingParts[it.id] = Pair(parentId, it) } - } - override suspend fun completeLightningOutgoingPayment(id: UUID, status: LightningOutgoingPayment.Status.Completed) { require(outgoing.contains(id)) { "outgoing payment with id=$id doesn't exist" } val payment = outgoing[id]!! diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/PaymentsDbTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/PaymentsDbTestsCommon.kt index 800bd8822..64cab4a3f 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/PaymentsDbTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/PaymentsDbTestsCommon.kt @@ -165,10 +165,10 @@ class PaymentsDbTestsCommon : LightningTestSuite() { LightningOutgoingPayment.Part(UUID.randomUUID(), 5_000.msat, listOf(HopDesc(a, c)), LightningOutgoingPayment.Part.Status.Pending, 115), LightningOutgoingPayment.Part(UUID.randomUUID(), 10_000.msat, listOf(HopDesc(a, b)), LightningOutgoingPayment.Part.Status.Pending, 120), ) - assertFails { db.addOutgoingLightningParts(UUID.randomUUID(), newParts) } - assertFails { db.addOutgoingLightningParts(onePartFailed.id, newParts.map { it.copy(id = initialPayment.parts[0].id) }) } + assertFails { db.addLightningOutgoingPaymentParts(UUID.randomUUID(), newParts) } + assertFails { db.addLightningOutgoingPaymentParts(onePartFailed.id, newParts.map { it.copy(id = initialPayment.parts[0].id) }) } val withMoreParts = onePartFailed.copy(parts = onePartFailed.parts + newParts) - db.addOutgoingLightningParts(onePartFailed.id, newParts) + db.addLightningOutgoingPaymentParts(onePartFailed.id, newParts) assertEquals(withMoreParts, db.getLightningOutgoingPayment(initialPayment.id)) withMoreParts.parts.forEach { assertEquals(withMoreParts, db.getLightningOutgoingPaymentFromPartId(it.id)) } @@ -221,7 +221,7 @@ class PaymentsDbTestsCommon : LightningTestSuite() { LightningOutgoingPayment.Part(UUID.randomUUID(), amount = 115_000.msat, route = hops1, status = LightningOutgoingPayment.Part.Status.Pending, createdAt = 100), LightningOutgoingPayment.Part(UUID.randomUUID(), amount = 75_000.msat, route = hops2, status = LightningOutgoingPayment.Part.Status.Pending, createdAt = 105) ) - db.addOutgoingLightningParts(parentId = normalPayment.id, parts = normalParts) + db.addLightningOutgoingPaymentParts(parentId = normalPayment.id, parts = normalParts) db.completeLightningOutgoingPaymentPart(normalPayment.id, normalParts[0].id, LightningOutgoingPayment.Part.Status.Succeeded(preimage, 110)) db.completeLightningOutgoingPaymentPart(normalPayment.id, normalParts[1].id, LightningOutgoingPayment.Part.Status.Succeeded(preimage, 115)) db.completeLightningOutgoingPayment(normalPayment.id, LightningOutgoingPayment.Status.Succeeded(preimage, 120)) @@ -250,7 +250,7 @@ class PaymentsDbTestsCommon : LightningTestSuite() { val swapOutParts = listOf( LightningOutgoingPayment.Part(UUID.randomUUID(), amount = 157_000.msat, route = hops, status = LightningOutgoingPayment.Part.Status.Pending, createdAt = 100), ) - db.addOutgoingLightningParts(parentId = swapOutPayment.id, parts = swapOutParts) + db.addLightningOutgoingPaymentParts(parentId = swapOutPayment.id, parts = swapOutParts) db.completeLightningOutgoingPaymentPart(swapOutPayment.id, swapOutParts[0].id, LightningOutgoingPayment.Part.Status.Succeeded(preimage, 110)) db.completeLightningOutgoingPayment(swapOutPayment.id, LightningOutgoingPayment.Status.Succeeded(preimage, 120)) val swapOutPaymentInDb = db.getLightningOutgoingPayment(swapOutPayment.id) From 73d215543dcf966b460ee6f1e5d5cc2b36d6fb10 Mon Sep 17 00:00:00 2001 From: pm47 Date: Tue, 17 Dec 2024 16:14:19 +0100 Subject: [PATCH 3/5] use completedAt=lockedAt for all on-chain payments It makes sense because all on-chain payments are final. THe decision on how to display payments based on their confirmation status is the concern of integrators. --- .../commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt index ea59030e8..c36b57d34 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt @@ -32,6 +32,7 @@ interface PaymentsDb : IncomingPaymentsDb, OutgoingPaymentsDb { } interface IncomingPaymentsDb { + /** Add a new expected incoming payment (not yet received). */ suspend fun addIncomingPayment(incomingPayment: IncomingPayment) @@ -449,6 +450,7 @@ sealed class OnChainOutgoingPayment : OutgoingPayment() { abstract override val createdAt: Long abstract val confirmedAt: Long? abstract val lockedAt: Long? + override val completedAt: Long? get() = lockedAt companion object { /** Helper method to facilitate updating child classes */ @@ -484,7 +486,6 @@ data class SpliceOutgoingPayment( ) : OnChainOutgoingPayment() { override val amount: MilliSatoshi = (recipientAmount + miningFees).toMilliSatoshi() override val fees: MilliSatoshi = miningFees.toMilliSatoshi() - override val completedAt: Long? = confirmedAt } data class SpliceCpfpOutgoingPayment( @@ -498,7 +499,6 @@ data class SpliceCpfpOutgoingPayment( ) : OnChainOutgoingPayment() { override val amount: MilliSatoshi = miningFees.toMilliSatoshi() override val fees: MilliSatoshi = miningFees.toMilliSatoshi() - override val completedAt: Long? = confirmedAt } data class InboundLiquidityOutgoingPayment( @@ -515,7 +515,6 @@ data class InboundLiquidityOutgoingPayment( val serviceFees: Satoshi = purchase.fees.serviceFee override val fees: MilliSatoshi = (localMiningFees + purchase.fees.total).toMilliSatoshi() override val amount: MilliSatoshi = fees - override val completedAt: Long? = lockedAt val fundingFee: LiquidityAds.FundingFee = LiquidityAds.FundingFee(purchase.fees.total.toMilliSatoshi(), txId) /** * Even in the "from future htlc" case the mining fee corresponding to the previous channel output @@ -568,7 +567,6 @@ data class ChannelCloseOutgoingPayment( ) : OnChainOutgoingPayment() { override val amount: MilliSatoshi = (recipientAmount + miningFees).toMilliSatoshi() override val fees: MilliSatoshi = miningFees.toMilliSatoshi() - override val completedAt: Long? = confirmedAt } data class HopDesc(val nodeId: PublicKey, val nextNodeId: PublicKey, val shortChannelId: ShortChannelId? = null) { From 6a87282afc4a5cb7fa48944020f60367f4e7121f Mon Sep 17 00:00:00 2001 From: pm47 Date: Thu, 19 Dec 2024 15:52:37 +0100 Subject: [PATCH 4/5] address @t-bast comments --- .../fr/acinq/lightning/db/PaymentsDb.kt | 34 +++++++++---------- .../payment/OutgoingPaymentHandler.kt | 14 +++++--- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt index c36b57d34..b4933b46c 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt @@ -452,25 +452,23 @@ sealed class OnChainOutgoingPayment : OutgoingPayment() { abstract val lockedAt: Long? override val completedAt: Long? get() = lockedAt - companion object { - /** Helper method to facilitate updating child classes */ - fun OnChainOutgoingPayment.setLocked(lockedAt: Long): OnChainOutgoingPayment = - when (this) { - is SpliceOutgoingPayment -> copy(lockedAt = lockedAt) - is SpliceCpfpOutgoingPayment -> copy(lockedAt = lockedAt) - is InboundLiquidityOutgoingPayment -> copy(lockedAt = lockedAt) - is ChannelCloseOutgoingPayment -> copy(lockedAt = lockedAt) - } + /** Helper method to facilitate updating child classes */ + fun OnChainOutgoingPayment.setLocked(lockedAt: Long): OnChainOutgoingPayment = + when (this) { + is SpliceOutgoingPayment -> copy(lockedAt = lockedAt) + is SpliceCpfpOutgoingPayment -> copy(lockedAt = lockedAt) + is InboundLiquidityOutgoingPayment -> copy(lockedAt = lockedAt) + is ChannelCloseOutgoingPayment -> copy(lockedAt = lockedAt) + } - /** Helper method to facilitate updating child classes */ - fun OnChainOutgoingPayment.setConfirmed(confirmedAt: Long): OnChainOutgoingPayment = - when (this) { - is SpliceOutgoingPayment -> copy(confirmedAt = confirmedAt) - is SpliceCpfpOutgoingPayment -> copy(confirmedAt = confirmedAt) - is InboundLiquidityOutgoingPayment -> copy(confirmedAt = confirmedAt) - is ChannelCloseOutgoingPayment -> copy(confirmedAt = confirmedAt) - } - } + /** Helper method to facilitate updating child classes */ + fun OnChainOutgoingPayment.setConfirmed(confirmedAt: Long): OnChainOutgoingPayment = + when (this) { + is SpliceOutgoingPayment -> copy(confirmedAt = confirmedAt) + is SpliceCpfpOutgoingPayment -> copy(confirmedAt = confirmedAt) + is InboundLiquidityOutgoingPayment -> copy(confirmedAt = confirmedAt) + is ChannelCloseOutgoingPayment -> copy(confirmedAt = confirmedAt) + } } data class SpliceOutgoingPayment( diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandler.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandler.kt index e422272ab..40c0dba4e 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandler.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandler.kt @@ -231,8 +231,8 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle } logger.info { "payment successfully sent (fees=${payment.fees})" } - db.completeLightningOutgoingPaymentPart(payment.request.paymentId, event.paymentId, LightningOutgoingPayment.Part.Status.Succeeded(preimage)) - db.completeLightningOutgoingPayment(payment.request.paymentId, LightningOutgoingPayment.Status.Succeeded(preimage)) + db.completeLightningOutgoingPaymentPart(payment.request.paymentId, event.paymentId, preimage) + db.completeLightningOutgoingPayment(payment.request.paymentId, preimage) removeFromState(payment.request.paymentId) val status = LightningOutgoingPayment.Status.Succeeded(preimage) val part = payment.pending.copy(status = LightningOutgoingPayment.Part.Status.Succeeded(preimage)) @@ -248,10 +248,10 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle } else -> { val logger = MDCLogger(logger, staticMdc = mapOf("childPaymentId" to partId) + payment.mdc()) - db.completeLightningOutgoingPaymentPart(payment.id, partId, LightningOutgoingPayment.Part.Status.Succeeded(preimage)) + db.completeLightningOutgoingPaymentPart(payment.id, partId, preimage) logger.info { "payment successfully sent (wallet restart)" } val request = PayInvoice(payment.id, payment.recipientAmount, payment.details) - db.completeLightningOutgoingPayment(payment.id, LightningOutgoingPayment.Status.Succeeded(preimage)) + db.completeLightningOutgoingPayment(payment.id, preimage) removeFromState(payment.id) // NB: we reload the payment to ensure all parts status are updated // this payment cannot be null @@ -330,6 +330,12 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle } } + private suspend fun OutgoingPaymentsDb.completeLightningOutgoingPayment(id: UUID, preimage: ByteVector32) = + completeLightningOutgoingPayment(id, LightningOutgoingPayment.Status.Succeeded(preimage)) + + private suspend fun OutgoingPaymentsDb.completeLightningOutgoingPaymentPart(id: UUID, partId: UUID, preimage: ByteVector32) = + completeLightningOutgoingPaymentPart(id, partId, LightningOutgoingPayment.Part.Status.Succeeded(preimage)) + private suspend fun OutgoingPaymentsDb.completeLightningOutgoingPayment(id: UUID, failure: FinalFailure) = completeLightningOutgoingPayment(id, LightningOutgoingPayment.Status.Failed(failure)) From 92b2cb1527597ea365450d8c145d3dc77d10fd04 Mon Sep 17 00:00:00 2001 From: pm47 Date: Thu, 19 Dec 2024 16:21:48 +0100 Subject: [PATCH 5/5] fixup! address @t-bast comments --- .../src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt index b4933b46c..c062b3182 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt @@ -453,7 +453,7 @@ sealed class OnChainOutgoingPayment : OutgoingPayment() { override val completedAt: Long? get() = lockedAt /** Helper method to facilitate updating child classes */ - fun OnChainOutgoingPayment.setLocked(lockedAt: Long): OnChainOutgoingPayment = + fun setLocked(lockedAt: Long): OnChainOutgoingPayment = when (this) { is SpliceOutgoingPayment -> copy(lockedAt = lockedAt) is SpliceCpfpOutgoingPayment -> copy(lockedAt = lockedAt) @@ -462,7 +462,7 @@ sealed class OnChainOutgoingPayment : OutgoingPayment() { } /** Helper method to facilitate updating child classes */ - fun OnChainOutgoingPayment.setConfirmed(confirmedAt: Long): OnChainOutgoingPayment = + fun setConfirmed(confirmedAt: Long): OnChainOutgoingPayment = when (this) { is SpliceOutgoingPayment -> copy(confirmedAt = confirmedAt) is SpliceCpfpOutgoingPayment -> copy(confirmedAt = confirmedAt)