比特币源码解析(21)

来源:互联网 发布:ubuntu 系统备份 编辑:程序博客网 时间:2024/06/05 12:28

0x00 摘要

经过前面20章的分析,我们已经渐渐接近比特币的核心功能部分了,也就是它的共识、交易处理等等。虽然前面基本上都是做的一些初始化的工作,但是这些工作对于比特币的整体运行来说都是必不可缺的,并且就像在之前讲过的信号处理、并发处理等等都是值得学习的部分,本章主要介绍AppInitMain中的Step 6,代码略微有些长所以就分割成小段来进行分析。

0x01 AppInitMain Step 6: network initialization

    // ********************************************************* Step 6: network initialization    // Note that we absolutely cannot open any actual connections    // until the very end ("start node") as the UTXO/block state    // is not yet setup and may end up being set up twice if we    // need to reindex later.    assert(!g_connman);    g_connman = std::unique_ptr<CConnman>(new CConnman(GetRand(std::numeric_limits<uint64_t>::max()), GetRand(std::numeric_limits<uint64_t>::max())));    CConnman& connman = *g_connman;    peerLogic.reset(new PeerLogicValidation(&connman));    RegisterValidationInterface(peerLogic.get());    RegisterNodeSignals(GetNodeSignals());

先看开头注释,这里提示只有在最后Start node的时候才能进行实际的网络连接,也就是说这一段还是进行一些参数的设置,并不会实际开启连接,原因是区块的状态还没有配置好并且,如果后面设置了重新索引,那么区块的状态就会被设置两次。

PeerLogicValidation

再来看代码,首先一句断言确保g_connman为空,之前的代码中也经常看到断言语句,这是一种很好的习惯,能确保变量在指定的范围内,同时易于调试。接下来创建了一个CConnman对象,用于设置连接的参数,接着又创建了一个PeerLogicValidation类型的变量,这个类的实现如下,

class PeerLogicValidation : public CValidationInterface {private:    CConnman* connman;public:    explicit PeerLogicValidation(CConnman* connmanIn);    void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexConnected, const std::vector<CTransactionRef>& vtxConflicted) override;    void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override;    void BlockChecked(const CBlock& block, const CValidationState& state) override;    void NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock) override;};

通过这几个函数名称大概可以看出来,这个类实现的是在产生一个新的block时,节点如何处理,这些函数的具体实现过程将在后面调用时再来分析。

注册节点之间的消息处理信号

void RegisterValidationInterface(CValidationInterface* pwalletIn) {    g_signals.m_internals->UpdatedBlockTip.connect(boost::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, _1, _2, _3));    g_signals.m_internals->TransactionAddedToMempool.connect(boost::bind(&CValidationInterface::TransactionAddedToMempool, pwalletIn, _1));    g_signals.m_internals->BlockConnected.connect(boost::bind(&CValidationInterface::BlockConnected, pwalletIn, _1, _2, _3));    g_signals.m_internals->BlockDisconnected.connect(boost::bind(&CValidationInterface::BlockDisconnected, pwalletIn, _1));    g_signals.m_internals->SetBestChain.connect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1));    g_signals.m_internals->Inventory.connect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1));    g_signals.m_internals->Broadcast.connect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1, _2));    g_signals.m_internals->BlockChecked.connect(boost::bind(&CValidationInterface::BlockChecked, pwalletIn, _1, _2));    g_signals.m_internals->NewPoWValidBlock.connect(boost::bind(&CValidationInterface::NewPoWValidBlock, pwalletIn, _1, _2));}

注册节点信号

void RegisterNodeSignals(CNodeSignals& nodeSignals){    nodeSignals.ProcessMessages.connect(&ProcessMessages);    nodeSignals.SendMessages.connect(&SendMessages);    nodeSignals.InitializeNode.connect(&InitializeNode);    nodeSignals.FinalizeNode.connect(&FinalizeNode);}

这里是注册几个节点处理、发送消息的信号。

