您现在的位置是:首页 > 资讯 > 热点资讯网站首页热点资讯
从零开始学习区块链技术(二):如何接入比特币网络以及其原理分析
- 热点资讯
- 2018-12-12
- 3
从零开始学习区块链技术(一):从源代码编译比特币
1、如何接入比特币网络?
其实接入比特币网络是非常简单的,我说了你一定不信,启动比特币客户端即可:
在命令行终端输入启动命令:./src/bitcoind -testnet
输入之后会有一个和网络同步数据的过程,你会看到:
这个过程需要一点时间,同步数据完成后,即接入了比特币网络。
2、启动流程鸟瞰
虽然说一句命令即搞定,但是,这个背后代码运行的逻辑可就不简单咯~
来,我给大家分析一下
当在命令行终端输入启动命令:./src/bitcoind -testnet
后,操作系统就会找到这个文件中的 main 函数,开始比特币客户端的启动。
对于所有的c++代码,整个程序都是从main函数开始执行的,bitcoind 的main函数位于 src/bitcoind.cpp
,代码拉到最后就找到了我们的 main 函数。
main 函数本身没有太多东西,主要是调用3个函数来执行,它们的主要作用是设置环境变量、设置信号处理和启动系统。
具体代码如下:
int main(int argc, char* argv[])
{
SetupEnvironment();
// Connect bitcoind signal handlers noui_connect();
return (AppInit(argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
SetupEnvironment
函数,主要用来设置系统的环境变量,包括:malloc
分配内存的行为、Locale、文件路径的本地化设置等。noui_connect
函数,设置连接到 bitcoind 的信号的处理。AppInit
函数,进行系统启动。
AppInit
函数的执行
- 调用
SetupServerArgs
函数,设置系统可接受的所有命令行参数。然后开始解析命令行传递的各种参数。系统执行的重要一步就是设置可以接收的参数并解析用户启动时传递的各种参数,SetupServerArgs
函数就是完成这个目的。下面来看这个函数的执行流程。- 首先,调用
CreateBaseChainParams
函数,生成默认的基本参数,包括:使用的数据目录和监听的端口。根据不同的网络类型,主网络使用 8332 端口和指定目录下的当前目录,测试网络使用 18332 端口和指定目录下的 testnet3 子目录,回归测试网络 使用 18443 端口和指定目录下的 regtest 子目录。 - 然后,调用
CreateChainParams
函数,生成默认的区块链参数。这个方法也会区分不同的网络。如果是主网络,则生成CMainParams
对象进行初始化。在构造函数中,进行如下的设置:- 设置网络ID 为
main
; - 设置共识参数(
Consensus::Params
)的各个值: - 每隔多少个块(
nSubsidyHalvingInterval
)后续比特币的奖励会减半,值为 210000。根据创世区块奖励的数量(50),根据等比数列求和公式:50∗(1/(1−0.5))∗210000" role="presentation" style="box-sizing: border-box; outline: 0px; display: inline; line-height: normal; word-spacing: normal; overflow-wrap: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border: 0px; padding: 0px; margin: 0px; word-break: break-all; position: relative;">50∗(1/(1−0.5))∗21000050∗(1/(1−0.5))∗210000,可计算货币总量为 2100W个比特币。 - BIP34 激活高度(
BIP34Height
)为 227931。 - BIP34 激活哈希(
BIP34Hash
)为0x000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8
。 - BIP65 激活高度(
BIP65Height
)为 388381。 - BIP66 激活高度(
BIP66Height
)为 363725。 - 工作量限制(
powLimit
)为一个大整数。 - 难度改变的周期(
nPowTargetTimespan
)为 2周。 - 平均出块时间(
nPowTargetSpacing
)为10分钟。 - 改变共识需要的区块数(
nRuleChangeActivationThreshold
)为 1916,即 2016 的 95%。 - 矿工确认窗口(
nMinerConfirmationWindow
)为 2016,等于难度改变周期除以平均出块时间。 - 接下来设置区块链相关的部署状态,包括:测试相关的(
DEPLOYMENT_TESTDUMMY
)、CSV 软分叉相关的(涉及到 BIP68、BIP112、BIP113)和隔离见证相关的(涉及到 BIP141、BIP143、BIP147)。 - 最佳区块链的最小工作量。
- 设置默认端口(
nDefaultPort
)为 8333。 - 达到多少个区块之后进行区块修剪(
nPruneAfterHeight
),当前值为 100000。 - 接下来,调用
CreateGenesisBlock
方法,生成创世区块。这个方法的参数是固定的,指定了创世区块的时间、随机数、难度值、版本号、奖励等。在方法内部,生成创世区块的输出脚本和输入脚本,中本聪那句著名的评论就出现在创世区块的第一个交易的签名中,他写道:The Times 03/Jan/2009 Chancellor on brink of second bailout for banks。 - 设置创世区块的哈希为刚生成的创业区块的哈希。
- 设置 DNS 种子节点
vSeeds
集合包含的 DNS 种子有:seed.bitcoin.sipa.be
,dnsseed.bluematt.me
,dnsseed.bitcoin.dashjr.org
,seed.bitcoinstats.com
,seed.bitcoin.jonasschnelli.ch
,seed.btc.petertodd.org
,seed.bitcoin.sprovoost.nl
等,通过解析 DNS 种子节点,比特币节点启动时可以找到更多的对等节点来进行连接。 - 接下来,设置相关的检查点数据。
CTestNetParams
对象进行初始化。(供开发完成后测试使用。)如果是回归测试网络,则生成
CRegTestParams
对象进行初始化。(供开发时连接使用。)对于这两种测试网络,处理基本和主网络相同,只是某些参数不一样。
上面提到的3个对象
CMainParams
CTestNetParams
CRegTestParams
的定义都在chainparams.cpp
文件中。感兴趣同学的可以对照源代码进一步探究。 - 设置网络ID 为
- 接下来,设置系统可接收的所有参数。部分参数解释如下:
- ?,显示帮助信息;
- -version,打印版本信息,并退出系统。
- -assumevalid=hex,如果指定的区块存在区块链中,假定它及其祖先有效并可能跳过其脚本验证。
- -blocksdir=dir,指定区块链存放的目录。
- -blocknotify=cmd,指定当主链上的区块改变时执行的命令。
- -conf=file,指定配置文件的目录,相对于下面指定的数据目录。
- -datadir=dir,指定数据目录。
- -dbcache=n,设置数据库缓存大小。
- -debuglogfile=file,设置调试文件的位置。
- -feefilter,告诉其他节点通过最小交易费用过滤发送给我们的库存消息。
- -loadblock=file,在启动时,从外部 blk000??.dat 文件导入区块。
- -maxmempool=n,指定交易池的最大内存数,单位为兆字节。
- -maxorphantx=n,指定内存中最大的孤儿交易数量。
- -mempoolexpiry=n,指定交易池中不跟踪超过指定时间(小时)的交易。
- -par=n,指定脚本签名的线程数量。
- -persistmempool,指定是否持久化交易池中的交易,启动时恢复加载。
- -pid=file,指定进程文件。
- -prune=n,通过启用旧区块的修剪(删除)来降低存储要求。 这允许调用
pruneblockchain
RPC 来删除特定块,并且如果提供目标大小,则启用对旧块的自动修剪。 此模式与-txindex
和-rescan
不兼容。 - -reindex,根据硬盘上的
blk*.dat
文件重建区块链状态和区块的索引。 - -reindex-chainstate,根据当前区块的索引重建区块链的状态。
- -txindex,维护所有交易的索引,被
getrawtransaction
RPC 命令调用。 - -addnode=ip,添加一个节点,并连接它,并保持连接。
- -banscore=n,断开行为不端的同伴的门槛。
- -bantime=n,不诚实节点重新连接需要的秒数。
- -bind=addr,绑定到指定的IP,并总是连接到这个地址。
- -connect=ip,仅仅只连接到指定的节点,如果不是ip而是0,则表示禁止自动连接。
- -discover,是否发现自己的IP地址。
- -dns,对于
-addnode
、-seednode
、-connect
总是使用 DNS 查找。 - -dnsseed,指定如果已有地址比较少,则进行 DNS 查找来获取对等节点。
- -enablebip61,允许发送 BIP61 定义的拒绝消息。
- -externalip=ip,指定自身的外部 IP 地址。
- -forcednsseed,总是通过 DNS 查找来获取对等节点的地址。
- -listen,接收外部对等节点的连接。
- -listenonion,自动创建 Tor 隐藏服务。
- -maxconnections=n,维护到别的节点的最大连接数。
- -maxreceivebuffer=n,每个对等节点的最大接收缓存。
- -maxsendbuffer=n,每个对等节点的最大发送缓存。
- -onion=ip:port,设置 SOCKS5 代理。
- -peerbloomfilters,支持布隆过滤器过滤区块和交易。
- -permitbaremultisig,中继非 P2SH 多重签名。
- -port=port,指定默认的监听端口。
- -proxy=ip:port,通过 SOCKS5 代理进行连接。
- -proxyrandomize,随机化每个代理连接的凭据。 从而使Tor流进行隔离。
- -seednode=ip,指定一个节点来检索其他的节点,随后就从这个接点进行断开。
- -torcontrol=ip:port,在 onion 启用的情况下,指定 Tor 控制器使用的端口。
- -torpassword=pass,Tor 控制器的密码。
- -checkblocks=n,在启动时要检查多少个区块。
- -checklevel=n,
checkblocks
验证区块的程度。 - -checkblockindex,进行完整的一致性检查,包括:mapBlockIndex、setBlockIndexCandidates、chainActive、mapBlocksUnlinked 等。
- -checkmempool=n,每多少个交易进行检验。
- -checkpoints,提供检查点,对已知链的历史不进行检验。
- -deprecatedrpc=method,不赞成使用的 RPC 方法。
- -limitancestorcount=n,如果交易池中的祖先交易达到或超过指定的值时,不再接收交易。
- -limitancestorsize=n,如果交易池中的祖先交易大小达到或超过指定的值时,不再接收交易。
- -limitdescendantcount=n,如果交易池中祖先交易的后代已经达到或超过指定的值时,不再接收交易。
- -blockmaxweight=n,设置 BIP141 区块的最大 weight。
- -blockmintxfee=amt,设置包含在创建区块的交易最小费用。
- -rpcuser=user,进行 RPC 调用的用户名。
- -rpcpassword=pw,进行 RPC 调用的用户密码。
- -rpcport=port,进行 RPC 调用的端口
- 首先,调用
- 接下来,检查用户指定命令参数是否正确。
if (!gArgs.ParseParameters(argc, argv, error)) { fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str()); return false; }
- 1
- 2
- 3
- 4
- 如果传递的是帮助和版本参数,则显示帮助或版本信息,然后退出。
- 检查数据目录(可指定或默认)是否是存在。如果不存在,则打印错误信息,然后退出。
if (!fs::is_directory(GetDataDir(false))) { fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str()); return false; }
- 1
- 2
- 3
- 4
- 5
GetDataDir
方法中,根据用户是否在命令行提供datadir
参数来确定使用默认的数据目录还是用户指定的数据目录。 - 读取并解析配置文件,同时检查指定数据目录是否存在。如果任何一个步骤出错,都打印错误信息,然后退出。
if (!gArgs.ReadConfigFiles(error, true)) { fprintf(stderr, "Error reading configuration file: %s\n", error.c_str()); return false; }
- 1
- 2
- 3
- 4
ReadConfigFiles
方法具体处理如下:- 首先,调用
GetArg
方法,获取配置文件名称,默认为bitcoin.conf
。 - 然后,通过
GetConfigFile
方法获取配置文件的绝对路径(方法内部会委托AbsPathForConfigVal
方法进行处理,后者决定根据用户指定的路径或使用默认路径来生成配置文件的绝对路径)。在得到配置文件的绝对路径之后,构造文件输入流,从而读取配置文件fs::ifstream stream(GetConfigFile(confPath))
。 - 在成功构造输入流之后,调用
ReadConfigStream
方法开始读取配置文件的内容。方法内部按行读取配置文件,并以键值对的形式保存在m_config_args
集合中。
- 调用
SelectParams(gArgs.GetChainName())
函数,生成全局的区块链参数,并设置系统的网络类型。如果有错误,则打印错误,然后退出。gArgs.GetChainName()
方法会返回当前使用的网络。针对主网络,返回字符串main
;测试网络,返回字符串test
;回归测试网络,返回字符串regtest
。SelectParams
方法的实现如下所示:void SelectParams(const std::string& network) { SelectBaseParams(network); globalChainParams = CreateChainParams(network); }
- 1
- 2
- 3
- 4
- 5
SelectBaseParams
方法会根据指定的网络参数生成CBaseChainParams
对象,并保存在globalChainBaseParams
变量中,并在指定gArgs
对象中保存网络类型(m_network
属性)。CBaseChainParams
对象中仅保存系统的数据目录和运行的端口,所以称之为基本区块链参数对象。CreateChainParams
方法会根据不同的网络参数生成CChainParams
类的子对象,可能为以下三种:CMainParams、CTestNetParams、CRegTestParams。CChainParams
对象包含了区块链对象的所有重要信息,比如:共识规则、部署状态、检查点、创世区块等。 - 检查所有命令行参数,如果有错误,则打印错误,并退出。
- 设置参数
-server
默认为真。bitcoind 守护进程默认server
为真。 - 调用
InitLogging
函数,初始化系统所用日志,并打印系统的版本信息。具体代码如下,根据是否指定debuglogfile
、printtoconsole
等确定日志打印到文件或是控制台。void InitLogging() { g_logger->m_print_to_file = !gArgs.IsArgNegated("-debuglogfile"); g_logger->m_file_path = AbsPathForConfigVal(gArgs.GetArg("-debuglogfile", DEFAULT_DEBUGLOGFILE));
LogPrintf("\n\n\n\n\n");
g_logger->m_print_to_console = gArgs.GetBoolArg("-printtoconsole", !gArgs.GetBoolArg("-daemon", false)); g_logger->m_log_timestamps = gArgs.GetBoolArg("-logtimestamps", DEFAULT_LOGTIMESTAMPS); g_logger->m_log_time_micros = gArgs.GetBoolArg("-logtimemicros", DEFAULT_LOGTIMEMICROS);
fLogIPs = gArgs.GetBoolArg("-logips", DEFAULT_LOGIPS);
std::string version_string = FormatFullVersion();
LogPrintf(PACKAGE_NAME " version %s\n", version_string); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 调用
InitParameterInteraction
函数,根据参数间的关系,检查所有的交互参数。 - 调用
AppInitBasicSetup
函数,进行基本的设置。如果有错误,则打印错误,然后退出。经过前面漫长的检查与设置,终于开始了应用基本的设置。具体解读见第二部分。 - 调用
AppInitSanityChecks
函数,处理底层加密函数相关内容。具体解读见第二部分。 - 调用
AppInitLockDataDirectory
函数,检查并锁定数据目录。具体解读见第二部分。 - 调用
AppInitMain
函数,比特币主要的启动过程。具体解读见第二部分。 - 如果应用初始化主函数出错,则调用
Interrupt
函数进行中止,否则调用WaitForShutdown
函数等待系统结束。WaitForShutdown
函数是一个无限循环函数。
2、系统启动详述
以下为系统启动过程中重要的步骤。第1步,应用初始化基本设置(src/bitcoind.cpp
)
AppInitBasicSetup
函数进行基本的设置。
- 调用
SetupNetworking
函数,进行网络设置。主要是针对 Win32 系统处理套接字,别的系统直接返回真。 - 如果不是 WIN32 系统,进行下面的处理:
- 如果设置
sysperms
参数为真,调用umask
函数,设置位码为 077。 - 调用
registerSignalHandler
函数,设置SIGTERM
信息处理器为HandleSIGTERM
;SIGINT
为HandleSIGTERM
;SIGHUP
为HandleSIGHUP
。
- 如果设置
第2步,应用初始参数交互设置(src/bitcoind.cpp
)
AppInitParameterInteraction
函数前半部分。
- 首先,调用
Params
方法,获取前面初始化的globalChainParams
区块链对象。 - 检查指定的区块目录是否存。如果不存在,则返回初始化错误。
- 如果同时指定了
prune
、txindex
,则抛出初始化错误。如果指定了区块修剪prune
,就要禁止交易索引txindex
,两者不兼容,只能其一。 - 如果同时指定了
bind
或whitebind
就不能同时指定listen
。 - 确保有足够的文件符可用。因为在类 Unix 系统中,每个套接字都是一个文件,都需要一个文件描述符。所以要检查指定的最大连接数
maxconnections
是否超过系统可用限制。
第3步,参数到内部标志的处理(src/bitcoind.cpp
)
AppInitParameterInteraction
函数后半部分。
- 处理
debug
、debugexclude
、debugnet
等参数。 - 如果指定了
socks
,则提示使用 SOCKS5 - 如果指定了
tor
,则提示使用onion
。 - 如果指定了
benchmark
,则提示使用-debug=bench
。 - 如果指定了
whitelistalwaysrelay
,则提示使用whitelistrelay
,或whitelistforcerelay
。 - 如果指定了
blockminsize
,则提示使用blockminsize
。 - 在回归模式
regtest
下,Checkmempool
和checkblockindex
默认为真。 - 处理
assumevalid
参数。 - 根据是否指定
minimumchainwork
,计算最小区块链工作量。 - 计算内存池限制,包括处理
maxmempool
、limitdescendantsize
- 如果指定了
incrementalrelayfee
,则进行相关处理。 - 处理
par
参数。 - 处理区块修剪参数
prune
。 - 处理连接超时时间
timeout
。 - 处理
minrelaytxfee
参数。 - 处理
blockmintxfee
参数。 - 处理
dustrelayfee
参数。 - 处理
acceptnonstdtxn
参数。 - 处理
bytespersigop
参数。 - 调用钱包初始接口对象的
ParameterInteraction
方法,初始钱包相关的参数。本方法在wallet/init.cpp
文件中。- 检查是否禁止钱包
disablewallet
。 - 设置
wallet
在没有指定情况默认为空字符串。 - 处理
blocksonly
、walletbroadcast
参数。 - 处理
salvagewallet
、rescan
参数。 - 处理
zapwallettxes
、persistmempool
参数。 - 处理
upgradewallet
参数。 - 处理
maxtxfee
参数。
- 检查是否禁止钱包
- 获取
permitbaremultisig
、datacarrier
、datacarriersize
等参数的值。 - 调用
SetMockTime
方法,设置模拟时间。 - 根据
peerbloomfilters
参数,设置本地支持的服务。 - 检测
rpcserialversion
参数是否小于0,是否大于1。 - 获取
maxtipage
参数值,表示区块链顶端值存活时间。 - 处理
mempoolreplacement
参数。 - 处理
vbparams
参数。
第4步,检查相关的加密函数(src/bitcoind.cpp
)
AppInitSanityChecks
函数初始相关的加密曲线与函数。
同时,调用 LockDataDirectory
函数,锁定数据目录,确保只有 Bitcoind 在运行。
第4a 步,应用程序初始化(src/init.cpp::AppInitMain()
)
AppInitMain
函数是应用初始化的主体,包括本步骤在内的以下步骤的主体都是在这个函数内部执行。
- 调用
Params
函数,获取chainparams
。方法定义在src/chainparams.cpp
文件中。这个变量主要是包含一些共识的参数,自身是根据选择不同的网络main
、testnet
或者regtest
来生成不同的参数。 - 如果是非 Windows 系统,则调用
CreatePidFile
函数,创建进程的PID文件。pid 文件简介如下:- pid文件的内容pid文件为文本文件,内容只有一行, 记录了该进程的ID。 用cat命令可以看到。
- pid文件的作用防止进程启动多个副本。只有获得pid文件(固定路径固定文件名)写入权限(F_WRLCK)的进程才能正常启动并把自身的PID写入该文件中。其它同一个程序的多余进程则自动退出。
- 如果命令行指定了
shrinkdebugfile
参数或默认的调试文件,则调用日志对象的ShrinkDebugFile
方法,处理debug.log
文件。如果日志长度小于11MB,那么就不做处理;否则读取文件的最后RECENT_DEBUG_HISTORY_SIZE
10M 内容,重新保存到debug.log文件中。 - 调用日志对象的
OpenDebugLog
方法,打开日志文件。如果不能打开则抛出异常。 - 调用
InitSignatureCache
函数,设置签名缓冲区大小。 - 调用
InitScriptExecutionCache
函数,设置脚本执行缓存区大小。 - 根据
nScriptCheckThreads
变量的值,循环调用threadGroup.create_thread
方法,创建指定数量的线程,并放入线程组。nScriptCheckThreads
变量在前面根据命令行参数par
进行设置。线程内部调用ThreadScriptCheck
函数进行执行。ThreadScriptCheck
函数过程如下:- 首先调用
RenameThread
函数(内部调用pthread_setname_np
函数)将当前线程重命名为bitcoin-scriptch
。 - 然后调用
CCheckQueue
队列对象的Thread
方法,开启内部循环。Thread
方法又调用内部私有方法Loop
方法,生成一个脚本验证工作者,然后进行无限循环,在循环内部调用工作者的wait(lock)
方法,从而线程进入阻塞,直到有新的任务被加到队列中中时,才会被唤醒执行任务。
- 首先调用
- 调用
boost::bind
方法,生成CScheduler
对象serviceQueue
方法的替代方法。然后调用threadGroup.create_thread
方法,创建一个线程。线程执行的方法是boost::bind
返回的替代方法,bind
方法的第一个参数为TraceThread
函数,第二个参数为线程的名字,第三个参数为serviceQueue
方法的替代方法。TraceThread
函数内部调用RenameThread
方法修改线程名字,此处线程名字修改为bitcoin-scheduler
;然后执行传入的可调用对象,此处为前面的替代方法,即CScheduler
对象serviceQueue
方法。serviceQueue
方法主体是一个无限循环方法,如果队列为空,则进程进入阻塞,直到队列有任务,则醒来执行任务,并把任务从队列中移除。 - 调用
GetMainSignals().RegisterBackgroundSignalScheduler
方法,注册后台信号调度器。 - 调用
GetMainSignals().RegisterWithMempoolSignals
方法,注册内存池信号处理器。 - 调用内联函数
RegisterAllCoreRPCCommands
,注册所有核心的 RPC 命令。第一步,调用RegisterBlockchainRPCCommands
方法,注册所有关于区块链的 RPC 命令。第二步,调用RegisterNetRPCCommands
方法,注册所有关于网络相关的 RPC 命令。第三步,调用RegisterMiscRPCCommands
方法,注册所有的杂项 RPC 命令。第四步,调用RegisterMiningRPCCommands
方法,注册所有关于挖矿相关的 RPC 命令。第五步,调用RegisterRawTransactionRPCCommands
方法,注册所有关于原始交易的 RPC 命令。 - 调用钱包接口的
RegisterRPC
方法,注册钱包接口的 RPC 命令。实现类为wallet/init.cpp
,方法内部调用RegisterWalletRPCCommands
进行注册,后者又调用wallet/rpcwallet.cpp
文件中的RegisterWalletRPCCommands
方法,完成注册钱包的 RPC 命令。 - 如果命令参数指定
server
,则调用AppInitServers
方法,注册服务器。方法内处理流程如下:- 调用
RPCServer::OnStarted
方法,设置 RPC 服务器启动时的处理方法。 - 调用
RPCServer::OnStopped
方法,设置 RPC 服务器关闭时的处理方法。 - 调用
InitHTTPServer
方法,初始化 HTTP 服务器。 - 调用
StartRPC
方法,启动 RPC 信号监听。 - 调用
StartHTTPRPC
方法,启动 HTTP RPC 服务器。方法内部调用RegisterHTTPHandler
方法,注册/
请求处理方法为HTTPReq_JSONRPC
。调用RegisterHTTPHandler
方法,注册/wallet/
请求处理方法为HTTPReq_JSONRPC
。 - 如果命令参数指定
rest
,调用StartREST
方法,设置/rest/xxx
一系列 HTTP 请求的处理器。 - 调用
StartHTTPServer
方法,启动 HTTP 服务器。
- 调用
第5步,验证钱包数据库完整性(src/init.cpp::AppInitMain()
)
调用钱包接口的 Verify
方法,验证钱包数据库。实现类为 wallet/init.cpp
,内部处理流程如下:
- 检查命令行指定了禁止钱包
disablewallet
,如果禁止,则直接返回。 - 如果设置了钱包
walletdir
,则检查钱包数据库目录是否存在,是否为目录、且是否为常规的的路径。 - 检查所有的钱包文件:不存在名称相同的,没有问题(调用
CWallet::Verify
方法进行验证)。
第6步,网络初始化(src/init.cpp::AppInitMain()
)
- 根据命令行参数
uacomment
,处理用户代理。 - 检查版本是否大于
version
消息指定的消息最大长度。 - 如果指定了
onlynet
参数,则设置仅可以连接的节点。 - 如果指定了代理
proxy
,且不等于 0,则:根据代理参数、dns
查找等,调用Lookup
方法,查找/设置代理服务器;调用SetProxy
方法,设置 IPv4、IPv6、Tor 网络的代理;调用SetNameProxy
方法,设置命名(域名)代理;调用SetLimited
方法,设置不自动连接到 Tor 网络。 - 如果指定了
onion
参数,则处理洋葱网络的相关设置。 - 处理通过
externalip
参数设置的外部 IP,调用Lookup
方法查找外部地址,如果成功则调用AddLocal
方法,保存新的地址。 - 如果设置了
maxuploadtarget
参数,则设置最大出站限制。
第7步,加载区块链(src/init.cpp::AppInitMain()
)
首先,计算缓存的大小。包括:区块索引数据库、区块状态数据库、内存中 UTXO 集。
然后,以下开始循环处理。
- 调用
UnloadBlockIndex
方法,卸载区块相关的索引。 - 重置 Coin 相关的结构。
- 调用
LoadBlockIndex
方法,加载区块索引。 - 调用
LookupBlockIndex
方法,加载区块索引,并检查是否包含创世区块。如果出错,则返回异常。 - 如果指定有修剪,但又没有处于修剪模式,则退出循环。
- 如果不重建索引,调用
LoadGenesisBlock
加载创世区块失败,则退出循环。 - 检查是否需要升级数据库格式。
第8步,开始索引(src/init.cpp::AppInitMain()
)
如果指定了 txindex
参数,则调用 MakeUnique
函数,生成交易索引对象,然后调用其 Start
方法,开始建立索引。
第9步,加载钱包(src/init.cpp::AppInitMain()
)
调用钱包接口对象的 Open
方法,开始加载钱包。
第10,数据目录维护(src/init.cpp::AppInitMain()
)
如果当前为修剪模式,本地服务去掉 NODE_NETWORK
,然后如果不需要索引则调用 PruneAndFlush
函数,修剪并刷新。
第11步,导入区块(src/init.cpp::AppInitMain()
)
- 调用
CheckDiskSpace
函数,检查硬盘空间是否足够。如果没有足够的硬盘空间,则退出。 - 检查最佳区块链顶端指示指针。如果顶端打针为空,UI界面进行通知。如果不空,则设置有创世区块,即
fHaveGenesis
设为真。 - 如果指定了
blocknotify
参数,设置界面通知为BlockNotifyCallback
。 - 遍历参数
loadblock
指定要加载的区块文件,放进向量变量vImportFiles
中。然后调用threadGroup.create_thread
方法,创建一个线程。线程执行的函数为ThreadImport
,参数为要加载的区块文件。 - 获取
cs_GenesisWait
锁,等待创世区块被处理完成。
第12步,启动节点(src/init.cpp::AppInitMain()
)
- 如果指定了监听洋葱网络
listenonion
,调用StartTorControl
函数,开始 Tor 控制。 - 调用
Discover
函数,开始发现外部节点。 - 如果指定了
upnp
参数,则调用StartMapPort
函数,开始进行端口映射。 - 生成选项对象,并进行初始化。
- 如果指定了
bind
参数,则对所有的绑定地址,调用Lookup
方法,查找并进行绑定,然后放入选项对象的vBinds
属性中。 - 如果指定了
whitebind
参数,则对所有的绑定地址,调用Lookup
方法,查找并进行绑定,然后放入选项对象的vWhiteBinds
属性中。whitebind
参数指定的地址需要带有端口号。 - 如果指定了
whitelist
参数,遍历列表,调用LookupSubNet
方法,生成对应的子网,然后放入选项对象的vWhitelistedRange
属性中。 - 取得参数
seednode
指定的值,放入选项对象的vSeedNodes
属性中。 - 调用
CConnman
对象的Start
方法,初始所有的出站连接。本方法非常非常重要,因为它启动了一个重要的流程,即底层的 P2P 网络建立和消息处理流动。
第13步,结束启动(src/init.cpp::AppInitMain()
)
- 调用钱包接口对象的
Start
方法,开始进行钱包相关的处理,并定时刷新钱包数据到数据库中。
我是区小白,区块链开发者,区块链技术爱好者,深入研究比特币,以太坊,EOS Dash,Rsk,Java, Nodejs,PHP,Python,C++ 现为Ulord全球社区联盟(优得社区)核心开发者。
我希望能聚集更多区块链开发者,一起学习共同进步。
敬请期待下一篇文章:如何启动比特币系统并加入比特币网络
转载地址:https://www.8btc.com/article/273447