Skip to content

Commit cffa5ee

Browse files
committed
Merge #11531: Check that new headers are not a descendant of an invalid block (more effeciently)
f3d4adf Make p2p-acceptablock not an extended test (Matt Corallo) 00dcda6 [qa] test that invalid blocks on an invalid chain get a disconnect (Matt Corallo) 015a525 Reject headers building on invalid chains by tracking invalidity (Matt Corallo) 932f118 Accept unrequested blocks with work equal to our tip (Matt Corallo) 3d9c70c Stop always storing blocks from whitelisted peers (Matt Corallo) 3b4ac43 Rewrite p2p-acceptblock in preparation for slight behavior changes (Matt Corallo) Pull request description: @sdaftuar pointed out that the version in #11487 was somewhat DoS-able as someone could feed you a valid chain that forked off the the last checkpoint block and force you to do lots of work just walking backwards across blocks for each new block they gave you. We came up with a few proposals but settled on the one implemented here as likely the simplest without obvious DoS issues. It uses our existing on-load mapBlockIndex walk to make sure everything that descends from an invalid block is marked as such, and then simply caches blocks which we attempted to connect but which were found to be invalid. To avoid DoS issues during IBD, this will need to depend on #11458. Includes tests from #11487. Tree-SHA512: 46aff8332908e122dae72ceb5fe8cd241902c2281a87f58a5fb486bf69d46458d84a096fdcb5f3e8e07fbcf7466232b10c429f4d67855425f11b38ac0bf612e1
2 parents db2f83e + f3d4adf commit cffa5ee

File tree

4 files changed

+269
-134
lines changed

4 files changed

+269
-134
lines changed

src/net_processing.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2531,11 +2531,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
25312531

25322532
LogPrint(BCLog::NET, "received block %s peer=%d\n", pblock->GetHash().ToString(), pfrom->GetId());
25332533

2534-
// Process all blocks from whitelisted peers, even if not requested,
2535-
// unless we're still syncing with the network.
2536-
// Such an unrequested block may still be processed, subject to the
2537-
// conditions in AcceptBlock().
2538-
bool forceProcessing = pfrom->fWhitelisted && !IsInitialBlockDownload();
2534+
bool forceProcessing = false;
25392535
const uint256 hash(pblock->GetHash());
25402536
{
25412537
LOCK(cs_main);

src/validation.cpp

Lines changed: 70 additions & 12 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);
@@ -3117,7 +3170,7 @@ static bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CValidation
31173170
// process an unrequested block if it's new and has enough work to
31183171
// advance our tip, and isn't too many blocks ahead.
31193172
bool fAlreadyHave = pindex->nStatus & BLOCK_HAVE_DATA;
3120-
bool fHasMoreWork = (chainActive.Tip() ? pindex->nChainWork > chainActive.Tip()->nChainWork : true);
3173+
bool fHasMoreOrSameWork = (chainActive.Tip() ? pindex->nChainWork >= chainActive.Tip()->nChainWork : true);
31213174
// Blocks that are too out-of-order needlessly limit the effectiveness of
31223175
// pruning, because pruning will not delete block files that contain any
31233176
// blocks which are too close in height to the tip. Apply this test
@@ -3134,9 +3187,9 @@ static bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CValidation
31343187
// and unrequested blocks.
31353188
if (fAlreadyHave) return true;
31363189
if (!fRequested) { // If we didn't ask for it:
3137-
if (pindex->nTx != 0) return true; // This is a previously-processed block that was pruned
3138-
if (!fHasMoreWork) return true; // Don't process less-work chains
3139-
if (fTooFarAhead) return true; // Block height is too high
3190+
if (pindex->nTx != 0) return true; // This is a previously-processed block that was pruned
3191+
if (!fHasMoreOrSameWork) return true; // Don't process less-work chains
3192+
if (fTooFarAhead) return true; // Block height is too high
31403193

31413194
// Protect against DoS attacks from low-work chains.
31423195
// If our tip is behind, a peer could try to send us
@@ -3494,6 +3547,10 @@ bool static LoadBlockIndexDB(const CChainParams& chainparams)
34943547
pindex->nChainTx = pindex->nTx;
34953548
}
34963549
}
3550+
if (!(pindex->nStatus & BLOCK_FAILED_MASK) && pindex->pprev && (pindex->pprev->nStatus & BLOCK_FAILED_MASK)) {
3551+
pindex->nStatus |= BLOCK_FAILED_CHILD;
3552+
setDirtyBlockIndex.insert(pindex);
3553+
}
34973554
if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && (pindex->nChainTx || pindex->pprev == nullptr))
34983555
setBlockIndexCandidates.insert(pindex);
34993556
if (pindex->nStatus & BLOCK_FAILED_MASK && (!pindexBestInvalid || pindex->nChainWork > pindexBestInvalid->nChainWork))
@@ -3884,6 +3941,7 @@ void UnloadBlockIndex()
38843941
nLastBlockFile = 0;
38853942
nBlockSequenceId = 1;
38863943
setDirtyBlockIndex.clear();
3944+
g_failed_blocks.clear();
38873945
setDirtyFileInfo.clear();
38883946
versionbitscache.Clear();
38893947
for (int b = 0; b < VERSIONBITS_NUM_BITS; b++) {

0 commit comments

Comments
 (0)