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
9 changed files with 541 additions and 146 deletions

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;
}