Skip to content

Commit

Permalink
Move block-storage-related logic to ChainstateManager
Browse files Browse the repository at this point in the history
Separate the notion of which blocks are stored on disk, and what data is in our
block index, from what tip a chainstate might be able to get to. We can use
chainstate-agnostic data to determine when to store a block on disk (primarily,
an anti-DoS set of criteria) and let the chainstates figure out for themselves
when a block is of interest for being a candidate tip.

Note: some of the invariants in CheckBlockIndex are modified, but more work is
needed (ie to move CheckBlockIndex to ChainstateManager, as most of what
CheckBlockIndex is doing is checking the consistency of the block index, which
is outside of Chainstate).
  • Loading branch information
sdaftuar committed Jul 17, 2023
1 parent 3cfc753 commit c8f4a87
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 73 deletions.
3 changes: 1 addition & 2 deletions src/bench/load_external.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,13 @@ static void LoadExternalBlockFile(benchmark::Bench& bench)
fclose(file);
}

Chainstate& chainstate{testing_setup->m_node.chainman->ActiveChainstate()};
std::multimap<uint256, FlatFilePos> blocks_with_unknown_parent;
FlatFilePos pos;
bench.run([&] {
// "rb" is "binary, O_RDONLY", positioned to the start of the file.
// The file will be closed by LoadExternalBlockFile().
FILE* file{fsbridge::fopen(blkfile, "rb")};
chainstate.LoadExternalBlockFile(file, &pos, &blocks_with_unknown_parent);
testing_setup->m_node.chainman->LoadExternalBlockFile(file, &pos, &blocks_with_unknown_parent);
});
fs::remove(blkfile);
}
Expand Down
4 changes: 2 additions & 2 deletions src/node/blockstorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -907,7 +907,7 @@ void ImportBlocks(ChainstateManager& chainman, std::vector<fs::path> vImportFile
break; // This error is logged in OpenBlockFile
}
LogPrintf("Reindexing block file blk%05u.dat...\n", (unsigned int)nFile);
chainman.ActiveChainstate().LoadExternalBlockFile(file, &pos, &blocks_with_unknown_parent);
chainman.LoadExternalBlockFile(file, &pos, &blocks_with_unknown_parent);
if (chainman.m_interrupt) {
LogPrintf("Interrupt requested. Exit %s\n", __func__);
return;
Expand All @@ -926,7 +926,7 @@ void ImportBlocks(ChainstateManager& chainman, std::vector<fs::path> vImportFile
FILE* file = fsbridge::fopen(path, "rb");
if (file) {
LogPrintf("Importing blocks file %s...\n", fs::PathToString(path));
chainman.ActiveChainstate().LoadExternalBlockFile(file);
chainman.LoadExternalBlockFile(file);
if (chainman.m_interrupt) {
LogPrintf("Interrupt requested. Exit %s\n", __func__);
return;
Expand Down
2 changes: 1 addition & 1 deletion src/test/coinstatsindex_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_unclean_shutdown, TestChain100Setup)
LOCK(cs_main);
BlockValidationState state;
BOOST_CHECK(CheckBlock(block, state, params.GetConsensus()));
BOOST_CHECK(chainstate.AcceptBlock(new_block, state, &new_block_index, true, nullptr, nullptr, true));
BOOST_CHECK(m_node.chainman->AcceptBlock(new_block, state, &new_block_index, true, nullptr, nullptr, true));
CCoinsViewCache view(&chainstate.CoinsTip());
BOOST_CHECK(chainstate.ConnectBlock(block, state, new_block_index, view));
}
Expand Down
4 changes: 2 additions & 2 deletions src/test/fuzz/load_external_block_file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ FUZZ_TARGET_INIT(load_external_block_file, initialize_load_external_block_file)
// Corresponds to the -reindex case (track orphan blocks across files).
FlatFilePos flat_file_pos;
std::multimap<uint256, FlatFilePos> blocks_with_unknown_parent;
g_setup->m_node.chainman->ActiveChainstate().LoadExternalBlockFile(fuzzed_block_file, &flat_file_pos, &blocks_with_unknown_parent);
g_setup->m_node.chainman->LoadExternalBlockFile(fuzzed_block_file, &flat_file_pos, &blocks_with_unknown_parent);
} else {
// Corresponds to the -loadblock= case (orphan blocks aren't tracked across files).
g_setup->m_node.chainman->ActiveChainstate().LoadExternalBlockFile(fuzzed_block_file);
g_setup->m_node.chainman->LoadExternalBlockFile(fuzzed_block_file);
}
}
3 changes: 2 additions & 1 deletion src/test/validation_chainstate_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,11 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup)
LOCK(::cs_main);
bool checked = CheckBlock(*pblock, state, chainparams.GetConsensus());
BOOST_CHECK(checked);
bool accepted = background_cs.AcceptBlock(
bool accepted = chainman.AcceptBlock(
pblock, state, &pindex, true, nullptr, &newblock, true);
BOOST_CHECK(accepted);
}

