[ 10 / 16 ] · Monad课程

第 10 课:Monad 的网络层

8 分钟200 XP

踏上构建生产级应用的旅程。

在「架构」那一课里,你已经知道:每条区块链的网络层(network layer)都做两件事——节点发现(peer discovery,找到其他节点)和节点通信(node communication,与它们对话)。说起来很简单。真正有意思的是怎么做。网络层的设计选择,决定了共识可以跑多快、验证者集合(validator set)能怎样组织,以及区块能多可靠地送达每一个节点。

这一课讲 Monad 的网络层是怎么工作的。不写代码,只讲心智模型。


为什么网络层重要

你可以设计出全世界最聪明的共识算法。但如果运行它的节点找不到彼此、无法可靠地传递消息、跟不上彼此的广播,那这个算法就是没用的。

网络层就是「水管系统」。它上面的一切(共识、执行、应用)都依赖这套水管能正常工作。当一条链对外宣称某个出块时间时,那个数字本质上受限于:一份区块提案能多快到达每一个验证者,以及它们的投票能多快返回。

所以网络层的设计选择不是细节,而是上层一切的约束。


设计目标

Monad 的网络层是为了在不牺牲去中心化的前提下,做到亚秒级确定性最终性(sub-second deterministic finality)。验证者集合是无许可(permissionless)的:只要质押足够,任何人都可以成为验证者,验证者也不由某个中心方挑选。不同之处在于:节点一旦进入这个集合后,它们怎样找到彼此。

最早一代可编程区块链使用的是这样一种发现模型:节点从对网络一无所知开始,沿着分布式哈希表(distributed hash table)一路爬,找到对等节点。区块通过 gossip 传播,从一个节点跳到另一个节点,直到饱和。这种模型放在 12 秒的出块预算下绰绰有余。它确立了什么是可行的,也暴露了那些真正棘手的问题。

亚秒级最终性是另一种问题。现在每一毫秒的网络开销都重要,因为整个预算就是 800ms。需要好几秒才能收敛的发现机制不行。跳数不固定的 gossip 传播也不行。网络层必须按更紧的规格构建,同时还要保持验证者集合的开放性。

下面是 Monad 的做法。


启动引导:种子地址

启动一个 Monad 节点(node)时,你会从一份配置文件和一组来自 MonadBFT 协议的预设种子地址(seed addresses)开始。这些就是你最初的连接入口。

你在启动时就知道该跟谁通信。不需要遍历路由表,也不需要等节点发现机制收敛。对于一个共识以 400ms 出块间隔运行的网络来说,提前知道自己的「花名册」至关重要。


节点发现:验证者通讯录

从种子地址出发,你的节点会请求一份验证者通讯录(validator contact list):当前验证者集合里每个成员的 IP 地址加上密码学签名。你的节点验证这些签名,确认每个联系人确实是它声称的那个验证者,然后直接与它们建立连接。

这份通讯录是结构化分发的,而不是「自己刨」出来的。每个节点都知道完整的验证者集合,也能推理出谁该跟谁通信。整个拓扑(topology)是可预测的——这正是共识在亚秒级速度下运行所需要的。

一个 Monad 节点是如何找到它最初的对等节点的?


节点通信:专用端口、签名消息

节点在一个专用端口(dedicated port)上通信。这一点在运维上很关键:当流量没有跟其他十几种协议复用在一起时,防火墙规则、DDoS 防护和监控都会简单得多。

每条消息都带有一个数字签名,用来证明发送方的身份。除非一条消息能被某个已知验证者用密码学方式认证过,否则它不会被信任。这从根本上排除了一整类依赖匿名消息注入的攻击。

当你的节点刚启动、还没有完成同步时,区块头(block headers)会立刻开始到达,但你的节点会先把它们缓冲起来,而不是直接处理。等你的状态追上之后,再把缓冲下来的区块头依次应用,整个过程不会漏掉任何东西。


追赶进度:状态同步与区块同步

一个落后于链顶(tip)的节点有两种追赶方式,用哪一种取决于它落后多少。

状态同步(state sync) 适用于落后很多、或者全新启动的节点。你的节点从已经同步的对等节点那里下载状态快照(state snapshots):账户余额、合约存储、合约字节码(contract bytecode)。这能把你从「什么都不知道」迅速带到「截至某个近期区块的所有状态都知道」。你是在拷贝答案,而不是回放历史去算出答案。

区块同步(block sync) 适用于只比链顶落后几个区块的节点。这种情况下再跑一次状态同步就太重了。直接请求那几个缺失的区块然后应用上去更快。区块同步也用于处理单个错过的区块——比如某个验证者由于丢包错过了一个区块时的恢复。

