Skip to content

Commit a40e953

Browse files
committed
Merge bitcoin/bitcoin#30479: validation: Add eligible ancestors of reconsidered block to setBlockIndexCandidates
8cc3ac6 validation: Don't use IsValid() to filter for invalid blocks (Martin Zumsande) 86d98b9 test: verify that ancestors of a reconsidered block can become the chain tip (stratospher) 3c39a55 validation: Add ancestors of reconsiderblock to setBlockIndexCandidates (Martin Zumsande) Pull request description: When we call `reconsiderblock` for some block,  `Chainstate::ResetBlockFailureFlags` puts the descendants of that block into `setBlockIndexCandidates` (if they meet the criteria, i.e. have more work than the tip etc.), but never put any ancestors into the set even though we do clear their failure flags. I think that this is wrong, because `setBlockIndexCandidates` should always contain all eligible indexes that have at least as much work as the current tip, which can include ancestors of the reconsidered block. This is being checked by `CheckBlockIndex()`, which could fail if it was invoked after `ActivateBestChain` connects a block and releases `cs_main`: ``` diff diff --git a/src/validation.cpp b/src/validation.cpp index 7b04bd9a5b..ff0c3c9f58 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3551,6 +3551,7 @@ bool Chainstate::ActivateBestChain(BlockValidationState& state, std::shared_ptr<              }          }          // When we reach this point, we switched to a new tip (stored in pindexNewTip). +        m_chainman.CheckBlockIndex();            if (exited_ibd) {              // If a background chainstate is in use, we may need to rebalance our ``` makes `rpc_invalidateblock.py` fail on master. Even though we don't currently have a `CheckBlockIndex()` in that place, after `cs_main` is released other threads could invoke it, which is happening in the rare failures of #16444 where an invalid header received from another peer could trigger a `CheckBlockIndex()` call that would fail. Fix this by adding eligible ancestors to `setBlockIndexCandidates` in `Chainstate::ResetBlockFailureFlags` (also simplifying that function a bit). Fixes #16444 ACKs for top commit: achow101: ACK 8cc3ac6 TheCharlatan: Re-ACK 8cc3ac6 stratospher: reACK 8cc3ac6. Tree-SHA512: 53f27591916246be4093d64b86a0494e55094abd8c586026b1247e4a36747bc3d6dbe46dc26ee4a22f47b8eb0d9699d13e577dee0e7198145f3c9b11ab2a30b7
2 parents 1ca62ed + 8cc3ac6 commit a40e953

File tree

5 files changed

+30
-14
lines changed

5 files changed

+30
-14
lines changed

src/chain.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ class CBlockIndex
292292
std::string ToString() const;
293293

294294
//! Check whether this block index entry is valid up to the passed validity level.
295-
bool IsValid(enum BlockStatus nUpTo = BLOCK_VALID_TRANSACTIONS) const
295+
bool IsValid(enum BlockStatus nUpTo) const
296296
EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
297297
{
298298
AssertLockHeld(::cs_main);

src/test/fuzz/chain.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ FUZZ_TARGET(chain)
3030
(void)disk_block_index->GetMedianTimePast();
3131
(void)disk_block_index->GetUndoPos();
3232
(void)disk_block_index->HaveNumChainTxs();
33-
(void)disk_block_index->IsValid();
33+
(void)disk_block_index->IsValid(BLOCK_VALID_TRANSACTIONS);
3434
}
3535

3636
const CBlockHeader block_header = disk_block_index->GetBlockHeader();

