1. 程式人生 > >區塊鏈學習1.5-比特幣原始碼的學習-比特幣網路

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

本篇文章有部分內容直接出自《Mastering Bitcoin》

比特幣網路層主要是由 P2P網路,傳播機制,驗證機制三部分組成。

白皮書關於network的內容回顧一下:

The steps to run the network are as follows:

  1. 1)  New transactions are broadcast to all nodes.

  2. 2)  Each node collects new transactions into a block.

  3. 3)  Each node works on finding a difficult proof-of-work for its block.

  4. 4)  When a node finds a proof-of-work, it broadcasts the block to all nodes.

  5. 5)  Nodes accept the block only if all transactions in it are valid and not already spent.

  6. 6)  Nodes express their acceptance of the block by working on creating the next block in the

    chain, using the hash of the accepted block as the previous hash.

Nodes always consider the longest chain to be the correct one and will keep working on extending it. If two nodes broadcast different versions of the next block simultaneously, some nodes may receive one or the other first. In that case, they work on the first one they received, but save the other branch in case it becomes longer. The tie will be broken when the next proof- of-work is found and one branch becomes longer; the nodes that were working on the other branch will then switch to the longer one.

New transaction broadcasts do not necessarily need to reach all nodes. As long as they reach many nodes, they will get into a block before long. Block broadcasts are also tolerant of dropped messages. If a node does not receive a block, it will request it when it receives the next block and realizes it missed one.

這就是比特幣網路執行的基本步驟和基本規則。

比特幣網路實質是執行比特幣P2P協議的節點集合,但這只是現階段的協議,以後甚至其他幣種已經有了很多擴充套件協議。這些橋接進比特幣網路的節點屬於擴充套件比特幣網路。他們共同構成了比特幣網路。

一. 比特幣網路

1.1 節點類別

比特幣P2P網路的去中心,準確地說是節點之間的關係對等,而不是所有節點功能完全一致。比特幣網路中的節點有如下幾種功能:

1.網路路由(Network Route, 簡寫為N)

2.完整區塊鏈(Full Blockchain, 簡寫為B)

3.礦工(Miner, 簡寫為M)

4.錢包(Wallet, 簡寫為Wallet)。

如果一個比特幣節點具有上述全部四種功能,那麼這個節點叫做全節點。如果一個節點只儲存部分割槽塊,同時使用簡化支付驗證 (Simplified Payment Verification, SPV)的方法驗證交易,那麼這個節點叫做SPV節點或者輕量級節點。比特幣網路中的大部分節點都不是全節點, 本地資料庫只儲存部分割槽塊鏈的情況下,節點依然可以具有錢包功能或者挖礦功能。概括下來,擴充套件比特幣網路中的節點主要分為以下幾種:

· 參考客戶端 包含錢包,礦工,完整區塊鏈資料庫,比特幣P2P網路路由節點 

比特幣節點型別-參考客戶的.png-57.3kB

· 完整區塊鏈節點 包含完整區塊鏈資料庫,比特幣P2P網路路由節點

完整區塊鏈節點.png-38.3kB

· 輕量級(spv)錢包 包含錢包和比特幣P2P網路路由節點,不包含區塊鏈資料庫

輕量級(SPV)錢包.png-38kB

· 完整區塊鏈節點 包含挖礦函式,完整區塊鏈資料庫,比特幣P2P網路路由節點

純礦工.png-46.5kB

· 礦池協議伺服器 閘道器路由,用於連線比特幣P2P協議網路的其他節點

礦池協議伺服器.png-30.6kB

· 礦池礦工節點 包含挖礦函式,不包含完整區塊鏈資料庫,但執行著其他礦池協議

礦池礦工節點.png-27.8kB

· 輕量級(spv)Stratum錢包 包含錢包和Stratum協議節點,不包含區塊鏈資料

輕量級Stratum錢包.png-38.9kB

比特幣協議,stratum協議和礦池協議組成了比特幣網路的基礎,擴充套件比特幣網路結構如下所示。少量的全節點客戶端,少量的獨立礦工,礦池及其背後大量礦池礦工。

完整網路.png-776.6kB

二. 典型場景

