比特币源码研读之二十三

作者: 菜菜子_forest | 来源:发表于2018-04-09 22:40 被阅读659次

    我们今天将继续深入AppInitMain函数,下面我们一起来对其进行详细分析。

    本文将着重分析网络初始化相关的代码。本文主要涉及的源码文件包括:

    src/bitcond.cpp、src/init.h、src/init.cpp、src/util.h、src/util.cpp、src/net.h、src/net.cpp、src/net_processing.h、src/net_processing.cpp、src/validationinterface.h中,在src/validationinterface.cpp

    一、网络管理变量初始化

    网络初始化代码从初始化两个关键变量开始的,分别是g_connman与peerLogic。这两个变量的初始定义位于init.cpp的上方:

    std::unique_ptr g_connman;

    std::unique_ptr peerLogic;

    由其定义我们可以知道g_connman的类型为CConnman,peerLogic的类型为PeerLogicValidation。我们分别来看下g_connman与peerLogic的定义及其类型。

    (1)g_connman

    g_connman是一个全局变量,在init.cpp中是该变量的定义,既然是全局变量,那么就有声明的地方,其声明的地方位于src/net.h中:

    extern std::unique_ptr g_connman;

    从其定义我们可以看出g_connman的类型为CConnman类,该类的定义位于src/net.h中。通过该类中定义的变量、枚举类型NumConnections、选项结构体Options、地址管理函数(AddNewAddress、GetAddresses等)、防DDOS攻击函数(Ban、SetBanned等)、节点管理变量(AddNode、RemoveAddedNode等)、接收发送数据量函数(GetTotalBytesRecv、GetTotalBytesSent)等信息,我们可以看出该类为比特币节点与其他节点连接的管理类,在该类中实现当前节点与全网其他节点的一个连接关系以及连接节点的具体信息,该类的具体说明我们将会在后续的比特币模块分析专题中详细说明,此处做简要概述。我们也可以通过比特币核心的界面来看下比特币节点间连接涉及的具体信息都包括哪些,以下是某个比特币节点与其他节点的一个连接信息:

    该图是在比特币核心图形界面客户端的调试窗口,在调试窗口的“同伴”页面中,我们可以看到其显示了当前节点与其相连节点的相关信息,显示的这些信息都可以从CConnman类中找到相关出处的,具体对应关系,敬请期待我们在第四期的分模块源码分析文章。

    我们再回头来看下g_connman的定义,其定义并未简单的CConnman类指针对象,而是通过智能指针unique_ptr来实现的。这个unique_ptr的定义如下:

    unique_ptr是一种定义在中的智能指针(smart pointer)。它持有对对象的独有权—两个unique_ptr不能指向一个对象,不能进行复制操作只能进行移动操作。

    unique_ptr拥有它所指向的对象,在某一时刻,只能有一个unique_ptr指向特定的对象。当unique_ptr被销毁时,它所指向的对象也会被销毁。因此不允许多个unique_ptr指向同一个对象,所以不允许拷贝与赋值。其具体使用方法可以参考该贴:

    https://blog.csdn.net/qq_33266987/article/details/78784286

    通过以上分析我们指导,通过unique_ptr智能指针,整个程序中只有一个唯一的节点连接管理对象g_connman,可以很好地保证连接信息的唯一性。

    1、assert(!g_connman);

    2、g_connman=std::unique_ptr(newCConnman(GetRand(std::numeric_limits::max()),GetRand(std::numeric_limits::max())));

    3、CConnman&

    connman = *g_connman;

    1行:通过断言来确定g_connman未初始化,否则程序发出断言错误,并退出;

    2行:通过CConnman的构造函数对g_connman进行初始化。CConnman的构造函数为,src/net.h中:

    CConnman(uint64_t seed0, uint64_t seed1);

    构造函数实现代码,src/net.cpp中:

    CConnman::CConnman(uint64_tnSeed0In, uint64_t nSeed1In) : nSeed0(nSeed0In), nSeed1(nSeed1In)

    {

       fNetworkActive = true;

       setBannedIsDirty = false;

       fAddressesInitialized = false;

        nLastNodeId= 0;

       nSendBufferMaxSize = 0;

       nReceiveFloodSize = 0;

        semOutbound= NULL;

        semAddnode= NULL;

       nMaxConnections = 0;

       nMaxOutbound = 0;

        nMaxAddnode= 0;

        nBestHeight= 0;

       clientInterface = NULL;

       flagInterruptMsgProc = false;

    }

    在第2行代码中,传入了的2个参数为uint64_t全范围内的随机数,同时对全局网络连接变量g_connman进行了初始化;

    3行:赋值给局部变量connman,该函数中后续代码都将使用connman来执行网络连接的相关操作。

    (2) peerLogic

    peerLogic变量为PeerLogicValidation的智能指针对象,该变量为init.cpp中的全局变量,其定义如下:

    std::unique_ptr peerLogic;

    在AppinitMain函数中的初始化代码为:

    peerLogic.reset(newPeerLogicValidation(&connman));

    RegisterValidationInterface(peerLogic.get());

    RegisterNodeSignals(GetNodeSignals());

    在分析这3行初始化代码之前,我们首先来分析下peerLogic的类PeerLogicValidation。该类定义于src/net_processing.h中,该类的定义很简单,具体如下:

    通过该类的定义我们可以看出其继承于CValidationInterface接口,CValidationInterface接口主要定义了对交易同步、区块工作量、挖矿等内容的验证操作。PeerLogicValidation在CValidationInterface的基础上实现了4类验证操作工作,分别是:

    virtual void SyncTransaction(constCTransaction& tx, const CBlockIndex* pindex, int nPosInBlock);

    virtual void UpdatedBlockTip(constCBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload);

    virtual void BlockChecked(constCBlock& block, const CValidationState& state);

    virtual void NewPoWValidBlock(constCBlockIndex *pindex, const std::shared_ptr& pblock);

    SyncTransaction:是对区块中的孤块交易进行验证,擦除区块中的孤交易,交易擦除函数为src/net_processing.cpp中的EraseOrphanTx函数。

    int static EraseOrphanTx(uint256 hash)EXCLUSIVE_LOCKS_REQUIRED(cs_main)

    {

       std::map::iterator it =mapOrphanTransactions.find(hash);

        if (it ==mapOrphanTransactions.end())

            return 0;

       BOOST_FOREACH(const CTxIn& txin, it->second.tx->vin)

        {

            autoitPrev = mapOrphanTransactionsByPrev.find(txin.prevout);

            if(itPrev == mapOrphanTransactionsByPrev.end())

               continue;

           itPrev->second.erase(it);

            if(itPrev->second.empty())

               mapOrphanTransactionsByPrev.erase(itPrev);

        }

       mapOrphanTransactions.erase(it);

        return 1;

    }

    UpdatedBlockTip:更新本节点中区块信息,函数代码如下:

    void PeerLogicValidation::UpdatedBlockTip(constCBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) {

        const intnNewHeight = pindexNew->nHeight;

       connman->SetBestHeight(nNewHeight);

        if(!fInitialDownload) {

            // Findthe hashes of all blocks that weren't previously in the best chain.

           std::vector vHashes;

            constCBlockIndex *pindexToAnnounce = pindexNew;

            while(pindexToAnnounce != pindexFork) {

               vHashes.push_back(pindexToAnnounce->GetBlockHash());

               pindexToAnnounce = pindexToAnnounce->pprev;

                if(vHashes.size() == MAX_BLOCKS_TO_ANNOUNCE) {

                   // Limit announcements in case of a huge reorganization.

                   // Rely on the peer's synchronization mechanism in that case.

                   break;

                }

            }

            //Relay inventory, but don't relay old inventory during initial block download.

           connman->ForEachNode([nNewHeight, &vHashes](CNode* pnode) {

                if(nNewHeight > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight -2000 : 0)) {

                   BOOST_REVERSE_FOREACH(const uint256& hash, vHashes) {

                       pnode->PushBlockHash(hash);

                   }

                }

            });

           connman->WakeMessageHandler();

        }

       nTimeBestReceived = GetTime();

    }

    在这个函数中,首先将当前同步的最大区块高度赋值到当前节点中,随后判断是否为初始化区块同步,如果不是则在while循环体中将之前未包含在最长链中的区块放入链中,存入当前传入最高区块,并将该最高区块的父区块、爷爷区块、爷爷爷爷区块……也放入链中,直到存入vHashes的区块数量达到MAX_BLOCKS_TO_ANNOUNCE值,该宏定义于src/validation.h中,其值为8,即包括传入区块以及往前回溯7个区块,随后将这些区块通过pnode->PushBlockHash(hash);存入节点中。

    BlockChecked对收到的区块进行验证,该函数中传入了2个参数,第一个为区块信息,第二个参数为区块验证参数,该参数类型定义于src/consensus/validation.h中CValidationState,通过该类实现对区块的验证。

    NewPoWValidBlock:该函数实现将本节点新发现区块进行广播,发送给其他节点。

    好了,在完成PeerLogicValidation类中包含的4类验证操作后,我们再来看下开始提到的3行代码:

    1行:peerLogic定义,传入的参数为connman,也就是我们之前分析过的g_Connman变量,在PeerLogicValidation中传入该参数的目的是为了获得相连节点的信息,从而实现对同步交易与区块的验证;

    第2行:RegisterValidationInterface函数定义于src/validationinterface.h中,在src/validationinterface.cpp中实现,通过其注释可以知道该函数的功能是实现对钱包的注册,从而实现钱包可以接收比特币核心的更新消息,这些更新消息可以从其函数实现中找到,包括:最高区块、交易同步、交易更新、区块确认以及区块传播等,与这个函数相对应的还有取消注册函数UnRegisterValidationInterface,即取消前面说到消息的接收与处理;

    第3行:RegisterNodeSignals函数也是定义于src/validationinterface.h中,并在src/validationinterface.cpp中实现,通过其注释可以知道该函数的功能是实现与其他相连网络节点交互消息的注册,包括:消息处理、发送消息、初始化节点以及断开连接,与这个函数相对应的还有取消注册函数UnRegisterNodeSignals,即取消与其他节点的交互消息注册。

    以上即为网络管理变量初始化的源码分析,这部分仅为网络初始化的开始部分,单独拿出一篇文章来分析是非常有必要的,因为简短的这2部分代码完成了网络连接、网络管理以及网络节点间建立连接的初始化工作,包含的内容也较多,而且有了这个基础,后面的代码才能有效运行。本文只是网络初始化代码分析的第一篇文章,后面的文章将接着本文继续,敬请期待!

    区块链研习社比特币源码研读班 菜菜子

    比特币源码研读班小密圈:

    相关文章

      网友评论

        本文标题:比特币源码研读之二十三

        本文链接:https://www.haomeiwen.com/subject/bfmwhftx.html