前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >二层网络 Optimism 智能合约要点解析

二层网络 Optimism 智能合约要点解析

作者头像
Tiny熊
发布2022-11-07 10:17:34
7470
发布2022-11-07 10:17:34
举报

译文出自:登链翻译计划[1] 译者:翻译小组[2] 校对:Tiny 熊[3]

Optimism 是一个建立在以太坊之上的乐观 Rollup。什么是乐观 Rollup?它又是如何在代码层面上工作的?这篇文章将解释。

我们还将介绍为什么 Rollup 需要链间通信以及这种通信是如何实现的。我们将看到实现 rollup 最重要功能的实际代码片断。

以下是这篇文章的大纲:

  • 什么是乐观 Rollup?
  • Optimism 合约的宏观概述
  • L1-L2 桥的代码
  • Rollup 交易的代码
  • 处理争议的代码

什么是乐观 Rollup?

首先,什么是 rollup?它是使以太坊更有效率的方法之一,通常被称为 L2 解决方案。有 3 种 L2 解决方案类型:状态通道、plasma 和 Rollup。我很快会有一篇关于 L2解决方案的分类的文章,将详细介绍这个问题。下面是关于什么是 Rollup,特别是乐观 Rollup 的一个简短总结。

以太坊上有一个智能合约(称为RollupL1),它允许 ETH 的存款/提款。当你的钱存入RollupL1时,你可以认为它是在 L2。L2 的资金流动比 L1 的资金快得多,因为 L2 的交易更有效,更快。这一点是如何实现的呢?

还有一个在以太坊外的程序(称为RollupL2)。它可以更快地处理交易,因为它不需要通过以太坊缓慢而昂贵的共识机制。它可以处理一堆交易,将它们合并(把它们卷在一起,即 Rollup)成一个批,并将该批次提交给RollupL1

RollupL2可以是另一个更快的区块链上的智能合约,也可以是一个传统的 web2 服务器。每种方法都有优点和缺点,如延迟和去中心化。

通过在链外处理交易,你可以从 2 个方面更节省:

  1. 数据压缩:一个批处理所占的空间比单个交易堆叠在一起要小。见 Vatalik 的文章[4]以了解原因。
  2. 需要一次以太坊缓慢而昂贵的共识。

还有一个省力轴:你不需要在以太坊的每个交易之后计算新的状态。你看,当你直接在以太坊上提交一个交易,以太坊需要计算账户的新状态。这是很昂贵的。通过将这项工作转移到 L2,你可以避免在以太坊上进行这种昂贵的计算。

但是,RollupL1是否应该相信RollupL2提交给它的新状态?它应该验证吗?如果它验证,它浪费了同样的计算,所以失去了 Rollup 的意义。

乐观 Rollup 通过先信任来解决这个问题:他们只是相信新提交的状态而不做任何验证(他们是非常乐观的 ✨)。但是,他们将新提交的批次锁定一个星期(称为 挑战窗口)。任何人都可以在这个挑战窗口期间提交数学证明,如果他们发现了欺诈性的状态更新,就可以获得奖励。如果该批次在这一周内没有争议,它就被认为是最终的。

奖励的资金来自于提交批次的人的质押金。如果你想提交批次交易,你需要提交一个质押金。

这就是乐观 Rollup 工作的宏观描述。

Optimism 合约的宏观概述

从上层来看,乐观 Rollup 需要 3 个的功能:

  1. 一个在 L1 和 L2 之间移动资金的双向桥
  2. 处理交易并将其 Rollup 到一个批次中
  3. 争议/证明无效的状态更新

以下是实现上述功能的 Optimism 智能合约的图示:

来自 Optimism 文档[5]

现在让我们来看看最重要部分的实际代码:

L1-L2 桥的代码

这个桥的工作原理是在 L1 锁定资金,在 L2 铸造相应的数量。要提取资金,桥会销毁 L2 的资金并释放锁定的 L1 资金。

下面是存入资金的函数( L1StandardBridge.sol):

代码语言:javascript
复制
/**
 * @title L1StandardBridge
 * @dev The L1 ETH and ERC20 Bridge is a contract which stores deposited L1 funds and standard
 * tokens that are in use on L2. It synchronizes a corresponding L2 Bridge, informing it of deposits
 * and listening to it for newly finalized withdrawals.
 *
 */
contract L1StandardBridge is IL1StandardBridge, CrossDomainEnabled {
    function depositETHTo(
        address _to,
        uint32 _l2Gas,
        bytes calldata _data
    ) external payable {
        _initiateETHDeposit(msg.sender, _to, _l2Gas, _data);
    }

    /**
     * @dev Performs the logic for deposits by storing the ETH and informing the L2 ETH Gateway of
     * the deposit.
     * @param _from Account to pull the deposit from on L1.
     * @param _to Account to give the deposit to on L2.
     * @param _l2Gas Gas limit required to complete the deposit on L2.
     * @param _data Optional data to forward to L2. This data is provided
     *        solely as a convenience for external contracts. Aside from enforcing a maximum
     *        length, these contracts provide no guarantees about its content.
     */
    function _initiateETHDeposit(
        address _from,
        address _to,
        uint32 _l2Gas,
        bytes memory _data
    ) internal {
        // Construct calldata for finalizeDeposit call
        bytes memory message = abi.encodeWithSelector(
            IL2ERC20Bridge.finalizeDeposit.selector,
            address(0),
            Lib_PredeployAddresses.OVM_ETH,
            _from,
            _to,
            msg.value,
            _data
        );
        // Send calldata into L2
        // slither-disable-next-line reentrancy-events
        sendCrossDomainMessage(l2TokenBridge, _l2Gas, message);
        // slither-disable-next-line reentrancy-events
        emit ETHDepositInitiated(_from, _to, msg.value, _data);
    }

    // ... other functions (OMITTED)
}

该函数是L1StandardBridge[6]合约的一部分,它部署在以太坊上。它非常简单:接受 ETH(通过payable关键字自动完成),将函数的所有参数编码为一条消息,并将消息发送到跨域信使。

跨域信使在 L1 和 L2 之间广播消息。我们将在稍后介绍它。

在 L2 上有一个相应的函数来监听这些消息。L2StandardBridge合约就是这样做的。这个合约部署在一个独立的 L2 区块链上(比以太坊快)。

代码语言:javascript
复制
/**
 * @title L2StandardBridge
 * @dev The L2 Standard bridge is a contract which works together with the L1 Standard bridge to
 * enable ETH and ERC20 transitions between L1 and L2.
 * This contract acts as a minter for new tokens when it hears about deposits into the L1 Standard
 * bridge.
 * This contract also acts as a burner of the tokens intended for withdrawal, informing the L1
 * bridge to release L1 funds.
 */
contract L2StandardBridge is IL2ERC20Bridge, CrossDomainEnabled {
    function finalizeDeposit(
        address _l1Token,
        address _l2Token,
        address _from,
        address _to,
        uint256 _amount,
        bytes calldata _data
    ) external virtual onlyFromCrossDomainAccount(l1TokenBridge) {
        // Check the target token is compliant and
        // verify the deposited token on L1 matches the L2 deposited token representation here
        if (
            // slither-disable-next-line reentrancy-events
            ERC165Checker.supportsInterface(_l2Token, 0x1d1d8b63) &&
            _l1Token == IL2StandardERC20(_l2Token).l1Token()
        ) {
            // When a deposit is finalized, we credit the account on L2 with the same amount of
            // tokens.
            // slither-disable-next-line reentrancy-events
            IL2StandardERC20(_l2Token).mint(_to, _amount);
            // slither-disable-next-line reentrancy-events
            emit DepositFinalized(_l1Token, _l2Token, _from, _to, _amount, _data);
        } else {
            // ... handle error (OMITTED)
        }
    }

    // ... other functions (OMITTED)
}

这个函数只是运行一些检查和铸造新的代币。我应该有提到,你可以使用这个桥来转账任意的 ERC-20 代币,而不仅仅是 ETH(ETH 只是被包裹在一个 ERC-20 接口中)。

同样,也有相应的功能用于将资金从 L2 转移到 L1。也是用一个 X 域(x-domain)信使来完成。为了简洁起见,我将跳过它们。

跨域信息传递

L1 和 L2 之间的通信是通过一个 X 域信使合约进行的(每个链上都有一个副本)。在内部,这个合约只是存储消息,并依靠 中继器来通知其他链(L1 或 L2)有新消息。

