比特币源码研读之四
比特币源码研读系列已经更新了3篇文章了,每篇文章都得到了很多朋友的关注和讨论。也有很多朋友在看了我的研读系列之后加入我们的区块链研习社精英群、比特币源码研读班小密圈以及区块链研习社币圈交流小密圈的,因为他们也看到了我们区块链研习社是重视价值与技术学习的,不是简单地带着大家炒币,而是通过区块链知识的传递,让大家更好地了解区块链领域,正确地分析自己关注的区块链产品,让我们真正拥有站在投资对象背后的资格,从而让我们可以真正做到处乱不惊,长期持有。
照惯例,文将继续沿着我们比特币核心后台进程的运行过程进行研读分析,话不多说,继续我们的征程!
本文主要涉及的源码文件包括:
src/bitcond.cpp、src/util.h、src/util.cpp、src/init.h、src/init.cpp、src/utilstrencodings.、src/utilstrencodings.cpp、src/clientversion.h、src/ clientversion.cpp、config/bitcoin-config.h
本文主要讲述参数处理中的版本信息与帮助信息打印实现代码。
一、版本信息
在完成了上一步的参数解析之后,程序开始进入“参数处理”阶段。首先我们来看ParseParameters函数后面的这段代码。
//Process help and version before taking care about datadir
if (IsArgSet("-?") || IsArgSet("-h") ||IsArgSet("-help") ||IsArgSet("-version"))
{
std::string strUsage =strprintf(_("%s Daemon"), _(PACKAGE_NAME)) + " " +_("version") + " " + FormatFullVersion() + "\n";
if (IsArgSet("-version"))
{
strUsage +=FormatParagraph(LicenseInfo());
}
else
{
strUsage += "\n" +_("Usage:") + "\n" +
"bitcoind [options]" +strprintf(_("Start %s Daemon"), _(PACKAGE_NAME)) + "\n";
strUsage += "\n" +HelpMessage(HMM_BITCOIND);
}
fprintf(stdout,"%s", strUsage.c_str());
return true;
}
(1)代码注释。这段代码的注释的含义为:在处理数据目录操作前,先完成版本与帮助命令的处理。所以,通过这段代码,比特币后台进程将可根据用户输入相应参数给出对应的程序版本与帮助信息。
(2)条件判断。在注释之后的if判断语句中判断bitcoind后台进程参数中是否包含“-?”、“-h”、“-help”或者“-version”,如果包含则执行If中包含的代码,执行完成后返回true,程序运行结束。否则不执行其包含的内容,跳出If语句包含内容,执行其后语句。
此处判断是否包含这几个参数的方法为IsArgSet,该函数的实现位于我们熟悉的src/util.cpp文件中,其代码实现为:
bool IsArgSet(const std::string& strArg)
{
LOCK(cs_args);
returnmapArgs.count(strArg);
}
我们应该还记得上一篇文章中的mapArgs变量,该变量中存储了用户输入的所有参数及其值。所有此处通过mapArgs查找是否包含“-?”、“-h”、“-help”或者“-version”,如果查找到了则返回true,反之为false。还需要说明的是程序通过map类型的变量实现对参数的存储,由于其采用的是键值对存储方式,对于参数信息的快速查找相比于使用数组或队列方式优势很明显。
(3)版本信息。在If语句成立时,其第一行代码的含义为通过strUsage字符串变量存储包含比特币后台进程名称与版本信息内容。
std::string strUsage = strprintf(_("%sDaemon"), _(PACKAGE_NAME)) + " " + _("version") +" " + FormatFullVersion() + "\n";
strprintf函数为字符串格式化命令,主要功能是把格式化的数据写入某个字符串中。此处是将PACKAGE_NAME写入_("%s Daemon")中,生成PACKAGE_NAME Daemon形式的字符串内容。PACKAGE_NAME的定义位于src/config/bitcoin-config.h中,其定义为:
该文件在我们下载的源码中一开始是不存在的,需经过对源码进行./configure命令后才能生成。源码的./configure过程可参见我的《聊聊比特币(Bitcoin)客户端源码编译那些事》一文。
FormatFullVersion函数的功能是输出比特币核心的完整版本信息。该函数的实现位于src/clientversion.cpp中,其实现代码如下:
std::string FormatFullVersion()
{
return CLIENT_BUILD;
}
函数中直接调用了CLIENT_BUILD函数,该函数的定义也在当前文件
const std::string CLIENT_BUILD(BUILD_DESC CLIENT_VERSION_SUFFIX);
再来看BUILD_DESC,其定义就在当前文件:
#ifndef BUILD_DESC
#ifdef BUILD_SUFFIX
#define BUILD_DESCBUILD_DESC_WITH_SUFFIX(CLIENT_VERSION_MAJOR, CLIENT_VERSION_MINOR,CLIENT_VERSION_REVISION, CLIENT_VERSION_BUILD, BUILD_SUFFIX)
#elif defined(GIT_COMMIT_ID)
#define BUILD_DESCBUILD_DESC_FROM_COMMIT(CLIENT_VERSION_MAJOR, CLIENT_VERSION_MINOR,CLIENT_VERSION_REVISION, CLIENT_VERSION_BUILD, GIT_COMMIT_ID)
#else
#define BUILD_DESC BUILD_DESC_FROM_UNKNOWN(CLIENT_VERSION_MAJOR,CLIENT_VERSION_MINOR, CLIENT_VERSION_REVISION, CLIENT_VERSION_BUILD)
#endif
#endif
通过分析#ifndef BUILD_DESC,我们可以判断BUILD_DESC将在BUILD_DESC_FROM_UNKNOWN函数中执行,该函数采用的是预编译实现方式,这样的好处是对于小型、通用性函数采用预编译方式可以提高程序的执行效率。
#define BUILD_DESC_FROM_UNKNOWN(maj, min,rev, build) \
"v" DO_STRINGIZE(maj) "." DO_STRINGIZE(min)"." DO_STRINGIZE(rev) "." DO_STRINGIZE(build)"-unk"
在函数实现中调用了DO_STRINGIZE函数,该函数的实现位于src/clientversion.h中
/**
* Converts theparameter X to a string after macro replacement on X has been performed.
* Don't mergethese into one macro!
*/
#define STRINGIZE(X) DO_STRINGIZE(X)
#define DO_STRINGIZE(X) #X
通过其注释,我们可以知道该函数的作用是将宏定义的参数X转变为字符串。那问题来了,BUILD_DESC_FROM_UNKNOWN函数中调用的4次DO_STRINGIZE函数包含的变量是宏定义变量吗?答案肯定是的,不然程序就出错了。那包含的maj, min, rev, build在哪定义呢?那我们就需要看BUILD_DESC_FROM_UNKNOWN调用位置传入的四个变量:
CLIENT_VERSION_MAJOR
CLIENT_VERSION_MINOR
CLIENT_VERSION_REVISION
CLIENT_VERSION_BUILD
他们的定义位于src/clientversion.h中,通过定义我们可知其为宏定义,因此传入DO_STRINGIZE函数中是没问题的。
#define CLIENT_VERSION_MAJOR 0
#define CLIENT_VERSION_MINOR 14
#define CLIENT_VERSION_REVISION 2
#define CLIENT_VERSION_BUILD 0
再来看BUILD_DESC_FROM_UNKNOWN的实现,其功能是将版本信息的主要、次要、修正以及建立4个值进行拼接,从而输出完整的版本号信息。
(4)版权许可信息
如果参数中包含"-version",则将执行以下语句:
if (IsArgSet("-version"))
{
strUsage +=FormatParagraph(LicenseInfo());
}
我们首先看到strUsage字符串将FormatParagraph(LicenseInfo());返回的内容进行拼接,组成完整的输出信息。
下面来看FormatParagraph函数,其定义位于src/utilstrencodings.h中,实现位于src/utilstrencodings.cpp中,该函数在头文件中的注释内容如下:
/**
* Format aparagraph of text to a fixed width, adding spaces for
* indentationto any added line.
*/
std::string FormatParagraph(const std::string& in, size_t width = 79, size_t indent = 0);
其含义是对成段落的文本信息进行处理,形成固定宽度,为缩进排版的代码行添加空格等格式化处理功能。
FormatParagraph在该处主要是对比特币核心的版权许可信息进行格式化处理。版权许可信息的内容位于src/init.cpp中的LicenseInfo()函数中,
该函数主要实现比特币版权相关信息的输出,函数中使用的CopyrightHolders函数在src/util.cpp中有具体的实现。其主要作用为补全版本信息。
至此,我们完成了版本信息输出实现源码,本文以ubuntu为例,我们在其终端中输入“bitcoind -version”命令来验证,我们对代码理解的正确性。其运行结果如图所示。
图中1为输入bitcoind –version命令,2为输出的比特币版本信息,3为比特币版权信息。通过比较我们可以发现与我们分析的内容基本一致。
二、帮助信息
帮助信息的输出位于版权信息输出之后,当输入的参数为“-?”" -h"、" -help"时,程序将会输出帮助信息。在有了查看版本信息命令的经验后,我们在终端中输入“bitcoind -?”、"bitcoind -h"或" bitcoind -help"将获取相同的帮助内容,均为比特币后台进程包含参数使用方法的帮助信息。具体效果如图所示。
而此帮助信息均在HelpMessage(HMM_BITCOIND);函数中给出,该函数的定义与实现位于src/init.h、src/init.cpp中。该函数的参数类型为枚举变量,该枚举变量的定义如下(注:中文为我自己添加的):
/** The help message mode determines what helpmessage to show */
/**帮助信息模式定义了将显示的帮助信息内容*/
enum HelpMessageMode {
HMM_BITCOIND, //比特币后台进程帮助信息
HMM_BITCOIN_QT //比特币前端界面程序帮助信息
};
此处我们研读的代码为后台进程bitcoind程序,所以参数为HMM_BITCOIND。在HelpMessage函数中,将会根据具体的类型输出相应的帮助信息内容,其帮助内容主要为后台进程涉及参数的使用方法说明。所以,大家后续在使用后台进程时,如果遇到不会的命令,可以通过“bitcoind -?”、"bitcoind -h"或" bitcoind -help"得到帮助信息。
最后程序通过fprintf(stdout, "%s",strUsage.c_str());实现版本或帮助信息的输出。这里要说明的是stdout为控制台对象,在linux中对应的是终端,windows中对应的是cmd窗口。
至此,我们完成了参数处理中的版本与帮助信息参数处理源码与流程的分析,后续我们将继续完成参数处理的其他部分,并逐步深入到比特币源码的核心代码,让我们更清晰地理解比特币是如何实现钱包、挖矿、交易以及交易脚本等功能的。
作者:区块链研习社比特币源码研读班 菜菜子
以下是广告:
我们区块链研习社已创建“区块链研习社币圈交流”小密圈”,在小密圈中,我们将带领大家一起学习区块链的原理与投资,还将提供区块链基本原理解答、交易所注册与交易操作、ICO交易与操作、投资分析、风险分析等内容。
目前入圈价格初始定价50元,50人调整一次价格,每次调整幅度为50元!