Skip to content

Lightning send never reconnects the LSP peer → RouteNotFound during the reconnect window (send/receive asymmetry) #100

Description

@hash-money

Follow-up to #88 (closed). #88 established that the LSP peer reconnect is paid lazily by the next operation that needs the peer, and that the receive/JIT path triggers it. This is the corollary from the send side: pay() never triggers that reconnect, so a Lightning send during the reconnect window fails RouteNotFound with zero first hops even though the internet is already back.

The asymmetry (orange-sdk 5a540f6, ldk-node 0cea341)

Receive connects. LightningWallet::get_bolt11_invoicereceive_via_jit_channel → ldk-node receive_via_jit_channel_inner hands connection_manager to the LSPS2 client (lsps2_receive_to_jit_channel(…, connection_manager, …)), which connects to the LSP as part of negotiating the JIT channel.

Send does not. LightningWallet::pay (Bolt11) → ldk_node.bolt11_payment().send_using_amount()send_internalsend_payment. No connection_manager, no connect. send_payment routes over list_usable_channels(), and a channel is only usable while its peer is connected — so with the LSP peer down the router gets zero first hops and returns RouteNotFound immediately. Nothing re-establishes the connection; the send just waits for ldk-node's background reconnect loop to catch up.

Field incident (2026-07-25, mobile, staging)

Network flapped; internet returned in ~2 s, but the Lightning peer stayed down for ~48 s. During that window a Lightning send failed RouteNotFound in ~25 s (same hash, zero HTLCs dispatched) — while the mint (HTTPS) was reachable the whole time. A receive during the same window would have reconnected the peer immediately.

Not an edge case: under a small-trusted-float model most sends from a funded wallet exceed the float and route over Lightning, so they're peer-dependent — a connectivity flap then reliably produces failing sends until the background reconnect lands.

Proposed direction

Before dispatching a Lightning send, ensure the LSP peer is connected — mirroring what the JIT-receive path already does. is_connected_to_lsp() and lsp_node_id are already on hand, so it's a small guard in pay() (if !is_connected_to_lsp() { connect(…) }).

The one call that's yours to make: block the send until the reconnect completes, or best-effort with a short timeout. Happy to open a PR once you're comfortable with the direction — we have an on-device harness to validate against.

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