由于没有原生的 L1 ↔ L2 通信。每一方都有onNewMessage这样的函数,中继器需要使用传统的 web2 HTTPs 来调用它们。

例如,下面是 L1→L2 交易如何在 L1 上存储/排队代码(CanonicalTransactionChain.sol):

代码语言:javascript
复制
/**
 * @title CanonicalTransactionChain
 * @dev The Canonical Transaction Chain (CTC) contract is an append-only log of transactions
 * which must be applied to the rollup state. It defines the ordering of rollup transactions by
 * writing them to the 'CTC:batches' instance of the Chain Storage Container.
 * The CTC also allows any account to 'enqueue' an L2 transaction, which will require that the
 * Sequencer will eventually append it to the rollup state.
 *
 */
contract CanonicalTransactionChain is ICanonicalTransactionChain, Lib_AddressResolver {
    uint40 private _nextQueueIndex; // index of the first queue element not yet included
    Lib_OVMCodec.QueueElement[] queueElements;

    /**
     * Adds a transaction to the queue.
     * @param _target Target L2 contract to send the transaction to.
     * @param _gasLimit Gas limit for the enqueued L2 transaction.
     * @param _data Transaction data.
     */
    function enqueue(
        address _target,
        uint256 _gasLimit,
        bytes memory _data
    ) external {
        // ...a bunch of unimportant stuff omitted

        bytes32 transactionHash = keccak256(abi.encode(sender, _target, _gasLimit, _data));

        queueElements.push(
            Lib_OVMCodec.QueueElement({
                transactionHash: transactionHash,
                timestamp: uint40(block.timestamp),
                blockNumber: uint40(block.number)
            })
        );
        uint256 queueIndex = queueElements.length - 1;
        emit TransactionEnqueued(sender, _target, _gasLimit, _data, queueIndex, block.timestamp);
    }
}

中继器会通知 L2 在队列中有一个新消息。

卷起交易的代码

Optimism 上有一个排序器,其工作是接受 L2 交易,检查其有效性,并将状态更新作为一个待定块应用到其本地状态。这些待处理区块会定期大批量地提交给以太坊(L1)进行最终确定性处理。

以太坊上接受这些批次的函数是appendSequencerBatch,这是 L1 上CanonicalTransactionChain合约的一部分。在内部,appendSequencerBatch使用下面的函数来处理批次(CanonicalTransactionChain.sol):

代码语言:javascript
复制
/**
 * @title CanonicalTransactionChain
 * @dev The Canonical Transaction Chain (CTC) contract is an append-only log of transactions
 * which must be applied to the rollup state. It defines the ordering of rollup transactions by
 * writing them to the 'CTC:batches' instance of the Chain Storage Container.
 * The CTC also allows any account to 'enqueue' an L2 transaction, which will require that the
 * Sequencer will eventually append it to the rollup state.
 *
 */
contract CanonicalTransactionChain is ICanonicalTransactionChain, Lib_AddressResolver {
    /**
     * Inserts a batch into the chain of batches.
     * @param _transactionRoot Root of the transaction tree for this batch.
     * @param _batchSize Number of elements in the batch.
     * @param _numQueuedTransactions Number of queue transactions in the batch.
     * @param _timestamp The latest batch timestamp.
     * @param _blockNumber The latest batch blockNumber.
     */
    function _appendBatch(
        bytes32 _transactionRoot,
        uint256 _batchSize,
        uint256 _numQueuedTransactions,
        uint40 _timestamp,
        uint40 _blockNumber
    ) internal {
        IChainStorageContainer batchesRef = batches();
        (uint40 totalElements, uint40 nextQueueIndex, , ) = _getBatchExtraData();

        Lib_OVMCodec.ChainBatchHeader memory header = Lib_OVMCodec.ChainBatchHeader({
            batchIndex: batchesRef.length(),
            batchRoot: _transactionRoot,
            batchSize: _batchSize,
            prevTotalElements: totalElements,
            extraData: hex""
        });

        emit TransactionBatchAppended(
            header.batchIndex,
            header.batchRoot,
            header.batchSize,
            header.prevTotalElements,
            header.extraData
        );

        bytes32 batchHeaderHash = Lib_OVMCodec.hashBatchHeader(header);
        bytes27 latestBatchContext = _makeBatchExtraData(
            totalElements + uint40(header.batchSize),
            nextQueueIndex + uint40(_numQueuedTransactions),
            _timestamp,
            _blockNumber
        );

        // slither-disable-next-line reentrancy-no-eth, reentrancy-events
        batchesRef.push(batchHeaderHash, latestBatchContext);
    }
}
  • batchesRef是一个用于数据存储的辅助合约。那是存储批处理的地方。
  • 函数首先计算批次头,然后计算其哈希值。
  • 然后计算出批次的上下文。批次头和上下文只是关于批次的附加信息。
  • 然后,它将哈希值和上下文存储在存储器中(batchesRef)。