如果要重放从创世块以来的每一个区块,按照 Monad 的吞吐量是不可承受的。「双工具」组合——落后很多时用快照、接近链顶时直接请求区块——才是高吞吐链该有的形状。

为什么 Monad 同时使用状态同步和区块同步,而不是直接重放每一个历史区块?


RaptorCast:解决 leader 的带宽问题

下面这个问题,是每一条高吞吐链都必须解决的硬骨头。

在共识过程中,一个验证者会轮流担任 leader。leader 提议一个区块,其他每一个验证者都需要拿到这个区块的副本才能投票。在 Monad 上,leader 每 400 毫秒轮换一次。

最朴素的办法是:leader 把完整的区块(可能有 2MB)直接发给每一个验证者。这个方案不可扩展。验证者数量上几百时,leader 就需要在不到 400ms 的时间里推送数百兆数据。即便在快网下,这也是个瓶颈。

Monad 的解决方案是 RaptorCast。leader 不再把整块发给每一个验证者,而是:

  1. 把区块用纠删码(erasure coding)切成数千个小块。 纠删编码意味着只要拿到足够多的块,就能重建原始区块。如果你切出 1000 个块,任意 600 个就足够还原原始区块。
  2. 以两级扇出(two-level fan-out)的方式分发这些块。 leader 把块发给第一层验证者。每个第一层验证者再把自己收到的块转发给下一层。一个源头,许多搬运工。这跟 CDN 分发一个大文件的思路很像。

效果是:leader 的带宽开销被分摊到整个验证者集合上,而不是集中在一台机器上。没有任何一个节点会成为瓶颈。亚秒级出块时间因此变得可能,又不要求每个验证者都拥有不切实际的上行带宽。

RaptorCast 处于网络与共识的交界处。它就是「区块数据如何在 MonadBFT 所需的时间预算内,从 leader 物理性地搬运到所有人手里」的答案。

RaptorCast 解决的是什么问题?


一笔交易在网络中的旅程

让我们把这些拼图合起来,跟着一笔交易从你的钱包走到最终状态(finalized state)。

  1. 提交。 你在钱包里签好一笔交易,发到一个 RPC 端点。RPC 节点接收你的交易,然后把它转发进网络。
  2. 抵达 leader。 交易流到当前担任 leader 的验证者。leader 在 MonadBFT 中每 400ms 轮换一次。
  3. leader 提议区块。 leader 的轮次开始时,它把待处理的交易(包括你这笔)打包成一个区块提案。
  4. RaptorCast 分发区块。 leader 用纠删码把区块切成若干小块,再以两级扇出的方式发出去。在 400ms 窗口的一个小段时间内,每个验证者都拿到了足够的块来重建完整区块。
  5. 验证者投票。 每个验证者验证区块、签出投票,通过专用网络通道发送给下一个 leader。
  6. 两轮投票。 MonadBFT 用两轮投票来完成最终性确认,整个过程落在 800ms 以内。
  7. 执行(异步进行)。 当共识在就第 N+1 个区块达成一致时,第 N 个区块里你的那笔交易由执行层(execution layer)在后台执行,状态在后台更新。
  8. 最终。 在交易到达 leader 后大约 800ms,包含你那笔交易的区块完成最终性确认,无法再被回滚。

每一步都依赖网络层正常工作。是这套水管让整个时间预算成为可能。


回顾

每条区块链的网络层都做两件事:节点发现和节点通信。它怎么做这两件事,决定了上面一切的节奏。

Monad 的答案是:

  • 种子地址启动引导,让节点在启动时就知道该联系谁。
  • 签名版的验证者通讯录,让拓扑结构化、可预测。
  • 专用端口加签名消息,让节点间流量经过认证、运维上也干净。
  • 状态同步加区块同步,让节点不用重放历史就能追上链顶。
  • RaptorCast,让 leader 在 400ms 窗口内把一个区块送达整个验证者集合。

这每一项,都是亚秒级最终性逼着你回答的某个具体问题的答案。把它们放到一起,上层共识层就拥有了击中 800ms 最终性所需要的物理交付保证。没有这一层,本课程其余部分所建立的性能数字根本无法实现。


接下来是什么

到这里,「Monad 在底层是怎么工作的」这一段就讲完了。从下一课开始,焦点会切回到你正在上层构建的那个应用本身。接下来是 支付(加密)(Payments (Crypto))——为产品加上每个真正产品都需要的一件事:收钱的方式。

0/3 正确

0% — 全部答对即可完成

注册以记录进度