1. 程式人生 > >比特幣原始碼解析(22)

比特幣原始碼解析(22)

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 stateblock index

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

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

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

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

首先從命令列中獲取兩個引數,這兩個重索引預設都是不啟用。接下來開始計算快取的大小,首先是總的快取大小用nTotalCache表示,通過-dbcache引數設定,然後這個值要取在nMinDbCachenMaxDbCache之間。接下來計算nBlockTreeDBCachenCoinDBCache以及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 state
void 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變數,並且從此往後fReindexfReset就表示不同的含義。我們再來看看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在之前介紹過,是根據不同的網路MainRegTestTestNet三個不同的引數設定靜態寫好的引數。然後檢查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;
                }

相關推薦

原始碼解析(22)

0x01 AppInitMain Step 7: load block chain 計算快取大小 fReindex = gArgs.GetBoolArg("-reindex", false); bool fReindexChainSt

原始碼解析(7)

0x00 摘要 區塊是區塊鏈的組成基本結構,也是交易資訊的載體,礦工通過挖礦的形式來產生新的區塊並獲得獎勵,新塊產生的過程也是一個交易打包的過程,只有加入到區塊中的交易才會被系統所有其他節點所認可,才

原始碼解析(7) - 資料結構 - 區塊

比特幣原始碼解析(7) - 資料結構 - 區塊    https://blog.csdn.net/u012183589/article/details/77776730 0x00 摘要 區塊是區塊鏈的組成基本結構,也是交易資訊的載體,礦工通過挖礦的形式來產生新的區

原始碼解析(9)

0x00 摘要 bitcoin-cli:是Bitcoind的一個功能完備的RPC客戶端,包括查詢區塊,交易資訊等等,具體將在相應章節介紹。 bitcoind:是比特幣執行的核心程式俗稱bitcoin core,也是我們分析的重點。 bitcoin-qt

原始碼解析(18)

