从源代码编译比特币--从零开始学习区块链技术(一)

以下为系统启动过程中重要的步骤。

第1步,应用初始化基本设置(src/bitcoind.cpp

AppInitBasicSetup 函数进行基本的设置。

  1. 调用 SetupNetworking 函数,进行网络设置。

    主要是针对 Win32 系统处理套接字,别的系统直接返回真。

  2. 如果不是 WIN32 系统,进行下面的处理:

    • 如果设置 sysperms 参数为真,调用 umask 函数,设置位码为 077。
    • 调用 registerSignalHandler 函数,设置 SIGTERM 信号处理器为 HandleSIGTERMSIGINTHandleSIGTERMSIGHUPHandleSIGHUP

第2步,应用初始参数交互设置(src/bitcoind.cpp

AppInitParameterInteraction 函数前半部分。

  1. 首先,调用 Params 方法,获取前面初始化的 globalChainParams 区块链对象。

     const CChainParams& chainparams = Params();

    根据不同的网络,chainparams 的真实类型可能是 CMainParams,代表主网络;或者是 CTestNetParams,代表测试网络;或者是 CRegTestParams 代表回归测试网络。

  2. 检查指定的区块目录是否存。如果不存在,则返回初始化错误。

     if (!fs::is_directory(GetBlocksDir(false))) {
         return InitError(strprintf(_("Specified blocks directory \"%s\" does not exist."), gArgs.GetArg("-blocksdir", "").c_str()));
     }
  3. 如果同时指定了 prunetxindex,则抛出初始化错误。

    如果指定了区块修剪 prune,就要禁止交易索引 txindex,两者不兼容,只能其一。

    if (gArgs.GetArg("-prune", 0)) {
       if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX))
           return InitError(_("Prune mode is incompatible with -txindex."));
    }
  4. 检查是否指定了 -bind-whitebind 两者之一,并且同时禁止其他节点连接(listen)。如果是,则抛出初始化错误。

     size_t nUserBind = gArgs.GetArgs("-bind").size() + gArgs.GetArgs("-whitebind").size();
     if (nUserBind != 0 && !gArgs.GetBoolArg("-listen", DEFAULT_LISTEN)) {
         return InitError("Cannot set -bind or -whitebind together with -listen=0");
     }
  5. 确保有足够的文件符可用。

    因为在类 Unix 系统中,每个套接字都是一个文件,都需要一个文件描述符。所以要检查指定的最大连接数 maxconnections 是否超过系统可用限制。

    int nBind = std::max(nUserBind, size_t(1));
    nUserMaxConnections = gArgs.GetArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS);
    nMaxConnections = std::max(nUserMaxConnections, 0);
    
    nMaxConnections = std::max(std::min(nMaxConnections, FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS), 0);
    nFD = RaiseFileDescriptorLimit(nMaxConnections + MIN_CORE_FILEDESCRIPTORS + MAX_ADDNODE_CONNECTIONS);
    if (nFD < MIN_CORE_FILEDESCRIPTORS)
       return InitError(_("Not enough file descriptors available."));
    nMaxConnections = std::min(nFD - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS, nMaxConnections);
    
    if (nMaxConnections < nUserMaxConnections)
       InitWarning(strprintf(_("Reducing -maxconnections from %d to %d, because of system limitations."), nUserMaxConnections, nMaxConnections));

