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.
251txid = send_res["txid"]253tx = rbf_node.gettransaction(txid=txid, verbose=True)254assert_equal(len(tx["decoded"]["vout"]), 2)256change_pos = find_vout_for_address(rbf_node, txid, change_addr)257change_value = tx["decoded"]["vout"][change_pos]["value"]259bumped = rbf_node.bumpfee(txid, {"original_change_index": change_pos})260new_txid = bumped["txid"]262new_tx = rbf_node.gettransaction(txid=new_txid, verbose=True)263assert_equal(len(new_tx["decoded"]["vout"]), 2)264new_change_pos = find_vout_for_address(rbf_node, new_txid, change_addr)265new_change_value = new_tx["decoded"]["vout"][new_change_pos]["value"]267assert_greater_than(change_value, new_change_value)270def test_single_output(self):271self.log.info("Test that single output txs can be bumped")272node = self.nodes[1]274node.createwallet("single_out_rbf")275wallet = node.get_wallet_rpc("single_out_rbf")277addr = wallet.getnewaddress()278amount = Decimal("0.001")279# Make 2 UTXOs280self.nodes[0].sendtoaddress(addr, amount)281self.nodes[0].sendtoaddress(addr, amount)282self.generate(self.nodes[0], 1)283utxos = wallet.listunspent()285tx = wallet.sendall(recipients=[wallet.getnewaddress()], fee_rate=2, options={"inputs": [utxos[0]]})287# Set the only output with a crazy high feerate as change, should fail as the output would be dust288assert_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})290# Specify single output as change successfully291bumped = wallet.bumpfee(txid=tx["txid"], options={"fee_rate": 10, "original_change_index": 0})292bumped_tx = wallet.gettransaction(txid=bumped["txid"], verbose=True)293assert_equal(len(bumped_tx["decoded"]["vout"]), 1)294assert_equal(len(bumped_tx["decoded"]["vin"]), 1)295assert_equal(bumped_tx["decoded"]["vout"][0]["value"] + bumped["fee"], amount)296assert_fee_amount(bumped["fee"], bumped_tx["decoded"]["vsize"], Decimal(10) / Decimal(1e8) * 1000)298# Bumping without specifying change adds a new input and output299bumped = wallet.bumpfee(txid=bumped["txid"], options={"fee_rate": 20})300bumped_tx = wallet.gettransaction(txid=bumped["txid"], verbose=True)301assert_equal(len(bumped_tx["decoded"]["vout"]), 2)302assert_equal(len(bumped_tx["decoded"]["vin"]), 2)303assert_fee_amount(bumped["fee"], bumped_tx["decoded"]["vsize"], Decimal(20) / Decimal(1e8) * 1000)305wallet.unloadwallet()307def test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address):308self.log.info('Test simple bumpfee: {}'.format(mode))309rbfid = spend_one_input(rbf_node, dest_address)310rbftx = rbf_node.gettransaction(rbfid)311self.sync_mempools((rbf_node, peer_node))312assert rbfid in rbf_node.getrawmempool() and rbfid in peer_node.getrawmempool()313if mode == "fee_rate":314bumped_psbt = rbf_node.psbtbumpfee(rbfid, fee_rate=str(NORMAL))315bumped_tx = rbf_node.bumpfee(rbfid, fee_rate=NORMAL)316elif mode == "new_outputs":317new_address = peer_node.getnewaddress()318bumped_psbt = rbf_node.psbtbumpfee(rbfid, outputs={new_address: 0.0003})319bumped_tx = rbf_node.bumpfee(rbfid, outputs={new_address: 0.0003})320else:321bumped_psbt = rbf_node.psbtbumpfee(rbfid)322bumped_tx = rbf_node.bumpfee(rbfid)323assert_equal(bumped_tx["errors"], [])324assert bumped_tx["fee"] > -rbftx["fee"]325assert_equal(bumped_tx["origfee"], -rbftx["fee"])326assert "psbt" not in bumped_tx327assert_equal(bumped_psbt["errors"], [])328assert bumped_psbt["fee"] > -rbftx["fee"]329assert_equal(bumped_psbt["origfee"], -rbftx["fee"])330assert "psbt" in bumped_psbt331# check that bumped_tx propagates, original tx was evicted and has a wallet conflict332self.sync_mempools((rbf_node, peer_node))333assert bumped_tx["txid"] in rbf_node.getrawmempool()334assert bumped_tx["txid"] in peer_node.getrawmempool()335assert rbfid not in rbf_node.getrawmempool()336assert rbfid not in peer_node.getrawmempool()337oldwtx = rbf_node.gettransaction(rbfid)338assert len(oldwtx["walletconflicts"]) > 0339# check wallet transaction replaces and replaced_by values340bumpedwtx = rbf_node.gettransaction(bumped_tx["txid"])341assert_equal(oldwtx["replaced_by_txid"], bumped_tx["txid"])342assert_equal(bumpedwtx["replaces_txid"], rbfid)343# if this is a new_outputs test, check that outputs were indeed replaced344if mode == "new_outputs":345assert_equal(len(bumpedwtx["details"]), 1)346assert_equal(bumpedwtx["details"][0]["address"], new_address)347self.clear_mempool()350def test_segwit_bumpfee_succeeds(self, rbf_node, dest_address):351self.log.info('Test that segwit-sourcing bumpfee works')352# Create a transaction with segwit output, then create an RBF transaction353# which spends it, and make sure bumpfee can be called on it.355segwit_out = rbf_node.getnewaddress(address_type='bech32')356segwitid = rbf_node.send({segwit_out: "0.0009"}, options={"change_position": 1})["txid"]358rbfraw = rbf_node.createrawtransaction([{359'txid': segwitid,360'vout': 0,361"sequence": MAX_BIP125_RBF_SEQUENCE362}], {dest_address: Decimal("0.0005"),363rbf_node.getrawchangeaddress(): Decimal("0.0003")})364rbfsigned = rbf_node.signrawtransactionwithwallet(rbfraw)365rbfid = rbf_node.sendrawtransaction(rbfsigned["hex"])366assert rbfid in rbf_node.getrawmempool()368bumped_tx = rbf_node.bumpfee(rbfid)369assert bumped_tx["txid"] in rbf_node.getrawmempool()370assert rbfid not in rbf_node.getrawmempool()371self.clear_mempool()374def test_nonrbf_bumpfee_succeeds(self, peer_node, dest_address):375self.log.info("Test that we can replace a non RBF transaction")376not_rbfid = peer_node.sendtoaddress(dest_address, Decimal("0.00090000"))377peer_node.bumpfee(not_rbfid)378self.clear_mempool()381def test_notmine_bumpfee(self, rbf_node, peer_node, dest_address):382self.log.info('Test that it cannot bump fee if non-owned inputs are included')383# here, the rbftx has a peer_node coin and then adds a rbf_node input384# Note that this test depends upon the RPC code checking input ownership prior to change outputs385# (since it can't use fundrawtransaction, it lacks a proper change output)386fee = Decimal("0.001")387utxos = [node.listunspent(minimumAmount=fee)[-1] for node in (rbf_node, peer_node)]388inputs = [{389"txid": utxo["txid"],390"vout": utxo["vout"],391"address": utxo["address"],392"sequence": MAX_BIP125_RBF_SEQUENCE393} for utxo in utxos]394output_val = sum(utxo["amount"] for utxo in utxos) - fee395rawtx = rbf_node.createrawtransaction(inputs, {dest_address: output_val})396signedtx = rbf_node.signrawtransactionwithwallet(rawtx)397signedtx = peer_node.signrawtransactionwithwallet(signedtx["hex"])398rbfid = rbf_node.sendrawtransaction(signedtx["hex"])399entry = rbf_node.getmempoolentry(rbfid)400old_fee = entry["fees"]["base"]401old_feerate = int(old_fee / entry["vsize"] * Decimal(1e8))402assert_raises_rpc_error(-4, "Transaction contains inputs that don't belong to this wallet",403rbf_node.bumpfee, rbfid)405def finish_psbtbumpfee(psbt):406psbt = rbf_node.walletprocesspsbt(psbt)407psbt = peer_node.walletprocesspsbt(psbt["psbt"])408res = rbf_node.testmempoolaccept([psbt["hex"]])409assert res[0]["allowed"]410assert_greater_than(res[0]["fees"]["base"], old_fee)412self.log.info("Test that psbtbumpfee works for non-owned inputs")413psbt = rbf_node.psbtbumpfee(txid=rbfid)414finish_psbtbumpfee(psbt["psbt"])416psbt = rbf_node.psbtbumpfee(txid=rbfid, fee_rate=old_feerate + 10)417finish_psbtbumpfee(psbt["psbt"])419self.clear_mempool()422def test_bumpfee_with_descendant_fails(self, rbf_node, rbf_node_address, dest_address):423self.log.info('Test that fee cannot be bumped when it has descendant')424# parent is send-to-self, so we don't have to check which output is change when creating the child tx425parent_id = spend_one_input(rbf_node, rbf_node_address)426tx = rbf_node.createrawtransaction([{"txid": parent_id, "vout": 0}], {dest_address: 0.00020000})427tx = rbf_node.signrawtransactionwithwallet(tx)428rbf_node.sendrawtransaction(tx["hex"])429assert_raises_rpc_error(-8, "Transaction has descendants in the wallet", rbf_node.bumpfee, parent_id)431# create tx with descendant in the mempool by using MiniWallet432miniwallet = MiniWallet(rbf_node)433parent_id = spend_one_input(rbf_node, miniwallet.get_address())434tx = rbf_node.gettransaction(txid=parent_id, verbose=True)['decoded']435miniwallet.scan_tx(tx)436miniwallet.send_self_transfer(from_node=rbf_node)437assert_raises_rpc_error(-8, "Transaction has descendants in the mempool", rbf_node.bumpfee, parent_id)438self.clear_mempool()441def test_bumpfee_with_abandoned_descendant_succeeds(self, rbf_node, rbf_node_address, dest_address):442self.log.info('Test that fee can be bumped when it has abandoned descendant')443# parent is send-to-self, so we don't have to check which output is change when creating the child tx444parent_id = spend_one_input(rbf_node, rbf_node_address)445# Submit child transaction with low fee446child_id = rbf_node.send(outputs={dest_address: 0.00020000},447options={"inputs": [{"txid": parent_id, "vout": 0}], "fee_rate": 2})["txid"]448assert child_id in rbf_node.getrawmempool()450# Restart the node with higher min relay fee so the descendant tx is no longer in mempool so that we can abandon it451self.restart_node(1, ['-minrelaytxfee=0.00005'] + self.extra_args[1])452rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)453self.connect_nodes(1, 0)454assert parent_id in rbf_node.getrawmempool()455assert child_id not in rbf_node.getrawmempool()456# Should still raise an error even if not in mempool457assert_raises_rpc_error(-8, "Transaction has descendants in the wallet", rbf_node.bumpfee, parent_id)458# Now abandon the child transaction and bump the original459rbf_node.abandontransaction(child_id)460bumped_result = rbf_node.bumpfee(parent_id, {"fee_rate": HIGH})461assert bumped_result['txid'] in rbf_node.getrawmempool()462assert parent_id not in rbf_node.getrawmempool()463# Cleanup464self.restart_node(1, self.extra_args[1])465rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)466self.connect_nodes(1, 0)467self.clear_mempool()470def test_small_output_with_feerate_succeeds(self, rbf_node, dest_address):471self.log.info('Testing small output with feerate bump succeeds')473# Make sure additional inputs exist474self.generatetoaddress(rbf_node, COINBASE_MATURITY + 1, rbf_node.getnewaddress())475rbfid = spend_one_input(rbf_node, dest_address)476input_list = rbf_node.getrawtransaction(rbfid, 1)["vin"]477assert_equal(len(input_list), 1)478original_txin = input_list[0]479self.log.info('Keep bumping until transaction fee out-spends non-destination value')480tx_fee = 0481while True:482input_list = rbf_node.getrawtransaction(rbfid, 1)["vin"]483new_item = list(input_list)[0]484assert_equal(len(input_list), 1)485assert_equal(original_txin["txid"], new_item["txid"])486assert_equal(original_txin["vout"], new_item["vout"])487rbfid_new_details = rbf_node.bumpfee(rbfid)488rbfid_new = rbfid_new_details["txid"]489raw_pool = rbf_node.getrawmempool()490assert rbfid not in raw_pool491assert rbfid_new in raw_pool492rbfid = rbfid_new493tx_fee = rbfid_new_details["fee"]495# Total value from input not going to destination496if tx_fee > Decimal('0.00050000'):497break499# input(s) have been added500final_input_list = rbf_node.getrawtransaction(rbfid, 1)["vin"]
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.