从零开始学习比特币开发(四):网络初始化,加载区块链和钱包,导入区块启动节点

当前位置:首页 > 币圈百科 > 从零开始学习比特币开发(四):网络初始化,加载区块链和钱包,导入区块启动节点

从零开始学习比特币开发(四):网络初始化,加载区块链和钱包,导入区块启动节点

2023-02-04币圈百科215

第6步,网络初始化(src/init。CPP:AppInitMain())

生成智能指针对象科曼,类型为?科曼。g _ connman=STD:unique _ ptr(new CConnman(GetRand(STD:numeric _ limits:max())),GetRand(STD:numeric _ limits:max());科曼科曼=* g _科曼生成智能指针对象peerLogic,类型为?对等逻辑验证peerLogic.reset(新peerlogicalvalidation(connman,scheduler,gArgs .GetBoolArg('-enablebip61 'DEFAULT _ ENABLE _ bip 蓑衣网小编2022 61)));

对等逻辑验证继承了CValidationInterface、NetEventsInterface两个类。实现CValidationInterface这个类可以订阅验证过程中产生的事件。实现网络事件界面这个类可以处理消息网络消息。注册各种验证处理器,即信号处理器,在发送信号时会调用这些处理器RegisterValidationInterface(对等逻辑。get());

方法具体实现如下:

void RegisterValidationInterface(CValidationInterface * pwalletIn){ g _ signals。m _ internals-UpdatedBlockTip。connect(boost:bind(CValidationInterface:UpdatedBlockTip,pwalletIn,_1,_2,_ 3));g _信号。m _ internals-transactionadddtomempool。connect(boost:bind(CValidationInterface:transactionadddtomepool,pwalletIn,_ 1));g _信号。m _内部构件-块连接。connect(boost:bind(CValidationInterface:block connected,pwalletIn,_1,_2,_ 3));g _信号。m _ internals-block已断开。connect(boost:bind(CValidationInterface:block disconnected,pwalletIn,_ 1));g _信号。m _ internals-transactionremovedfromenpool。connect(boost:bind(CValidationInterface:transactionremovedfromempool,pwalletIn,_ 1));g _信号。m _ internals-链状态已刷新。connect(boost:bind(CValidationInterface:chain state flushed,pwalletIn,_ 1));g _信号。m _ internals-广播。connect(boost:bind(CValidationInterface:ResendWalletTransactions,pwalletIn,_1,_ 2));g _信号。m _ internals-block已检查。connect(boost:bind(CValidationInterface:block checked,pwalletIn,_1,_ 2));g _信号。m _ internals-NewPoWValidBlock。connect(boost:bind(CValidationInterface:NewPoWValidBlock,pwalletIn,_1,_ 2));}

静态变量g _信号在程序启动前生成,m _内部构件在第4a步应用程序初始化过程中生成。根据命令行参数?-uacomment,处理追加到用户代理的字符串STD:vector ua注释;for (const std:string cmt : gArgs .GetArgs('-ua comment '){ if(CMT!=sanitis string(CMT,SAFE_CHARS_UA_COMMENT))返回InitError(strprintf(_('用户代理注释(%s)包含不安全的字符,CMT);ua评论。push _ back(CMT);}构造并检查版本字符串长度是否大于?版本?消息中版本的最大长度strSubVersion=格式SubVersion(客户端名称,客户端版本,ua注释);if(strsubversion。size()MAX _ SUBVERSION _ LENGTH){ return init error(str printf(_('网络版本字符串总长度(%i))超过最大长度(%i).减少ua注释的数量或大小3.1)、strSubVersion.size()、MAX _ SUBVERSION _ LENGTH);}如果指定了?onlynet?参数,则设置可以对接进行连接的类型,比如:ipv4、ipv6、洋葱。如果(加格斯IsArgSet('-only net '){ STD:set nets;for (const std:string snet : gArgs .get args('-only net '){ enum Network net=parse Network(snet);if (net==NET_UNROUTABLE)返回InitError(strprintf(_('在-onlynet中指定的未知网络:' %s ' '),snet));网。插入(网);} for(int n=0;NET _ MAX { enum Network NET=(enum Network)n;如果(!网。count(net))set limited(net);} }

上面的代码首先把?-onlynet?参数指定的只允许对外连接的网络类型加入集合中,然后进行为遍历,如果当前的类型不在允许的集合中,则调用?SetLimited?方法,设置这些类型为受限的。 获取是否允许进行域名服务器(域名服务器)查找,是否进行代理随机fNameLookup=gArgs .GetBoolArg('-dns 'DEFAULT _ NAME _ LOOKUP);bool proxyRandomize=gArgs .GetBoolArg('-proxyrandomize 'DEFAULT _ PROXYRANDOMIZE);

两者默认都为真。处理网络代理。如果指定了?-代理人,且不等于0,即指定了代理地址,进行下面的处理:调用?查找?方法,根据指定的代理,通过域名服务器(域名服务器)查找,发现代理服务器的地址。生成代理类型对象。设置IPv4、IPv6、Tor网络的代理。设置命名(域名)代理。设置不限制连接到突岩网络十.具体代码如下:

std:string proxyArg=gArgs .GetArg('-proxy '' ');set limited(NET _ ONION);if (proxyArg!='' proxyArg!=' 0 '){ CService proxyAddr;如果(!Lookup(proxyArg.c_str(),proxyAddr,9050,fname lookup)){ return init error(str printf(_('无效-代理地址或主机名:' %s ' '),proxy arg));}代理类型addr proxy=代理类型(proxyAddr,proxyRandomize);如果(!addrProxy .IsValid())返回InitError(strprintf(_('无效-代理地址或主机名:' %s ' '),proxy arg));SetProxy(NET_IPV4,addr proxy);SetProxy(NET_IPV6,addr proxy);SetProxy(NET_ONION,addr proxy);setname代理(addr代理);SetLimited(NET_ONION,false);//默认情况下,-代理将洋葱设置为可到达,除非-noonion稍后}处理洋葱网络。如果指定了?洋葱?参数,则处理洋葱网络的相关设置。如果指定了?洋葱,且不等于空字符串,即指定了洋葱代理地址,进行下面的处理:如果参数等于0,设置洋葱网络受限,即不可达。否则,进行下面的处理。调用?查找?方法,根据指定的代理,通过域名服务器(域名服务器)查找,发现代理服务器的地址。生成代理类型对象。设置突岩网络的代理。设置不限制连接到突岩网络十.具体代码如下:

std:string onionArg=gArgs .格塔格('-洋葱'' ');如果(onionArg!=' '){ if(ONION arg==' 0 '){//Handle-noonion/-ONION=0 set limited(NET _ ONION);//将洋葱设置为不可达} else { CService onionProxy如果(!Lookup(onionArg.c_str(),onionProxy,9050,fname lookup)){ return init error(str printf(_(' Invalid-onion地址或主机名:' %s ' '),onion arg));}代理类型addrOnion=代理类型(onion proxy,proxyRandomize);如果(!阿德罗尼安. IsValid())返回初始化错误(strprintf(_('无效-洋葱地址或主机名:' %s ' '),onion arg));SetProxy(NET_ONION,addrOnion);SetLimited(NET_ONION,false);}}处理通过?-externalip?参数设置的外部互联网协议(互联网协议)地址。获取并遍历所有指定的外部地址,进行如下处理:调用?查找?方法进行域名服务器(域名服务器)查找。如果成功则调用?AddLocal?方法,保存新的地址。否则,抛出初始化错误for(const STD:string strAddr:gArgs .get args('-external IP '){ CService addr local;if (Lookup(strAddr.c_str()、addrLocal、GetListenPort()、fNameLookup) addrLocal .IsValid()) AddLocal(addrLocal,LOCAL _ MANUAL);否则返回init错误(resolveermsg('外部IP 'strAddr));}如果设置了?maxuploadtarget?参数,则设置最大出站限制。如果(加格斯IsArgSet('-maxuploadtarget '){ nMaxOutboundLimit=gArgs .GetArg('-maxuploadtarget 'DEFAULT _ MAX _ UPLOAD _ TARGET)* 1024 * 1024;}

第七步,加载区块链(src/init。CPP:AppInitMain())

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

fReindex=gArgs .GetBoolArg('-reindex 'false);bool fReindexChainState=gArgs .GetBoolArg('-reindex-chainstate", false);// cache size calculationsint64_t nTotalCache = (gArgs.GetArg("-dbcache", nDefaultDbCache) << 20);nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be less than nMinDbCachenTotalCache = std::min(nTotalCache, nMaxDbCache << 20); // total cache cannot be greater than nMaxDbcacheint64_t nBlockTreeDBCache = std::min(nTotalCache / 8, nMaxBlockDBCache << 20);nTotalCache -= nBlockTreeDBCache;int64_t nTxIndexCache = std::min(nTotalCache / 8, gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX) ? nMaxTxIndexCache << 20 : 0);nTotalCache -= nTxIndexCache;int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cachenCoinDBCache = std::min(nCoinDBCache, nMaxCoinsDBCache << 20); // cap total coins db cachenTotalCache -= nCoinDBCache;nCoinCacheUsage = nTotalCache; // the rest goes to in-memory cacheint64_t nMempoolSizeMax = gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;LogPrintf("Cache configuration:\n");LogPrintf("* Using %.1fMiB for block index database\n", nBlockTreeDBCache * (1.0 / 1024 / 1024));if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { LogPrintf("* Using %.1fMiB for transaction index database\n", nTxIndexCache * (1.0 / 1024 / 1024));}

然后,只要加载标志为真且没有收到关闭系统的请求,即进行以下 while 循环。

进行 do … while 循环处理:调用?UnloadBlockIndex?方法,卸载区块相关的索引。重置指向活跃 CCoinsView 的全局智能指针变量 pcoinsTip。重置指向 coins 数据库的全局智能指针变量 蓑衣网小编2022 pcoinsdbview。重置 CCoinsViewErrorCatcher 的智能指针静态变量 pcoinscatcher。重置指向活动区块树的全局智能指针变量 pblocktree。如果?-reset?参数为真,那么:调用活跃区块树 pblocktree 的?WriteReindexing?方法,保存重写索引标志。如果当前处于修剪模式,调用?CleanupBlockRevFiles?方法,清除特定区块的数据文件。if (fReset) { pblocktree->WriteReindexing(true); //If we're reindexing in prune mode, wipe away unusable block files and all undo data files if (fPruneMode) CleanupBlockRevFiles();}如果收到结束请求,则退出循环。 if (ShutdownRequested()) break;调用?LoadBlockIndex?方法,加载区块索引。 if (!LoadBlockIndex(chainparams)) { strLoadError = _("Error loading block database"); break; }如果区块索引成功加载,则检查是否包含区块。 if (!mapBlockIndex.empty() && !LookupBlockIndex(chainparams.GetConsensus().hashGenesisBlock)) { return InitError(_("Incorrect or no genesis block found. Wrong datadir for network?")); }如果指定有修剪,但又没有处于修剪模式,则退出循环。 if (fHavePruned && !fPruneMode) { strLoadError = _("You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain"); break; }如果不重建索引,调用?LoadGenesisBlock?加载创世区块。如果失败,则退出循环。 if (!fReindex && !LoadGenesisBlock(chainparams)) { strLoadError = _("Error initializing block database"); break; }生成两个智能指针对象。 pcoinsdbview.reset(new CCoinsViewDB(nCoinDBCache, false, fReset || fReindexChainState)); pcoinscatcher.reset(new CCoinsViewErrorCatcher(pcoinsdbview.get()));

两个变量的含义见前面说明。升级数据库格式。 if (!pcoinsdbview->Upgrade()) { strLoadError = _("Error upgrading chainstate database"); break; }重放区块,用来处理数据库不一致。 if (!ReplayBlocks(chainparams, pcoinsdbview.get())) { strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate."); break; }当系统走到这一步时,硬盘上的 coinsdb 数据库已经片于一致状态了。现在创建指向活跃 CCoinsView 的全局智能指针变量 pcoinsTip。 pcoinsTip.reset(new CCoinsViewCache(pcoinscatcher.get()));

第8步,开始索引(src/init.cpp::AppInitMain())

如果指定了?-txindex?参数,则生成交易索引对象 g_txindex,类型为?TxIndex;然后调用其?Start?方法,开始建立索引。

if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { g_txindex = MakeUnique(nTxIndexCache, false, fReindex); g_txindex->Start();}

start?方法处理如下:

首先,调用?RegisterValidationInterface?方法注册?TxIndex?为?MainSignalsInstance?上各种事件的信号处理器,在发送信号时会调用这些处理器。 RegisterValidationInterface(this);然后,调用?Init?方法升级交易索引从老的数据库到新的数据库。TxIndex?子类重载了这个方法,会调用?m_db->MigrateData(*pblocktree, chainActive.GetLocator())?方法来升级数据库。然后,调用父类?BaseIndex?的同名方法进行处理。在父类的?Init?方法中,首先会调用?ReadBestBlock?方法从数据库中读取 Key 为?B?的区块做为定位器(可能是所有没有分叉的区块)。然后,调用?FindForkInGlobalIndex?方法,找到活跃区块链上的分叉前的最后一区块索引(从这个区块产生了分叉)。如果这个索引对应的区块和活跃区块链的顶端区块是相同的,设置同步完成标志为真。启动一个线程,线程执行的真正方法为?BaseIndex::ThreadSync。线程的主要作用在于当没有同步完成时,通过读取活跃区块链的下一个区块来进行同步,并把没有分叉的区块以 Key 为?B?写入数据库中。

第9步,加载钱包(src/init.cpp::AppInitMain())

调用钱包接口对象的?Open?方法,开始加载钱包。具体方法在?wallet/init.cpp?文件中。内容如下:

bool WalletInit::Open() const{ if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { LogPrintf("Wallet disabled!\n"); return true; } for (const std::string& walletFile : gArgs.GetArgs("-wallet")) { std::shared_ptr pwallet = CWallet::CreateWalletFromFile(walletFile, fs::absolute(walletFile, GetWalletDir())); if (!pwallet) { return false; } AddWallet(pwallet); } return true;}

首先检查是否禁止钱包,如果禁止直接返回。否则遍历所有钱包,调用?CWallet::CreateWalletFromFile?方法,要根据钱包文件生成钱包对象,如果成功生成钱包,则调用?AddWallet?方法把钱包加入?vpwallets?集合中。

第10步,数据目录维护(src/init.cpp::AppInitMain())

如果当前为修剪模式,本地服务去掉?NODE_NETWORK?标志,然后如果不需要索引则调用?PruneAndFlush?函数,修剪并刷新到硬盘中。

if (fPruneMode) { LogPrintf("Unsetting NODE_NETWORK on prune mode\n"); nLocalServices = ServiceFlags(nLocalServices & ~NODE_NETWORK); if (!fReindex) { uiInterface.InitMessage(_("Pruning blockstore...")); PruneAndFlush(); }}

第11步,导入区块(src/init.cpp::AppInitMain())

调用?CheckDiskSpace?函数,检查硬盘空间是否足够。如果没有足够的硬盘空间,则退出。检查最佳区块链顶端指示指针是否为空。如果顶端打针为空,UI界面进行通知。如果不空,则设置有创世区块,即?fHaveGenesis?设为真。if (chainActive.Tip() == nullptr) { uiInterface.NotifyBlockTip_connect(BlockNotifyGenesisWait);} else { fHaveGenesis = true;}如果指定了?blocknotify?参数,设置界面通知为?BlockNotifyCallback。遍历参数?loadblock?指定要加载的区块文件,放进向量变量?vImportFiles?集合中。然后调用?threadGroup.create_thread?方法,创建一个线程。线程执行的函数为?ThreadImport,参数为要加载的区块文件。 std::vector vImportFiles; for (const std::string& strFile : gArgs.GetArgs("-loadblock")) { vImportFiles.push_back(strFile); } threadGroup.create_thread(boost::bind(&ThreadImport, vImportFiles));获取?cs_GenesisWait?锁,等待创世区块被处理完成。 { WaitableLock lock(cs_GenesisWait); // We previously could hang here if StartShutdown() is called prior to // ThreadImport getting started, so instead we just wait on a timer to // check ShutdownRequested() regularly. while (!fHaveGenesis && !ShutdownRequested()) { condvar_GenesisWait.wait_for(lock, std::chrono::milliseconds(500)); } uiInterface.NotifyBlockTip_disconnect(BlockNotifyGenesisWait); }

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

获取活跃区块链的当前调度。 chain_active_height = chainActive.Height();如果指定了监听洋葱网络?-listenonion,调用?StartTorControl?函数,开始 Tor 控制。代码如下所示:void StartTorControl(){ assert(!gBase);#ifdef WIN32 evthread_use_windows_threads();#else evthread_use_pthreads();#endif gBase = event_base_new(); if (!gBase) { LogPrintf("tor: Unable to create event_base\n"); return; } torControlThread = std::thread(std::bind(&TraceThread, "torcontrol", &TorControlThread));}

libevent默认情况下是单线程,每个线程有且仅有一个event_base。为了保存多线程下是安全的,首先需要调用?evthread_use_pthreads?、evthread_use_windows_threads?等两个方法,前面是 linux 下的,后面是 windows 下的。

在处理完多线程设置后,调用?event_base_new?方法,创建一个默认的 event_base。

最后,启动一个 Tor 控制线程。具体调用?std::thread?方法,创建一个线程,线程的具体执行方法为?std::bind?返回的绑定函数。标准绑定函数的第一个参数为要执行的函数,此处为?TraceThread,第二个参数为线程的名字?torcontrol,第三个参数为线程要执行的真正方法,此处为?TorControlThread?函数,后面两个参数都会做为参数,传递到第一个函数。

TraceThread?函数,调用?RenameThread?方法,把线程名字设置为?bitcoin-torcontrol,然后执行传递进来的?TorControlThread?函数。后者会生成一个 Tor 控制器,然后调用?event_base_dispatch?方法,分发事件。代码如下:

static void TorControlThread(){ TorController ctrl(gBase, gArgs.GetArg("-torcontrol", DEFAULT_TOR_CONTROL)); event_base_dispatch(gBase);}

TorController?构造函数中会做几件重要的事情:

首先,调用?event_new?方法生成一个 event 对象,event 对象的回调函数为?reconnect_cb?。然后,调用?TorControlConnection::Connect?方法连接到 Tor 控制器。这个方法又会做几件事情:解析 Tor 控制器的地址。调用?bufferevent_socket_new?方法,基于套接字生成一个 bufferevent。设置 bufferevent 的回调方法,包括:读取回调函数为?TorControlConnection::readcb,写入回调函数为空,事件回调函数为?TorControlConnection::eventcb,同时指定 bufferevent 启用读写标志。设置?TorControlConnection?连接、断开连接的两个指针函数分别为:TorController::connected_cb?和?TorController::disconnected_cb。调用?bufferevent_socket_connect?方法,连接到前面生成的 bufferevent。方法在连接成功后,会立即调用事件回调函数?TorControlConnection::eventcb。调用?Discover?函数,开始发现本节点的地址。3. 调用?Discover?函数,开始发现本节点的地址。3. 调用?Discover?函数,开始发现本节点的地址。方法内首先判断是否已经处理过。如果没有,那么开始发现本节点的地址。具体处理分为 windows 和 linux,下面主要讲述 linux 下的处理。方法内首先判断是否已经处理过。如果没有,那么开始发现本节点的地址。具体处理分为 windows 和 linux,下面主要讲述 linux 下的处理。调用?getifaddrs?方法,查找系统所有的网络接口的信息,包括以太网卡接口和回环接口等。本方法返回一个如下的结构体:调用?getifaddrs?方法,查找系统所有的网络接口的信息,包括以太网卡接口和回环接口等。本方法返回一个如下的结构体:struct ifaddrs { struct ifaddrs *ifa_next; /* 列表中的下一个条目 */ char *ifa_name; /* 接口的名称 */ unsigned int ifa_flags; /* 来自 SIOCGIFFLAGS 的标志 */ struct sockaddr *ifa_addr; /* 接口的地址 */ struct sockaddr *ifa_netmask; /* 接口的网络掩码 */ union { struct sockaddr *ifu_broadaddr; /* 接口的广播地址 */ struct sockaddr *ifu_dstaddr; /* 点对点的目标地址 */ } ifa_ifu; #define ifa_broadaddr ifa_ifu.ifu_broadaddr #define ifa_dstaddr ifa_ifu.ifu_dstaddr void *ifa_data; /* Address-specific data */ };

如果可以获取接口信息,则遍历每一个接口,进行如下处理:

如果接口地址为空,则处理下一个。如果不是接口标志不是 IFF_UP ,则处理下一个。如果接口名称是 lo 或 lo0,则处理下一个。如果接口是 tcp,TCP 等,则生成 IP 地址对象,然后调用?AddLocal?方法,保存本地地址。如果接口是 IPV6,则则生成 IP 地址对象,然后调用?AddLocal?方法,保存本地地址。

代码如下所示:

if (getifaddrs(&myaddrs) == 0){ for (struct ifaddrs* ifa = myaddrs; ifa != nullptr; ifa = ifa->ifa_next) { if (ifa->ifa_addr == nullptr) continue; if ((ifa->ifa_flags & IFF_UP) == 0) continue; if (strcmp(ifa->ifa_name, "lo") == 0) continue; if (strcmp(ifa->ifa_name, "lo0") == 0) continue; if (ifa->ifa_addr->sa_family == AF_INET) { struct sockaddr_in* s4 = (struct sockaddr_in*)(ifa->ifa_addr); CNetAddr addr(s4->sin_addr); if (AddLocal(addr, LOCAL_IF)) LogPrintf("%s: IPv4 %s: %s\n", __func__, ifa->ifa_name, addr.ToString()); } else if (ifa->ifa_addr->sa_family == AF_INET6) { struct sockaddr_in6* s6 = (struct sockaddr_in6*)(ifa->ifa_addr); CNetAddr addr(s6->sin6_addr); if (AddLocal(addr, LOCAL_IF)) LogPrintf("%s: IPv6 %s: %s\n", __func__, ifa->ifa_name, addr.ToString()); } } freeifaddrs(myaddrs);}如果指定了?upnp?参数,则调用?StartMapPort?函数,开始进行端口映射。 if (gArgs.GetBoolArg("-upnp", DEFAULT_UPNP)) { StartMapPort(); }生成选项对象,并进行初始化。 CConnman::Options connOptions; connOptions.nLocalServices = nLocalServices; connOptions.nMaxConnections = nMaxConnections; connOptions.nMaxOutbound = std::min(MAX_OUTBOUND_CONNECTIONS, connOptions.nMaxConnections); connOptions.nMaxAddnode = MAX_ADDNODE_CONNECTIONS; connOptions.nMaxFeeler = 1; connOptions.nBestHeight = chain_active_height; connOptions.uiInterface = &uiInterface; connOptions.m_msgproc = peerLogic.get(); connOptions.nSendBufferMaxSize = 1000*gArgs.GetArg("-maxsendbuffer", DEFAULT_MAXSENDBUFFER); connOptions.nReceiveFloodSize = 1000*gArgs.GetArg("-maxreceivebuffer", DEFAULT_MAXRECEIVEBUFFER); connOptions.m_added_nodes = gArgs.GetArgs("-addnode"); connOptions.nMaxOutboundTimeframe = nMaxOutboundTimeframe; connOptions.nMaxOutboundLimit = nMaxOutboundLimit;

上面的代码基本就是设置本地支持的服务、最大连接数、最大出站数、最大节点数、最大费率、活跃区块链的高度、节点逻辑验证器、发送的最大缓冲值、接收的最大缓冲值、连接的节点数等。如果指定了?-bind?参数,则处理绑定参数。 for (const std::string& strBind : gArgs.GetArgs("-bind")) { CService addrBind; if (!Lookup(strBind.c_str(), addrBind, GetListenPort(), false)) { return InitError(ResolveErrMsg("bind", strBind)); } connOptions.vBinds.push_back(addrBind); }

遍历所有的绑定地址,调用?Lookup?方法,进行 DNS查找。如果可以找到对应 IP地址,把生成的?CService?对象放入选项对象的?vBinds?属性中。如果指定了?-whitebind?参数,则处理绑定参数。 for (const std::string& strBind : gArgs.GetArgs("-whitebind")) { CService addrBind; if (!Lookup(strBind.c_str(), addrBind, 0, false)) { return InitError(ResolveErrMsg("whitebind", strBind)); } if (addrBind.GetPort() == 0) { return InitError(strprintf(_("Need to specify a port with -whitebind: '%s'"), strBind)); } connOptions.vWhiteBinds.push_back(addrBind); }

遍历所有的绑定地址,调用?Lookup?方法,进行 DNS查找。如果可以找到对应 IP地址,且对应的端口号不等于0,把生成的?CService?对象放入选项对象的?vWhiteBinds?属性中。如果指定了?-whitelist?参数,则处理白名单列表。 for (const auto& net : gArgs.GetArgs("-whitelist")) { CSubNet subnet; LookupSubNet(net.c_str(), subnet); if (!subnet.IsValid()) return InitError(strprintf(_("Invalid netmask specified in -whitelist: '%s'"), net)); connOptions.vWhitelistedRange.push_back(subnet); }

遍历白名单列表,调用?LookupSubNet?方法,查找对应的子网掩码,如果对应的子网掩码是有效的,那么放入选项对象的?vWhitelistedRange?属性中。取得参数?seednode?指定的值,放入选项对象的?vSeedNodes?属性中。 connOptions.vSeedNodes = gArgs.GetArgs("-seednode");调用?CConnman?对象的?Start?方法,初始所有的出站连接。本方法非常非常重要,因为它启动了一个重要的流程,即底层的 P2P 网络建立和消息处理流程。具体分析如下:调用?Init?方法,根据选项对象设置对象的属性,不细说,代码如下: void Init(const Options& connOptions) { nLocalServices = connOptions.nLocalServices; nMaxConnections = connOptions.nMaxConnections; nMaxOutbound = std::min(connOptions.nMaxOutbound, connOptions.nMaxConnections); nMaxAddnode = connOptions.nMaxAddnode; nMaxFeeler = connOptions.nMaxFeeler; nBestHeight = connOptions.nBestHeight; clientInterface = connOptions.uiInterface; m_msgproc = connOptions.m_msgproc; nSendBufferMaxSize = connOptions.nSendBufferMaxSize; nReceiveFloodSize = connOptions.nReceiveFloodSize; { LOCK(cs_totalBytesSent); nMaxOutboundTimeframe = connOptions.nMaxOutboundTimeframe; nMaxOutboundLimit = connOptions.nMaxOutboundLimit; } vWhitelistedRange = connOptions.vWhitelistedRange; { LOCK(cs_vAddedNodes); vAddedNodes = connOptions.m_added_nodes; } }接下来,使用锁初始一些比较重要的属性。 { LOCK(cs_totalBytesRecv); nTotalBytesRecv = 0; } { LOCK(cs_totalBytesSent); nTotalBytesSent = 0; nMaxOutboundTotalBytesSentInCycle = 0; nMaxOutboundCycleStartTime = 0; }再接下来,获取节点绑定的本地地址和端口,并生成对应的套接字,接受别的节点的请求。 if (fListen && !InitBinds(connOptions.vBinds, connOptions.vWhiteBinds)) { if (clientInterface) { clientInterface->ThreadSafeMessageBox( _("Failed to listen on any port. Use -listen=0 if you want this."), "", CClientUIInterface::MSG_ERROR); } return false; }`InitBinds` 方法,接收 `-bind` 和 `-whitebind` 参数生成的集合,并解析各个地址,生成套接字,并进行监听。具体分析如下:- 首先,处理`-bind` 地址集合。 for (const auto& addrBind : binds) { fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR)); }- 然后,处理 `-whitebind` 地址集合。 for (const auto& addrBind : whiteBinds) { fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR | BF_WHITELIST)); }- 如果,两个参数都没有指定,则使用下面代码进行处理。 if (binds.empty() && whiteBinds.empty()) { struct in_addr inaddr_any; inaddr_any.s_addr = INADDR_ANY; struct in6_addr inaddr6_any = IN6ADDR_ANY_INIT; fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE); fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE); }从以上代码可以看出来,三种情况下,处理基本相同,都是调用 `Bind` 方法来处理。下面,我们进进入这个方法一控究竟。这个方法的主体是调用 `BindListenPort` 方法进行处理。下面我们开始讲解这个方法。- 首先,生成一个通用的网络地址 sockaddr 对象,类型为 sockaddr_storage,它的长度是 128个字节。- 然后,调用 `addrBind.GetSockAddr((struct sockaddr*)&sockaddr, &len)` 方法来设置网络地址 sockaddr。 `GetSockAddr` 方法内部根据地址是 IPV4 或 IPV6,分别进行处理。 如果是 IPV4,则生成 sockaddr_in 地址对象,然后调用 `memset` 把结构体所占内存用0填充,然后调用 `GetInAddr` 方法来设置地址对象的地址字段,最后设置地址类型为 AF_INET 和端口号。 如果是 IPV6,则生成 sockaddr_in6 地址对象,然后调用 `memset` 把结构体所占内存用0填充,然后调用 `GetIn6Addr` 方法来设置地址对象的地址字段,最后设置地址类型为 AF_INET6 和端口号。- 再然后,调用 `CreateSocket(addrBind)` 方法生成套接字对象。 方法处理如下: - 首先,生成一个通用的网络地址 sockaddr 对象,类型为 sockaddr_storage,然后,调用 `addrBind.GetSockAddr((struct sockaddr*)&sockaddr, &len)` 方法来设置网络地址 sockaddr。具体分析详见上面。 - 然后,生成套接字。 socket(((struct sockaddr*)&sockaddr)->sa_family, SOCK_STREAM, IPPROTO_TCP) - 再然后,对套接字进行一些检查和处理,不详述。- 套接字生成之后,接下来把套接字绑定到指定的地址上,并监听入站请求。 if (::bind(hListenSocket, (struct sockaddr*)&sockaddr, len) == SOCKET_ERROR) { int nErr = WSAGetLastError(); if (nErr == WSAEADDRINUSE) strError = strprintf(_("Unable to bind to %s on this computer. %s is probably already running."), addrBind.ToString(), _(PACKAGE_NAME)); else strError = strprintf(_("Unable to bind to %s on this computer (bind returned error %s)"), addrBind.ToString(), NetworkErrorString(nErr)); LogPrintf("%s\n", strError); CloseSocket(hListenSocket); return false; } LogPrintf("Bound to %s\n", addrBind.ToString()); // Listen for incoming connections if (listen(hListenSocket, SOMAXCONN) == SOCKET_ERROR) { strError = strprintf(_("Error: Listening for incoming connections failed (listen returned error %s)"), NetworkErrorString(WSAGetLastError())); LogPrintf("%s\n", strError); CloseSocket(hListenSocket); return false; }- 最后,进行一些收尾工作。 把套接字放入 `vhListenSocket` 集合中。如果地址是可达的,并且不是白名单中的地址,则调用 `AddLocal` 方法,加入本地地址集合中。处理完地址绑定之后,接下来处理种子节点参数指定集合。 void CConnman::AddOneShot(const std::string& strDest) { LOCK(cs_vOneShots); vOneShots.push_back(strDest); }这个方法非常简单,把每个种子节点加入 `vOneShots` 集合。接下来,从文件数据库中加载地址列表和禁止地址列表。 { CAddrDB adb; if (adb.Read(addrman)) LogPrintf("Loaded %i addresses from peers.dat %dms\n", addrman.size(), GetTimeMillis() - nStart); else { addrman.Clear(); // Addrman can be in an inconsistent state after failure, reset it LogPrintf("Invalid or missing peers.dat; recreating\n"); DumpAddresses(); } } CBanDB bandb; banmap_t banmap; if (bandb.Read(banmap)) { SetBanned(banmap); // thread save setter SetBannedSetDirty(false); // no need to write down, just read data SweepBanned(); // sweep out unused entries LogPrint(BCLog::NET, "Loaded %d banned node ips/subnets from banlist.dat %dms\n", banmap.size(), GetTimeMillis() - nStart); } else { LogPrintf("Invalid or missing banlist.dat; recreating\n"); SetBannedSetDirty(true); // force write DumpBanlist(); }代码比较简单,一看便知,不作具体展开。最后,重中之重的线程相关处理终于要到来了。首先,生成套接字相关的线程,以便进行网络的接收和发送。处理方法和前面线程的类似,代码如下: threadSocketHandler = std::thread(&TraceThread

, "net", std::function(std::bind(&CConnman::ThreadSocketHandler, this)));真正执行的方法是 `ThreadSocketHandler`,这个方法太重要了,我们留在下一课网络处理中细讲。接下来,处理 DNS 种子节点线程,处理 DNS 种子相关的逻辑。代码如下: if (!gArgs.GetBoolArg("-dnsseed", true)) LogPrintf("DNS seeding disabled\n"); else threadDNSAddressSeed = std::thread(&TraceThread

, "dnsseed", std::function(std::bind(&CConnman::ThreadDNSAddressSeed, this)));真正执行的方法是 `ThreadDNSAddressSeed`,这个方法太重要了,我们留在下一课网络处理中细讲。接下来,处理出站连接。代码如下: threadOpenAddedConnections = std::thread(&TraceThread, "addcon", std::function(std::bind(&CConnman::ThreadOpenAddedConnections, this)));真正执行的方法是 `ThreadOpenAddedConnections`,这个方法太重要了,我们留在下一课网络处理中细讲。接下来,处理打开连接的线程。代码如下: if (connOptions.m_use_addrman_outgoing || !connOptions.m_specified_outgoing.empty()) threadOpenConnections = std::thread(&TraceThread, "opencon", std::function(std::bind(&CConnman::ThreadOpenConnections, this, connOptions.m_specified_outgoing)));真正执行的方法是 `ThreadOpenConnections`,这个方法太重要了,我们留在下一课网络处理中细讲。最最重要的线程–处理消息的线程,隆重登场。 threadMessageHandler = std::thread(&TraceThread, "msghand", std::function(std::bind(&CConnman::ThreadMessageHandler, this)));真正执行的方法是?ThreadMessageHandler,这个方法太重要了,我们留在下一课网络处理中细讲。- 最后,定时把节点地址和禁止列表刷新到数据库文件中。

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

调用?SetRPCWarmupFinished()?方法,设置热身结束。方法内部主要设置?fRPCInWarmup?变量为假,表示热身结束。调用钱包接口对象的?Start?方法,开始进行钱包相关的处理,并定时刷新钱包数据到数据库中。代码如下:for (const std::shared_ptr& pwallet : GetWallets()) { pwallet->postInitProcess();}// Run a thread to flush wallet periodicallyscheduler.scheduleEvery(MaybeCompactWalletDB, 500);

方法中,首先便利所有钱包对象,调用其?postInitProcess?方法,进行后初始化设置。主要是把钱包中存在,但是交易池中不存在的交易添加到交易池中。

然后,设置调度器定时调用?MaybeCompactWalletDB?方法,刷新钱包数据到数据库中。


从零开始学习比特币开发(四):网络初始化,加载区块链和钱包,导入区块启动节点

从零开始学习比特币开发(四):网络初始化,加载区块链和钱包,导入区块启动节点 | 分享给朋友: