L2 — Deep Dive into OVM

Trapdoor-Tech
7 min readNov 28, 2020

--

Optimistic Rollup is a potential solution candidate for Layer2. Over the weekend I poked around and didn’t come through with much articles deep diving into Optimistic Rollup, not to mention introudctions about OVM low level technological details. I was intrigued, so I read about Smart Contracts that involve Optimism implemented OVM features, and it is quite helpful for understanding Optimistic Rollup in my opinion. Here to summarize them up for folks that are interested.

1. Optimistic Rollup vs. zk Rollup

There is not much articles on the comparison for these two Rollups. The main source is this one:

https://medium.com/matter-labs/optimistic-vs-zk-rollup-deep-dive-ea141e71e075

And its related transcript:

https://ethfans.org/posts/optimistic-vs-zk-rollup-deep-dive

If you are interested in details about security and safety comparison, you can jump into this article. From my standpoint, due to the immaturity for both mothods, the TPS that can be reached is purely theoraticle. There is no need for much discussion on this. Let us talk about the difference on two Rollup implementaion techniques:

Both methods are Rollup. All Layer 2Transaction info will be saved in Layer one as CallData, and Layer2 states will be able to synced to Layer1. The difference is that, the guarantee of correctedness of the Layer2 states in Layer1. Optimistic Rollup takes the approach of “inspection”. If any Layer2 state error found at any node, a proof needs to be submitted. If the error state is verified, the Layer2 state in Layer one needs to rollback, and node that submitted the error state will be punished. zk Rollup takes a more straight forward way. While Layer2 state is submitted to Layer1, the proof for related state change is also submitted. The proof is generated in Layer2. That is also to say, zk Rollup submits layer2 state to Layer1, at the same time submitting Layer2 state transfer proof of calculation. Such calculation is done by zero-knowledge proof. Therefore, if the transfer state is complicated, generating zero-knowledge proof takes longer.

For now, zk Rollup only supports simple account system and Plasma State, and does not support complex Plasma State such as Smart Contract. Optimistic Rollup supports Smart Contract, though in fact, due to the limitation for Layer1 calculation ability, such support is rather limited as well. The execution environment that supports Optimistic Rollup, such as EVM, is called OVM (Optimistic Virtual Machine).

2 OVM

OVM — Optimistic Virtual Machine. OVM is the execution environment for Layer2 transaction. Since states submitted to Layer1 needs to be verified, Layer1 needs to “replay” Layer2 transactions. That is to say, under certain circumstances, Layer1 requires OVM transaction for execution. This is where Optimistic Rollup gets complicated — using EVM to simulate OVM, and executing Layer2 transactions.

Optimism implements EVM simulating OVM logic. Please refer to the Github repo:

https://github.com/ethereum-optimism/contracts-v2

The last submittion info for code used in this article as below:

commit ca1fede6c8cb9e4eacd8205c1d53284d0c8debdc
Author: Mark Tyneway <mark.tyneway@gmail.com>
Date: Fri Oct 30 12:14:50 2020 -0700

