9.9 data directory maintenance
这一步主要是针对裁剪模式进行boost::signals2的裁剪
// ********************************************************* Step 9: data directory maintenance
// if pruning, unset the service bit and perform the initial blockstore prune
// after any wallet rescanning has taken place.
if (fPruneMode) {
LogPrintf("Unsetting NODE_NETWORK on prune mode\n");
nLocalServices = ServiceFlags(nLocalServices & ~NODE_NETWORK);
if (!fReindex) {
uiInterface.InitMessage(_("Pruning blockstore..."));
PruneAndFlush();
}
}
//关于隔离见证的软分叉合并处理
if (Params().GetConsensus().vDeployments[Consensus::DEPLOYMENT_SEGWIT].nTimeout != 0) {
// Only advertize witness capabilities if they have a reasonable start time.
// This allows us to have the code merged without a defined softfork, by setting its
// end time to 0.
// Note that setting NODE_WITNESS is never required: the only downside from not
// doing so is that after activation, no upgraded nodes will fetch from you.
nLocalServices = ServiceFlags(nLocalServices | NODE_WITNESS);
// Only care about others providing witness capabilities if there is a softfork
// defined.
nRelevantServices = ServiceFlags(nRelevantServices | NODE_WITNESS);
}
9.10 import blocks
9.10.1 CheckDiskSpace
// ********************************************************* Step 10: import blocks
if (!CheckDiskSpace())
return false;
首先检查是否有足够的磁盘空间能存储即将到来的区块
//main.h
/** Check whether enough disk space is available for an incoming block */
bool CheckDiskSpace(uint64_t nAdditionalBytes = 0);
/** Minimum disk space required - used in CheckDiskSpace() */
static const uint64_t nMinDiskSpace = 52428800;
//main.cpp
bool CheckDiskSpace(uint64_t nAdditionalBytes)
{
uint64_t nFreeBytesAvailable = boost::filesystem::space(GetDataDir()).available;
// Check for nMinDiskSpace bytes (currently 50MB)
if (nFreeBytesAvailable < nMinDiskSpace + nAdditionalBytes)
return AbortNode("Disk space is low!", _("Error: Disk space is low!"));
return true;
}
nAdditionalBytes默认值为0,当前目录下需要的最小空间目前定为50M
9.10.2 fHaveGenesis
// 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() == NULL) {
uiInterface.NotifyBlockTip.connect(BlockNotifyGenesisWait);
} else {
fHaveGenesis = true;
}
或者安装一个处理程序在genesis
激活时通知我们,或者直接设置fHaveGenesis
。不需要锁定,因为这在任何后台线程启动之前发生。
这里的chainActive
是类CChain
的实例。
岔开来看一下这个类
9.10.3 CChain
区块的内存索引链
/** An in-memory indexed chain of blocks. */
class CChain {
private:
std::vector<CBlockIndex*> vChain;
public:
/** Returns the index entry for the genesis block of this chain, or NULL if none. */
//返回此链的genesis块(创世块)的索引条目,如果没有,则返回NULL
CBlockIndex *Genesis() const {
return vChain.size() > 0 ? vChain[0] : NULL;
}
/** Returns the index entry for the tip of this chain, or NULL if none. */
//返回此链的tip的索引条目,如果没有则返回NULL,对于tip不是很理解,看代码应该是指最后此链的尾部长度
CBlockIndex *Tip() const {
return vChain.size() > 0 ? vChain[vChain.size() - 1] : NULL;
}
/** Returns the index entry at a particular height in this chain, or NULL if no such height exists. */
//返回此链中特定高度的索引条目,如果不存在此高度,则返回NULL
CBlockIndex *operator[](int nHeight) const {
if (nHeight < 0 || nHeight >= (int)vChain.size())
return NULL;
return vChain[nHeight];
}
/** Compare two chains efficiently. */
//比较两个链的有效性,代码实现是比较长度
friend bool operator==(const CChain &a, const CChain &b) {
return a.vChain.size() == b.vChain.size() &&
a.vChain[a.vChain.size() - 1] == b.vChain[b.vChain.size() - 1];
}
/** Efficiently check whether a block is present in this chain. */
//检查这个区块在当前链是否有效
bool Contains(const CBlockIndex *pindex) const {
return (*this)[pindex->nHeight] == pindex;
}
/** Find the successor of a block in this chain, or NULL if the given index is not found or is the tip. */
CBlockIndex *Next(const CBlockIndex *pindex) const {
if (Contains(pindex))
return (*this)[pindex->nHeight + 1];
else
return NULL;
}
/** Return the maximal height in the chain. Is equal to chain.Tip() ? chain.Tip()->nHeight : -1. */
//返回链中的最大高度。
int Height() const {
return vChain.size() - 1;
}
/** Set/initialize a chain with a given tip. */
void SetTip(CBlockIndex *pindex);
/** Return a CBlockLocator that refers to a block in this chain (by default the tip). */
CBlockLocator GetLocator(const CBlockIndex *pindex = NULL) const;
/** Find the last common block between this chain and a block index entry. */
//找到此链和块索引条目之间的最后一个公共块。
const CBlockIndex *FindFork(const CBlockIndex *pindex) const;
};
这个类是封装了对 std::vector<CBlockIndex*> vChain
这个区块索引队列的一系列操作。
接下来返回代码部分,当链长度为0,也就是没有创世块则开启一个监听,否则就是已存在创世块。
9.10.4 loadblock
if (mapArgs.count("-blocknotify"))
uiInterface.NotifyBlockTip.connect(BlockNotifyCallback);
std::vector<boost::filesystem::path> vImportFiles;
if (mapArgs.count("-loadblock"))
{
BOOST_FOREACH(const std::string& strFile, mapMultiArgs["-loadblock"])
vImportFiles.push_back(strFile);
}
-blocknotify
:Execute command when the best block changes (%s in cmd is replaced by block hash)
-loadblock
:Imports blocks from external blk000??.dat file on startup
如果设置了-loadblock
,则从指定的外部blk000??.dat
文件导入数据块
这段代码首先定义了一个boost::filesystem::path
的数组,关于boost::filesystem
,参考https://www.boost.org/doc/libs/1_31_0/libs/filesystem/doc/index.htm
boost::filesystem
:这个广泛使用的库提供了安全、可移植且易用的 C++ 接口,用于执行文件系统操作。可以从 Boost 站点免费下载此库。
boost::filesystem::path
:A possibly empty sequence of names. Each element in the sequence, except the last, names a directory which contains the next element. The last element may name either a directory or file. The first element is closest to the root of the directory tree, the last element is farthest from the root.
通过-loadblock
参数获取要导入的blk000??.dat
文件,并存入上面的数组中,通过建立一个新的线程来加载区块数据。这个线程执行ThreadImport
,并且把数组vImportFiles
作为参数传入。
9.10.5 ThreadImport
开启区块导入的线程
threadGroup.create_thread(boost::bind(&ThreadImport, vImportFiles));
在bitcoin/doc/developer-notes.md中有关于线程的介绍,其他的线程之后再介绍,我们主要关注ThreadImport
Threads
- ThreadScriptCheck : Verifies block scripts.
ThreadImport
: Loads blocks from blk*.dat files or bootstrap.dat.- StartNode : Starts other threads.
- ThreadDNSAddressSeed : Loads addresses of peers from the DNS.
- ThreadMapPort : Universal plug-and-play startup/shutdown
- ThreadSocketHandler : Sends/Receives data from peers on port 8333.
- ThreadOpenAddedConnections : Opens network connections to added nodes.
- ThreadOpenConnections : Initiates new connections to peers.
- ThreadMessageHandler : Higher-level message handling (sending and receiving).
- DumpAddresses : Dumps IP addresses of nodes to peers.dat.
- ThreadFlushWalletDB : Close the wallet.dat file if it hasn’t been used in 500ms.
- ThreadRPCServer : Remote procedure call handler, listens on port 8332 for connections and services them.
- BitcoinMiner : Generates bitcoins (if wallet is enabled).
- Shutdown : Does an orderly shutdown of everything.
这个线程的作用是从blk*.dat文件或者bootstrap.dat中导入数据块。下面来看这个线程
//init.cpp
void ThreadImport(std::vector<boost::filesystem::path> vImportFiles)
{
const CChainParams& chainparams = Params();
RenameThread("bitcoin-loadblk");//1.给当前线程重命名
CImportingNow imp;
// -reindex 2.将所有区块文件从0重新加载一遍
if (fReindex) {
int nFile = 0;
while (true) {
CDiskBlockPos pos(nFile, 0);
if (!boost::filesystem::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):
InitBlockIndex(chainparams);//3.为防止没有创世区块导致中断,需要重新初始化
}
// hardcoded $DATADIR/bootstrap.dat 4.加载bootstrap.dat文件
boost::filesystem::path pathBootstrap = GetDataDir() / "bootstrap.dat";
if (boost::filesystem::exists(pathBootstrap)) {
FILE *file = fopen(pathBootstrap.string().c_str(), "rb");
if (file) {
boost::filesystem::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());
}
}
// -loadblock= 5.遍历传入的区块文件数组,加载区块数据
BOOST_FOREACH(const boost::filesystem::path& path, vImportFiles) {
FILE *file = fopen(path.string().c_str(), "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
//6.扫描区块链中更好的链
CValidationState state;
if (!ActivateBestChain(state, chainparams)) {
LogPrintf("Failed to connect best block");
StartShutdown();
}
if (GetBoolArg("-stopafterblockimport", DEFAULT_STOPAFTERBLOCKIMPORT)) {
LogPrintf("Stopping after block import\n");
StartShutdown();
}
}
-stopafterblockimport
:Stop running after importing blocks from disk (default: %u)
9.10.5.1 ActivateBestChain
上述代码第六步通过调用ActivateBestChain
函数查找并激活最好的区块链,传入的参数是CValidationState
类的对象
//src/consensus/validation.h
/** Capture information about block/transaction validation */
class CValidationState {
private:
enum mode_state {
MODE_VALID, //!< everything ok
MODE_INVALID, //!< network rule violation (DoS value may be set)
MODE_ERROR, //!< run-time error
} mode;
int nDoS;
std::string strRejectReason;
unsigned int chRejectCode;
bool corruptionPossible;
std::string strDebugMessage;
public:
CValidationState() : mode(MODE_VALID), nDoS(0), chRejectCode(0), corruptionPossible(false) {}
···
这个类是验证区块或交易有效性的类,这个类定义了三种状态,有效、无效、错误,默认设置为有效状态。接下来看ActivateBestChain
函数
//main.h
bool ActivateBestChain(CValidationState& state, const CChainParams& chainparams, const CBlock* pblock = NULL);
//main.cpp
/**
* Make the best chain active, in multiple steps. The result is either failure
* or an activated best chain. pblock is either NULL or a pointer to a block
* that is already loaded (to avoid loading it again from disk).
*/
//通过多个步骤激活最佳链,要么成功激活要么失败,pblock是只想已加载块的指针或者为空,避免再次加载
bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams, const CBlock *pblock) {
CBlockIndex *pindexMostWork = NULL;
CBlockIndex *pindexNewTip = NULL;
do {
boost::this_thread::interruption_point();
if (ShutdownRequested())
break;
//1.先获得当前链的最高区块
const CBlockIndex *pindexFork;
bool fInitialDownload;
int nNewHeight;
{
LOCK(cs_main);
CBlockIndex *pindexOldTip = chainActive.Tip();
if (pindexMostWork == NULL) {
//2.查找工作量最多(也就是最长)的链
pindexMostWork = FindMostWorkChain();
}
//3.如果未找到或者长的链就是现在的链,则直接返回true
// Whether we have anything to do at all.
if (pindexMostWork == NULL || pindexMostWork == chainActive.Tip())
return true;
//4.尝试使pindexMostWork称为激活的块取得一些进展,从代码看是断开旧块连接,连接到新的块
bool fInvalidFound = false;
if (!ActivateBestChainStep(state, chainparams, pindexMostWork, pblock && pblock->GetHash() == pindexMostWork->GetBlockHash() ? pblock : NULL, fInvalidFound))
return false;
//5.擦出缓存,我们可能需要另外的分支
if (fInvalidFound) {
// Wipe cache, we may need another branch now.
pindexMostWork = NULL;
}
pindexNewTip = chainActive.Tip();
pindexFork = chainActive.FindFork(pindexOldTip);
fInitialDownload = IsInitialBlockDownload();
nNewHeight = chainActive.Height();
}
// When we reach this point, we switched to a new tip (stored in pindexNewTip).
//到这一步,我们已经切换到新的最高点(区块链的尾部)
// Notifications/callbacks that can run without cs_main
// Always notify the UI if a new block tip was connected
if (pindexFork != pindexNewTip) {
uiInterface.NotifyBlockTip(fInitialDownload, pindexNewTip);
if (!fInitialDownload) {
// Find the hashes of all blocks that weren't previously in the best chain.
std::vector<uint256> vHashes;
CBlockIndex *pindexToAnnounce = pindexNewTip;
while (pindexToAnnounce != pindexFork) {
vHashes.push_back(pindexToAnnounce->GetBlockHash());
pindexToAnnounce = pindexToAnnounce->pprev;
if (vHashes.size() == MAX_BLOCKS_TO_ANNOUNCE) {
// Limit announcements in case of a huge reorganization.
// Rely on the peer's synchronization mechanism in that case.
break;
}
}
// Relay inventory, but don't relay old inventory during initial block download.
{
LOCK(cs_vNodes);
BOOST_FOREACH(CNode* pnode, vNodes) {
if (nNewHeight > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight - 2000 : 0)) {
BOOST_REVERSE_FOREACH(const uint256& hash, vHashes) {
pnode->PushBlockHash(hash);
}
}
}
}
// Notify external listeners about the new tip.
if (!vHashes.empty()) {
GetMainSignals().UpdatedBlockTip(pindexNewTip);
}
}
}
} while (pindexNewTip != pindexMostWork);//直到最新的链是最长的链退出循环
CheckBlockIndex(chainparams.GetConsensus());
// Write changes periodically to disk, after relay.
if (!FlushStateToDisk(state, FLUSH_STATE_PERIODIC)) {
return false;
}
return true;
}
9.10.6 GenesisWait
// Wait for genesis block to be processed
{
boost::unique_lock<boost::mutex> lock(cs_GenesisWait);
while (!fHaveGenesis) {
condvar_GenesisWait.wait(lock);
}
uiInterface.NotifyBlockTip.disconnect(BlockNotifyGenesisWait);
}
如果创世块未被加载,则等待直到被加载,然后断开对其的监听。
/** New block has been accepted */
boost::signals2::signal<void (bool, const CBlockIndex *)> NotifyBlockTip;
NotifyBlockTip
用于监听新的区块被接收
来了解下boost::signals2
boost::signals2
:signals2基于Boost的另一个库signals,实现了线程安全的观察者模式。在signals2中,观察者模式被称为信号/插槽(signals and slots),它是一种函数回调机制,一个信号关联了多个插槽,当信号发出时,所有关联它的插槽都会被调用。
至于uiInterface是类CClientUIInterface的对象,这个类封装了各种交互信号
//ui_interface.h class CClientUIInterface
···
/** Show message box. */
boost::signals2::signal<bool (const std::string& message, const std::string& caption, unsigned int style), boost::signals2::last_value<bool> > ThreadSafeMessageBox;
/** If possible, ask the user a question. If not, falls back to ThreadSafeMessageBox(noninteractive_message, caption, style) and returns false. */
boost::signals2::signal<bool (const std::string& message, const std::string& noninteractive_message, const std::string& caption, unsigned int style), boost::signals2::last_value<bool> > ThreadSafeQuestion;
/** Progress message during initialization. */
boost::signals2::signal<void (const std::string &message)> InitMessage;
/** Number of network connections changed. */
boost::signals2::signal<void (int newNumConnections)> NotifyNumConnectionsChanged;
/**
* Status bar alerts changed.
*/
···