src/validation.cpp

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3851,9 +3851,9 @@ void Chainstate::ResetBlockFailureFlags(CBlockIndex *pindex) {
38513851

38523852
int nHeight = pindex->nHeight;
38533853

3854-
// Remove the invalidity flag from this block and all its descendants.
3854+
// Remove the invalidity flag from this block and all its descendants and ancestors.
38553855
for (auto& [_, block_index] : m_blockman.m_block_index) {
3856-
if (!block_index.IsValid() && block_index.GetAncestor(nHeight) == pindex) {
3856+
if ((block_index.nStatus & BLOCK_FAILED_MASK) && (block_index.GetAncestor(nHeight) == pindex || pindex->GetAncestor(block_index.nHeight) == &block_index)) {
38573857
block_index.nStatus &= ~BLOCK_FAILED_MASK;
38583858
m_blockman.m_dirty_blockindex.insert(&block_index);
38593859
if (block_index.IsValid(BLOCK_VALID_TRANSACTIONS) && block_index.HaveNumChainTxs() && setBlockIndexCandidates.value_comp()(m_chain.Tip(), &block_index)) {
@@ -3865,15 +3865,6 @@ void Chainstate::ResetBlockFailureFlags(CBlockIndex *pindex) {
38653865
}
38663866
}
38673867
}
3868-
3869-
// Remove the invalidity flag from all ancestors too.
3870-
while (pindex != nullptr) {
3871-
if (pindex->nStatus & BLOCK_FAILED_MASK) {
3872-
pindex->nStatus &= ~BLOCK_FAILED_MASK;
3873-
m_blockman.m_dirty_blockindex.insert(pindex);
3874-
}
3875-
pindex = pindex->pprev;
3876-
}
38773868
}
38783869

38793870
void Chainstate::TryAddBlockIndexCandidate(CBlockIndex* pindex)

src/validation.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,7 @@ class Chainstate
745745
/** Set invalidity status to all descendants of a block */
746746
void SetBlockFailureFlags(CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
747747

748-
/** Remove invalidity status from a block and its descendants. */
748+
/** Remove invalidity status from a block, its descendants and ancestors and reconsider them for activation */
749749
void ResetBlockFailureFlags(CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
750750

751751
/** Replay blocks that aren't fully applied to the database. */

test/functional/rpc_invalidateblock.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,23 @@ def run_test(self):
8585
self.wait_until(lambda: self.nodes[0].getblockcount() == 4, timeout=5)
8686
self.wait_until(lambda: self.nodes[1].getblockcount() == 4, timeout=5)
8787

88+
self.log.info("Verify that ancestors can become chain tip candidates when we reconsider blocks")
89+
# Invalidate node0's current chain (1' -> 2' -> 3' -> 4') so that we don't reorg back to it in this test
90+
badhash = self.nodes[0].getblockhash(1)
91+
self.nodes[0].invalidateblock(badhash)
92+
# Reconsider the tip so that node0's chain becomes this chain again : 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> header 7
93+
self.nodes[0].reconsiderblock(tip)
94+
blockhash_3 = self.nodes[0].getblockhash(3)
95+
blockhash_4 = self.nodes[0].getblockhash(4)
96+
blockhash_6 = self.nodes[0].getblockhash(6)
97+
assert_equal(self.nodes[0].getbestblockhash(), blockhash_6)
98+
99+
# Invalidate block 4 so that chain becomes : 1 -> 2 -> 3
100+
self.nodes[0].invalidateblock(blockhash_4)
101+
assert_equal(self.nodes[0].getbestblockhash(), blockhash_3)
102+
assert_equal(self.nodes[0].getblockchaininfo()['blocks'], 3)
103+
assert_equal(self.nodes[0].getblockchaininfo()['headers'], 3)
104+
88105
self.log.info("Verify that we reconsider all ancestors as well")
89106
blocks = self.generatetodescriptor(self.nodes[1], 10, ADDRESS_BCRT1_UNSPENDABLE_DESCRIPTOR, sync_fun=self.no_op)
90107
assert_equal(self.nodes[1].getbestblockhash(), blocks[-1])
@@ -111,6 +128,14 @@ def run_test(self):
111128
# Should report consistent blockchain info
112129
assert_equal(self.nodes[1].getblockchaininfo()["headers"], self.nodes[1].getblockchaininfo()["blocks"])
113130

131+
# Reconsider the header
132+
self.nodes[0].reconsiderblock(block.hash)
133+
# Since header doesn't have block data, it can't be chain tip
134+
# Check if it's possible for an ancestor (with block data) to be the chain tip
135+
assert_equal(self.nodes[0].getbestblockhash(), blockhash_6)
136+
assert_equal(self.nodes[0].getblockchaininfo()['blocks'], 6)
137+
assert_equal(self.nodes[0].getblockchaininfo()['headers'], 7)
138+
114139
self.log.info("Verify that invalidating an unknown block throws an error")
115140
assert_raises_rpc_error(-5, "Block not found", self.nodes[1].invalidateblock, "00" * 32)
116141
assert_equal(self.nodes[1].getbestblockhash(), blocks[-1])

0 commit comments

Comments
 (0)