Skip to content

trampoline: accumulate inbound trampoline htlcs#4493

Draft
carlaKC wants to merge 25 commits intolightningdevkit:mainfrom
carlaKC:2299-mpp-accumulation
Draft

trampoline: accumulate inbound trampoline htlcs#4493
carlaKC wants to merge 25 commits intolightningdevkit:mainfrom
carlaKC:2299-mpp-accumulation

Conversation

@carlaKC
Copy link
Copy Markdown
Contributor

@carlaKC carlaKC commented Mar 18, 2026

This PR handles accumulation of inbound MPP trampoline parts, including handling of timeout and MPP validation. When all parts are successfully accumulated, we'll fail the MPP set backwards as we do not yet have support for outbound dispatch.

It does not include:

  • Handling trampoline replays / reload from disk (we currently refuse to read HTLCSource::TrampolineForward to prevent downgrade with trampoline in flight).
  • Interception of trampoline forwards, which I think we should add a separate flag for because it's difficult to map to our existing structure when we don't know the outbound channel at time of interception.

A few PR notes:

  • There's quite a lot of refactoring here, because a lot of the work added to support trampoline receives didn't consider the mpp forwarding case.
  • Happy to pull the refactoring out into a separate PR, there's a lot of mechanical stuff here that I think could be separated out easily.

@ldk-reviews-bot
Copy link
Copy Markdown

👋 Hi! I see this is a draft PR.
I'll wait to assign reviewers until you mark it as ready for review.
Just convert it out of draft status when you're ready for review!

@carlaKC carlaKC force-pushed the 2299-mpp-accumulation branch 2 times, most recently from 2f01cdc to 9d17783 Compare March 18, 2026 17:53
@valentinewallace
Copy link
Copy Markdown
Contributor

I find it easier to be confident in smaller PRs, so happy to see this broken up as mentioned on the dev call!

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 24, 2026

Codecov Report

❌ Patch coverage is 92.86798% with 47 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.23%. Comparing base (ab31f99) to head (2a44215).
⚠️ Report is 56 commits behind head on main.

Files with missing lines Patch % Lines
lightning/src/ln/channelmanager.rs 93.47% 29 Missing and 5 partials ⚠️
lightning/src/ln/onion_payment.rs 76.66% 7 Missing ⚠️
lightning/src/ln/onion_utils.rs 64.28% 3 Missing and 2 partials ⚠️
lightning/src/blinded_path/payment.rs 98.33% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4493      +/-   ##
==========================================
+ Coverage   86.19%   87.23%   +1.04%     
==========================================
  Files         160      163       +3     
  Lines      107537   109279    +1742     
  Branches   107537   109279    +1742     
==========================================
+ Hits        92693    95332    +2639     
+ Misses      12220    11452     -768     
+ Partials     2624     2495     -129     
Flag Coverage Δ
fuzzing 40.13% <36.14%> (?)
tests 86.33% <92.86%> (+0.14%) ⬆️

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.

@carlaKC carlaKC removed this from Weekly Goals Mar 26, 2026
@valentinewallace
Copy link
Copy Markdown
Contributor

Can you let me know your thoughts on the top two claude'd commits here? https://github.com/valentinewallace/rust-lightning/tree/2026-03-mpp-accumulation-wip

I think I prefer not entirely repurposing the existing claimable structs for trampoline. The top commit is pretty large though, admittedly, though it's super mechanical. The nice part is that there isn't a need to add the PaymentPurpose::Trampoline, or have fields that don't apply present in either claimable HTLCs or trampoline HTLCs, such as the skimmed fee.

@carlaKC
Copy link
Copy Markdown
Contributor Author

carlaKC commented Mar 26, 2026

Can you let me know your thoughts on the top two claude'd commits here? https://github.com/valentinewallace/rust-lightning/tree/2026-03-mpp-accumulation-wip
I think I prefer not entirely repurposing the existing claimable structs for trampoline. The top commit is pretty large though, admittedly, though it's super mechanical.

Nice cleanup! Didn't think that repurposing was too bad because it's relatively contained, but def nice to not need an unused PaymentPurpose/few fields. Will incorporate in the prefactor 👍

@carlaKC carlaKC force-pushed the 2299-mpp-accumulation branch from b36e5ab to dd6fb0f Compare April 1, 2026 18:05
carlaKC added 16 commits April 2, 2026 10:50
Pull out all fields that are common to incoming claimable and trampoline
MPP HTLCs. This will be used in future commits to accumulate MPP HTLCs
that are part of trampoline forwards - we can't claim these, but need
to accumulate them in the same way as receives before forwarding onwards.
We'll use this shared logic when we need to timeout trampoline HTLCs.

Note that there's a slight behavior change in this commit. Previously,
we'd do a first pass to check out total received value and return
early if we'd reached it without applying a MPP tick to any HTLC.
Now, we'll apply the MPP tick as we accumulate our total value received.

