比特币源码解析(26) - 可执行程序 - Bitcoind

0x01 Step 11: Import Blocks

从这一步的名字也可以看出这一步主要的内容就是导入区块,也就是从外部文件中导入区块信息,并加载到内存,更新区块索引等信息,具体代码如下。

// ********************************************************* Step 11: import blocks

    if (!CheckDiskSpace() && !CheckDiskSpace(0, true))
        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);

首先来看前面部分,第一行先检查磁盘空间,主要是看GetBlocksDir()GetDataDir()目录下的磁盘空间是否大于nMinDiskSpace目前也就是50MB,但是这一句并没有十分看懂检查的意义,因为这一步似乎并没有创建新的文件。接下来判断chainActive.Tip()是否为空,也就是判断Genesis区块是否已经加载了,如果没有那么链接一个信号BlockNotifyGenesisWait,到Genesis加载成功后触发该信号;如果Genesis已经加载了,那么就直接将fHaveGenesis变量设置为true。接下来是判断参数-blocknotify是否设置了,该参数的解释如下

Execute command when the best block changes (%s in cmd is replaced by block hash).

当最佳区块(即区块头)变化时,执行该参数指定的命令,参数中的%s会被替换为区块哈希值。

如果设置了该参数,那么将BlockNotifyCallback加入到信号中,以便在有新区块到来时进行回调,该函数的实现比较简单,如下

static void BlockNotifyCallback(bool initialSync, const CBlockIndex *pBlockIndex)
{
     
    if (initialSync || !pBlockIndex)
        return;

    std::string strCmd = gArgs.GetArg("-blocknotify", "");
    if (!strCmd.empty()) {
     
        boost::replace_all(strCmd, "%s", pBlockIndex->GetBlockHash().GetHex());
        std::thread t(runCommand, strCmd);
        t.detach(); // thread runs free
    }
}

主要就是替换%s,然后创建一个线程去执行指令。

    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
    {
     
        WAIT_LOCK(g_genesis_wait_mutex, lock);
        // 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()) {
     
            g_genesis_wait_cv.wait_for(lock, std::chrono::milliseconds(500));
        }
        uiInterface.NotifyBlockTip_disconnect(BlockNotifyGenesisWait);
    }

    if (ShutdownRequested()) {
     
        return false;
    }

接下来再判断-loadblock是否设置了,该参数的解释如下

Imports blocks from external blk000??.dat file on startup.

启动时从外部文件导入区块。

该参数指定了导入区块的目录路径,指定多个路径,首先将所有的路径放入vImportFiles变量中,然后创建一个线程去执行ThreadImport函数,来从路径中导入所有的区块。最后,如果Genesis区块还没有创建成功,那么就等待该区块创建,直到该区块创建成功。而在上述等待的过程中,有可能用户已经请求终止运行了,所以在等待结束之后判断用户是否请求终止运行了,如果是那么直接退出。第11步也就到此结束了。

0x02 Step 12: Start Node

到这一步为止,所有的初始化、缓存和索引等信息就已经全部完成了,现在就开始启动节点,开始节点之间的通信了。

    // ********************************************************* Step 12: start node

    int chain_active_height;

     debug print
    {
     
        LOCK(cs_main);
        LogPrintf("mapBlockIndex.size() = %u\n", mapBlockIndex.size());
        chain_active_height = chainActive.Height();
    }
    LogPrintf("nBestHeight = %d\n", chain_active_height);

    if (gArgs.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION))
        StartTorControl();

    Discover();

    // Map ports with UPnP
    if (gArgs.GetBoolArg("-upnp", DEFAULT_UPNP)) {
     
        StartMapPort();
    }

这几步比较简单,就是将当前最新区块的高度放在变量chain_active_height中,然后判断是否启用了洋葱路由协议和UPnP协议,其中Discover()是查找本机的ip地址和主机名。接下来就是一堆参数的设置,其中每个参数的含义如注释所示。

    CConnman::Options connOptions;
    connOptions.nLocalServices = nLocalServices; // 启用的服务标识
    connOptions.nMaxConnections = nMaxConnections; // 最大连接数
    connOptions.nMaxOutbound = std::min(MAX_OUTBOUND_CONNECTIONS, connOptions.nMaxConnections); // 向外发出的最多连接数
    connOptions.nMaxAddnode = MAX_ADDNODE_CONNECTIONS; // 最大添加节点数
    connOptions.nMaxFeeler = 1;
    connOptions.nBestHeight = chain_active_height; // 当前的最新区块高度
    connOptions.uiInterface = &uiInterface; // 所有的信号回调关系
    connOptions.m_msgproc = peerLogic.get();  // 节点之间消息处理逻辑
    connOptions.nSendBufferMaxSize = 1000*gArgs.GetArg("-maxsendbuffer", DEFAULT_MAXSENDBUFFER); // 最大发送区缓存
    connOptions.nReceiveFloodSize = 1000*gArgs.GetArg("-maxreceivebuffer", DEFAULT_MAXRECEIVEBUFFER); // 最大接收区缓存
    connOptions.m_added_nodes = gArgs.GetArgs("-addnode"); // 添加节点地址列表

    connOptions.nMaxOutboundTimeframe = nMaxOutboundTimeframe;
    connOptions.nMaxOutboundLimit = nMaxOutboundLimit;

