Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework validation logic for assumeutxo #27746

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/bench/load_external.cpp
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
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
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
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
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
90 changes: 56 additions & 34 deletions src/validation.cpp
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()) {
ryanofsky marked this conversation as resolved.
Show resolved Hide resolved
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,23 +3924,24 @@ 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();
ryanofsky marked this conversation as resolved.
Show resolved Hide resolved

if (!accepted_header)
return false;

// Try to process all requested blocks that we don't have, but only
// process an unrequested block if it's new and has enough work to
// advance our tip, and isn't too many blocks ahead.
// Check all requested blocks that we do not already have for validity and
// save them to disk. Skip processing of unrequested blocks as an anti-DoS
// measure, unless the blocks have more work than the active chain tip, and
// aren't too far ahead of it, so are likely to be attached soon.
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "Move block-storage-related logic to ChainstateManager" (034f920)

It seems like fHasMoreOrSameWork will always be false for blocks that were downloaded to be added to the background chain, because they will have less work than the active snapshot chain.

Is that intentional? It would helpful to have a comment here either that either explains why this ok, or adds a TODO indicating that there will be some change later.

I do understand there is not a change in behavior here in this commit because in both places where
Chainstate::AcceptBlock was called previously (in ProcessNewBlock and LoadExternalBlockFile), it was only called on the active chainstate, so it is still only looking at the active chainstate now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea here is that this check is part of a heuristic that we use when we receive a block that was not requested. For anti-DoS reasons, back in #5875 we introduced logic to generally not process unrequested blocks, but we left in an optimization that was intended to not throw away blocks that would immediately advance our tip, in case we had some peer (like Matt's old fast-relay-network, if I remember right) that was sending such blocks (see eg comment here #5875 (comment)).

My view is that we don't need to process unrequested blocks in general, and that if we do so it's just an optimization to make block relay at the tip work better. We could probably add special-case logic to allow processing of unrequested blocks that we haven't yet validated that are ancestors of the ancestor block, but this doesn't strike me as something that is at all necessary (and perhaps not desirable?).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re: c8f4a87#r1251271941

Thanks, that makes sense, and thanks for linking to #5875.

To make this less confusing, I think "Try to process all requested blocks" code comment above could be updated to mention the active chain and better match the code.

Would suggest: "// Check all requested blocks that we do not already have for validity and save them to disk. Skip processing of unrequested blocks as an anti-Dos measure, unless the blocks have more work than the active chain tip, and aren't too far ahead of it, so are likely to be attached soon."

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

d0d40ea Maybe add: Background validation always ignores unrequested blocks.

// 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 +3961,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 +3977,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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

d0d40ea: // We don't relay background validation blocks.

if (!IsInitialBlockDownload() && m_chain.Tip() == pindex->pprev)
if (!ActiveChainstate().IsInitialBlockDownload() && ActiveTip() == pindex->pprev)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "Move block-storage-related logic to ChainstateManager" (034f920)

Do you think it would be good if the IsInitialBlockDownload method were moved to ChainstateManager. Is there any place it should be actually called on the background chainstate rather than the active chainstate? Could add a TODO comment if IsInitialBlockDownload method should move like other methods are moving.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't done a careful review of all the places we use IsInitialBlockDownload() to confirm this, but my intuition would be that if we're using an assumeutxo snapshot, what we care about is whether our snapshot-based chainstate is caught up or not -- basically by definition, the background chainstate will always be "in initial block download" until it finishes and is no longer in use, so it wouldn't make sense to condition anything on whether it's behind or not.

So yeah I think moving this to ChainstateManager would probably make sense...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re: #27746 (comment)

Thanks, it makes sense that the background chainstate only has one state because it is deleted as soon as it finishes syncing.

Would be great if a followup PR moved IsInitialBlockDownload from Chainstate class to ChainstateManager so the IsInitialBlockDownload implementation could refer directly to the active chainstate, and IsInitialBlockDownload callers wouldn't need to figure out which chainstate to call it on.

GetMainSignals().NewPoWValidBlock(pindex, pblock);

// Write block to history file
Expand All @@ -3987,12 +3990,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();
ryanofsky marked this conversation as resolved.
Show resolved Hide resolved

return true;
}
Expand All @@ -4018,7 +4028,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 +4517,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);
ryanofsky marked this conversation as resolved.
Show resolved Hide resolved

// 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 +4544,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 +4619,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()) {
ryanofsky marked this conversation as resolved.
Show resolved Hide resolved
BlockValidationState state;
if (!c->ActivateBestChain(state, nullptr)) {
sdaftuar marked this conversation as resolved.
Show resolved Hide resolved
genesis_activation_failure = true;
break;
}
}
if (genesis_activation_failure) {
break;
}
}
Expand All @@ -4625,14 +4640,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());
ryanofsky marked this conversation as resolved.
Show resolved Hide resolved

if (!blocks_with_unknown_parent) continue;

Expand All @@ -4658,7 +4680,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 +4699,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