Skip to content

Conversation

@valentinewallace
Copy link
Contributor

@valentinewallace valentinewallace commented Feb 9, 2026

Previously, the InboundUpdateAdd::Forwarded enum variant contained an
HTLCPreviousHopData, which had a lot of fields that were redundant with the
outer InboundHTLCOutput/Channel structs. Here we dedup those fields, which is
important because the pending InboundUpdateAdds are persisted whenever the
ChannelManager is persisted.

Based on #4303
Partially addresses #4286

@valentinewallace valentinewallace added this to the 0.3 milestone Feb 9, 2026
@valentinewallace valentinewallace self-assigned this Feb 9, 2026
@valentinewallace valentinewallace added the weekly goal Someone wants to land this this week label Feb 9, 2026
@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Feb 9, 2026

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

@codecov
Copy link

codecov bot commented Feb 9, 2026

Codecov Report

❌ Patch coverage is 96.03175% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.06%. Comparing base (caf0aac) to head (ab0ba65).
⚠️ Report is 24 commits behind head on main.

Files with missing lines Patch % Lines
lightning/src/ln/channel.rs 94.23% 3 Missing ⚠️
lightning/src/ln/channelmanager.rs 97.01% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4405      +/-   ##
==========================================
+ Coverage   86.05%   86.06%   +0.01%     
==========================================
  Files         156      156              
  Lines      103384   103431      +47     
  Branches   103384   103431      +47     
