Uh oh!
There was an error while loading. Please reload this page.
lightningd: don't force-close when fulfilled HTLC removal is in progress - #8940
Conversation
d6d8693 to
e316362Compare…e force-close Add test_fulfilled_htlc_deadline_no_force_close to reproduce the bug where CLN force-closes a channel with "Fulfilled HTLC SENT_REMOVE_HTLC cltv hit deadline" even though it has the preimage and just needs to reconnect to send update_fulfill_htlc upstream. The test sets up l1->l2->l3, sends a payment, and disconnects l2 from l1 right before update_fulfill_htlc is sent (-WIRE_UPDATE_FULFILL_HTLC). This leaves the incoming HTLC on the l1-l2 channel stuck in SENT_REMOVE_HTLC (or SENT_REMOVE_COMMIT under Valgrind). Mining blocks to the deadline triggers the buggy force-close. Reproduces: ElementsProject/lightning#8899 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3ad64fb to
cbcc032Compare4cf3776 to
066b3c7CompareWhen an incoming HTLC has been fulfilled (preimage known) and is in SENT_REMOVE_HTLC or later state, the removal is already in progress: channeld has been told to send update_fulfill_htlc upstream (or will be told on reconnect). Force-closing the channel is counterproductive because: 1. The preimage is persisted to DB and onchaind can claim on-chain 2. The cooperative path (reconnect + fulfill) is cheaper and faster 3. If the peer goes on-chain themselves, onchaind handles it Instead of force-closing, log a warning and let the removal complete through the normal state machine. CI run of the reproducer test (cbcc032) confirms the bug exists. Before this fix, the force-close fires: lightningd: Peer permanent failure in CHANNELD_NORMAL: Fulfilled HTLC 0 SENT_REMOVE_HTLC cltv 119 hit deadline After this fix, the force-close is skipped and a warning is logged: lightningd: UNUSUAL: Fulfilled HTLC 0 SENT_REMOVE_HTLC cltv 119 hit deadline, but removal already in progress Also updates test_htlc_no_force_close and test_htlc_in_timeout which depended on the old force-close behavior: l3/l2 no longer force-close for fulfilled HTLCs past deadline; instead the offering peer (l2/l1) force-closes for the offered HTLC timeout, and the fulfilling node claims on-chain via onchaind using the preimage. Changelog-Fixed: Don't force-close channel when fulfilled HTLC hits deadline but removal is already in progress. Fixes: ElementsProject/lightning#8899 Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5ec99ea to
c95850fCompareWhen a BOLT 12 invoice is created via createinvoice and is associated with a local offer, include the offer_id in the invoice_creation event notification. This allows plugins to determine which offer triggered an invoice creation without having to call listinvoices for every created invoice. For bolt11 invoices and bolt12 invoices not associated with a local offer, the field is omitted. Fixes #8191 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ting test_fulfilled_htlc_deadline_no_force_close
| * on-chain, onchaind will claim using the | ||
| * preimage. | ||
| * See https://github.com/ElementsProject/lightning/issues/8899 */ | ||
| if (hin->hstate >= SENT_REMOVE_HTLC) { |
There was a problem hiding this comment.
Isnt the new guard hin->hstate >= SENT_REMOVE_HTLC is unconditionally true at the point it's checked, so channel_fail_permanent() for a fulfilled HTLC becomes dead code - not just for the #8899 race, but for every case?
I mean, from what i see on master:
htlcs_notify_new_block()already requireshin->preimage != NULLto reach this code (lightningd/peer_htlcs.c at line 2998 -if (!hin->preimage) continue;)hin->preimageis set in exactly one place,fulfill_htlc()(lightningd/peer_htlcs.c at line 397), and the very next line unconditionally advances state -htlc_in_update_state(channel, hin, SENT_REMOVE_HTLC)(line 400), with the comment "We update state now to signal it's in progress, for persistence." No other call site assignshin->preimage(the only other match, htlc_end.c at line 158, sets it to NULL).
Sohin->preimage != NULLimplieshin->hstate >= SENT_REMOVE_HTLCalways? The newifblock will always be taken, and the originalchannel_fail_permanent("Fulfilled HTLC /// hit deadline")can never fire again
| @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd anchors unsupported') | ||
| def test_fulfilled_htlc_deadline_no_force_close(node_factory, bitcoind): |
There was a problem hiding this comment.
And after the forst comment, test_fulfilled_htlc_deadline_no_force_close and test_fulfilled_htlc_deadline_reconnect don't actually test "removal in progress -> skip" vs. "removal stuck -> still force-close". Once comment is fixed with a real distinguishing condition, these tests should be extended to also cover the case where the peer never reconnects and the channel should still eventually force-close
Summary
Fixes#8899: CLN force-closes a channel with
Fulfilled HTLC SENT_REMOVE_HTLC cltv hit deadlineeven though the node has the preimage and the fulfill is already in progress.The bug
In
htlcs_notify_new_block(), when a fulfilled incoming HTLC hits the cltv deadline, CLN force-closes the channel without checking whether the HTLC removal is already in progress. The HTLC state machine shows the fulfill was queued to channeld (SENT_REMOVE_HTLC), but the force-close fires before the upstream peer receivesupdate_fulfill_htlc.This was reported in the wild: the upstream peer was connected and exchanging pings, but CLN force-closed instead of sending the fulfill message.
The fix
When the HTLC is in
SENT_REMOVE_HTLCor later state (>= SENT_REMOVE_HTLC), skip the force-close and log a warning instead. This is safe because:onchaindcan claim on-chain if neededupdate_fulfill_htlconchaindhandles it with the known preimageCommits
Fulfilled HTLC $ID SENT_REMOVE_HTLC cltv $CLTV hit deadlinewithout attempt to claim #8899 — Test that triggers the buggy force-close (l2 disconnects from l1 before sendingupdate_fulfill_htlc, then mining blocks to the deadline)hin->hstate >= SENT_REMOVE_HTLC, loglog_unusualinsteadChangelog-Fixed: Don't force-close channel when fulfilled HTLC hits deadline but removal is already in progress.
Test plan
test_fulfilled_htlc_deadline_no_force_closereproduces the bug (first commit)🤖 Generated with Claude Code