From cf7fa74c2ca8001696b326c4405ff5678c4c1d33 Mon Sep 17 00:00:00 2001 From: Ken Sedgwick Date: Thu, 23 Jul 2026 13:09:21 -0700 Subject: [PATCH] tests: fix flaky test_onchain_rbf_stops_after_confirmation The test censors l2's sendrawtransaction with an rpcproxy mock while the node RBFs its penalty tx three times, then removes the mock so the next RBF version reaches bitcoind for real and gets mined. But each 'RBF onchain txid' log line precedes the corresponding broadcast, so wait_for_log can return -- and the test un-mock -- while the third replacement is still in flight. That older version then lands in bitcoind's real mempool, the next version's broadcast is rejected as a conflict, and the block mines a version the node no longer tracks, so RBF-ing continues after confirmation and the final assertion fails. Seen under valgrind, where the window between log line and broadcast is wide. Count the broadcasts the censoring mock swallows and wait for all four (initial penalty tx plus three replacements) to have reached the proxy before un-mocking, so no broadcast can be in flight when censoring stops. Fixes: #9347 Changelog-None --- tests/test_closing.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/test_closing.py b/tests/test_closing.py index 97690f53447b..1f9be80c990b 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -1745,8 +1745,13 @@ def test_onchain_rbf_stops_after_confirmation(node_factory, bitcoind): l1.pay(l2, 1000000) l1.rpc.stop() - # Censor l2 so the penalty tx never reaches miners. + # Censor l2 so the penalty tx never reaches miners. Count the swallowed + # broadcasts: each 'RBF onchain txid' log line precedes the corresponding + # broadcast, so un-mocking is only safe once none is in flight. + censored = [] + def censoring_sendrawtx(r): + censored.append(r) return {'id': r['id'], 'result': {}} l2.daemon.rpcproxy.mock_rpc('sendrawtransaction', censoring_sendrawtx) @@ -1765,8 +1770,13 @@ def censoring_sendrawtx(r): bitcoind.generate_block(1) l2.daemon.wait_for_log('RBF onchain txid') - # Stop censoring. Generate a block to trigger rebroadcast (the penalty tx - # enters bitcoind's mempool) but filter it out so the next block mines it. + # Stop censoring — but only once all four censored broadcasts (initial + # penalty tx plus three replacements) have reached the proxy: un-mocking + # while one is in flight lets an old version into bitcoind's real mempool, + # where it gets mined in place of the version the node tracks (#9347). + # Then generate a block to trigger rebroadcast (the penalty tx enters + # bitcoind's mempool) but filter it out so the next block mines it. + wait_for(lambda: len(censored) >= 4) l2.daemon.rpcproxy.mock_rpc('sendrawtransaction', None) bitcoind.generate_block(1, needfeerate=10000000) l2.daemon.wait_for_log('RBF onchain txid')