==========================================
+ Hits        88964    89021      +57     
+ Misses      11905    11899       -6     
+ Partials     2515     2511       -4     
Flag Coverage Δ
tests 86.06% <96.03%> (+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.

valentinewallace and others added 3 commits February 10, 2026 13:19
Useful when using these macros in lightning-tests/upgrade_downgrade_tests
In the next commit, we want to dedup fields between the
InboundUpdateAdd::Forwarded's HTLCPreviousHopData and the outer
InboundHTLCOutput/Channel structs, since many fields are duplicated in both
places at the moment. As part of doing this cleanly, we first refactor the
method that retrieves these InboundUpdateAdds for reconstructing the set of
pending HTLCs during ChannelManager deconstruction.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previously, the InboundUpdateAdd::Forwarded enum variant contained an
HTLCPreviousHopData, which had a lot of fields that were redundant with the
outer InboundHTLCOutput/Channel structs. Here we dedup those fields, which is
important because the pending InboundUpdateAdds are persisted whenever the
ChannelManager is persisted.
@valentinewallace
Copy link
Contributor Author

Still need to finalize what manager version to write (#4303 (comment)), but otherwise this should be good to go.

@valentinewallace valentinewallace marked this pull request as ready for review February 11, 2026 22:55
@valentinewallace valentinewallace changed the title Dedup InboundUpdateAdd::Forwarded data with outer structs Dedup InboundUpdateAdd::Forwarded data; fix PaymentForwarded fields Feb 11, 2026
@valentinewallace valentinewallace requested review from TheBlueMatt and joostjager and removed request for tankyleo February 11, 2026 23:00
Copy link
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.

Didn't review the test changes but otherwise basically lgtm.

Copy link
Contributor

@joostjager joostjager left a comment

Choose a reason for hiding this comment

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

Clean PR.

The PR description motivates the dedup as important for reducing persistence size, how much is actually saved? Claude says 153 bytes per HTLC.

Given that the number of in-flight forwarded HTLCs is typically low, the net persistence savings seem marginal.

// Each 1M sat outbound channel has 100M msat max in-flight, so 150M msat requires splitting.
let amt_msat = 150_000_000;
let (route, payment_hash, payment_preimage, payment_secret) =
get_route_and_payment_hash!(nodes[0], nodes[2], amt_msat);
Copy link
Contributor

Choose a reason for hiding this comment

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

In other tests, I've seen the routes being constructed in the test, for full control. Also a bit more self-documenting perhaps.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I prefer it this way, it's more concise and looks less janky IMO.

@joostjager
Copy link
Contributor

One concern with reconstructing forwarding state from channel monitors: will trampoline make this significantly harder?

Today a forward is a 1-to-1 link between two channels, but with trampoline a single logical forward can involve multiple inbound and multiple outbound HTLCs across different channels (MPP on both sides). Reconstructing that M-to-N relationship from individual monitors, without any explicit record tying them together, seems pretty complicated?

@TheBlueMatt
Copy link
Collaborator

TheBlueMatt commented Feb 12, 2026

It should be relatively doable - we need to assign a "trampoline id" for a trampoline anyway so that the outbound edges know where to go to update state/claim (in the HTLCSource) so when we're reloading without state we should be able to just keep a map of "htlcs inbound to trampoline id" and track if there are any "htlcs outbound for trampoline id", equivalent to the current code that just tracks "htlcs inbound unresolved" and removes "htlcs outbound unresolved" from it.

@joostjager
Copy link
Contributor

The id seems helpful indeed. But even with that, reconstruction adds complexity on top of what's already a non-trivial trampoline state machine. And also it is already hard to get the current reconstruction right.

I am still not fully sold that the idea of storing all state in the monitors one way or another, and the performance benefit that that might give us, is worth it at the current stage of the lightning game. I guess we'll find out.

Makes an upcoming commit cleaner: when we add a next_hop variable we want to
distinguish it from the previous hop.
Adds support for passing user_channel_id into the pending_claims_to_replay vec,
which is used by the ChannelManager on startup.

For now user_channel_id is always set to None, but in upcoming commits we will
set it to Some when the downstream channel is still open (this is currently a
bug). Separated out here for reviewability.
We need these fields to generate a correct PaymentForwarded event if we need to
claim this inbound HTLC backwards after restart and it's already been claimed
and removed on the outbound edge.
Previously, we were spuriously using the upstream channel's info when we
should've been using the downstream channel's.
Previously, if a forwarding node reloaded mid-HTLC-forward with a preimage in
the outbound edge monitor and the outbound edge channel still open, and
subsequently reclaimed the inbound HTLC backwards, the PaymentForwarded event
would be missing the next_user_channel_id field.
Test that if we restart and had two inbound MPP-part HTLCs received over the
same channel in the holding cell prior to shutdown, and we lost the holding
cell prior to restart, those HTLCs will still be claimed backwards.

Test largely written by Claude
We previously had 5 due to wanting some flexibility to bump versions in
between, but eventually concluded that wasn't necessary.
@valentinewallace valentinewallace force-pushed the 2026-02-dedup-htlc-fwd-data branch from 58edcb2 to ab0ba65 Compare February 12, 2026 20:30
@valentinewallace
Copy link
Contributor Author

The PR description motivates the dedup as important for reducing persistence size, how much is actually saved? Claude says 153 bytes per HTLC.

It could potentially add up, especially for a node like c=, given how often we persist the manager. If 153 bytes is correct and all slots are filled then a single channel could have an additional ~73kib (Matt previously said his entire node is ~255kib, cc #4227 (comment)). Also nice to not have the potential for data to be inconsistent, of course, as you previously pointed out.

@joostjager
Copy link
Contributor

Even after the dedup, the total per-HTLC storage for a forwarded-but-unresolved HTLC is still around 600 bytes (Clauded) across both channel sides.

Matt's 255 kb node size also depends heavily on how busy the node is. Not sure if that is a useful baseline number. Also 483 htlcs in flight is an extreme case.

Happy that the potential for inconsistencies has been fixed though!

Copy link
Contributor

@joostjager joostjager left a comment

Choose a reason for hiding this comment

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

Much better with the struct extracted!

@valentinewallace valentinewallace merged commit 5ec66dc into lightningdevkit:main Feb 13, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

weekly goal Someone wants to land this this week

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants