015 | 詳解區塊鏈P2P網絡

原創文章,轉載請註明:轉載自Keegan小鋼 並標明原文連接:http://keeganlee.me/post/blockchain/20180313 微信訂閱號:keeganlee_me 寫於2018-03-13算法


專欄地址:xiaozhuanlan.com/fullstack數據庫


根據前一篇文章《從微觀到宏觀理解區塊鏈》咱們已經瞭解到,微觀上,區塊鏈本質就是一種不可篡改且可追蹤溯源的哈希鏈條;宏觀上,還具有了另外三個基本特徵:分佈式存儲、P2P 網絡和共識機制。分佈式存儲無非就是網絡上大部分節點都保存了整條區塊鏈,這容易理解也不復雜,因此就不必再展開細講了。但區塊鏈的 P2P 網絡和共識機制相對則複雜得多,所以我將用兩篇文章分別展開講講這兩部份內容,本篇文章就先來了解區塊鏈的 P2P 網絡。編程

P2P 網絡

因爲大部分人對 P2P 網絡瞭解甚少,所以有必要先聊聊 P2P 網絡的一些基本原理。這個章節的內容主要來自**《P2P對等網絡原理與應用》**這本書,這本書較爲系統地介紹了 P2P 的理論基礎,很是適合但願全面掌握 P2P 知識的初級讀者,建議大夥均可以看看。服務器

P2P 網絡不一樣於傳統的客戶端/服務端(client/server,C/S)結構,P2P 網絡中的每一個節點均可以既是客戶端也是服務端,所以也不適合使用 HTTP 協議進行節點之間的通訊,通常都是直接使用 Socket 進行網絡編程。微信

P2P 主要存在四種不一樣的網絡模型,也表明着 P2P 技術的四個發展階段:集中式、純分佈式、混合式和結構化模型。不過須要指出的是,這裏所說的網絡模型主要是指路由查詢結構,即不一樣節點之間如何創建鏈接通道,兩個節點之間一旦創建鏈接,具體傳輸什麼數據則是兩個節點之間的事情了。網絡

最簡單的路由方式就是集中式,即存在一箇中心節點保存了其餘全部節點的索引信息,索引信息通常包括節點 IP 地址、端口、節點資源等。集中式路由的優勢就是結構簡單、實現容易。但缺點也很明顯,因爲中心節點須要存儲全部節點的路由信息,當節點規模擴展時,就很容易出現性能瓶頸;並且也存在單點故障問題。 架構

那第二種路由結構則是純分佈式的,移除了中心節點,在 P2P 節點之間創建隨機網絡,就是在一個新加入節點和 P2P 網絡中的某個節點間隨機創建鏈接通道,從而造成一個隨機拓撲結構。新節點加入該網絡的實現方法也有不少種,最簡單的就是隨機選擇一個已經存在的節點並創建鄰居關係。像比特幣的話,則是使用 DNS 的方式來查詢其餘節點,DNS 通常是硬編碼到代碼裏的,這些 DNS 服務器就會提供比特幣節點的 IP 地址列表,從而新節點就能夠找到其餘節點創建鏈接通道。新節點與鄰居節點創建鏈接後,還須要進行全網廣播,讓整個網絡知道該節點的存在。全網廣播的方式就是,該節點首先向鄰居節點廣播,鄰居節點收到廣播消息後,再繼續向本身的鄰居節點廣播,以此類推,從而廣播到整個網絡。這種廣播方法也稱爲泛洪機制。純分佈式結構不存在集中式結構的單點性能瓶頸問題和單點故障問題,具備較好的可擴展性,但泛洪機制引入了新的問題,主要是可控性差的問題,包括兩個較大的問題,一是容易造成泛洪循環,好比節點 A 發出的消息通過節點 B 到 節點 C,節點 C 再廣播到節點 A,這就造成了一個循環;另外一個棘手問題則是響應消息風暴問題,若是節點 A 想請求的資源被不少節點所擁有,那麼在很短期內,會出現大量節點同時向節點 A 發送響應消息,這就可能會讓節點 A 瞬間癱瘓。 分佈式

再來看看第三種路由結構:混合式。混合式其實就是混合了集中式和分佈式結構,以下圖所示,網絡中存在多個超級節點組成分佈式網絡,而每一個超級節點則有多個普通節點與它組成局部的集中式網絡。一個新的普通節點加入,則先選擇一個超級節點進行通訊,該超級節點再推送其餘超級節點列表給新加入節點,加入節點再根據列表中的超級節點狀態決定選擇哪一個具體的超級節點做爲父節點。這種結構的泛洪廣播就只是發生在超級節點之間,就能夠避免大規模泛洪存在的問題。在實際應用中,混合式結構是相對靈活而且比較有效的組網架構,實現難度也相對較小,所以目前較多系統基於混合式結構進行開發實現。其實,比特幣網絡現在也是這種結構,後面再細說。 函數

最後一種網絡則是結構化 P2P 網絡,它也是一種分佈式網絡結構,但與純分佈式結構不一樣。純分佈式網絡就是一個隨機網絡,而結構化網絡則將全部節點按照某種結構進行有序組織,好比造成一個環狀網絡或樹狀網絡。而結構化網絡的具體實現上,廣泛都是基於 **DHT(Distributed Hash Table,分佈式哈希表) **算法思想。DHT 只是提出一種網絡模型,並不涉及具體實現,主要想解決如何在分佈式環境下快速而又準確地路由、定位數據的問題。具體的實現方案有 Chord、Pastry、CAN、Kademlia 等算法,其中 Kademlia 也是以太坊網絡的實現算法,不少經常使用的 P2P 應用如 BitTorrent、電驢等也是使用 Kademlia。由於篇幅有限,就不展開講這些算法的具體原理了。目前,咱們主要理解 DHT 的核心思想便可。post

在 P2P 網絡中,能夠抽象出兩種空間:資源空間節點空間。資源空間就是全部節點保存的資源集合,節點空間就是全部節點的集合。對全部資源和節點分別進行編號,如把資源名稱或內容用 Hash 函數變成一個數值(這也是 DHT 經常使用的一種方法),這樣,每一個資源就有對應的一個 ID,每一個節點也有一個 ID,資源 ID 和節點 ID 之間創建起一種映射關係,好比,將資源 n 的全部索引信息存放到節點 n 上,那要搜索資源 n 時,只要找到節點 n 便可,從而就能夠避免泛洪廣播,能更快速而又準確地路由和定位數據。固然,在實際應用中,資源 ID 和節點 ID 之間是沒法作到一一對應的,但由於 ID 都是數字,就存在大小關係或偏序關係等,基於這些關係就能創建二者的映射關係。這就是 DHT 的核心思想。DHT 算法在資源編號和節點編號上就是使用了分佈式哈希表,使得資源空間和節點空間的編號有惟一性、均勻分佈式等較好的性質,可以適合結構化分佈式網絡的要求。

綜上,這就是 P2P 網絡的一點理論基礎,不一樣的區塊鏈可能會使用不同的網絡模型,但基本原理是同樣的。後面分別講解下最有表明性的兩個區塊鏈的網絡:比特幣網絡和以太坊網絡。

比特幣網絡

首先,比特幣網絡中的節點主要有四大功能:錢包、挖礦、區塊鏈數據庫、網絡路由。每一個節點都會具有路由功能,但其餘功能不必定都具有,不一樣類型的節點可能只包含部分功能,通常只有**比特幣核心(bitcoin core)**節點纔會包含全部四大功能。

