跳至主要内容
此页面是从英文翻译而来的。请注意,与原始页面相比,可能会出现错误或差异。真实的文档来源应始终是英文版本。

网络对等协议

1。概述

EOS 区块链上的节点必须能够相互通信,以便在节点之间中继交易、推送区块和同步状态。点对点 (p2p) 协议,其中的一部分 nodeos 在每个节点上运行的服务可以达到这个目的。同步状态的能力对于每个区块最终在区块链的全局状态下达到最终状态并允许每个节点推进最后一个不可逆区块(LIB)至关重要。在这方面,p2p协议的基本目标是同步区块并在节点之间传播交易,以达成共识并推进区块链状态。

1.1。目标

为了将多个交易添加到一个区块中,并在指定的0.5秒生产时间内进行调整,p2p协议的设计必须考虑到速度和效率。这两个目标转化为在有效带宽范围内最大限度地提高交易吞吐量,并减少网络和运营延迟。实现这一目标的一些策略包括:

  • 在一个区块内容纳更多交易,以实现更好的规模经济。
  • 尽量减少区块和交易之间的冗余信息。
  • 允许更高效地广播和同步节点状态。
  • 通过数据压缩和二进制编码最大限度地减少有效载荷占用空间。

这些策略中的大多数都是在EOS软件中完全或部分实现的。数据压缩是可选的,是在事务级别实现的。通过网络发送对象实例和协议消息时,网络序列化器会实现二进制编码。

2。建筑

p2p 协议的主要目标是安全高效地同步节点。为了实现这一总体目标,系统将功能委托为四个主要组件:

  • Net Plugin:定义用于在对等方之间同步区块和转发交易的协议。
  • Chain Controller:在节点内调度/管理收到的区块和交易。
  • Net Serializer :序列化消息、区块和交易以进行网络传输。
  • 本地链:保存节点的区块链本地副本,包括可逆区块。

上述组件之间的相互作用如下图所示:

最高级别是 Net Plugin,它在节点与其对等方之间交换消息以同步区块和交易。典型的消息流如下:

1.节点 A 通过 Net 插件向节点 B 发送消息(参见上图)。 1.节点 A 的 Net Serializer 打包消息并将其发送到节点 B 2.节点 B 的 Net Serializer 解压缩消息并将其中继到其 Net 插件。 2.消息由节点 B 的 Net 插件处理,调度正确的操作。 3.如有必要,网络插件通过链控制器访问本地链以推送或检索方块。

2.1。本地连锁店

本地链是节点的区块链本地副本。它由节点收到的不可逆和可逆区块组成,每个区块都以密码方式链接到前一个区块。不可逆区块列表包含不可变区块链的实际副本。可逆区块列表的长度通常较短,当链控制器向其推送区块时,它由分叉数据库管理。本地连锁店如下所示。

![](/images/protocol-p2p_local_chain.png "Local Chain (before pruning)“)

每个节点在接收区块和交易并与其他节点同步其状态时,都会构建自己的区块链本地副本。可逆方块是那些收到但尚未进入终结状态的新方块。因此,它们很可能会形成源自主要共同祖先的分支,即LIB(最后一个不可逆的区块)。其他与 LIB 不同的常见祖先也可以用于可逆方块。实际上,任何两个兄弟分支总是有最接近的共同祖先。例如,在上图中,方块 52b 是从区块 53a 和 53b 开始的分支的最接近的共同祖先,它与 LIB 不同。本地链中的每个活跃分支都有可能成为区块链的一部分。

2.1.1。LIB 方块

在节点中构造的所有不可逆方块应与来自其他节点的不可逆区块 (LIB) 相匹配,直到每个节点的最后一个不可逆区块 (LIB)。这就是区块链的分布式性质。最终,当 LIB 区块之后的方块达到终点时,LIB 区块在追上头块 (HB) 时通过其中一个分支向上移动。当 LIB 区块向前推进时,不可变的区块链实际上会增长。在此过程中,Head block 可能会根据收到的潜在头块数量及其时间戳多次切换分支,时间戳最终被用作决胜局。

2.2。链条控制器

Chain Controller 管理区块和交易的基本操作,这些操作会改变本地链状态,例如验证和执行交易、推送区块等。链控制器接收来自 Net Plugin 的命令,并根据网络插件收到的网络消息在区块或交易上调度正确的操作。当EOS节点相互通信以同步区块和交易状态时,网络消息在EOS节点之间持续交换。

2.2.1。Signals 的制作者和消费者

控制器中定义的信号的产生者和使用者及其正常操作、分叉和重播期间的生命周期如下:

pre_accepted_block(carry signed_block_ptr)

-由... 制作

模块函数条件
控制器push_block在将方块添加到 fork db 之前
replay_push_block在将重播的方块添加到 fork db 之前(前提是重播的方块不是不可逆的,因为在重播期间没有将不可逆的方块添加到 fork db 中)

-消耗者

模块用法
chain_plugin检查点验证
将数据转发到 pre_accepted_block_channel
accepted_block_header(carry block_state_ptr)

-由... 制作

模块函数条件
controllerpush_block在方块被添加到 fork db 之后
commit_block在将区块添加到 fork db 之后(前提是你是生成区块的人,换句话说,这不适用于从其他人那里收到的区块)
replay_push_block在将重播的方块添加到 fork db 之后

-被消耗者

模块用法
chain_plugin转发数据到 accepted_block_header_channel
accepted_block(carry block_state_ptr)

-由... 制作

模块函数条件
控制器commit_block区块完成时

-消耗者

模块用法
net_plugin向其他同行广播区块
不可逆转_block(carry block_state_ptr)

-由... 制作

模块函数条件
controllerlog_revirture在它被追加到区块日志之前和提交链库数据库之前
replay_push_block重玩不可逆方块时

-消耗者

模块用法
控制器设置 wasm_interface 的当前库
chain_plugin将数据转发到不可逆转_block_channel
accepted_transaction_ptr(进位交易_metadata_ptr)

-由... 制作

模块函数条件
controllerpush_transaction当交易成功执行时(只有一次,即当它未应用并重新应用时,信号不会发出)
push_scheduled_transaction计划事务成功执行时
当计划交易失败时(主观/软/硬)
计划交易何时到期
应用后 onerror

-消耗者

模块用法
chain_plugin将数据转发到已接受的交易频道
applied_transaction (carry std:: tuple<const transaction_ptr&,const signed_transaction&>)

-由... 制作

模块函数条件
控制器push_transaction当事务成功执行时
push_scheduled_transaction计划事务成功执行时
当计划交易失败时(主观/软/硬)
计划交易何时到期
应用后 onerror

-消耗者

模块用法
chain_plugin将数据转发到应用交易频道
bad_alloc

未使用。

2.2.2。信号的生命周期

A. 输入区块和交易的正常操作

1.当交易被推送到区块链时(通过 RPC 或由对等方广播) 1.交易要么成功执行,要么验证失败-> accepted_transaction 由控制器发出 2. chainplugin 会对信号做出反应,将 transaction_metadata 转发到 accepted_transaction 2.当计划交易被推送到区块链时 1.交易执行成功/主观失败/软失败/硬失败-> accepted_transaction 由控制器发出 2. chainplugin 会对信号做出反应,将 transaction_metadata 转发到 accepted_transaction 3.当区块被推送到区块链时(通过 RPC 或由对等方广播) 1.在将区块添加到 fork db 之前-> pre_accepted_block 将由控制器发出 2. chain_plugin 将对区块进行验证的信号做出反应,将 block_state 转发到 accepted_block_header_channel 并使用检查点对其进行验证 3.将区块添加到 fork db 之后-> accepted_block_header 将由控制器发出 4. chain_plugin 会对信号做出反应,将 block_state 转发到 accepted_block_header_channel 5.然后将应用区块,此时区块内的所有交易和scheduled_transactions都将被推送。所有与 push_transaction 和 push_scheduled_transaction 相关的信号(参见 A.1 和 A.2 点)都将发出。 6.提交区块时-> accepted_block 将由控制器发出 7. net_plugin 会对信号做出反应并将区块广播给对等方 8.如果新区块变得不可逆转,则将发出与不可逆区块相关的信号(参见第 A.5 点) 4.生成方块时 1.对于您生成的区块,该区块将在提交时添加到 fork_db 中-> accepted_block_header 将由控制器发出 2. chain_plugin 将对信号做出反应,将 block_state 转发到 accepted_block_header_channel 并使用检查点对其进行验证 3.紧接着(在提交区块期间)-> accepted_block 将由控制器发出 4. net_plugin 会对信号做出反应并将区块广播给对等方 5.如果新区块变得不可逆转,则将发出与不可逆区块相关的信号(参见第 A.5 点) 5.当区块变得不可逆时 1.一旦区块被视为不可逆转-> irreversible_block 将在区块附加到区块日志并提交 chainbase db 之前由控制器发出 2. chain_plugin 会对信号做出反应,将 block_state 转发到 irreversible_block_channel,还会设置 wasm_interface 的库

B. 呈现和解决分叉的操作

1.当出现分叉时,区块链会将所有现有区块弹出到分叉点,然后在分叉中应用所有新区块。 2.应用新区块时,区块内的所有交易和scheduled_transactions都将被推送。所有与 push_transaction 和 push_scheduled_transaction 相关的信号(参见 A.1 和 A.2 点)都将发出。 3.然后在提交新区块时-> accepted_block 将由控制器发出 4. net_plugin 会对信号做出反应并将区块广播给对等方 5.如果新区块变得不可逆转,则将发出与不可逆区块相关的信号(参见第 A.5 点)

C. 普通重播(有或没有重播优化)

1.重播不可逆方块时-> irreversible_block 将由控制器发出 2.请参阅 A.5 了解操作方法 irreversible_block 信号已响应 3.重播可逆方块时,在将方块添加到 fork_db 之前-> pre_accepted_block 将由控制器发出 4.重播可逆方块时,将方块添加到 fork db 之后-> accepted_block_header 将由控制器发出 5.重玩可逆方块时,当方块提交时-> accepted_block 将由控制器发出 6.请参阅 A.3 了解操作方法 pre_accepted_block, accepted_block_headeraccepted_block 信号已响应

2.2.3。分叉数据库

Fork 数据库 (Fork DB) 为链控制器提供了一个内部接口,用于在节点的本地链上执行操作。当从其他对等方收到新区块时,链控制器会将这些区块推送到 Fork DB。然后,每个区块都以加密方式链接到前一个区块。由于之前可能有多个区块,因此该过程很可能会生成名为 mini-forks 的临时分支。因此,Fork DB 有三个主要用途:

  • 解决推送的方块(新头方块)将从哪个分支上构建。
  • 前进头块、根块和 LIB 方块。
  • 修剪无效分支并清除孤立方块。

从本质上讲,Fork DB 包含节点内的所有候选区块分支,这些分支可能会成为区块链继续发展的实际分支。根块始终标记可逆块树的开始,并且将与 LIB 块匹配,除非当 LIB 向前移动,在这种情况下,根块必须赶上。当 LIB 区块在 Fork DB 中的新区块中前进时,其计算最终将决定选择哪个分支。随着 LIB 区块的推进,根块会赶上新的 LIB,任何其祖先节点位于 LIB 后面的候选分支都会被修剪。如下所示。

![](/images/protocol-p2p_local_chain_prunning.png "Local Chain (after pruning)“)

在上图中,在 LIB 从节点 51 前进到区块 52c 然后 53c 之后,从方块 52b 开始的分支会被修剪(方块 52b、53a、53b 无效)。当 LIB 穿过可逆区块时,它们会从 Fork DB 移动到本地链,因为它们现在已成为不可变区块链的一部分。最后,区块 54d 保留在 Fork DB 中,因为可能仍会从中构建新方块。

2.3。网络插件

网络插件定义了 EOS 节点之间的实际点对点通信消息。Net Plugin 的主要目标是根据请求同步有效区块,并始终转发有效的交易。为此,网络插件将功能委托给以下组件:

  • Sync Manager:保持节点相对于其对等节点的区块同步状态。
  • 调度管理器:维护节点发送的区块和交易列表。
  • 连接列表:节点当前连接的活动对等方列表。
  • 消息处理程序:向相应的处理程序发送协议消息。(请参阅 4.2。协议消息)。

2.3.1。同步管理器

同步管理器实现了在节点与其对等节点之间同步区块状态的功能。它处理每个对等方发送的消息,并根据节点的 LIB 或 head block 相对于该对等方执行区块的实际同步。在任何时候,节点都可能处于以下任何同步状态:

  • LIB Catch-Up :节点即将与另一个对等方的 LIB 区块同步。
  • Head Catch-Up :节点即将与另一个对等方的 HEAD 区块同步。
  • In-Sync:LIB 和 HEAD 模块都与其他对等方同步。

如果节点的 LIB 或 head block 在后面,则该节点将生成同步请求消息,以从连接的对等方检索丢失的块。同样,如果连接的对等方的 LIB 或 head block 在后面,则该节点将发送通知消息,通知节点需要与哪些区块同步。有关同步模式的更多信息,请参阅 3.操作模式

2.3.2。调度经理

调度管理器维护节点收到的区块和松散交易的状态。该状态包含用于识别区块或交易的基本信息,并保存在两个区块状态和交易状态的索引列表中:

  • 区块状态列表:节点为收到的所有区块管理的区块状态列表。
  • 交易状态列表:节点为收到的所有交易管理的交易状态列表。

这使得可以非常快速地找到哪个对等方拥有给定的区块或交易。

2.3.2.1。区块状态

区块状态标识一个区块及其来自哪个对等体。它本质上是短暂的,因此只有在节点处于活动状态时才有效。区块状态包含以下字段:

区块状态字段描述
id256 位区块标识符。区块内容和区块编号的函数。
block_num32 位无符号计数器值,自创世以来按顺序标识方块。
connection_id32 位无符号整数,用于标识区块来自的连接对等体。
have_block表示节点是否已收到实际区块的布尔值。

区块状态列表按区块 ID、区块号和连接 ID 进行索引,以便更快地查找。这允许在列表中查询给定一个或多个索引属性的任何块。

2.3.2.2。交易状态

交易状态标识松散的交易及其来自哪个对等方。它本质上也是短暂的,因此只有在节点处于活动状态时才有效。交易状态包含以下字段:

交易状态字段描述
id交易实例的 256 位哈希,用作交易标识符。
expires自 EOS 区块时间戳纪元(2000 年 1 月 1 日)以来的到期时间。
block_num当前的头块编号。当 LIB 赶上它时,交易就会掉线。
connection_id32 位整数,用于标识交易来自的连接对等方。

那个 block_num 在收到交易时存储节点的头部区块号。无论到期时间如何,它都用作在 LIB 区块号赶上头块编号时丢弃交易的备份机制。

交易状态列表按交易 ID、到期时间、区块号和连接 ID 进行索引,以便更快地查找。这允许在列表中查询给定一个或多个索引属性的任何交易。

2.3.2.3。国家回收

随着 LIB 区块的推进(见 3.3.1。LIB 追赶模式),新 LIB 区块之前的所有区块都被视为已完成,因此它们的状态将从本地区块状态列表中删除,包括节点维护的连接列表中每个对等方拥有的区块状态列表。同样,交易状态会根据到期时间从交易列表中删除。因此,事务到期后,其状态将从所有事务状态列表中删除。

区块状态和交易状态列表占用空间小,轮换率高,因此它们保存在内存中以便更快地访问。节点接收到的区块和交易的实际内容分别临时存储在分叉数据库以及已应用和未应用交易的各种传入队列中。

2.3.3。连接清单

连接列表包含每个对等体的连接状态。它保存有关 p2p 协议版本、节点所知道的区块和交易的状态、节点当前是否正在与该对等方同步、最后一次发送和接收的握手消息、对等方是否已从节点请求信息、套接字状态、节点 ID 等的信息。连接状态包括以下相关字段:

  • 已请求信息:对等方是否已向节点请求信息。
  • 套接字状态:指向保持 TCP 连接状态的套接字结构的指针。
  • 节点 ID:区分对等节点与其他节点的节点的实际节点 ID。
  • 收到的最后一次握手消息:从对等方收到的最后一次握手消息实例。
  • Last Handshake Sent:发送给对等方的最后一次握手消息实例。
  • 握手发送次数:发送给对等体的握手消息数量。
  • 同步:节点是否正在与对等节点同步。
  • 协议版本:由对等方的网络插件实现的内部协议版本。

区块状态由以下字段组成:

  • 区块 ID:区块序列化内容的哈希值。
  • 区块号:自创世以来的实际区块号。

交易状态由以下字段组成:

  • 交易 ID:交易序列化内容的哈希值。
  • 区块号:交易所包含的实际区块号。
  • 到期时间:交易到期的时间(以秒为单位)。

2.4。网络序列化器

网络序列化器有两个主要角色:

  • 序列化需要通过网络传输的对象和消息。
  • 序列化需要进行加密哈希处理的对象和消息。

在第一种情况下,每个序列化的对象或消息在从网络收到后都需要在另一端进行反序列化以进行进一步处理。在后一种情况下,需要对对象实例中的特定字段进行序列化才能生成其内容的加密哈希。为给定对象类型(操作、交易、区块等)生成的大多数 ID 都由对象实例中相关字段的加密哈希组成。

3。操作模式

从操作角度来看,相对于连接的对等体,节点可以处于三种状态之一:

  • 同步模式:节点与对等节点同步,因此不需要该对等方进行任何区块。
  • LIB Catch-Up 模式:节点需要方块,因为 LIB 区块位于该对等方的 LIB 后面。
  • HEAD 追赶模式:节点需要方块,因为 HEAD 方块位于该对等方的 Head 后面。

每个节点的操作模式存储在 nodeos 服务的 Net Plugin 中的同步管理器上下文中。因此,与其连接的对等节点相比,节点始终处于同步模式或追赶模式的某种变体。这允许节点在 LIB 和 head blocks 更新以及从其他对等方接收新的方块时在追赶模式和同步模式之间来回切换。

3.1。区块标识

EOS 软件通过比较两个区块的区块 ID 来检查两个区块是否匹配或包含相同的内容。区块 ID 是一个取决于区块标题和区块号内容的函数(参见 共识协议:5.1。区块结构)。检查两个区块是否相等对于将节点的本地链与其对等节点的本地链同步至关重要。要根据区块内容生成区块 ID,需要序列化区块标头并创建 SHA-256 摘要。为最高有效的 32 位分配区块号,而哈希值中最低有效的 224 位则被保留。请注意,区块标头包括交易 merkle 树和操作 merkle 树的根哈希。因此,区块 ID 取决于区块中包含的所有交易以及每笔交易中包含的所有操作。

3.2。同步模式

在同步模式下,节点的 head block 会被对等方的 head block 赶上,这意味着该节点在区块方面处于同步状态。当节点处于同步模式时,它不会向对等方请求更多区块,而是继续执行其他功能:

  • 验证交易,如果无效,则将其删除;如果有效,则将其转发给其他对等方。
  • 验证区块,如果无效,则将其丢弃;如果有效,则根据请求将其转发给其他对等方。

因此,这种模式以带宽换取延迟,这对于验证依赖于 TapOS(交易作为权益证明)的交易特别有用,因为处理开销较低。

请注意,如果松散的交易有效且未过期,则总是会被转发。另一方面,只有在有效且由对等方明确请求时才会转发区块。这减少了网络开销。

3.3。追赶模式

当节点的头块位于对等方的 LIB 或对等方的 head block 后面时,节点处于追赶模式。如果需要同步,则按两个连续步骤执行:

1.将节点的 LIB 从最近的共同祖先 + 1 同步到对等方的 LIB。 2.将节点的头部从最近的共同祖先 + 1 同步到对等方的头部。

因此,首先更新节点的 LIB 块,然后更新节点的 head 块。

3.3.1。LIB 追赶模式

上面的情况 1,节点的 LIB 块需要赶上对等方的 LIB 块,如下图所示,即同步前后(注意:为了清楚起见,已删除了不适用的分支):

在上图中,通过将已完成的区块 91 和 92(对等方的 LIB)追加到节点的 LIB(区块 90)中,节点的本地链与对等方的本地链同步。请注意,这会丢弃由方块 91n、92n、93n 组成的临时分叉。另请注意,这些节点的后缀为 “n”(节点的缩写),表示它们尚未最终确定,因此可能与对等节点的不同。这同样适用于对等方上未完成的区块;它们以 “p”(对等方的缩写)结尾。同步后,请注意,节点上的 LIB (lib) 和 head block (hb) 的区块号相同。

3.3.2。Head Catch-Up 模式

节点的 LIB 区块与对等方的 LIB 区块同步后,将有新的区块推送到任一链。上面的案例 2 涵盖了 peer 节点的链长于节点链的情况。如下图所示,该图显示了同步前后的节点和对等方的本地链:

在上述任何一种情况 1 或 2 中,节点中的同步过程都涉及从节点的头块开始定位第一个共同祖先块,向后遍历链,最后在 LIB 块中,这些块现在处于同步状态(参见 3.3.1。LIB 追赶模式)。在最坏的情况下,同步的 LIB 是最接近的共同祖先。在上图中,节点的链是从 head block 94n、93n 等遍历的,试图匹配对等方链中的方块 94p、93p 等。匹配的第一个方块是最接近的共同祖先(图中的 93n 和 93p 方块)。因此,以下方块 94p 和 95p 会被检索并附加到节点的链中,紧随最接近的共同祖先,现在重新标记为 93n,p(参见 3.3.3。区块检索 进程)。最后,区块 95p 成为节点的头块,并且由于节点与对等方完全同步,节点切换到同步模式。

3.3.3。区块检索

找到共同祖先后,将发送一条同步请求消息以检索节点所需的区块,从最接近的共同祖先之后的下一个区块开始,以对等方的头块结束。

为了有效利用带宽,需要从不同的对等方获取所需的块,而不仅仅是从一个对等方获得。根据所需的区块数量,通过指定要从给定对等方下载的起始区块号和结束区块号来分块请求区块。节点使用区块状态列表来跟踪每个对等方拥有哪些区块,因此此信息用于确定向哪些连接的对等方请求区块块。此过程如下图所示:

当 LIB 和 head blocks 相对于对等方都被捕获时,同步管理器中的操作模式将从追赶模式切换到同步模式。

3.4。模式切换

最终,节点及其对等节点都会从其他节点那里获得新的新区块,这些节点反过来又将区块推送到各自的本地链。这会导致每条链上的头块向前移动。根据哪条链先生长,会发生以下操作之一:

  • 节点向对等方发送追加请求消息,其中包含其头块信息。
  • 节点发送追赶通知消息,通知节点需要同步。

在第一种情况下,节点将模式从同步模式切换到头部追赶模式。在第二种情况下,对等方在收到来自节点的通知消息后切换到 head catchup 模式。实际上,同步模式是短暂的。当EOS区块链非常繁忙时,节点将大部分时间都花在追赶模式下,验证交易,并在收到追赶消息后同步其链。

4。协议算法

p2p 协议算法在每个节点上运行,转发经过验证的交易和经过验证的区块。从 EOS v2.0 开始,节点还会转发其收到的未经验证的区块的区块 ID。一般来说,简化过程如下:

1.节点请求数据或向对等体发送控制消息。 2.如果可以满足请求,则对等方执行请求;重复 1。

数据消息包含区块内容或交易内容。控制消息使节点与其对等节点之间的区块和交易同步成为可能(参见 协议消息)。为了实现此类同步,每个节点都必须能够检索有关其自身区块和交易状态以及对等节点状态的信息。

4.1。节点/对等体状态

在尝试同步状态之前,每个节点都需要知道自己的区块和交易的当前状态。它还必须能够查询其他对等方才能获得相同的信息。特别是,节点必须能够按需获得以下内容:

  • 每个节点都可以找出当前有哪些区块和交易。
  • 所有节点都可以找出对等节点有哪些区块和交易。
  • 每个节点都可以找出它请求了哪些区块和交易。
  • 所有节点都可以知道每个节点何时收到给定交易。

为了执行这些查询,然后在同步状态时,Net Plugin 定义了要在节点之间交换的特定通信消息。这些消息在通过 TCP 连接传输和接收时由 Net 插件发送。

4.2。协议消息

p2p 协议为点对点节点通信定义了以下控制消息:

控制消息描述
handshake_message启动与另一个对等体的连接并发送 Lib/Head 状态。
chain_size_message向对等方请求 LIB/Head 状态。目前尚未实施。
go_away_message向正在连接或已连接的对等方发送断开连接通知。
time_message传输用于对等同步和错误检测的时间戳。
notice_message通知对等节点当前有哪些区块和交易。
request_message告知对等方当前需要哪些区块和交易节点。
sync_request_message根据区块的起始/结束区块号,请求对等一系列区块。

该协议还定义了以下数据消息,用于在p2p网络上的对等方之间交换区块或松散交易的实际内容:

数据消息描述
signed_block已签名区块的序列化内容。
packed_transaction序列化打包交易的内容。

4.2.1。握手消息

握手消息由节点在连接到另一个对等体时发送。连接节点使用它将其链状态(LIB 编号/ID 和头块编号/ID)传递给对等节点。当节点首次连接时,它还用于在节点上执行基本验证,例如它是否属于同一个区块链,验证字段是否在范围内,检测节点上不一致的区块状态,例如它的 LIB 是否在头区块之前,等等。握手消息由以下字段组成:

消息字段描述
network_version内部网络插件版本,用于跟踪协议更新。
chain_id创世状态和配置选项的哈希值。用于识别链。
node_id将对等节点的节点与其他节点区分开来的实际节点 ID。
key公钥供对等方验证节点;可以是生产者密钥或对等密钥,也可以为空。
time时间戳握手消息是自 epoch(2000 年 1 月 1 日)以来创建的。
tokenSHA-256 时间戳摘要,用于证明节点拥有上述密钥的私钥。
sig节点使用上面密钥的私钥对上面的摘要进行签名。
p2p_address节点的 IP 地址。
last_irreversible_block_num自创世以来 LIB 区块的实际区块数。
last_irreversible_block_idLIB 区块序列化内容的哈希值。
head_num自创世以来头方块的实际区块数。
head_idhead 区块序列化内容的哈希值。
os节点运行的操作系统。这是自动检测到的。
agent节点提供的用于在同级节点中标识自己的名称。
generation计数 handshake_message 调用;检测第一次验证调用。

如果所有检查都成功,则对等体会继续根据连接节点进行身份验证 --allowed-connection 在以下情况下为该对等方的网络插件指定的设置 nodeos 已开始:

  • 任何:无需身份验证即可进行连接。
  • 制作者:对等密钥通过 p2p 协议获得。
  • 指定:对等密钥是通过设置提供的。
  • :节点不允许连接请求。

对等密钥对应于尝试连接到对等节点的节点的公钥。如果身份验证成功,则接收节点通过向连接节点发送握手消息来确认连接节点,连接节点将以与上述相同的方式进行验证。最后,接收节点会检查对等方的 head block 还是自己的头块需要同步。这是通过检查头块的状态和连接节点的 LIB 相对于其自身的状态来完成的。通过这些检查,接收节点决定哪条链需要同步。

4.2.2。链条尺寸信息

链大小消息已定义以备将来使用,但目前尚未实现。当时的想法是在成功连接到另一个 Peer 节点后,发送节点链状态的临时状态通知。链大小消息包含以下字段:

消息字段描述
last_irreversible_block_num自创世以来 LIB 区块的实际区块数。
last_irreversible_block_idLIB 区块序列化内容的哈希值。
head_num自创世以来头方块的实际区块数。
head_idhead 区块序列化内容的哈希值。

链大小消息被握手消息所取代,握手消息还会发送 LIB 和 head blocks 的状态,但包括其他信息,因此它是首选。

4.2.3。走开留言

在关闭连接之前,会向对等方发送消失消息。这通常是由于错误导致节点无法继续使用 p2p 协议所致。消失消息包含以下字段:

消息字段描述
reason表示与对等方断开连接的原因的错误代码。
node_id断开连接的节点的节点 ID;用于重复通知。

当前的原因代码定义如下:

  • 没有理由:表示实际上没有错误;默认值。
  • Self:节点正在尝试自我连接。
  • 重复:检测到来自对等方的冗余连接。
  • 错误的链:对方的链 ID 不匹配。
  • 版本错误:对等方的网络版本不匹配。
  • 分叉:对等方的不可逆方块不同
  • Unlinkable:对等方发送了一个我们无法使用的方块
  • 错误交易:对等方发送的交易验证失败。
  • 验证:对等方发送了一个验证失败的区块。
  • 良性其他:超时等原因。不是致命的,但需要重置。
  • Fatal other:对于尚未隔离的致命错误,一劳永逸。
  • 身份验证:对等体身份验证失败。

对等方收到 go away 消息后,对等方也应关闭连接。

4.2.4。时间消息

时间消息用于同步对等方之间的事件、测量时间间隔和检测网络异常,例如重复消息、无效时间戳、节点损坏等。时间消息由以下字段组成:

消息字段描述
org原始时间戳;在标记时间间隔的开始时设置。
rec接收时间戳;设置消息从网络到达的时间。
xmt传输时间戳;在将消息放入发送队列时设置。
dst目标时间戳;标记时间间隔结束时设置。

4.2.5。通知消息

发送通知消息是为了通知节点当前有哪些阻塞和松散的交易。通知消息包含以下字段:

消息字段描述
known_trx已知事务 ID 节点的排序列表可用。
known_blocks已知区块 ID 节点的排序列表可用。

请注意,消息是轻量级的,因为它们仅包含区块 ID 和交易 ID,而不包含实际的区块或交易。

4.2.6。请求留言

发送请求消息是为了通知节点当前需要哪些阻塞和松散的交易。请求消息包含以下字段:

消息字段描述
req_trx节点所需的已请求交易 ID 的排序列表。
req_blocks节点所需的已请求区块 ID 的排序列表。

4.2.7。同步请求消息

同步请求消息向对等方请求一系列区块。同步请求消息包含以下字段:

消息字段描述
start_block要从对等方接收的区块范围的起始区块号。
end_block要从对等方接收的区块范围的结束区块号。

收到同步请求消息后,对等方将指定区块号范围内的实际区块发送回去。

4.3。消息处理器

p2p 协议使用事件驱动的模型来处理消息,因此在收到消息时不涉及轮询或循环。在内部,每条消息都放置在队列中,行中的下一条消息被分派到相应的消息处理程序进行处理。简而言之,消息处理程序可以定义如下:

   receiver/read handler:
if handshake message:
verify that peer's network protocol is valid
if node's LIB < peer's LIB:
sync LIB with peer's; continue
if node's LIB > peer's LIB:
send LIB catchup notice message; continue
if notice message:
update list of blocks/transactions known by remote peer
if trx message:
insert into global state as unvalidated
validate transaction; drop if invalid, forward if valid
else
close the connection

4.4。发送队列

协议消息被放置在缓冲队列中并发送到相应的连接对等方。在更高级别上,节点以循环方式对每个连接的对等体执行以下操作:

   send/write loop:
if peer knows the LIB:
if peer does not know we have a block or transaction:
next iteration
if peer does not know about a block:
send transactions for block that peer does not know
next iteration
if peer does not know about transactions:
sends oldest transactions unknown to remote peer
next iteration
wait for new validated block, transaction, or peer signal
else:
assume peer is in catchup mode (operating on request/response)
wait for notice of sync from the read loop

5。协议改进

p2p 协议的任何软件更新还必须在所有节点上逐步且一致地扩展。这意味着安装更新可以减少操作停机时间,并可能最大限度地减少停机时间,同时尽可能以向后兼容的方式部署新功能。另一方面,可以通过采取措施最大限度地减少消息占用空间来提高数据吞吐量,例如使用数据压缩和协议消息的二进制编码。