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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012183589/article/details/78037699

0x01 Step 3: parameter-to-internal-flags - continue

由于Step 3中的内容太多,所以上一章未能完成,这一章继续分析Step 3中剩下的内容。

连接超时时间

    nConnectTimeout = gArgs.GetArg("-timeout", DEFAULT_CONNECT_TIMEOUT);
    if (nConnectTimeout <= 0)
        nConnectTimeout = DEFAULT_CONNECT_TIMEOUT;

-timeout:表示在发起TCP连接时的等待时间,单位是毫秒,默认值为5000。

节点费用设置

   if (gArgs.IsArgSet("-minrelaytxfee")) {
        CAmount n = 0;
        if (!ParseMoney(gArgs.GetArg("-minrelaytxfee", ""), n)) {
            return InitError(AmountErrMsg("minrelaytxfee", gArgs.GetArg("-minrelaytxfee", "")));
        }
        // High fee check is done afterward in WalletParameterInteraction()
        ::minRelayTxFee = CFeeRate(n);
    } else if (incrementalRelayFee > ::minRelayTxFee) {
        // Allow only setting incrementalRelayFee to control both
        ::minRelayTxFee = incrementalRelayFee;
        LogPrintf("Increasing minrelaytxfee to %s to match incrementalrelayfee\n",::minRelayTxFee.ToString());
    }

    // Sanity check argument for min fee for including tx in block
    // TODO: Harmonize which arguments need sanity checking and where that happens
    if (gArgs.IsArgSet("-blockmintxfee"))
    {
        CAmount n = 0;
        if (!ParseMoney(gArgs.GetArg("-blockmintxfee", ""), n))
            return InitError(AmountErrMsg("blockmintxfee", gArgs.GetArg("-blockmintxfee", "")));
    }

    // Feerate used to define dust.  Shouldn't be changed lightly as old
    // implementations may inadvertently create non-standard transactions
    if (gArgs.IsArgSet("-dustrelayfee"))
    {
        CAmount n = 0;
        if (!ParseMoney(gArgs.GetArg("-dustrelayfee", ""), n) || 0 == n)
            return InitError(AmountErrMsg("dustrelayfee", gArgs.GetArg("-dustrelayfee", "")));
        dustRelayFee = CFeeRate(n);
    }

在上一章http://blog.csdn.net/pure_lady/article/details/77982837#t3部分我们介绍了几种不同的费用,以及节点在收到交易时的处理流程。现在这里就到了设置这些变量的值的时候,首先是minrelayfee,最小转发费用,从代码中可以发现如果incrementalRelayFee大于minRelayFee ,那么minRelayFee=incrementalRelayFee

-blockmintxfee:设置交易被打包进区块的最小费用率,单位为BTC/KB,默认值为0.00001。

接下来的blockmintxfee是针对矿工而言的,矿工在将交易打包进区块之前先判断交易费是否满足条件,避免出现入不敷出的情况,因为矿工在挖矿时也有一定的成本,而交易费也是收益的一部分来源,所以矿工也要尽量让自己利益最大化。最后的dustrelayfee在上一章也介绍过,这里只是读取命令行中设置的值并传给destRelayFee变量。

判断非标准交易

    fRequireStandard = !gArgs.GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard());
    if (chainparams.RequireStandard() && !fRequireStandard)
        return InitError(strprintf("acceptnonstdtxn is not currently supported for %s chain", chainparams.NetworkIDString()));
    nBytesPerSigOp = gArgs.GetArg("-bytespersigop", nBytesPerSigOp);

-acceptnonstdtxn:是否接受或者转发非标准交易,只适用于testnet和regtest,默认值为1.

-bytespersigop:设置交易中每个sigop的大小,单位为字节,默认值为20.

接下来就是判断chainparams中的参数和-acceptnonstdtxn参数的值是否相互冲突。-bytespersigop是用来计算交易大小的参数,配合nSigOpsCost,表示交易中操作符的数量,两者的乘积就是交易的大小,像下面的函数中实现的。

