比特币源码学习0.13(六)

这一篇开始介绍Appinit2 step 7

 // ********************************************************* Step 7: load block chain


9.7 加载区块链

区块文件升级

fReindex = GetBoolArg("-reindex", false);
bool fReindexChainState = GetBoolArg("-reindex-chainstate", false);
// Upgrading to 0.8; hard-link the old blknnnn.dat files into /blocks/
    boost::filesystem::path blocksDir = GetDataDir() / "blocks";
    if (!boost::filesystem::exists(blocksDir))
    {
        boost::filesystem::create_directories(blocksDir);
        bool linked = false;
        for (unsigned int i = 1; i < 10000; i++) {
            boost::filesystem::path source = GetDataDir() / strprintf("blk%04u.dat", i);
            if (!boost::filesystem::exists(source)) break;
            boost::filesystem::path dest = blocksDir / strprintf("blk%05u.dat", i-1);
            try {
                boost::filesystem::create_hard_link(source, dest);
                LogPrintf("Hardlinked %s -> %s\n", source.string(), dest.string());
                linked = true;
            } catch (const boost::filesystem::filesystem_error& e) {
                // Note: hardlink creation failing is not a disaster, it just means
                // blocks will get re-downloaded from peers.
                LogPrintf("Error hardlinking blk%04u.dat: %s\n", i, e.what());
                break;
            }
        }
        if (linked)
        {
            fReindex = true;
        }
    }

-reindex:Rebuild chain state and block index from the blk*.dat files on disk
-reindex-chainstate:Rebuild chain state from the currently indexed blocks

这段代码的目的是把老的已blknnnn.dat命名的文件通过硬连接的方式放到/block/目录下,转为blknnnnn.dat格式,可以理解为10000的计数不够用,扩展为100000的计数格式,因此是遍历10000以内的文件进行处理,最后置fReindexture


计算缓存大小

// cache size calculations
    int64_t nTotalCache = (GetArg("-dbcache", nDefaultDbCache) << 20);
    nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be less than nMinDbCache
    nTotalCache = std::min(nTotalCache, nMaxDbCache << 20); // total cache cannot be greater than nMaxDbcache
    int64_t nBlockTreeDBCache = nTotalCache / 8;
    nBlockTreeDBCache = std::min(nBlockTreeDBCache, (GetBoolArg("-txindex", DEFAULT_TXINDEX) ? nMaxBlockDBAndTxIndexCache : nMaxBlockDBCache) << 20);
    nTotalCache -= nBlockTreeDBCache;
    int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache
    nCoinDBCache = std::min(nCoinDBCache, nMaxCoinsDBCache << 20); // cap total coins db cache
    nTotalCache -= nCoinDBCache;
    nCoinCacheUsage = nTotalCache; // the rest goes to in-memory cache
    LogPrintf("Cache configuration:\n");
    LogPrintf("* Using %.1fMiB for block index database\n", nBlockTreeDBCache * (1.0 / 1024 / 1024));
    LogPrintf("* Using %.1fMiB for chain state database\n", nCoinDBCache * (1.0 / 1024 / 1024));
    LogPrintf("* Using %.1fMiB for in-memory UTXO set\n", nCoinCacheUsage * (1.0 / 1024 / 1024));

-dbcache:Set database cache size in megabytes (%d to %d, default: %d)
-txindex:Maintain a full transaction index, used by the getrawtransaction rpc call (default: %u)

计算缓存,总的缓存大小用nTotalCache表示,通过-dbcache参数设置,这个值需要介于nMinDbCachenMaxDbCache之间。接下来计算nBlockTreeDBCachenCoinDBCachenCoinCacheUsagenTotalCache=nBlockTreeDBCache+nCoinDBCache+nCoinCacheUsage。之后是在日志打印缓存信息。
可以看到是使用总缓存的1/8维护区块索引数据库,25-50%缓存用于保存链状态数据库,剩下的用于保存UTXO交易。
这里写图片描述


加载区块索引

