美文网首页
Optimism欺诈证明

Optimism欺诈证明

作者: ttblack | 来源:发表于2021-04-20 19:03 被阅读0次

CanonicalTransactionChain (CTC)和 StateCommitmentChain(SCC)

上次说到OVM的作用就是为了在Layer1上重放Layer2上的交易时,保持一致性。也就是为了验证某个交易是否有人作恶。

在这之前我们必须先理解两个合约,分别是CanonicalTransactionChain.sol和StateCommitmentChain.sol。

这两份合约分别实现了两条链,这也是为什么 Optimism 被称为Layer2的原因,因为他是工作在以太坊之上的事务状态。

  • CanonicalTransactionChain 维护的是Layer2上的交易信息,是由交易组成的一个列表。通过enqueue 方法入队。然后emit 一个 TransactionEnqueued 事件,由data-transport-layer 侦听并存储。以供l2geth获取并打包进一个区块,把区块的stateroot再通过 batch-submitter提交到StateCommitmentChain.sol
  • StateCommitmentChain StateCommitmentChain.sol就是维护交易的状态根的列表。

为了能验证某个交易是否在layer2上打包了。也是利用了Merkle树证明。首先,layer2的所有的交易信息,每隔一段时间都会通过batch-submitter 组成一个Batch, 每个Batch中的交易hash再组成Merkle树。 所以CanonicalTransactionChain 也会存储一个个Batch交易的Merkle树根。。用来判断某个具体的交易是否真的被打包。

同理,StateCommitmentChain也会通过每个交易的状态根也由Batch-submitter组成一个个Batch提到StateCommitmentChain 中,然后,每个Batch中的stateRoot再次组成Merkle树。用来判断某个状态是否在链中。

这些交易信息在CanonicalTransactionChain 和 StateCommitmentChain 是一一对应的。所以验证过程就是从CanonicalTransactionChain 中 拿到一个交易在OVM中运行,将运行的结果和StateCommitmentChain 中相应的状态根进行比较。如果一样,表示验证通过。如果不一样,则表示这个提交Batch的 submitter有作恶。则将StateCommitmentChain中的这个交易开始的后面的状态列表都删除。并扣除质押金。

了解了这些信息,我们就可以从源码角度看下验证过程了。

verification

上面的代码就是组成欺诈验证的所有合约。 这些代码工作在Layer1上。只有在需要验证的时候才需要通过OVM执行判断某个交易执行是否正确。

  • Abs_FraudContributor.sol 记录 要验证的交易hash和preStateRoot,及gas消耗
  • OVM_BondManager.sol 押金管理器,管理Sequencer的质押金,和提供证明的gas消耗
  • OVM_FraudVerifier.sol 欺诈验证逻辑的主文件,整个验证过程都在这里面
  • OVM_StateTransitioner.sol 状态转换管理, 控制着 statemanager.sol 和 ExecutionManager.sol,通过 applyTransaction 调用 ExecutionManager 的run 方法执行交易来记录状态根。OVM_FraudVerifier 通过比较提供的stateroot 和 执行的stateroot来比较检查是否有欺诈
  • OVM_StateTransitionerFactory.sol 一个工厂类,OVM_FraudVerifier用来创建OVM_StateTransitioner.

证明过程如下:

1. 初始化

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
    )
        override
        public
        contributesToFraudProof(_preStateRoot, Lib_OVMCodec.hashTransaction(_transaction))
    {
        bytes32 _txHash = Lib_OVMCodec.hashTransaction(_transaction);

        if (_hasStateTransitioner(_preStateRoot, _txHash)) {
            return;
        }

        iOVM_StateCommitmentChain ovmStateCommitmentChain = iOVM_StateCommitmentChain(resolve("OVM_StateCommitmentChain"));
        iOVM_CanonicalTransactionChain ovmCanonicalTransactionChain = iOVM_CanonicalTransactionChain(resolve("OVM_CanonicalTransactionChain"));

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

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

        require (
            _preStateRootBatchHeader.prevTotalElements + _preStateRootProof.index + 1 == _transactionBatchHeader.prevTotalElements + _transactionProof.index,
            "Pre-state root global index must equal to the transaction root global index."
        );

        _deployTransitioner(_preStateRoot, _txHash, _preStateRootProof.index);

        emit FraudProofInitialized(
            _preStateRoot,
            _preStateRootProof.index,
            _txHash,
            msg.sender
        );
    }

