@@ -2930,67 +2930,3 @@ def mock_fail_sendrawtx(r):
29302930 bitcoind .generate_block (1 )
29312931 sync_blockheight (bitcoind , [l1 ])
29322932 assert l1 .db_query ('SELECT COUNT(*) as c FROM outputs WHERE status=0' )[0 ]['c' ] == 0
2933-
2934-
2935- @unittest .skipIf (TEST_NETWORK != 'regtest' , "Uses regtest-specific address types" )
2936- def test_withdraw_stuck_reserved_on_broadcast_failure (node_factory , bitcoind ):
2937- """Test funds don't get stuck as reserved after withdraw fails due to
2938- broadcast rejection (e.g. feerate below mempoolminfee).
2939-
2940- """
2941- l1 = node_factory .get_node (random_hsm = True )
2942- addr = l1 .rpc .newaddr ('p2tr' )['p2tr' ]
2943-
2944- # Fund the node
2945- bitcoind .rpc .sendtoaddress (addr , 0.01 )
2946- bitcoind .generate_block (1 )
2947- wait_for (lambda : len (l1 .rpc .listfunds ()['outputs' ]) == 1 )
2948-
2949- output = only_one (l1 .rpc .listfunds ()['outputs' ])
2950- assert output ['status' ] == 'confirmed'
2951- assert not output .get ('reserved' , False )
2952-
2953- waddr = bitcoind .rpc .getnewaddress ()
2954-
2955- # Mock sendrawtransaction to simulate bitcoind rejecting the transaction
2956- # because the feerate is below its mempoolminfee
2957- def mock_fail_sendrawtx (r ):
2958- # Self-remove after first call so subsequent transactions aren't blocked
2959- l1 .daemon .rpcproxy .mock_rpc ('sendrawtransaction' , None )
2960- return {
2961- 'id' : r ['id' ],
2962- 'error' : {
2963- 'code' : - 26 ,
2964- 'message' : 'min relay fee not met, 253 < 5000' ,
2965- },
2966- 'result' : None ,
2967- }
2968-
2969- l1 .daemon .rpcproxy .mock_rpc ('sendrawtransaction' , mock_fail_sendrawtx )
2970-
2971- with pytest .raises (RpcError , match = r'Error broadcasting transaction' ):
2972- l1 .rpc .withdraw (waddr , 'all' )
2973-
2974- # BUG: UTXOs remain reserved despite the failed broadcast.
2975- # sendpsbt_done correctly unreserves the reservation it added (72 blocks),
2976- # but fundpsbt's prior reservation (72 blocks) is NOT cleaned up.
2977- outputs = l1 .rpc .listfunds ()['outputs' ]
2978- reserved = [o for o in outputs if o .get ('reserved' , False )]
2979- assert len (reserved ) > 0 , \
2980- "Expected UTXOs to be reserved after failed broadcast (known bug)"
2981-
2982- with pytest .raises (RpcError , match = r'Could not afford' ):
2983- l1 .rpc .withdraw (waddr , 'all' )
2984-
2985- # Workaround: build a PSBT from the stuck UTXOs and call unreserveinputs.
2986- stuck_utxos = [{'txid' : o ['txid' ], 'vout' : o ['output' ]} for o in reserved ]
2987- psbt = bitcoind .rpc .createpsbt (stuck_utxos , [])
2988- l1 .rpc .unreserveinputs (psbt )
2989-
2990- outputs = l1 .rpc .listfunds ()['outputs' ]
2991- assert not any (o .get ('reserved' , False ) for o in outputs )
2992-
2993- l1 .rpc .withdraw (waddr , 'all' )
2994- bitcoind .generate_block (1 )
2995- sync_blockheight (bitcoind , [l1 ])
2996- assert l1 .db_query ('SELECT COUNT(*) as c FROM outputs WHERE status=0' )[0 ]['c' ] == 0
0 commit comments