0x01 InitHTTPServer 初始化訪問控制列表(ACL) if (!InitHTTPAllowList()) return false; if (gArgs.GetBoolArg("-rpcssl", f

原始碼解析(21)

0x00 摘要 經過前面20章的分析,我們已經漸漸接近比特幣的核心功能部分了,也就是它的共識、交易處理等等。雖然前面基本上都是做的一些初始化的工作,但是這些工作對於比特幣的整體執行來說都是必不可缺的,並且就像在之前講過的訊號處理、併發處理等等都是值得學習的部分

原始碼解析(1)

0x00 寫在前面 研究了這麼久的區塊鏈卻一直都沒有完整的看過一個區塊鏈專案的程式碼,甚至還一度沉迷各種ICO,每天看著各種貨幣層出不窮,跌跌漲漲,起起伏伏,不亦樂乎。現在看來,也許整體來講賺了點小錢,可是那又有什麼意義呢?終究不是長久之計。這兩天終於靜下來大

原始碼閱讀(1)--雜記與加密部分(爽之小刀)

比特幣原始碼閱讀(1)–雜記與加密部分(爽之小刀) 歡迎轉載和引用 若有問題請聯絡請聯絡 Email : [email protected] QQ:2279557541 最近從成都來到了杭州拼一槍 由於老婆為了圓自己的一個大公司夢來到了杭州

區塊鏈學習1.5-原始碼的學習-網路

本篇文章有部分內容直接出自《Mastering Bitcoin》 比特幣網路層主要是由 P2P網路,傳播機制,驗證機制三部分組成。 白皮書關於network的內容回顧一下: The steps to run the network are as follows:

區塊鏈學習1.4-原始碼的學習-基礎

1.3就已經提到區塊鏈的四大技術組合,我認為還是有必要了解背後的原理的。下面做一個簡要的介紹。 一. 區塊鏈資料結構和數字簽名演算法 1.資料結構Merkel樹 說到merkle樹就不得不談到交易,merkle樹就是用於存放交易的 資料結構。如下圖: 它是一個雜湊二叉樹,雜湊的

原始碼研讀--埠對映

在P2P網路中,一個節點既是客戶又是伺服器,它還要接受其他節點的連線,為網路中其他節點提供服務。這篇文章著重分析一下比特幣P2P網路中是如何通過upnp來實現埠對映的。 1 從騰訊的一道面試題說起     筆者所在團隊的總監在面試的時候必然要問面試者這

【區塊鏈】原始碼學習

比特幣原始碼學習 - 1 - 交易 參考部落格:here and here 一、交易概念 1、 交易形式 比特幣交易中的基礎構建單元是交易輸出。在比特幣的世界裡既沒有賬戶,也沒有餘額,只有分散到區塊鏈裡的UTXO[未花費的交易輸出]。 例如,你有20比特幣

【區塊鏈】原始碼

比特幣原始碼 - 2 - 金鑰和地址 一、基本概念 這裡摘抄一下《精通比特幣》裡面的描述: 比特幣的所有權是通過數字金鑰、比特幣地址和數字簽名來確立的。數字金鑰實際上並不是儲存在網路中,而是由使用者生成並存儲在一個檔案或簡單的資料庫中,稱為錢包。 每筆比特幣交

原始碼分析--深入理解交易

交易是比特幣最重要的一塊,比特幣系統的其他部分都是為交易服務的。前面的章節中已經學習了各種共識演算法以及比特幣PoW共識的實現,本文來分析比特幣中的交易相關的原始碼。 1 初識比特幣交易     通過比特幣核心客戶端的命令getrawtransaction和decoder

原始碼研讀(二)之搭環境遇到的那些坑

首先說一下度《精通比特幣》是一直不理解的一個地方: 上面兩處被圈起來的地方都提到了一個數字2256,特別是第一句話更是讓人費解,如果私鑰只能在1到2256間產生那豈不是太容易重複了。關於這點,我認為是在翻譯或者排版是出現了錯誤,2256應該是想表達2的256次方的意

原始碼研讀--交易細節

0x00 讀碼即挖礦 前兩天看到群裡還有人在討論ETH和EOS的DAPP開發,看來區塊鏈的落地還是一線希望,大家可以繼續給自己的信仰充值。充值方式眾多,比如加倉,Fomo,或是寫DAPP,讀程式碼。那我繼續前兩次的操作,繼續閱讀BTC的程式碼,版本0.8.2。上次粗讀了一番

原始碼分析--P2P網路初始化

     區塊鏈和AI無疑是近期業界當之無愧的兩大風口。AI就不說了,區塊鏈從17年各種數字貨幣被炒上了天,一下成為了人們街頭巷議的焦點,本文撇開數字貨幣的投資不說,僅僅從技術層面來剖析一下區塊鏈各個部分的原理。畢竟目前已經有包括BAT等巨頭在內的許多公司投入到了區塊鏈的研發

原始碼學習筆記(二)

第二章 本章繼上一章交易建立之後介紹比特幣客戶端序列化資料的過程。 比特幣客戶端所有的序列化函式均在seriliaze.h中實現。其中,CDataStream類是資料序列化的核心結構。 CDataStream CDataStream擁有一個字元類容器用來存放序列化之後的資料

原始碼解讀之整體框架

      本文主要描述了程序啟動時初始化(載入地址、區塊、錢包資訊,啟動節點執行緒以及礦工挖礦執行緒等)、比特幣客戶端交易的發起(交易初始化、提交交易請求、確認和廣播及交易)以及比特幣客戶端當前節點地址和收款方地址。下文將根據總體框架圖分別描述各個功能在原始碼中的函式實現(

原始碼linux下環境配置編譯執行bitcoin

github原始碼連結(https://github.com/bitcoin/bitcoin/) 由於近期學習區塊鏈,需要學習下比特幣原始碼,所以嘗試著在windows和linux環境下編譯執行,但是windows下的環境配置很繁瑣總是在裝qt的時候報錯,下面