本文将继续开展源码研读之旅,本文将开展应用程序参数交互源码部分(AppInitParameterInteraction)的研读与分析。
本文主要涉及的源码文件包括:
src/bitcond.cpp、src/util.h、src/util.cpp、src/init.h、src/init.cpp、src/net.h、src/compat.h、src/validation.h
在研读AppInitParameterInteraction的过程中,发现代码中包含了Step 2: parameter interactions和Step 3: parameter-to-internal-flags两部分,既然有Setp 2和Setp 3,那肯定有Step 1,在哪呢?按常理应该从这个函数的前面去查找Step 1。发现Step 1就在我们前一篇文章AppInitBasicSetup中包含了。从此可以很清楚地看出AppInit系列的执行顺序,中本聪对代码注释做得还是很不错的,让读者可以清晰地研读源码。既然这样,我们就跟随Step的步伐前行吧!本文主要开展Step 2: parameter interactions,应用程序参数交互源码的解读。
一、区块修剪参数处理
区块修剪参数处理额代码很简单,3行代码搞定,如图所示。
其修剪参数为prune,从代码可以出该参数与txindex是有冲突的,这点虽然可以从上述代码看出一些端倪,但是我们并不知道二者为什么会存在不兼容性。我们来看看prune参数与txindex参数都有什么作用!
(1)prune参数
刚开始看到prune参数,还有这段代码上面的注释我还以为是直接把区块修剪掉,但仔细一想,这肯定不对的啊,把区块都删除了那还能叫区块链吗?区块链不完整了,那我们常说的账本还能全吗?当然不行,所以带着这个疑惑,我去网上搜索了一番!功夫不负有心人,经过努力让我找到了一篇好帖子,让我对prune参数有了很好的理解。
来源:https://bitcoin.stackexchange.com/questions/36100/pruning-the-branches-in-merkle-tree/
从该页面的提问部分我知道了修剪是针对默克尔树(Merkle Tree)来说的,所以修剪一词用得很贴切,可以直观地看出prune是对树上的节点进行修剪。其修剪的对象又是谁呢?从回答中我们可以看出其修剪对象有2种:
一种是所有输出都被花费的叶子节点(交易);
另一种是节点包含的所有子节点均已被修剪。
根据以上分析我们可以看出修剪的目的是为了节省存储空间。但回答者在后面补充到比特币核心未实现此修剪功能,比特币核心针对修剪功能只以两种模式进行:
不修剪(默认选项)
不保留区块和默克尔树,只跟踪未花费输出(UTXO)和公钥脚本,但该模式不允许你帮助新节点进行同步操作。
我们从代码GetArg("-prune", 0)可以看出prune的默认值确实为不修剪,除非是在参数中设置为1,否则不进行修剪操作。该帖子是在2015年2月18日发出来的,帖子的最后说到:
Bitcoin core doesn't implement this yet. Pruning is likely to be implemented in 0.11(the next version).
0.11版之后的版本开始实现Prune功能了。下面我们再来看看txindex参数。
(2)txindex参数
该参数的注释我们可以从Src/init.cpp的HelpMessage(HelpMessageMode mode)函数中可以找到,当然我们也可以从bitcoind的命令行中获得其帮助信息,该帮助信息其实就是这段源码实现的,所以咱们看源码就可以了。
strUsage += HelpMessageOpt("-txindex",strprintf(_("Maintain a full transaction index, used by thegetrawtransaction rpc call (default: %u)"), DEFAULT_TXINDEX));
从注释我们可以看出txindex参数的作用是维护一个全交易索引,所以其与prune存在不兼容性问题就很明白了。如果两个都设置了,出现一个要剪、一个要全保留的,程序是会乱的,所以此时程序如果发现二者都设置了,将了会报错("Prune mode is incompatible with-txindex."),并退出程序。
二、文件描述符处理
Step 2的第二部分是对比特币核心中使用到的文件描述符进行处理,主要是处理用于连接的文件描述符数量,将其控制在一个合理的范围内。那么何为文件描述符呢?
在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。如果此时去打开一个新的文件,它的文件描述符会是3。POSIX标准要求每次打开文件时(含socket)必须使用当前进程中最小可用的文件描述符号码,因此,在网络通信过程中稍不注意就有可能造成串话。
在编写文件操作的或者网络通信的软件时,初学者一般可能会遇到“Too many open files”的问题。这主要是因为文件描述符是系统的一个重要资源,虽然说系统内存有多少就可以打开多少的文件描述符,但是在实际实现过程中内核是会做相应的处理的,一般最大打开文件数会是系统内存的10%(以KB来计算)(称之为系统级限制),查看系统级别的最大打开文件数可以使用sysctl -a| grep fs.file-max命令查看。与此同时,内核为了不让某一个进程消耗掉所有的文件资源,其也会对单个进程最大打开文件数做默认值处理(称之为用户级限制),默认值一般是1024,使用ulimit -n命令可以查看。
详细描述见:http://blog.csdn.net/cywosp/article/details/38965239
通过对代码的研读可以发现其主要是确保程序中有足够多的文件描述符,用于程序所需文件,套接字的操作。此处的代码中设置了几个重要的宏定义变量,它们分别是:
DEFAULT_MAX_PEER_CONNECTIONS:定义于src/net.h中,代表了最大可维护的节点连接数,默认为125个
static const unsigned intDEFAULT_MAX_PEER_CONNECTIONS = 125;
FD_SETSIZE:定义于src/compat.h,代表可包含的最大文件描述符的个数,默认为1024
#ifdef FD_SETSIZE
#undef FD_SETSIZE// prevent redefinition compiler warning
#endif
#define FD_SETSIZE1024 // max number of fds in fd_set
MIN_CORE_FILEDESCRIPTORS:定义于src/init.cpp中,代表了最小核心文件描述符个数,window下为0,linux下为150
#ifdef WIN32
// Win32 LevelDBdoesn't use filedescriptors, and the ones used for
// accessing blockfiles don't count towards the fd_set size limit
// anyway.
#defineMIN_CORE_FILEDESCRIPTORS 0
#else
#defineMIN_CORE_FILEDESCRIPTORS 150
#endif
MAX_ADDNODE_CONNECTIONS:定义于src/net.h中,代表了最大增加节点连接数,默认为8
/** Maximum numberof addnode outgoing nodes */
static const intMAX_ADDNODE_CONNECTIONS = 8;
至此,我们一起完成了AppInitParameterInteraction中的第一部分Step 2,区块修剪与文件描述符处理,代码虽然不多,但包含的内容其实是挺丰富的,所以单独用一篇文件详细对其进行了描述,对于我们理解后续的代码将会很有帮助的。
作者:区块链研习社比特币源码研读班 菜菜子
以下是广告:
我们区块链研习社已创建“区块链研习社币圈交流”小密圈”,在小密圈中,我们将带领大家一起学习区块链的原理与投资,还将提供区块链基本原理解答、交易所注册与交易操作、ICO交易与操作、投资分析、风险分析等内容。
目前入圈价格初始定价50元,50人调整一次价格,每次调整幅度为50元!
网友评论
但是第二条:不太理解:我觉得没有什么节点的孩子节点才对呀!Merkle树只有根节点才存的呀;
我查了一下,有人说是,一个区块中的所有交易输出都使用了,所以这个区块也丢弃的了;
想应的输出一条消息"输出都已经消耗,区块头可以获得并验证,继续吧:"
不知道是
{
};
这个 [[noreturn]] 是什么用法, 对cpp不熟, 感觉很诡异的用法
有一些函数是永远不会返回的,比如abort或者exit之类,调用它们就结束程序。