本篇文章涉及的文件有:
init.cpp、validation.h、register.h、blockchain.cpp、net.cpp、misc.cpp、server.h、rpcwallet.cpp
我们接着上篇文章继续介绍一些参数初始化的操作。在上一篇文章中我们分析了设置mempool容量的相关参数,也就是说mempool的大小是由限制的,当mempool不足以容纳网络中所有未被打包的交易时,节点可以根据交易费用的大小,有选择的控制哪些交易可以进入交易池。-incrementalrelayfee就是其中之一,故当mempool容量不足时,将根据incrementalrelayfee调整能进入mempool的交易(有待检验具体使用)。
if (gArgs.IsArgSet("-incrementalrelayfee"))
{
CAmount n = 0;
if (!ParseMoney(gArgs.GetArg("-incrementalrelayfee", ""), n))
return InitError(AmountErrMsg("incrementalrelayfee", gArgs.GetArg("-incrementalrelayfee", "")));
incrementalRelayFee = CFeeRate(n);
}
比特币(UTXO)的所有权是通过记录在交易输出中的锁定脚本来控制的,在验证交易有效性时,很重要的一步就是验证解锁脚本是否能正确执行。可以通过设置参数-par和-nScriptCheckThreads来指定验证解锁脚本的线程数。如果-par没有设置或设置为小于0,则取当前系统支持同时运行的线程数量,但不可超过最大值16。
// -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)//最大值16
nScriptCheckThreads = MAX_SCRIPTCHECK_THREADS;
比特币网络自从2009年运行至写本文章时,共产生了490608个块,总数据量约150G(Bitcoin Core钱包在Mac上的统计)。但很多交易历史记录是可以不存储的(如交易的所有输出都已经花费了),故从0.12版本程序中提供了参数-prune来设置存储区块数据(blocks/blk.dat)和恢复数据(blocks/rev.dat)的最大值(https://github.com/bitcoin/bitcoin/blob/v0.12.0/doc/release-notes.md#wallet-pruning
)。-prune参数的单位是M,如果设置为1,表示可以通过rpc调用pruneblockchain(height)进行手动裁剪,容量大小取uint64_t类型的最大值。但设置的-prune的值不能小于MIN_DISK_SPACE_FOR_BLOCK_FILES 550M. 值得注意的是,即使启动-prune模式,节点也还是要下载整个区块链网络的数据的,只不过不会存储所有的数据。另外,如果启动了-prune模式,需要重新建立索引时,需要重新下载整个区块链网络数据。相关代码如下:
int64_t nPruneArg = gArgs.GetArg("-prune", 0);
if (nPruneArg < 0) {//不能小于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();//uint64_t类型的最大值
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;
}
应用程序一般都是通过RPC与Bitcoin节点进行交互,通过节点提供的RPC服务获取钱包、交易、块等一系列信息。我们来看一下这些RPC命令在程序中的定义:
RegisterAllCoreRPCCommands(tableRPC);
static inline void RegisterAllCoreRPCCommands(CRPCTable &t)
{
RegisterBlockchainRPCCommands(t);// 注册链、块、交易等相关的RPC命令
RegisterNetRPCCommands(t);//注册网络相关的RPC命令
RegisterMiscRPCCommands(t);// 注册一些混合的命令,日志、验证地址等
RegisterMiningRPCCommands(t);// 注册挖矿相关的RPC命令
RegisterRawTransactionRPCCommands(t);// 注册raw交易(编码级)相关的RPC命令
}
我们以RegisterBlockchainRPCCommands为例看下具体的RPC命令注册流程,其他方法都是类似的,就不展开说明了。
void RegisterBlockchainRPCCommands(CRPCTable &t)
{
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
t.appendCommand(commands[vcidx].name, &commands[vcidx]);
}
RegisterBlockchainRPCCommands会把一系列RPC命令的定义注册在CRPCTable中,即当接到RPC请求时,将从CRPCTable找到匹配的RPC调用。CRPCTable中定义一个键值对mapCommands,值为RPC命令的名称,键为命令对应的信息(CRPCCommand),并且可以通过appendCommand方法将该对应关系存储于CRPCTable中。
std::map<std::string, const CRPCCommand*> mapCommands;//定义于CRPCTable中。
class CRPCCommand
{
public:
std::string category;//种类
std::string name;//名称
rpcfn_type actor;//执行方法
bool okSafeMode;//是否允许在安全模式下被调用
std::vector<std::string> argNames;//参数
};
我们可以看下RegisterBlockchainRPCCommands中涉及的CRPCCommand的定义:
static const CRPCCommand commands[] =
{ // category name actor (function) okSafe argNames
// --------------------- ------------------------ ----------------------- ------ ----------
{ "blockchain", "getblockchaininfo", &getblockchaininfo, true, {} },
{ "blockchain", "getchaintxstats", &getchaintxstats, true, {"nblocks", "blockhash"} },
{ "blockchain", "getbestblockhash", &getbestblockhash, true, {} },
{ "blockchain", "getblockcount", &getblockcount, true, {} },
{ "blockchain", "getblock", &getblock, true, {"blockhash","verbosity|verbose"} },
{ "blockchain", "getblockhash", &getblockhash, true, {"height"} },
{ "blockchain", "getblockheader", &getblockheader, true, {"blockhash","verbose"} },
{ "blockchain", "getchaintips", &getchaintips, true, {} },
{ "blockchain", "getdifficulty", &getdifficulty, true, {} },
{ "blockchain", "getmempoolancestors", &getmempoolancestors, true, {"txid","verbose"} },
{ "blockchain", "getmempooldescendants", &getmempooldescendants, true, {"txid","verbose"} },
{ "blockchain", "getmempoolentry", &getmempoolentry, true, {"txid"} },
{ "blockchain", "getmempoolinfo", &getmempoolinfo, true, {} },
{ "blockchain", "getrawmempool", &getrawmempool, true, {"verbose"} },
{ "blockchain", "gettxout", &gettxout, true, {"txid","n","include_mempool"} },
{ "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, true, {} },
{ "blockchain", "pruneblockchain", &pruneblockchain, true, {"height"} },
{ "blockchain", "verifychain", &verifychain, true, {"checklevel","nblocks"} },
{ "blockchain", "preciousblock", &preciousblock, true, {"blockhash"} }
};
如果节点也作为钱包使用,可以注册与钱包相关的RPC命令,如获取新地址、获取余额、导出私钥等,这里就不展开讲了,跟上面介绍的RegisterBlockchainRPCCommands类似。
#ifdef ENABLE_WALLET
RegisterWalletRPCCommands(tableRPC);
#endif
区块链节点之间组成一个P2P网络,节点之间的建立连接的超时设置通过-timeout参数来指定,默认值为5000毫秒。
nConnectTimeout = gArgs.GetArg("-timeout", DEFAULT_CONNECT_TIMEOUT);
if (nConnectTimeout <= 0)
nConnectTimeout = DEFAULT_CONNECT_TIMEOUT;
在0.14版本的release note(https://bitcoin.org/en/release/v0.14.0)中提到:
Since the changes in 0.12 to automatically limit the size of the mempool and improve the performance of block creation in mining code it has not been important for relay nodes or miners to set -minrelaytxfee. With this release the following concepts that were tied to this option have been separated out:
• incremental relay fee used for calculating BIP 125 replacement and mempool limiting. (1000 satoshis/kB)
• calculation of threshold for a dust output. (effectively 3 * 1000 satoshis/kB)
• minimum fee rate of a package of transactions to be included in a block created by the mining code. If miners wish to set this minimum they can use the new -blockmintxfee option. (defaults to 1000 satoshis/kB)
The -minrelaytxfee option continues to exist but is recommended to be left unset.
我们可以看出参数-minrelaytxfee一分为三:-incrementalRelayFee、-dustrelayfee、-blockmintxfee。分别用于控制交易费用增长率、dust(灰尘)交易费率、交易被打包到块中的最小费率。同时,也建议不要设置- minrelaytxfee了。
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 CWallet::ParameterInteraction()
::minRelayTxFee = CFeeRate(n);
} else if (incrementalRelayFee > ::minRelayTxFee) {//最低不能低于incrementalRelayFee
// 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"))//检查参数blockmintxfee的值是否符合Money的格式要求
{
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"))//后续将分析什么是dust utxo以及怎么处理和避免生成
{
CAmount n = 0;
if (!ParseMoney(gArgs.GetArg("-dustrelayfee", ""), n) || 0 == n)
return InitError(AmountErrMsg("dustrelayfee", gArgs.GetArg("-dustrelayfee", "")));
dustRelayFee = CFeeRate(n);
}
好了,本篇文章就先分析到这。因本人水平有限,如有问题,欢迎大家批评指出,非常感谢。