Merge #16849: Fix block index inconsistency in InvalidateBlock()

2a4e60b48261d3f0ec3d85f97af998ef989134e0 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 2a4e60b48261d3f0ec3d85f97af998ef989134e0. 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
This commit is contained in:
Wladimir J. van der Laan 2019-10-02 13:39:34 +02:00 committed by UdjinM6
parent 6f0646a262
commit fba2a546f6
No known key found for this signature in database
GPG Key ID: 83592BD1400D58D9

View File

@ -3191,6 +3191,38 @@ bool CChainState::InvalidateBlock(CValidationState& state, const CChainParams& c
bool pindex_was_in_chain = false; bool pindex_was_in_chain = false;
int disconnected = 0; int disconnected = 0;
// We do not allow ActivateBestChain() to run while InvalidateBlock() is
// running, as that could cause the tip to change while we disconnect
// blocks.
LOCK(m_cs_chainstate);
// We'll be acquiring and releasing cs_main below, to allow the validation
// callbacks to run. However, we should keep the block index in a
// consistent state as we disconnect blocks -- in particular we need to
// add equal-work blocks to setBlockIndexCandidates as we disconnect.
// To avoid walking the block index repeatedly in search of candidates,
// build a map once so that we can look up candidate blocks by chain
// work as we go.
std::multimap<const arith_uint256, CBlockIndex *> candidate_blocks_by_work;
{
LOCK(cs_main);
for (const auto& entry : m_blockman.m_block_index) {
CBlockIndex *candidate = entry.second;
// We don't need to put anything in our active chain into the
// multimap, because those candidates will be found and considered
// as we disconnect.
// Instead, consider only non-active-chain blocks that have at
// least as much work as where we expect the new tip to end up.
if (!m_chain.Contains(candidate) &&
!CBlockIndexWorkComparator()(candidate, pindex->pprev) &&
candidate->IsValid(BLOCK_VALID_TRANSACTIONS) &&
candidate->HaveTxsDownloaded()) {
candidate_blocks_by_work.insert(std::make_pair(candidate->nChainWork, candidate));
}
}
}
// Disconnect (descendants of) pindex, and mark them invalid. // Disconnect (descendants of) pindex, and mark them invalid.
while (true) { while (true) {
if (ShutdownRequested()) break; if (ShutdownRequested()) break;
@ -3243,11 +3275,24 @@ bool CChainState::InvalidateBlock(CValidationState& state, const CChainParams& c
setDirtyBlockIndex.insert(to_mark_failed); setDirtyBlockIndex.insert(to_mark_failed);
} }
// Add any equal or more work headers to setBlockIndexCandidates
auto candidate_it = candidate_blocks_by_work.lower_bound(invalid_walk_tip->pprev->nChainWork);
while (candidate_it != candidate_blocks_by_work.end()) {
if (!CBlockIndexWorkComparator()(candidate_it->second, invalid_walk_tip->pprev)) {
setBlockIndexCandidates.insert(candidate_it->second);
candidate_it = candidate_blocks_by_work.erase(candidate_it);
} else {
++candidate_it;
}
}
// Track the last disconnected block, so we can correct its BLOCK_FAILED_CHILD status in future // Track the last disconnected block, so we can correct its BLOCK_FAILED_CHILD status in future
// iterations, or, if it's the last one, call InvalidChainFound on it. // iterations, or, if it's the last one, call InvalidChainFound on it.
to_mark_failed = invalid_walk_tip; to_mark_failed = invalid_walk_tip;
} }
CheckBlockIndex(chainparams.GetConsensus());
{ {
LOCK(cs_main); LOCK(cs_main);
if (m_chain.Contains(to_mark_failed)) { if (m_chain.Contains(to_mark_failed)) {
@ -3261,8 +3306,13 @@ bool CChainState::InvalidateBlock(CValidationState& state, const CChainParams& c
setBlockIndexCandidates.erase(to_mark_failed); setBlockIndexCandidates.erase(to_mark_failed);
m_blockman.m_failed_blocks.insert(to_mark_failed); m_blockman.m_failed_blocks.insert(to_mark_failed);
// The resulting new best tip may not be in setBlockIndexCandidates anymore, so // If any new blocks somehow arrived while we were disconnecting
// add it again. // (above), then the pre-calculation of what should go into
// setBlockIndexCandidates may have missed entries. This would
// technically be an inconsistency in the block index, but if we clean
// it up here, this should be an essentially unobservable error.
// Loop back over all block index entries and add any missing entries
// to setBlockIndexCandidates.
BlockMap::iterator it = g_blockman.m_block_index.begin(); BlockMap::iterator it = g_blockman.m_block_index.begin();
while (it != g_blockman.m_block_index.end()) { while (it != g_blockman.m_block_index.end()) {
if (it->second->IsValid(BLOCK_VALID_TRANSACTIONS) && !(it->second->nStatus & BLOCK_CONFLICT_CHAINLOCK) && it->second->HaveTxsDownloaded() && !setBlockIndexCandidates.value_comp()(it->second, m_chain.Tip())) { if (it->second->IsValid(BLOCK_VALID_TRANSACTIONS) && !(it->second->nStatus & BLOCK_CONFLICT_CHAINLOCK) && it->second->HaveTxsDownloaded() && !setBlockIndexCandidates.value_comp()(it->second, m_chain.Tip())) {