Merge #17737: Add ChainstateManager, remove BlockManager global

c9017ce3bc protect g_chainman with cs_main (James O'Beirne)
2b081c4568 test: add basic tests for ChainstateManager (James O'Beirne)
4ae29f5f0c use ChainstateManager to initialize chainstate (James O'Beirne)
5b690f0aae refactor: move RewindBlockIndex to CChainState (James O'Beirne)
89cdf4d569 validation: introduce unused ChainstateManager (James O'Beirne)
8e2ecfe249 validation: add CChainState.m_from_snapshot_blockhash (James O'Beirne)

Pull request description:

  This is part of the [assumeutxo project](https://github.com/bitcoin/bitcoin/projects/11):

  Parent PR: #15606
  Issue: #15605
  Specification: https://github.com/jamesob/assumeutxo-docs/tree/master/proposal

  ---

  This changeset introduces `ChainstateManager`, which is responsible for creating and managing access to multiple chainstates. Until we allow chainstate creation from UTXO snapshots (next assumeutxo PR?) it's basically unnecessary, but it is a prerequisite for background IBD support.

  Changes are also made to the initialization process to make use of `g_chainman` and thus clear the way for multiple chainstates being loaded on startup.

  One immediate benefit of this change is that we no longer have the `g_blockman` global, but instead have the ChainstateManager inject a reference of its shared BlockManager into any chainstate it creates.

  Another immediate benefit is that uses of `ChainActive()` and `ChainstateActive()` are now covered by lock annotations. Because use of `g_chainman` is annotated to require cs_main, these two functions subsequently follow.

  Because of whitespace changes, this diff looks bigger than it is. E.g., 4813167d98 is most easily reviewed with
  ```sh
  git show --color-moved=dimmed_zebra -w 4813167d98
  ```

ACKs for top commit:
  MarcoFalke:
    re-ACK c9017ce3bc 📙
  fjahr:
    Code Review Re-ACK c9017ce3bc
  ariard:
    Code Review ACK c9017ce
  ryanofsky:
    Code review ACK c9017ce3bc. No changes since last review other than a straight rebase

Tree-SHA512: 3f250d0dc95d4bfd70852ef1e39e081a4a9b71a4453f276e6d474c2ae06ad6ae6a32b4173084fe499e1e9af72dd9007f4a8a375c63ce9ac472ffeaada41ab508
This commit is contained in:
MarcoFalke 2020-04-10 13:01:47 -04:00
commit 10358a381a
No known key found for this signature in database
GPG key ID: CE2B75697E69A548
9 changed files with 541 additions and 146 deletions

View file

@ -245,6 +245,7 @@ BITCOIN_TESTS =\
test/uint256_tests.cpp \
test/util_tests.cpp \
test/validation_block_tests.cpp \
test/validation_chainstatemanager_tests.cpp \
test/validation_flush_tests.cpp \
test/validationinterface_tests.cpp \
test/versionbits_tests.cpp

View file

@ -243,13 +243,12 @@ void Shutdown(NodeContext& node)
}
// FlushStateToDisk generates a ChainStateFlushed callback, which we should avoid missing
//
// g_chainstate is referenced here directly (instead of ::ChainstateActive()) because it
// may not have been initialized yet.
{
LOCK(cs_main);
if (g_chainstate && g_chainstate->CanFlushToDisk()) {
g_chainstate->ForceFlushStateToDisk();
for (CChainState* chainstate : g_chainman.GetAll()) {
if (chainstate->CanFlushToDisk()) {
chainstate->ForceFlushStateToDisk();
}
}
}
@ -273,9 +272,11 @@ void Shutdown(NodeContext& node)
{
LOCK(cs_main);
if (g_chainstate && g_chainstate->CanFlushToDisk()) {
g_chainstate->ForceFlushStateToDisk();
g_chainstate->ResetCoinsViews();
for (CChainState* chainstate : g_chainman.GetAll()) {
if (chainstate->CanFlushToDisk()) {
chainstate->ForceFlushStateToDisk();
chainstate->ResetCoinsViews();
}
}
pblocktree.reset();
}
@ -719,11 +720,17 @@ static void ThreadImport(std::vector<fs::path> vImportFiles)
}
// scan for better chains in the block chain database, that are not yet connected in the active best chain
BlockValidationState state;
if (!ActivateBestChain(state, chainparams)) {
LogPrintf("Failed to connect best block (%s)\n", state.ToString());
StartShutdown();
return;
// We can't hold cs_main during ActivateBestChain even though we're accessing
// the g_chainman unique_ptrs since ABC requires us not to be holding cs_main, so retrieve
// the relevant pointers before the ABC call.
for (CChainState* chainstate : WITH_LOCK(::cs_main, return g_chainman.GetAll())) {
BlockValidationState state;
if (!chainstate->ActivateBestChain(state, chainparams, nullptr)) {
LogPrintf("Failed to connect best block (%s)\n", state.ToString());
StartShutdown();
return;
}
}
if (gArgs.GetBoolArg("-stopafterblockimport", DEFAULT_STOPAFTERBLOCKIMPORT)) {
@ -1513,17 +1520,18 @@ bool AppInitMain(NodeContext& node)
bool fLoaded = false;
while (!fLoaded && !ShutdownRequested()) {
bool fReset = fReindex;
auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull();
};
std::string strLoadError;
uiInterface.InitMessage(_("Loading block index...").translated);
do {
const int64_t load_block_index_start_time = GetTimeMillis();
bool is_coinsview_empty;
try {
LOCK(cs_main);
// This statement makes ::ChainstateActive() usable.
g_chainstate = MakeUnique<CChainState>();
g_chainman.InitializeChainstate();
UnloadBlockIndex();
// new CBlockTreeDB tries to delete the existing file, which
@ -1576,43 +1584,53 @@ bool AppInitMain(NodeContext& node)
// At this point we're either in reindex or we've loaded a useful
// block tree into BlockIndex()!
::ChainstateActive().InitCoinsDB(
/* cache_size_bytes */ nCoinDBCache,
/* in_memory */ false,
/* should_wipe */ fReset || fReindexChainState);
bool failed_chainstate_init = false;
::ChainstateActive().CoinsErrorCatcher().AddReadErrCallback([]() {
uiInterface.ThreadSafeMessageBox(
_("Error reading from database, shutting down.").translated,
"", CClientUIInterface::MSG_ERROR);
});
for (CChainState* chainstate : g_chainman.GetAll()) {
LogPrintf("Initializing chainstate %s\n", chainstate->ToString());
chainstate->InitCoinsDB(
/* cache_size_bytes */ nCoinDBCache,
/* in_memory */ false,
/* should_wipe */ fReset || fReindexChainState);
// If necessary, upgrade from older database format.
// This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
if (!::ChainstateActive().CoinsDB().Upgrade()) {
strLoadError = _("Error upgrading chainstate database").translated;
break;
}
chainstate->CoinsErrorCatcher().AddReadErrCallback([]() {
uiInterface.ThreadSafeMessageBox(
_("Error reading from database, shutting down.").translated,
"", CClientUIInterface::MSG_ERROR);
});
// ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
if (!::ChainstateActive().ReplayBlocks(chainparams)) {
strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.").translated;
break;
}
// The on-disk coinsdb is now in a good state, create the cache
::ChainstateActive().InitCoinsCache();
assert(::ChainstateActive().CanFlushToDisk());
is_coinsview_empty = fReset || fReindexChainState ||
::ChainstateActive().CoinsTip().GetBestBlock().IsNull();
if (!is_coinsview_empty) {
// LoadChainTip initializes the chain based on CoinsTip()'s best block
if (!::ChainstateActive().LoadChainTip(chainparams)) {
strLoadError = _("Error initializing block database").translated;
// If necessary, upgrade from older database format.
// This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
if (!chainstate->CoinsDB().Upgrade()) {
strLoadError = _("Error upgrading chainstate database").translated;
failed_chainstate_init = true;
break;
}
assert(::ChainActive().Tip() != nullptr);
// ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
if (!chainstate->ReplayBlocks(chainparams)) {
strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.").translated;
failed_chainstate_init = true;
break;
}
// The on-disk coinsdb is now in a good state, create the cache
chainstate->InitCoinsCache();
assert(chainstate->CanFlushToDisk());
if (!is_coinsview_empty(chainstate)) {
// LoadChainTip initializes the chain based on CoinsTip()'s best block
if (!chainstate->LoadChainTip(chainparams)) {
strLoadError = _("Error initializing block database").translated;
failed_chainstate_init = true;
break; // out of the per-chainstate loop
}
assert(chainstate->m_chain.Tip() != nullptr);
}
}
if (failed_chainstate_init) {
break; // out of the chainstate activation do-while
}
} catch (const std::exception& e) {
LogPrintf("%s\n", e.what());
@ -1620,49 +1638,76 @@ bool AppInitMain(NodeContext& node)
break;
}
if (!fReset) {
// Note that RewindBlockIndex MUST run even if we're about to -reindex-chainstate.
// It both disconnects blocks based on ::ChainActive(), and drops block data in
// BlockIndex() based on lack of available witness data.
uiInterface.InitMessage(_("Rewinding blocks...").translated);
if (!RewindBlockIndex(chainparams)) {
strLoadError = _("Unable to rewind the database to a pre-fork state. You will need to redownload the blockchain").translated;
break;
bool failed_rewind{false};
// Can't hold cs_main while calling RewindBlockIndex, so retrieve the relevant
// chainstates beforehand.
for (CChainState* chainstate : WITH_LOCK(::cs_main, return g_chainman.GetAll())) {
if (!fReset) {
// Note that RewindBlockIndex MUST run even if we're about to -reindex-chainstate.
// It both disconnects blocks based on the chainstate, and drops block data in
// BlockIndex() based on lack of available witness data.
uiInterface.InitMessage(_("Rewinding blocks...").translated);
if (!chainstate->RewindBlockIndex(chainparams)) {
strLoadError = _(
"Unable to rewind the database to a pre-fork state. "
"You will need to redownload the blockchain").translated;
failed_rewind = true;
break; // out of the per-chainstate loop
}
}
}
if (failed_rewind) {
break; // out of the chainstate activation do-while
}
bool failed_verification = false;
try {
LOCK(cs_main);
if (!is_coinsview_empty) {
uiInterface.InitMessage(_("Verifying blocks...").translated);
if (fHavePruned && gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS) > MIN_BLOCKS_TO_KEEP) {
LogPrintf("Prune: pruned datadir may not have more than %d blocks; only checking available blocks\n",
MIN_BLOCKS_TO_KEEP);
}
CBlockIndex* tip = ::ChainActive().Tip();
RPCNotifyBlockChange(true, tip);
if (tip && tip->nTime > GetAdjustedTime() + 2 * 60 * 60) {
strLoadError = _("The block database contains a block which appears to be from the future. "
"This may be due to your computer's date and time being set incorrectly. "
"Only rebuild the block database if you are sure that your computer's date and time are correct").translated;
break;
}
for (CChainState* chainstate : g_chainman.GetAll()) {
if (!is_coinsview_empty(chainstate)) {
uiInterface.InitMessage(_("Verifying blocks...").translated);
if (fHavePruned && gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS) > MIN_BLOCKS_TO_KEEP) {
LogPrintf("Prune: pruned datadir may not have more than %d blocks; only checking available blocks\n",
MIN_BLOCKS_TO_KEEP);
}
if (!CVerifyDB().VerifyDB(chainparams, &::ChainstateActive().CoinsDB(), gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL),
gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS))) {
strLoadError = _("Corrupted block database detected").translated;
break;
const CBlockIndex* tip = chainstate->m_chain.Tip();
RPCNotifyBlockChange(true, tip);
if (tip && tip->nTime > GetAdjustedTime() + 2 * 60 * 60) {
strLoadError = _("The block database contains a block which appears to be from the future. "
"This may be due to your computer's date and time being set incorrectly. "
"Only rebuild the block database if you are sure that your computer's date and time are correct").translated;
failed_verification = true;
break;
}
// Only verify the DB of the active chainstate. This is fixed in later
// work when we allow VerifyDB to be parameterized by chainstate.
if (&::ChainstateActive() == chainstate &&
!CVerifyDB().VerifyDB(
chainparams, &chainstate->CoinsDB(),
gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL),
gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS))) {
strLoadError = _("Corrupted block database detected").translated;
failed_verification = true;
break;
}
}
}
} catch (const std::exception& e) {
LogPrintf("%s\n", e.what());
strLoadError = _("Error opening block database").translated;
failed_verification = true;
break;
}
fLoaded = true;
LogPrintf(" block index %15dms\n", GetTimeMillis() - load_block_index_start_time);
if (!failed_verification) {
fLoaded = true;
LogPrintf(" block index %15dms\n", GetTimeMillis() - load_block_index_start_time);
}
} while(false);
if (!fLoaded && !ShutdownRequested()) {
@ -1726,8 +1771,11 @@ bool AppInitMain(NodeContext& node)
LogPrintf("Unsetting NODE_NETWORK on prune mode\n");
nLocalServices = ServiceFlags(nLocalServices & ~NODE_NETWORK);
if (!fReindex) {
uiInterface.InitMessage(_("Pruning blockstore...").translated);
::ChainstateActive().PruneAndFlush();
LOCK(cs_main);
for (CChainState* chainstate : g_chainman.GetAll()) {
uiInterface.InitMessage(_("Pruning blockstore...").translated);
chainstate->PruneAndFlush();
}
}
}

View file

@ -82,6 +82,7 @@ void AppTests::appTests()
// Reset global state to avoid interfering with later tests.
AbortShutdown();
UnloadBlockIndex();
WITH_LOCK(::cs_main, g_chainman.Reset());
}
//! Entry point for BitcoinGUI tests.

View file

@ -1314,7 +1314,7 @@ static UniValue getchaintips(const JSONRPCRequest& request)
/*
* Idea: the set of chain tips is ::ChainActive().tip, plus orphan blocks which do not have another orphan building off of them.
* Algorithm:
* - Make one pass through g_blockman.m_block_index, picking out the orphan blocks, and also storing a set of the orphan block's pprev pointers.
* - Make one pass through BlockIndex(), picking out the orphan blocks, and also storing a set of the orphan block's pprev pointers.
* - Iterate through the orphan blocks. If the block isn't pointed to by another orphan, it is a chain tip.
* - add ::ChainActive().Tip()
*/

View file

@ -114,7 +114,8 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha
GetMainSignals().RegisterBackgroundSignalScheduler(*g_rpc_node->scheduler);
pblocktree.reset(new CBlockTreeDB(1 << 20, true));
g_chainstate = MakeUnique<CChainState>();
g_chainman.InitializeChainstate();
::ChainstateActive().InitCoinsDB(
/* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false);
assert(!::ChainstateActive().CanFlushToDisk());
@ -161,7 +162,7 @@ TestingSetup::~TestingSetup()
m_node.mempool = nullptr;
m_node.scheduler.reset();
UnloadBlockIndex();
g_chainstate.reset();
g_chainman.Reset();
pblocktree.reset();
}

View file

@ -0,0 +1,104 @@
// Copyright (c) 2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
//
#include <chainparams.h>
#include <random.h>
#include <uint256.h>
#include <consensus/validation.h>
#include <sync.h>
#include <test/util/setup_common.h>
#include <validation.h>
#include <vector>
#include <boost/test/unit_test.hpp>
BOOST_FIXTURE_TEST_SUITE(validation_chainstatemanager_tests, TestingSetup)
//! Basic tests for ChainstateManager.
//!
//! First create a legacy (IBD) chainstate, then create a snapshot chainstate.
BOOST_AUTO_TEST_CASE(chainstatemanager)
{
ChainstateManager manager;
std::vector<CChainState*> chainstates;
const CChainParams& chainparams = Params();
// Create a legacy (IBD) chainstate.
//
ENTER_CRITICAL_SECTION(cs_main);
CChainState& c1 = manager.InitializeChainstate();
LEAVE_CRITICAL_SECTION(cs_main);
chainstates.push_back(&c1);
c1.InitCoinsDB(
/* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false);
WITH_LOCK(::cs_main, c1.InitCoinsCache());
BOOST_CHECK(!manager.IsSnapshotActive());
BOOST_CHECK(!manager.IsSnapshotValidated());
BOOST_CHECK(!manager.IsBackgroundIBD(&c1));
auto all = manager.GetAll();
BOOST_CHECK_EQUAL_COLLECTIONS(all.begin(), all.end(), chainstates.begin(), chainstates.end());
auto& active_chain = manager.ActiveChain();
BOOST_CHECK_EQUAL(&active_chain, &c1.m_chain);
BOOST_CHECK_EQUAL(manager.ActiveHeight(), -1);
auto active_tip = manager.ActiveTip();
auto exp_tip = c1.m_chain.Tip();
BOOST_CHECK_EQUAL(active_tip, exp_tip);
auto& validated_cs = manager.ValidatedChainstate();
BOOST_CHECK_EQUAL(&validated_cs, &c1);
// Create a snapshot-based chainstate.
//
ENTER_CRITICAL_SECTION(cs_main);
CChainState& c2 = manager.InitializeChainstate(GetRandHash());
LEAVE_CRITICAL_SECTION(cs_main);
chainstates.push_back(&c2);
c2.InitCoinsDB(
/* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false);
WITH_LOCK(::cs_main, c2.InitCoinsCache());
// Unlike c1, which doesn't have any blocks. Gets us different tip, height.
c2.LoadGenesisBlock(chainparams);
BlockValidationState _;
BOOST_CHECK(c2.ActivateBestChain(_, chainparams, nullptr));
BOOST_CHECK(manager.IsSnapshotActive());
BOOST_CHECK(!manager.IsSnapshotValidated());
BOOST_CHECK(manager.IsBackgroundIBD(&c1));
BOOST_CHECK(!manager.IsBackgroundIBD(&c2));
auto all2 = manager.GetAll();
BOOST_CHECK_EQUAL_COLLECTIONS(all2.begin(), all2.end(), chainstates.begin(), chainstates.end());
auto& active_chain2 = manager.ActiveChain();
BOOST_CHECK_EQUAL(&active_chain2, &c2.m_chain);
BOOST_CHECK_EQUAL(manager.ActiveHeight(), 0);
auto active_tip2 = manager.ActiveTip();
auto exp_tip2 = c2.m_chain.Tip();
BOOST_CHECK_EQUAL(active_tip2, exp_tip2);
// Ensure that these pointers actually correspond to different
// CCoinsViewCache instances.
BOOST_CHECK(exp_tip != exp_tip2);
auto& validated_cs2 = manager.ValidatedChainstate();
BOOST_CHECK_EQUAL(&validated_cs2, &c1);
auto& validated_chain = manager.ValidatedChain();
BOOST_CHECK_EQUAL(&validated_chain, &c1.m_chain);
auto validated_tip = manager.ValidatedTip();
exp_tip = c1.m_chain.Tip();
BOOST_CHECK_EQUAL(validated_tip, exp_tip);
// Avoid triggering the address sanitizer.
WITH_LOCK(::cs_main, manager.Unload());
}
BOOST_AUTO_TEST_SUITE_END()

View file

@ -20,6 +20,7 @@
#include <index/txindex.h>
#include <logging.h>
#include <logging/timer.h>
#include <optional.h>
#include <policy/fees.h>
#include <policy/policy.h>
#include <policy/settings.h>
@ -76,20 +77,19 @@ bool CBlockIndexWorkComparator::operator()(const CBlockIndex *pa, const CBlockIn
return false;
}
namespace {
BlockManager g_blockman;
} // anon namespace
ChainstateManager g_chainman;
std::unique_ptr<CChainState> g_chainstate;
CChainState& ChainstateActive() {
assert(g_chainstate);
return *g_chainstate;
CChainState& ChainstateActive()
{
LOCK(::cs_main);
assert(g_chainman.m_active_chainstate);
return *g_chainman.m_active_chainstate;
}
CChain& ChainActive() {
assert(g_chainstate);
return g_chainstate->m_chain;
CChain& ChainActive()
{
LOCK(::cs_main);
return ::ChainstateActive().m_chain;
}
/**
@ -151,8 +151,8 @@ namespace {
CBlockIndex* LookupBlockIndex(const uint256& hash)
{
AssertLockHeld(cs_main);
BlockMap::const_iterator it = g_blockman.m_block_index.find(hash);
return it == g_blockman.m_block_index.end() ? nullptr : it->second;
BlockMap::const_iterator it = g_chainman.BlockIndex().find(hash);
return it == g_chainman.BlockIndex().end() ? nullptr : it->second;
}
CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& locator)
@ -1242,10 +1242,9 @@ void CoinsViews::InitCache()
m_cacheview = MakeUnique<CCoinsViewCache>(&m_catcherview);
}
// NOTE: for now m_blockman is set to a global, but this will be changed
// in a future commit.
CChainState::CChainState() : m_blockman(g_blockman) {}
CChainState::CChainState(BlockManager& blockman, uint256 from_snapshot_blockhash)
: m_blockman(blockman),
m_from_snapshot_blockhash(from_snapshot_blockhash) {}
void CChainState::InitCoinsDB(
size_t cache_size_bytes,
@ -1253,6 +1252,10 @@ void CChainState::InitCoinsDB(
bool should_wipe,
std::string leveldb_name)
{
if (!m_from_snapshot_blockhash.IsNull()) {
leveldb_name += "_" + m_from_snapshot_blockhash.ToString();
}
m_coins_views = MakeUnique<CoinsViews>(
leveldb_name, cache_size_bytes, in_memory, should_wipe);
}
@ -1294,7 +1297,8 @@ static CBlockIndex *pindexBestForkTip = nullptr, *pindexBestForkBase = nullptr;
BlockMap& BlockIndex()
{
return g_blockman.m_block_index;
LOCK(::cs_main);
return g_chainman.m_blockman.m_block_index;
}
static void AlertNotify(const std::string& strMessage)
@ -3443,7 +3447,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio
if (fCheckpointsEnabled) {
// Don't accept any forks from the main chain prior to last checkpoint.
// GetLastCheckpoint finds the last checkpoint in MapCheckpoints that's in our
// g_blockman.m_block_index.
// BlockIndex().
CBlockIndex* pcheckpoint = GetLastCheckpoint(params.Checkpoints());
if (pcheckpoint && nHeight < pcheckpoint->nHeight) {
LogPrintf("ERROR: %s: forked chain older than last checkpoint (height %d)\n", __func__, nHeight);
@ -3651,7 +3655,8 @@ bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, BlockValid
LOCK(cs_main);
for (const CBlockHeader& header : headers) {
CBlockIndex *pindex = nullptr; // Use a temp pindex instead of ppindex to avoid a const_cast
bool accepted = g_blockman.AcceptBlockHeader(header, state, chainparams, &pindex);
bool accepted = g_chainman.m_blockman.AcceptBlockHeader(
header, state, chainparams, &pindex);
::ChainstateActive().CheckBlockIndex(chainparams.GetConsensus());
if (!accepted) {
@ -3853,7 +3858,7 @@ void PruneOneBlockFile(const int fileNumber)
{
LOCK(cs_LastBlockFile);
for (const auto& entry : g_blockman.m_block_index) {
for (const auto& entry : g_chainman.BlockIndex()) {
CBlockIndex* pindex = entry.second;
if (pindex->nFile == fileNumber) {
pindex->nStatus &= ~BLOCK_HAVE_DATA;
@ -3867,12 +3872,12 @@ void PruneOneBlockFile(const int fileNumber)
// to be downloaded again in order to consider its chain, at which
// point it would be considered as a candidate for
// m_blocks_unlinked or setBlockIndexCandidates.
auto range = g_blockman.m_blocks_unlinked.equal_range(pindex->pprev);
auto range = g_chainman.m_blockman.m_blocks_unlinked.equal_range(pindex->pprev);
while (range.first != range.second) {
std::multimap<CBlockIndex *, CBlockIndex *>::iterator _it = range.first;
range.first++;
if (_it->second == pindex) {
g_blockman.m_blocks_unlinked.erase(_it);
g_chainman.m_blockman.m_blocks_unlinked.erase(_it);
}
}
}
@ -4109,9 +4114,11 @@ void BlockManager::Unload() {
bool static LoadBlockIndexDB(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
if (!g_blockman.LoadBlockIndex(
chainparams.GetConsensus(), *pblocktree, ::ChainstateActive().setBlockIndexCandidates))
if (!g_chainman.m_blockman.LoadBlockIndex(
chainparams.GetConsensus(), *pblocktree,
::ChainstateActive().setBlockIndexCandidates)) {
return false;
}
// Load block file info
pblocktree->ReadLastBlockFile(nLastBlockFile);
@ -4133,7 +4140,7 @@ bool static LoadBlockIndexDB(const CChainParams& chainparams) EXCLUSIVE_LOCKS_RE
// Check presence of blk files
LogPrintf("Checking all blk files are present...\n");
std::set<int> setBlkDataFiles;
for (const std::pair<const uint256, CBlockIndex*>& item : g_blockman.m_block_index)
for (const std::pair<const uint256, CBlockIndex*>& item : g_chainman.BlockIndex())
{
CBlockIndex* pindex = item.second;
if (pindex->nStatus & BLOCK_HAVE_DATA) {
@ -4510,26 +4517,15 @@ bool CChainState::RewindBlockIndex(const CChainParams& params)
PruneBlockIndexCandidates();
CheckBlockIndex(params.GetConsensus());
}
}
return true;
}
bool RewindBlockIndex(const CChainParams& params) {
if (!::ChainstateActive().RewindBlockIndex(params)) {
return false;
}
LOCK(cs_main);
if (::ChainActive().Tip() != nullptr) {
// FlushStateToDisk can possibly read ::ChainActive(). Be conservative
// and skip it here, we're about to -reindex-chainstate anyway, so
// it'll get called a bunch real soon.
BlockValidationState state;
if (!::ChainstateActive().FlushStateToDisk(params, state, FlushStateMode::ALWAYS)) {
LogPrintf("RewindBlockIndex: unable to flush state to disk (%s)\n", state.ToString());
return false;
// FlushStateToDisk can possibly read ::ChainActive(). Be conservative
// and skip it here, we're about to -reindex-chainstate anyway, so
// it'll get called a bunch real soon.
BlockValidationState state;
if (!FlushStateToDisk(params, state, FlushStateMode::ALWAYS)) {
LogPrintf("RewindBlockIndex: unable to flush state to disk (%s)\n", state.ToString());
return false;
}
}
}
@ -4547,8 +4543,7 @@ void CChainState::UnloadBlockIndex() {
void UnloadBlockIndex()
{
LOCK(cs_main);
::ChainActive().SetTip(nullptr);
g_blockman.Unload();
g_chainman.Unload();
pindexBestInvalid = nullptr;
pindexBestHeader = nullptr;
mempool.clear();
@ -4561,8 +4556,6 @@ void UnloadBlockIndex()
warningcache[b].clear();
}
fHavePruned = false;
::ChainstateActive().UnloadBlockIndex();
}
bool LoadBlockIndex(const CChainParams& chainparams)
@ -4572,7 +4565,7 @@ bool LoadBlockIndex(const CChainParams& chainparams)
if (!fReindex) {
bool ret = LoadBlockIndexDB(chainparams);
if (!ret) return false;
needs_init = g_blockman.m_block_index.empty();
needs_init = g_chainman.m_blockman.m_block_index.empty();
}
if (needs_init) {
@ -4693,7 +4686,7 @@ bool LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, FlatFi
// Activate the genesis block so normal node progress can continue
if (hash == chainparams.GetConsensus().hashGenesisBlock) {
BlockValidationState state;
if (!ActivateBestChain(state, chainparams)) {
if (!ActivateBestChain(state, chainparams, nullptr)) {
break;
}
}
@ -4923,6 +4916,14 @@ void CChainState::CheckBlockIndex(const Consensus::Params& consensusParams)
assert(nNodes == forward.size());
}
std::string CChainState::ToString()
{
CBlockIndex* tip = m_chain.Tip();
return strprintf("Chainstate [%s] @ height %d (%s)",
m_from_snapshot_blockhash.IsNull() ? "ibd" : "snapshot",
tip ? tip->nHeight : -1, tip ? tip->GetBlockHash().ToString() : "null");
}
std::string CBlockFileInfo::ToString() const
{
return strprintf("CBlockFileInfo(blocks=%u, size=%u, heights=%u...%u, time=%s...%s)", nBlocks, nSize, nHeightFirst, nHeightLast, FormatISO8601Date(nTimeFirst), FormatISO8601Date(nTimeLast));
@ -5110,10 +5111,99 @@ public:
CMainCleanup() {}
~CMainCleanup() {
// block headers
BlockMap::iterator it1 = g_blockman.m_block_index.begin();
for (; it1 != g_blockman.m_block_index.end(); it1++)
BlockMap::iterator it1 = g_chainman.BlockIndex().begin();
for (; it1 != g_chainman.BlockIndex().end(); it1++)
delete (*it1).second;
g_blockman.m_block_index.clear();
g_chainman.BlockIndex().clear();
}
};
static CMainCleanup instance_of_cmaincleanup;
Optional<uint256> ChainstateManager::SnapshotBlockhash() const {
if (m_active_chainstate != nullptr) {
// If a snapshot chainstate exists, it will always be our active.
return m_active_chainstate->m_from_snapshot_blockhash;
}
return {};
}
std::vector<CChainState*> ChainstateManager::GetAll()
{
std::vector<CChainState*> out;
if (!IsSnapshotValidated() && m_ibd_chainstate) {
out.push_back(m_ibd_chainstate.get());
}
if (m_snapshot_chainstate) {
out.push_back(m_snapshot_chainstate.get());
}
return out;
}
CChainState& ChainstateManager::InitializeChainstate(const uint256& snapshot_blockhash)
{
bool is_snapshot = !snapshot_blockhash.IsNull();
std::unique_ptr<CChainState>& to_modify =
is_snapshot ? m_snapshot_chainstate : m_ibd_chainstate;
if (to_modify) {
throw std::logic_error("should not be overwriting a chainstate");
}
to_modify.reset(new CChainState(m_blockman, 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;
}
CChain& ChainstateManager::ActiveChain() const
{
assert(m_active_chainstate);
return m_active_chainstate->m_chain;
}
bool ChainstateManager::IsSnapshotActive() const
{
return m_snapshot_chainstate && m_active_chainstate == m_snapshot_chainstate.get();
}
CChainState& ChainstateManager::ValidatedChainstate() const
{
if (m_snapshot_chainstate && IsSnapshotValidated()) {
return *m_snapshot_chainstate.get();
}
assert(m_ibd_chainstate);
return *m_ibd_chainstate.get();
}
bool ChainstateManager::IsBackgroundIBD(CChainState* chainstate) const
{
return (m_snapshot_chainstate && chainstate == m_ibd_chainstate.get());
}
void ChainstateManager::Unload()
{
for (CChainState* chainstate : this->GetAll()) {
chainstate->m_chain.SetTip(nullptr);
chainstate->UnloadBlockIndex();
}
m_blockman.Unload();
}
void ChainstateManager::Reset()
{
m_ibd_chainstate.reset();
m_snapshot_chainstate.reset();
m_active_chainstate = nullptr;
m_snapshot_validated = false;
}

View file

@ -14,6 +14,7 @@
#include <coins.h>
#include <crypto/common.h> // for ReadLE64
#include <fs.h>
#include <optional.h>
#include <policy/feerate.h>
#include <protocol.h> // For CMessageHeader::MessageStartChars
#include <script/script_error.h>
@ -379,9 +380,6 @@ bool TestBlockValidity(BlockValidationState& state, const CChainParams& chainpar
* Note that transaction witness validation rules are always enforced when P2SH is enforced. */
bool IsWitnessEnabled(const CBlockIndex* pindexPrev, const Consensus::Params& params);
/** When there are blocks in the active chain with missing data, rewind the chainstate and remove them from the block index */
bool RewindBlockIndex(const CChainParams& params) LOCKS_EXCLUDED(cs_main);
/** Compute at which vout of the block's coinbase transaction the witness commitment occurs, or -1 if not found */
int GetWitnessCommitmentIndex(const CBlock& block);
@ -539,6 +537,9 @@ enum class CoinsCacheSizeState
OK = 0
};
// Defined below, but needed for `friend` usage in CChainState.
class ChainstateManager;
/**
* CChainState stores and provides an API to update our local knowledge of the
* current best chain.
@ -591,8 +592,7 @@ private:
std::unique_ptr<CoinsViews> m_coins_views;
public:
CChainState(BlockManager& blockman) : m_blockman(blockman) {}
CChainState();
explicit CChainState(BlockManager& blockman, uint256 from_snapshot_blockhash = uint256());
/**
* Initialize the CoinsViews UTXO set database management data structures. The in-memory
@ -620,6 +620,13 @@ public:
//! @see CChain, CBlockIndex.
CChain m_chain;
/**
* The blockhash which is the base of the snapshot this chainstate was created from.
*
* IsNull() if this chainstate was not created from a snapshot.
*/
const uint256 m_from_snapshot_blockhash{};
/**
* The set of all CBlockIndex entries with BLOCK_VALID_TRANSACTIONS (for itself and all ancestors) and
* as good as our current tip or better. Entries may be failed, though, and pruning nodes may be
@ -741,6 +748,8 @@ public:
size_t max_coins_cache_size_bytes,
size_t max_mempool_size_bytes) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
std::string ToString() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
private:
bool ActivateBestChainStep(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs);
bool ConnectTip(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs);
@ -753,6 +762,8 @@ private:
//! Mark a block as not having block data
void EraseBlockData(CBlockIndex* index) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
friend ChainstateManager;
};
/** Mark a block as precious and reorganize.
@ -768,6 +779,150 @@ bool InvalidateBlock(BlockValidationState& state, const CChainParams& chainparam
/** Remove invalidity status from a block and its descendants. */
void ResetBlockFailureFlags(CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/**
* Provides an interface for creating and interacting with one or two
* chainstates: an IBD chainstate generated by downloading blocks, and
* an optional snapshot chainstate loaded from a UTXO snapshot. Managed
* chainstates can be maintained at different heights simultaneously.
*
* This class provides abstractions that allow the retrieval of the current
* most-work chainstate ("Active") as well as chainstates which may be in
* background use to validate UTXO snapshots.
*
* Definitions:
*
* *IBD chainstate*: a chainstate whose current state has been "fully"
* validated by the initial block download process.
*
* *Snapshot chainstate*: a chainstate populated by loading in an
* assumeutxo UTXO snapshot.
*
* *Active chainstate*: the chainstate containing the current most-work
* chain. Consulted by most parts of the system (net_processing,
* wallet) as a reflection of the current chain and UTXO set.
* This may either be an IBD chainstate or a snapshot chainstate.
*
* *Background IBD chainstate*: an IBD chainstate for which the
* IBD process is happening in the background while use of the
* active (snapshot) chainstate allows the rest of the system to function.
*
* *Validated chainstate*: the most-work chainstate which has been validated
* locally via initial block download. This will be the snapshot chainstate
* if a snapshot was loaded and all blocks up to the snapshot starting point
* have been downloaded and validated (via background validation), otherwise
* it will be the IBD chainstate.
*/
class ChainstateManager
{
private:
//! The chainstate used under normal operation (i.e. "regular" IBD) or, if
//! a snapshot is in use, for background validation.
//!
//! Its contents (including on-disk data) will be deleted *upon shutdown*
//! after background validation of the snapshot has completed. We do not
//! free the chainstate contents immediately after it finishes validation
//! to cautiously avoid a case where some other part of the system is still
//! using this pointer (e.g. net_processing).
//!
//! Once this pointer is set to a corresponding chainstate, it will not
//! be reset until init.cpp:Shutdown(). This means it is safe to acquire
//! the contents of this pointer with ::cs_main held, release the lock,
//! and then use the reference without concern of it being deconstructed.
//!
//! This is especially important when, e.g., calling ActivateBestChain()
//! on all chainstates because we are not able to hold ::cs_main going into
//! that call.
std::unique_ptr<CChainState> m_ibd_chainstate;
//! A chainstate initialized on the basis of a UTXO snapshot. If this is
//! non-null, it is always our active chainstate.
//!
//! Once this pointer is set to a corresponding chainstate, it will not
//! be reset until init.cpp:Shutdown(). This means it is safe to acquire
//! the contents of this pointer with ::cs_main held, release the lock,
//! and then use the reference without concern of it being deconstructed.
//!
//! This is especially important when, e.g., calling ActivateBestChain()
//! on all chainstates because we are not able to hold ::cs_main going into
//! that call.
std::unique_ptr<CChainState> m_snapshot_chainstate;
//! Points to either the ibd or snapshot chainstate; indicates our
//! most-work chain.
//!
//! Once this pointer is set to a corresponding chainstate, it will not
//! be reset until init.cpp:Shutdown(). This means it is safe to acquire
//! the contents of this pointer with ::cs_main held, release the lock,
//! and then use the reference without concern of it being deconstructed.
//!
//! This is especially important when, e.g., calling ActivateBestChain()
//! on all chainstates because we are not able to hold ::cs_main going into
//! that call.
CChainState* m_active_chainstate{nullptr};
//! If true, the assumed-valid chainstate has been fully validated
//! by the background validation chainstate.
bool m_snapshot_validated{false};
// For access to m_active_chainstate.
friend CChainState& ChainstateActive();
friend CChain& ChainActive();
public:
//! A single BlockManager instance is shared across each constructed
//! chainstate to avoid duplicating block metadata.
BlockManager m_blockman GUARDED_BY(::cs_main);
//! Instantiate a new chainstate and assign it based upon whether it is
//! from a snapshot.
//!
//! @param[in] snapshot_blockhash If given, signify that this chainstate
//! is based on a snapshot.
CChainState& InitializeChainstate(const uint256& snapshot_blockhash = uint256())
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! Get all chainstates currently being used.
std::vector<CChainState*> GetAll();
//! The most-work chain.
CChain& ActiveChain() const;
int ActiveHeight() const { return ActiveChain().Height(); }
CBlockIndex* ActiveTip() const { return ActiveChain().Tip(); }
BlockMap& BlockIndex() EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
{
return m_blockman.m_block_index;
}
bool IsSnapshotActive() const;
Optional<uint256> SnapshotBlockhash() const;
//! Is there a snapshot in use and has it been fully validated?
bool IsSnapshotValidated() const { return m_snapshot_validated; }
//! @returns true if this chainstate is being used to validate an active
//! snapshot in the background.
bool IsBackgroundIBD(CChainState* chainstate) const;
//! Return the most-work chainstate that has been fully validated.
//!
//! During background validation of a snapshot, this is the IBD chain. After
//! background validation has completed, this is the snapshot chain.
CChainState& ValidatedChainstate() const;
CChain& ValidatedChain() const { return ValidatedChainstate().m_chain; }
CBlockIndex* ValidatedTip() const { return ValidatedChain().Tip(); }
//! Unload block index and chain data before shutdown.
void Unload() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! Clear (deconstruct) chainstate data.
void Reset();
};
extern ChainstateManager g_chainman GUARDED_BY(::cs_main);
/** @returns the most-work valid chainstate. */
CChainState& ChainstateActive();
@ -777,11 +932,6 @@ CChain& ChainActive();
/** @returns the global block index map. */
BlockMap& BlockIndex();
// Most often ::ChainstateActive() should be used instead of this, but some code
// may not be able to assume that this has been initialized yet and so must use it
// directly, e.g. init.cpp.
extern std::unique_ptr<CChainState> g_chainstate;
/** Global variable that points to the active block tree (protected by cs_main) */
extern std::unique_ptr<CBlockTreeDB> pblocktree;

View file

@ -454,7 +454,7 @@ public:
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
wallet = MakeUnique<CWallet>(m_chain.get(), WalletLocation(), WalletDatabase::CreateMock());
{
LOCK(wallet->cs_wallet);
LOCK2(::cs_main, wallet->cs_wallet);
wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
}
bool firstRun;