-
Notifications
You must be signed in to change notification settings - Fork 427
BOLT12 Recurrence: Proof-of-Concept Implementation (Payer Flow) #4302
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
shaavan
wants to merge
15
commits into
lightningdevkit:main
Choose a base branch
from
shaavan:payer-recurrence
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This commit begins the introduction of BOLT12 recurrence support in LDK. It adds the core recurrence-related fields to `Offer`, enabling subscription-style and periodic payments as described in the draft spec. Since this is a PoC, the focus is on establishing the data model and documenting the intended semantics. Where the spec is ambiguous or redundant, accompanying comments note possible simplifications or improvements. This lays the foundation for the following commits, which will implement invoice-request parsing, payee-side validation, and period/paywindow handling. Spec reference: https://github.com/rustyrussell/bolts/blob/guilt/offers-recurrence/12-offer-encoding.md#tlv-fields-for-offers
This commit adds the recurrence-related TLVs to `InvoiceRequest`, allowing payers to specify the intended period index, an optional starting offset, and (when applicable) a recurrence cancellation signal. Spec reference: https://github.com/rustyrussell/bolts/blob/guilt/offers-recurrence/12-offer-encoding.md#tlv-fields-for-invoice_request
This commit adds the recurrence-related TLVs to the `Invoice` encoding, allowing the payee to include `invoice_recurrence_basetime`. This field anchors the start time (UNIX timestamp) of the recurrence schedule and is required for validating period boundaries across successive invoices. Additional initialization logic, validation notes, and design considerations are documented inline within the commit. Spec reference: https://github.com/rustyrussell/bolts/blob/guilt/offers-recurrence/12-offer-encoding.md#invoices
This begins the payee-side recurrence implementation by adding a dedicated builder API for constructing Offers that include recurrence fields. The new `create_offer_builder_with_recurrence` helper mirrors the existing offer builder but ensures that the recurrence TLVs are always included, making it easier for users to define subscription-style Offers.
This commit adds a minimal state tracker in `ChannelManager` for handling inbound recurring BOLT12 payments. Each entry records the payer’s recurrence progress (offset, next expected counter, and basetime), giving the payee enough information to validate successive `invoice_request`s and produce consistent invoices. LDK inbound payments have historically been fully stateless. Introducing a stateful mechanism here is a deliberate PoC choice to make recurrence behavior correct and testable end-to-end. For production, we may instead push this state to the user layer, or provide hooks so nodes can manage their own recurrence state externally. For now, this internal tracker gives us a clear foundation to build and evaluate the recurrence flow.
…` split) This refactor removes the separate `respond_with` / `respond_with_no_std` variants and replaces them with a single unified `respond_using_derived_keys(created_at)` API. Reasoning: - Upcoming recurrence logic requires setting `invoice_recurrence_basetime` based on the invoice’s `created_at` timestamp. - For consistency with Offer and Refund builders, we want a single method that accepts an explicit `created_at` value at the callsite. - The only real difference between the std/no_std response paths was how `created_at` was sourced; once it becomes a parameter, the split becomes unnecessary. This change consolidates the response flow, reduces API surface, and makes future recurrence-related changes simpler and more uniform across Offer, InvoiceRequest, and Refund builders.
This commit adds payee-side handling for recurrence-enabled `InvoiceRequest`s. The logic now: - Distinguishes between one-off requests, initial recurring requests, and successive recurring requests. - Initializes a new `RecurrenceData` session on the first recurring request (counter = 0). - Validates successive requests against stored session state (offset, expected counter, basetime). - Enforces paywindow timing when applicable. - Handles recurrence cancellation by removing the session and returning no invoice. This forms the core stateful logic required for a node to act as a BOLT12 recurrence payee. Payment-acceptance and state-update logic will follow in the next commit.
This commit adds the final piece of the payee-side recurrence flow: updating the internal `next_payable_counter` once a recurring payment has been successfully claimed. The update is performed immediately before emitting the `PaymentClaimed` event, ensuring the counter is advanced only after the payment is fully completed and acknowledged by the node. This provides a clear correctness boundary and avoids premature state transitions. The approach is intentionally conservative for this PoC. Future refinements may place the update earlier in the pipeline or integrate it more tightly with the payment-claim flow, but the current design offers simple and reliable semantics.
This commit introduces a minimal internal state tracker for outbound BOLT12 recurrence payments. Each outbound recurrence session records the Offer being paid, the payer signing public key, and the recurrence progress required to validate and advance successive invoices. Unlike one-off BOLT12 payments, recurring payments require the payer to maintain continuity across invoice boundaries. For this PoC, that state is tracked internally in `ChannelManager` so the payer can: - Validate incoming recurring invoices - Enforce expected counters and basetime - Drive the outbound recurrence payment flow end-to-end
Earlier refactors removed explicit payer signing pubkey methods from the `InvoiceRequest` builders, ensuring keys are always derived internally to improve security and pseudo-anonymity. However, the BOLT12 recurrence spec requires the payer to use a consistent payer signing pubkey across all invoice requests within a recurrence sequence. This commit reintroduces a minimal, internal-only set of builder helpers that allow specifying the payer signing pubkey explicitly. These helpers are not exposed publicly and are intended solely to support recurrence signing in the payer flow. Subsequent commits will use this API to construct and validate recurring invoice requests.
This commit introduces the first public payer-side API for BOLT12 recurrence: `pay_for_offer_with_recurrence`. The API allows a user to send the primary invoice request for a recurring Offer and initializes an outbound recurrence session for tracking subsequent payments. Unlike one-off payments, recurrence requires the payer to explicitly sign invoice requests using a stable payer signing key and to retain that key material across the lifetime of the recurrence session. This commit implements the minimal machinery required to support that flow. Since this is a PoC, key handling is intentionally simplified and is not yet suitable for production use. The current approach prioritizes end-to-end correctness and testability over long-term key management robustness. Detailed notes and design considerations are documented inline in the implementation. Future iterations will replace this with a more reliable and secure key management mechanism.
Recurring BOLT12 payments require the payer to maintain continuity across invoices rather than treating each invoice as an isolated object. This commit introduces payer-side recurrence handling that validates incoming invoices against an existing outbound recurrence session before any payment attempt is made. The payer now acts as a stateful participant in the recurrence protocol, ensuring that: - invoices correspond to a known recurrence session, - recurrence counters and period boundaries are respected, and - recurrence state advances only after a successful payment. The implementation is intentionally strict: any ambiguity or mismatch in recurrence state causes the invoice to be rejected. This keeps the payer flow simple, correct, and predictable at the PoC stage. Validation rules and state transitions are documented inline.
Recurring BOLT12 payments require the payer to initiate subsequent invoice requests based on previously established recurrence state, rather than starting from an Offer each time. This commit introduces `pay_for_recurrence`, a public API that allows the payer to continue an active outbound recurrence session by constructing and sending the next invoice request using the stored recurrence context. The API reuses the existing recurrence session to derive the payer signing key, populate recurrence metadata, and enqueue the invoice request while preserving continuity guarantees across payments. This separates the “start a recurrence” flow from the “continue a recurrence” flow, making the payer-side API explicit and harder to misuse. Detailed design notes and safety considerations are documented inline in the implementation.
This commit introduces `cancel_recurrence`, a public payer-side API for explicitly terminating an active BOLT12 recurrence session. If payer wish to cancel recurrence, they should signal cancellation to the payee to ensure no further invoices are issued or accepted. This API provides a structured way for the payer to send a final invoice request carrying a recurrence cancellation signal, derived from the existing outbound recurrence session state. Cancellation is treated as an explicit protocol action rather than a local state change. Once the cancellation request is enqueued, the corresponding outbound recurrence session is removed, ensuring no further payments can be initiated for that recurrence ID. Where the specification is ambiguous (for example, around which recurrence fields must accompany a cancellation request), this PoC chooses a conservative approach and preserves counter and start continuity. The rationale and alternatives are documented inline in the code.
Recurring BOLT12 payments are inherently time-bounded. Both payer and payee must eventually discard recurrence sessions that can no longer produce or accept valid protocol messages. This commit introduces explicit expiry logic for active recurrence sessions on both the outbound (payer) and inbound (payee) sides. For outbound sessions, a recurrence is pruned once it is no longer payable, either because the offer-defined recurrence limit has been reached or because the allowed payment window for the current period has elapsed. For inbound sessions, a recurrence is pruned once it can no longer accept a valid recurrence-enabled `invoice_request`, either due to reaching the recurrence limit or because the request window for the next period has expired. Sessions that have not yet entered an active recurrence phase (for example, those awaiting the first invoice) are preserved. By making expiry an explicit, time-driven state transition, this commit ensures that recurrence state remains bounded, self-cleaning, and aligned with the temporal assumptions of the protocol. Detailed rationale and edge-case handling are documented inline in the code.
|
👋 Hi! I see this is a draft PR. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Builds on #4245
BOLT12 Recurrence: Proof-of-Concept Implementation (Payer Flow)
This PR extends the existing BOLT12 Recurrence Proof-of-Concept by implementing the payer-side recurrence flow in LDK. It builds on the payee-side recurrence draft, completing the end-to-end protocol loop by allowing a payer to initiate, continue, validate, cancel, and expire recurring payments against a recurrence-enabled Offer.
What This PR Adds
1. Outbound Recurrence State (Payer Side)
Adds a minimal in-memory state tracker in
ChannelManagerto maintain outbound recurrence sessions. Each session records the originalOffer, the payer signing public key, and the recurrence progress required to validate invoices and advance payments safely. This mirrors the inbound recurrence tracker introduced on the payee side and provides a single source of truth for payer-side recurrence flow.2. Recurrence Initiation and Continuation APIs
Introduces explicit payer-side APIs for managing recurrence lifecycles:
pay_for_offer_with_recurrenceto initiate a recurrence by sending the primary invoice request and creating recurrence state.pay_for_recurrenceto continue an active recurrence using stored session context.This separation makes recurrence boundaries explicit and avoids overloading the existing one-off payment APIs.
3. Payer-Side Recurrence Invoice Handling
Adds payer-side validation for recurrence-enabled invoices. Incoming invoices are checked against outbound recurrence state to enforce counter continuity, basetime consistency, and period alignment. Recurrence state is advanced only after successful payment, preventing incorrect progression if invoices are produced but not settled.
4. Explicit Cancellation and Expiry
Adds support for explicit payer-side recurrence cancellation via a final invoice request carrying a cancellation signal. Also introduces time-based expiry logic for both outbound and inbound recurrence sessions, ensuring that sessions are removed once they are no longer payable or valid.
Design Notes
Intentional PoC Scope
As with the payee-side implementation, this PR makes deliberate simplifications to keep the logic auditable and reviewable. Recurrence state is not persisted across restarts, key handling is minimal, and period calculations are simplified. All such choices are documented inline.
What This PR Does Not Implement Yet
These are intentionally deferred to keep the payer flow focused.