Bytom P2P网络 节点发现

节点发现

节点连接主要负责发现P2P网络中的其它节点。Bytom实现了类Kademlia的DHT存储有关网络中Bytom节点的信息。协议维护了节点路由表,节点路由表依据距离把网络中节点信息存储到表中不同的bucket中。

节点发现协议

discovery
udp
基于udp节点发现

节点ID及距离

网络中每个节点都有一个唯一ID,节点每次启动时都会产生ED25519公私钥对,其中公钥作为节点的ID,长度32byte。

两个节点之间的距离是以下公式计算而来,所以节点之间的距离代表逻辑距离,而不是节点间的真实物理距离。

1
distance(n₁, n₂) = keccak256(n₁) XOR keccak256(n₂)

节点路由表

bucket index node
k-bucket 0 距离[1,2)
k-bucket 1 距离[2,4)
k-bucket 2 距离[4,8)
k-bucket i 距离 [2^i,2^(i+1)
k-bucket 255 距离[2^255, 2^(255+1))
节点路由表

根据Kademilia协议节点需要保留其邻居节点的信息。邻居节点存储在由“k-buckets”组成的路由表中。对于每个bucket[i] 0≤i<256,存储距离自己“2^i”和“2^(i + 1)”之间的距离节点。

协议使用“k = 16”,即每个k-bucket最多存储16个节点。bucket中节点按上次查看的时间排序 - 最近看到的节点位于bucket头部,最先看到的在bucket尾部。

每当遇到新节点N1时,它就可以插入相应的桶中。如果桶中包含少于“k”的节点,则可以简单地将N1添加到桶头部。如果该桶已经包含k个节点,则取桶中最先看到的节点N2,需要通过发送ping数据包重新验证节点是否在线。如果没有从N2收到答复,那就是被认为是无效的,N2被移除bucket并且N1被添加到桶的前面。
否则将N2加入cache中,如果cache也满了,则把最先放入cache中的节点N3删除。当有节点掉线时则会把cache中的最新节点放入bucket中。

路由表刷新

节点通过定期迭代查找距离targetID距离近的节点来更新路由表。工作过程如下:

a. 随机生成目标节点Id,记为TargetId,从1开始记录发现次数和刷新时间。

b. 在当前节点的K桶里查找与目标节点最近的16个节点

c. 向b中得到的每个节点发送findnode命令,接收到每个节点传回的neighbours节点

d. 对c返回的每个节点进行ping-pong测试然后更新到本地k桶

如果一轮FindNode查询无法返回比已知最近的节点更近的节点,启动器将FindNode重新发送到k个最近节点中未查找过得节点。

协议消息及编码

协议实现了Kademlia协议的两组命令:

ping<------------->pong

findnode <---------->neighbors

节点发现消息作为UDP数据报发送。数据包的最大大小为1280字节。

1
packet = packet-header || packet-data

每个数据包都以header开头:

1
2
3
packet-header = hash ||nodeID ||signature||packet-type
hash = sha3(nodeID || signature || packet-type || packet-data)
signature = sign(packet-type || packet-data)

hash 字段使在同一个UDP端口上运行多个协议时可识别。

packet-type 是定义消息类型。

Ping Packet(0x01)

1
2
3
packet-data = [version,from,to,expiration]
from = [sender-ip,sender-udp-port,sender-tcp-port]
to = [recipient-ip,recipient-udp-port,0]

expiration 字段是绝对的UNIX时间戳。包含过去时间戳的数据包会因为过期而无法处理。
收到ping数据包后,收件人应使用pong数据包进行回复。

Pong Packet(0x02)

1
packet-data = [to,ping-hash,expiration]

Pong是对ping的回复。

ping-hash 应该等于相应ping包的hash。应该忽略没有包含最新ping-hash的pong数据包,因为不是对最新ping消息做出的响应。

FindNode Packet(0x03)

1
packet-data = [target,expiration]

FindNode 数据包请求接近target的节点的信息。收到FindNode后,收件人应回复Neighbors数据包,在其本地表中找到最接近target的16个节点并返回。

Neighbors Packet(0x04)

1
2
packet-data = [node,expiration]
nodes = [[ip,udp-port,tcp-port,node-id],...]

Neighbors是对FindNode的回复。