Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 480
Don't persist inbound committed onions in prod#4599
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
TheBlueMatt
merged 1 commit into
lightningdevkit:main
from
valentinewallace:2026-05-no-persist-onions-yetMay 19, 2026
Uh oh!
There was an error while loading. Please reload this page.
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -17600,16 +17600,16 @@ pub fn provided_init_features(config: &UserConfig) -> InitFeatures { | ||
| const SERIALIZATION_VERSION: u8 = 1; | ||
| const MIN_SERIALIZATION_VERSION: u8 = 1; | ||
| // We plan to start writing this version in 0.5. | ||
| // We plan to start writing this version a few versions after we start writing inbound committed | ||
joostjager marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| // payment onions in `Channel`, which is already done in tests but not yet switched on in prod. | ||
| // | ||
| // LDK 0.5+ will reconstruct the set of pending HTLCs from `Channel{Monitor}` data that started | ||
| // being written in 0.3, ignoring legacy `ChannelManager` HTLC maps on read and not writing them. | ||
| // LDK 0.5+ will automatically fail to read if the pending HTLC set cannot be reconstructed, i.e. | ||
| // if we were last written with pending HTLCs on 0.2- or if the new 0.3+ fields are missing. | ||
| // If we see this version on read, we will use said onions when reconstructing the set of pending | ||
| // HTLCs, ignoring legacy `ChannelManager` HTLC maps on read and not writing them. We'll also | ||
| // automatically fail to read if the pending HTLC set cannot be reconstructed, i.e. if the new | ||
| // payment onion field is missing. | ||
| // | ||
| // If 0.3 or 0.4 reads this manager version, it knows that the legacy maps were not written and | ||
| // acts accordingly. | ||
| const RECONSTRUCT_HTLCS_FROM_CHANS_VERSION: u8 = 2; | ||
| // Left as `None` for now until we are committed to writing inbound committed onions in `Channel`s. | ||
| const RECONSTRUCT_HTLCS_FROM_CHANS_VERSION: Option<u8> = None; | ||
| impl_writeable_tlv_based!(PhantomRouteHints, { | ||
| (2, channels, required_vec), | ||
| @@ -18435,7 +18435,7 @@ impl<'a, ES: EntropySource, SP: SignerProvider, L: Logger> | ||
| } | ||
| let forward_htlcs_legacy: HashMap<u64, Vec<HTLCForwardInfo>> = | ||
| if version < RECONSTRUCT_HTLCS_FROM_CHANS_VERSION { | ||
| if RECONSTRUCT_HTLCS_FROM_CHANS_VERSION.map_or(true, |v| version < v) { | ||
joostjager marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| let forward_htlcs_count: u64 = Readable::read(reader)?; | ||
| let mut fwds = hash_map_with_capacity(cmp::min(forward_htlcs_count as usize, 128)); | ||
| for _ in 0..forward_htlcs_count { | ||
| @@ -19573,7 +19573,8 @@ impl< | ||
| // `reconstruct_manager_from_monitors` is set below. Currently we set in tests randomly to | ||
| // ensure the legacy codepaths also have test coverage. | ||
| #[cfg(not(test))] | ||
| let reconstruct_manager_from_monitors = _version >= RECONSTRUCT_HTLCS_FROM_CHANS_VERSION; | ||
| let reconstruct_manager_from_monitors = | ||
| RECONSTRUCT_HTLCS_FROM_CHANS_VERSION.is_some_and(|v| _version >= v); | ||
| #[cfg(test)] | ||
| let reconstruct_manager_from_monitors = | ||
| args.reconstruct_manager_from_monitors.unwrap_or_else(|| { | ||
| @@ -19636,7 +19637,6 @@ impl< | ||
| if reconstruct_manager_from_monitors { | ||
| if let Some(chan) = peer_state.channel_by_id.get(channel_id) { | ||
| if let Some(funded_chan) = chan.as_funded() { | ||
| // Legacy HTLCs are from pre-LDK 0.3 and cannot be reconstructed. | ||
| if funded_chan.has_legacy_inbound_htlcs() { | ||
| return Err(DecodeError::InvalidValue); | ||
| } | ||
Oops, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: The write side is now gated by
#[cfg(test)], so in productioninbound_committed_update_addsremains empty and TLV field 75 is never written. However, on the read side (line 16554),inbound_committed_update_adds_optis initialized toSome(Vec::new())by theoptional_vecmacro (when the TLV is absent, it stays asSome(empty_vec)). The deserialization code at line 16554-16564 then enters theif let Some(update_adds)block unconditionally and callsiter.next().ok_or(DecodeError::InvalidValue)?for each committed HTLC — which immediately fails because the iterator is empty.Impact: Any production node that restarts while it has committed inbound HTLCs will fail to deserialize its channel state with
DecodeError::InvalidValue. This is a critical availability bug.Fix: The deserialization code at line 16554 needs a corresponding guard. For example:
Or gate the entire read-side block with
#[cfg(test)]to match the write side.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned in claude's comment below, this was a false positive. We have test coverage of the case it was outlining as well, in
upgrade_downgradetests.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't actually think so?
cfg(test)isn't set inlightning-testsbecauselightningis technically being compiled as a dependency there. I canassert!(inbound_committed_update_adds.is_empty())andlightning-testsstill passes.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be visible in the coverage report too with the
testsflag enabled?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify, I meant that if the prod panic bug claude was pointing out was actually present, it would've been hit in
lightning-testsbecause of exactly what you say (cfg(test)isn't set there). It's (to me at least) expected behavior thatassert!(inbound_committed_update_adds.is_empty())would pass inlightning-testsw/ the changes in this PRThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My assumption was that
do_upgrade_mid_htlc_forwardrelied on the now-test-only code but I guess its really an upgrade test not a downgrade test...it does seem like its a dead test though asreconstruct_manager_from_monitorswill always befalse? Is it just intended to run once the code is in prod?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's all correct. I did update the test's comment to say it was a "preemptive" test. If I rebased #4359 on top of this, it would use it.
I could remove it, but I was thinking we could just delete everything related to inbound onion persistence (or not) at the same time, rather than deleting some stuff and possibly having to remember to re-add it later.