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) 2021-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 <chain.h>6#include <chainparams.h>7#include <coins.h>8#include <consensus/consensus.h>9#include <consensus/validation.h>10#include <kernel/coinstats.h>11#include <node/blockstorage.h>12#include <node/utxo_snapshot.h>13#include <primitives/block.h>14#include <primitives/transaction.h>15#include <serialize.h>16#include <span.h>17#include <streams.h>18#include <sync.h>19#include <test/fuzz/FuzzedDataProvider.h>20#include <test/fuzz/fuzz.h>21#include <test/fuzz/util.h>22#include <test/util/mining.h>23#include <test/util/setup_common.h>24#include <test/util/time.h>25#include <uint256.h>26#include <util/check.h>27#include <util/fs.h>28#include <util/result.h>29#include <util/time.h>30#include <validation.h>32#include <cstdint>33#include <functional>34#include <ios>35#include <memory>36#include <optional>37#include <vector>39using node::SnapshotMetadata;41namespace {43const std::vector<std::shared_ptr<CBlock>>* g_chain;44TestingSetup* g_setup{nullptr};46/** Sanity check the assumeutxo values hardcoded in chainparams for the fuzz target. */47void sanity_check_snapshot()48{49Assert(g_chain && g_setup == nullptr);51// Create a temporary chainstate manager to connect the chain to.52const auto tmp_setup{MakeNoLogFileContext<TestingSetup>(ChainType::REGTEST, TestOpts{.setup_net = false})};53const auto& node{tmp_setup->m_node};54for (auto& block: *g_chain) {55ProcessBlock(node, block);56}58// Connect the chain to the tmp chainman and sanity check the chainparams snapshot values.59LOCK(cs_main);60auto& cs{node.chainman->ActiveChainstate()};61cs.ForceFlushStateToDisk(/*wipe_cache=*/false);62const auto stats{*Assert(kernel::ComputeUTXOStats(kernel::CoinStatsHashType::HASH_SERIALIZED, cs.CoinsDB(), node.chainman->m_blockman))};63const auto cp_au_data{*Assert(node.chainman->GetParams().AssumeutxoForHeight(2 * COINBASE_MATURITY))};64Assert(stats.nHeight == cp_au_data.height);65Assert(stats.nTransactions + 1 == cp_au_data.m_chain_tx_count); // +1 for the genesis tx.66Assert(stats.hashBlock == cp_au_data.blockhash);67Assert(AssumeutxoHash{stats.hashSerialized} == cp_au_data.hash_serialized);68}70template <bool INVALID>71void initialize_chain()72{73const auto params{CreateChainParams(ArgsManager{}, ChainType::REGTEST)};74static const auto chain{CreateBlockChain(2 * COINBASE_MATURITY, *params)};75g_chain = &chain;76FakeNodeClock node_clock{chain.back()->Time()};78// Make sure we can generate a valid snapshot.79sanity_check_snapshot();81static const auto setup{82MakeNoLogFileContext<TestingSetup>(ChainType::REGTEST,83TestOpts{84.setup_net = false,85.setup_validation_interface = false,86.min_validation_cache = true,87}),88};89if constexpr (INVALID) {90auto& chainman{*setup->m_node.chainman};91for (const auto& block : chain) {92BlockValidationState dummy;93bool processed{chainman.ProcessNewBlockHeaders({{*block}}, true, dummy)};94Assert(processed);95const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};96Assert(index);97}98}99g_setup = setup.get();100}102template <bool INVALID>103void utxo_snapshot_fuzz(FuzzBufferType buffer)104{105SeedRandomStateForTest(SeedRand::ZEROS);106FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());107FakeNodeClock node_clock{ConsumeTime(fuzzed_data_provider, /*min=*/1296688602)}; // regtest genesis block timestamp108auto& setup{*g_setup};109bool dirty_chainman{false}; // Reuse the global chainman, but reset it when it is dirty110auto& chainman{*setup.m_node.chainman};112const auto snapshot_path = gArgs.GetDataDirNet() / "fuzzed_snapshot.dat";114Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash);116{117AutoFile outfile{fsbridge::fopen(snapshot_path, "wb")};118// Metadata119if (fuzzed_data_provider.ConsumeBool()) {120std::vector<uint8_t> metadata{ConsumeRandomLengthByteVector(fuzzed_data_provider)};121outfile << std::span{metadata};122} else {123auto msg_start = chainman.GetParams().MessageStart();124int base_blockheight{fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 2 * COINBASE_MATURITY)};125uint256 base_blockhash{g_chain->at(base_blockheight - 1)->GetHash()};126uint64_t m_coins_count{fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(1, 3 * COINBASE_MATURITY)};127SnapshotMetadata metadata{msg_start, base_blockhash, m_coins_count};128outfile << metadata;129}130// Coins131if (fuzzed_data_provider.ConsumeBool()) {132std::vector<uint8_t> file_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)};133outfile << std::span{file_data};134} else {135int height{1};136for (const auto& block : *g_chain) {137auto coinbase{block->vtx.at(0)};138outfile << coinbase->GetHash();139WriteCompactSize(outfile, 1); // number of coins for the hash140WriteCompactSize(outfile, 0); // index of coin141outfile << Coin(coinbase->vout[0], height, /*fCoinBaseIn=*/true);142height++;143}144}145if constexpr (INVALID) {146// Append an invalid coin to ensure invalidity. This error will be147// detected late in PopulateAndValidateSnapshot, and allows the148// INVALID fuzz target to reach more potential code coverage.149const auto& coinbase{g_chain->back()->vtx.back()};150outfile << coinbase->GetHash();151WriteCompactSize(outfile, 1); // number of coins for the hash152WriteCompactSize(outfile, 999); // index of coin153outfile << Coin{coinbase->vout[0], /*nHeightIn=*/999, /*fCoinBaseIn=*/false};154}155assert(outfile.fclose() == 0);156}158const auto ActivateFuzzedSnapshot{[&] {159AutoFile infile{fsbridge::fopen(snapshot_path, "rb")};160auto msg_start = chainman.GetParams().MessageStart();161SnapshotMetadata metadata{msg_start};162try {163infile >> metadata;164} catch (const std::ios_base::failure&) {165return false;166}167return !!chainman.ActivateSnapshot(infile, metadata, /*in_memory=*/true);168}};170if (fuzzed_data_provider.ConsumeBool()) {171// Consume the bool, but skip the code for the INVALID fuzz target172if constexpr (!INVALID) {173for (const auto& block : *g_chain) {174BlockValidationState dummy;175bool processed{chainman.ProcessNewBlockHeaders({{*block}}, true, dummy)};176Assert(processed);177const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};178Assert(index);179}180dirty_chainman = true;181}182}184if (ActivateFuzzedSnapshot()) {185LOCK(::cs_main);186Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull());187const auto& coinscache{chainman.ActiveChainstate().CoinsTip()};188for (const auto& block : *g_chain) {189Assert(coinscache.HaveCoin(COutPoint{block->vtx.at(0)->GetHash(), 0}));190const auto* index{chainman.m_blockman.LookupBlockIndex(block->GetHash())};191Assert(index);192Assert(index->nTx == 0);193if (index->nHeight == chainman.ActiveChainstate().SnapshotBase()->nHeight) {194auto params{chainman.GetParams().AssumeutxoForHeight(index->nHeight)};195Assert(params.has_value());196Assert(params.value().m_chain_tx_count == index->m_chain_tx_count);197} else {198Assert(index->m_chain_tx_count == 0);199}200}201Assert(g_chain->size() == coinscache.GetCacheSize());202dirty_chainman = true;203} else {204Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash);205}206// Snapshot should refuse to load a second time regardless of validity207Assert(!ActivateFuzzedSnapshot());208if constexpr (INVALID) {209// Activating the snapshot, or any other action that makes the chainman210// "dirty" can and must not happen for the INVALID fuzz target211Assert(!dirty_chainman);212}213if (dirty_chainman) {214setup.m_node.chainman.reset();215setup.m_make_chainman();216setup.LoadVerifyActivateChainstate();217}218}220// There are two fuzz targets:221//222// The target 'utxo_snapshot', which allows valid snapshots, but is slow,223// because it has to reset the chainstate manager on almost all fuzz inputs.224// Otherwise, a dirty header tree or dirty chainstate could leak from one fuzz225// input execution into the next, which makes execution non-deterministic.226//227// The target 'utxo_snapshot_invalid', which is fast and does not require any228// expensive state to be reset.229FUZZ_TARGET(utxo_snapshot /*valid*/, .init = initialize_chain<false>) { utxo_snapshot_fuzz<false>(buffer); }230FUZZ_TARGET(utxo_snapshot_invalid, .init = initialize_chain<true>) { utxo_snapshot_fuzz<true>(buffer); }232} // namespace
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.