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