

L2 - 深入理解OVM
source link: https://learnblockchain.cn/article/1675
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在Layer2, Optimistic Rollup通过OVM执行智能合约,并使用“检察”的方式确定Layer2世界状态在Layer1的正确性。Optimistic Rollup的难点也在OVM,需要在EVM的基础上模拟OVM的执行,并判断状态的正确性。目前,Optimistic Rollup的挑战期为7天。也就是说,只有7天前的状态是“确定”的,不会回滚。
Optimistic Rollup是Layer2潜在的一种方案。周末有点时间,在网络上翻了翻。网络上的文章,Optimistic Rollup深入技术的文章不多,介绍OVM底层技术细节的文章则更少。感兴趣看了看Optimism实现的OVM功能相关的智能合约,对Optimistic Rollup的理解很有帮助。总结一下,感兴趣的小伙伴可以看看。
1. Optimistic Rollup vs. zk Rollup
网络上对比这两种Rollup的方案文章不多。主要的一篇文章是:
https://medium.com/matter-labs/optimistic-vs-zk-rollup-deep-dive-ea141e71e075
国内也有对应的翻译文章:
Optimistic Rollup vs. ZK Rollup 对比
具体的性能和安全性对比,感兴趣的小伙伴可以直接看这篇文章。个人觉得,因为方案都不够成熟,目前方案能够达到的TPS都只是理论值。没必要太多的讨论。主要说说两种Rollup的技术实现的区别:
两种方案都是Rollup,Layer2的所有Transaction的信息都会作为CallData“存储”在Layer1,并且Layer2的状态也及时同步到Layer1。两者的区别在于,Layer2的状态在Layer1的正确性保证。Optimistic Rollup采用的是“检察”的方式,任何一个节点发现Layer2的状态的错误,提交相关的证明。如果错误的状态被验证,在Layer1的Layer2的状态需要回滚,提交错误状态的节点被惩罚。zk Rollup采用的方式直接了当,在向Layer1提交Layer2状态的同时,提交相关状态改变的证明。这些证明都是在Layer2生成。也就是说,zk Rollup在向Layer1提交Layer2状态的同时,同时提交了Layer2状态转换的计算证明。这个计算证明是通过零知识证明的算法产生。简单的说,如果转换的状态复杂,生成零知识证明的时间越长。
目前,zk Rollup只是支持简单的账户系统以及世界状态,并不能支持智能合约等复杂的世界状态。Optimistic Rollup虽然能支持智能合约,事实上,因为Layer1的计算能力比较弱,智能合约的支持也比较有限。Optimistic Rollup支持的智能合约的执行环境,类似EVM,称为 OVM(Optimistic Virtual Machine)。
2 OVM
OVM - Optimistic Virtual Machine。OVM是Layer2交易的执行环境。因为提交到Layer1的状态需要检验正确性,Layer1需要“重放”Layer2的交易,也就是说,Layer1在有些情况下需要执行OVM交易的执行。Optimistic Rollup最复杂的地方也在于此,用EVM模拟OVM,并执行Layer2的交易。
Optimism实现了EVM模拟OVM的逻辑,相关的项目的Github地址:
https://github.com/ethereum-optimism/contracts-v2
本文中使用的代码的最后一个提交信息如下:
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目录是各种库的实现,包括编解码,二叉树等等。
2.1 OVM/chain
Layer1的智能合约中用两条链维护交易信息和状态信息,分别是CanonicalTransactionChain和StateCommitmentChain。
Layer2的所有的交易信息,一个个Batch的通过CallData提交到Layer1。每个Batch中的交易的Hash信息组织成Merkle树。简单的说,CanonicalTransactionChain存储的是一个个Batch交易的Merkle树根。这些树根用来判断某个具体的交易是否在链中。
Layer2的世界状态,通过一个个交易的状态改变来表示。每个交易后的状态也是通过一个个Batch提交到Layer1。每个Batch中的状态,也再次组织成Merkle树。这些树根用来判断某个状态是否在链中。
具体两条链的存储信息,可以查看源代码:OVM_CanonicalTransactionChain.sol和OVM_StateCommitmentChain.sol。
2.2 OVM/execute
execute是OVM在EVM执行的核心逻辑,包括 ExecuteManager , StateManager 以及 SafetyChecker 。对应的源代码分别是:OVM_ExecutionManager.sol,OVM_SafetyChecker.sol和OVM_StateManager.sol。
ExecuteManager是整个智能合约执行环境以及指令集的处理。OVM其实和EVM逻辑上采用同样的指令集,但是在OVM的环境下,特别在Layer1的EVM执行OVM时,需要将这些指令集“转义”。之所以叫OVM的原因,可能很大程度为了区分EVM,表述方便。蛮多指令需要转义,把OVM在Layer1的实现想象成虚拟机。这些指令包括: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) {
2.3 OVM/verification
verification是OVM调用的业务逻辑。在Layer1,只是在验证的时候才需要通过OVM执行判断某个交易执行是否正确。verification逻辑中包括了BondManager(抵押管理),StateTransitioner(状态转换管理)和FraudVerifier(错误状态验证逻辑)。FraudVerifier逻辑是最核心的逻辑。整个验证过程的逻辑调用关系如下:
通过调用initializeFraudVerification函数,开始让Layer1开始验证某个交易执行后的状态是否正确。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/当前交易的执行。整个逻辑涉及到多次Layer1的交易,除此之外,还需要足够的时间保证链上数据能够同步并检查。目前,世界状态的挑战过程必须在相应交易后的7天内完成:
/// The dispute period uint256 public constant disputePeriodSeconds = 7 days;
总结:
Optimistic Rollup是Layer2潜在的一种方案。和zk Rollup一样,所有Transaction的信息都会作为CallData“存储”在Layer1。在Layer2, Optimistic Rollup通过OVM执行智能合约,并使用“检察”的方式确定Layer2世界状态在Layer1的正确性。Optimistic Rollup的难点也在OVM,需要在EVM的基础上模拟OVM的执行,并判断状态的正确性。目前,Optimistic Rollup的挑战期为7天。也就是说,只有7天前的状态是“确定”的,不会回滚。
Optimistic Rollup是Layer2潜在的一种方案。周末有点时间,在网络上翻了翻。网络上的文章,Optimistic Rollup深入技术的文章不多,介绍OVM底层技术细节的文章则更少。感兴趣看了看Optimism实现的OVM功能相关的智能合约,对Optimistic Rollup的理解很有帮助。总结一下,感兴趣的小伙伴可以看看。
1. Optimistic Rollup vs. zk Rollup
网络上对比这两种Rollup的方案文章不多。主要的一篇文章是:
https://medium.com/matter-labs/optimistic-vs-zk-rollup-deep-dive-ea141e71e075
国内也有对应的翻译文章:
Optimistic Rollup vs. ZK Rollup 对比
具体的性能和安全性对比,感兴趣的小伙伴可以直接看这篇文章。个人觉得,因为方案都不够成熟,目前方案能够达到的TPS都只是理论值。没必要太多的讨论。主要说说两种Rollup的技术实现的区别:
两种方案都是Rollup,Layer2的所有Transaction的信息都会作为CallData“存储”在Layer1,并且Layer2的状态也及时同步到Layer1。两者的区别在于,Layer2的状态在Layer1的正确性保证。Optimistic Rollup采用的是“检察”的方式,任何一个节点发现Layer2的状态的错误,提交相关的证明。如果错误的状态被验证,在Layer1的Layer2的状态需要回滚,提交错误状态的节点被惩罚。zk Rollup采用的方式直接了当,在向Layer1提交Layer2状态的同时,提交相关状态改变的证明。这些证明都是在Layer2生成。也就是说,zk Rollup在向Layer1提交Layer2状态的同时,同时提交了Layer2状态转换的计算证明。这个计算证明是通过零知识证明的算法产生。简单的说,如果转换的状态复杂,生成零知识证明的时间越长。
目前,zk Rollup只是支持简单的账户系统以及世界状态,并不能支持智能合约等复杂的世界状态。Optimistic Rollup虽然能支持智能合约,事实上,因为Layer1的计算能力比较弱,智能合约的支持也比较有限。Optimistic Rollup支持的智能合约的执行环境,类似EVM,称为 OVM(Optimistic Virtual Machine)。
2 OVM
OVM - Optimistic Virtual Machine。OVM是Layer2交易的执行环境。因为提交到Layer1的状态需要检验正确性,Layer1需要“重放”Layer2的交易,也就是说,Layer1在有些情况下需要执行OVM交易的执行。Optimistic Rollup最复杂的地方也在于此,用EVM模拟OVM,并执行Layer2的交易。
Optimism实现了EVM模拟OVM的逻辑,相关的项目的Github地址:
https://github.com/ethereum-optimism/contracts-v2
本文中使用的代码的最后一个提交信息如下:
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目录是各种库的实现,包括编解码,二叉树等等。
2.1 OVM/chain
Layer1的智能合约中用两条链维护交易信息和状态信息,分别是CanonicalTransactionChain和StateCommitmentChain。
Layer2的所有的交易信息,一个个Batch的通过CallData提交到Layer1。每个Batch中的交易的Hash信息组织成Merkle树。简单的说,CanonicalTransactionChain存储的是一个个Batch交易的Merkle树根。这些树根用来判断某个具体的交易是否在链中。
Layer2的世界状态,通过一个个交易的状态改变来表示。每个交易后的状态也是通过一个个Batch提交到Layer1。每个Batch中的状态,也再次组织成Merkle树。这些树根用来判断某个状态是否在链中。
具体两条链的存储信息,可以查看源代码:OVM_CanonicalTransactionChain.sol和OVM_StateCommitmentChain.sol。
2.2 OVM/execute
execute是OVM在EVM执行的核心逻辑,包括 ExecuteManager , StateManager 以及 SafetyChecker 。对应的源代码分别是:OVM_ExecutionManager.sol,OVM_SafetyChecker.sol和OVM_StateManager.sol。
ExecuteManager是整个智能合约执行环境以及指令集的处理。OVM其实和EVM逻辑上采用同样的指令集,但是在OVM的环境下,特别在Layer1的EVM执行OVM时,需要将这些指令集“转义”。之所以叫OVM的原因,可能很大程度为了区分EVM,表述方便。蛮多指令需要转义,把OVM在Layer1的实现想象成虚拟机。这些指令包括: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) {
2.3 OVM/verification
verification是OVM调用的业务逻辑。在Layer1,只是在验证的时候才需要通过OVM执行判断某个交易执行是否正确。verification逻辑中包括了BondManager(抵押管理),StateTransitioner(状态转换管理)和FraudVerifier(错误状态验证逻辑)。FraudVerifier逻辑是最核心的逻辑。整个验证过程的逻辑调用关系如下:
通过调用initializeFraudVerification函数,开始让Layer1开始验证某个交易执行后的状态是否正确。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/当前交易的执行。整个逻辑涉及到多次Layer1的交易,除此之外,还需要足够的时间保证链上数据能够同步并检查。目前,世界状态的挑战过程必须在相应交易后的7天内完成:
/// The dispute period uint256 public constant disputePeriodSeconds = 7 days;
总结:
Optimistic Rollup是Layer2潜在的一种方案。和zk Rollup一样,所有Transaction的信息都会作为CallData“存储”在Layer1。在Layer2, Optimistic Rollup通过OVM执行智能合约,并使用“检察”的方式确定Layer2世界状态在Layer1的正确性。Optimistic Rollup的难点也在OVM,需要在EVM的基础上模拟OVM的执行,并判断状态的正确性。目前,Optimistic Rollup的挑战期为7天。也就是说,只有7天前的状态是“确定”的,不会回滚。
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
- 发表于 49分钟前
- 阅读 ( 13 )
- 学分 ( 0 )
- 分类:Rollup
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK