1. 引言
fault proof(错误证明),又名欺诈证明,或,交互游戏,其包含3大要素:
- 1)Fault Proof Program(客户端角色):已知对所有rollup inputs(L1数据)的承诺值,和,dispute争议,可statelessly无状态地验证该争议。
- 2)Fault Proof VM(服务端角色):已知某stateless无状态程序及其输入,追踪任意指令步骤,并在L1证明。
- 3)Fault Proof Interactive Dispute Game:将争议一分为二为一条指令,并使用VM解决base-case基本情况。
这3大要素可具有不同的实现,从而组合为不同的proof stacks,并为解决某争议贡献proof多样性。
程序及其单独指令的“无状态执行”是指通过使用Pre-image Oracle的authenticating输入来再现完全相同的计算。
2. Pre-image Oracle
Program(客户端)与 VM(服务端)之间的唯一通讯方式为:
- pre-image oracle
二者通讯使用简单的request-response wire协议。具体见后面的Pre-image communication。
Program使用pre-image oracle来查询用户可以使用的任意输入数据:
- 用于bootstrap该program的初始输入。具体见后面的Bootstrapping章节。
- 还不是程序代码的外部数据。具体见Pre-image hinting routes。
pre-images以bytes32
type-prefixed key来表示:
- 第一个字节标识request类型。
- 剩下的31个字节标识该pre-image key。
其中requestleix you :
- Type 0:Zero key。0前缀是非法的。
- Type 1:Local key。用于program bootstrapping,以通过索引或名称来标识初始输入参数。
- Type 2:Global keccak256 key。用作global pre-image store contract,其是完全上下文独立和无许可的。
- Type 3:Global generic key。保留。
- Type 4-128:保留范围。
- Type 129-255:应用使用。
3. Bootstrapping
初始输入是确定性的,但不一定是单一的或全局的:可能同时存在多个不同的争议,每个争议都有自己的争议主张和L1上下文。
为了bootstrap,该program使用pre-image key type 1 来从VM请求初始输入。
VM知道外部上下文,并基于它们的类型映射所请求的pre-image keys,即type 1的local lookup或 type 2的global lookup,且可选地支持其他key类型。
4. Hinting
客户端与服务端之间还有另一可选通讯方式:
- pre-image hinting
hinting是可选的,为L1 VM实现中的no-op。
hint本身在链上的成本非常低:
- hint可为单个write系统调用,这是即时的,因为作为hint的内存写入实际上不需要作为链上证明的一部分加载。
hinting允许程序在生成链外证明时,指示VM其所感兴趣的数据。
VM可选择在任何时候执行所请求的hint:
- 本地执行(对于标准请求),
- 或者通过将hint重定向到VM program附带的工具,以模块化的形式执行。
hints不必直接执行:它们可能首先被记录下来以显示program的意图,最新的hint可能会被缓冲以供延迟执行,或者在只读模式下完全删除(如onchain链上)。
当pre-image oracle服务某请求,并且不能从现有的pre-images集合(如,某local pre-image缓存)服务时,VM可以执行该hint以检索丢失的pre-image(s)。该program有责任为每个pre-image请求提供足够的hinting。某些hints可能需要重复:在处理丢失的pre-image时,VM只需要执行最后一个hint。
注意,hint可以产生多个preimage:如,具有交易列表的以太坊区块的hint可为:
- 区块头
- 每笔交易
- 形成交易列表merkle-Pricia Trie的中间merkle节点
准备pre-image。
hinting是通过阻塞双向流上的request-acknowledgement协议实现的:
<request> := <length prefix> <hint bytes>
<response> := <ack>
<length prefix> := big-endian uint32 # length of <hint bytes>
<hint bytes> := byte sequence
<ack> := 1-byte zero value
ack通知客户端该hint已处理。服务端可能会异步回复hints和pre-image请求,因为这是分开的streams。为避免正在请求的pre-images暂未获取,客户端应在观察到该hint acknowledgement之后再请求该pre-image。
5. pre-image通信
pre-images通过通过阻塞双向流上的最小wire-protocol来通信。该协议以阻塞读写系统调用来实现:
<request> := <bytes32> # the type-prefixed pre-image key
<response> := <length prefix> <pre-image bytes>
<length prefix> := big-endian uint64 # length of <pre-image bytes>, note: uint64
其中:
- <length prefix>:可为任意高的值:若其所需的pre-image内容已读取,客户端可随时停止读取。
6. Fault proof program
Fault proof program:
- 将,L2 rollup的状态变更,的claims,
- 定义为,
- 基于L1数据的纯函数。
op-program
是指该program的实现,基于op-node
和op-geth
来实现的。
该program主要包含:
- Prologue:加载输入,已知最小bootstrapping,和可能的test-overrides
- Main content:处理L2状态变更,即,根据L1输入派生该状态更改。
- Epilogue:检查该状态更改来验证该claim。
6.1 Prologue
program以2个主要输入来bootstrap:
- l1_head:将其看成是L1链tip的L1区块哈希,证实所有之前L1的真实性。
- dispute:标识所验证的claim。
bootstrapping,通过对program host的特殊输入请求来启动。
此外,还有隐含输入,这些输入源于上述主要输入,但可出于测试目的被覆盖:
- l2_head:l2区块哈希,它将被视为l2链tip的之前共识,用于验证所有先前的L2历史。
- 链配置:链配置可嵌入到程序中,也可根据L1上已标识的
dispute
属性来确定。- l1_chain_config:L1链的链配置(也称为l1_genesis.json)
- l2_chain_config:L2链的链配置(也称为l2_generation.json)
- rollup_config:rollup节点使用的rollup配置(也称为rollup.json)
隐含输入依赖于L1-introspection,通过争议游戏接口在L1历史(直到指定的L1_head)中加载争议的属性。争议可能是claim本身,也可能是指向L1中特定先前claimed数据的指针,这取决于争议游戏接口。
在实际的核心状态转换函数执行之前,隐含的输入被加载到“prologue”中。在测试期间,可以使用加载重写的简化prologue。
注意:目前只支持test-prologue,因争议游戏接口正在积极更改。
6.2 Main content
为验证L2 state的某claim,program首先基于之前认可的L2历史,应用L1数据,重新生成L2 state。该流程称为L2派生过程,且匹配 rollup节点 和 L2 execution engine内的处理流程。
不同于通过RPC来获取输入并对disk应用状态变更,输入通过pre-image oracle加载,并在内存中累加状态变化。
以2个数据源来派生该执行:
- 1)对L1链只读的接口,由pre-image oracle来back:
- l1_head:确定了可用的L1数据:后续没有可用的L1数据。
- 链的实现从l1_head向下遍历header-chain,以按区块号查询服务。
- l1_head:为L1 unsafe head、safe head和finalized head。
- 2)L2 engine API接口:
- 之前的L2链历史由pre-image oracle back,与L1链类似:
- 初始的l2_head确定了可用的L2数据:后续没有可用的L2数据。
- 链的实现从l2_head向下遍历header-chain,以按区块号查询服务。
- l2_head:为L2 unsafe head、safe head和finalized head。
- 新的L2链历史在内存中累加:
- 若内存有限,尽管可使用pre-image oracle,按哈希来恢复数据,但program应首选在内存中保持最新创建的链数据,以最小化pre-image oracle访问。
- L2 unsafe head、safe head和finalized head,可随着派生流程而变化。
- L2 state包含了内存中的变化差量,可通过只读L2历史来访问任意未变化状态节点。
- 之前的L2链历史由pre-image oracle back,与L1链类似:
6.3 Epilogue
当main-content生成了disputed L2 state,epilogue会对该disputed claim下结论。
program会生成二进制输出来验证该claim,使用标准的single-byte Unix exit-code:
- 0:成功,即该claim是正确的。
- 非零值:表示失败,即该claim是不正确的:
- 推荐使用1来标识不正确的claim
- 其它非零值来表示runtime错误:如program代码中的bug所导致的panic或unexpected error。
7. Fault proof VM
fault proof VM实现:
- 一个智能合约:来验证单个execution-trace step,如单个MIPS指令。
- 一个CLI命令行:来为单个execution-trace step,生成proof。
- 一个CLI命令行:来计算在第 N N N个step的VM state-root。
fault proof VM依赖于fault proof program,来基于hints提供任意丢失pre-images的接口。
VM仿真该program,为目标架构VM准备,并通过VM CLI生成state-root或指令proof。
Fault proof VM有:
- Cannon:big-endian 32-bit MIPS proof,由OP Labs开发,当前已Public archive了。
- Asterisc:little-endian 64-bit RISC-V proof,由protolambda开发,当前正在活跃开发中。
8. Fault proof interactive dispute game
互动纠纷游戏允许参与者通过链上挑战响应游戏解决纠纷,该游戏将不一致的区块 n → n + 1 n\rightarrow n+1 n→n+1状态转换,然后基于为该状态转换建模的VM的executiont race,以证明单个VM trace步骤的基本情况为界。
游戏是多玩家的:不同的不结盟参与者可以在bond时参与。
响应时间是根据树分支中的剩余时间以及与claim的一致性来分配的。分配的响应时间受到争议游戏窗口的限制,当bond不足时,基于L1费用所需的任何额外时间都会发生变化。
注:计时、bond、一分为二争议游戏正在开发中。
9. Fault Proofs:MIPS.sol
MIPS.sol合约 是虚拟机 (VM) 的链上实现,包含 32 位、Big-Endian、MIPS III 指令集架构 (Instruction Set Architecture,ISA)。该合约与同一 ISA 的链下 MIPSEVM golang 实现相对应。链上和链下虚拟机实现共同构成了 Cannon——Optimism 的Fault Proof Virtual Machine (FPVM)。Cannon 是 FPVM 的一个单一实例,可以用作 Optimism(和 Base)乐观汇总 L2 区块链Dispute Game的一部分。Dispute Game自身是模块化的,允许在争议中使用任何 FPVM;然而,Cannon 是目前唯一实现的 FPVM,因此将在所有争议中使用。
OP团队于2023年9月发布了Fault Proof System alpha版本,详情见:
从该视频可知,MIPS.sol 合约与其他两个合约交互:【更多详情,参考:Fault Proofs: MIPS.sol】
- FaultDisputeGame.sol合约:
- 是针对活动争议的故障争议博弈的部署实例
- 在高层次上,故障争议博弈将有效地确定当前达成一致的 L2 状态,并遍历 L2 状态,直到找到第一个不一致的状态。如何确定Pre-images并将其填充到 PreimageOracle.sol 合约中超出了 MIPS.sol 合约参考文档的范围,因为该合约仅使用已由链下Cannon提供的Pre-images。
- PreimageOracle.sol合约:
- 存储预映像。
- 原像包含来自 L1 和 L2 的数据,其中包括区块头、交易、收据、世界状态节点等信息。原像用作推导过程的输入,用于计算真实的 L2 状态,随后真实的 L2 状态用于解决争议博弈。
以仅有16条指令的trace为例,在Mock VM中模拟的相应Dispute Game示例为:
总体架构为:
根据2023年11月视频 Onchain Summit: Fault Proof Workshop by Protolambda,OP的L2角色总览为:
L2 state-transition为:
类似于L1客户端多样性,L2 Proof多样性,多样性可创造恢复能力:
为此,需采用Multi-Proof架构:
为支持proof多样性,实现更模块化、去中心化的生态,OP L2架构设计为:
Fault Proof Virtual Machine(FP-VM):
- execution-trace proving
为“Bare-metal裸机”:无环境的简单执行:
每条指令都是简单的:
这些简单的指令会重复数十亿次:
无需证明所有,只需证明有分歧的单一指令即可。
交互式二分游戏为,对execution trace进行二分:
FP(Fault Proof)program为:
10. Goshen Network的Optimistic rollup
Goshen Network协议使用RISC-V虚拟机来支持链上计算,且当有挑战时,其仅需识别RISC-V-Chain程序中的“one step”来执行prune fraud state。
区别于其它Optimistic rollup,Goshen Network 通过分层架构确保简单性和多功能性。底层是基于 RISC-V 的 L2 通用计算环境,以去信任的方式将 L1 计算迁移到链下。这样,L2 实现了 L1 的状态转换逻辑,确保与 L1 生态系统的完全兼容。进一步构建可靠的跨层消息通信机制,提供L1和L2之间的互操作性,用于构建token bridge等上层应用。对于链上挑战,交互式挑战协议不仅降低了链上成本,还提高了协议的鲁棒性。
参考资料
[1] Optimism的Fault proof
[2] 2023年3月 RISC-V for L2 state transition computation by Goshen
[3] Fault Proofs: MIPS.sol
[4] 2023年10月视频 Walkthrough: OP Stack Fault Proof System alpha release
[5] 2023年11月视频 Onchain Summit: Fault Proof Workshop by Protolambda