Compare commits

...

7 Commits

Author SHA1 Message Date
stratospher 33dc87d15a
Merge cfadc8038c into b510893d00 2025-10-08 15:03:44 +00:00
stratospher cfadc8038c test: check LoadBlockIndex correctly recomputes invalidity flags
Add a test for block index transitioning from legacy
BLOCK_FAILED_CHILD to BLOCK_FAILED_VALID behavior.

In the scenario where a valid block has a BLOCK_FAILED_CHILD
parent and a BLOCK_FAILED_VALID grandparent, ensure that all
three blocks are correctly marked as BLOCK_FAILED_VALID
after reloading the block index.
2025-10-08 20:27:20 +05:30
stratospher 87833a18ed validation: reset BLOCK_FAILED_CHILD to BLOCK_FAILED_VALID when loading from disk
- there maybe existing block indexes stored in disk with
  BLOCK_FAILED_CHILD
- since they don't exist anymore, clean up block index entries with
  BLOCK_FAILED_CHILD and reset it to BLOCK_FAILED_VALID.
2025-10-08 20:27:20 +05:30
stratospher 451f834676 validation: remove BLOCK_FAILED_MASK
since it's the same as BLOCK_FAILED_VALID now
2025-10-08 20:27:20 +05:30
stratospher 350b2ad29c validation: remove BLOCK_FAILED_CHILD
even though we have a distinction between BLOCK_FAILED_VALID
and BLOCK_FAILED_CHILD in the codebase, we don't use it for
anything. since there's no functional difference between them
and it's unnecessary code complexity to categorise them correctly,
just mark as BLOCK_FAILED_VALID instead.
2025-10-08 20:27:20 +05:30
stratospher f284834170 refactor/validation: remove to_mark_failed
to_mark_failed was useful to track last disconnected block so that
it's children can be marked as BLOCK_FAILED_CHILD. since the
previous commit removes BLOCK_FAILED_CHILD usage in InvalidateBlock,
the existing variable invalid_walk_tip is sufficient.

Improve upon the variable name for `invalid_walk_tip` to make the
InvalidateBlock logic easier to read. Block tip before disconnection
is now tracked directly via `disconnected_tip`, and `new_tip`
is the tip after the disconnect.

Co-authored-by: stickies-v <stickies-v@protonmail.com>
2025-10-08 20:26:37 +05:30
stratospher d121579abc validation: don't update BLOCK_FAILED_VALID to BLOCK_FAILED_CHILD in InvalidateBlock
- there is no functional difference between BLOCK_FAILED_VALID and BLOCK_FAILED_CHILD
and it's unnecessary code complexity to correctly categorise them.
2025-07-10 16:55:52 +05:30
7 changed files with 72 additions and 68 deletions

View File

