Skip to content

Handle outbound terminal LDK events when the payment record is missing #968

Description

@joostjager

Problem

src/event.rs, EventHandler::handle_event, handles outbound terminal LDK events by updating PaymentStore and then queueing a user-facing app event. However, the current match accepts DataStoreUpdateResult::NotFound as success because it matches Ok(_).

For LdkEvent::PaymentSent:

  1. The handler builds a PaymentDetailsUpdate with the payment hash, preimage, fee, and PaymentStatus::Succeeded.
  2. It calls self.payment_store.update(update).await.
  3. The match arm is Ok(_) => {}, so NotFound is treated the same as Updated or Unchanged.
  4. It optionally calls self.payment_store.get(&payment_id) only for logging.
  5. It queues Event::PaymentSuccessful with self.event_queue.add_event(event).await.

For LdkEvent::PaymentFailed, the same pattern applies: it builds a failed update, calls self.payment_store.update(update).await, treats Ok(_) as success, and then queues Event::PaymentFailed.

The underlying DataStore::update in src/data_store.rs explicitly returns Ok(DataStoreUpdateResult::NotFound) when the object is missing. That result does not create or persist a new record.

This also matters for future duplicate protection. In src/payment/bolt11.rs, Bolt11Payment::send_internal checks self.payment_store.get(&payment_id) before calling LDK, and known_payment_blocks_send only blocks a send when the local record is Pending or Succeeded. If a successful outbound terminal event is emitted without reconstructing a missing Succeeded record, ldk-node's duplicate gate may later have no durable record proving that the invoice or payment hash was already paid.

Consequence

An application can receive PaymentSuccessful or PaymentFailed while Node::payment and Node::list_payments still have no corresponding payment record. For success, this can look like sats were spent with no durable ldk-node payment history. It can also weaken duplicate-payment protection: if LDK no longer tracks the payment in recent payment state and ldk-node has no Succeeded record, ldk-node may not block a later attempt to pay the same invoice or payment hash before handing it to LDK. For failure, retry behavior becomes harder to reason about because the terminal state was emitted as an event but not recorded in the payment store.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions