1. 程式人生 > >【我的區塊鏈之路】- 理解傳統Kademlia和以太坊Kademlia網路

【我的區塊鏈之路】- 理解傳統Kademlia和以太坊Kademlia網路

本文章參考自:

大家好,今天我們來說一說以太坊的Kad網路;在此之前我們先來聊一聊少部分P2P方面的知識,P2P 主要存在四種不同的網路模型,也代表著 P2P 技術的四個發展階段:集中式純分散式混合式結構化模型

  • 集中式:即存在一箇中心節點儲存了其他所有節點的索引資訊,索引資訊一般包括節點 IP 地址、埠、節點資源等。

             優點:就是結構簡單、實現容易。

             缺點:由於中心節點需要儲存所有節點的路由資訊,當節點規模擴充套件時,就很容易出現效能瓶頸;而且也存在單點故障問題。

  • 純分散式:移除了中心節點,在 P2P 節點之間建立隨機網路,就是在一個新加入節點和 P2P 網路中的某個節點間隨機建立連線通道,從而形成一個隨機拓撲結構。          

                     新節點加入該網路的實現方法也有很多種,最簡單的就是隨機選擇一個已經存在的節點並建立鄰居關係。像比特幣的話,則是使用 DNS 的方式來查詢其他節點,DNS 一般是硬編碼到程式碼裡的,這些 DNS 伺服器就會提供比特幣節點的 IP 地址列表,從而新節點就可以找到其他節點建立連線通道新節點與鄰居節點建立連線後,還需要進行全網廣播,讓整個網路知道該節點的存在

                   全網廣播的方式:該節點首先向鄰居節點廣播,鄰居節點收到廣播訊息後,再繼續向自己的鄰居節點廣播,以此類推,從而廣播到整個網路。這種廣播方法也稱為泛洪機制純分散式結構不存在集中式結構的單點效能瓶頸問題

單點故障問題,具有較好的可擴充套件性,但泛洪機制引入了新的問題,主要是可控性差的問題:

                  【 一是】:容易形成泛洪迴圈,比如節點 A 發出的訊息經過節點 B 到 節點 C,節點 C 再廣播到節點 A,這就形成 了一個迴圈;

                  【二是】:響應訊息風暴問題,如果節點 A 想請求的資源被很多節點所擁有,那麼在很短時間內,會出現大量節點同時向節點 A 傳送響應訊息,這就可能會讓節點 A 瞬間癱瘓。

  • 混合式:混合了集中式和分散式結構,如下圖所示,網路中存在多個超級節點組成分散式網路,而每個超級節點則有多個普通節點與它組成區域性的集中式網路。一個新的普通節點加入,則先選擇一個超級節點進行通訊,該超級節點再推送
    其他超級節點列表給新加入節點加入節點再根據列表中的超級節點狀態決定選擇哪個具體的超級節點作為父節點。這種結構的泛洪廣播就只是發生在超級節點之間,就可以避免大規模泛洪存在的問題。在實際應用中,混合式結構是相對靈活並且比較有效的組網架構,實現難度也相對較小,因此目前較多系統基於混合式結構進行開發實現。其實,比特幣網路如今也是這種結構。

  • 結構化 P2P 網路它也是一種分散式網路結構,但與純分散式結構不同。純分散式網路就是一個隨機網路,而結構化網路則將所有節點按照某種結構進行有序組織,比如形成一個環狀網路或樹狀網路。而結構化網路的具體實現上,普遍都是基於 DHT(Distributed Hash Table,分散式雜湊表) 演算法思想。DHT 只是提出一種網路模型,並不涉及具體實現,主要想解決如何在分散式環境下快速而又準確地路由、定位資料的問題。具體的實現方案有 Chord、Pastry、CAN、Kademlia 等演算法,其中 Kademlia 也是以太坊網路的實現演算法,很多常用的 P2P 應用如 BitTorrent、電驢等也是使用 Kademlia。

 P2P 網路中,可以抽象出兩種空間:資源空間節點空間資源空間就是所有節點儲存的資源集合,節點空間就是所有節點的集合。對所有資源和節點分別進行編號,如把資源名稱或內容用 Hash 函式變成一個數值(這也是 DHT 常用的一種方法),這樣,每個資源就有對應的一個 ID,每個節點也有一個 ID,資源 ID 和節點 ID 之間建立起一種對映關係,比如,將資源 n數值 的所有索引資訊存放到 n數值節點上,那要搜尋資源 n 時,只要找到節點 n 即可,從而就可以避免泛洪廣播,能更快速而又準確地路由和定位資料。【當然,在實際應用中,資源 ID 和節點 ID 之間是無法做到一一對應的(因為無法確保數值為100的資源ID可以在全網中找到數值也為100的節點ID),通常的解決方案是,但因為 ID 都是數字,就存在大小關係或偏序關係等來決定數值為 100 的資源ID應該存放到那些離數值 100 最近的k個節點上,基於這些關係就能建立兩者的對映關係】。這就是 DHT 的核心思想。DHT 演算法在資源編號和節點編號上就是使用了分散式雜湊表,使得資源空間和節點空間的編號有唯一性、均勻分散式等較好的性質,能夠適合結構化分散式網路的要求。

