日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

基于tutk方案的p2p源码_以太坊源码分析--p2p节点发现

發(fā)布時(shí)間:2023/12/10 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 基于tutk方案的p2p源码_以太坊源码分析--p2p节点发现 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

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)部分的實(shí)現(xiàn)

數(shù)據(jù)結(jié)構(gòu)

節(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è)以太坊客戶端啟動(dòng)后都會(huì)在本地運(yùn)行一個(gè)Server,并將網(wǎng)絡(luò)拓?fù)渲邢噜彽墓?jié)點(diǎn)視為Node,而Table是Node的容器,udp則是負(fù)責(zé)維持底層的連接。下面重點(diǎn)描述它們中重要的字段和事件循環(huán)處理的關(guān)鍵部分。

Server

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)啟動(dòng)時(shí)會(huì)首先去向它們發(fā)起連接,建立鄰居關(guān)系

newTransport - 下層傳輸層實(shí)現(xiàn),定義握手過(guò)程中的數(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)的版本號(hào)以及支持的上層協(xié)議

addpeer - 連接握手完成后,連接過(guò)程通過(guò)這個(gè)通道通知Server

Server.listenLoop()

Server的監(jiān)聽循環(huán),啟動(dòng)底層監(jiān)聽socket,當(dāng)收到連接請(qǐng)求時(shí),Accept后調(diào)用setupConn()開始連接建立過(guò)程

Server.run()

Server的主要事件處理和功能實(shí)現(xiàn)循環(huán)

進(jìn)行主動(dòng)的節(jié)點(diǎn)發(fā)現(xiàn),詳見(jià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é)議,詳見(jiàn)[TODO]p2p協(xié)議運(yùn)行

Node

Node唯一表示網(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端口號(hào)

ID - 以太坊網(wǎng)絡(luò)中唯一標(biāo)識(shí)一個(gè)節(jié)點(diǎn),本質(zhì)上是一個(gè)橢圓曲線公鑰(PublicKey),與Server的PrivateKey對(duì)應(yīng)。一個(gè)節(jié)點(diǎn)的IP地址不一定是固定的,但I(xiàn)D是唯一的。

sha - 用于節(jié)點(diǎn)間的距離計(jì)算

Table

Table主要用來(lái)管理與本節(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àn)之后的節(jié)點(diǎn)維護(hù)

refreshReq - 更新Table請(qǐng)求通道

Table.loop()

Table的主要事件循環(huán),主要負(fù)責(zé)控制refresh和revalidate過(guò)程。

refresh.C - 定時(shí)(30s)啟動(dòng)Peer刷新過(guò)程的定時(shí)器

refreshReq - 接收其他線程投遞到Table的刷新Peer連接的通知,當(dāng)收到該通知時(shí)啟動(dòng)更新,詳見(jiàn)之后的更新鄰居關(guān)系

revalidate.C - 定時(shí)重新檢查以連接節(jié)點(diǎn)的有效性的定時(shí)器,詳見(jiàn)之后的探活檢測(cè)

udp

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用來(lái)接收pending的channel。使用場(chǎng)景為:當(dāng)我們向其他節(jié)點(diǎn)發(fā)送數(shù)據(jù)包后(packet)后可能會(huì)期待收到它的回復(fù),pending用來(lái)記錄一次這種還沒(méi)有到來(lái)的回復(fù)。舉個(gè)例子,當(dāng)我們發(fā)送ping包時(shí),總是期待對(duì)方回復(fù)pong包。這時(shí)就可以將構(gòu)造一個(gè)pending結(jié)構(gòu),其中包含期待接收的pong包的信息以及對(duì)應(yīng)的callback函數(shù),將這個(gè)pengding投遞到udp的這個(gè)channel。udp在收到匹配的pong后,執(zhí)行預(yù)設(shè)的callback。

gotreply - udp用來(lái)接收其他節(jié)點(diǎn)回復(fù)的通道,配合上面的addpending,收到回復(fù)后,遍歷已有的pending鏈表,看是否有匹配的pending。

Table - 和Server中的ntab是同一個(gè)Table

udp.loop()

udp的處理循環(huán),負(fù)責(zé)控制消息的向上遞交和收發(fā)控制

addpending 接收其他線程投遞來(lái)的pending需求

gotreply 接收udp.readLoop()投遞過(guò)來(lái)的pending的回復(fù)

udp.readLoop()

udp的底層接受數(shù)據(jù)包循環(huán),負(fù)責(zé)接收其他節(jié)點(diǎn)的packet

接受其他節(jié)點(diǎn)發(fā)送的packet并解析,如果是回復(fù)包則投遞到udp.loop()

節(jié)點(diǎn)維護(hù)

以太坊使用Kademlia分布式路由存儲(chǔ)協(xié)議來(lái)進(jìn)行網(wǎng)絡(luò)拓?fù)渚S護(hù),了解該協(xié)議建議先閱讀易懂分布式。更權(quán)威的資料可以查看wiki。總的來(lái)說(shuō)該協(xié)議:

使用UDP進(jìn)行節(jié)點(diǎn)間消息通信,有 4 種消息

ping - 用于探測(cè)其他節(jié)點(diǎn)是否還存在

store - 接收者受到后,將信息中key/value對(duì)存儲(chǔ)在本節(jié)點(diǎn)

findnode - 接受者向發(fā)送者返回 k 個(gè)它知道的與目標(biāo)結(jié)點(diǎn)距離最近的節(jié)點(diǎn)

