这个函数包括源码中的Step 2和Step 3两个,主要实现的功能是设置区块链运行时的一些参数。
// if using block pruning, then disallow txindex
if (gArgs.GetArg("-prune", 0)) {
if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX))
return InitError(_("Prune mode is incompatible with -txindex."));
}
-prune
参数表示启用区块修剪(block pruning),根据bitcoin release note中的描述,
Block pruning allows Bitcoin Core to delete the raw block and undo data once it’s been validated and used to build the databases. At that point, the raw data is used only to relay blocks to other nodes, to handle reorganizations, to look up old transactions (if -txindex is enabled or via the RPC/REST interfaces), or for rescanning the wallet. The block index continues to hold the metadata about all blocks in the blockchain.
区块修剪允许bitcoin core删除raw block和undo data,一旦这些数据已经被验证和更新过数据库。这时候的raw data只能用来转发区块到其他节点、处理区块重组、查看过去的交易(如果启用了-txindex交易索引或者通过RPC/REST接口调用)以及重新扫描钱包。区块索引依然维护所有区块的元数据。
我们知道在比特币运行的本地环境中,有四种类型的数据(在Linux环境下查看~/.bitcoin/
目录),
blk***.dat
rev***.dat
。Chain reorganization是指某一个节点发现存在一条比节点当前本地维护的链更长的链,那么该节点就需要进行Chain reorganization,所以这个操作只是针对某一个节点而言的。~/.bitcoin/blocks/index
下的.ldb
level db数据库文件。~/.bitcoin/chainstate/
中的.ldb
文件。而block pruing删除的就是raw block和undo data两种数据,通过-prune=N
参数N
来指定raw block + undo data数据的大小,单位为MB
。 N
的最小值为550
,代表288个区块的大小,按照每个block 10 Min的速率,代表2天的时间。
因为block pruning需要删除一些区块的信息,而-txindex
是对所有交易建立索引,所以这两者不兼容,如果同时设置了,那么则提示错误。
// -bind and -whitebind can't be set when not listening 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
和bind
之间的冲突问题,也就是如果设置了bind
的地址而没有设置listen
那么就会报错并退出程序。
// Make sure enough file descriptors are available
int nBind = std::max(nUserBind, size_t(1));
nUserMaxConnections = gArgs.GetArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS);
nMaxConnections = std::max(nUserMaxConnections, 0);
// Trim requested connection counts, to fit into system limitations
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
在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。如果此时去打开一个新的文件,它的文件描述符会是3。POSIX标准要求每次打开文件时(含socket)必须使用当前进程中*最小可用的文件描述符号码,因此,在网络通信过程中稍不注意就有可能造成串话*。
内核为了不让某一个进程消耗掉所有的文件资源,其也会对单个进程最大打开文件数做默认值处理(称之为用户级限制),默认值一般是1024,使用ulimit -n命令可以查看。在Web服务器中,通过更改系统默认值文件描述符的最大值来优化服务器是最常见的方式之一。
从上述文章中我们知道文件描述符就是一个打开文件的索引,首先代码计算了用户设置的bind的地址数量nBind
,然后或者命令行中的-maxconnections
,这个参数的默认值为125。
// src/net.h line 75
/** The maximum number of peer connections to maintain. */
static const unsigned int DEFAULT_MAX_PEER_CONNECTIONS = 125;
然后开始计算最大连接数,公式中涉及到几个变量,分别位于如下位置,
// src/compat.h line 27
#define FD_SETSIZE 1024 // max number of fds in fd_set
// src/init.cpp line 89
#define MIN_CORE_FILEDESCRIPTORS 150
// src/net.h line 61
/** Maximum number of addnode outgoing nodes */
static const int MAX_ADDNODE_CONNECTIONS = 8;
虽然还没有明白这个公式的原理,但是功能大概明白了,首先判断文件描述符的数量是否够用,如果不够用那么直接报错并退出程序;然后判断命令行设置的-maxconnections
是否超过了系统支持的最大连接数,如果超过了,那么就提示强制设置为系统的最大连接数。
if (gArgs.IsArgSet("-debug")) {
// Special-case: if -debug=0/-nodebug is set, turn off debugging messages
const std::vector<std::string> categories = gArgs.GetArgs("-debug");
if (find(categories.begin(), categories.end(), std::string("0")) == categories.end()) {
for (const auto& cat : categories) {
uint32_t flag = 0;
if (!GetLogCategory(&flag, &cat)) {
InitWarning(strprintf(_("Unsupported logging category %s=%s."), "-debug", cat));
continue;
}
logCategories |= flag;
}
}
}
// Now remove the logging categories which were explicitly excluded
for (const std::string& cat : gArgs.GetArgs("-debugexclude")) {
uint32_t flag = 0;
if (!GetLogCategory(&flag, &cat)) {
InitWarning(strprintf(_("Unsupported logging category %s=%s."), "-debugexclude", cat));
continue;
}
logCategories &= ~flag;
}
首先的一段代码是判断应该对哪些目录写入调试日志,所有的目录包括以下类型(src/util.cpp line 220
),
const CLogCategoryDesc LogCategories[] =
{
{BCLog::NONE, "0"},
{BCLog::NET, "net"},
{BCLog::TOR, "tor"},
{BCLog::MEMPOOL, "mempool"},
{BCLog::HTTP, "http"},
{BCLog::BENCH, "bench"},
{BCLog::ZMQ, "zmq"},
{BCLog::DB, "db"},
{BCLog::RPC, "rpc"},
{BCLog::ESTIMATEFEE, "estimatefee"},
{BCLog::ADDRMAN, "addrman"},
{BCLog::SELECTCOINS, "selectcoins"},
{BCLog::REINDEX, "reindex"},
{BCLog::CMPCTBLOCK, "cmpctblock"},
{BCLog::RAND, "rand"},
{BCLog::PRUNE, "prune"},
{BCLog::PROXY, "proxy"},
{BCLog::MEMPOOLREJ, "mempoolrej"},
{BCLog::LIBEVENT, "libevent"},
{BCLog::COINDB, "coindb"},
{BCLog::QT, "qt"},
{BCLog::LEVELDB, "leveldb"},
{BCLog::ALL, "1"},
{BCLog::ALL, "all"},
};
每个目录都对应了一个编号,代码中的logCategories
变量就是记录所有的日志目录的集合,类型是uint32_t
,而目录对应的编号每一个都对应32位中的一位,所以每做一次|
操作,就表示将当前的目录编号加进集合。而后面的-debugexclude
参数就是从集合中删除掉不想记录日志的目录,这时使用的是&= ~flag
操作,~
表示每一位取反,与上反码就表示将当前的目录编号从集合中去掉。
再接下来一段代码中的几个if
语句就是检查一下不支持的参数,或者有变更的参数命令,比较容易理解。
// Checkmempool and checkblockindex default to true in regtest mode
int ratio = std::min<int>(std::max<int>(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);
根据帮助信息中的解释,
-checkmempool
:表示每隔多少个交易进行一次sanity check。
-checkblockindex
:每隔一段时间检查mapBlockIndex
、setBlockIndexCandidates
、chainActive
和mapBlockUnlinked
变量的一致性。
-checkpoints
:该变量默认为1,表示不验证当前已经存在的链;如果为0,表示要检查一些校验点的区块信息是否正确,所有校验点的信息也都保存在chainparams中的checkpointdata中。
代码首先判断chainparams
中的DefaultConsistencyChecks
是否为true
,如果这个变量为false
,那么ratio=0
,也就是不进行sanity check。Sanity check之前在http://blog.csdn.net/pure_lady/article/details/77776716#t2中CTxMemPool类中介绍过,表示检查mempool中所有交易的一致性(没有双花,所有的输入都是合法的)。对于chainparams
这个变量的也在http://blog.csdn.net/pure_lady/article/details/77895680中的SelectParams
函数中介绍过,首先根据设置的网络MAIN
、TESTNET
或者REGTEST
选择相应的参数,三个网络根据src/chainparams.cpp
中给参数定义不同的值,对于DefaultConsistencyChecks
这个参数,MAIN
和TESTNET
都为false
,而REGTEST
中此变量为true
。
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");
-assumevalid=blockid
:表示在blockid
之前的所有区块都假设正确的,也就是不用再去验证。如果没有设置,那么就要验证之前所有区块的签名信息。
// mempool limits
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)));
// incremental relay fee sets the minimum feerate increase necessary for BIP 125 replacement in the mempool
// and the amount the mempool min fee increases above the feerate of txs evicted due to mempool limiting.
if (gArgs.IsArgSet("-incrementalrelayfee"))
{
CAmount n = 0;
if (!ParseMoney(gArgs.GetArg("-incrementalrelayfee", ""), n))
return InitError(AmountErrMsg("incrementalrelayfee", gArgs.GetArg("-incrementalrelayfee", "")));
incrementalRelayFee = CFeeRate(n);
}
接下来这段代码首先计算mempool的最大值,后面乘以1000000是将单位从MB转换成B,然后计算最小的限制,其中,-limitdescendantsize
的含义如下,后面乘以1000将单位从KB转化成B,再乘以40表示最小可以容纳40个这个的交易族。
-limitdescendantsize
:如果某个交易在mempool中所有的祖先size之和超过该限制值,那么则拒绝接受该交易。单位为KB。
在每个节点内部都可以设置以下几种费用来避免接收过多的交易,
所以一个full-node对交易的处理流程如下:(1)首先判断交易的费用之和是否大于minrelayfee;(2)然后判断是否是dustrelayfee,如果是的话就转发给其他节点,自己忽略该交易;(3)最后判断费用是否满足当前的费用条件,当前的费用会根据交易数量动态的变化,当交易数量过多时,增加,交易减少时,也减小。
源码中这地方就是判断命令行中是否设置了-incrementalrelayfee
,如果设置了就设置变量incrementalRelayFee
。
// -par=0 means autodetect, but nScriptCheckThreads==0 means no concurrency
nScriptCheckThreads = gArgs.GetArg("-par", DEFAULT_SCRIPTCHECK_THREADS);
if (nScriptCheckThreads <= 0)
nScriptCheckThreads += GetNumCores();
if (nScriptCheckThreads <= 1)
nScriptCheckThreads = 0;
else if (nScriptCheckThreads > MAX_SCRIPTCHECK_THREADS)
nScriptCheckThreads = MAX_SCRIPTCHECK_THREADS;
-par=N
:设置脚本验证线程数量,取值范围为[-2,16],小于0表示令N个保持空闲,0表示自动检测,1表示不允许并行,大于等于2表示同一时刻最大线程数量,默认值为0。
其中MAX_SCRIPTCHECK_THREADS
定义的值为16。
// block pruning; get the amount of disk space (in MiB) to allot for block & undo files
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<uint64_t>::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;
}
-prune
参数在Step 2中已经介绍过,用来删除已经验证过的区块,取值有以下几种:
RegisterAllCoreRPCCommands(tableRPC);
#ifdef ENABLE_WALLET
RegisterWalletRPCCommands(tableRPC);
#endif
所谓注册RPC命令,其实解释将信号和处理函数connect起来,方式就是使用boost的signal/slot模式,但是这里做的只是将一些指令添加到一个类型为CRPCTable的tableRPC变量中,这个tableRPC维护了所有的命令和对应的处理函数,收到相应的RPC命令是再调用CRPCTable类中execute函数执行请求。
再看看All core RPC commands包括了哪些命令,
static inline void RegisterAllCoreRPCCommands(CRPCTable &t)
{
RegisterBlockchainRPCCommands(t);
RegisterNetRPCCommands(t);
RegisterMiscRPCCommands(t);
RegisterMiningRPCCommands(t);
RegisterRawTransactionRPCCommands(t);
}
可见这句代码注册了几乎所有核心的命令。