比特幣源碼解析(21) - 可執行程式 - Bitcoind

來源:互聯網
上載者:User
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為空白,之前的代碼中也經常看到Assert 陳述式,這是一種很好的習慣,能確保變數在指定的範圍內,同時易於調試。接下來建立了一個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_NAME、CLIENT_VERSION和uacomments按照/CLIENT_NAME:CLIENT_VERSION(comments1;comments2;...)/的格式串連起來,最後判斷格式化後的字串是否超過了最大長度限制,這個MAX_SUBVERSION_LENGTH在src/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並檢查代理的合法性之後,再為IPV4、IPV6以及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參數來設定最大上傳速度。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.