Add BOLT12 support to LSPS2 via custom Router implementation#4463
Add BOLT12 support to LSPS2 via custom Router implementation#4463tnull wants to merge 9 commits into
Router implementation#4463Conversation
|
👋 Thanks for assigning @jkczyz as a reviewer! |
2cb0546 to
25ab3bc
Compare
|
🔔 1st Reminder Hey @jkczyz! This PR has been waiting for your review. |
|
🔔 2nd Reminder Hey @jkczyz! This PR has been waiting for your review. |
25ab3bc to
5786409
Compare
|
No issues found. I re-reviewed the full current state of the PR (commit
Security: the decoded payment metadata is authored by the recipient and authenticated via the offer's reply-path context HMAC, so a payer cannot inject malicious LSP parameters. Safe. One low-confidence observation I deliberately did not file: No inline comments posted this pass. |
5786409 to
98a9e9d
Compare
8800d48 to
7ca886d
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #4463 +/- ##
==========================================
+ Coverage 87.08% 87.13% +0.04%
==========================================
Files 161 162 +1
Lines 109255 109707 +452
Branches 109255 109707 +452
==========================================
+ Hits 95147 95589 +442
- Misses 11627 11632 +5
- Partials 2481 2486 +5
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
7ca886d to
2ff16d7
Compare
bcc4e10 to
5602e07
Compare
ea05389 to
3acf915
Compare
| pub fn register_intercept_scid( | ||
| &self, intercept_scid: u64, invoice_params: LSPS2Bolt12InvoiceParameters, | ||
| ) -> Option<LSPS2Bolt12InvoiceParameters> { | ||
| debug_assert_eq!(intercept_scid, invoice_params.intercept_scid); |
There was a problem hiding this comment.
lol why on earth are we providing both just to assert that they're the same?
There was a problem hiding this comment.
Because I found it preferable to still communicate that the parameters will be keyed by intercept SCID, especially since we also want to remove them by intercept SCID. And dropping the intercept SCID from the parameters is also not great.
a0bcfcf to
0cef8dc
Compare
|
Addressed pending comments, also had to rebase as we now had a minor conflict. Let me know if I can squash. |
| self.check_refresh_async_receive_offer_cache(false).unwrap(); | ||
| } | ||
|
|
||
| /// Requests fresh async receive offer paths from the configured static invoice server, if any. |
There was a problem hiding this comment.
Why do we need a public version of this? We already do it on timer tick and any time we connect to a peer, istm this is entirely redundant.
There was a problem hiding this comment.
Exactly because we auto-request offers when connecting to a peer we found that we need this refresh during the LDK Node integration. Otherwise, there would already a refresh without the intercept SCIDs inflight when we start negotiating the LSPS2 flow and start injecting the SCIDs, which fails the flow. The alternative workaround would be a busy-loop that calls timer_tick_occurred until we can successfully generate an offer, but that's way uglier, hence why we added this API.
There was a problem hiding this comment.
Hmm, I don't think this currently works? If we build a static invoice when we connect to peers while we're negotiating LSPS2 (ie we have no LSPS2 parameters configured yet) we'll upload the static invoice to our static invoice storage server and there will be a window during which we cannot receive because the static invoice that is stored is bogus. LSPS2BOLT12Router really needs to fail until we have configured parameters so that we won't upload a bogus static invoice.
The docs here really need to mention that this should never really be required unless using LSPS.
There was a problem hiding this comment.
Hmm, I think you're right, but not quite sure what the best API for this would be? I guess we could add a flag that temporarily will have the router error, but that seems like a very race-y approach when potentially dealing with potentially a) multiple in-flight connections b) multiple LSPs c) multiple subsequent or parallel JIT flows per LSP.
Do you have any specific API in mind that would allow for these cases?
There was a problem hiding this comment.
I think the only way to do it would be to have the router error until its configured, yea. I think that should be mostly-fine, though - if we are building a blinded path and don't have any LSP information then having the router return a blinded path is just gonna result in a payment failure anyway (and probably error since we don't have any channels!). It seems fine to hard-code that.
| return Ok(offer); | ||
| } | ||
|
|
||
| self.flow.get_async_receive_offer_ready_future().await; |
There was a problem hiding this comment.
Can't this race? We probably need a bit more smarts in the flow to make sure we always return a pre-completed Future if async offers are already available in a race-free way.
There was a problem hiding this comment.
Can't this race? We probably need a bit more smarts in the flow to make sure we always return a pre-completed Future if async offers are already available in a race-free way.
I'm not quite sure I understand what you mean, could you expand? What would race what?
There was a problem hiding this comment.
If the async receive offer becomes ready between the get_async_receive_offer returning Err and hitting this we might block forever, no?
|
Added back a commit that derives |
|
Does this need updating with the changes from #4584? |
Yes, probably we should now wait for that to land first. |
Allow integrations to intercept blinded onion-message hops that identify the next node by short channel id, so LSPS-style protocols can resolve those hops out of band instead of dropping the message. Co-Authored-By: HAL 9000
4342483 to
e56ea06
Compare
Allow BOLT12 blinded payment paths to be supplemented from LSPS2 parameters decoded out of payment metadata, so recipients can advertise JIT-channel paths without maintaining router-local SCID state. Co-Authored-By: HAL 9000
Allow tests and callers to key offer metadata by offer id without wrapping the identifier. Co-Authored-By: HAL 9000
Let async recipients explicitly refresh receive offers, wait for readiness, and preserve payment metadata across static-invoice refreshes. Co-Authored-By: HAL 9000
Align LSPS2 CLTV deltas with the wire format and the rest of LDK's routing types. Co-Authored-By: HAL 9000
Clarify how LSPS2 invoice parameters relate to BOLT11 route hints and BOLT12 blinded payment path creation. Co-Authored-By: HAL 9000
Let integration tests force specific blinded payment paths so LSPS2 BOLT12 routing behavior can be exercised deterministically. Co-Authored-By: HAL 9000
Exercise decoder-provided LSPS2 parameters across custom-router, end-to-end, compact message path, and async-payment BOLT12 flows. Co-Authored-By: HAL 9000
Record the new LSPS2 BOLT12 routing support and its related compatibility note for the next release notes. Co-Authored-By: HAL 9000
e56ea06 to
26da1dd
Compare
|
Now updated to use the new payment-metadata-in-BOLT12-context flow. Also updated lightningdevkit/ldk-node#817 to use this. |
| /// | ||
| /// The metadata is persisted with the async receive offer cache so future static-invoice | ||
| /// refreshes for the same offer continue to include it. | ||
| pub fn refresh_async_receive_offers_with_payment_metadata( |
There was a problem hiding this comment.
Why do we need this? Doesn't our router inject the metadata for us?
There was a problem hiding this comment.
The MessageRouter in LDK Node will do that for regular offers, but that doesn't work for the async path:
For regular offers, ldk-node does this:
- Wraps MessageRouter.
- When create_offer_builder_using_router(...) builds blinded invoice-request paths, the wrapper sees MessageContext::Offers(InvoiceRequest { payment_metadata, .. }).
- It writes LSPS2 metadata there.
- Later, when an invoice request arrives, LDK turns that into PaymentContext::Bolt12Offer { payment_metadata }.
- Then LSPS2BOLT12Router can decode it while building the actual invoice payment paths.
For async static invoices, the order is different:
- The recipient proactively builds a StaticInvoice before any payer invoice request exists.
- The static invoice’s payment paths are built via Router::create_blinded_payment_paths.
- That call receives PaymentContext::AsyncBolt12Offer { payment_metadata }.
- The MessageRouter is not involved in that payment-path construction, so it has no place to inject the metadata.
There was a problem hiding this comment.
Right, then shouldn't we wrap the router rather than adding a new top level method here?
| self.check_refresh_async_receive_offer_cache(false).unwrap(); | ||
| } | ||
|
|
||
| /// Requests fresh async receive offer paths from the configured static invoice server, if any. |
There was a problem hiding this comment.
Hmm, I don't think this currently works? If we build a static invoice when we connect to peers while we're negotiating LSPS2 (ie we have no LSPS2 parameters configured yet) we'll upload the static invoice to our static invoice storage server and there will be a window during which we cannot receive because the static invoice that is stored is bogus. LSPS2BOLT12Router really needs to fail until we have configured parameters so that we won't upload a bogus static invoice.
The docs here really need to mention that this should never really be required unless using LSPS.
|
🔔 1st Reminder Hey @jkczyz! This PR has been waiting for your review. |
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