好了,廢話逼逼了太多了(咳咳,應該是抄了人家的太多了),現在我們來說一說傳統kad網路和以太坊的kad網路,【因為他咩的,以太坊是用來做節點發現的,而類似於Bittorrent之類的除了可能要做節點發現外還需要做資源發現的】

來,我們來看一看 Kad是個什麼樣的東東:

  1. 在kad網路中每一個節點都必須有自己的節點ID 【160 bit 的 ID 值作為標誌符,Key 也是一個 160 bit 的標誌符,每一個加入 Kad 網路的計算機都會在 160 bit 的 key 空間被分配一個節點 ID(node ID)值(可以認為 ID 是隨機產生的), <key,value> 對的資料就存放在 ID 值“最”接近 key 值的節點上】
  2. 在kad網路中,所有資源(條目)都是以<key, value>的形式儲存在節點上的,其中key和節點的ID一樣也是 160 bit,這顯然是有目的的,因為希望“儘量”的把數值為 n 的key 對應的 <key, value> 資源存放到 同樣數值為 n 的節點ID上,我們可以將滿足(ID==key)這一條件的節點命名為目標節點N。這樣紙 ,一個查詢條目的問題便被簡單地轉化 成為了一個查詢ID等於Key值的節點的問題(當然這是理想化的情況下,現實中,不一定存在 和key的數字一樣的節點ID,所以我們說是 “儘量”把<key, value> 儲存在和數值 n 相近的K個節點上,這裡的k是個經驗取值)。
  3. 在kad網路中我們是通過 兩個節點ID的異或運算得出來的值來算節點間的距離的,而不是依靠物理距離、路由器跳數來衡量的(因為這樣需要做很多的計算才可以知道對方的遠近,效率相當的慢),而通過異或這類的計算可以快速的精準的定位到對方節點

節點的距離:

異或操作也是單向性的。對於任意給定的節點 x 和距離 Δ≥0 ,總會存在一個精確的節點 y ,使得 d(x,y)=Δ。

單向性也確保了對於同一個 key 值的所有查詢都會逐步收斂到同一個路徑上,而不管查詢的起始節點位置如何。這樣,只要沿著查詢路徑上的節點都快取這個 <key,value> 對,就可以減輕存放熱門 key 值節點的壓力,同時也能夠加快查詢響應速度。【這句話的理解是:把<key, value> 均勻的放置到 和 key 的數值相近的 某一部分節點上,這樣紙的話我們在查詢key時,總能命中這些節點上,這樣紙減輕了把 <key, value> 放置到 某單個節點上所造成的訪問壓力,及能夠加快查詢到 value 的速度】;所以,在Kad網路中ID越靠近key的節點區域,該條目儲存的份數就越多,儲存得也越集中;事實上,為了實現較短的查詢響應 延遲,在條目查詢的過程中,任一條目可被cache到任意節點之上;同時為了防止過度cache、保證資訊足夠新鮮,必須考慮條目在節點上儲存的時效性: 越接近目標結點N,該條目儲存的時間將越長,反之,其超時時間就越短;儲存在目標節點之上的條目最多能夠被保留24小時,如果在此期間該條目被其釋出源重 新發布(每個節點都會把自身的資源每個固定時間重新發布給臨近節點)的話,其儲存時間還可以進一步延長。

