1. 程式人生 > >以太坊p2p網路(二):以太坊P2P節點發現演算法原理剖析

以太坊p2p網路(二):以太坊P2P節點發現演算法原理剖析

以太坊底層分散式網路即P2P網路,使用了經典的Kademlia網路,簡稱kad。

一、核心資料結構

NodeTable類負責以太坊的節點發現,NodeTable採用kademlia(KAD)演算法進行節點發現

  • NodeTable維護一個網路節點列表,此列表為當前可用節點,供上層使用
  • 由於NodeID經過sha3生成出的Hash為256位。列表有256-1=255項,其中-1是因為刨除了當前節點(本機)
  • 列表的每一項位一個節點桶(NodeBucket),每個桶中最多放16個節點
  • 列表的第i項代表據當前節點(本機)距離為i+1的網路節點集合

二、白話Kademlia演算法

Kademlia演算法是一種分散式儲存及路由的演算法。什麼是分散式儲存?試想一下,一所1000人的學校,現在學校突然決定拆掉圖書館(不設立中心化的伺服器),將圖書館裡所有的書都分發到每位學生手上(所有的檔案分散儲存在各個節點上)。即是所有的學生,共同組成了一個分散式的圖書館。

在這種場景下,有幾個關鍵的問題需要回答。

(1) 關鍵問題

  1. 每個同學手上都分配哪些書。即如何分配儲存內容到各個節點,新增/刪除內容如何處理。
  2. 當你需要找到一本書,譬如《分散式演算法》的時候,如何知道哪位同學手上有《分散式演算法》(對1000個人挨個問一遍,“你有沒有《分散式演算法》?”,顯然是個不經濟的做法),又如何聯絡上這位同學。即一個節點如果想獲取某個特定的檔案,如何找到儲存檔案的節點/地址/路徑。

接下來,讓我們來看看Kademlia演算法如何巧妙地解決這些問題。

(2)節點的要素

首先我們來看看每個同學(節點)都有哪些屬性:

  • 學號(Node ID,2進位制,160位)
  • 手機號碼(節點的IP地址及埠)

每個同學會維護以下內容:

  • 從圖書館分發下來的書本(被分配到需要儲存的內容),每本書當然都有書名和書本內容(內容以<key, value>對的形式儲存,可以理解為檔名和檔案內容);
  • 一個通訊錄,包含一小部分其他同學的學號和手機號,通訊錄按學號分層(一個路由表,稱為“k-bucket”,按Node ID分層,記錄有限個數的其他節點的ID和IP地址及埠)。

根據上面那個類比,可以看看這個表格: (Hash的概念解釋,可參見百度百科-雜湊演算法)

關於為什麼不是每個同學都有全量通訊錄(每個節點都維護全量路由資訊):

  1. 分散式系統中節點的進入和退出是相當頻繁的,每次有變動時都全網廣播通訊錄更新,通訊量會很大;
  2. 一旦任意一個同學被壞人綁架了(節點被黑客攻破),則壞人馬上就擁有了所有人的手機號碼,這並不安全。

(3) 檔案的儲存及查詢

原來收藏在圖書館裡,按索引號碼得整整齊齊的書,以一種什麼樣的方式分發到同學們手裡呢?大致的原則,包括: (1)書本能夠比較均衡地分佈在同學們的手裡,不會出現部分同學手裡書特別多、而大部分同學連一本書都沒有的情況; (2)同學想找一本特定的書的時候,能夠一種相對簡單的索引方式找到這本書。

