本文档介绍了L2输出根提案规范,这是Optimism Rollup中将L2状态同步到L1(结算层)的关键环节。Proposer的角色是构建并提交输出根到L1上的L2OutputOracle合约,以此作为桥梁的L2 状态视图,并可接受fault proof挑战。其中详细说明了L2输出承诺的构建方式和L2OutputOracle智能合约的接口定义与配置,以及安全方面的考虑,例如 L1 重组。
<!-- 本文件中所有的词汇表引用。 -->
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> 目录
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
在处理一个或多个区块后,输出需要与结算层(L1)同步,以便对 L2 到 L1 的消息传递(例如提款)进行无需信任的执行。 这些输出提案充当桥对 L2 状态的视图。 被称为 "Proposers" 的参与者将输出根提交到结算层(L1),并且可以通过欺诈证明提出异议, 如果证明是错误的,则会损失一部分抵押金。 op-proposer 是 proposer 的一个实现。
注意:Optimism 上的欺诈证明目前尚未完全指定。虽然欺诈证明 构造和验证 已在 Cannon 中实现, 但欺诈证明博弈规范以及将输出根挑战者集成到 rollup-node 中是后续规范里程碑的一部分。
Proposer 的作用是构造和提交输出根,这些输出根是对 L2 状态的承诺,
提交到 L1 上的 L2OutputOracle
合约(结算层)。为此,proposer 定期
查询 rollup 节点 以获取从最新的
最终确定 的 L1 区块派生的最新输出根。然后,它获取输出根并
将其提交到结算层(L1)上的 L2OutputOracle
合约。
输出提案的提交被授权给单个帐户。预计此 帐户会随着时间的推移继续提交输出提案,以确保用户的提款不会停止。
L2 输出 proposer 预计会以确定的
间隔提交输出根,该间隔基于 L2OutputOracle
中配置的 SUBMISSION_INTERVAL
。SUBMISSION_INTERVAL
越大,
发送到 L2OutputOracle
合约的 L1 交易就越少,但是 L2 用户需要等待更长的时间才能将输出根包含在 L1(结算层)中,
该输出根包含他们从系统提款的意图。
诚实的 op-proposer
算法假定与 L2OutputOracle
合约建立连接,以了解
与必须提交的下一个输出提案相对应的 L2 区块号。它还假定与 op-node
建立连接,以便能够查询 optimism_syncStatus
RPC 端点。
import time
while True:
next_checkpoint_block = L2OutputOracle.nextBlockNumber()
rollup_status = op_node_client.sync_status()
if rollup_status.finalized_l2.number >= next_checkpoint_block:
output = op_node_client.output_at_block(next_checkpoint_block)
tx = send_transaction(output)
time.sleep(poll_interval)
CHALLENGER
帐户可以通过调用 deleteL2Outputs()
函数
并指定要删除的第一个输出的索引来删除多个输出根,这也将删除所有后续输出。
输出提案的提交是无需许可的,并且没有必须提交输出提案的间隔。预计用户将 "just in time" 地提出一个输出 提案,以方便他们自己的提款。必须在输出提案中放置一个保证金,以阻止恶意输出的提案。如果可以证明该输出是恶意的, 无论是通过欺诈证明还是通过证明证明,则可以削减保证金并将其用作 支付给支付 gas 费以删除恶意输出的用户的报酬。
op-proposer
仍然可以用于提交输出提案。op-proposer
的一个简单的实现
将按间隔提交输出提案。但是,这不是必需的,其他
proposer 实现可以在任何时候提交有效的输出。一个更理想的实现
将使用启发式方法,例如上次提交的时间或尚未包含在输出提案中的待处理提款数量。
下面描述了此 proposer 的一次迭代(将一个输出根发布到 L1):
sequenceDiagram
participant L1
participant Rollup Node
participant Proposer
L1->>L1: L1 区块已经最终确认
L1->>Rollup Node: L1 区块已经最终确认
Proposer->>Rollup Node: optimism_syncStatus
Rollup Node->>Proposer: 同步状态 { 已经最终确认的 L1 区块号 }
Proposer->>Rollup Node: optimism_outputAtBlock
Rollup Node->>Proposer: 输出根
Proposer->>L1: 查询 L2OutputOracle 以获取此输出根
L1->>Proposer: 输出根或 nil
Proposer->>Proposer: 如果当前输出已经提出则停止
Proposer->>L1: L2OutputOracle.proposeOutputRoot
由于启用无需许可的输出提案时可能同时运行多个 proposer,
因此 op-proposer 将在发送提案交易之前检查是否已针对给定的 L2 区块
号发布了其输出根。当 Proposer
查询 L2OutputOracle
以获取输出根时,这在上面的序列图中显示。如果它收到一个等于从 rollup 节点收到的
输出根,它将不会在交易中将此输出根发送到 L2OutputOracle
。
另请注意,虽然 op-proposer 实现仅基于已最终确认的 或安全的 L2 区块提交输出,但其他 proposer 实现可能会提交与不安全(未最终确认的)L2 区块相对应的输出。这会带来风险,因为 batchers 可能会提交与已提供(意味着这些输出现在无效)的 输出不对应的 L2 区块。
版本 v2.0.0
包括对 L2OutputOracle
ABI 的重大更改。
output_root
是一个 32 字节的字符串,它基于版本化的方案派生:
output_root = keccak256(version_byte || payload)
其中:
version_byte
(bytes32
) 一个简单的版本字符串,只要输出根的构造发生变化,就会递增。
payload
(bytes
) 是一个任意长度的字节字符串。
在输出承诺构造的初始版本中,版本为 bytes32(0)
,有效负载定义为:
payload = state_root || withdrawal_storage_root || latest_block_hash
其中:
latest_block_hash
(bytes32
) 是最新 L2 区块的区块哈希。
state_root
(bytes32
) 是所有执行层帐户的 Merkle-Patricia-Trie (MPT) 根。
该值经常使用,因此更接近 L2 输出根,从而无需证明其
包含在 latest_block_hash
的前映像中。这降低了 Merkle 证明深度和在 L1 上访问 L2 状态根的成本。
withdrawal_storage_root
(bytes32
) 提升了 Message
Passer 合约 存储的 Merkle-Patricia-Trie (MPT) 根。而不是对状态根进行 MPT 证明的提款(首先证明 L2toL1MessagePasser 的存储根与状态根,
然后针对该存储根的提款),我们可以直接针对 L2toL1MessagePasser 的存储根进行证明,
从而降低了 L1 上提款的验证成本。
L2 区块以 L2_BLOCK_TIME
(2 秒)的恒定速率产生。
每个 SUBMISSION_INTERVAL
必须将一个新的 L2 输出附加到链中一次,该间隔基于区块的数量。
确切的数字尚未确定,并且将取决于欺诈证明博弈的设计。
L2 输出 Oracle 合约实现以下接口:
/**
* @notice 此合约中记录的第一个 L2 区块的编号。
*/
uint256 public startingBlockNumber;
/**
* @notice 此合约中记录的第一个 L2 区块的时间戳。
*/
uint256 public startingTimestamp;
/**
* @notice 接受 L2 outputRoot 和相应 L2 区块的时间戳。该
* 时间戳必须等于 `nextTimestamp()` 返回的当前值才能被接受。
* 此函数只能由 Proposer 调用。
*
* @param _l2Output 检查点区块的 L2 输出。
* @param _l2BlockNumber 导致 _l2Output 的 L2 区块号。
* @param _l1Blockhash 必须包含在当前链中的区块哈希。
* @param _l1BlockNumber 具有指定区块哈希的区块号。
*/
function proposeL2Output(
bytes32 _l2Output,
uint256 _l2BlockNumber,
bytes32 _l1Blockhash,
uint256 _l1BlockNumber
)
/**
* @notice 删除给定输出索引对应的提案之后和包括该提案的所有输出提案。
* 只有挑战者地址可以删除输出。
*
* @param _l2OutputIndex 要删除的第一个 L2 输出的索引。此之后的所有输出
* 输出也将被删除。
*/
function deleteL2Outputs(uint256 _l2OutputIndex) external
/**
* @notice 计算需要进行检查点设置的下一个 L2 区块的区块号。
*/
function nextBlockNumber() public view returns (uint256)
startingBlockNumber
必须至少是第一个 Bedrock 区块的编号。
startingTimestamp
必须与起始区块的时间戳相同。
因此,提出的第一个 outputRoot
将位于高度 startingBlockNumber + SUBMISSION_INTERVAL
如果在生成和提交输出后 L1 发生重组,则 L2 状态和正确的输出可能会发生变化,从而导致错误的提案。这可以通过允许 proposer 在附加新输出时将 L1 区块号和哈希提交到输出 Oracle 来缓解。如果发生重组,区块哈希将与该编号的区块的哈希不匹配,并且调用将恢复。
- 原文链接: github.com/ethereum-opti...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!