節點的狀態:

在 Kad 網路中,所有節點都被當作一顆二叉樹的葉子【這個資訊是以各個節點本地的k-bucket所記錄的,可能每個節點的k-bucket所記錄的內容不盡一樣,但是大家所維護的k-bucket 組成的整網的節點狀態既是這樣】,並且每一個節點的位置都由其 ID 值的最短字首唯一的確定。【這句話我們先記下,不明白也沒關係,後面的內容會讓我們明白】,對於任意一個節點,都可以把這顆二叉樹分解為一系列連續的,不包含自己的子樹。最高層的子樹,由整顆樹不包含自己的樹的另一半組成;下一層子樹由剩下部分不包含自己的一半組成;依此類推,直到分割完整顆樹。比如節點0011如何進行子樹的劃分,如圖:

/media/note/2010/03/21/kademlia/fig1.png

其中,虛線包含的部分就是各子樹,由上到下各層的字首分別為 1010000010 。Kad 協議確保每個節點知道其各子樹的至少一個節點。在這個前提下,每個節點都可以通過ID值來找到任何一個節點。這個路由的過程是通過所謂的 XOR(異或)距離得到的。

下面演示了節點0011如何通過連續查詢來找到節點1110的。節點0011通過在逐步底層的子樹間不斷學習並查詢最佳節點,獲得了越來越接近的節點,最終收斂到目標節點上:

/media/note/2010/03/21/kademlia/fig2.png

由於網上很多說法都是互抄的,其實上光看這個圖還是不能明白,如何的收斂到目標節點:是這樣紙的,首先,需要知道請求發起節點 0011 和 目標節點 1110 的 距離 n ,然後從 請求節點 0011 的本地 k-bucket 的 n 桶中返回 k個 節點資訊,然後 0011 再給這幾個節點廣播,如果 這幾個節點中剛好有距離是 n 的節點,那麼就會用 0011 ,否則,就從這些節點的bendi k-bucket 中繼續上述操作,直到找到 1110 節點,所以這個過程是個遞迴的過程。

K桶:

Kad 的路由表是通過一些稱之為 K 桶的表格構造起來的,傳統的kad的k-bucket 的數目取值為  0≤i≤160 ;每個節點都儲存有一些和自己距離範圍在區間 [2i,2i+1) 內的一些節點資訊,這些資訊由一些 (IP address,UDP port,Node ID) 資料列表構成(Kad 網路是靠 UDP 協議交換資訊的)。每一個這樣的列表都稱之為一個 K 桶,每一個list(k-桶)中最多存放k個對端節點資訊;【注意】此處的k與上文所提到的條目被存放的最近節點數含義是一致的;每一個 list中的對端節點資訊均按訪問時間排序,最早訪問的在list頭部,而最近新訪問的則放在list的尾部

如圖:

表格形式如:

不過通常來說當 i 值很小時,K 桶通常是空的(也就是說沒有足夠多的節點,比如當 i = 0 時,就最多可能只有1項);而當 i 值很大時,其對應 K 桶的項數又很可能會超過 k 個(當然,覆蓋距離範圍越廣,存在較多節點的可能性也就越大),這裡 k 是為平衡系統性能和網路負載而設定的一個常數,是個經驗取值,但必須是偶數,比如 k = 20。在 BitTorrent 的實現中,取值為 k = 8。

經過證明,對於一個有 N 個節點的 Kad 網路,最多隻需要經過 logN 步查詢,就可以準確定位到目標節點。

K-Bucket的更新機制:

當節點 x 收到一個 PRC 訊息時,傳送者 y 的 IP 地址就被用來更新對應的 K 桶,具體步驟如下:

  1. 計算自己和傳送者的距離: d(x,y)=x⊕y ,注意:x 和 y 是 ID 值,不是 IP 地址
  2. 通過距離 d 選擇對應的 K 桶進行更新操作
  3. 如果 y 的 IP 地址已經存在於這個 K 桶中,則把對應項移到該該 K 桶的尾部
  4. 如果 y 的 IP 地址沒有記錄在該 K 桶中
    1. 如果該 K 桶的記錄項小於 k 個,則直接把 y 的 (IP address, UDP port, Node ID) 資訊插入佇列尾部
    2. 如果該 K 桶的記錄項大於 k 個,則選擇頭部的記錄項(假如是節點 z)進行 RPC_PING 操作
      1. 如果 z 沒有響應,則從 K 桶中移除 z 的資訊,並把 y 的資訊插入佇列尾部
      2. 如果 z 有響應,則把 z 的資訊移到佇列尾部,同時忽略 y 的資訊

K 桶的更新機制非常高效的實現了一種把最近看到的節點更新的策略,除非線上節點一直未從 K 桶中移出過。也就是說線上時間長的節點具有較高的可能性繼續保留在 K 桶列表中;這是有依據得的:線上時間長一點的節點更值得我們信任,因為它在下一個小時以內保持線上的可能性將比我們最新訪問的節點更大 這對應 Kad 網路的穩定性和減少網路維護成本(不需要頻繁構建節點的路由表)帶來很大好處。

【好處】這種機制的另一個好處是能在一定程度上防禦 DOS 攻擊,因為只有當老節點失效後,Kad 才會更新 K 桶的資訊,這就避免了通過新節點的加入來泛洪路由資訊

為了防止 K 桶老化,所有在一定時間之內無更新操作的 K 桶,都會分別從自己的 K 桶中隨機選擇一些節點執行 RPC_PING 操作

上述這些 K 桶機制使 Kad 緩和了流量瓶頸(所有節點不會同時進行大量的更新操作),同時也能對節點的失效進行迅速響應。

Kademlia的協議操作型別:

Kademlia 協議包括四種遠端 RPC 操作:PINGSTOREFIND_NODEFIND_VALUE

  1. PING 操作的作用是探測一個節點,用以判斷其是否仍然線上。

  2. STORE 操作的作用是通知一個節點儲存一個 <key,value> 對,以便以後查詢需要。

  3. FIND_NODE 操作使用一個 160 bit 的 ID 作為引數。本操作的接受者返回它所知道的更接近目標 ID 的 K 個節點的 (IP address, UDP port, Node ID) 資訊。

    這些節點的資訊可以是從一個單獨的 K 桶獲得,也可以從多個 K 桶獲得(如果最接近目標 ID 的 K 桶未滿)。不管是哪種情況,接受者都將返回 K 個節點的資訊給操作發起者。但如果接受者所有 K 桶的節點資訊加起來也沒有 K 個,則它會返回全部節點的資訊給發起者。

  4. FIND_VALUE 操作和 FIND_NODE 操作類似,不同的是它只需要返回一個節點的 (IP address, UDP port, Node ID) 資訊。如果本操作的接受者收到同一個 key 的 STORE 操作,則會直接返回儲存的 value 值。

【注意】為了防止偽造地址,在所有 RPC 操作中,接受者都需要響應一個隨機的 160 bit 的 ID 值 【及接受者自己的ID】。另外,為了確信傳送者的網路地址,PING 操作還可以附帶在接受者的 RPC 回覆資訊中

路由查詢機制:

Kad 技術的最大特點之一就是能夠提供快速的節點查詢機制,並且還可以通過引數進行查詢速度的調節

