Skip to content

SubscribeEvents: support resuming from an event index for reliable at-least-once delivery #245

Open

Description

@joschisan

Motivation

SubscribeEvents is currently best-effort: events are fanned out over a bounded broadcast channel, so a subscriber that is disconnected, restarting, or too slow misses events with no way to recover them from the stream (the API guide documents this: "A slow subscriber that falls behind will miss events").

For payment-critical consumers this is hard to build on. A daemon driving hodl invoices via Bolt11ReceiveForHash must not miss PaymentClaimable or PaymentSuccessful; accounting and forwarding-analytics consumers need every event exactly because they don't know in advance which payment ids matter, so reconciling via ListPayments/GetPaymentDetails after a gap is either racy or a full rescan.

Proposed API

Two additive proto changes, no new RPC:

message EventEnvelope {
  // Globally monotonic index assigned to every event, across all event kinds.
  uint64 event_index = 1;

  oneof event { ... }  // unchanged
}

message SubscribeEventsRequest {
  // If set, first replays all events with event_index greater than this
  // value, in order, then continues with live events on the same stream.
  // If unset, the stream starts from now (current behavior).
  optional uint64 resume_after_index = 1;
}

Semantics:

  • One server stream, one ordered sequence: replayed events first, then live events, indistinguishable to the client.
  • Delivery is at-least-once. The client persists the last processed event_index and, on any disconnect or error, simply resubscribes with it. Duplicate delivery at the seam is allowed; clients dedup by index.
  • A single global sequence covering all event kinds (payments, forwards, channel state), so one cursor per subscriber is sufficient. Multiple independent subscribers each hold their own cursor; the server keeps no per-subscriber state.
  • If the server bounds how much history it retains, a defined error for a too-old cursor lets the client detect that it must fall back to a full resync — this contract should be part of the API even though retention itself is an implementation choice.

Relevance to ldk-node

The same API shape would arguably be practical for ldk-node itself.

Activity

  1. f3r10 commented on Aug 3, 2026

    @f3r10
    Contributor

    hi @benthecarman is this issue open to work on?

  2. benthecarman commented on Aug 3, 2026

    @benthecarman
    Collaborator

    @f3r10 this is something we'd need to support in ldk-node first

  3. f3r10 commented on Aug 4, 2026

    @f3r10
    Contributor

    oh ok you are right @benthecarman
    I am checking ldk-node's EventQueue (src/event.rs), and it looks like events don't currently have any stable/persistent index, and once event_handled() is called, the event is popped and discarded — nothing is retained after it's processed. So ldk-server can only re-broadcast events live over an in-memory bounded channel today, with nothing to replay from if a subscriber falls behind or reconnects.
    I checked for existing ldk-node issues/PRs covering this and didn't find one that's quite the same (there's one about deduplicating events on restart, and a broader persistence architecture discussion, but neither covers retaining/replaying handled events for external consumers). So it looks like this would need a stable event index plus retaining handled events for a bit so they can be replayed, added upstream in ldk-node first.

    I'm still fairly new to this codebase, so wanted to check before opening anything — does that read correct to you, and does it make sense for me to open an issue on ldk-node proposing that, so it can be discussed/implemented there before this one builds on top of it?

  4. benthecarman commented on Aug 4, 2026

    @benthecarman
    Collaborator

    @f3r10 yeah should open a similiar issue on ldk-node to discuss

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions