1. 程式人生 > >匯入區塊(import blocks)

匯入區塊(import blocks)

接下來主要是分析Bitcoin Core初始化中的匯入區塊過程,對應的原始碼是src/init.cpp的AppInitMain()方法的Step 10: import blocks這部分,詳見:https://github.com/bitcoin/bitcoin/blob/v0.16.1/src/init.cpp

    // ********************************************************* Step 10: import blocks

    //檢查磁碟可用空間,如果可用空間小於nMinDiskSpace(目前是50MB)指定的空間,則會報錯
    if (!CheckDiskSpace())
        return
false; // Either install a handler to notify us when genesis activates, or set fHaveGenesis directly. // No locking, as this happens before any background thread is started. if (chainActive.Tip() == nullptr) { uiInterface.NotifyBlockTip.connect(BlockNotifyGenesisWait); } else { fHaveGenesis = true
; } if (gArgs.IsArgSet("-blocknotify")) uiInterface.NotifyBlockTip.connect(BlockNotifyCallback); std::vector<fs::path> vImportFiles; for (const std::string& strFile : gArgs.GetArgs("-loadblock")) { vImportFiles.push_back(strFile); } threadGroup.create_thread(boost::bind(&ThreadImport, vImportFiles)); // Wait for genesis block to be processed
{ WaitableLock lock(cs_GenesisWait); // We previously could hang here if StartShutdown() is called prior to // ThreadImport getting started, so instead we just wait on a timer to // check ShutdownRequested() regularly. while (!fHaveGenesis && !ShutdownRequested()) { condvar_GenesisWait.wait_for(lock, std::chrono::milliseconds(500)); } uiInterface.NotifyBlockTip.disconnect(BlockNotifyGenesisWait); } if (ShutdownRequested()) { return false; }

接下去一步一步解析上述原始碼

1、檢查磁碟可用空間

    if (!CheckDiskSpace())
        return false;

呼叫CheckDiskSpace()方法檢查磁碟可用空間,如果可用空間小於nMinDiskSpace(目前是50MB)指定的空間,則會報錯。

2、創世區塊啟用時傳送通知

    // Either install a handler to notify us when genesis activates, or set fHaveGenesis directly.
    // No locking, as this happens before any background thread is started.
    if (chainActive.Tip() == nullptr) {
        uiInterface.NotifyBlockTip.connect(BlockNotifyGenesisWait);
    } else {
        fHaveGenesis = true;
    }

chainActive.Tip()返回的是該鏈的末端區塊,如果為nullptr,說明創世區塊還沒有同步完成,則會連線到BlockNotifyGenesisWait,否則說明創世區塊已經同步完成了,fHaveGenesis 直接設定為true。

3、最佳塊改變時的操作

    if (gArgs.IsArgSet("-blocknotify"))
        uiInterface.NotifyBlockTip.connect(BlockNotifyCallback);

根據”-blocknotify”引數選項判斷,若設定了該引數,則連線到BlockNotifyCallback回撥函式。”-blocknotify”引數在幫助檔案中的解釋是:當最佳區塊改變時執行命令。即當最佳區塊被更改時,則會回撥BlockNotifyCallback函式。

4、從外部匯入區塊資料

    std::vector<fs::path> vImportFiles;
    for (const std::string& strFile : gArgs.GetArgs("-loadblock")) {
        vImportFiles.push_back(strFile);
    }

    threadGroup.create_thread(boost::bind(&ThreadImport, vImportFiles));

“-loadblock”引數在幫助檔案中的解釋為:在啟動時從外部blk?????.dat檔案匯入區塊,即匯入的區塊資料存在外部的blk?????.dat樣式命名的檔案中。
關鍵分析下ThreadImport()函式原始碼:

void ThreadImport(std::vector<fs::path> vImportFiles)
{
    const CChainParams& chainparams = Params();
    RenameThread("bitcoin-loadblk");

    {
    CImportingNow imp;

    // -reindex
    if (fReindex) {
        int nFile = 0;
        while (true) {
            CDiskBlockPos pos(nFile, 0);
            if (!fs::exists(GetBlockPosFilename(pos, "blk")))
                break; // No block files left to reindex
            FILE *file = OpenBlockFile(pos, true);
            if (!file)
                break; // This error is logged in OpenBlockFile
            LogPrintf("Reindexing block file blk%05u.dat...\n", (unsigned int)nFile);
            LoadExternalBlockFile(chainparams, file, &pos);
            nFile++;
        }
        pblocktree->WriteReindexing(false);
        fReindex = false;
        LogPrintf("Reindexing finished\n");
        // To avoid ending up in a situation without genesis block, re-try initializing (no-op if reindexing worked):
        LoadGenesisBlock(chainparams);
    }

    // hardcoded $DATADIR/bootstrap.dat
    fs::path pathBootstrap = GetDataDir() / "bootstrap.dat";
    if (fs::exists(pathBootstrap)) {
        FILE *file = fsbridge::fopen(pathBootstrap, "rb");
        if (file) {
            fs::path pathBootstrapOld = GetDataDir() / "bootstrap.dat.old";
            LogPrintf("Importing bootstrap.dat...\n");
            LoadExternalBlockFile(chainparams, file);
            RenameOver(pathBootstrap, pathBootstrapOld);
        } else {
            LogPrintf("Warning: Could not open bootstrap file %s\n", pathBootstrap.string());
        }
    }

    //遍歷vImportFiles中的區塊路徑,呼叫LoadExternalBlockFile()方法載入區塊資料
    // -loadblock=
    for (const fs::path& path : vImportFiles) {
        FILE *file = fsbridge::fopen(path, "rb");
        if (file) {
            LogPrintf("Importing blocks file %s...\n", path.string());
            LoadExternalBlockFile(chainparams, file);
        } else {
            LogPrintf("Warning: Could not open blocks file %s\n", path.string());
        }
    }

    // scan for better chains in the block chain database, that are not yet connected in the active best chain
    CValidationState state;
    if (!ActivateBestChain(state, chainparams)) {
        LogPrintf("Failed to connect best block");
        StartShutdown();
        return;
    }

    if (gArgs.GetBoolArg("-stopafterblockimport", DEFAULT_STOPAFTERBLOCKIMPORT)) {
        LogPrintf("Stopping after block import\n");
        StartShutdown();
        return;
    }
    } // End scope of CImportingNow
    if (gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) {
        LoadMempool();
        fDumpMempoolLater = !fRequestShutdown;
    }
}

首先會判斷$DATADIR/bootstrap.dat檔案是否存在(即在AppData/Roaming/Bitcoin 或者 ~/.bitcoin目錄下判斷bootstrap.dat檔案是否存在),如果存在,則先從該檔案載入區塊資料。

Bitcoin Core 0.7以上版本的客戶端支援bootstrap.dat檔案。bootstrap.dat檔案包含了從創世區塊開始的大量的區塊資料,可以通過迅雷等下載軟體先下載好,然後放到Bitcoin Core的資料目錄(即AppData/Roaming/Bitcoin 或者 ~/.bitcoin)下即可。
比特幣專案官方每隔一段時間會把資料檔案打包成bootstrap.dat 檔案,可以在比特幣的專案主頁下載區塊資料檔案的種子檔案。通過這種方式下載區塊不僅可以加快區塊同步速度,還減輕了比特幣的P2P網路的流量負擔。
注意:
原本比特幣官網https://bitcoin.org是有bootstrap.dat.torrent種子檔案的下載連結的,但是後面的版本把這個連結去除了,原因是官方不建議使用這種方式下載區塊資料,可以在這裡檢視版本說明:https://bitcoin.org/en/release/v0.10.0#faster-synchronization
參考:https://bitcoin.stackexchange.com/questions/37927/what-happened-to-the-bootstrap-dat-provided-by-bitcoin-org

然後是一個for迴圈遍歷”-loadblock”選項指定的區塊資料檔案路徑,將結果儲存在vImportFiles向量容器中,然後建立一個執行緒完成匯入區塊的過程,執行緒繫結的函式是ThreadImport(),引數就是vImportFiles。

等待創世區塊處理完成

    // Wait for genesis block to be processed
    {
        WaitableLock lock(cs_GenesisWait);
        // We previously could hang here if StartShutdown() is called prior to
        // ThreadImport getting started, so instead we just wait on a timer to
        // check ShutdownRequested() regularly.
        while (!fHaveGenesis && !ShutdownRequested()) {
            condvar_GenesisWait.wait_for(lock, std::chrono::milliseconds(500));
        }
        uiInterface.NotifyBlockTip.disconnect(BlockNotifyGenesisWait);
    }

如果創世區塊一直沒有處理完成,則會迴圈呼叫condvar_GenesisWait.wait_for()進行等待,等創世區塊處理完成後斷開與BlockNotifyGenesisWait的連線。