-
Notifications
You must be signed in to change notification settings - Fork 36
节点发现协议
FNODE 节点发现协议,实现了一个类 Kademlia DHT 网络。
每个节点有一个加密的 ID,该 ID 是定义在 secp256k1 椭圆曲线上的密钥对应的公钥。
两个节点 ID 之间的‘距离’是公钥哈希的按位异或,通过节点距离可以对节点进行分类排序。
节点距离计算公式:
-
id1 = keccack256(n1)
-
id2 = keccack256(n2)
-
distance(id1, id2) = log2(id1 ^ id2)
节点发现协议中保存了自身节点以及邻居节点的信息。邻居节点存储在一个由‘K 桶’组成的路由表中。
每个节点根据邻居节点到自身的节点距离,对邻居节点进行分类排序,并将节点距离相近的节点放到一个集合中,称为 bucket,多个 bucket 组成 K 桶。
目前每个 bucket 包含 16 个邻居节点。bucket 中的邻居节点通过最后活跃时间来排序:最后通信过的节点排在头部,最先通信过的节点放在尾部。
当一个新的节点N1
加入了,它会根据节点距离将其插入到对应的 bucket。如果该 bucket 中节点数少于 16,N1
可以直接加到头部。如果 bucket 已满,则尾部的节点N2
需要被发送 ping 包。如果没有收到N2
的回复,则移除N2
,插入N1
。
K 桶添加节点有一些其他的限制:
- K 桶有 17 个 bucket,节点距离小于等于(256-17)的节点加入 bucket[0],之后距离每增加 1,bucket 位置也增加 1。
- 每个 bucket 有两个节点列表:
- 活动列表:当前使用的列表。容量为 16。
- 备用列表:活动列表中的节点失效,则会从备用列表中取出并补充到活动列表。容量为 10。
- 每个 bucket 限制同一网段的节点数不超过 2 个,划分网段为 IP/24。
- K 桶限制所有 bucket 中同一网段的节点总数不超过 10 个,划分网段为 IP/24。
- 添加节点时会先尝试加入活动列表,如果活动列表已满,则加入备用列表,如果备用列表也满了,则丢弃。
一次查找会查找 k 个节点距离最近的节点。
当查找目标 ID 的网络节点时,会先从本地 K 桶中找出节点距离最接近的一组节点,并向其发送 findnode 消息(包含目标 ID),而应答则是 neighbors 消息(包含了距离最接近目标 ID 的一组节点)。 如果节点没有快速应答,则将其移除。
目前主要通过定时随机生成 ID,并向邻居节点发送 findnode 来发现新的节点。
为了防止流量放大攻击,需要验证查询的发送方是否参与了发现协议,发送方需要在 12 小时内发送过有效的 Pong 包。
节点发现协议使用 UDP 协议通信。最大的包被限制在 1280 字节内。
packet 由 packet-header 和 packet-data 域组成:
packet = packet-header || packet-data
每个 packet 都由 header 开始。
packet-header:
packet-header = hash || signature || packet-type
hash = keccak256(signature || packet-type || packet-data)
signature = sign(packet-type || packet-data)
每个 packet 需要被节点 ID 对应的私钥钥签名。signature 字段编码为一个 65 字节的数组,包含了r
,s
,v
。
packet-type 字段是一个单字节,定义了消息类型。packet-data 就是对应消息类型的 RLP 编码。
packet-data = [version, NetID, from, to, expiration, ...]
version = 5
NetID = cmd.param
from = [sender-ip, sender-udp-port, sender-tcp-port]
to = [recipient-ip, recipient-udp-port, 0]
- packet-data 尾部多余的数据会被忽略(可用于协议升级)。
- version 值不同也会被忽略(可用于升级协议)。
- NetID 字段为配置字段,不同 NetID 的消息会被忽略。
- expiration 字段是 UNIX 时间戳。过期的消息会被忽略。
当收到 ping 包后,需要回复一个 pong 包,并将发送者加入到节点列表中。
如果 ping 包的发送者已经超过 12 小时没有联系了,则还需发送一个额外的 ping 给发送者。
packet-data = [version, NetID, to, ping-hash, expiration, ...]
Pong 包用来回应 Ping 包。
ping-hash 为对应 Ping 包的哈希。如果收到的 Pong 包哈希和 Ping 包不一致,则忽略。
packet-data = [version, NetID, target, expiration, ...]
FindNode 包用来请求节点距离和 target 相近的节点。
target 字段是一个 64 字节数组,表示 secp256k1 公钥。
当 FindNode 收到后,需要回复 Neighbors 包,Neighbors 包中包含了最多 16 个和 target 节点距离相近的节点。
packet-data = [version, NetID, nodes, expiration, ...]
nodes = [[ip, udp-port, tcp-port, node-id], ...]
Neighbors 包用来回应 FindNode 包。