Nodes

在任何給定時間,我的節點連接到多少個節點?隨著網路中節點數量的增加,它們之間的通信會更快嗎?

  • November 28, 2016

我一直在閱讀 DEVp2p 文件,似乎無法理解網路的某些方面。具體來說,我的節點連接了多少個節點?當我執行交易時,這個數字會改變嗎?隨著更多節點可用,我的節點是否連接到速度方面最方便的節點?我可以使用哪些其他資源來了解有關底層基礎架構的更多資訊?

我一直在閱讀以下幾頁,但似乎無法理解它: 1 2

您可以通過admin.peers在 Geth 控制台中鍵入來查看已連接的對等點。最大對等點數是使用-maxpeers nGeth 中的標誌設置的。有一個基於Kademlia的發現過程用於查找節點,然後是一個握手過程,它們通過該過程確定它們支持哪些 devp2p 協議(Eth、Bzz、Shh)。P2P 層監控每個節點的服務質量。它計算統計數據,刪除甚至禁止壞節點,並嘗試保留好的節點,即正常執行時間長且對 ping 消息響應迅速的節點。因此,對等點的數量將隨著節點離線或服務質量的變化而變化。所以最好的辦法是保持線上,在這種情況下,您連接的同伴的質量將逐漸提高,您的質量評級也會提高。


在您問題的第二部分中,您詢問了有線協議。我自己並不完全理解它,但是我查看了各種客戶的文件和原始碼。因此,我無法真正解釋整個 p2p 的工作原理,但這是我認為 Dev p2p 發現協議的工作原理。

開發 p2p 發現協議

https://github.com/ethereum/go-ethereum/wiki/Peer-to-Peer

RLPx協議用於創建 P2P 網路並為乙太坊同步(’eth’)、耳語(“shh”)和群(“bzz”)等協議提供基礎。

所以我們需要做的第一件事是形成一個 P2P 網路並找到我們的對等點,這就是發現協議的用武之地。文件說

RLPx 使用類似 Kademlia路由,該路由已被重新用作p2p 鄰居發現協議。RLPx 發現使用 512 位公鑰作為節點 ID,使用 sha3(node-id) 作為 xor 度量。未實現 DHT 功能。

Kademlia類協議,其中每個對等點都維護一個路由表,其中包含最接近它的所有節點的完整集,隨著距離的增加呈指數級稀疏。從消息傳遞的角度來看,該協議由以下基於 UDP 的 RPC 函式組成:

  • Ping - 探測節點以查看它是否線上。接收者應該回復一個 Pong 數據包。如果一個節點收到一個 ping,它會在回復一個 pong 後,將它自己的 ping 發送到第一個節點。
  • Pong - 回复 ping
  • Find_node - 發送查找節點數據包以定位靠近給定目標 ID 的節點。接收者應該回復一個鄰居數據包,其中包含它所知道的最接近目標的 k 個節點。
  • 鄰居 - 是對查找節點的回复。它包含多達 k 個節點,發送者知道哪些節點最接近請求的目標。

解碼 UDP 數據包

對等方在 上偵聽 UDP 數據報port 30303 by default

數據包的框架如下:

|-------|-hash----| signature | packet-type| packet-data   |
|-------|---------| ----------| ---------- |               |
|length | 32 bytes| 65 bytes  | single byte| rest of packet|
|offset | 0       | 32        | 97         | 98            |

檢查消息完整性和真實性

定義message為字節數組,我們可以按如下方式提取這些組件:首先要做的是檢查雜湊是否message[:32]等於消息其餘部分的SHA3-256雜湊(也稱為Keccak256message[32:] )( )。如果不是,則消息已損壞,我們將丟棄數據包。

在python中這是:

mdc_hash = message[:32] //bytes zero to 32
the_rest = message[32:] //bytes 32 to length-1
assert mdc == sha3_256(the_rest).digest()

接下來要檢查的是簽名。該消息使用橢圓曲線數字簽名算法 ( ECDSA ) 特別是secp256k1進行簽名,其中公鑰是發送者的節點 ID。給定簽名 ( 65-byte compact ECDSA signature containing the recovery id as the last element.) 和被簽名的消息 ( 32 byte sha3 hash of the message),以及曲線的知識,可以恢復與用於簽名消息的私鑰對應的公鑰:

注意:python 客戶端從比特幣導入一個用於 ECDSA 的庫。Go 客戶端包裝,libsecp256k1 而 java 客戶端改編來自bitcoinj項目的程式碼。

signature = message[32:97]
signed_data = sha3_256(message[97:])
remote_pubkey = crypto.ecdsa_recover(signed_data, signature)

如果我們無法恢復密鑰,我們會丟棄消息

assert len(remote_pubkey) == 512 / 8

現在我們知道了發送者的節點 ID,但我們仍然需要驗證簽名的消息,如果消息無法驗證,則拋出錯誤。

if not crypto.verify(remote_pubkey, signature, signed_data):
   raise InvalidSignature()

弄清楚它是什麼類型的消息並將其解碼為適當的消息結構

消息類型由單個字節給出message[97],我們只需查找即可。

|value | 1   | 2   | 3         | 4         |
|--| --- | --- | ---| ---|
|type | ping| pong| find_node | neighbours|

這可以通過簡單的字典或基於列舉的 switch 語句來完成,如果值不在 1-4 範圍內,則刪除消息。

** 將其解碼為適當的消息結構**

分組數據是遞歸線性前綴 (RLP)編碼列表。這是一個乙太坊特定的資料結構,用於序列化任意嵌套的二進制數據數組,例如

[
 "some string",
 "some bytes",
 ["element1","element2"],
 [["a","b"],["c"]],
]

所以首先讓我們了解編碼是如何工作的,規範給我們一個下面的 python 程式碼作為例子。

def rlp_encode(input):
if isinstance(input,str):
   if len(input) == 1 and ord(input) < 128: return input
   else: return encode_length(len(input),128) + input
elif isinstance(input,list):
   output = ''
   for item in input: output += rlp_encode(item)
   return encode_length(len(output),192) + output

def encode_length(L,offset):
if L < 56:
    return chr(L + offset)
elif L < 256**8:
    BL = to_binary(L)
    return chr(len(BL) + offset + 55) + BL
else:
    raise Exception("input too long")

def to_binary(x):
return '' if x == 0 else to_binary(int(x / 256)) + chr(x % 256)

因此,解碼的關鍵點是查看第一個字節的範圍,並根據該範圍對有效載荷的其餘部分進行不同的解碼。

|range| meaning | encoding
|-----|----- |
|0x00, 0x7f| Single byte as its self | single byte|
|0x80, 0xb7|string 0-55 bytes long | 0x80 + length of string | rest of string|
|0xb8, 0xbf |string more than 55 bytes long| 0xb7 plus how many bytes are required to represent the length | length of the string as byte array | the string |
|0xc0, 0xf7 | payload e.g. list or list of lists the combined encoded length of which is 0-55 bytes long| length of total rlp encoded payload | RLP encoded payload|
|0xf8, 0xff | payload e.g. list or list of lists the combined encoded length of which is more than 55 bytes long| 0xf7 plus the length in bytes required to represent the length of the payload in binary form | length of the payload as byte array | RLP encoded payload |

** 你有一個 ping 數據包 **

ping 消息具有以下結構,因此您需要將字節數據讀入正確的數據類型。

[
uint Version, // big-endian encoded 32 bit integer protocol version reject if doesn't match own
Endpoint to,
 Endpoint from,
 uint32 expirationTimestamp //big-endian encoded 32 bit integer reject if time() > timestamp to prevent replay attacks.
}

為簡潔起見並遵循 Go 程式碼,我們將 Endpoint 類型定義為以下結構:

[
 bytes ip, // big-endian encoded or  4-byte (32-bit)  or 16-byte (128-bit) address (size determines ipv4 vs ipv6)
 uint16 udp-port, //big-endian encoded 16 bit integer
 uint16 tcp-port //big-endian encoded 16 bit integer
]

在最初的 Kademlia 協議中,ping 消息的接收者將更新與發送者對應的儲存桶。然而,為了防止 IP 地址欺騙,乙太坊協議沒有。相反,ping 消息的接收者應該只回復一個 pong 消息,然後稍後發送自己的 ping:

[
 Byte[] replyToken, // This contains the hash of the ping packet.
 Endpoint to, //The endpoint that sent the ping message
 uint32 expirationTimestamp
]

