Skip to content

节点发现协议

erick yan edited this page Sep 2, 2019 · 2 revisions

节点发现协议

FNODE 节点发现协议,实现了一个类 Kademlia DHT 网络。

节点 ID

每个节点有一个加密的 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 桶添加节点有一些其他的限制:

  • 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 编码。

消息类型

PingPacket(0x01)

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 给发送者。

Pong Packet (0x02)

packet-data = [version, NetID, to, ping-hash, expiration, ...]

Pong 包用来回应 Ping 包。

ping-hash 为对应 Ping 包的哈希。如果收到的 Pong 包哈希和 Ping 包不一致,则忽略。

FindNode Packet (0x03)

packet-data = [version, NetID, target, expiration, ...]

FindNode 包用来请求节点距离和 target 相近的节点。

target 字段是一个 64 字节数组,表示 secp256k1 公钥。

当 FindNode 收到后,需要回复 Neighbors 包,Neighbors 包中包含了最多 16 个和 target 节点距离相近的节点。

Neighbors Packet (0x04)

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

Neighbors 包用来回应 FindNode 包。