Kademlia作了下面這種安排: 假設《分散式演算法》這本書的書名的hash值是 00010000,那麼這本書就會被要求存在學號為00010000的同學手上。(這要求hash演算法的值域與node ID的值域一致。 但還得考慮到會有同學缺勤。萬一00010000今天沒來上學(節點沒有上線或徹底退出網路),那《分散式演算法》這本書豈不是誰都拿不到了?那演算法要求這本書不能只存在一個同學手上,而是被要求同時儲存在學號最接近00010000的k位同學手上,即00010001、00010010、00010011…等同學手上都會有這本書。

同樣地,當你需要找《分散式演算法》這本書時,將書名hash一下,得到 00010000,這個便是索書號,你就知道該找哪(幾)位同學了。剩下的問題,就是找到這(幾)位同學的手機號。

(4) 節點的異或距離

由於你手上只有一部分同學的通訊錄,你很可能並沒有00010000的手機號(IP地址)。那如何聯絡上目標同學呢?

一個可行的思路就是在你的通訊錄裡找到一位擁有目標同學的聯絡方式的同學。前面提到,每位同學手上的通訊錄都是按距離分層的。演算法的設計是,如果一個同學離你越近,你手上的通訊錄裡存有ta的手機號碼的概率越大。而演算法的核心的思路就可以是:當你知道目標同學Z與你之間的距離,你可以在你的通訊錄上先找到一個你認為與同學Z最相近的同學B,請同學B再進一步去查詢同學Z的手機號。

上文提到的距離,是學號(Node ID)之間的異或距離(XOR distance)。異或是針對yes/no或者二進位制的運算。

異或的運演算法則為:0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0(同為0,異為1)

舉2個例子: 01010000與01010010距離(即是2個ID的異或值)為00000010(換算為十進位制即為2); 01000000與00000001距離為01000001(換算為十進位制即為26+1,即65); 如此類推。

那通訊錄是如何按距離分層呢?下面的示例會告訴你,按異或距離分層,基本上可以理解為按位數分層。設想以下情景: 以0000110為基礎節點,如果一個節點的ID,前面所有位數都與它相同,只有最後1位不同,這樣的節點只有1個——0000111,與基礎節點的異或值為0000001,即距離為1;對於0000110而言,這樣的節點歸為“k-bucket 1”; 如果一個節點的ID,前面所有位數相同,從倒數第2位開始不同,這樣的節點只有2個:0000101、0000100,與基礎節點的異或值為0000011和0000010,即距離範圍為3和2;對於0000110而言,這樣的節點歸為“k-bucket 2”; …… 如果一個節點的ID,前面所有位數相同,從倒數第n位開始不同,這樣的節點只有2(i-1)個,與基礎節點的距離範圍為[2(i-1), 2i);對於0000110而言,這樣的節點歸為“k-bucket i”;

對上面描述的另一種理解方式:如果將整個網路的節點梳理為一個按節點ID排列的二叉樹,樹最末端的每個葉子便是一個節點,則下圖就比較直觀的展現出,節點之間的距離的關係。

回到我們的類比。每個同學只維護一部分的通訊錄,這個通訊錄按照距離分層(可以理解為按學號與自己的學號從第幾位開始不同而分層),即k-bucket1, k-bucket 2, k-bucket 3…雖然每個k-bucket中實際存在的同學人數逐漸增多,但每個同學在它自己的每個k-bucket中只記錄k位同學的手機號(k個節點的地址與埠,這裡的k是一個可調節的常量引數)。

由於學號(節點的ID)有160位,所以每個同學的通訊錄中共分160層(節點共有160個k-bucket)。整個網路最多可以容納2^160個同學(節點),但是每個同學(節點)最多隻維護160 * k 行通訊錄(其他節點的地址與埠)。

(5) 節點定位

我們現在來闡述一個完整的索書流程。 A同學(學號00000110)想找《分散式演算法》,A首先需要計算書名的雜湊值,hash(《分散式演算法》) = 00010000。那麼A就知道ta需要找到00010000號同學(命名為Z同學)或學號與Z鄰近的同學。 Z的學號00010000與自己的異或距離為 00010110,距離範圍在[24, 25),所以這個Z同學可能在k-bucket 5中(或者說,Z同學的學號與A同學的學號從第5位開始不同,所以Z同學可能在k-bucket 5中)。 然後A同學看看自己的k-bucket 5有沒有Z同學:

  • 如果有,那就直接聯絡Z同學要書;
  • 如果沒有,在k-bucket 5裡隨便找一個B同學(注意任意B同學,它的學號第5位肯定與Z相同,即它與Z同學的距離會小於24,相當於比Z、A之間的距離縮短了一半以上),請求B同學在它自己的通訊錄裡按同樣的查詢方式找一下Z同學:
  • 如果B知道Z同學,那就把Z同學的手機號(IP Address)告訴A;
  • 如果B也不知道Z同學,那B按同樣的搜尋方法,可以在自己的通訊錄裡找到一個離Z更近的C同學(Z、C之間距離小於23),把C同學推薦給A;A同學請求C同學進行下一步查詢。

Kademlia的這種查詢機制,有點像是將一張紙不斷地對摺來收縮搜尋範圍,保證對於任意n個學生,最多隻需要查詢log2(n)次,即可找到獲得目標同學的聯絡方式(即在對於任意一個有[2(n−1), 2n)個節點的網路,最多隻需要n步搜尋即可找到目標節點)。

以上便是Kademlia演算法的基本原理。以下再簡要介紹協議中的技術細節。

(6)、演算法的三個引數:keyspace,k和α

  • keyspace – 即ID有多少位 – 決定每個節點的通訊錄有幾層
  • k – 每個一層k-bucket裡裝k個node的資訊,即<node ID, IP Adress, port> – 每次查詢node時,返回k個node的資訊 – 對於某個特定的data,離其key最近的k個節點被會要求儲存這個data
  • α – 每次向其他node請求查詢某個node時,會向α個node發出請求

(7) 節點的指令

Kademlia演算法中,每個節點只有4個指令:

  • PING – 測試一個節點是否線上
  • STORE – 要求一個節點儲存一份資料
  • FIND_NODE – 根據節點ID查詢一個節點
  • FIND_VALUE – 根據KEY查詢一個數據,實則上跟FIND_NODE非常類似

(8) k-bucket的維護及更新機制

  • 每個bucket裡的節點都按最後一次接觸的時間倒序排列
  • 每次執行四個指令中的任意一個都會觸發更新
  • 當一個節點與自己接觸時,檢查它是否在K-bucket中 – 如果在,那麼將它挪到k-bucket列表的最底(最新) – 如果不在,先判斷一個列表裡面是否已滿 — 1) 如果沒滿,將它放到k-bucket列表的最底 — 2) 如果已滿,,PING一下列表最上面(最舊)的一個節點 ----a) 如果PING通了,將舊節點挪到列表最底,並丟棄新節點 ----b) 如果PING不通,刪除舊節點,並將新節點加入列表 該機制保證了任意節點加入和離開都不影響整體網路。

