Skip to content

Commit 5ec99ea

Browse files
pytest: update tests for fulfilled HTLC no-force-close behavior
Update test_htlc_no_force_close and test_htlc_in_timeout to work with the new behavior where nodes don't force-close when a fulfilled HTLC hits the deadline but removal is already in progress. test_htlc_no_force_close: l3 no longer force-closes for the fulfilled HTLC — it logs a warning instead. l2 still force-closes for the offered HTLC timeout. Add dev-no-reconnect to prevent l2/l3 from reconnecting and resolving normally (which would skip the on-chain scenario the test exercises). test_htlc_in_timeout: l2 no longer force-closes for the fulfilled HTLC. Instead, l1 force-closes when the offered HTLC hits its deadline (cltv_expiry + 1), and l2 claims on-chain using the preimage from l1's unilateral close (THEIR_UNILATERAL/THEIR_HTLC instead of OUR_UNILATERAL/THEIR_HTLC). Add dev-no-reconnect to l2 to prevent automatic reconnection. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 066b3c7 commit 5ec99ea

File tree

2 files changed

+38
-13
lines changed

2 files changed

+38
-13
lines changed

tests/test_closing.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3887,7 +3887,16 @@ def test_closing_anchorspend_htlc_tx_rbf(node_factory, bitcoind):
38873887
@pytest.mark.parametrize("anchors", [False, True])
38883888
def test_htlc_no_force_close(node_factory, bitcoind, anchors):
38893889
"""l2<->l3 force closes while an HTLC is in flight from l1, but l2 can't timeout because the feerate has spiked. It should do so anyway."""
3890-
opts = [{}, {}, {'disconnect': ['-WIRE_UPDATE_FULFILL_HTLC']}]
3890+
# l3 disconnects before sending update_fulfill_htlc to l2, and
3891+
# dev-no-reconnect prevents automatic reconnection. This leaves
3892+
# l3's incoming HTLC in SENT_REMOVE_HTLC: l3 has the preimage but
3893+
# can't deliver it. l3 no longer force-closes for this (the
3894+
# preimage is safe in the DB and onchaind will claim on-chain),
3895+
# so l2 force-closes first for the offered HTLC timeout.
3896+
opts = [{'dev-no-reconnect': None},
3897+
{'dev-no-reconnect': None},
3898+
{'disconnect': ['-WIRE_UPDATE_FULFILL_HTLC'],
3899+
'dev-no-reconnect': None}]
38913900
if anchors is False:
38923901
for opt in opts:
38933902
opt['dev-force-features'] = "-23"
@@ -3911,17 +3920,20 @@ def test_htlc_no_force_close(node_factory, bitcoind, anchors):
39113920

39123921
htlc_txs = []
39133922

3914-
# l3 drops to chain, holding htlc (but we stop it xmitting txs)
3923+
# l3 won't force-close (removal in progress), so censor its
3924+
# onchaind txs for when l2 goes on-chain and l3 sees the
3925+
# commitment.
39153926
def censoring_sendrawtx(r):
39163927
htlc_txs.append(r['params'][0])
39173928
return {'id': r['id'], 'result': {}}
39183929

39193930
l3.daemon.rpcproxy.mock_rpc('sendrawtransaction', censoring_sendrawtx)
39203931

3921-
# l3 gets upset, drops to chain when there are < 4 blocks remaining.
3922-
# But tx doesn't get mined...
3932+
# l3 should NOT force-close: it has the preimage and the removal is
3933+
# already in progress. It just logs a warning.
39233934
bitcoind.generate_block(8)
3924-
l3.daemon.wait_for_log("Peer permanent failure in CHANNELD_NORMAL: Fulfilled HTLC 0 SENT_REMOVE_.* cltv 119 hit deadline")
3935+
l3.daemon.wait_for_log(r'but removal already in progress')
3936+
assert not l3.daemon.is_in_log(r'Peer permanent failure in CHANNELD_NORMAL: Fulfilled HTLC 0 SENT_REMOVE')
39253937

39263938
# l2 closes drops the commitment tx at block 115 (one block after timeout)
39273939
bitcoind.generate_block(4)

tests/test_misc.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -521,13 +521,18 @@ def test_htlc_out_timeout(node_factory, bitcoind, executor):
521521
def test_htlc_in_timeout(node_factory, bitcoind, executor):
522522
"""Test that we drop onchain if the peer doesn't accept fulfilled HTLC"""
523523

524-
# HTLC 1->2, 1 fails after 2 has sent committed the fulfill
524+
# HTLC 1->2, 1 fails after 2 has sent committed the fulfill.
525+
# l2 has the preimage but can't deliver it (l1 disconnected and
526+
# won't reconnect). l2 no longer force-closes for this (removal
527+
# is in progress, preimage safe in DB). Instead l1 force-closes
528+
# when the offered HTLC hits its deadline, and l2 claims on-chain
529+
# using the preimage via onchaind.
525530
disconnects = ['-WIRE_REVOKE_AND_ACK*2']
526531
# Feerates identical so we don't get gratuitous commit to update them
527532
l1 = node_factory.get_node(disconnect=disconnects,
528533
options={'dev-no-reconnect': None},
529534
feerates=(7500, 7500, 7500, 7500))
530-
l2 = node_factory.get_node()
535+
l2 = node_factory.get_node(options={'dev-no-reconnect': None})
531536
# Give it some sats for anchor spend!
532537
l2.fundwallet(25000, mine_block=False)
533538

@@ -556,15 +561,23 @@ def test_htlc_in_timeout(node_factory, bitcoind, executor):
556561
assert not l2.daemon.is_in_log('hit deadline')
557562
bitcoind.generate_block(1)
558563

559-
l2.daemon.wait_for_log('Fulfilled HTLC 0 SENT_REMOVE_COMMIT cltv .* hit deadline')
560-
l2.daemon.wait_for_log('sendrawtx exit 0')
561-
l2.bitcoin.generate_block(1)
562-
l2.daemon.wait_for_log(' to ONCHAIN')
564+
# l2 has the preimage but removal is in progress — it should NOT
565+
# force-close. It logs a warning instead.
566+
l2.daemon.wait_for_log(r'but removal already in progress')
567+
assert not l2.daemon.is_in_log(r'Peer permanent failure in CHANNELD_NORMAL: Fulfilled HTLC')
568+
569+
# l1's offered HTLC hits deadline (cltv_expiry + 1), which is a
570+
# few blocks after l2's fulfilled deadline. l1 force-closes.
571+
bitcoind.generate_block(4 + shadowlen)
572+
l1.daemon.wait_for_log('Offered HTLC 0 SENT_ADD_ACK_REVOCATION cltv .* hit deadline')
573+
l1.daemon.wait_for_log('sendrawtx exit 0')
574+
l1.bitcoin.generate_block(1)
563575
l1.daemon.wait_for_log(' to ONCHAIN')
576+
l2.daemon.wait_for_log(' to ONCHAIN')
564577

565-
# L2 will collect HTLC (iff no shadow route)
578+
# L2 will collect HTLC using the preimage (from l1's unilateral)
566579
_, txid, blocks = l2.wait_for_onchaind_tx('OUR_HTLC_SUCCESS_TX',
567-
'OUR_UNILATERAL/THEIR_HTLC')
580+
'THEIR_UNILATERAL/THEIR_HTLC')
568581
assert blocks == 0
569582

570583
# If we try to reuse the same output as we used for the anchor spend, then

0 commit comments

Comments
 (0)