假如節點 x 要查詢 ID 值為 t 的節點,Kad 按照如下遞迴操作步驟進行路由查詢:

  1. 計算到 t 的距離: d(x,y)=x⊕y
  2. 從 x 的第 [logd] 個 K 桶中取出 α 個節點的資訊(“[”“]”是取整符號),同時想這些節點進行 FIND_NODE 操作。如果這個 K 桶中的資訊少於 α 個,則從附近多個桶中選擇距離最接近 d 的總共 α 個節點。
  3. 對接受到查詢操作的每個節點,如果發現自己就是 t,則回答自己是最接近 t 的;否則測量自己和 t 的距離,並從自己對應的 K 桶中選擇 α 個節點的資訊給 x。
  4. X 對新接受到的每個節點並從中中挑選出若干沒有請求過的,再次執行 FIND_NODE 操作,此過程不斷重複執行,直到每一個分支都有節點響應自己是最接近 t 的。【注意】在查詢過程中,沒有及時響應的節點將立即被排除;因為查詢者必須保證最終獲得的k個最近節點都是活動的。
  5. 通過上述查詢操作,x 得到了 k 個最接近 t 的節點資訊。

【注意】:這裡用“最接近”這個說法,是因為 ID 值為 t 的節點不一定存在網路中,也就是說 t 沒有分配給任何一臺電腦。所以,Kad之所以沒有把節點查詢過程嚴格地定義成為僅僅只查詢單個目標節點的過程,這主要是因為Kad網路並沒有對節點的上線時間作出 任何前提假設,因此在多數情況下我們並不能肯定需要查詢的目標節點一定線上或存在

這裡 α 也是為系統優化而設立的一個引數,就像 K 一樣。在 BitTorrent 實現中,取值為 α=3 。

當 α=1 時,查詢過程就類似於 Chord 的逐跳查詢過程,如圖:

整個路由查詢過程是遞迴操作的,其過程可用數學公式表示為:

n0=x (即查詢操作的發起者)

N1=find −noden0(t)

N2=find −noden1(t)

... ...

Nl=find −nodenl−1(t)

這個遞迴過程一直持續到 Nl=t ,或者 Nl 的路由表中沒有任何關於 t 的資訊,即查詢失敗。

由於每次查詢都能從更接近 t 的 K 桶中獲取資訊,這樣的機制保證了每一次遞迴操作都能夠至少獲得距離減半(或距離減少 1 bit)的效果,從而保證整個查詢過程的收斂速度為 O(logN) ,這裡 N 為網路全部節點的數量。【因為:對於一個有 N 個節點的 Kad 網路,最多隻需要經過 logN 步查詢

<key, Value>的查詢:

當節點 x 要查詢 <key,value> 對時,和查詢節點的操作類似,x 選擇 k 個 ID 值最接近 key 值的節點,執行 FIND_VALUE 操作,並對每一個返回的新節點重複執行 FIND_VALUE 操作,直到某個節點返回 value 值。

一旦 FIND_VALUE 操作成功執行,則 <key,value> 對資料會快取在沒有返回 value 值的最接近的節點上。這樣下一次查詢相同的 key 時就會更加快速的得到結果。通過這樣的方式,熱門 <key,value> 對資料的快取範圍就逐步擴大,使系統具有極佳的響應速度。cache的超時時間為節點-key之間的距離越遠則超時時間越短。(【因為cache 為存活24小時,但是目標節點上的內容時每1小時向其他最近節點重新發布<key, value>使得資料的超時時間得以重新整理,而遠離目標節點的節點的資料存活時間當然就可能不會被重新發布到,所以也就是資料快取的超時時間和節點的距離成反比咯)

資料的存放:

存放 <key,value> 對資料的過程為:

  1. 發起者首先定位 k 個 ID 值最接近 key 的節點
  2. 發起者對這 k 個節點發起 STORE 操作
  3. 執行 STORE 操作的 k 個節點每小時重發布自己所有的 <key,value> 對資料
  4. 為了限制失效資訊,所有 <key,value> 對資料在初始釋出24小時後過期

另外,為了保證資料釋出、搜尋的一致性,規定在任何時候,當節點 w 發現新節點 u 比 w 上的某些 <key,value> 對資料(即 對Key更接近)更接近,則 w 把這些 <key,value> 對資料複製到 u 上,但是並不會從 w 上刪除(超時時間到了,且遠離了key則後續會自動被刪除)

節點加入和離開:

如果節點 u 要想加入 Kad 網路,它必須要和一個已經在 Kad 網路的節點,比如 w,取得聯絡。

