This commit is contained in:
Martin Zumsande 2025-04-29 11:49:19 +02:00 committed by GitHub
commit ddd788d1a8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 39 additions and 77 deletions

View file

@ -2091,7 +2091,6 @@ void Chainstate::InvalidBlockFound(CBlockIndex* pindex, const BlockValidationSta
AssertLockHeld(cs_main); AssertLockHeld(cs_main);
if (state.GetResult() != BlockValidationResult::BLOCK_MUTATED) { if (state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {
pindex->nStatus |= BLOCK_FAILED_VALID; pindex->nStatus |= BLOCK_FAILED_VALID;
m_chainman.m_failed_blocks.insert(pindex);
m_blockman.m_dirty_blockindex.insert(pindex); m_blockman.m_dirty_blockindex.insert(pindex);
setBlockIndexCandidates.erase(pindex); setBlockIndexCandidates.erase(pindex);
InvalidChainFound(pindex); InvalidChainFound(pindex);
@ -3690,7 +3689,7 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde
// To avoid walking the block index repeatedly in search of candidates, // 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 // build a map once so that we can look up candidate blocks by chain
// work as we go. // work as we go.
std::multimap<const arith_uint256, CBlockIndex *> candidate_blocks_by_work; std::multimap<const arith_uint256, CBlockIndex*> highpow_outofchain_headers;
{ {
LOCK(cs_main); LOCK(cs_main);
@ -3703,9 +3702,8 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde
// least as much work as where we expect the new tip to end up. // least as much work as where we expect the new tip to end up.
if (!m_chain.Contains(candidate) && if (!m_chain.Contains(candidate) &&
!CBlockIndexWorkComparator()(candidate, pindex->pprev) && !CBlockIndexWorkComparator()(candidate, pindex->pprev) &&
candidate->IsValid(BLOCK_VALID_TRANSACTIONS) && !(candidate->nStatus & BLOCK_FAILED_MASK)) {
candidate->HaveNumChainTxs()) { highpow_outofchain_headers.insert(std::make_pair(candidate->nChainWork, candidate));
candidate_blocks_by_work.insert(std::make_pair(candidate->nChainWork, candidate));
} }
} }
} }
@ -3716,6 +3714,7 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde
// Make sure the queue of validation callbacks doesn't grow unboundedly. // Make sure the queue of validation callbacks doesn't grow unboundedly.
if (m_chainman.m_options.signals) LimitValidationInterfaceQueue(*m_chainman.m_options.signals); if (m_chainman.m_options.signals) LimitValidationInterfaceQueue(*m_chainman.m_options.signals);
m_chainman.CheckBlockIndex();
LOCK(cs_main); LOCK(cs_main);
// Lock for as long as disconnectpool is in scope to make sure MaybeUpdateMempoolForReorg is // Lock for as long as disconnectpool is in scope to make sure MaybeUpdateMempoolForReorg is
@ -3754,15 +3753,35 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde
m_blockman.m_dirty_blockindex.insert(to_mark_failed); m_blockman.m_dirty_blockindex.insert(to_mark_failed);
} }
// Add any equal or more work headers to setBlockIndexCandidates // Mark descendants of the invalidated block as invalid
auto candidate_it = candidate_blocks_by_work.lower_bound(invalid_walk_tip->pprev->nChainWork); // (possibly replacing a pre-existing BLOCK_FAILED_VALID with BLOCK_FAILED_CHILD)
while (candidate_it != candidate_blocks_by_work.end()) { // Add any equal or more work headers that are not invalidated to setBlockIndexCandidates
if (!CBlockIndexWorkComparator()(candidate_it->second, invalid_walk_tip->pprev)) { // Recalculate the best header if it became invalid.
setBlockIndexCandidates.insert(candidate_it->second); auto candidate_it = highpow_outofchain_headers.lower_bound(invalid_walk_tip->pprev->nChainWork);
candidate_it = candidate_blocks_by_work.erase(candidate_it);
} else { const bool best_header_needs_update{m_chainman.m_best_header->GetAncestor(invalid_walk_tip->nHeight) == invalid_walk_tip};
++candidate_it; if (best_header_needs_update) {
m_chainman.m_best_header = invalid_walk_tip->pprev; // this block is definitely still valid at this point, but there may be better ones
} }
while (candidate_it != highpow_outofchain_headers.end()) {
if (candidate_it->second->GetAncestor(invalid_walk_tip->nHeight) == invalid_walk_tip) {
candidate_it->second->nStatus = (candidate_it->second->nStatus & ~BLOCK_FAILED_VALID) | BLOCK_FAILED_CHILD;
m_blockman.m_dirty_blockindex.insert(candidate_it->second);
// If invalidated, the block is irrelevant for setBlockIndexCandidates and m_best_header and can be removed from the cache
candidate_it = highpow_outofchain_headers.erase(candidate_it);
continue;
}
if (!CBlockIndexWorkComparator()(candidate_it->second, invalid_walk_tip->pprev) &&
candidate_it->second->IsValid(BLOCK_VALID_TRANSACTIONS) &&
candidate_it->second->HaveNumChainTxs()) {
setBlockIndexCandidates.insert(candidate_it->second);
}
if (best_header_needs_update &&
!CBlockIndexWorkComparator()(candidate_it->second, m_chainman.m_best_header)) {
m_chainman.m_best_header = candidate_it->second;
}
++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
@ -3784,7 +3803,6 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde
pindex->nStatus |= BLOCK_FAILED_VALID; pindex->nStatus |= BLOCK_FAILED_VALID;
m_blockman.m_dirty_blockindex.insert(pindex); m_blockman.m_dirty_blockindex.insert(pindex);
setBlockIndexCandidates.erase(pindex); setBlockIndexCandidates.erase(pindex);
m_chainman.m_failed_blocks.insert(pindex);
} }
// If any new blocks somehow arrived while we were disconnecting // If any new blocks somehow arrived while we were disconnecting
@ -3852,7 +3870,6 @@ void Chainstate::ResetBlockFailureFlags(CBlockIndex *pindex) {
// Reset invalid block marker if it was pointing to one of those. // Reset invalid block marker if it was pointing to one of those.
m_chainman.m_best_invalid = nullptr; m_chainman.m_best_invalid = nullptr;
} }
m_chainman.m_failed_blocks.erase(&block_index);
} }
} }
@ -3861,7 +3878,6 @@ void Chainstate::ResetBlockFailureFlags(CBlockIndex *pindex) {
if (pindex->nStatus & BLOCK_FAILED_MASK) { if (pindex->nStatus & BLOCK_FAILED_MASK) {
pindex->nStatus &= ~BLOCK_FAILED_MASK; pindex->nStatus &= ~BLOCK_FAILED_MASK;
m_blockman.m_dirty_blockindex.insert(pindex); m_blockman.m_dirty_blockindex.insert(pindex);
m_chainman.m_failed_blocks.erase(pindex);
} }
pindex = pindex->pprev; pindex = pindex->pprev;
} }
@ -4357,45 +4373,6 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida
LogDebug(BCLog::VALIDATION, "%s: Consensus::ContextualCheckBlockHeader: %s, %s\n", __func__, hash.ToString(), state.ToString()); LogDebug(BCLog::VALIDATION, "%s: Consensus::ContextualCheckBlockHeader: %s, %s\n", __func__, hash.ToString(), state.ToString());
return false; return false;
} }
/* Determine if this block descends from any block which has been found
* invalid (m_failed_blocks), then mark pindexPrev and any blocks between
* them as failed. For example:
*
* D3
* /
* B2 - C2
* / \
* A D2 - E2 - F2
* \
* B1 - C1 - D1 - E1
*
* In the case that we attempted to reorg from E1 to F2, only to find
* C2 to be invalid, we would mark D2, E2, and F2 as BLOCK_FAILED_CHILD
* but NOT D3 (it was not in any of our candidate sets at the time).
*
* In any case D3 will also be marked as BLOCK_FAILED_CHILD at restart
* in LoadBlockIndex.
*/
if (!pindexPrev->IsValid(BLOCK_VALID_SCRIPTS)) {
// The above does not mean "invalid": it checks if the previous block
// hasn't been validated up to BLOCK_VALID_SCRIPTS. This is a performance
// optimization, in the common case of adding a new block to the tip,
// we don't need to iterate over the failed blocks list.
for (const CBlockIndex* failedit : m_failed_blocks) {
if (pindexPrev->GetAncestor(failedit->nHeight) == failedit) {
assert(failedit->nStatus & BLOCK_FAILED_VALID);
CBlockIndex* invalid_walk = pindexPrev;
while (invalid_walk != failedit) {
invalid_walk->nStatus |= BLOCK_FAILED_CHILD;
m_blockman.m_dirty_blockindex.insert(invalid_walk);
invalid_walk = invalid_walk->pprev;
}
LogDebug(BCLog::VALIDATION, "header %s has prev block invalid: %s\n", hash.ToString(), block.hashPrevBlock.ToString());
return state.Invalid(BlockValidationResult::BLOCK_INVALID_PREV, "bad-prevblk");
}
}
}
} }
if (!min_pow_checked) { if (!min_pow_checked) {
LogDebug(BCLog::VALIDATION, "%s: not adding new block header %s, missing anti-dos proof-of-work validation\n", __func__, hash.ToString()); LogDebug(BCLog::VALIDATION, "%s: not adding new block header %s, missing anti-dos proof-of-work validation\n", __func__, hash.ToString());
@ -4540,6 +4517,7 @@ bool ChainstateManager::AcceptBlock(const std::shared_ptr<const CBlock>& pblock,
if (state.IsInvalid() && state.GetResult() != BlockValidationResult::BLOCK_MUTATED) { if (state.IsInvalid() && state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {
pindex->nStatus |= BLOCK_FAILED_VALID; pindex->nStatus |= BLOCK_FAILED_VALID;
m_blockman.m_dirty_blockindex.insert(pindex); m_blockman.m_dirty_blockindex.insert(pindex);
ActiveChainstate().InvalidChainFound(pindex);
} }
LogError("%s: %s\n", __func__, state.ToString()); LogError("%s: %s\n", __func__, state.ToString());
return false; return false;
@ -5280,6 +5258,7 @@ void ChainstateManager::CheckBlockIndex()
// are not yet validated. // are not yet validated.
CChain best_hdr_chain; CChain best_hdr_chain;
assert(m_best_header); assert(m_best_header);
assert(!(m_best_header->nStatus & BLOCK_FAILED_MASK));
best_hdr_chain.SetTip(*m_best_header); best_hdr_chain.SetTip(*m_best_header);
std::multimap<CBlockIndex*,CBlockIndex*> forward; std::multimap<CBlockIndex*,CBlockIndex*> forward;
@ -5393,6 +5372,8 @@ void ChainstateManager::CheckBlockIndex()
if (pindexFirstInvalid == nullptr) { if (pindexFirstInvalid == nullptr) {
// Checks for not-invalid blocks. // Checks for not-invalid blocks.
assert((pindex->nStatus & BLOCK_FAILED_MASK) == 0); // The failed mask cannot be set for blocks without invalid parents. assert((pindex->nStatus & BLOCK_FAILED_MASK) == 0); // The failed mask cannot be set for blocks without invalid parents.
} else {
assert(pindex->nStatus & BLOCK_FAILED_MASK); // Invalid blocks and their descendants must be marked as invalid
} }
// Make sure m_chain_tx_count sum is correctly computed. // Make sure m_chain_tx_count sum is correctly computed.
if (!pindex->pprev) { if (!pindex->pprev) {
@ -5406,6 +5387,8 @@ void ChainstateManager::CheckBlockIndex()
// block, and must be set if it is. // block, and must be set if it is.
assert((pindex->m_chain_tx_count != 0) == (pindex == snap_base)); assert((pindex->m_chain_tx_count != 0) == (pindex == snap_base));
} }
// There should be no block with more work than m_best_header, unless it's known to be invalid
assert((pindex->nStatus & BLOCK_FAILED_MASK) || pindex->nChainWork <= m_best_header->nChainWork);
// Chainstate-specific checks on setBlockIndexCandidates // Chainstate-specific checks on setBlockIndexCandidates
for (auto c : GetAll()) { for (auto c : GetAll()) {

View file

@ -1038,27 +1038,6 @@ public:
} }
/**
* In order to efficiently track invalidity of headers, we keep the set of
* blocks which we tried to connect and found to be invalid here (ie which
* were set to BLOCK_FAILED_VALID since the last restart). We can then
* walk this set and check if a new header is a descendant of something in
* this set, preventing us from having to walk m_block_index when we try
* to connect a bad block and fail.
*
* While this is more complicated than marking everything which descends
* from an invalid block as invalid at the time we discover it to be
* invalid, doing so would require walking all of m_block_index to find all
* descendants. Since this case should be very rare, keeping track of all
* BLOCK_FAILED_VALID blocks in a set should be just fine and work just as
* well.
*
* Because we already walk m_block_index in height-order at startup, we go
* ahead and mark descendants of invalid blocks as FAILED_CHILD at that time,
* instead of putting things in this set.
*/
std::set<CBlockIndex*> m_failed_blocks;
/** Best header we've seen so far (used for getheaders queries' starting points). */ /** Best header we've seen so far (used for getheaders queries' starting points). */
CBlockIndex* m_best_header GUARDED_BY(::cs_main){nullptr}; CBlockIndex* m_best_header GUARDED_BY(::cs_main){nullptr};