翻起上一篇比特币的分析还是2017年11月16日,现已经2018年6月26了,半年时间过的真快啊。中间也一直想继续写,但总是因为各种事耽误了,回想起来其实也没有那么忙,总结一下还是没有计划加上拖延症晚期。看到写的博客有这么多人阅读,有这么多评论真的很开心,这次一定要把代码分析完了!!
在上一篇的最后,我们分析到了LoadGenesisBlock这个函数,函数的作用如其名,也就是加载创世区块(区块链的第一个区块的统称),函数的定义在src/validation.cpp line 4353
,具体如下,
// src/valication.cpp line 4353
bool LoadGenesisBlock(const CChainParams& chainparams)
{
return g_chainstate.LoadGenesisBlock(chainparams);
}
函数中g_chainstate
是一个CChainState
类型的全局变量,该结构主要是提供更新本地链状态的API,定义在src/validation.cpp line 107
,具体功能可以查看定义。该结构中就包含了LoadGenesisBlock
函数,该函数实现如下:
bool CChainState::LoadGenesisBlock(const CChainParams& chainparams)
{
LOCK(cs_main); // 多线程安全
// 首先判断是否已经初始化了创世块
if (mapBlockIndex.count(chainparams.GenesisBlock().GetHash()))
return true;
try {
// 创建创世块并保存到硬盘
CBlock &block = const_cast(chainparams.GenesisBlock());
CDiskBlockPos blockPos = SaveBlockToDisk(block, 0, chainparams, nullptr);
if (blockPos.IsNull())
return error("%s: writing genesis block to disk failed", __func__);
// 将区块索引加入到mapBlockIndex中
CBlockIndex *pindex = AddToBlockIndex(block);
CValidationState state;
if (!ReceivedBlockTransactions(block, state, pindex, blockPos, chainparams.GetConsensus()))
return error("%s: genesis block not accepted (%s)", __func__, FormatStateMessage(state));
} catch (const std::runtime_error& e) {
return error("%s: failed to write genesis block: %s", __func__, e.what());
}
return true;
}
上面的函数中调用了一个ReceivedBlockTransactions
函数,这个函数的作用是将区块的信息更新到pindex
参数中,同时使用BFS(深度优先搜索)方法寻找当前链所有可能的下一个区块。具体实现当中,是位于src/validation.cpp line 2953
,将当前所有可能的区块索引保存在一个deque
中,不断的从孤立的区块集合mapBlocksUnlinked
中查找,并将当前链的所有可能的下一个区块保存到setBlockIndexCandidates
中。ReceivedBlockTransactions
这个函数的实现上理解稍微比之前复杂一点,但明白功能之后再看就容易理解了。
// src/validation.cpp line 2953
bool CChainState::ReceivedBlockTransactions(const CBlock &block, CValidationState& state, CBlockIndex *pindexNew, const CDiskBlockPos& pos, const Consensus::Params& consensusParams)
{
// 将block中的信息更新到pindexNew中
pindexNew->nTx = block.vtx.size();
pindexNew->nChainTx = 0;
pindexNew->nFile = pos.nFile;
pindexNew->nDataPos = pos.nPos;
pindexNew->nUndoPos = 0;
pindexNew->nStatus |= BLOCK_HAVE_DATA;
if (IsWitnessEnabled(pindexNew->pprev, consensusParams)) {
pindexNew->nStatus |= BLOCK_OPT_WITNESS;
}
pindexNew->RaiseValidity(BLOCK_VALID_TRANSACTIONS);
setDirtyBlockIndex.insert(pindexNew);
// 创世区块或者父区块已经连上主链
// nChainTx表示从创世区块到当前区块总共包含多少交易,如果次值为非0那么说明所有父亲都是有效的
if (pindexNew->pprev == nullptr || pindexNew->pprev->nChainTx) {
std::deque queue;
queue.push_back(pindexNew);
// 递归处理chainActive.Tip()下一个所有可能的区块,保存在setBlockIndexCandidates中
while (!queue.empty()) {
CBlockIndex *pindex = queue.front();
queue.pop_front();
pindex->nChainTx = (pindex->pprev ? pindex->pprev->nChainTx : 0) + pindex->nTx;
{
LOCK(cs_nBlockSequenceId);
pindex->nSequenceId = nBlockSequenceId++;
}
if (chainActive.Tip() == nullptr || !setBlockIndexCandidates.value_comp()(pindex, chainActive.Tip())) {
setBlockIndexCandidates.insert(pindex);
}
std::pair<std::multimap ::iterator, std::multimap ::iterator> range = mapBlocksUnlinked.equal_range(pindex);
while (range.first != range.second) {
std::multimap ::iterator it = range.first;
queue.push_back(it->second);
range.first++;
mapBlocksUnlinked.erase(it);
}
}
} else {
if (pindexNew->pprev && pindexNew->pprev->IsValid(BLOCK_VALID_TREE)) {
mapBlocksUnlinked.insert(std::make_pair(pindexNew->pprev, pindexNew));
}
}
return true;
}
到这里,目前的状态要么是在reindex,即重新建立所有区块索引;要么已经将所有区块的索引加载到mapBlockIndex
中了。继续看下面的代码
pcoinsdbview.reset(new CCoinsViewDB(nCoinDBCache, false, fReset || fReindexChainState));
pcoinscatcher.reset(new CCoinsViewErrorCatcher(pcoinsdbview.get()));
if (!pcoinsdbview->Upgrade()) {
strLoadError = _("Error upgrading chainstate database");
break;
}
这段代码的目的是新建CCoinsViewDB
类型的对象,并将其地址赋值给pcoinsdbview
,其中CCoinsViewDB
继承了抽象类CCoinsView
,主要是实现从COutPoint
向Coin
类型的转换,COutPoint
的结构我们在之前(https://blog.csdn.net/pure_lady/article/details/77771392)介绍过,而Coin
其实就是UTXO。所以这里新建CCoinsViewDB
对象也就是为了更加方便的获取本地数据库中的每一个UTXO的状态,并对其进行操作。后面一个CCoinsViewErrorCatcher
对象是为了捕获数据库读取错误,该对象的定义在src/init.cpp line 158
。最后一个Upgrade()
函数是将数据库中的数据的旧格式CCoins
转换到新格式Coin
,区别是CCoins
是将一个交易中所有的UTXO
放到一个vector
中,而Coin
是每一个UTXO
单独占有的对象,在前面CCoinsViewDB
中我们也可以发现现在所有的访问都是操作Coin
对象,而不是CCoins
对象了。
接下来是一个ReplayBlocks
函数的调用,这个函数的实现如下,
// src/validation.cpp line 4090
bool CChainState::ReplayBlocks(const CChainParams& params, CCoinsView* view)
{
LOCK(cs_main);
CCoinsViewCache cache(view);
std::vector hashHeads = view->GetHeadBlocks();
if (hashHeads.empty()) return true; // We're already in a consistent state.
if (hashHeads.size() != 2) return error("ReplayBlocks(): unknown inconsistent state");
uiInterface.ShowProgress(_("Replaying blocks..."), 0, false);
LogPrintf("Replaying blocks\n");
const CBlockIndex* pindexOld = nullptr; // Old tip during the interrupted flush.
const CBlockIndex* pindexNew; // New tip during the interrupted flush.
const CBlockIndex* pindexFork = nullptr; // Latest block common to both the old and the new tip.
if (mapBlockIndex.count(hashHeads[0]) == 0) {
return error("ReplayBlocks(): reorganization to unknown block requested");
}
pindexNew = mapBlockIndex[hashHeads[0]];
if (!hashHeads[1].IsNull()) { // The old tip is allowed to be 0, indicating it's the first flush.
if (mapBlockIndex.count(hashHeads[1]) == 0) {
return error("ReplayBlocks(): reorganization from unknown block requested");
}
pindexOld = mapBlockIndex[hashHeads[1]];
pindexFork = LastCommonAncestor(pindexOld, pindexNew);
assert(pindexFork != nullptr);
}
// 回滚旧分支
while (pindexOld != pindexFork) {
if (pindexOld->nHeight > 0) { // Never disconnect the genesis block.
CBlock block;
if (!ReadBlockFromDisk(block, pindexOld, params.GetConsensus())) {
return error("RollbackBlock(): ReadBlockFromDisk() failed at %d, hash=%s", pindexOld->nHeight, pindexOld->GetBlockHash().ToString());
}
LogPrintf("Rolling back %s (%i)\n", pindexOld->GetBlockHash().ToString(), pindexOld->nHeight);
DisconnectResult res = DisconnectBlock(block, pindexOld, cache);
if (res == DISCONNECT_FAILED) {
return error("RollbackBlock(): DisconnectBlock failed at %d, hash=%s", pindexOld->nHeight, pindexOld->GetBlockHash().ToString());
}
}
pindexOld = pindexOld->pprev;
}
// 建立从fork point到新分叉点的分支
int nForkHeight = pindexFork ? pindexFork->nHeight : 0;
for (int nHeight = nForkHeight + 1; nHeight <= pindexNew->nHeight; ++nHeight) {
const CBlockIndex* pindex = pindexNew->GetAncestor(nHeight);
LogPrintf("Rolling forward %s (%i)\n", pindex->GetBlockHash().ToString(), nHeight);
if (!RollforwardBlock(pindex, cache, params)) return false;
}
cache.SetBestBlock(pindexNew->GetBlockHash());
cache.Flush();
uiInterface.ShowProgress("", 100, false);
return true;
}
函数的第三句调用了CCoinwView
中的GetHeadBlocks
函数,这个函数的代码注释(位于src/coins.h line 160
)如下,
Retrieve the range of blocks that may have been only partially written. If the database is in a consistent state, the result is the empty vector. Otherwise, a two-element vector is returned consisting of the new and the old block hash, in that order.
翻译一下就是,获取一段区块的范围,这个范围中的区块可能只有一部分被写入到文件中。说人话其实就是如果出现了分叉,那么节点肯定只保存了其中一个分叉(对应only partially written),这时候返回两个分叉的头节点,其他情况(即没有分叉,也就是consistent state)都返回empty vector。
通过注释我们可以大概了解了GetHeadBlocks
这个函数的功能,也就是如果存在分叉,那么返回分叉的两个端点。通关这个函数其实我们也大概可以明白ReplaysBlocks
函数的功能了:解决分叉!再往下看就是通过三个指针pindexOld
、pindexNew
和pindexFork
分别确立三个点的位置,然后回滚就分支,接着roll forward新分支,就完成了整个过程。