Skip to content

Add BOLT12 support to LSPS2 via custom Router implementation#4463

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

Add BOLT12 support to LSPS2 via custom Router implementation#4463
tnull wants to merge 21 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 @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
Copy link
Copy Markdown
Collaborator

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

I've completed a thorough review of the current state of the diff. All critical bugs identified in prior review passes (12 passes) have been confirmed fixed in this revision. The code is in good shape.

Review Summary — PR #4463 (Review pass 13)

No new bugs or security issues found. All critical issues from prior passes are confirmed resolved.

Verified fixes from prior passes

  • Deadlock in htlc_intercepted (cleanup_intercept_scids removed)
  • InvoiceRequestReceived even event number (event removed)
  • pending_released_async_htlcs race condition (field removed)
  • ? discarding valid paths in router loop (uses continue)
  • Empty paths returning Ok(vec![]) (guard added at router.rs:208)
  • Type mismatches in tests (cltv_expiry_delta is u16 throughout)
  • Changelog type name corrected (OnionMessenger::new_with_offline_peer_interception)

Previously posted nits still applicable (not reposted per instructions)

  • router.rs:55 — Unused [Event::HTLCIntercepted] rustdoc link definition
  • router.rs:73-74 — Redundant intercept_scid parameter (mitigated by debug_assert_eq!)
  • service.rs:819cleanup_intercept_scids defined but never called
  • service.rs:1920 — Pruned SCIDs can leak from peer_by_intercept_scid if peer removal is skipped

Changelog improvement

The backward compatibility note (pending_changelog/4463-LSPS2-BOLT12.txt) has been substantially improved since pass 12 — type name is now correct and the scope is properly limited to manual persistence. The phrase "will fail to deserialize" is still slightly imprecise (tag 37 is odd, so unknown older versions would silently skip rather than fail), but the overall warning is reasonable.

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

codecov Bot commented Mar 24, 2026

Codecov Report

❌ Patch coverage is 88.01653% with 58 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.10%. Comparing base (2313bd5) to head (0cef8dc).

Files with missing lines Patch % Lines
lightning-liquidity/src/lsps2/router.rs 93.39% 27 Missing ⚠️
lightning/src/ln/channelmanager.rs 29.62% 18 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.10%   +0.01%     
==========================================
  Files         161      162       +1     
  Lines      109255   109716     +461     
  Branches   109255   109716     +461     
==========================================
+ Hits        95147    95564     +417     
- Misses      11627    11669      +42     
- Partials     2481     2483       +2     
Flag Coverage Δ
fuzzing-fake-hashes 30.87% <7.04%> (-0.11%) ⬇️
fuzzing-real-hashes 22.56% <4.22%> (-0.09%) ⬇️
tests 86.18% <88.01%> (+0.01%) ⬆️

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
@ldk-reviews-bot
Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! 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 @TheBlueMatt! 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 @TheBlueMatt! 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.

pub fn register_intercept_scid(
&self, intercept_scid: u64, invoice_params: LSPS2Bolt12InvoiceParameters,
) -> Option<LSPS2Bolt12InvoiceParameters> {
debug_assert_eq!(intercept_scid, invoice_params.intercept_scid);
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.

lol why on earth are we providing both just to assert that they're the same?

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.

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.

Comment thread lightning-liquidity/src/lsps2/router.rs Outdated
Comment thread lightning-liquidity/src/lsps2/router.rs Outdated
Comment thread lightning/src/events/mod.rs Outdated
Comment thread pending_changelog/4463-LSPS2-BOLT12.txt Outdated
Comment thread lightning/src/ln/channelmanager.rs Outdated
Comment thread lightning/src/offers/flow.rs Outdated
Comment thread lightning/src/offers/flow.rs
Comment thread lightning/src/offers/flow.rs Outdated
Comment thread pending_changelog/4463-LSPS2-BOLT12.txt Outdated
tnull added 21 commits April 24, 2026 11:08
We extend the `OnionMessenger` capabilities to also intercept onion
messages if they are for unknown SCIDs.

Co-Authored-By: HAL 9000
Add an `intercept_for_unknown_scids: bool` parameter to
`OnionMessenger::new_with_offline_peer_interception`. When false, we
preserve the pre-0.3 behavior of dropping onion messages whose next
hop is a `ShortChannelId` that cannot be resolved. When true, we
generate an `Event::OnionMessageIntercepted` with a
`NextMessageHop::ShortChannelId` next hop.

This protects downgrade compatibility for users persisting
`OnionMessageIntercepted` events who may need to roll back to LDK
0.2, since the `ShortChannelId` variant of the event is not
deserializable there.

Co-Authored-By: HAL 9000
Replace the `debug_assert!(false, ...)` + `Ok(None)` branch in
`Event::OnionMessageIntercepted` deserialization with a plain
`ok_or(DecodeError::InvalidValue)?`. Returning an error on
malformed input matches the conventions used elsewhere in this
file and avoids silently dropping events that were written with
neither a `peer_node_id` nor a `next_hop` TLV.

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
The unknown-SCID interception is now gated on
`intercept_for_unknown_scids`, so the test must opt in via a new
`with_unknown_scid_interception` helper.

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
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>
Intercept SCIDs are only unique on a per-node basis, so two different
peers can legitimately hand out the same SCID. Keying the registration
map on the SCID alone would let the second registration silently
overwrite the first.

Co-Authored-By: HAL 9000
The bounds were never required by the struct or its Router impl.

Co-Authored-By: HAL 9000
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
Replace the blocking `await_async_receive_offer(Duration)` with an
`async fn await_async_receive_offer(&self) -> Result<Offer, ()>` and a
new `get_async_receive_offer_ready_future() -> Future` helper.

Async callers can `.await` the offer directly; synchronous callers can
fetch the underlying [`Future`] and call `wait_timeout` on it, which
lets them combine with whatever timing primitive their runtime
provides.

Co-Authored-By: HAL 9000
`Notifier`, `Future`, and `.await` are all available in no-std; only
`Future::wait_timeout` requires std. Stop gating the notifier field and
callers on `std`, and also revert the incidental blank-line removals
that crept into the async-receive-offer refresh changes.

Co-Authored-By: HAL 9000
Now that `get_async_receive_offer_ready_future` and
`await_async_receive_offer` are available in no-std, their rustdoc
intra-doc links to `Future::wait_timeout` (which is itself still
std-only) break the no-std rustdoc build. Wrap the sentence and
reference definition that mention `Future::wait_timeout` in
`cfg_attr(feature = "std", doc = ...)` so the links only appear when
they can resolve.

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>
Reword the backwards-compatibility note to call out that the downgrade
risk is limited to users who (1) manually persist
`Event::OnionMessageIntercepted` events and (2) opt in to the new
`intercept_for_unknown_scids` flag on
`OnionMessenger::new_with_offline_peer_interception`. LDK itself does
not persist these events, so anyone who doesn't persist them manually
is unaffected regardless of the flag.

Co-Authored-By: HAL 9000
@tnull tnull force-pushed the 2026-03-lsps2-bolt12-alt branch from a0bcfcf to 0cef8dc Compare April 24, 2026 09:17
@tnull
Copy link
Copy Markdown
Contributor Author

tnull commented Apr 24, 2026

Addressed pending comments, also had to rebase as we now had a minor conflict. Let me know if I can squash.

@tnull tnull requested a review from TheBlueMatt April 24, 2026 09:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Goal: Merge

Development

Successfully merging this pull request may close these issues.

BOLT 12 support for bLIP-52/LSPS2

5 participants