以下为系统启动过程中重要的步骤。
src/bitcoind.cpp
)AppInitBasicSetup
函数进行基本的设置。
调用 SetupNetworking
函数,进行网络设置。
主要是针对 Win32 系统处理套接字,别的系统直接返回真。
如果不是 WIN32 系统,进行下面的处理:
sysperms
参数为真,调用 umask
函数,设置位码为 077。registerSignalHandler
函数,设置 SIGTERM
信号处理器为 HandleSIGTERM
;SIGINT
为 HandleSIGTERM
;SIGHUP
为 HandleSIGHUP
。src/bitcoind.cpp
)AppInitParameterInteraction
函数前半部分。
首先,调用 Params
方法,获取前面初始化的 globalChainParams
区块链对象。
const CChainParams& chainparams = Params();
根据不同的网络,chainparams 的真实类型可能是 CMainParams
,代表主网络;或者是 CTestNetParams
,代表测试网络;或者是 CRegTestParams
代表回归测试网络。
检查指定的区块目录是否存。如果不存在,则返回初始化错误。
if (!fs::is_directory(GetBlocksDir(false))) {
return InitError(strprintf(_("Specified blocks directory \"%s\" does not exist."), gArgs.GetArg("-blocksdir", "").c_str()));
}
如果同时指定了 prune
、txindex
,则抛出初始化错误。
如果指定了区块修剪 prune
,就要禁止交易索引 txindex
,两者不兼容,只能其一。
if (gArgs.GetArg("-prune", 0)) {
if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX))
return InitError(_("Prune mode is incompatible with -txindex."));
}
检查是否指定了 -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");
}
确保有足够的文件符可用。
因为在类 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));
src/bitcoind.cpp
)AppInitParameterInteraction
函数后半部分。
处理 debug
、debugexclude
、debugnet
等参数。
如果指定了 -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));
}
}
}
}
如果指定了 -socks
参数,则提示使用 SOCKS5
if (gArgs.IsArgSet("-socks"))
return InitError(_("Unsupported argument -socks found. Setting SOCKS version isn't possible anymore, only SOCKS5 proxies are supported."));
如果指定了 -tor
参数,则提示使用 -onion
。
if (gArgs.GetBoolArg("-tor", false))
return InitError(_("Unsupported argument -tor found, use -onion."));
如果指定了 -benchmark
参数,则提示使用 -debug=bench
。
if (gArgs.GetBoolArg("-benchmark", false))
InitWarning(_("Unsupported argument -benchmark ignored, use -debug=bench."));
如果指定了 -whitelistalwaysrelay
参数,则提示使用 -whitelistrelay
,或-whitelistforcerelay
。
if (gArgs.GetBoolArg("-whitelistalwaysrelay", false))
InitWarning(_("Unsupported argument -whitelistalwaysrelay ignored, use -whitelistrelay and/or -whitelistforcerelay."));
如果指定了 -blockminsize
参数,则提示使用 -blockminsize
。
if (gArgs.IsArgSet("-blockminsize"))
InitWarning("Unsupported argument -blockminsize ignored.");
根据是否指定 -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());
设置检查点默认打开
fCheckpointsEnabled = gArgs.GetBoolArg("-checkpoints", DEFAULT_CHECKPOINTS_ENABLED);
处理 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");
根据是否指定 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);
}
计算内存池/交易池限制,包括处理 maxmempool
、limitdescendantsize
参数。
前者表示最大内存池,后者表示最小内存池,如果最大值小于最小值,则抛出初始化错误。
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)));
如果指定了 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);
}
处理 -par
参数,指定脚本签名的线程数量。
代码如下:
if (nScriptCheckThreads <= 0)
nScriptCheckThreads += GetNumCores();
if (nScriptCheckThreads <= 1)
nScriptCheckThreads = 0;
else if (nScriptCheckThreads > MAX_SCRIPTCHECK_THREADS)
nScriptCheckThreads = MAX_SCRIPTCHECK_THREADS;
处理区块修剪参数 -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;
}
处理连接超时时间 -timeout
。
代码如下:
nConnectTimeout = gArgs.GetArg("-timeout", DEFAULT_CONNECT_TIMEOUT);
if (nConnectTimeout <= 0)
nConnectTimeout = DEFAULT_CONNECT_TIMEOUT;
处理 -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());
}
处理 -blockmintxfee
参数。
设置要在块创建中包含的事务满足的最低费率,即低于这个费率,交易将不进行打包。代码如下:
if (gArgs.IsArgSet("-blockmintxfee"))
{
CAmount n = 0;
if (!ParseMoney(gArgs.GetArg("-blockmintxfee", ""), n))
return InitError(AmountErrMsg("blockmintxfee", gArgs.GetArg("-blockmintxfee", "")));
}
处理 -dustrelayfee
参数。
具体代码如下:
if (gArgs.IsArgSet("-dustrelayfee"))
{
CAmount n = 0;
if (!ParseMoney(gArgs.GetArg("-dustrelayfee", ""), n))
return InitError(AmountErrMsg("dustrelayfee", gArgs.GetArg("-dustrelayfee", "")));
dustRelayFee = CFeeRate(n);
}
处理 -acceptnonstdtxn
参数。
这个参数代表中继和挖掘非标准交易。具体代码如下:
fRequireStandard = !gArgs.GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard());
if (chainparams.RequireStandard() && !fRequireStandard)
return InitError(strprintf("acceptnonstdtxn is not currently supported for %s chain", chainparams.NetworkIDString()));
处理 -bytespersigop
参数。
计算中继和挖掘中交易的 sigop 的等效字节数。
nBytesPerSigOp = gArgs.GetArg("-bytespersigop", nBytesPerSigOp);
调用钱包初始接口对象的 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()));
}
}
获取 -permitbaremultisig
、-datacarrier
、-datacarriersize
等参数。
fIsBareMultisigStd = gArgs.GetBoolArg("-permitbaremultisig", DEFAULT_PERMIT_BAREMULTISIG);
fAcceptDatacarrier = gArgs.GetBoolArg("-datacarrier", DEFAULT_ACCEPT_DATACARRIER);
nMaxDatacarrierBytes = gArgs.GetArg("-datacarriersize", nMaxDatacarrierBytes);
调用 -SetMockTime
方法,设置模拟时间。
SetMockTime(gArgs.GetArg("-mocktime", 0));
根据 -peerbloomfilters
参数,设置本地支持的服务。
if (gArgs.GetBoolArg("-peerbloomfilters", DEFAULT_PEERBLOOMFILTERS))
nLocalServices = ServiceFlags(nLocalServices | NODE_BLOOM);
检测 -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.");
获取 -maxtipage
参数值。
nMaxTipAge = gArgs.GetArg("-maxtipage", DEFAULT_MAX_TIP_AGE);
处理 -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());
}
处理 -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]));
}
}
}
src/bitcoind.cpp
)AppInitSanityChecks
函数初始相关的加密曲线与函数,并且确保只有 Bitcoind 在运行。
SHA256AutoDetect()
方法,探测使用的 SHA256 算法。RandomInit
方法,初始化随机数。ECC_Start
方法,初始化椭圆曲线。globalVerifyHandle.reset(new ECCVerifyHandle())
方法,重置验证处理器。InitSanityCheck
方法,进行完整性检查。主要是进行各种底层检查。LockDataDirectory
方法,锁定数据目录,确保只有一个 bitcoind 进程在使用数据目录。src/init.cpp::AppInitMain()
)AppInitMain
函数是应用初始化的主体,包括本步骤在内的以下步骤的主体都是在这个函数内部执行。
调用 Params
函数,获取 chainparams
。
方法定义在 src/chainparams.cpp
文件中。这个变量主要是包含一些共识的参数,自身是根据选择不同的网络 main
、testnet
或者 regtest
来生成不同的参数。
如果是非 Windows 系统,则调用 CreatePidFile
函数,创建进程的PID文件。
pid 文件简介如下:
pid文件的内容
pid文件为文本文件,内容只有一行, 记录了该进程的ID。 用cat命令可以看到。
pid文件的作用
防止进程启动多个副本。只有获得pid文件(固定路径固定文件名)写入权限(F_WRLCK)的进程才能正常启动并把自身的PID写入该文件中。其它同一个程序的多余进程则自动退出。
如果命令行指定了 shrinkdebugfile
参数或默认的调试文件,则调用日志对象的 ShrinkDebugFile
方法,处理 debug.log
文件。
如果日志长度小于11MB,那么就不做处理;否则读取文件的最后 RECENT_DEBUG_HISTORY_SIZE
10M 内容,重新保存到debug.log文件中。
调用日志对象的 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()));
}
}
调用 InitSignatureCache
函数,设置签名缓冲区大小。
方法内部会根据 -maxsigcachesize
参数和默认签名缓冲区的大小来设置最终签名缓冲区大小。
调用 InitScriptExecutionCache
函数,设置脚本执行缓存区大小。
方法内部会根据 -maxsigcachesize
参数和默认签名缓冲区的大小来设置最终脚本执行缓冲区大小。
创建指定数量的签名验证线程,并放入线程组。
具体创建多少个线程,即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)
方法,从而线程进入阻塞,直到有新的任务被加到队列中中时,才会被唤醒执行任务。
创建一个轻量级的任务定时线程。
具体代码如下:
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
不一定必须第一个出现,也不一定只出现一次。
注册后台信号调度器。
代码如下:
GetMainSignals().RegisterBackgroundSignalScheduler(scheduler);
GetMainSignals
方法返回类型为 CMainSignals
的静态全局变量 g_signals。CMainSignals
拥有一个类型为 MainSignalsInstance
的智能指针 m_internals。
MainSignalsInstance
是一个结构体,包含了系统的主要信号和一个调度器,包括:
RegisterBackgroundSignalScheduler
方法生成智能指针 m_internals 对象。在第6步,网络初始化时会指定各种处理器。
简单介绍下信号槽。什么是信号槽?
比特币中使用的是signals2 信号槽。signals2基于Boost的另一个库signals,实现了线程安全的观察者模式。在signals2库中,观察者模式被称为信号/插槽(signals and slots),他是一种函数回调机制,一个信号关联了多个插槽,当信号发出时,所有关联它的插槽都会被调用。
调用 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 方法。每当有信号产生时,就会调用这个方法。
调用内联函数 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 命令有如下一些:
invalidateblock
的效果。第二步,调用 RegisterNetRPCCommands
方法,注册所有关于网络相关的 RPC 命令。
方法内部会遍历 commands
数组,把每一个命令保存到 CRPCTable
对象的 mapCommands
集合中。
网络相关的 RPC 命令有如下一些:
第三步,调用 RegisterMiscRPCCommands
方法,注册所有的杂项 RPC 命令。
方法内部会遍历 commands
数组,把每一个命令保存到 CRPCTable
对象的 mapCommands
集合中。
杂项相关的 RPC 命令有如下一些:
第四步,调用 RegisterMiningRPCCommands
方法,注册所有关于挖矿相关的 RPC 命令。
方法内部会遍历 commands
数组,把每一个命令保存到 CRPCTable
对象的 mapCommands
集合中。
挖矿相关的 RPC 命令有如下一些:
第五步,调用 RegisterRawTransactionRPCCommands
方法,注册所有关于原始交易的 RPC 命令。
方法内部会遍历 commands
数组,把每一个命令保存到 CRPCTable
对象的 mapCommands
集合中。
原始交易相关的 RPC 命令有如下一些:
调用钱包接口的 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返回之前)到钱包中指定的地址。
如果命令参数指定 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);
}
}
下面简单讲述下方法内部的处理:
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;
}
src/init.cpp::AppInitMain()
)生成智能指针对象 g_connman,类型为 CConnman
。
g_connman = std::unique_ptr (new CConnman(GetRand(std::numeric_limits ::max()), GetRand(std::numeric_limits ::max())));
CConnman& connman = *g_connman;
生成智能指针对象 peerLogic,类型为 PeerLogicValidation
。
peerLogic.reset(new PeerLogicValidation(&connman, scheduler, gArgs.GetBoolArg("-enablebip61", DEFAULT_ENABLE_BIP61)));
PeerLogicValidation 继承了 CValidationInterface、NetEventsInterface 两个类。实现 CValidationInterface 这个类可以订阅验证过程中产生的事件。实现 NetEventsInterface 这个类可以处理消息网络消息。
注册各种验证处理器,即信号处理器,在发送信号时会调用这些处理器。
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 步应用程序初始化过程中生成。
根据命令行参数 -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);
}
构造并检查版本字符串长度是否大于 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));
}
如果指定了 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
方法,设置这些类型为受限的。
获取是否允许进行 DNS 查找,是否进行代理随机
fNameLookup = gArgs.GetBoolArg("-dns", DEFAULT_NAME_LOOKUP);
bool proxyRandomize = gArgs.GetBoolArg("-proxyrandomize", DEFAULT_PROXYRANDOMIZE);
两者默认都为真。
处理网络代理。
如果指定了 -proxy
,且不等于 0,即指定了代理地址,进行下面的处理:
Lookup
方法,根据指定的代理,通过 DNS查找,发现代理服务器的地址。具体代码如下:
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
}
处理洋葱网络。 如果指定了 onion
参数,则处理洋葱网络的相关设置。
如果指定了 -onion
,且不等于空字符串,即指定了洋葱代理地址,进行下面的处理:
Lookup
方法,根据指定的代理,通过 DNS查找,发现代理服务器的地址。具体代码如下:
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);
}
}
处理通过 -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));
}
如果设置了 maxuploadtarget
参数,则设置最大出站限制。
if (gArgs.IsArgSet("-maxuploadtarget")) {
nMaxOutboundLimit = gArgs.GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET)*1024*1024;
}
指令一:
./src/bitcoin-cli -regtest generatetoaddress 100 "mz58vZcVa4EwYxaA3yBjes7munMjBLdQCP"
(指令一里面的mz58vZcVa4EwYxaA3yBjes7munMjBLdQCP
为笔者的比特币钱包地址,请自己运行 listlabels 指令,以及getaddressesbylabel 获取自己的比特币钱包地址,然后替换它)
指令二:
./src/bitcoin-cli -regtest generate 100
为了更高效的交流探讨区块链开发过程中遇到的问题,我建立了一个技术交流群。
欢迎将以上问题的答案发在群中讨论,或者在帖子下面留言。
QQ群:253968045
QQ号:77078193 或者 705706498
微信:joepeak
我是区小白,区块链开发者,区块链技术爱好者,深入研究比特币,以太坊,EOS Dash,Rsk,Java, Nodejs,PHP,Python,C++
现为Ulord全球社区联盟(优得社区)核心开发者。我希望能聚集更多区块链开发者,一起学习共同进步。
敬请期待下一篇文章:接入比特币网络的关键步骤(下)
往期文章:
从源代码编译比特币
如何接入比特币网络以及步骤分析
原文转载自:
优得社区–从0开始学习比特币专题
从零开始学习比特币–如何接入比特币网络以及原理分析
从零开始学习区块链技术(一)–从源代码编译比特币