匯入區塊(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的連線。