u 首先把 w 插入自己適當的 K 桶中,然後對自己的節點 ID 執行一次 FIND_NODE 操作,然後根據接收到的資訊更新自己的 K 桶內容。通過對自己鄰近節點由近及遠的逐步查詢,u 完成了仍然是空的 K 桶資訊的構建,同時也把自己的資訊釋出到其他節點的 K 桶中。

所以, 節點 u 必須做三件事,【其一】不管通過何種途徑,獲知一個已經加入Kad網路的節點資訊(我們可以稱之為節點 w),並將其加入自己的k-buckets;【其二】向該節點發起一次針對自己ID的節點查詢請求,從而通過節點I獲取一系列與自己距離鄰近的其他節點的信 息;【最後】重新整理所有的k-bucket,保證自己所獲得的節點資訊全部都是新鮮的

k-bucket和二叉樹:

還記得在 節點狀態 那一小節中我們講了,在 Kad 網路中,所有節點都被當作一顆二叉樹的葉子,並且每一個節點的位置都由其 ID 值的最短字首唯一的確定。那麼我們下面就講一講這個是怎麼個設計:

在 Kad 網路中,每個節點的路由表都表示為一顆二叉樹,葉子節點為 K 桶 (因為k桶裡面裝的也是節點),K 桶存放的是有相同 ID 字首的節點資訊,而這個字首就是該 K 桶在二叉樹中的位置字首值確定了節點應該放到 什麼距離的k-桶中】。這樣,每個 K 桶都覆蓋了 ID 空間的一部分,全部 K 桶的資訊加起來就覆蓋了整個 160 bit 的 ID 空間,而且沒有重疊。

如:0000111 和 0000001是屬於同一個k-桶的兩個節點,其中對於他們的字首可能是 0000 ; 而對於 1111111 和 1011111 來說 他們所屬於字首為 1 的這個 k-桶,總之當前節點 N 來說,它本地的全部 k-桶 組成了 節點N 所感知的全網節點的拓撲,當然這肯定只是真實的全網節點拓撲的一部分而已,但是對於節點N來說卻是他所認為的全網拓撲,這樣紙的各個節點本地的k-桶所組成的各個節點所認為的全網拓撲組成了一張邏輯上的真實全網拓撲。

下面是對某節點本地的k-桶進行二叉樹的分割原理,以節點 u 為例,其路由表的生成過程為:

  1. 最初,u 的路由表為一個單個的 K 桶,覆蓋了整個 160 bit ID 空間
  2. 學習到新的節點資訊後,則 u 會嘗試把新節點的資訊,根據其字首值插入到對應的 K 桶中:
    1. 如果該 K 桶沒有滿,則新節點直接插入到這個 K 桶中;
    2. 如果該 K 桶已經滿了,
      1. 如果該 K 桶覆蓋範圍包含了節點 u 的 ID,則把該 K 桶分裂為兩個大小相同的新 K 桶,並對原 K 桶內的節點資訊按照新的 K 桶字首值進行重新分配【這一步就是為了分出新的k-桶好讓新加入的節點放置到k-桶中】
      2. 如果該 K 桶覆蓋範圍沒有包節點 u 的 ID,則直接丟棄該新節點資訊 【沒有包含u說明,當前k-桶不可以再分裂了,由於k-已經滿了,所以新節點就被拋棄】

上述過程不斷重複,最終會達到距離近的節點的資訊多,距離遠的節點的資訊少的結果【因為隨著新節點的加入需要對原有某包含了當前節點u的k-桶進行拆分,可知,圖中越來越多的節點加入k-桶中,隨著k-桶的拆分,我們知道離目標節點越近的二叉樹(k-

桶)越多】,保證了路由查詢過程能快速收斂。具體原理為圖中所示:

當 K 桶 010 滿了之後,由於其覆蓋範圍包含了節點 0100 的 ID,故該 K 桶分裂為兩個新的 K 桶:0101 和 0100,原 K 桶 010 的資訊會根據其其字首值重新分佈到這兩個新的 K 桶中。注意,這裡並沒有使用 160 bit 的 ID 值表示法,只是為了方便原理的演示,實際 Kad 網路中的 ID 值都是 160 bit 的。

