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.
252self.log.info(253"Creating transactions and mining them at a block size that is just big enough"254)255# Generate transactions while mining 10 more blocks, this time with node1256# which mines blocks with capacity just above the rate that transactions are being created257self.transact_and_mine(10, self.nodes[1])258check_estimates(self.nodes[1], self.fees_per_kb)260# Finish by mining a normal-sized block:261while len(self.nodes[1].getrawmempool()) > 0:262self.generate(self.nodes[1], 1)264self.log.info("Final estimates after emptying mempools")265check_estimates(self.nodes[1], self.fees_per_kb)267def test_estimates_with_highminrelaytxfee(self):268high_val = 3 * self.nodes[1].estimatesmartfee(2, "economical", {"fee_rate_estimator": "block_policy"})["feerate"]269self.restart_node(1, extra_args=[f"-minrelaytxfee={high_val}"])270check_smart_estimates(self.nodes[1], self.fees_per_kb)271self.restart_node(1)273def sanity_check_rbf_estimates(self):274"""During 5 blocks, broadcast low fee transactions. Only 10% of them get275confirmed and the remaining ones get RBF'd with a high fee transaction at276the next block.277The block policy estimator should return the high feerate.278"""279# The broadcaster and block producer280node = self.nodes[0]281miner = self.nodes[1]282# In sat/vb283low_feerate = 1284high_feerate = 10285# Cache the utxos of which to replace the spender after it failed to get286# confirmed287utxos_to_respend = []288txids_to_replace = []290assert_greater_than_or_equal(len(self.confutxo), 250)291for _ in range(5):292# Broadcast 45 low fee transactions that will need to be RBF'd293txs = []294for _ in range(45):295u = self.confutxo.pop(0)296tx = make_tx(self.wallet, u, low_feerate)297utxos_to_respend.append(u)298txids_to_replace.append(tx["txid"])299txs.append(tx)300# Broadcast 5 low fee transaction which don't need to301for _ in range(5):302tx = make_tx(self.wallet, self.confutxo.pop(0), low_feerate)303self.memutxo.append(tx["new_utxo"])304txs.append(tx)305batch_send_tx = [node.sendrawtransaction.get_request(tx["hex"]) for tx in txs]306for n in self.nodes:307n.batch(batch_send_tx)308# Mine the transactions on another node309self.sync_mempools(wait=0.1, nodes=[node, miner])310for txid in txids_to_replace:311miner.prioritisetransaction(txid=txid, fee_delta=-COIN)312mined = miner.getblock(self.generate(miner, 1)[0], True)["tx"]313self.update_utxo(mined)314# RBF the low-fee transactions315while len(utxos_to_respend) > 0:316u = utxos_to_respend.pop(0)317tx = make_tx(self.wallet, u, high_feerate)318self.memutxo.append(tx["new_utxo"])319node.sendrawtransaction(tx["hex"])320txs.append(tx)321dec_txs = [res["result"] for res in node.batch([node.decoderawtransaction.get_request(tx["hex"]) for tx in txs])]322self.wallet.scan_txs(dec_txs)325# Mine the last replacement txs326self.sync_mempools(wait=0.1, nodes=[node, miner])327mined = miner.getblock(self.generate(miner, 1)[0], True)["tx"]328self.update_utxo(mined)330# Only 10% of the transactions were really confirmed with a low feerate,331# the rest needed to be RBF'd. We must return the 90% conf rate feerate.332high_feerate_kvb = Decimal(high_feerate) / COIN * 10 ** 3333est_feerate = node.estimatesmartfee(2, "economical", {"fee_rate_estimator": "block_policy"})["feerate"]334assert_equal(est_feerate, high_feerate_kvb)336def test_fallback_when_mempool_estimator_has_insufficient_data(self):337assert_equal(self.nodes[0].estimatesmartfee(1, "economical", {"fee_rate_estimator": "auto"})["estimator"], "mempool_policy")338self.stop_node(0)339# Move the mempool estimator data aside so the mempool policy estimator has insufficient data.340original_path = self.nodes[0].chain_path / "fees" / "mempool_policy_estimator.dat"341temp_path = self.nodes[0].chain_path / "fees" / "mempool_policy_estimator.dat.bak"342original_path.replace(temp_path)343self.start_node(0)344self.wait_until(lambda: self.nodes[0].getmempoolinfo()["loaded"])345assert_equal(self.nodes[0].estimatesmartfee(1, "economical", {"fee_rate_estimator": "mempool_policy"})["errors"],346["mempool_policy: Not enough recent block data for fee rate estimation"])347assert_equal(self.nodes[0].estimatesmartfee(1, "economical", {"fee_rate_estimator": "auto"})["estimator"], "block_policy")349# Restore the mempool estimator data.350self.stop_node(0)351temp_path.replace(original_path)352self.start_node(0)354def test_old_fee_estimate_file(self):355# Get the initial fee rate while node is running356fee_rate = self.nodes[0].estimatesmartfee(1, "economical", {"fee_rate_estimator": "block_policy"})["feerate"]358# Restart node to ensure block policy estimator file is read359self.restart_node(0)360assert_equal(self.nodes[0].estimatesmartfee(1, "economical", {"fee_rate_estimator": "block_policy"})["feerate"], fee_rate)362block_policy_fee_dat = self.nodes[0].chain_path / BLOCK_POLICY_ESTIMATOR_FILE_PATH363legacy_fee_dat = self.nodes[0].chain_path / "fee_estimates.dat"365# If only the legacy fee_estimates.dat file exists, it is migrated to366# the new block policy estimator path.367self.stop_node(0)368os.rename(block_policy_fee_dat, legacy_fee_dat)369self.start_node(0)370assert_equal(self.nodes[0].estimatesmartfee(1, "economical", {"fee_rate_estimator": "block_policy"})["feerate"], fee_rate)371self.stop_node(0)372assert_equal(os.path.isfile(block_policy_fee_dat), True)373assert_equal(os.path.isfile(legacy_fee_dat), False)375# If both files exist, the new block policy estimator path is used and376# the obsolete legacy file is removed.377with open(legacy_fee_dat, "wb") as f:378f.write(b"ignored legacy fee estimates")379self.start_node(0)380assert_equal(self.nodes[0].estimatesmartfee(1, "economical", {"fee_rate_estimator": "block_policy"})["feerate"], fee_rate)381self.stop_node(0)382assert_equal(os.path.isfile(legacy_fee_dat), False)384# Stop the node and backdate the block policy estimator file more than MAX_FILE_AGE385last_modified_time = time.time() - (MAX_FILE_AGE + 1) * SECONDS_PER_HOUR386os.utime(block_policy_fee_dat, (last_modified_time, last_modified_time))388# Start node and ensure the block policy estimator file was not read389self.start_node(0)390assert_equal(self.nodes[0].estimatesmartfee(1, "economical", {"fee_rate_estimator": "block_policy"})["errors"], [BLOCK_POLICY_ESTIMATOR_ERROR])393def test_estimate_dat_is_flushed_periodically(self):394block_policy_fees_dat = self.nodes[0].chain_path / BLOCK_POLICY_ESTIMATOR_FILE_PATH395mempool_policy_dat = self.nodes[0].chain_path / "fees/mempool_policy_estimator.dat"396mempool_estimator_name_str = "mempool_policy"397os.remove(block_policy_fees_dat) if os.path.exists(block_policy_fees_dat) else None398os.remove(mempool_policy_dat) if os.path.exists(mempool_policy_dat) else None399if os.path.isdir(block_policy_fees_dat.parent):400os.rmdir(block_policy_fees_dat.parent)402# Verify that estimator data files and their parent directory do not exist403assert_equal(os.path.isfile(block_policy_fees_dat), False)404assert_equal(os.path.isfile(mempool_policy_dat), False)405assert_equal(os.path.isdir(block_policy_fees_dat.parent), False)406# Verify if the string "Flushed fee estimates to block_policy_estimates.dat." is present in the debug log file.407# If present, it indicates that fee estimator data has been successfully flushed to disk.408block_policy_estimator_message = f"Flushed fee estimates to {block_policy_fees_dat}."409mempool_policy_estimator_message = (410f"{mempool_estimator_name_str}: mined-block stats flushed to {mempool_policy_dat}."411)412expected_messages = [block_policy_estimator_message, mempool_policy_estimator_message]413with self.nodes[0].assert_debug_log(expected_msgs=expected_messages, timeout=1):414# Mock the scheduler for an hour to flush estimator data.415self.nodes[0].mockscheduler(SECONDS_PER_HOUR)417# Verify that estimator data was flushed and the estimator directory and files are created418assert_equal(os.path.isdir(block_policy_fees_dat.parent), True)419assert_equal(os.path.isfile(block_policy_fees_dat), True)420assert_equal(os.path.isfile(mempool_policy_dat), True)421# Verify that estimator data remains the same if there are no blocks in the flush interval422block_hash_before = self.nodes[0].getbestblockhash()423block_policy_fees_dat_initial_content = open(block_policy_fees_dat, "rb").read()424mempool_policy_dat_initial_content = open(mempool_policy_dat, "rb").read()425with self.nodes[0].assert_debug_log(expected_msgs=expected_messages, timeout=1):426# Mock the scheduler for an hour to flush estimator data.427self.nodes[0].mockscheduler(SECONDS_PER_HOUR)428# Verify that there were no blocks in between the flush interval429assert_equal(block_hash_before, self.nodes[0].getbestblockhash())430block_policy_fees_dat_current_content = open(block_policy_fees_dat, "rb").read()431mempool_policy_dat_current_content = open(mempool_policy_dat, "rb").read()432assert_equal(block_policy_fees_dat_initial_content, block_policy_fees_dat_current_content)433assert_equal(mempool_policy_dat_initial_content, mempool_policy_dat_current_content)434# Verify that estimator data remains the same after shutdown with no blocks before shutdown435self.restart_node(0)436block_policy_fees_dat_current_content = open(block_policy_fees_dat, "rb").read()437mempool_policy_dat_current_content = open(mempool_policy_dat, "rb").read()438assert_equal(block_policy_fees_dat_initial_content, block_policy_fees_dat_current_content)439assert_equal(mempool_policy_dat_initial_content, mempool_policy_dat_current_content)440# Verify that estimator data changes if new blocks were produced in the flush interval441with self.nodes[0].assert_debug_log(expected_msgs=expected_messages, timeout=1):442# Mock the scheduler for an hour to flush estimator data.443self.generate(self.nodes[0], 5, sync_fun=self.no_op)444self.nodes[0].mockscheduler(SECONDS_PER_HOUR)445block_policy_fees_dat_current_content = open(block_policy_fees_dat, "rb").read()446assert_not_equal(block_policy_fees_dat_current_content, block_policy_fees_dat_initial_content)447block_policy_fees_dat_initial_content = block_policy_fees_dat_current_content448mempool_policy_dat_current_content = open(mempool_policy_dat, "rb").read()449assert_not_equal(mempool_policy_dat_current_content, mempool_policy_dat_initial_content)450mempool_policy_dat_initial_content = mempool_policy_dat_current_content451# Generate blocks before shutdown and verify that estimator data changes452self.generate(self.nodes[0], 5, sync_fun=self.no_op)453self.restart_node(0)454block_policy_fees_dat_current_content = open(block_policy_fees_dat, "rb").read()455mempool_policy_dat_current_content = open(mempool_policy_dat, "rb").read()456assert_not_equal(block_policy_fees_dat_initial_content, block_policy_fees_dat_current_content)457assert_not_equal(mempool_policy_dat_initial_content, mempool_policy_dat_current_content)460def test_acceptstalefeeestimates_option(self):461# Get the initial fee rate while node is running462fee_rate = self.nodes[0].estimatesmartfee(1, "economical", {"fee_rate_estimator": "block_policy"})["feerate"]464self.stop_node(0)466fee_dat = self.nodes[0].chain_path / BLOCK_POLICY_ESTIMATOR_FILE_PATH468# Stop the node and backdate the block policy estimator file more than MAX_FILE_AGE469last_modified_time = time.time() - (MAX_FILE_AGE + 1) * SECONDS_PER_HOUR470os.utime(fee_dat, (last_modified_time, last_modified_time))472# Restart node with -acceptstalefeeestimates option to ensure block policy estimator file is read473self.start_node(0,extra_args=["-acceptstalefeeestimates"])474assert_equal(self.nodes[0].estimatesmartfee(1, "economical", {"fee_rate_estimator": "block_policy"})["feerate"], fee_rate)476def clear_estimates(self):477self.log.info("Restarting node with fresh estimation")478self.stop_node(0)479fee_dat = self.nodes[0].chain_path / BLOCK_POLICY_ESTIMATOR_FILE_PATH480os.remove(fee_dat)481self.start_node(0)482self.connect_nodes(0, 1)483self.connect_nodes(0, 2)484self.sync_blocks()485assert_equal(self.nodes[0].estimatesmartfee(1, "economical", {"fee_rate_estimator": "block_policy"})["errors"], ["Insufficient data or no feerate found"])487def broadcast_and_maybe_mine(self, broadcaster, feerate, txs, blocks=1, miner=None):488"""Broadcast and maybe mine some number of transactions with a specified fee rate."""489for _ in range(blocks):490tx_batch = []491for _ in range(txs):492tx = self.wallet.create_self_transfer(fee_rate=feerate, utxo_to_spend=self.confutxo.pop(0))493self.memutxo.append(tx["new_utxo"])494tx_batch.append(tx)495# To speed up the test, submit the transactions in batches to the nodes directly496# avoiding having to wait for p2p to propagate them between the nodes.497batch_send_tx = [broadcaster.sendrawtransaction.get_request(hexstring=tx["hex"]) for tx in tx_batch]498for node in self.nodes:499node.batch(batch_send_tx)500self.sync_mempools(wait=0.1, nodes=[self.nodes[0], self.nodes[1], self.nodes[2]])
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.