int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost)
{
    return (std::max(nWeight, nSigOpCost * nBytesPerSigOp) + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR;
}

初始化钱包参数&设定交易中数据大小

#ifdef ENABLE_WALLET
    if (!WalletParameterInteraction())
        return false;
#endif

    fIsBareMultisigStd = gArgs.GetBoolArg("-permitbaremultisig", DEFAULT_PERMIT_BAREMULTISIG);
    fAcceptDatacarrier = gArgs.GetBoolArg("-datacarrier", DEFAULT_ACCEPT_DATACARRIER);
    nMaxDatacarrierBytes = gArgs.GetArg("-datacarriersize", nMaxDatacarrierBytes);

接下来这段代码首先判断是否启用了钱包,如果启用了,那么就进行钱包参数的初始化,点开WalletParameterInteraction()函数可以发现,该函数还是比较简单,就是把命令行中相应的参数赋值给CWallet中的变量,钱包部分暂且跳过,分析完主要功能部分之后可以再来单独分析钱包的实现。接下来分别设定三个参数,

permitbaremultisig:是否允许转发non-P2SH多签名,默认值为1。

datacarrier:表示是否允许在交易中写入数据,默认为1.

datacarriersize:表示交易中写入数据的最大大小,默认值为83.

系统中总共定义了6种交易类型,类型包含在CTxOut中的scriptPubKey中,交易主要分为两种:标准和非标准,除了非标准的,其他5种都是标准的交易,6种类型分别如下,

交易类型 描述
TX_NONSTANDARD 非标准的交易
TX_PUBKEY 公钥
TX_PUBKEYHASH 公钥哈希
TX_SCRIPTHASH 脚本哈希
TX_MULTISIG 多重签名
TX_NULL_DATA 空数据

datacarrier对应的类型就是最后一种TX_NULL_DATA,并由datacarriersize指定写入数据的最大长度,具体实现函数位于src/policy/policy.cpp中:

bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType, const bool witnessEnabled)
{
    std::vector<std::vector<unsigned char> > vSolutions;
    if (!Solver(scriptPubKey, whichType, vSolutions))  // Solver函数解析scriptPubKey中的信息
        return false;

    if (whichType == TX_MULTISIG)
    {
        unsigned char m = vSolutions.front()[0];
        unsigned char n = vSolutions.back()[0];
        // Support up to x-of-3 multisig txns as standard
        if (n < 1 || n > 3)
            return false;
        if (m < 1 || m > n)
            return false;
    } else if (whichType == TX_NULL_DATA &&
               (!fAcceptDatacarrier || scriptPubKey.size() > nMaxDatacarrierBytes))
          return false;  // 这里判断

    else if (!witnessEnabled && (whichType == TX_WITNESS_V0_KEYHASH || whichType == TX_WITNESS_V0_SCRIPTHASH))
        return false;

    return whichType != TX_NONSTANDARD;
}

Initial Block Download

    // Option to startup with mocktime set (used for regression testing):
    SetMockTime(gArgs.GetArg("-mocktime", 0)); // SetMockTime(0) is a no-op

    if (gArgs.GetBoolArg("-peerbloomfilters", DEFAULT_PEERBLOOMFILTERS))
        nLocalServices = ServiceFlags(nLocalServices | NODE_BLOOM);

    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.");

    nMaxTipAge = gArgs.GetArg("-maxtipage", DEFAULT_MAX_TIP_AGE);

上面这段代码主要涉及下面几个参数:

-mocktime=<n>:设定系统模拟时间,只适用于regression test,模拟时间表示将时间设置为创世后n秒,即时间从0年0月0日0时0分n秒开始。

-peerbloomfilters:是否支持使用bloom filter来过滤区块和交易,默认为1.