这一段比较长while循环语句,我们一段段来分析

    bool fLoaded = false;
    while (!fLoaded) {
        bool fReset = fReindex;
        std::string strLoadError;

        uiInterface.InitMessage(_("Loading block index..."));

        nStart = GetTimeMillis();
        do {
            try {
                UnloadBlockIndex();
                delete pcoinsTip;
                delete pcoinsdbview;
                delete pcoinscatcher;
                delete pblocktree;

首先设置一个标记变量fLoaded表示索引加载是否成功,如果执行完循环体发现此变量还是false并且没有请求关闭程序,那么就再执行一遍。由于此循环体可能不止执行一遍,所以先调用UnloadBlockIndex()来清除上次循环可能设置的一些变量。下面来看UnloadBlockIndex()这个函数的实现

//main.h
/** Unload database information */
void UnloadBlockIndex();
//main.cpp
void UnloadBlockIndex()
{
    LOCK(cs_main);//线程安全访问
    setBlockIndexCandidates.clear();
    chainActive.SetTip(NULL);
    pindexBestInvalid = NULL;
    pindexBestHeader = NULL;
    mempool.clear();
    mapOrphanTransactions.clear();
    mapOrphanTransactionsByPrev.clear();
    nSyncStarted = 0;
    mapBlocksUnlinked.clear();
    vinfoBlockFile.clear();
    nLastBlockFile = 0;
    nBlockSequenceId = 1;
    mapBlockSource.clear();
    mapBlocksInFlight.clear();
    nPreferredDownload = 0;
    setDirtyBlockIndex.clear();
    setDirtyFileInfo.clear();
    mapNodeState.clear();
    recentRejects.reset(NULL);
    versionbitscache.Clear();
    for (int b = 0; b < VERSIONBITS_NUM_BITS; b++) {
        warningcache[b].clear();
    }

    BOOST_FOREACH(BlockMap::value_type& entry, mapBlockIndex) {
        delete entry.second;
    }
    mapBlockIndex.clear();//维护所有的区块索引
    fHavePruned = false;
}

返回上一段代码,继续后面的代码

 pblocktree = new CBlockTreeDB(nBlockTreeDBCache, false, fReindex);
 pcoinsdbview = new CCoinsViewDB(nCoinDBCache, false, fReindex || fReindexChainState);
 pcoinscatcher = new CCoinsViewErrorCatcher(pcoinsdbview);
 pcoinsTip = new CCoinsViewCache(pcoinscatcher);

新建四个类分别用于向/blocks/index/的区块数据进行读写,查看 coin database ,对数据库错误读取的捕获,coin database的另一个缓存

class CBlockTreeDB /* Access to the block database (blocks/index/) /
class CCoinsViewDB /* CCoinsView backed by the coin database (chainstate/) /
class CCoinsViewErrorCatcher 关闭数据库的错误读取
class CCoinsViewCache /*CCoinsView that adds a memory cache for transactions to another CCoinsView/

这里再来详细介绍下上述四个类

CBlockTreeDB

用于处理区块的数据库,定义在txdb.h中

** Access to the block database (blocks/index/) */
class CBlockTreeDB : public CDBWrapper
{
public:
    CBlockTreeDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false);
private:
    CBlockTreeDB(const CBlockTreeDB&);
    void operator=(const CBlockTreeDB&);
public:
    bool WriteBatchSync(const std::vector<std::pair<int, const CBlockFileInfo*> >& fileInfo, int nLastFile, const std::vector<const CBlockIndex*>& blockinfo);
    bool ReadBlockFileInfo(int nFile, CBlockFileInfo &fileinfo);
    bool ReadLastBlockFile(int &nFile);
    bool WriteReindexing(bool fReindex);
    bool ReadReindexing(bool &fReindex);
    bool ReadTxIndex(const uint256 &txid, CDiskTxPos &pos);
    bool WriteTxIndex(const std::vector<std::pair<uint256, CDiskTxPos> > &list);
    bool WriteFlag(const std::string &name, bool fValue);
    bool ReadFlag(const std::string &name, bool &fValue);
    bool LoadBlockIndexGuts(boost::function<CBlockIndex*(const uint256&)> insertBlockIndex);
};

可以看到这个类继承于CDBWrapper,而这个类封装的就是leveldb的操作。这个leveldb就是存储我们比特币所有区块的地方,也就是数据库

Leveldb:是一个google实现的非常高效的kv数据库,目前的版本1.2能够支持billion级别的数据量了。 在这个数量级别下还有着非常高的性能,主要归功于它的良好的设计。
KV数据库:使用键值(Key-Value)存储数据库,这是一种NoSQL(非关系型数据库)模型,其数据按照键值对的形式进行组织、索引和存储。

//dbwrapper.h
class CDBWrapper
{
    friend const std::vector<unsigned char>& dbwrapper_private::GetObfuscateKey(const CDBWrapper &w);
private:
    //! custom environment this database is using (may be NULL in case of default environment)
    leveldb::Env* penv;

    //! database options used
    leveldb::Options options;

    //! options used when reading from the database
    leveldb::ReadOptions readoptions;

    //! options used when iterating over values of the database
    leveldb::ReadOptions iteroptions;
       ···

在编译的时候就提到过比特币采用的数据库是leveldb,然后由CDBWrapper进行了封装。


CCoinsViewDB

//txdb.h
/** CCoinsView backed by the coin database (chainstate/) */
class CCoinsViewDB : public CCoinsView
{
protected:
    CDBWrapper db;
public:
    CCoinsViewDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false);

    bool GetCoins(const uint256 &txid, CCoins &coins) const;
    bool HaveCoins(const uint256 &txid) const;
    uint256 GetBestBlock() const;
    bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
    CCoinsViewCursor *Cursor() const;
};

这个类继承自CCoinsView,来看CCoinsView

/** Abstract view on the open txout dataset. */
class CCoinsView
{
public:
    //! Retrieve the CCoins (unspent transaction outputs) for a given txid
    virtual bool GetCoins(const uint256 &txid, CCoins &coins) const;

    //! Just check whether we have data for a given txid.
    //! This may (but cannot always) return true for fully spent transactions
    virtual bool HaveCoins(const uint256 &txid) const;

    //! Retrieve the block hash whose state this CCoinsView currently represents
    virtual uint256 GetBestBlock() const;

    //! Do a bulk modification (multiple CCoins changes + BestBlock change).
    //! The passed mapCoins can be modified.
    virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);

    //! Get a cursor to iterate over the whole state
    virtual CCoinsViewCursor *Cursor() const;

    //! As we use CCoinsViews polymorphically, have a virtual destructor
    virtual ~CCoinsView() {}
};

这个类是对我们所拥有多少比特币的操作,可以看到这个类的函数都是虚函数,所以这些函数的操作都是在CCoinsViewDB中进行实现的,其中的CCoins就是我们的UTXO的分装。

//coins.h 有详细的注释,有时间看一下
class CCoins
{
public:
    //! whether transaction is a coinbase
    bool fCoinBase;

    //! unspent transaction outputs; spent outputs are .IsNull(); spent outputs at the end of the array are dropped
    std::vector<CTxOut> vout;

    //! at which height this transaction was included in the active block chain
    int nHeight;
···

CCoinsViewCache

这个类是一个缓存类。。。用到了再说


接下来判断fReindex是否为true,这个变量就是-reindex用来设定是否重新创建所有的索引,如果为true,那么就调用CBlockTreeDB的WriteReindexing向数据库写入数据

 if (fReindex) {
                    pblocktree->WriteReindexing(true);
                    //If we're reindexing in prune mode, wipe away unusable block files and all undo data files
                    if (fPruneMode)
                        CleanupBlockRevFiles();
                }

如果在裁剪模式重建索引,擦除不可用的块文件和所有撤消数据文件,来看WriteReindexing的具体调用过程

//txdb.cpp
bool CBlockTreeDB::WriteReindexing(bool fReindexing) {
    if (fReindexing)
        return Write(DB_REINDEX_FLAG, '1');
    else
        return Erase(DB_REINDEX_FLAG);
}
//dbwrapper.h class CDBBatch
template <typename K, typename V>
    void Write(const K& key, const V& value)
    {
        CDataStream ssKey(SER_DISK, CLIENT_VERSION);
        ssKey.reserve(ssKey.GetSerializeSize(key));
        ssKey << key;
        leveldb::Slice slKey(&ssKey[0], ssKey.size());

        CDataStream ssValue(SER_DISK, CLIENT_VERSION);
        ssValue.reserve(ssValue.GetSerializeSize(value));
        ssValue << value;
        ssValue.Xor(dbwrapper_private::GetObfuscateKey(parent));
        leveldb::Slice slValue(&ssValue[0], ssValue.size());
        batch.Put(slKey, slValue);
    }
    //把传入的参数转换数据格式,调用同类中定义的WriteBatch类的batch的函数Put写入数据库
    leveldb::WriteBatch batch;
    //实现向leveldb数据库写入数据
    void WriteBatch::Put(const Slice& key, const Slice& value) {
  WriteBatchInternal::SetCount(this, WriteBatchInternal::Count(this) + 1);
  rep_.push_back(static_cast<char>(kTypeValue));
  PutLengthPrefixedSlice(&rep_, key);
  PutLengthPrefixedSlice(&rep_, value);
}

CleanupBlockRevFiles()的实现如下

If we’re using -prune with -reindex, then delete block files that will be ignored by the reindex. Since reindexing works by starting at block file 0 and looping until a blockfile is missing, do the same here to delete any later block files after a gap. Also delete all rev files since they’ll be rewritten by the reindex anyway. This ensures that vinfoBlockFile is in sync with what’s actually on disk by the time we start downloading, so that pruning works correctly.

如果我们同时使用-prune-reindex,那么将删除被reindex忽略的块文件。 由于重建索引的工作原理是从块文件0开始并循环直到缺少块文件,所以在此处执行相同操作以删除缺失后的所有块文件。 同时删除所有rev文件,因为它们将被reindex重写。 这确保了vinfoBlockFile与我们开始下载时实际在磁盘上的内容同步,因此修剪正常执行。

//init.cpp
void CleanupBlockRevFiles()
{
    using namespace boost::filesystem;
    map<string, path> mapBlockFiles;

    // Glob all blk?????.dat and rev?????.dat files from the blocks directory.
    // Remove the rev files immediately and insert the blk file paths into an
    // ordered map keyed by block file index.
    LogPrintf("Removing unusable blk?????.dat and rev?????.dat files for -reindex with -prune\n");
    path blocksdir = GetDataDir() / "blocks";
    for (directory_iterator it(blocksdir); it != directory_iterator(); it++) {
        if (is_regular_file(*it) &&
            it->path().filename().string().length() == 12 &&
            it->path().filename().string().substr(8,4) == ".dat")
        {
            if (it->path().filename().string().substr(0,3) == "blk")
                mapBlockFiles[it->path().filename().string().substr(3,5)] = it->path();
            else if (it->path().filename().string().substr(0,3) == "rev")
                remove(it->path());
        }
    }
    // Remove all block files that aren't part of a contiguous set starting at
    // zero by walking the ordered map (keys are block file indices) by
    // keeping a separate counter.  Once we hit a gap (or if 0 doesn't exist)
    // start removing block files.
    int nContigCounter = 0;
    BOOST_FOREACH(const PAIRTYPE(string, path)& item, mapBlockFiles) {
        if (atoi(item.first) == nContigCounter) {
            nContigCounter++;
            continue;
        }
        remove(item.second);
    }
}

从注释内容来看,这个函数的工作就是删除某个缺失的区块之后的所有区块数据,以及rev开头的文件。那么来看代码就比较好理解了,第一段是先把所有的blk,rev文件和对应的路径保存到一个map(mapBlockFiles)中,第二段开始从0开始计数,直到遇到一个不一致的文件名,就从这个开始删除。


LoadBlockIndex

  if (!LoadBlockIndex()) {
                    strLoadError = _("Error loading block database");
                    break;
                }
//main.cpp
bool LoadBlockIndex()
{
    // Load block index from databases
    if (!fReindex && !LoadBlockIndexDB())
        return false;
    return true;
}

LoadBlockIndex的作用是从数据库加载区块索引。调用LoadBlockIndex()主要是调用其中的LoadBlockIndexDB(),这个函数会从数据库加载并检查区块索引,并打印日志信息

//main.cpp
bool static LoadBlockIndexDB()
{
    const CChainParams& chainparams = Params();
    if (!pblocktree->LoadBlockIndexGuts(InsertBlockIndex))
        return false;

    boost::this_thread::interruption_point();

    // Calculate nChainWork
    vector<pair<int, CBlockIndex*> > vSortedByHeight;
    vSortedByHeight.reserve(mapBlockIndex.size());
    BOOST_FOREACH(const PAIRTYPE(uint256, CBlockIndex*)& item, mapBlockIndex)
    {
        CBlockIndex* pindex = item.second;
        vSortedByHeight.push_back(make_pair(pindex->nHeight, pindex));
    }
    sort(vSortedByHeight.begin(), vSortedByHeight.end());
    BOOST_FOREACH(const PAIRTYPE(int, CBlockIndex*)& item, vSortedByHeight)
    {
        CBlockIndex* pindex = item.second;
        pindex->nChainWork = (pindex->pprev ? pindex->pprev->nChainWork : 0) + GetBlockProof(*pindex);
        // We can link the chain of blocks for which we've received transactions at some point.
        // Pruned nodes may have deleted the block.
        if (pindex->nTx > 0) {
            if (pindex->pprev) {
                if (pindex->pprev->nChainTx) {
                    pindex->nChainTx = pindex->pprev->nChainTx + pindex->nTx;
                } else {
                    pindex->nChainTx = 0;
                    mapBlocksUnlinked.insert(std::make_pair(pindex->pprev, pindex));
                }
            } else {
                pindex->nChainTx = pindex->nTx;
            }
        }
        if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && (pindex->nChainTx || pindex->pprev == NULL))
            setBlockIndexCandidates.insert(pindex);
        if (pindex->nStatus & BLOCK_FAILED_MASK && (!pindexBestInvalid || pindex->nChainWork > pindexBestInvalid->nChainWork))
            pindexBestInvalid = pindex;
        if (pindex->pprev)
            pindex->BuildSkip();
        if (pindex->IsValid(BLOCK_VALID_TREE) && (pindexBestHeader == NULL || CBlockIndexWorkComparator()(pindexBestHeader, pindex)))
            pindexBestHeader = pindex;
    }

    // Load block file info
    pblocktree->ReadLastBlockFile(nLastBlockFile);
    vinfoBlockFile.resize(nLastBlockFile + 1);
    LogPrintf("%s: last block file = %i\n", __func__, nLastBlockFile);
    for (int nFile = 0; nFile <= nLastBlockFile; nFile++) {
        pblocktree->ReadBlockFileInfo(nFile, vinfoBlockFile[nFile]);
    }
    LogPrintf("%s: last block file info: %s\n", __func__, vinfoBlockFile[nLastBlockFile].ToString());
    for (int nFile = nLastBlockFile + 1; true; nFile++) {
        CBlockFileInfo info;
        if (pblocktree->ReadBlockFileInfo(nFile, info)) {
            vinfoBlockFile.push_back(info);
        } else {
            break;
        }
    }

    // Check presence of blk files
    LogPrintf("Checking all blk files are present...\n");
    set<int> setBlkDataFiles;
    BOOST_FOREACH(const PAIRTYPE(uint256, CBlockIndex*)& item, mapBlockIndex)
    {
        CBlockIndex* pindex = item.second;
        if (pindex->nStatus & BLOCK_HAVE_DATA) {
            setBlkDataFiles.insert(pindex->nFile);
        }
    }
    for (std::set<int>::iterator it = setBlkDataFiles.begin(); it != setBlkDataFiles.end(); it++)
    {
        CDiskBlockPos pos(*it, 0);
        if (CAutoFile(OpenBlockFile(pos, true), SER_DISK, CLIENT_VERSION).IsNull()) {
            return false;
        }
    }

    // Check whether we have ever pruned block & undo files
    pblocktree->ReadFlag("prunedblockfiles", fHavePruned);
    if (fHavePruned)
        LogPrintf("LoadBlockIndexDB(): Block files have previously been pruned\n");

    // Check whether we need to continue reindexing
    bool fReindexing = false;
    pblocktree->ReadReindexing(fReindexing);
    fReindex |= fReindexing;

    // Check whether we have a transaction index
    pblocktree->ReadFlag("txindex", fTxIndex);
    LogPrintf("%s: transaction index %s\n", __func__, fTxIndex ? "enabled" : "disabled");

    // Load pointer to end of best chain
    BlockMap::iterator it = mapBlockIndex.find(pcoinsTip->GetBestBlock());
    if (it == mapBlockIndex.end())
        return true;
    chainActive.SetTip(it->second);

    PruneBlockIndexCandidates();

    LogPrintf("%s: hashBestChain=%s height=%d date=%s progress=%f\n", __func__,
        chainActive.Tip()->GetBlockHash().ToString(), chainActive.Height(),
        DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()),
        Checkpoints::GuessVerificationProgress(chainparams.Checkpoints(), chainActive.Tip()));

    return true;
}

