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) 2020-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 <index/coinstatsindex.h>7#include <arith_uint256.h>8#include <chain.h>9#include <chainparams.h>10#include <coins.h>11#include <common/args.h>12#include <consensus/amount.h>13#include <crypto/muhash.h>14#include <dbwrapper.h>15#include <index/base.h>16#include <index/db_key.h>17#include <interfaces/chain.h>18#include <interfaces/types.h>19#include <kernel/coinstats.h>20#include <primitives/block.h>21#include <primitives/transaction.h>22#include <script/script.h>23#include <serialize.h>24#include <uint256.h>25#include <undo.h>26#include <util/check.h>27#include <util/fs.h>28#include <util/log.h>29#include <validation.h>31#include <compare>32#include <limits>33#include <string>34#include <utility>35#include <vector>37using kernel::ApplyCoinHash;38using kernel::CCoinsStats;39using kernel::GetBogoSize;40using kernel::RemoveCoinHash;42static constexpr uint8_t DB_MUHASH{'M'};44namespace {46struct DBVal {47uint256 muhash{uint256::ZERO};48uint64_t transaction_output_count{0};49uint64_t bogo_size{0};50CAmount total_amount{0};51CAmount total_subsidy{0};52arith_uint256 total_prevout_spent_amount{0};53arith_uint256 total_new_outputs_ex_coinbase_amount{0};54arith_uint256 total_coinbase_amount{0};55CAmount total_unspendables_genesis_block{0};56CAmount total_unspendables_bip30{0};57CAmount total_unspendables_scripts{0};58CAmount total_unspendables_unclaimed_rewards{0};60SERIALIZE_METHODS(DBVal, obj)61{62uint256 prevout_spent, new_outputs, coinbase;63SER_WRITE(obj, prevout_spent = ArithToUint256(obj.total_prevout_spent_amount));64SER_WRITE(obj, new_outputs = ArithToUint256(obj.total_new_outputs_ex_coinbase_amount));65SER_WRITE(obj, coinbase = ArithToUint256(obj.total_coinbase_amount));67READWRITE(obj.muhash);68READWRITE(obj.transaction_output_count);69READWRITE(obj.bogo_size);70READWRITE(obj.total_amount);71READWRITE(obj.total_subsidy);72READWRITE(prevout_spent);73READWRITE(new_outputs);74READWRITE(coinbase);75READWRITE(obj.total_unspendables_genesis_block);76READWRITE(obj.total_unspendables_bip30);77READWRITE(obj.total_unspendables_scripts);78READWRITE(obj.total_unspendables_unclaimed_rewards);80SER_READ(obj, obj.total_prevout_spent_amount = UintToArith256(prevout_spent));81SER_READ(obj, obj.total_new_outputs_ex_coinbase_amount = UintToArith256(new_outputs));82SER_READ(obj, obj.total_coinbase_amount = UintToArith256(coinbase));83}84};85}; // namespace87std::unique_ptr<CoinStatsIndex> g_coin_stats_index;89CoinStatsIndex::CoinStatsIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory, bool f_wipe)90: BaseIndex(std::move(chain), "coinstatsindex", "coinstatsidx")91{92// An earlier version of the index used "indexes/coinstats" but it contained93// a bug and is superseded by a fixed version at "indexes/coinstatsindex".94// The original index is kept around until the next release in case users95// decide to downgrade their node.96auto old_path = gArgs.GetDataDirNet() / "indexes" / "coinstats";97if (fs::exists(old_path)) {98// TODO: Change this to deleting the old index with v31.99LogWarning("Old version of coinstatsindex found at %s. This folder can be safely deleted unless you " \100"plan to downgrade your node to version 29 or lower.", fs::PathToString(old_path));101}102fs::path path{gArgs.GetDataDirNet() / "indexes" / "coinstatsindex"};103fs::create_directories(path);105m_db = std::make_unique<CoinStatsIndex::DB>(path / "db", n_cache_size, f_memory, f_wipe);106}108bool CoinStatsIndex::CustomAppend(const interfaces::BlockInfo& block)109{110const CAmount block_subsidy{GetBlockSubsidy(block.height, Params().GetConsensus())};111m_total_subsidy += block_subsidy;113// Ignore genesis block114if (block.height > 0) {115uint256 expected_block_hash{*Assert(block.prev_hash)};116if (m_current_block_hash != expected_block_hash) {117LogError("previous block header belongs to unexpected block %s; expected %s",118m_current_block_hash.ToString(), expected_block_hash.ToString());119return false;120}122// Add the new utxos created from the block123assert(block.data);124for (size_t i = 0; i < block.data->vtx.size(); ++i) {125const auto& tx{block.data->vtx.at(i)};126const bool is_coinbase{tx->IsCoinBase()};128// Skip duplicate txid coinbase transactions (BIP30).129if (is_coinbase && IsBIP30Unspendable(block.hash, block.height)) {130m_total_unspendables_bip30 += block_subsidy;131continue;132}134for (uint32_t j = 0; j < tx->vout.size(); ++j) {135const CTxOut& out{tx->vout[j]};136const Coin coin{out, block.height, is_coinbase};137const COutPoint outpoint{tx->GetHash(), j};139// Skip unspendable coins140if (coin.out.scriptPubKey.IsUnspendable()) {141m_total_unspendables_scripts += coin.out.nValue;142continue;143}145ApplyCoinHash(m_muhash, outpoint, coin);147if (is_coinbase) {148m_total_coinbase_amount += coin.out.nValue;149} else {150m_total_new_outputs_ex_coinbase_amount += coin.out.nValue;151}153++m_transaction_output_count;154m_total_amount += coin.out.nValue;155m_bogo_size += GetBogoSize(coin.out.scriptPubKey);156}158// The coinbase tx has no undo data since no former output is spent159if (!is_coinbase) {160const auto& tx_undo{Assert(block.undo_data)->vtxundo.at(i - 1)};162for (size_t j = 0; j < tx_undo.vprevout.size(); ++j) {163const Coin& coin{tx_undo.vprevout[j]};164const COutPoint outpoint{tx->vin[j].prevout.hash, tx->vin[j].prevout.n};166RemoveCoinHash(m_muhash, outpoint, coin);168m_total_prevout_spent_amount += coin.out.nValue;170--m_transaction_output_count;171m_total_amount -= coin.out.nValue;172m_bogo_size -= GetBogoSize(coin.out.scriptPubKey);173}174}175}176} else {177// genesis block178m_total_unspendables_genesis_block += block_subsidy;179}181// If spent prevouts + block subsidy are still a higher amount than182// new outputs + coinbase + current unspendable amount this means183// the miner did not claim the full block reward. Unclaimed block184// rewards are also unspendable.185const CAmount temp_total_unspendable_amount{m_total_unspendables_genesis_block + m_total_unspendables_bip30 + m_total_unspendables_scripts + m_total_unspendables_unclaimed_rewards};186const arith_uint256 unclaimed_rewards{(m_total_prevout_spent_amount + m_total_subsidy) - (m_total_new_outputs_ex_coinbase_amount + m_total_coinbase_amount + temp_total_unspendable_amount)};187assert(unclaimed_rewards <= arith_uint256(std::numeric_limits<CAmount>::max()));188m_total_unspendables_unclaimed_rewards += static_cast<CAmount>(unclaimed_rewards.GetLow64());190std::pair<uint256, DBVal> value;191value.first = block.hash;192value.second.transaction_output_count = m_transaction_output_count;193value.second.bogo_size = m_bogo_size;194value.second.total_amount = m_total_amount;195value.second.total_subsidy = m_total_subsidy;196value.second.total_prevout_spent_amount = m_total_prevout_spent_amount;197value.second.total_new_outputs_ex_coinbase_amount = m_total_new_outputs_ex_coinbase_amount;198value.second.total_coinbase_amount = m_total_coinbase_amount;199value.second.total_unspendables_genesis_block = m_total_unspendables_genesis_block;200value.second.total_unspendables_bip30 = m_total_unspendables_bip30;201value.second.total_unspendables_scripts = m_total_unspendables_scripts;202value.second.total_unspendables_unclaimed_rewards = m_total_unspendables_unclaimed_rewards;204uint256 out;205m_muhash.Finalize(out);206value.second.muhash = out;208m_current_block_hash = block.hash;210// Intentionally do not update DB_MUHASH here so it stays in sync with211// DB_BEST_BLOCK, and the index is not corrupted if there is an unclean shutdown.212m_db->Write(index_util::DBHeightKey(block.height), value);213return true;214}216bool CoinStatsIndex::CustomRemove(const interfaces::BlockInfo& block)217{218CDBBatch batch(*m_db);219std::unique_ptr<CDBIterator> db_it(m_db->NewIterator());221// During a reorg, copy the block's hash digest from the height index to the hash index,222// ensuring it's still accessible after the height index entry is overwritten.223if (!index_util::CopyHeightIndexToHashIndex<DBVal>(*db_it, batch, m_name, block.height)) {224return false;225}227m_db->WriteBatch(batch);229if (!RevertBlock(block)) {230return false; // failure cause logged internally231}233return true;234}236std::optional<CCoinsStats> CoinStatsIndex::LookUpStats(const CBlockIndex& block_index) const237{238CCoinsStats stats{block_index.nHeight, block_index.GetBlockHash()};239stats.index_used = true;241DBVal entry;242if (!index_util::LookUpOne(*m_db, {block_index.GetBlockHash(), block_index.nHeight}, entry)) {243return std::nullopt;244}246stats.hashSerialized = entry.muhash;247stats.nTransactionOutputs = entry.transaction_output_count;248stats.nBogoSize = entry.bogo_size;249stats.total_amount = entry.total_amount;250stats.total_subsidy = entry.total_subsidy;