接下来同样是一堆的参数设置,不过下面这些主要偏向连接的节点信息。

    for (const std::string& strBind : gArgs.GetArgs("-bind")) {
     
        CService addrBind;
        if (!Lookup(strBind.c_str(), addrBind, GetListenPort(), false)) {
     
            return InitError(ResolveErrMsg("bind", strBind));
        }
        connOptions.vBinds.push_back(addrBind);
    }
    for (const std::string& strBind : gArgs.GetArgs("-whitebind")) {
     
        CService addrBind;
        if (!Lookup(strBind.c_str(), addrBind, 0, false)) {
     
            return InitError(ResolveErrMsg("whitebind", strBind));
        }
        if (addrBind.GetPort() == 0) {
     
            return InitError(strprintf(_("Need to specify a port with -whitebind: '%s'"), strBind));
        }
        connOptions.vWhiteBinds.push_back(addrBind);
    }

    for (const auto& net : gArgs.GetArgs("-whitelist")) {
     
        CSubNet subnet;
        LookupSubNet(net.c_str(), subnet);
        if (!subnet.IsValid())
            return InitError(strprintf(_("Invalid netmask specified in -whitelist: '%s'"), net));
        connOptions.vWhitelistedRange.push_back(subnet);
    }

    connOptions.vSeedNodes = gArgs.GetArgs("-seednode");

    // Initiate outbound connections unless connect=0
    connOptions.m_use_addrman_outgoing = !gArgs.IsArgSet("-connect");
    if (!connOptions.m_use_addrman_outgoing) {
     
        const auto connect = gArgs.GetArgs("-connect");
        if (connect.size() != 1 || connect[0] != "0") {
     
            connOptions.m_specified_outgoing = connect;
        }
    }
    if (!connman.Start(scheduler, connOptions)) {
     
        return false;
    }

在上一步的最后,调用了一个connman.Start(scheduler, connOptions)函数,其中scheduler是线程调度的结构体,主要包含周期性的执行某一个函数,后一个参数connOptions则是节点之间连接的相关信息。此函数的实现在net.cpp line 2266,除了创建节点之间的连接之外,还创建了几个新的线程,分别是:

  • ThreadSocketHandler:从socket中发送和接受消息。
  • ThreadDNSAddressSeed:从DNS种子中去寻找其他节点地址。
  • ThreadOpenAddedConnections:处理从参数传入的节点的连接。
  • ThreadOpenConnections:自动寻找节点进行连接。
  • ThreadMessageHandler:处理节点之间的消息。

其中最重要的是ThreadMessageHandler这个线程,因为节点之间消息的传输包括交易、区块等信息都是在此线程中进行处理,我们将在后面等章节来详细介绍其中的过程。

0x03 Step 13: Finished

虽然上一步中有些主要的内容还没有介绍,但是我们先看最后一步的内容。

    // ********************************************************* Step 13: finished

    SetRPCWarmupFinished();
    uiInterface.InitMessage(_("Done loading"));

    g_wallet_init_interface.Start(scheduler);

    return true;

首先通过一个函数去修改了一个标志变量fRPCInWarmup将其值改为false,接下来在最后一步调用g_wallet_init_interface.Start(scheduler)去重复的刷新钱包,该函数的实现如下

void WalletInit::Start(CScheduler& scheduler) const
{
     
    for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {
     
        pwallet->postInitProcess();
    }

    // Run a thread to flush wallet periodically
    scheduler.scheduleEvery(MaybeCompactWalletDB, 500);
}

在函数MaybeCompactWalletDB中不断的去查询钱包的状态,如果有任何的变化泽及时更新钱包的状态。

0x04 基本步骤总结

在之前的十三个步骤当中,主要介绍了可执行程序bitcoind所有的命令行参数,以及这些参数的设置和运用,同时还包括了索引和缓存的创建、创世区块Genesis的创建、节点的启动等内容,这些步骤所处理的仅仅只是非核心的功能,但同时也是一个完整的系统必不可少的功能。这些基本步骤的介绍就到此结束,后续的文章将围绕Step 12最后创建的几个线程来展开分析,核心功能为节点之间进行通信的过程。

你可能感兴趣的:(比特币源码分析,比特币,区块链,源码分析)