區塊鏈中需要高效率的通訊工具來確保節點之間的順暢互動。而libp2p 正是開發者在點對點通訊中不可或缺的框架,提供了強大的模組化功能,使得去中心化網路中的訊息傳遞變得更加靈活且安全。在Substrate 中,libp2p 的整合幫助開發者輕鬆實現各種客製化協議,簡化了區塊鏈節點之間的通訊流程,讓複雜的網路互動變得更加直觀和高效。
本篇文章是由PaperMoon kaichao 老師所撰寫的Substrate 進階課程技術文章的六篇,將帶你深入了解點對點通訊的優勢、libp2p 的架構設計,以及Substrate 如何將這些技術應用於去中心化區塊鍊網路的搭建,幫助你在開發自訂區塊鏈應用時,輕鬆應對複雜的網路環境。
點對點通訊模型的崛起與意義
在Web2.0 時代,絕大多數網路應用程式採用了基於TCP/IP的「客戶端-伺服器」通訊架構,在客戶端採集資料並發送給伺服器,伺服器儲存和處理數據,客戶端進而取得並使用這些處理後的數據。這種模式支撐了網路近三十年的蓬勃發展,在給予一般使用者便利的同時,也出現了各種各樣的問題,例如:
洩漏用戶隱私;
販賣用戶數據;
服務商發布沒有底線的廣告對不明真相的用戶造成不可挽回的損失;
不經協商,隨意刪除用戶發佈的內容;
壟斷市場和定價;
過度利用使用者心理,無節制地佔據使用者的時間;
……
客戶端-伺服器通訊模型如下圖:
在為上述問題尋找解決方案時,點對點(即peer to peer)的通訊機制逐漸走進了科技先驅們的視野。在網路早期的時候,點對點通訊主要用於檔案共享,如音樂共享服務Napster 和串流下載服務BitTorrent。點對點服務更廣泛的應用,還需要一定的治理機制,來處理資源的版權問題和現實世界的監管,這些不是本文的重點,不做過多地介紹。
點對點通訊模型如下圖:
在點對點的網路裡,所有的節點都是對等的,即任何節點都可以儲存和處理資料(作為服務端);也可以發送待處理的資料給網路中的其它節點,取得經過網路處理後的數據(作為客戶端)。透過這樣的通訊機制,可以保證,
網路具備開放性,節點可以自由加入和退出;
不依賴單一服務節點,網路的服務更可靠、更有效率;
節點運行的程式碼公開可見,規則更加透明。
根據網路中傳輸的資料和提供服務的不同,點對點應用出現了不同的應用場景,包括檔案儲存和讀取、資料計算、內容共享、資料交易等服務。在開發這些應用的過程中,可能涉及的技術要點有:
節點身份,唯一標識網路中的節點及位址格式;
發現機制,在沒有中心化的協調服務存在的情況下,如何發現新的節點;
路由,本地節點無法儲存網路中所有節點的信息,透過路由演算法查找需要的節點;
多種通訊協定如TCP、UDP、WebSocket、QUIC 等等;
加密和認證,保證訊息的可靠和安全;
NAT 穿透,解決NAT 後面的內部IP無法存取的難題;
多路復用以節省資源;
訊息訂閱,高效率的獲取更新而不會對網路造成負擔;
中繼,當需要建立通訊的兩個節點都無法直接被訪問,例如都在NAT 網路中,需要透過中繼節點傳遞訊息;
……
以上列出的這些技術要點/需求並不會出現在每個點對點應用裡,大多數只會使用其中的一部分功能,儘管如此,還是存在嚴重地重複造輪子的現象。也有一些應用為了避免重複開發,選擇了fork 已有開源應用的功能程式碼,這種方式引進了原有應用的技術債,難於客製化和擴充。
複雜多變的網路拓樸和膨脹的應用狀態導致了點對點應用的開發、推廣和普及都極為困難,出現一個高度模組化的點對點通訊開發框架也就不足為奇,也就是接下來我們要介紹的libp2p 。
模組化點對點通信,框架的誕生— libp2p
Libp2p 是一個開發點對點應用的框架,它最早源於去中心的文件共享服務IPFS,把網絡通信相關的內容抽離並重新設計,形成了現在的libp2p,目前比較成熟的幾個語言版本包括js- libp2p、go-libp2p、rust-libp2p,並且定義了一套參考規範,不同語言的實作版本只要符合此規範,就可以實作互通訊。
Libp2p 提供的核心功能包括,
在節點之間建立安全可重複使用的網路連線;
可驗證的節點身份和可連接的位址。
安全可重複使用的連接
Libp2p 支援的底層(傳輸層)協定包含TCP/IP、UDP、WebSocket、QUIC等,不同語言版本的實作完成度不盡相同。連線的安全性是透過對傳輸內容進行加密來保證的,節點的身份也會進行相應的驗證。
為了提升連接的利用率以及應對複雜的網路場景如各種形式的防火牆和NAT,對建立的底層連接進行多路復用十分有必要, s tream 就是可實現復用的一種上層連接形式,它可以是雙向的,也可以是單向的。
QUIC 協議有內建的安全性和復用組件,對於沒有此類功能的協議,使用libp2p 可以對原始連接進行upgrade,添加所需的安全和可復用的套件,安全套件有secio 、 Noise ,可復用套件有yamux和mplex 。
Upgrade 協議的流程如下圖:
在stream 裡可以傳輸各種各樣的libp2p 內建或用戶自訂的應用層協議,這些協議定義了節點間交換資訊的方式和內容,例如:
ping ,用來定時檢查節點是否在線;
identity ,用於節點間交換資訊如節點的public key 與網路中的位址;
kad-dht ,基於Kademlia 演算法的分佈雜湊表,用於節點間路由;
……
以identity 協定為例,它的協定id(具有路徑格式的字串)為/ipfs/id/1.0.0
,訊息的表示和序列化使用的是protocol buffer ,
節點身份
節點啟動時需要提供一個private key(也可以隨機產生) ,主要用於
將節點雙方的公鑰透過Diffie-Helman key exchange對訊息進行加解密;
對節點的public key 進行哈希,產生PeerId 即節點身分。
Libp2p 支援的公鑰加密演算法包括RSA、Ed25519、Secp256k1 等。 PeerId 的生成採用了multihashes的形式,即支援多種雜湊演算法,經過base 58 編碼後的格式如QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N
。
將PeerId 與multiaddr結合可以用來在網路中定位節點和驗證身份,例如IP 位址為7.7.7.7、監聽在4242 連接埠、擁有上述PeerId 的節點的multiaddr(多層位址)為:
以上只列出了libp2p 提供的部分功能,更多內容例如訊息訂閱、中繼、NAT 穿透等等可以參考相關文檔,使用libp2p 開發點對點應用可以解決以上提到的大部分難題和技術點,節約大量的開發時間,增加系統的可維護性和可擴充性。接下來,我們來看看如何使用rust-libp2p 實作簡單的自訂應用協定。
簡單應用
這裡我們基於rust-libp2p,編寫一個簡單的點對點應用,可以完成迴聲(echo)的功能,即其中一個節點發送一個字符串,另一個節點接收該字符串並回復相同的字符,這裡我們需要自定義一個應用層的協定EchoProtocol
,需要實作libp2p 提供的UpgradeInfo
介面。
這裡的protocol_info
方法傳回了協定的名字和格式。接著實作InboundUpgrade
和OutboundUpgrade
,這兩個介面都繼承自UpgradeInfo。
NegotiatedSubstream
表示協商好的某個協議將會使用的I/O 流。當遠端的節點支援目前協定時,呼叫upgrade_inbound
和upgrade_outbound
分別在listener 和dialer 端開啟握手訊號。
之後,定義處理連線請求的handler,也就是我們這裡的結構體EchoHandler
,它保存了處理過程中所使用的狀態資訊。
還需要一個自訂的枚舉event 枚舉類型。
接著就可以實作libp2p::swarm 裡所提供的ProtocolsHandler
介面了,
當節點為dialer,handler 在輪詢( ProtocolsHandler::poll()
)時,需要傳回包含EchoProtocol
實例的ProtocolsHandlerEvent::OutboundSubstreamRequest
,用於發起並協商連接使用的協定。如果協商成功,呼叫ProtocolsHandler::inject_fully_negotiated_outbound
,在這裡我們將handler 保存的outbount 狀態由None 更新為Some(send_echo(stream).boxed())
,其中send_echo
接收協商好的IO stream,無錯誤發生時返回該stream。
我們接著看Pr
otocolsHandler::poll
裡的實現,當outbound為Some,send_echo 回傳的future 輪詢的結果為Poll::Pending
時,更新outbound 為self.outbound = Some(send_echo_future)
,保證下次輪詢時依然有效,當結果為Poll::Ready
時傳回對應的事件資訊。
當節點為listener,連線中出現新的請求流時,自動呼叫ProtocolsHandler::listen_protocol
傳回一個InboundUpgrade
的實例來協商流使用的協定。協商成功之後,呼叫inject_fully_negotiated_inbound
,其中一個參數為協商好的stream,在該方法內,將handler 的inbound 屬性狀態更新為Some(recv_echo(stream).boxed())
, recv_echo
方法的實作為。
這裡泛型S
需要滿足futures_io提供的AsyncRead
和AsyncWrite
限制。
點對點網路就像一個蜂群(Swarm) ,而蜂群的整體行為是由單一個體的行為所組成的,單一個體的行為由一系列的規則所製定,此類的規則可以組合使用,在rust- libp2p 中,規定的定義需要實作NetworkBehaviour
接口,這裡我們先定義一個結構體,對規則的狀態進行保存。
本結構體包含了與Swarm 溝通的訊息events
,行為定義所需的初始配置。接著,就可以實現NetworkBehaviour
介面了,
當連接建立或嘗試去呼叫節點時會呼叫new_handler
,返回我們先前定義的handler 即EchoHandler
,作為該連接的後台處理線程,behaviour 和handler 透過訊息傳遞的機制進行通信, inject_event
可以把handler 的訊息傳給behaviour ,behaviour在poll 的時候返回SendEvent
將訊息傳遞給handler。到這裡,我們已經完成了一個簡單的echo 點對點通訊協議,現在我們來看看main 函數裡如何使用。
程式碼的簡單說明如下:
透過
Keypair::generate_ed25519
產生用於節點間通訊加密的金鑰,其中的公鑰可以派生出節點的PeerId
。libp2p::build_development_transport
建構了開發常用的傳輸層,支援TCP/IP、WebSocket,使用noise 協定作為加密層,yamux 和mplex 多重化協定。解析傳入參數,如果包含呼叫的節點訊息,則是dialer(客戶端),將構造behaviour 的初始參數
init_echo
設為true。使用上面建構的傳輸層、behaviour、節點id,呼叫
Swarm::new(transport, behaviour, peer_id)
模擬網路的swarm。當節點為dialer 時,呼叫傳入的遠端節點
Swarm::dial_addr(&mut swarm, remote)?
,將該節點加入swarm 節點池。對swarm 進行輪詢
swarm.poll_next_unpin(cx)
,如果有behaviour 觸發的訊息,處理對應的訊息。
小結,libp2p 對點對點通訊進行了高度的抽象,在開始接觸這些概念時,容易摸不著頭腦,需要不斷去熟悉劃分的層次和常用的協議; rust-libp2p 的實現,針對libp2p 定義的層次和協議,封裝出了不同的接口,在開發自訂協定的同時,需要深入了解這些抽象的接口及接口間通訊的方式。總體來說,點對點通訊開發的難度比傳統的客戶端-伺服器通訊形式高很多,libp2p 的設計在於彌合這其中的一些痛點,但也還有很長的路要走,應用開發者需要更多地了解底層的機制才能更好的開發應用協定。目前,使用libp2p 的應用包括IPFS,Substrate/Polkadot,Libra,Ethereum 2.0 等等,接下來我們來了解下Substrate 如何使用的libp2p。
Substrate 網路層架構,多協定支援與節點發現機制的實現
區塊鏈網路是由去中心(或是點對點)的節點所組成,節點之間透過網路連接傳遞訊息, Substrate 作為通用的區塊鏈開發框架,它的網路層使用了rust-libp2p,可以很容易使用、擴展一系列的通訊協議,例如:
傳輸層支援TCP/IP(位址格式為
/ip4/1.2.3.4/tcp/5
)、WebSocket(位址格式為/ip4/1.2.3.4/tcp/5/ws
)、DNS(位址格式/dns/
example.com/tcp/5
或/dns/
example.com/tcp/5/ws
),以及對應的IPv6 格式;在傳輸層之上應用了加密協定Noise ;
支援多路復用協定Yamux和Mplex ,其中mplex會逐步廢棄;
使用libp2p 標準的ping 協定(
/ipfs/ping/1.0.0
),週期性的檢查節點間的網路連線是否還活著,如果檢查失敗會斷開連線;使用libp2p 標準的id 協定(
/ipfs/id/1.0.0
),節點之間透過該協定週期性地交換節點各自的資訊;libp2p 標準的Kademlia 協定(
/<protocol_id>/kad
),執行Kademlia random walk查詢,其中protocol_id 可以用來區分不同的鏈,在Substrate chain spec 中進行定義;自訂的sync 協議(
/<protocol-id>/sync/2
),用來同步區塊信息,請求和返回結果的數據格式定義在api.v1.proto文件中;自訂的light 協定(
/<protocol-id>/light/2
),輕客戶端用此協定同步鏈上的狀態信息,資料格式定義在light.v1.proto檔案中;自訂的transactions 協定(
/<protocol-id>/transactions/1
),用來廣播節點接收到的交易訊息,它的格式是交易集合的SCALE 編碼結果;自訂的區塊廣播協定(
/<protocol-id>/block-announces/1
),當節點產出或接收到區塊時,將此區塊向其它節點進行廣播;自訂的gossip 協定(
/paritytech/grandpa/1
), GRANDPA 用來通知其它節點相關的投票資訊;自訂的Substrate legacy 協議(
/substrate/<protocol-id>/<version>
),是一個即將被棄用的協議,它也可以同步、廣播區塊信息,處理輕型客戶端請求, Gossiping(被GRANDPA使用)等等。
結合以上的底層和應用層通訊協議,Substrate 的節點之間可以透過三種發現機制建立起連接,
啟動節點(bootstrap nodes),它的位址和PeerId 都是固定的,適用於網路的冷啟動和某節點剛加入網路時,透過啟動節點進入網路;
mDNS ,在本地網路透過廣播UDP 封包,如果有節點回應,則可以建立起連接;
Kademlia random walk,當連接建立後,當前節點可以透過
FIND_NODE
請求遠端節點,取得遠端節點關於當前網路中節點組成的視角。
以上的協定共同構成了Substrate 的通用網路層,而這一網路層的使用是透過NetworkWorker
和NetworkService
結構體來實現的,在node template 節點程式中的使用範例如下:
去中心的通訊模型為網路應用開啟了新的範式,也帶來了相當大的挑戰,libp2p 規範的出現,逐步減輕了開發者在開發點對點應用所遇到的痛點。 S ubstrate 借助libp2p 的優良特性,在區塊鏈這一細分的去中心應用領域,可以很方便的讓普通開發者無需過多的關注底層的通信機制,就可以完成複雜的自定義區塊鏈應用。
倒數計時開啟!波卡黑客松曼谷站,即將迎來最終衝刺
為推動開發者深入探索Polkadot 生態與Web3, OneBlock+ 社群自2024 年7 月11 日起舉辦的波卡黑客松大賽現已進入最後階段。曼谷站的Demo Day 將於11 月16 日精彩呈現,距離代碼提交截止(10 月23 日中午12:00 UTC+8)僅剩不多時間!作為本次大賽的第二場,曼谷站吸引了許多創新團隊,共同競爭總獎金池高達63 萬美元的豐厚獎項。抓住最後機會,加入波卡生態,讓你的專案在舞台上閃耀!
🏄♂️ 立即報名:https://forms.gle/4pNpmp92pnX2wWSZ8
🧺 參賽指南:
曼谷站:https://dorahacks.io/zh/hackathon/polkadot-2024-bangkok/detail
🛠️ Github 程式庫:https://github.com/OneBlockPlus/polkadot-hackathon-2024
🗳️ 技術資源庫:https://github.com/OneBlockPlus/Technical-docs/blob/main/Substrate-technical-docs.md
第八期Substrate 開發進階課程,深度解析與專案實戰,幫助您領先區塊鏈
想要快速掌握區塊鏈技術的核心,建構屬於自己的應用? OneBlock+ 聯合Polkadot 推出第八期《Substrate 開發進階與專案實戰》課程,邀請了業界資深專家——王大錘、周俊和孫凱超,為學員提供專業指導。課程將全面解析Substrate 的關鍵技術,幫助你掌握前沿開發技巧,並透過專案實戰提升動手能力。不論你是想在區塊鏈產業嶄露頭角,還是期待實現職業突破,本次課程將為你打開成功的道路。
🪅立即加入:https://wj.qq.com/s2/14825200/0zv4/
增加小助手Emma ( 🆔 oneblockEmma) 取得更多資訊!