findvalue - 和findnode 差不多,區(qū)別是如果接收者本地存在與目標(biāo)結(jié)點(diǎn)對(duì)應(yīng)的value,那么就回復(fù)這個(gè)值給發(fā)送者。

每個(gè)節(jié)點(diǎn)根據(jù)與鄰居節(jié)點(diǎn)距離之間的距離(NodeID的差距),分別放到不同的桶(bucket)中。

本文說(shuō)的距離,均是指兩個(gè)節(jié)點(diǎn)NodeID的距離,計(jì)算方式可見(jiàn)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)過(guò)bond的節(jié)點(diǎn),并且其順序是越新bond通過(guò)了探活檢測(cè)(Revalidate)的節(jié)點(diǎn)位置越靠前。

replacemenets數(shù)組中保存候補(bǔ)節(jié)點(diǎn),如果entries 數(shù)組數(shù)量滿了,之后的節(jié)點(diǎn)會(huì)被加入該數(shù)組

節(jié)點(diǎn)可以在entries和replacements互相轉(zhuǎn)化,一個(gè)entries節(jié)點(diǎn)如果Validate失敗,那么它會(huì)被原本將一個(gè)原本在replacements數(shù)組的節(jié)點(diǎn)替換。

探活檢測(cè)(Revalidate)

有效性檢測(cè)就是利用ping消息進(jìn)行探活操作。Table.loop()啟動(dòng)了一個(gè)定時(shí)器(0~10s),定期隨機(jī)選擇一個(gè)bucket,向其entries中末尾的節(jié)點(diǎn)發(fā)送ping消息,如果對(duì)方回應(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通過(guò),由于entries已滿,只能加入到replacements = [C, D, E, F]。 此時(shí)Revalidate定時(shí)器到期,則會(huì)對(duì) B進(jìn)行檢測(cè),如果通過(guò),則entries=[B, A],如果不通過(guò),則將隨機(jī)選擇replacements中的一項(xiàng)(假設(shè)為D)替換B的位置,最終entries=[A, D],replacements = [C, E, F]

更新鄰居關(guān)系

Table.loop()會(huì)定期(定時(shí)器超時(shí))或不定期(收到refreshReq)地進(jìn)行更新鄰居關(guān)系(發(fā)現(xiàn)新鄰居),兩者都調(diào)用doRefresh()方法,該方法對(duì)在網(wǎng)絡(luò)上查找離自身和三個(gè)隨機(jī)節(jié)點(diǎn)最近的若干個(gè)節(jié)點(diǎn)。

節(jié)點(diǎn)查找

Table的lookup()方法用來(lái)實(shí)現(xiàn)節(jié)點(diǎn)查找目標(biāo)節(jié)點(diǎn),它的實(shí)現(xiàn)就是Kademlia協(xié)議,通過(guò)節(jié)點(diǎn)間的接力,一步一步接近目標(biāo)。

鄰居初始化

當(dāng)一個(gè)節(jié)點(diǎn)啟動(dòng)后,它會(huì)首先向配置的靜態(tài)節(jié)點(diǎn)發(fā)起連接,發(fā)起連接的過(guò)程稱為Dial,源碼中通過(guò)創(chuàng)建dialTask跟蹤這個(gè)過(guò)程

dialTask

dialTask表示一次向其他節(jié)點(diǎn)主動(dòng)發(fā)起連接的任務(wù)

p2p/dial.go

type dialTask struct {

flags connFlag

dest *discover.Node

......

}

在Server啟動(dòng)時(shí),會(huì)調(diào)用newDialState()根據(jù)預(yù)配置的StaticNodes初始化一批dialTask, 并在Server.run()方法中,啟動(dòng)這些這些任務(wù)。

diatask (1).png

Dial過(guò)程需要知道目標(biāo)節(jié)點(diǎn)(dest)的IP地址,如果不知道的話,就要先使用 recolve()解析出目標(biāo)的IP地址,怎么解析?就是先要用借助Kademlia協(xié)議在網(wǎng)絡(luò)中查找目標(biāo)節(jié)點(diǎn)。

resolve (1).png

當(dāng)?shù)玫侥繕?biāo)節(jié)點(diǎn)的IP后,下一步便是建立連接,這是通過(guò)dialTask.dial()建立連接

連接建立

連接建立的握手過(guò)程分為兩個(gè)階段,在在SetupConn()中實(shí)現(xiàn)

第一階段為ECDH密鑰建立:

enchand.png

第二階段為協(xié)議握手,互相交換支持的上層協(xié)議

dial-receive.png

如果兩次握手都通過(guò),dialTask將向Server的addpeer通道發(fā)送peer的信息

serverrun-dial-remote.png

總結(jié)

p2p節(jié)點(diǎn)發(fā)現(xiàn)(discover)負(fù)責(zé)管理以太坊網(wǎng)絡(luò)中各個(gè)節(jié)點(diǎn)間的連接建立,更新和刪除,Server是p2p功能的入口,Table負(fù)責(zé)記錄peer節(jié)點(diǎn)信息, udp負(fù)責(zé)底層通信

以太坊使用Kademlia分布式路由存儲(chǔ)協(xié)議來(lái)進(jìn)行網(wǎng)絡(luò)拓?fù)渚S護(hù),將不同距離的peer節(jié)點(diǎn)放在不同的bucket中。

總結(jié)

以上是生活随笔為你收集整理的基于tutk方案的p2p源码_以太坊源码分析--p2p节点发现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。