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:
1 parent
f9f1735f13
commit
252abd1e8b
7 files changed
+87
-34
No files matched your search
+21
-3
@@ -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, {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
@@ -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
@@ -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();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
Reference in new issue
Block a user