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 fee estimation code."""6from copy import deepcopy7from decimal import Decimal, ROUND_DOWN8import os9import random10import shutil11import time13from test_framework.messages import (14COIN,15DEFAULT_BLOCK_RESERVED_WEIGHT,16MAX_BLOCK_WEIGHT,17WITNESS_SCALE_FACTOR,18)19from test_framework.script import (20CScript,21OP_TRUE,22)23from test_framework.test_framework import BitcoinTestFramework24from test_framework.util import (25assert_not_equal,26assert_equal,27assert_greater_than,28assert_greater_than_or_equal,29assert_raises_rpc_error,30satoshi_round,31)32from test_framework.wallet import MiniWallet34MAX_FILE_AGE = 6035SECONDS_PER_HOUR = 60 * 6036MIN_BUCKET_FEERATE = Decimal(100) / Decimal(COIN)37TXS_COUNT = 2438MEMPOOL_HEALTH_WINDOW = 639BLOCK_POLICY_ESTIMATOR_ERROR = "Insufficient data or no feerate found"40BLOCK_POLICY_ESTIMATOR_FILE_PATH = "fees/block_policy_estimates.dat"42def small_txpuzzle_randfee(43wallet, from_node, conflist, unconflist, amount, min_fee, fee_increment, batch_reqs44):45"""Create and send a transaction with a random fee using MiniWallet.47The function takes a list of confirmed outputs and unconfirmed outputs48and attempts to use the confirmed list first for its inputs.49It adds the newly created outputs to the unconfirmed list.50Returns (raw transaction, fee)."""52# It's best to exponentially distribute our random fees53# because the buckets are exponentially spaced.54# Exponentially distributed from 1-128 * fee_increment55rand_fee = float(fee_increment) * (1.1892 ** random.randint(0, 28))56# Total fee ranges from min_fee to min_fee + 127*fee_increment57fee = min_fee - fee_increment + satoshi_round(rand_fee, rounding=ROUND_DOWN)58utxos_to_spend = []59total_in = Decimal("0.00000000")60while total_in <= (amount + fee) and len(conflist) > 0:61t = conflist.pop(0)62total_in += t["value"]63utxos_to_spend.append(t)64while total_in <= (amount + fee) and len(unconflist) > 0:65t = unconflist.pop(0)66total_in += t["value"]67utxos_to_spend.append(t)68if total_in <= amount + fee:69raise RuntimeError(f"Insufficient funds: need {amount + fee}, have {total_in}")70tx = wallet.create_self_transfer_multi(71utxos_to_spend=utxos_to_spend,72fee_per_output=0,73)["tx"]74tx.vout[0].nValue = int((total_in - amount - fee) * COIN)75tx.vout.append(deepcopy(tx.vout[0]))76tx.vout[1].nValue = int(amount * COIN)77txid = tx.txid_hex78tx_hex = tx.serialize().hex()80batch_reqs.append(from_node.sendrawtransaction.get_request(hexstring=tx_hex, maxfeerate=0))81unconflist.append({"txid": txid, "vout": 0, "value": total_in - amount - fee})82unconflist.append({"txid": txid, "vout": 1, "value": amount})84return (tx.get_vsize(), fee)87def check_raw_estimates(node, fees_seen):88"""Call estimaterawfee and verify that the estimates meet certain invariants."""90delta = 1.0e-6 # account for rounding error91for i in range(1, 26):92for _, e in node.estimaterawfee(i).items():93feerate = float(e["feerate"])94assert_greater_than(feerate, 0)96if feerate + delta < min(fees_seen) or feerate - delta > max(fees_seen):97raise AssertionError(98f"Estimated fee ({feerate}) out of range ({min(fees_seen)},{max(fees_seen)})"99)102def check_smart_estimates(node, fees_seen):103"""Call estimatesmartfee and verify that the estimates meet certain invariants."""105delta = 1.0e-6 # account for rounding error106all_smart_estimates = [node.estimatesmartfee(i, "economical", {"fee_rate_estimator": "block_policy"}) for i in range(1, 26)]107mempoolMinFee = node.getmempoolinfo()["mempoolminfee"]108minRelaytxFee = node.getmempoolinfo()["minrelaytxfee"]109feerate_ceiling = max(max(fees_seen), float(mempoolMinFee), float(minRelaytxFee))110last_feerate = feerate_ceiling111for i, e in enumerate(all_smart_estimates): # estimate is for i+1112feerate = float(e["feerate"])113assert_greater_than(feerate, 0)114assert_greater_than_or_equal(feerate, float(mempoolMinFee))115assert_greater_than_or_equal(feerate, float(minRelaytxFee))117if feerate + delta < min(fees_seen) or feerate - delta > feerate_ceiling:118raise AssertionError(119f"Estimated fee ({feerate}) out of range ({min(fees_seen)},{feerate_ceiling})"120)121if feerate - delta > last_feerate:122raise AssertionError(123f"Estimated fee ({feerate}) larger than last fee ({last_feerate}) for lower number of confirms"124)125last_feerate = feerate127if i == 0:128assert_equal(e["blocks"], 2)129else:130assert_greater_than_or_equal(i + 1, e["blocks"])133def check_estimates(node, fees_seen):134check_raw_estimates(node, fees_seen)135check_smart_estimates(node, fees_seen)138def make_tx(wallet, utxo, feerate, target_vsize=0):139"""Create a 1in-1out transaction with a specific input and feerate (sat/vb)."""140return wallet.create_self_transfer(141utxo_to_spend=utxo,142fee_rate=Decimal(feerate * 1000) / COIN,143target_vsize=target_vsize,144)146def check_fee_estimates_btw_modes(node, expected_conservative, expected_economical):147fee_est_conservative = node.estimatesmartfee(1, "conservative", {"fee_rate_estimator": "block_policy"})['feerate']148fee_est_economical = node.estimatesmartfee(1, "economical", {"fee_rate_estimator": "block_policy"})['feerate']149# Omit estimate_mode to check that the default mode is economical.150fee_est_default = node.estimatesmartfee(1, options={"fee_rate_estimator": "block_policy"})['feerate']151assert_equal(fee_est_conservative, expected_conservative)152assert_equal(fee_est_economical, expected_economical)153assert_equal(fee_est_default, expected_economical)155def verify_estimate_response(estimate, feerate, errors):156if feerate is not None:157assert_equal(estimate["feerate"], feerate)158if errors:159assert all(err in estimate["errors"] for err in errors)160else:161assert "errors" not in estimate164class EstimateFeeTest(BitcoinTestFramework):165def set_test_params(self):166self.num_nodes = 3167# whitelist peers to speed up tx relay / mempool sync168self.noban_tx_relay = True169self.extra_args = [170[],171["-blockmaxweight=72000"],172["-blockmaxweight=36000"],173]175def setup_network(self):176"""177We'll setup the network to have 3 nodes that all mine with different parameters.178But first we need to use one node to create a lot of outputs179which we will use to generate our transactions.180"""181self.add_nodes(3, extra_args=self.extra_args)182# Use node0 to mine blocks for input splitting183# Node1 mines small blocks but that are bigger than the expected transaction rate.184# NOTE: the CreateNewBlock code starts counting block weight at 4,000 weight,185# (68k weight is room enough for 120 or so transactions)186# Node2 is a stingy miner, that187# produces too small blocks (room for only 55 or so transactions)189def update_utxo(self, mined):190# update which txouts are confirmed191newmem = []192for utx in self.memutxo:193if utx["txid"] in mined:194self.confutxo.append(utx)195else:196newmem.append(utx)197self.memutxo = newmem199def transact_and_mine(self, numblocks, mining_node):200min_fee = MIN_BUCKET_FEERATE201# We will now mine numblocks blocks generating on average 100 transactions between each block202# We shuffle our confirmed txout set before each set of transactions203# small_txpuzzle_randfee will use the transactions that have inputs already in the chain when possible204# resorting to tx's that depend on the mempool when those run out205for _ in range(numblocks):206random.shuffle(self.confutxo)207batch_sendtx_reqs = []208for _ in range(random.randrange(100 - 50, 100 + 50)):209from_index = random.randint(1, 2)210(tx_bytes, fee) = small_txpuzzle_randfee(211self.wallet,212self.nodes[from_index],213self.confutxo,214self.memutxo,215Decimal("0.005"),216min_fee,217min_fee,218batch_sendtx_reqs,219)220tx_kbytes = tx_bytes / 1000.0221self.fees_per_kb.append(float(fee) / tx_kbytes)222for node in self.nodes:223node.batch(batch_sendtx_reqs)224self.sync_mempools(wait=0.1)225mined = mining_node.getblock(self.generate(mining_node, 1)[0], True)["tx"]226self.update_utxo(mined)228def initial_split(self, node):229"""Split two coinbase UTxOs into many small coins"""230self.confutxo = self.wallet.send_self_transfer_multi(231from_node=node,232utxos_to_spend=[self.wallet.get_utxo() for _ in range(2)],233num_outputs=2048)['new_utxos']234while len(node.getrawmempool()) > 0:235self.generate(node, 1, sync_fun=self.no_op)237def sanity_check_estimates_range(self):238"""Populate estimation buckets, assert estimates are in a sane range and239are strictly increasing as the target decreases."""240self.fees_per_kb = []241self.memutxo = []242self.log.info("Will output estimates for 1/2/3/6/15/25 blocks")244for _ in range(2):245self.log.info(246"Creating transactions and mining them with a block size that can't keep up"247)248# Create transactions and mine 10 small blocks with node 2, but create txs faster than we can mine249self.transact_and_mine(10, self.nodes[2])250check_estimates(self.nodes[1], self.fees_per_kb)
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.