Skip to content

Fix async release before HTLC decode#4548

Open
valentinewallace wants to merge 1 commit intolightningdevkit:mainfrom
valentinewallace:2026-04-async-held-htlc-ordering-v2
Open

Fix async release before HTLC decode#4548
valentinewallace wants to merge 1 commit intolightningdevkit:mainfrom
valentinewallace:2026-04-async-held-htlc-ordering-v2

Conversation

@valentinewallace
Copy link
Copy Markdown
Contributor

Handle ReleaseHeldHtlc messages that arrive before the sender-side LSP has even queued the held HTLC for onion decoding. Unlike #4106, which covers releases arriving after the HTLC is in decode_update_add_htlcs but before it reaches pending_intercepted_htlcs, this preserves releases that arrive one step earlier and would otherwise be dropped as HTLC not found.

Supersedes #4542

Handle `ReleaseHeldHtlc` messages that arrive before the sender-side LSP
has even queued the held HTLC for onion decoding. Unlike lightningdevkit#4106, which
covers releases arriving after the HTLC is in `decode_update_add_htlcs`
but before it reaches `pending_intercepted_htlcs`, this preserves
releases that arrive one step earlier and would otherwise be dropped as
HTLC not found.

Co-Authored-By: HAL 9000
Co-Authored-By: Elias Rohrer <dev@tnull.de>
@valentinewallace valentinewallace added this to the 0.3 milestone Apr 8, 2026
@valentinewallace valentinewallace self-assigned this Apr 8, 2026
@valentinewallace valentinewallace added the weekly goal Someone wants to land this this week label Apr 8, 2026
@ldk-reviews-bot
Copy link
Copy Markdown

ldk-reviews-bot commented Apr 8, 2026

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

Comment on lines +7532 to 7534
} else {
htlc_forwards.push(pending_add);
}
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.

When released_in_transit is true, the HTLC is pushed directly to htlc_forwards, bypassing the intercept_forward check at line 7535. In contrast, the existing release-from-pending_intercepted_htlcs path (lines 17316-17356) does check whether the forward should be intercepted (forward_needs_intercept_to_known_chan) before forwarding.

This means a held HTLC released early (before entering the decode pipeline) will skip user-level interception, while one released later (from pending_intercepted_htlcs) will be checked for interception. Should the else branch here also consider intercept_forward?

Suggested change
} else {
htlc_forwards.push(pending_add);
}
} else if intercept_forward {
let intercept_id = intercept_id();
let mut pending_intercepts =
self.pending_intercepted_htlcs.lock().unwrap();
match pending_intercepts.entry(intercept_id) {
hash_map::Entry::Vacant(entry) => {
if let Ok(intercept_ev) =
create_htlc_intercepted_event(intercept_id, &pending_add)
{
log_debug!(
logger,
"Intercepting released held HTLC with ID {intercept_id}"
);
let ev_entry = (intercept_ev, None);
self.pending_events
.lock()
.unwrap()
.push_back(ev_entry);
entry.insert(pending_add);
}
},
hash_map::Entry::Occupied(_) => {
debug_assert!(false, "Should never have two HTLCs with the same channel id and htlc id");
},
}
} else {
htlc_forwards.push(pending_add);
}

@ldk-claude-review-bot
Copy link
Copy Markdown
Collaborator

Review Summary

After a thorough review of all files and hunks in this PR, I found one issue:

Inline comments posted:

  • lightning/src/ln/channelmanager.rs:7532-7534 — Released-in-transit held HTLCs bypass the intercept_forward check that the existing release-from-pending_intercepted_htlcs path performs (lines 17316-17356). When released_in_transit is true, the HTLC goes directly to htlc_forwards, skipping user-level interception. This is a behavioral inconsistency between the two release paths.

Overall assessment:

The core logic for the new release_pending_inbound_held_htlc / is_inbound_htlc_released mechanism is correct. The three-stage check (monitor_pending_update_adds → pre-Committed states → Committed/Signaled) properly covers the race window where ReleaseHeldHtlc arrives before the HTLC enters the decode pipeline. The Released vs Signaled distinction correctly differentiates between pre-clone and post-clone states. The test exercises the exact race condition being fixed.

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

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants