Skip to content

Add BOLT12 support to LSPS2 via custom Router implementation#4463

Open
tnull wants to merge 11 commits into
lightningdevkit:mainfrom
tnull:2026-03-lsps2-bolt12-alt
Open

Add BOLT12 support to LSPS2 via custom Router implementation#4463
tnull wants to merge 11 commits into
lightningdevkit:mainfrom
tnull:2026-03-lsps2-bolt12-alt

Conversation

@tnull

@tnull tnull commented Mar 5, 2026

Copy link
Copy Markdown
Contributor

Closes #4272.

This is an alternative approach to #4394 which leverages a custom Router implementation on the client side to inject the respective.

LDK Node integration PR over at lightningdevkit/ldk-node#817

@tnull tnull requested review from TheBlueMatt and jkczyz March 5, 2026 13:36
@ldk-reviews-bot

ldk-reviews-bot commented Mar 5, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@tnull tnull force-pushed the 2026-03-lsps2-bolt12-alt branch from 2cb0546 to 25ab3bc Compare March 5, 2026 14:05
Comment thread lightning/src/onion_message/messenger.rs Outdated
Comment thread lightning-liquidity/src/lsps2/router.rs Outdated
Comment thread lightning/src/onion_message/messenger.rs Outdated
@tnull tnull moved this to Goal: Merge in Weekly Goals Mar 5, 2026
@tnull tnull self-assigned this Mar 5, 2026
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @jkczyz! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @jkczyz! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Comment thread lightning-liquidity/src/lsps2/router.rs
@tnull tnull force-pushed the 2026-03-lsps2-bolt12-alt branch from 25ab3bc to 5786409 Compare March 24, 2026 14:34
Comment thread lightning-liquidity/src/lsps2/service.rs Outdated
Comment thread lightning-liquidity/src/lsps2/router.rs Outdated
@ldk-claude-review-bot

ldk-claude-review-bot commented Mar 24, 2026

Copy link
Copy Markdown
Collaborator

No issues found.

I re-reviewed the full current state of the PR (head commit abdecc2b9). The PR has been substantially refactored since my prior review passes — it is now a focused, self-contained payment-path Router wrapper. Notably, the current diff no longer touches events/mod.rs, onion_message/messenger.rs, or channelmanager.rs; the OnionMessageIntercepted event (with prev_hop/next_hop) is now pre-existing in the base branch. As a result, all of my prior inline comments targeted an older, different design and are obsolete — none of them map to current code lines, so I am not re-posting them.

What I verified in the current diff:

  • router.rs (new) — create_blinded_payment_paths is correct: async-offer early Err(()) guard, delegates to inner router, appends LSPS2 paths with continue on BlindedPaymentPath::new failure, guards against an empty path set with Err(()), and increases max_cltv_expiry by the forwarding delta in the correct direction. cltv_expiry_delta is now u16, so the as u32 in saturating_add is lossless. The () and Fn blanket LSPS2Bolt12PaymentMetadataDecoder impls don't conflict. Well covered by 6 unit tests.
  • PaymentContext::payment_metadata() (payment.rs:583) exists and returns Option<&BTreeMap<u64, Vec<u8>>> for all three Bolt12 variants — used correctly.
  • event.rs / msgs.rs / service.rs — the u32u16 change is consistent across the client event, the LSPS2BuyResponse wire struct, and the service API, and aligns with PaymentRelay.cltv_expiry_delta (u16) and the BOLT route-hint width. The LSPS2BuyResponse field is JSON (serde) and the value is semantically a u16 per the BOLT spec, so the narrowing is intentional and correct.
  • Integration tests — the three new BOLT12 tests use u16 consistently (execute_lsps2_dance now takes u16, so the prior type-mismatch concern is resolved), and the compact-message-path test relies only on pre-existing base machinery.

Security: the decoded payment metadata is authored by the recipient and authenticated via the offer/reply-path context HMAC, so a payer cannot inject malicious LSP parameters. Safe.

One low-confidence observation I deliberately did not file (consistent with my prior pass): router.rs:240 uses plain MIN_FINAL_CLTV_EXPIRY_DELTA, whereas the BOLT11 route-hint path uses MIN_FINAL_CLTV_EXPIRY_DELTA + 2. The end-to-end test completes (client claims), demonstrating the plain value is functionally sufficient for the blinded-path case, so this is not a defect.

No inline comments posted this pass.

@tnull tnull force-pushed the 2026-03-lsps2-bolt12-alt branch from 5786409 to 98a9e9d Compare March 24, 2026 14:50
Comment thread lightning-liquidity/src/lsps2/service.rs Outdated
Comment thread lightning-liquidity/tests/lsps2_integration_tests.rs Outdated
@tnull tnull force-pushed the 2026-03-lsps2-bolt12-alt branch 2 times, most recently from 8800d48 to 7ca886d Compare March 24, 2026 15:14
@codecov