以后,哈希和上下文将被用来验证争议。

将交易卷成一个批次并提交的排序器 目前还是中心化的--由 Optimism 组织控制。但他们有计划在未来将这个角色去中心化。你也可以直接向CanonicalTransactionChain提交你自己的批次,而不通过排序器,但这将是更昂贵的,因为提交批次的固定成本完全由自己支付,而不是在许多的交易中分摊。

处理争议的代码

在高层次上,争议的工作方式是提交一个状态更新无效的证明,并根据存储的状态更新(存储的批次的元数据:哈希和上下文)验证这个证明。

负责处理争议的合约是OVMFraudVerifier。该合约是 OVM - Optimism 虚拟机的一部分(类似于 EVM - 以太坊虚拟机)。以下是处理纠纷的主要函数:

代码语言:javascript
复制
contract OVM_FraudVerifier is Lib_AddressResolver, OVM_FraudContributor, iOVM_FraudVerifier {
    /**
     * Finalizes the fraud verification process.
     * @param _preStateRoot State root before the fraudulent transaction.
     * @param _preStateRootBatchHeader Batch header for the provided pre-state root.
     * @param _preStateRootProof Inclusion proof for the provided pre-state root.
     * @param _txHash The transaction for the state root
     * @param _postStateRoot State root after the fraudulent transaction.
     * @param _postStateRootBatchHeader Batch header for the provided post-state root.
     * @param _postStateRootProof Inclusion proof for the provided post-state root.
     */
    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);

        // ... a bunch of require statements omitted

        // 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
        );
    }

    /**
     * Removes a state transition from the state commitment chain.
     * @param _postStateRootBatchHeader Header for the post-state root.
     * @param _preStateRoot Pre-state root hash.
     */
    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
        );
    }
}
  • finalizeFraudVerification检查_postStateRoot(由验证者提交)是否不等于由排序者提交的状态根。
  • 如果不是,那么我们在_cancelStateTransition中删除该批次,并削减排序者的存款(为了成为一个排序者,你需要锁定一个存款。当你提交一个欺诈性的批次时,你的押金就会被削减,这笔钱就会给验证者,作为保持整个机制运行的激励)。

这就是 Optimism 智能合约的要点解析。我希望你对以太坊和 L2 的未来感到乐观!


本翻译由 Duet Protocol[7] 赞助支持。

原文:https://betterprogramming.pub/optimism-smart-contract-breakdown-18f87a7b1823

参考资料

[1]

登链翻译计划: https://github.com/lbc-team/Pioneer

[2]

翻译小组: https://learnblockchain.cn/people/412

[3]

Tiny 熊: https://learnblockchain.cn/people/15

[4]

文章: https://vitalik.ca/general/2021/01/05/rollup.html#how-does-compression-work

[5]

文档: https://community.optimism.io/docs/protocol/protocol-2.0/#system-overview

[6]

L1StandardBridge: https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L1/messaging/L1StandardBridge.sol

[7]

Duet Protocol: https://duet.finance/?utm_souce=learnblockchain

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-07-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 深入浅出区块链技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是乐观 Rollup?
  • Optimism 合约的宏观概述
  • L1-L2 桥的代码
  • 跨域信息传递
  • 卷起交易的代码
  • 处理争议的代码
    • 参考资料
    相关产品与服务
    区块链
    云链聚未来,协同无边界。腾讯云区块链作为中国领先的区块链服务平台和技术提供商,致力于构建技术、数据、价值、产业互联互通的区块链基础设施,引领区块链底层技术及行业应用创新,助力传统产业转型升级,推动实体经济与数字经济深度融合。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档