ethereum - build unstoppable applications
节点发现
以太坊的节点发现协议使用的是KAD算法(kademlia)
-
数据存储结构
它在一个数组里维护了256个bucket,每个bucket的数据下标即为其深度,每个bucket最多存储16个节点,所以一个节点可存储的节点数为256 * 16个节点
这是在NodeTable中定义的
- 距离算法
每个节点都有一个nodeId,这个nodeId是根据节点的地址经过哈希算法并使用ECC加密之后,导出的一个512位的公钥
final ECKey generatedNodeKey = ECKey.fromPrivate(sha3(addressOrEnode.getBytes()));
public byte[] getNodeId() {
if (nodeId == null) {
nodeId = pubBytesWithoutFormat(this.pub);
}
return nodeId;
}
两个节点的距离就是两个nodeId异或之后取最高位1所在的位置得来的,举例说明:
假如节点A的nodeId为1011011(二进制表示),节点B的nodeId为1100110,那么二者的距离为6,距离越小那么存储的bucket越浅,也就是越近,本节点就会选取最近的16个节点发起节点查询请求
- 消息类型
- ping 询问节点是否存活
- pong ping的响应
- findnode 节点查询,向目标节点询问附近节点列表
- neighbours findnode消息的响应
节点发现实现
首先节点发现协议使用的Udp,入口是UDPListener,在这个类初始化的时候,它会读取配置文件配置(peer.discovery.ip.list)的初始化的节点发现的IP列表,然后通过start方法启动了3个任务:
- 节点服务端监听任务
使用netty绑定默认为30303的端口,消息的处理器是messageHandler,收到消息的时候会使用NodeManager的handleInbound方法处理各种上面提到的消息类型
while (!shutdown) {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioDatagramChannel.class)
.handler(new ChannelInitializer<NioDatagramChannel>() {
@Override
public void initChannel(NioDatagramChannel ch)
throws Exception {
ch.pipeline().addLast(stats.udp);
ch.pipeline().addLast(new PacketDecoder());
MessageHandler messageHandler = new MessageHandler(ch, nodeManager);
nodeManager.setMessageSender(messageHandler);
ch.pipeline().addLast(messageHandler);
}
});
channel = b.bind(address, port).sync().channel();
channel.closeFuture().sync();
...省略
}
- 节点发现任务 每30秒向临近节点发送findnode消息
discoverer.scheduleWithFixedDelay(
new DiscoverTask(nodeManager),
1, KademliaOptions.DISCOVER_CYCLE, TimeUnit.SECONDS);
- 刷新任务 每7200毫秒刷新下临近节点列表,其实也是发送findnode消息
refresher.scheduleWithFixedDelay(
new RefreshTask(nodeManager),
1, KademliaOptions.BUCKET_REFRESH, TimeUnit.MILLISECONDS);