codecov Bot commented Mar 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.38174% with 56 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.13%. Comparing base (2313bd5) to head (4342483).
⚠️ Report is 169 commits behind head on main.

Files with missing lines Patch % Lines
lightning-liquidity/src/lsps2/router.rs 93.39% 27 Missing ⚠️
lightning/src/ln/channelmanager.rs 32.00% 16 Missing and 1 partial ⚠️
lightning/src/events/mod.rs 33.33% 5 Missing and 1 partial ⚠️
lightning/src/offers/flow.rs 66.66% 2 Missing and 1 partial ⚠️
lightning/src/onion_message/messenger.rs 86.66% 2 Missing ⚠️
lightning/src/util/test_utils.rs 92.30% 1 Missing ⚠️
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     
Flag Coverage Δ
fuzzing-fake-hashes 31.10% <7.35%> (+0.12%) ⬆️
fuzzing-real-hashes 22.79% <4.41%> (+0.13%) ⬆️
tests 86.20% <88.38%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tnull tnull force-pushed the 2026-03-lsps2-bolt12-alt branch from 7ca886d to 2ff16d7 Compare March 25, 2026 08:23
Comment thread lightning-liquidity/src/lsps2/service.rs Outdated
@tnull tnull force-pushed the 2026-03-lsps2-bolt12-alt branch 4 times, most recently from bcc4e10 to 5602e07 Compare March 25, 2026 12:27
Comment thread lightning-liquidity/src/lsps2/service.rs Outdated
@tnull tnull force-pushed the 2026-03-lsps2-bolt12-alt branch 2 times, most recently from ea05389 to 3acf915 Compare March 25, 2026 13:24
@tnull

tnull commented Apr 28, 2026

Copy link
Copy Markdown
Contributor Author

Added back a commit that derives Hash for OfferId, as we need that downstream (slightly orthogonal, but connected to this PR).

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Does this need updating with the changes from #4584?

@tnull

tnull commented May 5, 2026

Copy link
Copy Markdown
Contributor Author

Does this need updating with the changes from #4584?

Yes, probably we should now wait for that to land first.

@tnull

tnull commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

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(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this? Doesn't our router inject the metadata for us?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, then shouldn't we wrap the router rather than adding a new top level method here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, no? I think we're going in circles here - if we have the router inject, then it would need to be stateful, meaning LDK Node will need to persist the LSPS2 params, and we just went through quite a bit of effort to avoid that. Or maybe I'm missing something?

Comment thread lightning/src/ln/channelmanager.rs Outdated
self.check_refresh_async_receive_offer_cache(false).unwrap();
}

/// Requests fresh async receive offer paths from the configured static invoice server, if any.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @jkczyz! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @jkczyz! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd Reminder

Hey @jkczyz! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

tnull added 11 commits June 17, 2026 09:07
Let BOLT12 blinded payment paths include LSPS2 parameters decoded
from payment metadata, so JIT-channel paths work without router
state for the intercepted SCID.

Co-Authored-By: HAL 9000
Fail async static-invoice path creation when no LSPS2 metadata exists.

This prevents automatic refreshes from uploading unusable invoices.

Co-Authored-By: HAL 9000
Allow offer metadata maps to key entries directly by offer id
without wrapping the identifier.

Co-Authored-By: HAL 9000
Let async recipients refresh receive offers explicitly, wait for
readiness, and preserve payment metadata across static-invoice
refreshes.

Co-Authored-By: HAL 9000
Return a completed future when an async receive offer is already ready.

Drop redundant public wrappers now covered by lower-level APIs.

Co-Authored-By: HAL 9000
Align LSPS2 CLTV deltas with the wire format and LDK 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
Assert no static invoice is uploaded before LSPS2 metadata is ready.

Keep LSPS2 integration tests compiling with current upstream types.

Co-Authored-By: HAL 9000
Record the new LSPS2 BOLT12 routing support and its compatibility
note for the next release notes.

Co-Authored-By: HAL 9000
@tnull tnull force-pushed the 2026-03-lsps2-bolt12-alt branch from 26da1dd to abdecc2 Compare June 17, 2026 10:30
@tnull tnull requested a review from TheBlueMatt June 17, 2026 10:35
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 4th Reminder

Hey @TheBlueMatt @jkczyz! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

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

Projects

Status: Goal: Merge

Development

Successfully merging this pull request may close these issues.

BOLT 12 support for bLIP-52/LSPS2

5 participants