aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPieter Wuille <pieter.wuille@gmail.com>2015-03-13 09:25:34 -0700
committerPieter Wuille <pieter.wuille@gmail.com>2015-03-27 13:38:48 -0700
commit3fcfbc8ac5fcba474151ceedf61c4e433e433474 (patch)
tree669f0294f92722ef5ee4df8b98a6ad4ba570ea0e /src
parent8e4fd0cc315cad1e2925907ef7c62549a83730a5 (diff)
Add a consistency check for the block chain data structures
This adds a -checkblockindex (defaulting to true for regtest), which occasionally does a full consistency check for mapBlockIndex, setBlockIndexCandidates, chainActive, and mapBlocksUnlinked.
Diffstat (limited to 'src')
-rw-r--r--src/chainparams.cpp6
-rw-r--r--src/chainparams.h6
-rw-r--r--src/init.cpp5
-rw-r--r--src/main.cpp146
-rw-r--r--src/main.h1
-rw-r--r--src/test/test_bitcoin.cpp1
6 files changed, 153 insertions, 12 deletions
diff --git a/src/chainparams.cpp b/src/chainparams.cpp
index 3e20d9f8f2..8633d51b2f 100644
--- a/src/chainparams.cpp
+++ b/src/chainparams.cpp
@@ -167,7 +167,7 @@ public:
fRequireRPCPassword = true;
fMiningRequiresPeers = true;
- fDefaultCheckMemPool = false;
+ fDefaultConsistencyChecks = false;
fRequireStandard = true;
fMineBlocksOnDemand = false;
fTestnetToBeDeprecatedFieldRPC = false;
@@ -222,7 +222,7 @@ public:
fRequireRPCPassword = true;
fMiningRequiresPeers = true;
- fDefaultCheckMemPool = false;
+ fDefaultConsistencyChecks = false;
fRequireStandard = false;
fMineBlocksOnDemand = false;
fTestnetToBeDeprecatedFieldRPC = true;
@@ -263,7 +263,7 @@ public:
fRequireRPCPassword = false;
fMiningRequiresPeers = false;
- fDefaultCheckMemPool = true;
+ fDefaultConsistencyChecks = true;
fRequireStandard = false;
fMineBlocksOnDemand = true;
fTestnetToBeDeprecatedFieldRPC = false;
diff --git a/src/chainparams.h b/src/chainparams.h
index aa2ec1e301..d0613beb45 100644
--- a/src/chainparams.h
+++ b/src/chainparams.h
@@ -57,8 +57,8 @@ public:
bool RequireRPCPassword() const { return fRequireRPCPassword; }
/** Make miner wait to have peers to avoid wasting work */
bool MiningRequiresPeers() const { return fMiningRequiresPeers; }
- /** Default value for -checkmempool argument */
- bool DefaultCheckMemPool() const { return fDefaultCheckMemPool; }
+ /** Default value for -checkmempool and -checkblockindex argument */
+ bool DefaultConsistencyChecks() const { return fDefaultConsistencyChecks; }
/** Allow mining of a min-difficulty block */
bool AllowMinDifficultyBlocks() const { return consensus.fPowAllowMinDifficultyBlocks; }
/** Make standard checks */
@@ -92,7 +92,7 @@ protected:
std::vector<CAddress> vFixedSeeds;
bool fRequireRPCPassword;
bool fMiningRequiresPeers;
- bool fDefaultCheckMemPool;
+ bool fDefaultConsistencyChecks;
bool fRequireStandard;
bool fMineBlocksOnDemand;
bool fTestnetToBeDeprecatedFieldRPC;
diff --git a/src/init.cpp b/src/init.cpp
index 2a13af6690..d6957ebdbc 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -697,8 +697,9 @@ bool AppInit2(boost::thread_group& threadGroup)
if (GetBoolArg("-benchmark", false))
InitWarning(_("Warning: Unsupported argument -benchmark ignored, use -debug=bench."));
- // Checkmempool defaults to true in regtest mode
- mempool.setSanityCheck(GetBoolArg("-checkmempool", Params().DefaultCheckMemPool()));
+ // Checkmempool and checkblockindex default to true in regtest mode
+ mempool.setSanityCheck(GetBoolArg("-checkmempool", Params().DefaultConsistencyChecks()));
+ fCheckBlockIndex = GetBoolArg("-checkblockindex", Params().DefaultConsistencyChecks());
Checkpoints::fEnabled = GetBoolArg("-checkpoints", true);
// -par=0 means autodetect, but nScriptCheckThreads==0 means no concurrency
diff --git a/src/main.cpp b/src/main.cpp
index 1d78eedc1a..b8f9b949a7 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -53,6 +53,7 @@ bool fImporting = false;
bool fReindex = false;
bool fTxIndex = false;
bool fIsBareMultisigStd = true;
+bool fCheckBlockIndex = false;
unsigned int nCoinCacheSize = 5000;
/** Fees smaller than this (in satoshi) are considered zero fee (for relaying and mining) */
@@ -74,6 +75,7 @@ void EraseOrphansFor(NodeId peer);
* and going backwards.
*/
static bool IsSuperMajority(int minVersion, const CBlockIndex* pstart, unsigned int nRequired);
+static void CheckBlockIndex();
/** Constant stuff for coinbase transactions we create: */
CScript COINBASE_FLAGS;
@@ -85,7 +87,7 @@ namespace {
struct CBlockIndexWorkComparator
{
- bool operator()(CBlockIndex *pa, CBlockIndex *pb) {
+ bool operator()(CBlockIndex *pa, CBlockIndex *pb) const {
// First sort by most total work, ...
if (pa->nChainWork > pb->nChainWork) return false;
if (pa->nChainWork < pb->nChainWork) return true;
@@ -107,8 +109,8 @@ namespace {
CBlockIndex *pindexBestInvalid;
/**
- * The set of all CBlockIndex entries with BLOCK_VALID_TRANSACTIONS or better that are at least
- * as good as our current tip. Entries may be failed, though.
+ * The set of all CBlockIndex entries with BLOCK_VALID_TRANSACTIONS (for itself and all ancestors) and
+ * as good as our current tip or better. Entries may be failed, though.
*/
set<CBlockIndex*, CBlockIndexWorkComparator> setBlockIndexCandidates;
/** Number of nodes with fSyncStarted. */
@@ -2226,6 +2228,7 @@ bool ActivateBestChain(CValidationState &state, CBlock *pblock) {
uiInterface.NotifyBlockTip(hashNewTip);
}
} while(pindexMostWork != chainActive.Tip());
+ CheckBlockIndex();
// Write changes periodically to disk, after relay.
if (!FlushStateToDisk(state, FLUSH_STATE_PERIODIC)) {
@@ -2362,7 +2365,9 @@ bool ReceivedBlockTransactions(const CBlock &block, CValidationState& state, CBl
CBlockIndex *pindex = queue.front();
queue.pop_front();
pindex->nChainTx = (pindex->pprev ? pindex->pprev->nChainTx : 0) + pindex->nTx;
- setBlockIndexCandidates.insert(pindex);
+ if (chainActive.Tip() == NULL || !setBlockIndexCandidates.value_comp()(pindex, chainActive.Tip())) {
+ setBlockIndexCandidates.insert(pindex);
+ }
std::pair<std::multimap<CBlockIndex*, CBlockIndex*>::iterator, std::multimap<CBlockIndex*, CBlockIndex*>::iterator> range = mapBlocksUnlinked.equal_range(pindex);
while (range.first != range.second) {
std::multimap<CBlockIndex*, CBlockIndex*>::iterator it = range.first;
@@ -2725,6 +2730,7 @@ bool ProcessNewBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, CDis
if (pindex && pfrom) {
mapBlockSource[pindex->GetBlockHash()] = pfrom->GetId();
}
+ CheckBlockIndex();
if (!ret)
return error("%s: AcceptBlock FAILED", __func__);
}
@@ -3213,6 +3219,136 @@ bool LoadExternalBlockFile(FILE* fileIn, CDiskBlockPos *dbp)
return nLoaded > 0;
}
+void static CheckBlockIndex()
+{
+ if (!fCheckBlockIndex) {
+ return;
+ }
+
+ LOCK(cs_main);
+
+ // Build forward-pointing map of the entire block tree.
+ std::multimap<CBlockIndex*,CBlockIndex*> forward;
+ for (BlockMap::iterator it = mapBlockIndex.begin(); it != mapBlockIndex.end(); it++) {
+ forward.insert(std::make_pair(it->second->pprev, it->second));
+ }
+
+ assert(forward.size() == mapBlockIndex.size());
+
+ std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> rangeGenesis = forward.equal_range(NULL);
+ CBlockIndex *pindex = rangeGenesis.first->second;
+ rangeGenesis.first++;
+ assert(rangeGenesis.first == rangeGenesis.second); // There is only one index entry with parent NULL.
+
+ // Iterate over the entire block tree, using depth-first search.
+ // Along the way, remember whether there are blocks on the path from genesis
+ // block being explored which are the first to have certain properties.
+ size_t nNodes = 0;
+ int nHeight = 0;
+ CBlockIndex* pindexFirstInvalid = NULL; // Oldest ancestor of pindex which is invalid.
+ CBlockIndex* pindexFirstMissing = NULL; // Oldest ancestor of pindex which does not have BLOCK_HAVE_DATA.
+ CBlockIndex* pindexFirstNotTreeValid = NULL; // Oldest ancestor of pindex which does not have BLOCK_VALID_TREE (regardless of being valid or not).
+ CBlockIndex* pindexFirstNotChainValid = NULL; // Oldest ancestor of pindex which does not have BLOCK_VALID_CHAIN (regardless of being valid or not).
+ CBlockIndex* pindexFirstNotScriptsValid = NULL; // Oldest ancestor of pindex which does not have BLOCK_VALID_SCRIPTS (regardless of being valid or not).
+ while (pindex != NULL) {
+ nNodes++;
+ if (pindexFirstInvalid == NULL && pindex->nStatus & BLOCK_FAILED_VALID) pindexFirstInvalid = pindex;
+ if (pindexFirstMissing == NULL && !(pindex->nStatus & BLOCK_HAVE_DATA)) pindexFirstMissing = pindex;
+ if (pindex->pprev != NULL && pindexFirstNotTreeValid == NULL && (pindex->nStatus & BLOCK_VALID_MASK) < BLOCK_VALID_TREE) pindexFirstNotTreeValid = pindex;
+ if (pindex->pprev != NULL && pindexFirstNotChainValid == NULL && (pindex->nStatus & BLOCK_VALID_MASK) < BLOCK_VALID_CHAIN) pindexFirstNotChainValid = pindex;
+ if (pindex->pprev != NULL && pindexFirstNotScriptsValid == NULL && (pindex->nStatus & BLOCK_VALID_MASK) < BLOCK_VALID_SCRIPTS) pindexFirstNotScriptsValid = pindex;
+
+ // Begin: actual consistency checks.
+ if (pindex->pprev == NULL) {
+ // Genesis block checks.
+ assert(pindex->GetBlockHash() == Params().HashGenesisBlock()); // Genesis block's hash must match.
+ assert(pindex == chainActive.Genesis()); // The current active chain's genesis block must be this block.
+ }
+ assert((pindexFirstMissing != NULL) == (pindex->nChainTx == 0)); // nChainTx == 0 is used to signal that all parent block's transaction data is available.
+ assert(pindex->nHeight == nHeight); // nHeight must be consistent.
+ 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.
+ assert(nHeight < 2 || (pindex->pskip && (pindex->pskip->nHeight < nHeight))); // The pskip pointer must point back for all but the first 2 blocks.
+ assert(pindexFirstNotTreeValid == NULL); // All mapBlockIndex entries must at least be TREE valid
+ if ((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_TREE) assert(pindexFirstNotTreeValid == NULL); // TREE valid implies all parents are TREE valid
+ if ((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_CHAIN) assert(pindexFirstNotChainValid == NULL); // CHAIN valid implies all parents are CHAIN valid
+ if ((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_SCRIPTS) assert(pindexFirstNotScriptsValid == NULL); // SCRIPTS valid implies all parents are SCRIPTS valid
+ if (pindexFirstInvalid == NULL) {
+ // Checks for not-invalid blocks.
+ assert((pindex->nStatus & BLOCK_FAILED_MASK) == 0); // The failed mask cannot be set for blocks without invalid parents.
+ }
+ if (!CBlockIndexWorkComparator()(pindex, chainActive.Tip()) && pindexFirstMissing == NULL) {
+ if (pindexFirstInvalid == NULL) { // If this block sorts at least as good as the current tip and is valid, it must be in setBlockIndexCandidates.
+ assert(setBlockIndexCandidates.count(pindex));
+ }
+ } else { // If this block sorts worse than the current tip, it cannot be in setBlockIndexCandidates.
+ assert(setBlockIndexCandidates.count(pindex) == 0);
+ }
+ // Check whether this block is in mapBlocksUnlinked.
+ std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> rangeUnlinked = mapBlocksUnlinked.equal_range(pindex->pprev);
+ bool foundInUnlinked = false;
+ while (rangeUnlinked.first != rangeUnlinked.second) {
+ assert(rangeUnlinked.first->first == pindex->pprev);
+ if (rangeUnlinked.first->second == pindex) {
+ foundInUnlinked = true;
+ break;
+ }
+ rangeUnlinked.first++;
+ }
+ if (pindex->pprev && pindex->nStatus & BLOCK_HAVE_DATA && pindexFirstMissing != NULL) {
+ if (pindexFirstInvalid == NULL) { // If this block has block data available, some parent doesn't, and has no invalid parents, it must be in mapBlocksUnlinked.
+ assert(foundInUnlinked);
+ }
+ } else { // If this block does not have block data available, or all parents do, it cannot be in mapBlocksUnlinked.
+ assert(!foundInUnlinked);
+ }
+ // assert(pindex->GetBlockHash() == pindex->GetBlockHeader().GetHash()); // Perhaps too slow
+ // End: actual consistency checks.
+
+ // Try descending into the first subnode.
+ std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> range = forward.equal_range(pindex);
+ if (range.first != range.second) {
+ // A subnode was found.
+ pindex = range.first->second;
+ nHeight++;
+ continue;
+ }
+ // This is a leaf node.
+ // Move upwards until we reach a node of which we have not yet visited the last child.
+ while (pindex) {
+ // We are going to either move to a parent or a sibling of pindex.
+ // If pindex was the first with a certain property, unset the corresponding variable.
+ if (pindex == pindexFirstInvalid) pindexFirstInvalid = NULL;
+ if (pindex == pindexFirstMissing) pindexFirstMissing = NULL;
+ if (pindex == pindexFirstNotTreeValid) pindexFirstNotTreeValid = NULL;
+ if (pindex == pindexFirstNotChainValid) pindexFirstNotChainValid = NULL;
+ if (pindex == pindexFirstNotScriptsValid) pindexFirstNotScriptsValid = NULL;
+ // Find our parent.
+ CBlockIndex* pindexPar = pindex->pprev;
+ // Find which child we just visited.
+ std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> rangePar = forward.equal_range(pindexPar);
+ while (rangePar.first->second != pindex) {
+ assert(rangePar.first != rangePar.second); // Our parent must have at least the node we're coming from as child.
+ rangePar.first++;
+ }
+ // Proceed to the next one.
+ rangePar.first++;
+ if (rangePar.first != rangePar.second) {
+ // Move to the sibling.
+ pindex = rangePar.first->second;
+ break;
+ } else {
+ // Move up further.
+ pindex = pindexPar;
+ nHeight--;
+ continue;
+ }
+ }
+ }
+
+ // Check that we actually traversed the entire map.
+ assert(nNodes == forward.size());
+}
+
//////////////////////////////////////////////////////////////////////////////
//
// CAlert
@@ -3971,6 +4107,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
LogPrint("net", "more getheaders (%d) to end to peer=%d (startheight:%d)\n", pindexLast->nHeight, pfrom->id, pfrom->nStartingHeight);
pfrom->PushMessage("getheaders", chainActive.GetLocator(pindexLast), uint256());
}
+
+ CheckBlockIndex();
}
else if (strCommand == "block" && !fImporting && !fReindex) // Ignore blocks received while importing
diff --git a/src/main.h b/src/main.h
index ac73b242f1..73a31d6d70 100644
--- a/src/main.h
+++ b/src/main.h
@@ -123,6 +123,7 @@ extern bool fReindex;
extern int nScriptCheckThreads;
extern bool fTxIndex;
extern bool fIsBareMultisigStd;
+extern bool fCheckBlockIndex;
extern unsigned int nCoinCacheSize;
extern CFeeRate minRelayTxFee;
diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp
index 7d5207b11e..b5c729d6a9 100644
--- a/src/test/test_bitcoin.cpp
+++ b/src/test/test_bitcoin.cpp
@@ -29,6 +29,7 @@ extern void noui_connect();
BasicTestingSetup::BasicTestingSetup()
{
fPrintToDebugLog = false; // don't want to write to debug.log file
+ fCheckBlockIndex = true;
SelectParams(CBaseChainParams::MAIN);
}
BasicTestingSetup::~BasicTestingSetup()