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.
3251fs::path temp_db_path{fsbridge::AbsPathJoin(tmppath.parent_path(), fs::u8path(temp_db_name))};3253// Only create the on-disk temp directory when not using in-memory mode3254std::optional<TemporaryUTXODatabase> temp_db_cleaner;3255if (!in_memory) {3256temp_db_cleaner.emplace(temp_db_path);3257} else {3258LogInfo("Using in-memory database for UTXO-set rollback (this may require significant RAM).");3259}3261// Create temporary database3262DBParams db_params{3263.path = temp_db_path,3264.cache_bytes = 0,3265.memory_only = in_memory,3266.wipe_data = true,3267.obfuscate = false,3268.options = DBOptions{}3269};3271std::unique_ptr<CCoinsViewDB> temp_db = std::make_unique<CCoinsViewDB>(3272std::move(db_params),3273CoinsViewOptions{}3274);3276const CBlockIndex* tip = nullptr;3277LogInfo("Copying current UTXO set to temporary database.");3278{3279CCoinsViewCache temp_cache(temp_db.get());3280std::unique_ptr<CCoinsViewCursor> cursor;3281{3282LOCK(::cs_main);3283tip = chainstate.m_chain.Tip();3284chainstate.ForceFlushStateToDisk(/*wipe_cache=*/false);3285cursor = chainstate.CoinsDB().Cursor();3286}3287temp_cache.SetBestBlock(tip->GetBlockHash());3289size_t coins_count = 0;3290while (cursor->Valid()) {3291node.rpc_interruption_point();3293COutPoint key;3294Coin coin;3295if (cursor->GetKey(key) && cursor->GetValue(coin)) {3296temp_cache.AddCoin(key, std::move(coin), false);3297coins_count++;3299// Log every 10M coins (optimized for mainnet)3300if (coins_count % 10'000'000 == 0) {3301LogInfo("Copying UTXO set: %uM coins copied.", coins_count / 1'000'000);3302}3304// Flush periodically3305if (coins_count % 100'000 == 0) {3306temp_cache.Flush();3307}3308}3309cursor->Next();3310}3312temp_cache.Flush();3313LogInfo("UTXO set copy complete: %u coins total", coins_count);3314}3316LogInfo("Rolling back from height %d to %d", tip->nHeight, target->nHeight);3318const CBlockIndex* block_index{tip};3319const size_t total_blocks{static_cast<size_t>(block_index->nHeight - target->nHeight)};3320CCoinsViewCache rollback_cache(temp_db.get());3321rollback_cache.SetBestBlock(block_index->GetBlockHash());3322size_t blocks_processed = 0;3323int last_progress{0};3324DisconnectResult res;3326while (block_index->nHeight > target->nHeight) {3327node.rpc_interruption_point();3329CBlock block;3330if (!node.chainman->m_blockman.ReadBlock(block, *block_index)) {3331throw JSONRPCError(RPC_INTERNAL_ERROR,3332strprintf("Failed to read block at height %d", block_index->nHeight));3333}3335WITH_LOCK(::cs_main, res = chainstate.DisconnectBlock(block, block_index, rollback_cache));3336if (res == DISCONNECT_FAILED) {3337throw JSONRPCError(RPC_INTERNAL_ERROR,3338strprintf("Failed to roll back block at height %d", block_index->nHeight));3339}3341blocks_processed++;3342int progress{static_cast<int>(blocks_processed * 100 / total_blocks)};3343if (progress >= last_progress + 5) {3344LogInfo("Rolled back %d%% of blocks.", progress);3345last_progress = progress;3346rollback_cache.Flush();3347}3349block_index = block_index->pprev;3350}3352CHECK_NONFATAL(rollback_cache.GetBestBlock() == target->GetBlockHash());3353rollback_cache.Flush();3355LogInfo("Rollback complete. Computing UTXO statistics for created txoutset dump.");3356std::optional<CCoinsStats> maybe_stats = GetUTXOStats(*temp_db,3357chainstate.m_blockman,3358CoinStatsHashType::HASH_SERIALIZED,3359node.rpc_interruption_point);3361if (!maybe_stats) {3362throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to compute UTXO statistics");3363}3365std::unique_ptr<CCoinsViewCursor> pcursor{temp_db->Cursor()};3367LogInfo("Writing snapshot to disk.");3368return WriteUTXOSnapshot(chainstate,3369pcursor.get(),3370&(*maybe_stats),3371target,3372std::move(afile),3373path,3374tmppath,3375node.rpc_interruption_point);3376}3378std::tuple<std::unique_ptr<CCoinsViewCursor>, CCoinsStats, const CBlockIndex*>3379PrepareUTXOSnapshot(3380Chainstate& chainstate,3381const std::function<void()>& interruption_point)3382{3383std::unique_ptr<CCoinsViewCursor> pcursor;3384std::optional<CCoinsStats> maybe_stats;3385const CBlockIndex* tip;3387{3388// We need to lock cs_main to ensure that the coinsdb isn't written to3389// between (i) flushing coins cache to disk (coinsdb), (ii) getting stats3390// based upon the coinsdb, and (iii) constructing a cursor to the3391// coinsdb for use in WriteUTXOSnapshot.3392//3393// Cursors returned by leveldb iterate over snapshots, so the contents3394// of the pcursor will not be affected by simultaneous writes during3395// use below this block.3396//3397// See discussion here:3398// https://github.com/bitcoin/bitcoin/pull/15606#discussion_r2744793693399//3400AssertLockHeld(::cs_main);3402chainstate.ForceFlushStateToDisk(/*wipe_cache=*/false);3404maybe_stats = GetUTXOStats(chainstate.CoinsDB(), chainstate.m_blockman, CoinStatsHashType::HASH_SERIALIZED, interruption_point);3405if (!maybe_stats) {3406throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");3407}3409pcursor = chainstate.CoinsDB().Cursor();3410tip = CHECK_NONFATAL(chainstate.m_blockman.LookupBlockIndex(maybe_stats->hashBlock));3411}3413return {std::move(pcursor), *CHECK_NONFATAL(maybe_stats), tip};3414}3416UniValue WriteUTXOSnapshot(3417Chainstate& chainstate,3418CCoinsViewCursor* pcursor,3419CCoinsStats* maybe_stats,3420const CBlockIndex* tip,3421AutoFile&& afile,3422const fs::path& path,3423const fs::path& temppath,3424const std::function<void()>& interruption_point)3425{3426LOG_TIME_SECONDS(strprintf("writing UTXO snapshot at height %s (%s) to file %s (via %s)",3427tip->nHeight, tip->GetBlockHash().ToString(),3428fs::PathToString(path), fs::PathToString(temppath)));3430SnapshotMetadata metadata{chainstate.m_chainman.GetParams().MessageStart(), tip->GetBlockHash(), maybe_stats->coins_count};3432afile << metadata;3434COutPoint key;3435Txid last_hash;3436Coin coin;3437unsigned int iter{0};3438size_t written_coins_count{0};3439std::vector<std::pair<uint32_t, Coin>> coins;3441// To reduce space the serialization format of the snapshot avoids3442// duplication of tx hashes. The code takes advantage of the guarantee by3443// leveldb that keys are lexicographically sorted.3444// In the coins vector we collect all coins that belong to a certain tx hash3445// (key.hash) and when we have them all (key.hash != last_hash) we write3446// them to file using the below lambda function.3447// See also https://github.com/bitcoin/bitcoin/issues/256753448auto write_coins_to_file = [&](AutoFile& afile, const Txid& last_hash, const std::vector<std::pair<uint32_t, Coin>>& coins, size_t& written_coins_count) {3449afile << last_hash;3450WriteCompactSize(afile, coins.size());3451for (const auto& [n, coin] : coins) {3452WriteCompactSize(afile, n);3453afile << coin;3454++written_coins_count;3455}3456};3458pcursor->GetKey(key);3459last_hash = key.hash;3460while (pcursor->Valid()) {3461if (iter % 5000 == 0) interruption_point();3462++iter;3463if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {3464if (key.hash != last_hash) {3465write_coins_to_file(afile, last_hash, coins, written_coins_count);3466last_hash = key.hash;3467coins.clear();3468}3469coins.emplace_back(key.n, coin);3470}3471pcursor->Next();3472}3474if (!coins.empty()) {3475write_coins_to_file(afile, last_hash, coins, written_coins_count);3476}3478CHECK_NONFATAL(written_coins_count == maybe_stats->coins_count);3480if (afile.fclose() != 0) {3481throw std::ios_base::failure(3482strprintf("Error closing %s: %s", fs::PathToString(temppath), SysErrorString(errno)));3483}3485UniValue result(UniValue::VOBJ);3486result.pushKV("coins_written", written_coins_count);3487result.pushKV("base_hash", tip->GetBlockHash().ToString());3488result.pushKV("base_height", tip->nHeight);3489result.pushKV("path", path.utf8string());3490result.pushKV("txoutset_hash", maybe_stats->hashSerialized.ToString());3491result.pushKV("nchaintx", tip->m_chain_tx_count);3492return result;3493}3495UniValue CreateUTXOSnapshot(3496node::NodeContext& node,3497Chainstate& chainstate,3498AutoFile&& afile,3499const fs::path& path,3500const fs::path& tmppath)