Reject headers building on invalid chains by tracking invalidity

This tracks the set of all known invalid-themselves blocks (ie
blocks which we attempted to connect but which were found to be
invalid). This is used to cheaply check if new headers build on an
invalid chain.

While we're at it we also resolve an edge-case in invalidateblock
on pruned nodes which results in them needing a reindex if they
fail to reorg.
This commit is contained in:
Matt Corallo 2017-10-19 16:55:31 -04:00
parent 932f118e6a
commit 015a5258ad

View file

@ -156,6 +156,26 @@ namespace {
/** chainwork for the last block that preciousblock has been applied to. */ /** chainwork for the last block that preciousblock has been applied to. */
arith_uint256 nLastPreciousChainwork = 0; arith_uint256 nLastPreciousChainwork = 0;
/** 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 mapBlockIndex 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 mapBlockIndex 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 alreardy walk mapBlockIndex 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*> g_failed_blocks;
/** Dirty block index entries. */ /** Dirty block index entries. */
std::set<CBlockIndex*> setDirtyBlockIndex; std::set<CBlockIndex*> setDirtyBlockIndex;
@ -1180,6 +1200,7 @@ void static InvalidChainFound(CBlockIndex* pindexNew)
void static InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state) { void static InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state) {
if (!state.CorruptionPossible()) { if (!state.CorruptionPossible()) {
pindex->nStatus |= BLOCK_FAILED_VALID; pindex->nStatus |= BLOCK_FAILED_VALID;
g_failed_blocks.insert(pindex);
setDirtyBlockIndex.insert(pindex); setDirtyBlockIndex.insert(pindex);
setBlockIndexCandidates.erase(pindex); setBlockIndexCandidates.erase(pindex);
InvalidChainFound(pindex); InvalidChainFound(pindex);
@ -2533,17 +2554,18 @@ bool InvalidateBlock(CValidationState& state, const CChainParams& chainparams, C
{ {
AssertLockHeld(cs_main); AssertLockHeld(cs_main);
// Mark the block itself as invalid. // We first disconnect backwards and then mark the blocks as invalid.
pindex->nStatus |= BLOCK_FAILED_VALID; // This prevents a case where pruned nodes may fail to invalidateblock
setDirtyBlockIndex.insert(pindex); // and be left unable to start as they have no tip candidates (as there
setBlockIndexCandidates.erase(pindex); // are no blocks that meet the "have data and are not invalid per
// nStatus" criteria for inclusion in setBlockIndexCandidates).
bool pindex_was_in_chain = false;
CBlockIndex *invalid_walk_tip = chainActive.Tip();
DisconnectedBlockTransactions disconnectpool; DisconnectedBlockTransactions disconnectpool;
while (chainActive.Contains(pindex)) { while (chainActive.Contains(pindex)) {
CBlockIndex *pindexWalk = chainActive.Tip(); pindex_was_in_chain = true;
pindexWalk->nStatus |= BLOCK_FAILED_CHILD;
setDirtyBlockIndex.insert(pindexWalk);
setBlockIndexCandidates.erase(pindexWalk);
// ActivateBestChain considers blocks already in chainActive // ActivateBestChain considers blocks already in chainActive
// unconditionally valid already, so force disconnect away from it. // unconditionally valid already, so force disconnect away from it.
if (!DisconnectTip(state, chainparams, &disconnectpool)) { if (!DisconnectTip(state, chainparams, &disconnectpool)) {
@ -2554,6 +2576,21 @@ bool InvalidateBlock(CValidationState& state, const CChainParams& chainparams, C
} }
} }
// Now mark the blocks we just disconnected as descendants invalid
// (note this may not be all descendants).
while (pindex_was_in_chain && invalid_walk_tip != pindex) {
invalid_walk_tip->nStatus |= BLOCK_FAILED_CHILD;
setDirtyBlockIndex.insert(invalid_walk_tip);
setBlockIndexCandidates.erase(invalid_walk_tip);
invalid_walk_tip = invalid_walk_tip->pprev;
}
// Mark the block itself as invalid.
pindex->nStatus |= BLOCK_FAILED_VALID;
setDirtyBlockIndex.insert(pindex);
setBlockIndexCandidates.erase(pindex);
g_failed_blocks.insert(pindex);
// DisconnectTip will add transactions to disconnectpool; try to add these // DisconnectTip will add transactions to disconnectpool; try to add these
// back to the mempool. // back to the mempool.
UpdateMempoolForReorg(disconnectpool, true); UpdateMempoolForReorg(disconnectpool, true);
@ -2591,6 +2628,7 @@ bool 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.
pindexBestInvalid = nullptr; pindexBestInvalid = nullptr;
} }
g_failed_blocks.erase(it->second);
} }
it++; it++;
} }
@ -3066,6 +3104,21 @@ static bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state
return state.DoS(100, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk"); return state.DoS(100, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk");
if (!ContextualCheckBlockHeader(block, state, chainparams, pindexPrev, GetAdjustedTime())) if (!ContextualCheckBlockHeader(block, state, chainparams, pindexPrev, GetAdjustedTime()))
return error("%s: Consensus::ContextualCheckBlockHeader: %s, %s", __func__, hash.ToString(), FormatStateMessage(state)); return error("%s: Consensus::ContextualCheckBlockHeader: %s, %s", __func__, hash.ToString(), FormatStateMessage(state));
if (!pindexPrev->IsValid(BLOCK_VALID_SCRIPTS)) {
for (const CBlockIndex* failedit : g_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;
setDirtyBlockIndex.insert(invalid_walk);
invalid_walk = invalid_walk->pprev;
}
return state.DoS(100, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk");
}
}
}
} }
if (pindex == nullptr) if (pindex == nullptr)
pindex = AddToBlockIndex(block); pindex = AddToBlockIndex(block);
@ -3492,6 +3545,10 @@ bool static LoadBlockIndexDB(const CChainParams& chainparams)
pindex->nChainTx = pindex->nTx; pindex->nChainTx = pindex->nTx;
} }
} }
if (!(pindex->nStatus & BLOCK_FAILED_MASK) && pindex->pprev && (pindex->pprev->nStatus & BLOCK_FAILED_MASK)) {
pindex->nStatus |= BLOCK_FAILED_CHILD;
setDirtyBlockIndex.insert(pindex);
}
if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && (pindex->nChainTx || pindex->pprev == nullptr)) if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && (pindex->nChainTx || pindex->pprev == nullptr))
setBlockIndexCandidates.insert(pindex); setBlockIndexCandidates.insert(pindex);
if (pindex->nStatus & BLOCK_FAILED_MASK && (!pindexBestInvalid || pindex->nChainWork > pindexBestInvalid->nChainWork)) if (pindex->nStatus & BLOCK_FAILED_MASK && (!pindexBestInvalid || pindex->nChainWork > pindexBestInvalid->nChainWork))
@ -3882,6 +3939,7 @@ void UnloadBlockIndex()
nLastBlockFile = 0; nLastBlockFile = 0;
nBlockSequenceId = 1; nBlockSequenceId = 1;
setDirtyBlockIndex.clear(); setDirtyBlockIndex.clear();
g_failed_blocks.clear();
setDirtyFileInfo.clear(); setDirtyFileInfo.clear();
versionbitscache.Clear(); versionbitscache.Clear();
for (int b = 0; b < VERSIONBITS_NUM_BITS; b++) { for (int b = 0; b < VERSIONBITS_NUM_BITS; b++) {