从零开始学习区块链技术(二)--如何接入比特币网络以及其原理分析

版权声明:本文为博主原创文章,未经博主允许不得转载。

如何接入比特币网络以及原理分析

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. SetupEnvironment 函数,主要用来设置系统的环境变量,包括:malloc 分配内存的行为、Locale、文件路径的本地化设置等。
  2. noui_connect 函数,设置连接到 bitcoind 的信号的处理。
  3. AppInit 函数,进行系统启动。

下面我们重点讲下 AppInit 函数的执行

  1. 调用 SetupServerArgs 函数,设置系统可接受的所有命令行参数。然后开始解析命令行传递的各种参数。

    系统执行的重要一步就是设置可以接收的参数并解析用户启动时传递的各种参数,SetupServerArgs 函数就是完成这个目的。下面来看这个函数的执行流程。

    • 首先,调用 CreateBaseChainParams 函数,生成默认的基本参数,包括:使用的数据目录和监听的端口。根据不同的网络类型,主网络使用 8332 端口和指定目录下的当前目录,测试网络使用 18332 端口和指定目录下的 testnet3 子目录,回归测试网络 使用 18443 端口和指定目录下的 regtest 子目录。

    • 然后,调用 CreateChainParams 函数,生成默认的区块链参数。这个方法也会区分不同的网络。

      如果是主网络,则生成 CMainParams 对象进行初始化。在构造函数中,进行如下的设置:

      • 设置网络ID 为 main
      • 设置共识参数(Consensus::Params)的各个值:
      • 每隔多少个块(nSubsidyHalvingInterval)后续比特币的奖励会减半,值为 210000。根据创世区块奖励的数量(50),根据等比数列求和公式:
        50 ( 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.bednsseed.bluematt.mednsseed.bitcoin.dashjr.orgseed.bitcoinstats.comseed.bitcoin.jonasschnelli.chseed.btc.petertodd.orgseed.bitcoin.sprovoost.nl 等,通过解析 DNS 种子节点,比特币节点启动时可以找到更多的对等节点来进行连接。
      • 接下来,设置相关的检查点数据。

      如果是测试网络,则生成 CTestNetParams 对象进行初始化。(供开发完成后测试使用。)

      如果是回归测试网络,则生成 CRegTestParams 对象进行初始化。(供开发时连接使用。)

      对于这两种测试网络,处理基本和主网络相同,只是某些参数不一样。

      上面提到的3个对象 CMainParams CTestNetParams CRegTestParams的定义都在 chainparams.cpp 文件中。感兴趣同学的可以对照源代码进一步探究。

    • 接下来,设置系统可接收的所有参数。

      部分参数解释如下:

      • ?,显示帮助信息;
      • -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 调用的端口

    上面是一些常用的参数,通过这些参数可以影响比特币核心的命令。应用开发者比较关注的是 RPC 相关的设置,通过 RPC 接口,我们调用比特币核心提供的多种服务。这些命令通常会在配置文件中进行设置,不用在命令行指定。

  2. 接下来,检查用户指定命令参数是否正确。

     if (!gArgs.ParseParameters(argc, argv, error)) {
         fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str());
         return false;
     }
  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;
     }

    GetDataDir 方法中,根据用户是否在命令行提供 datadir 参数来确定使用默认的数据目录还是用户指定的数据目录。

  5. 读取并解析配置文件,同时检查指定数据目录是否存在。如果任何一个步骤出错,都打印错误信息,然后退出。

     if (!gArgs.ReadConfigFiles(error, true)) {
         fprintf(stderr, "Error reading configuration file: %s\n", error.c_str());
         return false;
     }

    其中 ReadConfigFiles 方法具体处理如下:

    • 首先,调用 GetArg 方法,获取配置文件名称,默认为 bitcoin.conf
    • 然后,通过 GetConfigFile 方法获取配置文件的绝对路径(方法内部会委托 AbsPathForConfigVal 方法进行处理,后者决定根据用户指定的路径或使用默认路径来生成配置文件的绝对路径)。在得到配置文件的绝对路径之后,构造文件输入流,从而读取配置文件 fs::ifstream stream(GetConfigFile(confPath))
    • 在成功构造输入流之后,调用 ReadConfigStream 方法开始读取配置文件的内容。方法内部按行读取配置文件,并以键值对的形式保存在 m_config_args 集合中。
  6. 调用 SelectParams(gArgs.GetChainName()) 函数,生成全局的区块链参数,并设置系统的网络类型。如果有错误,则打印错误,然后退出。

    gArgs.GetChainName() 方法会返回当前使用的网络。针对主网络,返回字符串 main;测试网络,返回字符串 test;回归测试网络,返回字符串 regtest

    SelectParams 方法的实现如下所示:

    void SelectParams(const std::string& network)
    {
       SelectBaseParams(network);
       globalChainParams = CreateChainParams(network);
    }

    SelectBaseParams 方法会根据指定的网络参数生成 CBaseChainParams 对象,并保存在 globalChainBaseParams 变量中,并在指定 gArgs 对象中保存网络类型(m_network 属性)。CBaseChainParams 对象中仅保存系统的数据目录和运行的端口,所以称之为基本区块链参数对象。

    CreateChainParams 方法会根据不同的网络参数生成 CChainParams 类的子对象,可能为以下三种:CMainParams、CTestNetParams、CRegTestParams。CChainParams 对象包含了区块链对象的所有重要信息,比如:共识规则、部署状态、检查点、创世区块等。

  7. 检查所有命令行参数,如果有错误,则打印错误,并退出。

  8. 设置参数 -server 默认为真。

    bitcoind 守护进程默认 server 为真。

  9. 调用 InitLogging 函数,初始化系统所用日志,并打印系统的版本信息。

    具体代码如下,根据是否指定 debuglogfileprinttoconsole 等确定日志打印到文件或是控制台。

    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);
    }
  10. 调用 InitParameterInteraction 函数,根据参数间的关系,检查所有的交互参数。

  11. 调用 AppInitBasicSetup 函数,进行基本的设置。如果有错误,则打印错误,然后退出。

    经过前面漫长的检查与设置,终于开始了应用基本的设置。具体解读见第二部分。

  12. 调用 AppInitSanityChecks 函数,处理底层加密函数相关内容。

    具体解读见第二部分。

  13. 调用 AppInitLockDataDirectory 函数,检查并锁定数据目录。

    具体解读见第二部分。

  14. 调用 AppInitMain 函数,比特币主要的启动过程。

    具体解读见第二部分。

  15. 如果应用初始化主函数出错,则调用 Interrupt 函数进行中止,否则调用 WaitForShutdown 函数等待系统结束。

    WaitForShutdown 函数是一个无限循环函数。

