比特幣源碼解析(22) - 可執行程式 - Bitcoind

來源:互聯網
上載者:User
0x01 AppInitMain Step 7: load block chain 計算緩衝大小
    fReindex = gArgs.GetBoolArg("-reindex", false);    bool fReindexChainState = gArgs.GetBoolArg("-reindex-chainstate", false);    // cache size calculations    int64_t nTotalCache = (gArgs.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, (gArgs.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    int64_t nMempoolSizeMax = gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;    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 (plus up to %.1fMiB of unused mempool space)\n", nCoinCacheUsage * (1.0 / 1024 / 1024), nMempoolSizeMax * (1.0 / 1024 / 1024));

-reindex:從磁碟上的blk*.dat中重建chain state和block index。

-reindex-chainstate:從當前的區塊索引中建立chain state。

-dbcache:設定資料庫緩衝大小,單位為MB,預設值為450.

-txindex:維護完整的交易索引,主要是被getrawtransaction這個rpc調用來使用,預設不啟用。

-maxmempool:設定交易記憶體池的最大大小,單位為MB,預設值為300。

首先從命令列中擷取兩個參數,這兩個重索引預設都是不啟用。接下來開始計算緩衝的大小,首先是總的緩衝大小用nTotalCache表示,通過-dbcache參數設定,然後這個值要取在nMinDbCache和nMaxDbCache之間。接下來計算nBlockTreeDBCache和nCoinDBCache以及nCoinCacheUsage,並且nTotalCache = nBlockTreeDBCache +nCoinDBCache + nCoinCacheUsage。 載入區塊索引