deploy: use layer 2 chainid (#42)

Core code in under contracts-v2/contracts/optimistic-ethereum/OVM directory. Besides OVM, IOVM directories as interface definitions, libraries directory contains implementation for all libraries, including Codec, binary trees, etc.

2.1 OVM/chain

Layer1 Smart Contract uses two blockchains to maintain transaction information and state information. They are called CanonicalTransactionChain and StateCommitmentChain.

All transaction information in Layer2 is submitted through CallData, batch by batch, to Layer1. The Hash information forms Merkle tree in each Batch. That is to say, CanonicalTransactionChain saves the Merkle tree root in a Batch transaction. These roots are used to check if a certain transaction exsit in a blockchain.

Layer2 Plasma State is updated through each transaction state. Each post-transaction state is submitted to Layer1, batch by batch as well. All the states in a Batch again form a Merkle tree. These tree roots are used to check if a state exists in the blockchain.

You can see more details about the storage information for both chains at OVM_CanonicalTransactionChain.sol and OVM_StateCommitmentChain.sol。

2.2 OVM/execute

execute is the core logic for OVM executing in EVM, including ExecuteManager, StateManager and SafetyChecker. The corresponding source code are: OVM_ExecutionManager.sol, OVM_SafetyChecker.sol and OVM_StateManager.sol.

ExecuteManager is the handler for entire Smart Contract execution environemnt and instruction set. OVM actually uses the same command set as the EVM logically. However, under OVM environment, esperiaclly when executing OVM in Layer1 EVM, these instruction sets needs “escape”. The reason that OVM takes its name, is very likely just to differentiate from EVM, to make easier in expressions. Quite amount of these commands need escape expression, and take the OVM Layer1 implementation as virtual machine. These commands include: TIMESTAMP,CALL,STATICCALL,DELEGATECALL,GASLIMIT,SLOAD,SSTORE, etc. An execution of a transaction starts from the run function in ExecuteManager:

function run(
Lib_OVMCodec.Transaction memory _transaction,
address _ovmStateManager
)

run function provides the executing transaction, and pre-transaction state.

StateManager implements Smart Contract and account storage state management. ExecuteManager updates an executing transaction state through StateManagera.

SafetyChecker checks the command set in the OVM command contract to see if any surpassing the executable range. This security check is done through function isBytecodeSafe in OVM_SafetyChecker.sol.

function isBytecodeSafe(
bytes memory _bytecode
)
override
external
pure
returns (bool)
{

2.3 OVM/verification

Verification is a business logic called by OVM. In Layer1, it is only required during verification to check if certain transaction was executed correctly through OVM. Verification logic contains BondManager, StateTransitioner and FraudVerifier. FraudVerifier logic is the core. The entire verification inter-relationship shown as follows:

By calling function initializeFraudVerification, we start verification process by letting Layer1 check certain post-transaction state. StateTransitioner prepares the pre-transaction Plasma State and the state storage during the transaction process. As soon as Plasma State is ready (proveContractState/proveStorageSlot), we use the run function in ExecutionManager to check transaction and update state. After updating the state, the Plasma State is generated by function completeTransition in StateTransitioner. The generated Plasma State and submitted Plasma state is then compared, once verified invalid, the node submitted the Plasma state will be punished by BondManager.

Let’s dig into function completeTransition and finalizeFraudVerification in FraudVerifier的initializeFraudVerification. Start with 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 in the Merkle tree root for the previous Plasma State。Through _preStateRootBatchHeader and _preStateRootProof, we can verify if certain state exists in StateCommitmentChain.

require(
ovmStateCommitmentChain.verifyStateCommitment(
_preStateRoot,
_preStateRootBatchHeader,
_preStateRootProof
),
"Invalid pre-state root inclusion proof."
);

_transction is the transaction information that needs verification. Through _txChainElement, _transactionBatchHeader and _transactionProof, we can prove that if certain transaction exists in CanonicalTransactionChain.

require(
ovmCanonicalTransactionChain.verifyTransaction(
_transaction,
_txChainElement,
_transactionBatchHeader,
_transactionProof
),
"Invalid transaction inclusion proof."
);

After verifying that both transaction and state are legal, we create StateTransitioner to prepare the execution of transaction.

transitioners[_preStateRoot] = iOVM_StateTransitionerFactory(
resolve("OVM_StateTransitionerFactory")
).create(
address(libAddressManager),
_preStateRootProof.index,
_preStateRoot,
Lib_OVMCodec.hashTransaction(_transaction)
);

Here we will pass the topic on transaction execution logic. If you are interested, take a look at the function applyTransaction in OVM_StateTransitioner.sol. After finishing the transaction, we check the post-transaction Plasma State result by using function 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
)

First check if both two Plasma States exist in the 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."
);

At the same time, the two states need to be consecutive:

require(
_postStateRootProof.index == _preStateRootProof.index + 1,
"Invalid post-state root index."
);

Check if the Plasma State executed by OVM is identical to the one in submission:

require(
_postStateRoot != transitioner.getPostStateRoot(),
"State transition has not been proven fraudulent."
);

Roll back if they are not identical:

ovmStateCommitmentChain.deleteStateBatch(
_postStateRootBatchHeader
);

And punish the node which submitted the false Plasma State:

ovmBondManager.finalize(
_preStateRoot,
_postStateRootBatchHeader.batchIndex,
publisher,
timestamp
);

To sum it up, OVM’s simulation in EVM cover two crucial parts: 1/ the expression of previous Plasma State 2/ the execution of currect transaction. The logic covers multiple Layer1 transactions, additionally, requiring enough time for syncing up and checking data on the chain. As of now, the Challenge of Plasma State have to conclude in the 7 day window following the transaction completion.

/// The dispute period
uint256 public constant disputePeriodSeconds = 7 days;

Summary:

Optimistic Rollup is a potential solution candidate for Layer2. Similar to zk Rollup, all Transaction information will be “saved” as CallData in Layer1. In Layer2, Optimistic Rollup executes Smart Contract through OVM, and it ensures the validity of Layer2 Plasma state in Layer one by “inspection”. The challenging part of Optimistic Rollup is OVM as well — it requires simulating OVM excution based on EVM, and checking the correctiveness of the state. As of now, Optimistic Rollup Challenge period is 7 days. That is to say, only states posted before 7 days are certain that won’t be rolled back.

--

--

Trapdoor-Tech
Trapdoor-Tech

Written by Trapdoor-Tech

Trapdoor-Tech tries to connect the world with zero-knowledge proof technologies. zk-SNARK/STARK solution and proving acceleration are our first small steps :)

No responses yet