每隔一段时间都是说要写完,结果都是写了一篇就搁下了。。啪啪啪!打脸,嗯,加油写!
在上一章中,我们分析到了init.cpp line 1515
这里,到此为止整体的状态是我们已经从内存中读取了所有区块的信息,并且建立了所有区块的索引,按常理来讲,接下来可能就开始启动节点进行通信了,我们继续来看下面的代码。
// The on-disk coinsdb is now in a good state, create the cache
pcoinsTip.reset(new CCoinsViewCache(pcoinscatcher.get()));
bool is_coinsview_empty = fReset || fReindexChainState || pcoinsTip->GetBestBlock().IsNull();
if (!is_coinsview_empty) {
// LoadChainTip sets chainActive based on pcoinsTip's best block
if (!LoadChainTip(chainparams)) {
strLoadError = _("Error initializing block database");
break;
}
assert(chainActive.Tip() != nullptr);
}
首先创建CCoinsViewCache
,并将新建的对象保存在指针pcoinsTip
中,CCoinsViewCache
对象声明在src/coin.h
中,并在src/coin.cpp
中进行实现。该类主要是维护一个CCoinsMap
对象cacheCoins
,并使用此对象对当前所有的COutPoint
进行缓存,从而减少对CCoinsViewDB
的直接操作。在这个当中,需要注意的一点是CCoinsViewCache
对象中的base
实际指向的是pcoinsdbview
对象中的CCoinsView
,所以该类中调用的方法例如Flush
,
bool CCoinsViewCache::Flush() {
bool fOk = base->BatchWrite(cacheCoins, hashBlock);
cacheCoins.clear();
cachedCoinsUsage = 0;
return fOk;
}
实际上base
指向的BatchWrite
调用的是pcoinsdbview
对象中的BatchWrite
,而并不是CCoinsView
中的BatchWrite
。这里继承关系稍微复杂一点,所以要注意指针实际指向的对象,避免理解错调用的函数。
设置完了缓存之后,接下来的两个参数fReset
和fReindexChainState
我们在之前https://blog.csdn.net/pure_lady/article/details/78555973已经介绍过,这两个参数的关系是:fReset
是重新建立整个block index,而fReindexChainState
表示从block index中更新chain state;如果设置了fReset
,那么fReindexChainState
就不起作用,如果没有设置fReset
,那么也可以单独设置fReindexChainState
,使用当前的block index去更新chain state。然后接着使用GetBestBlock
从db中读取当前最长链的区块头,如果最终的is_coinsview_empty
为false
,那么说明没有重新建立索引并且需要更新chainActive
中的最新区块指向的位置,接着就需要调用LoadChainTip
函数根据pcoinsTip
中的GetBestBlock
信息去设置chainActive
中的tip。最后再通过assert去检查调用的结果。
if (!fReset) {
// 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(chainparams)) {
strLoadError = _("Unable to rewind the database to a pre-fork state. You will need to redownload the blockchain");
break;
}
}
接下来判断如果没有重新建立区块索引,那么需要调用RewindBlockIndex
去混滚交易索引,这个函数中主要是检查隔离见证,从顶部区块开始,移除所有启用了隔离见证但区块的结构却不符合隔离见证的要求的区块,如果回滚失败,那么就直接退出程序。
if (!is_coinsview_empty) {
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\n",
MIN_BLOCKS_TO_KEEP);
}
CBlockIndex* tip = chainActive.Tip();
RPCNotifyBlockChange(true, tip);
if (tip && tip->nTime > GetAdjustedTime() + 2 * 60 * 60) {
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(chainparams, pcoinsdbview.get(), gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL), gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS))) {
strLoadError = _("Corrupted block database detected");
break;
}
}
这部分是内部while循环的最后一步,主要是检查区块的完整性。首先判断如果启用了区块剪枝,并且-checkblocks
设置的数量大于剪枝保留的最少区块数量MIN_BLOCKS_TO_KEEP
,那么发出提醒,只能检查MIN_BLOCKS_TO_KEEP
这么多个区块。接下来一个判断链顶部区块的时间和当前的系统时间对比,如果区块中包含的时间晚于当前系统时间两小时以上,那么说明当前的系统时钟没有设置正确,需要重新调整,并退出程序。最后根据-checkblocks
参数检查数据库中存储的区块的有效性。
if (!fLoaded && !ShutdownRequested()) {
// first suggest a reindex
if (!fReset) {
bool fRet = uiInterface.ThreadSafeQuestion(
strLoadError + ".\n\n" + _("Do you want to rebuild the block database now?"),
strLoadError + ".\nPlease restart with -reindex or -reindex-chainstate to recover.",
"", CClientUIInterface::MSG_ERROR | CClientUIInterface::BTN_ABORT);
if (fRet) {
fReindex = true;
AbortShutdown();
} else {
LogPrintf("Aborted block database rebuild. Exiting.\n");
return false;
}
} else {
return InitError(strLoadError);
}
}
最后判断如果没有加载成功,并且没有收到终止信号,如果开始没有设置过fReset
,也就是重新加载区块索引,那么就先尝试重新建立区块索引,并重新返回最开始的while循环,重复一遍上述过程。
// As LoadBlockIndex can take several minutes, it's possible the user
// requested to kill the GUI during the last operation. If so, exit.
// As the program has not fully started yet, Shutdown() is possibly overkill.
if (ShutdownRequested()) {
LogPrintf("Shutdown requested. Exiting.\n");
return false;
}
fs::path est_path = GetDataDir() / FEE_ESTIMATES_FILENAME;
CAutoFile est_filein(fsbridge::fopen(est_path, "rb"), SER_DISK, CLIENT_VERSION);
// Allowed to fail as this file IS missing on first startup.
if (!est_filein.IsNull())
::feeEstimator.Read(est_filein);
fFeeEstimatesInitialized = true;
在第七步的最后,因为加载区块索引可能需要很长的时间,在这中间可能会收到终止的信号,但是在前面的循环当中可能会没有检测到,所以在循环结束之后立即进行检测,如果收到此信号,那么则立即退出程序。然后定义了一个文件路径指向~/.bitcoin/fee_estimates.dat
这个文件,然后通过CBlockPolicyEstimator
类型的对象feeEstimator
中的Read
函数来读取文件中心的信息,这个函数的实现位于fees.cpp line 924
,这个操作相当于是将feeEstimator
这个对象进行了持久化,保存到了文件当中,然后这里从文件中进行恢复。在后面用到这个对象中的其他函数时再来介绍其中的作用。第七步的所有步骤也就到此结束了。