Skip to content

Commit f5149dd

Browse files
validation: mark blocks building on an invalid block as BLOCK_FAILED_CHILD
Without doing so, header-only chains building on a chain that will be marked as invalid would still be eligible for m_best_header. This improves both getblockchaininfo and getchaintips behavior. While this adds an iteration over the entire block index, it can only be triggered by the user (invalidateblock) or by others at a cost (the header needs to be accepted in the first place, so it needs valid PoW). Co-authored-by: TheCharlatan <[email protected]>
1 parent 783cb73 commit f5149dd

File tree

3 files changed

+39
-1
lines changed

3 files changed

+39
-1
lines changed

src/validation.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2045,6 +2045,7 @@ void Chainstate::InvalidChainFound(CBlockIndex* pindexNew)
20452045
if (!m_chainman.m_best_invalid || pindexNew->nChainWork > m_chainman.m_best_invalid->nChainWork) {
20462046
m_chainman.m_best_invalid = pindexNew;
20472047
}
2048+
SetBlockFailureFlags(pindexNew);
20482049
if (m_chainman.m_best_header != nullptr && m_chainman.m_best_header->GetAncestor(pindexNew->nHeight) == pindexNew) {
20492050
m_chainman.RecalculateBestHeader();
20502051
}
@@ -3785,6 +3786,17 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde
37853786
return true;
37863787
}
37873788

3789+
void Chainstate::SetBlockFailureFlags(CBlockIndex* invalid_block)
3790+
{
3791+
AssertLockHeld(cs_main);
3792+
3793+
for (auto& [_, block_index] : m_blockman.m_block_index) {
3794+
if (block_index.GetAncestor(invalid_block->nHeight) == invalid_block && !(block_index.nStatus & BLOCK_FAILED_MASK)) {
3795+
block_index.nStatus |= BLOCK_FAILED_CHILD;
3796+
}
3797+
}
3798+
}
3799+
37883800
void Chainstate::ResetBlockFailureFlags(CBlockIndex *pindex) {
37893801
AssertLockHeld(cs_main);
37903802

src/validation.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,9 @@ class Chainstate
735735
EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex)
736736
LOCKS_EXCLUDED(::cs_main);
737737

738+
/** Set invalidity status to all descendants of a block */
739+
void SetBlockFailureFlags(CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
740+
738741
/** Remove invalidity status from a block and its descendants. */
739742
void ResetBlockFailureFlags(CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
740743

test/functional/rpc_invalidateblock.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
from test_framework.test_framework import BitcoinTestFramework
88
from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR
9+
from test_framework.blocktools import (
10+
create_block,
11+
create_coinbase,
12+
)
913
from test_framework.util import (
1014
assert_equal,
1115
assert_raises_rpc_error,
@@ -35,15 +39,34 @@ def run_test(self):
3539
self.connect_nodes(0, 1)
3640
self.sync_blocks(self.nodes[0:2])
3741
assert_equal(self.nodes[0].getblockcount(), 6)
38-
badhash = self.nodes[1].getblockhash(2)
42+
43+
# Add a header to the tip of node 0 without submitting the block. This shouldn't
44+
# affect results since this chain will be invalidated next.
45+
tip = self.nodes[0].getbestblockhash()
46+
block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1
47+
block = create_block(int(tip, 16), create_coinbase(self.nodes[0].getblockcount()), block_time, version=4)
48+
block.solve()
49+
self.nodes[0].submitheader(block.serialize().hex())
50+
assert_equal(self.nodes[0].getblockchaininfo()["headers"], self.nodes[0].getblockchaininfo()["blocks"] + 1)
3951

4052
self.log.info("Invalidate block 2 on node 0 and verify we reorg to node 0's original chain")
53+
badhash = self.nodes[1].getblockhash(2)
4154
self.nodes[0].invalidateblock(badhash)
4255
assert_equal(self.nodes[0].getblockcount(), 4)
4356
assert_equal(self.nodes[0].getbestblockhash(), besthash_n0)
4457
# Should report consistent blockchain info
4558
assert_equal(self.nodes[0].getblockchaininfo()["headers"], self.nodes[0].getblockchaininfo()["blocks"])
4659

60+
self.log.info("Reconsider block 6 on node 0 again and verify that the best header is set correctly")
61+
self.nodes[0].reconsiderblock(tip)
62+
assert_equal(self.nodes[0].getblockchaininfo()["headers"], self.nodes[0].getblockchaininfo()["blocks"] + 1)
63+
64+
self.log.info("Invalidate block 2 on node 0 and verify we reorg to node 0's original chain again")
65+
self.nodes[0].invalidateblock(badhash)
66+
assert_equal(self.nodes[0].getblockcount(), 4)
67+
assert_equal(self.nodes[0].getbestblockhash(), besthash_n0)
68+
assert_equal(self.nodes[0].getblockchaininfo()["headers"], self.nodes[0].getblockchaininfo()["blocks"])
69+
4770
self.log.info("Make sure we won't reorg to a lower work chain:")
4871
self.connect_nodes(1, 2)
4972
self.log.info("Sync node 2 to node 1 so both have 6 blocks")

0 commit comments

Comments
 (0)