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// Copyright (c) The Bitcoin Core developers2// Distributed under the MIT software license. See the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.5#include <kernel/mempool_entry.h>6#include <policy/fees/estimator_args.h>7#include <policy/fees/mempool_estimator.h>8#include <policy/policy.h>9#include <primitives/block.h>10#include <random.h>11#include <test/util/setup_common.h>12#include <test/util/txmempool.h>13#include <txmempool.h>14#include <uint256.h>15#include <util/feefrac.h>16#include <util/fees.h>17#include <util/time.h>18#include <validation.h>20#include <boost/test/unit_test.hpp>22#include <string>24BOOST_FIXTURE_TEST_SUITE(mempool_fee_estimator_tests, TestingSetup)26static inline CTransactionRef MakeRandomTx()27{28auto rng = FastRandomContext();29auto tx = CMutableTransaction();30tx.vin.resize(1);31tx.vout.resize(1);32tx.vin[0].prevout.hash = Txid::FromUint256(rng.rand256());33tx.vin[0].prevout.n = 0;34tx.vin[0].scriptSig << OP_TRUE;35tx.vout[0].scriptPubKey = CScript() << OP_TRUE;36tx.vout[0].nValue = COIN;37return MakeTransactionRef(tx);38}40void AddRemovedBlock(MemPoolFeeRateEstimator& fee_est,41int32_t removed_txs_weight,42int32_t block_txs_weight,43unsigned int& height)44{45auto block = std::make_shared<CBlock>();46std::vector<RemovedMempoolTransactionInfo> removed_txs;47TestMemPoolEntryHelper entry;48Assert(block_txs_weight >= removed_txs_weight);49block->vtx.emplace_back(MakeRandomTx()); // Add a coinbase tx50while (block_txs_weight > 0) {51auto tx = MakeRandomTx();52auto tx_weight = GetTransactionWeight(*tx);53if (block_txs_weight - tx_weight < 0) break;54block->vtx.emplace_back(tx);55block_txs_weight -= tx_weight;56if (removed_txs_weight - tx_weight >= 0) {57removed_txs.emplace_back(entry.FromTx(tx));58removed_txs_weight -= tx_weight;59}60}61fee_est.MempoolTxsRemovedForBlock(block, removed_txs, height);62height += 1;63}65BOOST_AUTO_TEST_CASE(calculate_max_weight_percentiles)66{67// With no chunks neither percentile can be populated.68const auto empty = MemPoolFeeRateEstimator::CalculateMaxWeightPercentiles({});69BOOST_CHECK(empty.p50.IsEmpty());70BOOST_CHECK(empty.p75.IsEmpty());71const int32_t chunk_size{10};72const int32_t individual_tx_vsize = static_cast<int32_t>(DEFAULT_BLOCK_MAX_WEIGHT / WITNESS_SCALE_FACTOR) / chunk_size;73const FeePerVSize super_high_fee_rate{500 * individual_tx_vsize, individual_tx_vsize};74const FeePerVSize high_fee_rate{100 * individual_tx_vsize, individual_tx_vsize};75const FeePerVSize medium_fee_rate{50 * individual_tx_vsize, individual_tx_vsize};76const FeePerVSize low_fee_rate{10 * individual_tx_vsize, individual_tx_vsize};77std::vector<FeePerVSize> chunk_feerates;78chunk_feerates.reserve(chunk_size);79for (int i = 0; i < chunk_size; ++i) {80if (i < 3) {81chunk_feerates.emplace_back(super_high_fee_rate);82} else if (i < 5) {83chunk_feerates.emplace_back(high_fee_rate);84} else if (i < 8) {85chunk_feerates.emplace_back(medium_fee_rate);86// Once 50% coverage is reached but 75% is not, only the p50 (conservative)87// percentile is populated; p75 (economical) is left empty for the caller to floor.88if (i < 7) {89const auto partial = MemPoolFeeRateEstimator::CalculateMaxWeightPercentiles(chunk_feerates);90BOOST_CHECK_EQUAL(partial.p50.fee, high_fee_rate.fee);91BOOST_CHECK_EQUAL(partial.p50.size, high_fee_rate.size);92BOOST_CHECK(partial.p75.IsEmpty());93}94} else {95chunk_feerates.emplace_back(low_fee_rate);96}97}98const auto percentiles = MemPoolFeeRateEstimator::CalculateMaxWeightPercentiles(chunk_feerates);99BOOST_CHECK_EQUAL(percentiles.p50.fee, high_fee_rate.fee);100BOOST_CHECK_EQUAL(percentiles.p50.size, high_fee_rate.size);101BOOST_CHECK_EQUAL(percentiles.p75.fee, medium_fee_rate.fee);102BOOST_CHECK_EQUAL(percentiles.p75.size, medium_fee_rate.size);103BOOST_CHECK(ByRatio{percentiles.p50} > ByRatio{percentiles.p75});104}106BOOST_AUTO_TEST_CASE(mempool_fee_rate_estimator_cache)107{108FakeNodeClock clock{};109MemPoolFeeRateEstimatorCache cache;110const uint256 tip_hash{uint256::ONE};111const uint256 next_tip_hash{uint256{2}};112const FeePerVSize conservative{2, 1};113const FeePerVSize economical{1, 1};115BOOST_CHECK(cache.IsStale());116BOOST_CHECK(!cache.GetCachedEstimate(tip_hash));118cache.Update(conservative, economical, tip_hash);119BOOST_CHECK(!cache.IsStale());120const auto cached{cache.GetCachedEstimate(tip_hash)};121BOOST_REQUIRE(cached);122BOOST_CHECK(cached->m_conservative == conservative);123BOOST_CHECK(cached->m_economical == economical);124BOOST_CHECK(!cache.GetCachedEstimate(next_tip_hash));126clock += CACHE_LIFE + std::chrono::seconds{1};127BOOST_CHECK(cache.IsStale());128BOOST_CHECK(!cache.GetCachedEstimate(tip_hash));129}131BOOST_AUTO_TEST_CASE(MempoolFeeRateEstimator)132{133auto mempool_estimator = MemPoolFeeRateEstimator(MempoolPolicyEstimatorPath(*m_node.args), *m_node.mempool, *m_node.chainman);134BOOST_CHECK_EQUAL(mempool_estimator.MaximumTarget(), MEMPOOL_FEE_ESTIMATOR_MAX_TARGET);135// Before the mempool has finished loading, no estimate is available.136{137const std::string unloaded_err = strprintf("%s: Mempool not loaded yet, no fee rate estimate available",138FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY));139const auto result = mempool_estimator.EstimateFeeRate(/*conservative=*/true);140BOOST_CHECK(!result);141BOOST_CHECK_EQUAL(result.error().reason, unloaded_err);142}143m_node.mempool->SetLoadTried(true);145BOOST_CHECK(!mempool_estimator.IsMempoolHealthy());146BOOST_CHECK(mempool_estimator.GetMempoolHealth() == MemPoolFeeRateEstimator::MempoolHealth::INSUFFICIENT_DATA);147{148const auto result = mempool_estimator.EstimateFeeRate(/*conservative=*/true);149const std::string insufficient_err{strprintf("%s: Not enough recent block data for fee rate estimation",150FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY))};151BOOST_CHECK(!result);152BOOST_CHECK_EQUAL(result.error().reason, insufficient_err);153}154{155MemPoolFeeRateEstimator custom_mempool_estimator{156MempoolPolicyEstimatorPath(*m_node.args), *m_node.mempool, *m_node.chainman};157unsigned int custom_height{100};158for (size_t block_count{1}; block_count < MEMPOOL_HEALTH_WINDOW_BLOCKS; ++block_count) {159AddRemovedBlock(custom_mempool_estimator,160/*removed_txs_weight=*/0,161/*block_txs_weight=*/0,162custom_height);163BOOST_CHECK(!custom_mempool_estimator.IsMempoolHealthy());164}165{166const int64_t low_activity_weight{1000};167AddRemovedBlock(custom_mempool_estimator, low_activity_weight / 2, low_activity_weight, custom_height);168}169// Below one block worth of total activity across the full window, even170// poor coverage in the only non-empty block is too noisy to reject the171// mempool as unhealthy.172BOOST_CHECK(custom_mempool_estimator.IsMempoolHealthy());173}174size_t block_count = 1;175const int64_t weight{DEFAULT_BLOCK_MAX_WEIGHT / 2};176unsigned int height = 100;177// Equal weight178while (block_count <= MEMPOOL_HEALTH_WINDOW_BLOCKS) {179AddRemovedBlock(mempool_estimator, weight, weight, height);180if (block_count < MEMPOOL_HEALTH_WINDOW_BLOCKS) {181BOOST_CHECK(!mempool_estimator.IsMempoolHealthy());182}183block_count += 1;184}185// Total txs weight ~11999k WU (~3.0 blocks), removed txs ~11999k WU (~3.0 blocks); coverage = 100%.186BOOST_CHECK(mempool_estimator.IsMempoolHealthy());187// Adding a single underrepresented block will not make the mempool unhealthy188// while the window coverage remains above the threshold.189AddRemovedBlock(mempool_estimator, weight / 2, weight, height);190// Total txs weight ~11999k WU (~3.0 blocks), removed txs ~10999k WU (~2.75 blocks); coverage = ~92%.191BOOST_CHECK(mempool_estimator.IsMempoolHealthy());192// Empty block193// Total txs weight ~9999k WU (~2.5 blocks), removed txs ~8999k WU (~2.25 blocks); coverage = 90%.194AddRemovedBlock(mempool_estimator, 0, 0, height);195BOOST_CHECK(mempool_estimator.IsMempoolHealthy());196// Total txs weight ~9999k WU (~2.5 blocks), removed txs ~7999k WU (~2.0 blocks); coverage = 80%.197AddRemovedBlock(mempool_estimator, weight / 2, weight, height);198BOOST_CHECK(mempool_estimator.IsMempoolHealthy());199// Total txs weight ~9999k WU (~2.5 blocks), removed txs ~7000k WU (~1.75 blocks); coverage = 70%.200AddRemovedBlock(mempool_estimator, weight / 2, weight, height);201BOOST_CHECK(!mempool_estimator.IsMempoolHealthy());202block_count = 1;203while (block_count <= 3) {204AddRemovedBlock(mempool_estimator, weight, weight, height);205if (block_count < 3) {206BOOST_CHECK(!mempool_estimator.IsMempoolHealthy());207}208block_count += 1;209}210// Total txs weight ~9999k WU (~2.5 blocks), removed txs ~7999k WU (~2.0 blocks); coverage = 80%.211BOOST_CHECK(mempool_estimator.IsMempoolHealthy());213// Reorg out and replace the last block. Replacing the tip block should keep a full214// healthy window when the replacement block has good mempool representation.215height -= 1;216AddRemovedBlock(mempool_estimator, weight, weight, height);217BOOST_CHECK(mempool_estimator.IsMempoolHealthy());219// Reorg out the last two blocks. The estimator should discard the stale suffix,220// become temporarily unhealthy due to having fewer than MEMPOOL_HEALTH_WINDOW_BLOCKS stats,221// then recover after the replacement chain catches up.222height -= 2;223AddRemovedBlock(mempool_estimator, weight, weight, height);224BOOST_CHECK(!mempool_estimator.IsMempoolHealthy());225AddRemovedBlock(mempool_estimator, weight, weight, height);226BOOST_CHECK(mempool_estimator.IsMempoolHealthy());228// A forward height gap (e.g. stale persisted stats after an unclean shutdown229// while the chain advanced) resets the tracked window entirely; the estimator230// stays unhealthy until a full window of contiguous blocks is seen again.231height += 3;232AddRemovedBlock(mempool_estimator, weight, weight, height);233BOOST_CHECK(!mempool_estimator.IsMempoolHealthy());234for (size_t i = 1; i < MEMPOOL_HEALTH_WINDOW_BLOCKS; ++i) {235AddRemovedBlock(mempool_estimator, weight, weight, height);236if (i < MEMPOOL_HEALTH_WINDOW_BLOCKS - 1) {237BOOST_CHECK(!mempool_estimator.IsMempoolHealthy());238}239}240BOOST_CHECK(mempool_estimator.IsMempoolHealthy());241{242LOCK(m_node.mempool->cs);243BOOST_CHECK_EQUAL(m_node.mempool->GetTotalTxSize(), 0);244}245// With an empty mempool there is nothing to build a feerate estimate from, so both246// estimates fall back to the floor fee rate: the higher of the minimum relay fee rate247// and the current mempool minimum fee rate.248const FeePerVSize floor{std::max(m_node.mempool->m_opts.min_relay_feerate, m_node.mempool->GetMinFee()).GetFeePerVSize()};249{250const auto result = mempool_estimator.EstimateFeeRate(/*conservative=*/true);
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.