aboutsummaryrefslogtreecommitdiff
path: root/src/validation.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/validation.cpp')
-rw-r--r--src/validation.cpp255
1 files changed, 195 insertions, 60 deletions
diff --git a/src/validation.cpp b/src/validation.cpp
index 4acd5c7cb0..30b3dde74f 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -5,6 +5,7 @@
#include <validation.h>
+#include <kernel/chain.h>
#include <kernel/coinstats.h>
#include <kernel/mempool_persist.h>
@@ -68,6 +69,7 @@
#include <optional>
#include <string>
#include <utility>
+#include <tuple>
using kernel::CCoinsStats;
using kernel::CoinStatsHashType;
@@ -2551,11 +2553,14 @@ bool Chainstate::FlushStateToDisk(
if (nManualPruneHeight > 0) {
LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune (manual)", BCLog::BENCH);
- m_blockman.FindFilesToPruneManual(setFilesToPrune, std::min(last_prune, nManualPruneHeight), m_chain.Height());
+ m_blockman.FindFilesToPruneManual(
+ setFilesToPrune,
+ std::min(last_prune, nManualPruneHeight),
+ *this, m_chainman);
} else {
LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune", BCLog::BENCH);
- m_blockman.FindFilesToPrune(setFilesToPrune, m_chainman.GetParams().PruneAfterHeight(), m_chain.Height(), last_prune, m_chainman.IsInitialBlockDownload());
+ m_blockman.FindFilesToPrune(setFilesToPrune, last_prune, *this, m_chainman);
m_blockman.m_check_for_pruning = false;
}
if (!setFilesToPrune.empty()) {
@@ -2594,7 +2599,11 @@ bool Chainstate::FlushStateToDisk(
LOG_TIME_MILLIS_WITH_CATEGORY("write block and undo data to disk", BCLog::BENCH);
// First make sure all block and undo data is flushed to disk.
- m_blockman.FlushBlockFile();
+ // TODO: Handle return error, or add detailed comment why it is
+ // safe to not return an error upon failure.
+ if (!m_blockman.FlushChainstateBlockFile(m_chain.Height())) {
+ LogPrintLevel(BCLog::VALIDATION, BCLog::Level::Warning, "%s: Failed to flush block file.\n", __func__);
+ }
}
// Then update all block file information (which may refer to block and undo files).
@@ -2641,7 +2650,7 @@ bool Chainstate::FlushStateToDisk(
}
if (full_flush_completed) {
// Update best block in wallet (so we can detect restored wallets).
- GetMainSignals().ChainStateFlushed(m_chain.GetLocator());
+ GetMainSignals().ChainStateFlushed(this->GetRole(), m_chain.GetLocator());
}
} catch (const std::runtime_error& e) {
return FatalError(m_chainman.GetNotifications(), state, std::string("System error while flushing: ") + e.what());
@@ -3188,6 +3197,7 @@ bool Chainstate::ActivateBestChain(BlockValidationState& state, std::shared_ptr<
CBlockIndex *pindexMostWork = nullptr;
CBlockIndex *pindexNewTip = nullptr;
+ bool exited_ibd{false};
do {
// Block until the validation queue drains. This should largely
// never happen in normal operation, however may happen during
@@ -3201,6 +3211,7 @@ bool Chainstate::ActivateBestChain(BlockValidationState& state, std::shared_ptr<
LOCK(cs_main);
// Lock transaction pool for at least as long as it takes for connectTrace to be consumed
LOCK(MempoolMutex());
+ const bool was_in_ibd = m_chainman.IsInitialBlockDownload();
CBlockIndex* starting_tip = m_chain.Tip();
bool blocks_connected = false;
do {
@@ -3233,7 +3244,7 @@ bool Chainstate::ActivateBestChain(BlockValidationState& state, std::shared_ptr<
for (const PerBlockConnectTrace& trace : connectTrace.GetBlocksConnected()) {
assert(trace.pblock && trace.pindex);
- GetMainSignals().BlockConnected(trace.pblock, trace.pindex);
+ GetMainSignals().BlockConnected(this->GetRole(), trace.pblock, trace.pindex);
}
// This will have been toggled in
@@ -3248,16 +3259,21 @@ bool Chainstate::ActivateBestChain(BlockValidationState& state, std::shared_ptr<
if (!blocks_connected) return true;
const CBlockIndex* pindexFork = m_chain.FindFork(starting_tip);
- bool fInitialDownload = m_chainman.IsInitialBlockDownload();
+ bool still_in_ibd = m_chainman.IsInitialBlockDownload();
+
+ if (was_in_ibd && !still_in_ibd) {
+ // Active chainstate has exited IBD.
+ exited_ibd = true;
+ }
// Notify external listeners about the new tip.
// Enqueue while holding cs_main to ensure that UpdatedBlockTip is called in the order in which blocks are connected
- if (pindexFork != pindexNewTip) {
+ if (this == &m_chainman.ActiveChainstate() && pindexFork != pindexNewTip) {
// Notify ValidationInterface subscribers
- GetMainSignals().UpdatedBlockTip(pindexNewTip, pindexFork, fInitialDownload);
+ GetMainSignals().UpdatedBlockTip(pindexNewTip, pindexFork, still_in_ibd);
// Always notify the UI if a new block tip was connected
- if (kernel::IsInterrupted(m_chainman.GetNotifications().blockTip(GetSynchronizationState(fInitialDownload), *pindexNewTip))) {
+ if (kernel::IsInterrupted(m_chainman.GetNotifications().blockTip(GetSynchronizationState(still_in_ibd), *pindexNewTip))) {
// Just breaking and returning success for now. This could
// be changed to bubble up the kernel::Interrupted value to
// the caller so the caller could distinguish between
@@ -3268,8 +3284,25 @@ bool Chainstate::ActivateBestChain(BlockValidationState& state, std::shared_ptr<
}
// When we reach this point, we switched to a new tip (stored in pindexNewTip).
+ if (exited_ibd) {
+ // If a background chainstate is in use, we may need to rebalance our
+ // allocation of caches once a chainstate exits initial block download.
+ LOCK(::cs_main);
+ m_chainman.MaybeRebalanceCaches();
+ }
+
if (WITH_LOCK(::cs_main, return m_disabled)) {
// Background chainstate has reached the snapshot base block, so exit.
+
+ // Restart indexes to resume indexing for all blocks unique to the snapshot
+ // chain. This resumes indexing "in order" from where the indexing on the
+ // background validation chain left off.
+ //
+ // This cannot be done while holding cs_main (within
+ // MaybeCompleteSnapshotValidation) or a cs_main deadlock will occur.
+ if (m_chainman.restart_indexes) {
+ m_chainman.restart_indexes();
+ }
break;
}
@@ -3506,7 +3539,8 @@ void Chainstate::ResetBlockFailureFlags(CBlockIndex *pindex) {
void Chainstate::TryAddBlockIndexCandidate(CBlockIndex* pindex)
{
AssertLockHeld(cs_main);
- // The block only is a candidate for the most-work-chain if it has more work than our current tip.
+ // The block only is a candidate for the most-work-chain if it has the same
+ // or more work than our current tip.
if (m_chain.Tip() != nullptr && setBlockIndexCandidates.value_comp()(pindex, m_chain.Tip())) {
return;
}
@@ -4144,6 +4178,12 @@ bool ChainstateManager::ProcessNewBlock(const std::shared_ptr<const CBlock>& blo
return error("%s: ActivateBestChain failed (%s)", __func__, state.ToString());
}
+ Chainstate* bg_chain{WITH_LOCK(cs_main, return BackgroundSyncInProgress() ? m_ibd_chainstate.get() : nullptr)};
+ BlockValidationState bg_state;
+ if (bg_chain && !bg_chain->ActivateBestChain(bg_state, block)) {
+ return error("%s: [background] ActivateBestChain failed (%s)", __func__, bg_state.ToString());
+ }
+
return true;
}
@@ -4271,7 +4311,7 @@ VerifyDBResult CVerifyDB::VerifyDB(
bool skipped_l3_checks{false};
LogPrintf("Verification progress: 0%%\n");
- const bool is_snapshot_cs{!chainstate.m_from_snapshot_blockhash};
+ const bool is_snapshot_cs{chainstate.m_from_snapshot_blockhash};
for (pindex = chainstate.m_chain.Tip(); pindex && pindex->pprev; pindex = pindex->pprev) {
const int percentageDone = std::max(1, std::min(99, (int)(((double)(chainstate.m_chain.Height() - pindex->nHeight)) / (double)nCheckDepth * (nCheckLevel >= 4 ? 50 : 100))));
@@ -4502,7 +4542,7 @@ bool ChainstateManager::LoadBlockIndex()
// Load block index from databases
bool needs_init = fReindex;
if (!fReindex) {
- bool ret{m_blockman.LoadBlockIndexDB()};
+ bool ret{m_blockman.LoadBlockIndexDB(SnapshotBlockhash())};
if (!ret) return false;
m_blockman.ScanAndUnlinkAlreadyPrunedFiles();
@@ -4798,6 +4838,10 @@ void ChainstateManager::CheckBlockIndex()
CBlockIndex* pindexFirstAssumeValid = nullptr; // Oldest ancestor of pindex which has BLOCK_ASSUMED_VALID
while (pindex != nullptr) {
nNodes++;
+ if (pindex->pprev && pindex->nTx > 0) {
+ // nChainTx should increase monotonically
+ assert(pindex->pprev->nChainTx <= pindex->nChainTx);
+ }
if (pindexFirstAssumeValid == nullptr && pindex->nStatus & BLOCK_ASSUMED_VALID) pindexFirstAssumeValid = pindex;
if (pindexFirstInvalid == nullptr && pindex->nStatus & BLOCK_FAILED_VALID) pindexFirstInvalid = pindex;
if (pindexFirstMissing == nullptr && !(pindex->nStatus & BLOCK_HAVE_DATA)) {
@@ -5089,19 +5133,7 @@ Chainstate& ChainstateManager::InitializeChainstate(CTxMemPool* mempool)
return *m_active_chainstate;
}
-const AssumeutxoData* ExpectedAssumeutxo(
- const int height, const CChainParams& chainparams)
-{
- const MapAssumeutxo& valid_assumeutxos_map = chainparams.Assumeutxo();
- const auto assumeutxo_found = valid_assumeutxos_map.find(height);
-
- if (assumeutxo_found != valid_assumeutxos_map.end()) {
- return &assumeutxo_found->second;
- }
- return nullptr;
-}
-
-static bool DeleteCoinsDBFromDisk(const fs::path db_path, bool is_snapshot)
+[[nodiscard]] static bool DeleteCoinsDBFromDisk(const fs::path db_path, bool is_snapshot)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
{
AssertLockHeld(::cs_main);
@@ -5153,6 +5185,14 @@ bool ChainstateManager::ActivateSnapshot(
return false;
}
+ {
+ LOCK(::cs_main);
+ if (Assert(m_active_chainstate->GetMempool())->size() > 0) {
+ LogPrintf("[snapshot] can't activate a snapshot when mempool not empty\n");
+ return false;
+ }
+ }
+
int64_t current_coinsdb_cache_size{0};
int64_t current_coinstip_cache_size{0};
@@ -5198,19 +5238,8 @@ bool ChainstateManager::ActivateSnapshot(
static_cast<size_t>(current_coinstip_cache_size * SNAPSHOT_CACHE_PERC));
}
- 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) {
- LOCK(::cs_main);
+ auto cleanup_bad_snapshot = [&](const char* reason) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
+ LogPrintf("[snapshot] activation failed - %s\n", reason);
this->MaybeRebalanceCaches();
// PopulateAndValidateSnapshot can return (in error) before the leveldb datadir
@@ -5227,23 +5256,48 @@ bool ChainstateManager::ActivateSnapshot(
}
}
return false;
- }
+ };
- {
+ if (!this->PopulateAndValidateSnapshot(*snapshot_chainstate, coins_file, metadata)) {
LOCK(::cs_main);
- assert(!m_snapshot_chainstate);
- m_snapshot_chainstate.swap(snapshot_chainstate);
- const bool chaintip_loaded = m_snapshot_chainstate->LoadChainTip();
- assert(chaintip_loaded);
-
- m_active_chainstate = m_snapshot_chainstate.get();
+ return cleanup_bad_snapshot("population failed");
+ }
- LogPrintf("[snapshot] successfully activated snapshot %s\n", base_blockhash.ToString());
- LogPrintf("[snapshot] (%.2f MB)\n",
- m_snapshot_chainstate->CoinsTip().DynamicMemoryUsage() / (1000 * 1000));
+ LOCK(::cs_main); // cs_main required for rest of snapshot activation.
- this->MaybeRebalanceCaches();
+ // Do a final check to ensure that the snapshot chainstate is actually a more
+ // work chain than the active chainstate; a user could have loaded a snapshot
+ // very late in the IBD process, and we wouldn't want to load a useless chainstate.
+ if (!CBlockIndexWorkComparator()(ActiveTip(), snapshot_chainstate->m_chain.Tip())) {
+ return cleanup_bad_snapshot("work does not exceed active chainstate");
+ }
+ // If not in-memory, persist the base blockhash for use during subsequent
+ // initialization.
+ if (!in_memory) {
+ if (!node::WriteSnapshotBaseBlockhash(*snapshot_chainstate)) {
+ return cleanup_bad_snapshot("could not write base blockhash");
+ }
}
+
+ assert(!m_snapshot_chainstate);
+ m_snapshot_chainstate.swap(snapshot_chainstate);
+ const bool chaintip_loaded = m_snapshot_chainstate->LoadChainTip();
+ assert(chaintip_loaded);
+
+ // Transfer possession of the mempool to the snapshot chainstate.
+ // Mempool is empty at this point because we're still in IBD.
+ Assert(m_active_chainstate->m_mempool->size() == 0);
+ Assert(!m_snapshot_chainstate->m_mempool);
+ m_snapshot_chainstate->m_mempool = m_active_chainstate->m_mempool;
+ m_active_chainstate->m_mempool = nullptr;
+ m_active_chainstate = m_snapshot_chainstate.get();
+ m_blockman.m_snapshot_height = this->GetSnapshotBaseHeight();
+
+ LogPrintf("[snapshot] successfully activated snapshot %s\n", base_blockhash.ToString());
+ LogPrintf("[snapshot] (%.2f MB)\n",
+ m_snapshot_chainstate->CoinsTip().DynamicMemoryUsage() / (1000 * 1000));
+
+ this->MaybeRebalanceCaches();
return true;
}
@@ -5285,7 +5339,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
CBlockIndex* snapshot_start_block = WITH_LOCK(::cs_main, return m_blockman.LookupBlockIndex(base_blockhash));
if (!snapshot_start_block) {
- // Needed for ComputeUTXOStats and ExpectedAssumeutxo to determine the
+ // Needed for ComputeUTXOStats to determine the
// height and to avoid a crash when base_blockhash.IsNull()
LogPrintf("[snapshot] Did not find snapshot start blockheader %s\n",
base_blockhash.ToString());
@@ -5293,7 +5347,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
}
int base_height = snapshot_start_block->nHeight;
- auto maybe_au_data = ExpectedAssumeutxo(base_height, GetParams());
+ const auto& maybe_au_data = GetParams().AssumeutxoForHeight(base_height);
if (!maybe_au_data) {
LogPrintf("[snapshot] assumeutxo height in snapshot metadata not recognized "
@@ -5303,6 +5357,14 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
const AssumeutxoData& au_data = *maybe_au_data;
+ // This work comparison is a duplicate check with the one performed later in
+ // ActivateSnapshot(), but is done so that we avoid doing the long work of staging
+ // a snapshot that isn't actually usable.
+ if (WITH_LOCK(::cs_main, return !CBlockIndexWorkComparator()(ActiveTip(), snapshot_start_block))) {
+ LogPrintf("[snapshot] activation failed - height does not exceed active chainstate\n");
+ return false;
+ }
+
COutPoint outpoint;
Coin coin;
const uint64_t coins_count = metadata.m_coins_count;
@@ -5562,7 +5624,7 @@ SnapshotCompletionResult ChainstateManager::MaybeCompleteSnapshotValidation()
CCoinsViewDB& ibd_coins_db = m_ibd_chainstate->CoinsDB();
m_ibd_chainstate->ForceFlushStateToDisk();
- auto maybe_au_data = ExpectedAssumeutxo(curr_height, m_options.chainparams);
+ const auto& maybe_au_data = m_options.chainparams.AssumeutxoForHeight(curr_height);
if (!maybe_au_data) {
LogPrintf("[snapshot] assumeutxo data not found for height "
"(%d) - refusing to validate snapshot\n", curr_height);
@@ -5714,16 +5776,22 @@ bool ChainstateManager::DetectSnapshotChainstate(CTxMemPool* mempool)
LogPrintf("[snapshot] detected active snapshot chainstate (%s) - loading\n",
fs::PathToString(*path));
- this->ActivateExistingSnapshot(mempool, *base_blockhash);
+ this->ActivateExistingSnapshot(*base_blockhash);
return true;
}
-Chainstate& ChainstateManager::ActivateExistingSnapshot(CTxMemPool* mempool, uint256 base_blockhash)
+Chainstate& ChainstateManager::ActivateExistingSnapshot(uint256 base_blockhash)
{
assert(!m_snapshot_chainstate);
m_snapshot_chainstate =
- std::make_unique<Chainstate>(mempool, m_blockman, *this, base_blockhash);
+ std::make_unique<Chainstate>(nullptr, m_blockman, *this, base_blockhash);
LogPrintf("[snapshot] switching active chainstate to %s\n", m_snapshot_chainstate->ToString());
+
+ // Mempool is empty at this point because we're still in IBD.
+ Assert(m_active_chainstate->m_mempool->size() == 0);
+ Assert(!m_snapshot_chainstate->m_mempool);
+ m_snapshot_chainstate->m_mempool = m_active_chainstate->m_mempool;
+ m_active_chainstate->m_mempool = nullptr;
m_active_chainstate = m_snapshot_chainstate.get();
return *m_snapshot_chainstate;
}
@@ -5740,15 +5808,20 @@ bool IsBIP30Unspendable(const CBlockIndex& block_index)
(block_index.nHeight==91812 && block_index.GetBlockHash() == uint256S("0x00000000000af0aed4792b1acee3d966af36cf5def14935db8de83d6f9306f2f"));
}
-util::Result<void> Chainstate::InvalidateCoinsDBOnDisk()
+static fs::path GetSnapshotCoinsDBPath(Chainstate& cs) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
{
AssertLockHeld(::cs_main);
// Should never be called on a non-snapshot chainstate.
- assert(m_from_snapshot_blockhash);
- auto storage_path_maybe = this->CoinsDB().StoragePath();
+ assert(cs.m_from_snapshot_blockhash);
+ auto storage_path_maybe = cs.CoinsDB().StoragePath();
// Should never be called with a non-existent storage path.
assert(storage_path_maybe);
- fs::path snapshot_datadir = *storage_path_maybe;
+ return *storage_path_maybe;
+}
+
+util::Result<void> Chainstate::InvalidateCoinsDBOnDisk()
+{
+ fs::path snapshot_datadir = GetSnapshotCoinsDBPath(*this);
// Coins views no longer usable.
m_coins_views.reset();
@@ -5779,6 +5852,33 @@ util::Result<void> Chainstate::InvalidateCoinsDBOnDisk()
return {};
}
+bool ChainstateManager::DeleteSnapshotChainstate()
+{
+ AssertLockHeld(::cs_main);
+ Assert(m_snapshot_chainstate);
+ Assert(m_ibd_chainstate);
+
+ fs::path snapshot_datadir = GetSnapshotCoinsDBPath(*m_snapshot_chainstate);
+ if (!DeleteCoinsDBFromDisk(snapshot_datadir, /*is_snapshot=*/ true)) {
+ LogPrintf("Deletion of %s failed. Please remove it manually to continue reindexing.\n",
+ fs::PathToString(snapshot_datadir));
+ return false;
+ }
+ m_active_chainstate = m_ibd_chainstate.get();
+ m_snapshot_chainstate.reset();
+ return true;
+}
+
+ChainstateRole Chainstate::GetRole() const
+{
+ if (m_chainman.GetAll().size() <= 1) {
+ return ChainstateRole::NORMAL;
+ }
+ return (this != &m_chainman.ActiveChainstate()) ?
+ ChainstateRole::BACKGROUND :
+ ChainstateRole::ASSUMEDVALID;
+}
+
const CBlockIndex* ChainstateManager::GetSnapshotBaseBlock() const
{
return m_active_chainstate ? m_active_chainstate->SnapshotBase() : nullptr;
@@ -5876,3 +5976,38 @@ bool ChainstateManager::ValidatedSnapshotCleanup()
}
return true;
}
+
+Chainstate& ChainstateManager::GetChainstateForIndexing()
+{
+ // We can't always return `m_ibd_chainstate` because after background validation
+ // has completed, `m_snapshot_chainstate == m_active_chainstate`, but it can be
+ // indexed.
+ return (this->GetAll().size() > 1) ? *m_ibd_chainstate : *m_active_chainstate;
+}
+
+std::pair<int, int> ChainstateManager::GetPruneRange(const Chainstate& chainstate, int last_height_can_prune)
+{
+ if (chainstate.m_chain.Height() <= 0) {
+ return {0, 0};
+ }
+ int prune_start{0};
+
+ if (this->GetAll().size() > 1 && m_snapshot_chainstate.get() == &chainstate) {
+ // Leave the blocks in the background IBD chain alone if we're pruning
+ // the snapshot chain.
+ prune_start = *Assert(GetSnapshotBaseHeight()) + 1;
+ }
+
+ int max_prune = std::max<int>(
+ 0, chainstate.m_chain.Height() - static_cast<int>(MIN_BLOCKS_TO_KEEP));
+
+ // last block to prune is the lesser of (caller-specified height, MIN_BLOCKS_TO_KEEP from the tip)
+ //
+ // While you might be tempted to prune the background chainstate more
+ // aggressively (i.e. fewer MIN_BLOCKS_TO_KEEP), this won't work with index
+ // building - specifically blockfilterindex requires undo data, and if
+ // we don't maintain this trailing window, we hit indexing failures.
+ int prune_end = std::min(last_height_can_prune, max_prune);
+
+ return {prune_start, prune_end};
+}