節點離開:

節點離開 Kad 網路不需要釋出任何資訊,Kademlia 協議的目標之一就是能夠彈性工作在任意節點隨時失效的情況下。為此,Kad 要求每個節點必須週期性的釋出全部自己存放的 <key,value> 對資料並把這些資料快取在自己的 k 個最近鄰居處,這樣存放在失效節點的資料會很快被更新到其他新節點上。所以有節點離開了,那麼就離開了,而且節點中的k-桶重新整理機制也能保證會把已經不線上的節點資訊從自己本地k-桶中移除。

以太坊的Kad網路:

我們知道常規的kad網路節點的本地維護者 最多 160 個k-桶的資訊,每個桶中 最多有K個臨近節點的資訊 (K 為經驗取值, K 取值的原則是 不管什麼時刻都能確保這K個節點中有 一個節點在網路中是存活的,通常取值 k == 20 為最好);以太坊的節點本地有 256 個k-bucket,每個裡面最多 16 個臨近節點的資訊;

而且傳統的kad網路除了需要節點發現外還需要資源發現;而以太坊中主要是主要節點發現,所以kad協議的操作型別和傳統的不一樣:

  1. Ping:用於探測一個節點是否線上
  2. Pong:用於響應 Ping 命令
  3. FindNode:用於查詢與 Target 節點異或距離最近的其他節點
  4. Neighbours:用於響應 FindNode 命令,會返回一或多個節點

節點的發現:

鄰居節點發現流程說明:

  1. 系統第一次啟動隨機生成本機節點NodeId,記為LocalId,生成後將固定不變,本地節點記為local-eth。

  2. 系統讀取公共節點資訊,ping-pong握手完成後,將其寫入K桶。

  3. 系統每隔7200ms重新整理一次K桶。

重新整理K桶流程如下:

a.      隨機生成目標節點Id,記為TargetId,從1開始記錄發現次數和重新整理時間。

b.      計算TargetId與LocalId的距離,記為Dlt,然後從該距離的桶中取出所有kadId。

c.      K桶中節點的NodeId記為KadId,計算KadId與TargetId的距離,記為Dkt

d.      找出K桶中Dlt 【這裡的Dlt 是 KadId 和 LocalId的距離,因為任何和LocalId的距離都叫做Dlt】大於Dkt的節點,記為k桶節點,向k桶節點發送FindNODE命令,FindNODE命令包含TargetId

e.      K桶節點收到FindNODE命令後,同樣執行b-d的過程【這時候,上一個KadId成為了新的LocalId了】,將從K桶中找到的節點使用Neighbours命令發回給本機節點。

f.       本機節點收到Neighbours後,將收到的節點寫入到K桶中。

g.      搜尋次數不超過8次,重新整理時間不超過600ms,則返回到b步驟迴圈執行。

鄰居節點網路拓撲及重新整理機制:

1 TargetId為隨機生成的虛擬節點ID。

2 以太坊Kad網路與傳統Kad網路的區別:

  1. 以太坊節點在發現鄰居節點的8次迴圈中,所查詢的節點均在距離上向隨機生成的TargetId收斂。

  2. 傳統Kad網路發現節點時,在距離上向目標節點本身收斂。

可能上面這種說法不是很明白,來,我們下面用另外一種說法來說一說,既然以太坊刷桶,是基於節點發現這個角度出發的,那麼我們應該發現的是所有和目標節點相鄰的節點集,基於上面的說法和下面這個圖,我們應該會明白:

即,每一次都用新的LocalId和返回的桶裡面的KadId分別和targetId的距離做對比,符合:Dlt > Dkt 的kadId,然後繼續傳送 FindNODE命令。【假想: 如果用 Dlt < Dkt 的呢?自己畫個圖就知道了,這樣紙獲取的KadId 永遠距離 targetId越來越遠了

至此,我們先告一段落,放心,後面發現有不足的我都會及時補充~