Skip to content

Commit 3e8a1f2

Browse files
committed
Merge pull request #5900
3fcfbc8 Add a consistency check for the block chain data structures (Pieter Wuille)
2 parents f7dea1c + 3fcfbc8 commit 3e8a1f2

File tree

6 files changed

+153
-12
lines changed

6 files changed

+153
-12
lines changed

src/chainparams.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ class CMainParams : public CChainParams {
167167

168168
fRequireRPCPassword = true;
169169
fMiningRequiresPeers = true;
170-
fDefaultCheckMemPool = false;
170+
fDefaultConsistencyChecks = false;
171171
fRequireStandard = true;
172172
fMineBlocksOnDemand = false;
173173
fTestnetToBeDeprecatedFieldRPC = false;
@@ -222,7 +222,7 @@ class CTestNetParams : public CMainParams {
222222

223223
fRequireRPCPassword = true;
224224
fMiningRequiresPeers = true;
225-
fDefaultCheckMemPool = false;
225+
fDefaultConsistencyChecks = false;
226226
fRequireStandard = false;
227227
fMineBlocksOnDemand = false;
228228
fTestnetToBeDeprecatedFieldRPC = true;
@@ -263,7 +263,7 @@ class CRegTestParams : public CTestNetParams {
263263

264264
fRequireRPCPassword = false;
265265
fMiningRequiresPeers = false;
266-
fDefaultCheckMemPool = true;
266+
fDefaultConsistencyChecks = true;
267267
fRequireStandard = false;
268268
fMineBlocksOnDemand = true;
269269
fTestnetToBeDeprecatedFieldRPC = false;

src/chainparams.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ class CChainParams
5757
bool RequireRPCPassword() const { return fRequireRPCPassword; }
5858
/** Make miner wait to have peers to avoid wasting work */
5959
bool MiningRequiresPeers() const { return fMiningRequiresPeers; }
60-
/** Default value for -checkmempool argument */
61-
bool DefaultCheckMemPool() const { return fDefaultCheckMemPool; }
60+
/** Default value for -checkmempool and -checkblockindex argument */
61+
bool DefaultConsistencyChecks() const { return fDefaultConsistencyChecks; }
6262
/** Allow mining of a min-difficulty block */
6363
bool AllowMinDifficultyBlocks() const { return consensus.fPowAllowMinDifficultyBlocks; }
6464
/** Make standard checks */
@@ -92,7 +92,7 @@ class CChainParams
9292
std::vector<CAddress> vFixedSeeds;
9393
bool fRequireRPCPassword;
9494
bool fMiningRequiresPeers;
95-
bool fDefaultCheckMemPool;
95+
bool fDefaultConsistencyChecks;
9696
bool fRequireStandard;
9797
bool fMineBlocksOnDemand;
9898
bool fTestnetToBeDeprecatedFieldRPC;

src/init.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -697,8 +697,9 @@ bool AppInit2(boost::thread_group& threadGroup)
697697
if (GetBoolArg("-benchmark", false))
698698
InitWarning(_("Warning: Unsupported argument -benchmark ignored, use -debug=bench."));
699699

700-
// Checkmempool defaults to true in regtest mode
701-
mempool.setSanityCheck(GetBoolArg("-checkmempool", Params().DefaultCheckMemPool()));
700+
// Checkmempool and checkblockindex default to true in regtest mode
701+
mempool.setSanityCheck(GetBoolArg("-checkmempool", Params().DefaultConsistencyChecks()));
702+
fCheckBlockIndex = GetBoolArg("-checkblockindex", Params().DefaultConsistencyChecks());
702703
Checkpoints::fEnabled = GetBoolArg("-checkpoints", true);
703704

704705
// -par=0 means autodetect, but nScriptCheckThreads==0 means no concurrency

src/main.cpp

Lines changed: 142 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ bool fImporting = false;
5353
bool fReindex = false;
5454
bool fTxIndex = false;
5555
bool fIsBareMultisigStd = true;
56+
bool fCheckBlockIndex = false;
5657
unsigned int nCoinCacheSize = 5000;
5758

5859
/** Fees smaller than this (in satoshi) are considered zero fee (for relaying and mining) */
@@ -74,6 +75,7 @@ void EraseOrphansFor(NodeId peer);
7475
* and going backwards.
7576
*/
7677
static bool IsSuperMajority(int minVersion, const CBlockIndex* pstart, unsigned int nRequired);
78+
static void CheckBlockIndex();
7779

7880
/** Constant stuff for coinbase transactions we create: */
7981
CScript COINBASE_FLAGS;
@@ -85,7 +87,7 @@ namespace {
8587

8688
struct CBlockIndexWorkComparator
8789
{
88-
bool operator()(CBlockIndex *pa, CBlockIndex *pb) {
90+
bool operator()(CBlockIndex *pa, CBlockIndex *pb) const {
8991
// First sort by most total work, ...
9092
if (pa->nChainWork > pb->nChainWork) return false;
9193
if (pa->nChainWork < pb->nChainWork) return true;
@@ -107,8 +109,8 @@ namespace {
107109
CBlockIndex *pindexBestInvalid;
108110

109111
/**
110-
* The set of all CBlockIndex entries with BLOCK_VALID_TRANSACTIONS or better that are at least
111-
* as good as our current tip. Entries may be failed, though.
112+
* The set of all CBlockIndex entries with BLOCK_VALID_TRANSACTIONS (for itself and all ancestors) and
113+
* as good as our current tip or better. Entries may be failed, though.
112114
*/
113115
set<CBlockIndex*, CBlockIndexWorkComparator> setBlockIndexCandidates;
114116
/** Number of nodes with fSyncStarted. */
@@ -2226,6 +2228,7 @@ bool ActivateBestChain(CValidationState &state, CBlock *pblock) {
22262228
uiInterface.NotifyBlockTip(hashNewTip);
22272229
}
22282230
} while(pindexMostWork != chainActive.Tip());
2231+
CheckBlockIndex();
22292232

22302233
// Write changes periodically to disk, after relay.
22312234
if (!FlushStateToDisk(state, FLUSH_STATE_PERIODIC)) {
@@ -2362,7 +2365,9 @@ bool ReceivedBlockTransactions(const CBlock &block, CValidationState& state, CBl
23622365
CBlockIndex *pindex = queue.front();
23632366
queue.pop_front();
23642367
pindex->nChainTx = (pindex->pprev ? pindex->pprev->nChainTx : 0) + pindex->nTx;
2365-
setBlockIndexCandidates.insert(pindex);
2368+
if (chainActive.Tip() == NULL || !setBlockIndexCandidates.value_comp()(pindex, chainActive.Tip())) {
2369+
setBlockIndexCandidates.insert(pindex);
2370+
}
23662371
std::pair<std::multimap<CBlockIndex*, CBlockIndex*>::iterator, std::multimap<CBlockIndex*, CBlockIndex*>::iterator> range = mapBlocksUnlinked.equal_range(pindex);
23672372
while (range.first != range.second) {
23682373
std::multimap<CBlockIndex*, CBlockIndex*>::iterator it = range.first;
@@ -2725,6 +2730,7 @@ bool ProcessNewBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, CDis
27252730
if (pindex && pfrom) {
27262731
mapBlockSource[pindex->GetBlockHash()] = pfrom->GetId();
27272732
}
2733+
CheckBlockIndex();
27282734
if (!ret)
27292735
return error("%s: AcceptBlock FAILED", __func__);
27302736
}
@@ -3213,6 +3219,136 @@ bool LoadExternalBlockFile(FILE* fileIn, CDiskBlockPos *dbp)
32133219
return nLoaded > 0;
32143220
}
32153221

3222+
void static CheckBlockIndex()
3223+
{
3224+
if (!fCheckBlockIndex) {
3225+
return;
3226+
}
3227+
3228+
LOCK(cs_main);
3229+
3230+
// Build forward-pointing map of the entire block tree.
3231+
std::multimap<CBlockIndex*,CBlockIndex*> forward;
3232+
for (BlockMap::iterator it = mapBlockIndex.begin(); it != mapBlockIndex.end(); it++) {
3233+
forward.insert(std::make_pair(it->second->pprev, it->second));
3234+
}
3235+
3236+
assert(forward.size() == mapBlockIndex.size());
3237+
3238+
std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> rangeGenesis = forward.equal_range(NULL);
3239+
CBlockIndex *pindex = rangeGenesis.first->second;
3240+
rangeGenesis.first++;
3241+
assert(rangeGenesis.first == rangeGenesis.second); // There is only one index entry with parent NULL.
3242+
3243+
// Iterate over the entire block tree, using depth-first search.
3244+
// Along the way, remember whether there are blocks on the path from genesis
3245+
// block being explored which are the first to have certain properties.
3246+
size_t nNodes = 0;
3247+
int nHeight = 0;
3248+
CBlockIndex* pindexFirstInvalid = NULL; // Oldest ancestor of pindex which is invalid.
3249+
CBlockIndex* pindexFirstMissing = NULL; // Oldest ancestor of pindex which does not have BLOCK_HAVE_DATA.
3250+
CBlockIndex* pindexFirstNotTreeValid = NULL; // Oldest ancestor of pindex which does not have BLOCK_VALID_TREE (regardless of being valid or not).
3251+
CBlockIndex* pindexFirstNotChainValid = NULL; // Oldest ancestor of pindex which does not have BLOCK_VALID_CHAIN (regardless of being valid or not).
3252+
CBlockIndex* pindexFirstNotScriptsValid = NULL; // Oldest ancestor of pindex which does not have BLOCK_VALID_SCRIPTS (regardless of being valid or not).
3253+
while (pindex != NULL) {
3254+
nNodes++;
3255+
if (pindexFirstInvalid == NULL && pindex->nStatus & BLOCK_FAILED_VALID) pindexFirstInvalid = pindex;
3256+
if (pindexFirstMissing == NULL && !(pindex->nStatus & BLOCK_HAVE_DATA)) pindexFirstMissing = pindex;
3257+
if (pindex->pprev != NULL && pindexFirstNotTreeValid == NULL && (pindex->nStatus & BLOCK_VALID_MASK) < BLOCK_VALID_TREE) pindexFirstNotTreeValid = pindex;
3258+
if (pindex->pprev != NULL && pindexFirstNotChainValid == NULL && (pindex->nStatus & BLOCK_VALID_MASK) < BLOCK_VALID_CHAIN) pindexFirstNotChainValid = pindex;
3259+
if (pindex->pprev != NULL && pindexFirstNotScriptsValid == NULL && (pindex->nStatus & BLOCK_VALID_MASK) < BLOCK_VALID_SCRIPTS) pindexFirstNotScriptsValid = pindex;
3260+
3261+
// Begin: actual consistency checks.
3262+
if (pindex->pprev == NULL) {
3263+
// Genesis block checks.
3264+
assert(pindex->GetBlockHash() == Params().HashGenesisBlock()); // Genesis block's hash must match.
3265+
assert(pindex == chainActive.Genesis()); // The current active chain's genesis block must be this block.
3266+
}
3267+
assert((pindexFirstMissing != NULL) == (pindex->nChainTx == 0)); // nChainTx == 0 is used to signal that all parent block's transaction data is available.
3268+
assert(pindex->nHeight == nHeight); // nHeight must be consistent.
3269+
assert(pindex->pprev == NULL || pindex->nChainWork >= pindex->pprev->nChainWork); // For every block except the genesis block, the chainwork must be larger than the parent's.
3270+
assert(nHeight < 2 || (pindex->pskip && (pindex->pskip->nHeight < nHeight))); // The pskip pointer must point back for all but the first 2 blocks.
3271+
assert(pindexFirstNotTreeValid == NULL); // All mapBlockIndex entries must at least be TREE valid
3272+
if ((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_TREE) assert(pindexFirstNotTreeValid == NULL); // TREE valid implies all parents are TREE valid
3273+
if ((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_CHAIN) assert(pindexFirstNotChainValid == NULL); // CHAIN valid implies all parents are CHAIN valid
3274+
if ((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_SCRIPTS) assert(pindexFirstNotScriptsValid == NULL); // SCRIPTS valid implies all parents are SCRIPTS valid
3275+
if (pindexFirstInvalid == NULL) {
3276+
// Checks for not-invalid blocks.
3277+
assert((pindex->nStatus & BLOCK_FAILED_MASK) == 0); // The failed mask cannot be set for blocks without invalid parents.
3278+
}
3279+
if (!CBlockIndexWorkComparator()(pindex, chainActive.Tip()) && pindexFirstMissing == NULL) {
3280+
if (pindexFirstInvalid == NULL) { // If this block sorts at least as good as the current tip and is valid, it must be in setBlockIndexCandidates.
3281+
assert(setBlockIndexCandidates.count(pindex));
3282+
}
3283+
} else { // If this block sorts worse than the current tip, it cannot be in setBlockIndexCandidates.
3284+
assert(setBlockIndexCandidates.count(pindex) == 0);
3285+
}
3286+
// Check whether this block is in mapBlocksUnlinked.
3287+
std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> rangeUnlinked = mapBlocksUnlinked.equal_range(pindex->pprev);
3288+
bool foundInUnlinked = false;
3289+
while (rangeUnlinked.first != rangeUnlinked.second) {
3290+
assert(rangeUnlinked.first->first == pindex->pprev);
3291+
if (rangeUnlinked.first->second == pindex) {
3292+
foundInUnlinked = true;
3293+
break;
3294+
}
3295+
rangeUnlinked.first++;
3296+
}
3297+
if (pindex->pprev && pindex->nStatus & BLOCK_HAVE_DATA && pindexFirstMissing != NULL) {
3298+
if (pindexFirstInvalid == NULL) { // If this block has block data available, some parent doesn't, and has no invalid parents, it must be in mapBlocksUnlinked.
3299+
assert(foundInUnlinked);
3300+
}
3301+
} else { // If this block does not have block data available, or all parents do, it cannot be in mapBlocksUnlinked.
3302+
assert(!foundInUnlinked);
3303+
}
3304+
// assert(pindex->GetBlockHash() == pindex->GetBlockHeader().GetHash()); // Perhaps too slow
3305+
// End: actual consistency checks.
3306+
3307+
// Try descending into the first subnode.
3308+
std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> range = forward.equal_range(pindex);
3309+
if (range.first != range.second) {
3310+
// A subnode was found.
3311+
pindex = range.first->second;
3312+
nHeight++;
3313+
continue;
3314+
}
3315+
// This is a leaf node.
3316+
// Move upwards until we reach a node of which we have not yet visited the last child.
3317+
while (pindex) {
3318+
// We are going to either move to a parent or a sibling of pindex.
3319+
// If pindex was the first with a certain property, unset the corresponding variable.
3320+
if (pindex == pindexFirstInvalid) pindexFirstInvalid = NULL;
3321+
if (pindex == pindexFirstMissing) pindexFirstMissing = NULL;
3322+
if (pindex == pindexFirstNotTreeValid) pindexFirstNotTreeValid = NULL;
3323+
if (pindex == pindexFirstNotChainValid) pindexFirstNotChainValid = NULL;
3324+
if (pindex == pindexFirstNotScriptsValid) pindexFirstNotScriptsValid = NULL;
3325+
// Find our parent.
3326+
CBlockIndex* pindexPar = pindex->pprev;
3327+
// Find which child we just visited.
3328+
std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> rangePar = forward.equal_range(pindexPar);
3329+
while (rangePar.first->second != pindex) {
3330+
assert(rangePar.first != rangePar.second); // Our parent must have at least the node we're coming from as child.
3331+
rangePar.first++;
3332+
}
3333+
// Proceed to the next one.
3334+
rangePar.first++;
3335+
if (rangePar.first != rangePar.second) {
3336+
// Move to the sibling.
3337+
pindex = rangePar.first->second;
3338+
break;
3339+
} else {
3340+
// Move up further.
3341+
pindex = pindexPar;
3342+
nHeight--;
3343+
continue;
3344+
}
3345+
}
3346+
}
3347+
3348+
// Check that we actually traversed the entire map.
3349+
assert(nNodes == forward.size());
3350+
}
3351+
32163352
//////////////////////////////////////////////////////////////////////////////
32173353
//
32183354
// CAlert
@@ -3971,6 +4107,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
39714107
LogPrint("net", "more getheaders (%d) to end to peer=%d (startheight:%d)\n", pindexLast->nHeight, pfrom->id, pfrom->nStartingHeight);
39724108
pfrom->PushMessage("getheaders", chainActive.GetLocator(pindexLast), uint256());
39734109
}
4110+
4111+
CheckBlockIndex();
39744112
}
39754113

39764114
else if (strCommand == "block" && !fImporting && !fReindex) // Ignore blocks received while importing

src/main.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ extern bool fReindex;
122122
extern int nScriptCheckThreads;
123123
extern bool fTxIndex;
124124
extern bool fIsBareMultisigStd;
125+
extern bool fCheckBlockIndex;
125126
extern unsigned int nCoinCacheSize;
126127
extern CFeeRate minRelayTxFee;
127128

src/test/test_bitcoin.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ BasicTestingSetup::BasicTestingSetup()
3030
{
3131
SetupEnvironment();
3232
fPrintToDebugLog = false; // don't want to write to debug.log file
33+
fCheckBlockIndex = true;
3334
SelectParams(CBaseChainParams::MAIN);
3435
}
3536
BasicTestingSetup::~BasicTestingSetup()

0 commit comments

Comments
 (0)