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) 2022-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.6from decimal import Decimal, getcontext8from test_framework.test_framework import BitcoinTestFramework9from test_framework.util import (10assert_greater_than_or_equal,11assert_equal,12find_vout_for_address,13)15class UnconfirmedInputTest(BitcoinTestFramework):16def set_test_params(self):17getcontext().prec=918self.setup_clean_chain = True19self.num_nodes = 121def setup_and_fund_wallet(self, walletname):22self.nodes[0].createwallet(walletname)23wallet = self.nodes[0].get_wallet_rpc(walletname)25self.def_wallet.sendtoaddress(address=wallet.getnewaddress(), amount=2)26self.generate(self.nodes[0], 1) # confirm funding tx27return wallet29def skip_test_if_missing_module(self):30self.skip_if_no_wallet()32def calc_fee_rate(self, tx):33fee = Decimal(-1e8) * tx["fee"]34vsize = tx["decoded"]["vsize"]35return fee / vsize37def calc_set_fee_rate(self, txs):38fees = Decimal(-1e8) * sum([tx["fee"] for tx in txs]) # fee is negative!39vsizes = sum([tx["decoded"]["vsize"] for tx in txs])40return fees / vsizes42def assert_spends_only_parents(self, tx, parent_txids):43parent_checklist = parent_txids.copy()44number_inputs = len(tx["decoded"]["vin"])45assert_equal(number_inputs, len(parent_txids))46for i in range(number_inputs):47txid_of_input = tx["decoded"]["vin"][i]["txid"]48assert txid_of_input in parent_checklist49parent_checklist.remove(txid_of_input)51def assert_undershoots_target(self, tx):52resulting_fee_rate = self.calc_fee_rate(tx)53assert_greater_than_or_equal(self.target_fee_rate, resulting_fee_rate)55def assert_beats_target(self, tx):56resulting_fee_rate = self.calc_fee_rate(tx)57assert_greater_than_or_equal(resulting_fee_rate, self.target_fee_rate)59# Meta-Test: try feerate testing function on confirmed UTXO60def test_target_feerate_confirmed(self):61self.log.info("Start test feerate with confirmed input")62wallet = self.setup_and_fund_wallet("confirmed_wallet")64ancestor_aware_txid = wallet.sendtoaddress(address=self.def_wallet.getnewaddress(), amount=0.5, fee_rate=self.target_fee_rate)65ancestor_aware_tx = wallet.gettransaction(txid=ancestor_aware_txid, verbose=True)66self.assert_beats_target(ancestor_aware_tx)68wallet.unloadwallet()70# Spend unconfirmed UTXO from high-feerate parent71def test_target_feerate_unconfirmed_high(self):72self.log.info("Start test feerate with high feerate unconfirmed input")73wallet = self.setup_and_fund_wallet("unconfirmed_high_wallet")75# Send unconfirmed transaction with high feerate to testing wallet76parent_txid = wallet.sendtoaddress(address=wallet.getnewaddress(), amount=1, fee_rate=3*self.target_fee_rate)77parent_tx = wallet.gettransaction(txid=parent_txid, verbose=True)78self.assert_beats_target(parent_tx)80ancestor_aware_txid = wallet.sendtoaddress(address=self.def_wallet.getnewaddress(), amount=0.5, fee_rate=self.target_fee_rate)81ancestor_aware_tx = wallet.gettransaction(txid=ancestor_aware_txid, verbose=True)83self.assert_spends_only_parents(ancestor_aware_tx, [parent_txid])85self.assert_beats_target(ancestor_aware_tx)87wallet.unloadwallet()89# Spend unconfirmed UTXO from low-feerate parent. Expect that parent gets90# bumped to target feerate.91def test_target_feerate_unconfirmed_low(self):92self.log.info("Start test feerate with low feerate unconfirmed input")93wallet = self.setup_and_fund_wallet("unconfirmed_low_wallet")95parent_txid = wallet.sendtoaddress(address=wallet.getnewaddress(), amount=1, fee_rate=1)96parent_tx = wallet.gettransaction(txid=parent_txid, verbose=True)98self.assert_undershoots_target(parent_tx)100ancestor_aware_txid = wallet.sendtoaddress(address=self.def_wallet.getnewaddress(), amount=0.5, fee_rate=self.target_fee_rate)101ancestor_aware_tx = wallet.gettransaction(txid=ancestor_aware_txid, verbose=True)103self.assert_spends_only_parents(ancestor_aware_tx, [parent_txid])105self.assert_beats_target(ancestor_aware_tx)106resulting_ancestry_fee_rate = self.calc_set_fee_rate([parent_tx, ancestor_aware_tx])107assert_greater_than_or_equal(resulting_ancestry_fee_rate, self.target_fee_rate)108assert_greater_than_or_equal(self.target_fee_rate*1.01, resulting_ancestry_fee_rate)110wallet.unloadwallet()112# Spend UTXO with unconfirmed low feerate parent and grandparent113# txs. Expect that both ancestors get bumped to target feerate.114def test_chain_of_unconfirmed_low(self):115self.log.info("Start test with parent and grandparent tx")116wallet = self.setup_and_fund_wallet("unconfirmed_low_chain_wallet")118grandparent_txid = wallet.sendtoaddress(address=wallet.getnewaddress(), amount=1.8, fee_rate=1)119gp_tx = wallet.gettransaction(txid=grandparent_txid, verbose=True)121self.assert_undershoots_target(gp_tx)123parent_txid = wallet.sendtoaddress(address=wallet.getnewaddress(), amount=1.5, fee_rate=2)124p_tx = wallet.gettransaction(txid=parent_txid, verbose=True)126self.assert_undershoots_target(p_tx)128ancestor_aware_txid = wallet.sendtoaddress(address=self.def_wallet.getnewaddress(), amount=1.3, fee_rate=self.target_fee_rate)129ancestor_aware_tx = wallet.gettransaction(txid=ancestor_aware_txid, verbose=True)130self.assert_spends_only_parents(ancestor_aware_tx, [parent_txid])132self.assert_beats_target(ancestor_aware_tx)133resulting_ancestry_fee_rate = self.calc_set_fee_rate([gp_tx, p_tx, ancestor_aware_tx])134assert_greater_than_or_equal(resulting_ancestry_fee_rate, self.target_fee_rate)135assert_greater_than_or_equal(self.target_fee_rate*1.01, resulting_ancestry_fee_rate)137wallet.unloadwallet()139# Spend unconfirmed UTXOs from two low feerate parent txs.140def test_two_low_feerate_unconfirmed_parents(self):141self.log.info("Start test with two unconfirmed parent txs")142wallet = self.setup_and_fund_wallet("two_parents_wallet")144# Add second UTXO to tested wallet145self.def_wallet.sendtoaddress(address=wallet.getnewaddress(), amount=2)146self.generate(self.nodes[0], 1) # confirm funding tx148parent_one_txid = wallet.sendtoaddress(address=wallet.getnewaddress(), amount=1.5, fee_rate=2)149p_one_tx = wallet.gettransaction(txid=parent_one_txid, verbose=True)150self.assert_undershoots_target(p_one_tx)152parent_two_txid = wallet.sendtoaddress(address=wallet.getnewaddress(), amount=1.5, fee_rate=1)153p_two_tx = wallet.gettransaction(txid=parent_two_txid, verbose=True)154self.assert_undershoots_target(p_two_tx)156ancestor_aware_txid = wallet.sendtoaddress(address=self.def_wallet.getnewaddress(), amount=2.8, fee_rate=self.target_fee_rate)157ancestor_aware_tx = wallet.gettransaction(txid=ancestor_aware_txid, verbose=True)158self.assert_spends_only_parents(ancestor_aware_tx, [parent_one_txid, parent_two_txid])160self.assert_beats_target(ancestor_aware_tx)161resulting_ancestry_fee_rate = self.calc_set_fee_rate([p_one_tx, p_two_tx, ancestor_aware_tx])162assert_greater_than_or_equal(resulting_ancestry_fee_rate, self.target_fee_rate)163assert_greater_than_or_equal(self.target_fee_rate*1.01, resulting_ancestry_fee_rate)165wallet.unloadwallet()167# Spend two unconfirmed inputs, one each from low and high feerate parents168def test_mixed_feerate_unconfirmed_parents(self):169self.log.info("Start test with two unconfirmed parent txs one of which has a higher feerate")170wallet = self.setup_and_fund_wallet("two_mixed_parents_wallet")172# Add second UTXO to tested wallet173self.def_wallet.sendtoaddress(address=wallet.getnewaddress(), amount=2)174self.generate(self.nodes[0], 1) # confirm funding tx176high_parent_txid = wallet.sendtoaddress(address=wallet.getnewaddress(), amount=1.5, fee_rate=self.target_fee_rate*2)177p_high_tx = wallet.gettransaction(txid=high_parent_txid, verbose=True)178# This time the parent is greater than the child179self.assert_beats_target(p_high_tx)181parent_low_txid = wallet.sendtoaddress(address=wallet.getnewaddress(), amount=1.5, fee_rate=1)182p_low_tx = wallet.gettransaction(txid=parent_low_txid, verbose=True)183# Other parent needs bump184self.assert_undershoots_target(p_low_tx)186ancestor_aware_txid = wallet.sendtoaddress(address=self.def_wallet.getnewaddress(), amount=2.8, fee_rate=self.target_fee_rate)187ancestor_aware_tx = wallet.gettransaction(txid=ancestor_aware_txid, verbose=True)188self.assert_spends_only_parents(ancestor_aware_tx, [parent_low_txid, high_parent_txid])190self.assert_beats_target(ancestor_aware_tx)191resulting_ancestry_fee_rate = self.calc_set_fee_rate([p_high_tx, p_low_tx, ancestor_aware_tx])192assert_greater_than_or_equal(resulting_ancestry_fee_rate, self.target_fee_rate)194resulting_bumped_ancestry_fee_rate = self.calc_set_fee_rate([p_low_tx, ancestor_aware_tx])195assert_greater_than_or_equal(resulting_bumped_ancestry_fee_rate, self.target_fee_rate)196assert_greater_than_or_equal(self.target_fee_rate*1.01, resulting_bumped_ancestry_fee_rate)198wallet.unloadwallet()200# Spend from chain with high feerate grandparent and low feerate parent201def test_chain_of_high_low(self):202self.log.info("Start test with low parent and high grandparent tx")203wallet = self.setup_and_fund_wallet("high_low_chain_wallet")205grandparent_txid = wallet.sendtoaddress(address=wallet.getnewaddress(), amount=1.8, fee_rate=self.target_fee_rate * 10)206gp_tx = wallet.gettransaction(txid=grandparent_txid, verbose=True)207# grandparent has higher feerate208self.assert_beats_target(gp_tx)210parent_txid = wallet.sendtoaddress(address=wallet.getnewaddress(), amount=1.5, fee_rate=1)211# parent is low feerate212p_tx = wallet.gettransaction(txid=parent_txid, verbose=True)213self.assert_undershoots_target(p_tx)215ancestor_aware_txid = wallet.sendtoaddress(address=self.def_wallet.getnewaddress(), amount=1.3, fee_rate=self.target_fee_rate)216ancestor_aware_tx = wallet.gettransaction(txid=ancestor_aware_txid, verbose=True)217self.assert_spends_only_parents(ancestor_aware_tx, [parent_txid])219self.assert_beats_target(ancestor_aware_tx)220resulting_ancestry_fee_rate = self.calc_set_fee_rate([p_tx, ancestor_aware_tx])221assert_greater_than_or_equal(resulting_ancestry_fee_rate, self.target_fee_rate)222assert_greater_than_or_equal(self.target_fee_rate*1.01, resulting_ancestry_fee_rate)223resulting_ancestry_fee_rate_with_high_feerate_gp = self.calc_set_fee_rate([gp_tx, p_tx, ancestor_aware_tx])224# Check that we bumped the parent without relying on the grandparent225assert_greater_than_or_equal(resulting_ancestry_fee_rate_with_high_feerate_gp, self.target_fee_rate*1.1)227wallet.unloadwallet()229# Spend UTXO from chain of unconfirmed transactions with low feerate230# grandparent and even lower feerate parent231def test_chain_of_high_low_below_target_feerate(self):232self.log.info("Start test with low parent and higher low grandparent tx")233wallet = self.setup_and_fund_wallet("low_and_lower_chain_wallet")235grandparent_txid = wallet.sendtoaddress(address=wallet.getnewaddress(), amount=1.8, fee_rate=5)236gp_tx = wallet.gettransaction(txid=grandparent_txid, verbose=True)238# grandparent has higher feerate, but below target239self.assert_undershoots_target(gp_tx)241parent_txid = wallet.sendtoaddress(address=wallet.getnewaddress(), amount=1.5, fee_rate=1)242p_tx = wallet.gettransaction(txid=parent_txid, verbose=True)243# parent even lower244self.assert_undershoots_target(p_tx)246ancestor_aware_txid = wallet.sendtoaddress(address=self.def_wallet.getnewaddress(), amount=1.3, fee_rate=self.target_fee_rate)247ancestor_aware_tx = wallet.gettransaction(txid=ancestor_aware_txid, verbose=True)248self.assert_spends_only_parents(ancestor_aware_tx, [parent_txid])250self.assert_beats_target(ancestor_aware_tx)
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.