摘要:前言負(fù)責(zé)以太坊底層節(jié)點(diǎn)間的通信,主要包括底層節(jié)點(diǎn)發(fā)現(xiàn)和上層協(xié)議運(yùn)行兩大部分。啟動了一個(gè)定時(shí)器,定期隨機(jī)選擇一個(gè),向其中末尾的節(jié)點(diǎn)發(fā)送消息,如果對方回應(yīng)了,則探活成功。
前言
p2p(peer to peer)負(fù)責(zé)以太坊底層節(jié)點(diǎn)間的通信,主要包括底層節(jié)點(diǎn)發(fā)現(xiàn)(discover)和上層協(xié)議運(yùn)行兩大部分。
節(jié)點(diǎn)發(fā)現(xiàn)節(jié)點(diǎn)發(fā)現(xiàn)功能主要涉及 Server Table udp 這幾個(gè)數(shù)據(jù)結(jié)構(gòu),它們有獨(dú)自的事件響應(yīng)循環(huán),節(jié)點(diǎn)發(fā)現(xiàn)功能便是它們互相協(xié)作完成的。其中,每個(gè)以太坊客戶端啟動后都會在本地運(yùn)行一個(gè)Server,并將網(wǎng)絡(luò)拓?fù)渲邢噜彽墓?jié)點(diǎn)視為Node,而Table是Node的容器,udp則是負(fù)責(zé)維持底層的連接。這些結(jié)構(gòu)的關(guān)系如下圖
p2p/server.go type Server struct { PrivateKey *ecdsa.PrivateKey Protocols []protocol StaticNodes[] *discover.Node newTransport func(net.Conn) transport ntab disvocerTable ourHandshake *protoHandshake addpeer chan *conn ...... }
PrivateKey - 本節(jié)點(diǎn)的私鑰,用于與其他節(jié)點(diǎn)建立時(shí)的握手協(xié)商
Protocols - 支持的所有上層協(xié)議
StaticNodes - 預(yù)設(shè)的靜態(tài)Peer,節(jié)點(diǎn)啟動時(shí)會首先去向它們發(fā)起連接,建立鄰居關(guān)系
newTransport - 下層傳輸層實(shí)現(xiàn),定義握手過程中的數(shù)據(jù)加密解密方式,默認(rèn)的傳輸層實(shí)現(xiàn)是用newRLPX()創(chuàng)建的rlpx,這不是本文的重點(diǎn)
ntab - 典型實(shí)現(xiàn)是Table,所有peer以Node的形式存放在Table
ourHandshake - 與其他節(jié)點(diǎn)建立連接時(shí)的握手信息,包含本地節(jié)點(diǎn)的版本號以及支持的上層協(xié)議
addpeer - 連接握手完成后,連接過程通過這個(gè)通道通知Server
Server的監(jiān)聽循環(huán),啟動底層監(jiān)聽socket,當(dāng)收到連接請求時(shí),Accept后調(diào)用setupConn()開始連接建立過程
Server的主要事件處理和功能實(shí)現(xiàn)循環(huán)
進(jìn)行主動的節(jié)點(diǎn)發(fā)現(xiàn),詳見之后的節(jié)點(diǎn)發(fā)現(xiàn)部分
posthandshake channel 接收已經(jīng)完成第一階段的連接,這些連接的身份已經(jīng)被確認(rèn),但還需要驗(yàn)證
addpeer channel 接收已經(jīng)完成第二階段的連接,這些連接已經(jīng)驗(yàn)證,調(diào)用runPeer()運(yùn)行本節(jié)點(diǎn)與Peer連接上的協(xié)議
NodeNode唯一表示網(wǎng)絡(luò)上的一個(gè)節(jié)點(diǎn)
p2p/discover/node.go type Node struct { IP net.IP UDP, TCP uint16 ID NodeID sha common.Hash }
IP - IP地址
UDP/TCP - 連接使用的UDP/TCP端口號
ID - 以太坊網(wǎng)絡(luò)中唯一標(biāo)識一個(gè)節(jié)點(diǎn),本質(zhì)上是一個(gè)橢圓曲線公鑰(PublicKey),與Server的PrivateKey對應(yīng)。一個(gè)節(jié)點(diǎn)的IP地址不一定是固定的,但I(xiàn)D是唯一的。
sha - 用于節(jié)點(diǎn)間的距離計(jì)算
Table主要用來管理與本節(jié)點(diǎn)與其他節(jié)點(diǎn)的連接的建立更新刪除
p2p/discover/table.go type Table struct { bucket [nBuckets]* bucket refreshReq chan chan struct{} ...... }
bucket - 所有peer按與本節(jié)點(diǎn)的距離遠(yuǎn)近放在不同的桶(bucket)中,詳見之后的節(jié)點(diǎn)維護(hù)
refreshReq - 更新Table請求通道
Table的主要事件循環(huán),主要負(fù)責(zé)控制refresh和revalidate過程。
refresh.C - 定時(shí)(30s)啟動Peer刷新過程的定時(shí)器
refreshReq - 接收其他線程投遞到Table的刷新Peer連接的通知,當(dāng)收到該通知時(shí)啟動更新,詳見之后的更新鄰居關(guān)系
revalidate.C - 定時(shí)重新檢查以連接節(jié)點(diǎn)的有效性的定時(shí)器,詳見之后的探活檢測
udp負(fù)責(zé)節(jié)點(diǎn)間通信的底層消息控制,是Table運(yùn)行的Kademlia協(xié)議的底層組件
type udp struct { conn conn addpending chan *pending gotreply chan reply *Table }
conn - 底層監(jiān)聽端口的連接
addpending -udp用來接收pending的channel。使用場景為:當(dāng)我們向其他節(jié)點(diǎn)發(fā)送數(shù)據(jù)包后(packet)后可能會期待收到它的回復(fù),pending用來記錄一次這種還沒有到來的回復(fù)。舉個(gè)例子,當(dāng)我們發(fā)送ping包時(shí),總是期待對方回復(fù)pong包。這時(shí)就可以將構(gòu)造一個(gè)pending結(jié)構(gòu),其中包含期待接收的pong包的信息以及對應(yīng)的callback函數(shù),將這個(gè)pengding投遞到udp的這個(gè)channel。udp在收到匹配的pong后,執(zhí)行預(yù)設(shè)的callback。
gotreply - udp用來接收其他節(jié)點(diǎn)回復(fù)的通道,配合上面的addpending,收到回復(fù)后,遍歷已有的pending鏈表,看是否有匹配的pending。
Table - 和Server中的ntab是同一個(gè)Table
udp的處理循環(huán),負(fù)責(zé)控制消息的向上遞交和收發(fā)控制
addpending 接收其他線程投遞來的pending需求
gotreply 接收udp.readLoop()投遞過來的pending的回復(fù)
udp的底層接受數(shù)據(jù)包循環(huán),負(fù)責(zé)接收其他節(jié)點(diǎn)的packet
接受其他節(jié)點(diǎn)發(fā)送的packet并解析,如果是回復(fù)包則投遞到udp.loop()
以太坊使用Kademlia分布式路由存儲協(xié)議來進(jìn)行網(wǎng)絡(luò)拓?fù)渚S護(hù),了解該協(xié)議建議先閱讀易懂分布式。更權(quán)威的資料可以查看wiki。總的來說該協(xié)議:
使用UDP進(jìn)行節(jié)點(diǎn)間消息通信,有 4 種消息
ping - 用于探測其他節(jié)點(diǎn)是否還存在
store - 接收者受到后,將信息中key/value對存儲在本節(jié)點(diǎn)
findnode - 接受者向發(fā)送者返回 k 個(gè)它知道的與目標(biāo)結(jié)點(diǎn)距離最近的節(jié)點(diǎn)
findvalue - 和findnode 差不多,區(qū)別是如果接收者本地存在與目標(biāo)結(jié)點(diǎn)對應(yīng)的value,那么就回復(fù)這個(gè)值給發(fā)送者。
每個(gè)節(jié)點(diǎn)根據(jù)與鄰居節(jié)點(diǎn)距離之間的距離(NodeID的差距),分別放到不同的桶(bucket)中。
本文說的距離,均是指兩個(gè)節(jié)點(diǎn)NodeID的距離,計(jì)算方式可見p2p/discover/node.go的logdist()方法
源碼中由Table結(jié)構(gòu)保存所有bucket,bucket結(jié)構(gòu)如下
p2p/discover/table.go type bucket struct { entries []*Node replacemenets []*Node ips netutil.DistinctNetSet }
entries 數(shù)組中保存經(jīng)過bond的節(jié)點(diǎn),并且其順序是越新bond通過了探活檢測(Revalidate)的節(jié)點(diǎn)位置越靠前。
replacemenets數(shù)組中保存候補(bǔ)節(jié)點(diǎn),如果entries 數(shù)組數(shù)量滿了,之后的節(jié)點(diǎn)會被加入該數(shù)組
節(jié)點(diǎn)可以在entries和replacements互相轉(zhuǎn)化,一個(gè)entries節(jié)點(diǎn)如果Validate失敗,那么它會被原本將一個(gè)原本在replacements數(shù)組的節(jié)點(diǎn)替換。
探活檢測(Revalidate)有效性檢測就是利用ping消息進(jìn)行探活操作。Table.loop()啟動了一個(gè)定時(shí)器(0~10s),定期隨機(jī)選擇一個(gè)bucket,向其entries中末尾的節(jié)點(diǎn)發(fā)送ping消息,如果對方回應(yīng)了pong,則探活成功。
舉個(gè)栗子,假設(shè)某個(gè)bucket, entries最多保存2個(gè)節(jié)點(diǎn),replacements最多保存4個(gè)節(jié)點(diǎn)。初始情況下entries=[A, B], replacements = [C, D, E],如果此時(shí)節(jié)點(diǎn)F加入網(wǎng)絡(luò),bond通過,由于entries已滿,只能加入到replacements = [C, D, E, F]。 此時(shí)Revalidate定時(shí)器到期,則會對 B進(jìn)行檢測,如果通過,則entries=[B, A],如果不通過,則將隨機(jī)選擇replacements中的一項(xiàng)(假設(shè)為D)替換B的位置,最終entries=[A, D],replacements = [C, E, F]更新鄰居關(guān)系
Table.loop()會定期(定時(shí)器超時(shí))或不定期(收到refreshReq)地進(jìn)行更新鄰居關(guān)系(發(fā)現(xiàn)新鄰居),兩者都調(diào)用doRefresh()方法,該方法對在網(wǎng)絡(luò)上查找離自身和三個(gè)隨機(jī)節(jié)點(diǎn)最近的若干個(gè)節(jié)點(diǎn)。
節(jié)點(diǎn)查找Table的lookup()方法用來實(shí)現(xiàn)節(jié)點(diǎn)查找目標(biāo)節(jié)點(diǎn),它的實(shí)現(xiàn)就是Kademlia協(xié)議,通過節(jié)點(diǎn)間的接力,一步一步接近目標(biāo)。
鄰居初始化當(dāng)一個(gè)節(jié)點(diǎn)啟動后,它會首先向配置的靜態(tài)節(jié)點(diǎn)發(fā)起連接,發(fā)起連接的過程稱為Dial,源碼中通過創(chuàng)建dialTask跟蹤這個(gè)過程
dialTaskdialTask表示一次向其他節(jié)點(diǎn)主動發(fā)起連接的任務(wù)
p2p/dial.go type dialTask struct { flags connFlag dest *discover.Node ...... }
在Server啟動時(shí),會調(diào)用newDialState()根據(jù)預(yù)配置的StaticNodes初始化一批dialTask, 并在Server.run()方法中,啟動這些這些任務(wù)。
Dial過程需要知道目標(biāo)節(jié)點(diǎn)(dest)的IP地址,如果不知道的話,就要先使用 recolve()解析出目標(biāo)的IP地址,怎么解析?就是先要用借助Kademlia協(xié)議在網(wǎng)絡(luò)中查找目標(biāo)節(jié)點(diǎn)。
當(dāng)?shù)玫侥繕?biāo)節(jié)點(diǎn)的IP后,下一步便是建立連接,這是通過dialTask.dial()建立連接
連接建立的握手過程分為兩個(gè)階段,在在SetupConn()中實(shí)現(xiàn)
第一階段為ECDH密鑰建立:
sequenceDiagram Note left of Dialer: Calc token Note left of Dialer: Generate Random PrikeyNonce Note left of Dialer: Sign Dialer->>Receiver: AuthMsg Note right of Receiver: Calc token Note right of Receiver: Check Signature Note right of Receiver: Generate Random PrikeyNonce Receiver->>Dialer: AuthResp
第二階段為協(xié)議握手,互相交換支持的上層協(xié)議
sequenceDiagram Dialer->>Receiver: protoHandshake Receiver->>Dialer: protoHandshake
如果兩次握手都通過,dialTask將向Server的addpeer通道發(fā)送peer的信息
sequenceDiagram participant Server.run() participant dialTask participant Remote Node dialTask->>Remote Node:EncHandshake Remote Node->>dialTask:EncHandshake dialTask->>Server.run(): posthandshake dialTask->>Remote Node:ProtoHandshake Remote Node->>dialTask:ProtoHandshake dialTask->>Server.run(): addpeer Note over Server.run(): go runPeer()協(xié)議運(yùn)行
協(xié)議運(yùn)行并不單單指某個(gè)特定的協(xié)議,準(zhǔn)確地說應(yīng)該是若干個(gè)獨(dú)立的協(xié)議同時(shí)在兩個(gè)節(jié)點(diǎn)間運(yùn)行。在p2p節(jié)點(diǎn)發(fā)現(xiàn)提到過,節(jié)點(diǎn)間建立連接的時(shí)候會經(jīng)過兩次握手,其中的第二次握手,節(jié)點(diǎn)間會交換自身所支持的協(xié)議。最終兩個(gè)節(jié)點(diǎn)間生效的協(xié)議為兩個(gè)節(jié)點(diǎn)支持的協(xié)議的交集。
功能主要涉及 Peer protoRW 這幾個(gè)數(shù)據(jù)結(jié)構(gòu),其關(guān)系如圖
rw - 節(jié)點(diǎn)間連接的底層信息,比如使用的socket以及對端節(jié)點(diǎn)支持的協(xié)議(capabilities)
running - 節(jié)點(diǎn)間生效運(yùn)行的協(xié)議簇
Peer.run()負(fù)責(zé)連接建立后啟動運(yùn)行上層協(xié)議,它自身運(yùn)行在一個(gè)獨(dú)立的go routine,具有自己的事件處理循環(huán),除此之外,它還會額外創(chuàng)建2+n個(gè)go routine, 其中2包括一個(gè)用于保活的pingLoop() go routine和一個(gè)用于接收協(xié)議數(shù)據(jù)的readLoop() go routine ,而 n 為運(yùn)行于其上的n個(gè)協(xié)議的go routine,即每個(gè)協(xié)議調(diào)用自己的Run()方法運(yùn)行在自己多帶帶的go routine
Run 每種協(xié)議自身的運(yùn)行入口,以新的go routine形式啟動.
總結(jié)p2p主要由底層節(jié)點(diǎn)發(fā)現(xiàn)和上層協(xié)議運(yùn)行兩部分組成,節(jié)點(diǎn)發(fā)現(xiàn)負(fù)責(zé)管理以太坊網(wǎng)絡(luò)中各個(gè)節(jié)點(diǎn)間的連接建立,更新和刪除,Server是p2p功能的入口,Table負(fù)責(zé)記錄peer節(jié)點(diǎn)信息, udp負(fù)責(zé)底層通信。而在底層的基礎(chǔ)上,節(jié)點(diǎn)間可以運(yùn)行多個(gè)獨(dú)立的協(xié)議。
以太坊使用Kademlia分布式路由存儲協(xié)議來進(jìn)行網(wǎng)絡(luò)拓?fù)渚S護(hù),將不同距離的peer節(jié)點(diǎn)放在不同的bucket中。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/24380.html
摘要:前言是以太坊中一項(xiàng)非常有趣的技術(shù),它是一個(gè)基于身份的通信系統(tǒng),被設(shè)計(jì)用于之間少量數(shù)據(jù)通信。協(xié)議運(yùn)行在以太坊協(xié)議框架之上,所有運(yùn)行協(xié)議的節(jié)點(diǎn)以下簡稱節(jié)點(diǎn)組成一個(gè)網(wǎng)絡(luò)。 [TOC] 前言 Whisper是以太坊中一項(xiàng)非常有趣的技術(shù),它是一個(gè)基于身份的通信系統(tǒng),被設(shè)計(jì)用于Dapp之間少量數(shù)據(jù)通信。Whisper協(xié)議運(yùn)行在以太坊p2p協(xié)議框架之上,所有運(yùn)行Whisper協(xié)議的節(jié)點(diǎn)(以下簡稱節(jié)點(diǎn)...
摘要:為什么區(qū)塊鏈會選擇作為網(wǎng)絡(luò)基礎(chǔ)上面介紹的時(shí)候說過,他是無中心服務(wù)器的,中心服務(wù)器就意味著,當(dāng)受到攻擊的時(shí)候,中心服務(wù)器一旦宕機(jī),整個(gè)網(wǎng)絡(luò)和服務(wù)就會出現(xiàn)問題。區(qū)塊鏈的核心是去中心化,這和網(wǎng)絡(luò)的觀念不約而同,所以選擇的理由也就很充分。 區(qū)塊鏈中P2P介紹 p2p是什么 為什么區(qū)塊鏈需要P2P 比特幣、以太坊、超級賬本和EOS的P2P對比 P2P是什么 P2P作為區(qū)塊鏈網(wǎng)絡(luò)中去中心化...
摘要:引言給迷失在如何學(xué)習(xí)區(qū)塊鏈技術(shù)的同學(xué)一個(gè)指引,區(qū)塊鏈技術(shù)是隨比特幣誕生,因此要搞明白區(qū)塊鏈技術(shù),應(yīng)該先了解下比特幣。但區(qū)塊鏈技術(shù)不單應(yīng)用于比特幣,還有非常多的現(xiàn)實(shí)應(yīng)用場景,想做區(qū)塊鏈應(yīng)用開發(fā),可進(jìn)一步閱讀以太坊系列。 本文始發(fā)于深入淺出區(qū)塊鏈社區(qū), 原文:區(qū)塊鏈技術(shù)學(xué)習(xí)指引 原文已更新,請讀者前往原文閱讀 本章的文章越來越多,本文是一個(gè)索引帖,方便找到自己感興趣的文章,你也可以使用左側(cè)...
摘要:說明的視頻片段分發(fā)現(xiàn)在沒做出什么成果作者還提了一句,協(xié)議有望成為直播內(nèi)容的傳播協(xié)議。仿佛也沒能掩飾住不知道怎么分發(fā)視頻片段的尷尬說了這么多,看了代碼發(fā)現(xiàn)視頻片段還是通過分發(fā)總結(jié)最終將建立一個(gè)可擴(kuò)展的,即用即付的直播網(wǎng)絡(luò) Background Livepeer旨在構(gòu)建帶有激勵(lì)機(jī)制的視頻直播分布式網(wǎng)絡(luò) Blockchain 以太坊 智能合約和交易基于Ethereum以太坊網(wǎng)絡(luò) DP...
閱讀 820·2021-11-11 16:54
閱讀 3172·2021-09-26 09:55
閱讀 2069·2021-09-07 10:20
閱讀 1280·2019-08-30 10:58
閱讀 1112·2019-08-28 18:04
閱讀 778·2019-08-26 13:57
閱讀 3667·2019-08-26 13:45
閱讀 1224·2019-08-26 11:42