Skip to content

Commit

Permalink
Simplify outgoing payment state machine (#692)
Browse files Browse the repository at this point in the history
We previously supported having multiple channels with our peer, because
we didn't yet support splicing. Now that we support splicing, we always
have at most one active channel with our peer. This lets us simplify
greatly the outgoing payment state machine: payments are always made
with a single outgoing HTLC instead of potentially multiple HTLCs (MPP).

We don't need any kind of path-finding: we simply need to check the
balance of our active channel, if any.

We may introduce support for connecting to multiple peers in the future.
When that happens, we will still have a single active channel per peer,
but we may allow splitting outgoing payments across our peers. We will
need to re-work the outgoing payment state machine when this happens,
but it is too early to support this now anyway.

This refactoring makes it easier to create payment onion, by creating
the trampoline onion *and* the outer onion in the same function call.
This will make it simpler to migrate to the version of trampoline
that is currently specified in lightning/bolts#836
where some fields will be included in the payment onion instead of the
trampoline onion.

Co-authored-by: Thomas HUET <81159533+thomash-acinq@users.noreply.github.com>
  • Loading branch information
t-bast and thomash-acinq authored Oct 21, 2024
1 parent f86fffe commit 192f3bf
Show file tree
Hide file tree
Showing 12 changed files with 939 additions and 1,825 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,11 @@ data class CannotAffordFirstCommitFees (override val channelId: Byte
data class CannotAffordFees (override val channelId: ByteVector32, val missing: Satoshi, val reserve: Satoshi, val fees: Satoshi) : ChannelException(channelId, "can't pay the fee: missing=$missing reserve=$reserve fees=$fees")
data class CannotSignWithoutChanges (override val channelId: ByteVector32) : ChannelException(channelId, "cannot sign when there are no change")
data class CannotSignBeforeRevocation (override val channelId: ByteVector32) : ChannelException(channelId, "cannot sign until next revocation hash is received")
data class CannotSignDisconnected (override val channelId: ByteVector32) : ChannelException(channelId, "disconnected before signing outgoing payments")
data class UnexpectedRevocation (override val channelId: ByteVector32) : ChannelException(channelId, "received unexpected RevokeAndAck message")
data class InvalidRevocation (override val channelId: ByteVector32) : ChannelException(channelId, "invalid revocation")
data class InvalidFailureCode (override val channelId: ByteVector32) : ChannelException(channelId, "UpdateFailMalformedHtlc message doesn't have BADONION bit set")
data class CannotDecryptFailure (override val channelId: ByteVector32, val details: String) : ChannelException(channelId, "cannot decrypt failure message: $details")
data class PleasePublishYourCommitment (override val channelId: ByteVector32) : ChannelException(channelId, "please publish your local commitment")
data class CommandUnavailableInThisState (override val channelId: ByteVector32, val state: String) : ChannelException(channelId, "cannot execute command in state=$state")
data class ForbiddenDuringSplice (override val channelId: ByteVector32, val command: String?) : ChannelException(channelId, "cannot process $command while splicing")
Expand Down
16 changes: 5 additions & 11 deletions src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -812,34 +812,28 @@ class Peer(
is ChannelAction.ProcessIncomingHtlc -> processIncomingPayment(Either.Right(action.add))
is ChannelAction.ProcessCmdRes.NotExecuted -> logger.warning(action.t) { "command not executed" }
is ChannelAction.ProcessCmdRes.AddFailed -> {
when (val result = outgoingPaymentHandler.processAddFailed(actualChannelId, action, _channels)) {
is OutgoingPaymentHandler.Progress -> {
_eventsFlow.emit(PaymentProgress(result.request, result.fees))
result.actions.forEach { input.send(it) }
}

when (val result = outgoingPaymentHandler.processAddFailed(actualChannelId, action)) {
is OutgoingPaymentHandler.Failure -> _eventsFlow.emit(PaymentNotSent(result.request, result.failure))
null -> logger.debug { "non-final error, more partial payments are still pending: ${action.error.message}" }
}
}
is ChannelAction.ProcessCmdRes.AddSettledFail -> {
val currentTip = currentTipFlow.filterNotNull().first()
when (val result = outgoingPaymentHandler.processAddSettled(actualChannelId, action, _channels, currentTip)) {
when (val result = outgoingPaymentHandler.processAddSettledFailed(actualChannelId, action, _channels, currentTip)) {
is OutgoingPaymentHandler.Progress -> {
_eventsFlow.emit(PaymentProgress(result.request, result.fees))
result.actions.forEach { input.send(it) }
}

is OutgoingPaymentHandler.Success -> _eventsFlow.emit(PaymentSent(result.request, result.payment))
is OutgoingPaymentHandler.Failure -> _eventsFlow.emit(PaymentNotSent(result.request, result.failure))
null -> logger.debug { "non-final error, more partial payments are still pending: ${action.result}" }
null -> logger.debug { "non-final error, another payment attempt (retry) is still pending: ${action.result}" }
}
}
is ChannelAction.ProcessCmdRes.AddSettledFulfill -> {
when (val result = outgoingPaymentHandler.processAddSettled(action)) {
when (val result = outgoingPaymentHandler.processAddSettledFulfilled(action)) {
is OutgoingPaymentHandler.Success -> _eventsFlow.emit(PaymentSent(result.request, result.payment))
is OutgoingPaymentHandler.PreimageReceived -> logger.debug(mapOf("paymentId" to result.request.paymentId)) { "payment preimage received: ${result.preimage}" }
null -> logger.debug { "unknown payment" }
null -> logger.error { "unknown payment fulfilled: this should never happen" }
}
}
is ChannelAction.Storage.StoreState -> {
Expand Down
Loading

0 comments on commit 192f3bf

Please sign in to comment.