init: add utxo snapshot detection

Add functionality for activating a snapshot-based chainstate if one is
detected on-disk.

Also cautiously initialize chainstate cache usages so that we don't
somehow blow past our cache allowances during initialization, then
rebalance at the end of init.

Co-authored-by: Russell Yanofsky <russ@yanofsky.org>
This commit is contained in:
James O'BeirneandRussell Yanofsky committed 2022-09-13 13:30:14 -04:00
1 parent f9f1735f13
commit 252abd1e8b
7 files changed
+87 -34

No files matched your search

+21 -3
View File
@@ -48,10 +48,15 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
} }
LOCK(cs_main); LOCK(cs_main);
chainman.InitializeChainstate(options.mempool);
chainman.m_total_coinstip_cache = cache_sizes.coins; chainman.m_total_coinstip_cache = cache_sizes.coins;
chainman.m_total_coinsdb_cache = cache_sizes.coins_db; chainman.m_total_coinsdb_cache = cache_sizes.coins_db;
// Load the fully validated chainstate.
chainman.InitializeChainstate(options.mempool);
// Load a chain created from a UTXO snapshot, if any exist.
chainman.DetectSnapshotChainstate(options.mempool);
auto& pblocktree{chainman.m_blockman.m_block_tree_db}; auto& pblocktree{chainman.m_blockman.m_block_tree_db};
// new CBlockTreeDB tries to delete the existing file, which // new CBlockTreeDB tries to delete the existing file, which
// fails if it's still open from the previous loop. Close it first: // fails if it's still open from the previous loop. Close it first:
@@ -98,12 +103,20 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
return {ChainstateLoadStatus::FAILURE, _("Error initializing block database")}; return {ChainstateLoadStatus::FAILURE, _("Error initializing block database")};
} }
// 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 // At this point we're either in reindex or we've loaded a useful
// block tree into BlockIndex()! // block tree into BlockIndex()!
for (Chainstate* chainstate : chainman.GetAll()) { for (Chainstate* chainstate : chainman.GetAll()) {
LogPrintf("Initializing chainstate %s\n", chainstate->ToString());
chainstate->InitCoinsDB( chainstate->InitCoinsDB(
/*cache_size_bytes=*/cache_sizes.coins_db, /*cache_size_bytes=*/chainman.m_total_coinsdb_cache * init_cache_fraction,
/*in_memory=*/options.coins_db_in_memory, /*in_memory=*/options.coins_db_in_memory,
/*should_wipe=*/options.reindex || options.reindex_chainstate); /*should_wipe=*/options.reindex || options.reindex_chainstate);
@@ -125,7 +138,7 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
} }
// The on-disk coinsdb is now in a good state, create the cache // The on-disk coinsdb is now in a good state, create the cache
chainstate->InitCoinsCache(cache_sizes.coins); chainstate->InitCoinsCache(chainman.m_total_coinstip_cache * init_cache_fraction);
assert(chainstate->CanFlushToDisk()); assert(chainstate->CanFlushToDisk());
if (!is_coinsview_empty(chainstate)) { if (!is_coinsview_empty(chainstate)) {
@@ -146,6 +159,11 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
}; };
} }
// 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 {ChainstateLoadStatus::SUCCESS, {}}; return {ChainstateLoadStatus::SUCCESS, {}};
} }
+12
View File
@@ -8,6 +8,7 @@
#include <logging.h> #include <logging.h>
#include <streams.h> #include <streams.h>
#include <uint256.h> #include <uint256.h>
#include <util/system.h>
#include <validation.h> #include <validation.h>
#include <cstdio> #include <cstdio>
@@ -76,4 +77,15 @@ std::optional<uint256> ReadSnapshotBaseBlockhash(fs::path chaindir)
return base_blockhash; return base_blockhash;
} }
std::optional<fs::path> 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 } // namespace node
+6
View File
@@ -58,8 +58,14 @@ bool WriteSnapshotBaseBlockhash(Chainstate& snapshot_chainstate)
std::optional<uint256> ReadSnapshotBaseBlockhash(fs::path chaindir) std::optional<uint256> ReadSnapshotBaseBlockhash(fs::path chaindir)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main); 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"; constexpr std::string_view SNAPSHOT_CHAINSTATE_SUFFIX = "_snapshot";
//! Return a path to the snapshot-based chainstate dir, if one exists.
std::optional<fs::path> FindSnapshotChainstateDir();
} // namespace node } // namespace node
#endif // BITCOIN_NODE_UTXO_SNAPSHOT_H #endif // BITCOIN_NODE_UTXO_SNAPSHOT_H
@@ -63,7 +63,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
// Create a snapshot-based chainstate. // Create a snapshot-based chainstate.
// //
const uint256 snapshot_blockhash = GetRandHash(); const uint256 snapshot_blockhash = GetRandHash();
Chainstate& c2 = WITH_LOCK(::cs_main, return manager.InitializeChainstate( Chainstate& c2 = WITH_LOCK(::cs_main, return manager.ActivateExistingSnapshot(
&mempool, snapshot_blockhash)); &mempool, snapshot_blockhash));
chainstates.push_back(&c2); chainstates.push_back(&c2);
@@ -133,7 +133,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
// Create a snapshot-based chainstate. // Create a snapshot-based chainstate.
// //
Chainstate& c2 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool, GetRandHash())); Chainstate& c2 = WITH_LOCK(cs_main, return manager.ActivateExistingSnapshot(&mempool, GetRandHash()));
chainstates.push_back(&c2); chainstates.push_back(&c2);
c2.InitCoinsDB( c2.InitCoinsDB(
/*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false); /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
@@ -383,7 +383,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
BOOST_CHECK_EQUAL(expected_assumed_valid, num_assumed_valid); BOOST_CHECK_EQUAL(expected_assumed_valid, num_assumed_valid);
Chainstate& cs2 = WITH_LOCK(::cs_main, Chainstate& cs2 = WITH_LOCK(::cs_main,
return chainman.InitializeChainstate(&mempool, GetRandHash())); return chainman.ActivateExistingSnapshot(&mempool, GetRandHash()));
reload_all_block_indexes(); reload_all_block_indexes();
+34 -19
View File
@@ -4744,28 +4744,15 @@ std::vector<Chainstate*> ChainstateManager::GetAll()
return out; return out;
} }
Chainstate& ChainstateManager::InitializeChainstate( Chainstate& ChainstateManager::InitializeChainstate(CTxMemPool* mempool)
CTxMemPool* mempool, const std::optional<uint256>& snapshot_blockhash)
{ {
AssertLockHeld(::cs_main); AssertLockHeld(::cs_main);
bool is_snapshot = snapshot_blockhash.has_value(); assert(!m_ibd_chainstate);
std::unique_ptr<Chainstate>& to_modify = assert(!m_active_chainstate);
is_snapshot ? m_snapshot_chainstate : m_ibd_chainstate;
if (to_modify) { m_ibd_chainstate = std::make_unique<Chainstate>(mempool, m_blockman, *this);
throw std::logic_error("should not be overwriting a chainstate"); m_active_chainstate = m_ibd_chainstate.get();
} return *m_active_chainstate;
to_modify.reset(new Chainstate(mempool, m_blockman, *this, snapshot_blockhash));
// Snapshot chainstates and initial IBD chaintates always become active.
if (is_snapshot || (!is_snapshot && !m_active_chainstate)) {
LogPrintf("Switching active chainstate to %s\n", to_modify->ToString());
m_active_chainstate = to_modify.get();
} else {
throw std::logic_error("unexpected chainstate activation");
}
return *to_modify;
} }
const AssumeutxoData* ExpectedAssumeutxo( const AssumeutxoData* ExpectedAssumeutxo(
@@ -5134,3 +5121,31 @@ ChainstateManager::~ChainstateManager()
i.clear(); i.clear();
} }
} }
bool ChainstateManager::DetectSnapshotChainstate(CTxMemPool* mempool)
{
assert(!m_snapshot_chainstate);
std::optional<fs::path> path = node::FindSnapshotChainstateDir();
if (!path) {
return false;
}
std::optional<uint256> 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, *base_blockhash);
return true;
}
Chainstate& ChainstateManager::ActivateExistingSnapshot(CTxMemPool* mempool, uint256 base_blockhash)
{
assert(!m_snapshot_chainstate);
m_snapshot_chainstate =
std::make_unique<Chainstate>(mempool, m_blockman, *this, 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;
}
+11 -8
View File
@@ -929,17 +929,11 @@ public:
//! coins databases. This will be split somehow across chainstates. //! coins databases. This will be split somehow across chainstates.
int64_t m_total_coinsdb_cache{0}; int64_t m_total_coinsdb_cache{0};
//! Instantiate a new chainstate and assign it based upon whether it is //! Instantiate a new chainstate.
//! from a snapshot.
//! //!
//! @param[in] mempool The mempool to pass to the chainstate //! @param[in] mempool The mempool to pass to the chainstate
// constructor // constructor
//! @param[in] snapshot_blockhash If given, signify that this chainstate Chainstate& InitializeChainstate(CTxMemPool* mempool) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! is based on a snapshot.
Chainstate& InitializeChainstate(
CTxMemPool* mempool,
const std::optional<uint256>& snapshot_blockhash = std::nullopt)
LIFETIMEBOUND EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! Get all chainstates currently being used. //! Get all chainstates currently being used.
std::vector<Chainstate*> GetAll(); std::vector<Chainstate*> GetAll();
@@ -1053,6 +1047,15 @@ public:
* information. */ * information. */
void ReportHeadersPresync(const arith_uint256& work, int64_t height, int64_t timestamp); void ReportHeadersPresync(const arith_uint256& work, int64_t height, int64_t timestamp);
//! 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) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! Switch the active chainstate to one based on a UTXO snapshot that was loaded
//! previously.
Chainstate& ActivateExistingSnapshot(CTxMemPool* mempool, uint256 base_blockhash)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
~ChainstateManager(); ~ChainstateManager();
}; };
-1
View File
@@ -55,7 +55,6 @@ class InitStressTest(BitcoinTestFramework):
b'Loading P2P addresses', b'Loading P2P addresses',
b'Loading banlist', b'Loading banlist',
b'Loading block index', b'Loading block index',
b'Switching active chainstate',
b'Checking all blk files are present', b'Checking all blk files are present',
b'Loaded best chain:', b'Loaded best chain:',
b'init message: Verifying blocks', b'init message: Verifying blocks',