diff --git a/doc/design/assumeutxo.md b/doc/design/assumeutxo.md index 123c02ac138..f1559cf4975 100644 --- a/doc/design/assumeutxo.md +++ b/doc/design/assumeutxo.md @@ -97,14 +97,13 @@ sequentially. ### Background chainstate hits snapshot base block Once the tip of the background chainstate hits the base block of the snapshot -chainstate, we stop use of the background chainstate by setting `m_disabled`, in -`MaybeCompleteSnapshotValidation()`, which is checked in `ActivateBestChain()`). We hash the +chainstate, we hash the background chainstate's UTXO set contents and ensure it matches the compiled value in `CMainParams::m_assumeutxo_data`. | | | | ---------- | ----------- | -| number of chainstates | 2 (ibd has `m_disabled=true`) | +| number of chainstates | 2 | | active chainstate | snapshot | The background chainstate data lingers on disk until the program is restarted. diff --git a/src/validation.cpp b/src/validation.cpp index 0831b07ccd9..4c6432de84c 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1882,6 +1882,7 @@ Chainstate::Chainstate( : m_mempool(mempool), m_blockman(blockman), m_chainman(chainman), + m_assumeutxo(from_snapshot_blockhash ? Assumeutxo::UNVALIDATED : Assumeutxo::VALIDATED), m_from_snapshot_blockhash(from_snapshot_blockhash) {} const CBlockIndex* Chainstate::SnapshotBase() const @@ -3393,12 +3394,11 @@ bool Chainstate::ActivateBestChain(BlockValidationState& state, std::shared_ptr< // we use m_chainstate_mutex to enforce mutual exclusion so that only one caller may execute this function at a time LOCK(m_chainstate_mutex); - // Belt-and-suspenders check that we aren't attempting to advance the background - // chainstate past the snapshot base block. - if (WITH_LOCK(::cs_main, return m_disabled)) { - LogError("m_disabled is set - this chainstate should not be in operation. " - "Please report this as a bug. %s", CLIENT_BUGREPORT); - return false; + // Belt-and-suspenders check that we aren't attempting to advance the + // chainstate past the target block. + if (WITH_LOCK(::cs_main, return m_target_utxohash)) { + LogError("%s", STR_INTERNAL_BUG("m_target_utxohash is set - this chainstate should not be in operation.")); + return Assume(false); } CBlockIndex *pindexMostWork = nullptr; @@ -5609,7 +5609,7 @@ std::vector ChainstateManager::GetAll() std::vector out; for (Chainstate* cs : {m_ibd_chainstate.get(), m_snapshot_chainstate.get()}) { - if (this->IsUsable(cs)) out.push_back(cs); + if (cs && cs->m_assumeutxo != Assumeutxo::INVALID && !cs->m_target_utxohash) out.push_back(cs); } return out; @@ -6071,9 +6071,11 @@ SnapshotCompletionResult ChainstateManager::MaybeCompleteSnapshotValidation() // If a snapshot does not need to be validated... if (m_ibd_chainstate.get() == &this->ActiveChainstate() || // Or if either chainstate is unusable... - !this->IsUsable(m_snapshot_chainstate.get()) || - !this->IsUsable(m_ibd_chainstate.get()) || + !m_snapshot_chainstate || + m_snapshot_chainstate->m_assumeutxo != Assumeutxo::UNVALIDATED || !m_snapshot_chainstate->m_from_snapshot_blockhash || + !m_ibd_chainstate || + m_ibd_chainstate->m_assumeutxo != Assumeutxo::VALIDATED || !m_ibd_chainstate->m_chain.Tip() || // Or the validated chainstate is not targeting the snapshot block... !m_ibd_chainstate->m_target_blockhash || @@ -6112,9 +6114,7 @@ SnapshotCompletionResult ChainstateManager::MaybeCompleteSnapshotValidation() m_ibd_chainstate->SetTargetBlock(nullptr); m_active_chainstate = m_ibd_chainstate.get(); - m_snapshot_chainstate->m_disabled = true; - assert(!this->IsUsable(m_snapshot_chainstate.get())); - assert(this->IsUsable(m_ibd_chainstate.get())); + m_snapshot_chainstate->m_assumeutxo = Assumeutxo::INVALID; auto rename_result = m_snapshot_chainstate->InvalidateCoinsDBOnDisk(); if (!rename_result) { @@ -6131,7 +6131,6 @@ SnapshotCompletionResult ChainstateManager::MaybeCompleteSnapshotValidation() assert(snapshot_base_height == curr_height); assert(snapshot_base_height == index_new.nHeight); - assert(this->IsUsable(m_snapshot_chainstate.get())); assert(this->GetAll().size() == 2); CCoinsViewDB& ibd_coins_db = m_ibd_chainstate->CoinsDB(); @@ -6187,7 +6186,8 @@ SnapshotCompletionResult ChainstateManager::MaybeCompleteSnapshotValidation() LogInfo("[snapshot] snapshot beginning at %s has been fully validated", snapshot_blockhash.ToString()); - m_ibd_chainstate->m_disabled = true; + m_snapshot_chainstate->m_assumeutxo = Assumeutxo::VALIDATED; + m_ibd_chainstate->m_target_utxohash = AssumeutxoHash{ibd_stats.hashSerialized}; this->MaybeRebalanceCaches(); return SnapshotCompletionResult::SUCCESS; @@ -6209,34 +6209,30 @@ bool ChainstateManager::IsSnapshotActive() const void ChainstateManager::MaybeRebalanceCaches() { AssertLockHeld(::cs_main); - bool ibd_usable = this->IsUsable(m_ibd_chainstate.get()); - bool snapshot_usable = this->IsUsable(m_snapshot_chainstate.get()); - assert(ibd_usable || snapshot_usable); - - if (ibd_usable && !snapshot_usable) { + Chainstate& current_cs{*Assert(m_active_chainstate)}; + Chainstate* historical_cs{HistoricalChainstate()}; + if (!historical_cs && !current_cs.m_from_snapshot_blockhash) { // Allocate everything to the IBD chainstate. This will always happen // when we are not using a snapshot. - m_ibd_chainstate->ResizeCoinsCaches(m_total_coinstip_cache, m_total_coinsdb_cache); - } - else if (snapshot_usable && !ibd_usable) { + current_cs.ResizeCoinsCaches(m_total_coinstip_cache, m_total_coinsdb_cache); + } else if (!historical_cs) { // If background validation has completed and snapshot is our active chain... LogInfo("[snapshot] allocating all cache to the snapshot chainstate"); // Allocate everything to the snapshot chainstate. - m_snapshot_chainstate->ResizeCoinsCaches(m_total_coinstip_cache, m_total_coinsdb_cache); - } - else if (ibd_usable && snapshot_usable) { + current_cs.ResizeCoinsCaches(m_total_coinstip_cache, m_total_coinsdb_cache); + } else { // If both chainstates exist, determine who needs more cache based on IBD status. // // Note: shrink caches first so that we don't inadvertently overwhelm available memory. if (IsInitialBlockDownload()) { - m_ibd_chainstate->ResizeCoinsCaches( + historical_cs->ResizeCoinsCaches( m_total_coinstip_cache * 0.05, m_total_coinsdb_cache * 0.05); - m_snapshot_chainstate->ResizeCoinsCaches( + current_cs.ResizeCoinsCaches( m_total_coinstip_cache * 0.95, m_total_coinsdb_cache * 0.95); } else { - m_snapshot_chainstate->ResizeCoinsCaches( + current_cs.ResizeCoinsCaches( m_total_coinstip_cache * 0.05, m_total_coinsdb_cache * 0.05); - m_ibd_chainstate->ResizeCoinsCaches( + historical_cs->ResizeCoinsCaches( m_total_coinstip_cache * 0.95, m_total_coinsdb_cache * 0.95); } } @@ -6394,12 +6390,7 @@ bool ChainstateManager::DeleteSnapshotChainstate() ChainstateRole Chainstate::GetRole() const { - if (m_chainman.GetAll().size() <= 1) { - return ChainstateRole::NORMAL; - } - return (this != &m_chainman.ActiveChainstate()) ? - ChainstateRole::BACKGROUND : - ChainstateRole::ASSUMEDVALID; + return m_target_blockhash ? ChainstateRole::BACKGROUND : m_assumeutxo == Assumeutxo::UNVALIDATED ? ChainstateRole::ASSUMEDVALID : ChainstateRole::NORMAL; } const CBlockIndex* ChainstateManager::GetSnapshotBaseBlock() const @@ -6526,10 +6517,11 @@ std::pair ChainstateManager::GetPruneRange(const Chainstate& chainstat } 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; + if (chainstate.m_from_snapshot_blockhash && chainstate.m_assumeutxo != Assumeutxo::VALIDATED) { + // Only prune blocks _after_ the snapshot if this is a snapshot chain + // that has not been fully validated yet. The earlier blocks need to be + // kept to validate the snapshot + prune_start = Assert(chainstate.SnapshotBase())->nHeight + 1; } int max_prune = std::max( diff --git a/src/validation.h b/src/validation.h index e547f88c674..d2d9e4196d2 100644 --- a/src/validation.h +++ b/src/validation.h @@ -514,6 +514,16 @@ constexpr int64_t LargeCoinsCacheThreshold(int64_t total_space) noexcept total_space - MAX_BLOCK_COINSDB_USAGE_BYTES); } +//! Chainstate assumeutxo validity. +enum class Assumeutxo { + //! Every block in the chain has been validated. + VALIDATED, + //! Blocks after an assumeutxo snapshot have been validated but the snapshot itself has not been validated. + UNVALIDATED, + //! The assumeutxo snapshot failed validation. + INVALID, +}; + /** * Chainstate stores and provides an API to update our local knowledge of the * current best chain. @@ -545,19 +555,6 @@ protected: //! Manages the UTXO set, which is a reflection of the contents of `m_chain`. std::unique_ptr m_coins_views; - //! This toggle exists for use when doing background validation for UTXO - //! snapshots. - //! - //! In the expected case, it is set once the background validation chain reaches the - //! same height as the base of the snapshot and its UTXO set is found to hash to - //! the expected assumeutxo value. It signals that we should no longer connect - //! blocks to the background chainstate. When set on the background validation - //! chainstate, it signifies that we have fully validated the snapshot chainstate. - //! - //! In the unlikely case that the snapshot chainstate is found to be invalid, this - //! is set to true on the snapshot chainstate. - bool m_disabled GUARDED_BY(::cs_main) {false}; - //! Cached result of LookupBlockIndex(*m_from_snapshot_blockhash) mutable const CBlockIndex* m_cached_snapshot_base GUARDED_BY(::cs_main){nullptr}; @@ -616,6 +613,11 @@ public: //! @see CChain, CBlockIndex. CChain m_chain; + //! Assumeutxo state indicating whether all blocks in the chain were + //! validated, or if the chainstate is based on an assumeutxo snapshot and + //! the snapshot has not been validated. + Assumeutxo m_assumeutxo GUARDED_BY(::cs_main); + /** * The blockhash which is the base of the snapshot this chainstate was created from. * @@ -629,6 +631,10 @@ public: //! blocks up to the target block, not newer blocks. std::optional m_target_blockhash GUARDED_BY(::cs_main); + //! Hash of the UTXO set at the target block, computed when the chainstate + //! reaches the target block, and null before then. + std::optional m_target_utxohash GUARDED_BY(::cs_main); + /** * The base of the snapshot this chainstate was created from. * @@ -993,15 +999,6 @@ private: /** Most recent headers presync progress update, for rate-limiting. */ MockableSteadyClock::time_point m_last_presync_update GUARDED_BY(GetMutex()){}; - //! Return true if a chainstate is considered usable. - //! - //! This is false when a background validation chainstate has completed its - //! validation of an assumed-valid chainstate, or when a snapshot - //! chainstate has been found to be invalid. - bool IsUsable(const Chainstate* const cs) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { - return cs && !cs->m_disabled; - } - //! A queue for script verifications that have to be performed by worker threads. CCheckQueue m_script_check_queue; @@ -1150,7 +1147,7 @@ public: Chainstate* HistoricalChainstate() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) { auto* cs{m_ibd_chainstate.get()}; - return IsUsable(cs) && cs->m_target_blockhash ? cs : nullptr; + return cs && cs->m_target_blockhash && !cs->m_target_utxohash ? cs : nullptr; } //! The most-work chain. @@ -1179,7 +1176,7 @@ public: //! Is there a snapshot in use and has it been fully validated? bool IsSnapshotValidated() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { - return m_snapshot_chainstate && m_ibd_chainstate && m_ibd_chainstate->m_disabled; + return m_snapshot_chainstate && m_snapshot_chainstate->m_assumeutxo == Assumeutxo::VALIDATED; } /** Check whether we are doing an initial block download (synchronizing from disk or network) */