比特币源码研读之十七
今天我将继续第十七篇源码研读,这篇文章我们将结束AppInitParameterInteraction函数的解析。该函数的解析从第十篇源码研读开始,到此已有8篇文章在分析其源码了,足以说明该函数信息量之大,大家有兴趣可以详细看看该函数的功能实现及其处理的参数。
本文将继续开展应用程序参数交互源码部分(AppInitParameterInteraction)的研读与分析。
本文主要涉及的源码文件包括:
src/bitcond.cpp、src/init.h、src/init.cpp、src/util.h、src/net、src/validation.h、src/validation.cpp、src/utiltime.h、src/utiltime.cpp、src/protocol.h、src/net.h、src/rpc/server.h、src/script/standard.h、src/script/standard.cpp、src/ policy /policy.h、src/ policy /policy.cpp
本文的分析按以下七部分进行:
一、交易相关参数
此处涉及的交易与挖矿相关的三个参数分别是permitbaremultisig、datacarrier以及datacarriersize。
(1)permitbaremultisig
permitbaremultisig参数的处理代码如下:
fIsBareMultisigStd =GetBoolArg("-permitbaremultisig", DEFAULT_PERMIT_BAREMULTISIG);
通过搜索和代码分析了解到-permitbaremultisig代表的含义是允许发送非P2SH脚本多重签名(baremultisig)。其默认参数DEFAULT_PERMIT_BAREMULTISIG定义在src/validation.h中,默认值为true,具体情况如下:
/** Default for -permitbaremultisig */
static const boolDEFAULT_PERMIT_BAREMULTISIG = true;
也就是默认允许非P2SH多重签名的交易在全网传播。
此处的fIsBareMultisigStd变量为全局变量,其在src/validation.h中声明,在src/validation.cpp中定义,其默认值为DEFAULT_PERMIT_BAREMULTISIG。
fIsBareMultisigStd参数在src/policy/policy.cpp中使用,使用的函数为IsStandardTx,在其中参与判断交易是否为标准交易,如果该参数为false,则该函数返回baremultisig为非标准交易,并且给出的原因是当前交易为“bare-multisig”。
if ((whichType == TX_MULTISIG) &&(!fIsBareMultisigStd))
{
reason = "bare-multisig";
return false;
}
(2)datacarrier与datacarriersize
这两个参数放可以放在一起分析,因为它们均为从0.10.0版本开始加入到比特币客户端的命令参数,其作用是允许交易OP_RETURN交易是否可以包含除交易信息之外的其他数据信息,datacarrier参数表示是否可以传播和挖矿是否包含交易意外的数据内容,其默认值为true,即是允许的。在datacarrier为true的情况下,我们再来看datacarriersize参数,其表示包含数据的交易大小默认值,其默认值为83字节。83字节的信息我们可以从源码的src/script/standard.h中找到:
static const boolDEFAULT_ACCEPT_DATACARRIER = true;
static const unsigned intMAX_OP_RETURN_RELAY = 83; //!< bytes (+1 for OP_RETURN, +2 for the pushdataopcodes)
extern bool fAcceptDatacarrier;
extern unsigned nMaxDatacarrierBytes;
我们可以看到fAcceptDatacarrier与nMaxDatacarrierBytes都是全局变量,都在该头文件中声明,并在standard.cpp中定义:
bool fAcceptDatacarrier =DEFAULT_ACCEPT_DATACARRIER;
unsigned nMaxDatacarrierBytes =MAX_OP_RETURN_RELAY;
我们可以再来看下fAcceptDatacarrier与nMaxDatacarrierBytes使用之处,在policy.cpp中的IsStandard函数中,程序通过分析二者的值判断交易是否为标准交易,防止DoS攻击:
二、单元测试参数处理
此处的mocktime为用于测试网络起始时间,通过SetMockTime设置测试网络起始时间。在分析该函数之前,我们先来了解下mock测试,在百度百科中其解释为:
mock测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。
其包含的内容有:
通过上面的注释我们可以很好地理解mockTime的作用与意义。我们再来看setmocktime函数,该函数在src/utiltime.h中定义:
src/utiltime.h
void SetMockTime(int64_t nMockTimeIn);
utiltime.cpp
static int64_t nMockTime = 0; //!< For unit testin
void SetMockTime(int64_t nMockTimeIn)
{
nMockTime =nMockTimeIn;
}
我们可以通过nMockTime赋值语句可以看到其主要是用于单元测试。通过其默认值为0,在0的情况下将为no-op,即无操作的设置,因为我们在平时运行时并不是测试状态,所以如果为测试模式,将需要设置nMockTime的值。
三、Bloom过滤参数处理
此处代码主要用于判断当前节点是否支持针对区块和交易的bloom过滤,我们可以通过src/init.cpp中的HelpMessage函数确认peerblommfilters参数的具体含义,其具体帮助信息如下:
strUsage +=HelpMessageOpt("-peerbloomfilters", strprintf(_("Supportfiltering of blocks and transaction with bloom filters (default: %u)"),DEFAULT_PEERBLOOMFILTERS));
我们可以看到其默认值为DEFAULT_PEERBLOOMFILTERS,该值在src/validation.h中定义:
src/validation.h
static const bool DEFAULT_PEERBLOOMFILTERS = true;
也就是说默认是支持bloom过滤器的。在支持该过滤器的前提下,程序中设置了当前运行节点的服务模式:
nLocalServices =ServiceFlags(nLocalServices | NODE_BLOOM);
此处的nLocalServices在src/init.cpp中定义,具体如下:
namespace { // Variables internal to initializationprocess only
ServiceFlags nRelevantServices = NODE_NETWORK;
int nMaxConnections;
int nUserMaxConnections;
int nFD;
ServiceFlags nLocalServices = NODE_NETWORK;
}
nLocalServices的数据类型为ServiceFlags,该类型在src/protocol.h中定义,具体如图所示:
我们可以看到ServiceFlags是枚举变量,在当前代码中我们对nLocalServices赋值为NODE_NETWORK与NODE_BLOOM,即具备全节点信息存储与bloom过滤器功能,需要说明的是二者对于所有客户端来说也是默认具备的。
四、rpcserialversion参数处理
通过HelpMessage函数确认rpcserialversion参数的具体含义,其具体帮助信息如下:
strUsage +=HelpMessageOpt("-rpcserialversion", strprintf(_("Sets theserialization of raw transaction or block hex returned in non-verbose mode,non-segwit(0) or segwit(1) (default: %d)"),DEFAULT_RPC_SERIALIZE_VERSION));
我们可以看到其默认值为DEFAULT_RPC_SERIALIZE_VERSION,该值在src/rpc/server.h中定义:
src/rpc/server.h
static const unsigned intDEFAULT_RPC_SERIALIZE_VERSION = 1;
也就是说默认情况,在非冗长模式、非隔离见证模式(0)或隔离见证(1)模式下原始交易或区块以十六进制序列化方式呈现。
通过其代码我们可以了解到rpcserialversion不能为负数,也不能大于1,而其类型又为unsigned int,所以其值只能为0或1。
if (GetArg("-rpcserialversion",DEFAULT_RPC_SERIALIZE_VERSION) < 0)
returnInitError("rpcserialversion must be non-negative.");
if (GetArg("-rpcserialversion",DEFAULT_RPC_SERIALIZE_VERSION) > 1)
returnInitError("unknown rpcserialversion requested.");
五、maxtipage参数处理
此处我们要分析的是maxtipage参数,其赋值代码如下:
nMaxTipAge = GetArg("-maxtipage",DEFAULT_MAX_TIP_AGE);
maxtipage参数的作用我们可以通过HelpMessage函数确认maxtipage参数的具体含义,其具体帮助信息如下:
strUsage +=HelpMessageOpt("-maxtipage=", strprintf("Maximum tipage in seconds to consider node in initial block download (default: %u)", DEFAULT_MAX_TIP_AGE));
其中,nMaxTipAge变量在src/validation.h中声明,在src/validation.cpp中定义,其默认值为DEFAULT_MAX_TIP_AGE,DEFAULT_MAX_TIP_AGE也定义于src/validation.h中。
src/validation.h
static const int64_t DEFAULT_MAX_TIP_AGE =24 * 60 * 60;
通过其帮助提示信息我们可以看到该参数的用途是当我们运行的节点包含的区块信息落后于主网最长几点24小时后,我们的比特币客户端将进行Initial block download(IBD)操作,进行区块同步下载。我们也可以看下比特币官网中对IBD的解释(https://bitcoin.org/en/developer-guide#connecting-to-peers):
从其解释我们可以看出,这里说的Initial并不是说只在刚启动时执行IBD操作,而是当我们的节点信息比全网最长链落后了24小时或者144个块时就会执行IBD操作。当然程序默认为24小时,我们可以根据具体情况修改该值。
六、mempoolreplacement参数处理
此处的mempoolreplacement参数的详细含义我们可以从bitcoinwiki上找到,其对应的解释为Transaction replacement,在该维基解释为:
Transaction replaceability occurs when a full node allows one or more of the transactions in its memory pool (mempool) to be replaced with a different transaction that spends some or all of the same inputs. Transaction replaceability was enabled in the first version of Bitcoin but was disabled in the 0.3.12 release with the comment, "Disable replacement feature for now".Since then, there have been various attempts to make transaction replaceability widely available again.
通过以上注释我们可以看到其作用是可以在拥有全节点的客户端替换交易池中的交易,即针对同一输入,可以用花费了该输入的一部分或全部金额的交易替换交易池中的交易。这功能在比特币的第一版就有了,后来在0.3.12中被禁止了,但后来的版本中又广泛使用起来。
综上所述,交易池中的交易是可以被替换的,但前提是替换的交易产生于同一输入。
此处交易替换参数处理代码为:
fEnableReplacement =GetBoolArg("-mempoolreplacement", DEFAULT_ENABLE_REPLACEMENT);
if ((!fEnableReplacement) &&IsArgSet("-mempoolreplacement")) {
// Minimal effort at forwards compatibility
std::string strReplacementModeList = GetArg("-mempoolreplacement","");// default is impossible
std::vector vstrReplacementModes;
boost::split(vstrReplacementModes, strReplacementModeList,boost::is_any_of(","));
fEnableReplacement = (std::find(vstrReplacementModes.begin(),vstrReplacementModes.end(), "fee") != vstrReplacementModes.end());
}
代码首先处理了交易池参数设置状态,我们可以看到其默认值为DEFAULT_ENABLE_REPLACEMENT,其定义于src/validation.h中,默认为true:
/** Default for -mempoolreplacement */
static const boolDEFAULT_ENABLE_REPLACEMENT = true;
fEnableReplacement为全局变量,声明于src/validation.h中,定义在src/validation.cpp中,具体定义如下:
bool fEnableReplacement =DEFAULT_ENABLE_REPLACEMENT;
从分析中我们可以看出fEnableReplacement默认为true,即交易池中的交易按照既定规则是可以被替换的。
在后面的if ((!fEnableReplacement) &&IsArgSet("-mempoolreplacement"))处理中,其条件是当fEnableReplacement设置我false,并且客户端是添加了mempoolreplacement参数的情况下,程序会获取交易池替换模式内容,判断其是否包含“fee”模式,如果包含,则将fEnableReplacement设置为true,即可执行交易替换操作。
七、bip9params参数处理
我们首选在HelpMessage函数中看bip9params参数的作用:
strUsage += HelpMessageOpt("-bip9params=deployment:start:end","Use given start/end times for specified BIP9 deployment(regtest-only)");
从上可以看出bip9params为比特币程序在私有网络测试时使用的参数,其作用为执行部署、执行部署开始和部署结束时间。而这个部署一般是指什么部署呢?我们来看下BIP9这个改进协议的作用:
新的软分叉升级规范BIP9
比特币的软/硬分叉升级一直采用块的version字段来完成。由于是一个分布式系统,必然需要采用灰度发布模式。
传统的升级过程
在实施BIP9前是这样升级的,当前块版本为version,那么新块版本是version + 1,当近1000个块中的版本超过95%都是新版本时,则触发启用新特性,同时不再接收旧版本号的块。由于中间存在1000个块的窗口期,大约一周,所以给出足够的时间给当前网络中的节点实施升级。
上图是来自BTC.COM统计的比特币历史上几个升级过程,最近的v3升级v4的过程大约花费了一个半月左右。前面几次的升级时间更长。
这种依次递增版本号的方法,有一个明显的弊端:每次仅能进行一个特性升级。当需要同时进行多个升级时,则无法完成。BIP9的诞生就是为了解决这个问题的,同时把向下兼容性升级过程制定了规范。
(以上摘自http://blog.biqu.io/2016/04/21/BIP9/)
我们再来看整个参数的处理代码,可以了解到其主要是针对bip9params参数的值进行私有网络测试部署,以测试软分叉后软件是否正常运行。
以上就是本篇研读记录的全部内容,也是AppInitParameterInteraction处理函数的最后一部分,后面我们将根据我们的研读流程图继续前行,也就是即将进入AppInitSanityChecks函数,进行完整性检测部分。
区块链研习社源码研读班 菜菜子