用户 调用OVM_FraudVerifier 的initializeFraudVerification方法,提供前置状态根及其在SCC中的证明和要要验证的交易 及其在CTC 中的证明。
上面的代码逻辑分为

  • 验证前置状态的有效性,和要验证交易的有效性:就是通过在SCC Chain和CTC Chain中的merkle 树来证明其有效性的。
  • 保证交易顺序是一致的
  • 通过前置状态根,txHash, 前置状态号初始化一个状态转换器(OVM_StateTransitioner), 就是通过OVM_StateTransitionerFactory创建的,这时状态转换器处于预执行阶段(PRE_EXECUTION)

2 上传所有交易状态

现在虽然创建的状态转换器,但现在还不能执行交易,因为现在Layer1上的stateManager,还没有任何涉及的Layer2上的状态,这时需要调用状态转换器的proveContractState,将Layer2的OVM合约与Layer1的OVM合约进行链接,下面是代码:

function proveContractState(
        address _ovmContractAddress,
        address _ethContractAddress,
        bytes memory _stateTrieWitness
    )
        override
        public
        onlyDuringPhase(TransitionPhase.PRE_EXECUTION)
        contributesToFraudProof(preStateRoot, transactionHash)
    {
        // Exit quickly to avoid unnecessary work.
        require(
            (
                ovmStateManager.hasAccount(_ovmContractAddress) == false
                && ovmStateManager.hasEmptyAccount(_ovmContractAddress) == false
            ),
            "Account state has already been proven."
        );

        // Function will fail if the proof is not a valid inclusion or exclusion proof.
        (
            bool exists,
            bytes memory encodedAccount
        ) = Lib_SecureMerkleTrie.get(
            abi.encodePacked(_ovmContractAddress),
            _stateTrieWitness,
            preStateRoot
        );

        if (exists == true) {
            // Account exists, this was an inclusion proof.
            Lib_OVMCodec.EVMAccount memory account = Lib_OVMCodec.decodeEVMAccount(
                encodedAccount
            );

            address ethContractAddress = _ethContractAddress;
            if (account.codeHash == EMPTY_ACCOUNT_CODE_HASH) {
                // Use a known empty contract to prevent an attack in which a user provides a
                // contract address here and then later deploys code to it.
                ethContractAddress = 0x0000000000000000000000000000000000000000;
            } else {
                // Otherwise, make sure that the code at the provided eth address matches the hash
                // of the code stored on L2.
                require(
                    Lib_EthUtils.getCodeHash(ethContractAddress) == account.codeHash,
                    "OVM_StateTransitioner: Provided L1 contract code hash does not match L2 contract code hash."
                );
            }

            ovmStateManager.putAccount(
                _ovmContractAddress,
                Lib_OVMCodec.Account({
                    nonce: account.nonce,
                    balance: account.balance,
                    storageRoot: account.storageRoot,
                    codeHash: account.codeHash,
                    ethAddress: ethContractAddress,
                    isFresh: false
                })
            );
        } else {
            // Account does not exist, this was an exclusion proof.
            ovmStateManager.putEmptyAccount(_ovmContractAddress);
        }
    }

