Add BOLT12 support to LSPS2 via custom Router implementation#4463
Add BOLT12 support to LSPS2 via custom Router implementation#4463tnull wants to merge 8 commits intolightningdevkit:mainfrom
Router implementation#4463Conversation
Add `register_peer_for_interception()` and `deregister_peer_for_interception()` methods to `OnionMessenger`, allowing specific peers to be registered for onion message interception without enabling blanket interception for all offline peers. When a registered peer is offline and an onion message needs to be forwarded to them, `Event::OnionMessageIntercepted` is emitted. When a registered peer connects, `Event::OnionMessagePeerConnected` is emitted. This works alongside the existing global `new_with_offline_peer_interception()` flag — if either the global flag is set or the peer is specifically registered, interception occurs. This enables LSPS2 services to intercept onion messages only for peers with active JIT channel sessions, rather than intercepting messages for all offline peers. Co-Authored-By: HAL 9000
|
👋 Thanks for assigning @jkczyz as a reviewer! |
Define the `OnionMessageInterceptor` trait with `register_peer_for_interception()` and `deregister_peer_for_interception()` methods, and implement it for `OnionMessenger`. This allows external components to register peers for onion message interception via a trait object, without needing to know the concrete `OnionMessenger` type. Wire the trait into `LSPS2ServiceHandler` as an optional `Arc<dyn OnionMessageInterceptor>`. When provided: - On init, all peers with active intercept SCIDs are registered - In `invoice_parameters_generated()`, the counterparty is registered when a new intercept SCID is assigned This ensures that onion messages for LSPS2 clients with active JIT channel sessions are intercepted when those clients are offline, enabling the LSP to store and forward messages when the client reconnects. Co-Authored-By: HAL 9000
When intercept SCIDs are removed during cleanup, also clean up the handler-level `peer_by_intercept_scid` map and deregister the peer from onion message interception if they have no remaining active SCIDs. Cleanup is added in all relevant paths: - `prune_expired_request_state()` now returns pruned SCIDs - `peer_disconnected()` cleans up after pruning - `htlc_intercepted()` error path (fixes existing TODO) - `channel_open_abandoned()` cleans up after removing the SCID - `persist()` cleans up both `peer_by_intercept_scid` and `peer_by_channel_id` when removing a peer entry entirely Co-Authored-By: HAL 9000
Introduce a router wrapper that maps BOLT12 offer ids to LSPS2 invoice parameters and injects intercept-SCID blinded payment paths while delegating all other routing logic to an inner router. Co-Authored-By: HAL 9000
Clarify that InvoiceParametersReady supports BOLT11 route hints and BOLT12 offer flows via LSPS2BOLT12Router registration. Co-Authored-By: HAL 9000
Exercise the LSPS2 buy flow and assert that a registered `OfferId` produces a blinded payment path whose first forwarding hop uses the negotiated intercept `SCID`. This validates the custom-router wiring used for LSPS2 + `BOLT12`. Co-Authored-By: HAL 9000
Allow tests to provide a override that receives the caller's , enabling custom blinded-path generation while preserving valid bindings. Co-Authored-By: HAL 9000
Exercise the full flow through onion-message invoice exchange, , JIT channel opening, and settlement to confirm paths integrate with LSPS2 service handling. Co-Authored-By: HAL 9000
2cb0546 to
25ab3bc
Compare
| dns_resolver_handler: DRH, | ||
| custom_handler: CMH, | ||
| intercept_messages_for_offline_peers: bool, | ||
| peers_registered_for_interception: Mutex<HashSet<PublicKey>>, |
There was a problem hiding this comment.
Why not just ignore the events for peers that are offline? Not quite sure I get why we need to move the filtering logic into OnionMessenger.
| &self, payment_context: &PaymentContext, | ||
| ) -> Option<LSPS2Bolt12InvoiceParameters> { | ||
| // We intentionally only match `Bolt12Offer` here and not `AsyncBolt12Offer`, as LSPS2 | ||
| // JIT channels are not applicable to async (always-online) BOLT12 offer flows. |
There was a problem hiding this comment.
I don't think this is true? We need to support JIT opening for async offers as well.
There was a problem hiding this comment.
Yes, should have formulated that better, but IMO that is a next/follow-up step somewhat orthogonal to this PR?
There was a problem hiding this comment.
We can do it in a separate PR indeed, but I'm not really sure LSPS2 support for BOLT12 only for always-online nodes is nearly as useful has for async recipients. ISTM the second part is the more important usecase.
There was a problem hiding this comment.
The big difference is that there are other LSPS2 (client and service) implementations out there that LSPs are running, while async payments isn't deployed at all yet, and will require both sides to be LDK for the time being.
There was a problem hiding this comment.
I mean that's fair but are there other LSPS servers that support intercepting blinded paths and doing a JIT channel? I imagine we'll in practice require LDK for both ends for that as well.
There was a problem hiding this comment.
In any case my point is that both sides are a similar priority, not that they have to happen in one PR.
| .entry(next_node_id) | ||
| .or_insert_with(|| OnionMessageRecipient::ConnectedPeer(VecDeque::new())); | ||
|
|
||
| let should_intercept = self.intercept_messages_for_offline_peers |
There was a problem hiding this comment.
Shoulnd't we also expand interception to unknown SCIDs for blinded message path creation prior to channel open? I guess its not critical for this to work but it would make the generated offers much smaller as we'd be able to use the SCID encoding rather than pubkey encoding.
Closes #4272.
This is an alternative approach to #4394 which leverages a custom
Routerimplementation on the client side to inject the respective.LDK Node integration PR over at lightningdevkit/ldk-node#817