Skip to content

Add BOLT12 support to LSPS2 via custom Router implementation#4463

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

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

Conversation

@tnull
Copy link
Copy Markdown
Contributor

@tnull tnull commented Mar 5, 2026

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
Copy link
Copy Markdown

ldk-reviews-bot commented Mar 5, 2026

👋 Thanks for assigning @jkczyz 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
@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.

@tnull tnull force-pushed the 2026-03-lsps2-bolt12-alt branch from 25ab3bc to 5786409 Compare March 24, 2026 14:34
@ldk-claude-review-bot
Copy link
Copy Markdown
Collaborator

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

After thorough re-review of the entire PR diff and the affected files in the codebase, I've compared all findings against my 20+ prior inline comments. The vast majority of previously flagged issues have been resolved in this revision:

  • cleanup_intercept_scids removed (fixing deadlock, dead code, and SCID leak issues)
  • MessageRouter implementation removed from LSPS2BOLT12Router (fixing event reference errors, HashMap collision, broken rustdoc links)
  • continue instead of ? in loop + empty paths guard added
  • from_context_data method, BestBlock import, type mismatches, various typos all fixed
  • InvoiceRequestReceived event removed, pending_released_async_htlcs race removed

No new bugs, security vulnerabilities, or logic errors found in this pass.

Review Summary — PR #4463 (Re-review pass 8)

No new issues found.

Previously Flagged Issues — Status

All significant bugs from prior passes have been resolved. Two minor nits remain from prior passes (already posted, not re-posted):

  1. lightning-liquidity/src/lsps2/router.rs:55 — Unused [Event::HTLCIntercepted] rustdoc link definition (defined but never referenced in any doc comment)
  2. lightning-liquidity/src/lsps2/router.rs:70-73 — Redundant intercept_scid parameter alongside invoice_params.intercept_scid (mitigated by debug_assert_eq!)

@tnull tnull force-pushed the 2026-03-lsps2-bolt12-alt branch from 5786409 to 98a9e9d Compare March 24, 2026 14:50
@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
Copy link
Copy Markdown

codecov bot commented Mar 24, 2026

Codecov Report

❌ Patch coverage is 89.82684% with 47 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.99%. Comparing base (066859b) to head (ff32073).
⚠️ Report is 2859 commits behind head on main.

Files with missing lines Patch % Lines
lightning-liquidity/src/lsps2/router.rs 92.98% 27 Missing ⚠️
lightning/src/events/mod.rs 23.07% 9 Missing and 1 partial ⚠️
lightning/src/ln/channelmanager.rs 80.00% 7 Missing ⚠️
lightning/src/onion_message/messenger.rs 81.81% 2 Missing ⚠️
lightning/src/util/test_utils.rs 92.85% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4463      +/-   ##
==========================================
+ Coverage   84.55%   86.99%   +2.43%     
==========================================
  Files         135      164      +29     
  Lines       76569   109094   +32525     
  Branches    76569   109094   +32525     
==========================================
+ Hits        64745    94909   +30164     
- Misses       9783    11703    +1920     
- Partials     2041     2482     +441     
Flag Coverage Δ
fuzzing 40.19% <2.23%> (?)
tests 86.09% <89.78%> (?)

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
@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
@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 force-pushed the 2026-03-lsps2-bolt12-alt branch from 071aa74 to c37392e Compare April 7, 2026 13:22
@tnull tnull force-pushed the 2026-03-lsps2-bolt12-alt branch 2 times, most recently from 5d148e9 to 101a176 Compare April 8, 2026 13:10
@tnull tnull force-pushed the 2026-03-lsps2-bolt12-alt branch from 733d3f2 to 9f95a94 Compare April 8, 2026 13:25
@tnull tnull force-pushed the 2026-03-lsps2-bolt12-alt branch from 9f95a94 to b20cf2c Compare April 8, 2026 14:22
@tnull tnull requested review from TheBlueMatt and jkczyz April 8, 2026 14:22
@tnull
Copy link
Copy Markdown
Contributor Author

tnull commented Apr 8, 2026

Addressed pending comments. Let me know if I can squash. This/actually making it work in LDK Node is still blocked on #4542 (or rather the replacement for it). Let me know if you prefer me dropping the DROPME commit here, so that we can land this independently.

Copy link
Copy Markdown
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

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

Yes, feel free to squash. IMO we should drop the last commit and land this to get it out of the way and we can land the fix commit separately.

// triggering channel open. We however also keep the inner paths so the payer can use
// pre-existing inbound liquidity when available rather than always triggering a JIT
// channel open. As BOLT12 specifies that paths should be ordered by preference, adding
// JIT-paths to the end of the list *should* have the payee prefer pre-existing channels.
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.

TIL lol. We definitely don't do this and I cannot imagine anyone else has implemented a router with preference that dominates fee or success probabilities to the introduction point. Maybe we should do this at least for "introduction point the same", but in that case it shouldn't matter cause the LSP should use what's already open....in any case, it might be nice to document this a bit forcefully at the struct level but otherwise not much we can do.

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.

Ah, I assumed we did, esp. given Jeff brought it up above ^^

tnull added 3 commits April 9, 2026 09:48
We extend the `OnionMessenger` capabilities to also intercept onion
messages if they are for unknown SCIDs.

Co-Authored-By: HAL 9000
Add `intercept_unknown_scid_oms` test that verifies the
`OnionMessenger` correctly generates `OnionMessageIntercepted` events
with a `ShortChannelId` next hop when a blinded path uses an
unresolvable SCID. This complements the existing
`intercept_offline_peer_oms` test which only covers the `NodeId`
variant (offline peer case).

