我们要开启一个系列,讲讲我们如何让 GenLayer 尽可能接近 100% 安全。原因很简单:GenLayer 是一个关键任务系统,它托管资金、作出判断。在这里,一个 bug 不只是一次服务中断,还可能意味着很多人损失很多钱。我们对此极为重视。
我们从失效特征相似、任何错误都后果严重的系统中汲取灵感:飞行控制器、工业控制软件。我们采用的方法是纵深防御,即多层相互独立的测试与验证:基于示例的测试,以及每次合并前对整个技术栈的端到端运行;对照参考模型的生成式一致性测试;故意破坏代码的 CI,用来证明测试确实能发现问题;共识协议的形式化 TLA+ 模型,其关键性质都有经机器检验的证明;等等。
但第一层防御是架构本身:设计良好的系统,在写下任何测试之前就消除了整类 bug。这也是为什么不了解架构,就无法理解这些测试:它们全都直接源于系统的构建方式。所以,这第一篇文章介绍几个关键组件以及它们之间的接口。我假设你已经知道 GenLayer 是什么、解决什么问题;如果还不知道,可以先读《Optimistic Democracy:正在改变商业的 AI 原生共识》和我们的文档。这篇文章聚焦的是幕后的工程。
GenLayer 由三个相互通信的主要组件构成:GenLayer Chain 上的共识合约、验证者节点和 GenVM。下图展示了它们如何组合在一起,文章其余部分会逐一介绍。
共识合约
每一笔 GenLayer 交易都要走过一个状态机,它由 GenLayer Chain(一条 EVM rollup)上的 Solidity 合约实现。合约追踪交易的完整生命周期:交易进入队列,由领导者提议,经验证者投票,被接受,并在申诉期过后最终确认。申诉会把同一笔交易升级到更大的验证者评估组,最多可扩大到整个验证者集合。验证者集合本身也记录在这里:验证者通过质押加入集合;对每一笔交易,合约都会从集合中选出一名领导者和一个评估组。
链是唯一的事实来源。除此之外,没有任何地方持有权威状态:想知道一笔交易走到了生命周期的哪一步,答案就在合约里。每次状态变化都会发出一个事件,系统的其余部分正是靠这些事件得知下一步该做什么。
链同时也是通信层。节点之间从不直接通信:没有点对点网络,也没有 gossip。提议是一笔交易,投票也是一笔交易,由链为它们全部排定顺序。这样一来,一整类共识问题还没出现就被消除了:不会分叉,因为所有人读取的是同一条规范的事件序列;不会重复提交,也不会出现矛盾投票(equivocation),因为合约在结构上就会拒绝第二次投票,而不是事后再去检测;我们的协议里也没有网络分区处理或视图切换逻辑,因为消息的送达和排序是底层链的工作。就连超时也是客观的:“领导者没有及时响应”是合约可以根据区块时间核实的事实,而不是某个节点的本地看法。链负责确定发生了什么;Optimistic Democracy 只需判断这意味着什么。
代价是协议的每一步都是一笔链上交易,因此区块时间就是共识延迟的下限。这是有意为之的取舍:用延迟换来整类分布式系统故障的彻底消失。这也意味着,每一个共识决定的完整历史都是一份有序、可重放的日志,这一点在本系列后面会很重要。
验证者节点
验证者节点是运营者运行的软件。首先要弄清它不是什么:它不是 GenLayer Chain 的节点。这条链是一条普通的 EVM 链,有自己的全节点和自己的 RPC,排序就发生在那里。验证者节点是这条链的客户端:它连接某个链节点的 RPC,订阅事件,发送交易。没错,这就有了两层节点、两层 RPC。不过分工很清晰:链节点就事件的顺序达成一致,验证者节点则根据这些事件采取行动。
拆开来看,它是把三样东西装进了同一个二进制程序:
- 共识 worker。这是它的主要工作:节点监听链上事件,一旦被选为某笔交易的领导者或验证者,就通过 GenVM 执行这笔交易(使用运营者配置的模型,无论是哪些),然后提交投票并揭示。在链看来,它只是又一个发送签名交易的账户。
- 全节点。它跟随链,维护 GenLayer 的状态。链上存储的是有序日志和共识状态;Intelligent Contracts 自身的状态则是派生出来的:节点通过 GenVM 重放已被接受的交易,把得到的状态保存在自己的本地数据库里。
- RPC。这是面向 GenLayer 的接口,供用户、SDK 和各类工具调用。GenLayer 特有的读取请求由派生状态来回答;标准的
eth_方法则直接透传给某个链节点。交易总是发往链上,透传只是让你只需维护一个连接。
所以,节点在下层消费一个 RPC,在上层提供另一个 RPC。你也可以只运行这个二进制程序而不做验证,它照样同步、照样提供 RPC。但它的主要工作,也是本系列关心的部分,是验证。你质押,正是为了这一部分。
GenVM
GenVM 是合约真正执行的地方,正是它的设计让 Optimistic Democracy 得以运转:它把确定性与受控的非确定性结合在一起,LLM 调用和网页访问只出现在开发者要求的地方,别处一概没有。
每个合约的顶层都以确定性模式运行。GenVM 是一个 WASM 沙盒:确定性的指令集,隔离的内存,除了虚拟机暴露的宿主函数(存储、calldata、合约间消息)之外,没有任何途径接触外部世界。因此,执行同一笔交易的每个验证者都会得到完全相同的结果,连哈希都一样。
在这次执行内部,合约可以开启非确定性代码块。每个代码块都在自己独立的 GenVM 实例中运行,这个实例会额外获得读取网页和调用 LLM 的宿主函数。只有在这里才具备这些能力:确定性代码根本够不着它们,因为相应的导入压根不存在。
共识与这种划分直接对应。和任何普通区块链一样,确定性执行必须在每个节点上完全一致。非确定性代码块则是验证者之间的结果可以合理存在差异的地方,也正是 Optimistic Democracy 的投票机制和等价原则接手的地方。
正因如此,我们才能把一个完整的 CPython 解释器放到链上,并直接用普通的 Python 作为合约语言。我们没有发明一种确定性的 Python 方言:GenVM 把真正的解释器编译成 WASM,放在沙盒里运行。合约代码原样运行;Python 内部带有的任何非确定性,都被固定在确定性环境之中。
链上有真正的解释器,就意味着能用上这门语言的全部能力,包括 eval 这样的功能。合约可以运行部署时还不存在的代码:LLM 可以在执行过程中写出代码,合约在同一次调用里就能执行它。这样做依然安全、依然确定,因为无论运行什么,都待在同一个沙盒里,只能使用同样的宿主函数,别无其他。
解释器以 runner(运行器)的形式发布:一个内容寻址的执行环境,通过哈希固定在合约的头部。因此,执行你合约的每个验证者,运行的都是逐字节完全相同的环境,连解释器的构建版本都一样。
不过,Python 只是默认选项,而不是边界。GenVM 的合约接口是 WASM:任何能编译成 WASM 的东西都可以成为合约。愿意的话,你可以直接用 Rust 或 C 来写。Python runner 只是一个标准环境,让最常见的情况用起来更方便。
一笔交易的完整旅程
- 用户向链提交一笔交易,可以直接发给链节点,也可以经由验证者节点透传。交易进入共识合约,开始排队。
- 合约选出一名领导者和一个评估组,并通过一个事件公布。
- 领导者的节点接收到事件,在自己的 GenVM 中执行这笔交易:确定性代码在沙盒中运行,非确定性代码块通过受控开放的宿主函数运行。结果作为领导者的提议,回到链上。
- 每个验证者的节点用自己的 GenVM 重新执行这笔交易,再把结果与提议比较。比较要回答的是领导者的结果是否可接受,而不是是否完全相同:这就是等价原则,判断标准由合约开发者针对每次调用分别定义。投票先提交,再揭示。
- 多数同意,交易即被接受,其决定随之生效。如果多数反对,网络就轮换到新的领导者,再试一次;如果仍然无法达成共识,交易以未决(undetermined)状态结束,合约状态保持不变。被接受的结果也还不是最终结果:在申诉期内,任何人都可以提出申诉,把交易交给更大的评估组。
- 申诉期结束,交易最终确认。至此,结果永久生效。
这趟旅程中的每一个箭头,都跨越了两道接缝中的一道:合约与节点之间,是向下的事件和向上的交易;节点与 GenVM 之间,是执行与结果。
第一道防线
这篇文章描述的一切,都贯穿着同一个主题:在纵深防御的方法里,架构本身就是第一层。它直接消除了整类 bug:所有通信都经由链,分叉、重复投票和网络分区处理就都不再是我们需要解决的问题。它带来一些靠构造而不是靠测试来保证的性质:确定性来自 WASM 沙盒,可复现性来自用哈希固定的 runner。它还划定了边界,让系统从根本上变得可以测试:图中的每一道接缝,都是可以对照明确定义的接口去检查某一层的地方。
后面的几层,针对的是架构无法靠构造来保证的一切。每个组件的失效模式不同,测试方法也就各不相同:共识合约作为状态机来测试,验证者节点对照合约的真实行为来测试,GenVM 则要验证我们继承来的确定性确实成立。而且,这些组件必须协同工作,所以测试也不会止步于各自的边界,而是贯穿这些层之间的边界与组合,一直延伸到对整个技术栈的端到端运行。这就是本系列接下来要讲的内容。



