From 18f9984dcc22a73177adde8650ca2412a5ae3b17 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Fri, 5 Dec 2025 20:47:19 -0600 Subject: [PATCH] Merge bitcoin/bitcoin#25667: assumeutxo: snapshot initialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- doc/design/assumeutxo.md | 5 +- src/Makefile.am | 1 + src/dbwrapper.cpp | 2 +- src/dbwrapper.h | 18 ++++ src/node/blockstorage.cpp | 10 +++ src/node/chainstate.cpp | 24 +++++- src/node/utxo_snapshot.cpp | 91 ++++++++++++++++++++ src/node/utxo_snapshot.h | 33 +++++++ src/streams.h | 6 +- src/test/util/setup_common.cpp | 36 ++++++-- src/test/util/setup_common.h | 19 ++++- src/txdb.h | 4 + src/validation.cpp | 109 +++++++++++++++++++++++- src/validation.h | 19 ++++- test/functional/feature_init.py | 1 - test/lint/lint-circular-dependencies.py | 1 + 16 files changed, 354 insertions(+), 25 deletions(-) create mode 100644 src/node/utxo_snapshot.cpp diff --git a/doc/design/assumeutxo.md b/doc/design/assumeutxo.md index 667b3deda0b5..4f11208219db 100644 --- a/doc/design/assumeutxo.md +++ b/doc/design/assumeutxo.md @@ -76,8 +76,9 @@ original chainstate remains in use as active. Once the snapshot chainstate is loaded and validated, it is promoted to active chainstate and a sync to tip begins. A new chainstate directory is created in the -datadir for the snapshot chainstate called -`chainstate_[SHA256 blockhash of snapshot base block]`. +datadir for the snapshot chainstate called `chainstate_snapshot`. When this directory +is present in the datadir, the snapshot chainstate will be detected and loaded as +active on node startup (via `DetectSnapshotChainstate()`). | | | | ---------- | ----------- | diff --git a/src/Makefile.am b/src/Makefile.am index 77b384d144e2..95b4f67499b0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -569,6 +569,7 @@ libbitcoin_node_a_SOURCES = \ node/psbt.cpp \ node/transaction.cpp \ node/txreconciliation.cpp \ + node/utxo_snapshot.cpp \ node/interface_ui.cpp \ noui.cpp \ policy/fees.cpp \ diff --git a/src/dbwrapper.cpp b/src/dbwrapper.cpp index c4d9cf615faa..4f264e56dfda 100644 --- a/src/dbwrapper.cpp +++ b/src/dbwrapper.cpp @@ -127,7 +127,7 @@ static leveldb::Options GetOptions(size_t nCacheSize) } CDBWrapper::CDBWrapper(const fs::path& path, size_t nCacheSize, bool fMemory, bool fWipe, bool obfuscate) - : m_name{fs::PathToString(path.stem())} + : m_name{fs::PathToString(path.stem())}, m_path{path}, m_is_memory{fMemory} { penv = nullptr; readoptions.verify_checksums = true; diff --git a/src/dbwrapper.h b/src/dbwrapper.h index bd6e847180a6..de10b1f0f713 100644 --- a/src/dbwrapper.h +++ b/src/dbwrapper.h @@ -51,6 +51,10 @@ class dbwrapper_error : public std::runtime_error class CDBWrapper; +namespace dbwrapper { + using leveldb::DestroyDB; +} + /** These should be considered an implementation detail of the specific database. */ namespace dbwrapper_private { @@ -250,6 +254,12 @@ class CDBWrapper std::vector CreateObfuscateKey() const; + //! path to filesystem storage + const fs::path m_path; + + //! whether or not the database resides in memory + bool m_is_memory; + public: /** * @param[in] path Location in the filesystem where leveldb data will be stored. @@ -325,6 +335,14 @@ class CDBWrapper return WriteBatch(batch, fSync); } + //! @returns filesystem path to the on-disk data. + std::optional StoragePath() { + if (m_is_memory) { + return {}; + } + return m_path; + } + template bool Exists(const K& key) const { diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index 76fd09843ac7..c9b8d8643967 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -557,6 +557,16 @@ void BlockManager::FlushUndoFile(int block_file, bool finalize) void BlockManager::FlushBlockFile(bool fFinalize, bool finalize_undo) { LOCK(cs_LastBlockFile); + + if (m_blockfile_info.size() < 1) { + // Return if we haven't loaded any blockfiles yet. This happens during + // chainstate init, when we call ChainstateManager::MaybeRebalanceCaches() (which + // then calls FlushStateToDisk()), resulting in a call to this function before we + // have populated `m_blockfile_info` via LoadBlockIndexDB(). + return; + } + assert(static_cast(m_blockfile_info.size()) > m_last_blockfile); + FlatFilePos block_pos_old(m_last_blockfile, m_blockfile_info[m_last_blockfile].nSize); if (!BlockFileSeq().Flush(block_pos_old, fFinalize)) { AbortNode("Flushing block file to disk failed. This is likely the result of an I/O error."); diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index 8812f7b64829..32ba0ea03c4e 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -79,10 +79,15 @@ std::optional LoadChainstate(bool fReset, mnhf_manager.reset(); mnhf_manager = std::make_unique(*evodb); - chainman.InitializeChainstate(mempool, *evodb, chain_helper); chainman.m_total_coinstip_cache = nCoinCacheUsage; chainman.m_total_coinsdb_cache = nCoinDBCache; + // Load the fully validated chainstate. + chainman.InitializeChainstate(mempool, *evodb, chain_helper); + + // Load a chain created from a UTXO snapshot, if any exist. + chainman.DetectSnapshotChainstate(mempool, *evodb, chain_helper); + auto& pblocktree{chainman.m_blockman.m_block_tree_db}; // new CBlockTreeDB tries to delete the existing file, which // fails if it's still open from the previous loop. Close it first: @@ -160,12 +165,20 @@ std::optional LoadChainstate(bool fReset, return ChainstateLoadingError::ERROR_LOAD_GENESIS_BLOCK_FAILED; } + // Conservative value which is arbitrarily chosen, as it will ultimately be changed + // by a call to `chainman.MaybeRebalanceCaches()`. We just need to make sure + // that the sum of the two caches (40%) does not exceed the allowable amount + // during this temporary initialization state. + double init_cache_fraction = 0.2; + // At this point we're either in reindex or we've loaded a useful // block tree into BlockIndex()! for (CChainState* chainstate : chainman.GetAll()) { + LogPrintf("Initializing chainstate %s\n", chainstate->ToString()); + chainstate->InitCoinsDB( - /*cache_size_bytes=*/nCoinDBCache, + /*cache_size_bytes=*/chainman.m_total_coinsdb_cache * init_cache_fraction, /*in_memory=*/coins_db_in_memory, /*should_wipe=*/fReset || fReindexChainState); @@ -185,7 +198,7 @@ std::optional LoadChainstate(bool fReset, } // The on-disk coinsdb is now in a good state, create the cache - chainstate->InitCoinsCache(nCoinCacheUsage); + chainstate->InitCoinsCache(chainman.m_total_coinstip_cache * init_cache_fraction); assert(chainstate->CanFlushToDisk()); // flush evodb @@ -214,6 +227,11 @@ std::optional LoadChainstate(bool fReset, return ChainstateLoadingError::ERROR_UPGRADING_EVO_DB; } + // Now that chainstates are loaded and we're able to flush to + // disk, rebalance the coins caches to desired levels based + // on the condition of each chainstate. + chainman.MaybeRebalanceCaches(); + return std::nullopt; } diff --git a/src/node/utxo_snapshot.cpp b/src/node/utxo_snapshot.cpp new file mode 100644 index 000000000000..f4019f3b32ff --- /dev/null +++ b/src/node/utxo_snapshot.cpp @@ -0,0 +1,91 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace node { + +bool WriteSnapshotBaseBlockhash(CChainState& snapshot_chainstate) +{ + AssertLockHeld(::cs_main); + assert(snapshot_chainstate.m_from_snapshot_blockhash); + + const std::optional chaindir = snapshot_chainstate.CoinsDB().StoragePath(); + assert(chaindir); // Sanity check that chainstate isn't in-memory. + const fs::path write_to = *chaindir / node::SNAPSHOT_BLOCKHASH_FILENAME; + + FILE* file{fsbridge::fopen(write_to, "wb")}; + AutoFile afile{file}; + if (afile.IsNull()) { + LogPrintf("[snapshot] failed to open base blockhash file for writing: %s\n", + fs::PathToString(write_to)); + return false; + } + afile << *snapshot_chainstate.m_from_snapshot_blockhash; + + if (afile.fclose() != 0) { + LogPrintf("[snapshot] failed to close base blockhash file %s after writing\n", + fs::PathToString(write_to)); + return false; + } + return true; +} + +std::optional ReadSnapshotBaseBlockhash(fs::path chaindir) +{ + if (!fs::exists(chaindir)) { + LogPrintf("[snapshot] cannot read base blockhash: no chainstate dir " /* Continued */ + "exists at path %s\n", fs::PathToString(chaindir)); + return std::nullopt; + } + const fs::path read_from = chaindir / node::SNAPSHOT_BLOCKHASH_FILENAME; + const std::string read_from_str = fs::PathToString(read_from); + + if (!fs::exists(read_from)) { + LogPrintf("[snapshot] snapshot chainstate dir is malformed! no base blockhash file " /* Continued */ + "exists at path %s. Try deleting %s and calling loadtxoutset again?\n", + fs::PathToString(chaindir), read_from_str); + return std::nullopt; + } + + uint256 base_blockhash; + FILE* file{fsbridge::fopen(read_from, "rb")}; + AutoFile afile{file}; + if (afile.IsNull()) { + LogPrintf("[snapshot] failed to open base blockhash file for reading: %s\n", + read_from_str); + return std::nullopt; + } + afile >> base_blockhash; + + if (std::fgetc(afile.Get()) != EOF) { + LogPrintf("[snapshot] warning: unexpected trailing data in %s\n", read_from_str); + } else if (std::ferror(afile.Get())) { + LogPrintf("[snapshot] warning: i/o error reading %s\n", read_from_str); + } + return base_blockhash; +} + +std::optional FindSnapshotChainstateDir() +{ + fs::path possible_dir = + gArgs.GetDataDirNet() / fs::u8path(strprintf("chainstate%s", SNAPSHOT_CHAINSTATE_SUFFIX)); + + if (fs::exists(possible_dir)) { + return possible_dir; + } + return std::nullopt; +} + +} // namespace node diff --git a/src/node/utxo_snapshot.h b/src/node/utxo_snapshot.h index 401d4baaeb92..4440b46b584f 100644 --- a/src/node/utxo_snapshot.h +++ b/src/node/utxo_snapshot.h @@ -6,8 +6,14 @@ #ifndef BITCOIN_NODE_UTXO_SNAPSHOT_H #define BITCOIN_NODE_UTXO_SNAPSHOT_H +#include #include #include +#include + +#include + +extern RecursiveMutex cs_main; namespace node { //! Metadata describing a serialized version of a UTXO set from which an @@ -33,6 +39,33 @@ class SnapshotMetadata SERIALIZE_METHODS(SnapshotMetadata, obj) { READWRITE(obj.m_base_blockhash, obj.m_coins_count); } }; + +//! The file in the snapshot chainstate dir which stores the base blockhash. This is +//! needed to reconstruct snapshot chainstates on init. +//! +//! Because we only allow loading a single snapshot at a time, there will only be one +//! chainstate directory with this filename present within it. +const fs::path SNAPSHOT_BLOCKHASH_FILENAME{"base_blockhash"}; + +//! Write out the blockhash of the snapshot base block that was used to construct +//! this chainstate. This value is read in during subsequent initializations and +//! used to reconstruct snapshot-based chainstates. +bool WriteSnapshotBaseBlockhash(CChainState& snapshot_chainstate) + EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + +//! Read the blockhash of the snapshot base block that was used to construct the +//! chainstate. +std::optional ReadSnapshotBaseBlockhash(fs::path chaindir) + EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + +//! Suffix appended to the chainstate (leveldb) dir when created based upon +//! a snapshot. +constexpr std::string_view SNAPSHOT_CHAINSTATE_SUFFIX = "_snapshot"; + + +//! Return a path to the snapshot-based chainstate dir, if one exists. +std::optional FindSnapshotChainstateDir(); + } // namespace node #endif // BITCOIN_NODE_UTXO_SNAPSHOT_H diff --git a/src/streams.h b/src/streams.h index 9790914f47cd..701bf4e5d17e 100644 --- a/src/streams.h +++ b/src/streams.h @@ -507,12 +507,14 @@ class AutoFile AutoFile(const AutoFile&) = delete; AutoFile& operator=(const AutoFile&) = delete; - void fclose() + int fclose() { + int retval{0}; if (file) { - ::fclose(file); + retval = ::fclose(file); file = nullptr; } + return retval; } /** Get wrapped FILE* with transfer of ownership. diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 5d657d1c4d15..478207e842bd 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -296,8 +296,7 @@ ChainTestingSetup::~ChainTestingSetup() m_node.scheduler.reset(); } -TestingSetup::TestingSetup(const std::string& chainName, const std::vector& extra_args) - : ChainTestingSetup(chainName, extra_args) +void TestingSetup::LoadVerifyActivateChainstate() { const CChainParams& chainparams = Params(); // Ideally we'd move all the RPC tests to the functional testing framework @@ -331,8 +330,8 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector& extra_args, + const bool coins_db_in_memory, + const bool block_tree_db_in_memory) + : ChainTestingSetup(chainName, extra_args), + m_coins_db_in_memory(coins_db_in_memory), + m_block_tree_db_in_memory(block_tree_db_in_memory) +{ + LoadVerifyActivateChainstate(); +} + TestingSetup::~TestingSetup() { m_node.peerman.reset(); @@ -414,13 +425,22 @@ TestingSetup::~TestingSetup() DashChainstateSetupClose(m_node); } -TestChain100Setup::TestChain100Setup(const std::string& chain_name, const std::vector& extra_args) - : TestChainSetup{100, chain_name, extra_args} +TestChain100Setup::TestChain100Setup( + const std::string& chain_name, + const std::vector& extra_args, + const bool coins_db_in_memory, + const bool block_tree_db_in_memory) + : TestChainSetup{100, chain_name, extra_args, coins_db_in_memory, block_tree_db_in_memory} { } -TestChainSetup::TestChainSetup(int num_blocks, const std::string& chain_name, const std::vector& extra_args) - : TestingSetup{chain_name, extra_args} +TestChainSetup::TestChainSetup( + int num_blocks, + const std::string& chain_name, + const std::vector& extra_args, + const bool coins_db_in_memory, + const bool block_tree_db_in_memory) + : TestingSetup{chain_name, extra_args, coins_db_in_memory, block_tree_db_in_memory} { SetMockTime(1598887952); constexpr std::array vchKey = { diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index 10c7363a5095..2077baf53309 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -120,7 +120,16 @@ struct ChainTestingSetup : public BasicTestingSetup { /** Testing setup that configures a complete environment. */ struct TestingSetup : public ChainTestingSetup { - explicit TestingSetup(const std::string& chainName = CBaseChainParams::MAIN, const std::vector& extra_args = {}); + bool m_coins_db_in_memory{true}; + bool m_block_tree_db_in_memory{true}; + + void LoadVerifyActivateChainstate(); + + explicit TestingSetup( + const std::string& chainName = CBaseChainParams::MAIN, + const std::vector& extra_args = {}, + const bool coins_db_in_memory = true, + const bool block_tree_db_in_memory = true); ~TestingSetup(); }; @@ -138,7 +147,9 @@ struct TestChainSetup : public TestingSetup { TestChainSetup(int num_blocks, const std::string& chain_name = CBaseChainParams::REGTEST, - const std::vector& extra_args = {}); + const std::vector& extra_args = {}, + const bool coins_db_in_memory = true, + const bool block_tree_db_in_memory = true); ~TestChainSetup(); /** @@ -202,7 +213,9 @@ struct TestChainSetup : public TestingSetup */ struct TestChain100Setup : public TestChainSetup { TestChain100Setup(const std::string& chain_name = CBaseChainParams::REGTEST, - const std::vector& extra_args = {}); + const std::vector& extra_args = {}, + const bool coins_db_in_memory = true, + const bool block_tree_db_in_memory = true); }; /** diff --git a/src/txdb.h b/src/txdb.h index 493be625128f..e46588b8dbfb 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -75,6 +76,9 @@ class CCoinsViewDB final : public CCoinsView //! Dynamically alter the underlying leveldb cache size. void ResizeCache(size_t new_cache_size) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + //! @returns filesystem path to on-disk storage or std::nullopt if in memory. + std::optional StoragePath() { return m_db->StoragePath(); } }; /** Access to the block database (blocks/index/) */ diff --git a/src/validation.cpp b/src/validation.cpp index 7686968416c2..1b5de74187e4 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1666,7 +1666,7 @@ void CChainState::InitCoinsDB( fs::path leveldb_name) { if (m_from_snapshot_blockhash) { - leveldb_name += "_" + m_from_snapshot_blockhash->ToString(); + leveldb_name += node::SNAPSHOT_CHAINSTATE_SUFFIX; } m_coins_views = std::make_unique( @@ -5716,6 +5716,46 @@ const AssumeutxoData* ExpectedAssumeutxo( return nullptr; } +static bool DeleteCoinsDBFromDisk(const fs::path db_path, bool is_snapshot) + EXCLUSIVE_LOCKS_REQUIRED(::cs_main) +{ + AssertLockHeld(::cs_main); + + if (is_snapshot) { + fs::path base_blockhash_path = db_path / node::SNAPSHOT_BLOCKHASH_FILENAME; + + if (fs::exists(base_blockhash_path)) { + bool removed = fs::remove(base_blockhash_path); + if (!removed) { + LogPrintf("[snapshot] failed to remove file %s\n", + fs::PathToString(base_blockhash_path)); + } + } else { + LogPrintf("[snapshot] snapshot chainstate dir being removed lacks %s file\n", + fs::PathToString(node::SNAPSHOT_BLOCKHASH_FILENAME)); + } + } + + std::string path_str = fs::PathToString(db_path); + LogPrintf("Removing leveldb dir at %s\n", path_str); + + // We have to destruct before this call leveldb::DB in order to release the db + // lock, otherwise `DestroyDB` will fail. See `leveldb::~DBImpl()`. + const bool destroyed = dbwrapper::DestroyDB(path_str, {}).ok(); + + if (!destroyed) { + LogPrintf("error: leveldb DestroyDB call failed on %s\n", path_str); + } + + // Datadir should be removed from filesystem; otherwise initialization may detect + // it on subsequent statups and get confused. + // + // If the base_blockhash_path removal above fails in the case of snapshot + // chainstates, this will return false since leveldb won't remove a non-empty + // directory. + return destroyed && !fs::exists(db_path); +} + bool ChainstateManager::ActivateSnapshot( AutoFile& coins_file, const SnapshotMetadata& metadata, @@ -5777,11 +5817,34 @@ bool ChainstateManager::ActivateSnapshot( static_cast(current_coinstip_cache_size * SNAPSHOT_CACHE_PERC)); } - const bool snapshot_ok = this->PopulateAndValidateSnapshot( + bool snapshot_ok = this->PopulateAndValidateSnapshot( *snapshot_chainstate, coins_file, metadata); + // If not in-memory, persist the base blockhash for use during subsequent + // initialization. + if (!in_memory) { + LOCK(::cs_main); + if (!node::WriteSnapshotBaseBlockhash(*snapshot_chainstate)) { + snapshot_ok = false; + } + } if (!snapshot_ok) { - WITH_LOCK(::cs_main, this->MaybeRebalanceCaches()); + LOCK(::cs_main); + this->MaybeRebalanceCaches(); + + // PopulateAndValidateSnapshot can return (in error) before the leveldb datadir + // has been created, so only attempt removal if we got that far. + if (auto snapshot_datadir = node::FindSnapshotChainstateDir()) { + // We have to destruct leveldb::DB in order to release the db lock, otherwise + // DestroyDB() (in DeleteCoinsDBFromDisk()) will fail. See `leveldb::~DBImpl()`. + // Destructing the chainstate (and so resetting the coinsviews object) does this. + snapshot_chainstate.reset(); + bool removed = DeleteCoinsDBFromDisk(*snapshot_datadir, /*is_snapshot=*/true); + if (!removed) { + AbortNode(strprintf("Failed to remove snapshot chainstate dir (%s). " + "Manually remove it before restarting.\n", fs::PathToString(*snapshot_datadir))); + } + } return false; } @@ -6049,6 +6112,13 @@ void ChainstateManager::MaybeRebalanceCaches() } } +void ChainstateManager::ResetChainstates() +{ + m_ibd_chainstate.reset(); + m_snapshot_chainstate.reset(); + m_active_chainstate = nullptr; +} + ChainstateManager::~ChainstateManager() { LOCK(::cs_main); @@ -6072,3 +6142,36 @@ bool IsBIP30Unspendable(const CBlockIndex& block_index) return (block_index.nHeight==91722 && block_index.GetBlockHash() == uint256S("0x00000000000271a2dc26e7667f8419f2e15416dc6955e5a6c6cdf3f2574dd08e")) || (block_index.nHeight==91812 && block_index.GetBlockHash() == uint256S("0x00000000000af0aed4792b1acee3d966af36cf5def14935db8de83d6f9306f2f")); } + +bool ChainstateManager::DetectSnapshotChainstate(CTxMemPool* mempool, + CEvoDB& evoDb, + const std::unique_ptr& chain_helper) +{ + assert(!m_snapshot_chainstate); + std::optional path = node::FindSnapshotChainstateDir(); + if (!path) { + return false; + } + std::optional base_blockhash = node::ReadSnapshotBaseBlockhash(*path); + if (!base_blockhash) { + return false; + } + LogPrintf("[snapshot] detected active snapshot chainstate (%s) - loading\n", + fs::PathToString(*path)); + + this->ActivateExistingSnapshot(mempool, evoDb, chain_helper, *base_blockhash); + return true; +} + +CChainState& ChainstateManager::ActivateExistingSnapshot(CTxMemPool* mempool, + CEvoDB& evoDb, + const std::unique_ptr& chain_helper, + uint256 base_blockhash) +{ + assert(!m_snapshot_chainstate); + m_snapshot_chainstate = + std::make_unique(mempool, m_blockman, *this, evoDb, chain_helper, base_blockhash); + LogPrintf("[snapshot] switching active chainstate to %s\n", m_snapshot_chainstate->ToString()); + m_active_chainstate = m_snapshot_chainstate.get(); + return *m_snapshot_chainstate; +} diff --git a/src/validation.h b/src/validation.h index 290f721280c9..82f21cd81165 100644 --- a/src/validation.h +++ b/src/validation.h @@ -952,8 +952,7 @@ class ChainstateManager //! coins databases. This will be split somehow across chainstates. int64_t m_total_coinsdb_cache{0}; - //! Instantiate a new chainstate and assign it based upon whether it is - //! from a snapshot. + //! Instantiate a new chainstate. //! //! @param[in] mempool The mempool to pass to the chainstate // constructor @@ -1063,6 +1062,22 @@ class ChainstateManager //! ResizeCoinsCaches() as needed. void MaybeRebalanceCaches() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + //! When starting up, search the datadir for a chainstate based on a UTXO + //! snapshot that is in the process of being validated. + bool DetectSnapshotChainstate(CTxMemPool* mempool, + CEvoDB& evoDb, + const std::unique_ptr& chain_helper) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + + void ResetChainstates() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + + //! Switch the active chainstate to one based on a UTXO snapshot that was loaded + //! previously. + CChainState& ActivateExistingSnapshot(CTxMemPool* mempool, + CEvoDB& evoDb, + const std::unique_ptr& chain_helper, + uint256 base_blockhash) + EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + ~ChainstateManager(); }; diff --git a/test/functional/feature_init.py b/test/functional/feature_init.py index b4227e65fd54..2dda0b6d9880 100755 --- a/test/functional/feature_init.py +++ b/test/functional/feature_init.py @@ -64,7 +64,6 @@ def check_clean_start(): b'Loading P2P addresses', b'Loading banlist', b'Loading block index', - b'Switching active chainstate', b'Checking all blk files are present', b'Loaded best chain:', b'init message: Verifying blocks', diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py index 87a6f11e6fcd..b20c570e1659 100755 --- a/test/lint/lint-circular-dependencies.py +++ b/test/lint/lint-circular-dependencies.py @@ -14,6 +14,7 @@ EXPECTED_CIRCULAR_DEPENDENCIES = ( "chainparamsbase -> util/system -> chainparamsbase", "node/blockstorage -> validation -> node/blockstorage", + "node/utxo_snapshot -> validation -> node/utxo_snapshot", "policy/fees -> txmempool -> policy/fees", "qt/addresstablemodel -> qt/walletmodel -> qt/addresstablemodel", "qt/recentrequeststablemodel -> qt/walletmodel -> qt/recentrequeststablemodel",