简而言之:
- 旨在发现要连接的PLPx节点
- 基于UDP的RPC协议(类似kademila)
- 定义4种数据包类型:
ping
,pong
,findnode
和neighbours
原文:Node Discovery Protocol v4
本文档定义了节点发现协议版本4,这是一个类似Kademlia
的DHT
,用于存储有关以太坊节点的信息。选择Kademlia结构是因为它产生了低直径的拓扑结构。
节点标识
每个节点都有一个加密标识,这是椭圆曲线secp256k1
上的一个key。节点的公钥作为其标识符或node ID
。
两个节点ID之前的“距离”是按位排他的或公钥的散列值,即数字。
distance(n₁, n₂) = keccak256(n₁) XOR keccak256(n₂)
节点表
发现协议中的节点保留有关其附近的其他节点的信息。相邻节点存储在由'k-bucket'组成的路由表中。对于0 ≤ i < 256
的每一个节点,都保留一个k-bucket
,表示2i
和2i+1
节点之间的距离。
节点发现协议使用k = 16
,即每个k-bucket包含多达16个节点条目。这些条目按最近一次看到的时间排序 - 最近最少看到的节点在头部,最近最多看到的节点在尾部。
每遇到一个新节点N₁
,它就可以插入到相应的bucket中。如果bucket中包含少于k条目N₁可以简单地添加为第一个条目。如果bucket已经包含k条目,则bucket中最近最少可见的节点N₂
需要通过发送ping数据包来重新生效。如果没有收到来自N₂的应答,则认为该节点N₂不生效了,并将N₁加到bucket的前面。
端点证明
为防止流量放大攻击,实现必须验证查询的发送方是否参与了发现协议。如果数据包的发送者在过去12小时内发送了匹配ping哈希的有效pong响应,则认为该数据包的发送者已被验证。
递归查找
“查找”定位与节点ID最近的k个节点。
查找发起者首先选择α
最接近它所知道的目标的节点。然后发起者将并发FindNode
数据包发送到这些节点。α
是系统范围的并发参数,例如3.在递归步骤中,发起方将FindNode
重新发送到它从前面的查找中了解到的节点。“k”节点的发起者监听到最近的目标节点,它选择α
尚未查找的节点并向其重新发送FindNode
。无法快速响应的节点将被排除在考虑之内,除非它们确实响应了。
如果一轮FindNode
查询无法返回比最近看到的”最近节点“更近的节点,则发起方将find node
重新发送到k
尚未查询过的所有最近的节点。当发起者查找并从k
最近的节点获得响应时,查找终止。
Wire协议
节点发现消息作为UDP数据报发送。任何数据包的最大大小是1280字节。
packet = packet-header || packet-data
每个数据包都以一个标题开头:
packet-header = hash || signature || packet-type
hash = keccak256(signature || packet-type || packet-data)
signature = sign(packet-type || packet-data)
当在同一个UDP端口上运行多个协议时,hash
的存在使包的格式变得可识别。它没有别的用处。
每个数据包都由节点的身份密钥签名。signature
被编码为长度为65的字节数组,作为签名值r
、s
和“恢复id”v
的连接。
这packet-type
是定义消息类型的单个字节。下面列出了有效的数据包类型。报头后的数据特定于数据包类型,并被编码为RLP列表。按照EIP-8,实现应该忽略列表中的任何其他元素以及列表之后的任何额外数据。
Ping数据包(0x01)
packet-data = [version, from, to, expiration]
version = 4
from = [sender-ip, sender-udp-port, sender-tcp-port]
to = [recipient-ip, recipient-udp-port, 0]
该expiration
字段是绝对的UNIX时间戳。包含过去时间戳的数据包过期可能无法处理。
当收到ping
包时,收件人应该用pong
包回复。它也可以考虑将发送者添加到节点表中。
如果在过去的12小时内没有与发件人进行任何沟通,除了pong
之外,还应发送一个ping
来获得端点证明。
Pong数据包 (0x02)
packet-data = [to, ping-hash, expiration]
Pong
是对ping
的回复。
ping-hash
应该等于相应ping数据包的hash
。实现应该忽略不包含最近ping数据包hash的未经请求的pong数据包。
FindNode数据包 (0x03)
packet-data = [target, expiration]
FindNode
数据包请求关于靠近target
节点的信息。这target
是一个65字节的secp256k1公钥。当接收到FindNode
时,收件人应回复包含最近的16个节点的neighbors
数据包,该节点在其本地表中找到。
为防止流量放大攻击,只有在端点证明过程验证了FindNode的发送方时才应发送neighbors
回复。
Neighbors数据包 (0x04)
packet-data = [nodes, expiration]
nodes = [[ip, udp-port, tcp-port, node-id], ... ]
Neighbors是对FindNode的回复。
已知问题和实现建议
expiration
存在于所有数据包中的字段应该防止数据包重放。由于它是绝对时间戳,因此节点的时钟必须准确无误才能正确验证。自从协议于2016年发布以来,我们收到了无数关于用户时钟错误连接问题的报告。
端点证明是不准确的,因为FindNode的发件人无法确定收件人是否看到最近的pong。Geth如下处理它:如果在最后12小时内没有与收件人通信,请发送ping命令启动该过程。等待来自另一端的ping,回复并发送FindNode。