第3步,参数到内部标志的转换处理(src/bitcoind.cpp

AppInitParameterInteraction 函数后半部分。

  1. 处理 debugdebugexcludedebugnet 等参数。

    如果指定了 -debug,则解析每个类别是否是支持的类别。如果不支持,则输入警告消息。如果需要同时指定多个类别,可分开指定,比如要调试网络与RPC 相关的信息,则配置如下:-debug=net -debug=rpc

    if (gArgs.IsArgSet("-debug")) {
       const std::vector<std::string> categories = gArgs.GetArgs("-debug");
    
       if (std::none_of(categories.begin(), categories.end(),
           [](std::string cat){return cat == "0" || cat == "none";})) {
           for (const auto& cat : categories) {
               if (!g_logger->EnableCategory(cat)) {
                   InitWarning(strprintf(_("Unsupported logging category %s=%s."), "-debug", cat));
               }
           }
       }
    }
  2. 如果指定了 -socks 参数,则提示使用 SOCKS5

     if (gArgs.IsArgSet("-socks"))
         return InitError(_("Unsupported argument -socks found. Setting SOCKS version isn't possible anymore, only SOCKS5 proxies are supported."));
  3. 如果指定了 -tor 参数,则提示使用 -onion

     if (gArgs.GetBoolArg("-tor", false))
         return InitError(_("Unsupported argument -tor found, use -onion."));
  4. 如果指定了 -benchmark 参数,则提示使用 -debug=bench

     if (gArgs.GetBoolArg("-benchmark", false))
         InitWarning(_("Unsupported argument -benchmark ignored, use -debug=bench."));
  5. 如果指定了 -whitelistalwaysrelay 参数,则提示使用 -whitelistrelay,或-whitelistforcerelay

     if (gArgs.GetBoolArg("-whitelistalwaysrelay", false))
         InitWarning(_("Unsupported argument -whitelistalwaysrelay ignored, use -whitelistrelay and/or -whitelistforcerelay."));
  6. 如果指定了 -blockminsize 参数,则提示使用 -blockminsize

     if (gArgs.IsArgSet("-blockminsize"))
         InitWarning("Unsupported argument -blockminsize ignored.");
  7. 根据是否指定 -checkmempool 参数,确定是否进行合理性检查。

    在不指定这个参数的情况下,当运行主网络和测试网络时,不进行交易池合理性检查,当运行回归测试网络时,进行合理性检查。代码如下:

    // Checkmempool and checkblockindex default to true in regtest mode
    
    // 当运行主网络和测试网络时,DefaultConsistencyChecks 函数返回假,导致变量 ratio 为1, 为0,从而不进行交易池设置;当运行回归测试网络时,函数返回真,从而变量 ratio 为1,从而进行交易池设置
    int ratio = std::min(std::max(gArgs.GetArg("-checkmempool", chainparams.DefaultConsistencyChecks() ? 1 : 0), 0), 1000000);
    if (ratio != 0) {
       mempool.setSanityCheck(1.0 / ratio);
    }
    
    fCheckBlockIndex = gArgs.GetBoolArg("-checkblockindex", chainparams.DefaultConsistencyChecks());
  8. 设置检查点默认打开

     fCheckpointsEnabled = gArgs.GetBoolArg("-checkpoints", DEFAULT_CHECKPOINTS_ENABLED);
  9. 处理 assumevalid 参数。

    这个参数的意思是假设有效,即如果如果这个块在链中,则假定它和它的祖先是有效的,并且可能跳过它们的脚本验证。否则会验证所有的块。

    hashAssumeValid = uint256S(gArgs.GetArg("-assumevalid", chainparams.GetConsensus().defaultAssumeValid.GetHex()));
    if (!hashAssumeValid.IsNull())
       LogPrintf("Assuming ancestors of block %s have valid signatures.\n", hashAssumeValid.GetHex());
    else
       LogPrintf("Validating signatures for all blocks.\n");
  10. 根据是否指定 minimumchainwork 参数,确定使用默认的最小工作量还是使用用户指定的最小工作量。

      if (gArgs.IsArgSet("-minimumchainwork")) {
          const std::string minChainWorkStr = gArgs.GetArg("-minimumchainwork", "");
          if (!IsHexNumber(minChainWorkStr)) {
              return InitError(strprintf("Invalid non-hex (%s) minimum chain work value specified", minChainWorkStr));
          }
          nMinimumChainWork = UintToArith256(uint256S(minChainWorkStr));
      } else {
          nMinimumChainWork = UintToArith256(chainparams.GetConsensus().nMinimumChainWork);
      }
  11. 计算内存池/交易池限制,包括处理 maxmempoollimitdescendantsize 参数。

    前者表示最大内存池,后者表示最小内存池,如果最大值小于最小值,则抛出初始化错误。

    int64_t nMempoolSizeMax = gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
    int64_t nMempoolSizeMin = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000 * 40;
    if (nMempoolSizeMax < 0 || nMempoolSizeMax < nMempoolSizeMin)
        return InitError(strprintf(_("-maxmempool must be at least %d MB"), std::ceil(nMempoolSizeMin / 1000000.0)));
  12. 如果指定了 incrementalrelayfee,则进行相关处理。

    incrementalrelayfee 定义中继的成本费率,应用于交易池限制和 BIP 125 替换。代码如下:

    if (gArgs.IsArgSet("-incrementalrelayfee"))
    {
        CAmount n = 0;
        if (!ParseMoney(gArgs.GetArg("-incrementalrelayfee", ""), n))
            return InitError(AmountErrMsg("incrementalrelayfee", gArgs.GetArg("-incrementalrelayfee", "")));
        incrementalRelayFee = CFeeRate(n);
    }
  13. 处理 -par 参数,指定脚本签名的线程数量。

    代码如下:

    if (nScriptCheckThreads <= 0)
        nScriptCheckThreads += GetNumCores();
    if (nScriptCheckThreads <= 1)
        nScriptCheckThreads = 0;
    else if (nScriptCheckThreads > MAX_SCRIPTCHECK_THREADS)
        nScriptCheckThreads = MAX_SCRIPTCHECK_THREADS;
  14. 处理区块修剪参数 -prune

    代码如下:

    int64_t nPruneArg = gArgs.GetArg("-prune", 0);
    if (nPruneArg < 0) {
        return InitError(_("Prune cannot be configured with a negative value."));
    
    nPruneTarget = (uint64_t) nPruneArg * 1024 * 1024;
    if (nPruneArg == 1) {  // manual pruning: -prune=1
        LogPrintf("Block pruning enabled.  Use RPC call pruneblockchain(height) to manually prune block and undo files.\n");
        nPruneTarget = std::numeric_limits::max();
        fPruneMode = true;
    } else if (nPruneTarget) {
        if (nPruneTarget < MIN_DISK_SPACE_FOR_BLOCK_FILES) {
            return InitError(strprintf(_("Prune configured below the minimum of %d MiB.  Please use a higher number."), MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024));
        }
        LogPrintf("Prune configured to target %uMiB on disk for block and undo files.\n", nPruneTarget / 1024 / 1024);
        fPruneMode = true;
    }
  15. 处理连接超时时间 -timeout

    代码如下:

    nConnectTimeout = gArgs.GetArg("-timeout", DEFAULT_CONNECT_TIMEOUT);
    if (nConnectTimeout <= 0)
        nConnectTimeout = DEFAULT_CONNECT_TIMEOUT;
  16. 处理 -minrelaytxfee 参数。

    对于中继、挖矿和交易创建,小于此的费用被认为是零费用。解析并计算最小中继交易费用。代码如下:

    if (gArgs.IsArgSet("-minrelaytxfee")) {
        CAmount n = 0;
        if (!ParseMoney(gArgs.GetArg("-minrelaytxfee", ""), n)) {
            return InitError(AmountErrMsg("minrelaytxfee", gArgs.GetArg("-minrelaytxfee", "")));
        }
        ::minRelayTxFee = CFeeRate(n);
    } else if (incrementalRelayFee > ::minRelayTxFee) {
        ::minRelayTxFee = incrementalRelayFee;
        LogPrintf("Increasing minrelaytxfee to %s to match incrementalrelayfee\n",::minRelayTxFee.ToString());
    }
  17. 处理 -blockmintxfee 参数。

    设置要在块创建中包含的事务满足的最低费率,即低于这个费率,交易将不进行打包。代码如下:

    if (gArgs.IsArgSet("-blockmintxfee"))
    {
        CAmount n = 0;
        if (!ParseMoney(gArgs.GetArg("-blockmintxfee", ""), n))
            return InitError(AmountErrMsg("blockmintxfee", gArgs.GetArg("-blockmintxfee", "")));
    }
  18. 处理 -dustrelayfee 参数。

    具体代码如下:

    if (gArgs.IsArgSet("-dustrelayfee"))
    {
        CAmount n = 0;
        if (!ParseMoney(gArgs.GetArg("-dustrelayfee", ""), n))
            return InitError(AmountErrMsg("dustrelayfee", gArgs.GetArg("-dustrelayfee", "")));
        dustRelayFee = CFeeRate(n);
    }
  19. 处理 -acceptnonstdtxn 参数。

    这个参数代表中继和挖掘非标准交易。具体代码如下:

    fRequireStandard = !gArgs.GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard());
    if (chainparams.RequireStandard() && !fRequireStandard)
        return InitError(strprintf("acceptnonstdtxn is not currently supported for %s chain", chainparams.NetworkIDString()));
  20. 处理 -bytespersigop 参数。

    计算中继和挖掘中交易的 sigop 的等效字节数。

    nBytesPerSigOp = gArgs.GetArg("-bytespersigop", nBytesPerSigOp);
  21. 调用钱包初始接口对象的 ParameterInteraction 方法,初始钱包相关的参数。本方法在 wallet/init.cpp 文件中。

    调用代码如下:

    if (!g_wallet_init_interface.ParameterInteraction()) return false;

    代码内部具体处理如下:

    • 检查是否禁用钱包 -disablewallet。如果禁用,就不会加载钱包,并且会禁用钱包 RPC,这种情况下忽略 -wallet

      if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
          for (const std::string& wallet : gArgs.GetArgs("-wallet")) {
              LogPrintf("%s: parameter interaction: -disablewallet -> ignoring -wallet=%s\n", __func__, wallet);
          }
      
          return true;
      }
    • 确定指定的是单一钱包还是多钱包。

      gArgs.SoftSetArg("-wallet", "");
      const bool is_multiwallet = gArgs.GetArgs("-wallet").size() > 1;
    • 处理 -blocksonly-walletbroadcast 参数。

    • 处理 -salvagewallet-rescan 参数。

      如果指定了在启动时试图从损坏的钱包中恢复私钥,即 -salvagewallet 参数,那么不能使用多个钱包。

      if (gArgs.GetBoolArg("-salvagewallet", false)) {
        if (is_multiwallet) {
            return InitError(strprintf("%s is only allowed with a single wallet file", "-salvagewallet"));
        }
      
        if (gArgs.SoftSetBoolArg("-rescan", true)) {
            LogPrintf("%s: parameter interaction: -salvagewallet=1 -> setting -rescan=1\n", __func__);
        }
      }
    • 处理 -zapwallettxes-persistmempool 参数。

      -zapwallettxes 参数表示删除所有钱包交易,并且仅在启动时通过 -rescan 恢复区块链相关的那些部分(1 =保留tx元数据,例如账户所有者和支付请求信息,2 =丢弃tx元数据)。它暗示了在启动时删除交易池中的那些交易。同时,它也暗示了进行区块链扫描,即不能是多钱包。

      bool zapwallettxes = gArgs.GetBoolArg("-zapwallettxes", false);
      if (zapwallettxes && gArgs.SoftSetBoolArg("-persistmempool", false)) {
        LogPrintf("%s: parameter interaction: -zapwallettxes enabled -> setting -persistmempool=0\n", __func__);
      }
      
      if (zapwallettxes) {
        if (is_multiwallet) {
            return InitError(strprintf("%s is only allowed with a single wallet file", "-zapwallettxes"));
        }
        if (gArgs.SoftSetBoolArg("-rescan", true)) {
            LogPrintf("%s: parameter interaction: -zapwallettxes enabled -> setting -rescan=1\n", __func__);
        }
      }      
    • 检查是否指定了 -upgradewallet 参数。

      在多钱包情况下,不能进行钱包升级。

      if (is_multiwallet) {
        if (gArgs.GetBoolArg("-upgradewallet", false)) {
            return InitError(strprintf("%s is only allowed with a single wallet file", "-upgradewallet"));
        }
      }
    • 检查是否指定了 -sysperms 参数。

      如果指定了 -sysperms 参数,则抛出初始异常错误。这个参数表示了用系统默认的权限创建一个新文件,而不是 077,只有在禁用钱包功能的情况下才有效。

      if (gArgs.GetBoolArg("-sysperms", false))
        return InitError("-sysperms is not allowed in combination with enabled wallet functionality");
    • 检查是否同时指定了 -prune-rescan 参数。

      在指定了修剪模式的情况下,不能执行扫描区块链的动作。所以如果同时指定了这两个参数,抛出错误。

      if (gArgs.GetArg("-prune", 0) && gArgs.GetBoolArg("-rescan", false))
        return InitError(_("Rescans are not possible in pruned mode. You will need to use -reindex which will download the whole blockchain again."));
    • 处理 -maxtxfee 参数。

      -maxtxfee 参数表示了在单个钱包的交易或原始交易中使用的最高总费用。如果设置过小,可能会中止大型交易。这个值不能小于最小中继交易费用。

      if (gArgs.IsArgSet("-maxtxfee"))
      {
        CAmount nMaxFee = 0;
        if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee))
            return InitError(AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", "")));
        if (nMaxFee > HIGH_MAX_TX_FEE)
            InitWarning(_("-maxtxfee is set very high! Fees this large could be paid on a single transaction."));
        maxTxFee = nMaxFee;
        if (CFeeRate(maxTxFee, 1000) < ::minRelayTxFee)
        {
            return InitError(strprintf(_("Invalid amount for -maxtxfee=: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)"),
                                       gArgs.GetArg("-maxtxfee", ""), ::minRelayTxFee.ToString()));
        }
      }
  22. 获取 -permitbaremultisig-datacarrier-datacarriersize等参数。

     fIsBareMultisigStd = gArgs.GetBoolArg("-permitbaremultisig", DEFAULT_PERMIT_BAREMULTISIG);
     fAcceptDatacarrier = gArgs.GetBoolArg("-datacarrier", DEFAULT_ACCEPT_DATACARRIER);
     nMaxDatacarrierBytes = gArgs.GetArg("-datacarriersize", nMaxDatacarrierBytes);
  23. 调用 -SetMockTime 方法,设置模拟时间。

     SetMockTime(gArgs.GetArg("-mocktime", 0)); 
  24. 根据 -peerbloomfilters 参数,设置本地支持的服务。

     if (gArgs.GetBoolArg("-peerbloomfilters", DEFAULT_PEERBLOOMFILTERS))
         nLocalServices = ServiceFlags(nLocalServices | NODE_BLOOM);
  25. 检测 -rpcserialversion 参数是否小于0,是否大于1。

     if (gArgs.GetArg("-rpcserialversion", DEFAULT_RPC_SERIALIZE_VERSION) < 0)
         return InitError("rpcserialversion must be non-negative.");
    
     if (gArgs.GetArg("-rpcserialversion", DEFAULT_RPC_SERIALIZE_VERSION) > 1)
         return InitError("unknown rpcserialversion requested.");
  26. 获取 -maxtipage 参数值。

     nMaxTipAge = gArgs.GetArg("-maxtipage", DEFAULT_MAX_TIP_AGE);
  27. 处理 -mempoolreplacement 参数。

    -mempoolreplacement 参数表示是否启用交易池交易替换。

    fEnableReplacement = gArgs.GetBoolArg("-mempoolreplacement", DEFAULT_ENABLE_REPLACEMENT);
    if ((!fEnableReplacement) && gArgs.IsArgSet("-mempoolreplacement")) {
        std::string strReplacementModeList = gArgs.GetArg("-mempoolreplacement", "");  // default is impossible
        std::vector<std::string> vstrReplacementModes;
        boost::split(vstrReplacementModes, strReplacementModeList, boost::is_any_of(","));
        fEnableReplacement = (std::find(vstrReplacementModes.begin(), vstrReplacementModes.end(), "fee") != vstrReplacementModes.end());
    }
  28. 处理 -vbparams 参数。

    -vbparams 参数代表了对于指定的版本位部署,使用给定的开始/结束时间。

    重载版本位只在回归测试模式下才允许,否则会抛出初始异常错误。在回归测试模式下检查所有指定的版本位。

    if (gArgs.IsArgSet("-vbparams")) {
        // Allow overriding version bits parameters for testing
        if (!chainparams.MineBlocksOnDemand()) {
            return InitError("Version bits parameters may only be overridden on regtest.");
        }
        for (const std::string& strDeployment : gArgs.GetArgs("-vbparams")) {
            std::vector<std::string> vDeploymentParams;
            boost::split(vDeploymentParams, strDeployment, boost::is_any_of(":"));
            if (vDeploymentParams.size() != 3) {
                return InitError("Version bits parameters malformed, expecting deployment:start:end");
            }
            int64_t nStartTime, nTimeout;
            if (!ParseInt64(vDeploymentParams[1], &nStartTime)) {
                return InitError(strprintf("Invalid nStartTime (%s)", vDeploymentParams[1]));
            }
            if (!ParseInt64(vDeploymentParams[2], &nTimeout)) {
                return InitError(strprintf("Invalid nTimeout (%s)", vDeploymentParams[2]));
            }
            bool found = false;
            for (int j=0; j<(int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++j)
            {
                if (vDeploymentParams[0].compare(VersionBitsDeploymentInfo[j].name) == 0) {
                    UpdateVersionBitsParameters(Consensus::DeploymentPos(j), nStartTime, nTimeout);
                    found = true;
                    LogPrintf("Setting version bits activation parameters for %s to start=%ld, timeout=%ld\n", vDeploymentParams[0], nStartTime, nTimeout);
                    break;
                }
            }
            if (!found) {
                return InitError(strprintf("Invalid deployment (%s)", vDeploymentParams[0]));
            }
        }
    }

第4步,检查相关的加密函数(src/bitcoind.cpp

AppInitSanityChecks 函数初始相关的加密曲线与函数,并且确保只有 Bitcoind 在运行。

  1. 调用 SHA256AutoDetect() 方法,探测使用的 SHA256 算法。
  2. 调用 RandomInit 方法,初始化随机数。
  3. 调用 ECC_Start 方法,初始化椭圆曲线。
  4. 调用 globalVerifyHandle.reset(new ECCVerifyHandle()) 方法,重置验证处理器。
  5. 调用 InitSanityCheck 方法,进行完整性检查。主要是进行各种底层检查。
  6. 调用 LockDataDirectory 方法,锁定数据目录,确保只有一个 bitcoind 进程在使用数据目录。

第4a 步,应用程序初始化(src/init.cpp::AppInitMain()

AppInitMain 函数是应用初始化的主体,包括本步骤在内的以下步骤的主体都是在这个函数内部执行。

  1. 调用 Params 函数,获取 chainparams

    方法定义在 src/chainparams.cpp 文件中。这个变量主要是包含一些共识的参数,自身是根据选择不同的网络 maintestnet 或者 regtest 来生成不同的参数。

  2. 如果是非 Windows 系统,则调用 CreatePidFile 函数,创建进程的PID文件。

    pid 文件简介如下:

    • pid文件的内容

      pid文件为文本文件,内容只有一行, 记录了该进程的ID。 用cat命令可以看到。

    • pid文件的作用

      防止进程启动多个副本。只有获得pid文件(固定路径固定文件名)写入权限(F_WRLCK)的进程才能正常启动并把自身的PID写入该文件中。其它同一个程序的多余进程则自动退出。

  3. 如果命令行指定了 shrinkdebugfile 参数或默认的调试文件,则调用日志对象的 ShrinkDebugFile 方法,处理 debug.log 文件。

    如果日志长度小于11MB,那么就不做处理;否则读取文件的最后 RECENT_DEBUG_HISTORY_SIZE 10M 内容,重新保存到debug.log文件中。

  4. 调用日志对象的 OpenDebugLog 方法,打开日志文件。如果不能打开则抛出异常。

    3,4 两步代码如下:

    if (g_logger->m_print_to_file) {
       if (gArgs.GetBoolArg("-shrinkdebugfile", g_logger->DefaultShrinkDebugFile())) {
           // Do this first since it both loads a bunch of debug.log into memory,
           // and because this needs to happen before any other debug.log printing
           g_logger->ShrinkDebugFile();
       }
       if (!g_logger->OpenDebugLog()) {
           return InitError(strprintf("Could not open debug log file %s",
                                      g_logger->m_file_path.string()));
       }
    }
  5. 调用 InitSignatureCache 函数,设置签名缓冲区大小。

    方法内部会根据 -maxsigcachesize 参数和默认签名缓冲区的大小来设置最终签名缓冲区大小。

  6. 调用 InitScriptExecutionCache 函数,设置脚本执行缓存区大小。

    方法内部会根据 -maxsigcachesize 参数和默认签名缓冲区的大小来设置最终脚本执行缓冲区大小。

  7. 创建指定数量的签名验证线程,并放入线程组。

    具体创建多少个线程,即nScriptCheckThreads 变量在前面根据命令行参数 par 进行设置。创建线程代码如下:

    if (nScriptCheckThreads) {
       for (int i=0; i1; i++)
           threadGroup.create_thread(&ThreadScriptCheck);
    }

    线程内部调用 ThreadScriptCheck 函数进行执行。 ThreadScriptCheck 函数过程如下:

    • 首先调用 RenameThread 函数(内部调用 pthread_setname_np 函数)将当前线程重命名为 bitcoin-scriptch

    • 然后调用 CCheckQueue 队列对象的 Thread 方法,开启内部循环。

      Thread 方法又调用内部私有方法 Loop 方法,生成一个脚本验证工作者,然后进行无限循环,在循环内部调用工作者的 wait(lock) 方法,从而线程进入阻塞,直到有新的任务被加到队列中中时,才会被唤醒执行任务。

  8. 创建一个轻量级的任务定时线程。

    具体代码如下:

    CScheduler::Function serviceLoop = boost::bind(&CScheduler::serviceQueue, &scheduler);
    threadGroup.create_thread(boost::bind(&TraceThread<CScheduler::Function>, "scheduler", serviceLoop));

    代码首先调用 boost::bind 方法,生成 CScheduler 对象 serviceQueue 方法的替代方法。然后调用 threadGroup.create_thread 方法,创建一个线程。

    线程执行的方法是 boost::bind 返回的替代方法,bind 方法的第一个参数为 TraceThread 函数,第二个参数为线程的名字,第三个参数为serviceQueue 方法的替代方法。

    TraceThread 函数内部调用 RenameThread 方法修改线程名字,此处线程名字修改为 bitcoin-scheduler;然后执行传入的可调用对象,此处为前面的替代方法,即 CScheduler 对象 serviceQueue 方法。

    serviceQueue 方法主体是一个无限循环方法,如果队列为空,则进程进入阻塞,直到队列有任务,则醒来执行任务,并把任务从队列中移除。

    bind 方法简介:

    bind并不是一个单独的类或函数,而是非常庞大的家族,依据绑定的参数的个数和要绑定的调用对象的类型,总共有数十种不同的形式,编译器会根据具体的绑定代码制动确定要使用的正确的形式。

    bind接收的第一个参数必须是一个可调用的对象f,包括函数、函数指针、函数对象、和成员函数指针,之后bind最多接受9个参数,参数数量必须与f的参数数量相等,这些参数被传递给f作为入参。 绑定完成后,bind会返回一个函数对象,它内部保存了f的拷贝,具有operator(),返回值类型被自动推导为f的返回类型。在发生调用时这个函数对象将把之前存储的参数转发给f完成调用。

    bind的真正威力在于它的占位符,它们分别定义为_1_2_3 一直到 _9,位于一个匿名的名字空间。占位符可以取代 bind 参数的位置,在发生调用时才接受真正的参数。占位符的名字表示它在调用式中的顺序,而在绑定的表达式中没有没有顺序的要求,_1不一定必须第一个出现,也不一定只出现一次。

  9. 注册后台信号调度器。

    代码如下:

    GetMainSignals().RegisterBackgroundSignalScheduler(scheduler);

    GetMainSignals 方法返回类型为 CMainSignals 的静态全局变量 g_signals。CMainSignals 拥有一个类型为 MainSignalsInstance 的智能指针 m_internals。

    MainSignalsInstance 是一个结构体,包含了系统的主要信号和一个调度器,包括:

    • UpdatedBlockTip
    • TransactionAddedToMempool
    • BlockConnected
    • BlockDisconnected
    • TransactionRemovedFromMempool
    • ChainStateFlushed
    • Broadcast
    • BlockChecked
    • NewPoWValidBlock

    RegisterBackgroundSignalScheduler 方法生成智能指针 m_internals 对象。在第6步,网络初始化时会指定各种处理器

    简单介绍下信号槽。什么是信号槽?

    • 简单来说,信号槽是观察者模式的一种实现,或者说是一种升华。
    • 一个信号就是一个能够被观察的事件,或者至少是事件已经发生的一种通知;一个槽就是一个观察者,通常就是在被观察的对象发生改变的时候——也可以说是信号发出的时候——被调用的函数;你可以将信号和槽连接起来,形成一种观察者-被观察者的关系;当事件或者状态发生改变的时候,信号就会被发出;同时,信号发出者有义务调用所有注册的对这个事件(信号)感兴趣的函数(槽)。
    • 信号和槽是多对多的关系。一个信号可以连接多个槽,而一个槽也可以监听多个信号。
    • 另外信号可以有附加信息。

    比特币中使用的是signals2 信号槽。signals2基于Boost的另一个库signals,实现了线程安全的观察者模式。在signals2库中,观察者模式被称为信号/插槽(signals and slots),他是一种函数回调机制,一个信号关联了多个插槽,当信号发出时,所有关联它的插槽都会被调用。

  10. 调用 GetMainSignals().RegisterWithMempoolSignals 方法,注册内存池信号处理器。

    方法实现如下:

        void CMainSignals::RegisterWithMempoolSignals(CTxMemPool& pool) {
            pool.NotifyEntryRemoved.connect(boost::bind(&CMainSignals::MempoolEntryRemoved, this, _1, _2));
        }

    pool.NotifyEntryRemoved 变量定义如下:

    boost::signals2::signal (CTransactionRef, MemPoolRemovalReason)> NotifyEntryRemoved;

    上面 connect 方法,把插槽连接到信号上,相当于为信号(事件)增加了一个处理器,本例中处理器为 CMainSignals::MempoolEntryRemoved 返回的 bind 方法。每当有信号产生时,就会调用这个方法。

  11. 调用内联函数 RegisterAllCoreRPCCommands ,注册所有核心的 RPC 命令。

    这里及下面的钱包注册 RPC 命令 只讲下每个 RPC 的作用,具体代码与使用后面会进行细讲。如果想要查看系统提供的 RCP 命令/接口,在命令行下输入 ./src/bitcoin-cli -regtest help 就会显示所有非隐藏的 RPC 命令。如果想要显示某个具体的 RPC 接口,比如 getblockchaininfo,执行如下命令 ./src/bitcoin-cli -regtest help getblockchaininfo,即可显示指定 RPC 的相关信息。

    • 第一步,调用 RegisterBlockchainRPCCommands 方法,注册所有关于区块链的 RPC 命令。

      方法内部会遍历 commands 数组,把每一个命令保存到 CRPCTable 对象的 mapCommands 集合中。

      区块链相关的 RPC 命令有如下一些:

      • getblockchaininfo,返回一个包含区块链各种状态信息的对象。
      • getchaintxstats,计算有关链中交易总数和费率的统计数据。
      • getbestblockhash,返回最长区块链的最佳高度区块的哈希。
      • getblockstats,计算给定窗口的区块统计信息。
      • getblockcount,返回最长区块链的区块数量。
      • getblock,返回指定区块的数据。
      • getblockhash,返回区块哈希值。
      • getblockheader,返回指定区块的头部。
      • getchaintips,返回所有区块树的顶端区块信息,包括最佳区块链,孤儿区块链等。
      • getdifficulty,返回POW难度值。
      • getmempoolancestors
      • getmempooldescendants
      • getmempoolentry,返回给定交易的交易池数据。
      • getmempoolinfo,返回交易池活跃状态的详细信息。
      • getrawmempool,返回交易池中的所有交易ID。
      • gettxout,返回未花费交易输出的详细信息。
      • gettxoutsetinfo,返回未花费交易输出的统计信息。
      • pruneblockchain,修剪区块链。
      • savemempool,将内存池转储到磁盘。
      • verifychain,验证区块链数据库。
      • preciousblock
      • scantxoutset,扫描符合某些特定描述的未花费的交易输出集。
      • invalidateblock,永久性地将块标记为无效,就好像它违反了共识规则一样。
      • reconsiderblock,删除块及其后代的无效状态,重新考虑它们以便进行激活。这可用于撤消 invalidateblock 的效果。
      • waitfornewblock,等待特定的新区块并返回有关它的有用信息。
      • waitforblock,等待特定的新区块并返回有关它的有用信息。如果超时或区块不存在,则返回指定的区块。
      • waitforblockheight,等待(最少多少)区块高度并返回区块链顶端的高度和哈希值。如果超时或区块不存在,则返回指定的区块高度和哈希值。
      • syncwithvalidationinterfacequeue
    • 第二步,调用 RegisterNetRPCCommands 方法,注册所有关于网络相关的 RPC 命令。

      方法内部会遍历 commands 数组,把每一个命令保存到 CRPCTable 对象的 mapCommands 集合中。

      网络相关的 RPC 命令有如下一些:

      • getconnectioncount,返回连接到其他节点的数量。
      • ping,将 ping 请求发送到其他节点,以测量 ping 的时间。
      • getpeerinfo,返回每一个连接节点的信息。
      • addnode,添加、或移除、或连接到一个节点一次(目的为了获取其他节点)。
      • disconnectnode,立即从某个节点断开。
      • getaddednodeinfo,返回给定节点,或所有节点的信息。
      • getnettotals,返回网络传输的一些信息。
      • getnetworkinfo,返回P2P网络的各种状态信息。
      • setban,向禁止列表中添加或移除IP地址/子网。
      • listbanned,显示禁止列表的内容
      • clearbanned,清空禁止列表。
      • setnetworkactive,禁止或打开所有 P2P 网络活动。
    • 第三步,调用 RegisterMiscRPCCommands 方法,注册所有的杂项 RPC 命令。

      方法内部会遍历 commands 数组,把每一个命令保存到 CRPCTable 对象的 mapCommands 集合中。

      杂项相关的 RPC 命令有如下一些:

      • getmemoryinfo,返回一个包含内存使用信息的对象。
      • logging,获取或设置日志配置。
      • validateaddress,验证一个比特币地址是否有效。
      • createmultisig,创建一个多重签名。
      • verifymessage,验证一个签名过的消息。
      • signmessagewithprivkey,用私钥签名一个消息。
      • setmocktime,设置本地时间,只在回归测试下使用。
      • echo,简单回显输入参数。此命令用于测试。
      • echojson,简单回显输入参数。此命令用于测试。
    • 第四步,调用 RegisterMiningRPCCommands 方法,注册所有关于挖矿相关的 RPC 命令。

      方法内部会遍历 commands 数组,把每一个命令保存到 CRPCTable 对象的 mapCommands 集合中。

      挖矿相关的 RPC 命令有如下一些:

      • getnetworkhashps,根据最后的 n 个区块数据,估算网络每秒哈希速率。
      • getmininginfo,返回与挖矿相关的信息。
      • prioritisetransaction,以更高(或更低)的优先级接受已挖掘的块中的事务。
      • getblocktemplate,获取区块链模板,聚合挖矿会用到这个方法,详见 BIPs 22, 23, 9 和 145。
      • submitblock,提交一个新区块到网络上。
      • submitheader,将给定的十六进制数字解码为区标头部,并将其作为候选区块链顶端区块头部提交(如果有效)。
      • generatetoaddress,立即挖掘指定数量的区块,在回归测试中可以快速生成区块。
      • estimatefee,0.17 版本中被移除。
      • estimatesmartfee,估计交易所需的费用。
      • estimaterawfee,估计交易所需的费用。
    • 第五步,调用 RegisterRawTransactionRPCCommands 方法,注册所有关于原始交易的 RPC 命令。

      方法内部会遍历 commands 数组,把每一个命令保存到 CRPCTable 对象的 mapCommands 集合中。

      原始交易相关的 RPC 命令有如下一些:

      • getrawtransaction,返回原始交易。
      • createrawtransaction,基于输入创建交易,返回交易的16进制。
      • decoderawtransaction,解码原始交易,返回表示原始交易的JSON对象。
      • decodescript,解码16进制编码过的脚本。
      • sendrawtransaction,提交一个原始交易到本地接点和网络。
      • combinerawtransaction,将多个部分签名的交易合并到一个交易中。合并的交易可以是另一个部分签名的交易或完整签署交易。
      • signrawtransaction,签名一个原始交易。不建议使用。
      • signrawtransactionwithkey,用私钥签名一个原始交易。
      • testmempoolaccept,测试一个原始交易是否能被交易池接受。
      • decodepsbt,返回一个表示序列化的、base64 编码过的部分签名交易对象。
      • combinepsbt,合并多个部分签名的交易到一个交易中。
      • finalizepsbt
      • createpsbt,创建一个部分签名交易格式的交易。
      • converttopsbt,转化一个网络序列化的交易到 PSBT。
      • gettxoutproof,区块链方面的。返回包含在区块中的交易的十六进制编码的证明。
      • verifytxoutproof,区块链方面的。验证区块中交易的证明。
  12. 调用钱包接口的 RegisterRPC 方法,注册钱包接口的 RPC 命令。

    实现类为 wallet/init.cpp 文件中的 WalletInit ,方法内部调用 RegisterWalletRPCCommands 进行注册,后者又调用 wallet/rpcwallet.cpp 文件中的 RegisterWalletRPCCommands 方法,完成注册钱包的 RPC 命令。

    钱包相关的 RPC 命令有如下一些:

    • fundrawtransaction,添加一个输入到交易中,直到交易可以满足输出。

    • walletprocesspsbt,用钱包里面的输入来更新交易,然后签名输入。

    • walletcreatefundedpsbt,以部分签名格式(PSBT)创建和funds交易。

    • resendwallettransactions,立即广播未确认的交易到所有节点。

    • abandontransaction,将钱包内的交易标记为已放弃。

    • abortrescan,停止当前钱包扫描。

    • addmultisigaddress,添加一个 nrequired-to-sign 多重签名地址到钱包。

    • addwitnessaddress,不建议使用。

    • backupwallet,备份钱包。

    • bumpfee

    • createwallet,创建并加载一个新钱包

      系统会创建一个默认的钱包,名字为空。可以用 listwallets 显示所有加载的钱包。可以用 importprivkey 命令添加一个私钥到钱包。

      当有多个钱包时,为了操作某个特定钱包,需要使用 -rpcwallet=钱包名字,比如:显示默认钱包的命令为:./src/bitcoin-cli -regtest -rpcwallet= getwalletinfo

    • dumpprivkey,显示与地址相关联的私钥,importprivkey 可以使用这个输出。

    • dumpwallet,将所有钱包密钥以人类可读的格式转储到服务器端文件。

    • encryptwallet,用密码加密钱包。

    • getaddressinfo,显示比特币地址信息。

    • getbalance,返回总的可用余额。

    • getnewaddress,返回一个新的比特币地址

      生成地址的过程会先生成私钥,可以通过 dumpprivkey 命令来显示与之相关的私钥,可以通过 setlabel 命令设置与给定地址相关的标签。

    • getrawchangeaddress,返回一个新的比特币地址用于找零。这个用于原始交易,不是常规使用。

    • getreceivedbyaddress,返回至少具有指定确认的交易中给定地址收到的总金额。

    • gettransaction,返回钱包中指定交易的详细信息。

    • getunconfirmedbalance,返回未确认的余额总数。

    • getwalletinfo,返回钱包的信息。

    • importmulti,导入地址或脚本,以 one-shot-only 方式重新扫描所有地址。

    • importprivkey,添加一个私钥到钱包。

      对多个钱包来说要在命令行需要使用 -rpcwallet=钱包名字 来指定使馆名字。比如:./src/bitcoin-cli -regtest -rpcwallet= importprivkey "cQM91nga98fMG2xGQHe6LYVH46Yo8tQbHBNQqwMNnrFZPcUs3MMf" ,在执行这个命令时记得要换成你的私钥。

    • importwallet,从转储文件中导入钱包。

    • importaddress,添加一个可以查看的地址或脚本(十六进制),就好像它在钱包里但不能用来支付。

    • importprunedfunds

    • importpubkey,添加一个可以查看的公钥,就好像它在钱包里但不能用来支付。

    • keypoolrefill

    • listaddressgroupings

    • listlockunspent,返回未花费输出的列表。

    • listreceivedbyaddress,列出接收地址的余额。

    • listsinceblock,获取指定区块以来的所有交易,如果省略区块,则获取所有交易。

    • listtransactions,返回指定数量的最近交易,跳过指定账户的第一个开始的交易。

    • listunspent,返回未花费交易输出。

    • listwallets,返回当前已经的钱包列表。

    • loadwallet,从钱包文件或目录中加载钱包。

    • lockunspent,更新暂时不能花费的输出列表。临时解锁或锁定特定的交易输出。

    • sendmany

    • sendtoaddress,发送一定的币到指定的地址。

    • settxfee,设置每 kb 交易费用。

    • signmessage,用某个地址的私钥签名消息。

    • signrawtransactionwithwallet,签名原始交易的输入。

    • unloadwallet,卸载请求端点引用的钱包,否则卸载参数中指定的钱包。

    • walletlock,从内存中移除钱包的加密,锁定钱包。

    • walletpassphrasechange,更新钱包的密码。

    • walletpassphrase,在内存中存储钱包的解密密钥。

    • removeprunedfunds,从钱包中删除指定的交易。

    • rescanblockchain,重新扫描本地区块链进行钱包相关交易。

    • sethdseed,设置或生成确定性分层性钱包的种子。

    • getaccountaddress,不建议使用,即将移除。

    • getaccount,不建议使用,即将移除。

    • getaddressesbyaccount,不建议使用,即将移除。

    • getreceivedbyaccount,不建议使用,即将移除。

    • listaccounts,不建议使用,即将移除。

    • listreceivedbyaccount,不建议使用,即将移除。

    • setaccount,不建议使用,即将移除。

    • sendfrom,不建议使用,即将移除。

    • move,不建议使用,即将移除。

    • getaddressesbylabel,返回与标签相关的所有地址列表。

    • getreceivedbylabel,返回与标签相关的、并且至少指定确认的所有交易的比特币数量。

    • listlabels,返回所有的标签,或与特定用途关联地址相关的标签列表。

    • listreceivedbylabel,返回与标签对应的接收的交易。

    • setlabel,设置与给定地址相关的标签。

    • generate,立即挖出指定的区块(在RPC返回之前)到钱包中指定的地址。

  13. 如果命令参数指定 server ,则调用 AppInitServers 方法,注册服务器。

    具体代码如下:

    if (gArgs.GetBoolArg("-server", false))
    {
        uiInterface.InitMessage_connect(SetRPCWarmupStatus);
        if (!AppInitServers())
            return InitError(_("Unable to start HTTP server. See debug log for details."));
    }

    AppInitServers 方法内处理流程如下:

    • 调用 RPCServer::OnStarted 方法,设置 RPC 服务器启动时的处理方法。

      具体处理方法如下,以后再讲这个方法:

      static void OnRPCStarted()
      {
        uiInterface.NotifyBlockTip_connect(&RPCNotifyBlockChange);
      }
    • 调用 RPCServer::OnStopped 方法,设置 RPC 服务器关闭时的处理方法。

      具体处理方法如下,以后再讲这个方法:

      static void OnRPCStopped()
      {
        uiInterface.NotifyBlockTip_disconnect(&RPCNotifyBlockChange);
        RPCNotifyBlockChange(false, nullptr);
        g_best_block_cv.notify_all();
        LogPrint(BCLog::RPC, "RPC stopped.\n");
      }
    • 调用 InitHTTPServer 方法,初始化 HTTP 服务器。

      if (!InitHTTPServer())
          return false;

      InitHTTPServer 方法首先会调用 InitHTTPAllowList 方法初始化允许 JSON-RPC 调用的地址列表。然后生成一个 HTTP 服务器,并设置服务器的超时时间、最大头部大小、最大消息体大小、绑定到指定的地址上(以便允许这些地址发起请求)。最后,生成 HTTP 工作者队列。

    • 调用 StartRPC 方法,启动 RPC 信号监听。

    • 调用 StartHTTPRPC 方法,启动 HTTP RPC 服务器。

      具体代码如下:

      if (!StartHTTPRPC())
        return false;

      StartHTTPRPC 方法处理如下:

      • 首先,调用 InitRPCAuthentication 方法,设置 JSON-RPC 调用的鉴权方法。
      • 然后, RegisterHTTPHandler 方法,注册 / 请求处理方法为 HTTPReq_JSONRPC 方法。
      • 再然后,调用 RegisterHTTPHandler 方法,注册 /wallet/ 请求处理方法为 HTTPReq_JSONRPC 方法。
    • 如果命令参数指定 rest,调用 StartREST 方法,设置 /rest/xxx 一系列 HTTP 请求的处理器。

    • 调用 StartHTTPServer 方法,启动 HTTP 服务器。

      StartHTTPServer 方法代码如下:

      void StartHTTPServer()
      {
        LogPrint(BCLog::HTTP, "Starting HTTP server\n");
        int rpcThreads = std::max((long)gArgs.GetArg("-rpcthreads", DEFAULT_HTTP_THREADS), 1L);
        LogPrintf("HTTP: starting %d worker threads\n", rpcThreads);
        std::packaged_task<bool(event_base*)> task(ThreadHTTP);
        threadResult = task.get_future();
        threadHTTP = std::thread(std::move(task), eventBase);
      
        for (int i = 0; i < rpcThreads; i++) {
            g_thread_http_workers.emplace_back(HTTPWorkQueueRun, workQueue);
        }
      }

      下面简单讲述下方法内部的处理:

      • 首先,根据命令参数获取处理 RPC 命令的线程数量。
      • 然后,生成一个任务对象 task,从而得到一个事件分发线程。
      • 最好,生成指定数量的处理 RPC 命令的线程。

第5步,验证钱包数据库完整性(src/init.cpp::AppInitMain()

调用钱包接口的 Verify 方法,验证钱包数据库。实现类为 wallet/init.cpp 文件中的 WalletInit ,方法处理流程如下:

  • 检查命令行指定了禁止钱包 disablewallet,如果禁止,则直接返回。

    代码如下:

    if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
      return true;
    }
  • 如果设置了钱包路径 walletdir,则检查钱包数据库目录是否存在,是否为目录、且是否为常规的的路径。

    代码如下:

    if (gArgs.IsArgSet("-walletdir")) {
      fs::path wallet_dir = gArgs.GetArg("-walletdir", "");
      if (!fs::exists(wallet_dir)) {
          return InitError(strprintf(_("Specified -walletdir \"%s\" does not exist"), wallet_dir.string()));
      } else if (!fs::is_directory(wallet_dir)) {
          return InitError(strprintf(_("Specified -walletdir \"%s\" is not a directory"), wallet_dir.string()));
      } else if (!wallet_dir.is_absolute()) {
          return InitError(strprintf(_("Specified -walletdir \"%s\" is a relative path"), wallet_dir.string()));
      }
    }
  • 检查所有的钱包文件。

    首先,确保钱包不存在名称相同的。然后,调用 CWallet::Verify 方法检查钱包的路径。

    代码如下:

    for (const auto& wallet_file : wallet_files) {
      fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir());
    
      if (!wallet_paths.insert(wallet_path).second) {
          return InitError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), wallet_file));
      }
    
      std::string error_string;
      std::string warning_string;
      bool verify_success = CWallet::Verify(wallet_file, salvage_wallet, error_string, warning_string);
      if (!error_string.empty()) InitError(error_string);
      if (!warning_string.empty()) InitWarning(warning_string);
      if (!verify_success) return false;
    }

