以太坊作爲一個去中心化平臺,其核心功能依賴於 p2p(peer-to-peer)網絡,p2p 網絡負責執行層節點之間的通信,爲其他子協議提供底層能力、傳播區塊和交易,並支持以太坊實現去中心化。
1. p2p 網絡設計概述
1.1 DevP2P 簡介
以太坊執行層 p2p 網絡功能的實現基於 DevP2P 協議棧所定義的標準,p2p 網絡層獨立於共識機制(無論是之前的 PoW 還是當前的 PoS,後者由共識層協調)。Geth 的網絡架構包含兩個並行工作的協議棧:基於 UDP 的發現協議棧(主要用於網絡節點間發現)和基於 TCP 的通信協議棧(主要用於節點間數據交換與同步)。
1.2 DevP2P 協議棧
DevP2P 並非指單一協議,而是爲以太坊 p2p 網絡定製的一套網絡協議。其設計不侷限於特定的區塊鏈,但主要服務與以太坊生態的需求。其核心包括:
-
節點發現層:基於 UDP 協議,用於在 p2p 網絡上定位其他以太坊節點。包含如 DiscoveryV4、DiscoveryV5、DNS Discovery 等協議。
-
數據傳輸層:基於 TCP 的協議,爲節點間提供加密且經過身份驗證的通信會話。主要由 RLPx 傳輸協議提供支持,基於此協議還衍生出多種應用層協議。
-
應用層子協議:在 RLPx 建立的節點發現和安全連接的基礎之上,處理節點間的具體數據交互和應用邏輯。這些協議使得節點能夠進行區塊鏈同步、交易傳播、狀態查詢等操作。包含如核心的 ETH 協議(Ethereum Wire Protocol)、服務輕客戶端的 LES 協議(Light Ethereum Subprotocol)、用於快照同步的 SNAP 協議,以及 WIT(Witness Protocol)、PIP (Parity Light Protocol) 。
以下爲 DevP2P 協議棧整體結構圖:
DevP2P 協議棧整體結構圖
應用層子協議簡要說明:
協議 |
版本 |
主要功能 |
以太坊線路協議(Ethereum Wire Protocol) |
eth/68 |
區塊鏈同步和交易交換的主協議 |
以太坊快照協議(Ethereum Snapshot Protocol) |
snap/1 |
高效狀態快照交換 |
輕量級以太坊子協議(Light Ethereum Subprotocol) |
les/4 |
輕客戶端協議(暫未在 Geth 中實現) |
Parity 輕協議(Parity Light Protocol) |
pip/1 |
Parity 對輕客戶端的實現(暫未在 Geth 中實現) |
以太坊見證協議(Ethereum Witness Protocol) |
wit/0 |
節點間見證數據交換(暫未在 Geth 中實現) |
1.3 DevP2P 與 LibP2P 的關係
LibP2P(library peer-to-peer)是一個 p2p 網絡框架,與以太坊的 DevP2P 在功能定位上有相似之處,它通過一系列協議、規範和庫爲 p2p 應用的開發提供支持。儘管兩者存在相似性,以太坊在其發展初期(約 2014-2015年)並未選擇採用LibP2P 作爲其P2P層。
原因是以太坊團隊開發 p2p 網絡模塊(最終形成了 DevP2P)的關鍵時期,LibP2P 雖然已經作爲 IPFS 項目的一部分,但它在當時尚未成熟到能夠被以太坊直接集成。爲了滿足以太坊自身的需求,以太坊的開發者們自行設計和實現了一套 p2p 協議。
與 DevP2P 不同,LibP2P 最開始的核心目標便是構建一個更具普適性、高度模塊化的 P2P 網絡基礎,旨在服務多樣化的去中心化應用,而非僅僅應用在某一特定平臺或應用場景。
DevP2P 可以被視爲一個協議集合,明確定義了以太坊所需的組件,如 ENR、discv5 和 RLPx。而 LibP2P 更像是一個編程庫集合,提供了可組合的模塊來構建各種 P2P 功能,包括傳輸、流多路複用、安全通道、節點發現、發佈/訂閱消息傳遞等。
目前在以太坊共識層,如 Lighthouse,Prysm,Teku,Nimbus 等客戶端,主要採用 LibP2P,共識層利用了 LibP2P 的傳輸層實現(TCP, QUIC)、加密(Noise)、流多路複用(mplex, yamux)、節點發現(基於 ENR 的 discv5)以及強大的發佈/訂閱系統(gossipsub)來高效廣播證明和區塊。
2. DevP2P 協議棧詳解
2.1 發現層
發現協議V4(Discovery Protocol v4)
發現協議V4 (簡稱DiscV4)是以太坊 DevP2P 協議棧中的核心組件之一,它是一種基於 Kademlia 算法思想的分佈式哈希表(DHT),用於網絡中的節點發現。Kademlia 的核心機制在於通過節點 ID 間的特定“距離”度量(通過 XOR 異或運算得到)來組織節點,並將其他節點的信息(如IP地址、端口和節點 ID)存儲到稱爲“k桶”(k-buckets)的路由表中。這些信息會被組織爲爲以太坊節點記錄(Ethereum Node Records, ENR),其中包含了節點的網絡地址(IP地址、TCP 與 UDP 端口)、由公鑰生成的唯一節點 ID、公鑰本身,以及用於驗證記錄有效性的簽名和序列號。
因此,DiscV4 的目標是幫助節點在未知網絡中其他節點地址的情況下,能夠高效地發現並連接到這些對等節點。這種高效性得益於其迭代查詢方法:當一個節點 A 要查找目標節點 B 時,它會向自己k 桶中已知且 ID “距離”目標 B 最近的一批節點發送查詢請求。這些被查詢的節點會回覆它們各自路由表中離目標 B 更近的節點列表。節點 A 持續向這些新發現的、更近的節點發起查詢,逐步逼近目標,通常能在對數時間複雜度(O(log N),其中 N爲網絡節點總數)內定位到網絡中的任意節點,這遠比隨機探測或全網廣播更爲高效且節省網絡資源。
發現協議V5(Discovery Protocol v5)
發現協議V5(簡稱Discv5,當前最新規範爲v5.1)作爲對 DiscV4 的重大升級版本,在保留了基於 UDP 的 Kademlia DHT 核心思想(例如通過節點 ID 的 XOR “距離”定義網絡拓撲、使用“k桶”維護路由表,以及通過 PING
/PONG
和迭代的 FINDNODE
/NODES
消息進行節點發現)的同時,引入了多項關鍵改進以彌補前代協議在地址管理、信息承載和可擴展性上的不足。其關鍵更新在於原生實現了以太坊節點記錄(ENR,遵循 EIP-778標準)。
這與 Discv4 後期才通過擴展有限度支持 ENR 不同,DiscV5 將 ENR 作爲其基礎數據單元,不僅允許 ENR 攜帶更豐富、可驗證的鍵值對元數據(如支持的協議、特定角色等),而且 ENR 直接包含在常規消息(例如 NODES
響應)中進行交換,無需像 DiscV4 那樣需要特定的 ENRRequest
。另一項改進是 DiscV5 引入了基於“主題”(Topic)的發現機制;節點可以通過 TOPICREGISTER
消息廣播其提供的服務或對特定主題的興趣,其他節點則能通過 TOPICQUERY
精確查找到這些具有特定能力的對等節點,實現了比 DiscV4 更精細化的服務發現能力。
此外,DiscV5 還定義了全新的、具有更強安全保障的 discv5-wire 底層協議,通過改進握手過程(使用臨時密鑰和認證標籤 AuthTag)來建立加密通信通道,並對消息格式進行了更爲嚴格和清晰的規範。
特性 |
Discv4 |
Discv5 |
ENR 集成與規範基礎 |
對 EIP-778 定義的 ENR 支持是後期通過 EIP-868 作爲擴展加入的。 |
從設計之初就原生基於 EIP-778 定義的 ENR 構建,ENR 是其核心基礎。 |
ENR 交換機制 |
獲取最新 ENR 通常需要發送一個特定的 |
ENR直接包含在常規的消息中(如 |
ENR 內容與功能利用 |
主要用於提供更豐富的節點元數據和改進的端點證明。 |
充分利用ENR的鍵值對靈活性,支持如主題宣告(Topic Advertisement)等高級功能,允許節點聲明特定服務或協議,實現更精確的節點發現。 |
身份方案 |
ENR 使用 "v4" 身份方案。(此處的 v4 爲身份方法的名稱,而非上文的 discv4) |
依賴 EIP-778 定義的身份方案,具有更好的擴展性(儘管 "v4" 仍常用)。 |
DNS 發現(DNS Discovery) 該機制作爲基於 UDP 的 P2P 發現協議(Discv4/Discv5)的補充,旨在提供一個穩定且易於更新的節點列表(特別是引導節點)作爲客戶端連接網絡的初始入口點。文檔詳細說明了如何將以太坊節點信息(通常是以太坊節點記錄 ENR)編碼成特定的字符串格式,並存儲在 DNS 記錄(通常是 TXT
記錄)中。這些 DNS 域名和記錄由可信的列表維護者管理和發佈。客戶端通過查詢這些特定的 DNS 域名來獲取 ENR 列表,然後可以使用列表中的節點啓動 Discv5 或 Discv4 p2p 發現流程。
2.2 數據傳輸層
RLPx(Recursive Length Prefix Transport Protocol)作爲 DevP2P 協議棧的底層傳輸協議,運行在 TCP 協議之上,專爲以太坊節點間提供經過認證、加密且支持多路複用的安全通信通道。
在兩個通過發現協議定位彼此並決定建立連接的節點之間,RLPx 通過初始握手流程來確保通信雙方身份的真實性並協商加密參數,此過程利用非對稱加密技術,節點會交換臨時密鑰並結合其長期身份密鑰進行相互認證,最終通過橢圓曲線迪菲-赫爾曼密鑰交換(ECDH)算法安全地生成一個共享密鑰。在握手成功之後,該共享密鑰會用於加密後續在該 TCP 連接上所有傳輸的數據,從而保障了通信內容的機密性。
RLPx 的一個關鍵特性是其多路複用能力,它允許在單一的 TCP 連接上同時承載和區分多個不同的上層應用協議(即 DevP2P 子協議,如 ETH 協議用於區塊鏈同步,LES 協議服務於輕客戶端等)的消息流,通常通過消息頭部內嵌的協議類型 ID 來識別和路由。所有在 RLPx 之上傳輸的應用層消息都會被組織成特定格式的“幀”(frames)進行傳輸,這些幀不僅包含了加密後的消息負載(通常是已經過 RLP 編碼的應用層數據),還附帶有消息大小、協議類型等必要的元數據,並支持將較大的消息分割成多個幀進行分塊傳輸。
總之,RLPx 通過加密握手、消息封裝和多路複用機制,爲以太坊節點提供了一個獨立於具體應用邏輯的、通用的安全管道,是實現高效數據同步、交易廣播等功能的基礎。
2.3 應用層子協議
ETH(Ethereum Wire Protocol)
ETH
通過 RLPx 傳輸協議促進對等節點間以太坊區塊鏈信息的交換。ETH
協議定義了節點如何交換與區塊鏈相關的信息,例如區塊頭、區塊體、交易數據以及狀態信息。包括了協議的版本協商機制、不同消息類型的結構(使用RLP編碼)、消息代碼以及這些消息在諸如區塊同步、交易廣播等過程中的交互流程,確保網絡中的節點能夠有效地同步和驗證區塊鏈數據,從而共同維護以太坊網絡的一致性和完整性。
SNAP(Ethereum Snapshot Protocol)
SNAP
運行在 RLPx 之上,用於在節點間傳播以太坊的狀態快照。其主要目的是提供近期狀態的動態快照以供半實時數據檢索,但不參與鏈的維護,需要與 ETH
協議協同工作。SNAP
支持檢索狀態樹中的連續賬戶片段或特定存儲樹中的存儲槽,並帶有默克爾證明以便快速驗證,同時也能批量檢索字節碼。該協議通過允許節點直接獲取賬戶和存儲數據並在本地重組狀態樹,減少了對中間默克爾樹節點的下載需求,從而降低了網絡帶寬消耗和延遲。它主要作爲新加入全節點的引導輔助工具,並依賴 ETH
協議。
LES(Light Ethereum Subprotocol)
LES
專爲輕客戶端設計,使它們能夠安全地訪問以太坊區塊鏈而無需參與共識過程,主要通過下載區塊頭並按需獲取其他區塊鏈部分。該協議利用規範哈希樹(CHT)進行快速同步和安全數據檢索(如區塊頭、總難度),並採用 BloomBits 技術優化日誌搜索。爲了防止服務器過載並管理請求速率,LES
還引入了客戶端流控制機制,通過緩衝區限制、最小充值速率和最大請求成本等參數進行調節,從而爲輕客戶端提供高效且安全的區塊鏈交互方式。
PIP(Parity Light Protocol)
PIP
是 Parity Technologies 爲其以太坊客戶端設計的 LES
協議變種。它也使用規範哈希樹(CHT),但每2048個區塊生成一次。PIP
採用類似令牌桶的流量控制機制,客戶端需鏡像服務器的狀態。協議消息包括狀態、宣告、請求批處理、響應批處理、更新信用參數等。值得注意的是,PIP 的請求和響應消息是批處理的,不可單獨發送,旨在優化客戶端與服務器的交互輪次。協議支持多種請求/響應對,用於檢索區塊頭、交易信息、區塊體、賬戶及存儲證明、合約代碼和執行證明等。
WIT(Ethereum Witness Protocol)
WIT
運行在 RLPx 之上,用於在對等節點間交換以太坊狀態見證。它旨在幫助客戶端同步到鏈的頂端,並最終支持無狀態客戶端操作,但不參與鏈的維護,而是與 ETH
協議並行工作。協議的第 0 版主要提供見證的元數據(如區塊執行期間讀取的樹節點哈希列表,包括存儲、字節碼和賬戶節點),以輔助節點通過 ETH
協議下載實際見證,從而更快地完成同步。
3. 以太坊節點生命週期和數據流圖
節點生命週期和數據流圖
流程圖簡要說明:
-
節點啓動:以太坊客戶端(Geth)開始運行
-
加載配置:讀取配置文件,包括網絡 ID、創世區塊信息、端口設置等
-
初始化 p2p 服務器:設置 p2p 服務器,準備監聽和發起連接
-
節點發現:節點開始尋找網絡中的其他對等節點
-
DNS Discovery:通過 DNS 種子節點列表獲取初始節點信息
-
Kademlia(Discv4/v5):使用節點發現協議發現其他節點
-
-
發現潛在節點:彙總來自不同發現機制的節點信息
-
連接建立:
-
監聽傳入連接:接受來自其他節點的連接請求
-
主動撥號:向已發現的節點發起連接請求
-
-
接受連接請求/發起連接:成功建立 TCP 連接
-
RLPx 加密握手:在 TCP 連接之上進行加密握手,確保通信安全,協商會話密鑰
-
DevP2P 協議握手:RLPx 握手成功後,進行 DevP2P 層的握手。節點交換各自支持的子協議(如
eth
,snap
,les
等)及其版本。雙方會就共同支持的最高版本協議進行協商 -
子協議交互:
-
ETH 協議交互:如果協商成功
eth
協議,節點將通過此協議交換區塊、交易、鏈狀態等信息 -
Snap 協議交互:如果協商成功
snap
協議,節點可以通過此協議更高效地同步狀態數據,如賬戶、存儲、字節碼和 Trie 節點 -
其他子協議交互:如
les
(Light Ethereum Subprotocol)等
-
-
Peer 管理:節點會維護一個對等節點列表,跟蹤其狀態(如健康狀況、支持的協議、最新的區塊高度等),並根據需要添加新節點或移除斷開/行爲不當的節點
-
數據交換與處理:
-
區塊/交易同步與廣播:通過 ETH 協議,節點下載新的區塊和交易,驗證它們,並將自己的新區塊和交易廣播給其他節點
-
狀態同步:通過 snap 協議(或 eth 協議的某些消息),節點請求並接收賬戶數據、存儲槽、合約字節碼和狀態樹(Merkle Trie)的節點數據,用於快速構建或更新本地狀態
-
數據處理與驗證:接收到的所有數據(區塊、交易、狀態片段)都需要經過嚴格驗證,確保其符合協議規則和鏈的一致性
-
-
更新本地鏈狀態/數據庫:驗證通過的數據被用於更新本地的區塊鏈數據庫和狀態樹
-
對外提供服務/響應查詢:節點根據其角色(全節點、輕節點等)對外提供服務,如響應 RPC 請求、處理交易等
下面將對該流程圖結合源碼進行詳細說明。
4. Geth 代碼具體實現
下文將通過對 geth 中相關代碼的分析來說明 devp2p 協議規範的具體實現。
4.1 節點初始化與發現
4.1.1 節點的啓動與配置加載
網絡中的一個參與者(即節點)通常是一臺運行以太坊客戶端軟件(如 Geth)的計算機。每個節點都擁有其獨特的身份標識,主要包括 enode ID
(節點 ID)和 ENR
(Ethereum Node Record)。
具體來說,enode ID
是基於節點自身加密密鑰對(通常爲 secp256k1 公鑰)通過特定哈希運算產生的唯一標識。而 ENR
則是由節點自行構建並簽名的一個可擴展記錄。它封裝了節點的網絡聯絡信息(如 IP 地址、用於 P2P 通信的 TCP/UDP 端口——通常默認爲 30303,以及節點 ID 和公鑰)和其提供的服務能力,充當其在網絡中的名片。這些 ENR 隨後被節點發現協議(如 Discovery v4/v5)用來幫助節點互相定位和建立連接。執行層節點包含以下類型:
-
引導節點(Bootnode):這類節點的主要職責是幫助新加入的節點接入 p2p 網絡。它們的 ENR 信息通常被硬編碼在客戶端的配置文件中,或通過 DNS 發現機制提供,作爲新節點首次連接網絡的入口點
-
本地節點(LocalNode):指用戶當前正在直接運行和控制的那個以太坊節點實例
-
遠程節點(RemoteNode):泛指在以太坊 p2p 網絡中,除了本地節點以外的所有其他參與節點
Geth 節點的程序入口位於 cmd/geth/main.go
。緊接着會加載節點的配置文件,具體包括網絡 ID、端口、種子節點、引導節點等。
這裏的 main
函數會解析命令行參數,創建和配置節點。main
中的 app.Run
會默認執行 geth
函數。在 geth 函數中,涉及到了 makeFullNode
和 startNode
,其中 makeFullNode
主要加載配置(makeConfigNode
)並創建節點的核心服務,包括以太坊服務(utils.RegisterEthService
)。而 startNode
負責則啓動已經配置好的節點。
配置加載主要在 makeFullNode
和 makeConfigNode
中完成,結合了默認配置、配置文件以及命令行參數。cmd/geth/makeFullNode → makeConfigNode → loadBaseConfig
負責加載基礎配置。節點配置相關的結構體和默認值定義在 node/config.go
,引導節點列表硬編碼在 params/bootnodes.go
中。
func loadBaseConfig(ctx *cli.Context) gethConfig {// 加載默認配置cfg := gethConfig{Eth: ethconfig.Defaults,Node: defaultNodeConfig(),Metrics: metrics.DefaultConfig,}// 加載配置文件(如果有 TOML 文件的話)if file := ctx.String(configFileFlag.Name); file != "" {if err := loadConfig(file, &cfg); err != nil {utils.Fatalf("%v", err)}}// 應用節點utils.SetNodeConfig(ctx, &cfg.Node)return cfg}
4.1.2 節點發現
在一個動態的、不斷有節點加入和離開的網絡中,一個節點需要一種機制來找到其他可以連接的節點。以太坊的節點發現協議包含上文介紹過的三種發現協議,可通過下表快速回顧:
協議名稱
|
特點
|
關鍵代碼
|
DiscV4
|
基於 Kademlia 思想的分佈式哈希表(DHT),用於網絡中的節點發現。
|
p2p/discover
|
DiscV5
|
對 Discv4 的升級,且原生支持 ENR。
|
p2p/discover
|
DNS Discovery
|
應用於公共引導節點列表的維護,以及私有網絡的節點引導。
|
p2p/enr
|
DNS Discovery 實現位於 p2p/dnsdisc
包中。核心是 dnsdisc.Client
結構體(定義在 p2p/dnsdisc/client.go
)。Client
負責與配置的 DNS 服務器交互,獲取 DNS TXT 記錄,這些記錄指向一個 ENR 樹。客戶端會解析這個樹來獲取節點列表。p2p.Server
會使用 dnsdisc.Client
來輪詢 DNS 列表,並將發現的節點添加到撥號候選池中。
Discv4的實現位於 p2p/discover/v4_udp.go
-
ping
用於發送 PING 消息檢查節點是否在線,並獲取其 ENR 序列號。 -
findnode
向目標節點發送 FINDNODE 請求,獲取其 Kademlia 桶中距離特定目標最近的一組節點。 -
LookupPubkey
在DHT網絡中查找與給定公鑰最接近的節點。 -
ListenV4
用於啓動v4發現協議的監聽和處理。
Discv5的實現位於 p2p/discover/v5_udp.go
。
-
ping
v5版本的PING操作。 -
Findnode
v5版本的FINDNODE操作,允許查詢特定距離的節點。 -
Lookup
v5版本的節點查找。 -
ListenV5
(實際行號可能根據版本變化)負責v5協議的運行。
p2p/discover/table.go
實現了 Kademlia 路由表,用於存儲和管理已發現的節點信息。p2p/discover/v4wire
和 p2p/discover/v5wire
定義了 Discovery v4/v5
協議的網絡消息格式。
在 p2p/server.go
文件中,Server.setupDiscovery
方法會根據節點配置來初始化並啓動節點發現服務,這可能包括 Discovery v4/v5。該方法會調用相應的 discover.ListenV4
或 discover.ListenV5
函數來啓動 UDP 監聽,以接收來自其他節點的發現消息。
當節點需要查找新的對等節點或定位某個特定節點時,會觸發相應的查找操作,例如 discv5 中的 Lookup
或 discv4 中的 LookupPubkey
。這些操作是一個迭代的查詢過程:節點會向其路由表中已知的、距離目標最近的一批節點發送 FINDNODE
請求。收到對端節點回復的 NEIGHBORS
(discv4)或 NODES
(discv5)響應後,節點會將響應中新發現的、更近的節點信息更新到自己的路由表中,並繼續向這些新節點發起查詢。這個迭代過程會持續進行,直到找到目標節點,或者無法找到比當前已知節點更接近目標的節點爲止。
此外,爲了維護路由表的健康和時效性,節點會定期向表中的其他節點發送 PING
消息,以檢查它們的在線狀態並刷新相關信息。收到對方回覆的 PONG
消息即確認該節點仍然活躍。在某些情況下,例如需要獲取某個節點的最新詳細信息時,節點還可以通過發送 REQUESTENR
消息來請求對方的最新 ENR 信息。
4.2 p2p 服務的啓動與連接握手
4.2.1 初始化 p2p 服務
根據配置初始化 p2p 服務,設置本地節點信息、加密密鑰等。
以太坊 p2p 服務的初始化過程主要涉及 node/node.go
文件中的 node.New
函數和 node.Node.Start
方法。首先,在 node.New
函數內部,系統會依據傳入的配置(config.P2P
)來創建並配置一個 p2p.Server
的實例。
隨後,在 node.Node.Start
方法中,openEndpoints
這一步驟會調用位於 p2p/server.go
文件中的 Server.Start
方法。Server.Start
方法是啓動 p2p 服務各項核心功能的入口,包括啓動網絡監聽、節點發現服務等。在 Server.Start
的執行過程中,會進行一系列關鍵的設置和初始化操作。例如,通過 srv.setupLocalNode
來配置和準備本地節點的信息,加載用於節點身份驗證的私鑰至 srv.PrivateKey
,並通過 srv.setupDialScheduler
設置連接的撥號調度器。
最終,srv.setupListening
方法會被調用以啓動監聽循環。這個監聽循環可以視爲 p2p 服務的主事件循環,它持續運行,負責處理所有的 p2p 網絡活動,包括響應節點發現請求、管理新連接的握手過程以及處理節點間的消息傳遞等。
4.2.2 連接建立與通信
以太坊節點間的連接建立是一個確保通信安全與有效的多階段過程。在基礎的連接管理層面,節點間的數據交換依賴於標準的 TCP 連接。節點可以通過“撥號”(Dialing)操作主動向目標節點的 IP 地址和 TCP 端口發起連接,同時也通過“監聽”(Listening)特定 TCP 端口來接受入站連接請求。爲了有效管理網絡資源和連接穩定性,客戶端通常會配置如最大連接數、最大嘗試撥號節點數等參數。
底層的 TCP 連接建立由 Go 的 net
包處理。而 server.go
下的 conn
結構體封裝了底層的 net.Conn
,並增加了與 p2p 協議相關的狀態和標誌位,如 inboundConn
和 trustedConn
等。
// conn wraps a network connection with information gathered// during the two handshakes.type conn struct {fd net.Conn // 底層的網絡連接transport // 定義了加密握手 doEncHandshake 和協議握手 doProtoHandshake 的接口,以及消息讀寫 MsgReadWriter。node *enode.Node// 遠端節點的 enode.Node 表示flags connFlag // 連接的標誌位,如 inboundConn , staticDialedConn , trustedConn 等。cont chan error // 用於在 run 循環和 SetupConn 之間傳遞錯誤。caps []Cap // 協議握手後遠端節點支持的能力(Capabilities)。name string // 協議握手後遠端節點的名稱。}
對於入站連接的監聽與接收,服務器端的 listenLoop
方法會持續監聽在預先配置好的 TCP 地址和端口上。一旦其內部的 listener.Accept
成功接收到一個新的 TCP 連接請求,系統會先執行一些初步的有效性檢查,比如覈實請求方 IP 地址是否位於受限列表(黑名單)中,或者其連接嘗試是否過於頻繁等。只有通過了這些前置檢查的連接,服務器纔會爲其啓動一個新的 goroutine,並調用 Server.SetupConn
方法,引導該連接進入後續的詳細設置與握手階段。
與此同時,p2p 服務也會根據自身維護網絡狀態的需求(例如保持足夠的對等節點數量、或連接到特定的靜態節點等)主動發起出站連接。這項任務主要由位於 p2p/dial.go
文件中的撥號調度器(dialScheduler
)來管理。該調度器會選擇合適的目標節點併發起 TCP 連接嘗試。當 TCP 連接成功建立後,與處理入站連接的後續步驟類似,系統同樣會調用 Server.SetupConn
方法來對這個新建立的出站連接進行配置。
無論是入站還是出站連接,在基礎的 TCP 通道打通並通過初步校驗之後,都會進入由 Server.SetupConn
方法負責的連接設置流程。在此方法中,系統首先會爲這個新連接創建一個代表 p2p 層連接狀態的 p2p.conn
對象。緊接着,爲了處理 RLPx 傳輸層協議的細節,會進一步創建一個 rlpx.Conn
對象,該對象封裝了底層的原始網絡連接(net.Conn
)。最後,也是建立安全通信渠道的關鍵步驟,系統會調用此 conn
對象的 doEncHandshake
方法(該方法內部實際上會觸發 rlpx.Conn.Handshake
的執行),以便與對等節點完成 RLPx 加密握手,從而正式構建起一個安全的通信會話。
連接一旦成功建立,雙方節點必須進行後續的握手流程,以驗證身份並協商通信參數。這個關鍵過程主要分爲兩步:
-
RLPx握手:此步驟旨在通過 RLPx 協議(以太坊的底層傳輸協議)構建一個安全的、經過加密的通信通道。它通過以下交互實現:發起方首先發送一個包含其臨時公鑰和簽名的
auth
消息;接收方驗證簽名後,亦生成臨時密鑰對,並回復一個包含其臨時公鑰的ack
消息。基於雙方交換的臨時公鑰及各自的臨時私鑰,它們通過橢圓曲線迪菲-赫爾曼密鑰交換(ECDH)算法計算出共享密鑰。此後,在該 TCP 連接上傳輸的所有數據都將使用此共享密鑰進行對稱加密,保障通信內容的機密性。 -
協議握手(也稱應用層握手):在 RLPx 加密通道安全建立之後,節點間還需進一步明確彼此支持的應用層子協議及其版本,例如
eth/66
、snap/1
等。爲此,雙方會互發一個Hello
消息,其中詳細列出了各自的節點 ID、客戶端軟件版本以及所支持的協議列表(稱爲 Capabilities 或 Caps )。通過比對這些信息,節點會選取雙方共同支持的、版本號最高的協議作爲在該連接上進行後續具體數據(如區塊、交易等)交換的標準。
4.2.3 RLPx 握手
RLPx 協議的握手過程是確保以太坊節點間 p2p 通信安全的關鍵步驟,其實現位於 p2p/rlpx/rlpx.go
。整個過程圍繞 Conn
對象展開,該對象封裝了底層的 TCP 連接並管理着包括加密密鑰、MAC密鑰在內的會話狀態。握手的入口點是 Conn.Handshake
方法,它會根據節點是連接發起方還是接收方,分別調用 runInitiator
或 runRecipient
函數來執行具體的握手邏輯。在整個握手期間,rlpx.handshakeState
對象會臨時存儲必要的中間狀態信息,如臨時密鑰對和 Nonces。
// protoHandshake is the RLP structure of the protocol handshake.type protoHandshake struct {Version uint64 // DevP2P 協議版本。Name string // 客戶端名稱和版本(例如 "Geth/v1.10.0/...").Caps []Cap // 節點支持的子協議能力列表,每個 Cap 包含協議名稱(如 "eth")和版本(如 66)。ListenPort uint64 // 節點的監聽端口。ID []byte // secp256k1 節點的公鑰(Enode ID)。// Ignore additional fields(for forward compatibility).Rest []rlp.RawValue `rlp:"tail"`}
握手流程:
-
發起方:
runInitiator
生成臨時橢圓曲線迪菲-赫爾曼(ECDH)密鑰對和隨機 NonceinitNonce
,並計算與接收方的靜態共享密鑰staticSharedSecret
。接着,使用其臨時 ECDH 私鑰對靜態共享密鑰與initNonce
的異或值進行簽名。然後,將此簽名、自身的靜態公鑰InitiatorPubkey
和initNonce
打包成authMsgV4
消息。最後,使用接收方的靜態公鑰通過橢圓曲線集成加密方案(ECIES)加密authMsgV4
併發送。 -
接收方:
runRecipient
收到並解密authMsgV4
後,利用消息中的InitiatorPubkey
、NonceinitNonce
及本地計算的靜態共享密鑰來驗證簽名,並恢復出發起方的臨時公鑰remoteRandomPub
。隨後,接收方也生成自己的臨時 ECDH 密鑰對和隨機 NoncerespNonce
。它將自己的臨時公鑰RandomPubkey
和respNonce
構造成authRespV4
消息。最後,使用發起方的靜態公鑰通過 ECIES 加密authRespV4
併發送。 -
密鑰派生:雙方首先使用各自的臨時私鑰和對方的臨時公鑰計算 ECDHE 共享密鑰
ecdheSecret
。隨後,結合ecdheSecret
、respNonce
和initNonce
,通過 Keccak256 哈希運算依次派生出主共享祕密sharedSecret
、AES 密鑰aesSecret
和 MAC 密鑰macSecret
。最後,雙方使用macSecret
分別與respNonce
和initNonce
進行異或,並結合各自發送和接收的初始認證消息(authMsg
或authRespMsg
),初始化出站egressMAC
和入站ingressMAC
的 Keccak256 哈希實例,用於後續消息的完整性校驗。 -
握手完成:至此,雙方都擁有了對稱的 AES 密鑰和 MAC 密鑰,可以用於後續 RLPx 幀數據的加密和完整性驗證,標誌着握手成功。在
p2p/server.go
的實現中,Server.checkpoint
會在 RLPx 握手成功後,將此連接推送到checkpointPostHandshake
通道,以等待進行更高級別的 devp2p 協議層握手。
握手成功後,節點間的應用層消息通過 RLPx 協議被封裝成幀。較大的消息會被分割成一或多個幀進行傳輸,每個幀都包含了必要的元數據(如消息代碼、長度)及加密後的消息體。整個過程實現了即使在不安全的網絡上,兩個節點也能安全地協商出會話密鑰,用於後續通信的加密和完整性保護。
4.2.4 DevP2P 應用子協議握手
在 RLPx 傳輸層的加密握手成功之後,節點間會立即進行 devp2p 協議層面的握手。這個過程的核心是通過交換一個 Hello
消息(其消息碼爲 0x00
)來完成的,目的是讓雙方節點了解彼此的能力和身份。
"Hello" 消息(protoHandshake
結構體)包含了節點的協議版本、客戶端名稱(如 "Geth/v1.10.0")、支持的子協議和版本(Capabilities)、監聽端口以及節點ID(公鑰)。
在 p2p/peer.go
文件中,每個 Peer
對象通過其 run
方法來管理與對端節點的主通信循環。在此循環開始,即連接建立之初,會調用 Peer.handshake
方法。這個方法負責主動向對端發送本地節點的 Hello
消息,並同時等待接收來自對方的 Hello
消息。
當雙方都完成 Hello
消息的交換後,服務器端的邏輯(在 Server.run
方法中,通過 checkpointPostHandshake
和 checkpointAddPeer
等檢查點)會進一步驗證握手結果的有效性。這些檢查包括確認連接是否仍然存活、對方是否是節點自身(防止自連接)、當前連接數是否已達到上限等,具體的檢查邏輯可以參考 Server.postHandshakeChecks
和 Server.addPeerChecks
等函數。
最後,雙方節點會比較各自在 Hello
消息中聲明的支持子協議列表。通過這個比較,它們會協商出共同支持的子協議及其版本,並以此爲基礎在後續的通信中運行這些選定的子協議,進行如區塊同步、交易廣播等具體的數據交換。
在 DevP2P 基礎協議握手成功之後,如果雙方節點都支持某些共同的子協議(例如 eth 協議),它們會爲這些子協議進行特定的握手。
p2p/server.go
中當一個新對等節點成功完成基礎握手並通過檢查後( checkpointAddPeer
邏輯), Server.launchPeer
函數會被調用,其會爲每個協商成功的子協議啓動一個對應的處理例程。它會遍歷 srv.Protocols
,並與對端節點的能力( c.caps
)進行匹配。匹配中會創建一個 protoRW
實例,並通過 go p.run()
啓動該協議的通信循環(見 peer.go
中的 Peer.startProtocols
)。
ETH 協議的特定握手與消息處理:
ETH
協議自身也有一個特定的握手環節,它緊隨 DevP2P 的 Hello
消息交換之後進行。此握手通過雙方交換 Status
消息完成,該消息包含了節點的網絡ID、鏈的總難度(TD)、當前最佳區塊哈希等關鍵區塊鏈狀態信息,用於快速評估節點間的兼容性和同步需求。這個 Status
交換邏輯主要在 eth/protocols/eth/handshake.go
的 Handshake
函數中實現。
Status 消息結構如下:
type StatusPacket struct {ProtocolVersion uint32 // eth 協議的版本NetworkID uint64 // 網絡的 ID(例如,1代表主網)TD *big.Int // (Total Difficulty)鏈的總難度Head common.Hash // 頭區塊的哈希Genesis common.Hash // 創世區塊的哈希ForkID forkid.ID // 分叉的 ID}
一旦 ETH
協議的 Status
握手成功,後續的通信便由 eth/protocols/eth/handler.go
中的 Handle
函數(及其內部的 handleMessage
方法)主導。Handle
函數會進入一個消息處理循環,負責接收和處理該 ETH
子協議下定義的各種消息。這些消息類型多樣,例如用於廣播新區塊哈希的 NewBlockHashesMsg
、請求區塊頭的 GetBlockHeadersMsg
、傳遞區塊體的 BlockBodiesMsg
以及處理交易的 TransactionsMsg
等(其具體消息代碼和處理函數通常與協議版本對應,如 eth/68
版本)。通過這個循環,節點得以執行區塊同步、交易傳播等核心操作。
爲了支持不同版本的 ETH
協議(如 eth/66
, eth/67
, eth/68
),同樣在 handler.go
中的 MakeProtocols
函數會爲每個版本創建相應的 p2p.Protocol
定義。當一個新的對等節點連接並協商確定使用特定版本的 ETH
協議時,該 p2p.Protocol
定義中的 Run
方法即作爲此子協議的執行入口。 Run
方法通常會初始化一個特定於 ETH
協議的 Peer
對象(這是對通用 p2p.Peer
的封裝,增加了ETH
協議相關的狀態與功能),然後調用 backend.RunPeer(peer, Handle)
來啓動該對等節點的 ETH
協議消息處理主循環,並將 Handle
函數作爲核心回調傳入。
SNAP協議:
除了 ETH
協議,Geth客戶端還支持 SNAP 協議,主要用於加速節點的狀態快照同步。其實現位於 eth/protocols/snap
目錄。與 ETH
協議類似,SNAP
協議也有其特定的消息處理邏輯,主要由該目錄下的 handler.go
中的 Handle
和 HandleMessage
函數負責,專門處理與狀態快照數據請求、傳輸和驗證相關的消息。
4.3 數據同步與處理
數據處理與同步的主要結構如下圖所示:
4.3.1 數據同步
數據同步是新節點加入網絡或落後節點追趕最新鏈狀態的核心過程。在 Geth
中,這個過程主要由 eth/downloader/downloader.go
模塊負責。
同步啓動與模式選擇
同步過程主要通過 Downloader.synchronise
方法啓動。Geth 支持多種同步模式,例如完整同步(FullSync)和快照同步(SnapSync),具體模式由 d.mode
變量控制,並可通過 Downloader.getMode
方法獲取。值得一提的是,在 SnapSync 模式下,Geth 會先禁用 Trie 數據庫的寫入並暫停快照維護,以確保數據的一致性。
對等節點(Peer)管理
Geth 通過以下方法管理同步過程中的對等節點:Downloader.RegisterPeer
方法用於註冊新的下載節點,這些節點保存在 d.peers
( peerSet
實例)中。相應的,Downloader.UnregisterPeer
方法則用於移除節點。此外,peerDropFn
回調函數會在節點行爲不當(如發送無效數據)時被觸發,以便及時將其移除。
數據獲取與調度
數據獲取的調度由 d.queue
( queue
實例)負責,它管理着需要下載的數據哈希。Geth 定義了諸如 MaxBlockFetch
、MaxHeaderFetch
、MaxReceiptFetch
等常量,用以限制單次請求可以獲取的數據量。而 fetchRequest
結構體則表示一個正在進行的數據檢索操作。
同步過程中的特定優化
在以太坊合併之後,Geth 引入了骨架同步(Skeleton Sync),用於高效地同步信標鏈的頭部信息,並通過 Downloader.findBeaconAncestor
尋找共同祖先。同時,在 SnapSync 模式下,Geth 會選擇一個 pivotHeader
作爲狀態同步的起點。
同步過程控制與事件通知
Geth 提供了一套完善的控制機制來管理同步的生命週期。d.cancelCh
通道可用於中途中止同步過程。d.syncStatsChainOrigin
和 d.syncStatsChainHeight
記錄了同步的起始和目標高度,方便進度追蹤。此外,d.mux
( event.TypeMux
實例)負責廣播同步相關的事件,例如 StartEvent
(同步開始)、DoneEvent
(同步完成)和 FailedEvent
(同步失敗),這些事件對於客戶端狀態顯示和外部監控至關重要。
4.3.2 數據處理與驗證
在 Geth 客戶端中,當從網絡獲取到數據後,需要對其進行處理、驗證,並最終整合到本地的區塊鏈中。
區塊頭(Header)處理
下載的區塊頭(types.Header
)會通過 d.headerProcCh
通道傳遞給專門的頭部處理器。headerTask
結構體則封裝了一批下載的區塊頭及其預計算的哈希值。Geth 通過 maxHeadersProcess
常量限制一次性導入鏈中的區塊頭數量,以優化處理效率。
區塊(Block)與收據(Receipt)處理
fetchResult
結構體負責收集部分下載結果,包括區塊體、叔塊、交易、收據和提款,直到所有部分都完整獲取。數據準備就緒後,InsertChain
方法用於將一批區塊插入本地鏈,而 InsertReceiptChain
方法則用於將一批區塊及其對應的收據插入本地鏈。值得注意的是,ancientLimit
定義了被認爲是“遠古”數據的最大區塊號,早於此號的數據在 SnapSync 模式下可能會直接寫入專門的遠古存儲。chainCutoffNumber
和 chainCutoffHash
則定義了同步時可以跳過的鏈段截止點。
數據驗證與錯誤處理
Geth 對獲取到的數據進行嚴格驗證,並定義了多種錯誤類型來指示驗證失敗,例如:
-
errInvalidChain
:檢索到的哈希鏈無效。 -
errInvalidBody
:檢索到的區塊體無效。 -
errInvalidReceipt
:檢索到的收據無效。
如果 Geth 檢測到無效數據或發現對等節點的惡意行爲,會立即通過 dropPeer
回調函數移除對應的節點,以維護網絡健康。badBlockFn
回調函數則用於在異步信標同步中,通知調用者某個區塊因驗證失敗而被拒絕。
狀態同步
d.SnapSyncer
(一個 snap.Syncer
實例)專門負責處理快照同步中的狀態數據下載和處理。d.stateSyncStart
通道用於啓動狀態同步過程,而 d.stateFetcher
(一個在 New
方法中啓動的 goroutine)則具體負責狀態數據的獲取。
數據提交與異常處理
d.committed
布爾值標記着數據是否已最終提交。在 SnapSync 模式中,數據可能直到同步完全完成前都不會被最終提交,以確保原子性。SnapSyncCommitHead
方法用於在快照同步中直接提交頭區塊。同步過程中可能出現的錯誤類型包括 errBusy
(下載器正忙)、errTimeout
(超時)和 errCanceled
(同步被取消)等,Geth 會對這些異常情況進行妥善處理。
4.3.3 更新本地鏈狀態/數據庫
經過驗證的數據會被寫入本地數據庫,從而更新節點的鏈狀態。Geth 中本地鏈狀態和數據庫的更新主要通過以下機制實現:
區塊和收據插入
-
Downloader
結構體依賴一個BlockChain
接口,該接口定義了插入區塊和收據的方法。-
BlockChain.InsertChain
將一批區塊插入本地鏈。 -
BlockChain.InsertReceiptChain
將一批區塊及其收據插入本地鏈。對於早於ancientLimit
的數據,會直接存入 ancient store。 -
BlockChain.InsertHeadersBeforeCutoff
在配置的chainCutoffNumber
之前插入一批區塊頭到 ancient store。
-
-
processFullSyncContent
和processSnapSyncContent
(在fetcher.go
中,但由downloader
調用)函數負責處理下載到的區塊和收據,並調用上述BlockChain
接口的方法將它們插入數據庫。
Snap Sync 狀態更新
-
Downloader
包含一個Downloader.SnapSyncer
(snap.Syncer
類型),專門負責 Snap Sync 過程中的狀態下載和應用。 -
在 Snap Sync 期間,狀態數據(賬戶、存儲、字節碼等)會直接寫入
Downloader.stateDB
。 -
syncToHead
函數中,如果採用 Snap Sync 模式,會調用rawdb.WriteLastPivotNumber
將 pivot 區塊號寫入數據庫,以便在回滾時能夠重新啓用 Snap Sync。 -
SnapSyncer.Progress()
方法用於獲取 Snap Sync 的進度,包括已同步的賬戶、字節碼、存儲等數量。
Ancient Store 和 Chain Cutoff
-
Downloader.ancientLimit
:定義了可以被視爲 ancient 數據的最大區塊號。在 Snap Sync 模式下,早於此限制的數據會直接寫入 ancient store。 -
Downloader.chainCutoffNumber
和Downloader.chainCutoffHash
:定義了同步時跳過區塊體和收據的截止點。
數據庫提交
-
Downloader.committed
標誌位:在 Snap Sync 模式下,如果 pivot 區塊號不爲 0,初始時committed
爲false
,表示數據庫中的狀態可能是不完整的,直到同步完成。
4.3.4 對等節點管理
以太坊節點通過對等節點管理(Peer Management)來維護其 P2P 網絡連接的穩定性和效率。一個成功與本地節點建立連接並完成所有必需握手流程的遠程節點,即成爲一個對等節點(Peer)。有效的對等節點管理對於保障網絡的連通性、促進數據的順暢交換以及優化節點自身資源利用至關重要,這一管理過程主要涵蓋了以下幾個核心方面:
-
對等節點的識別與連接池維護:在本地節點成功與遠程節點完成所有握手步驟,確認其爲對等節點後,節點會在本地維護一個當前所有這些活躍對等節點的列表或池(Connection Pool)。這個連接池是節點進行一切網絡通信的動態基礎。
-
連接操作與訪問策略:節點會執行一系列連接操作,如通過主動“撥號”出站或“監聽”並接受入站請求來添加新的對等節點。同時,節點也會根據通信錯誤、超時、對方離線或預設的訪問策略(例如優先保障“受信任的” Peer 的連接,或對總連接數、單個 IP 連接數進行限制)來決定斷開連接並移除相應的對等節點。
-
對等節點狀態事件的響應:系統中對等節點的各種狀態變化,例如新連接的成功建立、現有連接的意外斷開或在通信過程中發生的錯誤,通常會觸發內部的事件通知。這些事件使得客戶端軟件中的其他相關模塊(如負責區塊鏈同步的邏輯單元或交易池管理等)能夠及時感知到 P2P 網絡拓撲的實時動態,並據此作出必要的響應或調整。
對等節點(Peer)管理
-
Downloader.RegisterPeer
將新的 peer 注入到下載源集合中。 -
Downloader.UnregisterPeer
從已知列表中移除 peer,並嘗試將該 peer 待處理的 fetch 任務返回到隊列中。 -
Downloader.dropPeer
當檢測到 peer 行爲不當(例如發送無效數據)時,會調用此函數來移除該 peer。
錯誤類型如下:
errBusy // 表示下載器正忙,無法處理新的同步請求errBadPeer // 表示來自不良 peer 的操作被忽略errTimeout // 表示操作超時errInvalidChain // 表示檢索到的哈希鏈無效errInvalidBody // 表示檢索到的區塊體無效errInvalidReceipt // 表示檢索到的收據無效errCancelStateFetch // 表示取消狀態獲取errCancelContentProcessing // 表示取消內容執行errCanceled // 表示同步被取消errNoPivotHeader // 表示未找到 pivot header
取消機制
-
Downloader.cancelCh
一個 channel,用於取消正在進行的同步操作。 -
Downloader.Cancel
(在synchronise
函數的defer
中調用)確保在同步過程退出時關閉cancelCh
,並等待所有fetcher
goroutine 退出。 -
cancelLock
和cancelWg
用於保護cancelCh
的併發訪問和確保 goroutine 正確退出。
處理無效數據和壞塊
-
當從 peer 接收到的數據(如區塊頭、區塊體、收據)驗證失敗時,會返回相應的錯誤(如
errInvalidChain
,errInvalidBody
,errInvalidReceipt
)。 -
Downloader.badBlock
用於異步 beacon sync 通知調用者,請求同步的源頭 header 產生了一個包含壞塊的鏈。 -
queue.DeliverHeaders
,queue.DeliverBodies
,queue.DeliverReceipts
(在queue.go
中)等函數會進行數據校驗,如果校驗失敗,可能會導致 peer 被標記爲不良或被移除。
同步失敗事件
-
當同步過程因任何錯誤而意外終止時,Geth 會通過
d.mux.Post(FailedEvent{err})
發佈一個FailedEvent
事件,其中包含了具體的錯誤信息。這使得 Geth 的其他模塊或外部監控工具能夠及時感知同步狀態的變化並做出相應處理。
5. 測試關鍵步驟
Geth 中包含了大量測試用例,我們可以對上文提到的關鍵步驟直接進行測試。
5.1 節點發現測試
在目錄 p2p/discover
下
v4_udp_test.go
此文件包含了針對基於UDP的v4發現協議的測試
-
TestUDPv4_findnode
測試findnode
請求,這是節點發現的核心。它會檢查是否能找到並返回最近的鄰居節點。 -
TestUDPv4_pingTimeout
測試ping
請求的超時處理,ping/pong
用於檢查節點是否在線。 -
TestUDPv4_packetErrors
測試各種數據包錯誤處理,例如過期的包、未請求的回覆等。
v5_udp_test.go
這個文件包含了針對基於 UDP 的 discv5 發現協議的測試。
-
TestUDPv5_lookupE2E
進行端到端的節點查找測試,確保節點可以成功發現網絡中的其他節點。 -
TestUDPv5_pingHandling
測試對 PING 請求的處理和 PONG 回覆的生成。 -
TestUDPv5_unknownPacket
測試當收到未知來源的數據包時的處理邏輯,這通常會觸發握手流程。
table_test.go
包含對路由表(Kademlia 表)操作的測試,例如節點的添加、刪除、查找等。
v4_lookup_test.go
這個文件用於 discv4 發現協議中的節點查找(lookup)邏輯測試。
5.2 建立連接
handshake_test.go
這個文件專門測試以太坊協議(eth protocol)的握手過程。
-
通常會包含測試握手消息的編碼/解碼、狀態轉換(例如,從等待狀態到握手成功/失敗)、版本協商、能力通告等。
v5_udp_test.go
(也與握手相關):
-
TestUDPv5_handshakeRepeatChallenge
測試在握手過程中重複收到挑戰(challenge)時的處理邏輯,確保握手的健壯性。 -
TestUDPv5_unknownPacketKnownNode
測試當收到來自已知節點的未知數據包時的行爲,這可能也與握手或會話重新建立有關。
5.3 RLPx 握手測試
RLPx 測試(加密握手、消息收發等)主要位於 p2p/rlpx_test.go
文件:
-
TestHandshake
測試握手,裏面包括創建對等節點。 -
createPeers
創建對等節點,並嘗試進行握手。 -
doHandshake
進行握手測試。
5.4 節點管理測試
主要集中在 p2p/server_test.go
、 p2p/discover/v4_udp_test.go
、 p2p/discover/v4_lookup_test.go
、 p2p/discover/v5_udp_test.go
。
-
例如
server_test.go
涵蓋了節點的添加、移除、信任管理、連接建立等核心邏輯。 -
v4_udp_test.go
和v5_udp_test.go
主要測試節點發現協議(如 Ping/Pong、FindNode、Neighbors、Whoareyou 等包的處理),包括節點查找、握手、包處理異常等。 -
v4_lookup_test.go
重點測試節點查找流程、查找迭代器、查找結果的正確性和排序。
6. 總結
Geth 的網絡核心構建在 DevP2P 協議棧之上,這是一個專爲以太坊網絡層通信設計的協議集合。節點首先通過多種機制發現對等節點,主要包括基於 Kademlia 的分佈式哈希表(如 Discv4/v5 協議)和 DNS 種子節點。一旦發現潛在的對等節點,Geth 會嘗試建立 TCP 連接。成功建立 TCP 連接後,會進行 RLPx 加密握手,以確保通信的機密性和完整性,並協商會話密鑰。
在 RLPx 握手之後,節點會進行 DevP2P 協議層的握手。在這一階段,節點會交換它們各自支持的子協議及其版本,例如 eth (以太坊主協議)、 snap (快速同步協議)、 les (輕客戶端協議)等。雙方會協商使用共同支持的最高版本的子協議進行後續通信。這個過程確保了不同能力的節點可以有效地交互。
協商成功後,節點便通過選定的子協議進行具體的數據交換。例如,通過 eth 協議,節點同步區塊、交易、鏈狀態等核心數據,這是以太坊網絡功能的基礎。而 snap 協議則允許節點更高效地同步狀態數據,加速新節點的初始同步過程。Geth 會持續管理其對等節點列表,監控連接狀態,並根據網絡情況和節點行爲動態調整連接。
總之,Geth 的網絡模塊是一個分層且模塊化的系統,從底層的節點發現和安全連接建立,到上層的特定功能子協議協商與數據交換,共同構成了一個健壯且高效的去中心化網絡通信框架。這種設計使得 Geth 能夠靈活適應網絡變化,並支持以太坊生態系統的持續發展。
References
[1]https://github.com/ethereum/go-ethereum
[2]https://github.com/ethereum/devp2p
[3]https://www.geeksforgeeks.org/what-is-the-difference-between-libp2p-devp2p-and-rlpx/
[4]Welcome to go-ethereum | go-ethereum
[5]https://ethereum.org/zh/developers/docs/