以太坊原始碼分析(14)P2P分析

尹成發表於2018-05-13
#概述
Kademlia(簡稱Kad)是一種分散式雜湊表技術,用於建立p2p網路拓撲結構。

基本原理就是以兩個節點ID的異或值作為兩節點間的距離d,每個節點都將其他節點的資訊儲存到稱之為K桶的表結構中,該表結構按照d的為1的最高bit位分層(可理解為桶索引),每層中儲存最多K個節點資訊。如下:
| I | 距離範圍 | 鄰居 |
|:---:|:---------:|:-----------:|
| 0 |[2^0, 2^1 ) | (IP,UDP,NodeID) <br>...|
| i |[2^i, 2^i+1 ) | (IP,UDP,NodeID) <br>...|

節點查詢時,通過詢問距離自己最近的a個節點,讓對方返回距離目標最近的a個節點,重複這個過程直到找到目標節點或者能問的都問了一遍。


參考資料:
[references/Kademlia協議原理簡介.pdf](references/Kademlia協議原理簡介.pdf)
[https://www.jianshu.com/p/f2c31e632f1d](https://www.jianshu.com/p/f2c31e632f1d)

#以太坊中的實現概述
##幾個概念
1. 計算距離的ID值,位數代表了有多少個K桶,經典演算法中是160位的。在以太坊中,NodeID為節點的PublicKey,__參與距離計算的是NodeID的sha3雜湊值,長度256位__
2. K桶的項數不超過K,K值是為平衡系統效能和網路負載而設定的一個常數,但必須是偶數。eth中K=16。
3. 查詢鄰居節點時,返回節點數最多是a個,a也是為了系統優化而定的引數,eth中 a=3。

##資料結構及儲存
* p2p模組使用獨立的leveldb持久化儲存所有的鄰居節點資訊,從而節點重新啟動時能直接利用歷史上已找到的節點。
* 儲存的key為NodeID,value為Node結構體,包含IP、UDP、TCP、ID(即NodeID)等資訊。

##p2p網路維護的實現
table.go主要實現了p2p的Kademlia協議。其中定義了K桶的結構並實現節點的維護策略。

###啟動過程
節點啟動時,初始化配置資訊後,會啟動p2p server,在server啟動過程中,會執行udp物件建立、table物件建立、監聽udp埠等處理。table物件建立中就包含了啟動goroutine執行節點發現及維護的服務。

1. 從leveldb中隨機選取若干種子節點(新節點第一次啟動時,使用啟動引數或原始碼中提供的啟動節點作為種子節點),出入桶結構中(記憶體);
2. 啟動後臺過期goroutine,負責從leveldb中刪除過期的資料(stale data);
3. 啟動loop,後臺執行節點重新整理、重驗證等處理。下面寫的步驟就在這個goroutine中;主要就是doRefresh:
4. 載入種子節點
5. 以自身為目標執行查詢
6. 迴圈3遍:隨機生成目標,執行查詢。

###鄰居節點發現流程 table.lookup
~~~
1. 在本地K桶中查詢距離target最近的一批節點,最多bucketSize個(16個);記為result;(節點加入result的邏輯:從列表中查詢節點i,使得d(i,target) > d(n,target);如果列表中還有空間,直接加入節點n;如果找到了有效的i,則用n替換i位置的節點)
2. 如果步驟1沒有找到節點,則等待重新整理完成(遺留:這裡尚未看懂);
3. 從result中併發發起alpha個(3個)查詢,向對方詢問距離target最近的若干節點(udp.findnode);
4. 若查詢失敗,更新失敗節點資訊,若該節點總失敗次數達到maxFindnodeFailures次(5次),則從本地移除該節點資訊;
5. 若查詢成功,對返回的節點執行bondall處理(__注意:這裡會執行更新K桶的操作,不管pingpong是否成功,都會加入K桶。__如果某個節點總是連不上,會被重新整理機制刪掉的),去掉不線上節點;對線上節點建立連線;
6. 對線上節點,如果未見過,則按照步驟1的規則加入result中;
7. 迴圈從3開始的步驟,直到result中的所有節點都查詢過了;
8. 返回result中的節點。
~~~
###節點連線及本地K桶維護流程 table.bond
在lookup中會對返回的節點執行bondall處理,bondall中主要是對每個節點執行bond處理。
bond確保本地節點和給定的遠端節點有一個連線,如果連線成功,會放到本地一個連線table中。在執行findnode之前,必須有連線已建立。活躍的連線數有一定限制,以便限制網路負載佔用。

不管pingpong是否成功,節點都會更新到本地K桶中。
~~~
1. 如果節點有一段時間沒出現了或者對他執行findnode失敗過,則執行pingpong;
2. 無論前述步驟是否執行或執行是否成功,都執行更新節點到K桶的處理。
~~~

__節點n更新到本地K桶:__

1. 獲取n對應的K桶:設距離為d,計算log2(d)。實現上是獲取d二進位制表示時的最高位1所在的位置。若結果≤ bucketMinDistance(239),返回K[0],否則返回 K[結果-bucketMinDistance-1];
2. 如果n在K桶中已經存在,則將其移到最前面;否則如果K桶未滿,則加進去;
3. 如果n沒進入K桶中,則將其維護進候選列表中。

~~~
// add attempts to add the given node its corresponding bucket. If the
// bucket has space available, adding the node succeeds immediately.
// Otherwise, the node is added if the least recently active node in
// the bucket does not respond to a ping packet.
//
// The caller must not hold tab.mutex.
func (tab *Table) add(new *Node) {
    tab.mutex.Lock()
    defer tab.mutex.Unlock()

    b := tab.bucket(new.sha)
    if !tab.bumpOrAdd(b, new) {
        // Node is not in table. Add it to the replacement list.
        tab.addReplacement(b, new)
    }
}
~~~




網址:http://www.qukuailianxueyuan.io/



欲領取造幣技術與全套虛擬機器資料

區塊鏈技術交流QQ群:756146052  備註:CSDN

尹成學院微信:備註:CSDN


相關文章