diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -225,7 +225,9 @@ // FlushStateToDisk generates a SetBestChain callback, which we should avoid // missing - FlushStateToDisk(); + if (pcoinsTip != nullptr) { + FlushStateToDisk(); + } // After there are no more peers/RPC left to give us new data which may // generate CValidationInterface callbacks, flush them... @@ -1044,7 +1046,7 @@ LogPrintf("Reindexing finished\n"); // To avoid ending up in a situation without genesis block, re-try // initializing (no-op if reindexing worked): - InitBlockIndex(config); + LoadGenesisBlock(config.GetChainParams()); } // hardcoded $DATADIR/bootstrap.dat @@ -1993,9 +1995,6 @@ pblocktree = new CBlockTreeDB(nBlockTreeDBCache, false, fReindex); - pcoinsdbview = new CCoinsViewDB(nCoinDBCache, false, - fReindex || fReindexChainState); - pcoinscatcher = new CCoinsViewErrorCatcher(pcoinsdbview); if (fReindex) { pblocktree->WriteReindexing(true); @@ -2004,14 +2003,15 @@ if (fPruneMode) { CleanupBlockRevFiles(); } - } else if (!pcoinsdbview->Upgrade()) { - strLoadError = _("Error upgrading chainstate database"); - break; } + if (fRequestShutdown) { break; } + // LoadBlockIndex will load fTxIndex from the db, or set it if + // we're reindexing. It will also load fHavePruned if we've + // ever removed a block file from disk. if (!LoadBlockIndex(config)) { strLoadError = _("Error loading block database"); break; @@ -2027,13 +2027,6 @@ "Wrong datadir for network?")); } - // Initialize the block index (no-op if non-empty database was - // already loaded) - if (!InitBlockIndex(config)) { - strLoadError = _("Error initializing block database"); - break; - } - // Check for changed -txindex state if (fTxIndex != gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { strLoadError = _("You need to rebuild the database using " @@ -2052,6 +2045,32 @@ break; } + // At this point blocktree args are consistent with what's on + // disk. If we're not mid-reindex (based on disk + args), add a + // genesis block on disk. This is called again in ThreadImport + // if the reindex completes. + if (!fReindex && !LoadGenesisBlock(chainparams)) { + strLoadError = _("Error initializing block database"); + break; + } + + // At this point we're either in reindex or we've loaded a + // useful block tree into mapBlockIndex! + + pcoinsdbview = new CCoinsViewDB(nCoinDBCache, false, + fReindex || fReindexChainState); + pcoinscatcher = new CCoinsViewErrorCatcher(pcoinsdbview); + + // If necessary, upgrade from older database format. + // This is a no-op if we cleared the coinsviewdb with -reindex + // or -reindex-chainstate + if (!pcoinsdbview->Upgrade()) { + strLoadError = _("Error upgrading chainstate database"); + break; + } + + // ReplayBlocks is a no-op if we cleared the coinsviewdb with + // -reindex or -reindex-chainstate if (!ReplayBlocks(config, pcoinsdbview)) { strLoadError = _("Unable to replay blocks. You will need to rebuild " @@ -2059,10 +2078,24 @@ break; } + // The on-disk coinsdb is now in a good state, create the cache pcoinsTip = new CCoinsViewCache(pcoinscatcher); - LoadChainTip(chainparams); - if (!fReindex && chainActive.Tip() != nullptr) { + if (!fReindex && !fReindexChainState) { + // LoadChainTip sets chainActive based on pcoinsTip's best + // block + if (!LoadChainTip(config)) { + strLoadError = _("Error initializing block database"); + break; + } + assert(chainActive.Tip() != nullptr); + } + + if (!fReindex) { + // Note that RewindBlockIndex MUST run even if we're about + // to -reindex-chainstate. It both disconnects blocks based + // on chainActive, and drops block data in mapBlockIndex + // based on lack of available witness data. uiInterface.InitMessage(_("Rewinding blocks...")); if (!RewindBlockIndex(config)) { strLoadError = _("Unable to rewind the database to a " @@ -2072,39 +2105,43 @@ } } - uiInterface.InitMessage(_("Verifying blocks...")); - if (fHavePruned && - gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS) > - MIN_BLOCKS_TO_KEEP) { - LogPrintf("Prune: pruned datadir may not have more than %d " - "blocks; only checking available blocks", - MIN_BLOCKS_TO_KEEP); - } + if (!fReindex && !fReindexChainState) { + uiInterface.InitMessage(_("Verifying blocks...")); + if (fHavePruned && + gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS) > + MIN_BLOCKS_TO_KEEP) { + LogPrintf("Prune: pruned datadir may not have more " + "than %d blocks; only checking available " + "blocks", + MIN_BLOCKS_TO_KEEP); + } - { - LOCK(cs_main); - CBlockIndex *tip = chainActive.Tip(); - RPCNotifyBlockChange(true, tip); - if (tip && - tip->nTime > - GetAdjustedTime() + MAX_FUTURE_BLOCK_TIME) { - strLoadError = - _("The block database contains a block which " - "appears to be from the future. " - "This may be due to your computer's date and " - "time being set incorrectly. " - "Only rebuild the block database if you are sure " - "that your computer's date and time are correct"); - break; + { + LOCK(cs_main); + CBlockIndex *tip = chainActive.Tip(); + RPCNotifyBlockChange(true, tip); + if (tip && + tip->nTime > + GetAdjustedTime() + MAX_FUTURE_BLOCK_TIME) { + strLoadError = + _("The block database contains a block which " + "appears to be from the future. This may be " + "due to your computer's date and time being " + "set incorrectly. Only rebuild the block " + "database if you are sure that your " + "computer's date and time are correct"); + break; + } } - } - if (!CVerifyDB().VerifyDB( - config, pcoinsdbview, - gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL), - gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS))) { - strLoadError = _("Corrupted block database detected"); - break; + if (!CVerifyDB().VerifyDB( + config, pcoinsdbview, + gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL), + gArgs.GetArg("-checkblocks", + DEFAULT_CHECKBLOCKS))) { + strLoadError = _("Corrupted block database detected"); + break; + } } } catch (const std::exception &e) { LogPrintf("%s\n", e.what()); diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -78,10 +78,11 @@ TestingSetup::TestingSetup(const std::string &chainName) : BasicTestingSetup(chainName) { + const Config &config = GetConfig(); + const CChainParams &chainparams = config.GetChainParams(); // Ideally we'd move all the RPC tests to the functional testing framework // instead of unit tests, but for now we need these here. - const Config &config = GetConfig(); RPCServer rpcServer; RegisterAllRPCCommands(config, rpcServer, tableRPC); ClearDatadirCache(); @@ -100,7 +101,7 @@ pblocktree = new CBlockTreeDB(1 << 20, true); pcoinsdbview = new CCoinsViewDB(1 << 23, true); pcoinsTip = new CCoinsViewCache(pcoinsdbview); - if (!InitBlockIndex(config)) { + if (!LoadGenesisBlock(chainparams)) { throw std::runtime_error("InitBlockIndex failed."); } { diff --git a/src/validation.h b/src/validation.h --- a/src/validation.h +++ b/src/validation.h @@ -352,19 +352,21 @@ CDiskBlockPos *dbp = nullptr); /** - * Initialize a new block tree database + block data on disk. + * Ensures we have a genesis block in the block tree, possibly writing one to + * disk. */ -bool InitBlockIndex(const Config &config); +bool LoadGenesisBlock(const CChainParams &chainparams); /** - * Load the block tree and coins database from disk. + * Load the block tree and coins database from disk, initializing state if we're + * running with -reindex. */ bool LoadBlockIndex(const Config &config); /** * Update the chain tip based on database information. */ -void LoadChainTip(const CChainParams &chainparams); +bool LoadChainTip(const Config &config); /** * Unload database information. diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -4046,16 +4046,26 @@ return true; } -void LoadChainTip(const CChainParams &chainparams) { +bool LoadChainTip(const Config &config) { if (chainActive.Tip() && chainActive.Tip()->GetBlockHash() == pcoinsTip->GetBestBlock()) { - return; + return true; + } + + if (pcoinsTip->GetBestBlock().IsNull() && mapBlockIndex.size() == 1) { + // In case we just added the genesis block, connect it now, so + // that we always have a chainActive.Tip() when we return. + LogPrintf("%s: Connecting genesis block...\n", __func__); + CValidationState state; + if (!ActivateBestChain(config, state)) { + return false; + } } // Load pointer to end of best chain BlockMap::iterator it = mapBlockIndex.find(pcoinsTip->GetBestBlock()); if (it == mapBlockIndex.end()) { - return; + return false; } chainActive.SetTip(it->second); @@ -4067,7 +4077,9 @@ chainActive.Tip()->GetBlockHash().ToString(), chainActive.Height(), DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()), - GuessVerificationProgress(chainparams.TxData(), chainActive.Tip())); + GuessVerificationProgress(config.GetChainParams().TxData(), + chainActive.Tip())); + return true; } CVerifyDB::CVerifyDB() { @@ -4403,12 +4415,19 @@ } } - PruneBlockIndexCandidates(); + if (chainActive.Tip() != nullptr) { + // We can't prune block index candidates based on our tip if we have + // no tip due to chainActive being empty! + PruneBlockIndexCandidates(); - CheckBlockIndex(params.GetConsensus()); + CheckBlockIndex(params.GetConsensus()); - if (!FlushStateToDisk(params, state, FLUSH_STATE_ALWAYS)) { - return false; + // FlushStateToDisk can possibly read chainActive. Be conservative + // and skip it here, we're about to -reindex-chainstate anyway, so + // it'll get called a bunch real soon. + if (!FlushStateToDisk(params, state, FLUSH_STATE_ALWAYS)) { + return false; + } } return true; @@ -4441,56 +4460,65 @@ bool LoadBlockIndex(const Config &config) { // Load block index from databases - if (!fReindex && !LoadBlockIndexDB(config)) { - return false; + bool needs_init = fReindex; + if (!fReindex) { + bool ret = LoadBlockIndexDB(config); + if (!ret) { + return false; + } + + needs_init = mapBlockIndex.empty(); } + if (needs_init) { + // Everything here is for *new* reindex/DBs. Thus, though + // LoadBlockIndexDB may have set fReindex if we shut down + // mid-reindex previously, we don't check fReindex and + // instead only check it prior to LoadBlockIndexDB to set + // needs_init. + + LogPrintf("Initializing databases...\n"); + // Use the provided setting for -txindex in the new database + fTxIndex = gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX); + pblocktree->WriteFlag("txindex", fTxIndex); + } return true; } -bool InitBlockIndex(const Config &config) { +bool LoadGenesisBlock(const CChainParams &chainparams) { LOCK(cs_main); - // Check whether we're already initialized - if (chainActive.Genesis() != nullptr) { + // Check whether we're already initialized by checking for genesis in + // mapBlockIndex. Note that we can't use chainActive here, since it is + // set based on the coins db, not the block index db, which is the only + // thing loaded at this point. + if (mapBlockIndex.count(chainparams.GenesisBlock().GetHash())) { return true; } - // Use the provided setting for -txindex in the new database - fTxIndex = gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX); - pblocktree->WriteFlag("txindex", fTxIndex); - LogPrintf("Initializing databases...\n"); - // Only add the genesis block if not reindexing (in which case we reuse the // one already on disk) - if (!fReindex) { - try { - const CChainParams &chainparams = config.GetChainParams(); - CBlock &block = const_cast(chainparams.GenesisBlock()); - // Start new block file - unsigned int nBlockSize = - ::GetSerializeSize(block, SER_DISK, CLIENT_VERSION); - CDiskBlockPos blockPos; - CValidationState state; - if (!FindBlockPos(state, blockPos, nBlockSize + 8, 0, - block.GetBlockTime())) { - return error("LoadBlockIndex(): FindBlockPos failed"); - } - - if (!WriteBlockToDisk(block, blockPos, chainparams.DiskMagic())) { - return error( - "LoadBlockIndex(): writing genesis block to disk failed"); - } - - CBlockIndex *pindex = AddToBlockIndex(block); - if (!ReceivedBlockTransactions(block, state, pindex, blockPos)) { - return error("LoadBlockIndex(): genesis block not accepted"); - } - } catch (const std::runtime_error &e) { - return error( - "LoadBlockIndex(): failed to initialize block database: %s", - e.what()); + try { + CBlock &block = const_cast(chainparams.GenesisBlock()); + // Start new block file + unsigned int nBlockSize = + ::GetSerializeSize(block, SER_DISK, CLIENT_VERSION); + CDiskBlockPos blockPos; + CValidationState state; + if (!FindBlockPos(state, blockPos, nBlockSize + 8, 0, + block.GetBlockTime())) { + return error("%s: FindBlockPos failed", __func__); + } + if (!WriteBlockToDisk(block, blockPos, chainparams.DiskMagic())) { + return error("%s: writing genesis block to disk failed", __func__); } + CBlockIndex *pindex = AddToBlockIndex(block); + if (!ReceivedBlockTransactions(block, state, pindex, blockPos)) { + return error("%s: genesis block not accepted", __func__); + } + } catch (const std::runtime_error &e) { + return error("%s: failed to write genesis block: %s", __func__, + e.what()); } return true;