下面我們從更微觀的角度解釋比特幣網路。比特幣的網路模組主要包括以下幾個功能:

1)建立初識連線

2)地址傳播發現

3)同步區塊資料

4)斷開連線

與比特幣相關的程式碼主要在src/net.cpp, src/netbase.cpp, src/net_processing.比特幣網路訊息型別定義見src/protocol.h

要理解每一種訊息的場景和含義,才能掌握比特幣網路的核心內容。

version - verack 建立連線

addr - getaddr 地址傳播

getblocks - inv - getdata 同步區塊鏈資料

namespace NetMsgType {
/**
 * The version message provides information about the transmitting node to the
 * receiving node at the beginning of a connection.
 * @see https://bitcoin.org/en/developer-reference#version
 */
extern const char *VERSION;
/**
 * The verack message acknowledges a previously-received version message,
 * informing the connecting node that it can begin to send other messages.
 * @see https://bitcoin.org/en/developer-reference#verack
 */
extern const char *VERACK;
/**
 * The addr (IP address) message relays connection information for peers on the
 * network.
 * @see https://bitcoin.org/en/developer-reference#addr
 */
extern const char *ADDR;
/**
 * The inv message (inventory message) transmits one or more inventories of
 * objects known to the transmitting peer.
 * @see https://bitcoin.org/en/developer-reference#inv
 */
extern const char *INV;
/**
 * The getdata message requests one or more data objects from another node.
 * @see https://bitcoin.org/en/developer-reference#getdata
 */
extern const char *GETDATA;
/**
 * The merkleblock message is a reply to a getdata message which requested a
 * block using the inventory type MSG_MERKLEBLOCK.
 * @since protocol version 70001 as described by BIP37.
 * @see https://bitcoin.org/en/developer-reference#merkleblock
 */
extern const char *MERKLEBLOCK;
/**
 * The getblocks message requests an inv message that provides block header
 * hashes starting from a particular point in the block chain.
 * @see https://bitcoin.org/en/developer-reference#getblocks
 */
extern const char *GETBLOCKS;
/**
 * The getheaders message requests a headers message that provides block
 * headers starting from a particular point in the block chain.
 * @since protocol version 31800.
 * @see https://bitcoin.org/en/developer-reference#getheaders
 */
extern const char *GETHEADERS;
/**
 * The tx message transmits a single transaction.
 * @see https://bitcoin.org/en/developer-reference#tx
 */
extern const char *TX;
/**
 * The headers message sends one or more block headers to a node which
 * previously requested certain headers with a getheaders message.
 * @since protocol version 31800.
 * @see https://bitcoin.org/en/developer-reference#headers
 */
extern const char *HEADERS;
/**
 * The block message transmits a single serialized block.
 * @see https://bitcoin.org/en/developer-reference#block
 */
extern const char *BLOCK;
/**
 * The getaddr message requests an addr message from the receiving node,
 * preferably one with lots of IP addresses of other receiving nodes.
 * @see https://bitcoin.org/en/developer-reference#getaddr
 */
extern const char *GETADDR;

2.1 建立連線

在比特幣客戶端,可以用bitcoin-cli getpeerinfo命令之後檢視可以連線到的網路節點資訊:

$ bitcoin-cli getpeerinfo
[{
    "addr": "85.213.199.39:8333",
    "services": "00000001",
    "lastsend": 1405634126,
    "lastrecv": 1405634127,
    "bytessent": 23487651,
    "bytesrecv": 138679099,
    "conntime": 1405021768,
    "pingtime": 0.00000000,
    "version": 70002,
    "subver": "/Satoshi:0.9.2.1/",
    "inbound": false,
    "startingheight": 310131,
    "banscore": 0,
    "syncnode": true
}, {
    "addr": "58.23.244.20:8333",
    "services": "00000001",
    "lastsend": 1405634127,
    "lastrecv": 1405634124,
    "bytessent": 4460918,
    "bytesrecv": 8903575,
    "conntime": 1405559628,
    "pingtime": 0.00000000,
    "version": 70001,
    "subver": "/Satoshi:0.8.6/",
    "inbound": false,
    "startingheight": 311074,
    "banscore": 0,
    "syncnode": false
}]

找到相鄰節點後,客戶端就開始和同伴建立TCP連線。節點A向B傳送版本號ver,B收到後,如果與自己相容則確認連線,B返回verack,同時向A傳送自己的版本號,如果A也相容,A在此返回verack,成功建立連線。整個過程如下圖:

初始連線.png-95.9kB

建立連線的更多細節見src/net.cpp。輸入待連線的地址addrConnect,返回該地址的節點pnode。如果該節點已經連線,直接返回該節點。如果是新節點,嘗試建立Socket連線或者通過代理連線。

CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure)
{
    if (pszDest == nullptr) {
        if (IsLocal(addrConnect))
            return nullptr;

        // Look for an existing connection
        CNode* pnode = FindNode(static_cast<CService>(addrConnect));
        if (pnode)
        {
            LogPrintf("Failed to open new connection, already connected\n");
            return nullptr;
        }
    }
    // 省略部分程式碼
    bool connected = false;
    SOCKET hSocket = INVALID_SOCKET;
    proxyType proxy;
    if (addrConnect.IsValid()) {
        bool proxyConnectionFailed = false;
        // 網路代理部分程式碼省略
        std::string host;
        int port = default_port;
        SplitHostPort(std::string(pszDest), port, host);
        connected = ConnectThroughProxy(proxy, host, port, hSocket, nConnectTimeout, nullptr);
    }
    if (!connected) {
        CloseSocket(hSocket);
        return nullptr;
    }
    NodeId id = GetNewNodeId();
    uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize();
    CAddress addr_bind = GetBindAddress(hSocket);
    CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(),hSocket, addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, addr_bind, pszDest ?pszDest : "", false);
    pnode->AddRef();

    return pnode;
}