通过上面的代码可以看到执行这个方法前需要当前状态是 PRE_EXECUTION。相应的参数就是要链接的合约在Layer2和在layer1上相应的地址。及账户状态。最后 通过ovmStateManager.putAccount进行相应的链接。
链接后再 调用调用proveStorageSlot,设置执行交易前的账户状态。以方便在执行交易后的账户状态和layer2上的账户状态相比较。

    function proveStorageSlot(
        address _ovmContractAddress,
        bytes32 _key,
        bytes memory _storageTrieWitness
    )
        override
        public
        onlyDuringPhase(TransitionPhase.PRE_EXECUTION)
        contributesToFraudProof(preStateRoot, transactionHash)
    {
        // Exit quickly to avoid unnecessary work.
        require(
            ovmStateManager.hasContractStorage(_ovmContractAddress, _key) == false,
            "Storage slot has already been proven."
        );

        require(
            ovmStateManager.hasAccount(_ovmContractAddress) == true,
            "Contract must be verified before proving a storage slot."
        );

        bytes32 storageRoot = ovmStateManager.getAccountStorageRoot(_ovmContractAddress);
        bytes32 value;

        if (storageRoot == EMPTY_ACCOUNT_STORAGE_ROOT) {
            // Storage trie was empty, so the user is always allowed to insert zero-byte values.
            value = bytes32(0);
        } else {
            // Function will fail if the proof is not a valid inclusion or exclusion proof.
            (
                bool exists,
                bytes memory encodedValue
            ) = Lib_SecureMerkleTrie.get(
                abi.encodePacked(_key),
                _storageTrieWitness,
                storageRoot
            );

            if (exists == true) {
                // Inclusion proof.
                // Stored values are RLP encoded, with leading zeros removed.
                value = Lib_BytesUtils.toBytes32PadLeft(
                    Lib_RLPReader.readBytes(encodedValue)
                );
            } else {
                // Exclusion proof, can only be zero bytes.
                value = bytes32(0);
            }
        }

        ovmStateManager.putContractStorage(
            _ovmContractAddress,
            _key,
            value
        );
    }

3.执行交易

做完执行交易前的准备后,就由用户调用OVM_StateTransitioner的 applyTransaction 来执行交易

function applyTransaction(
        Lib_OVMCodec.Transaction memory _transaction
    )
        override
        public
        onlyDuringPhase(TransitionPhase.PRE_EXECUTION)
        contributesToFraudProof(preStateRoot, transactionHash)
    {
        require(
            Lib_OVMCodec.hashTransaction(_transaction) == transactionHash,
            "Invalid transaction provided."
        );

        // We require gas to complete the logic here in run() before/after execution,
        // But must ensure the full _tx.gasLimit can be given to the ovmCALL (determinism)
        // This includes 1/64 of the gas getting lost because of EIP-150 (lost twice--first
        // going into EM, then going into the code contract).
        require(
            gasleft() >= 100000 + _transaction.gasLimit * 1032 / 1000, // 1032/1000 = 1.032 = (64/63)^2 rounded up
            "Not enough gas to execute transaction deterministically."
        );

        iOVM_ExecutionManager ovmExecutionManager = iOVM_ExecutionManager(resolve("OVM_ExecutionManager"));

        // We call `setExecutionManager` right before `run` (and not earlier) just in case the
        // OVM_ExecutionManager address was updated between the time when this contract was created
        // and when `applyTransaction` was called.
        ovmStateManager.setExecutionManager(address(ovmExecutionManager));

        // `run` always succeeds *unless* the user hasn't provided enough gas to `applyTransaction`
        // or an INVALID_STATE_ACCESS flag was triggered. Either way, we won't get beyond this line
        // if that's the case.
        ovmExecutionManager.run(_transaction, address(ovmStateManager));

        // Prevent the Execution Manager from calling this SM again.
        ovmStateManager.setExecutionManager(address(0));
        phase = TransitionPhase.POST_EXECUTION;
    }

通过代码可以看到,执行前有状态检查和Abs_FraudContributor中contributesToFraudProof,来记录验证的花费和交易. 然后通过OVM_ExecutionManager来run 这个交易,状态存在ovmStateManager中。最后将状态设置为POST_EXECUTION 我们就叫提交状态吧。

在这个阶段下,由于状态转换器/状态管理器对不知道整个L2状态,因此它们无法自动计算新的后状态根。需要用户调用 commitContractState 和 commitStorageSlot 来提交用户的账户和存储信息。

function commitContractState(
        address _ovmContractAddress,
        bytes memory _stateTrieWitness
    )
        override
        public
        onlyDuringPhase(TransitionPhase.POST_EXECUTION)
        contributesToFraudProof(preStateRoot, transactionHash)
    {
        require(
            ovmStateManager.getTotalUncommittedContractStorage() == 0,
            "All storage must be committed before committing account states."
        );

        require (
            ovmStateManager.commitAccount(_ovmContractAddress) == true,
            "Account state wasn't changed or has already been committed."
        );
    
        Lib_OVMCodec.Account memory account = ovmStateManager.getAccount(_ovmContractAddress);
    
        postStateRoot = Lib_SecureMerkleTrie.update(
            abi.encodePacked(_ovmContractAddress),
            Lib_OVMCodec.encodeEVMAccount(
                Lib_OVMCodec.toEVMAccount(account)
            ),
            _stateTrieWitness,
            postStateRoot
        );
    
        // Emit an event to help clients figure out the proof ordering.
        emit AccountCommitted(
            _ovmContractAddress
        );
    }
    
    /**
     * Allows a user to commit the final state of a contract storage slot.
     * @param _ovmContractAddress Address of the contract on the OVM.
     * @param _key Claimed account slot key.
     * @param _storageTrieWitness Proof of the storage slot.
     */
    function commitStorageSlot(
        address _ovmContractAddress,
        bytes32 _key,
        bytes memory _storageTrieWitness
    )
        override
        public
        onlyDuringPhase(TransitionPhase.POST_EXECUTION)
        contributesToFraudProof(preStateRoot, transactionHash)
{
        require(
            ovmStateManager.commitContractStorage(_ovmContractAddress, _key) == true,
            "Storage slot value wasn't changed or has already been committed."
        );
    
        Lib_OVMCodec.Account memory account = ovmStateManager.getAccount(_ovmContractAddress);
        bytes32 value = ovmStateManager.getContractStorage(_ovmContractAddress, _key);
    
        account.storageRoot = Lib_SecureMerkleTrie.update(
            abi.encodePacked(_key),
            Lib_RLPWriter.writeBytes(
                Lib_Bytes32Utils.removeLeadingZeros(value)
            ),
            _storageTrieWitness,
            account.storageRoot
        );
    
        ovmStateManager.putAccount(_ovmContractAddress, account);
    
        // Emit an event to help clients figure out the proof ordering.
        emit ContractStorageCommitted(
            _ovmContractAddress,
            _key
        );
    }

提交完所有用户账户数据后,就是completeTransition 转换结束阶段。

   function completeTransition()
        override
        public
        onlyDuringPhase(TransitionPhase.POST_EXECUTION)
    {
        require(
            ovmStateManager.getTotalUncommittedAccounts() == 0,
            "All accounts must be committed before completing a transition."
        );

        require(
            ovmStateManager.getTotalUncommittedContractStorage() == 0,
            "All storage must be committed before completing a transition."
        );
    
        phase = TransitionPhase.COMPLETE;
    }

这个阶段就是检查目前阶段是否是提交状态(POST_EXECUTION),然后检查未提交的账户是否是0,和未提交的合约存储是否是0.最后设置转换状态为完成状态(COMPLETE).

4.完成验证 finalizeFraudVerification

当状态转换器状态是COMPLETE后,就要执行OVM_FraudVerifier的finalizeFraudVerification方法来对验证结果做处理,来看代码

function finalizeFraudVerification(
       bytes32 _preStateRoot,
       Lib_OVMCodec.ChainBatchHeader memory _preStateRootBatchHeader,
       Lib_OVMCodec.ChainInclusionProof memory _preStateRootProof,
       bytes32 _txHash,
       bytes32 _postStateRoot,
       Lib_OVMCodec.ChainBatchHeader memory _postStateRootBatchHeader,
       Lib_OVMCodec.ChainInclusionProof memory _postStateRootProof
   )
       override
       public
       contributesToFraudProof(_preStateRoot, _txHash)
   {
       iOVM_StateTransitioner transitioner = getStateTransitioner(_preStateRoot, _txHash);
       iOVM_StateCommitmentChain ovmStateCommitmentChain = iOVM_StateCommitmentChain(resolve("OVM_StateCommitmentChain"));

       require(
           transitioner.isComplete() == true,
           "State transition process must be completed prior to finalization."
       );
   
       require (
           _postStateRootBatchHeader.prevTotalElements + _postStateRootProof.index == _preStateRootBatchHeader.prevTotalElements + _preStateRootProof.index + 1,
           "Post-state root global index must equal to the pre state root global index plus one."
       );
   
       require(
           ovmStateCommitmentChain.verifyStateCommitment(
               _preStateRoot,
               _preStateRootBatchHeader,
               _preStateRootProof
           ),
           "Invalid pre-state root inclusion proof."
       );
   
       require(
           ovmStateCommitmentChain.verifyStateCommitment(
               _postStateRoot,
               _postStateRootBatchHeader,
               _postStateRootProof
           ),
           "Invalid post-state root inclusion proof."
       );
   
       // If the post state root did not match, then there was fraud and we should delete the batch
       require(
           _postStateRoot != transitioner.getPostStateRoot(),
           "State transition has not been proven fraudulent."
       );
       
       _cancelStateTransition(_postStateRootBatchHeader, _preStateRoot);
   
       // TEMPORARY: Remove the transitioner; for minnet.
       transitioners[keccak256(abi.encodePacked(_preStateRoot, _txHash))] = iOVM_StateTransitioner(0x0000000000000000000000000000000000000000);
   
       emit FraudProofFinalized(
           _preStateRoot,
           _preStateRootProof.index,
           _txHash,
           msg.sender
       );
   }

这个方法会检查状态转换器是否是COMPLETE状态(transitioner.isComplete),并且拿_postStateRoot和转换器执行的stateRoot相比较。如果不一样,表示有欺诈.就会调用_cancelStateTransition 来运行处罚程序.

    function _cancelStateTransition(
        Lib_OVMCodec.ChainBatchHeader memory _postStateRootBatchHeader,
        bytes32 _preStateRoot
    )
        internal
    {
        iOVM_StateCommitmentChain ovmStateCommitmentChain = iOVM_StateCommitmentChain(resolve("OVM_StateCommitmentChain"));
        iOVM_BondManager ovmBondManager = iOVM_BondManager(resolve("OVM_BondManager"));
    
        // Delete the state batch.
        ovmStateCommitmentChain.deleteStateBatch(
            _postStateRootBatchHeader
        );
    
        // Get the timestamp and publisher for that block.
        (uint256 timestamp, address publisher) = abi.decode(_postStateRootBatchHeader.extraData, (uint256, address));
    
        // Slash the bonds at the bond manager.
        ovmBondManager.finalize(
            _preStateRoot,
            publisher,
            timestamp
        );
    }

这里面会调用SCC Chain中的deleteStateBatch来删除(包括)有争议的交易之后的所有状态根Batch.CTC保持不变,因此原始交易将以相同顺序重新执行。来更新删除后的SCC Chain. 最后通过 质押管理器处理质押金和奖励验证者 ovmBondManager.finalize。

好了。到这篇文章后,基本上对Optimism这个系统也有了初步认识,,可能是不对的地方。望纠正。。

相关文章

网友评论

      本文标题:Optimism欺诈证明

      本文链接:https://www.haomeiwen.com/subject/foonlltx.html