Optimistic Rollup 的难点在于 OVM,需要在 EVM 的基础上模拟 OVM 的执行,并判断状态的正确性。
原文标题:《L2 - 深入理解 OVM》
撰文:Star Li
Optimistic Rollup 是 Layer 2 潜在的一种方案。周末有点时间,在网络上翻了翻。网络上的文章,Optimistic Rollup 深入技术的文章不多,介绍 OVM 底层技术细节的文章则更少。感兴趣看了看 Optimism 实现的 OVM 功能相关的智能合约,对 Optimistic Rollup 的理解很有帮助。总结一下,感兴趣的小伙伴可以看看。
Optimistic Rollup vs. ZK Rollup
网络上对比这两种 Rollup 的方案文章不多。
主要的一篇文章:
https://medium.com/matter-labs/optimistic-vs-zk-rollup-deep-pe-ea141e71e075
对应的翻译文章:
https://www.chainnews.com/articles/932935429481.htm
具体的性能和安全性对比,感兴趣的小伙伴可以直接看这篇文章。个人觉得,因为方案都不够成熟,目前方案能够达到的 TPS 都只是理论值。没必要太多的讨论。主要说说两种 Rollup 的技术实现的区别:
两种方案都是 Rollup,Layer 2 的所有 Transaction 的信息都会作为 CallData「存储」在 Layer 1,并且 Layer 2 的状态也及时同步到 Layer 1。两者的区别在于,Layer 2 的状态在 Layer 1 的正确性保证。Optimistic Rollup 采用的是「检察」的方式,任何一个节点发现 Layer 2 的状态的错误,提交相关的证明。如果错误的状态被验证,在 Layer 1 的 Layer 2 的状态需要回滚,提交错误状态的节点被惩罚。ZK Rollup 采用的方式直接了当,在向 Layer 1 提交 Layer 2 状态的同时,提交相关状态改变的证明。这些证明都是在 Layer 2 生成。也就是说,ZK Rollup 在向 Layer 1 提交 Layer 2 状态的同时,同时提交了 Layer 2 状态转换的计算证明。这个计算证明是通过零知识证明的算法产生。简单的说,如果转换的状态复杂,生成零知识证明的时间越长。
目前,ZK Rollup 只是支持简单的账户系统以及世界状态,并不能支持智能合约等复杂的世界状态。Optimistic Rollup 虽然能支持智能合约,事实上,因为 Layer 1 的计算能力比较弱,智能合约的支持也比较有限。Optimistic Rollup 支持的智能合约的执行环境,类似 EVM,称为 OVM (Optimistic Virtual Machine)。
OVM
OVM - Optimistic Virtual Machine。OVM 是 Layer 2 交易的执行环境。因为提交到 Layer 1 的状态需要检验正确性,Layer 1 需要「重放」Layer 2 的交易,也就是说,Layer 1 在有些情况下需要执行 OVM 交易的执行。Optimistic Rollup 最复杂的地方也在于此,用 EVM 模拟 OVM,并执行 Layer 2 的交易。
Optimism 实现了 EVM 模拟 OVM 的逻辑,相关的项目的 Github 地址。
本文中使用的代码的最后一个提交信息如下:
commit ca1fede6c8cb9e4eacd8205c1d53284d0c8debdc Author: Mark Tyneway Date: Fri Oct 30 12:14:50 2020 -0700 deploy: use layer 2 chainid (#42)
核心代码在 contracts-v2/contracts/optimistic-ethereum/OVM 目录中。除了 OVM 目录,iOVM 目录是接口定义,libraries 目录是各种库的实现,包括编解码,二叉树等等。
OVM/chain
Layer 1 的智能合约中用两条链维护交易信息和状态信息,分别是 CanonicalTransactionChain 和 StateCommitmentChain。
Layer 2 的所有的交易信息,一个个 Batch 的通过 CallData 提交到 Layer 1。每个 Batch 中的交易的 Hash 信息组织成 Merkle 树。简单的说,CanonicalTransactionChain 存储的是一个个 Batch 交易的 Merkle 树根。这些树根用来判断某个具体的交易是否在链中。
Layer 2 的世界状态,通过一个个交易的状态改变来表示。每个交易后的状态也是通过一个个 Batch 提交到 Layer 1。每个 Batch 中的状态,也再次组织成 Merkle 树。这些树根用来判断某个状态是否在链中。
具体两条链的存储信息,可以查看源代码:OVM_CanonicalTransactionChain.sol 和 OVM_StateCommitmentChain.sol。
OVM/execute
execute 是 OVM 在 EVM 执行的核心逻辑,包括 ExecuteManager,StateManager 以及 SafetyChecker。对应的源代码分别是:OVM_ExecutionManager.sol,OVM_SafetyChecker.sol 和 OVM_StateManager.sol。
ExecuteManager 是整个智能合约执行环境以及指令集的处理。OVM 其实和 EVM 逻辑上采用同样的指令集,但是在 OVM 的环境下,特别在 Layer 1 的 EVM 执行 OVM 时,需要将这些指令集「转义」。之所以叫 OVM 的原因,可能很大程度为了区分 EVM,表述方便。蛮多指令需要转义,把 OVM 在 Layer 1 的实现想象成虚拟机。这些指令包括:TIMESTAMP,CALL,STATICCALL,DELEGATECALL,GASLIMIT,SLOAD,SSTORE 等等。一个交易的执行从 ExecuteManager 的 run 函数开始:
function run( Lib_OVMCodec.Transaction memory_transaction, address_ovmStateManager )
run 函数提供了执行的交易,以及执行交易前的状态。
StateManager 实现了智能合约以及账户的存储状态管理。ExecuteManager 在执行一个交易时会通过 StateManager 更新状态。
SafetyChecker 检查 OVM 的指令合约中的指令集是否正常,有没有超过目前可以执行的范围。安全性检查通过 OVM_SafetyChecker.sol 的 isBytecodeSafe 函数实现。
function isBytecodeSafe( bytes memory_bytecode ) override external pure returns (bool) {
OVM/verification
verification 是 OVM 调用的业务逻辑。在 Layer 1,只是在验证的时候才需要通过 OVM 执行判断某个交易执行是否正确。verification 逻辑中包括了 BondManager (抵押管理),StateTransitioner (状态转换管理)和 FraudVerifier (错误状态验证逻辑)。FraudVerifier 逻辑是最核心的逻辑。整个验证过程的逻辑调用关系如下:
通过调用 initializeFraudVerification 函数,开始让 Layer 1 开始验证某个交易执行后的状态是否正确。StateTransitioner 准备交易之前的世界状态以及交易执行的中间状态存储。在世界状态准备就绪后(proveContractState/proveStorageSlot),通过调用 ExecutionManager 的 run 函数执行交易并更新状态。更新后的状态通过 StateTransitioner 的 completeTransition 函数生成世界状态。生成的世界状态和提交的世界状态进行对比,如果不一致,之前提交世界状态的节点通过 BondManager 进行惩罚。
仔细的分析一下 FraudVerifier 的 initializeFraudVerification 和 finalizeFraudVerification 函数。先从 initializeFraudVerification 函数开始:
function initializeFraudVerification( bytes32_preStateRoot, Lib_OVMCodec.ChainBatchHeader memory_preStateRootBatchHeader, Lib_OVMCodec.ChainInclusionProof memory_preStateRootProof, Lib_OVMCodec.Transaction memory_transaction, Lib_OVMCodec.TransactionChainElement memory_txChainElement, Lib_OVMCodec.ChainBatchHeader memory_transactionBatchHeader, Lib_OVMCodec.ChainInclusionProof memory_transactionProof )
_preStateRoot 是之前的世界状态的 Merkle 树根。通过 _preStateRootBatchHeader 和 _preStateRootProof 可以验证某个状态是在 StateCommitmentChain 上。
require( ovmStateCommitmentChain.verifyStateCommitment( _preStateRoot, _preStateRootBatchHeader, _preStateRootProof ), "Invalid pre-state root inclusion proof." );
_transction 信息是需要验证的交易信息。通过 _txChainElement,_transactionBatchHeader 以及 _transactionProof 可以验证某个交易是否在 CanonicalTransactionChain 上。
require( ovmCanonicalTransactionChain.verifyTransaction( _transaction, _txChainElement, _transactionBatchHeader, _transactionProof ), "Invalid transaction inclusion proof." );
在确定了交易以及状态都合法后,创建 StateTransitioner 准备执行交易。
transitioners[_preStateRoot] = iOVM_StateTransitionerFactory( resolve("OVM_StateTransitionerFactory") ).create( address(libAddressManager), _preStateRootProof.index, _preStateRoot, Lib_OVMCodec.hashTransaction(_transaction) );
执行交易的逻辑,直接忽略,感兴趣的小伙伴可以看 OVM_StateTransitioner.sol 的 applyTransaction 函数。交易执行完,通过 finalizeFraudVerification 函数检查执行后的世界状态的结果。
function finalizeFraudVerification( bytes32_preStateRoot, Lib_OVMCodec.ChainBatchHeader memory_preStateRootBatchHeader, Lib_OVMCodec.ChainInclusionProof memory_preStateRootProof, bytes32_postStateRoot, Lib_OVMCodec.ChainBatchHeader memory_postStateRootBatchHeader, Lib_OVMCodec.ChainInclusionProof memory_postStateRootProof )
先检查提供的两个世界状态是否在 StateCommitmentChain 上存在:
require( ovmStateCommitmentChain.verifyStateCommitment( _preStateRoot, _preStateRootBatchHeader, _preStateRootProof ), "Invalid pre-state root inclusion proof." ); require( ovmStateCommitmentChain.verifyStateCommitment( _postStateRoot, _postStateRootBatchHeader, _postStateRootProof ), "Invalid post-state root inclusion proof." );
并且,保证两个状态是连续的:
require( _postStateRootProof.index ==_preStateRootProof.index + 1, "Invalid post-state root index." );
查看 OVM 执行的世界状态是否和提交的状态一致:
require( _postStateRoot != transitioner.getPostStateRoot(), "State transition has not been proven fraudulent." );
如果不一致,需要回滚世界状态:
ovmStateCommitmentChain.deleteStateBatch( _postStateRootBatchHeader );
并且对提交世界状态的节点进行惩罚:
ovmBondManager.finalize( _preStateRoot, _postStateRootBatchHeader.batchIndex, publisher, timestamp );
简单的看,OVM 在 EVM 的模拟,涉及到两个重要的点:1/ 之前世界状态的表示 2/ 当前交易的执行。整个逻辑涉及到多次 Layer 1 的交易,除此之外,还需要足够的时间保证链上数据能够同步并检查。目前,世界状态的挑战过程必须在相应交易后的 7 天内完成:
/// The dispute period uint256 public constant disputePeriodSeconds = 7 days;
总结
Optimistic Rollup 是 Layer 2 潜在的一种方案。和 ZK Rollup 一样,所有 Transaction 的信息都会作为 CallData「存储」在 Layer 1。在 Layer 2, Optimistic Rollup 通过 OVM 执行智能合约,并使用「检察」的方式确定 Layer 2 世界状态在 Layer 1 的正确性。Optimistic Rollup 的难点也在 OVM,需要在 EVM 的基础上模拟 OVM 的执行,并判断状态的正确性。目前,Optimistic Rollup 的挑战期为 7 天。也就是说,只有 7 天前的状态是「确定」的,不会回滚。
来源链接:mp.weixin.qq.com