在任何給定時間,我的節點連接到多少個節點?隨著網路中節點數量的增加,它們之間的通信會更快嗎?
我一直在閱讀 DEVp2p 文件,似乎無法理解網路的某些方面。具體來說,我的節點連接了多少個節點?當我執行交易時,這個數字會改變嗎?隨著更多節點可用,我的節點是否連接到速度方面最方便的節點?我可以使用哪些其他資源來了解有關底層基礎架構的更多資訊?
您可以通過
admin.peers
在 Geth 控制台中鍵入來查看已連接的對等點。最大對等點數是使用-maxpeers n
Geth 中的標誌設置的。有一個基於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 算法的關鍵。
引導程序
添加引導節點。到餐桌。
bond
即 Ping 遠端端並等待乒乓球。讓他們有機會 ping 我們。如果他們已經認識我們,他們將不會發回 ping。將三元組
id, IP, discover port, tcp port
插入數據庫將給定節點添加其對應的桶如果桶有可用空間,則添加節點立即成功並將節點添加到尾部。否則,如果桶中最近最少活動的節點沒有響應 ping 數據包,則添加該節點。
如果舊節點確實響應,請保留它(將其移動到尾部?)並且不要添加新節點。
進行自我查找以填滿儲存桶。
抬頭
Lookup 對靠近給定目標的節點執行網路搜尋。它通過在每次迭代中查詢更接近目標的節點來接近目標。給定的目標不需要是實際的節點標識符。
- 確定其自己的表中最接近給定 id 的 n 個節點
Kademlia concurrency factor
從此列表中選擇 3個節點- 向這些遠端節點發送
findnode
消息並等待 k 個鄰居消息。- 對於每個新節點,
bond
即 Ping 遠端端並等待一個 pong。然後添加到正確的儲存桶中。- 將這些新節點添加到要查詢的 n 個最近節點列表中。
- 當沒有更多節點要求停止時。
處理查找節點消息。
如果對等方未知,我們將不處理數據包。我們發現 n 個 closet 節點將它們作為多個鄰居消息發送。
neighbors struct { Nodes []rpcNode Expiration uint64 }