以太坊系列---P2P模組解析
1. 架構
筆者一直致力於區塊鏈底層技術研究。本文將講下以太坊geth原始碼P2P的啟動過程和原始碼結構。
P2P啟動位於p2p模組server.go, 初始化的工作放在Start()方法中。
1.1 啟動server
P2P啟動主要包括兩個部分啟動TCP和啟動UDP,預設埠30303。
UDP:udp主要用於p2p節點發現,包括nat地址轉換,discover發現模組。
TCP:tcp主要用於節點和節點連線的區塊鏈資料的處理, 包括接收其他節點的連線和主動連線其他節點的功能,主要邏輯在Peer,同時通過Nat向外暴露地址。
2.啟動UDP
udp啟動:
//啟動discover網路。 開啟UDP的監聽。
if !srv.NoDiscovery {
ntab, err := discover.ListenUDP(srv.PrivateKey, srv.ListenAddr, srv.NAT, srv.NodeDatabase, srv.NetRestrict)
if err != nil {
return err
}
//設定最開始的啟動節點。當找不到其他的節點的時候。 那麼就連線這些啟動節點。
if err := ntab.SetFallbackNodes(srv.BootstrapNodes); err != nil {
return err
}
srv.ntab = ntab
}
//新的節點發現協議,試驗階段未使用
if srv.DiscoveryV5 {
ntab, err := discv5.ListenUDP(srv.PrivateKey, srv.DiscoveryV5Addr, srv.NAT, "", srv.NetRestrict) //srv.NodeDatabase)
if err != nil {
return err
}
if err := ntab.SetFallbackNodes(srv.BootstrapNodesV5); err != nil {
return err
}
srv.DiscV5 = ntab
}
啟動流程如下:
2.1開啟UDP埠
conn, err := net.ListenUDP("udp", addr)
2.2通過nat暴露Udp埠
將服務開啟的Udp埠註冊(map)到Nat網路,這樣內網的程式有了外網的IP地址, 這樣公網的使用者就可以直接對你進行訪問。
if !realaddr.IP.IsLoopback() {
go nat.Map(natm, udp.closing, "udp", realaddr.Port, realaddr.Port, "ethereum discovery")
}
Nat:nat代表網路地址轉換。以太坊主要有upnp和pmp兩種nat網路協議。
upnp:採用goupnp實現,地址:github.com/huin/goupnp
pmp:採用go-nat-pmp實現,地址:github.com/jackpal/go-nat-pmp
2.3 網路資料讀取和處理
網路資料讀取和處理主要包括協議,節點發現和節點儲存3個部分。
1.協議:網路資料包包括4種資料包協議,分別是ping,pong,findnode和neighbors。
findnode 是用來查詢距離target比較近的節點
neighbors迴應資料包
2.節點發現:網路發現協議使用了Kademlia 協議。主要邏輯在discover/table.go
Kademlia: Kad 是一種分散式雜湊表( DHT) 技術, 不過和其他 DHT 實現技術比較,如
Chord、 CAN、 Pastry 等, Kad 通過獨特的以異或演算法( XOR)為距離度量基礎,建立了一種
全新的 DHT 拓撲結構,相比於其他演算法,大大提高了路由查詢速度。
3.節點儲存:發現節點的儲存主要使用leveldb。 主要邏輯在discover/database.go
3.啟動TCP
// listen/dial
if srv.ListenAddr != "" {
if err := srv.startListening(); err != nil {
return err
}
}
if srv.NoDial && srv.ListenAddr == "" {
log.Warn("P2P server will be useless, neither dialing nor listening")
}
srv.loopWG.Add(1)
//主動發起連線來連線外部節點的流程和已經連線的checkpoint邏輯
go srv.run(dialer)
啟動流程如下:
3.1開啟Tcp埠
listener, err := net.Listen("tcp", srv.ListenAddr)
3.2通過nat暴露Tcp埠
go func() {
nat.Map(srv.NAT, srv.quit, "tcp", laddr.Port, laddr.Port, "ethereum p2p")
srv.loopWG.Done()
}()
3.3節點和節點資料的處理
節點和節點資料處理主要啟動2個goroutine, srv.listenLoop(), srv.run(dialer)。
listenLoop:接收外部的請求
run:主動發起連線來連線外部節點的流程以及處理checkpoint佇列資訊的流程
TCP資料解析採用Rlpx協議,Tcp節點挑選和連線的處理主要在dial,節點之間的資料邏輯主要在peer。
1.rlpx:RLPx協議就定義了TCP連結過程的的加密過程。主要邏輯在rlpx.go
RLPX:Perfect Forward Secrecy, 連結的兩方生成生成隨機的私鑰,通過隨機的私鑰得到公鑰。 然後雙方交換各自的公鑰, 這樣雙方都可以通過自己隨機的私鑰和對方的公鑰來生成一個同樣的共享金鑰(shared-secret)。後續的通訊使用這個共享金鑰作為對稱加密演算法的金鑰。 這樣來說。如果有一天一方的私鑰被洩露,也只會影響洩露之後的訊息的安全性, 對於之前的通訊是安全的(因為通訊的金鑰是隨機生成的,用完後就消失了)。
2.dial:p2p裡面主要負責建立連結的部分工作。 比如發現建立連結的節點。 與節點建立連結。 通過discover來查詢指定節點的地址等功能。 主要邏輯在dial.go
3.peer:peer代表了一條建立好的網路鏈路。在一條鏈路上可能執行著多個協議。比如以太坊的協議(eth)。 Swarm的協議。 或者是Whisper的協議。 geth主要採用的是以太坊的協議。主要邏輯在peer.go
4.總結
以太坊P2P模組是區塊鏈資料交換的基石,學習並使用該模組尤為主要。