-
Notifications
You must be signed in to change notification settings - Fork 445
Description
Introduction
I want to use this issue to discuss the main points of decision for implementing recurrence support.
Spec PR: lightning/bolts#1240
A draft PoC implementation can be found here: #4302
The branch is structured roughly as:
- introducing the wire fields
- implementing the payee flow
- implementing the payer flow
The PoC intentionally takes a few faster or less optimal shortcuts to demonstrate the recurrence mechanism end-to-end and validate the overall approach.
During implementation I identified several architectural decision points that would benefit from discussion before moving toward a more finalized design.
Recurrence State Ownership
The first major design question concerns where recurrence session state should live.
To manage recurring payments, both inbound and outbound flows require maintaining a small amount of session state. In the PoC implementation this is handled through:
However, LDK today is stateless for inbound payments. Introducing inbound recurrence tracking directly inside LDK would therefore represent a notable architectural shift.
To maintain uniformity between inbound and outbound handling, an alternative approach is to move recurrence state management entirely to the application layer (i.e., LDK node).
This raises the question of how LDK and LDK node should communicate when recurrence state needs to be accessed or updated.
Two possible approaches emerge:
1. Event-based management
LDK emits events whenever recurrence state needs to be accessed or updated.
LDK node captures these events and performs the corresponding state operations through the available public APIs.
2. Trait-based management
OffersMessageFlow (and therefore ChannelManager) is parameterized over a RecurrenceManager trait.
All recurrence state reads and writes occur through trait methods, while LDK node implements the trait and manages the internal state.
Current reasoning
The trait-based approach appears preferable for one key reason.
An event-driven approach introduces asynchronous-style handling into a path that ideally remains synchronous. Since creating invoice requests is essentially free for an attacker while event processing carries real cost, an event-based design could introduce DOS-adjacent attack vectors.
By contrast, a trait-based approach:
- keeps recurrence handling fully synchronous
- maintains a clear separation of concerns
- avoids event amplification risks
The main downside is slightly increased generic complexity in ChannelManager, but this appears manageable.
This represents the first core design question for recurrence: who owns recurrence session state, and how LDK should interact with it.
Additional Design Questions
Beyond state ownership, the PoC surfaced several smaller but still important design questions that would benefit from discussion before the recurrence implementation is finalized.
These questions primarily relate to lifecycle handling, key management, and automation behaviour in recurrence sessions.
1. Recurrence Cycle Completion
One design question is when a recurrence cycle should be considered completed.
Two natural points exist in the payment lifecycle:
- When the payment reaches the
PaymentClaimablestage. - When the payment is fully processed and
PaymentClaimedis generated.
Updating recurrence state at PaymentClaimable would allow earlier state transitions, but it introduces the risk of advancing the recurrence session before the payment is actually finalized.
Updating the state when PaymentClaimed is generated ensures the payment has successfully completed before advancing the recurrence counter.
The PoC currently updates recurrence state when transitioning from PaymentClaimable to PaymentClaimed.
2. Signing Key Management for Recurrence InvoiceRequests
For a given recurrence session, all invoice requests must use the same signing key.
This raises the question of how that key should be managed.
Two possible approaches exist:
- Store the signing key directly as part of the recurrence session state.
- Derive the signing key deterministically, for example using a combination of
RecurrenceIdand the node’s expanded key.
Storing the key simplifies the implementation but introduces additional key storage.
Deterministic derivation avoids storing additional secrets but requires careful key derivation logic.
3. Offer Storage for the Payer Flow
When generating successive invoice requests in the payer flow, the current implementation requires access to the entire Offer object.
This introduces an awkward lifecycle:
- The outbound recurrence session is partially initialized when the primary invoice request is created.
- The
recurrence_basetimemay only become known when the first invoice is received. - The session must therefore be updated after the first invoice arrives.
One question is whether subsequent invoice requests could be constructed using OfferContents alone.
If that were possible, it would avoid storing the full Offer and allow the recurrence session to be initialized fully only after the first invoice is received.
4. Triggering Subsequent InvoiceRequests
When a recurrence period becomes due, the PoC automatically creates and sends the next invoice request.
An alternative approach would be to emit an event and require the application to explicitly create and send the next invoice request.
Automatic triggering simplifies the subscription-like behaviour of recurrence sessions, while event-driven triggering would give applications more control over scheduling and payment policies.
5. Retry Behaviour
When an invoice request for the next recurrence cycle is sent, the PoC currently schedules retries by advancing next_trigger_time by a short interval (e.g., ~30 seconds) until the payment for the current period succeeds.
Questions here include:
- whether retries should be handled automatically
- what retry interval is appropriate
- whether retry behaviour should be configurable
I would appreciate feedback on the points above, especially around the state ownership model and the trait vs event approach. Any thoughts on the proposed directions or alternative designs would be very helpful before moving forward with a more refined implementation.