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.
5751strprintf("%s (%.2f MB)",5752snapshot_loaded ? "saving snapshot chainstate" : "flushing coins cache",5753coins_cache.DynamicMemoryUsage() / (1000 * 1000)),5754BCLog::LogFlags::ALL);5756coins_cache.Flush();5757}5759struct StopHashingException : public std::exception5760{5761const char* what() const noexcept override5762{5763return "ComputeUTXOStats interrupted.";5764}5765};5767static void SnapshotUTXOHashBreakpoint(const util::SignalInterrupt& interrupt)5768{5769if (interrupt) throw StopHashingException();5770}5772util::Result<void> ChainstateManager::PopulateAndValidateSnapshot(5773Chainstate& snapshot_chainstate,5774AutoFile& coins_file,5775const SnapshotMetadata& metadata)5776{5777// It's okay to release cs_main before we're done using `coins_cache` because we know5778// that nothing else will be referencing the newly created snapshot_chainstate yet.5779CCoinsViewCache& coins_cache = *WITH_LOCK(::cs_main, return &snapshot_chainstate.CoinsTip());5781uint256 base_blockhash = metadata.m_base_blockhash;5783CBlockIndex* snapshot_start_block = WITH_LOCK(::cs_main, return m_blockman.LookupBlockIndex(base_blockhash));5785if (!snapshot_start_block) {5786// Needed for ComputeUTXOStats to determine the5787// height and to avoid a crash when base_blockhash.IsNull()5788return util::Error{Untranslated(strprintf("Did not find snapshot start blockheader %s",5789base_blockhash.ToString()))};5790}5792int base_height = snapshot_start_block->nHeight;5793const auto& maybe_au_data = GetParams().AssumeutxoForHeight(base_height);5795if (!maybe_au_data) {5796return util::Error{Untranslated(strprintf("Assumeutxo height in snapshot metadata not recognized "5797"(%d) - refusing to load snapshot", base_height))};5798}5800const AssumeutxoData& au_data = *maybe_au_data;5802// This work comparison is a duplicate check with the one performed later in5803// ActivateSnapshot(), but is done so that we avoid doing the long work of staging5804// a snapshot that isn't actually usable.5805if (WITH_LOCK(::cs_main, return !CBlockIndexWorkComparator()(ActiveTip(), snapshot_start_block))) {5806return util::Error{Untranslated("Work does not exceed active chainstate")};5807}5809const uint64_t coins_count = metadata.m_coins_count;5810uint64_t coins_left = metadata.m_coins_count;5812LogInfo("[snapshot] loading %d coins from snapshot %s", coins_left, base_blockhash.ToString());5813int64_t coins_processed{0};5815while (coins_left > 0) {5816try {5817Txid txid;5818coins_file >> txid;5819size_t coins_per_txid{0};5820coins_per_txid = ReadCompactSize(coins_file);5822if (coins_per_txid > coins_left) {5823return util::Error{Untranslated("Mismatch in coins count in snapshot metadata and actual snapshot data")};5824}5826for (size_t i = 0; i < coins_per_txid; i++) {5827COutPoint outpoint;5828Coin coin;5829outpoint.n = static_cast<uint32_t>(ReadCompactSize(coins_file));5830outpoint.hash = txid;5831coins_file >> coin;5832if (coin.nHeight > base_height ||5833outpoint.n >= std::numeric_limits<decltype(outpoint.n)>::max() // Avoid integer wrap-around in coinstats.cpp:ApplyHash5834) {5835return util::Error{Untranslated(strprintf("Bad snapshot data after deserializing %d coins",5836coins_count - coins_left))};5837}5838if (!MoneyRange(coin.out.nValue)) {5839return util::Error{Untranslated(strprintf("Bad snapshot data after deserializing %d coins - bad tx out value",5840coins_count - coins_left))};5841}5842coins_cache.EmplaceCoinInternalDANGER(outpoint, std::move(coin));5844--coins_left;5845++coins_processed;5847if (coins_processed % 1000000 == 0) {5848LogInfo("[snapshot] %d coins loaded (%.2f%%, %.2f MB)",5849coins_processed,5850static_cast<float>(coins_processed) * 100 / static_cast<float>(coins_count),5851coins_cache.DynamicMemoryUsage() / (1000 * 1000));5852}5854// Batch write and flush (if we need to) every so often.5855//5856// If our average Coin size is roughly 41 bytes, checking every 120,000 coins5857// means <5MB of memory imprecision.5858if (coins_processed % 120000 == 0) {5859if (m_interrupt) {5860return util::Error{Untranslated("Aborting after an interrupt was requested")};5861}5863const auto snapshot_cache_state = WITH_LOCK(::cs_main,5864return snapshot_chainstate.GetCoinsCacheSizeState());5866if (snapshot_cache_state >= CoinsCacheSizeState::CRITICAL) {5867// This is a hack - we don't know what the actual best block is, but that5868// doesn't matter for the purposes of flushing the cache here. We'll set this5869// to its correct value (`base_blockhash`) below after the coins are loaded.5870coins_cache.SetBestBlock(GetRandHash());5872// No need to acquire cs_main since this chainstate isn't being used yet.5873FlushSnapshotToDisk(coins_cache, /*snapshot_loaded=*/false);5874}5875}5876}5877} catch (const std::ios_base::failure&) {5878return util::Error{Untranslated(strprintf("Bad snapshot format or truncated snapshot after deserializing %d coins",5879coins_processed))};5880}5881}5883// Important that we set this. This and the coins_cache accesses above are5884// sort of a layer violation, but either we reach into the innards of5885// CCoinsViewCache here or we have to invert some of the Chainstate to5886// embed them in a snapshot-activation-specific CCoinsViewCache bulk load5887// method.5888coins_cache.SetBestBlock(base_blockhash);5890bool out_of_coins{false};5891try {5892std::byte left_over_byte;5893coins_file >> left_over_byte;5894} catch (const std::ios_base::failure&) {5895// We expect an exception since we should be out of coins.5896out_of_coins = true;5897}5898if (!out_of_coins) {5899return util::Error{Untranslated(strprintf("Bad snapshot - coins left over after deserializing %d coins",5900coins_count))};5901}5903LogInfo("[snapshot] loaded %d (%.2f MB) coins from snapshot %s",5904coins_count,5905coins_cache.DynamicMemoryUsage() / (1000 * 1000),5906base_blockhash.ToString());5908// No need to acquire cs_main since this chainstate isn't being used yet.5909FlushSnapshotToDisk(coins_cache, /*snapshot_loaded=*/true);5911assert(coins_cache.GetBestBlock() == base_blockhash);5913// As above, okay to immediately release cs_main here since no other context knows5914// about the snapshot_chainstate.5915const CCoinsViewDB& snapshot_coinsdb = WITH_LOCK(::cs_main, return snapshot_chainstate.CoinsDB());5917std::optional<CCoinsStats> maybe_stats;5919try {5920maybe_stats = ComputeUTXOStats(5921CoinStatsHashType::HASH_SERIALIZED, snapshot_coinsdb, m_blockman, [&interrupt = m_interrupt] { SnapshotUTXOHashBreakpoint(interrupt); });5922} catch (StopHashingException const&) {5923return util::Error{Untranslated("Aborting after an interrupt was requested")};5924}5925if (!maybe_stats.has_value()) {5926return util::Error{Untranslated("Failed to generate coins stats")};5927}5929// Assert that the deserialized chainstate contents match the expected assumeutxo value.5930if (AssumeutxoHash{maybe_stats->hashSerialized} != au_data.hash_serialized) {5931return util::Error{Untranslated(strprintf("Bad snapshot content hash: expected %s, got %s",5932au_data.hash_serialized.ToString(), maybe_stats->hashSerialized.ToString()))};5933}5935snapshot_chainstate.m_chain.SetTip(*snapshot_start_block);5937// The remainder of this function requires modifying data protected by cs_main.5938LOCK(::cs_main);5940// Fake various pieces of CBlockIndex state:5941CBlockIndex* index = nullptr;5943// Don't make any modifications to the genesis block since it shouldn't be5944// necessary, and since the genesis block doesn't have normal flags like5945// BLOCK_VALID_SCRIPTS set.5946constexpr int AFTER_GENESIS_START{1};5948for (int i = AFTER_GENESIS_START; i <= snapshot_chainstate.m_chain.Height(); ++i) {5949index = snapshot_chainstate.m_chain[i];5951// Fake BLOCK_OPT_WITNESS so that Chainstate::NeedsRedownload()5952// won't ask for -reindex on startup.5953if (DeploymentActiveAt(*index, *this, Consensus::DEPLOYMENT_SEGWIT)) {5954index->nStatus |= BLOCK_OPT_WITNESS;5955}5957m_blockman.m_dirty_blockindex.insert(index);5958// Changes to the block index will be flushed to disk after this call5959// returns in `ActivateSnapshot()`, when `MaybeRebalanceCaches()` is5960// called, since we've added a snapshot chainstate and therefore will5961// have to downsize the IBD chainstate, which will result in a call to5962// `FlushStateToDisk(FORCE_FLUSH)`.5963}5965assert(index);5966assert(index == snapshot_start_block);5967index->m_chain_tx_count = au_data.m_chain_tx_count;5969LogInfo("[snapshot] validated snapshot (%.2f MB)",5970coins_cache.DynamicMemoryUsage() / (1000 * 1000));5971return {};5972}5974// Currently, this function holds cs_main for its duration, which could be for5975// multiple minutes due to the ComputeUTXOStats call. Holding cs_main used to be5976// necessary (before d43a1f1a2fa3) to avoid advancing validated_cs farther than5977// its target block. Now it should be possible to avoid this, but simply5978// releasing cs_main here would not be possible because this function is invoked5979// by ConnectTip within ActivateBestChain.5980//5981// Eventually (TODO) it would be better to call this function outside of5982// ActivateBestChain, on a separate thread that should not require cs_main to5983// hash, because the UTXO set is only hashed after the historical chainstate5984// reaches its target block and is no longer changing.5985SnapshotCompletionResult ChainstateManager::MaybeValidateSnapshot(Chainstate& validated_cs, Chainstate& unvalidated_cs)5986{5987AssertLockHeld(cs_main);5989// If the snapshot does not need to be validated...5990if (unvalidated_cs.m_assumeutxo != Assumeutxo::UNVALIDATED ||5991// Or if either chainstate is unusable...5992!unvalidated_cs.m_from_snapshot_blockhash ||5993validated_cs.m_assumeutxo != Assumeutxo::VALIDATED ||5994!validated_cs.m_chain.Tip() ||5995// Or the validated chainstate is not targeting the snapshot block...5996!validated_cs.TargetBlockHash() ||5997*validated_cs.TargetBlockHash() != *unvalidated_cs.m_from_snapshot_blockhash ||5998// Or the validated chainstate has not reached the snapshot block yet...5999!validated_cs.ReachedTarget()) {6000// Then the snapshot cannot be validated and there is nothing to do.