2.2 地址傳播發現

一旦建立連線,節點便會向所有鄰居節點發送addr資訊,這條資訊包含自己的iP地址,用於向更多的節點告知自己。此外,節點還會向它所有的鄰居節點發送getaddr請求,獲取鄰居節點可以連線的節點列表。整個過程如下:

地址傳播發現.png-96.2kB

具體程式碼在開始所說的檔案中。

2.3 同步區塊鏈資料

連線建立後,兩個節點互相傳送同步請求,節點比較對方的BestHeight後,區塊鏈較多的一方向較少的一方傳送inv響應,讓落後的節點追上,收到inv響應後,落後的節點開始傳送getdata請求資料,整個過程如下:

同步區塊鏈資料.png-158.7kB

具體程式碼在開始所說的檔案中。

2.4 斷開連線

如果兩個節點建立網路後沒有流量,節點之間會定期傳送資訊保持連線,如果兩個節點超過一定時間沒有傳送資訊,那則認為節點已經斷開,並開始尋找新的節點。

具體程式碼在開始所說的檔案中。

三. 簡化支付驗證SPV

比特幣網路中重要的組成部分,輕量級錢包。

為了支援一些輕量級的裝置,於是有了簡化支付驗證SPV,使用者客戶端只需要儲存區塊header就可以驗證支付。使用者如果能夠從區塊鏈某處找到相符的交易,則證明網路已經認可了這筆交易,而且得到了網路的多少個確認。輕量級錢包只同步header也大大減少客戶端的網路開銷。

SPV節點同步區塊header.png-107kB

spv背後的技術原理是Bloom Filter。Bloom Filter是一種概率搜尋器,他在搜尋時不需要完整描述被搜尋的模式,這種查詢雖然存在一定比例的偽命中,但效率遠遠高出普通查詢。例如查詢名字是以g結尾,交易金額的小數部分是0.618的交易。搜尋條件的模糊程度和交易隱私洩露程度構成了一種權衡關係。有了SPV,我們可以在不暴露地址的情況下完成支付驗證。

這裡需要注意,SPV指的是“支付驗證“,而不是“交易驗證”。這兩種驗證有很大區別。”交易驗證”非常複雜,涉及到驗證是否有足夠餘額可供支出、是否存在雙花、指令碼能否通過等等,通常由執行完全節點的礦工來完成。“支付驗證”則比較簡單,只判斷用於“支付”的那筆交易是否已經被驗證過,並得到了多少的算力保護(多少確認數)。