Skip to content

Commit 015a525

Browse files
committed
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.
1 parent 932f118 commit 015a525

File tree

1 file changed

+66
-8
lines changed

1 file changed

+66
-8
lines changed

src/validation.cpp

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,26 @@ namespace {
156156
/** chainwork for the last block that preciousblock has been applied to. */
157157
arith_uint256 nLastPreciousChainwork = 0;
158158

159+
/** In order to efficiently track invalidity of headers, we keep the set of
160+
* blocks which we tried to connect and found to be invalid here (ie which
161+
* were set to BLOCK_FAILED_VALID since the last restart). We can then
162+
* walk this set and check if a new header is a descendant of something in
163+
* this set, preventing us from having to walk mapBlockIndex when we try
164+
* to connect a bad block and fail.
165+
*
166+
* While this is more complicated than marking everything which descends
167+
* from an invalid block as invalid at the time we discover it to be
168+
* invalid, doing so would require walking all of mapBlockIndex to find all
169+
* descendants. Since this case should be very rare, keeping track of all
170+
* BLOCK_FAILED_VALID blocks in a set should be just fine and work just as
171+
* well.
172+
*
173+
* Because we alreardy walk mapBlockIndex in height-order at startup, we go
174+
* ahead and mark descendants of invalid blocks as FAILED_CHILD at that time,
175+
* instead of putting things in this set.
176+
*/
177+
std::set<CBlockIndex*> g_failed_blocks;
178+
159179
/** Dirty block index entries. */
160180
std::set<CBlockIndex*> setDirtyBlockIndex;
161181

@@ -1180,6 +1200,7 @@ void static InvalidChainFound(CBlockIndex* pindexNew)
11801200
void static InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state) {
11811201
if (!state.CorruptionPossible()) {
11821202
pindex->nStatus |= BLOCK_FAILED_VALID;
1203+
g_failed_blocks.insert(pindex);
11831204
setDirtyBlockIndex.insert(pindex);
11841205
setBlockIndexCandidates.erase(pindex);
11851206
InvalidChainFound(pindex);
@@ -2533,17 +2554,18 @@ bool InvalidateBlock(CValidationState& state, const CChainParams& chainparams, C
25332554
{
25342555
AssertLockHeld(cs_main);
25352556

2536-
// Mark the block itself as invalid.
2537-
pindex->nStatus |= BLOCK_FAILED_VALID;
2538-
setDirtyBlockIndex.insert(pindex);
2539-
setBlockIndexCandidates.erase(pindex);
2557+
// We first disconnect backwards and then mark the blocks as invalid.
2558+
// This prevents a case where pruned nodes may fail to invalidateblock
2559+
// and be left unable to start as they have no tip candidates (as there
2560+
// are no blocks that meet the "have data and are not invalid per
2561+
// nStatus" criteria for inclusion in setBlockIndexCandidates).
2562+
2563+
bool pindex_was_in_chain = false;
2564+
CBlockIndex *invalid_walk_tip = chainActive.Tip();
25402565

25412566
DisconnectedBlockTransactions disconnectpool;
25422567
while (chainActive.Contains(pindex)) {
2543-
CBlockIndex *pindexWalk = chainActive.Tip();
2544-
pindexWalk->nStatus |= BLOCK_FAILED_CHILD;
2545-
setDirtyBlockIndex.insert(pindexWalk);
2546-
setBlockIndexCandidates.erase(pindexWalk);
2568+
pindex_was_in_chain = true;
25472569
// ActivateBestChain considers blocks already in chainActive
25482570
// unconditionally valid already, so force disconnect away from it.
25492571
if (!DisconnectTip(state, chainparams, &disconnectpool)) {
@@ -2554,6 +2576,21 @@ bool InvalidateBlock(CValidationState& state, const CChainParams& chainparams, C
25542576
}
25552577
}
25562578

2579+
// Now mark the blocks we just disconnected as descendants invalid
2580+
// (note this may not be all descendants).
2581+
while (pindex_was_in_chain && invalid_walk_tip != pindex) {
2582+
invalid_walk_tip->nStatus |= BLOCK_FAILED_CHILD;
2583+
setDirtyBlockIndex.insert(invalid_walk_tip);
2584+
setBlockIndexCandidates.erase(invalid_walk_tip);
2585+
invalid_walk_tip = invalid_walk_tip->pprev;
2586+
}
2587+
2588+
// Mark the block itself as invalid.
2589+
pindex->nStatus |= BLOCK_FAILED_VALID;
2590+
setDirtyBlockIndex.insert(pindex);
2591+
setBlockIndexCandidates.erase(pindex);
2592+
g_failed_blocks.insert(pindex);
2593+
25572594
// DisconnectTip will add transactions to disconnectpool; try to add these
25582595
// back to the mempool.
25592596
UpdateMempoolForReorg(disconnectpool, true);
@@ -2591,6 +2628,7 @@ bool ResetBlockFailureFlags(CBlockIndex *pindex) {
25912628
// Reset invalid block marker if it was pointing to one of those.
25922629
pindexBestInvalid = nullptr;
25932630
}
2631+
g_failed_blocks.erase(it->second);
25942632
}
25952633
it++;
25962634
}
@@ -3066,6 +3104,21 @@ static bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state
30663104
return state.DoS(100, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk");
30673105
if (!ContextualCheckBlockHeader(block, state, chainparams, pindexPrev, GetAdjustedTime()))
30683106
return error("%s: Consensus::ContextualCheckBlockHeader: %s, %s", __func__, hash.ToString(), FormatStateMessage(state));
3107+
3108+
if (!pindexPrev->IsValid(BLOCK_VALID_SCRIPTS)) {
3109+
for (const CBlockIndex* failedit : g_failed_blocks) {
3110+
if (pindexPrev->GetAncestor(failedit->nHeight) == failedit) {
3111+
assert(failedit->nStatus & BLOCK_FAILED_VALID);
3112+
CBlockIndex* invalid_walk = pindexPrev;
3113+
while (invalid_walk != failedit) {
3114+
invalid_walk->nStatus |= BLOCK_FAILED_CHILD;
3115+
setDirtyBlockIndex.insert(invalid_walk);
3116+
invalid_walk = invalid_walk->pprev;
3117+
}
3118+
return state.DoS(100, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk");
3119+
}
3120+
}
3121+
}
30693122
}
30703123
if (pindex == nullptr)
30713124
pindex = AddToBlockIndex(block);
@@ -3492,6 +3545,10 @@ bool static LoadBlockIndexDB(const CChainParams& chainparams)
34923545
pindex->nChainTx = pindex->nTx;
34933546
}
34943547
}
3548+
if (!(pindex->nStatus & BLOCK_FAILED_MASK) && pindex->pprev && (pindex->pprev->nStatus & BLOCK_FAILED_MASK)) {
3549+
pindex->nStatus |= BLOCK_FAILED_CHILD;
3550+
setDirtyBlockIndex.insert(pindex);
3551+
}
34953552
if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && (pindex->nChainTx || pindex->pprev == nullptr))
34963553
setBlockIndexCandidates.insert(pindex);
34973554
if (pindex->nStatus & BLOCK_FAILED_MASK && (!pindexBestInvalid || pindex->nChainWork > pindexBestInvalid->nChainWork))
@@ -3882,6 +3939,7 @@ void UnloadBlockIndex()
38823939
nLastBlockFile = 0;
38833940
nBlockSequenceId = 1;
38843941
setDirtyBlockIndex.clear();
3942+
g_failed_blocks.clear();
38853943
setDirtyFileInfo.clear();
38863944
versionbitscache.Clear();
38873945
for (int b = 0; b < VERSIONBITS_NUM_BITS; b++) {

0 commit comments

Comments
 (0)