[reliable payments] router payment state machine#2761
Conversation
e83fd93 to
2409e3d
Compare
Roasbeef
left a comment
There was a problem hiding this comment.
I really dig this approach! With the state machine, I find it much easier to follow than the prior attempts at a solution to this problem. I've completed an initial pass so far, and the main question in my mind is the size of the overlap between this new state machine and the existing control tower in the switch. At one point the control tower addressed a need within the codebase, but it seems like this new state machine can eventually subsume the responsibilities of the control tower.
78a0c81 to
1687cdc
Compare
joostjager
left a comment
There was a problem hiding this comment.
I am, as always, worried about the persistence and the work and inflexibility it may cause us in the future. Tried to mainly analyze this part of the PR.
Why don't you merge the switch pr first btw, work bottom up? Or will we have with just this PR already good working resumes of payments?
ce35b99 to
9d1bd42
Compare
joostjager
left a comment
There was a problem hiding this comment.
Definitely nicer without that intermediate persistent state.
Main comments:
- Code structure in router
- Consolidation of payment related stores
There was a problem hiding this comment.
nice refactor of the router/switch interactions, this should make the whole payment flow much tighter!
one question i have with new state introduced in the control tower, will we just treat all payments that are currently grounded as started but never attempted? an alternative would be to rename grounded payments as failed, and introduce a new state for initiated. not sure which is better atm
| return nil | ||
| } | ||
|
|
||
| ctx.router.cfg.GetPaymentResult = func(paymentID uint64) ( |
There was a problem hiding this comment.
many of these GetPaymentResult funcs seem similar, any way we can generate the closures w/ less code duplication?
7180772 to
7aae12a
Compare
fd20644 to
ab0f77c
Compare
This commit changes the format used to store payments within the DB. Previously this was serialized as one continuous struct OutgoingPayment, which also contained an Invoice struct we where only using a few fields of. We now split it up into two simpler sub-structs CreationInfo, AttemptInfo and PaymentPreimage. We also want to associate the payments more closely with payment statuses, so we move to this hierarchy: There's one top-level bucket "sentPaymentsBucket" which contains a set of sub-buckets indexed by a payment's payment hash. Each such sub-bucket contains several fields: paymentStatusKey -> the payment's status paymentCreationInfoKey -> the payment's CreationInfo. paymentAttemptInfoKey -> the payment's AttemptInfo. paymentSettleInfoKey -> the payment's preimage (or zeroes for non-settled payments) The CreationInfo is information that is static during the whole payment lifcycle. The attempt info is set each time a new payment attempt (route+paymentID) is sent on the network. The preimage is information only known when a payment succeeds. It therefore makes sense to split them. We keep legacy serialization code for migration puproses.
migrateOutgoingPayments moves the OutgoingPayments into a new bucket format where they all reside in a top-level bucket indexed by the payment hash. In this sub-bucket we store information relevant to this payment, such as the payment status. To avoid that the router resend payments that have the status InFlight (we cannot resume these payments for pre-migration payments) we delete those statuses, so only Completed payments remain in the new bucket structure.
Since we have performed a migration, the db should be in a consistent state, and we can remove the non-strict option.
This commit gives a new responsibility to the control tower, letting it populate the payment bucket structure as the payment goes through its different stages. The payment will transition states Grounded->InFlight->Success/Failed, where the CreationInfo/AttemptInfo/Preimage must be set accordingly. This will be the main driver for the router state machine.
This encapsulates all state needed to resume a payment from any point of the payment flow, and that must be shared between the different stages of the execution. This is done to prepare for breaking the send loop into smaller parts, and being able to resume the payment from any point from persistent state.
This commit makes the router use the ControlTower to drive the payment life cycle state machine, to keep track of active payments across restarts. This lets the router resume payments on startup, such that their final results can be handled and stored when ready.
On startup the router will fetch the in-flight payments from the control tower, and resume their execution.
TestRouterPaymentStateMachine tests that the router interacts as expected with the ControlTower during a payment lifecycle, such that it payment attempts are not sent twice to the switch, and results are handled after a restart.
And unexport deprecated code.
TestPaymentControlDeleteNonInFlight checks that calling DeletaPayments only deletes payments from the database that are not in-flight.
This PR introduces a persistent state machine to the
ChannelRouter's payment flow, ensuring we can handle payment results that are received after a restart. It also paves they way for adding cancellation of payments, and resuming payment sessions across restarts.Problem
lndcurrently runs into problems if it is restarted while an HTLC is in flight on the network. The primary reason for this is the way therouterhands the HTLC to theswitch. Therouterpersist no information about the payment, so when a result eventually comes back, we risk the information needed to properly populate theOutgoingPaymentin the database is lost.Solution
With the
paymentStateMachineintroduced in this PR, we store two pieces of key information:paymentID. This is used to query theSwitchwhether the HTLC is still active. If not active we know that we can safely retry the payment attempt. If active therouterwill wait for the result to be available, and store the result to the DB.route. This is added to the DB together with the preimage when the payment succeeds.Note
The
Switchdoes not currently persist the pending payment attempts across restarts. This will be added in a follow-up PR.Replaces #2475
Builds on