@ -123,8 +123,7 @@ enum BlockStatus : uint32_t {
BLOCK_HAVE_MASK = BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO, BLOCK_HAVE_MASK = BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO,
BLOCK_FAILED_VALID = 32, //!< stage after last reached validness failed BLOCK_FAILED_VALID = 32, //!< stage after last reached validness failed
BLOCK_FAILED_CHILD = 64, //!< descends from failed block BLOCK_FAILED_CHILD = 64, //!< Unused flag that was previously set when descending from failed block
BLOCK_FAILED_MASK = BLOCK_FAILED_VALID | BLOCK_FAILED_CHILD,
BLOCK_OPT_WITNESS = 128, //!< block data in blk*.dat was received with a witness-enforcing client BLOCK_OPT_WITNESS = 128, //!< block data in blk*.dat was received with a witness-enforcing client
@ -297,7 +296,7 @@ public:
{ {
AssertLockHeld(::cs_main); AssertLockHeld(::cs_main);
assert(!(nUpTo & ~BLOCK_VALID_MASK)); // Only validity flags allowed. assert(!(nUpTo & ~BLOCK_VALID_MASK)); // Only validity flags allowed.
if (nStatus & BLOCK_FAILED_MASK) if (nStatus & BLOCK_FAILED_VALID)
return false; return false;
return ((nStatus & BLOCK_VALID_MASK) >= nUpTo); return ((nStatus & BLOCK_VALID_MASK) >= nUpTo);
} }
@ -308,7 +307,7 @@ public:
{ {
AssertLockHeld(::cs_main); AssertLockHeld(::cs_main);
assert(!(nUpTo & ~BLOCK_VALID_MASK)); // Only validity flags allowed. assert(!(nUpTo & ~BLOCK_VALID_MASK)); // Only validity flags allowed.
if (nStatus & BLOCK_FAILED_MASK) return false; if (nStatus & BLOCK_FAILED_VALID) return false;
if ((nStatus & BLOCK_VALID_MASK) < nUpTo) { if ((nStatus & BLOCK_VALID_MASK) < nUpTo) {
nStatus = (nStatus & ~BLOCK_VALID_MASK) | nUpTo; nStatus = (nStatus & ~BLOCK_VALID_MASK) | nUpTo;

View File

@ -464,8 +464,9 @@ bool BlockManager::LoadBlockIndex(const std::optional<uint256>& snapshot_blockha
pindex->m_chain_tx_count = pindex->nTx; pindex->m_chain_tx_count = pindex->nTx;
} }
} }
if (!(pindex->nStatus & BLOCK_FAILED_MASK) && pindex->pprev && (pindex->pprev->nStatus & BLOCK_FAILED_MASK)) { if (!(pindex->nStatus & BLOCK_FAILED_VALID) && pindex->pprev && (pindex->pprev->nStatus & BLOCK_FAILED_VALID)) {
pindex->nStatus |= BLOCK_FAILED_CHILD; // BLOCK_FAILED_CHILD is deprecated, but may still exist on disk. Replace it with BLOCK_FAILED_VALID.
pindex->nStatus = (pindex->nStatus & ~BLOCK_FAILED_CHILD) | BLOCK_FAILED_VALID;
m_dirty_blockindex.insert(pindex); m_dirty_blockindex.insert(pindex);
} }
if (pindex->pprev) { if (pindex->pprev) {

View File

@ -1597,7 +1597,7 @@ static RPCHelpMan getchaintips()
if (active_chain.Contains(block)) { if (active_chain.Contains(block)) {
// This block is part of the currently active chain. // This block is part of the currently active chain.
status = "active"; status = "active";
} else if (block->nStatus & BLOCK_FAILED_MASK) { } else if (block->nStatus & BLOCK_FAILED_VALID) {
// This block or one of its ancestors is invalid. // This block or one of its ancestors is invalid.
status = "invalid"; status = "invalid";
} else if (!block->HaveNumChainTxs()) { } else if (!block->HaveNumChainTxs()) {

View File

@ -742,7 +742,7 @@ static RPCHelpMan getblocktemplate()
if (pindex) { if (pindex) {
if (pindex->IsValid(BLOCK_VALID_SCRIPTS)) if (pindex->IsValid(BLOCK_VALID_SCRIPTS))
return "duplicate"; return "duplicate";
if (pindex->nStatus & BLOCK_FAILED_MASK) if (pindex->nStatus & BLOCK_FAILED_VALID)
return "duplicate-invalid"; return "duplicate-invalid";
return "duplicate-inconclusive"; return "duplicate-inconclusive";
} }

View File

@ -124,34 +124,52 @@ BOOST_FIXTURE_TEST_CASE(invalidate_block, TestChain100Setup)
// Check BlockStatus when doing InvalidateBlock() // Check BlockStatus when doing InvalidateBlock()
BlockValidationState state; BlockValidationState state;
auto* orig_tip = active.Tip(); auto* orig_tip = active.Tip();
CBlockIndex* block_100 = active[100];
CBlockIndex* block_99 = active[99];
CBlockIndex* block_98 = active[98];
int height_to_invalidate = orig_tip->nHeight - 10; int height_to_invalidate = orig_tip->nHeight - 10;
auto* tip_to_invalidate = active[height_to_invalidate]; auto* tip_to_invalidate = active[height_to_invalidate];
m_node.chainman->ActiveChainstate().InvalidateBlock(state, tip_to_invalidate); m_node.chainman->ActiveChainstate().InvalidateBlock(state, tip_to_invalidate);
// tip_to_invalidate just got invalidated, so it's BLOCK_FAILED_VALID // tip_to_invalidate just got invalidated, so it's BLOCK_FAILED_VALID
WITH_LOCK(::cs_main, assert(tip_to_invalidate->nStatus & BLOCK_FAILED_VALID)); WITH_LOCK(::cs_main, assert(tip_to_invalidate->nStatus & BLOCK_FAILED_VALID));
WITH_LOCK(::cs_main, assert((tip_to_invalidate->nStatus & BLOCK_FAILED_CHILD) == 0));
// check all ancestors of the invalidated block are validated up to BLOCK_VALID_TRANSACTIONS and are not invalid // check all ancestors of the invalidated block are validated up to BLOCK_VALID_TRANSACTIONS and are not invalid
auto pindex = tip_to_invalidate->pprev; auto pindex = tip_to_invalidate->pprev;
while (pindex) { while (pindex) {
WITH_LOCK(::cs_main, assert(pindex->IsValid(BLOCK_VALID_TRANSACTIONS))); WITH_LOCK(::cs_main, assert(pindex->IsValid(BLOCK_VALID_TRANSACTIONS)));
WITH_LOCK(::cs_main, assert((pindex->nStatus & BLOCK_FAILED_MASK) == 0)); WITH_LOCK(::cs_main, assert((pindex->nStatus & BLOCK_FAILED_VALID) == 0));
pindex = pindex->pprev; pindex = pindex->pprev;
} }
// check all descendants of the invalidated block are BLOCK_FAILED_CHILD // check all descendants of the invalidated block are BLOCK_FAILED_VALID
pindex = orig_tip; pindex = orig_tip;
while (pindex && pindex != tip_to_invalidate) { while (pindex && pindex != tip_to_invalidate) {
WITH_LOCK(::cs_main, assert((pindex->nStatus & BLOCK_FAILED_VALID) == 0)); WITH_LOCK(::cs_main, assert(pindex->nStatus & BLOCK_FAILED_VALID));
WITH_LOCK(::cs_main, assert(pindex->nStatus & BLOCK_FAILED_CHILD));
pindex = pindex->pprev; pindex = pindex->pprev;
} }
// don't mark already invalidated block (orig_tip is BLOCK_FAILED_CHILD) with BLOCK_FAILED_VALID again {
m_node.chainman->ActiveChainstate().InvalidateBlock(state, orig_tip); // consider the chain of blocks block_98 <- block_99 <- block_100
WITH_LOCK(::cs_main, assert(orig_tip->nStatus & BLOCK_FAILED_CHILD)); // intentionally mark:
WITH_LOCK(::cs_main, assert((orig_tip->nStatus & BLOCK_FAILED_VALID) == 0)); // - block_98: BLOCK_FAILED_VALID (already marked as BLOCK_FAILED_VALID from previous test)
// - block_99: BLOCK_FAILED_CHILD (clear BLOCK_FAILED_VALID from previous test and mark as BLOCK_FAILED_CHILD)
// - block_100: not invalid (clear BLOCK_FAILED_VALID from previous test)
LOCK(::cs_main);
assert(block_98->nStatus & BLOCK_FAILED_VALID);
block_99->nStatus = (block_99->nStatus & ~BLOCK_FAILED_VALID) | BLOCK_FAILED_CHILD;
block_100->nStatus = (block_100->nStatus & ~BLOCK_FAILED_VALID);
// Reload block index to recompute block status validity flags from disk.
m_node.chainman->LoadBlockIndex();
// check block_98, block_99, block_100 is marked as BLOCK_FAILED_VALID after reloading from disk
assert(block_98->nStatus & BLOCK_FAILED_VALID);
assert(block_99->nStatus & BLOCK_FAILED_VALID);
assert(block_100->nStatus & BLOCK_FAILED_VALID);
}
} }
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

View File

@ -50,8 +50,6 @@ FUZZ_TARGET(chain)
BlockStatus::BLOCK_HAVE_UNDO, BlockStatus::BLOCK_HAVE_UNDO,
BlockStatus::BLOCK_HAVE_MASK, BlockStatus::BLOCK_HAVE_MASK,
BlockStatus::BLOCK_FAILED_VALID, BlockStatus::BLOCK_FAILED_VALID,
BlockStatus::BLOCK_FAILED_CHILD,
BlockStatus::BLOCK_FAILED_MASK,
BlockStatus::BLOCK_OPT_WITNESS, BlockStatus::BLOCK_OPT_WITNESS,
}); });
if (block_status & ~BLOCK_VALID_MASK) { if (block_status & ~BLOCK_VALID_MASK) {

View File

@ -3236,7 +3236,7 @@ CBlockIndex* Chainstate::FindMostWorkChain()
// which block files have been deleted. Remove those as candidates // which block files have been deleted. Remove those as candidates
// for the most work chain if we come across them; we can't switch // for the most work chain if we come across them; we can't switch
// to a chain unless we have all the non-active-chain parent blocks. // to a chain unless we have all the non-active-chain parent blocks.
bool fFailedChain = pindexTest->nStatus & BLOCK_FAILED_MASK; bool fFailedChain = pindexTest->nStatus & BLOCK_FAILED_VALID;
bool fMissingData = !(pindexTest->nStatus & BLOCK_HAVE_DATA); bool fMissingData = !(pindexTest->nStatus & BLOCK_HAVE_DATA);
if (fFailedChain || fMissingData) { if (fFailedChain || fMissingData) {
// Candidate chain is not usable (either invalid or missing data) // Candidate chain is not usable (either invalid or missing data)
@ -3247,7 +3247,7 @@ CBlockIndex* Chainstate::FindMostWorkChain()
// Remove the entire chain from the set. // Remove the entire chain from the set.
while (pindexTest != pindexFailed) { while (pindexTest != pindexFailed) {
if (fFailedChain) { if (fFailedChain) {
pindexFailed->nStatus |= BLOCK_FAILED_CHILD; pindexFailed->nStatus |= BLOCK_FAILED_VALID;
m_blockman.m_dirty_blockindex.insert(pindexFailed); m_blockman.m_dirty_blockindex.insert(pindexFailed);
} else if (fMissingData) { } else if (fMissingData) {
// If we're missing data, then add back to m_blocks_unlinked, // If we're missing data, then add back to m_blocks_unlinked,
@ -3619,10 +3619,6 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde
assert(pindex); assert(pindex);
if (pindex->nHeight == 0) return false; if (pindex->nHeight == 0) return false;
CBlockIndex* to_mark_failed = pindex;
bool pindex_was_in_chain = false;
int disconnected = 0;
// We do not allow ActivateBestChain() to run while InvalidateBlock() is // We do not allow ActivateBestChain() to run while InvalidateBlock() is
// running, as that could cause the tip to change while we disconnect // running, as that could cause the tip to change while we disconnect
// blocks. // blocks.
@ -3648,12 +3644,15 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde
// at least as good with CBlockIndexWorkComparator as the new tip. // at least as good with CBlockIndexWorkComparator as the new tip.
if (!m_chain.Contains(candidate) && if (!m_chain.Contains(candidate) &&
!CBlockIndexWorkComparator()(candidate, pindex->pprev) && !CBlockIndexWorkComparator()(candidate, pindex->pprev) &&
!(candidate->nStatus & BLOCK_FAILED_MASK)) { !(candidate->nStatus & BLOCK_FAILED_VALID)) {
highpow_outofchain_headers.insert({candidate->nChainWork, candidate}); highpow_outofchain_headers.insert({candidate->nChainWork, candidate});
} }
} }
} }
bool pindex_was_in_chain = false;
int disconnected = 0;
// Disconnect (descendants of) pindex, and mark them invalid. // Disconnect (descendants of) pindex, and mark them invalid.
while (true) { while (true) {
if (m_chainman.m_interrupt) break; if (m_chainman.m_interrupt) break;
@ -3667,8 +3666,8 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde
LOCK(MempoolMutex()); LOCK(MempoolMutex());
if (!m_chain.Contains(pindex)) break; if (!m_chain.Contains(pindex)) break;
pindex_was_in_chain = true; pindex_was_in_chain = true;
CBlockIndex *invalid_walk_tip = m_chain.Tip();
CBlockIndex* disconnected_tip{m_chain.Tip()};
// ActivateBestChain considers blocks already in m_chain // ActivateBestChain considers blocks already in m_chain
// unconditionally valid already, so force disconnect away from it. // unconditionally valid already, so force disconnect away from it.
DisconnectedBlockTransactions disconnectpool{MAX_DISCONNECTED_TX_POOL_BYTES}; DisconnectedBlockTransactions disconnectpool{MAX_DISCONNECTED_TX_POOL_BYTES};
@ -3680,49 +3679,42 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde
// keeping the mempool up to date is probably futile anyway). // keeping the mempool up to date is probably futile anyway).
MaybeUpdateMempoolForReorg(disconnectpool, /* fAddToMempool = */ (++disconnected <= 10) && ret); MaybeUpdateMempoolForReorg(disconnectpool, /* fAddToMempool = */ (++disconnected <= 10) && ret);
if (!ret) return false; if (!ret) return false;
assert(invalid_walk_tip->pprev == m_chain.Tip()); CBlockIndex* new_tip{m_chain.Tip()};
assert(disconnected_tip->pprev == new_tip);
// We immediately mark the disconnected blocks as invalid. // We immediately mark the disconnected blocks as invalid.
// This prevents a case where pruned nodes may fail to invalidateblock // This prevents a case where pruned nodes may fail to invalidateblock
// and be left unable to start as they have no tip candidates (as there // and be left unable to start as they have no tip candidates (as there
// are no blocks that meet the "have data and are not invalid per // are no blocks that meet the "have data and are not invalid per
// nStatus" criteria for inclusion in setBlockIndexCandidates). // nStatus" criteria for inclusion in setBlockIndexCandidates).
invalid_walk_tip->nStatus |= BLOCK_FAILED_VALID; disconnected_tip->nStatus |= BLOCK_FAILED_VALID;
m_blockman.m_dirty_blockindex.insert(invalid_walk_tip); m_blockman.m_dirty_blockindex.insert(disconnected_tip);
setBlockIndexCandidates.erase(invalid_walk_tip); setBlockIndexCandidates.erase(disconnected_tip);
setBlockIndexCandidates.insert(invalid_walk_tip->pprev); setBlockIndexCandidates.insert(new_tip);
if (invalid_walk_tip == to_mark_failed->pprev && (to_mark_failed->nStatus & BLOCK_FAILED_VALID)) {
// We only want to mark the last disconnected block as BLOCK_FAILED_VALID; its children
// need to be BLOCK_FAILED_CHILD instead.
to_mark_failed->nStatus = (to_mark_failed->nStatus ^ BLOCK_FAILED_VALID) | BLOCK_FAILED_CHILD;
m_blockman.m_dirty_blockindex.insert(to_mark_failed);
}
// Mark out-of-chain descendants of the invalidated block as invalid // Mark out-of-chain descendants of the invalidated block as invalid
// (possibly replacing a pre-existing BLOCK_FAILED_VALID with BLOCK_FAILED_CHILD)
// Add any equal or more work headers that are not invalidated to setBlockIndexCandidates // Add any equal or more work headers that are not invalidated to setBlockIndexCandidates
// Recalculate m_best_header if it became invalid. // Recalculate m_best_header if it became invalid.
auto candidate_it = highpow_outofchain_headers.lower_bound(invalid_walk_tip->pprev->nChainWork); auto candidate_it = highpow_outofchain_headers.lower_bound(new_tip->nChainWork);
const bool best_header_needs_update{m_chainman.m_best_header->GetAncestor(invalid_walk_tip->nHeight) == invalid_walk_tip}; const bool best_header_needs_update{m_chainman.m_best_header->GetAncestor(disconnected_tip->nHeight) == disconnected_tip};
if (best_header_needs_update) { if (best_header_needs_update) {
// pprev is definitely still valid at this point, but there may be better ones // pprev is definitely still valid at this point, but there may be better ones
m_chainman.m_best_header = invalid_walk_tip->pprev; m_chainman.m_best_header = new_tip;
} }
while (candidate_it != highpow_outofchain_headers.end()) { while (candidate_it != highpow_outofchain_headers.end()) {
CBlockIndex* candidate{candidate_it->second}; CBlockIndex* candidate{candidate_it->second};
if (candidate->GetAncestor(invalid_walk_tip->nHeight) == invalid_walk_tip) { if (candidate->GetAncestor(disconnected_tip->nHeight) == disconnected_tip) {
// Children of failed blocks should be marked as BLOCK_FAILED_CHILD instead. // Children of failed blocks are marked as BLOCK_FAILED_VALID.
candidate->nStatus &= ~BLOCK_FAILED_VALID; candidate->nStatus |= BLOCK_FAILED_VALID;
candidate->nStatus |= BLOCK_FAILED_CHILD;
m_blockman.m_dirty_blockindex.insert(candidate); m_blockman.m_dirty_blockindex.insert(candidate);
// If invalidated, the block is irrelevant for setBlockIndexCandidates // If invalidated, the block is irrelevant for setBlockIndexCandidates
// and for m_best_header and can be removed from the cache. // and for m_best_header and can be removed from the cache.
candidate_it = highpow_outofchain_headers.erase(candidate_it); candidate_it = highpow_outofchain_headers.erase(candidate_it);
continue; continue;
} }
if (!CBlockIndexWorkComparator()(candidate, invalid_walk_tip->pprev) && if (!CBlockIndexWorkComparator()(candidate, new_tip) &&
candidate->IsValid(BLOCK_VALID_TRANSACTIONS) && candidate->IsValid(BLOCK_VALID_TRANSACTIONS) &&
candidate->HaveNumChainTxs()) { candidate->HaveNumChainTxs()) {
setBlockIndexCandidates.insert(candidate); setBlockIndexCandidates.insert(candidate);
@ -3735,23 +3727,19 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde
} }
++candidate_it; ++candidate_it;
} }
// Track the last disconnected block, so we can correct its BLOCK_FAILED_CHILD status in future
// iterations, or, if it's the last one, call InvalidChainFound on it.
to_mark_failed = invalid_walk_tip;
} }
m_chainman.CheckBlockIndex(); m_chainman.CheckBlockIndex();
{ {
LOCK(cs_main); LOCK(cs_main);
if (m_chain.Contains(to_mark_failed)) { if (m_chain.Contains(pindex)) {
// If the to-be-marked invalid block is in the active chain, something is interfering and we can't proceed. // If the to-be-marked invalid block is in the active chain, something is interfering and we can't proceed.
return false; return false;
} }
// Mark pindex as invalid if it never was in the main chain // Mark pindex as invalid if it never was in the main chain
if (!pindex_was_in_chain && !(pindex->nStatus & BLOCK_FAILED_MASK)) { if (!pindex_was_in_chain && !(pindex->nStatus & BLOCK_FAILED_VALID)) {
pindex->nStatus |= BLOCK_FAILED_VALID; pindex->nStatus |= BLOCK_FAILED_VALID;
m_blockman.m_dirty_blockindex.insert(pindex); m_blockman.m_dirty_blockindex.insert(pindex);
setBlockIndexCandidates.erase(pindex); setBlockIndexCandidates.erase(pindex);
@ -3770,7 +3758,7 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde
} }
} }
InvalidChainFound(to_mark_failed); InvalidChainFound(pindex);
} }
// Only notify about a new block tip if the active chain was modified. // Only notify about a new block tip if the active chain was modified.
@ -3784,8 +3772,8 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde
// changes. // changes.
(void)m_chainman.GetNotifications().blockTip( (void)m_chainman.GetNotifications().blockTip(
/*state=*/GetSynchronizationState(m_chainman.IsInitialBlockDownload(), m_chainman.m_blockman.m_blockfiles_indexed), /*state=*/GetSynchronizationState(m_chainman.IsInitialBlockDownload(), m_chainman.m_blockman.m_blockfiles_indexed),
/*index=*/*to_mark_failed->pprev, /*index=*/*pindex->pprev,
/*verification_progress=*/WITH_LOCK(m_chainman.GetMutex(), return m_chainman.GuessVerificationProgress(to_mark_failed->pprev))); /*verification_progress=*/WITH_LOCK(m_chainman.GetMutex(), return m_chainman.GuessVerificationProgress(pindex->pprev)));
// Fire ActiveTipChange now for the current chain tip to make sure clients are notified. // Fire ActiveTipChange now for the current chain tip to make sure clients are notified.
// ActivateBestChain may call this as well, but not necessarily. // ActivateBestChain may call this as well, but not necessarily.
@ -3802,7 +3790,7 @@ void Chainstate::SetBlockFailureFlags(CBlockIndex* invalid_block)
for (auto& [_, block_index] : m_blockman.m_block_index) { for (auto& [_, block_index] : m_blockman.m_block_index) {
if (invalid_block != &block_index && block_index.GetAncestor(invalid_block->nHeight) == invalid_block) { if (invalid_block != &block_index && block_index.GetAncestor(invalid_block->nHeight) == invalid_block) {
block_index.nStatus = (block_index.nStatus & ~BLOCK_FAILED_VALID) | BLOCK_FAILED_CHILD; block_index.nStatus |= BLOCK_FAILED_VALID;
m_blockman.m_dirty_blockindex.insert(&block_index); m_blockman.m_dirty_blockindex.insert(&block_index);
} }
} }
@ -3815,8 +3803,8 @@ void Chainstate::ResetBlockFailureFlags(CBlockIndex *pindex) {
// Remove the invalidity flag from this block and all its descendants and ancestors. // Remove the invalidity flag from this block and all its descendants and ancestors.
for (auto& [_, block_index] : m_blockman.m_block_index) { for (auto& [_, block_index] : m_blockman.m_block_index) {
if ((block_index.nStatus & BLOCK_FAILED_MASK) && (block_index.GetAncestor(nHeight) == pindex || pindex->GetAncestor(block_index.nHeight) == &block_index)) { if ((block_index.nStatus & BLOCK_FAILED_VALID) && (block_index.GetAncestor(nHeight) == pindex || pindex->GetAncestor(block_index.nHeight) == &block_index)) {
block_index.nStatus &= ~BLOCK_FAILED_MASK; block_index.nStatus &= ~BLOCK_FAILED_VALID;
m_blockman.m_dirty_blockindex.insert(&block_index); m_blockman.m_dirty_blockindex.insert(&block_index);
if (block_index.IsValid(BLOCK_VALID_TRANSACTIONS) && block_index.HaveNumChainTxs() && setBlockIndexCandidates.value_comp()(m_chain.Tip(), &block_index)) { if (block_index.IsValid(BLOCK_VALID_TRANSACTIONS) && block_index.HaveNumChainTxs() && setBlockIndexCandidates.value_comp()(m_chain.Tip(), &block_index)) {
setBlockIndexCandidates.insert(&block_index); setBlockIndexCandidates.insert(&block_index);
@ -4293,7 +4281,7 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida
CBlockIndex* pindex = &(miSelf->second); CBlockIndex* pindex = &(miSelf->second);
if (ppindex) if (ppindex)
*ppindex = pindex; *ppindex = pindex;
if (pindex->nStatus & BLOCK_FAILED_MASK) { if (pindex->nStatus & BLOCK_FAILED_VALID) {
LogDebug(BCLog::VALIDATION, "%s: block %s is marked invalid\n", __func__, hash.ToString()); LogDebug(BCLog::VALIDATION, "%s: block %s is marked invalid\n", __func__, hash.ToString());
return state.Invalid(BlockValidationResult::BLOCK_CACHED_INVALID, "duplicate-invalid"); return state.Invalid(BlockValidationResult::BLOCK_CACHED_INVALID, "duplicate-invalid");
} }
@ -4313,7 +4301,7 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida
return state.Invalid(BlockValidationResult::BLOCK_MISSING_PREV, "prev-blk-not-found"); return state.Invalid(BlockValidationResult::BLOCK_MISSING_PREV, "prev-blk-not-found");
} }
pindexPrev = &((*mi).second); pindexPrev = &((*mi).second);
if (pindexPrev->nStatus & BLOCK_FAILED_MASK) { if (pindexPrev->nStatus & BLOCK_FAILED_VALID) {
LogDebug(BCLog::VALIDATION, "header %s has prev block invalid: %s\n", hash.ToString(), block.hashPrevBlock.ToString()); LogDebug(BCLog::VALIDATION, "header %s has prev block invalid: %s\n", hash.ToString(), block.hashPrevBlock.ToString());
return state.Invalid(BlockValidationResult::BLOCK_INVALID_PREV, "bad-prevblk"); return state.Invalid(BlockValidationResult::BLOCK_INVALID_PREV, "bad-prevblk");
} }
@ -4978,7 +4966,7 @@ bool ChainstateManager::LoadBlockIndex()
chainstate->TryAddBlockIndexCandidate(pindex); chainstate->TryAddBlockIndexCandidate(pindex);
} }
} }
if (pindex->nStatus & BLOCK_FAILED_MASK && (!m_best_invalid || pindex->nChainWork > m_best_invalid->nChainWork)) { if (pindex->nStatus & BLOCK_FAILED_VALID && (!m_best_invalid || pindex->nChainWork > m_best_invalid->nChainWork)) {
m_best_invalid = pindex; m_best_invalid = pindex;
} }
if (pindex->IsValid(BLOCK_VALID_TREE) && (m_best_header == nullptr || CBlockIndexWorkComparator()(m_best_header, pindex))) if (pindex->IsValid(BLOCK_VALID_TREE) && (m_best_header == nullptr || CBlockIndexWorkComparator()(m_best_header, pindex)))
@ -5226,7 +5214,7 @@ void ChainstateManager::CheckBlockIndex() const
// are not yet validated. // are not yet validated.
CChain best_hdr_chain; CChain best_hdr_chain;
assert(m_best_header); assert(m_best_header);
assert(!(m_best_header->nStatus & BLOCK_FAILED_MASK)); assert(!(m_best_header->nStatus & BLOCK_FAILED_VALID));
best_hdr_chain.SetTip(*m_best_header); best_hdr_chain.SetTip(*m_best_header);
std::multimap<const CBlockIndex*, const CBlockIndex*> forward; std::multimap<const CBlockIndex*, const CBlockIndex*> forward;
@ -5339,9 +5327,9 @@ void ChainstateManager::CheckBlockIndex() const
if ((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_SCRIPTS) assert(pindexFirstNotScriptsValid == nullptr); // SCRIPTS valid implies all parents are SCRIPTS valid if ((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_SCRIPTS) assert(pindexFirstNotScriptsValid == nullptr); // SCRIPTS valid implies all parents are SCRIPTS valid
if (pindexFirstInvalid == nullptr) { if (pindexFirstInvalid == nullptr) {
// Checks for not-invalid blocks. // Checks for not-invalid blocks.
assert((pindex->nStatus & BLOCK_FAILED_MASK) == 0); // The failed mask cannot be set for blocks without invalid parents. assert((pindex->nStatus & BLOCK_FAILED_VALID) == 0); // The failed flag cannot be set for blocks without invalid parents.
} else { } else {
assert(pindex->nStatus & BLOCK_FAILED_MASK); // Invalid blocks and their descendants must be marked as invalid assert(pindex->nStatus & BLOCK_FAILED_VALID); // Invalid blocks and their descendants must be marked as invalid
} }
// Make sure m_chain_tx_count sum is correctly computed. // Make sure m_chain_tx_count sum is correctly computed.
if (!pindex->pprev) { if (!pindex->pprev) {
@ -5356,7 +5344,7 @@ void ChainstateManager::CheckBlockIndex() const
assert((pindex->m_chain_tx_count != 0) == (pindex == snap_base)); assert((pindex->m_chain_tx_count != 0) == (pindex == snap_base));
} }
// There should be no block with more work than m_best_header, unless it's known to be invalid // There should be no block with more work than m_best_header, unless it's known to be invalid
assert((pindex->nStatus & BLOCK_FAILED_MASK) || pindex->nChainWork <= m_best_header->nChainWork); assert((pindex->nStatus & BLOCK_FAILED_VALID) || pindex->nChainWork <= m_best_header->nChainWork);
// Chainstate-specific checks on setBlockIndexCandidates // Chainstate-specific checks on setBlockIndexCandidates
for (const Chainstate* c : {m_ibd_chainstate.get(), m_snapshot_chainstate.get()}) { for (const Chainstate* c : {m_ibd_chainstate.get(), m_snapshot_chainstate.get()}) {
@ -5700,7 +5688,7 @@ util::Result<CBlockIndex*> ChainstateManager::ActivateSnapshot(
base_blockhash.ToString()))}; base_blockhash.ToString()))};
} }
bool start_block_invalid = snapshot_start_block->nStatus & BLOCK_FAILED_MASK; bool start_block_invalid = snapshot_start_block->nStatus & BLOCK_FAILED_VALID;
if (start_block_invalid) { if (start_block_invalid) {
return util::Error{Untranslated(strprintf("The base block header (%s) is part of an invalid chain", base_blockhash.ToString()))}; return util::Error{Untranslated(strprintf("The base block header (%s) is part of an invalid chain", base_blockhash.ToString()))};
} }
@ -6413,7 +6401,7 @@ void ChainstateManager::RecalculateBestHeader()
AssertLockHeld(cs_main); AssertLockHeld(cs_main);
m_best_header = ActiveChain().Tip(); m_best_header = ActiveChain().Tip();
for (auto& entry : m_blockman.m_block_index) { for (auto& entry : m_blockman.m_block_index) {
if (!(entry.second.nStatus & BLOCK_FAILED_MASK) && m_best_header->nChainWork < entry.second.nChainWork) { if (!(entry.second.nStatus & BLOCK_FAILED_VALID) && m_best_header->nChainWork < entry.second.nChainWork) {
m_best_header = &entry.second; m_best_header = &entry.second;
} }
} }