Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1#!/usr/bin/env python32# Copyright (c) 2016-present The Bitcoin Core developers3# Distributed under the MIT software license, see the accompanying4# file COPYING or http://www.opensource.org/licenses/mit-license.php.5"""Test the bumpfee RPC.7Verifies that the bumpfee RPC creates replacement transactions successfully when8its preconditions are met, and returns appropriate errors in other cases.10This module consists of around a dozen individual test cases implemented in the11top-level functions named as test_<test_case_description>. The test functions12can be disabled or reordered if needed for debugging. If new test cases are13added in the future, they should try to follow the same convention and not14make assumptions about execution order.15"""16from decimal import Decimal18from test_framework.blocktools import (19COINBASE_MATURITY,20)21from test_framework.descriptors import descsum_create22from test_framework.extendedkey import ExtendedPrivateKey23from test_framework.messages import (24MAX_BIP125_RBF_SEQUENCE,25MAX_SEQUENCE_NONFINAL,26)27from test_framework.test_framework import BitcoinTestFramework28from test_framework.util import (29assert_equal,30assert_fee_amount,31assert_greater_than,32assert_raises_rpc_error,33get_fee,34find_vout_for_address,35)36from test_framework.wallet import MiniWallet39WALLET_PASSPHRASE = "test"40WALLET_PASSPHRASE_TIMEOUT = 360042# Fee rates (sat/vB)43INSUFFICIENT = 144ECONOMICAL = 5045NORMAL = 10046HIGH = 50047TOO_HIGH = 10000049def get_change_address(tx, node):50tx_details = node.getrawtransaction(tx, 1)51txout_addresses = [txout['scriptPubKey']['address'] for txout in tx_details["vout"]]52return [address for address in txout_addresses if node.getaddressinfo(address)["ischange"]]54class BumpFeeTest(BitcoinTestFramework):55def set_test_params(self):56self.num_nodes = 257self.setup_clean_chain = True58# whitelist peers to speed up tx relay / mempool sync59self.noban_tx_relay = True60self.extra_args = [[61"-mintxfee=0.00002",62"-addresstype=bech32",63] for i in range(self.num_nodes)]65def skip_test_if_missing_module(self):66self.skip_if_no_wallet()68def clear_mempool(self):69# Clear mempool between subtests. The subtests may only depend on chainstate (utxos)70self.generate(self.nodes[1], 1)72def run_test(self):73# Encrypt wallet for test_locked_wallet_fails test74self.nodes[1].encryptwallet(WALLET_PASSPHRASE)75self.nodes[1].walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)77peer_node, rbf_node = self.nodes78rbf_node_address = rbf_node.getnewaddress()80# fund rbf node with 10 coins of 0.001 btc (100,000 satoshis)81self.log.info("Mining blocks...")82self.generate(peer_node, 110)83for _ in range(25):84peer_node.sendtoaddress(rbf_node_address, 0.001)85self.sync_all()86self.generate(peer_node, 1)87assert_equal(rbf_node.getbalance(), Decimal("0.025"))89self.log.info("Running tests")90dest_address = peer_node.getnewaddress()91for mode in ["default", "fee_rate", "new_outputs"]:92test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address)93self.test_invalid_parameters(rbf_node, peer_node, dest_address)94test_segwit_bumpfee_succeeds(self, rbf_node, dest_address)95test_nonrbf_bumpfee_succeeds(self, peer_node, dest_address)96test_notmine_bumpfee(self, rbf_node, peer_node, dest_address)97test_bumpfee_with_descendant_fails(self, rbf_node, rbf_node_address, dest_address)98test_bumpfee_with_abandoned_descendant_succeeds(self, rbf_node, rbf_node_address, dest_address)99test_dust_to_fee(self, rbf_node, dest_address)100test_watchonly_psbt(self, peer_node, rbf_node, dest_address)101test_rebumping(self, rbf_node, dest_address)102test_rebumping_not_replaceable(self, rbf_node, dest_address)103test_bumpfee_already_spent(self, rbf_node, dest_address)104test_unconfirmed_not_spendable(self, rbf_node, rbf_node_address)105test_bumpfee_metadata(self, rbf_node, dest_address)106test_locked_wallet_fails(self, rbf_node, dest_address)107test_change_script_match(self, rbf_node, dest_address)108test_maxtxfee_fails(self, rbf_node, dest_address)109# These tests wipe out a number of utxos that are expected in other tests110test_small_output_with_feerate_succeeds(self, rbf_node, dest_address)111test_no_more_inputs_fails(self, rbf_node, dest_address)112self.test_bump_back_to_yourself()113self.test_provided_change_pos(rbf_node)114self.test_single_output()116# Context independent tests117test_feerate_checks_replaced_outputs(self, rbf_node, peer_node)118test_bumpfee_with_feerate_ignores_walletincrementalrelayfee(self, rbf_node, peer_node)120def test_invalid_parameters(self, rbf_node, peer_node, dest_address):121self.log.info('Test invalid parameters')122rbfid = spend_one_input(rbf_node, dest_address)123self.sync_mempools((rbf_node, peer_node))124assert rbfid in rbf_node.getrawmempool() and rbfid in peer_node.getrawmempool()126for key in ["totalFee", "feeRate"]:127assert_raises_rpc_error(-3, "Unexpected key {}".format(key), rbf_node.bumpfee, rbfid, {key: NORMAL})129# Bumping to just above minrelay should fail to increase the total fee enough.130assert_raises_rpc_error(-8, "Insufficient total fee 0.00000141", rbf_node.bumpfee, rbfid, fee_rate=INSUFFICIENT)132self.log.info("Test invalid fee rate settings")133assert_raises_rpc_error(-4, "Specified or calculated fee 0.141 is too high (cannot be higher than -maxtxfee 0.10",134rbf_node.bumpfee, rbfid, fee_rate=TOO_HIGH)135# Test fee_rate with zero values.136msg = "Insufficient total fee 0.00"137for zero_value in [0, 0.000, 0.00000000, "0", "0.000", "0.00000000"]:138assert_raises_rpc_error(-8, msg, rbf_node.bumpfee, rbfid, fee_rate=zero_value)139msg = "Invalid amount"140# Test fee_rate values that don't pass fixed-point parsing checks.141if not self.options.usecli:142for invalid_value in ["", 0.000000001, 1e-09, 1.111111111, 1111111111111111, "31.999999999999999999999"]:143assert_raises_rpc_error(-3, msg, rbf_node.bumpfee, rbfid, fee_rate=invalid_value)144# Test fee_rate values that cannot be represented in sat/vB.145for invalid_value in [0.0001, 0.00000001, 0.00099999, 31.99999999]:146assert_raises_rpc_error(-3, msg, rbf_node.bumpfee, rbfid, fee_rate=invalid_value)147# Test fee_rate out of range (negative number).148assert_raises_rpc_error(-3, "Amount out of range", rbf_node.bumpfee, rbfid, fee_rate=-1)149# Test type error.150for value in [{"foo": "bar"}, True]:151assert_raises_rpc_error(-3, "Amount is not a number or string", rbf_node.bumpfee, rbfid, fee_rate=value)153self.log.info("Test explicit fee rate raises RPC error if both fee_rate and conf_target are passed")154assert_raises_rpc_error(-8, "Cannot specify both conf_target and fee_rate. Please provide either a confirmation "155"target in blocks for automatic fee estimation, or an explicit fee rate.",156rbf_node.bumpfee, rbfid, conf_target=NORMAL, fee_rate=NORMAL)158self.log.info("Test explicit fee rate raises RPC error if both fee_rate and estimate_mode are passed")159assert_raises_rpc_error(-8, "Cannot specify both estimate_mode and fee_rate",160rbf_node.bumpfee, rbfid, estimate_mode="economical", fee_rate=NORMAL)162self.log.info("Test invalid conf_target settings")163assert_raises_rpc_error(-8, "confTarget and conf_target options should not both be set",164rbf_node.bumpfee, rbfid, {"confTarget": 123, "conf_target": 456})166self.log.info("Test invalid estimate_mode settings")167if not self.options.usecli:168for k, v in {"number": 42, "object": {"foo": "bar"}}.items():169assert_raises_rpc_error(-3, f"JSON value of type {k} for field estimate_mode is not of expected type string",170rbf_node.bumpfee, rbfid, estimate_mode=v)171for mode in ["foo", Decimal("3.1415"), "sat/B", "BTC/kB"]:172assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"',173rbf_node.bumpfee, rbfid, estimate_mode=mode)175self.log.info("Test invalid outputs values")176assert_raises_rpc_error(-8, "Invalid parameter, output argument cannot be an empty array",177rbf_node.bumpfee, rbfid, {"outputs": []})178assert_raises_rpc_error(-8, "Invalid parameter, duplicated address: " + dest_address,179rbf_node.bumpfee, rbfid, {"outputs": [{dest_address: 0.1}, {dest_address: 0.2}]})180assert_raises_rpc_error(-8, "Invalid parameter, duplicate key: data",181rbf_node.bumpfee, rbfid, {"outputs": [{"data": "deadbeef"}, {"data": "deadbeef"}]})183self.log.info("Test original_change_index option")184assert_raises_rpc_error(-1, "JSON integer out of range", rbf_node.bumpfee, rbfid, {"original_change_index": -1})185assert_raises_rpc_error(-8, "Change position is out of range", rbf_node.bumpfee, rbfid, {"original_change_index": 2})187self.log.info("Test outputs and original_change_index cannot both be provided")188assert_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}]})190self.clear_mempool()192def test_bump_back_to_yourself(self):193self.log.info("Test that bumpfee can send coins back to yourself")194node = self.nodes[1]196node.createwallet("back_to_yourself")197wallet = node.get_wallet_rpc("back_to_yourself")199# Make 3 UTXOs200addr = wallet.getnewaddress()201for _ in range(3):202self.nodes[0].sendtoaddress(addr, 5)203self.generate(self.nodes[0], 1)205# Create a tx with two outputs. recipient and change.206tx = wallet.send(outputs={wallet.getnewaddress(): 9}, fee_rate=2)207tx_info = wallet.gettransaction(txid=tx["txid"], verbose=True)208assert_equal(len(tx_info["decoded"]["vout"]), 2)209assert_equal(len(tx_info["decoded"]["vin"]), 2)211# Bump tx, send coins back to change address.212change_addr = get_change_address(tx["txid"], wallet)[0]213out_amount = 10214bumped = wallet.bumpfee(txid=tx["txid"], options={"fee_rate": 20, "outputs": [{change_addr: out_amount}]})215bumped_tx = wallet.gettransaction(txid=bumped["txid"], verbose=True)216assert_equal(len(bumped_tx["decoded"]["vout"]), 1)217assert_equal(len(bumped_tx["decoded"]["vin"]), 2)218assert_equal(bumped_tx["decoded"]["vout"][0]["value"] + bumped["fee"], out_amount)220# Bump tx again, now test send fewer coins back to change address.221out_amount = 6222bumped = wallet.bumpfee(txid=bumped["txid"], options={"fee_rate": 40, "outputs": [{change_addr: out_amount}]})223bumped_tx = wallet.gettransaction(txid=bumped["txid"], verbose=True)224assert_equal(len(bumped_tx["decoded"]["vout"]), 2)225assert_equal(len(bumped_tx["decoded"]["vin"]), 2)226assert any(txout['value'] == out_amount - bumped["fee"] and txout['scriptPubKey']['address'] == change_addr for txout in bumped_tx['decoded']['vout'])227# Check that total out amount is still equal to the previously bumped tx228assert_equal(bumped_tx["decoded"]["vout"][0]["value"] + bumped_tx["decoded"]["vout"][1]["value"] + bumped["fee"], 10)230# Bump tx again, send more coins back to change address. The process will add another input to cover the target.231out_amount = 12232bumped = wallet.bumpfee(txid=bumped["txid"], options={"fee_rate": 80, "outputs": [{change_addr: out_amount}]})233bumped_tx = wallet.gettransaction(txid=bumped["txid"], verbose=True)234assert_equal(len(bumped_tx["decoded"]["vout"]), 2)235assert_equal(len(bumped_tx["decoded"]["vin"]), 3)236assert any(txout['value'] == out_amount - bumped["fee"] and txout['scriptPubKey']['address'] == change_addr for txout in bumped_tx['decoded']['vout'])237assert_equal(bumped_tx["decoded"]["vout"][0]["value"] + bumped_tx["decoded"]["vout"][1]["value"] + bumped["fee"], 15)239node.unloadwallet("back_to_yourself")241def test_provided_change_pos(self, rbf_node):242self.log.info("Test the original_change_index option")244change_addr = rbf_node.getnewaddress()245dest_addr = rbf_node.getnewaddress()246assert_equal(rbf_node.getaddressinfo(change_addr)["ischange"], False)247assert_equal(rbf_node.getaddressinfo(dest_addr)["ischange"], False)249send_res = rbf_node.send(outputs=[{dest_addr: 1}], options={"change_address": change_addr})250assert send_res["complete"]
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.