Merge pull request #5900
3fcfbc8
Add a consistency check for the block chain data structures (Pieter Wuille)
This commit is contained in:
commit
3e8a1f2725
6 changed files with 153 additions and 12 deletions
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
144
src/main.cpp
144
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;
|
||||
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
|
||||
|
|
|
@ -122,6 +122,7 @@ extern bool fReindex;
|
|||
extern int nScriptCheckThreads;
|
||||
extern bool fTxIndex;
|
||||
extern bool fIsBareMultisigStd;
|
||||
extern bool fCheckBlockIndex;
|
||||
extern unsigned int nCoinCacheSize;
|
||||
extern CFeeRate minRelayTxFee;
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ BasicTestingSetup::BasicTestingSetup()
|
|||
{
|
||||
SetupEnvironment();
|
||||
fPrintToDebugLog = false; // don't want to write to debug.log file
|
||||
fCheckBlockIndex = true;
|
||||
SelectParams(CBaseChainParams::MAIN);
|
||||
}
|
||||
BasicTestingSetup::~BasicTestingSetup()
|
||||
|
|
Loading…
Reference in a new issue