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.
501if miner:502mined = miner.getblock(self.generate(miner, 1)[0], True)["tx"]503self.update_utxo(mined)505def send_transactions(self, utxos, fee_rate, target_vsize):506for utxo in utxos:507self.wallet.send_self_transfer(508from_node=self.nodes[0],509utxo_to_spend=utxo,510fee_rate=fee_rate,511target_vsize=target_vsize,512)514def test_estimation_modes(self):515low_feerate = Decimal("0.001")516high_feerate = Decimal("0.005")517# Broadcast and mine high fee transactions for the first 12 blocks.518self.broadcast_and_maybe_mine(self.nodes[1], high_feerate, TXS_COUNT, 12, self.nodes[2])519check_fee_estimates_btw_modes(self.nodes[0], high_feerate, high_feerate)520# We now track 12 blocks; short horizon stats will start decaying.521# Broadcast and mine low fee transactions for the next 4 blocks.522self.broadcast_and_maybe_mine(self.nodes[1], low_feerate, TXS_COUNT, 4, self.nodes[2])523# conservative mode will consider longer time horizons while economical mode does not524# Check the fee estimates for both modes after mining low fee transactions.525check_fee_estimates_btw_modes(self.nodes[0], high_feerate, low_feerate)527def test_sub_1s_per_vb_estimates(self):528feerate_0_5_s_per_vb = MIN_BUCKET_FEERATE * 5529feerate_1_s_per_vb = Decimal(1000) / Decimal(COIN)530for i in range(6):531self.broadcast_and_maybe_mine(self.nodes[1], feerate_0_5_s_per_vb, TXS_COUNT)532self.broadcast_and_maybe_mine(self.nodes[1], feerate_1_s_per_vb, TXS_COUNT, 1, self.nodes[2])533assert_equal(feerate_0_5_s_per_vb, self.nodes[0].estimatesmartfee(1, "economical", {"fee_rate_estimator": "block_policy"})["feerate"])536def test_estimatesmartfee_return_mempool_estimates(self):537node0 = self.nodes[0]538miner = self.nodes[1]539self.log.info("Ensure node0's mempool is empty at the start")540assert_equal(node0.getmempoolinfo()['size'], 0)541self.log.info("Test estimatesmartfee with empty mempool and no block policy estimator data")542estimate_after_restart = node0.estimatesmartfee(1, "economical", {"fee_rate_estimator": "auto"})543verify_estimate_response(estimate_after_restart, None, [BLOCK_POLICY_ESTIMATOR_ERROR])544self.log.info("Populate block policy estimator with high-feerate history")545# Generate high-feerate transactions and mine them over 6 blocks to give block policy data.546high_feerate = Decimal("0.004")547self.broadcast_and_maybe_mine(node0, high_feerate, TXS_COUNT, 6, miner)548self.log.info("Test estimatesmartfee returns block policy estimator estimate when mempool is higher")549# Add 10 large insane-feerate transactions enough to generate a block template550num_txs = 10551target_vsize = int(((MAX_BLOCK_WEIGHT - DEFAULT_BLOCK_RESERVED_WEIGHT) / WITNESS_SCALE_FACTOR) / num_txs)552utxos = [self.wallet.get_utxo(confirmed_only=True) for _ in range(num_txs)]553insane_feerate = Decimal("0.01")554self.send_transactions(utxos, insane_feerate, target_vsize)555estimate_after_spike = node0.estimatesmartfee(1, "economical", {"verbosity": 2, "fee_rate_estimator": "auto"})556assert_equal(len(estimate_after_spike["mempool_health_statistics"]), 6)557current_height = node0.getchaintips()[0]['height']558for block_stat in estimate_after_spike["mempool_health_statistics"]:559assert_equal(block_stat['block_height'], current_height)560current_height -= 1561assert block_stat['block_weight']562assert block_stat['mempool_txs_weight']563verify_estimate_response(estimate_after_spike, high_feerate, [])564assert_equal(estimate_after_spike["estimator"], "block_policy")565mempool_policy_estimate = node0.estimatesmartfee(1, "economical", {"fee_rate_estimator": "mempool_policy"})566verify_estimate_response(mempool_policy_estimate, insane_feerate, [])567# Confirm the spike transactions so they leave the mempool; the mined block568# keeps the mempool representation healthy. Then broadcast fresh low-feerate569# transactions so the mempool estimate is now the lower of the two.570self.generate(node0, 1, sync_fun=lambda: None)571assert_equal(node0.getmempoolinfo()['size'], 0)572low_feerate = Decimal("0.00004")573low_utxos = [self.wallet.get_utxo(confirmed_only=True) for _ in range(num_txs)]574self.send_transactions(low_utxos, low_feerate, target_vsize)575lower_estimate = node0.estimatesmartfee(1, "economical", {"fee_rate_estimator": "auto"})576verify_estimate_response(lower_estimate, low_feerate, [])577# The mempool block stats are persisted across restarts, so the mempool578# stays healthy and the lower mempool estimate is still returned after a579# restart. Without persistence, the combined estimate would fall back to580# the block policy estimate until enough new blocks are observed.581self.restart_node(0)582estimate_post_restart = node0.estimatesmartfee(1, "economical", {"fee_rate_estimator": "auto"})583verify_estimate_response(estimate_post_restart, low_feerate, [])585self.log.info("Test estimatesmartfee returns the fee rate floor when the mempool is empty but healthy")586self.generate(node0, 1, sync_fun=lambda: None)587assert_equal(node0.getmempoolinfo()['size'], 0)588block_policy_estimate = node0.estimatesmartfee(1, "economical", {"fee_rate_estimator": "block_policy"})589assert "feerate" in block_policy_estimate590# With an empty but healthy mempool the mempool estimator has no percentile data,591# so it falls back to the fee rate floor: the max of minrelaytxfee and mempoolminfee.592# That floor is lower than the block policy estimate, so the combined estimator returns it.593mempool_info = node0.getmempoolinfo()594floor = max(mempool_info["minrelaytxfee"], mempool_info["mempoolminfee"])595combined_estimate = node0.estimatesmartfee(1, "economical", {"fee_rate_estimator": "auto"})596verify_estimate_response(combined_estimate, floor, [])597assert_equal(combined_estimate["estimator"], "mempool_policy")599def test_fallback_to_block_policy_on_low_coverage(self):600self.clear_estimates()601self.broadcast_and_maybe_mine(self.nodes[0], Decimal("0.004"), TXS_COUNT, MEMPOOL_HEALTH_WINDOW, self.nodes[1])602assert "feerate" in self.nodes[0].estimatesmartfee(1, "economical", {"fee_rate_estimator": "block_policy"})603assert_equal(self.nodes[0].estimatesmartfee(1, "economical", {"fee_rate_estimator": "auto"})["estimator"], "mempool_policy")604anyone_can_spend_script = CScript([OP_TRUE]).hex()605quarter_block_vsize = int(MAX_BLOCK_WEIGHT / WITNESS_SCALE_FACTOR / 4)606expected_stats = []607for _ in range(MEMPOOL_HEALTH_WINDOW):608unseen_txs = [make_tx(self.wallet, self.wallet.get_utxo(confirmed_only=True), 10, quarter_block_vsize) for _ in range(3)]609mempool_txs = [self.wallet.send_self_transfer(from_node=self.nodes[1]) for _ in range(3)]610self.sync_mempools([self.nodes[0], self.nodes[1]])611self.generateblock(self.nodes[1], output=f"raw({anyone_can_spend_script})",612transactions=[tx["hex"] for tx in unseen_txs] + [tx["txid"] for tx in mempool_txs],613sync_fun=lambda: self.sync_blocks([self.nodes[0], self.nodes[1]]))614expected_stats.append({615"block_height": self.nodes[0].getblockcount(),616"block_weight": sum(tx["tx"].get_weight() for tx in unseen_txs + mempool_txs),617"mempool_txs_weight": sum(tx["tx"].get_weight() for tx in mempool_txs),618})619combined = self.nodes[0].estimatesmartfee(1, "economical", {"fee_rate_estimator": "auto", "verbosity": 2})620assert_equal(combined["estimator"], "block_policy")621assert_equal(combined["feerate"], self.nodes[0].estimatesmartfee(1, "economical", {"fee_rate_estimator": "block_policy"})["feerate"])622assert_equal(combined["mempool_health_statistics"], list(reversed(expected_stats)))623mempool_policy = self.nodes[0].estimatesmartfee(1, "economical", {"fee_rate_estimator": "mempool_policy"})624assert_equal(mempool_policy["errors"], ["mempool_policy: Mempool is unreliable for fee rate estimation"])625self.restart_node(0)627def test_mined_block_stats_cleared_on_failed_mempool_load(self):628assert_equal(len(self.nodes[0].estimatesmartfee(1, "economical", {"fee_rate_estimator": "auto", "verbosity": 2})["mempool_health_statistics"]), MEMPOOL_HEALTH_WINDOW)629estimator_dat = self.nodes[0].chain_path / "fees" / "mempool_policy_estimator.dat"630mempool_dat = self.nodes[0].chain_path / "mempool.dat"631estimator_bak = estimator_dat.with_name(estimator_dat.name + ".bak")632mempool_bak = mempool_dat.with_name(mempool_dat.name + ".bak")633self.stop_node(0)634shutil.copyfile(estimator_dat, estimator_bak)635shutil.copyfile(mempool_dat, mempool_bak)637def assert_failed_load_clears_window(break_mempool_load, **start_kwargs):638shutil.copyfile(estimator_bak, estimator_dat)639shutil.copyfile(mempool_bak, mempool_dat)640break_mempool_load()641self.start_node(0, **start_kwargs)642self.wait_until(lambda: self.nodes[0].getmempoolinfo()["loaded"])643after = self.nodes[0].estimatesmartfee(1, "economical", {"fee_rate_estimator": "auto", "verbosity": 2})644assert_equal(after["mempool_health_statistics"], [])645assert_equal(after["estimator"], "block_policy")646assert_equal(self.nodes[0].estimatesmartfee(1, "economical", {"fee_rate_estimator": "mempool_policy"})["errors"],647["mempool_policy: Not enough recent block data for fee rate estimation"])648self.stop_node(0)650assert_failed_load_clears_window(mempool_dat.unlink)651assert_failed_load_clears_window(lambda: mempool_dat.write_bytes(b"not a valid mempool.dat"))652assert_failed_load_clears_window(lambda: None, extra_args=["-persistmempool=0"])654shutil.copyfile(estimator_bak, estimator_dat)655shutil.copyfile(mempool_bak, mempool_dat)656estimator_bak.unlink()657mempool_bak.unlink()658self.start_node(0)660def test_stale_mempool_block_stats_are_rejected_on_load(self):661# Persisted mempool block stats must be tied to the best block hash,662# not just height, because a reorg can replace the tip without663# changing the height.664node0 = self.nodes[0]665miner = self.nodes[1]666mempool_policy_dat = node0.chain_path / "fees/mempool_policy_estimator.dat"667healthy_feerate = Decimal("0.004")668self.connect_nodes(0, 1)669self.connect_nodes(0, 2)670self.sync_all()671# Build a full, healthy window whose tracked heights match the current tip.672self.broadcast_and_maybe_mine(node0, healthy_feerate, TXS_COUNT, 6, miner)673stale_stats = node0.estimatesmartfee(6741,675"economical",676{"verbosity": 2, "fee_rate_estimator": "auto"},677)["mempool_health_statistics"]678assert_equal(len(stale_stats), 6)679stale_height = node0.getblockcount()680assert_equal(stale_stats[0]["block_height"], stale_height)681stale_tip = node0.getbestblockhash()682self.stop_node(0)683stale_stats_snapshot = open(mempool_policy_dat, "rb").read()684self.start_node(0)685node0.invalidateblock(stale_tip)686assert_equal(node0.getblockcount(), stale_height - 1)687reorged_tip = self.generate(node0, 1, sync_fun=lambda: None)[0]688assert_equal(node0.getblockcount(), stale_height)689assert_not_equal(reorged_tip, stale_tip)690self.stop_node(0)691with open(mempool_policy_dat, "wb") as f:692f.write(stale_stats_snapshot)693self.start_node(0)694assert_equal(node0.getblockcount(), stale_height)695assert_equal(node0.getbestblockhash(), reorged_tip)696stats_after_restart = node0.estimatesmartfee(6971,698"economical",699{"verbosity": 2, "fee_rate_estimator": "auto"},700)["mempool_health_statistics"]701assert_equal(stats_after_restart, [])703def run_test(self):704self.log.info("This test is time consuming, please be patient")705self.log.info("Splitting inputs so we can generate tx's")707# Split two coinbases into many small utxos708self.start_node(0)709self.wallet = MiniWallet(self.nodes[0])710self.initial_split(self.nodes[0])711self.log.info("Finished splitting")713# Now we can connect the other nodes, didn't want to connect them earlier714# so the estimates would not be affected by the splitting transactions715self.start_node(1)716self.start_node(2)717self.connect_nodes(1, 0)718self.connect_nodes(0, 2)719self.connect_nodes(2, 1)720self.sync_all()722self.log.info("Testing estimates with single transactions.")723self.sanity_check_estimates_range()725self.log.info("Test fees/block_policy_estimates.dat is flushed periodically")726self.test_estimate_dat_is_flushed_periodically()728# check that estimatesmartfee feerate is greater than or equal to maximum of mempoolminfee and minrelaytxfee729self.log.info(730"Test fee rate estimation after restarting node with high minrelaytxfee"731)732self.test_estimates_with_highminrelaytxfee()734self.log.info("Test acceptstalefeeestimates option")735self.test_acceptstalefeeestimates_option()737self.log.info("Test fallback to block policy when the mempool estimator has insufficient data")738self.test_fallback_when_mempool_estimator_has_insufficient_data()740self.log.info("Test reading old block policy estimator file")741self.test_old_fee_estimate_file()743self.clear_estimates()745self.log.info("Testing estimates with RBF.")746self.sanity_check_rbf_estimates()748self.clear_estimates()749self.log.info("Test estimatesmartfee modes")750self.test_estimation_modes()
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.