Skip to content

Commit 2a4e60b

Browse files
committed
Fix block index inconsistency in InvalidateBlock()
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.
1 parent 1985c4e commit 2a4e60b

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
@@ -2778,6 +2778,38 @@ bool CChainState::InvalidateBlock(CValidationState& state, const CChainParams& c
27782778
bool pindex_was_in_chain = false;
27792779
int disconnected = 0;
27802780

2781+
// We do not allow ActivateBestChain() to run while InvalidateBlock() is
2782+
// running, as that could cause the tip to change while we disconnect
2783+
// blocks.
2784+
LOCK(m_cs_chainstate);
2785+
2786+
// We'll be acquiring and releasing cs_main below, to allow the validation
2787+
// callbacks to run. However, we should keep the block index in a
2788+
// consistent state as we disconnect blocks -- in particular we need to
2789+
// add equal-work blocks to setBlockIndexCandidates as we disconnect.
2790+
// To avoid walking the block index repeatedly in search of candidates,
2791+
// build a map once so that we can look up candidate blocks by chain
2792+
// work as we go.
2793+
std::multimap<const arith_uint256, CBlockIndex *> candidate_blocks_by_work;
2794+
2795+
{
2796+
LOCK(cs_main);
2797+
for (const auto& entry : m_blockman.m_block_index) {
2798+
CBlockIndex *candidate = entry.second;
2799+
// We don't need to put anything in our active chain into the
2800+
// multimap, because those candidates will be found and considered
2801+
// as we disconnect.
2802+
// Instead, consider only non-active-chain blocks that have at
2803+
// least as much work as where we expect the new tip to end up.
2804+
if (!m_chain.Contains(candidate) &&
2805+
!CBlockIndexWorkComparator()(candidate, pindex->pprev) &&
2806+
candidate->IsValid(BLOCK_VALID_TRANSACTIONS) &&
2807+
candidate->HaveTxsDownloaded()) {
2808+
candidate_blocks_by_work.insert(std::make_pair(candidate->nChainWork, candidate));
2809+
}
2810+
}
2811+
}
2812+
27812813
// Disconnect (descendants of) pindex, and mark them invalid.
27822814
while (true) {
27832815
if (ShutdownRequested()) break;
@@ -2820,11 +2852,24 @@ bool CChainState::InvalidateBlock(CValidationState& state, const CChainParams& c
28202852
setDirtyBlockIndex.insert(to_mark_failed);
28212853
}
28222854

2855+
// Add any equal or more work headers to setBlockIndexCandidates
2856+
auto candidate_it = candidate_blocks_by_work.lower_bound(invalid_walk_tip->pprev->nChainWork);
2857+
while (candidate_it != candidate_blocks_by_work.end()) {
2858+
if (!CBlockIndexWorkComparator()(candidate_it->second, invalid_walk_tip->pprev)) {
2859+
setBlockIndexCandidates.insert(candidate_it->second);
2860+
candidate_it = candidate_blocks_by_work.erase(candidate_it);
2861+
} else {
2862+
++candidate_it;
2863+
}
2864+
}
2865+
28232866
// Track the last disconnected block, so we can correct its BLOCK_FAILED_CHILD status in future
28242867
// iterations, or, if it's the last one, call InvalidChainFound on it.
28252868
to_mark_failed = invalid_walk_tip;
28262869
}
28272870

2871+
CheckBlockIndex(chainparams.GetConsensus());
2872+
28282873
{
28292874
LOCK(cs_main);
28302875
if (m_chain.Contains(to_mark_failed)) {
@@ -2838,8 +2883,13 @@ bool CChainState::InvalidateBlock(CValidationState& state, const CChainParams& c
28382883
setBlockIndexCandidates.erase(to_mark_failed);
28392884
m_blockman.m_failed_blocks.insert(to_mark_failed);
28402885

2841-
// The resulting new best tip may not be in setBlockIndexCandidates anymore, so
2842-
// add it again.
2886+
// If any new blocks somehow arrived while we were disconnecting
2887+
// (above), then the pre-calculation of what should go into
2888+
// setBlockIndexCandidates may have missed entries. This would
2889+
// technically be an inconsistency in the block index, but if we clean
2890+
// it up here, this should be an essentially unobservable error.
2891+
// Loop back over all block index entries and add any missing entries
2892+
// to setBlockIndexCandidates.
28432893
BlockMap::iterator it = m_blockman.m_block_index.begin();
28442894
while (it != m_blockman.m_block_index.end()) {
28452895
if (it->second->IsValid(BLOCK_VALID_TRANSACTIONS) && it->second->HaveTxsDownloaded() && !setBlockIndexCandidates.value_comp()(it->second, m_chain.Tip())) {

0 commit comments

Comments
 (0)