validation: add CChainState::m_disabled and ChainMan::isUsable

and remove m_snapshot_validated. This state can now be inferred by the
number of isUsable chainstates.

m_disabled is used to signal that a chainstate should no longer be used
by validation logic; it is used as a sentinel when background validation
completes or if the snapshot chainstate is found to be invalid.

isUsable is a convenience method that incorporates m_disabled.
This commit is contained in:
James O'Beirne 2022-02-02 14:40:47 -05:00
parent 5ee22cdafd
commit c29f26b47b
3 changed files with 39 additions and 17 deletions

View file

@ -107,7 +107,7 @@ sequentially.
### Background chainstate hits snapshot base block ### Background chainstate hits snapshot base block
Once the tip of the background chainstate hits the base block of the snapshot 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_stop_use` (not yet chainstate, we stop use of the background chainstate by setting `m_disabled` (not yet
committed - see #15606), in `CompleteSnapshotValidation()`, which is checked in committed - see #15606), in `CompleteSnapshotValidation()`, which is checked in
`ActivateBestChain()`). We hash the background chainstate's UTXO set contents and `ActivateBestChain()`). We hash the background chainstate's UTXO set contents and
ensure it matches the compiled value in `CMainParams::m_assumeutxo_data`. ensure it matches the compiled value in `CMainParams::m_assumeutxo_data`.
@ -119,10 +119,10 @@ The background chainstate data lingers on disk until shutdown, when in
| | | | | |
| ---------- | ----------- | | ---------- | ----------- |
| number of chainstates | 2 (ibd has `m_stop_use=true`) | | number of chainstates | 2 (ibd has `m_disabled=true`) |
| active chainstate | snapshot | | active chainstate | snapshot |
**Failure consideration:** if bitcoind unexpectedly halts after `m_stop_use` is set on **Failure consideration:** if bitcoind unexpectedly halts after `m_disabled` is set on
the background chainstate but before `CompleteSnapshotValidation()` can finish, the the background chainstate but before `CompleteSnapshotValidation()` can finish, the
need to complete snapshot validation will be detected on subsequent init by need to complete snapshot validation will be detected on subsequent init by
`ChainstateManager::CheckForUncleanShutdown()`. `ChainstateManager::CheckForUncleanShutdown()`.

View file

@ -4859,12 +4859,8 @@ std::vector<Chainstate*> ChainstateManager::GetAll()
LOCK(::cs_main); LOCK(::cs_main);
std::vector<Chainstate*> out; std::vector<Chainstate*> out;
if (!IsSnapshotValidated() && m_ibd_chainstate) { for (Chainstate* cs : {m_ibd_chainstate.get(), m_snapshot_chainstate.get()}) {
out.push_back(m_ibd_chainstate.get()); if (this->IsUsable(cs)) out.push_back(cs);
}
if (m_snapshot_chainstate) {
out.push_back(m_snapshot_chainstate.get());
} }
return out; return out;
@ -5263,17 +5259,22 @@ bool ChainstateManager::IsSnapshotActive() const
void ChainstateManager::MaybeRebalanceCaches() void ChainstateManager::MaybeRebalanceCaches()
{ {
AssertLockHeld(::cs_main); AssertLockHeld(::cs_main);
if (m_ibd_chainstate && !m_snapshot_chainstate) { 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) {
LogPrintf("[snapshot] allocating all cache to the IBD chainstate\n"); LogPrintf("[snapshot] allocating all cache to the IBD chainstate\n");
// Allocate everything to the IBD chainstate. // Allocate everything to the IBD chainstate.
m_ibd_chainstate->ResizeCoinsCaches(m_total_coinstip_cache, m_total_coinsdb_cache); m_ibd_chainstate->ResizeCoinsCaches(m_total_coinstip_cache, m_total_coinsdb_cache);
} }
else if (m_snapshot_chainstate && !m_ibd_chainstate) { else if (snapshot_usable && !ibd_usable) {
// If background validation has completed and snapshot is our active chain...
LogPrintf("[snapshot] allocating all cache to the snapshot chainstate\n"); LogPrintf("[snapshot] allocating all cache to the snapshot chainstate\n");
// Allocate everything to the snapshot chainstate. // Allocate everything to the snapshot chainstate.
m_snapshot_chainstate->ResizeCoinsCaches(m_total_coinstip_cache, m_total_coinsdb_cache); m_snapshot_chainstate->ResizeCoinsCaches(m_total_coinstip_cache, m_total_coinsdb_cache);
} }
else if (m_ibd_chainstate && m_snapshot_chainstate) { else if (ibd_usable && snapshot_usable) {
// If both chainstates exist, determine who needs more cache based on IBD status. // 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. // Note: shrink caches first so that we don't inadvertently overwhelm available memory.

View file

@ -473,6 +473,19 @@ protected:
//! Manages the UTXO set, which is a reflection of the contents of `m_chain`. //! Manages the UTXO set, which is a reflection of the contents of `m_chain`.
std::unique_ptr<CoinsViews> m_coins_views; std::unique_ptr<CoinsViews> 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};
public: public:
//! Reference to a BlockManager instance which itself is shared across all //! Reference to a BlockManager instance which itself is shared across all
//! Chainstate instances. //! Chainstate instances.
@ -840,10 +853,6 @@ private:
//! that call. //! that call.
Chainstate* m_active_chainstate GUARDED_BY(::cs_main) {nullptr}; Chainstate* m_active_chainstate GUARDED_BY(::cs_main) {nullptr};
//! If true, the assumed-valid chainstate has been fully validated
//! by the background validation chainstate.
bool m_snapshot_validated GUARDED_BY(::cs_main){false};
CBlockIndex* m_best_invalid GUARDED_BY(::cs_main){nullptr}; CBlockIndex* m_best_invalid GUARDED_BY(::cs_main){nullptr};
//! Internal helper for ActivateSnapshot(). //! Internal helper for ActivateSnapshot().
@ -876,6 +885,15 @@ private:
//! nullopt. //! nullopt.
std::optional<int> GetSnapshotBaseHeight() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main); std::optional<int> GetSnapshotBaseHeight() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! 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;
}
public: public:
using Options = kernel::ChainstateManagerOpts; using Options = kernel::ChainstateManagerOpts;
@ -987,7 +1005,10 @@ public:
std::optional<uint256> SnapshotBlockhash() const; std::optional<uint256> SnapshotBlockhash() const;
//! Is there a snapshot in use and has it been fully validated? //! Is there a snapshot in use and has it been fully validated?
bool IsSnapshotValidated() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { return m_snapshot_validated; } bool IsSnapshotValidated() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
{
return m_snapshot_chainstate && m_ibd_chainstate && m_ibd_chainstate->m_disabled;
}
/** /**
* Process an incoming block. This only returns after the best known valid * Process an incoming block. This only returns after the best known valid