(9) 總結

Kademlia是分散式雜湊表(Distributed Hash Table, DHT)的一種。而DHT是一類去中心化的分散式系統。在這類系統中,每個節點(node)分別維護一部分的儲存內容以及其他節點的路由/地址,使得網路中任何參與者(即節點)發生變更(進入/退出)時,對整個網路造成的影響最小。DHT可以用於構建更復雜的應用,包括分散式檔案系統、點對點技術檔案分享系統、合作的網頁快取記憶體、域名系統以及實時通訊等。 Kademlia演算法在2002年由Petar Maymounkov 和 David Mazières 所設計,以異或距離來對雜湊表進行分層是其特點。Kademlia後來被eMule、BitTorrent等P2P軟體採用作為底層演算法。Kademlia可以作為資訊保安技術的奠基之一。

Kademlia的優點在於:

  • 對於任意一個有[ 2(n−1) ,2?)個節點的網路,最多隻需要n步搜尋即可找到目標節點;
  • K-bucket的更新機制一定程度上保持了網路的活性和安全性

Kademlia作為一種高效的分散式路由演算法,還可以應用在分散式網路中的安全通訊。眾享互聯的分散式網路安全解決方案,將單點對單點的資訊傳輸方式改為在多節點的分散式網路中多路徑傳輸。採用Kademlia這種DHT演算法,使得物聯網裝置的通訊資訊在分散式網路中能夠更快更準確地到達目標裝置,並並大大提高了網路攻擊者徹底阻斷通訊路徑的難度,令物聯網裝置通訊的安全性極大地提高。

轉載原文