添加用户代理注释

    // sanitize comments per BIP-0014, format user agent and check total size    std::vector<std::string> uacomments;    for (const std::string& cmt : gArgs.GetArgs("-uacomment")) {        if (cmt != SanitizeString(cmt, SAFE_CHARS_UA_COMMENT))            return InitError(strprintf(_("User Agent comment (%s) contains unsafe characters."), cmt));        uacomments.push_back(cmt);    }    strSubVersion = FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, uacomments);    if (strSubVersion.size() > MAX_SUBVERSION_LENGTH) {        return InitError(strprintf(_("Total length of network version string (%i) exceeds maximum length (%i). Reduce the number or size of uacomments."),            strSubVersion.size(), MAX_SUBVERSION_LENGTH));    }

-uacomment:给用户代理字符串添加注释。

首先将用户对代理的注释信息保存到uacomments中,将CLIENT_NAMECLIENT_VERSIONuacomments按照/CLIENT_NAME:CLIENT_VERSION(comments1;comments2;...)/的格式连接起来,最后判断格式化后的字符串是否超过了最大长度限制,这个MAX_SUBVERSION_LENGTHsrc/net.h中定义为256

设定网络范围

    if (gArgs.IsArgSet("-onlynet")) {        std::set<enum Network> nets;        for (const std::string& snet : gArgs.GetArgs("-onlynet")) {            enum Network net = ParseNetwork(snet);            if (net == NET_UNROUTABLE)                return InitError(strprintf(_("Unknown network specified in -onlynet: '%s'"), snet));            nets.insert(net);        }        for (int n = 0; n < NET_MAX; n++) {            enum Network net = (enum Network)n;            if (!nets.count(net))                SetLimited(net);        }    }

-onlynet:只连接特定网络中的节点,取值有NET_UNROUTABLE,NET_IPV4,NET_IPV6,NET_TOR,NET_INTERNAL几种。

首先看看Network的定义,

enum Network{    NET_UNROUTABLE = 0,    NET_IPV4,    NET_IPV6,    NET_TOR,    NET_INTERNAL,    NET_MAX,};

定义了几种网络,-onlynet则将连接范围限定在某一种或几种网络内。

代理设置

    // Check for host lookup allowed before parsing any network related parameters    fNameLookup = gArgs.GetBoolArg("-dns", DEFAULT_NAME_LOOKUP);    bool proxyRandomize = gArgs.GetBoolArg("-proxyrandomize", DEFAULT_PROXYRANDOMIZE);    // -proxy sets a proxy for all outgoing network traffic    // -noproxy (or -proxy=0) as well as the empty string can be used to not set a proxy, this is the default    std::string proxyArg = gArgs.GetArg("-proxy", "");    SetLimited(NET_TOR);    if (proxyArg != "" && proxyArg != "0") {        CService proxyAddr;        if (!Lookup(proxyArg.c_str(), proxyAddr, 9050, fNameLookup)) {            return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg));        }        proxyType addrProxy = proxyType(proxyAddr, proxyRandomize);        if (!addrProxy.IsValid())            return InitError(strprintf(_("Invalid -proxy address or hostname: '%s'"), proxyArg));        SetProxy(NET_IPV4, addrProxy);        SetProxy(NET_IPV6, addrProxy);        SetProxy(NET_TOR, addrProxy);        SetNameProxy(addrProxy);        SetLimited(NET_TOR, false); // by default, -proxy sets onion as reachable, unless -noonion later    }

-dns:允许进行dns解析,默认为1.

-proxyrandomize:为每个代理连接都随机颁发一个证书,默认为1.

-proxy:为网络所有的通信设置一个代理,默认为空。

首先检查两个参数,然后通过SetLimited(NET_TOR)来禁用洋葱路由。然后检查如果代理不为空,那么根据代理域名进行dns查询,查到相应的ip并检查代理的合法性之后,再为IPV4IPV6以及TOR设置代理。最后禁用TOR,因为在上面先禁用了,所以这里进行启用,其实这里设不设置都没关系,后面会根据-onion参数再进行相应的设置。