接下來是一個很長的while迴圈語句,這個迴圈用來,我們一點一點來進行分析。

    bool fLoaded = false;    while (!fLoaded && !fRequestShutdown) {        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

// May NOT be used after any connections are up as much// of the peer-processing logic assumes a consistent// block index statevoid UnloadBlockIndex(){    LOCK(cs_main); // 安全執行緒訪問    setBlockIndexCandidates.clear(); //     chainActive.SetTip(nullptr);    pindexBestInvalid = nullptr;    pindexBestHeader = nullptr;    mempool.clear();    mapBlocksUnlinked.clear();    vinfoBlockFile.clear();    nLastBlockFile = 0;    nBlockSequenceId = 1;    setDirtyBlockIndex.clear();    setDirtyFileInfo.clear();    versionbitscache.Clear();    for (int b = 0; b < VERSIONBITS_NUM_BITS; b++) {        warningcache[b].clear();    }    for (BlockMap::value_type& entry : mapBlockIndex) {        delete entry.second;    }    mapBlockIndex.clear(); //維護所有的區塊索引    //mapBlockIndex類型為unordered_map<uint256, CBlockIndex*, BlockHasher>    fHavePruned = false;}
寫入重索引
                pblocktree = new CBlockTreeDB(nBlockTreeDBCache, false, fReset);                if (fReset) {                    pblocktree->WriteReindexing(true);                    //If we're reindexing in prune mode, wipe away unusable block files and all undo data files                    if (fPruneMode)                        CleanupBlockRevFiles();                }                if (fRequestShutdown) break;

接下來建立一個CBlockTreeDB類,這個類是用來向/blocks/index/*下面的檔案進行讀寫操作。然後判斷fReset是否為true,這個變數也就是-reindex用來設定是否重新建立所有的索引,如果為true,那麼就調用CBlockTreeDB中的WriteReindexing向資料庫中寫入資料,具體的調用過程如下:

bool CBlockTreeDB::WriteReindexing(bool fReindexing) {    if (fReindexing)        return Write(DB_REINDEX_FLAG, '1');    else        return Erase(DB_REINDEX_FLAG);}// WriteReindexing再調用從CDBWrapper中繼承的Write    template <typename K, typename V>    bool Write(const K& key, const V& value, bool fSync = false)    {        CDBBatch batch(*this);        batch.Write(key, value);        return WriteBatch(batch, fSync);    }//Write再調用同類中的WriteBatch實現向leveldb資料庫中寫入資料// 其中的pdb就是leveldb資料庫指標bool CDBWrapper::WriteBatch(CDBBatch& batch, bool fSync){    leveldb::Status status = pdb->Write(fSync ? syncoptions : writeoptions, &batch.batch);    dbwrapper_private::HandleError(status);    return true;}

接下來的fPruneMode參數在http://blog.csdn.net/pure_lady/article/details/77982837#t1已經介紹過,是用來修剪已確認的區塊的,這裡如果在啟用了重索引,那麼就得先刪除已驗證的區塊資訊。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.void CleanupBlockRevFiles(){    std::map<std::string, fs::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");    fs::path blocksdir = GetDataDir() / "blocks";    for (fs::directory_iterator it(blocksdir); it != fs::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;    for (const std::pair<std::string, fs::path>& item : mapBlockFiles) {        if (atoi(item.first) == nContigCounter) {            nContigCounter++;            continue;        }        remove(item.second);    }}

首先解釋下開頭的注釋,如果我們將-reindex和-prune一起用,那麼就將重索引時不考慮的一些區塊檔案直接刪除。因為重索引是從0號區塊一直連續的讀取,直到某一個區塊資訊缺失就停止讀取,缺失的區塊之後所有的區塊都會被直接刪除。同時還需要刪除rev檔案,因為這些檔案在重索引時會重建,關於rev檔案的介紹,可以參考之前說過的http://blog.csdn.net/pure_lady/article/details/77982837#t1。根據注釋的內容來看,這個函數要做的就是刪除某個缺失的區塊之後所有的區塊資料,以及rev開頭的檔案。那麼接下來的代碼就比較容易看懂了:先將所有的檔案和對應的路徑儲存到一個map中,然後用一個變數nContigCounter從0開始計數,直到遇到第一個不一致的檔案名稱,就從這個開始刪除。 LoadBlockIndex

// 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.// Note that it also sets fReindex based on the disk flag!// From here on out fReindex and fReset mean something different!if (!LoadBlockIndex(chainparams)) {  strLoadError = _("Error loading block database");  break;}

解釋下注釋:LoadBlockIndex首先將從資料庫中載入fTxIndex變數,如果是在進行重索引那麼就從命令列讀取fTxIndex的值。另外如果我們之前刪除過區塊檔案,那麼這裡還會架子啊fHavePruned變數,同時還會根據磁碟上的標記來設定fReindex變數,並且從此往後fReindex和fReset就表示不同的含義。我們再來看看LoadBlockIndex的實現:

bool LoadBlockIndex(const CChainParams& chainparams){    // Load block index from databases    bool needs_init = fReindex;    if (!fReindex) {        bool ret = LoadBlockIndexDB(chainparams);        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;}

首先參數chainparams在之前介紹過,是根據不同的網路Main,RegTest,TestNet三個不同的參數設定靜態寫好的參數。然後檢查fReindex變數,如果設定了這個變數,那麼之後會進行重新索引,這裡也就沒有必要先載入索引了;如果沒有設定fReindex,那麼這裡就是首次載入也是唯一的載入索引的地方。所謂載入索引,就是將/blocks/index/*中的檔案載入到記憶體,實現時就是通過LoadBlockIndexDB()並將結果儲存在變數mapBlockIndex中,如果載入成功,那麼mapBlockIndex就不為空白,needs_init也就為false。 合法性檢測

                // 檢查mapBlockIndex中是否載入了創世塊                if (!mapBlockIndex.empty() && mapBlockIndex.count(chainparams.GetConsensus().hashGenesisBlock) == 0)                    return InitError(_("Incorrect or no genesis block found. Wrong datadir for network?"));                // 檢查txindex的狀態,因為在上一個函數(LoadBlockIndex)中如果設定了reindex,                //那麼fTxindex也會被重設                 if (fTxIndex != gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {                    strLoadError = _("You need to rebuild the database using -reindex to change -txindex");                    break;                }                // 檢查-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;                }                // 如果沒有設定初始化,並且創世塊載入失敗                if (!fReindex && !LoadGenesisBlock(chainparams)) {                    strLoadError = _("Error initializing block database");                    break;                }

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.