// UpdateTip is called here
bool block_added = background_cs.ActivateBestChain(state, pblock);

Expand Down
83 changes: 52 additions & 31 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3426,7 +3426,7 @@ void Chainstate::TryAddBlockIndexCandidate(CBlockIndex* pindex)
}

/** Mark a block as having its data received and checked (up to BLOCK_VALID_TRANSACTIONS). */
void Chainstate::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const FlatFilePos& pos)
void ChainstateManager::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const FlatFilePos& pos)
{
AssertLockHeld(cs_main);
pindexNew->nTx = block.vtx.size();
Expand All @@ -3435,7 +3435,7 @@ void Chainstate::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pin
pindexNew->nDataPos = pos.nPos;
pindexNew->nUndoPos = 0;
pindexNew->nStatus |= BLOCK_HAVE_DATA;
if (DeploymentActiveAt(*pindexNew, m_chainman, Consensus::DEPLOYMENT_SEGWIT)) {
if (DeploymentActiveAt(*pindexNew, *this, Consensus::DEPLOYMENT_SEGWIT)) {
pindexNew->nStatus |= BLOCK_OPT_WITNESS;
}
pindexNew->RaiseValidity(BLOCK_VALID_TRANSACTIONS);
Expand All @@ -3451,8 +3451,10 @@ void Chainstate::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pin
CBlockIndex *pindex = queue.front();
queue.pop_front();
pindex->nChainTx = (pindex->pprev ? pindex->pprev->nChainTx : 0) + pindex->nTx;
pindex->nSequenceId = m_chainman.nBlockSequenceId++;
TryAddBlockIndexCandidate(pindex);
pindex->nSequenceId = nBlockSequenceId++;
for (Chainstate *c : GetAll()) {
c->TryAddBlockIndexCandidate(pindex);
}
std::pair<std::multimap<CBlockIndex*, CBlockIndex*>::iterator, std::multimap<CBlockIndex*, CBlockIndex*>::iterator> range = m_blockman.m_blocks_unlinked.equal_range(pindex);
while (range.first != range.second) {
std::multimap<CBlockIndex*, CBlockIndex*>::iterator it = range.first;
Expand Down Expand Up @@ -3912,7 +3914,7 @@ void ChainstateManager::ReportHeadersPresync(const arith_uint256& work, int64_t
}

/** Store block on disk. If dbp is non-nullptr, the file is known to already reside on disk */
bool Chainstate::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock, bool min_pow_checked)
bool ChainstateManager::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock, bool min_pow_checked)
{
const CBlock& block = *pblock;

Expand All @@ -3922,8 +3924,8 @@ bool Chainstate::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockV
CBlockIndex *pindexDummy = nullptr;
CBlockIndex *&pindex = ppindex ? *ppindex : pindexDummy;

bool accepted_header{m_chainman.AcceptBlockHeader(block, state, &pindex, min_pow_checked)};
CheckBlockIndex();
bool accepted_header{AcceptBlockHeader(block, state, &pindex, min_pow_checked)};
ActiveChainstate().CheckBlockIndex();

if (!accepted_header)
return false;
Expand All @@ -3932,13 +3934,13 @@ bool Chainstate::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockV
// process an unrequested block if it's new and has enough work to
// advance our tip, and isn't too many blocks ahead.
bool fAlreadyHave = pindex->nStatus & BLOCK_HAVE_DATA;
bool fHasMoreOrSameWork = (m_chain.Tip() ? pindex->nChainWork >= m_chain.Tip()->nChainWork : true);
bool fHasMoreOrSameWork = (ActiveTip() ? pindex->nChainWork >= ActiveTip()->nChainWork : true);
// Blocks that are too out-of-order needlessly limit the effectiveness of
// pruning, because pruning will not delete block files that contain any
// blocks which are too close in height to the tip. Apply this test
// regardless of whether pruning is enabled; it should generally be safe to
// not process unrequested blocks.
bool fTooFarAhead{pindex->nHeight > m_chain.Height() + int(MIN_BLOCKS_TO_KEEP)};
bool fTooFarAhead{pindex->nHeight > ActiveHeight() + int(MIN_BLOCKS_TO_KEEP)};

// TODO: Decouple this function from the block download logic by removing fRequested
// This requires some new chain data structure to efficiently look up if a
Expand All @@ -3958,13 +3960,13 @@ bool Chainstate::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockV
// If our tip is behind, a peer could try to send us
// low-work blocks on a fake chain that we would never
// request; don't process these.
if (pindex->nChainWork < m_chainman.MinimumChainWork()) return true;
if (pindex->nChainWork < MinimumChainWork()) return true;
}

const CChainParams& params{m_chainman.GetParams()};
const CChainParams& params{GetParams()};

if (!CheckBlock(block, state, params.GetConsensus()) ||
!ContextualCheckBlock(block, state, m_chainman, pindex->pprev)) {
!ContextualCheckBlock(block, state, *this, pindex->pprev)) {
if (state.IsInvalid() && state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {
pindex->nStatus |= BLOCK_FAILED_VALID;
m_blockman.m_dirty_blockindex.insert(pindex);
Expand All @@ -3974,7 +3976,7 @@ bool Chainstate::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockV

// Header is valid/has work, merkle tree and segwit merkle tree are good...RELAY NOW
// (but if it does not build on our best tip, let the SendMessages loop relay it)
if (!IsInitialBlockDownload() && m_chain.Tip() == pindex->pprev)
if (!ActiveChainstate().IsInitialBlockDownload() && ActiveTip() == pindex->pprev)
GetMainSignals().NewPoWValidBlock(pindex, pblock);

// Write block to history file
Expand All @@ -3987,12 +3989,19 @@ bool Chainstate::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockV
}
ReceivedBlockTransactions(block, pindex, blockPos);
} catch (const std::runtime_error& e) {
return FatalError(m_chainman.GetNotifications(), state, std::string("System error: ") + e.what());
return FatalError(GetNotifications(), state, std::string("System error: ") + e.what());
}

FlushStateToDisk(state, FlushStateMode::NONE);
// TODO: FlushStateToDisk() handles flushing of both block and chainstate
// data, so we should move this to ChainstateManager so that we can be more
// intelligent about how we flush.
// For now, since FlushStateMode::NONE is used, all that can happen is that
// the block files may be pruned, so we can just call this on one
// chainstate (particularly if we haven't implemented pruning with
// background validation yet).
ActiveChainstate().FlushStateToDisk(state, FlushStateMode::NONE);

CheckBlockIndex();
ActiveChainstate().CheckBlockIndex();

return true;
}
Expand All @@ -4018,7 +4027,7 @@ bool ChainstateManager::ProcessNewBlock(const std::shared_ptr<const CBlock>& blo
bool ret = CheckBlock(*block, state, GetConsensus());
if (ret) {
// Store to disk
ret = ActiveChainstate().AcceptBlock(block, state, &pindex, force_processing, nullptr, new_block, min_pow_checked);
ret = AcceptBlock(block, state, &pindex, force_processing, nullptr, new_block, min_pow_checked);
}
if (!ret) {
GetMainSignals().BlockChecked(*block, state);
Expand Down Expand Up @@ -4507,26 +4516,24 @@ bool Chainstate::LoadGenesisBlock()
return error("%s: writing genesis block to disk failed", __func__);
}
CBlockIndex* pindex = m_blockman.AddToBlockIndex(block, m_chainman.m_best_header);
ReceivedBlockTransactions(block, pindex, blockPos);
m_chainman.ReceivedBlockTransactions(block, pindex, blockPos);
} catch (const std::runtime_error& e) {
return error("%s: failed to write genesis block: %s", __func__, e.what());
}

return true;
}

