Skip to content

Commit 9efe546

Browse files
committed
Merge bitcoin/bitcoin#31835: validation: set BLOCK_FAILED_CHILD correctly
3c3548a validation: clarify final |= BLOCK_FAILED_VALID in InvalidateBlock (Matt Corallo) aac5488 validation: correctly update BlockStatus for invalid block descendants (stratospher) 9e29653 test: check BlockStatus when InvalidateBlock is used (stratospher) c996675 validation: fix traversal condition to mark BLOCK_FAILED_CHILD (stratospher) Pull request description: This PR addresses 3 issues related to how `BLOCK_FAILED_CHILD` is set: 1. In `InvalidateBlock()` - Previously, `BLOCK_FAILED_CHILD` was not being set when it should have been. - This was due to an incorrect traversal condition, which is fixed in this PR. 2. In `SetBlockFailure()` - `BLOCK_FAILED_VALID` is now cleared before setting `BLOCK_FAILED_CHILD`. 3. In `InvalidateBlock()` - if block is already marked as `BLOCK_FAILED_CHILD`, don't mark it as `BLOCK_FAILED_VALID` again. Also adds a unit test to check `BLOCK_FAILED_VALID` and `BLOCK_FAILED_CHILD` status in `InvalidateBlock()`. <details> <summary><h3>looking for feedback on an alternate approach</h3></summary> <br> An alternate approach could be removing `BLOCK_FAILED_CHILD` since 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. See similar discussion in bitcoin/bitcoin#16856. I have a branch with this approach in https://github.com/stratospher/bitcoin/commits/2025_02_remove_block_failed_child/. Compared to the version in #16856, it also resets `BLOCK_FAILED_CHILD` already on disk to `BLOCK_FAILED_VALID` when loading from disk so that we won't be in a dirty state in a no-`BLOCK_FAILED_CHILD`-world. I'm not sure if it's a good idea to remove `BLOCK_FAILED_CHILD` though. would be curious to hear what others think of this approach. thanks @ mzumsande for helpful discussion regarding this PR! </details> ACKs for top commit: achow101: ACK 3c3548a TheCharlatan: Re-ACK 3c3548a mzumsande: re-ACK 3c3548a Tree-SHA512: 83e0d29dea95b97519d4868135c965b86f6f43be50b15c0bd8f998b3476388fc7cc22b49c0c54ec532ae8222e57dfc436438f0c8e98f54757b384f220488b6a6
2 parents bd158ab + 3c3548a commit 9efe546

File tree

2 files changed

+48
-8
lines changed

2 files changed

+48
-8
lines changed

src/test/blockchain_tests.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,41 @@ BOOST_AUTO_TEST_CASE(num_chain_tx_max)
117117
BOOST_CHECK_EQUAL(block_index.m_chain_tx_count, std::numeric_limits<uint64_t>::max());
118118
}
119119

120+
BOOST_FIXTURE_TEST_CASE(invalidate_block, TestChain100Setup)
121+
{
122+
const CChain& active{*WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return &Assert(m_node.chainman)->ActiveChain())};
123+
124+
// Check BlockStatus when doing InvalidateBlock()
125+
BlockValidationState state;
126+
auto* orig_tip = active.Tip();
127+
int height_to_invalidate = orig_tip->nHeight - 10;
128+
auto* tip_to_invalidate = active[height_to_invalidate];
129+
m_node.chainman->ActiveChainstate().InvalidateBlock(state, tip_to_invalidate);
130+
131+
// tip_to_invalidate just got invalidated, so it's BLOCK_FAILED_VALID
132+
WITH_LOCK(::cs_main, assert(tip_to_invalidate->nStatus & BLOCK_FAILED_VALID));
133+
WITH_LOCK(::cs_main, assert((tip_to_invalidate->nStatus & BLOCK_FAILED_CHILD) == 0));
134+
135+
// check all ancestors of the invalidated block are validated up to BLOCK_VALID_TRANSACTIONS and are not invalid
136+
auto pindex = tip_to_invalidate->pprev;
137+
while (pindex) {
138+
WITH_LOCK(::cs_main, assert(pindex->IsValid(BLOCK_VALID_TRANSACTIONS)));
139+
WITH_LOCK(::cs_main, assert((pindex->nStatus & BLOCK_FAILED_MASK) == 0));
140+
pindex = pindex->pprev;
141+
}
142+
143+
// check all descendants of the invalidated block are BLOCK_FAILED_CHILD
144+
pindex = orig_tip;
145+
while (pindex && pindex != tip_to_invalidate) {
146+
WITH_LOCK(::cs_main, assert((pindex->nStatus & BLOCK_FAILED_VALID) == 0));
147+
WITH_LOCK(::cs_main, assert(pindex->nStatus & BLOCK_FAILED_CHILD));
148+
pindex = pindex->pprev;
149+
}
150+
151+
// don't mark already invalidated block (orig_tip is BLOCK_FAILED_CHILD) with BLOCK_FAILED_VALID again
152+
m_node.chainman->ActiveChainstate().InvalidateBlock(state, orig_tip);
153+
WITH_LOCK(::cs_main, assert(orig_tip->nStatus & BLOCK_FAILED_CHILD));
154+
WITH_LOCK(::cs_main, assert((orig_tip->nStatus & BLOCK_FAILED_VALID) == 0));
155+
}
156+
120157
BOOST_AUTO_TEST_SUITE_END()