全部節點都會參與校驗和廣播交易及區塊信息,且會發現和維持與其餘節點的鏈接。有些節點會包含完整的區塊鏈數據庫,包括全部交易數據,這種節點也稱爲全節點(Full Node)。另一些節點只存儲了區塊鏈數據庫的一部分,通常只存儲區塊頭而不存儲交易數據,它們會經過「簡化交易驗證(SPV)」的方式完成交易校驗,這樣的節點也稱爲 SPV節點輕節點(Lightweight Node)。錢包通常是 PC 或手機客戶端的功能,用戶經過錢包查看本身的帳戶金額、管理錢包地址和私鑰、發起交易等。除了比特幣核心錢包是全節點以外,大部分錢包都是輕節點。挖礦節點則經過解決工做量證實(PoW)算法問題,與其餘挖礦節點相互競爭建立新區塊。有些挖礦節點同時也是全節點,即也存儲了完整的區塊鏈數據庫,這種節點通常都是獨立礦工(Solo Miner)。還有一些挖礦節點不是獨立挖礦的,而是和其餘節點一塊兒鏈接到礦池,參與集體挖礦,這種節點通常也稱爲礦池礦工(Pool Miner)。這會造成一個局部的集中式礦池網絡,中心節點是一個礦池服務器,其餘挖礦節點所有鏈接到礦池服務器。礦池礦工礦池服務器之間的通訊也不是採用標準的比特幣協議,而是使用礦池挖礦協議,而礦池服務器做爲一個全節點再與其餘比特幣節點使用主網絡的比特幣協議進行通訊。

在整個比特幣網絡中,除了不一樣節點間使用比特幣協議做爲通訊協議的主網絡,也存在不少擴展網絡,包括上面提到的礦池網絡。不一樣的礦池網絡可能還會使用不一樣的礦池挖礦協議,目前主流的具體礦池協議應該是 Stratum協議,該協議除了支持挖礦節點,也支持瘦客戶端錢包。一個包含了比特幣協議主網絡各類節點和 Stratum 網絡,以及其餘礦池網絡的擴展比特幣網絡大概以下圖所示:

另外,挖礦這塊還有特殊需求。咱們知道,礦工建立新區塊後,是須要廣播給全網全部節點的,當全網都接受了該區塊,給礦工的挖礦獎勵纔算是有效的,這以後纔好開始下一個區塊 Hash 的計算。因此礦工必須最大限度縮短新區塊的廣播和下一個區塊 Hash 計算之間的時間。若是礦工之間傳播區塊只採用上圖所示的比特幣協議網絡,那無疑會有很高的網絡延遲,因此,須要一個專門的傳播網絡用來加快新區塊在礦工之間的同步傳播,這個專門網絡也叫比特幣傳播網絡比特幣中繼網絡(Bitcoin Relay Network)

以太坊網絡

和比特幣同樣,以太坊的節點也具有錢包、挖礦、區塊鏈數據庫、網絡路由四大功能,也一樣存在不少不一樣類型的節點,除了主網絡以外也一樣存在不少擴展網絡。但與比特幣不一樣的,比特幣主網的 P2P 網絡是無結構的,但以太坊的 P2P 網絡是有結構的。前面咱們已經提過,以太坊的 P2P 網絡主要採用了 Kademlia(簡稱 Kad) 算法實現,Kad 是一種**分佈式哈希表(DHT)**技術,使用該技術,能夠實如今分佈式環境下快速而又準確地路由、定位數據的問題。因此,下面主要講解下以太坊的 Kad 網絡。

在 Kad 網絡中,每一個節點都具備一個惟一的節點 ID。另外,也會計算不一樣節點之間的距離,但這個距離不是物理上的距離,而是邏輯上的距離,是經過對兩個節點 ID 進行 異或(符號爲^) 計算獲得的,即 A、B 兩節點之間的距離的計算公式爲:D(A,B) = A.ID^B.ID。異或有一個重要的性質:假設 a、b、c 爲任意三個數,若是 a^b = a^c 成立,那就必定 b = c。所以,若是給定一個結點 a 和距離 L,那就有且僅有一個結點 b, 會使得 D(a,b) = L。經過這種方式,就能有效度量 Kad 網絡中不一樣節點之間的邏輯距離。

