mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-11-10 14:08:40 +01:00
Merge bitcoin/bitcoin#25740: assumeutxo: background validation completion
2b373fe49ddocs: update assumeutxo.md (James O'Beirne)87a1108c81test: add snapshot completion unittests (James O'Beirne)d70919a88frefactor: make MempoolMutex() public (James O'Beirne)7300ced9delog: add LoadBlockIndex() message for assumedvalid blocks (James O'Beirne)d96c59cc5cvalidation: add ChainMan logic for completing UTXO snapshot validation (James O'Beirne)f2a4f3376fmove-only-ish: init: factor out chainstate initialization (James O'Beirne)637a90b973add Chainstate::HasCoinsViews() (James O'Beirne)c29f26b47bvalidation: add CChainState::m_disabled and ChainMan::isUsable (James O'Beirne)5ee22cdafdadd ChainstateManager.GetSnapshot{BaseHeight,BaseBlock}() (James O'Beirne) Pull request description: This is part of the [assumeutxo project](https://github.com/bitcoin/bitcoin/projects/11) (parent PR: https://github.com/bitcoin/bitcoin/pull/15606) Part two of replacing https://github.com/bitcoin/bitcoin/pull/24232. --- When a user activates a snapshot, the serialized UTXO set data is used to create an "assumed-valid" chainstate, which becomes active in an attempt to get the node to network tip as quickly as possible. Simultaneously in the background, the already-existing chainstate continues "conventional" IBD to both accumulate full block data and serve as a belt-and-suspenders to validate the assumed-valid chainstate. Once the background chainstate's tip reaches the base block of the snapshot used, we set `m_stop_use` on that chainstate and immediately take the hash of its UTXO set; we verify that this matches the assumeutxo value in the source code. Note that while we ultimately want to remove this background chainstate, we don't do so until the following initialization process, when we again check the UTXO set hash of the background chainstate, and if it continues to match, we remove the (now unnecessary) background chainstate, and move the (previously) assumed-valid chainstate into its place. We then reinitialize the chainstate in the normal way. As noted in previous comments, we could do the filesystem operations "inline" immediately when the background validation completes, but that's basically just an optimization that saves disk space until the next restart. It didn't strike me as worth the risk of moving chainstate data around on disk during runtime of the node, though maybe my concerns are overblown. The final result of this completion process is a fully-validated chain, where the only evidence that the user synced using assumeutxo is the existence of a `base_blockhash` file in the `chainstate` directory. ACKs for top commit: achow101: ACK2b373fe49dTree-SHA512: a204e1d6e6932dd83c799af3606b01a9faf893f04e9ee1a36d63f2f1ccfa9118bdc1c107d86976aa0312814267e6a42074bf3e2bf1dead4b2513efc6d955e13d
This commit is contained in:
@@ -28,38 +28,13 @@
|
||||
#include <vector>
|
||||
|
||||
namespace node {
|
||||
ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes,
|
||||
const ChainstateLoadOptions& options)
|
||||
// Complete initialization of chainstates after the initial call has been made
|
||||
// to ChainstateManager::InitializeChainstate().
|
||||
static ChainstateLoadResult CompleteChainstateInitialization(
|
||||
ChainstateManager& chainman,
|
||||
const CacheSizes& cache_sizes,
|
||||
const ChainstateLoadOptions& options) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
|
||||
{
|
||||
auto is_coinsview_empty = [&](Chainstate* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
|
||||
return options.reindex || options.reindex_chainstate || chainstate->CoinsTip().GetBestBlock().IsNull();
|
||||
};
|
||||
|
||||
if (!chainman.AssumedValidBlock().IsNull()) {
|
||||
LogPrintf("Assuming ancestors of block %s have valid signatures.\n", chainman.AssumedValidBlock().GetHex());
|
||||
} else {
|
||||
LogPrintf("Validating signatures for all blocks.\n");
|
||||
}
|
||||
LogPrintf("Setting nMinimumChainWork=%s\n", chainman.MinimumChainWork().GetHex());
|
||||
if (chainman.MinimumChainWork() < UintToArith256(chainman.GetConsensus().nMinimumChainWork)) {
|
||||
LogPrintf("Warning: nMinimumChainWork set below default value of %s\n", chainman.GetConsensus().nMinimumChainWork.GetHex());
|
||||
}
|
||||
if (chainman.m_blockman.GetPruneTarget() == std::numeric_limits<uint64_t>::max()) {
|
||||
LogPrintf("Block pruning enabled. Use RPC call pruneblockchain(height) to manually prune block and undo files.\n");
|
||||
} else if (chainman.m_blockman.GetPruneTarget()) {
|
||||
LogPrintf("Prune configured to target %u MiB on disk for block and undo files.\n", chainman.m_blockman.GetPruneTarget() / 1024 / 1024);
|
||||
}
|
||||
|
||||
LOCK(cs_main);
|
||||
chainman.m_total_coinstip_cache = cache_sizes.coins;
|
||||
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};
|
||||
// new CBlockTreeDB tries to delete the existing file, which
|
||||
// fails if it's still open from the previous loop. Close it first:
|
||||
@@ -111,6 +86,13 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
|
||||
return {ChainstateLoadStatus::FAILURE, _("Error initializing block database")};
|
||||
}
|
||||
|
||||
auto is_coinsview_empty = [&](Chainstate* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
|
||||
return options.reindex || options.reindex_chainstate || chainstate->CoinsTip().GetBestBlock().IsNull();
|
||||
};
|
||||
|
||||
assert(chainman.m_total_coinstip_cache > 0);
|
||||
assert(chainman.m_total_coinsdb_cache > 0);
|
||||
|
||||
// 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
|
||||
@@ -175,6 +157,84 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
|
||||
return {ChainstateLoadStatus::SUCCESS, {}};
|
||||
}
|
||||
|
||||
ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes,
|
||||
const ChainstateLoadOptions& options)
|
||||
{
|
||||
if (!chainman.AssumedValidBlock().IsNull()) {
|
||||
LogPrintf("Assuming ancestors of block %s have valid signatures.\n", chainman.AssumedValidBlock().GetHex());
|
||||
} else {
|
||||
LogPrintf("Validating signatures for all blocks.\n");
|
||||
}
|
||||
LogPrintf("Setting nMinimumChainWork=%s\n", chainman.MinimumChainWork().GetHex());
|
||||
if (chainman.MinimumChainWork() < UintToArith256(chainman.GetConsensus().nMinimumChainWork)) {
|
||||
LogPrintf("Warning: nMinimumChainWork set below default value of %s\n", chainman.GetConsensus().nMinimumChainWork.GetHex());
|
||||
}
|
||||
if (chainman.m_blockman.GetPruneTarget() == std::numeric_limits<uint64_t>::max()) {
|
||||
LogPrintf("Block pruning enabled. Use RPC call pruneblockchain(height) to manually prune block and undo files.\n");
|
||||
} else if (chainman.m_blockman.GetPruneTarget()) {
|
||||
LogPrintf("Prune configured to target %u MiB on disk for block and undo files.\n", chainman.m_blockman.GetPruneTarget() / 1024 / 1024);
|
||||
}
|
||||
|
||||
LOCK(cs_main);
|
||||
|
||||
chainman.m_total_coinstip_cache = cache_sizes.coins;
|
||||
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 [init_status, init_error] = CompleteChainstateInitialization(chainman, cache_sizes, options);
|
||||
if (init_status != ChainstateLoadStatus::SUCCESS) {
|
||||
return {init_status, init_error};
|
||||
}
|
||||
|
||||
// If a snapshot chainstate was fully validated by a background chainstate during
|
||||
// the last run, detect it here and clean up the now-unneeded background
|
||||
// chainstate.
|
||||
//
|
||||
// Why is this cleanup done here (on subsequent restart) and not just when the
|
||||
// snapshot is actually validated? Because this entails unusual
|
||||
// filesystem operations to move leveldb data directories around, and that seems
|
||||
// too risky to do in the middle of normal runtime.
|
||||
auto snapshot_completion = chainman.MaybeCompleteSnapshotValidation();
|
||||
|
||||
if (snapshot_completion == SnapshotCompletionResult::SKIPPED) {
|
||||
// do nothing; expected case
|
||||
} else if (snapshot_completion == SnapshotCompletionResult::SUCCESS) {
|
||||
LogPrintf("[snapshot] cleaning up unneeded background chainstate, then reinitializing\n");
|
||||
if (!chainman.ValidatedSnapshotCleanup()) {
|
||||
AbortNode("Background chainstate cleanup failed unexpectedly.");
|
||||
}
|
||||
|
||||
// Because ValidatedSnapshotCleanup() has torn down chainstates with
|
||||
// ChainstateManager::ResetChainstates(), reinitialize them here without
|
||||
// duplicating the blockindex work above.
|
||||
assert(chainman.GetAll().empty());
|
||||
assert(!chainman.IsSnapshotActive());
|
||||
assert(!chainman.IsSnapshotValidated());
|
||||
|
||||
chainman.InitializeChainstate(options.mempool);
|
||||
|
||||
// A reload of the block index is required to recompute setBlockIndexCandidates
|
||||
// for the fully validated chainstate.
|
||||
chainman.ActiveChainstate().UnloadBlockIndex();
|
||||
|
||||
auto [init_status, init_error] = CompleteChainstateInitialization(chainman, cache_sizes, options);
|
||||
if (init_status != ChainstateLoadStatus::SUCCESS) {
|
||||
return {init_status, init_error};
|
||||
}
|
||||
} else {
|
||||
return {ChainstateLoadStatus::FAILURE, _(
|
||||
"UTXO snapshot failed to validate. "
|
||||
"Restart to resume normal initial block download, or try loading a different snapshot.")};
|
||||
}
|
||||
|
||||
return {ChainstateLoadStatus::SUCCESS, {}};
|
||||
}
|
||||
|
||||
ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options)
|
||||
{
|
||||
auto is_coinsview_empty = [&](Chainstate* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
|
||||
|
||||
Reference in New Issue
Block a user