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) 2023-present 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 <node/mini_miner.h>7#include <boost/multi_index/detail/hash_index_iterator.hpp>8#include <boost/operators.hpp>9#include <consensus/amount.h>10#include <policy/feerate.h>11#include <primitives/transaction.h>12#include <sync.h>13#include <txmempool.h>14#include <uint256.h>15#include <util/check.h>17#include <algorithm>18#include <numeric>19#include <ranges>20#include <utility>22namespace node {24MiniMiner::MiniMiner(const CTxMemPool& mempool, const std::vector<COutPoint>& outpoints)25{26LOCK(mempool.cs);27// Find which outpoints to calculate bump fees for.28// Anything that's spent by the mempool is to-be-replaced29// Anything otherwise unavailable just has a bump fee of 030for (const auto& outpoint : outpoints) {31if (!mempool.exists(outpoint.hash)) {32// This UTXO is either confirmed or not yet submitted to mempool.33// If it's confirmed, no bump fee is required.34// If it's not yet submitted, we have no information, so return 0.35m_bump_fees.emplace(outpoint, 0);36continue;37}39// UXTO is created by transaction in mempool, add to map.40// Note: This will either create a missing entry or add the outpoint to an existing entry41m_requested_outpoints_by_txid[outpoint.hash].push_back(outpoint);43if (const auto ptx{mempool.GetConflictTx(outpoint)}) {44// This outpoint is already being spent by another transaction in the mempool. We45// assume that the caller wants to replace this transaction and its descendants. It46// would be unusual for the transaction to have descendants as the wallet won’t normally47// attempt to replace transactions with descendants. If the outpoint is from a mempool48// transaction, we still need to calculate its ancestors bump fees (added to49// m_requested_outpoints_by_txid below), but after removing the to-be-replaced entries.50//51// Note that the descendants of a transaction include the transaction itself. Also note,52// that this is only calculating bump fees. RBF fee rules should be handled separately.53CTxMemPool::setEntries descendants;54mempool.CalculateDescendants(mempool.GetIter(ptx->GetHash()).value(), descendants);55for (const auto& desc_txiter : descendants) {56m_to_be_replaced.insert(desc_txiter->GetTx().GetHash());57}58}59}61// No unconfirmed UTXOs, so nothing mempool-related needs to be calculated.62if (m_requested_outpoints_by_txid.empty()) return;64// Calculate the cluster and construct the entry map.65auto txids_needed{m_requested_outpoints_by_txid | std::views::keys};66const auto cluster = mempool.GatherClusters({txids_needed.begin(), txids_needed.end()});67if (cluster.empty()) {68// An empty cluster means that at least one of the transactions is missing from the mempool69// (should not be possible given processing above) or DoS limit was hit.70m_ready_to_calculate = false;71return;72}74// Add every entry to m_entries_by_txid and m_entries, except the ones that will be replaced.75for (const auto& txiter : cluster) {76if (!m_to_be_replaced.contains(txiter->GetTx().GetHash())) {77auto [ancestor_count, ancestor_size, ancestor_fee] = mempool.CalculateAncestorData(*txiter);78auto [mapiter, success] = m_entries_by_txid.emplace(txiter->GetTx().GetHash(),79MiniMinerMempoolEntry{/*tx_in=*/txiter->GetSharedTx(),80/*vsize_self=*/txiter->GetTxSize(),81/*vsize_ancestor=*/int64_t(ancestor_size),82/*fee_self=*/txiter->GetModifiedFee(),83/*fee_ancestor=*/ancestor_fee});84m_entries.push_back(mapiter);85} else {86auto outpoints_it = m_requested_outpoints_by_txid.find(txiter->GetTx().GetHash());87if (outpoints_it != m_requested_outpoints_by_txid.end()) {88// This UTXO is the output of a to-be-replaced transaction. Bump fee is 0; spending89// this UTXO is impossible as it will no longer exist after the replacement.90for (const auto& outpoint : outpoints_it->second) {91m_bump_fees.emplace(outpoint, 0);92}93m_requested_outpoints_by_txid.erase(outpoints_it);94}95}96}98// Build the m_descendant_set_by_txid cache.99for (const auto& txiter : cluster) {100const auto& txid = txiter->GetTx().GetHash();101// Cache descendants for future use. Unlike the real mempool, a descendant MiniMinerMempoolEntry102// will not exist without its ancestor MiniMinerMempoolEntry, so these sets won't be invalidated.103std::vector<MockEntryMap::iterator> cached_descendants;104const bool remove{m_to_be_replaced.contains(txid)};105CTxMemPool::setEntries descendants;106mempool.CalculateDescendants(txiter, descendants);107Assume(descendants.contains(txiter));108for (const auto& desc_txiter : descendants) {109const auto txid_desc = desc_txiter->GetTx().GetHash();110const bool remove_desc{m_to_be_replaced.contains(txid_desc)};111auto desc_it{m_entries_by_txid.find(txid_desc)};112Assume((desc_it == m_entries_by_txid.end()) == remove_desc);113if (remove) Assume(remove_desc);114// It's possible that remove=false but remove_desc=true.115if (!remove && !remove_desc) {116cached_descendants.push_back(desc_it);117}118}119if (remove) {120Assume(cached_descendants.empty());121} else {122m_descendant_set_by_txid.emplace(txid, cached_descendants);123}124}126// Release the mempool lock; we now have all the information we need for a subset of the entries127// we care about. We will solely operate on the MiniMinerMempoolEntry map from now on.128Assume(m_in_block.empty());129Assume(m_requested_outpoints_by_txid.size() <= outpoints.size());130SanityCheck();131}133MiniMiner::MiniMiner(const std::vector<MiniMinerMempoolEntry>& manual_entries,134const std::map<Txid, std::set<Txid>>& descendant_caches)135{136for (const auto& entry : manual_entries) {137const auto& txid = entry.GetTx().GetHash();138// We need to know the descendant set of every transaction.139if (!Assume(descendant_caches.contains(txid))) {140m_ready_to_calculate = false;141return;142}143// Just forward these args onto MiniMinerMempoolEntry144auto [mapiter, success] = m_entries_by_txid.emplace(txid, entry);145// Txids must be unique; this txid shouldn't already be an entry in m_entries_by_txid146if (Assume(success)) m_entries.push_back(mapiter);147}148// Descendant cache is already built, but we need to translate them to m_entries_by_txid iters.149for (const auto& [txid, desc_txids] : descendant_caches) {150// Descendant cache should include at least the tx itself.151if (!Assume(!desc_txids.empty())) {152m_ready_to_calculate = false;153return;154}155std::vector<MockEntryMap::iterator> descendants;156for (const auto& desc_txid : desc_txids) {157auto desc_it{m_entries_by_txid.find(desc_txid)};158// Descendants should only include transactions with corresponding entries.159if (!Assume(desc_it != m_entries_by_txid.end())) {160m_ready_to_calculate = false;161return;162} else {163descendants.emplace_back(desc_it);164}165}166m_descendant_set_by_txid.emplace(txid, descendants);167}168Assume(m_to_be_replaced.empty());169Assume(m_requested_outpoints_by_txid.empty());170Assume(m_bump_fees.empty());171Assume(m_inclusion_order.empty());172SanityCheck();173}175// Compare by min(ancestor feerate, individual feerate), then txid176//177// Under the ancestor-based mining approach, high-feerate children can pay for parents, but high-feerate178// parents do not incentive inclusion of their children. Therefore the mining algorithm only considers179// transactions for inclusion on basis of the minimum of their own feerate or their ancestor feerate.180struct AncestorFeerateComparator181{182template<typename I>183bool operator()(const I& a, const I& b) const {184auto min_feerate = [](const MiniMinerMempoolEntry& e) -> FeeFrac {185FeeFrac self_feerate(e.GetModifiedFee(), e.GetTxSize());186FeeFrac ancestor_feerate(e.GetModFeesWithAncestors(), e.GetSizeWithAncestors());187return std::min<ByRatioNegSize<FeeFrac>>(ancestor_feerate, self_feerate);188};189FeeFrac a_feerate{min_feerate(a->second)};190FeeFrac b_feerate{min_feerate(b->second)};191if (a_feerate != b_feerate) {192return ByRatioNegSize{a_feerate} > ByRatioNegSize{b_feerate};193}194// Use txid as tiebreaker for stable sorting195return a->first < b->first;196}197};199void MiniMiner::DeleteAncestorPackage(const std::set<MockEntryMap::iterator, IteratorComparator>& ancestors)200{201Assume(ancestors.size() >= 1);202// "Mine" all transactions in this ancestor set.203for (auto& anc : ancestors) {204Assume(!m_in_block.contains(anc->first));205m_in_block.insert(anc->first);206m_total_fees += anc->second.GetModifiedFee();207m_total_vsize += anc->second.GetTxSize();208auto it = m_descendant_set_by_txid.find(anc->first);209// Each entry’s descendant set includes itself210Assume(it != m_descendant_set_by_txid.end());211for (auto& descendant : it->second) {212// If this fails, we must be double-deducting. Don't check fees because negative is possible.213Assume(descendant->second.GetSizeWithAncestors() >= anc->second.GetTxSize());214descendant->second.UpdateAncestorState(-anc->second.GetTxSize(), -anc->second.GetModifiedFee());215}216}217// Delete these entries.218for (const auto& anc : ancestors) {219m_descendant_set_by_txid.erase(anc->first);220// The above loop should have deducted each ancestor's size and fees from each of their221// respective descendants exactly once.222Assume(anc->second.GetModFeesWithAncestors() == 0);223Assume(anc->second.GetSizeWithAncestors() == 0);224auto vec_it = std::find(m_entries.begin(), m_entries.end(), anc);225Assume(vec_it != m_entries.end());226m_entries.erase(vec_it);227m_entries_by_txid.erase(anc);228}229}231void MiniMiner::SanityCheck() const232{233// m_entries, m_entries_by_txid, and m_descendant_set_by_txid all same size234Assume(m_entries.size() == m_entries_by_txid.size());235Assume(m_entries.size() == m_descendant_set_by_txid.size());236// Cached ancestor values should be at least as large as the transaction's own size237Assume(std::all_of(m_entries.begin(), m_entries.end(), [](const auto& entry) {238return entry->second.GetSizeWithAncestors() >= entry->second.GetTxSize();}));239// None of the entries should be to-be-replaced transactions240Assume(std::all_of(m_to_be_replaced.begin(), m_to_be_replaced.end(),241[&](const auto& txid){ return !m_entries_by_txid.contains(txid); }));242}244void MiniMiner::BuildMockTemplate(std::optional<CFeeRate> target_feerate)245{246const auto num_txns{m_entries_by_txid.size()};247uint32_t sequence_num{0};248while (!m_entries_by_txid.empty()) {249// Sort again, since transaction removal may change some m_entries' ancestor feerates.250std::sort(m_entries.begin(), m_entries.end(), AncestorFeerateComparator());
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.