Co-Authored-By: HAL 9000
Add backwards compatibility tests for `Event::OnionMessageIntercepted`
serialization to verify that:

- Events serialized by LDK 0.2 (with `peer_node_id` in TLV field 0)
  can be deserialized by the current version as
  `NextMessageHop::NodeId`.
- Events with `NodeId` next hop serialized by the current version can
  be deserialized by LDK 0.2 (which reads `peer_node_id` from field
  0).
- Events with `ShortChannelId` next hop (which omit TLV field 0)
  correctly fail to deserialize in LDK 0.2, since the `peer_node_id`
  field is required there.

Co-Authored-By: HAL 9000
@tnull tnull force-pushed the 2026-03-lsps2-bolt12-alt branch from b20cf2c to bf0ead8 Compare April 9, 2026 07:51
@tnull
Copy link
Copy Markdown
Contributor Author

tnull commented Apr 9, 2026

Yes, feel free to squash. IMO we should drop the last commit and land this to get it out of the way and we can land the fix commit separately.

Squashed commits, reworded some commit messages to reflect the current state.

tnull added 8 commits April 9, 2026 10:00
Introduce `LSPS2BOLT12Router` to allow registering LSPS2 invoice
parameters and build blinded payment paths through the negotiated
intercept SCIDs. All other routing behavior still delegates to the
wrapped router.

Co-Authored-By: HAL 9000
Signed-off-by: Elias Rohrer <dev@tnull.de>
Describe how `InvoiceParametersReady` feeds both the existing `BOLT11`
route-hint flow and the new `LSPS2BOLT12Router` registration path for
`BOLT12` offers.

Co-Authored-By: HAL 9000
Exercise the LSPS2 buy flow and assert that registered LSPS2 parameters
produce a blinded payment path whose first forwarding hop uses the
negotiated intercept SCID.

Co-Authored-By: HAL 9000
Signed-off-by: Elias Rohrer <dev@tnull.de>
Allow tests to inject a custom `create_blinded_payment_paths` hook while
preserving the normal `ReceiveTlvs` bindings. This makes it possible to
exercise LSPS2-specific `BOLT12` path construction in integration tests.

Co-Authored-By: HAL 9000
Cover the full offer-payment flow from onion-message invoice exchange
through HTLC interception, JIT channel opening, and settlement. This
confirms the LSPS2 router and service handler work together in the
integrated path.

Co-Authored-By: HAL 9000
Test the full end-to-end flow of an async payment through an LSPS2 JIT
channel: static invoice server setup, `LSPS2BOLT12Router` registration,
async payment onion message exchange
(`HeldHtlcAvailable`/`ReleaseHeldHtlc`), HTLC interception, JIT channel
open, and payment claim.

Co-Authored-By: HAL 9000
Signed-off-by: Elias Rohrer <dev@tnull.de>
To enable suppoort for async payments via LSPS2 JIT channels, we expose
explicit async receive-offer refresh and readiness waiting so
integrators can sequence external setup before relying on a ready async
offer, instead of polling timer ticks.

Generated with AI assistance.

Co-Authored-By: HAL 9000
The BOLT 7 wire format defines `cltv_expiry_delta` as a 2-byte field,
and LDK uses u16 for it everywhere (`RouteHintHop`, `ChannelUpdateInfo`,
`UnsignedChannelUpdate`). Align the LSPS2 types accordingly.

serde_json will reject values exceeding `u16::MAX` during
deserialization, so a counterparty sending an out-of-range value is
handled gracefully.

Co-Authored-By: HAL 9000
Signed-off-by: Elias Rohrer <dev@tnull.de>
@tnull tnull force-pushed the 2026-03-lsps2-bolt12-alt branch from bf0ead8 to ff32073 Compare April 9, 2026 08:00

/// An identifier for an [`Offer`] built using [`DerivedMetadata`].
#[derive(Clone, Copy, Eq, PartialEq)]
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: not needed anymore?

Comment on lines +44 to +47
/// For **payment** blinded paths (in invoices), it returns the intercept SCID as the forwarding
/// hop so that the LSP can intercept the HTLC and open a JIT channel.
/// For **payment** blinded paths (in invoices), it appends paths using the intercept SCID as the
/// forwarding hop so that the LSP can intercept the HTLC and open a JIT channel. Paths from the
/// inner router (e.g., through pre-existing channels) are included as well, allowing payers to
/// use existing inbound liquidity when available.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seeing this in f538153. Should it belong in an earlier commit?

pub struct TestMessageRouter<'a> {
pub inner: TestMessageRouterInternal<'a>,
pub peers_override: Mutex<Vec<PublicKey>>,
pub forward_node_scid_override: Mutex<HashMap<PublicKey, u64>>,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is the TestMessageRouter changed needed anymore?

Comment on lines +168 to +183
/// Override closure type for [`TestRouter::override_create_blinded_payment_paths`].
///
/// This closure is called instead of the default [`Router::create_blinded_payment_paths`]
/// implementation when set, receiving the actual [`ReceiveTlvs`] so tests can construct custom
/// blinded payment paths using the same TLVs the caller generated.
pub type BlindedPaymentPathOverrideFn = Box<
dyn Fn(
PublicKey,
ReceiveAuthKey,
Vec<ChannelDetails>,
ReceiveTlvs,
Option<u64>,
) -> Result<Vec<BlindedPaymentPath>, ()>
+ Send
+ Sync,
>;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm... maybe we can have TestRouter wrap a dyn Router instead and provide a way to override it?

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