void Chainstate::LoadExternalBlockFile(
void ChainstateManager::LoadExternalBlockFile(
FILE* fileIn,
FlatFilePos* dbp,
std::multimap<uint256, FlatFilePos>* blocks_with_unknown_parent)
{
AssertLockNotHeld(m_chainstate_mutex);

// Either both should be specified (-reindex), or neither (-loadblock).
assert(!dbp == !blocks_with_unknown_parent);

const auto start{SteadyClock::now()};
const CChainParams& params{m_chainman.GetParams()};
const CChainParams& params{GetParams()};

int nLoaded = 0;
try {
Expand All @@ -4536,7 +4543,7 @@ void Chainstate::LoadExternalBlockFile(
// such as a block fails to deserialize.
uint64_t nRewind = blkdat.GetPos();
while (!blkdat.eof()) {
if (m_chainman.m_interrupt) return;
if (m_interrupt) return;

blkdat.SetPos(nRewind);
nRewind++; // start one byte further next time, in case of failure
Expand Down Expand Up @@ -4611,8 +4618,15 @@ void Chainstate::LoadExternalBlockFile(

// Activate the genesis block so normal node progress can continue
if (hash == params.GetConsensus().hashGenesisBlock) {
BlockValidationState state;
if (!ActivateBestChain(state, nullptr)) {
bool genesis_activation_failure = false;
for (auto c : GetAll()) {
BlockValidationState state;
if (!c->ActivateBestChain(state, nullptr)) {
genesis_activation_failure = true;
break;
}
}
if (genesis_activation_failure) {
break;
}
}
Expand All @@ -4625,14 +4639,21 @@ void Chainstate::LoadExternalBlockFile(
// until after all of the block files are loaded. ActivateBestChain can be
// called by concurrent network message processing. but, that is not
// reliable for the purpose of pruning while importing.
BlockValidationState state;
if (!ActivateBestChain(state, pblock)) {
LogPrint(BCLog::REINDEX, "failed to activate chain (%s)\n", state.ToString());
bool activation_failure = false;
for (auto c : GetAll()) {
BlockValidationState state;
if (!c->ActivateBestChain(state, pblock)) {
LogPrint(BCLog::REINDEX, "failed to activate chain (%s)\n", state.ToString());
activation_failure = true;
break;
}
}
if (activation_failure) {
break;
}
}

NotifyHeaderTip(*this);
NotifyHeaderTip(ActiveChainstate());

if (!blocks_with_unknown_parent) continue;

Expand All @@ -4658,7 +4679,7 @@ void Chainstate::LoadExternalBlockFile(
}
range.first++;
blocks_with_unknown_parent->erase(it);
NotifyHeaderTip(*this);
NotifyHeaderTip(ActiveChainstate());
}
}
} catch (const std::exception& e) {
Expand All @@ -4677,7 +4698,7 @@ void Chainstate::LoadExternalBlockFile(
}
}
} catch (const std::runtime_error& e) {
m_chainman.GetNotifications().fatalError(std::string("System error: ") + e.what());
GetNotifications().fatalError(std::string("System error: ") + e.what());
}
LogPrintf("Loaded %i blocks from external file in %dms\n", nLoaded, Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
}
Expand Down
Loading

0 comments on commit c8f4a87

Please sign in to comment.