2、系统启动详述

以下为系统启动过程中重要的步骤。

第1步,应用初始化基本设置(src/bitcoind.cpp

AppInitBasicSetup 函数进行基本的设置。

  1. 调用 SetupNetworking 函数,进行网络设置。

    主要是针对 Win32 系统处理套接字,别的系统直接返回真。

  2. 如果不是 WIN32 系统,进行下面的处理:

    • 如果设置 sysperms 参数为真,调用 umask 函数,设置位码为 077。
    • 调用 registerSignalHandler 函数,设置 SIGTERM 信息处理器为 HandleSIGTERMSIGINTHandleSIGTERMSIGHUPHandleSIGHUP

第2步,应用初始参数交互设置(src/bitcoind.cpp

AppInitParameterInteraction 函数前半部分。

  1. 首先,调用 Params 方法,获取前面初始化的 globalChainParams 区块链对象。

  2. 检查指定的区块目录是否存。如果不存在,则返回初始化错误。

  3. 如果同时指定了 prunetxindex,则抛出初始化错误。

    如果指定了区块修剪 prune,就要禁止交易索引 txindex,两者不兼容,只能其一。

  4. 如果同时指定了 bindwhitebind 就不能同时指定 listen

  5. 确保有足够的文件符可用。因为在类 Unix 系统中,每个套接字都是一个文件,都需要一个文件描述符。所以要检查指定的最大连接数 maxconnections 是否超过系统可用限制。

第3步,参数到内部标志的处理(src/bitcoind.cpp

AppInitParameterInteraction 函数后半部分。

  1. 处理 debugdebugexcludedebugnet 等参数。
  2. 如果指定了 socks,则提示使用 SOCKS5
  3. 如果指定了 tor,则提示使用 onion
  4. 如果指定了 benchmark,则提示使用 -debug=bench
  5. 如果指定了 whitelistalwaysrelay,则提示使用 whitelistrelay,或whitelistforcerelay
  6. 如果指定了 blockminsize,则提示使用 blockminsize
  7. 在回归模式regtest下,Checkmempoolcheckblockindex 默认为真。
  8. 处理 assumevalid 参数。
  9. 根据是否指定 minimumchainwork,计算最小区块链工作量。
  10. 计算内存池限制,包括处理 maxmempoollimitdescendantsize
  11. 如果指定了 incrementalrelayfee,则进行相关处理。
  12. 处理 par 参数。
  13. 处理区块修剪参数 prune
  14. 处理连接超时时间 timeout
  15. 处理 minrelaytxfee 参数。
  16. 处理 blockmintxfee 参数。
  17. 处理 dustrelayfee 参数。
  18. 处理 acceptnonstdtxn 参数。
  19. 处理 bytespersigop 参数。
  20. 调用钱包初始接口对象的 ParameterInteraction 方法,初始钱包相关的参数。本方法在 wallet/init.cpp 文件中。
    • 检查是否禁止钱包 disablewallet
    • 设置 wallet 在没有指定情况默认为空字符串。
    • 处理 blocksonlywalletbroadcast 参数。
    • 处理 salvagewalletrescan 参数。
    • 处理 zapwallettxespersistmempool 参数。
    • 处理 upgradewallet 参数。
    • 处理 maxtxfee 参数。
  21. 获取 permitbaremultisigdatacarrierdatacarriersize等参数的值。
  22. 调用 SetMockTime 方法,设置模拟时间。
  23. 根据 peerbloomfilters 参数,设置本地支持的服务。
  24. 检测 rpcserialversion 参数是否小于0,是否大于1。
  25. 获取 maxtipage 参数值,表示区块链顶端值存活时间。
  26. 处理 mempoolreplacement 参数。
  27. 处理 vbparams 参数。

第4步,检查相关的加密函数(src/bitcoind.cpp

AppInitSanityChecks 函数初始相关的加密曲线与函数。

同时,调用 LockDataDirectory 函数,锁定数据目录,确保只有 Bitcoind 在运行。

第4a 步,应用程序初始化(src/init.cpp::AppInitMain()

AppInitMain 函数是应用初始化的主体,包括本步骤在内的以下步骤的主体都是在这个函数内部执行。

  1. 调用 Params 函数,获取 chainparams

    方法定义在 src/chainparams.cpp 文件中。这个变量主要是包含一些共识的参数,自身是根据选择不同的网络 maintestnet 或者 regtest 来生成不同的参数。

  2. 如果是非 Windows 系统,则调用 CreatePidFile 函数,创建进程的PID文件。

    pid 文件简介如下:

    • pid文件的内容

      pid文件为文本文件,内容只有一行, 记录了该进程的ID。 用cat命令可以看到。

    • pid文件的作用

      防止进程启动多个副本。只有获得pid文件(固定路径固定文件名)写入权限(F_WRLCK)的进程才能正常启动并把自身的PID写入该文件中。其它同一个程序的多余进程则自动退出。

  3. 如果命令行指定了 shrinkdebugfile 参数或默认的调试文件,则调用日志对象的 ShrinkDebugFile 方法,处理 debug.log 文件。

    如果日志长度小于11MB,那么就不做处理;否则读取文件的最后 RECENT_DEBUG_HISTORY_SIZE 10M 内容,重新保存到debug.log文件中。

  4. 调用日志对象的 OpenDebugLog 方法,打开日志文件。如果不能打开则抛出异常。

  5. 调用 InitSignatureCache 函数,设置签名缓冲区大小。

  6. 调用 InitScriptExecutionCache 函数,设置脚本执行缓存区大小。

  7. 根据 nScriptCheckThreads 变量的值,循环调用 threadGroup.create_thread 方法,创建指定数量的线程,并放入线程组。

    nScriptCheckThreads 变量在前面根据命令行参数 par 进行设置。

    线程内部调用 ThreadScriptCheck 函数进行执行。 ThreadScriptCheck 函数过程如下:

    • 首先调用 RenameThread 函数(内部调用 pthread_setname_np 函数)将当前线程重命名为 bitcoin-scriptch

    • 然后调用 CCheckQueue 队列对象的 Thread 方法,开启内部循环。

      Thread 方法又调用内部私有方法 Loop 方法,生成一个脚本验证工作者,然后进行无限循环,在循环内部调用工作者的 wait(lock) 方法,从而线程进入阻塞,直到有新的任务被加到队列中中时,才会被唤醒执行任务。

  8. 调用 boost::bind 方法,生成 CScheduler 对象 serviceQueue 方法的替代方法。然后调用 threadGroup.create_thread 方法,创建一个线程。

    线程执行的方法是 boost::bind 返回的替代方法,bind 方法的第一个参数为 TraceThread 函数,第二个参数为线程的名字,第三个参数为serviceQueue 方法的替代方法。

    TraceThread 函数内部调用 RenameThread 方法修改线程名字,此处线程名字修改为 bitcoin-scheduler;然后执行传入的可调用对象,此处为前面的替代方法,即 CScheduler 对象 serviceQueue 方法。

    serviceQueue 方法主体是一个无限循环方法,如果队列为空,则进程进入阻塞,直到队列有任务,则醒来执行任务,并把任务从队列中移除。

  9. 调用 GetMainSignals().RegisterBackgroundSignalScheduler 方法,注册后台信号调度器。

  10. 调用 GetMainSignals().RegisterWithMempoolSignals 方法,注册内存池信号处理器。

  11. 调用内联函数 RegisterAllCoreRPCCommands ,注册所有核心的 RPC 命令。

    第一步,调用 RegisterBlockchainRPCCommands 方法,注册所有关于区块链的 RPC 命令。

    第二步,调用 RegisterNetRPCCommands 方法,注册所有关于网络相关的 RPC 命令。

    第三步,调用 RegisterMiscRPCCommands 方法,注册所有的杂项 RPC 命令。

    第四步,调用 RegisterMiningRPCCommands 方法,注册所有关于挖矿相关的 RPC 命令。

    第五步,调用 RegisterRawTransactionRPCCommands 方法,注册所有关于原始交易的 RPC 命令。

  12. 调用钱包接口的 RegisterRPC 方法,注册钱包接口的 RPC 命令。

    实现类为 wallet/init.cpp ,方法内部调用 RegisterWalletRPCCommands 进行注册,后者又调用 wallet/rpcwallet.cpp 文件中的 RegisterWalletRPCCommands 方法,完成注册钱包的 RPC 命令。

  13. 如果命令参数指定 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()

  1. 根据命令行参数 uacomment,处理用户代理。
  2. 检查版本是否大于 version 消息指定的消息最大长度。
  3. 如果指定了 onlynet 参数,则设置仅可以连接的节点。
  4. 如果指定了代理 proxy,且不等于 0,则:根据代理参数、dns查找等,调用 Lookup 方法,查找/设置代理服务器;调用 SetProxy 方法,设置 IPv4、IPv6、Tor 网络的代理;调用 SetNameProxy 方法,设置命名(域名)代理;调用 SetLimited 方法,设置不自动连接到 Tor 网络。
  5. 如果指定了 onion 参数,则处理洋葱网络的相关设置。
  6. 处理通过 externalip 参数设置的外部 IP,调用 Lookup 方法查找外部地址,如果成功则调用 AddLocal 方法,保存新的地址。
  7. 如果设置了 maxuploadtarget 参数,则设置最大出站限制。

第7步,加载区块链(src/init.cpp::AppInitMain()

首先,计算缓存的大小。包括:区块索引数据库、区块状态数据库、内存中 UTXO 集。

然后,以下开始循环处理。

  1. 调用 UnloadBlockIndex 方法,卸载区块相关的索引。
  2. 重置 Coin 相关的结构。
  3. 调用 LoadBlockIndex 方法,加载区块索引。
  4. 调用 LookupBlockIndex 方法,加载区块索引,并检查是否包含创世区块。如果出错,则返回异常。
  5. 如果指定有修剪,但又没有处于修剪模式,则退出循环。
  6. 如果不重建索引,调用 LoadGenesisBlock 加载创世区块失败,则退出循环。
  7. 检查是否需要升级数据库格式。

第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()

  1. 调用 CheckDiskSpace 函数,检查硬盘空间是否足够。

    如果没有足够的硬盘空间,则退出。

  2. 检查最佳区块链顶端指示指针。

    如果顶端打针为空,UI界面进行通知。如果不空,则设置有创世区块,即 fHaveGenesis 设为真。

  3. 如果指定了 blocknotify 参数,设置界面通知为 BlockNotifyCallback

  4. 遍历参数 loadblock 指定要加载的区块文件,放进向量变量 vImportFiles中。然后调用 threadGroup.create_thread 方法,创建一个线程。线程执行的函数为 ThreadImport,参数为要加载的区块文件。

  5. 获取 cs_GenesisWait 锁,等待创世区块被处理完成。

第12步,启动节点(src/init.cpp::AppInitMain()

  1. 如果指定了监听洋葱网络 listenonion,调用 StartTorControl 函数,开始 Tor 控制。

  2. 调用 Discover 函数,开始发现外部节点。

  3. 如果指定了 upnp 参数,则调用 StartMapPort 函数,开始进行端口映射。

  4. 生成选项对象,并进行初始化。

  5. 如果指定了 bind 参数,则对所有的绑定地址,调用 Lookup 方法,查找并进行绑定,然后放入选项对象的 vBinds 属性中。

  6. 如果指定了 whitebind 参数,则对所有的绑定地址,调用 Lookup 方法,查找并进行绑定,然后放入选项对象的 vWhiteBinds 属性中。

    whitebind 参数指定的地址需要带有端口号。

  7. 如果指定了 whitelist 参数,遍历列表,调用 LookupSubNet 方法,生成对应的子网,然后放入选项对象的 vWhitelistedRange 属性中。

  8. 取得参数 seednode 指定的值,放入选项对象的 vSeedNodes 属性中。

  9. 调用 CConnman 对象的 Start 方法,初始所有的出站连接

    本方法非常非常重要,因为它启动了一个重要的流程,即底层的 P2P 网络建立和消息处理流动。

第13步,结束启动(src/init.cpp::AppInitMain()

  1. 调用钱包接口对象的 Start 方法,开始进行钱包相关的处理,并定时刷新钱包数据到数据库中。

我是区小白,区块链开发者,区块链技术爱好者,深入研究比特币,以太坊,EOS Dash,Rsk,Java, Nodejs,PHP,Python,C++ 现为Ulord全球社区联盟(优得社区)核心开发者。

我希望能聚集更多区块链开发者,一起学习共同进步。

敬请期待下一篇文章:如何启动比特币系统并加入比特币网络

猜你喜欢

转载自blog.csdn.net/Ulord_123/article/details/82226428