mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-12-04 17:52:25 +01:00
Merge #15557: Enhance bumpfee to include inputs when targeting a feerate
184f8785fwallet_bumpfee.py: add test for change key preservation (Gregory Sanders)d08becff8add functional tests for feerate bumpfee with adding inputs (Gregory Sanders)0ea47ba7bgeneralize bumpfee to add inputs when needed (Gregory Sanders) Pull request description: When targeting a feerate using `bumpfee`, call a new function that directly uses `CWallet::CreateTransaction` and coin control to get the desired result. This allows us to get a superset of previous behavior, with an arbitrary RBF bump of a transaction provided it passes the preconditional checks and spare confirmed utxos are available. Note(s): 0) The coin selection will use knapsack solver for the residual selection. 1) This functionality, just like knapsack coin selection in general, will hoover up negative-value inputs when given the chance. 2) Newly added inputs must be confirmed due to current Core policy. See error: `replacement-adds-unconfirmed` 3) Supporting this with `totalFee` is difficult since the "minimum total fee" option in `CreateTransaction` logic was (rightly)taken out in #10390 . ACKs for commit 184f87: jnewbery: utACK184f8785f7Tree-SHA512: fb6542bdfb2c6010e328ec475cf9dcbff4eb2b1a1b27f78010214534908987a5635797196fa05edddffcbcf2987335872dc644a99261886d5cbb34a8f262ad3e
This commit is contained in:
@@ -79,6 +79,10 @@ class BumpFeeTest(BitcoinTestFramework):
|
||||
test_unconfirmed_not_spendable(rbf_node, rbf_node_address)
|
||||
test_bumpfee_metadata(rbf_node, dest_address)
|
||||
test_locked_wallet_fails(rbf_node, dest_address)
|
||||
test_change_script_match(rbf_node, dest_address)
|
||||
# These tests wipe out a number of utxos that are expected in other tests
|
||||
test_small_output_with_feerate_succeeds(rbf_node, dest_address)
|
||||
test_no_more_inputs_fails(rbf_node, dest_address)
|
||||
self.log.info("Success")
|
||||
|
||||
|
||||
@@ -179,6 +183,40 @@ def test_small_output_fails(rbf_node, dest_address):
|
||||
rbfid = spend_one_input(rbf_node, dest_address)
|
||||
assert_raises_rpc_error(-4, "Change output is too small", rbf_node.bumpfee, rbfid, {"totalFee": 50001})
|
||||
|
||||
def test_small_output_with_feerate_succeeds(rbf_node, dest_address):
|
||||
|
||||
# Make sure additional inputs exist
|
||||
rbf_node.generatetoaddress(101, rbf_node.getnewaddress())
|
||||
rbfid = spend_one_input(rbf_node, dest_address)
|
||||
original_input_list = rbf_node.getrawtransaction(rbfid, 1)["vin"]
|
||||
assert_equal(len(original_input_list), 1)
|
||||
original_txin = original_input_list[0]
|
||||
# Keep bumping until we out-spend change output
|
||||
tx_fee = 0
|
||||
while tx_fee < Decimal("0.0005"):
|
||||
new_input_list = rbf_node.getrawtransaction(rbfid, 1)["vin"]
|
||||
new_item = list(new_input_list)[0]
|
||||
assert_equal(len(original_input_list), 1)
|
||||
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
|
||||
tx_fee = rbfid_new_details["origfee"]
|
||||
|
||||
# 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"]]
|
||||
|
||||
rbf_node.generatetoaddress(1, rbf_node.getnewaddress())
|
||||
assert_equal(rbf_node.gettransaction(rbfid)["confirmations"], 1)
|
||||
|
||||
def test_dust_to_fee(rbf_node, dest_address):
|
||||
# check that if output is reduced to dust, it will be converted to fee
|
||||
@@ -278,19 +316,37 @@ def test_locked_wallet_fails(rbf_node, dest_address):
|
||||
rbf_node.walletlock()
|
||||
assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first.",
|
||||
rbf_node.bumpfee, rbfid)
|
||||
rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)
|
||||
|
||||
def test_change_script_match(rbf_node, dest_address):
|
||||
"""Test that the same change addresses is used for the replacement transaction when possible."""
|
||||
def get_change_address(tx):
|
||||
tx_details = rbf_node.getrawtransaction(tx, 1)
|
||||
txout_addresses = [txout['scriptPubKey']['addresses'][0] for txout in tx_details["vout"]]
|
||||
return [address for address in txout_addresses if rbf_node.getaddressinfo(address)["ischange"]]
|
||||
|
||||
def spend_one_input(node, dest_address):
|
||||
# Check that there is only one change output
|
||||
rbfid = spend_one_input(rbf_node, dest_address)
|
||||
change_addresses = get_change_address(rbfid)
|
||||
assert_equal(len(change_addresses), 1)
|
||||
|
||||
# Now find that address in each subsequent tx, and no other change
|
||||
bumped_total_tx = rbf_node.bumpfee(rbfid, {"totalFee": 2000})
|
||||
assert_equal(change_addresses, get_change_address(bumped_total_tx['txid']))
|
||||
bumped_rate_tx = rbf_node.bumpfee(bumped_total_tx["txid"])
|
||||
assert_equal(change_addresses, get_change_address(bumped_rate_tx['txid']))
|
||||
|
||||
def spend_one_input(node, dest_address, change_size=Decimal("0.00049000")):
|
||||
tx_input = dict(
|
||||
sequence=BIP125_SEQUENCE_NUMBER, **next(u for u in node.listunspent() if u["amount"] == Decimal("0.00100000")))
|
||||
rawtx = node.createrawtransaction(
|
||||
[tx_input], {dest_address: Decimal("0.00050000"),
|
||||
node.getrawchangeaddress(): Decimal("0.00049000")})
|
||||
destinations = {dest_address: Decimal("0.00050000")}
|
||||
if change_size > 0:
|
||||
destinations[node.getrawchangeaddress()] = change_size
|
||||
rawtx = node.createrawtransaction([tx_input], destinations)
|
||||
signedtx = node.signrawtransactionwithwallet(rawtx)
|
||||
txid = node.sendrawtransaction(signedtx["hex"])
|
||||
return txid
|
||||
|
||||
|
||||
def submit_block_with_tx(node, tx):
|
||||
ctx = CTransaction()
|
||||
ctx.deserialize(io.BytesIO(hex_str_to_bytes(tx)))
|
||||
@@ -307,6 +363,12 @@ def submit_block_with_tx(node, tx):
|
||||
node.submitblock(block.serialize(True).hex())
|
||||
return block
|
||||
|
||||
def test_no_more_inputs_fails(rbf_node, dest_address):
|
||||
# feerate rbf requires confirmed outputs when change output doesn't exist or is insufficient
|
||||
rbf_node.generatetoaddress(1, dest_address)
|
||||
# spend all funds, no change output
|
||||
rbfid = rbf_node.sendtoaddress(rbf_node.getnewaddress(), rbf_node.getbalance(), "", "", True)
|
||||
assert_raises_rpc_error(-4, "Unable to create transaction: Insufficient funds", rbf_node.bumpfee, rbfid)
|
||||
|
||||
if __name__ == "__main__":
|
||||
BumpFeeTest().main()
|
||||
|
||||
Reference in New Issue
Block a user