这里写图片描述


合法性检测

// If the loaded chain has a wrong genesis, bail out immediately
// (we're likely using a testnet datadir, or the other way around).
//检查mapBlockIndex中是否加载了创世区块
if (!mapBlockIndex.empty() && mapBlockIndex.count(chainparams.GetConsensus().hashGenesisBlock) == 0)
    return InitError(_("Incorrect or no genesis block found. Wrong datadir for network?"));
 // Initialize the block index (no-op if non-empty database was already loaded)
 //初始化区块索引,如果已加载了数据库那么这一步无操作
if (!InitBlockIndex(chainparams)) {
    strLoadError = _("Error initializing block database");
    break;
    }
// Check for changed -txindex state
//检查-txindex的状态,因为在上一个函数LoadBlockIndex中如果设置了reindex,那么-txindex也会被重置
if (fTxIndex != GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
    strLoadError = _("You need to rebuild the database using -reindex-chainstate to change -txindex");
    break;
 }
 // Check for changed -prune state.  What we are concerned about is a user who has pruned blocks
// in the past, but is now trying to run unpruned.
//检查-prune,因为用户可能会手动删除一些文件然后又想在非裁剪模式中运行
if (fHavePruned && !fPruneMode) {
    strLoadError = _("You need to rebuild the database using -reindex to go back to unpruned mode.  This will redownload the entire blockchain");
    break;
 }

step7里后面的代码都是对加载区块链的各种检查验证部分,后面的部分就不详细写了,后面就没有参考的文章了

猜你喜欢

转载自blog.csdn.net/m0_37847176/article/details/81662357