在異或距離度量的基礎上,Kad 還能夠將整個網絡拓撲組織成以下圖所示的一個二叉前綴樹,每一個 NodeID 會映射到二叉樹上的某個葉子。

映射規則主要是:

  1. 將 NodeID 以二進制形式表示,而後從高到低對每一位的 0 或 1 依次處理;
  2. 二進制的第 n 位就對應了二叉樹的第 n 層;
  3. 若是該位是 0,進入左子樹,是 1 則進入右子樹(反過來也能夠);
  4. 所有位都處理完後,這個 NodeID 就對應了二叉樹上的某個葉子。

在這種二叉樹結構下,對每一個節點來講,離它越近的節點異或距離也是越近的。接着,能夠按照離本身異或距離的遠近,對整顆二叉樹進行拆分。拆分規則是:從根節點開始,將不包括本身的那顆子樹拆分出來,而後在包含本身的子樹中,把不包括本身的下一層子樹再拆分出來,以此類推,直到只剩下本身。以上圖的 110 節點爲例,從根節點開始,因爲 110 節點在右子樹,因此將左邊的整顆子樹拆分出來,即包含 000、00一、010 這三個節點的這顆子樹;接着,到第二層子樹,將不包含 110 節點的左子樹再拆分出來,即包含 100 和 101 這兩個節點的子樹;最後,再將 111 拆分出來。這樣,就將 110 節點以外的整個二叉樹拆分出了三顆子樹。

完成子樹拆分後,只要知道每一個子樹裏面的其中一個節點,就能夠進行遞歸路由實現整顆二叉樹全部節點的遍歷。但在實際場景下,因爲節點是動態變化的,因此通常不會只知道每一個子樹的一個節點,而是須要知道多個節點。所以,Kad 中有一個叫 K-桶(K-bucket)的概念,每一個桶會記錄每顆子樹裏所知道的多個節點。其實,一個K-桶就是一張路由表,若是拆分出來有 m 顆子樹,那對應節點就須要維護 m 個路由表。每一個節點都會各自維護本身的 m 個 K-桶,每一個 K-桶裏記錄的節點信息通常會包括 NodeID、IP、Endpoint、與 Target 節點(即維護該 K-桶的節點)的異或距離等信息。以太坊中,每一個節點維護的 K-桶數量爲 256 個,這 256 個 K-桶會根據與 Target 節點的異或距離進行排序,每一個 K-桶保存的節點數量上限是 16。

在以太坊的 Kad 網絡中,節點之間的通訊是基於 UDP 的,另外設置了 4 個主要的通訊協議:

  1. Ping:用於探測一個節點是否在線
  2. Pong:用於響應 Ping 命令
  3. FindNode:用於查找與 Target 節點異或距離最近的其餘節點
  4. Neighbours:用於響應 FindNode 命令,會返回一或多個節點

經過以上 4 個命令,就能夠實現新節點的加入、K-桶的刷新等機制。具體的實現流程就不細講了,留給大夥本身去思考。

總結

不一樣結構的 P2P 網絡,會有不一樣的優勢和缺點。比特幣網絡的結構明顯容易理解,實現起來也相對容易得多,而以太坊網絡引入了異或距離、二叉前綴樹、K-桶等,結構上覆雜很多,但在節點路由上的確會比比特幣快不少。另外,無論是比特幣仍是以太坊,其實都只是一種或多種協議的集合,不一樣節點其實能夠用不一樣的具體實現,好比,比特幣就有用 C++ 實現的 Bitcoin Core,還有用 Java 實現的 BitcoinJ;以太坊也有用 Go 語言實現的 go-ethereum,也有用 C++ 實現的 go-ethereum,還有用 Java 實現的 Ethereum(J)。

思考和實踐

在以太坊的 Kad 網絡中,新節點的加入和 K-桶的刷新流程是怎樣的?比特幣的新節點加入流程又是怎樣的?哈希表有哪些實現方式?


掃描如下二維碼便可關注訂閱號。

相關文章
相關標籤/搜索