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) 2014-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 RBF code."""7from decimal import Decimal9from test_framework.messages import (10MAX_BIP125_RBF_SEQUENCE,11COIN,12)13from test_framework.test_framework import BitcoinTestFramework14from test_framework.util import (15assert_equal,16assert_greater_than,17assert_greater_than_or_equal,18assert_raises_rpc_error,19get_fee,20)21from test_framework.wallet import MiniWallet22from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE23from test_framework.mempool_util import DEFAULT_CLUSTER_LIMIT25MAX_REPLACEMENT_LIMIT = 10027class ReplaceByFeeTest(BitcoinTestFramework):28def set_test_params(self):29self.num_nodes = 230self.uses_wallet = None31self.extra_args = [["-deprecatedrpc=fullrbf", "-deprecatedrpc=bip125"], []]33def run_test(self):34self.wallet = MiniWallet(self.nodes[0])35self.generate(self.nodes[0], 100)37self.log.info("Running test simple doublespend...")38self.test_simple_doublespend()40self.log.info("Running test doublespend chain...")41self.test_doublespend_chain()43self.log.info("Running test doublespend tree...")44self.test_doublespend_tree()46self.log.info("Running test replacement feeperkb...")47self.test_replacement_feeperkb()49self.log.info("Running test spends of conflicting outputs...")50self.test_spends_of_conflicting_outputs()52self.log.info("Running test new unconfirmed inputs...")53self.test_new_unconfirmed_inputs()55self.log.info("Running test new unconfirmed input from low feerate tx...")56self.test_new_unconfirmed_input_with_low_feerate()58self.log.info("Running test too many replacements...")59self.test_too_many_replacements()61self.log.info("Running test RPC...")62self.test_rpc()64self.log.info("Running test prioritised transactions...")65self.test_prioritised_transactions()67self.log.info("Running test replacement relay fee...")68self.test_replacement_relay_fee()70self.log.info("Running test full replace by fee...")71self.test_fullrbf()73self.log.info("Running test incremental relay feerates...")74self.test_incremental_relay_feerates()76self.log.info("Passed")78def make_utxo(self, node, amount, *, confirmed=True, scriptPubKey=None):79"""Create a txout with a given amount and scriptPubKey81confirmed - txout created will be confirmed in the blockchain;82unconfirmed otherwise.83"""84tx = self.wallet.send_to(from_node=node, scriptPubKey=scriptPubKey or self.wallet.get_output_script(), amount=amount)86if confirmed:87mempool_size = len(node.getrawmempool())88while mempool_size > 0:89self.generate(node, 1)90new_size = len(node.getrawmempool())91# Error out if we have something stuck in the mempool, as this92# would likely be a bug.93assert new_size < mempool_size94mempool_size = new_size96return self.wallet.get_utxo(txid=tx["txid"], vout=tx["sent_vout"])98def test_simple_doublespend(self):99"""Simple doublespend"""100# we use MiniWallet to create a transaction template with inputs correctly set,101# and modify the output (amount, scriptPubKey) according to our needs102tx = self.wallet.create_self_transfer(fee_rate=Decimal("0.003"))["tx"]103tx1a_txid = self.nodes[0].sendrawtransaction(tx.serialize().hex())105# Should fail because we haven't changed the fee106tx.vout[0].scriptPubKey[-1] ^= 1107tx_hex = tx.serialize().hex()109# This will raise an exception due to insufficient fee110reject_reason = "insufficient fee"111reject_details = f"{reject_reason}, rejecting replacement {tx.txid_hex}, not enough additional fees to relay; 0.00 < 0.00000011"112res = self.nodes[0].testmempoolaccept(rawtxs=[tx_hex])[0]113assert_equal(res["reject-reason"], reject_reason)114assert_equal(res["reject-details"], reject_details)115assert_raises_rpc_error(-26, f"{reject_details}", self.nodes[0].sendrawtransaction, tx_hex, 0)118# Extra 0.1 BTC fee119tx.vout[0].nValue -= int(0.1 * COIN)120tx1b_hex = tx.serialize().hex()121# Works when enabled122tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0)124mempool = self.nodes[0].getrawmempool()126assert tx1a_txid not in mempool127assert tx1b_txid in mempool129assert_equal(tx1b_hex, self.nodes[0].getrawtransaction(tx1b_txid))131def test_doublespend_chain(self):132"""Doublespend of a long chain"""134initial_nValue = 5 * COIN135tx0_outpoint = self.make_utxo(self.nodes[0], initial_nValue)137prevout = tx0_outpoint138remaining_value = initial_nValue139chain_txids = []140for _ in range(DEFAULT_CLUSTER_LIMIT):141if remaining_value <= 1 * COIN:142break143remaining_value -= int(0.1 * COIN)144prevout = self.wallet.send_self_transfer(145from_node=self.nodes[0],146utxo_to_spend=prevout,147sequence=0,148fee=Decimal("0.1"),149)["new_utxo"]150chain_txids.append(prevout["txid"])152# Whether the double-spend is allowed is evaluated by including all153# child fees - 4 BTC - so this attempt is rejected.154dbl_tx = self.wallet.create_self_transfer(155utxo_to_spend=tx0_outpoint,156sequence=0,157fee=Decimal("3"),158)["tx"]159dbl_tx_hex = dbl_tx.serialize().hex()161# This will raise an exception due to insufficient fee162reject_reason = "insufficient fee"163reject_details = f"{reject_reason}, rejecting replacement {dbl_tx.txid_hex}, less fees than conflicting txs; 3.00 < 4.00"164res = self.nodes[0].testmempoolaccept(rawtxs=[dbl_tx_hex])[0]165assert_equal(res["reject-reason"], reject_reason)166assert_equal(res["reject-details"], reject_details)167assert_raises_rpc_error(-26, f"{reject_details}", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0)171# Accepted with sufficient fee172dbl_tx.vout[0].nValue = int(0.1 * COIN)173dbl_tx_hex = dbl_tx.serialize().hex()174self.nodes[0].sendrawtransaction(dbl_tx_hex, 0)176mempool = self.nodes[0].getrawmempool()177for doublespent_txid in chain_txids:178assert doublespent_txid not in mempool180def test_doublespend_tree(self):181"""Doublespend of a big tree of transactions"""183initial_nValue = 5 * COIN184tx0_outpoint = self.make_utxo(self.nodes[0], initial_nValue)186def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.00001 * COIN, _total_txs=None):187if _total_txs is None:188_total_txs = [0]189if _total_txs[0] >= max_txs:190return192txout_value = (initial_value - fee) // tree_width193if txout_value < fee:194return196tx = self.wallet.send_self_transfer_multi(197utxos_to_spend=[prevout],198from_node=self.nodes[0],199sequence=0,200num_outputs=tree_width,201amount_per_output=txout_value,202)204yield tx["txid"]205_total_txs[0] += 1207for utxo in tx["new_utxos"]:208for x in branch(utxo, txout_value,209max_txs,210tree_width=tree_width, fee=fee,211_total_txs=_total_txs):212yield x214fee = int(0.00001 * COIN)215n = DEFAULT_CLUSTER_LIMIT216tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))217assert_equal(len(tree_txs), n)219# Attempt double-spend, will fail because too little fee paid220dbl_tx_hex = self.wallet.create_self_transfer(221utxo_to_spend=tx0_outpoint,222sequence=0,223fee=(Decimal(fee) / COIN) * n,224)["hex"]225# This will raise an exception due to insufficient fee226assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0)228# 0.1 BTC fee is enough229dbl_tx_hex = self.wallet.create_self_transfer(230utxo_to_spend=tx0_outpoint,231sequence=0,232fee=(Decimal(fee) / COIN) * n + Decimal("0.1"),233)["hex"]234self.nodes[0].sendrawtransaction(dbl_tx_hex, 0)236mempool = self.nodes[0].getrawmempool()238for txid in tree_txs:239assert txid not in mempool241def test_replacement_feeperkb(self):242"""Replacement requires fee-per-KB to be higher"""243tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))245self.wallet.send_self_transfer(246from_node=self.nodes[0],247utxo_to_spend=tx0_outpoint,248sequence=0,249fee=Decimal("0.1"),250)
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.