(九)继续看bitcoind.cpp中的132-136行
对它的注释为:
InitError将被调用,并有详细的错误,最终将在控制台结束
这个是在AppInitBasicSetup()
函数初始化失败后返回的错误,并进行的操作。所以这个部分最重要的是AppInitBasicSetup()
函数,而且这个函数是AppInit()
函数的核心初始化之一。下面对这个函数进行解析:
这个函数的声明在init.h的33行:
初始化比特币的核心:基本的环境设置。
@note:这可以在daemonization之前完成。如果此函数失败,请不要调用Shutdown()函数。
@pre:对参数进行解析,并读取配置文件。
它的实现在init.cpp中第855行,它们也可以看到对这部分内容也有总的注释内容:
// ******************** Step 1: setup
第1步:安装。
其中它称为第1步,说明这只是初始化的开始一步,还有其他一系列的步骤,这个开始的步骤在这个函数中实现,它主要作用是:安装网络环境,挂接事件处理器等。
我们对这个函数由代码顺序分为六部分内容:
- 警告消息处理并解决异常错误捕获问题;
- 数据执行保护(DEP)功能处理
- 初始化网络连接
- 信号处理设置
- 内存分配失败处理
下面对这六个部分内容较详细解析:
1. 警告消息处理并解决异常错误捕获问题
这部分的代码在函数的858-864行:
#ifdef _MSC_VER
// 关闭微软堆转储的噪音
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_WARN, CreateFileA("NUL", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, 0));
//在abort上禁用令人困惑的“helpful”文本消息,ctrl-c
_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
#endif
可以发现,这是一段条件编译语句,由#ifdef
的条件判断标识符_MSC_VER
可以知道这段代码是针对微软的VS开发环境而设置的,而在其他的编译环境下这段代码是不会被执行编译的。这段代码主要调用了三个与VS开发环境相关的需要额外处理的函数_CrtSetReportMode
_CrtSetReportFile
和_set_abort_behavior
。
①_CrtSetReportMode
:设置开发编译环境报告类型为警告,报告的输出方式为文件输出。
②_CrtSetReportFile
:创建一个空的文件,把警告消息输出到这个文件中。即关闭警告消息。
③_set_abort_behavior
:处理在VS环境下的只会强制把异常抛给默认的调试器的问题,用该函数把异常抛给异常捕获函数。
详细的解释参考:http://blog.csdn.net/yuzhiyuxia/article/details/16889155
2.数据执行保护(DEP)功能处理
该部分的代码在函数的865-877行:
#ifdef WIN32
// 打开DEP
// 最小支持OS版本:WinXP SP3,WinVista >= SP1,Win Server 2008
// 失败是不重要的,不需要进一步的关注!
#ifndef PROCESS_DEP_ENABLE
// GCCs winbase.h将该功能限制在_WIN32_WINNT >= 0x0601 (Windows 7)才能使用,所以在代码中强制定义了宏定义。
#define PROCESS_DEP_ENABLE 0x00000001
#endif
typedef BOOL (WINAPI *PSETPROCDEPPOL)(DWORD);
PSETPROCDEPPOL setProcDEPPol = (PSETPROCDEPPOL)GetProcAddress(GetModuleHandleA("Kernel32.dll"), "SetProcessDEPPolicy");
if (setProcDEPPol != nullptr) setProcDEPPol(PROCESS_DEP_ENABLE);
#endif
数据执行保护(DEP)的目的是为了防止病毒或其他安全威胁造成损害,Windows XP SP2、WinXP SP3, WinVista >= SP1, Win Server 2008使用了数据执行保护(DEP)功能,而GCCs winbase.h将该功能限制在_WIN32_WINNT >= 0x0601 (Windows 7)才能使用,所以在代码中强制定义了宏定义。
通过函数指针获取Kernel32.dll中的SetProcessDEPPolicy函数对象,实现DEP功能的开启。
3.初始化网络连接
该部分的代码在函数的879-880行:
if (!SetupNetworking())
return InitError("Initializing networking failed");
这部分调用了一个函数SetupNetworking()
这个函数在util.cpp中的865-875行代码实现:
bool SetupNetworking()
{
#ifdef WIN32
// 初始化Windows Sockets
WSADATA wsadata;
int ret = WSAStartup(MAKEWORD(2,2), &wsadata);
if (ret != NO_ERROR || LOBYTE(wsadata.wVersion ) != 2 || HIBYTE(wsadata.wVersion) != 2)
return false;
#endif
return true;
}
由注释可以知道这个主要是实现Windows Sockets的初始化,下面是对Windows Sockets的简单解释:
Windows Sockets(Winsock )是Windows下得到广泛应用的、开放的、支持多种协议的网络编程接口。从1991年的1.0版到1995年的2.0.8版,经过不断完善并在Intel、Microsoft、Sun、SGI、Informix、Novell等公司的全力支持下,已成为Windows网络编程的事实上的标准。
其中初始化Windows Sockets服务的工作主要是由WSAStartup()
函数完成,并且也只有先经过它对Windows Sockets服务初始化后Sockets连接涉及的API才能被调用,否则是无法执行网络连接操作的。其中原因引用如下:
为了在应用程序当中调用任何一个Winsock API函数,首先第一件事情就是必须通过WSAStartup函数完成对Winsock服务的初始化,因此需要调用WSAStartup函数。使用Socket的程序在使用Socket之前必须调用WSAStartup函数。该函数的第一个参数指明程序请求使用的Socket版本,其中高位字节指明副版本、低位字节指明主版本;操作系统利用第二个参数返回请求的Socket的版本信息。当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。
4.信号处理设置
该部分的代码在函数的882-896:
#ifndef WIN32
//文件创建权限
if (!gArgs.GetBoolArg("-sysperms", false)) {
umask(077);
}
//彻底关闭信号SIGTERM
registerSignalHandler(SIGTERM, HandleSIGTERM);//终止信号处理
registerSignalHandler(SIGINT, HandleSIGTERM);//中断信号处理
//挂起信号SIGHUP并重新打开debug.log文件
registerSignalHandler(SIGHUP, HandleSIGHUP);
//忽略信号SIGPIPE,否则如果客户端意外关闭,它将使守护进程关闭
signal(SIGPIPE, SIG_IGN);
#endif
由条件编译符号#ifndef WIN32
可以知道,这段代码主要是针对非Windows系统的(因为我比特币客户端一般在linux系统中安装,所以一般会用到这个部分)。其中由代码注释和分析可以得到下面的四个部分:
(1)文件创建权限
这部分先是判断是否设置了-sysperms
参数。如果设置了该参数,则返回设置的状态值;如果没有(false),则执行命令:umask(077);
。umask()
函数用于设置文件与文件夹使用权限,此处077代表---rwxrwx,表示owner没有任何权限,group和other有完全的操作权限。
(2)进程终止信号处理
注释对它的解释为:
彻底关闭SIGTERM信号
这个部分涉及到两个函数 registerSignalHandler()
和HandleSIGTERM
。
①registerSignalHandler()
函数在init.cpp的300-307行定义:
static void registerSignalHandler(int signal, void(*handler)(int))
{
struct sigaction sa;//信号处理对象
sa.sa_handler = handler;//进程终止信号处理句柄
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(signal, &sa, nullptr);//中断信号处理
}
这个函数对信号对象的句柄、标志和掩码赋值,并将该信号对象传递给中断信号处理函数。
②HandleSIGTERM
函数在init.cpp的289-292行定义:
static void HandleSIGTERM(int)
{
fRequestShutdown = true;
}
这个函数就是简单的把全局变量fRequestShutdown
设置成true,所有正在运行的线程将根据一定的规则停止运行。
(3)挂起信号处理
函数的892行是挂起信号处理,同样用了registerSignalHandler()
函数,参数变成了SIGHUP进程和HandleSIGHUP
函数,其中HandleSIGHUP
函数定义在init.cpp的294-297行:
static void HandleSIGHUP(int)
{
fReopenDebugLog = true;
}
就是把全局变量fReopenDebugLog
设置成true。而这个变量设置成true后就会使util.cpp中的LogPrintStr()
将重新打开调试日志打印文件(355-360行)。
(4)信号错误处理
函数的895行实现该功能,对它的注释为:
//忽略信号SIGPIPE,否则如果客户端意外关闭,它将使守护进程关闭
对SIGPIPE信号在socket中会出现的问题详细解释可以参考:
这行代码其实就是为了解决SIGPIPE信号会出现的客户端异常关闭时会将守护进程连带着也给关闭,影响守护进程的正常运行的问题,其实就是用SIG_IGN
忽略SIGPIPE信号。
5.内存分配失败处理
该部分的代码在函数的第898行这一行:
std::set_new_handler(new_handler_terminate);
这一行代码其实调用了两个函数:set_new_handler()
和new_handler_terminate
。其中new_handler_terminate
函数主要是为了防止影响区块链被破坏,通过执行terminate命令,直接终止程序的方式解决内存分配失败导致的错误,并且进行日志打印;而set_new_handler()
函数是C++中常用的内存异常处理函数,对它的解释:
【函数说明】
- set_new_handler函数的作用是设置new_p指向的函数为new操作或new[]操作失败时调用的处理函数。
- 设置的处理函数可以尝试使更多空间变为可分配状态,这样新一次的new操作就可能成功。当且仅当该函数成功获得更多可用空间它才会返回;否则它将抛出bad_alloc异常(或者继承该异常的子类)或者终止程序(例如调用abort或exit)。
- 如果设置的处理函数返回了(例如,该函数成功获得了更多的可用空间),它可能将被反复调用,直到内存分配成功,或者它不再返回,或者被其它函数所替代。
- 在尚未用set_new_handler设置处理函数,或者设置的处理函数为空时,将调用默认的处理函数,该函数在内存分配失败时抛出bad_alloc异常。
【参数说明】
new_p:该函数指针所指的函数应为空参数列表且返回值类型为void
该函数可以尝试获得更多的可用空间,或者抛出异常,或者终止程序。
如果是一个空指针,处理函数将被重置为默认值(将会执行抛出bad_alloc异常)。
【返回值】
返回先前被设置的处理函数指针;如果尚未被设置或者已被重置,将返回空指针。
返回的函数指针是无参的void返回值类型的函数指针。
更详细的解释参考:http://blog.csdn.net/wzxq123/article/details/51502356
********************************************
第一步总结:
AppInitBasicSetup()
函数完成第一步的工作,总结一下这个函数就是:当用微软的VS编译时对它的警告消息进行处理,并把异常正常抛给异常捕获函数;在Windows系统中开启DEP功能保护数据安全;初始化网络的连接,启动Winsock服务,使之后的Sockets连接涉及的API能被正常调用;在非Windows系统下判断并设置文件与文件夹使用权限,关闭SIGTERM信号,挂起SIGHUP信号并重新打开调试日志打印我文件,忽略SIGPIPE信号防止引起错误;当内存失败时直接终止程序防止区块链被破坏,并进行日志打印;最终函数返回true;结束。
********************************************
(十)继续看bitcoind.cpp中的137-141行
这一部分的判断语句主要涉及到一个函数:
AppInitParameterInteraction()
这个函数的声明在init.h的39行:初始化:参数的交互。
@note:这可以在daemonization之前完成。如果此函数失败,请不要调用Shutdown()
。
@pre:参数应该被解析,并且应该读取配置文件,而且AppInitBasicSetup()
函数应该已经被调用。
由注释可以知道这个函数还必须在AppInitBasicSetup()
函数已经被调用了之后才能使用。这个函数的实现在init.cpp文件中的903-1165行,这是一段庞大的代码。我们也能找到代码中两个关键的注释:
// ********************Step 2: parameter interactions
第二步:参数的相互作用。
// ********************Step 3: parameter-to-internal-flags
第三步:参数转换为内部变量。
和前面一个函数AppInitBasicSetup()
中的注释为:“ 第一步:安装。”相似,这个是紧跟着的第二步和第三步。通过参看后面的代码,可以知道这样明确的注释解释的一共有12步,我们将在下面的其它函数中依次见到。
由注释知道AppInitParameterInteraction()
函数主要完成的是第二步和第三步的功能,我们将分别从它完成的这两个步骤对源码展开解析。
1.第二步:参数的相互作用
首先在这个注释后面紧跟着出现的一个注释:
// also see: InitParameterInteraction()
//也可以参考:InitParameterInteraction()
其中InitParameterInteraction()
函数在上一篇学习笔记中已经详细解析过了,我们已经知道这个函数是进行参数交互,并把交互记录写入日志文件debug.log中。而这个步骤部分也是一些参数的交互,只是主要把交互信息抛给InitError()
函数,而且其中有的很多函数,如gArgs.GetArg()
和gArgs.GetBoolArg
等和宏定义变量等和InitParameterInteraction()
函数中相同或相似,所以对这些将不详细解析,主要是对它的实现逻辑进行分析。
(1)prune和txindex参数不能同时设置
这个功能的实现在代码的911-914行:
// 如果使用区块修剪,那么就不允许txindex
if (gArgs.GetArg("-prune", 0)) {
if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX))
return InitError(_("Prune mode is incompatible with -txindex."));
}
①prune参数:这个修剪的对象是Merkle Tree,目的是为了节省存储空间。在比特币中默认是不修剪的,除非对它专门进行了开启设置。
②txindex参数:在命令行的帮助文件可以了解到这个参数作用是维护一个全交易索引,是要全保留交易信息的。
更详细的解释可以参考://www.greatytc.com/p/d5c9dc0cc815
所以这两个参数存在不兼容的问题,要让它们不能同时设置,如果同时设置了会报错,并退出程序。
(2)当监听外部连接未设置时bind和whitebind参数不会设置
这个功能在代码的917-920行:
// 当`-listen`参数为0时`-bind`或`-whitebind`不能被设置
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");
}
这段代码主要是判断当参数-listen
设置为0时,参数-bind
或-whitebind
是否有非0设置,如果有,则会报错,并退出程序。
(3)确保有足够可用的文件描述符
这个功能在代码的923-925行:
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, (int)(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));
文件描述符的概念详细解释可以参考:http://blog.csdn.net/cywosp/article/details/38965239
对它的几个重要宏定义变量进行简单说明:
①DEFAULT_MAX_PEER_CONNECTIONS
:定义位于net.h的第75行,代表了最大可维护的节点连接数,默认值为125。
②FD_SETSIZE
:定义位于compat.h的27行,代表可包含的最大文件描述符的个数,默认为1024。
③MIN_CORE_FILEDESCRIPTORS
:定义于init.cpp的86行或88行,代表了最小核心文件描述符个数,window下默认为0,linux下为默认为150。
④MAX_ADDNODE_CONNECTIONS
:定义位于net.h中的第61行,代表了最大增加节点连接数,默认为8。
********************************************
第二步总结:
这一步主要是进一步的参数交互设置:区块裁剪prune和txindex的冲突检查、listen参数关闭时bind参数或whitebind参数检测、文件描述符的限制检查。
总之是对参数设置检查,避免运行时出现故障。
********************************************
2.第三步:参数转换为内部变量
我们可以发现这个部分有着大量篇幅的代码,而代码主要是对许多的参数进行设置。下面对于这部分,我主要的学习方式是:按照参数在代码中出现的顺序,首先了解该参数的含义,然后对在这个部分参数的设置内容进行概括的解析。
(1)-debug
:标志参数。帮助文件中的解释为:输出调试信息。此处设置成:如果-debug=0
或者设置了-nodebug
参数,则关闭调试信息;如果-debug=1
则输出调试信息。
(2)-debugexclude
:帮助文件中的解释为:排除类别的调试信息。可以与-debug=1
一起使用,以输出除一个或多个指定类别之外的所有类别的调试日志。此处设置成:如果该参数设置了,就在调试日志中删除设置的日志类型。
(3)-debugnet
:标志参数。比特币程序目前已经不支持这个参数,需要用-debug=net
替代。此处设置成:如果检测该参数存在,发出警告信息。
(4)-socks
:标志参数。比特币程序目前已经不支持这个参数,socket通讯目前只支持SOCKS5代理协议。此处设置成:如果检测该参数存在,抛给错误信息。
(5)-tor
:标志参数。tor的英文全称为The Onion Router,即第二代洋葱路由(onion routing),用于匿名通信。比特币程序目前已经不支持这个参数,要使用-onion
参数代替。此处设置成:如果检测该参数存在,抛给错误信息。
(6)-benchmark
:标志参数。现在比特币中-benchmark
已被忽略,使用debug=bench
代替。此处设置成:如果检测该参数存在,发出警告信息。
(7)-whitelistalwaysrelay
:标志参数。现在比特币中-whitelistalwaysrelay
已被忽略,使用-whitelistrelay
、-whitelistforcerelay
两个参数之一或共同使用来代替。-whitelistrelay
参数的意义是节点间的通信优先在白名单节点之间实现。此处设置成:如果检测该参数存在,发出警告信息。
(8)-blockminsize
:标志参数。-blockminsize
参数也已被弃用。此处设置成:如果检测该参数存在,发出警告信息。
(9)-checkmempool
:检测交易池。帮助文件中的解释为:每n次事件检测一次。此处设置成:根据网络的不同设置不同值——私有网络默认开启,主网和测试网默认关闭。(检测程序是存在资源消耗的,会影响程序的运行效率。)
(10)-checkblockindex
:区块索引检测。帮助文件中的解释为:对mapBlockIndex, setBlockIndexCandidates, chainActive 和mapBlocksUnlinked进行完整的一致性检查。和还设置-checkmempool
同样设置。类似-checkmempool
的设置:只有在私有网模式下才会进行区块索引的检测,其他两个网默认是不检测的。(例如如果根据是私有网络设置成了true,会修改validation.h中的全局变量fCheckBlockIndex
,validation.cpp中的CheckBlockIndex()
函数会使用该变量,
将实现了区块索引信息的验证。)
(11)-checkpoints
:检测点参数提示参数。帮助文件中的解释为:禁用对已知链历史昂贵的验证。这个主要是移除检查点的意思。此处设置成:把该参数的结果(默认是true)返回给全局变量fCheckpointsEnabled
。
(12)-assumevalid
:哈希假定有效参数。帮助文件中的解释为:如果这个块在链中,假设它和它的父块是有效的,并且可能跳过他们的脚本验证。此处设置成:①获得链上共识参数:通过chainparams.GetConsensus()
函数获得链上共识参数。②默认假定有效对象:默认假定有效对象主要是需要存储二进制值,二进制数为区块的哈希值。③获取哈希值:通过GetHex()
函数获取哈希值的十六进值。
(13)-minimumchainwork
:最小链工作。帮助文件中的解释为:假设在十六进制的有效链上存在最小工作。此处设置成:只对最小链工作的数据部分进行检查(如对参数值0X002101540只检查数据头部的0X00如果符合要求,则无错误,反之报错),能够减少检查点的需要。
(14)-maxmempool
:交易池大小限定参数。帮助文件中的解释为:将交易内存池保持在n兆字节以下。此处设置成:交易池最大容量是该参数设置的值*1000000(该参数默认值是300)。
(15)-limitdescendantsize
:交易池大小限定参数。帮助文件中的解释为:如果任何父节点在交易池中有超过<n>KB的子节点,则不接受交易。此处设置成:交易池最小容量是该参数设置的值*1000*40(该参数默认值是101)。
【这两个交易池大小限定参数是判断交易池的最大容量要大于0且小于交易池最小容量,否则报错。】
(16)-incrementalrelayfee
:交易费增长量。帮助文件中的解释为:设置最低收费标准,增加formempool限制或bip125替换成本。此处对它设置注释大概可以理解为:incrementalRelayFee
的功能是设置最小费率增长量,通过设置交易费增长量与交易最小费的目的考虑交易池的容量限制,排除一些交易费过低的交易,即将其交易退回。该值可理解为最小交易费用设置的最低值,因为交易池中交易费的增量是以incrementalRelayFee
为基础的,所以每笔交易费必须大于等于incrementalRelayFee
,也就是说最小交易费也必须大于等于该值。此处设置成:先通过IsArgSet()
函数判断是否设置了-incrementalrelayfee
参数,如果设置了,则通过ParseMoney()
函数将输入的以字符串表达的交易增长费转换为数字型的增长交易费(ParseMoney()
与其反向求解的FormatMoney()
函数均定义在utilmoneystr.h,这两个函数FormatMoney()
是将数字转换为字符串,ParseMoney()
是将字符串转换为数字。),如果传入的金额无效则退出程序,反之为incrementalRelayFee赋值,为其费率值赋予传入的数值。通过CFeeRate()
(amount.h中定义与注释)我们可以知道传入的n的单位为每千字节需要n聪的金额。
(17)-par
:验证脚本线程数。帮助文件中的解释为:设置脚本验证线程的数量。此处设置成:由注释知(-par=0时意味着程序自动根据机器情况自动检测线程数,而nScriptCheckThreads==0意味着将不按并发方式实现脚本验证,即脚本验证线程数为0),该段程序就是把-par
的设置参数(默认是0,意味默认选择自动检测验证线程数)赋值给nScriptCheckThreads,然后判断这个变量的值,当nScriptCheckThreads输入值为0或负数时,程序将通过GetNumCores()函数获取程序运行机器能提供的线程数,然后nScriptCheckThreads加上获取的线程数获得脚本验证的线程数,新的值再次判断,最后的nScriptCheckThreads值是0或者16。
(18)-prune
:区块裁剪。这个前面的第二步中已经出现过了,那么在这第三步出现又是干什么的呢?我们通过注释(区块修剪;获取磁盘空间(MiB单位)为了分配给区块和撤销文件)可以知道,区块裁剪是针对预先设定的存储容量来进行的,即根据客户端所在计算机中的存储情况进行设定,如果超过了设定值,将进行区块裁剪,以防超过设定值,并且该设定值为MiB单位。此处设置流程:首先是获取-prune
参数值(默认为0)并赋值给nPruneArg变量;然后判断nPruneArg是否小于0,如果小于0,程序将出错,并退出(因为我们知道如果nPruneArg小于0,表示不会为区块提供存储空间了,程序将无法正常工作);如果nPruneArg大于0,则计算该值所对应的字节数,并把计算后的值赋值给nPruneTarget变量;然后判断-prune
是否为1,如果为1,打印日志,程序不会自动对区块进行裁剪,需要我们通过RPC命令pruneblockchain对相应区块进行裁剪,并且设置裁剪模式fPruneMode为true;如果-prune
不为1,首先判断设定的裁剪值是否小于程序默认设置的用于存储区块的最小硬盘存储空间MIN_DISK_SPACE_FOR_BLOCK_FILES(默认为550 * 1024 * 1024 = 550MiB,即为区块设定的最小存储空间为550MiB)如果小于,会报错。如果不小于会正常运行,并把裁剪模式fPruneMode设置为true。
///////////////////////////////////////////////////////
下面紧跟着会有一段RPC注册命令:
RegisterAllCoreRPCCommands(tableRPC);
#ifdef ENABLE_WALLET
RegisterWalletRPCCommands(tableRPC);
#endif
实现了区块链、P2P网络、挖矿、交易以及其他工具等模块的RPC命令的注册。(此处仅了解)
说明:在此处设置就是因为设置了该命令,接下来的参数就可以设置了,因为下面的参数和打开这些RPC有关。
///////////////////////////////////////////////////////
(19)-timeout
:节点超时参数。帮助文件中的解释为:指定连接超时的时间,以毫秒为单位。这个参数的意义在于:比特币网络中新加入的节点都会去寻找节点,加入比特币P2P网络中,与其他节点完成同步操作。但是在网络中寻找节点,并建立连接是有时间限制的,即会出现连接超时的问题。而这个超时时间就会用该参数设置,默认为5000毫秒,最小为1毫秒。此处设置成:(因为它的最小为值1)判断如果该值小于1,则把它设置成默认值。
(20)-minrelaytxfee
:最小交易费率。帮助文件中的解释为:比这个费用更低被认为是对传播交易、挖矿和创建交易的零费用。(该费率为每千字节所需的最小费率,该费率值的设置对于矿工来说很重要,需谨慎设置,切忌设置为为0,因为如果设置为0时,每个被挖出的区块中都将会被塞满1聪交易费的交易,这将会使得矿工入不敷出。所以最低交易费必须高于处理交易所需成本)此处设置成:如果该参数有值(默认为1000),得到该值,并用函数ParseMoney()
转换为数字,赋值给n;判断该值如果为0,会报错;在CWallet::ParameterInteraction()完成之后进行高费用检查;只允许incrementalRelayFee来控制上面的两个操作。
(21)-blockmintxfee
:区块中打包交易的最小费用值信息。帮助文件中的解释为:设置在块创建中包含的交易的最低收费率。即通过挖矿发现的区块打包交易的最低费率,默认为1000聪。此处设置成:由开始的注释(对包括tx在内的区块最小费用进行完整性检查。TODO:协调需要检查的参数和发生的地方)知道设置该参数的目的。那么判断该值是否为0,若为0则报错;不为0继续。
(22)-dustrelayfee
:灰尘交易。帮助文件中的解释为:这个费用率用来定义灰尘,其输出的价值将超过其在费用中所花费的费用。dustrelayfee为那些交易费用很低的交易,可以形象得理解为灰尘、忽略不计的费用。此处设置成:判断它的值(默认为1000聪),如果为0则报错;如果不为0则把值赋值给全局变量dustRelayFee。
(23)-acceptnonstdtxn
:非标准交易。含义是:比特币网络中是否需要非标准交易。是否接受标准交易主要看当前运行的是什么网络(主网、测试网、私有网),这3种网络对是否需要标准交易是有默认要求的。主网只接受标准交易,测试网与私有网可以接受非标准交易。该参数根据不同网络默认值让布尔变量fRequireStandard记录。
(24)-bytespersigop
:签名操作字节数。帮助文件中的解释为:用于传播和挖矿的交易数据的每个签名的等效字节数。此处设置成:把此参数的值(默认为20)赋值给全局变量nBytesPerSigOp。
///////////////////////////////////////////////////////
下面会出现一段条件编译命令:
#ifdef ENABLE_WALLET
if (!CWallet::ParameterInteraction())
return false;
#endif
由代码可以知道当判断条件满足时就直接返回false,下面的代码将不会被执行。通过学习知道这个判断的函数就是判断是否成功启动钱包功能。那么判断是否成功启动钱包功能的函数就是上面代码中的CWallet::ParameterInteraction()
,这个函数在wallet/wallet.h中的第1116行声明:
/* 钱包参数交互 */
static bool ParameterInteraction();
这个函数的实现在wallet/wallet.cpp中的4165-4283行100多行代码中。这段代码也是含有许多的参数,下面就对其中的一些参数简单功能描述:
1)-disablewallet
:禁用钱包功能(默认为false,即打开钱包功能)。在运行时设置了-disablewallet
参数,我们的钱包功能将被关闭,将不会加载钱包,同时禁用钱包的RPC调用,程序也将返回,并停止运行。
2)-blocksonly
:只以区块模式运行(比特币客户端以调试状态启动时才会使用。就是节点不接收临时的交易,只接受已确认的区块)。默认为false,即默认不会只以区块模式运行,因为如果在区块模式下运行,全网的交易将不会被打包,钱包的交易广播功能将失效。
3)-salvagewallet
:该参数的功能为试图在比特币客户端启动时从损坏的钱包中恢复私钥。该参数只有用-rescan
参数启动客户端的情况下才能生效。
4)-zapwallettxes
:参数用于删除钱包的所有交易记录,且只有用-rescan
参数启动客户端才能重新取回交易记录,且只有用-rescan
参数启动客户端才能重新取回交易记录。
5)-sysperms
:这个前面已经提到过了,在这里是该参数不能和钱包功能开启状态同时出现,会冲突,导致程序退出。
6)-prune
与-rescan
:使用-rescan
参数启动时,是不能用-prune
参数的,否则二者将会冲突,程序自动退出。
7)-minRelayTxFee
:(参考前面的(20))此处用法是:防止用户设置的最低手续费高于比特币程序中设置的最高手续费(0.01个比特币),导致手续费过高,影响比特币网络的正常运转。
8)-mintxfee
:最低手续费不应高于最高手续费,否则程序将给出警告提示。
9)-fallbackfee
:当交易池中没有足够数据支撑手续费的估算时,就使用-fallbackFee
作为较低最低收取费。
10)-paytxfee
与-maxtxfee
:分别是支付交易手续费与最高交易手续费。此处判断,若低于支付交易手续费则程序退出;若高于最高交易手续费,则警告超出了最高手续费。
11)-txconfirmtarget
:比特币的每一笔交易都需要经过n(默认为6)次区块确认才能算真正的交易成功了,且不能回退。
12)-spendzeroconfchange
:表示比特币客户端是否可以花费0确认的费用(默认true)。
13)-walletrbf
:它可以为钱包产生的所有新交易添加交易费,具体是指BIP125可选费用替代法(RBF)。这一功能可为先前未确认的交易添加手续费,以加大交易被确认的机会。默认是关闭的,如果想开启用在客户端用bitcoind -walletrbf
命令。
当钱包功能开启时下面的代码才会运行,那么就可以知道了,下面的参数就是和钱包功能有关的交互参数了。
///////////////////////////////////////////////////////
再次回到init.cpp中的1104行——
(25)-permitbaremultisig
:交易相关参数。该参数代表的含义是允许发送非P2SH脚本多重签名(baremultisig)。默认为true,即默认允许非P2SH多重签名的交易在全网传播。
(26)-datacarrier
:交易相关参数。该参数表示是否可以传播和挖矿是否包含交易意外的数据内容,其默认值为true,即是允许的。
(27)-datacarriersize
:交易相关参数。该参数表示包含数据的交易大小默认值,其默认值为83字节。该参数和上面的参数-datacarrier
一起作用,主要是先赋值给fAcceptDatacarrier与nMaxDatacarrierBytes,然后通过分析二者的值判断交易是否为标准交易,防止DoS攻击。
(28)-mocktime
:单元测试参数处理。此处的-mocktime
为用于测试网络起始时间,通过SetMockTime()
函数设置测试网络的起始时间。在非测试模式为无操作的设置,在测试模式下有值。
(29)-peerbloomfilters
:Bloom过滤参数处理。帮助文件中的解释为:支持针对区块和交易的bloom过滤。默认是true,即默认是支持bloom过滤器的。此处设置成:在支持该过滤器的前提下,程序中设置了当前运行节点的服务模式。
(30)-rpcserialversion
:帮助文件中的解释为:在非冗长模式、非隔离见证模式(0)或隔离见证(1)模式下原始交易或区块以十六进制序列化方式呈现。默认值为1,则在隔离见证模式下呈现。此处设置成:如果值小于0或大于1,则报错。
(31)-maxtipage
:该参数的用途是当我们运行的节点包含的区块信息落后于主网最长时间24小时后,我们的比特币客户端将进行Initial block download(IBD)操作,进行区块同步下载。并不是说只在刚启动时执行IBD操作,而是当我们的节点信息比全网最长链落后了24小时或者144个块时就会执行IBD操作。
(32)-mempoolreplacement
:该参数作用是可以在拥有全节点的客户端替换交易池中的交易,即针对同一输入,可以用花费了该输入的一部分或全部金额的交易替换交易池中的交易。默认为true,即交易池中的交易按照既定规则是可以被替换的。
(33)-vbparams
:帮助文件中的解释为:为指定的版本位部署使用给定的起始/结束时间(仅在私有网络测试时使用)。这个部分的设置注释为:允许重写版本位参数进行测试。即在私有测试网络中测试软分叉后软件是否正常运行。
********************************************
第三步总结:
该部分的篇幅较大,牵涉到许多的参数设置,主要是把这些从外部设置的参数转化为内部变量,即把各种配置文件和终端命令行中的命令参数转变成客户端内部要使用和这些参数相关的变量的值,使客户端按照用户设置的要求(或默认值)运行。
********************************************