网络层
以太坊是一个由数千个节点组成的点对点网络,节点之间必须能够使用标准化协议相互通信。
“网络层”是使节点能够找到彼此并交换信息的协议栈。
可交换信息包括网络上的“广播”信息(一对多通信),以及特定节点之间的交换请求和答复(一对一通信)。
每个节点必须遵守特定的网络规则,以确保发送和接收正确的信息。
客户端软件由两部分组成(执行客户端和共识客户端),它们都具有各自的网络堆栈。
除了与其他以太坊节点进行通信外,执行客户端和共识客户端还必须互相通信。 本页介绍了用以实现这种通信的协议。
执行客户端通过执行层的点对点网络广播交易信息。
这需要经验证的对等点之间进行加密通信。
当一个验证者被选中来提议区块时,该节点本地交易池中的交易将会通过一个本地远程过程调用连接传递至共识客户端,然后再将其打包进信标区块。
之后,共识客户端将在它们的对等网络中广播信标区块。
广播过程需要两个独立的对等网络:一个连接执行客户端,负责交易的广播;另一个连接共识客户端,负责区块的广播。
执行层
执行层的网络协议分为两个堆栈:
-
发现堆栈:建立在用户数据报协议之上,并使新节点能够找到相应节点并连接
-
DevP2P 堆栈:建立在传输控制协议之上,并使节点能够交换信息
这两个堆栈并行作用, 发现堆栈将新的网络参与者输送到网络中,DevP2P 则使它们进行交互。
发现
发现是在网络中寻找其他节点的过程。 该过程使用一小组引导节点(即地址硬编码为客户端的节点,以便它们能被立即找到,并将客户端连接至对等点)进行引导。
这些引导节点旨在将新节点引入一组对等点,这是它们唯一的目的。它们不参与普通的客户端任务,例如同步链,仅在第一次使用客户端时使用。
节点与引导节点交互所使用的协议是 Kademlia 的修改版,它使用分布式散列表共享节点列表。 每个节点都有一版此表格,其中包含连接到最近节点所需的信息。
这个“最近”不是指地理距离,而是由节点 ID 的相似性来界定的。 每个节点的表格都会定期刷新,作为一种安全特性。
例如,在 Discv5 中,发现协议节点也可以发送显示客户端支持的子协议的聚合发现服务,以便对等点协调通信所用的协议。
发现过程从 PING-PONG 游戏开始。 一个成功的 PING-PONG 将新节点“连接”到一个启动节点。
提醒引导节点有新节点进入网络的初始消息为 PING。
此 PING 包括关于新节点、引导节点和过期时间戳的哈希信息。
引导节点收到 PING 并返回一个包含 PING 哈希值的 PONG。
如果 PING 和 PONG 的哈希值相匹配,新节点和引导节点之间的连接就会得到验证,然后就认为它们已经“绑定”。
绑定之后,新节点即可向引导节点发送 FIND-NEIGHBOURS 请求。
引导节点返回的数据包含一个新节点可以连接的节点列表。
如果这两个节点没有绑定,FIND-NEIGHBOURS 请求将失败,新节点将无法进入网络。
新节点从引导节点收到邻居节点列表后,就会开始与每个邻居节点交换 PING-PONG。 成功的 PING-PONG
将新节点与邻居节点绑定在一起,以实现消息交换。
启动客户端 --> 连接到 bootnode --> 绑定到 bootnode --> 寻找邻居--> 绑定到邻居。
ENR:以太坊节点记录
以太坊节点记录 (ENR) 是一个包含三个基本元素的对象:签名(根据某种商定的身份识别方案创建的记录内容的散列)、跟踪记录更改的序号和键:值对的任意列表。
这种格式不会过时,使新对等点之间身份识别信息的交换更加容易,并且是以太坊节点的首选网络地址格式。
发现为什么建立在 UDP 协议上?
UDP 协议不支持任何错误检查、重新发送失败的数据包,或者动态地打开和关闭连接;相反,它只是将连续的信息流发送至目标,无论它们是否被对方成功接收。
这种最简化的功能会产生最少的连接开销,从而使这种连接非常迅速。
对于发现而言,如果某个节点只想让其它节点知道它的存在以便它与某个对等点建立正式的连接,UDP 协议就已经足够了。
然而,对网络协议栈的其余部分来说,UDP 协议就不那么合适了。
节点之间的信息交流相当复杂,因此需要一个功能更完善的协议来支持重新发送、错误检查等。
TCP 协议带来更多功能所产生的额外连接开销是值得的。
因此,对等网络协议栈中的大多数协议在 TCP 协议之上运行。
DevP2P
DevP2P 本身就是以太坊为建立和维护对等网络而实施的一整套协议。
新节点进入网络后,它们的交互由 DevP2P 堆栈中的协议管控。
这些操作均基于传输控制协议,包括 RLPx 传输协议、线路协议和若干子协议。
RLPx 是管理启动、验证和维护节点之间会话的协议。
使用 RLP(递归长前缀)的 RLPx 对消息进行编码。递归长度前缀是一种非常节省空间的编码方法,可将数据编码成最小结构,以便在节点之间发送。
两个节点之间的 RLPx 会话始于初始的加密握手。
这需要节点发送身份验证消息,然后等待对方进行验证。
成功验证后,对方会生成身份确认信息,并将信息返回初始节点。
这是一个密钥交换过程,使节点能够私下安全地进行沟通。
一次成功的加密握手会触发两个节点“在线”互相发送“hello”消息。 线路协议则通过成功地交换“hello”信息发起。
Hello 消息包含:
-
协议版本
-
客戶端 ID
-
端口
-
节点 ID
-
支持的子协议列表
成功交互需要这些信息,因为它们定义节点之间共享的能力并配置通信。
另外还有个子协议协调过程,届时会将每个节点支持的子协议列表进行对比,并能将两个节点共用的子协议用于会话。
除了“hello”消息之外,线路协议还可以发送一个“disconnect”消息,以警告对等点连接将被断开。
线路协议还包含定期发送的 PING 和 PONG 消息,以使会话保持开放。
因此,RLPx 和线路协议之间信息交换为节点之间的通信奠定了基础,并为根据特定子协议交换有用的信息提供了平台。
子协议
线路协议
连接对等点并启动 RLPx 会话后,线路协议定义了对等点间的通信方式。
起初,线路协议定义了三项主要任务:链同步、区块传播和交易交换。
但是当以太坊切换至权益证明之后,区块传播和链同步变为共识层的一部分。
交易交换仍由执行客户端负责。
交易交换所指的是节点之间互相交换待处理的交易,以便矿工能够选择其中一些交易放到下一区块中。
有关这些任务的详细信息可从这里获取。 支持这些子协议的客户端通过 JSON-RPC 将自己公开给网络中的其它部分。
les(以太坊轻客户端子协议)
这是用于同步轻量级客户端的最小协议。 传统上很少使用这一协议,因为全部节点都要求在没有任何奖励的情况下向轻量级客户端提供数据。 执行客户端的默认行为不是通过以太坊轻客户端子协议为轻量级客户端数据提供服务。 更多信息请见以太坊轻客户端子协议规范。
快照
快照协议是一种可选扩展,该扩展使对等点能够交换最近状态的快照,从而无需下载默克尔前缀树的内部节点就能验证帐户信息和存储的数据。
Wit(见证协议)
见证协议也是一种可选扩展,可以使对等点交换彼此的状态见证,从而帮助客户端与链端同步。
耳语
耳语协议旨在实现对等节点之间的安全消息传输,无需向区块链写入任何信息。 它曾是 DevP2P 线路协议的一部分,但现在已经弃用。 其他相关项目也存在类似目标。
共识层
共识客户端参与具有不同规范的单独对等网络。
共识客户端需要参与区块广播,以便它们可以从对等点接收新区块,并在轮到它们成为区块提议者时进行广播。
与执行层类似,这首先需要一个发现协议,以便节点可以找到对等点并建立安全会话,以交换区块、证明等。
发现
与执行客户端类似,共识客户端通过用户数据报协议使用 discv5,用以寻找对等点。
Discv5 的共识层实现与执行客户端的不同之处仅在于它包含一个将 discv5 连接到 libP2P 堆栈的适配器,而且弃用了 DevP2P。
执行层的 RLPx 会话已被弃用,取而代之的是 libP2P 的噪声安全信道握手。
以太坊节点记录
共识节点的以太坊节点记录包括节点的公钥、IP 地址、用户数据报协议和传输控制协议端口,以及两个共识特定字段:证明子网位字段和 eth2 密钥。
前者使节点更容易找到参与特定证明广播子网络的对等点。
eth2 密钥包含有关节点正在使用的以太坊分叉的版本信息,以确保对等点连接到正确的以太坊。
libP2P
LibP2P 堆栈支持发现后的所有通信。 根据其以太坊节点记录的定义,客户端可以在 IPv4 和/或 IPv6 上拨号和收听。 LibP2P 层上的协议可以细分为广播和请求-响应域。
广播
广播域包括必须在整个网络中快速传播的所有信息。
这包括信标块、证明、认证、退出和罚没。
这是使用 libP2P gossipsub v1 传输的,并且依赖于在每个节点本地存储的各种元数据,包括要接收和传输的广播有效载荷的上限。 有关广播域的详细信息可在此处找到。
请求-响应
请求-响应域包含客户端从其对等点请求特定信息的协议。
示例包括请求匹配某些根哈希值或在一定时隙范围内的特定信标块。 响应总是以快速压缩的简单序列化编码字节的形式返回。
为什么共识客户端更偏好简单序列化而不是递归长度前缀?
SSZ 代表简单序列化。
它使用固定偏移量,可以轻松解码编码消息的各个部分,而无需解码整个结构,这对于共识客户端非常有用,因为它可以有效地从编码消息中获取特定信息。
它还专门设计用于与默克尔协议集成,并提升与默克尔化有关的效率。
由于共识层中的所有哈希值都是默克尔树根,因此这带来了显著的改进。
简单序列化还保证了值的唯一表示。
连接执行客户端和共识客户端
共识客户端和执行客户端同时运行。 它们需要彼此连接,这样共识客户端才能向执行客户端提供指令,后者也才能向前者传送需要纳入信标区块的交易捆绑包。
两个客户端之间的通信可通过本地远程过程调用连接实现。 名为“引擎-API”的应用程序接口(Application Program Interface, API) 定义了两个客户端之间发送的指令。
由于两个客户端共用同一个网络身份,因此它们也共享同一个以太坊节点记录 (ENR),其中包含了每个客户端单独的密钥(eth1 密钥和 eth2 密钥)。
下面显示了控制流摘要,括号中是相关的网络堆栈。
当共识客户端不是区块生产者时:
-
共识客户端通过区块广播协议接收区块(共识对等网络)
-
共识客户端预先验证区块,即确保它来自具有正确元数据的有效发送人
-
区块中的交易作为执行有效载荷发送到执行层(本地远程过程调用连接)
-
执行层执行交易并验证区块头中的状态(即检查哈希匹配度)
-
执行层将验证数据传回共识层,现认为区块已验证(本地远程过程调用连接)
-
共识层将区块添加到自己的区块链头并对其进行证明,通过网络广播认证(共识对等网络)
当共识客户端是区块生产者时:
-
共识客户端收到通知,指出它将成为下一个区块的生产者(共识对等网络)
-
共识层在执行客户端调用 create block 方法(本地远程过程调用)
-
执行层访问由交易广播协议填充的交易内存池(执行对等网络)
-
执行客户端将交易打包为一个区块、执行交易并生成一个区块哈希
-
共识客户端从执行客户端获取交易和区块哈希,并将它们加入信标区块(本地远程过程调用)
-
共识客户端通过区块广播协议广播区块(共识对等网络)
-
其他客户端通过区块广播协议接收提议的区块,并如上所述进行验证(共识对等网络)
区块被足够多的验证者认证后,就会被添加到链头,经过合理化并最终确定。
网络地址
以太坊节点必须用一些基本信息来表明身份,才能连接到其他节点。
为了确保任何潜在对等点能够解析这些信息,它以任何以太坊节点能够理解的三种标准化格式之一进行传递:multiadder、enode 或以太坊节点记录 (ENR)。
以太坊节点记录是以太坊网络地址的现行标准。
Multiaddr
原始以太坊节点地址格式为“multiaddr”(“多地址”的缩写)。 Multiaddr 是一种通用格式,用于对等网络。
地址以键值对表示,键与值之间用正斜杠隔开。
例如,使用 IPv4 地址 192.168.22.27 监听 TCP 端口 33000 的节点可能具有以下类似的 multiaddr:
/ip4/192.168.22.27/tcp/33000
对于以太坊节点,multiaddr 包含节点 ID(其公钥的哈希值):
/ip4/192.168.22.27/tcp/33000/p2p/5t7Nv7dG2d6ffbvAiewVsEwWweU3LdebSqX2y1bPrW8br
Enode
Enode 使用 URL 地址格式来识别以太坊节点。
十六进制节点 ID 编码为 URL 的用户名部分,采用 @ 符号与主机分隔开来。
主机名只能作为 IP 地址给出;不允许给出 DNS 名称。
主机名部分中的端口是 TCP 监听端口。 如果传输控制协议和用户数据报协议(发现)端口不同,用户数据报协议端口将被指定为查询参数“disposport”
在下面的例子中,节点 URL 描述了一个 IP 地址为 10.3.58.6、TCP 端口为 30303、UDP 发现端口为 30301 的节点。
enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301
以太坊节点记录 (ENR)
以太坊节点记录 (ENR) 是以太坊网络地址的标准格式。
这种地址取代了 multiaddr 和 enode。
由于它们使节点之间能够进行更多的信息交流,因而尤其实用。
以太坊节点记录包含一个签名、序列号和字段,详细说明了用于生成和验证签名的身份识别方案。
以太坊节点记录还可以填充为采用键值对格式的任意数据。
这些键值对包含节点的 IP 地址和节点能够使用的子协议的信息。
共识客户端使用特定的以太坊节点记录结构来识别引导节点,并包括一个 eth2 字段,其中包含有关当前以太坊分叉和认证信息传播子网的信息。
上述子网将节点连接至证明被整合在一起的特定对等点集。
参考资料
https://ethereum.org/zh/developers/docs/networking-layer/
https://ethereum.org/zh/developers/docs/networking-layer/network-addresses/