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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

3 v4 中心节点固定_死磕以太坊源码分析之p2p节点发现

發布時間:2025/3/20 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 3 v4 中心节点固定_死磕以太坊源码分析之p2p节点发现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

死磕以太坊源碼分析之p2p節點發現

在閱讀節點發現源碼之前必須要理解kadmilia算法,可以參考:KAD算法詳解。

節點發現概述

節點發現,使本地節點得知其他節點的信息,進而加入到p2p網絡中。

以太坊的節點發現基于類似的kademlia算法,源碼中有兩個版本,v4和v5。v4適用于全節點,通過discover.ListenUDP使用,v5適用于輕節點通過discv5.ListenUDP使用,本文介紹的是v4版本。

節點發現功能主要涉及 Server Table udp 這幾個數據結構,它們有獨自的事件響應循環,節點發現功能便是它們互相協作完成的。其中,每個以太坊客戶端啟動后都會在本地運行一個Server,并將網絡拓撲中相鄰的節點視為Node,而TableNode的容器,udp則是負責維持底層的連接。這些結構的關系如下圖:

image-20201123210628944

p2p服務開啟節點發現

在P2p的server.go 的start方法中:

if?err?:=?srv.setupDiscovery();?err?!=?nil?{
??return?err
?}

進入到setupDiscovery中:

//?Discovery?V4
?var?unhandled?chan?discover.ReadPacket
?var?sconn?*sharedUDPConn
?if?!srv.NoDiscovery?{
??...
??ntab,?err?:=?discover.ListenUDP(conn,?srv.localnode,?cfg)
??....
?}

discover.ListenUDP方法即開啟了節點發現的功能.

首先解析出監聽地址的UDP端口,根據端口返回與之相連的UDP連接,之后返回連接的本地網絡地址,接著設置最后一個UDP-on-IPv4端口。到此為止節點發現的一些準備工作做好,接下下來開始UDP的監聽:

ntab,?err?:=?discover.ListenUDP(conn,?srv.localnode,?cfg)

然后進行UDP 的監聽,下面是監聽的過程:

監聽UDP

//?監聽給定的socket?上的發現的包
func?ListenUDP(c?UDPConn,?ln?*enode.LocalNode,?cfg?Config)?(*UDPv4,?error)?{
?return?ListenV4(c,?ln,?cfg)
}
func?ListenV4(c?UDPConn,?ln?*enode.LocalNode,?cfg?Config)?(*UDPv4,?error)?{
?closeCtx,?cancel?:=?context.WithCancel(context.Background())
?t?:=?&UDPv4{
??conn:????????????c,
??priv:????????????cfg.PrivateKey,
??netrestrict:?????cfg.NetRestrict,
??localNode:???????ln,
??db:??????????????ln.Database(),
??gotreply:????????make(chan?reply),
??addReplyMatcher:?make(chan?*replyMatcher),
??closeCtx:????????closeCtx,
??cancelCloseCtx:??cancel,
??log:?????????????cfg.Log,
?}
?if?t.log?==?nil?{
??t.log?=?log.Root()
?}

?tab,?err?:=?newTable(t,?ln.Database(),?cfg.Bootnodes,?t.log)?//?
?if?err?!=?nil?{
??return?nil,?err
?}
?t.tab?=?tab
?go?tab.loop()?//

?t.wg.Add(2)
?go?t.loop()?//
?go?t.readLoop(cfg.Unhandled)?//
?return?t,?nil
}

主要做了以下幾件事:

1.新建路由表

tab,?err?:=?newTable(t,?ln.Database(),?cfg.Bootnodes,?t.log)?

新建路由表做了以下幾件事:

  • 初始化table對象
  • 設置bootnode(setFallbackNodes)
    • 節點第一次啟動的時候,節點會與硬編碼在以太坊源碼中的bootnode進行連接,所有的節點加入幾乎都先連接了它。連接上bootnode后,獲取bootnode部分的鄰居節點,然后進行節點發現,獲取更多的活躍的鄰居節點
    • nursery 是在 Table 為空并且數據庫中沒有存儲節點時的初始連接節點(上文中的 6 個節點),通過 bootnode 可以發現新的鄰居
  • tab.seedRand:使用提供的種子值將生成器初始化為確定性狀態
  • loadSeedNodes:加載種子節點;從保留已知節點的數據庫中隨機的抽取30個節點,再加上引導節點列表中的節點,放置入k桶中,如果K桶沒有空間,則假如到替換列表中。

2.測試鄰居節點連通性

首先知道UDP協議是沒有連接的概念的,所以需要不斷的ping 來測試對端節點是否正常,在新建路由表之后,就來到下面的循環,不斷的去做上面的事。

go?tab.loop()

定時運行doRefresh、doRevalidate、copyLiveNodes進行刷新K桶。

以太坊的k桶設置:

