Skip to content

Add outbound BOLT12 recurrence tracking - #1207

Draft
shaavan wants to merge 7 commits into
lightningdevkit:mainfrom
shaavan:outbound-recurrence
Draft

shaavan wants to merge 7 commits into
lightningdevkit:mainfrom
shaavan:outbound-recurrence

Conversation

@shaavan

@shaavan shaavan commented Oct 26, 2026

Copy link
Copy Markdown
Member

Overview

This PR adds payer-side tracking and manual payment APIs for recurring BOLT 12 offers in LDK Node, based on lightning/bolts#1240.

rust-lightning handles the recurrence protocol and invoice requests. LDK Node persists each recurring relationship, validates payment parameters and windows, reserves attempts before submission, and records payment outcomes.

Applications can initiate a recurrence, pay its next period, retry a failed attempt, cancel it, and inspect saved progress.

Scope

  • Persistent state and Rust APIs: Save recurring relationships, payment parameters, continuity state, and progress. Reload records at construction and expose owned copies through lookup/list APIs.
  • Manual payment lifecycle: Initiate, pay the next period, and retry failed attempts. Validate offer amounts, quantities, and payment windows; reserve attempts before submission without advancing progress on failure.
  • Outcomes and replay: Record matching payment outcomes, advance success once, preserve newer attempts, and complete at the inclusive limit. Keep success authoritative over failure and request replay when persistence fails.
  • Cancellation: Serialize cancellation with submission, block unqueued reservations, and abandon active attempts while retaining their IDs. Preserve Cancelled status on late success and notify the payee using established continuity state.
  • Tests: Cover persistence, APIs, validation, replay, concurrency, and cancellation with late success.

Deferred work

Deferred work

The following work is deferred to follow-up PRs.

  • Restart reconciliation: Resolve unfinished attempts and claims using restored LDK state and replayed events. Preserve cancellation, apply progress once, and retain reservations until an outcome is established.
  • Scheduler: Reconcile missed windows and completion, retry eligible failures, and submit outbound payments when automatic payment is enabled. Persist that setting, account for active attempts and pending claims, and reuse mutation helpers within node lifecycle.
  • UniFFI: Expose outbound operations, automatic-payment configuration, and lookup/list APIs for both directions, including public records, statuses, IDs, errors, and the inbound composite key.

Update LDK Node's Cargo.toml to use recurrence-aware versions of
rust-lightning and bitcoin-payment-instructions.

This makes the recurrence APIs available for implementing recurrence
support in subsequent commits.

Co-authored-by: GLaDOS
Recurring offers outlive individual payment attempts and node processes.
To resume a recurrence safely after restart, the node must retain its
original offer terms, lifecycle state, payment progress, and payee-provided
opaque state.

Add RecurrenceDetails and RecurrenceStatus with TLV serialization and
RecurrenceId-based persistence. Load recurrence records during node
construction under a dedicated "recurrences" namespace, retaining all
entries for the lifetime of each recurrence.

Thread RecurrenceStore through Node, EventHandler, and Bolt12Payment to
establish the persistence foundation for recurrence scheduling and payment
handling. Recurring payment execution remains disabled in this commit.

Co-Authored-By: GLaDOS
Add an API to start an outbound recurrence by submitting its initial
invoice request.

Validate quantity against the offer and calculate its minimum total with
checked multiplication. Reserve the recurrence and initial payment before
submission, rejecting existing IDs rather than overwriting their records.

Retain submission failures without advancing paid progress. Failure updates
only affect the reserved attempt and preserve completed payments. Acquire
recurrence-store locks before payment-store locks.

Co-Authored-By: GLaDOS
@ldk-reviews-bot

Copy link
Copy Markdown

👋 Hi! I see this is a draft PR.
I'll wait to assign reviewers until you mark it as ready for review.
Just convert it out of draft status when you're ready for review!

@shaavan shaavan mentioned this pull request Oct 26, 2026
5 of 7 tasks
Allow callers to submit subsequent payments using the stored recurring
offer and payment parameters. Require an active recurrence, a successful
initial payment, no active attempt and an open payment window.

Validate the next period against the inclusive recurrence limit. After
submission, persist the payment record and associate its ID with the
recurrence for event handling.

Return the payment ID on successful submission and an error on synchronous
failure. Submission failures other than duplicate payment IDs are recorded
as failed payments, while the recurrence attempt is marked active.

Co-Authored-By: GLaDOS
Failed initial payments cannot be retried through pay_next_outbound_recurrence,
which requires the first payment to have succeeded.

Add retry_outbound_recurrence for active recurrences with a failed attempt.
Reuse reservation and submission without advancing the recurrence period.

Validate the payment window before reservation and again before queueing,
including initial retries with an explicit basetime. Permit an unknown
window only when the first payment has not established basetime and the
offer does not specify one.

Co-Authored-By: GLaDOS
Recurring payments need persisted outcomes so callers can determine whether
to retry an attempt or pay the next period after restart.

On success, retain the payment ID, advance paid progress, establish the
basetime, and save the payee's opaque state for the next request. Clear the
active attempt and mark the recurrence completed when its limit is reached.

On failure, retain the failed payment ID without advancing paid progress.
Match outcomes to the active payment ID and recheck ownership under the
mutation lock so replayed events cannot overwrite a newer attempt. Request
event replay when persistence fails.

Co-Authored-By: GLaDOS
Cancel an outbound recurrence locally and queue a cancellation request for
the payee after a successful payment.

Use the same mutation lock as submission so cancellation can prevent an
unqueued reservation from being submitted. Abandon an active attempt while
retaining its payment ID for late success or failure handling.

Read the latest recurrence state under the lock rather than overwriting
progress from an earlier snapshot. Persist local cancellation even when
remote cancellation queueing fails, then return the queueing error.

Co-Authored-By: GLaDOS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants