P2P和以太坊P2P网络技术

以太坊源码1-节点发现协议(discv4)

2018-10-18  本文已影响0人  jection

本文结合ethereumJ源码,分析以太坊的节点发现协议(discv4)实现过程。discv4是以太坊用来发现公链P2P网络中的其它节点,组成K桶网络的协议。

ethereumJ从协议上看

  1. discv4协议,用于节点发现 ,使用udp协议,实现节点之间高成功率的穿透,来发现节点,建立连接的信道
  2. rplx协议,节点通信协议,使用tcp协议,为了信息的可靠传输,在已经建好的信道基础上,使用TCP协议传输区块、交易、日志等数据;

discv4涉及的源码包:org.ethereum.net.rlpx.discover

节点发现过程

图片.png

节点发现流程说明:

  1. 本机节点开始运行时,生成本机节点nodeID,即localID,同时打开30303端口,监听节点发送协议网络(使用UDP);
  2. 从配置文件,加载20个引导节点信息,向这些节点循环发送ping报文,在线的引导节点将响应pong报文,将响应的引导节点加入k桶;
  3. 监听udp网络的同时,启动了两个任务:节点发现任务和节点刷新任务;
  4. 节点发现任务30秒循环一次,每次循环跑8遍,每遍以localId为目标节点TargetID,从k桶中获取距离TargetID最接近16个节点,循环向16个节点发送FindNode报文(包含TargetID);
  5. 收到FindNode命令的节点,也以TargetID为目标,从自己的K桶中找出距离最接近的16个节点,回传Neightbour报文;
  6. 本机节点收到Neightbour后,从报文取出新发现的节点,向新节点循环发送Ping报文,并将响应的节点加入k桶;
  7. 节点刷新任务和节点发现任务差不多,不一样的地方是,刷新任务的TargetID不是localId,而是随机生成的nodeId。还有刷新任务刷新速度更频繁,7.2秒循环一次;
  8. 就这样,不断发现和刷新节点,本地节点找到越来越多的邻居节点,组成K桶网络。

K桶

图片.png

上图是一个由K桶组成的路由表。
K桶是一个存储结构,是由k-buckets(k桶)组成的路由表中,因为节点最大距离是256,所以路由表一共有256个k桶,每个桶中最多放k=16个节点。每个以太坊的节点内部都会有一个路由表,用来存储的它发现的网络节点。
存储规则如下:
本地节点通过节点发现协议寻找网络其它节点,每发现一个节点,都会计算该节点与本地节点之间的距离(0-255),根据这个距离将该节点存储于路由表中对应的K桶位置。
如果发现一个新节点,但是这时路由表的K桶满时,会采取最后发现策略,将对应K桶最后发现的节点取出,通过ping命令探测该节点是否在线,如果没有收到响应,新节点将取代它。

节点距离计算

取1的最高位数12,那么这两个节点的距离为14。
因为参与XOR的是256位数字,所有,两个节点的距离是1-256。

注意:这里的节点距离与机器的物理距离无关,这个距离仅仅是逻辑上的一种约定

报文协议

discv4为节点之间互相发现,定义了四种报文协议(命令)

  1. ping:探测命令,用于探测对方节点是否在线。
  2. pong :探测应答命令,用于响应ping报文
  3. findNode:节点查询命令,用于向对方节点请求查找邻居节点
  4. neighbours:节点查询应答命令,用于响应findNode报文,回传找到的邻居节点列表
ping报文

发送

处理

pong报文

发送

处理

findNode报文

发送

处理

Neighbours报文

发送

处理

节点生命周期

图片.png

关键类图

图片.png

配置类

配置类——SystemProperties

类路径:org.ethereum.config.SystemProperties
主要方法:

处理类

入口类——UDPListener

类路径:org.ethereum.net.rlpx.discover.UDPListener
功能:

节点管理类——NodeManager

类路径:org.ethereum.net.rlpx.discover.NodeManager
功能:

节点处理类——NodeHandler

类路径:org.ethereum.net.rlpx.discover.NodeHandler
功能:

节点发现执行类——DiscoveryExecutor