src/validation.cpp

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3747,7 +3747,7 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde
37473747
m_blockman.m_dirty_blockindex.insert(invalid_walk_tip);
37483748
setBlockIndexCandidates.erase(invalid_walk_tip);
37493749
setBlockIndexCandidates.insert(invalid_walk_tip->pprev);
3750-
if (invalid_walk_tip->pprev == to_mark_failed && (to_mark_failed->nStatus & BLOCK_FAILED_VALID)) {
3750+
if (invalid_walk_tip == to_mark_failed->pprev && (to_mark_failed->nStatus & BLOCK_FAILED_VALID)) {
37513751
// We only want to mark the last disconnected block as BLOCK_FAILED_VALID; its children
37523752
// need to be BLOCK_FAILED_CHILD instead.
37533753
to_mark_failed->nStatus = (to_mark_failed->nStatus ^ BLOCK_FAILED_VALID) | BLOCK_FAILED_CHILD;
@@ -3779,11 +3779,13 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde
37793779
return false;
37803780
}
37813781

3782-
// Mark pindex (or the last disconnected block) as invalid, even when it never was in the main chain
3783-
to_mark_failed->nStatus |= BLOCK_FAILED_VALID;
3784-
m_blockman.m_dirty_blockindex.insert(to_mark_failed);
3785-
setBlockIndexCandidates.erase(to_mark_failed);
3786-
m_chainman.m_failed_blocks.insert(to_mark_failed);
3782+
// Mark pindex as invalid if it never was in the main chain
3783+
if (!pindex_was_in_chain && !(pindex->nStatus & BLOCK_FAILED_MASK)) {
3784+
pindex->nStatus |= BLOCK_FAILED_VALID;
3785+
m_blockman.m_dirty_blockindex.insert(pindex);
3786+
setBlockIndexCandidates.erase(pindex);
3787+
m_chainman.m_failed_blocks.insert(pindex);
3788+
}
37873789

37883790
// If any new blocks somehow arrived while we were disconnecting
37893791
// (above), then the pre-calculation of what should go into
@@ -3826,8 +3828,9 @@ void Chainstate::SetBlockFailureFlags(CBlockIndex* invalid_block)
38263828
AssertLockHeld(cs_main);
38273829

38283830
for (auto& [_, block_index] : m_blockman.m_block_index) {
3829-
if (block_index.GetAncestor(invalid_block->nHeight) == invalid_block && !(block_index.nStatus & BLOCK_FAILED_MASK)) {
3830-
block_index.nStatus |= BLOCK_FAILED_CHILD;
3831+
if (invalid_block != &block_index && block_index.GetAncestor(invalid_block->nHeight) == invalid_block) {
3832+
block_index.nStatus = (block_index.nStatus & ~BLOCK_FAILED_VALID) | BLOCK_FAILED_CHILD;
3833+
m_blockman.m_dirty_blockindex.insert(&block_index);
38313834
}
38323835
}
38333836
}

0 commit comments

Comments
 (0)