This does not make any difference, because we never MPP-timeout fully
accumulated MPP payments so it doesn't matter if we've applied the
tick when we've reached our full amount.
We'll re-use this to check trampoline MPP timeout in future commits.
In the commit that follows we're going to need to take ownership
of our htlc before this macro is used, so we pull out the information
we need in advance.
We're going to use the same logic for trampoline and for incoming MPP
payments, so we pull this out into a separate function.
To allow re-use with trampoline payments which won't use the
ClaimablePayment type, make handling generic for anything with MPP
parts.

Here we also move counterparty skimmed logic to claimable payments,
as this doesn't apply for trampoline.
For trampoline payments, we don't want to enforce a minimum cltv delta
between our incoming and outer onion outgoing CLTV because we'll
calculate our delta from the inner trampoline onion's value. However,
we still want to check that we get at least the CLTV that the sending
node intended for us and we still want to validate our incoming value.
Refactor to allow setting a zero delta, for use for trampoline payments.
To use helper functions for either trampoline or regular paths.
To create trampoline forwarding and single hop receiving tails.
We don't need to track a single trampoline secret in our HTLCSource
because this is already tracked in each of our previous hops contained
in the source. This field was unnecessarily added under the belief that
each inner trampoline onion we receive for inbound MPP trampoline would
have the same session key, and can be removed with breaking changes to
persistence because we have not yet released a version with the old
serialization - we currently refuse to decode trampoline forwards, and
will not read HTLCSource::Trampoline to prevent downgrades.
When we receive a trampoline forward, we need to wait for MPP parts to
arrive at our node before we can forward the outgoing payment onwards.
This commit threads this information through to our pending htlc struct
which we'll use to validate the parts we receive.
For regular blinded forwards, it's okay to use the amount in our
update_add_htlc to calculate the amount that we need to foward onwards
because we're only expecting on HTLC in and one HTLC out.

For blinded trampoline forwards, it's possible that we have multiple
incoming HTLCs that need to accumulate at our node that make our total
incoming amount from which we'll calculate the amount that we need to
forward onwards to the next trampoline. This commit updates our next
trampoline amount calculation to use the total intended incoming amount
for the payment so we can correctly calculate our next trampoline's
amount.

`decode_incoming_update_add_htlc_onion` is left unchanged because
the call to `check_blinded` will be removed in upcoming commits.
When we are a trampoline node receiving an incoming HTLC, we need access
to our outer onion's amount_to_forward to check that we have been
forwarded the correct amount. We can't use the amount in the inner
onion, because that contains our fee budget - somebody could forward us
less than we were intended to receive, and provided it is within the
trampoline fee budget we wouldn't know.

In this commit we set our outer onion values in PendingHTLCInfo to
perform this validation properly. In the commit that follows, we'll
start tracking our expected trampoline values in trampoline-specific
routing info.
carlaKC added 9 commits April 2, 2026 10:51
When we're forwarding a trampoline payment, we need to remember the
amount and CLTV that the next trampoline is expecting.
When we receive trampoline payments, we first want to validate the
values in our outer onion to ensure that we've been given the amount/
expiry that the sender was intending us to receive to make sure that
forwarding nodes haven't sent us less than they should.
When we are a trampoline router, we need to accumulate incoming HTLCs
(if MPP is used) before forwarding the trampoline-routed outgoing
HTLC(s). This commit adds a new map in channel manager, and mimics the
handling done for claimable_payments.

We will rely on our pending_outbound_payments (which will contain a
payment for trampoline forwards) for completing MPP claims,
not want to surface `PaymentClaimable` events for trampoline,
so do not need to have pending_claiming_payments like we have for MPP
receives.
Add our MPP accumulation logic for trampoline payments, but reject
them when they fully arrive. This allows us to test parts of our
trampoline flow without fully enabling it.

This commit keeps the same committed_to_claimable debug_assert behavior
as MPP claims, asserting that we do not fail our
check_claimable_incoming_htlc merge for the first HTLC that we add to a
set. This assert could also be hit if the intended amount exceeds
`MAX_VALUE_MSAT`, but we can't hit this in practice.
If we're a trampoline node and received an error from downstream that
we can't fully decrypt, we want to double-wrap it for the original
sender. Previously not implemented because we'd only focused on
receives, where there's no possibility of a downstream error.

While proper error handling will be added in a followup, we add the
bare minimum required here for testing.
While proper error handling will be added in a followup, we add the
bare minimum required here for testing.

Note that we intentionally keep the behavior of note setting
`payment_failed_permanently` for local failures because we can possibly
retry it.

For example, a local ChannelClosed error is considered to be permanent,
but we can still retry along another channel.
We can't perform proper validation because we don't know the outgoing
channel id until we forward the HTLC, so we just perform a basic CLTV
check.

Now that we've got rejection on inbound MPP accumulation, we relax this
check to allow testing of inbound MPP trampoline processing.
@carlaKC carlaKC force-pushed the 2299-mpp-accumulation branch from dd6fb0f to 2a44215 Compare April 2, 2026 14:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants