2016-12-09 15:45:27 -03:00
#!/usr/bin/env python3
2022-12-24 20:49:50 -03:00
# Copyright (c) 2016-2022 The Bitcoin Core developers
2016-12-09 15:45:27 -03:00
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
2017-03-20 18:17:27 -03:00
""" Test the bumpfee RPC.
Verifies that the bumpfee RPC creates replacement transactions successfully when
its preconditions are met , and returns appropriate errors in other cases .
This module consists of around a dozen individual test cases implemented in the
top - level functions named as test_ < test_case_description > . The test functions
can be disabled or reordered if needed for debugging . If new test cases are
2017-06-19 18:57:31 -04:00
added in the future , they should try to follow the same convention and not
2017-03-20 18:17:27 -03:00
make assumptions about execution order .
"""
2018-07-06 18:10:35 -04:00
from decimal import Decimal
2021-06-15 17:02:28 -04:00
from test_framework . blocktools import (
COINBASE_MATURITY ,
)
from test_framework . messages import (
2022-07-12 11:28:20 -04:00
MAX_BIP125_RBF_SEQUENCE ,
2021-06-15 17:02:28 -04:00
)
2016-12-09 15:45:27 -03:00
from test_framework . test_framework import BitcoinTestFramework
2019-04-06 19:38:51 -03:00
from test_framework . util import (
assert_equal ,
2023-03-17 19:49:42 -03:00
assert_fee_amount ,
2019-04-06 19:38:51 -03:00
assert_greater_than ,
assert_raises_rpc_error ,
2023-03-22 19:47:54 -03:00
get_fee ,
2022-11-07 17:07:51 -03:00
find_vout_for_address ,
2019-04-06 19:38:51 -03:00
)
2021-10-14 21:18:51 -03:00
from test_framework . wallet import MiniWallet
2016-12-09 15:45:27 -03:00
WALLET_PASSPHRASE = " test "
WALLET_PASSPHRASE_TIMEOUT = 3600
2020-11-04 09:13:17 -03:00
# Fee rates (sat/vB)
INSUFFICIENT = 1
ECONOMICAL = 50
NORMAL = 100
HIGH = 500
TOO_HIGH = 100000
2020-03-13 19:11:55 -03:00
2023-03-03 11:59:21 -03:00
def get_change_address ( tx , node ) :
tx_details = node . getrawtransaction ( tx , 1 )
txout_addresses = [ txout [ ' scriptPubKey ' ] [ ' address ' ] for txout in tx_details [ " vout " ] ]
return [ address for address in txout_addresses if node . getaddressinfo ( address ) [ " ischange " ] ]
2020-04-25 13:00:28 -04:00
2016-12-09 15:45:27 -03:00
class BumpFeeTest ( BitcoinTestFramework ) :
2022-11-09 08:53:13 -03:00
def add_options ( self , parser ) :
self . add_wallet_options ( parser )
2017-06-09 18:21:21 -04:00
def set_test_params ( self ) :
2016-12-09 15:45:27 -03:00
self . num_nodes = 2
self . setup_clean_chain = True
2023-03-14 10:18:47 -03:00
# whitelist peers to speed up tx relay / mempool sync
self . noban_tx_relay = True
2018-09-09 14:32:37 -03:00
self . extra_args = [ [
" -walletrbf= {} " . format ( i ) ,
" -mintxfee=0.00002 " ,
2019-10-19 21:03:13 -03:00
" -addresstype=bech32 " ,
2018-09-09 14:32:37 -03:00
] for i in range ( self . num_nodes ) ]
def skip_test_if_missing_module ( self ) :
self . skip_if_no_wallet ( )
2016-12-09 15:45:27 -03:00
2020-08-21 12:23:06 -04:00
def clear_mempool ( self ) :
# Clear mempool between subtests. The subtests may only depend on chainstate (utxos)
2021-08-19 11:10:24 -04:00
self . generate ( self . nodes [ 1 ] , 1 )
2020-08-21 12:23:06 -04:00
2017-06-09 16:35:17 -04:00
def run_test ( self ) :
2016-12-09 15:45:27 -03:00
# Encrypt wallet for test_locked_wallet_fails test
2018-02-20 18:09:51 -03:00
self . nodes [ 1 ] . encryptwallet ( WALLET_PASSPHRASE )
2016-12-09 15:45:27 -03:00
self . nodes [ 1 ] . walletpassphrase ( WALLET_PASSPHRASE , WALLET_PASSPHRASE_TIMEOUT )
peer_node , rbf_node = self . nodes
rbf_node_address = rbf_node . getnewaddress ( )
# fund rbf node with 10 coins of 0.001 btc (100,000 satoshis)
2017-03-07 20:46:17 -03:00
self . log . info ( " Mining blocks... " )
2021-08-19 11:10:24 -04:00
self . generate ( peer_node , 110 )
2020-08-02 19:10:56 -04:00
for _ in range ( 25 ) :
2016-12-09 15:45:27 -03:00
peer_node . sendtoaddress ( rbf_node_address , 0.001 )
self . sync_all ( )
2021-08-19 11:10:24 -04:00
self . generate ( peer_node , 1 )
2016-12-09 15:45:27 -03:00
assert_equal ( rbf_node . getbalance ( ) , Decimal ( " 0.025 " ) )
2017-03-07 20:46:17 -03:00
self . log . info ( " Running tests " )
2016-12-09 15:45:27 -03:00
dest_address = peer_node . getnewaddress ( )
2022-06-11 18:52:24 -04:00
for mode in [ " default " , " fee_rate " , " new_outputs " ] :
2020-10-22 10:50:03 -03:00
test_simple_bumpfee_succeeds ( self , mode , rbf_node , peer_node , dest_address )
2020-10-22 12:01:10 -03:00
self . test_invalid_parameters ( rbf_node , peer_node , dest_address )
2020-03-10 13:30:57 -03:00
test_segwit_bumpfee_succeeds ( self , rbf_node , dest_address )
test_nonrbf_bumpfee_fails ( self , peer_node , dest_address )
2021-10-05 23:17:28 -03:00
test_notmine_bumpfee ( self , rbf_node , peer_node , dest_address )
2020-03-10 13:30:57 -03:00
test_bumpfee_with_descendant_fails ( self , rbf_node , rbf_node_address , dest_address )
2022-12-09 15:31:27 -03:00
test_bumpfee_with_abandoned_descendant_succeeds ( self , rbf_node , rbf_node_address , dest_address )
2020-03-10 13:30:57 -03:00
test_dust_to_fee ( self , rbf_node , dest_address )
2019-11-28 09:02:58 -03:00
test_watchonly_psbt ( self , peer_node , rbf_node , dest_address )
2020-03-10 13:30:57 -03:00
test_rebumping ( self , rbf_node , dest_address )
test_rebumping_not_replaceable ( self , rbf_node , dest_address )
2022-09-19 14:48:29 -03:00
test_bumpfee_already_spent ( self , rbf_node , dest_address )
2020-03-10 13:30:57 -03:00
test_unconfirmed_not_spendable ( self , rbf_node , rbf_node_address )
test_bumpfee_metadata ( self , rbf_node , dest_address )
test_locked_wallet_fails ( self , rbf_node , dest_address )
test_change_script_match ( self , rbf_node , dest_address )
2020-03-30 16:59:04 -03:00
test_settxfee ( self , rbf_node , dest_address )
2019-07-02 10:16:36 -04:00
test_maxtxfee_fails ( self , rbf_node , dest_address )
2019-03-08 13:30:53 -03:00
# These tests wipe out a number of utxos that are expected in other tests
2020-03-10 13:30:57 -03:00
test_small_output_with_feerate_succeeds ( self , rbf_node , dest_address )
test_no_more_inputs_fails ( self , rbf_node , dest_address )
2023-03-03 11:59:21 -03:00
self . test_bump_back_to_yourself ( )
2022-11-07 17:07:51 -03:00
self . test_provided_change_pos ( rbf_node )
2023-03-17 19:49:42 -03:00
self . test_single_output ( )
2016-12-09 15:45:27 -03:00
2023-04-15 20:47:29 -04:00
# Context independent tests
test_feerate_checks_replaced_outputs ( self , rbf_node , peer_node )
2023-06-26 17:06:25 -04:00
test_bumpfee_with_feerate_ignores_walletincrementalrelayfee ( self , rbf_node , peer_node )
2023-04-15 20:47:29 -04:00
2020-10-22 12:01:10 -03:00
def test_invalid_parameters ( self , rbf_node , peer_node , dest_address ) :
self . log . info ( ' Test invalid parameters ' )
rbfid = spend_one_input ( rbf_node , dest_address )
self . sync_mempools ( ( rbf_node , peer_node ) )
assert rbfid in rbf_node . getrawmempool ( ) and rbfid in peer_node . getrawmempool ( )
2020-11-04 09:13:17 -03:00
for key in [ " totalFee " , " feeRate " ] :
assert_raises_rpc_error ( - 3 , " Unexpected key {} " . format ( key ) , rbf_node . bumpfee , rbfid , { key : NORMAL } )
2020-10-22 12:01:10 -03:00
2020-11-04 09:13:17 -03:00
# Bumping to just above minrelay should fail to increase the total fee enough.
2022-11-10 14:04:07 -03:00
assert_raises_rpc_error ( - 8 , " Insufficient total fee 0.00000141 " , rbf_node . bumpfee , rbfid , fee_rate = INSUFFICIENT )
2020-10-22 12:01:10 -03:00
2020-11-04 09:13:17 -03:00
self . log . info ( " Test invalid fee rate settings " )
assert_raises_rpc_error ( - 4 , " Specified or calculated fee 0.141 is too high (cannot be higher than -maxtxfee 0.10 " ,
2022-11-10 14:04:07 -03:00
rbf_node . bumpfee , rbfid , fee_rate = TOO_HIGH )
2020-12-04 07:28:47 -03:00
# Test fee_rate with zero values.
msg = " Insufficient total fee 0.00 "
for zero_value in [ 0 , 0.000 , 0.00000000 , " 0 " , " 0.000 " , " 0.00000000 " ] :
2022-11-10 14:04:07 -03:00
assert_raises_rpc_error ( - 8 , msg , rbf_node . bumpfee , rbfid , fee_rate = zero_value )
2020-12-03 06:55:15 -03:00
msg = " Invalid amount "
# Test fee_rate values that don't pass fixed-point parsing checks.
for invalid_value in [ " " , 0.000000001 , 1e-09 , 1.111111111 , 1111111111111111 , " 31.999999999999999999999 " ] :
2022-11-10 14:04:07 -03:00
assert_raises_rpc_error ( - 3 , msg , rbf_node . bumpfee , rbfid , fee_rate = invalid_value )
2020-12-02 09:55:26 -03:00
# Test fee_rate values that cannot be represented in sat/vB.
2023-07-26 21:14:19 -04:00
for invalid_value in [ 0.0001 , 0.00000001 , 0.00099999 , 31.99999999 ] :
2022-11-10 14:04:07 -03:00
assert_raises_rpc_error ( - 3 , msg , rbf_node . bumpfee , rbfid , fee_rate = invalid_value )
2020-12-04 07:28:47 -03:00
# Test fee_rate out of range (negative number).
2022-11-10 14:04:07 -03:00
assert_raises_rpc_error ( - 3 , " Amount out of range " , rbf_node . bumpfee , rbfid , fee_rate = - 1 )
2020-12-04 08:51:34 -03:00
# Test type error.
2020-11-04 09:13:17 -03:00
for value in [ { " foo " : " bar " } , True ] :
2022-11-10 14:04:07 -03:00
assert_raises_rpc_error ( - 3 , " Amount is not a number or string " , rbf_node . bumpfee , rbfid , fee_rate = value )
2020-10-22 12:01:10 -03:00
self . log . info ( " Test explicit fee rate raises RPC error if both fee_rate and conf_target are passed " )
2020-11-04 09:13:17 -03:00
assert_raises_rpc_error ( - 8 , " Cannot specify both conf_target and fee_rate. Please provide either a confirmation "
" target in blocks for automatic fee estimation, or an explicit fee rate. " ,
2022-11-10 14:04:07 -03:00
rbf_node . bumpfee , rbfid , conf_target = NORMAL , fee_rate = NORMAL )
2020-11-04 09:13:17 -03:00
self . log . info ( " Test explicit fee rate raises RPC error if both fee_rate and estimate_mode are passed " )
assert_raises_rpc_error ( - 8 , " Cannot specify both estimate_mode and fee_rate " ,
2022-11-10 14:04:07 -03:00
rbf_node . bumpfee , rbfid , estimate_mode = " economical " , fee_rate = NORMAL )
2020-10-22 12:01:10 -03:00
self . log . info ( " Test invalid conf_target settings " )
assert_raises_rpc_error ( - 8 , " confTarget and conf_target options should not both be set " ,
2020-11-04 09:13:17 -03:00
rbf_node . bumpfee , rbfid , { " confTarget " : 123 , " conf_target " : 456 } )
2020-10-22 12:01:10 -03:00
self . log . info ( " Test invalid estimate_mode settings " )
for k , v in { " number " : 42 , " object " : { " foo " : " bar " } } . items ( ) :
2022-10-04 13:51:24 -03:00
assert_raises_rpc_error ( - 3 , f " JSON value of type { k } for field estimate_mode is not of expected type string " ,
2022-11-10 14:04:07 -03:00
rbf_node . bumpfee , rbfid , estimate_mode = v )
2020-11-04 09:13:17 -03:00
for mode in [ " foo " , Decimal ( " 3.1415 " ) , " sat/B " , " BTC/kB " ] :
2020-11-10 08:29:01 -03:00
assert_raises_rpc_error ( - 8 , ' Invalid estimate_mode parameter, must be one of: " unset " , " economical " , " conservative " ' ,
2022-11-10 14:04:07 -03:00
rbf_node . bumpfee , rbfid , estimate_mode = mode )
2020-11-04 09:13:17 -03:00
2022-06-11 18:52:24 -04:00
self . log . info ( " Test invalid outputs values " )
assert_raises_rpc_error ( - 8 , " Invalid parameter, output argument cannot be an empty array " ,
rbf_node . bumpfee , rbfid , { " outputs " : [ ] } )
assert_raises_rpc_error ( - 8 , " Invalid parameter, duplicated address: " + dest_address ,
rbf_node . bumpfee , rbfid , { " outputs " : [ { dest_address : 0.1 } , { dest_address : 0.2 } ] } )
assert_raises_rpc_error ( - 8 , " Invalid parameter, duplicate key: data " ,
rbf_node . bumpfee , rbfid , { " outputs " : [ { " data " : " deadbeef " } , { " data " : " deadbeef " } ] } )
2023-09-19 08:09:23 -03:00
self . log . info ( " Test original_change_index option " )
assert_raises_rpc_error ( - 1 , " JSON integer out of range " , rbf_node . bumpfee , rbfid , { " original_change_index " : - 1 } )
assert_raises_rpc_error ( - 8 , " Change position is out of range " , rbf_node . bumpfee , rbfid , { " original_change_index " : 2 } )
2022-11-07 17:07:51 -03:00
2023-09-19 08:09:23 -03:00
self . log . info ( " Test outputs and original_change_index cannot both be provided " )
assert_raises_rpc_error ( - 8 , " The options ' outputs ' and ' original_change_index ' are incompatible. You can only either specify a new set of outputs, or designate a change output to be recycled. " , rbf_node . bumpfee , rbfid , { " original_change_index " : 2 , " outputs " : [ { dest_address : 0.1 } ] } )
2022-11-07 17:07:51 -03:00
2020-08-21 12:23:06 -04:00
self . clear_mempool ( )
2023-03-03 11:59:21 -03:00
def test_bump_back_to_yourself ( self ) :
self . log . info ( " Test that bumpfee can send coins back to yourself " )
node = self . nodes [ 1 ]
node . createwallet ( " back_to_yourself " )
wallet = node . get_wallet_rpc ( " back_to_yourself " )
# Make 3 UTXOs
addr = wallet . getnewaddress ( )
for _ in range ( 3 ) :
self . nodes [ 0 ] . sendtoaddress ( addr , 5 )
self . generate ( self . nodes [ 0 ] , 1 )
# Create a tx with two outputs. recipient and change.
tx = wallet . send ( outputs = { wallet . getnewaddress ( ) : 9 } , fee_rate = 2 )
tx_info = wallet . gettransaction ( txid = tx [ " txid " ] , verbose = True )
assert_equal ( len ( tx_info [ " decoded " ] [ " vout " ] ) , 2 )
assert_equal ( len ( tx_info [ " decoded " ] [ " vin " ] ) , 2 )
# Bump tx, send coins back to change address.
change_addr = get_change_address ( tx [ " txid " ] , wallet ) [ 0 ]
out_amount = 10
bumped = wallet . bumpfee ( txid = tx [ " txid " ] , options = { " fee_rate " : 20 , " outputs " : [ { change_addr : out_amount } ] } )
bumped_tx = wallet . gettransaction ( txid = bumped [ " txid " ] , verbose = True )
assert_equal ( len ( bumped_tx [ " decoded " ] [ " vout " ] ) , 1 )
assert_equal ( len ( bumped_tx [ " decoded " ] [ " vin " ] ) , 2 )
assert_equal ( bumped_tx [ " decoded " ] [ " vout " ] [ 0 ] [ " value " ] + bumped [ " fee " ] , out_amount )
# Bump tx again, now test send fewer coins back to change address.
out_amount = 6
bumped = wallet . bumpfee ( txid = bumped [ " txid " ] , options = { " fee_rate " : 40 , " outputs " : [ { change_addr : out_amount } ] } )
bumped_tx = wallet . gettransaction ( txid = bumped [ " txid " ] , verbose = True )
assert_equal ( len ( bumped_tx [ " decoded " ] [ " vout " ] ) , 2 )
assert_equal ( len ( bumped_tx [ " decoded " ] [ " vin " ] ) , 2 )
assert any ( txout [ ' value ' ] == out_amount - bumped [ " fee " ] and txout [ ' scriptPubKey ' ] [ ' address ' ] == change_addr for txout in bumped_tx [ ' decoded ' ] [ ' vout ' ] )
# Check that total out amount is still equal to the previously bumped tx
assert_equal ( bumped_tx [ " decoded " ] [ " vout " ] [ 0 ] [ " value " ] + bumped_tx [ " decoded " ] [ " vout " ] [ 1 ] [ " value " ] + bumped [ " fee " ] , 10 )
# Bump tx again, send more coins back to change address. The process will add another input to cover the target.
out_amount = 12
bumped = wallet . bumpfee ( txid = bumped [ " txid " ] , options = { " fee_rate " : 80 , " outputs " : [ { change_addr : out_amount } ] } )
bumped_tx = wallet . gettransaction ( txid = bumped [ " txid " ] , verbose = True )
assert_equal ( len ( bumped_tx [ " decoded " ] [ " vout " ] ) , 2 )
assert_equal ( len ( bumped_tx [ " decoded " ] [ " vin " ] ) , 3 )
assert any ( txout [ ' value ' ] == out_amount - bumped [ " fee " ] and txout [ ' scriptPubKey ' ] [ ' address ' ] == change_addr for txout in bumped_tx [ ' decoded ' ] [ ' vout ' ] )
assert_equal ( bumped_tx [ " decoded " ] [ " vout " ] [ 0 ] [ " value " ] + bumped_tx [ " decoded " ] [ " vout " ] [ 1 ] [ " value " ] + bumped [ " fee " ] , 15 )
node . unloadwallet ( " back_to_yourself " )
2016-12-09 15:45:27 -03:00
2022-11-07 17:07:51 -03:00
def test_provided_change_pos ( self , rbf_node ) :
2023-09-19 08:09:23 -03:00
self . log . info ( " Test the original_change_index option " )
2022-11-07 17:07:51 -03:00
change_addr = rbf_node . getnewaddress ( )
dest_addr = rbf_node . getnewaddress ( )
assert_equal ( rbf_node . getaddressinfo ( change_addr ) [ " ischange " ] , False )
assert_equal ( rbf_node . getaddressinfo ( dest_addr ) [ " ischange " ] , False )
send_res = rbf_node . send ( outputs = [ { dest_addr : 1 } ] , options = { " change_address " : change_addr } )
assert send_res [ " complete " ]
txid = send_res [ " txid " ]
tx = rbf_node . gettransaction ( txid = txid , verbose = True )
assert_equal ( len ( tx [ " decoded " ] [ " vout " ] ) , 2 )
change_pos = find_vout_for_address ( rbf_node , txid , change_addr )
change_value = tx [ " decoded " ] [ " vout " ] [ change_pos ] [ " value " ]
2023-09-19 08:09:23 -03:00
bumped = rbf_node . bumpfee ( txid , { " original_change_index " : change_pos } )
2022-11-07 17:07:51 -03:00
new_txid = bumped [ " txid " ]
new_tx = rbf_node . gettransaction ( txid = new_txid , verbose = True )
assert_equal ( len ( new_tx [ " decoded " ] [ " vout " ] ) , 2 )
new_change_pos = find_vout_for_address ( rbf_node , new_txid , change_addr )
new_change_value = new_tx [ " decoded " ] [ " vout " ] [ new_change_pos ] [ " value " ]
assert_greater_than ( change_value , new_change_value )
2023-03-17 19:49:42 -03:00
def test_single_output ( self ) :
self . log . info ( " Test that single output txs can be bumped " )
node = self . nodes [ 1 ]
node . createwallet ( " single_out_rbf " )
wallet = node . get_wallet_rpc ( " single_out_rbf " )
addr = wallet . getnewaddress ( )
amount = Decimal ( " 0.001 " )
# Make 2 UTXOs
self . nodes [ 0 ] . sendtoaddress ( addr , amount )
self . nodes [ 0 ] . sendtoaddress ( addr , amount )
self . generate ( self . nodes [ 0 ] , 1 )
utxos = wallet . listunspent ( )
tx = wallet . sendall ( recipients = [ wallet . getnewaddress ( ) ] , fee_rate = 2 , options = { " inputs " : [ utxos [ 0 ] ] } )
2023-09-19 08:09:23 -03:00
# Set the only output with a crazy high feerate as change, should fail as the output would be dust
assert_raises_rpc_error ( - 4 , " The transaction amount is too small to pay the fee " , wallet . bumpfee , txid = tx [ " txid " ] , options = { " fee_rate " : 1100 , " original_change_index " : 0 } )
2023-03-17 19:49:42 -03:00
2023-09-19 08:09:23 -03:00
# Specify single output as change successfully
bumped = wallet . bumpfee ( txid = tx [ " txid " ] , options = { " fee_rate " : 10 , " original_change_index " : 0 } )
2023-03-17 19:49:42 -03:00
bumped_tx = wallet . gettransaction ( txid = bumped [ " txid " ] , verbose = True )
assert_equal ( len ( bumped_tx [ " decoded " ] [ " vout " ] ) , 1 )
assert_equal ( len ( bumped_tx [ " decoded " ] [ " vin " ] ) , 1 )
assert_equal ( bumped_tx [ " decoded " ] [ " vout " ] [ 0 ] [ " value " ] + bumped [ " fee " ] , amount )
assert_fee_amount ( bumped [ " fee " ] , bumped_tx [ " decoded " ] [ " vsize " ] , Decimal ( 10 ) / Decimal ( 1e8 ) * 1000 )
2023-09-19 08:09:23 -03:00
# Bumping without specifying change adds a new input and output
2023-03-17 19:49:42 -03:00
bumped = wallet . bumpfee ( txid = bumped [ " txid " ] , options = { " fee_rate " : 20 } )
bumped_tx = wallet . gettransaction ( txid = bumped [ " txid " ] , verbose = True )
assert_equal ( len ( bumped_tx [ " decoded " ] [ " vout " ] ) , 2 )
assert_equal ( len ( bumped_tx [ " decoded " ] [ " vin " ] ) , 2 )
assert_fee_amount ( bumped [ " fee " ] , bumped_tx [ " decoded " ] [ " vsize " ] , Decimal ( 20 ) / Decimal ( 1e8 ) * 1000 )
wallet . unloadwallet ( )
2019-07-29 19:59:23 -04:00
def test_simple_bumpfee_succeeds ( self , mode , rbf_node , peer_node , dest_address ) :
2020-03-13 19:11:55 -03:00
self . log . info ( ' Test simple bumpfee: {} ' . format ( mode ) )
2017-02-06 20:27:24 -03:00
rbfid = spend_one_input ( rbf_node , dest_address )
2016-12-09 15:45:27 -03:00
rbftx = rbf_node . gettransaction ( rbfid )
2019-04-06 19:19:45 -03:00
self . sync_mempools ( ( rbf_node , peer_node ) )
2016-12-09 15:45:27 -03:00
assert rbfid in rbf_node . getrawmempool ( ) and rbfid in peer_node . getrawmempool ( )
2019-07-29 19:59:23 -04:00
if mode == " fee_rate " :
2022-11-10 14:04:07 -03:00
bumped_psbt = rbf_node . psbtbumpfee ( rbfid , fee_rate = str ( NORMAL ) )
bumped_tx = rbf_node . bumpfee ( rbfid , fee_rate = NORMAL )
2022-06-11 18:52:24 -04:00
elif mode == " new_outputs " :
new_address = peer_node . getnewaddress ( )
2022-11-10 14:04:07 -03:00
bumped_psbt = rbf_node . psbtbumpfee ( rbfid , outputs = { new_address : 0.0003 } )
bumped_tx = rbf_node . bumpfee ( rbfid , outputs = { new_address : 0.0003 } )
2019-07-29 19:59:23 -04:00
else :
2020-04-13 14:48:03 -04:00
bumped_psbt = rbf_node . psbtbumpfee ( rbfid )
2019-07-29 19:59:23 -04:00
bumped_tx = rbf_node . bumpfee ( rbfid )
2017-05-24 16:15:26 -04:00
assert_equal ( bumped_tx [ " errors " ] , [ ] )
2019-12-01 11:46:44 -03:00
assert bumped_tx [ " fee " ] > - rbftx [ " fee " ]
assert_equal ( bumped_tx [ " origfee " ] , - rbftx [ " fee " ] )
2019-11-28 09:02:58 -03:00
assert " psbt " not in bumped_tx
2020-04-13 14:48:03 -04:00
assert_equal ( bumped_psbt [ " errors " ] , [ ] )
assert bumped_psbt [ " fee " ] > - rbftx [ " fee " ]
assert_equal ( bumped_psbt [ " origfee " ] , - rbftx [ " fee " ] )
assert " psbt " in bumped_psbt
2017-08-15 19:24:39 -03:00
# check that bumped_tx propagates, original tx was evicted and has a wallet conflict
2019-04-06 19:19:45 -03:00
self . sync_mempools ( ( rbf_node , peer_node ) )
2016-12-09 15:45:27 -03:00
assert bumped_tx [ " txid " ] in rbf_node . getrawmempool ( )
assert bumped_tx [ " txid " ] in peer_node . getrawmempool ( )
assert rbfid not in rbf_node . getrawmempool ( )
assert rbfid not in peer_node . getrawmempool ( )
oldwtx = rbf_node . gettransaction ( rbfid )
assert len ( oldwtx [ " walletconflicts " ] ) > 0
# check wallet transaction replaces and replaced_by values
bumpedwtx = rbf_node . gettransaction ( bumped_tx [ " txid " ] )
assert_equal ( oldwtx [ " replaced_by_txid " ] , bumped_tx [ " txid " ] )
assert_equal ( bumpedwtx [ " replaces_txid " ] , rbfid )
2022-06-11 18:52:24 -04:00
# if this is a new_outputs test, check that outputs were indeed replaced
if mode == " new_outputs " :
assert len ( bumpedwtx [ " details " ] ) == 1
assert bumpedwtx [ " details " ] [ 0 ] [ " address " ] == new_address
2020-08-21 12:23:06 -04:00
self . clear_mempool ( )
2016-12-09 15:45:27 -03:00
2020-04-25 13:00:28 -04:00
2020-03-10 13:30:57 -03:00
def test_segwit_bumpfee_succeeds ( self , rbf_node , dest_address ) :
self . log . info ( ' Test that segwit-sourcing bumpfee works ' )
2016-12-09 15:45:27 -03:00
# Create a transaction with segwit output, then create an RBF transaction
# which spends it, and make sure bumpfee can be called on it.
2022-11-30 13:05:28 -03:00
segwit_out = rbf_node . getnewaddress ( address_type = ' bech32 ' )
segwitid = rbf_node . send ( { segwit_out : " 0.0009 " } , options = { " change_position " : 1 } ) [ " txid " ]
2016-12-09 15:45:27 -03:00
rbfraw = rbf_node . createrawtransaction ( [ {
' txid ' : segwitid ,
' vout ' : 0 ,
2022-07-12 11:28:20 -04:00
" sequence " : MAX_BIP125_RBF_SEQUENCE
2016-12-09 15:45:27 -03:00
} ] , { dest_address : Decimal ( " 0.0005 " ) ,
2017-02-06 15:50:08 -03:00
rbf_node . getrawchangeaddress ( ) : Decimal ( " 0.0003 " ) } )
2017-09-05 20:49:18 -03:00
rbfsigned = rbf_node . signrawtransactionwithwallet ( rbfraw )
2016-12-09 15:45:27 -03:00
rbfid = rbf_node . sendrawtransaction ( rbfsigned [ " hex " ] )
assert rbfid in rbf_node . getrawmempool ( )
bumped_tx = rbf_node . bumpfee ( rbfid )
assert bumped_tx [ " txid " ] in rbf_node . getrawmempool ( )
assert rbfid not in rbf_node . getrawmempool ( )
2020-08-21 12:23:06 -04:00
self . clear_mempool ( )
2016-12-09 15:45:27 -03:00
2020-03-10 13:30:57 -03:00
def test_nonrbf_bumpfee_fails ( self , peer_node , dest_address ) :
self . log . info ( ' Test that we cannot replace a non RBF transaction ' )
2017-02-06 20:27:24 -03:00
not_rbfid = peer_node . sendtoaddress ( dest_address , Decimal ( " 0.00090000 " ) )
2022-09-19 14:48:29 -03:00
assert_raises_rpc_error ( - 4 , " Transaction is not BIP 125 replaceable " , peer_node . bumpfee , not_rbfid )
2020-08-21 12:23:06 -04:00
self . clear_mempool ( )
2016-12-09 15:45:27 -03:00
2021-10-05 23:17:28 -03:00
def test_notmine_bumpfee ( self , rbf_node , peer_node , dest_address ) :
2020-03-10 13:30:57 -03:00
self . log . info ( ' Test that it cannot bump fee if non-owned inputs are included ' )
2016-12-09 15:45:27 -03:00
# here, the rbftx has a peer_node coin and then adds a rbf_node input
# Note that this test depends upon the RPC code checking input ownership prior to change outputs
# (since it can't use fundrawtransaction, it lacks a proper change output)
2020-08-24 07:52:17 -04:00
fee = Decimal ( " 0.001 " )
2022-11-10 14:04:07 -03:00
utxos = [ node . listunspent ( minimumAmount = fee ) [ - 1 ] for node in ( rbf_node , peer_node ) ]
2016-12-09 15:45:27 -03:00
inputs = [ {
" txid " : utxo [ " txid " ] ,
" vout " : utxo [ " vout " ] ,
" address " : utxo [ " address " ] ,
2022-07-12 11:28:20 -04:00
" sequence " : MAX_BIP125_RBF_SEQUENCE
2016-12-09 15:45:27 -03:00
} for utxo in utxos ]
2020-08-24 07:52:17 -04:00
output_val = sum ( utxo [ " amount " ] for utxo in utxos ) - fee
2016-12-09 15:45:27 -03:00
rawtx = rbf_node . createrawtransaction ( inputs , { dest_address : output_val } )
2017-09-05 20:49:18 -03:00
signedtx = rbf_node . signrawtransactionwithwallet ( rawtx )
signedtx = peer_node . signrawtransactionwithwallet ( signedtx [ " hex " ] )
2016-12-09 15:45:27 -03:00
rbfid = rbf_node . sendrawtransaction ( signedtx [ " hex " ] )
2021-10-05 23:17:28 -03:00
entry = rbf_node . getmempoolentry ( rbfid )
old_fee = entry [ " fees " ] [ " base " ]
old_feerate = int ( old_fee / entry [ " vsize " ] * Decimal ( 1e8 ) )
2017-07-12 10:33:46 -04:00
assert_raises_rpc_error ( - 4 , " Transaction contains inputs that don ' t belong to this wallet " ,
2018-09-23 09:50:47 -03:00
rbf_node . bumpfee , rbfid )
2021-10-05 23:17:28 -03:00
def finish_psbtbumpfee ( psbt ) :
psbt = rbf_node . walletprocesspsbt ( psbt )
psbt = peer_node . walletprocesspsbt ( psbt [ " psbt " ] )
2023-09-05 13:10:21 -03:00
res = rbf_node . testmempoolaccept ( [ psbt [ " hex " ] ] )
2021-10-05 23:17:28 -03:00
assert res [ 0 ] [ " allowed " ]
assert_greater_than ( res [ 0 ] [ " fees " ] [ " base " ] , old_fee )
self . log . info ( " Test that psbtbumpfee works for non-owned inputs " )
psbt = rbf_node . psbtbumpfee ( txid = rbfid )
finish_psbtbumpfee ( psbt [ " psbt " ] )
2022-11-10 14:04:07 -03:00
psbt = rbf_node . psbtbumpfee ( txid = rbfid , fee_rate = old_feerate + 10 )
2021-10-05 23:17:28 -03:00
finish_psbtbumpfee ( psbt [ " psbt " ] )
2020-08-21 12:23:06 -04:00
self . clear_mempool ( )
2016-12-09 15:45:27 -03:00
2020-03-10 13:30:57 -03:00
def test_bumpfee_with_descendant_fails ( self , rbf_node , rbf_node_address , dest_address ) :
self . log . info ( ' Test that fee cannot be bumped when it has descendant ' )
2016-12-09 15:45:27 -03:00
# parent is send-to-self, so we don't have to check which output is change when creating the child tx
2017-02-06 20:27:24 -03:00
parent_id = spend_one_input ( rbf_node , rbf_node_address )
2016-12-09 15:45:27 -03:00
tx = rbf_node . createrawtransaction ( [ { " txid " : parent_id , " vout " : 0 } ] , { dest_address : 0.00020000 } )
2017-09-05 20:49:18 -03:00
tx = rbf_node . signrawtransactionwithwallet ( tx )
2017-07-20 17:10:37 -04:00
rbf_node . sendrawtransaction ( tx [ " hex " ] )
2017-07-12 10:33:46 -04:00
assert_raises_rpc_error ( - 8 , " Transaction has descendants in the wallet " , rbf_node . bumpfee , parent_id )
2021-10-14 21:18:51 -03:00
# create tx with descendant in the mempool by using MiniWallet
miniwallet = MiniWallet ( rbf_node )
parent_id = spend_one_input ( rbf_node , miniwallet . get_address ( ) )
tx = rbf_node . gettransaction ( txid = parent_id , verbose = True ) [ ' decoded ' ]
miniwallet . scan_tx ( tx )
miniwallet . send_self_transfer ( from_node = rbf_node )
assert_raises_rpc_error ( - 8 , " Transaction has descendants in the mempool " , rbf_node . bumpfee , parent_id )
2020-08-21 12:23:06 -04:00
self . clear_mempool ( )
2016-12-09 15:45:27 -03:00
2020-04-25 13:00:28 -04:00
2022-12-09 15:31:27 -03:00
def test_bumpfee_with_abandoned_descendant_succeeds ( self , rbf_node , rbf_node_address , dest_address ) :
self . log . info ( ' Test that fee can be bumped when it has abandoned descendant ' )
# parent is send-to-self, so we don't have to check which output is change when creating the child tx
parent_id = spend_one_input ( rbf_node , rbf_node_address )
# Submit child transaction with low fee
child_id = rbf_node . send ( outputs = { dest_address : 0.00020000 } ,
options = { " inputs " : [ { " txid " : parent_id , " vout " : 0 } ] , " fee_rate " : 2 } ) [ " txid " ]
assert child_id in rbf_node . getrawmempool ( )
# Restart the node with higher min relay fee so the descendant tx is no longer in mempool so that we can abandon it
self . restart_node ( 1 , [ ' -minrelaytxfee=0.00005 ' ] + self . extra_args [ 1 ] )
rbf_node . walletpassphrase ( WALLET_PASSPHRASE , WALLET_PASSPHRASE_TIMEOUT )
self . connect_nodes ( 1 , 0 )
assert parent_id in rbf_node . getrawmempool ( )
assert child_id not in rbf_node . getrawmempool ( )
# Should still raise an error even if not in mempool
assert_raises_rpc_error ( - 8 , " Transaction has descendants in the wallet " , rbf_node . bumpfee , parent_id )
# Now abandon the child transaction and bump the original
rbf_node . abandontransaction ( child_id )
bumped_result = rbf_node . bumpfee ( parent_id , { " fee_rate " : HIGH } )
assert bumped_result [ ' txid ' ] in rbf_node . getrawmempool ( )
assert parent_id not in rbf_node . getrawmempool ( )
# Cleanup
self . restart_node ( 1 , self . extra_args [ 1 ] )
rbf_node . walletpassphrase ( WALLET_PASSPHRASE , WALLET_PASSPHRASE_TIMEOUT )
self . connect_nodes ( 1 , 0 )
self . clear_mempool ( )
2020-03-10 13:30:57 -03:00
def test_small_output_with_feerate_succeeds ( self , rbf_node , dest_address ) :
self . log . info ( ' Testing small output with feerate bump succeeds ' )
2019-03-08 13:30:53 -03:00
# Make sure additional inputs exist
2021-08-19 11:10:24 -04:00
self . generatetoaddress ( rbf_node , COINBASE_MATURITY + 1 , rbf_node . getnewaddress ( ) )
2019-03-08 13:30:53 -03:00
rbfid = spend_one_input ( rbf_node , dest_address )
2019-10-30 13:59:50 -03:00
input_list = rbf_node . getrawtransaction ( rbfid , 1 ) [ " vin " ]
assert_equal ( len ( input_list ) , 1 )
original_txin = input_list [ 0 ]
2020-03-10 13:33:50 -03:00
self . log . info ( ' Keep bumping until transaction fee out-spends non-destination value ' )
2019-03-08 13:30:53 -03:00
tx_fee = 0
2020-03-10 13:33:50 -03:00
while True :
2019-10-30 13:59:50 -03:00
input_list = rbf_node . getrawtransaction ( rbfid , 1 ) [ " vin " ]
new_item = list ( input_list ) [ 0 ]
assert_equal ( len ( input_list ) , 1 )
2019-03-08 13:30:53 -03:00
assert_equal ( original_txin [ " txid " ] , new_item [ " txid " ] )
assert_equal ( original_txin [ " vout " ] , new_item [ " vout " ] )
rbfid_new_details = rbf_node . bumpfee ( rbfid )
rbfid_new = rbfid_new_details [ " txid " ]
raw_pool = rbf_node . getrawmempool ( )
assert rbfid not in raw_pool
assert rbfid_new in raw_pool
rbfid = rbfid_new
2020-03-10 13:33:50 -03:00
tx_fee = rbfid_new_details [ " fee " ]
# Total value from input not going to destination
if tx_fee > Decimal ( ' 0.00050000 ' ) :
break
2019-03-08 13:30:53 -03:00
# input(s) have been added
final_input_list = rbf_node . getrawtransaction ( rbfid , 1 ) [ " vin " ]
assert_greater_than ( len ( final_input_list ) , 1 )
# Original input is in final set
assert [ txin for txin in final_input_list
if txin [ " txid " ] == original_txin [ " txid " ]
and txin [ " vout " ] == original_txin [ " vout " ] ]
2021-08-19 11:10:24 -04:00
self . generatetoaddress ( rbf_node , 1 , rbf_node . getnewaddress ( ) )
2019-03-08 13:30:53 -03:00
assert_equal ( rbf_node . gettransaction ( rbfid ) [ " confirmations " ] , 1 )
2020-08-21 12:23:06 -04:00
self . clear_mempool ( )
2016-12-09 15:45:27 -03:00
2020-04-25 13:00:28 -04:00
2020-03-10 13:30:57 -03:00
def test_dust_to_fee ( self , rbf_node , dest_address ) :
self . log . info ( ' Test that bumped output that is dust is dropped to fee ' )
2017-02-06 20:27:24 -03:00
rbfid = spend_one_input ( rbf_node , dest_address )
2016-12-09 15:45:27 -03:00
fulltx = rbf_node . getrawtransaction ( rbfid , 1 )
2020-04-03 12:12:08 -03:00
# The DER formatting used by Bitcoin to serialize ECDSA signatures means that signatures can have a
# variable size of 70-72 bytes (or possibly even less), with most being 71 or 72 bytes. The signature
# in the witness is divided by 4 for the vsize, so this variance can take the weight across a 4-byte
# boundary. Thus expected transaction size (p2wpkh, 1 input, 2 outputs) is 140-141 vbytes, usually 141.
if not 140 < = fulltx [ " vsize " ] < = 141 :
2020-04-06 12:37:50 -04:00
raise AssertionError ( " Invalid tx vsize of {} (140-141 expected), full tx: {} " . format ( fulltx [ " vsize " ] , fulltx ) )
2020-11-04 09:13:17 -03:00
# Bump with fee_rate of 350.25 sat/vB vbytes to create dust.
2020-04-03 12:12:08 -03:00
# Expected fee is 141 vbytes * fee_rate 0.00350250 BTC / 1000 vbytes = 0.00049385 BTC.
# or occasionally 140 vbytes * fee_rate 0.00350250 BTC / 1000 vbytes = 0.00049035 BTC.
# Dust should be dropped to the fee, so actual bump fee is 0.00050000 BTC.
2022-11-10 14:04:07 -03:00
bumped_tx = rbf_node . bumpfee ( rbfid , fee_rate = 350.25 )
2016-12-09 15:45:27 -03:00
full_bumped_tx = rbf_node . getrawtransaction ( bumped_tx [ " txid " ] , 1 )
2017-02-06 20:27:24 -03:00
assert_equal ( bumped_tx [ " fee " ] , Decimal ( " 0.00050000 " ) )
2016-12-09 15:45:27 -03:00
assert_equal ( len ( fulltx [ " vout " ] ) , 2 )
2018-09-23 09:50:47 -03:00
assert_equal ( len ( full_bumped_tx [ " vout " ] ) , 1 ) # change output is eliminated
2020-03-13 19:11:55 -03:00
assert_equal ( full_bumped_tx [ " vout " ] [ 0 ] [ ' value ' ] , Decimal ( " 0.00050000 " ) )
2020-08-21 12:23:06 -04:00
self . clear_mempool ( )
2016-12-09 15:45:27 -03:00
2020-03-10 13:30:57 -03:00
def test_settxfee ( self , rbf_node , dest_address ) :
self . log . info ( ' Test settxfee ' )
2018-08-20 14:34:39 -03:00
assert_raises_rpc_error ( - 8 , " txfee cannot be less than min relay tx fee " , rbf_node . settxfee , Decimal ( ' 0.000005 ' ) )
assert_raises_rpc_error ( - 8 , " txfee cannot be less than wallet min fee " , rbf_node . settxfee , Decimal ( ' 0.000015 ' ) )
2016-12-09 15:45:27 -03:00
# check that bumpfee reacts correctly to the use of settxfee (paytxfee)
2017-02-06 20:27:24 -03:00
rbfid = spend_one_input ( rbf_node , dest_address )
requested_feerate = Decimal ( " 0.00025000 " )
rbf_node . settxfee ( requested_feerate )
2016-12-09 15:45:27 -03:00
bumped_tx = rbf_node . bumpfee ( rbfid )
2017-11-30 21:49:11 -03:00
actual_feerate = bumped_tx [ " fee " ] * 1000 / rbf_node . getrawtransaction ( bumped_tx [ " txid " ] , True ) [ " vsize " ]
2017-02-06 20:27:24 -03:00
# Assert that the difference between the requested feerate and the actual
# feerate of the bumped transaction is small.
assert_greater_than ( Decimal ( " 0.00001000 " ) , abs ( requested_feerate - actual_feerate ) )
2016-12-09 15:45:27 -03:00
rbf_node . settxfee ( Decimal ( " 0.00000000 " ) ) # unset paytxfee
2020-03-30 16:59:04 -03:00
# check that settxfee respects -maxtxfee
self . restart_node ( 1 , [ ' -maxtxfee=0.000025 ' ] + self . extra_args [ 1 ] )
assert_raises_rpc_error ( - 8 , " txfee cannot be more than wallet max tx fee " , rbf_node . settxfee , Decimal ( ' 0.00003 ' ) )
self . restart_node ( 1 , self . extra_args [ 1 ] )
rbf_node . walletpassphrase ( WALLET_PASSPHRASE , WALLET_PASSPHRASE_TIMEOUT )
2020-08-21 12:23:06 -04:00
self . connect_nodes ( 1 , 0 )
self . clear_mempool ( )
2020-03-30 16:59:04 -03:00
2016-12-09 15:45:27 -03:00
2020-03-10 13:30:57 -03:00
def test_maxtxfee_fails ( self , rbf_node , dest_address ) :
2020-03-30 16:59:04 -03:00
self . log . info ( ' Test that bumpfee fails when it hits -maxtxfee ' )
2019-10-19 21:03:13 -03:00
# size of bumped transaction (p2wpkh, 1 input, 2 outputs): 141 vbytes
2020-03-13 19:11:55 -03:00
# expected bump fee of 141 vbytes * 0.00200000 BTC / 1000 vbytes = 0.00002820 BTC
# which exceeds maxtxfee and is expected to raise
2020-03-10 13:30:57 -03:00
self . restart_node ( 1 , [ ' -maxtxfee=0.000025 ' ] + self . extra_args [ 1 ] )
2019-07-02 10:16:36 -04:00
rbf_node . walletpassphrase ( WALLET_PASSPHRASE , WALLET_PASSPHRASE_TIMEOUT )
rbfid = spend_one_input ( rbf_node , dest_address )
2020-08-25 10:00:38 -04:00
assert_raises_rpc_error ( - 4 , " Unable to create transaction. Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate) " , rbf_node . bumpfee , rbfid )
2020-03-10 13:30:57 -03:00
self . restart_node ( 1 , self . extra_args [ 1 ] )
2019-07-02 10:16:36 -04:00
rbf_node . walletpassphrase ( WALLET_PASSPHRASE , WALLET_PASSPHRASE_TIMEOUT )
2020-08-21 12:23:06 -04:00
self . connect_nodes ( 1 , 0 )
self . clear_mempool ( )
2019-07-02 10:16:36 -04:00
2020-04-25 13:00:28 -04:00
2020-03-10 13:30:57 -03:00
def test_watchonly_psbt ( self , peer_node , rbf_node , dest_address ) :
self . log . info ( ' Test that PSBT is returned for bumpfee in watchonly wallets ' )
2019-11-28 09:02:58 -03:00
priv_rec_desc = " wpkh([00000001/84 ' /1 ' /0 ' ]tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0/*)#rweraev0 "
pub_rec_desc = rbf_node . getdescriptorinfo ( priv_rec_desc ) [ " descriptor " ]
priv_change_desc = " wpkh([00000001/84 ' /1 ' /0 ' ]tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/*)#j6uzqvuh "
pub_change_desc = rbf_node . getdescriptorinfo ( priv_change_desc ) [ " descriptor " ]
# Create a wallet with private keys that can sign PSBTs
rbf_node . createwallet ( wallet_name = " signer " , disable_private_keys = False , blank = True )
signer = rbf_node . get_wallet_rpc ( " signer " )
assert signer . getwalletinfo ( ) [ ' private_keys_enabled ' ]
2020-04-06 16:10:15 -04:00
reqs = [ {
2019-11-28 09:02:58 -03:00
" desc " : priv_rec_desc ,
" timestamp " : 0 ,
" range " : [ 0 , 1 ] ,
" internal " : False ,
" keypool " : False # Keys can only be imported to the keypool when private keys are disabled
} ,
{
" desc " : priv_change_desc ,
" timestamp " : 0 ,
" range " : [ 0 , 0 ] ,
" internal " : True ,
" keypool " : False
2020-04-06 16:10:15 -04:00
} ]
if self . options . descriptors :
result = signer . importdescriptors ( reqs )
else :
result = signer . importmulti ( reqs )
2019-11-28 09:02:58 -03:00
assert_equal ( result , [ { ' success ' : True } , { ' success ' : True } ] )
# Create another wallet with just the public keys, which creates PSBTs
rbf_node . createwallet ( wallet_name = " watcher " , disable_private_keys = True , blank = True )
watcher = rbf_node . get_wallet_rpc ( " watcher " )
assert not watcher . getwalletinfo ( ) [ ' private_keys_enabled ' ]
2020-04-06 16:10:15 -04:00
reqs = [ {
2019-11-28 09:02:58 -03:00
" desc " : pub_rec_desc ,
" timestamp " : 0 ,
2020-04-25 13:00:28 -04:00
" range " : [ 0 , 10 ] ,
2019-11-28 09:02:58 -03:00
" internal " : False ,
" keypool " : True ,
2020-04-06 16:10:15 -04:00
" watchonly " : True ,
" active " : True ,
2020-04-25 13:00:28 -04:00
} , {
2019-11-28 09:02:58 -03:00
" desc " : pub_change_desc ,
" timestamp " : 0 ,
" range " : [ 0 , 10 ] ,
" internal " : True ,
" keypool " : True ,
2020-04-06 16:10:15 -04:00
" watchonly " : True ,
" active " : True ,
} ]
if self . options . descriptors :
result = watcher . importdescriptors ( reqs )
else :
result = watcher . importmulti ( reqs )
2019-11-28 09:02:58 -03:00
assert_equal ( result , [ { ' success ' : True } , { ' success ' : True } ] )
funding_address1 = watcher . getnewaddress ( address_type = ' bech32 ' )
funding_address2 = watcher . getnewaddress ( address_type = ' bech32 ' )
peer_node . sendmany ( " " , { funding_address1 : 0.001 , funding_address2 : 0.001 } )
2021-08-19 11:10:24 -04:00
self . generate ( peer_node , 1 )
2019-11-28 09:02:58 -03:00
# Create single-input PSBT for transaction to be bumped
2022-03-07 10:46:49 -03:00
# Ensure the payment amount + change can be fully funded using one of the 0.001BTC inputs.
psbt = watcher . walletcreatefundedpsbt ( [ watcher . listunspent ( ) [ 0 ] ] , { dest_address : 0.0005 } , 0 ,
{ " fee_rate " : 1 , " add_inputs " : False } , True ) [ ' psbt ' ]
2019-11-28 09:02:58 -03:00
psbt_signed = signer . walletprocesspsbt ( psbt = psbt , sign = True , sighashtype = " ALL " , bip32derivs = True )
2023-09-05 13:10:21 -03:00
original_txid = watcher . sendrawtransaction ( psbt_signed [ " hex " ] )
2019-11-28 09:02:58 -03:00
assert_equal ( len ( watcher . decodepsbt ( psbt ) [ " tx " ] [ " vin " ] ) , 1 )
2021-01-08 20:12:57 -03:00
# bumpfee can't be used on watchonly wallets
assert_raises_rpc_error ( - 4 , " bumpfee is not available with wallets that have private keys disabled. Use psbtbumpfee instead. " , watcher . bumpfee , original_txid )
2019-11-28 09:02:58 -03:00
# Bump fee, obnoxiously high to add additional watchonly input
2022-11-10 14:04:07 -03:00
bumped_psbt = watcher . psbtbumpfee ( original_txid , fee_rate = HIGH )
2019-11-28 09:02:58 -03:00
assert_greater_than ( len ( watcher . decodepsbt ( bumped_psbt [ ' psbt ' ] ) [ " tx " ] [ " vin " ] ) , 1 )
assert " txid " not in bumped_psbt
assert_equal ( bumped_psbt [ " origfee " ] , - watcher . gettransaction ( original_txid ) [ " fee " ] )
assert not watcher . finalizepsbt ( bumped_psbt [ " psbt " ] ) [ " complete " ]
# Sign bumped transaction
bumped_psbt_signed = signer . walletprocesspsbt ( psbt = bumped_psbt [ " psbt " ] , sign = True , sighashtype = " ALL " , bip32derivs = True )
2023-09-05 13:10:21 -03:00
assert bumped_psbt_signed [ " complete " ]
2019-11-28 09:02:58 -03:00
# Broadcast bumped transaction
2023-09-05 13:10:21 -03:00
bumped_txid = watcher . sendrawtransaction ( bumped_psbt_signed [ " hex " ] )
2019-11-28 09:02:58 -03:00
assert bumped_txid in rbf_node . getrawmempool ( )
assert original_txid not in rbf_node . getrawmempool ( )
rbf_node . unloadwallet ( " watcher " )
rbf_node . unloadwallet ( " signer " )
2020-08-21 12:23:06 -04:00
self . clear_mempool ( )
2019-07-02 10:16:36 -04:00
2020-04-25 13:00:28 -04:00
2020-03-10 13:30:57 -03:00
def test_rebumping ( self , rbf_node , dest_address ) :
self . log . info ( ' Test that re-bumping the original tx fails, but bumping successor works ' )
2017-02-06 20:27:24 -03:00
rbfid = spend_one_input ( rbf_node , dest_address )
2022-11-10 14:04:07 -03:00
bumped = rbf_node . bumpfee ( rbfid , fee_rate = ECONOMICAL )
2022-09-19 14:48:29 -03:00
assert_raises_rpc_error ( - 4 , f " Cannot bump transaction { rbfid } which was already bumped by transaction { bumped [ ' txid ' ] } " ,
2022-11-10 14:04:07 -03:00
rbf_node . bumpfee , rbfid , fee_rate = NORMAL )
rbf_node . bumpfee ( bumped [ " txid " ] , fee_rate = NORMAL )
2020-08-21 12:23:06 -04:00
self . clear_mempool ( )
2016-12-09 15:45:27 -03:00
2020-03-10 13:30:57 -03:00
def test_rebumping_not_replaceable ( self , rbf_node , dest_address ) :
self . log . info ( ' Test that re-bumping non-replaceable fails ' )
2017-02-06 20:27:24 -03:00
rbfid = spend_one_input ( rbf_node , dest_address )
2022-11-10 14:04:07 -03:00
bumped = rbf_node . bumpfee ( rbfid , fee_rate = ECONOMICAL , replaceable = False )
2017-07-12 10:33:46 -04:00
assert_raises_rpc_error ( - 4 , " Transaction is not BIP 125 replaceable " , rbf_node . bumpfee , bumped [ " txid " ] ,
2020-03-13 19:11:55 -03:00
{ " fee_rate " : NORMAL } )
2020-08-21 12:23:06 -04:00
self . clear_mempool ( )
2016-12-09 15:45:27 -03:00
2022-09-19 14:48:29 -03:00
def test_bumpfee_already_spent ( self , rbf_node , dest_address ) :
self . log . info ( ' Test that bumping tx with already spent coin fails ' )
txid = spend_one_input ( rbf_node , dest_address )
self . generate ( rbf_node , 1 ) # spend coin simply by mining block with tx
spent_input = rbf_node . gettransaction ( txid = txid , verbose = True ) [ ' decoded ' ] [ ' vin ' ] [ 0 ]
assert_raises_rpc_error ( - 1 , f " { spent_input [ ' txid ' ] } : { spent_input [ ' vout ' ] } is already spent " ,
2022-11-10 14:04:07 -03:00
rbf_node . bumpfee , txid , fee_rate = NORMAL )
2022-09-19 14:48:29 -03:00
2020-03-10 13:30:57 -03:00
def test_unconfirmed_not_spendable ( self , rbf_node , rbf_node_address ) :
self . log . info ( ' Test that unconfirmed outputs from bumped txns are not spendable ' )
2017-02-06 20:27:24 -03:00
rbfid = spend_one_input ( rbf_node , rbf_node_address )
2016-12-09 15:45:27 -03:00
rbftx = rbf_node . gettransaction ( rbfid ) [ " hex " ]
assert rbfid in rbf_node . getrawmempool ( )
bumpid = rbf_node . bumpfee ( rbfid ) [ " txid " ]
assert bumpid in rbf_node . getrawmempool ( )
assert rbfid not in rbf_node . getrawmempool ( )
# check that outputs from the bump transaction are not spendable
# due to the replaces_txid check in CWallet::AvailableCoins
assert_equal ( [ t for t in rbf_node . listunspent ( minconf = 0 , include_unsafe = False ) if t [ " txid " ] == bumpid ] , [ ] )
# submit a block with the rbf tx to clear the bump tx out of the mempool,
2018-01-27 19:45:32 -03:00
# then invalidate the block so the rbf tx will be put back in the mempool.
# This makes it possible to check whether the rbf tx outputs are
2016-12-09 15:45:27 -03:00
# spendable before the rbf tx is confirmed.
2022-11-30 13:05:28 -03:00
block = self . generateblock ( rbf_node , output = " raw(51) " , transactions = [ rbftx ] )
2018-01-27 19:45:32 -03:00
# Can not abandon conflicted tx
assert_raises_rpc_error ( - 5 , ' Transaction not eligible for abandonment ' , lambda : rbf_node . abandontransaction ( txid = bumpid ) )
2022-11-30 13:05:28 -03:00
rbf_node . invalidateblock ( block [ " hash " ] )
2018-01-27 19:45:32 -03:00
# Call abandon to make sure the wallet doesn't attempt to resubmit
# the bump tx and hope the wallet does not rebroadcast before we call.
rbf_node . abandontransaction ( bumpid )
2022-05-16 16:58:19 -04:00
tx_bump_abandoned = rbf_node . gettransaction ( bumpid )
for tx in tx_bump_abandoned [ ' details ' ] :
assert_equal ( tx [ ' abandoned ' ] , True )
2016-12-09 15:45:27 -03:00
assert bumpid not in rbf_node . getrawmempool ( )
assert rbfid in rbf_node . getrawmempool ( )
# check that outputs from the rbf tx are not spendable before the
# transaction is confirmed, due to the replaced_by_txid check in
# CWallet::AvailableCoins
assert_equal ( [ t for t in rbf_node . listunspent ( minconf = 0 , include_unsafe = False ) if t [ " txid " ] == rbfid ] , [ ] )
# check that the main output from the rbf tx is spendable after confirmed
2020-11-10 14:02:31 -03:00
self . generate ( rbf_node , 1 , sync_fun = self . no_op )
2016-12-09 15:45:27 -03:00
assert_equal (
sum ( 1 for t in rbf_node . listunspent ( minconf = 0 , include_unsafe = False )
if t [ " txid " ] == rbfid and t [ " address " ] == rbf_node_address and t [ " spendable " ] ) , 1 )
2020-08-21 12:23:06 -04:00
self . clear_mempool ( )
2016-12-09 15:45:27 -03:00
2020-03-10 13:30:57 -03:00
def test_bumpfee_metadata ( self , rbf_node , dest_address ) :
self . log . info ( ' Test that bumped txn metadata persists to new txn record ' )
2022-10-04 10:18:42 -03:00
assert rbf_node . getbalance ( ) < 49
2021-08-19 11:10:24 -04:00
self . generatetoaddress ( rbf_node , 101 , rbf_node . getnewaddress ( ) )
2019-03-05 13:04:32 -03:00
rbfid = rbf_node . sendtoaddress ( dest_address , 49 , " comment value " , " to value " )
2017-02-02 19:34:22 -03:00
bumped_tx = rbf_node . bumpfee ( rbfid )
bumped_wtx = rbf_node . gettransaction ( bumped_tx [ " txid " ] )
assert_equal ( bumped_wtx [ " comment " ] , " comment value " )
assert_equal ( bumped_wtx [ " to " ] , " to value " )
2020-08-21 12:23:06 -04:00
self . clear_mempool ( )
2017-02-02 19:34:22 -03:00
2020-03-10 13:30:57 -03:00
def test_locked_wallet_fails ( self , rbf_node , dest_address ) :
self . log . info ( ' Test that locked wallet cannot bump txn ' )
2017-02-06 20:27:24 -03:00
rbfid = spend_one_input ( rbf_node , dest_address )
2016-12-09 15:45:27 -03:00
rbf_node . walletlock ( )
2017-07-12 10:33:46 -04:00
assert_raises_rpc_error ( - 13 , " Please enter the wallet passphrase with walletpassphrase first. " ,
2018-09-23 09:50:47 -03:00
rbf_node . bumpfee , rbfid )
2019-03-08 13:30:53 -03:00
rbf_node . walletpassphrase ( WALLET_PASSPHRASE , WALLET_PASSPHRASE_TIMEOUT )
2020-08-21 12:23:06 -04:00
self . clear_mempool ( )
2016-12-09 15:45:27 -03:00
2020-04-25 13:00:28 -04:00
2020-03-10 13:30:57 -03:00
def test_change_script_match ( self , rbf_node , dest_address ) :
2020-03-13 19:11:55 -03:00
self . log . info ( ' Test that the same change addresses is used for the replacement transaction when possible ' )
2020-03-10 13:30:57 -03:00
2019-03-08 18:43:40 -03:00
# Check that there is only one change output
rbfid = spend_one_input ( rbf_node , dest_address )
2023-03-03 11:59:21 -03:00
change_addresses = get_change_address ( rbfid , rbf_node )
2019-03-08 18:43:40 -03:00
assert_equal ( len ( change_addresses ) , 1 )
# Now find that address in each subsequent tx, and no other change
2022-11-10 14:04:07 -03:00
bumped_total_tx = rbf_node . bumpfee ( rbfid , fee_rate = ECONOMICAL )
2023-03-03 11:59:21 -03:00
assert_equal ( change_addresses , get_change_address ( bumped_total_tx [ ' txid ' ] , rbf_node ) )
2019-03-08 18:43:40 -03:00
bumped_rate_tx = rbf_node . bumpfee ( bumped_total_tx [ " txid " ] )
2023-03-03 11:59:21 -03:00
assert_equal ( change_addresses , get_change_address ( bumped_rate_tx [ ' txid ' ] , rbf_node ) )
2020-08-21 12:23:06 -04:00
self . clear_mempool ( )
2016-12-09 15:45:27 -03:00
2020-04-25 13:00:28 -04:00
2022-06-11 18:52:24 -04:00
def spend_one_input ( node , dest_address , change_size = Decimal ( " 0.00049000 " ) , data = None ) :
2017-03-16 11:49:42 -03:00
tx_input = dict (
2022-07-12 11:28:20 -04:00
sequence = MAX_BIP125_RBF_SEQUENCE , * * next ( u for u in node . listunspent ( ) if u [ " amount " ] == Decimal ( " 0.00100000 " ) ) )
2019-03-08 13:30:53 -03:00
destinations = { dest_address : Decimal ( " 0.00050000 " ) }
if change_size > 0 :
destinations [ node . getrawchangeaddress ( ) ] = change_size
2022-06-11 18:52:24 -04:00
if data :
destinations [ ' data ' ] = data
2019-03-08 13:30:53 -03:00
rawtx = node . createrawtransaction ( [ tx_input ] , destinations )
2017-09-05 20:49:18 -03:00
signedtx = node . signrawtransactionwithwallet ( rawtx )
2016-12-09 15:45:27 -03:00
txid = node . sendrawtransaction ( signedtx [ " hex " ] )
return txid
2020-04-25 13:00:28 -04:00
2020-03-10 13:30:57 -03:00
def test_no_more_inputs_fails ( self , rbf_node , dest_address ) :
self . log . info ( ' Test that bumpfee fails when there are no available confirmed outputs ' )
2019-03-08 13:30:53 -03:00
# feerate rbf requires confirmed outputs when change output doesn't exist or is insufficient
2021-08-19 11:10:24 -04:00
self . generatetoaddress ( rbf_node , 1 , dest_address )
2019-03-08 13:30:53 -03:00
# spend all funds, no change output
2022-08-29 13:33:16 -04:00
rbfid = rbf_node . sendall ( recipients = [ rbf_node . getnewaddress ( ) ] ) [ ' txid ' ]
2020-04-21 07:53:24 -04:00
assert_raises_rpc_error ( - 4 , " Unable to create transaction. Insufficient funds " , rbf_node . bumpfee , rbfid )
2020-08-21 12:23:06 -04:00
self . clear_mempool ( )
2016-12-09 15:45:27 -03:00
2020-03-13 19:11:55 -03:00
2023-04-15 20:47:29 -04:00
def test_feerate_checks_replaced_outputs ( self , rbf_node , peer_node ) :
# Make sure there is enough balance
peer_node . sendtoaddress ( rbf_node . getnewaddress ( ) , 60 )
self . generate ( peer_node , 1 )
2023-03-22 19:47:54 -03:00
self . log . info ( " Test that feerate checks use replaced outputs " )
outputs = [ ]
for i in range ( 50 ) :
outputs . append ( { rbf_node . getnewaddress ( address_type = " bech32 " ) : 1 } )
tx_res = rbf_node . send ( outputs = outputs , fee_rate = 5 )
tx_details = rbf_node . gettransaction ( txid = tx_res [ " txid " ] , verbose = True )
# Calculate the minimum feerate required for the bump to work.
# Since the bumped tx will replace all of the outputs with a single output, we can estimate that its size will 31 * (len(outputs) - 1) bytes smaller
tx_size = tx_details [ " decoded " ] [ " vsize " ]
est_bumped_size = tx_size - ( len ( tx_details [ " decoded " ] [ " vout " ] ) - 1 ) * 31
2023-06-25 11:54:35 -04:00
inc_fee_rate = rbf_node . getmempoolinfo ( ) [ " incrementalrelayfee " ]
2023-03-22 19:47:54 -03:00
# RPC gives us fee as negative
min_fee = ( - tx_details [ " fee " ] + get_fee ( est_bumped_size , inc_fee_rate ) ) * Decimal ( 1e8 )
min_fee_rate = ( min_fee / est_bumped_size ) . quantize ( Decimal ( " 1.000 " ) )
# Attempt to bumpfee and replace all outputs with a single one using a feerate slightly less than the minimum
new_outputs = [ { rbf_node . getnewaddress ( address_type = " bech32 " ) : 49 } ]
assert_raises_rpc_error ( - 8 , " Insufficient total fee " , rbf_node . bumpfee , tx_res [ " txid " ] , { " fee_rate " : min_fee_rate - 1 , " outputs " : new_outputs } )
# Bumpfee and replace all outputs with a single one using the minimum feerate
rbf_node . bumpfee ( tx_res [ " txid " ] , { " fee_rate " : min_fee_rate , " outputs " : new_outputs } )
2023-04-15 20:47:29 -04:00
self . clear_mempool ( )
2023-03-22 19:47:54 -03:00
2023-06-26 17:06:25 -04:00
def test_bumpfee_with_feerate_ignores_walletincrementalrelayfee ( self , rbf_node , peer_node ) :
self . log . info ( ' Test that bumpfee with fee_rate ignores walletincrementalrelayfee ' )
# Make sure there is enough balance
peer_node . sendtoaddress ( rbf_node . getnewaddress ( ) , 2 )
self . generate ( peer_node , 1 )
dest_address = peer_node . getnewaddress ( address_type = " bech32 " )
tx = rbf_node . send ( outputs = [ { dest_address : 1 } ] , fee_rate = 2 )
# Ensure you can not fee bump with a fee_rate below or equal to the original fee_rate
assert_raises_rpc_error ( - 8 , " Insufficient total fee " , rbf_node . bumpfee , tx [ " txid " ] , { " fee_rate " : 1 } )
assert_raises_rpc_error ( - 8 , " Insufficient total fee " , rbf_node . bumpfee , tx [ " txid " ] , { " fee_rate " : 2 } )
# Ensure you can not fee bump if the fee_rate is more than original fee_rate but the total fee from new fee_rate is
# less than (original fee + incrementalrelayfee)
assert_raises_rpc_error ( - 8 , " Insufficient total fee " , rbf_node . bumpfee , tx [ " txid " ] , { " fee_rate " : 2.8 } )
Fix lint-spelling warnings
These warnings were often polluting the CI output, e.g. https://github.com/bitcoin/bitcoin/pull/30499/checks?check_run_id=27745036545
> ./test/lint/lint-spelling.py
before the change:
```
doc/design/libraries.md:100: targetted ==> targeted
doc/developer-notes.md:495: dependant ==> dependent
src/bench/sign_transaction.cpp:49: hashIn ==> hashing, hash in
src/bitcoin-chainstate.cpp:213: hashIn ==> hashing, hash in
src/bitcoin-chainstate.cpp:213: hashIn ==> hashing, hash in
src/coins.cpp:24: viewIn ==> viewing, view in
src/coins.cpp:24: viewIn ==> viewing, view in
src/coins.cpp:29: viewIn ==> viewing, view in
src/coins.cpp:29: viewIn ==> viewing, view in
src/coins.h:44: outIn ==> outing, out in
src/coins.h:44: outIn ==> outing, out in
src/coins.h:45: outIn ==> outing, out in
src/coins.h:45: outIn ==> outing, out in
src/coins.h:215: viewIn ==> viewing, view in
src/coins.h:220: viewIn ==> viewing, view in
src/primitives/transaction.h:37: hashIn ==> hashing, hash in
src/primitives/transaction.h:37: hashIn ==> hashing, hash in
src/protocol.cpp:51: hashIn ==> hashing, hash in
src/protocol.cpp:51: hashIn ==> hashing, hash in
src/protocol.h:497: hashIn ==> hashing, hash in
src/qt/forms/optionsdialog.ui:344: incomin ==> incoming
src/qt/optionsdialog.cpp:445: proxys ==> proxies
src/rpc/mining.cpp:987: hashIn ==> hashing, hash in
src/rpc/mining.cpp:987: hashIn ==> hashing, hash in
src/script/interpreter.h:298: amountIn ==> amounting, amount in
src/script/interpreter.h:298: amountIn ==> amounting, amount in
src/script/interpreter.h:299: amountIn ==> amounting, amount in
src/script/interpreter.h:299: amountIn ==> amounting, amount in
src/script/sigcache.h:70: amountIn ==> amounting, amount in
src/script/sigcache.h:70: amountIn ==> amounting, amount in
src/signet.cpp:144: amountIn ==> amounting, amount in
src/test/fuzz/util/net.cpp:386: occured ==> occurred
src/test/fuzz/util/net.cpp:398: occured ==> occurred
src/util/vecdeque.h:79: deques ==> dequeues
src/util/vecdeque.h:160: deques ==> dequeues
src/util/vecdeque.h:184: deques ==> dequeues
src/util/vecdeque.h:194: deques ==> dequeues
src/validation.cpp:2130: re-declared ==> redeclared
src/validation.h:348: outIn ==> outing, out in
src/validation.h:349: outIn ==> outing, out in
test/functional/wallet_bumpfee.py:851: atleast ==> at least
```
2024-07-22 07:56:24 -04:00
# You can fee bump as long as the new fee set from fee_rate is at least (original fee + incrementalrelayfee)
2023-06-26 17:06:25 -04:00
rbf_node . bumpfee ( tx [ " txid " ] , { " fee_rate " : 3 } )
self . clear_mempool ( )
2016-12-09 15:45:27 -03:00
if __name__ == " __main__ " :
2024-07-16 17:05:14 -04:00
BumpFeeTest ( __file__ ) . main ( )