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;
|
fRequireRPCPassword = true;
|
||||||
fMiningRequiresPeers = true;
|
fMiningRequiresPeers = true;
|
||||||
fDefaultCheckMemPool = false;
|
fDefaultConsistencyChecks = false;
|
||||||
fRequireStandard = true;
|
fRequireStandard = true;
|
||||||
fMineBlocksOnDemand = false;
|
fMineBlocksOnDemand = false;
|
||||||
fTestnetToBeDeprecatedFieldRPC = false;
|
fTestnetToBeDeprecatedFieldRPC = false;
|
||||||
|
@ -222,7 +222,7 @@ public:
|
||||||
|
|
||||||
fRequireRPCPassword = true;
|
fRequireRPCPassword = true;
|
||||||
fMiningRequiresPeers = true;
|
fMiningRequiresPeers = true;
|
||||||
fDefaultCheckMemPool = false;
|
fDefaultConsistencyChecks = false;
|
||||||
fRequireStandard = false;
|
fRequireStandard = false;
|
||||||
fMineBlocksOnDemand = false;
|
fMineBlocksOnDemand = false;
|
||||||
fTestnetToBeDeprecatedFieldRPC = true;
|
fTestnetToBeDeprecatedFieldRPC = true;
|
||||||
|
@ -263,7 +263,7 @@ public:
|
||||||
|
|
||||||
fRequireRPCPassword = false;
|
fRequireRPCPassword = false;
|
||||||
fMiningRequiresPeers = false;
|
fMiningRequiresPeers = false;
|
||||||
fDefaultCheckMemPool = true;
|
fDefaultConsistencyChecks = true;
|
||||||
fRequireStandard = false;
|
fRequireStandard = false;
|
||||||
fMineBlocksOnDemand = true;
|
fMineBlocksOnDemand = true;
|
||||||
fTestnetToBeDeprecatedFieldRPC = false;
|
fTestnetToBeDeprecatedFieldRPC = false;
|
||||||
|
|
|
@ -57,8 +57,8 @@ public:
|
||||||
bool RequireRPCPassword() const { return fRequireRPCPassword; }
|
bool RequireRPCPassword() const { return fRequireRPCPassword; }
|
||||||
/** Make miner wait to have peers to avoid wasting work */
|
/** Make miner wait to have peers to avoid wasting work */
|
||||||
bool MiningRequiresPeers() const { return fMiningRequiresPeers; }
|
bool MiningRequiresPeers() const { return fMiningRequiresPeers; }
|
||||||
/** Default value for -checkmempool argument */
|
/** Default value for -checkmempool and -checkblockindex argument */
|
||||||
bool DefaultCheckMemPool() const { return fDefaultCheckMemPool; }
|
bool DefaultConsistencyChecks() const { return fDefaultConsistencyChecks; }
|
||||||
/** Allow mining of a min-difficulty block */
|
/** Allow mining of a min-difficulty block */
|
||||||
bool AllowMinDifficultyBlocks() const { return consensus.fPowAllowMinDifficultyBlocks; }
|
bool AllowMinDifficultyBlocks() const { return consensus.fPowAllowMinDifficultyBlocks; }
|
||||||
/** Make standard checks */
|
/** Make standard checks */
|
||||||
|
@ -92,7 +92,7 @@ protected:
|
||||||
std::vector<CAddress> vFixedSeeds;
|
std::vector<CAddress> vFixedSeeds;
|
||||||
bool fRequireRPCPassword;
|
bool fRequireRPCPassword;
|
||||||
bool fMiningRequiresPeers;
|
bool fMiningRequiresPeers;
|
||||||
bool fDefaultCheckMemPool;
|
bool fDefaultConsistencyChecks;
|
||||||
bool fRequireStandard;
|
bool fRequireStandard;
|
||||||
bool fMineBlocksOnDemand;
|
bool fMineBlocksOnDemand;
|
||||||
bool fTestnetToBeDeprecatedFieldRPC;
|
bool fTestnetToBeDeprecatedFieldRPC;
|
||||||
|
|
|
@ -697,8 +697,9 @@ bool AppInit2(boost::thread_group& threadGroup)
|
||||||
if (GetBoolArg("-benchmark", false))
|
if (GetBoolArg("-benchmark", false))
|
||||||
InitWarning(_("Warning: Unsupported argument -benchmark ignored, use -debug=bench."));
|
InitWarning(_("Warning: Unsupported argument -benchmark ignored, use -debug=bench."));
|
||||||
|
|
||||||
// Checkmempool defaults to true in regtest mode
|
// Checkmempool and checkblockindex default to true in regtest mode
|
||||||
mempool.setSanityCheck(GetBoolArg("-checkmempool", Params().DefaultCheckMemPool()));
|
mempool.setSanityCheck(GetBoolArg("-checkmempool", Params().DefaultConsistencyChecks()));
|
||||||
|
fCheckBlockIndex = GetBoolArg("-checkblockindex", Params().DefaultConsistencyChecks());
|
||||||
Checkpoints::fEnabled = GetBoolArg("-checkpoints", true);
|
Checkpoints::fEnabled = GetBoolArg("-checkpoints", true);
|
||||||
|
|
||||||
// -par=0 means autodetect, but nScriptCheckThreads==0 means no concurrency
|
// -par=0 means autodetect, but nScriptCheckThreads==0 means no concurrency
|
||||||
|
|
146
src/main.cpp
146
src/main.cpp
|
@ -53,6 +53,7 @@ bool fImporting = false;
|
||||||
bool fReindex = false;
|
bool fReindex = false;
|
||||||
bool fTxIndex = false;
|
bool fTxIndex = false;
|
||||||
bool fIsBareMultisigStd = true;
|
bool fIsBareMultisigStd = true;
|
||||||
|
bool fCheckBlockIndex = false;
|
||||||
unsigned int nCoinCacheSize = 5000;
|
unsigned int nCoinCacheSize = 5000;
|
||||||
|
|
||||||
/** Fees smaller than this (in satoshi) are considered zero fee (for relaying and mining) */
|
/** 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.
|
* and going backwards.
|
||||||
*/
|
*/
|
||||||
static bool IsSuperMajority(int minVersion, const CBlockIndex* pstart, unsigned int nRequired);
|
static bool IsSuperMajority(int minVersion, const CBlockIndex* pstart, unsigned int nRequired);
|
||||||
|
static void CheckBlockIndex();
|
||||||
|
|
||||||
/** Constant stuff for coinbase transactions we create: */
|
/** Constant stuff for coinbase transactions we create: */
|
||||||
CScript COINBASE_FLAGS;
|
CScript COINBASE_FLAGS;
|
||||||
|
@ -85,7 +87,7 @@ namespace {
|
||||||
|
|
||||||
struct CBlockIndexWorkComparator
|
struct CBlockIndexWorkComparator
|
||||||
{
|
{
|
||||||
bool operator()(CBlockIndex *pa, CBlockIndex *pb) {
|
bool operator()(CBlockIndex *pa, CBlockIndex *pb) const {
|
||||||
// First sort by most total work, ...
|
// First sort by most total work, ...
|
||||||
if (pa->nChainWork > pb->nChainWork) return false;
|
if (pa->nChainWork > pb->nChainWork) return false;
|
||||||
if (pa->nChainWork < pb->nChainWork) return true;
|
if (pa->nChainWork < pb->nChainWork) return true;
|
||||||
|
@ -107,8 +109,8 @@ namespace {
|
||||||
CBlockIndex *pindexBestInvalid;
|
CBlockIndex *pindexBestInvalid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The set of all CBlockIndex entries with BLOCK_VALID_TRANSACTIONS or better that are at least
|
* The set of all CBlockIndex entries with BLOCK_VALID_TRANSACTIONS (for itself and all ancestors) and
|
||||||
* as good as our current tip. Entries may be failed, though.
|
* as good as our current tip or better. Entries may be failed, though.
|
||||||
*/
|
*/
|
||||||
set<CBlockIndex*, CBlockIndexWorkComparator> setBlockIndexCandidates;
|
set<CBlockIndex*, CBlockIndexWorkComparator> setBlockIndexCandidates;
|
||||||
/** Number of nodes with fSyncStarted. */
|
/** Number of nodes with fSyncStarted. */
|
||||||
|
@ -2226,6 +2228,7 @@ bool ActivateBestChain(CValidationState &state, CBlock *pblock) {
|
||||||
uiInterface.NotifyBlockTip(hashNewTip);
|
uiInterface.NotifyBlockTip(hashNewTip);
|
||||||
}
|
}
|
||||||
} while(pindexMostWork != chainActive.Tip());
|
} while(pindexMostWork != chainActive.Tip());
|
||||||
|
CheckBlockIndex();
|
||||||
|
|
||||||
// Write changes periodically to disk, after relay.
|
// Write changes periodically to disk, after relay.
|
||||||
if (!FlushStateToDisk(state, FLUSH_STATE_PERIODIC)) {
|
if (!FlushStateToDisk(state, FLUSH_STATE_PERIODIC)) {
|
||||||
|
@ -2362,7 +2365,9 @@ bool ReceivedBlockTransactions(const CBlock &block, CValidationState& state, CBl
|
||||||
CBlockIndex *pindex = queue.front();
|
CBlockIndex *pindex = queue.front();
|
||||||
queue.pop_front();
|
queue.pop_front();
|
||||||
pindex->nChainTx = (pindex->pprev ? pindex->pprev->nChainTx : 0) + pindex->nTx;
|
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);
|
std::pair<std::multimap<CBlockIndex*, CBlockIndex*>::iterator, std::multimap<CBlockIndex*, CBlockIndex*>::iterator> range = mapBlocksUnlinked.equal_range(pindex);
|
||||||
while (range.first != range.second) {
|
while (range.first != range.second) {
|
||||||
std::multimap<CBlockIndex*, CBlockIndex*>::iterator it = range.first;
|
std::multimap<CBlockIndex*, CBlockIndex*>::iterator it = range.first;
|
||||||
|
@ -2725,6 +2730,7 @@ bool ProcessNewBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, CDis
|
||||||
if (pindex && pfrom) {
|
if (pindex && pfrom) {
|
||||||
mapBlockSource[pindex->GetBlockHash()] = pfrom->GetId();
|
mapBlockSource[pindex->GetBlockHash()] = pfrom->GetId();
|
||||||
}
|
}
|
||||||
|
CheckBlockIndex();
|
||||||
if (!ret)
|
if (!ret)
|
||||||
return error("%s: AcceptBlock FAILED", __func__);
|
return error("%s: AcceptBlock FAILED", __func__);
|
||||||
}
|
}
|
||||||
|
@ -3213,6 +3219,136 @@ bool LoadExternalBlockFile(FILE* fileIn, CDiskBlockPos *dbp)
|
||||||
return nLoaded > 0;
|
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
|
// 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);
|
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());
|
pfrom->PushMessage("getheaders", chainActive.GetLocator(pindexLast), uint256());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CheckBlockIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (strCommand == "block" && !fImporting && !fReindex) // Ignore blocks received while importing
|
else if (strCommand == "block" && !fImporting && !fReindex) // Ignore blocks received while importing
|
||||||
|
|
|
@ -122,6 +122,7 @@ extern bool fReindex;
|
||||||
extern int nScriptCheckThreads;
|
extern int nScriptCheckThreads;
|
||||||
extern bool fTxIndex;
|
extern bool fTxIndex;
|
||||||
extern bool fIsBareMultisigStd;
|
extern bool fIsBareMultisigStd;
|
||||||
|
extern bool fCheckBlockIndex;
|
||||||
extern unsigned int nCoinCacheSize;
|
extern unsigned int nCoinCacheSize;
|
||||||
extern CFeeRate minRelayTxFee;
|
extern CFeeRate minRelayTxFee;
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ BasicTestingSetup::BasicTestingSetup()
|
||||||
{
|
{
|
||||||
SetupEnvironment();
|
SetupEnvironment();
|
||||||
fPrintToDebugLog = false; // don't want to write to debug.log file
|
fPrintToDebugLog = false; // don't want to write to debug.log file
|
||||||
|
fCheckBlockIndex = true;
|
||||||
SelectParams(CBaseChainParams::MAIN);
|
SelectParams(CBaseChainParams::MAIN);
|
||||||
}
|
}
|
||||||
BasicTestingSetup::~BasicTestingSetup()
|
BasicTestingSetup::~BasicTestingSetup()
|
||||||
|
|
Loading…
Reference in a new issue