Inside Bitcoin
Opening the selected file…
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 <policy/fees/mempool_estimator.h>7#include <logging.h>8#include <node/miner.h>9#include <policy/feerate.h>10#include <policy/policy.h>11#include <primitives/block.h>12#include <serialize.h>13#include <streams.h>14#include <sync.h>15#include <tinyformat.h>16#include <txmempool.h>17#include <util/check.h>18#include <util/feefrac.h>19#include <util/fees.h>20#include <util/fs.h>21#include <util/overflow.h>22#include <util/syserror.h>23#include <validation.h>25#include <algorithm>26#include <iterator>27#include <numeric>28#include <optional>29#include <string>30#include <string_view>31#include <system_error>32#include <utility>34constexpr int CURRENT_MEMPOOL_ESTIMATOR_VERSION{1};36namespace {37struct MinedBlockStatsFormatter {38template <typename Stream>39void Ser(Stream& s, const MinedBlockStats& v)40{41s << v.m_height << v.m_removed_block_txs_weight << v.m_block_weight;42}43template <typename Stream>44void Unser(Stream& s, MinedBlockStats& v)45{46s >> v.m_height >> v.m_removed_block_txs_weight >> v.m_block_weight;47}48};50void AddMinedBlockStats(std::vector<MinedBlockStats>& mined_blocks, MinedBlockStats stats)51{52const auto stale_begin{std::find_if(mined_blocks.begin(), mined_blocks.end(), [&](const MinedBlockStats& block) {53return block.m_height >= stats.m_height;54})};55const auto stale_count{std::distance(stale_begin, mined_blocks.end())};56if (stale_count > 0) {57LogDebug(BCLog::ESTIMATEFEE,58"%s: connected block height=%s discards tracked mined-block stats "59"from height=%s to height=%s; stale_stats=%s",60FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY),61stats.m_height,62stale_begin->m_height,63mined_blocks.back().m_height,64stale_count);65}66mined_blocks.erase(stale_begin, mined_blocks.end());67if (!mined_blocks.empty() && mined_blocks.back().m_height + 1 != stats.m_height) {68LogDebug(BCLog::ESTIMATEFEE,69"%s: clearing mined-block stats after height gap; tracked_stats=%s "70"expected_height=%s received_height=%s",71FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY),72mined_blocks.size(),73mined_blocks.back().m_height + 1,74stats.m_height);75mined_blocks.clear();76}78if (mined_blocks.size() == MEMPOOL_HEALTH_WINDOW_BLOCKS) mined_blocks.erase(mined_blocks.begin());79mined_blocks.push_back(stats);80}82struct ActiveTip {83int height;84uint256 hash;85};87std::optional<ActiveTip> GetActiveTip(const ChainstateManager& chainman)88{89LOCK(::cs_main);90const CBlockIndex* tip{chainman.ActiveTip()};91if (!tip) return std::nullopt;92return ActiveTip{tip->nHeight, tip->GetBlockHash()};93}94} // namespace96MemPoolFeeRateEstimator::Percentiles MemPoolFeeRateEstimator::CalculateMaxWeightPercentiles(std::span<const FeePerVSize> chunk_feerates)97{98Assume(std::is_sorted(chunk_feerates.begin(), chunk_feerates.end(), [](const auto& a, const auto& b) { return ByRatio{a} > ByRatio{b}; }));99constexpr int64_t total_weight{DEFAULT_BLOCK_MAX_WEIGHT};100const int64_t p50_weight{total_weight / 2};101const int64_t p75_weight{total_weight * 3 / 4};102Percentiles percentiles{};103int64_t accumulated_weight{0};104for (const auto& curr_feerate : chunk_feerates) {105accumulated_weight += int64_t{curr_feerate.size} * WITNESS_SCALE_FACTOR;106if (accumulated_weight >= p50_weight && percentiles.p50.IsEmpty()) {107percentiles.p50 = curr_feerate;108}109if (accumulated_weight >= p75_weight && percentiles.p75.IsEmpty()) {110percentiles.p75 = curr_feerate;111break;112}113}114return percentiles;115}117bool MemPoolFeeRateEstimatorCache::IsStale() const118{119return !m_fee_rate_estimation || (m_last_updated + CACHE_LIFE) < NodeClock::now();120}122std::optional<MemPoolFeeRateEstimatorCache::FeeRateEstimate>123MemPoolFeeRateEstimatorCache::GetCachedEstimate(const uint256& tip_hash) const124{125if (IsStale() || tip_hash != m_tip_hash) return std::nullopt;126return m_fee_rate_estimation;127}129void MemPoolFeeRateEstimatorCache::Update(FeePerVSize conservative, FeePerVSize economical, const uint256& tip_hash)130{131m_fee_rate_estimation = {conservative, economical};132m_tip_hash = tip_hash;133m_last_updated = NodeClock::now();134}136void MemPoolFeeRateEstimatorCache::Clear()137{138m_fee_rate_estimation.reset();139m_tip_hash.SetNull();140m_last_updated = {};141}143//! Build the error result for a failed mempool fee rate estimation.144static util::Unexpected<FeeRateEstimationError> EstimationError(std::string error)145{146return EstimationError(FeeRateEstimatorType::MEMPOOL_POLICY, MEMPOOL_FEE_ESTIMATOR_MAX_TARGET, std::move(error));147}149static std::optional<std::string_view> MempoolHealthError(MemPoolFeeRateEstimator::MempoolHealth health)150{151switch (health) {152case MemPoolFeeRateEstimator::MempoolHealth::INSUFFICIENT_DATA:153return "Not enough recent block data for fee rate estimation";154case MemPoolFeeRateEstimator::MempoolHealth::LOW_COVERAGE:155return "Mempool is unreliable for fee rate estimation";156case MemPoolFeeRateEstimator::MempoolHealth::HEALTHY:157return std::nullopt;158}159Assume(false);160return std::nullopt;161}163MemPoolFeeRateEstimator::MemPoolFeeRateEstimator(fs::path mempool_estimator_file_path,164const CTxMemPool& mempool,165ChainstateManager& chainman)166: m_mempool(mempool),167m_chainman(chainman),168m_mempool_estimator_file_path(std::move(mempool_estimator_file_path))169{170ReadFromDisk();171}173void MemPoolFeeRateEstimator::ReadFromDisk()174{175AutoFile file{fsbridge::fopen(m_mempool_estimator_file_path, "rb")};176if (file.IsNull()) {177LogDebug(BCLog::ESTIMATEFEE, "%s: %s does not exist. Continuing anyway",178FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY),179fs::PathToString(m_mempool_estimator_file_path));180return;181}182if (Read(file)) {183LogDebug(BCLog::ESTIMATEFEE, "%s: mined-block stats successfully read from %s.",184FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY),185fs::PathToString(m_mempool_estimator_file_path));186}187}189bool MemPoolFeeRateEstimator::Read(AutoFile& file)190{191try {192int version_required;193file >> version_required;194if (version_required != CURRENT_MEMPOOL_ESTIMATOR_VERSION) {195LogWarning("%s: file version not supported; continuing anyway",196FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY));197return false;198}199// Stage into a local buffer and commit to the member only after validation passes.200std::vector<MinedBlockStats> blocks;201file >> Using<VectorFormatter<MinedBlockStatsFormatter>>(blocks);202uint256 tip_hash;203file >> tip_hash;204if (blocks.size() > MEMPOOL_HEALTH_WINDOW_BLOCKS) {205LogWarning("%s: Number of previously mined blocks read exceeds the maximum of %s; ignoring file",206FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY),207MEMPOOL_HEALTH_WINDOW_BLOCKS);208return false;209}210for (size_t i = 1; i < blocks.size(); ++i) {211const uint64_t expected_height{SaturatingAdd(blocks[i - 1].m_height, uint64_t{1})};212if (blocks[i].m_height != expected_height) {213LogWarning("%s: Non-consecutive block heights read, expected height %s but found %s; ignoring file",214FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY),215expected_height, blocks[i].m_height);216return false;217}218}219if (!blocks.empty()) {220const auto& last_block{blocks.back()};221const std::optional<ActiveTip> active_tip{GetActiveTip(m_chainman)};222if (!active_tip) {223LogWarning("%s: Mined-block stats read end at height %s block %s, but there is no active chain tip; ignoring file",224FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY),225last_block.m_height, tip_hash.ToString());226return false;227}228if (last_block.m_height != static_cast<uint64_t>(active_tip->height) || tip_hash != active_tip->hash) {229LogWarning("%s: Mined-block stats read end at height %s block %s, but the active chain tip is height %s block %s; ignoring file",230FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY),231last_block.m_height, tip_hash.ToString(),232active_tip->height, active_tip->hash.ToString());233return false;234}235}236LOCK(cs);237m_prev_mined_blocks = std::move(blocks);238m_mined_blocks_tip_hash = tip_hash;239m_cache.Clear();240} catch (const std::exception&) {241LogWarning("%s: Unable to read mined-block stats from stream (non-fatal)",242FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY));243return false;244}245return true;246}248bool MemPoolFeeRateEstimator::Write(AutoFile& file) const249{250try {
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.