-rpcserialversion:设置原始交易或者区块在non-verbose模式下的返回值,取值只有两种,0表示non-segwit,1表示segwit,默认值为1.

-maxtipage:执行IBD(Initial block download)的最大时间间隔,单位为秒,默认值为86400,也就是24小时。

首先模拟时间比较容易理解,就是将当前时间设为0+n秒;接着peerbloomfilters参数决定是否开启bloom filter服务,该服务的主要功能是按照一定条件过滤某些特定的交易给自己或者其他节点;然后rpcserialversion设定序列化版本,具体在何处使用到还的看接下来的分析;最后maxtipage表示如果当前时间和本地区块链最后一个区块生成的时间差大于maxtipage那么将执行IBD函数,IBD函数表示要一次性下载大量的区块,具体介绍请参考https://bitcoin.org/en/developer-guide#initial-block-download,默认值为24小时,也就是说如果节点一天没有更新本地的区块链信息,那么就会执行IBD来从网络同步区块。

mempoolreplacement

    fEnableReplacement = gArgs.GetBoolArg("-mempoolreplacement", DEFAULT_ENABLE_REPLACEMENT);
    if ((!fEnableReplacement) && gArgs.IsArgSet("-mempoolreplacement")) {
        // Minimal effort at forwards compatibility
        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());
    }

-mempoolreplacement:启用内存池中的交易替换。

所谓交易替换就是指全节点的mempool中如果有多个交易花费了相同的inputs,那么他们之间允许替换。不过代码的if语句没有看懂,从第一句GetBoolArg()来看,mempoolreplacement应该是数值类型,但是后面又要在数值型中查找字符串,那结果肯定是false,所以还是不明白为什么这么写。

Regtest测试新deployments

    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]));
            }
        }
    }

-vbparams=deploytment:start:end:设置新的机制启用时间和终止时间,只用于regtest。

这个参数是用于测试新的功能,所以只用于regtest,首先检测chainparams中的fMineBlocksOnDemand参数是否为true,这个参数的含义是让miner在挖到新的block后停止挖矿,直到接到新的命令,而fMineBlocksOnDemand参数在maintestnet中都为false,只有在regtest中才为truechainparams.MineBlocksOnDemand()函数就是直接返回fMineBlocksOnDemand变量的值。在命令行中可以指定多个-vbparams从而同时启用多个新的机制,代码中接下来的for循环就是枚举每一个机制进行处理,输入的形式是deployment:start:end,然后分别解析三个参数的值,其中第一个是string类型,后面两个是int64_t类型,解析完之后在系统的deployments表中查找对应名字的机制,系统的deployments表在src/versionbits.cpp中,如下,

const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_BITS_DEPLOYMENTS] = {
    {
        /*.name =*/ "testdummy",
        /*.gbt_force =*/ true,
    },
    {
        /*.name =*/ "csv",
        /*.gbt_force =*/ true,
    },
    {
        /*.name =*/ "segwit",
        /*.gbt_force =*/ true,
    }
};

可见目前只包含三个机制,每个机制又包含以下几个参数,该文件位于src/consensus/params.h中,

/**
 * Struct for each individual consensus rule change using BIP9.
 */
struct BIP9Deployment {
    /** Bit position to select the particular bit in nVersion. */
    int bit;
    /** Start MedianTime for version bits miner confirmation. Can be a date in the past */
    int64_t nStartTime;
    /** Timeout/expiry MedianTime for the deployment attempt. */
    int64_t nTimeout;
};

找到对应的名字之后就通过UpdateVersionBitsParameters()更新其中的nstartTimenTimeout变量值。

到此,整个AppInitParameterInteraction()函数就分析完了,虽然很长,但基本上也都是一些参数的设置,所以还是很容易看懂,其中主要的还是需要查阅大量的资料,有些参数网上很少有相关的介绍,需要自己去在整个代码中搜索相关信息,然后理解它的含义。

猜你喜欢

转载自blog.csdn.net/u012183589/article/details/78037699