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.
5501} else {5502// Otherwise, flush state to disk and deallocate the in-memory coins map.5503ret = FlushStateToDisk(state, FlushStateMode::FORCE_FLUSH);5504}5505return ret;5506}5508double ChainstateManager::GuessVerificationProgress(const CBlockIndex* pindex) const5509{5510AssertLockHeld(GetMutex());5511const ChainTxData& data{GetParams().TxData()};5512if (pindex == nullptr) {5513return 0.0;5514}5516if (pindex->m_chain_tx_count == 0) {5517LogDebug(BCLog::VALIDATION, "Block %d has unset m_chain_tx_count. Unable to estimate verification progress.\n", pindex->nHeight);5518return 0.0;5519}5521const int64_t nNow{TicksSinceEpoch<std::chrono::seconds>(NodeClock::now())};5522const auto block_time{5523(Assume(m_best_header) && std::abs(nNow - pindex->GetBlockTime()) <= Ticks<std::chrono::seconds>(2h) &&5524Assume(m_best_header->nHeight >= pindex->nHeight)) ?5525// When the header is known to be recent, switch to a height-based5526// approach. This ensures the returned value is quantized when5527// close to "1.0", because some users expect it to be. This also5528// avoids relying too much on the exact miner-set timestamp, which5529// may be off.5530nNow - (m_best_header->nHeight - pindex->nHeight) * GetConsensus().nPowTargetSpacing :5531pindex->GetBlockTime(),5532};5534double fTxTotal;5536if (pindex->m_chain_tx_count <= data.tx_count) {5537fTxTotal = data.tx_count + (nNow - data.nTime) * data.dTxRate;5538} else {5539fTxTotal = pindex->m_chain_tx_count + (nNow - block_time) * data.dTxRate;5540}5542return std::min<double>(pindex->m_chain_tx_count / fTxTotal, 1.0);5543}5545double ChainstateManager::GetBackgroundVerificationProgress(const CBlockIndex& pindex) const5546{5547AssertLockHeld(GetMutex());5548Assert(HistoricalChainstate());5549auto target_block = HistoricalChainstate()->TargetBlock();5551if (pindex.m_chain_tx_count == 0 || target_block->m_chain_tx_count == 0) {5552LogDebug(BCLog::VALIDATION, "[background validation] Block %d has unset m_chain_tx_count. Unable to estimate verification progress.", pindex.nHeight);5553return 0.0;5554}5555return static_cast<double>(pindex.m_chain_tx_count) / static_cast<double>(target_block->m_chain_tx_count);5556}5558Chainstate& ChainstateManager::InitializeChainstate(CTxMemPool* mempool)5559{5560AssertLockHeld(::cs_main);5561assert(m_chainstates.empty());5562m_chainstates.emplace_back(std::make_unique<Chainstate>(mempool, m_blockman, *this));5563return *m_chainstates.back();5564}5566[[nodiscard]] static bool DeleteCoinsDBFromDisk(const fs::path db_path, bool is_snapshot)5567EXCLUSIVE_LOCKS_REQUIRED(::cs_main)5568{5569AssertLockHeld(::cs_main);5571if (is_snapshot) {5572fs::path base_blockhash_path = db_path / node::SNAPSHOT_BLOCKHASH_FILENAME;5574try {5575bool existed = fs::remove(base_blockhash_path);5576if (!existed) {5577LogWarning("[snapshot] snapshot chainstate dir being removed lacks %s file",5578fs::PathToString(node::SNAPSHOT_BLOCKHASH_FILENAME));5579}5580} catch (const fs::filesystem_error& e) {5581LogWarning("[snapshot] failed to remove file %s: %s\n",5582fs::PathToString(base_blockhash_path), e.code().message());5583}5584}5586std::string path_str = fs::PathToString(db_path);5587LogInfo("Removing leveldb dir at %s\n", path_str);5589// We have to destruct before this call leveldb::DB in order to release the db5590// lock, otherwise `DestroyDB` will fail. See `leveldb::~DBImpl()`.5591const bool destroyed = DestroyDB(path_str);5593if (!destroyed) {5594LogError("leveldb DestroyDB call failed on %s", path_str);5595}5597// Datadir should be removed from filesystem; otherwise initialization may detect5598// it on subsequent statups and get confused.5599//5600// If the base_blockhash_path removal above fails in the case of snapshot5601// chainstates, this will return false since leveldb won't remove a non-empty5602// directory.5603return destroyed && !fs::exists(db_path);5604}5606util::Result<CBlockIndex*> ChainstateManager::ActivateSnapshot(5607AutoFile& coins_file,5608const SnapshotMetadata& metadata,5609bool in_memory)5610{5611uint256 base_blockhash = metadata.m_base_blockhash;5613CBlockIndex* snapshot_start_block{};5615{5616LOCK(::cs_main);5618if (this->CurrentChainstate().m_from_snapshot_blockhash) {5619return util::Error{Untranslated("Can't activate a snapshot-based chainstate more than once")};5620}5621if (!GetParams().AssumeutxoForBlockhash(base_blockhash).has_value()) {5622auto available_heights = GetParams().GetAvailableSnapshotHeights();5623std::string heights_formatted = util::Join(available_heights, ", ", [&](const auto& i) { return util::ToString(i); });5624return util::Error{Untranslated(strprintf("assumeutxo block hash in snapshot metadata not recognized (hash: %s). The following snapshot heights are available: %s",5625base_blockhash.ToString(),5626heights_formatted))};5627}5629snapshot_start_block = m_blockman.LookupBlockIndex(base_blockhash);5630if (!snapshot_start_block) {5631return util::Error{Untranslated(strprintf("The base block header (%s) must appear in the headers chain. Make sure all headers are syncing, and call loadtxoutset again",5632base_blockhash.ToString()))};5633}5635bool start_block_invalid = snapshot_start_block->nStatus & BLOCK_FAILED_VALID;5636if (start_block_invalid) {5637return util::Error{Untranslated(strprintf("The base block header (%s) is part of an invalid chain", base_blockhash.ToString()))};5638}5640if (!m_best_header || m_best_header->GetAncestor(snapshot_start_block->nHeight) != snapshot_start_block) {5641return util::Error{Untranslated("A forked headers-chain with more work than the chain with the snapshot base block header exists. Please proceed to sync without AssumeUtxo.")};5642}5644auto mempool{CurrentChainstate().GetMempool()};5645if (mempool && mempool->size() > 0) {5646return util::Error{Untranslated("Can't activate a snapshot when mempool not empty")};5647}5648}5650int64_t current_coinsdb_cache_size{0};5651int64_t current_coinstip_cache_size{0};5653// Cache percentages to allocate to each chainstate.5654//5655// These particular percentages don't matter so much since they will only be5656// relevant during snapshot activation; caches are rebalanced at the conclusion of5657// this function. We want to give (essentially) all available cache capacity to the5658// snapshot to aid the bulk load later in this function.5659static constexpr double IBD_CACHE_PERC = 0.01;5660static constexpr double SNAPSHOT_CACHE_PERC = 0.99;5662{5663LOCK(::cs_main);5664// Resize the coins caches to ensure we're not exceeding memory limits.5665//5666// Allocate the majority of the cache to the incoming snapshot chainstate, since5667// (optimistically) getting to its tip will be the top priority. We'll need to call5668// `MaybeRebalanceCaches()` once we're done with this function to ensure5669// the right allocation (including the possibility that no snapshot was activated5670// and that we should restore the active chainstate caches to their original size).5671//5672current_coinsdb_cache_size = this->ActiveChainstate().m_coinsdb_cache_size_bytes;5673current_coinstip_cache_size = this->ActiveChainstate().m_coinstip_cache_size_bytes;5675// Temporarily resize the active coins cache to make room for the newly-created5676// snapshot chain.5677this->ActiveChainstate().ResizeCoinsCaches(5678static_cast<size_t>(current_coinstip_cache_size * IBD_CACHE_PERC),5679static_cast<size_t>(current_coinsdb_cache_size * IBD_CACHE_PERC));5680}5682auto snapshot_chainstate = WITH_LOCK(::cs_main,5683return std::make_unique<Chainstate>(5684/*mempool=*/nullptr, m_blockman, *this, base_blockhash));5686{5687LOCK(::cs_main);5688snapshot_chainstate->InitCoinsDB(5689static_cast<size_t>(current_coinsdb_cache_size * SNAPSHOT_CACHE_PERC),5690in_memory, /*should_wipe=*/false);5691snapshot_chainstate->InitCoinsCache(5692static_cast<size_t>(current_coinstip_cache_size * SNAPSHOT_CACHE_PERC));5693}5695auto cleanup_bad_snapshot = [&](bilingual_str reason) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {5696this->MaybeRebalanceCaches();5698// PopulateAndValidateSnapshot can return (in error) before the leveldb datadir5699// has been created, so only attempt removal if we got that far.5700if (auto snapshot_datadir = node::FindAssumeutxoChainstateDir(m_options.datadir)) {5701// We have to destruct leveldb::DB in order to release the db lock, otherwise5702// DestroyDB() (in DeleteCoinsDBFromDisk()) will fail. See `leveldb::~DBImpl()`.5703// Destructing the chainstate (and so resetting the coinsviews object) does this.5704snapshot_chainstate.reset();5705bool removed = DeleteCoinsDBFromDisk(*snapshot_datadir, /*is_snapshot=*/true);5706if (!removed) {5707GetNotifications().fatalError(strprintf(_("Failed to remove snapshot chainstate dir (%s). "5708"Manually remove it before restarting.\n"), fs::PathToString(*snapshot_datadir)));5709}5710}5711return util::Error{std::move(reason)};5712};5714if (auto res{this->PopulateAndValidateSnapshot(*snapshot_chainstate, coins_file, metadata)}; !res) {5715LOCK(::cs_main);5716return cleanup_bad_snapshot(Untranslated(strprintf("Population failed: %s", util::ErrorString(res).original)));5717}5719LOCK(::cs_main); // cs_main required for rest of snapshot activation.5721// Do a final check to ensure that the snapshot chainstate is actually a more5722// work chain than the active chainstate; a user could have loaded a snapshot5723// very late in the IBD process, and we wouldn't want to load a useless chainstate.5724if (!CBlockIndexWorkComparator()(ActiveTip(), snapshot_chainstate->m_chain.Tip())) {5725return cleanup_bad_snapshot(Untranslated("work does not exceed active chainstate"));5726}5727// If not in-memory, persist the base blockhash for use during subsequent5728// initialization.5729if (!in_memory) {5730if (!node::WriteSnapshotBaseBlockhash(*snapshot_chainstate)) {5731return cleanup_bad_snapshot(Untranslated("could not write base blockhash"));5732}5733}5735Chainstate& chainstate{AddChainstate(std::move(snapshot_chainstate))};5736m_blockman.m_snapshot_height = Assert(chainstate.SnapshotBase())->nHeight;5738chainstate.PopulateBlockIndexCandidates();5740LogInfo("[snapshot] successfully activated snapshot %s", base_blockhash.ToString());5741LogInfo("[snapshot] (%.2f MB)",5742chainstate.CoinsTip().DynamicMemoryUsage() / (1000 * 1000));5744this->MaybeRebalanceCaches();5745return snapshot_start_block;5746}5748static void FlushSnapshotToDisk(CCoinsViewCache& coins_cache, bool snapshot_loaded)5749{5750LOG_TIME_MILLIS_WITH_CATEGORY_MSG_ONCE(