所以這裡有兩點需要注意:

  • pong 消息的replyToken接收者使用它來將其連結到他們的 ping 消息,並且只是mdc_hash之前檢查的數據包的。
  • to欄位應反映 ping 數據包的 UDP 信封地址,而不是消息from中聲明的端點,ping以便提供一種發現外部地址的方法(在 NAT 之後)。

你有一個乒乓球包

在收到一個pong 數據包時,節點檢查它是否被請求,即它向那個節點發送了一個ping,正在等待一個pong。然後它更新該節點的儲存桶

P2P網路

在第一次加入網路時,每個節點都會生成一個 ECDSA 密鑰對,該密鑰對會被保存並在後續連接enode://<hex node id>中使用。私鑰將用於簽署消息,512 位公鑰用於辨識節點。它還將知道IP address, UDP port, and node ID一些引導節點的。

每個節點都維護一個路由表,其中包含一個稱為聯繫人的已知對等方列表。

路由表由節點 ID 的每一位的列表(桶)組成。

對等表由行組成,最初只有一行,最多 255 行(通常少得多)。每行最多包含 k 個對等點(包含有關所述對等點的資訊的資料結構,例如它們的對等點地址、網路地址、時間戳、對等點的簽名以及可能的各種其他元數據),其中 k 是一個參數(不一定是全域的)典型值介於 5 和 20 之間。

行編號從 0 開始。每個行號 i 包含地址與該節點地址的前 i 位匹配的對等點。地址的第 i+1 位必須與除最後一行之外的所有行中的此節點地址不同。

每個列表對應於與節點的特定距離,因此第 n個桶在節點 ID 的第 n位與節點 ID不同

在 Kademlia 中,定義了距離並按位異或dist(pubk-A,pubk-e) = pubkA ^ pubkB,但因為 ECDSA 密鑰不是均勻分佈的 Devp2p 使用dist(pubkA, pubkB) = sha(pubkA) ^ sha(pubkB)

桶大小為 2 的 4 位範例如下所示:

最右邊的桶(第 0個桶)包含節點本身。最左邊的桶覆蓋了最高有效位不同的空間節點。請注意,桶可能覆蓋的地址空間隨著距離呈指數增長。

|Bucket|4 |3 |2 |1|
|-|- |- |- |-|
|Number of notes it could include if bucket size was unlimited  |8 |4 |2 |1|
|Number of notes it can include when bucket is full |2 |2 |2 |1|

然而,桶的大小是固定的(在本例 2 中),因此很明顯節點的路由表具有最接近它的所有節點的完整集合,隨著距離的增加呈指數級稀疏。此功能是 Kademlia 算法的關鍵。

引導程序

  1. 添加引導節點。到餐桌。

  2. bond即 Ping 遠端端並等待乒乓球。讓他們有機會 ping 我們。如果他們已經認識我們,他們將不會發回 ping。

  3. 將三元組id, IP, discover port, tcp port插入數據庫

  4. 將給定節點添加其對應的桶如果桶有可用空間,則添加節點立即成功並將節點添加到尾部。否則,如果桶中最近最少活動的節點沒有響應 ping 數據包,則添加該節點。

  5. 如果舊節點確實響應,請保留它(將其移動到尾部?)並且不要添加新節點。

  6. 進行自我查找以填滿儲存桶。

抬頭

Lookup 對靠近給定目標的節點執行網路搜尋。它通過在每次迭代中查詢更接近目標的節點來接近目標。給定的目標不需要是實際的節點標識符。

  1. 確定其自己的表中最接近給定 id 的 n 個節點
  2. Kademlia concurrency factor從此列表中選擇 3個節點
  3. 向這些遠端節點發送findnode消息並等待 k 個鄰居消息。
  4. 對於每個新節點,bond即 Ping 遠端端並等待一個 pong。然後添加到正確的儲存桶中。
  5. 將這些新節點添加到要查詢的 n 個最近節點列表中。
  6. 當沒有更多節點要求停止時。

處理查找節點消息。

如果對等方未知,我們將不處理數據包。我們發現 n 個 closet 節點將它們作為多個鄰居消息發送。

neighbors struct {
Nodes      []rpcNode
Expiration uint64
}

引用自:https://ethereum.stackexchange.com/questions/10140