@@ -3957,6 +3957,82 @@ def censoring_sendrawtx(r):
39573957 # FIXME: l2 should complain!
39583958
39593959
3960+ @unittest .skipIf (TEST_NETWORK != 'regtest' , 'elementsd anchors unsupported' )
3961+ def test_fulfilled_htlc_deadline_no_force_close (node_factory , bitcoind ):
3962+ """Test that l2 does not force-close when fulfilled HTLC is in
3963+ SENT_REMOVE_HTLC state (preimage known, fulfill queued to channeld
3964+ but not yet sent to upstream peer).
3965+
3966+ Reproduces https://github.com/ElementsProject/lightning/issues/8899:
3967+ CLN force-closed with 'Fulfilled HTLC SENT_REMOVE_HTLC cltv hit deadline'
3968+ without attempting to send update_fulfill_htlc upstream first.
3969+ """
3970+ # l1 -> l2 -> l3 topology.
3971+ # l2 disconnects from l1 right before sending update_fulfill_htlc,
3972+ # so the incoming HTLC on l1-l2 stays in SENT_REMOVE_HTLC.
3973+ # l2 cannot reconnect (dev-no-reconnect), simulating the scenario where
3974+ # the upstream peer appears connected but isn't processing messages.
3975+ #
3976+ # Use identical feerates to avoid gratuitous commits to update them.
3977+ opts = [{'dev-no-reconnect' : None ,
3978+ 'feerates' : (7500 , 7500 , 7500 , 7500 )},
3979+ {'disconnect' : ['-WIRE_UPDATE_FULFILL_HTLC' ],
3980+ 'dev-no-reconnect' : None ,
3981+ 'feerates' : (7500 , 7500 , 7500 , 7500 )},
3982+ {'feerates' : (7500 , 7500 , 7500 , 7500 )}]
3983+
3984+ l1 , l2 , l3 = node_factory .line_graph (3 , opts = opts , wait_for_announce = True )
3985+
3986+ amt = 12300000
3987+ inv = l3 .rpc .invoice (amt , 'test_fulfilled_deadline' , 'desc' )
3988+
3989+ # Use explicit route with known delays to have predictable cltv_expiry.
3990+ # delay=16 for first hop (cltv_delta=6 + cltv_final=10),
3991+ # delay=10 for second hop (cltv_final=10).
3992+ route = [{'amount_msat' : amt + 1 + amt * 10 // 1000000 ,
3993+ 'id' : l2 .info ['id' ],
3994+ 'delay' : 16 ,
3995+ 'channel' : first_scid (l1 , l2 )},
3996+ {'amount_msat' : amt ,
3997+ 'id' : l3 .info ['id' ],
3998+ 'delay' : 10 ,
3999+ 'channel' : first_scid (l2 , l3 )}]
4000+ l1 .rpc .sendpay (route , inv ['payment_hash' ],
4001+ payment_secret = inv ['payment_secret' ])
4002+
4003+ # l3 fulfills the HTLC, preimage flows back to l2.
4004+ # l2 transitions the incoming HTLC (from l1) to SENT_REMOVE_HTLC,
4005+ # then tries to send update_fulfill_htlc to l1 but disconnects.
4006+ l2 .daemon .wait_for_log ('dev_disconnect: -WIRE_UPDATE_FULFILL_HTLC' )
4007+
4008+ # Verify the HTLC is stuck: l2 has the preimage but can't send it
4009+ # to l1 because of the disconnect.
4010+ wait_for (lambda : only_one (l2 .rpc .listpeerchannels (l1 .info ['id' ])['channels' ])['htlcs' ] != [])
4011+
4012+ # Now mine blocks up to the deadline.
4013+ # Default regtest cltv_expiry_delta=6, so deadline = cltv_expiry - (6+1)/2 = cltv_expiry - 3.
4014+ # With delay=16 on first hop, cltv_expiry should be 119 (starting height ~103 + 16).
4015+ # Deadline = 119 - 3 = 116. We're at ~108 after mine_funding_to_announce,
4016+ # so we need ~8 blocks to hit it.
4017+ #
4018+ # BUG: l2 will force-close with "Fulfilled HTLC 0 SENT_REMOVE_HTLC cltv ... hit deadline"
4019+ # even though it has the preimage and just needs to reconnect to send it upstream.
4020+ # The correct behavior would be to NOT force-close when the HTLC is in
4021+ # SENT_REMOVE_HTLC (preimage known, fulfill already queued to channeld).
4022+ bitcoind .generate_block (7 )
4023+ sync_blockheight (bitcoind , [l2 ])
4024+ assert not l2 .daemon .is_in_log ('hit deadline' )
4025+
4026+ bitcoind .generate_block (1 )
4027+ sync_blockheight (bitcoind , [l2 ])
4028+
4029+ # This is the bug: l2 force-closes with SENT_REMOVE_HTLC.
4030+ # After a fix, this line should be changed to assert the force-close
4031+ # does NOT happen:
4032+ # assert not l2.daemon.is_in_log('Fulfilled HTLC 0 SENT_REMOVE_HTLC')
4033+ l2 .daemon .wait_for_log ('Fulfilled HTLC 0 SENT_REMOVE_HTLC cltv .* hit deadline' )
4034+
4035+
39604036def test_closing_tx_valid (node_factory , bitcoind ):
39614037 l1 , l2 = node_factory .line_graph (2 , opts = {'may_reconnect' : True ,
39624038 'dev-no-reconnect' : None })
0 commit comments