第6步,网络初始化(src/init.cpp::AppInitMain()

  1. 生成智能指针对象 g_connman,类型为 CConnman

     g_connman = std::unique_ptr(new CConnman(GetRand(std::numeric_limits::max()), GetRand(std::numeric_limits::max())));
     CConnman& connman = *g_connman;
  2. 生成智能指针对象 peerLogic,类型为 PeerLogicValidation

     peerLogic.reset(new PeerLogicValidation(&connman, scheduler, gArgs.GetBoolArg("-enablebip61", DEFAULT_ENABLE_BIP61)));

    PeerLogicValidation 继承了 CValidationInterface、NetEventsInterface 两个类。实现 CValidationInterface 这个类可以订阅验证过程中产生的事件。实现 NetEventsInterface 这个类可以处理消息网络消息。

  3. 注册各种验证处理器,即信号处理器,在发送信号时会调用这些处理器。

     RegisterValidationInterface(peerLogic.get());

    方法具体实现如下:

     void RegisterValidationInterface(CValidationInterface* pwalletIn) {
         g_signals.m_internals->UpdatedBlockTip.connect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1, _2, _3));
         g_signals.m_internals->TransactionAddedToMempool.connect(boost::bind(&CValidationInterface::TransactionAddedToMempool, pwalletIn, _1));
         g_signals.m_internals->BlockConnected.connect(boost::bind(&CValidationInterface::BlockConnected, pwalletIn, _1, _2, _3));
         g_signals.m_internals->BlockDisconnected.connect(boost::bind(&CValidationInterface::BlockDisconnected, pwalletIn, _1));
         g_signals.m_internals->TransactionRemovedFromMempool.connect(boost::bind(&CValidationInterface::TransactionRemovedFromMempool, pwalletIn, _1));
         g_signals.m_internals->ChainStateFlushed.connect(boost::bind(&CValidationInterface::ChainStateFlushed, pwalletIn, _1));
         g_signals.m_internals->Broadcast.connect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1, _2));
         g_signals.m_internals->BlockChecked.connect(boost::bind(&CValidationInterface::BlockChecked, pwalletIn, _1, _2));
         g_signals.m_internals->NewPoWValidBlock.connect(boost::bind(&CValidationInterface::NewPoWValidBlock, pwalletIn, _1, _2));
     }

    静态变量 g_signals 在程序启动前生成,m_internals 在第4a 步应用程序初始化过程中生成。

  4. 根据命令行参数 -uacomment,处理追加到用户代理的字符串。

     std::vector<std::string> uacomments;
     for (const std::string& cmt : gArgs.GetArgs("-uacomment")) {
         if (cmt != SanitizeString(cmt, SAFE_CHARS_UA_COMMENT))
             return InitError(strprintf(_("User Agent comment (%s) contains unsafe characters."), cmt));
         uacomments.push_back(cmt);
     }
  5. 构造并检查版本字符串长度是否大于 version 消息中版本的最大长度。

     strSubVersion = FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, uacomments);
     if (strSubVersion.size() > MAX_SUBVERSION_LENGTH) {
         return InitError(strprintf(_("Total length of network version string (%i) exceeds maximum length (%i). Reduce the number or size of uacomments."),
             strSubVersion.size(), MAX_SUBVERSION_LENGTH));
     }
  6. 如果指定了 onlynet 参数,则设置可以对接进行连接的类型,比如:ipv4、ipv6、onion。

     if (gArgs.IsArgSet("-onlynet")) {
         std::set<enum Network> nets;
         for (const std::string& snet : gArgs.GetArgs("-onlynet")) {
             enum Network net = ParseNetwork(snet);
             if (net == NET_UNROUTABLE)
                 return InitError(strprintf(_("Unknown network specified in -onlynet: '%s'"), snet));
             nets.insert(net);
         }
         for (int n = 0; n < NET_MAX; n++) {
             enum Network net = (enum Network)n;
             if (!nets.count(net))
                 SetLimited(net);
         }
     }

    上面的代码首先把 -onlynet 参数指定的只允许对外连接的网络类型加入集合中,然后进行 for 遍历,如果当前的类型不在允许的集合中,则调用 SetLimited 方法,设置这些类型为受限的。

  7. 获取是否允许进行 DNS 查找,是否进行代理随机

     fNameLookup = gArgs.GetBoolArg("-dns", DEFAULT_NAME_LOOKUP);
     bool proxyRandomize = gArgs.GetBoolArg("-proxyrandomize", DEFAULT_PROXYRANDOMIZE);

    两者默认都为真。

  8. 处理网络代理。

    如果指定了 -proxy,且不等于 0,即指定了代理地址,进行下面的处理:

    • 调用 Lookup 方法,根据指定的代理,通过 DNS查找,发现代理服务器的地址。
    • 生成 proxyType 对象。
    • 设置 IPv4、IPv6、Tor 网络的代理。
    • 设置命名(域名)代理。
    • 设置不限制连接到 Tor 网络。

    具体代码如下:

    std::string proxyArg = gArgs.GetArg("-proxy", "");
    SetLimited(NET_ONION);
    if (proxyArg != "" && proxyArg != "0") {
       CService proxyAddr;
       if (!Lookup(proxyArg.c_str(), proxyAddr, 9050, fNameLookup)) {
           return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg));
       }
    
       proxyType addrProxy = proxyType(proxyAddr, proxyRandomize);
       if (!addrProxy.IsValid())
           return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg));
    
       SetProxy(NET_IPV4, addrProxy);
       SetProxy(NET_IPV6, addrProxy);
       SetProxy(NET_ONION, addrProxy);
       SetNameProxy(addrProxy);
       SetLimited(NET_ONION, false); // by default, -proxy sets onion as reachable, unless -noonion later
    }
  9. 处理洋葱网络。 如果指定了 onion 参数,则处理洋葱网络的相关设置。

    如果指定了 -onion,且不等于空字符串,即指定了洋葱代理地址,进行下面的处理:

    • 如果参数等于 0,设置洋葱网络受限,即不可达。否则,进行下面的处理。
    • 调用 Lookup 方法,根据指定的代理,通过 DNS查找,发现代理服务器的地址。
    • 生成 proxyType 对象。
    • 设置 Tor 网络的代理。
    • 设置不限制连接到 Tor 网络。

    具体代码如下:

    std::string onionArg = gArgs.GetArg("-onion", "");
    if (onionArg != "") {
       if (onionArg == "0") { // Handle -noonion/-onion=0
           SetLimited(NET_ONION); // set onions as unreachable
       } else {
           CService onionProxy;
           if (!Lookup(onionArg.c_str(), onionProxy, 9050, fNameLookup)) {
               return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg));
           }
           proxyType addrOnion = proxyType(onionProxy, proxyRandomize);
           if (!addrOnion.IsValid())
               return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg));
           SetProxy(NET_ONION, addrOnion);
           SetLimited(NET_ONION, false);
       }
    }
  10. 处理通过 -externalip 参数设置的外部 IP地址。

    获取并遍历所有指定的外部地址,进行如下处理:调用 Lookup 方法进行DNS 查找。如果成功则调用 AddLocal 方法,保存新的地址。否则,抛出初始化错误。

    for (const std::string& strAddr : gArgs.GetArgs("-externalip")) {
        CService addrLocal;
        if (Lookup(strAddr.c_str(), addrLocal, GetListenPort(), fNameLookup) && addrLocal.IsValid())
            AddLocal(addrLocal, LOCAL_MANUAL);
        else
            return InitError(ResolveErrMsg("externalip", strAddr));
    }
  11. 如果设置了 maxuploadtarget 参数,则设置最大出站限制。

      if (gArgs.IsArgSet("-maxuploadtarget")) {
          nMaxOutboundLimit = gArgs.GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET)*1024*1024;
      }