设置洋葱路由

    // -onion can be used to set only a proxy for .onion, or override normal proxy for .onion addresses    // -noonion (or -onion=0) disables connecting to .onion entirely    // An empty string is used to not override the onion proxy (in which case it defaults to -proxy set above, or none)    std::string onionArg = gArgs.GetArg("-onion", "");    if (onionArg != "") {        if (onionArg == "0") { // Handle -noonion/-onion=0            SetLimited(NET_TOR); // set onions as unreachable        } else {            CService onionProxy;            if (!Lookup(onionArg.c_str(), onionProxy, 9050, fNameLookup)) {                return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg));            }            proxyType addrOnion = proxyType(onionProxy, proxyRandomize);            if (!addrOnion.IsValid())                return InitError(strprintf(_("Invalid -onion address or hostname: '%s'"), onionArg));            SetProxy(NET_TOR, addrOnion);            SetLimited(NET_TOR, false);        }    }

如果-onion!="" && != "0"那么跟设置代理类似,首先解析域名,启用洋葱路由。

设置external ip

    // see Step 2: parameter interactions for more information about these    fListen = gArgs.GetBoolArg("-listen", DEFAULT_LISTEN);    fDiscover = gArgs.GetBoolArg("-discover", true);    fRelayTxes = !gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY);    for (const std::string& strAddr : gArgs.GetArgs("-externalip")) {        CService addrLocal;        if (Lookup(strAddr.c_str(), addrLocal, GetListenPort(), fNameLookup) && addrLocal.IsValid())            AddLocal(addrLocal, LOCAL_MANUAL);        else            return InitError(ResolveErrMsg("externalip", strAddr));    }

-listen:接受从某个地址的连接请求。

-discover:发现拥有的ip地址。

-blocksonly:让节点进入blocksonly模式。

-externalip:指定公有地址。

前面两个参数比较好理解,那么什么是blocksonly模式呢?根据https://bitcointalk.org/index.php?topic=1377345.0的解释,

Bitcoin Core 0.12 introduced a new blocksonly setting. When set to blocksonly a node behaves normally but sends and receives no lose transactions; instead it handles only complete blocks. There are many applications for nodes where only confirmed transactions are interesting, and a node which still verifies and forwards blocks still contributes to network health– less, perhaps, than one that relays transactions: but it also consumes fewer resources to begin with. An additional downside they don’t get the latency advantages of signature caching since every transaction they see is totally new to them– this isn’t something miners should use.

How much less bandwidth does blocksonly use in practice? I recently measured this using two techniques: Once by instrumenting a node to measure bandwidth used for blocks vs all other traffic, and again by repeatedly running in both modes for a day and monitoring the hosts total network usage; both modes gave effectively the same result.

How much is the savings? Blocksonly reduced the node’s bandwidth usage by 88%.

简单来说,就是节点不接收临时的交易,只接受已确认的区块。

接下来对于指定的external ip首先查询对应的ip(指定的可以是域名,或者将字符串ip转换成CService),然后通过AddLocal将指定的ip添加到mapLocalHost中,由这个结构维护所有的本地ip。

ZMQ

#if ENABLE_ZMQ    pzmqNotificationInterface = CZMQNotificationInterface::Create();    if (pzmqNotificationInterface) {        RegisterValidationInterface(pzmqNotificationInterface);    }#endif    uint64_t nMaxOutboundLimit = 0; //unlimited unless -maxuploadtarget is set    uint64_t nMaxOutboundTimeframe = MAX_UPLOAD_TIMEFRAME;    if (gArgs.IsArgSet("-maxuploadtarget")) {        nMaxOutboundLimit = gArgs.GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET)*1024*1024;    }

-maxuploadtarget:设置最大上传速度,单位为MB,默认值为0,表示没有限制。

首先通过一个宏定义来表示是否启用ZMQ,关于zmq的介绍,参考https://www.cnblogs.com/rainbowzc/p/3357594.html,简单来说,zmq封装了网络通信、消息队列、线程调度等功能,向上层提供简洁的API,应用程序通过加载库文件,调用API函数来实现高性能网络通信。本章的前面介绍了RegisterValidationInterface函数,此函数注册了许多区块处理的信号。然后下面通过-maxuploadtarget参数来设置最大上传速度。

原创粉丝点击