Skip to content

Commit 30c2b0b

Browse files
committed
Merge #16849: Fix block index inconsistency in InvalidateBlock()
2a4e60b Fix block index inconsistency in InvalidateBlock() (Suhas Daftuar) Pull request description: Previously, we could release `cs_main` while leaving the block index in a state that would fail `CheckBlockIndex()`, because `setBlockIndexCandidates` was not being fully populated before releasing `cs_main`. ACKs for top commit: TheBlueMatt: utACK 2a4e60b. I also discovered another issue in InvalidateBlock while reviewing, see #16856. Sjors: ACK 2a4e60b. Tested on top of #16899. Also tested `invalidateblock` with `-checkblockindex=1`. fjahr: ACK 2a4e60b. Ran tests, reviewed code, inspected behavior while manually testing `invalidateblock`. Tree-SHA512: ced12f9dfff0d413258c709921543fb154789898165590b30d1ee0cdc72863382f189744f7669a7c924d3689a1cc623efdf4e5ae3efc60054572c1e6826de612
2 parents 27322cd + 2a4e60b commit 30c2b0b

File tree

1 file changed

+52
-2
lines changed

1 file changed

+52
-2
lines changed

src/validation.cpp

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2926,6 +2926,38 @@ bool CChainState::InvalidateBlock(CValidationState& state, const CChainParams& c
29262926
bool pindex_was_in_chain = false;
29272927
int disconnected = 0;
29282928

2929+
// We do not allow ActivateBestChain() to run while InvalidateBlock() is
2930+
// running, as that could cause the tip to change while we disconnect
2931+
// blocks.
2932+
LOCK(m_cs_chainstate);
2933+
2934+
// We'll be acquiring and releasing cs_main below, to allow the validation
2935+
// callbacks to run. However, we should keep the block index in a
2936+
// consistent state as we disconnect blocks -- in particular we need to
2937+
// add equal-work blocks to setBlockIndexCandidates as we disconnect.
2938+
// To avoid walking the block index repeatedly in search of candidates,
2939+
// build a map once so that we can look up candidate blocks by chain
2940+
// work as we go.
2941+
std::multimap<const arith_uint256, CBlockIndex *> candidate_blocks_by_work;
2942+
2943+
{
2944+
LOCK(cs_main);
2945+
for (const auto& entry : m_blockman.m_block_index) {
2946+
CBlockIndex *candidate = entry.second;
2947+
// We don't need to put anything in our active chain into the
2948+
// multimap, because those candidates will be found and considered
2949+
// as we disconnect.
2950+
// Instead, consider only non-active-chain blocks that have at
2951+
// least as much work as where we expect the new tip to end up.
2952+
if (!m_chain.Contains(candidate) &&
2953+
!CBlockIndexWorkComparator()(candidate, pindex->pprev) &&
2954+
candidate->IsValid(BLOCK_VALID_TRANSACTIONS) &&
2955+
candidate->HaveTxsDownloaded()) {
2956+
candidate_blocks_by_work.insert(std::make_pair(candidate->nChainWork, candidate));
2957+
}
2958+
}
2959+
}
2960+
29292961
// Disconnect (descendants of) pindex, and mark them invalid.
29302962
while (true) {
29312963
if (ShutdownRequested()) break;
@@ -2968,11 +3000,24 @@ bool CChainState::InvalidateBlock(CValidationState& state, const CChainParams& c
29683000
setDirtyBlockIndex.insert(to_mark_failed);
29693001
}
29703002

3003+
// Add any equal or more work headers to setBlockIndexCandidates
3004+
auto candidate_it = candidate_blocks_by_work.lower_bound(invalid_walk_tip->pprev->nChainWork);
3005+
while (candidate_it != candidate_blocks_by_work.end()) {
3006+
if (!CBlockIndexWorkComparator()(candidate_it->second, invalid_walk_tip->pprev)) {
3007+
setBlockIndexCandidates.insert(candidate_it->second);
3008+
candidate_it = candidate_blocks_by_work.erase(candidate_it);
3009+
} else {
3010+
++candidate_it;
3011+
}
3012+
}
3013+
29713014
// Track the last disconnected block, so we can correct its BLOCK_FAILED_CHILD status in future
29723015
// iterations, or, if it's the last one, call InvalidChainFound on it.
29733016
to_mark_failed = invalid_walk_tip;
29743017
}
29753018

3019+
CheckBlockIndex(chainparams.GetConsensus());
3020+
29763021
{
29773022
LOCK(cs_main);
29783023
if (m_chain.Contains(to_mark_failed)) {
@@ -2986,8 +3031,13 @@ bool CChainState::InvalidateBlock(CValidationState& state, const CChainParams& c
29863031
setBlockIndexCandidates.erase(to_mark_failed);
29873032
m_blockman.m_failed_blocks.insert(to_mark_failed);
29883033

2989-
// The resulting new best tip may not be in setBlockIndexCandidates anymore, so
2990-
// add it again.
3034+
// If any new blocks somehow arrived while we were disconnecting
3035+
// (above), then the pre-calculation of what should go into
3036+
// setBlockIndexCandidates may have missed entries. This would
3037+
// technically be an inconsistency in the block index, but if we clean
3038+
// it up here, this should be an essentially unobservable error.
3039+
// Loop back over all block index entries and add any missing entries
3040+
// to setBlockIndexCandidates.
29913041
BlockMap::iterator it = m_blockman.m_block_index.begin();
29923042
while (it != m_blockman.m_block_index.end()) {
29933043
if (it->second->IsValid(BLOCK_VALID_TRANSACTIONS) && it->second->HaveTxsDownloaded() && !setBlockIndexCandidates.value_comp()(it->second, m_chain.Tip())) {

0 commit comments

Comments
 (0)