const?(
?alpha???????????=?3??//?Kademlia并發參數,?是系統內一個優化參數,控制每次從K桶最多取出節點個數,ethereum取值3
??
?bucketSize??????=?16?//?K桶大小(可容納節點數)
??
?maxReplacements?=?10?//?每桶更換列表的大小
?hashBits??????????=?len(common.Hash{})?*?8?//每個節點ID長度,32*8=256,?32位16進制
?nBuckets??????????=?hashBits?/?15???????//??K桶個數
??)

首先搞清楚這三個定時器運行的時間:

refreshInterval????=?30?*?time.Minute
revalidateInterval?=?10?*?time.Second
copyNodesInterval??=?30?*?time.Second
doRefresh

doRefresh對隨機目標執行查找以保持K桶已滿。如果表為空(初始引導程序或丟棄的有故障),則插入種子節點。

主要以下幾步:

  • 從數據庫加載隨機節點和引導節點。這應該會產生一些以前見過的節點

    tab.loadSeedNodes()
  • 將本地節點ID作為目標節點進行查找最近的鄰居節點

    tab.net.lookupSelf()
    func?(t?*UDPv4)?lookupSelf()?[]*enode.Node?{
    ?return?t.newLookup(t.closeCtx,?encodePubkey(&t.priv.PublicKey)).run()
    }
    func?(t?*UDPv4)?newLookup(ctx?context.Context,?targetKey?encPubkey)?*lookup?{
    ?...
    ??return?t.findnode(n.ID(),?n.addr(),?targetKey)
    ?})
    ?return?it
    }

    向這些節點發起findnode操作查詢離target節點最近的節點列表,將查詢得到的節點進行ping-pong測試,將測試通過的節點落庫保存

    經過這個流程后,節點的K桶就能夠比較均勻地將不同網絡節點更新到本地K桶中。

    unc?(t?*UDPv4)?findnode(toid?enode.ID,?toaddr?*net.UDPAddr,?target?encPubkey)?([]*node,?error)?{
    ?t.ensureBond(toid,?toaddr)
    ?nodes?:=?make([]*node,?0,?bucketSize)
    ?nreceived?:=?0
    ??//?設置回應回調函數,等待類型為neighborsPacket的鄰近節點包,如果類型對,就執行回調請求
    ?rm?:=?t.pending(toid,?toaddr.IP,?p_neighborsV4,?func(r?interface{})?(matched?bool,?requestDone?bool)?{
    ??reply?:=?r.(*neighborsV4)
    ??for?_,?rn?:=?range?reply.Nodes?{
    ???nreceived++
    ??????//?得到一個簡單的node結構
    ???n,?err?:=?t.nodeFromRPC(toaddr,?rn)
    ???if?err?!=?nil?{
    ????t.log.Trace("Invalid?neighbor?node?received",?"ip",?rn.IP,?"addr",?toaddr,?"err",?err)
    ????continue
    ???}
    ???nodes?=?append(nodes,?n)
    ??}
    ??return?true,?nreceived?>=?bucketSize
    ?})
    ??//上面了一個管道事件,下面開始發送真正的findnode報文,然后進行等待了
    ?t.send(toaddr,?toid,?&findnodeV4{
    ??Target:?????target,
    ??Expiration:?uint64(time.Now().Add(expiration).Unix()),
    ?})
    ?return?nodes,?}
  • 查找3個隨機的目標節點

    for?i?:=?0;?i?3;?i++?{
    ??tab.net.lookupRandom()
    ?}
  • doRevalidate

    doRevalidate檢查隨機存儲桶中的最后一個節點是否仍然存在,如果不是,則替換或刪除該節點。

    主要以下幾步:

  • 返回隨機的非空K桶中的最后一個節點

    last,?bi?:=?tab.nodeToRevalidate()
  • 對最后的節點執行Ping操作,然后等待Pong

    remoteSeq,?err?:=?tab.net.ping(unwrapNode(last))
  • 如果節點ping通了的話,將節點移動到最前面

    tab.bumpInBucket(b,?last)
  • 沒有收到回復,選擇一個替換節點,或者如果沒有任何替換節點,則刪除該節點

    tab.replace(b,?last)
  • copyLiveNodes

    copyLiveNodes將表中的節點添加到數據庫,如果節點在表中的時間超過了5分鐘。

    這部分代碼比較簡單,就伸展闡述。

    if?n.livenessChecks?>?0?&&?now.Sub(n.addedAt)?>=?seedMinTableTime?{
    ????tab.db.UpdateNode(unwrapNode(n))
    ???}

    3.檢測各類信息

    go?t.loop()

    loop循環主要監聽以下幾類消息:

    • case
    • p :=
    • r :=

    4. 處理UDP數據包

    go?t.readLoop(cfg.Unhandled)

    主要有以下兩件事:

  • 循環接收其他節點發來的udp消息

    nbytes,?from,?err?:=?t.conn.ReadFromUDP(buf)
  • 處理接收到的UDP消息

    t.handlePacket(from,?buf[:nbytes])
  • 接下來對這兩個函數進行進一步的解析。

    接收UDP消息

    接收UDP消息比較的簡單,就是不斷的從連接中讀取Packet數據,它有以下幾種消息:

    • ping:用于判斷遠程節點是否在線。

    • pong:用于回復ping消息的響應。

    • findnode:查找與給定的目標節點相近的節點。

    • neighbors:用于回復findnode的響應,與給定的目標節點相近的節點列表


    處理UDP消息

    主要做了以下幾件事:

  • 數據包解碼

    packet,?fromKey,?hash,?err?:=?decodeV4(buf)
  • 檢查數據包是否有效,是否可以處理

    ?packet.preverify(t,?from,?fromID,?fromKey)

    在校驗這一塊,涉及不同的消息類型不同的校驗,我們來分別對各種消息進行分析。

    ①:ping

    ②:pong

    ③:findNodes

    ④:neighbors

    • 校驗消息是否過期
    • 用于回復findnode的響應,校驗回復是否正確
    • 校驗消息是否過期
    • 校驗節點是否是最近的節點
    • 校驗消息是否過期
    • 校驗回復是否正確
    • 校驗消息是否過期
    • 校驗公鑰是否有效

    處理packet數據

    packet.handle(t,?from,?fromID,?hash)

    相同的,也會有4種消息,但是我們這邊重點講處理findNodes的消息:

    func?(req?*findnodeV4)?handle(t?*UDPv4,?from?*net.UDPAddr,?fromID?enode.ID,?mac?[]byte)?{
    ...
    }

    我們這里就稍微介紹下如何處理findnode的消息:

    func?(req?*findnodeV4)?handle(t?*UDPv4,?from?*net.UDPAddr,?fromID?enode.ID,?mac?[]byte)?{
    ?//?確定最近的節點
    ?target?:=?enode.ID(crypto.Keccak256Hash(req.Target[:]))
    ?t.tab.mutex.Lock()
    ?//最接近的返回表中最接近給定id的n個節點
    ?closest?:=?t.tab.closest(target,?bucketSize,?true).entries
    ?t.tab.mutex.Unlock()
    ?//?以每個數據包最多maxNeighbors的塊的形式發送鄰居,以保持在數據包大小限制以下。
    ?p?:=?neighborsV4{Expiration:?uint64(time.Now().Add(expiration).Unix())}
    ?var?sent?bool
    ?for?_,?n?:=?range?closest?{?//掃描這些最近的節點列表,然后一個包一個包的發送給對方
    ??if?netutil.CheckRelayIP(from.IP,?n.IP())?==?nil?{
    ???p.Nodes?=?append(p.Nodes,?nodeToRPC(n))
    ??}
    ??if?len(p.Nodes)?==?maxNeighbors?{
    ???t.send(from,?fromID,?&p)//給對方發送?neighborsPacket?包,里面包含節點列表
    ???p.Nodes?=?p.Nodes[:0]
    ???sent?=?true
    ??}
    ?}
    ?if?len(p.Nodes)?>?0?||?!sent?{
    ??t.send(from,?fromID,?&p)
    ?}
    }

    首先先確定最近的節點,再一個包一個包的發給對方,并校驗節點的IP,最后把有效的節點發送給請求方。


    涉及的結構體:

    UDP

    • conn :接口,包括了從UDP中讀取和寫入,關閉UDP連接以及獲取本地地址。
    • netrestrict:IP網絡列表
    • localNode:本地節點
    • tab:路由表

    Table

    • buckets:所有節點都加到這個里面,按照距離

    • nursery:啟動節點

    • rand:隨機來源

    • ips:跟蹤IP,確保IP中最多N個屬于同一網絡范圍

    • net: UDP 傳輸的接口

      • 返回本地節點
      • 將enrRequest發送到給定的節點并等待響應
      • findnode向給定節點發送一個findnode請求,并等待該節點最多發送了k個鄰居
      • 返回查找最近的節點
      • 將ping消息發送到給定的節點,然后等待答復

    以下是table的結構圖:

    image-20201112104254003

    思維導圖

    思維導圖獲取地址

    image-20201123211034861

    關于我

    微信號:mindcarver ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 加我探討問題或商務合作

    個人博客網站:https://mindcarver.cn/ ? ? ? ? ? ? ? ?最新技術博客

    github開源項目:https://github.com/blockchainGuide ? ? ? ? ? ?持續會更新區塊鏈最新技術

    QQ群:1057497867

    - END -

    總結

    以上是生活随笔為你收集整理的3 v4 中心节点固定_死磕以太坊源码分析之p2p节点发现的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。