validation: stop using BLOCK_FAILED_CHILD
What changed, and why it matters
This is a code cleanup change in Bitcoin Core. It removes the use of a separate 'descendant of a failed block' flag and instead marks those blocks with the same 'validation failed' flag used for the block itself. The commit explicitly states the two flags had no functional difference and were only adding unnecessary complexity. There is no indication this fixes a security bug or changes network behavior.
No security action required. Treat as routine refactoring. Reviewers may want to confirm that BLOCK_FAILED_MASK semantics remain unchanged and that no downstream logic depends on the BLOCK_FAILED_CHILD bit being set independently.
Security signals we found
No security-relevant signals in commit message or diff
Change is explicitly described as non-functional code simplification
No validation rules, consensus checks, or network protocol behavior modified
BLOCK_FAILED_MASK still includes both bits, preserving existing disk-state interpretation
Evidence from the diff
The commit eliminates usage of BLOCK_FAILED_CHILD in validation logic, replacing it with BLOCK_FAILED_VALID. BLOCK_FAILED_CHILD is retained in the enum as an unused bit for backward compatibility with existing on-disk nStatus values. The change affects chain loading, chain reorganization failure handling, block invalidation propagation, and corresponding tests/fuzzers. The author states the distinction was unused and functionally equivalent.
Changed components
src/chain.hsrc/node/blockstorage.cppsrc/validation.cppsrc/test/blockchain_tests.cppsrc/test/fuzz/chain.cppInspect captured patch +6 / −14
diff --git a/src/chain.h b/src/chain.h
index 7b65c76d..459a83aa 100644
--- a/src/chain.h
+++ b/src/chain.h
@@ -77,7 +77,7 @@ enum BlockStatus : uint32_t {
BLOCK_HAVE_MASK = BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO,
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
diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp
index ddaaad19..62abcbc0 100644
--- a/src/node/blockstorage.cpp
+++ b/src/node/blockstorage.cpp
@@ -488,7 +488,7 @@ bool BlockManager::LoadBlockIndex(const std::optional<uint256>& snapshot_blockha
}
}
if (!(pindex->nStatus & BLOCK_FAILED_MASK) && pindex->pprev && (pindex->pprev->nStatus & BLOCK_FAILED_MASK)) {
- pindex->nStatus |= BLOCK_FAILED_CHILD;
+ pindex->nStatus |= BLOCK_FAILED_VALID;
m_dirty_blockindex.insert(pindex);
}
if (pindex->pprev) {
diff --git a/src/test/blockchain_tests.cpp b/src/test/blockchain_tests.cpp
index c6b35e32..8c297975 100644
--- a/src/test/blockchain_tests.cpp
+++ b/src/test/blockchain_tests.cpp
@@ -130,7 +130,6 @@ BOOST_FIXTURE_TEST_CASE(invalidate_block, TestChain100Setup)
// 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_CHILD) == 0));
// check all ancestors of the invalidated block are validated up to BLOCK_VALID_TRANSACTIONS and are not invalid
auto pindex = tip_to_invalidate->pprev;
@@ -140,18 +139,12 @@ BOOST_FIXTURE_TEST_CASE(invalidate_block, TestChain100Setup)
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;
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_CHILD));
+ WITH_LOCK(::cs_main, assert(pindex->nStatus & BLOCK_FAILED_VALID));
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()
diff --git a/src/test/fuzz/chain.cpp b/src/test/fuzz/chain.cpp
index 23499dd0..2c294fd5 100644
--- a/src/test/fuzz/chain.cpp
+++ b/src/test/fuzz/chain.cpp
@@ -50,7 +50,6 @@ FUZZ_TARGET(chain)
BlockStatus::BLOCK_HAVE_UNDO,
BlockStatus::BLOCK_HAVE_MASK,
BlockStatus::BLOCK_FAILED_VALID,
- BlockStatus::BLOCK_FAILED_CHILD,
BlockStatus::BLOCK_FAILED_MASK,
BlockStatus::BLOCK_OPT_WITNESS,
});
diff --git a/src/validation.cpp b/src/validation.cpp
index 93b7dc37..7dee16d9 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -3184,7 +3184,7 @@ CBlockIndex* Chainstate::FindMostWorkChain()
// Remove the entire chain from the set.
while (pindexTest != pindexFailed) {
if (fFailedChain) {
- pindexFailed->nStatus |= BLOCK_FAILED_CHILD;
+ pindexFailed->nStatus |= BLOCK_FAILED_VALID;
m_blockman.m_dirty_blockindex.insert(pindexFailed);
} else if (fMissingData) {
// If we're missing data, then add back to m_blocks_unlinked,
@@ -3740,7 +3740,7 @@ void Chainstate::SetBlockFailureFlags(CBlockIndex* invalid_block)
for (auto& [_, block_index] : m_blockman.m_block_index) {
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);
}
}
Why this scored 20/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.