Skip to content

lightningd: don't force-close when fulfilled HTLC removal is in progress - #9431

Open
vincenzopalazzo wants to merge 2 commits into
ElementsProject:masterfrom
vincenzopalazzo:test/reproduce-issue-8899-fulfilled-htlc-deadline
Open

lightningd: don't force-close when fulfilled HTLC removal is in progress#9431
vincenzopalazzo wants to merge 2 commits into
ElementsProject:masterfrom
vincenzopalazzo:test/reproduce-issue-8899-fulfilled-htlc-deadline

Conversation

@vincenzopalazzo

@vincenzopalazzo vincenzopalazzo commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

Fixes #8899.

Two commits, red then green: the first adds reproducer tests (they fail
on master with the force-close below), the second stops the force-close
and adapts the two existing tests which relied on it.

The bug

CLN force-closes the channel when a fulfilled incoming HTLC hits its
deadline, even though removal is already in progress:

lightningd: Peer permanent failure in CHANNELD_NORMAL:
  Fulfilled HTLC 0 SENT_REMOVE_HTLC cltv 119 hit deadline

But if the HTLC has reached SENT_REMOVE_HTLC (or later), channeld has
been told to send update_fulfill_htlc upstream, or will be on reconnect:
the preimage is in the DB, so onchaind can always claim on-chain, the
cooperative path is cheaper and faster, and if the peer goes on-chain,
onchaind handles it. Force-closing just burns fees.

After the fix we log an UNUSUAL and let the normal state machine finish
the removal:

lightningd: UNUSUAL: Fulfilled HTLC 0 SENT_REMOVE_HTLC cltv 119
  hit deadline, but removal already in progress

The tests

  • test_fulfilled_htlc_deadline_no_force_close (reproducer): l1->l2->l3
    line graph, pay, disconnect l2 from l1 right before update_fulfill_htlc
    is sent (-WIRE_UPDATE_FULFILL_HTLC), so the incoming HTLC is stuck in
    SENT_REMOVE_HTLC; mining to the deadline used to force-close.
  • test_fulfilled_htlc_deadline_reconnect: same, but l2 reconnects after
    the deadline fires and completes the removal cooperatively.
  • test_htlc_no_force_close / test_htlc_in_timeout are adapted: the
    fulfilling peer no longer force-closes, the offering peer does, and
    onchaind claims via the preimage.

(Also dropped an unrelated event-notifications.md doc blob that had
accidentally landed on this branch.)

@vincenzopalazzo

Copy link
Copy Markdown
Collaborator Author

Hi @daywalker90 👋 — this replaces #9408 (identical commits) and fixes #8899: nodes force-closing healthy channels on the fulfilled-HTLC removal deadline race (real-world logs in the issue from a BTCPay node). This is the most release-critical of my three v26.09 PRs — could you take a look before the RC? #9427 and #9428 are the other two.

vincenzopalazzo added a commit to vincenzopalazzo/lightning that referenced this pull request Aug 17, 2026
Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
vincenzopalazzo added a commit to vincenzopalazzo/lightning that referenced this pull request Aug 17, 2026
Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
@vincenzopalazzo
vincenzopalazzo force-pushed the test/reproduce-issue-8899-fulfilled-htlc-deadline branch 3 times, most recently from d5360be to 0d1bd4a Compare August 18, 2026 08:37
CLN force-closes with "Fulfilled HTLC SENT_REMOVE_HTLC cltv hit
deadline" even though it holds the preimage and just needs to
reconnect to send update_fulfill_htlc upstream.

test_fulfilled_htlc_deadline_no_force_close sets up l1->l2->l3,
pays, then disconnects l2 from l1 right before update_fulfill_htlc
is sent (-WIRE_UPDATE_FULFILL_HTLC): the incoming HTLC is left
stuck in SENT_REMOVE_HTLC, and mining to the deadline triggers the
force-close.

test_fulfilled_htlc_deadline_reconnect covers the edge case where
the deadline fires while removal is pending and l2 reconnects to
finish the job.

These fail until the next commit.

Reproduces: ElementsProject#8899
Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
Once an incoming HTLC is fulfilled and has reached
SENT_REMOVE_HTLC or later, removal is already in progress: channeld
has been told to send update_fulfill_htlc upstream, or will be on
reconnect.  Force-closing here is counterproductive:

1. the preimage is in the DB, so onchaind can claim on-chain anyway,
2. the cooperative path (reconnect + fulfill) is cheaper and faster,
3. if the peer goes on-chain, onchaind handles it.

So log an UNUSUAL and let the normal state machine finish the
removal.

This changes test_htlc_no_force_close and test_htlc_in_timeout,
which depended on the old force-close: the fulfilling peer no
longer force-closes, the offering peer does, and onchaind claims
via the preimage.  With this, the tests from the previous commit
pass.

Fixes: ElementsProject#8899
Changelog-Fixed: lightningd: don't force-close a channel when removal of a fulfilled HTLC is already in progress
Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
@vincenzopalazzo
vincenzopalazzo force-pushed the test/reproduce-issue-8899-fulfilled-htlc-deadline branch from 0d1bd4a to 3ad7f7e Compare August 18, 2026 08:58
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.

FC due to Fulfilled HTLC $ID SENT_REMOVE_HTLC cltv $CLTV hit deadline without attempt to claim

1 participant