类路径:org.ethereum.net.rlpx.discover.DiscoveryExecutor
功能:启动下面两个类

节点发现任务类——DiscoverTask

类路径:org.ethereum.net.rlpx.discover.DiscoverTask
功能:
使用findNode命令来发现邻居节点,30s执行一次,每次循环8遍,每遍都获取k桶最接近localId的16个节点,向这16个节点发送findNode信息(ethereumJ实际用了ALPHA=3个节点)

节点刷新任务类——RefreshTask

类路径:org.ethereum.net.rlpx.discover.RefreshTask
功能:
使用findNode命令刷新K桶,7.2s执行一次,每次循环8遍,和DiscoverTask的区别是,DiscoverTask使用的目标Id是localId,而RefreshTask使用的目标Id是随意Id,获取最接近随机Id的16个节点,向它们发送findNode信息。

节点持久化类——PeerSource

类路径:org.ethereum.db.PeerSource
功能:

节点相关类

节点表类——NodeTable

类路径:org.ethereum.net.rlpx.discover.table.NodeTable
功能:
这个类就是一个K桶,里面有一个NodeBucket数组,数组大小正好是256,用来存储所有发现的节点(包括本机节点)。

节点桶类——NodeBucket

类路径:org.ethereum.net.rlpx.discover.table.NodeBucket
功能:
这个类代表K桶的一个桶,NodeBucket里面有一个List<NodeEntry>,最多包含16个NodeEntry。

节点实体类——NodeEntry

类路径:org.ethereum.net.rlpx.discover.table.NodeEntry
功能:这个类代表K桶里面的一个节点
属性:

节点类——Node

类路径:org.ethereum.net.rlpx.Node
功能:Node是比NodeEntry更纯粹意义的节点,NodeEntry代表K桶中的一个节点,而Node仅仅代表一个节点。
属性:

报文类

discv4为四种报文协议定义了统一的报文格式:

packet = packet-header || packet-data
packet-header = mdc || signature || type
packet-data = data

对应的报文类在Message中定义。

报文基类——Message

类路径:org.ethereum.net.rlpx.Message
功能:定义基本的报文属性、编码、解码方法
属性:

wire = mdc || signature || type || data

mdc = keccak256(signature || type || data)

signature = ECDSASignature(keccak256(payload))
payload = type || data

//PingMessage
data = version || from || to || expiration
//PongMessage
data = to || ping-mac || expiration
//FindNodeMessage
data = target-nodeId || expiration
//NeighborsMessage
data = nodes || expiration
nodes = node || node || node ...
node = host || udpport || tcpport || nodeId

//PingMessage
type = 1
//PongMessage
type = 2

//FindNodeMessage
type = 3
//NeighborsMessage
type = 4

ping报文类——PingMessage

类路径:org.ethereum.net.rlpx.PingMessage

pong报文类——PongMessage

类路径:org.ethereum.net.rlpx.PongMessage

findNode报文类——FindNodeMessage

类路径:org.ethereum.net.rlpx.FindNodeMessage

neighbors报文类——NeighborsMessage

类路径:org.ethereum.net.rlpx.NeighborsMessage

报文疑问

  1. 所有发送的报文都包含签名,本来认为接收节点收到报文后,会验证签名,但是并不是这样,首先发送报文的节点没有发送节点的公钥(即nodeId),发送节点没有发送公钥(nodeId),那接收节点如何得到呢。这个过程正好是颠倒过来的,接收节点从签名和签名原文反推出公钥,从而得到nodeId,组装Node对象。
  2. 报文里面有expiration字段,值为报文发送时间90分钟后的时间戳,这个字段应该是用来做信息过期处理的,但是ethereumJ里面看不到任何相关的处理代码。
  3. 报文里的mac字段,设置应该是用来作为ping-pong或者findNode-Neighbors匹对的。比如pong报文发送的data里面包含了ping报文的mac,但是很奇怪,在代码实现里,收到pong的节点并没有去校验这个mac值是否和自己发送的ping匹配。
上一篇 下一篇

猜你喜欢

热点阅读