54个版本 (35个破坏性)
0.44.0 | 2024年7月18日 |
---|---|
0.41.0 | 2024年6月24日 |
0.37.0 | 2024年3月18日 |
0.33.0 | 2023年12月13日 |
0.8.0-alpha.5 | 2020年3月24日 |
#1 in #substreams
11,476 每月下载量
用于 129 个crate(39 个直接使用)
2MB
46K SLoC
Substrate特定P2P网络。
重要:此crate是不稳定的,API和用法可能会更改。
节点标识和地址
在去中心化网络中,每个节点都拥有一个网络私钥和一个网络公钥。在Substrate中,密钥基于ed25519曲线。
从节点的公钥中,我们可以推导出其 身份。在Substrate和libp2p中,节点的身份用PeerId
结构表示。网络中节点之间的所有网络通信都使用从双方密钥派生的加密,这意味着 身份不能伪造。
节点的身份唯一标识网络上的机器。如果您使用相同的网络密钥启动两个或更多客户端,将会发生大量的干扰。
Substrate的网络协议
基底的联网协议基于libp2p。目前既不可能也不计划允许使用除了libp2p网络栈和rust-libp2p库之外的任何其他东西。然而,libp2p框架非常灵活,rust-libp2p库可以被扩展以支持比libp2p提供的更广泛的协议。
发现机制
为了使我们的节点加入点到点网络,它必须知道属于该网络的一组节点列表。这包括节点标识和它们的地址(如何到达它们)。构建这样的列表被称为<强>发现强>机制。Substrate使用三种机制:
- 引导节点。这些是硬编码的节点标识和地址,它们随网络配置一起传递。
- mDNS。我们在本地网络上执行UDP广播。监听节点可能会响应其身份。更多信息请见这里。mDNS可以在网络配置中禁用。
- Kademlia随机游走。一旦连接,我们就会在配置的Kademlia DHT(每个配置的链协议一个)上执行随机的Kademlia
FIND_NODE
请求,以便节点将其对网络的看法传播给我们。有关Kademlia的更多信息可以在维基百科上找到。
连接建立
当节点Alice知道节点Bob的身份和地址时,它可以与Bob建立连接。所有连接都必须始终使用加密和复用。虽然某些节点地址(例如使用/quic
的地址)已经隐含了要使用哪种加密和/或复用,但对于其他节点,则使用<强>多流选择强>协议来协商加密层和/或复用层。
连接建立机制被称为<强>传输强>。
根据编写本文档时的情况,Substrate支持以下底层协议
- TCP/IP,用于形式为
/ip4/1.2.3.4/tcp/5
的地址。一旦TCP连接打开,就会在上层协商加密和复用层。 - WebSockets,用于形式为
/ip4/1.2.3.4/tcp/5/ws
的地址。首先建立一个TCP/IP连接,然后在上层协商WebSockets协议。然后,通信在WebSockets数据帧内进行。在这个通道内部再次协商加密和复用。 - DNS,用于形式为
/dns/example.com/tcp/5
或/dns/example.com/tcp/5/ws
的地址。一个节点的地址可以包含域名。 - (所有上述内容使用IPv6代替IPv4。)
在底层协议之上,协商并应用Noise协议。确切的握手协议是实验性的,可能会更改。
以下复用协议受到支持
子流
一旦建立了连接并使用复用,就可以打开子流。当子流打开时,使用<强>多流选择强>协议协商该子流上要使用的协议。
特定于某个链的协议在其名称中有<protocol-id>
。这个“协议ID”在链规范中定义。例如,Polkadot的协议ID是“dot”。在以下协议名称中,<protocol-id>
必须替换为相应的协议ID。
<强>注意强>:同一个连接可以被多个链使用。例如,可以在同一个连接上使用
/dot/sync/2
和/sub/sync/2
协议,前提是远程端支持它们。
Substrate使用以下标准libp2p协议
/ipfs/ping/1.0.0
. 我们定期打开一个短暂的子流来ping远程并检查连接是否仍然活跃。远程未回复将导致断开连接。/ipfs/id/1.0.0
. 我们定期打开一个短暂的子流来从远程获取信息。/<protocol_id>/kad
. 我们定期打开短暂的子流进行Kademlia随机游走查询。每个Kademlia查询都在一个单独的子流中完成。
此外,Substrate还使用以下非libp2p标准协议
/substrate/<protocol-id>/<version>
(其中<protocol-id>
必须替换为目标链的协议ID,而<version>
是介于2和6之间的数字)。对于每个连接,我们可选择保持一个额外的子流以保持所有基于Substrate的通信活跃。此协议被认为是遗留协议,正在逐步被替代方案取代。在本文档中,这被指定为“遗留的Substrate子流”。下面有更多详细信息。/<protocol-id>/sync/2
是一个请求-响应协议(见下文),允许执行有关块的信息请求。每个请求是BlockRequest
的编码,每个响应是BlockResponse
的编码,如在此源树中定义的api.v1.proto
文件中所述。/<protocol-id>/light/2
是一个请求-响应协议(见下文),允许执行与轻客户端相关的关于状态的信息请求。每个请求是light::Request
的编码,每个响应是light::Response
的编码,如在此源树中定义的light.v1.proto
文件中所述。/<protocol-id>/transactions/1
是一个通知协议(见下文),其中将交易推送到其他节点。握手在双方都是空的。消息格式是包含交易列表的SCALE编码,其中每个交易是一个不可见的字节序列列表。/<protocol-id>/block-announces/1
是一个通知协议(见下文),其中将区块公告推送到其他节点。握手在双方都是空的。消息格式是包含区块头和一个包含与该区块公告相关数据的不可见字节序列的SCALE编码元组,例如候选消息。- 使用
NetworkConfiguration::notifications_protocols
注册的通知协议。例如:/paritytech/grandpa/1
。下面有更多信息。
遗留的Substrate子流
底层数据使用了一个名为 peerset 管理器(PSM) 的组件。通过发现机制,PSM 知道网络中的节点,并决定我们应与哪些节点进行基于 Substrate 的通信。对于这些节点,如果需要,我们将打开一个连接,并为基于 Substrate 的通信打开一个唯一的子流。如果 PSM 决定我们应该断开一个节点的连接,则关闭该子流。
有关 PSM 的更多信息,请参阅 sc-peerset 模块。
请注意,目前还没有机制来解决连接的两端同时打开唯一子流所引发的问题。为了避免出现此类问题,仅允许连接的拨号端打开唯一的子流。当子流关闭时,整个连接也将关闭。这是一个将通过完全弃用协议来解决的问题。
在唯一的 Substrate 子流中,使用 [parity-scale-codec``](https://github.com/paritytech/parity-scale-codec) 编码的消息进行交换。这些消息的详细信息尚未完全确定,但可以在 message.rs 文件中找到。
子流打开后,第一步是交换双方的状态消息,其中包含诸如链根哈希、链头等信息。
该子流中的通信包括
- 同步。向其他节点宣布和请求区块。
- 轻客户端请求。当轻客户端需要信息时,我们选择一个与我们打开子流的随机节点,并从它那里请求信息。
- 传播。例如,由 grandpa 使用。
请求-响应协议
所谓的请求-响应协议定义如下
- 当打开子流时,打开方发送一个内容为协议特定的消息。该消息必须以一个表示其长度的 LEB128 编码的数字 开头。消息发送后,写入方关闭。
- 远程发送回带有 LEB128 编码长度的响应,并关闭其端点。
每个请求都在一个新的独立子流中执行。
通知协议
所谓的通知协议定义如下
- 当打开子流时,打开方发送一个内容为协议特定的握手消息。握手消息必须以一个表示其长度的 LEB128 编码的数字 开头。握手消息的长度可以为 0,在这种情况下,发送方必须发送单个
0
。 - 接收方随后要么立即关闭子流,要么以自己的 LEB128 编码的协议特定握手响应进行回答。消息的长度可以为 0,在这种情况下,必须发送单个
0
。 - 握手完成后,通知协议是单向的。只有启动子流的节点可以推送通知。如果远程想要发送通知,它必须打开自己的单向子流。
- 每个通知都必须以一个 LEB128 编码的长度开头。消息的编码对每个协议都是特定的。
- 任何一方都可以通过关闭其写入端来表示它不再需要通知子流。另一方应在之后尽快关闭其自己的写入端。
sc-network
的API允许注册用户定义的通知协议。对于每个已打开旧式子流的所有节点,sc-network
会自动尝试打开一个子流。然后,自动执行握手。
例如,sc-consensus-grandpa
仓库注册了/paritytech/grandpa/1
通知协议。
目前,为了与旧版本兼容,通知协议绑定到旧的Substrate子流。此外,握手消息被硬编码为表示节点角色的单个8位整数
- 1代表全节点。
- 2代表轻节点。
- 4代表权威节点。
但是,将来,这些限制将被移除。
同步
该库实现了一系列同步算法。同步算法的主要目的是将链同步到最新状态,并通过下载和导入新数据来保持与网络其他部分的同步,这些数据一旦可用。一旦节点启动,它就会使用以下列出的初始同步方法之一赶上网络,一旦完成,就会使用保持同步来下载新的块。
全同步和轻同步
这是初始和保持同步的默认同步方法。算法从当前最佳块开始,如果有可用,则从多个对等点渐进式下载块数据。一旦有连续的块准备导入,它们就会进入导入队列。全节点下载和执行完整块,而轻节点只下载和导入头部。这个过程会一直持续到每个对等点都没有更多的新块提供。
对于每个对等点,同步会维护与我们与该对等点的共同最佳块的数目。这个数字会在对等点宣布新块或我们的最佳块提升时更新。这允许跟踪具有新块数据的对等点,并在宣布后尽快请求新信息。在保持同步模式下,我们还会跟踪宣布所有分支而不是最佳分支的块的节点。同步算法会尝试贪婪地下载所有宣布的数据。
快速同步
在此模式下,初始下载并验证完整头部历史。这允许验证权威集转换并到达最近的头部。在验证并导入头部链后,节点开始使用状态请求协议下载状态快照。每个StateRequest
包含一个起始存储密钥,对于第一个请求是空的。StateResponse
包含从请求中的密钥开始的存储证明序列(但不包括该密钥)。在迭代证明 trie 与目标头部中的存储根后,节点发出下一个StateRequest
,并将起始密钥设置为上一个响应中的最后一个密钥。这会一直持续到 trie 迭代达到末尾。然后,状态被导入数据库,并开始以正常全/轻同步模式进行保持同步。
快速同步
这与快速同步类似,但算法只下载并验证最终确定的权威集更改。
GRANDPA快速同步
GRANDPA保存每个最终确定的权威集更改的证明。每个更改都由前一个集的权威签名。通过从创世纪开始下载和验证这些签署的交接,我们可以比下载完整的头部链更快地到达最近的头部。每个WarpSyncRequest
包含一个指向开始收集证明的块哈希。 WarpSyncResponse
包含一系列块头部和证明。证明下载器检查证明,并从最后一个头部哈希继续请求证明,直到到达某个最近的头部。
一旦证明了一个区块头的最终性链,与该区块头匹配的状态就会像快速同步期间一样被下载。该状态会被验证以确保与区块头存储根相匹配。在将状态导入数据库后,它会查询允许GRANDPA和BABE从该状态继续操作的信息。这包括BABE时代信息和GRANDPA权限集ID。
背景区块下载
在最新状态被导入后,节点完全可用,但仍缺少历史区块数据。也就是说,它无法提供除最新区块外的其他区块体和头信息。为确保所有节点都有区块历史记录,启动一个后台同步过程,下载所有缺失的区块。它与保持同步并行运行,不会干扰最近区块的下载。在此下载期间,我们还导入具有权限集变更的区块的GRANDPA证明,以便warp同步的节点拥有为其他节点提供服务的所有数据,这些节点可能希望使用任何方法从中同步。
用法
通过sc-network
包是通过NetworkWorker
结构来实现的。通过传递NetworkWorker
,创建此结构,然后像处理Future
一样轮询它。您可以从NetworkWorker
中提取一个Arc<NetworkService>
,以便在多个位置共享,以便对网络下达命令。
有关如何配置网络的更多信息,请参阅config
模块。
在创建NetworkWorker
之后,需要执行的重要操作包括:
- 通过调用
NetworkWorker::poll
来推进网络。这可以通过使用NetworkWorker
调度一个后台任务来实现。 - 每当一个区块被添加到客户端时,调用
on_block_import
。 - 每当一个区块被最终确定时,调用
on_block_finalized
。 - 当事务被添加到池中时,调用
trigger_repropagate
。
更精确的使用细节仍在开发中,并可能在未来发生变化。
许可证:GPL-3.0-or-later WITH Classpath-exception-2.0
依赖项
~71–110MB
~2M SLoC