思考题:

1.如何创建比特币钱包,如何创建钱包地址,以及如何将钱包和地址相关联?

2.通过 listlabels 指令,以及getaddressesbylabel 可以获得比特币钱包地址,然后再通过地址生成区块链。请比较两个指令生成的区块有什么不同。

指令一:

./src/bitcoin-cli -regtest generatetoaddress 100 "mz58vZcVa4EwYxaA3yBjes7munMjBLdQCP"

(指令一里面的mz58vZcVa4EwYxaA3yBjes7munMjBLdQCP 为笔者的比特币钱包地址,请自己运行 listlabels 指令,以及getaddressesbylabel 获取自己的比特币钱包地址,然后替换它)

指令二:

./src/bitcoin-cli -regtest generate 100

3.如何在以太坊钱包中接入比特币钱包?



为了更高效的交流探讨区块链开发过程中遇到的问题,我建立了一个技术交流群。

欢迎将以上问题的答案发在群中讨论,或者在帖子下面留言。

QQ群:253968045

QQ号:77078193 或者 705706498

微信:joepeak

从源代码编译比特币--从零开始学习区块链技术(一)_第1张图片

我是区小白,区块链开发者,区块链技术爱好者,深入研究比特币,以太坊,EOS Dash,Rsk,Java, Nodejs,PHP,Python,C++
现为Ulord全球社区联盟(优得社区)核心开发者。我希望能聚集更多区块链开发者,一起学习共同进步。

敬请期待下一篇文章:接入比特币网络的关键步骤(下)

往期文章:

从源代码编译比特币

如何接入比特币网络以及步骤分析

原文转载自:

优得社区–从0开始学习比特币专题

从零开始学习比特币–如何接入比特币网络以及原理分析

从零开始学习区块链技术(一)–从源代码编译比特币

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