Merge bitcoin/bitcoin#32950: validation: remove BLOCK_FAILED_CHILD

fb3e1bf9c9 test: check LoadBlockIndex correctly recomputes invalidity flags (stratospher)
29740c06ac validation: remove BLOCK_FAILED_MASK (stratospher)
b5b2956bda validation: reset BLOCK_FAILED_CHILD to BLOCK_FAILED_VALID when loading from disk (stratospher)
37bc207852 validation: stop using BLOCK_FAILED_CHILD (stratospher)
120c631e16 refactor: use clearer variables in InvalidateBlock() (stratospher)
18f11695c7 validation: don't update BLOCK_FAILED_VALID to BLOCK_FAILED_CHILD in InvalidateBlock (stratospher)

Pull request description:

  Fixes https://github.com/bitcoin/bitcoin/issues/32173

  even though we have a distinction between `BLOCK_FAILED_VALID` and `BLOCK_FAILED_CHILD` in the codebase,
  we don't use it for anything. Whenever we check for BlockStatus, we use `BLOCK_FAILED_MASK` which encompasses both of them.

  Since there is no functional difference between `BLOCK_FAILED_VALID` and `BLOCK_FAILED_CHILD` and it's added
  code complexity to correctly categorise them (ex: https://github.com/bitcoin/bitcoin/pull/31405#discussion_r1914366243, https://github.com/bitcoin/bitcoin/pull/16856#issuecomment-565506585), we could just remove it.

  Looking for conceptual feedback on whether it's better to improve handling of `BLOCK_FAILED_CHILD` in the codebase or remove `BLOCK_FAILED_CHILD`.

  Of less relevance, but it would also fix a `reconsiderblock` crash that could happen in the situation mentioned in https://github.com/bitcoin/bitcoin/issues/32173#issuecomment-2767030982

  Similar attempt in the past in https://github.com/bitcoin/bitcoin/pull/16856#issuecomment-568073859

ACKs for top commit:
  stickies-v:
    re-ACK fb3e1bf9c9
  alexanderwiederin:
    ACK fb3e1bf9c9
  mzumsande:
    re-ACK fb3e1bf9c9

Tree-SHA512: e97b739885c40a8c021966438e9767cc02bc183056236d6a8c64f6819347ae70c0fbcd71cc2528917560d9f4fd56aed45faf1b6c75d98de7b08b621693a97fbc
This commit is contained in:
merge-script committed 2026-02-18 15:47:57 +00:00
commit 655b9d12ee
9 files changed
+83 -68

No files matched your search

+3 -4
View File
@@ -77,8 +77,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
@@ -253,7 +252,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);
} }
@@ -264,7 +263,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;
+10 -2
View File
@@ -487,10 +487,18 @@ 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)) {
pindex->nStatus |= BLOCK_FAILED_CHILD; if (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->nStatus & BLOCK_FAILED_VALID) && pindex->pprev && (pindex->pprev->nStatus & BLOCK_FAILED_VALID)) {
// All descendants of invalid blocks are invalid too.
pindex->nStatus |= BLOCK_FAILED_VALID;
m_dirty_blockindex.insert(pindex);
}
if (pindex->pprev) { if (pindex->pprev) {
pindex->BuildSkip(); pindex->BuildSkip();
} }
+1 -1
View File
@@ -1595,7 +1595,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()) {
+1 -1
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";
} }
+3 -10
View File
@@ -130,28 +130,21 @@ BOOST_FIXTURE_TEST_CASE(invalidate_block, TestChain100Setup)
// 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);
WITH_LOCK(::cs_main, assert(orig_tip->nStatus & BLOCK_FAILED_CHILD));
WITH_LOCK(::cs_main, assert((orig_tip->nStatus & BLOCK_FAILED_VALID) == 0));
} }
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()
+3 -3
View File
@@ -61,7 +61,7 @@ FUZZ_TARGET(block_index_tree, .init = initialize_block_index_tree)
// Receive a header building on an existing valid one. This assumes headers are valid, so PoW is not relevant here. // Receive a header building on an existing valid one. This assumes headers are valid, so PoW is not relevant here.
LOCK(cs_main); LOCK(cs_main);
CBlockIndex* prev_block = PickValue(fuzzed_data_provider, blocks); CBlockIndex* prev_block = PickValue(fuzzed_data_provider, blocks);
if (!(prev_block->nStatus & BLOCK_FAILED_MASK)) { if (!(prev_block->nStatus & BLOCK_FAILED_VALID)) {
CBlockHeader header = ConsumeBlockHeader(fuzzed_data_provider, prev_block->GetBlockHash(), nonce_counter); CBlockHeader header = ConsumeBlockHeader(fuzzed_data_provider, prev_block->GetBlockHash(), nonce_counter);
CBlockIndex* index = blockman.AddToBlockIndex(header, chainman.m_best_header); CBlockIndex* index = blockman.AddToBlockIndex(header, chainman.m_best_header);
assert(index->nStatus & BLOCK_VALID_TREE); assert(index->nStatus & BLOCK_VALID_TREE);
@@ -74,7 +74,7 @@ FUZZ_TARGET(block_index_tree, .init = initialize_block_index_tree)
LOCK(cs_main); LOCK(cs_main);
CBlockIndex* index = PickValue(fuzzed_data_provider, blocks); CBlockIndex* index = PickValue(fuzzed_data_provider, blocks);
// Must be new to us and not known to be invalid (e.g. because of an invalid ancestor). // Must be new to us and not known to be invalid (e.g. because of an invalid ancestor).
if (index->nTx == 0 && !(index->nStatus & BLOCK_FAILED_MASK)) { if (index->nTx == 0 && !(index->nStatus & BLOCK_FAILED_VALID)) {
if (fuzzed_data_provider.ConsumeBool()) { // Invalid if (fuzzed_data_provider.ConsumeBool()) { // Invalid
BlockValidationState state; BlockValidationState state;
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "consensus-invalid"); state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "consensus-invalid");
@@ -124,7 +124,7 @@ FUZZ_TARGET(block_index_tree, .init = initialize_block_index_tree)
} }
// Connect blocks, possibly fail // Connect blocks, possibly fail
for (CBlockIndex* block : to_connect | std::views::reverse) { for (CBlockIndex* block : to_connect | std::views::reverse) {
assert(!(block->nStatus & BLOCK_FAILED_MASK)); assert(!(block->nStatus & BLOCK_FAILED_VALID));
assert(block->nStatus & BLOCK_HAVE_DATA); assert(block->nStatus & BLOCK_HAVE_DATA);
if (!block->IsValid(BLOCK_VALID_SCRIPTS)) { if (!block->IsValid(BLOCK_VALID_SCRIPTS)) {
if (fuzzed_data_provider.ConsumeBool()) { // Invalid if (fuzzed_data_provider.ConsumeBool()) { // Invalid
-2
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) {
@@ -591,6 +591,31 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.size(), num_indexes - last_assumed_valid_idx + 1); BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.size(), num_indexes - last_assumed_valid_idx + 1);
} }
BOOST_FIXTURE_TEST_CASE(loadblockindex_invalid_descendants, TestChain100Setup)
{
LOCK(Assert(m_node.chainman)->GetMutex());
// consider the chain of blocks grand_parent <- parent <- child
// intentionally mark:
// - grand_parent: BLOCK_FAILED_VALID
// - parent: BLOCK_FAILED_CHILD
// - child: not invalid
// Test that when the block index is loaded, all blocks are marked as BLOCK_FAILED_VALID
auto* child{m_node.chainman->ActiveChain().Tip()};
auto* parent{child->pprev};
auto* grand_parent{parent->pprev};
grand_parent->nStatus = (grand_parent->nStatus | BLOCK_FAILED_VALID);
parent->nStatus = (parent->nStatus & ~BLOCK_FAILED_VALID) | BLOCK_FAILED_CHILD;
child->nStatus = (child->nStatus & ~BLOCK_FAILED_VALID);
// Reload block index to recompute block status validity flags.
m_node.chainman->LoadBlockIndex();
// check grand_parent, parent, child is marked as BLOCK_FAILED_VALID after reloading the block index
BOOST_CHECK(grand_parent->nStatus & BLOCK_FAILED_VALID);
BOOST_CHECK(parent->nStatus & BLOCK_FAILED_VALID);
BOOST_CHECK(child->nStatus & BLOCK_FAILED_VALID);
}
//! Ensure that snapshot chainstate can be loaded when found on disk after a //! Ensure that snapshot chainstate can be loaded when found on disk after a
//! restart, and that new blocks can be connected to both chainstates. //! restart, and that new blocks can be connected to both chainstates.
BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_init, SnapshotTestSetup) BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_init, SnapshotTestSetup)
+37 -45
View File
@@ -3173,7 +3173,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)
@@ -3184,7 +3184,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,
@@ -3565,10 +3565,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.
@@ -3594,12 +3590,16 @@ 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});
} }
} }
} }
CBlockIndex* to_mark_failed = pindex;
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;
@@ -3613,7 +3613,7 @@ 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.
@@ -3626,49 +3626,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 // new_tip 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);
@@ -3682,9 +3675,8 @@ 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 // Track the last disconnected block to call InvalidChainFound on it.
// iterations, or, if it's the last one, call InvalidChainFound on it. to_mark_failed = disconnected_tip;
to_mark_failed = invalid_walk_tip;
} }
m_chainman.CheckBlockIndex(); m_chainman.CheckBlockIndex();
@@ -3697,7 +3689,7 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde
} }
// 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);
@@ -3748,7 +3740,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);
} }
} }
@@ -3761,8 +3753,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);
@@ -4245,7 +4237,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",
strprintf("block %s was previously marked invalid", hash.ToString())); strprintf("block %s was previously marked invalid", hash.ToString()));
@@ -4266,7 +4258,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");
} }
@@ -4960,7 +4952,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)))
@@ -5200,7 +5192,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;
@@ -5315,9 +5307,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) {
@@ -5332,7 +5324,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 auto& c : m_chainstates) { for (const auto& c : m_chainstates) {
@@ -5648,7 +5640,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()))};
} }
@@ -6293,7 +6285,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;
} }
} }