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.
501}503void TxConfirmStats::removeTx(unsigned int entryHeight, unsigned int nBestSeenHeight, unsigned int bucketindex, bool inBlock)504{505//nBestSeenHeight is not updated yet for the new block506int blocksAgo = nBestSeenHeight - entryHeight;507if (nBestSeenHeight == 0) // the BlockPolicyEstimator hasn't seen any blocks yet508blocksAgo = 0;509if (blocksAgo < 0) {510LogDebug(BCLog::ESTIMATEFEE, "Blockpolicy error, blocks ago is negative for mempool tx\n");511return; //This can't happen because we call this with our best seen height, no entries can have higher512}514if (blocksAgo >= (int)unconfTxs.size()) {515if (oldUnconfTxs[bucketindex] > 0) {516oldUnconfTxs[bucketindex]--;517} else {518LogDebug(BCLog::ESTIMATEFEE, "Blockpolicy error, mempool tx removed from >25 blocks,bucketIndex=%u already\n",519bucketindex);520}521}522else {523unsigned int blockIndex = entryHeight % unconfTxs.size();524if (unconfTxs[blockIndex][bucketindex] > 0) {525unconfTxs[blockIndex][bucketindex]--;526} else {527LogDebug(BCLog::ESTIMATEFEE, "Blockpolicy error, mempool tx removed from blockIndex=%u,bucketIndex=%u already\n",528blockIndex, bucketindex);529}530}531if (!inBlock && (unsigned int)blocksAgo >= scale) { // Only counts as a failure if not confirmed for entire period532assert(scale != 0);533unsigned int periodsAgo = blocksAgo / scale;534for (size_t i = 0; i < periodsAgo && i < failAvg.size(); i++) {535failAvg[i][bucketindex]++;536}537}538}540bool CBlockPolicyEstimator::removeTx(Txid hash)541{542LOCK(m_cs_fee_estimator);543return _removeTx(hash, /*inBlock=*/false);544}546bool CBlockPolicyEstimator::_removeTx(const Txid& hash, bool inBlock)547{548AssertLockHeld(m_cs_fee_estimator);549std::map<Txid, TxStatsInfo>::iterator pos = mapMemPoolTxs.find(hash);550if (pos != mapMemPoolTxs.end()) {551feeStats->removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex, inBlock);552shortStats->removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex, inBlock);553longStats->removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex, inBlock);554mapMemPoolTxs.erase(hash);555return true;556} else {557return false;558}559}561CBlockPolicyEstimator::CBlockPolicyEstimator(const fs::path& estimation_filepath, const bool read_stale_estimates)562: m_estimation_filepath{estimation_filepath}563{564static_assert(MIN_BUCKET_FEERATE > 0, "Min feerate must be nonzero");565size_t bucketIndex = 0;567for (double bucketBoundary = MIN_BUCKET_FEERATE; bucketBoundary <= MAX_BUCKET_FEERATE; bucketBoundary *= FEE_SPACING, bucketIndex++) {568buckets.push_back(bucketBoundary);569bucketMap[bucketBoundary] = bucketIndex;570}571buckets.push_back(INF_FEERATE);572bucketMap[INF_FEERATE] = bucketIndex;573assert(bucketMap.size() == buckets.size());575feeStats = std::unique_ptr<TxConfirmStats>(new TxConfirmStats(buckets, bucketMap, MED_BLOCK_PERIODS, MED_DECAY, MED_SCALE));576shortStats = std::unique_ptr<TxConfirmStats>(new TxConfirmStats(buckets, bucketMap, SHORT_BLOCK_PERIODS, SHORT_DECAY, SHORT_SCALE));577longStats = std::unique_ptr<TxConfirmStats>(new TxConfirmStats(buckets, bucketMap, LONG_BLOCK_PERIODS, LONG_DECAY, LONG_SCALE));579AutoFile est_file{fsbridge::fopen(m_estimation_filepath, "rb")};581if (est_file.IsNull()) {582LogInfo("%s is not found. Continue anyway.", fs::PathToString(m_estimation_filepath));583return;584}586std::chrono::hours file_age = GetFeeEstimatorFileAge();587if (file_age > MAX_FILE_AGE && !read_stale_estimates) {588LogWarning("Fee estimation file %s too old (age=%lld > %lld hours) and will not be used to avoid serving stale estimates.", fs::PathToString(m_estimation_filepath), Ticks<std::chrono::hours>(file_age), Ticks<std::chrono::hours>(MAX_FILE_AGE));589return;590}592if (!Read(est_file)) {593LogWarning("Failed to read fee estimates from %s. Continue anyway.", fs::PathToString(m_estimation_filepath));594}595}597CBlockPolicyEstimator::~CBlockPolicyEstimator() = default;599void CBlockPolicyEstimator::processTransaction(const NewMempoolTransactionInfo& tx)600{601LOCK(m_cs_fee_estimator);602const unsigned int txHeight = tx.info.txHeight;603const auto& hash = tx.info.m_tx->GetHash();604if (mapMemPoolTxs.contains(hash)) {605LogDebug(BCLog::ESTIMATEFEE, "Blockpolicy error mempool tx %s already being tracked\n",606hash.ToString());607return;608}610if (txHeight != nBestSeenHeight) {611// Ignore side chains and re-orgs; assuming they are random they don't612// affect the estimate. We'll potentially double count transactions in 1-block reorgs.613// Ignore txs if BlockPolicyEstimator is not in sync with ActiveChain().Tip().614// It will be synced next time a block is processed.615return;616}617// This transaction should only count for fee estimation if:618// - it's not being re-added during a reorg which bypasses typical mempool fee limits619// - the node is not behind620// - the transaction is not dependent on any other transactions in the mempool621// - it's not part of a package.622const bool validForFeeEstimation = !tx.m_mempool_limit_bypassed && !tx.m_submitted_in_package && tx.m_chainstate_is_current && tx.m_has_no_mempool_parents;624// Only want to be updating estimates when our blockchain is synced,625// otherwise we'll miscalculate how many blocks its taking to get included.626if (!validForFeeEstimation) {627untrackedTxs++;628return;629}630trackedTxs++;632// Feerates are stored and reported as BTC-per-kb:633const CFeeRate feeRate(tx.info.m_fee, tx.info.m_virtual_transaction_size);635mapMemPoolTxs[hash].blockHeight = txHeight;636unsigned int bucketIndex = feeStats->NewTx(txHeight, static_cast<double>(feeRate.GetFeePerK()));637mapMemPoolTxs[hash].bucketIndex = bucketIndex;638unsigned int bucketIndex2 = shortStats->NewTx(txHeight, static_cast<double>(feeRate.GetFeePerK()));639assert(bucketIndex == bucketIndex2);640unsigned int bucketIndex3 = longStats->NewTx(txHeight, static_cast<double>(feeRate.GetFeePerK()));641assert(bucketIndex == bucketIndex3);642}644bool CBlockPolicyEstimator::processBlockTx(unsigned int nBlockHeight, const RemovedMempoolTransactionInfo& tx)645{646AssertLockHeld(m_cs_fee_estimator);647if (!_removeTx(tx.info.m_tx->GetHash(), true)) {648// This transaction wasn't being tracked for fee estimation649return false;650}652// How many blocks did it take for miners to include this transaction?653// blocksToConfirm is 1-based, so a transaction included in the earliest654// possible block has confirmation count of 1655int blocksToConfirm = nBlockHeight - tx.info.txHeight;656if (blocksToConfirm <= 0) {657// This can't happen because we don't process transactions from a block with a height658// lower than our greatest seen height659LogDebug(BCLog::ESTIMATEFEE, "Blockpolicy error Transaction had negative blocksToConfirm\n");660return false;661}663// Feerates are stored and reported as BTC-per-kb:664CFeeRate feeRate(tx.info.m_fee, tx.info.m_virtual_transaction_size);666feeStats->Record(blocksToConfirm, static_cast<double>(feeRate.GetFeePerK()));667shortStats->Record(blocksToConfirm, static_cast<double>(feeRate.GetFeePerK()));668longStats->Record(blocksToConfirm, static_cast<double>(feeRate.GetFeePerK()));669return true;670}672void CBlockPolicyEstimator::processBlock(const std::vector<RemovedMempoolTransactionInfo>& txs_removed_for_block,673unsigned int nBlockHeight)674{675LOCK(m_cs_fee_estimator);676if (nBlockHeight <= nBestSeenHeight) {677// Ignore side chains and re-orgs; assuming they are random678// they don't affect the estimate.679// And if an attacker can re-org the chain at will, then680// you've got much bigger problems than "attacker can influence681// transaction fees."682return;683}685// Must update nBestSeenHeight in sync with ClearCurrent so that686// calls to removeTx (via processBlockTx) correctly calculate age687// of unconfirmed txs to remove from tracking.688nBestSeenHeight = nBlockHeight;690// Update unconfirmed circular buffer691feeStats->ClearCurrent(nBlockHeight);692shortStats->ClearCurrent(nBlockHeight);693longStats->ClearCurrent(nBlockHeight);695// Decay all exponential averages696feeStats->UpdateMovingAverages();697shortStats->UpdateMovingAverages();698longStats->UpdateMovingAverages();700unsigned int countedTxs = 0;701// Update averages with data points from current block702for (const auto& tx : txs_removed_for_block) {703if (processBlockTx(nBlockHeight, tx))704countedTxs++;705}707if (firstRecordedHeight == 0 && countedTxs > 0) {708firstRecordedHeight = nBestSeenHeight;709LogDebug(BCLog::ESTIMATEFEE, "Blockpolicy first recorded height %u\n", firstRecordedHeight);710}713LogDebug(BCLog::ESTIMATEFEE, "Blockpolicy estimates updated by %u of %u block txs, since last block %u of %u tracked, mempool map size %u, max target %u from %s\n",714countedTxs, txs_removed_for_block.size(), trackedTxs, trackedTxs + untrackedTxs, mapMemPoolTxs.size(),715MaxUsableEstimate(), HistoricalBlockSpan() > BlockSpan() ? "historical" : "current");717trackedTxs = 0;718untrackedTxs = 0;719}721CFeeRate CBlockPolicyEstimator::estimateFee(int confTarget) const722{723// It's not possible to get reasonable estimates for confTarget of 1724if (confTarget <= 1)725return CFeeRate(0);727return estimateRawFee(confTarget, DOUBLE_SUCCESS_PCT, FeeEstimateHorizon::MED_HALFLIFE);728}730CFeeRate CBlockPolicyEstimator::estimateRawFee(int confTarget, double successThreshold, FeeEstimateHorizon horizon, EstimationResult* result) const731{732TxConfirmStats* stats = nullptr;733double sufficientTxs = SUFFICIENT_FEETXS;734switch (horizon) {735case FeeEstimateHorizon::SHORT_HALFLIFE: {736stats = shortStats.get();737sufficientTxs = SUFFICIENT_TXS_SHORT;738break;739}740case FeeEstimateHorizon::MED_HALFLIFE: {741stats = feeStats.get();742break;743}744case FeeEstimateHorizon::LONG_HALFLIFE: {745stats = longStats.get();746break;747}748} // no default case, so the compiler can warn about missing cases749assert(stats);
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.