1. 程式人生 > >比特幣原始碼解析(21)

比特幣原始碼解析(21)

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:指定公有地址。

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引數來設定最大上傳速度。

相關推薦

原始碼解析(21)

0x00 摘要 經過前面20章的分析,我們已經漸漸接近比特幣的核心功能部分了,也就是它的共識、交易處理等等。雖然前面基本上都是做的一些初始化的工作,但是這些工作對於比特幣的整體執行來說都是必不可缺的,並且就像在之前講過的訊號處理、併發處理等等都是值得學習的部分

原始碼解析(7)

0x00 摘要 區塊是區塊鏈的組成基本結構,也是交易資訊的載體,礦工通過挖礦的形式來產生新的區塊並獲得獎勵,新塊產生的過程也是一個交易打包的過程,只有加入到區塊中的交易才會被系統所有其他節點所認可,才

原始碼解析(7) - 資料結構 - 區塊

比特幣原始碼解析(7) - 資料結構 - 區塊    https://blog.csdn.net/u012183589/article/details/77776730 0x00 摘要 區塊是區塊鏈的組成基本結構,也是交易資訊的載體,礦工通過挖礦的形式來產生新的區

原始碼解析(9)

0x00 摘要 bitcoin-cli:是Bitcoind的一個功能完備的RPC客戶端,包括查詢區塊,交易資訊等等,具體將在相應章節介紹。 bitcoind:是比特幣執行的核心程式俗稱bitcoin core,也是我們分析的重點。 bitcoin-qt

原始碼解析(18)

0x01 InitHTTPServer 初始化訪問控制列表(ACL) if (!InitHTTPAllowList()) return false; if (gArgs.GetBoolArg("-rpcssl", f

原始碼解析(22)

0x01 AppInitMain Step 7: load block chain 計算快取大小 fReindex = gArgs.GetBoolArg("-reindex", false); bool fReindexChainSt

原始碼解析(1)

0x00 寫在前面 研究了這麼久的區塊鏈卻一直都沒有完整的看過一個區塊鏈專案的程式碼,甚至還一度沉迷各種ICO,每天看著各種貨幣層出不窮,跌跌漲漲,起起伏伏,不亦樂乎。現在看來,也許整體來講賺了點小錢,可是那又有什麼意義呢?終究不是長久之計。這兩天終於靜下來大

原始碼閱讀(1)--雜記與加密部分(爽之小刀)

比特幣原始碼閱讀(1)–雜記與加密部分(爽之小刀) 歡迎轉載和引用 若有問題請聯絡請聯絡 Email : [email protected] QQ:2279557541 最近從成都來到了杭州拼一槍 由於老婆為了圓自己的一個大公司夢來到了杭州

區塊鏈學習1.5-原始碼的學習-網路

本篇文章有部分內容直接出自《Mastering Bitcoin》 比特幣網路層主要是由 P2P網路,傳播機制,驗證機制三部分組成。 白皮書關於network的內容回顧一下: The steps to run the network are as follows:

區塊鏈學習1.4-原始碼的學習-基礎

1.3就已經提到區塊鏈的四大技術組合,我認為還是有必要了解背後的原理的。下面做一個簡要的介紹。 一. 區塊鏈資料結構和數字簽名演算法 1.資料結構Merkel樹 說到merkle樹就不得不談到交易,merkle樹就是用於存放交易的 資料結構。如下圖: 它是一個雜湊二叉樹,雜湊的

原始碼研讀--埠對映

在P2P網路中,一個節點既是客戶又是伺服器,它還要接受其他節點的連線,為網路中其他節點提供服務。這篇文章著重分析一下比特幣P2P網路中是如何通過upnp來實現埠對映的。 1 從騰訊的一道面試題說起     筆者所在團隊的總監在面試的時候必然要問面試者這

【區塊鏈】原始碼學習

比特幣原始碼學習 - 1 - 交易 參考部落格:here and here 一、交易概念 1、 交易形式 比特幣交易中的基礎構建單元是交易輸出。在比特幣的世界裡既沒有賬戶,也沒有餘額,只有分散到區塊鏈裡的UTXO[未花費的交易輸出]。 例如,你有20比特幣

【區塊鏈】原始碼

比特幣原始碼 - 2 - 金鑰和地址 一、基本概念 這裡摘抄一下《精通比特幣》裡面的描述: 比特幣的所有權是通過數字金鑰、比特幣地址和數字簽名來確立的。數字金鑰實際上並不是儲存在網路中,而是由使用者生成並存儲在一個檔案或簡單的資料庫中,稱為錢包。 每筆比特幣交

原始碼分析--深入理解交易

交易是比特幣最重要的一塊,比特幣系統的其他部分都是為交易服務的。前面的章節中已經學習了各種共識演算法以及比特幣PoW共識的實現,本文來分析比特幣中的交易相關的原始碼。 1 初識比特幣交易     通過比特幣核心客戶端的命令getrawtransaction和decoder

原始碼研讀(二)之搭環境遇到的那些坑

首先說一下度《精通比特幣》是一直不理解的一個地方: 上面兩處被圈起來的地方都提到了一個數字2256,特別是第一句話更是讓人費解,如果私鑰只能在1到2256間產生那豈不是太容易重複了。關於這點,我認為是在翻譯或者排版是出現了錯誤,2256應該是想表達2的256次方的意

原始碼研讀--交易細節

0x00 讀碼即挖礦 前兩天看到群裡還有人在討論ETH和EOS的DAPP開發,看來區塊鏈的落地還是一線希望,大家可以繼續給自己的信仰充值。充值方式眾多,比如加倉,Fomo,或是寫DAPP,讀程式碼。那我繼續前兩次的操作,繼續閱讀BTC的程式碼,版本0.8.2。上次粗讀了一番

原始碼分析--P2P網路初始化

     區塊鏈和AI無疑是近期業界當之無愧的兩大風口。AI就不說了,區塊鏈從17年各種數字貨幣被炒上了天,一下成為了人們街頭巷議的焦點,本文撇開數字貨幣的投資不說,僅僅從技術層面來剖析一下區塊鏈各個部分的原理。畢竟目前已經有包括BAT等巨頭在內的許多公司投入到了區塊鏈的研發

原始碼學習筆記(二)

第二章 本章繼上一章交易建立之後介紹比特幣客戶端序列化資料的過程。 比特幣客戶端所有的序列化函式均在seriliaze.h中實現。其中,CDataStream類是資料序列化的核心結構。 CDataStream CDataStream擁有一個字元類容器用來存放序列化之後的資料

原始碼解讀之整體框架

      本文主要描述了程序啟動時初始化(載入地址、區塊、錢包資訊,啟動節點執行緒以及礦工挖礦執行緒等)、比特幣客戶端交易的發起(交易初始化、提交交易請求、確認和廣播及交易)以及比特幣客戶端當前節點地址和收款方地址。下文將根據總體框架圖分別描述各個功能在原始碼中的函式實現(

原始碼linux下環境配置編譯執行bitcoin

github原始碼連結(https://github.com/bitcoin/bitcoin/) 由於近期學習區塊鏈,需要學習下比特幣原始碼,所以嘗試著在windows和linux環境下編譯執行,但是windows下的環境配置很繁瑣總是在裝qt的時候報錯,下面