比特币源码解析(23) - 可执行程序 - Bitcoind

0x00 回来啦~

翻起上一篇比特币的分析还是2017年11月16日,现已经2018年6月26了,半年时间过的真快啊。中间也一直想继续写,但总是因为各种事耽误了,回想起来其实也没有那么忙,总结一下还是没有计划加上拖延症晚期。看到写的博客有这么多人阅读,有这么多评论真的很开心,这次一定要把代码分析完了!!

0x01 AppInitMain Step 7: load block chain 续(1)

LoadGenesisBlock

在上一篇的最后,我们分析到了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;
}

CCoinsViewDB

到这里,目前的状态要么是在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,主要是实现从COutPointCoin类型的转换,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

接下来是一个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函数的功能了:解决分叉!再往下看就是通过三个指针pindexOldpindexNewpindexFork分别确立三个点的位置,然后回滚就分支,接着roll forward新分支,就完成了整个过程。

你可能感兴趣的:(比特币源码分析)