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.
6001return SnapshotCompletionResult::SKIPPED;6002}6003assert(validated_cs.TargetBlock() == validated_cs.m_chain.Tip());6005auto handle_invalid_snapshot = [&]() EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {6006bilingual_str user_error = strprintf(_(6007"%s failed to validate the -assumeutxo snapshot state. "6008"This indicates a hardware problem, or a bug in the software, or a "6009"bad software modification that allowed an invalid snapshot to be "6010"loaded. As a result of this, the node will shut down and stop using any "6011"state that was built on the snapshot, resetting the chain height "6012"from %d to %d. On the next "6013"restart, the node will resume syncing from %d "6014"without using any snapshot data. "6015"Please report this incident to %s, including how you obtained the snapshot. "6016"The invalid snapshot chainstate will be left on disk in case it is "6017"helpful in diagnosing the issue that caused this error."),6018CLIENT_NAME, unvalidated_cs.m_chain.Height(),6019validated_cs.m_chain.Height(),6020validated_cs.m_chain.Height(), CLIENT_BUGREPORT);6022LogError("[snapshot] !!! %s\n", user_error.original);6023LogError("[snapshot] deleting snapshot, reverting to validated chain, and stopping node\n");6025// Reset chainstate target to network tip instead of snapshot block.6026validated_cs.SetTargetBlock(nullptr);6028unvalidated_cs.m_assumeutxo = Assumeutxo::INVALID;6030auto rename_result = unvalidated_cs.InvalidateCoinsDBOnDisk();6031if (!rename_result) {6032user_error += Untranslated("\n") + util::ErrorString(rename_result);6033}6035GetNotifications().fatalError(user_error);6036};6038CCoinsViewDB& validated_coins_db = validated_cs.CoinsDB();6039validated_cs.ForceFlushStateToDisk();6041const auto& maybe_au_data = m_options.chainparams.AssumeutxoForHeight(validated_cs.m_chain.Height());6042if (!maybe_au_data) {6043LogWarning("[snapshot] assumeutxo data not found for height "6044"(%d) - refusing to validate snapshot", validated_cs.m_chain.Height());6045handle_invalid_snapshot();6046return SnapshotCompletionResult::MISSING_CHAINPARAMS;6047}6049const AssumeutxoData& au_data = *maybe_au_data;6050std::optional<CCoinsStats> validated_cs_stats;6051LogInfo("[snapshot] computing UTXO stats for background chainstate to validate "6052"snapshot - this could take a few minutes");6053try {6054validated_cs_stats = ComputeUTXOStats(6055CoinStatsHashType::HASH_SERIALIZED,6056validated_coins_db,6057m_blockman,6058[&interrupt = m_interrupt] { SnapshotUTXOHashBreakpoint(interrupt); });6059} catch (StopHashingException const&) {6060return SnapshotCompletionResult::STATS_FAILED;6061}6063// XXX note that this function is slow and will hold cs_main for potentially minutes.6064if (!validated_cs_stats) {6065LogWarning("[snapshot] failed to generate stats for validation coins db");6066// While this isn't a problem with the snapshot per se, this condition6067// prevents us from validating the snapshot, so we should shut down and let the6068// user handle the issue manually.6069handle_invalid_snapshot();6070return SnapshotCompletionResult::STATS_FAILED;6071}6073// Compare the validated chainstate's UTXO set hash against the hard-coded6074// assumeutxo hash we expect.6075//6076// TODO: For belt-and-suspenders, we could cache the UTXO set6077// hash for the snapshot when it's loaded in its chainstate's leveldb. We could then6078// reference that here for an additional check.6079if (AssumeutxoHash{validated_cs_stats->hashSerialized} != au_data.hash_serialized) {6080LogWarning("[snapshot] hash mismatch: actual=%s, expected=%s",6081validated_cs_stats->hashSerialized.ToString(),6082au_data.hash_serialized.ToString());6083handle_invalid_snapshot();6084return SnapshotCompletionResult::HASH_MISMATCH;6085}6087LogInfo("[snapshot] snapshot beginning at %s has been fully validated",6088unvalidated_cs.m_from_snapshot_blockhash->ToString());6090unvalidated_cs.m_assumeutxo = Assumeutxo::VALIDATED;6091validated_cs.m_target_utxohash = AssumeutxoHash{validated_cs_stats->hashSerialized};6092this->MaybeRebalanceCaches();6094return SnapshotCompletionResult::SUCCESS;6095}6097Chainstate& ChainstateManager::ActiveChainstate() const6098{6099LOCK(::cs_main);6100return CurrentChainstate();6101}6103void ChainstateManager::MaybeRebalanceCaches()6104{6105AssertLockHeld(::cs_main);6106Chainstate& current_cs{CurrentChainstate()};6107Chainstate* historical_cs{HistoricalChainstate()};6108if (!historical_cs && !current_cs.m_from_snapshot_blockhash) {6109// Allocate everything to the IBD chainstate. This will always happen6110// when we are not using a snapshot.6111current_cs.ResizeCoinsCaches(m_total_coinstip_cache, m_total_coinsdb_cache);6112} else if (!historical_cs) {6113// If background validation has completed and snapshot is our active chain...6114LogInfo("[snapshot] allocating all cache to the snapshot chainstate");6115// Allocate everything to the snapshot chainstate.6116current_cs.ResizeCoinsCaches(m_total_coinstip_cache, m_total_coinsdb_cache);6117} else {6118// If both chainstates exist, determine who needs more cache based on IBD status.6119//6120// Note: shrink caches first so that we don't inadvertently overwhelm available memory.6121if (IsInitialBlockDownload()) {6122historical_cs->ResizeCoinsCaches(6123m_total_coinstip_cache * 0.05, m_total_coinsdb_cache * 0.05);6124current_cs.ResizeCoinsCaches(6125m_total_coinstip_cache * 0.95, m_total_coinsdb_cache * 0.95);6126} else {6127current_cs.ResizeCoinsCaches(6128m_total_coinstip_cache * 0.05, m_total_coinsdb_cache * 0.05);6129historical_cs->ResizeCoinsCaches(6130m_total_coinstip_cache * 0.95, m_total_coinsdb_cache * 0.95);6131}6132}6133}6135void ChainstateManager::ResetChainstates()6136{6137m_chainstates.clear();6138}6140/**6141* Apply default chain params to nullopt members.6142* This helps to avoid coding errors around the accidental use of the compare6143* operators that accept nullopt, thus ignoring the intended default value.6144*/6145static ChainstateManager::Options&& Flatten(ChainstateManager::Options&& opts)6146{6147if (!opts.check_block_index.has_value()) opts.check_block_index = opts.chainparams.DefaultConsistencyChecks();6148if (!opts.minimum_chain_work.has_value()) opts.minimum_chain_work = UintToArith256(opts.chainparams.GetConsensus().nMinimumChainWork);6149if (!opts.assumed_valid_block.has_value()) opts.assumed_valid_block = opts.chainparams.GetConsensus().defaultAssumeValid;6150return std::move(opts);6151}6153ChainstateManager::ChainstateManager(const util::SignalInterrupt& interrupt, Options options, node::BlockManager::Options blockman_options)6154: m_script_check_queue{/*batch_size=*/128, std::clamp(options.worker_threads_num, 0, MAX_SCRIPTCHECK_THREADS)},6155m_interrupt{interrupt},6156m_options{Flatten(std::move(options))},6157m_blockman{interrupt, std::move(blockman_options)},6158m_validation_cache{m_options.script_execution_cache_bytes, m_options.signature_cache_bytes}6159{6160}6162ChainstateManager::~ChainstateManager()6163{6164LOCK(::cs_main);6166m_versionbitscache.Clear();6167}6169Chainstate* ChainstateManager::LoadAssumeutxoChainstate()6170{6171assert(!CurrentChainstate().m_from_snapshot_blockhash);6172std::optional<fs::path> path = node::FindAssumeutxoChainstateDir(m_options.datadir);6173if (!path) {6174return nullptr;6175}6176std::optional<uint256> base_blockhash = node::ReadSnapshotBaseBlockhash(*path);6177if (!base_blockhash) {6178return nullptr;6179}6180LogInfo("[snapshot] detected active snapshot chainstate (%s) - loading",6181fs::PathToString(*path));6183auto snapshot_chainstate{std::make_unique<Chainstate>(nullptr, m_blockman, *this, base_blockhash)};6184LogInfo("[snapshot] switching active chainstate to %s", snapshot_chainstate->ToString());6185return &this->AddChainstate(std::move(snapshot_chainstate));6186}6188Chainstate& ChainstateManager::AddChainstate(std::unique_ptr<Chainstate> chainstate)6189{6190Chainstate& prev_chainstate{CurrentChainstate()};6191assert(prev_chainstate.m_assumeutxo == Assumeutxo::VALIDATED);6192// Set target block for historical chainstate to snapshot block.6193assert(!prev_chainstate.TargetBlockHash());6194prev_chainstate.SetTargetBlockHash(*Assert(chainstate->m_from_snapshot_blockhash));6195m_chainstates.push_back(std::move(chainstate));6196Chainstate& curr_chainstate{CurrentChainstate()};6197assert(&curr_chainstate == m_chainstates.back().get());6199// Transfer possession of the mempool to the chainstate.6200// Mempool is empty at this point because we're still in IBD.6201assert(!prev_chainstate.m_mempool || prev_chainstate.m_mempool->size() == 0);6202assert(!curr_chainstate.m_mempool);6203std::swap(curr_chainstate.m_mempool, prev_chainstate.m_mempool);6204return curr_chainstate;6205}6207bool IsBIP30Repeat(const CBlockIndex& block_index)6208{6209return (block_index.nHeight==91842 && block_index.GetBlockHash() == uint256{"00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec"}) ||6210(block_index.nHeight==91880 && block_index.GetBlockHash() == uint256{"00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721"});6211}6213bool IsBIP30Unspendable(const uint256& block_hash, int block_height)6214{6215return (block_height==91722 && block_hash == uint256{"00000000000271a2dc26e7667f8419f2e15416dc6955e5a6c6cdf3f2574dd08e"}) ||6216(block_height==91812 && block_hash == uint256{"00000000000af0aed4792b1acee3d966af36cf5def14935db8de83d6f9306f2f"});6217}6219util::Result<void> Chainstate::InvalidateCoinsDBOnDisk()6220{6221// Should never be called on a non-snapshot chainstate.6222assert(m_from_snapshot_blockhash);6224// Coins views no longer usable.6225m_coins_views.reset();6227const fs::path db_path{StoragePath()};6228const fs::path invalid_path{db_path + "_INVALID"};6229const std::string db_path_str{fs::PathToString(db_path)};6230const std::string invalid_path_str{fs::PathToString(invalid_path)};6231LogInfo("[snapshot] renaming snapshot datadir %s to %s", db_path_str, invalid_path_str);6233// The invalid storage directory is simply moved and not deleted because we may6234// want to do forensics later during issue investigation. The user is instructed6235// accordingly in MaybeValidateSnapshot().6236try {6237fs::rename(db_path, invalid_path);6238} catch (const fs::filesystem_error& e) {6239LogError("While invalidating the coins db: Error renaming file '%s' -> '%s': %s",6240db_path_str, invalid_path_str, e.what());6241return util::Error{strprintf(_(6242"Rename of '%s' -> '%s' failed. "6243"You should resolve this by manually moving or deleting the invalid "6244"snapshot directory %s, otherwise you will encounter the same error again "6245"on the next startup."),6246db_path_str, invalid_path_str, db_path_str)};6247}6248return {};6249}