L2链衍生规范

本文档详细描述了L2链衍生规范,重点在于如何从L1数据中生成L2链,包括区块衍生流程、区块提交方式(如批量提交、通道格式、帧格式等),以及涉及的关键架构组件(如L1遍历、L1检索、帧队列、通道银行、批量解码器等),以及Ecotone升级后的blob检索。同时,还涵盖了Payload属性的推导过程,包括交易列表的构建,以及和引擎交互,最终用于构建L2区块。

L2 链推导规范

<!-- 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 -->

<!-- 此文件中的所有术语表参考。 -->

概述

注意 以下假设只有一个排序器和 batcher。未来,该设计将被修改以适应 多个此类实体。

L2 链推导 - 从 L1 数据 推导 L2 - 是 rollup 节点 的主要职责之一,无论是在验证器模式下,还是在排序器模式下(其中推导充当对排序的健全性检查,并能够检测 L1 链重组)。

L2 链是从 L1 链推导出来的。特别是,L2 链开始 之后的每个 L1 块都被映射到一个 排序 epoch,该 epoch 至少包含一个 L2 块。每个 L2 块都恰好属于一个 epoch,并且我们称对应的 L1 块为其 L1 来源。epoch 的编号等于其 L1 来源块的编号。

为了推导 epoch 编号为 E 的 L2 块,我们需要以下输入:

  • 范围为 [E, E + SWS) 的 L1 块,称为 epoch 的 排序窗口,其中 SWS 是排序窗口大小。(请注意,排序窗口是重叠的。)
  • 来自排序窗口中块的 Batcher 交易
    • 这些交易使我们能够重建 epoch 的 排序器批量,每个批量将生成一个 L2 块。请注意:
    • L1 来源将永远不会包含任何构建排序器批量所需的数据,因为每个批量必须包含 L1 来源哈希。
    • 一个 epoch 可能没有排序器批量。
  • 在 L1 来源中进行的 存款(以 存款合约 发出的事件的形式)。
  • 来自 L1 来源的 L1 块属性(以推导 L1 属性存款交易)。
  • 上一个 epoch 的最后一个 L2 块之后的 L2 链状态,如果 E 是第一个 epoch,则为 L2 genesis 状态

要从头开始推导整个 L2 链,我们从 L2 genesis 状态L2 genesis 块 作为第一个 L2 块开始。然后,我们按顺序从每个 epoch 推导 L2 块,从 L2 链开始 之后的第一个 L1 块开始。有关我们如何在实践中实现这一点的更多信息,请参阅 架构部分。L2 链可能包含 pre-Bedrock 历史记录,但此处的 L2 genesis 指的是 Bedrock L2 genesis 块。

每个具有来源 l1_origin 的 L2 block 都受到以下约束(其值以秒为单位):

  • block.timestamp = prev_l2_timestamp + l2_block_time

    • prev_l2_timestamp 是紧邻此块之前的 L2 块的时间戳。如果没有前一个块,那么这是 genesis 块,并且明确指定其时间戳。
    • l2_block_time 是 L2 块之间时间的可配置参数(Optimism 上为 2 秒)。
  • l1_origin.timestamp &lt;= block.timestamp &lt;= max_l2_timestamp,其中

    • max_l2_timestamp = max(l1_origin.timestamp + max_sequencer_drift, prev_l2_timestamp + l2_block_time)
    • max_sequencer_drift 是一个可配置参数,用于限制排序器可以领先于 L1 的程度。

最后,每个 epoch 必须至少有一个 L2 块。

第一个约束意味着在 L2 链开始之后,必须每 l2_block_time 秒就有一个 L2 块。

第二个约束确保 L2 块时间戳永远不会早于其 L1 来源时间戳,并且永远不会超过其 max_sequencer_drift,除非在可能禁止每 l2_block_time 秒生成一个 L2 块的特殊情况下。(例如,在工作量证明 L1 上,可能会出现快速 L1 块生成的情况。)在任何一种情况下,排序器都会在超过 max_sequencer_drift 时强制执行 len(batch.transactions) == 0。有关更多详细信息,请参见 批量队列

每个 epoch 必须至少有一个 L2 块的最终要求确保 L1 中的所有相关信息(例如,存款)都以 L2 中的形式表示,即使它没有排序器批量。

合并后,以太坊具有固定的 12 秒 区块时间,尽管可以跳过某些 时间槽。在 2 秒 L2 区块时间下,因此我们期望每个 epoch 通常包含 12/2 = 6 个 L2 块。但是,为了在 L1 上跳过 时间槽 或暂时失去与 L1 的连接的情况下保持活跃性,排序器将生成更大的 epoch。对于连接丢失的情况,可能会在恢复连接后生成较小的 epoch,以防止 L2 时间戳越来越远。

预先区块推导

推导 L2 块要求我们已经构建了其排序器批量并推导了在其之前的所有 L2 块和状态更新。这意味着我们通常可以_急切地_推导 epoch 的 L2 块,而无需等待完整的排序窗口。仅在最坏的情况下,当 epoch 的第一个区块的排序器批量的某些部分出现在窗口的最后一个 L1 块中时,才需要在推导之前使用完整的排序窗口。请注意,这仅适用于_块_推导。排序器批量仍然可以被推导并临时排队,而无需从中推导区块。


批量提交

排序 & 批量提交概述

排序器接受来自用户的 L2 交易。它负责从这些交易中构建区块。对于每个这样的区块,它还会创建一个对应的 排序器批量。它还负责将每个批量提交给 数据可用性提供商(例如,以太坊 calldata),它通过其 batcher 组件来完成。

L2 块和批量之间的差异很微妙但很重要:该块包括 L2 状态根,而批量仅在给定的 L2 时间戳(等效地:L2 块号)提交到交易。一个块还包括对前一个块的引用(*)。

(*)这在某些边缘情况下很重要,即会发生 L1 重组,并且一个批量将被重新发布到 L1 链,但不会发布到先前的批量,而 L2 块的前任不可能更改。

这意味着即使排序器错误地应用了状态转换,批量中的交易仍将被视为规范 L2 链的一部分。批量仍然需要进行有效性检查(即,它们必须被正确编码),并且批量中的各个交易也一样(例如,签名必须有效)。无效的批量和无效的各个交易(在其他方面有效的批量中)将被正确的节点丢弃。

如果排序器错误地应用了状态转换并发布了 输出根,则此输出根将不正确。不正确的输出根将受到 fault proof 的质疑,然后将被为现有排序器批量的正确的输出根替换。

有关更多信息,请参阅 批量提交规范

批量提交线路格式

批量提交与 L2 链推导密切相关,因为推导过程必须解码已为批量提交目的而编码的批量。

Batcherbatcher 交易 提交给 数据可用性提供商。这些交易包含一个或多个 通道帧,这些帧是属于 通道 的数据块。

通道 是一系列压缩在一起的 排序器批量(对于任何 L2 块)。将多个批量分组在一起的原因仅仅是为了获得更好的压缩率,从而降低数据可用性成本。

通道可能太大而无法容纳在单个 batcher 交易 中,因此我们需要将其拆分为称为 通道帧 的块。单个 batcher 交易还可以承载多个帧(属于相同或不同的通道)。

这种设计使我们在如何将批量聚合到通道中以及如何将通道拆分到 batcher 交易中方面具有最大的灵活性。它特别使我们能够最大化 batcher 交易中的数据利用率:例如,它使我们能够将窗口的最后一个(小)帧与下一个窗口的大帧打包在一起。

将来,此通道识别功能还允许 batcher 使用多个签名者(私钥)并行提交一个或多个通道 (1)。

(1) 这有助于缓解以下问题:由于交易 nonce 值影响 L2 tx-pool,从而影响包含:由同一签名者进行的多个交易都卡在等待先前交易的包含中。

另请注意,我们使用流式压缩方案,并且当我们启动一个通道时,甚至当我们发送该通道中的第一个帧时,我们都不需要知道一个通道最终将包含多少个块。

并且通过在多个数据事务之间拆分通道,L2 可以具有比数据可用性层可能支持的更大的块数据。

所有这些都在下图说明。以下说明。

批量推导链图

第一行表示具有其编号的 L1 块。L1 块下的框表示包含在块内的 batcher 交易。L1 块下的弯曲线表示 存款(更具体地说,是由 存款合约 发出的事件)。

框内的每个彩色块表示一个 通道帧。因此,AB通道,而 A0A1B0B1B2 是帧。请注意:

  • 多个通道是交错的
  • 帧不需要按顺序传输
  • 单个 batcher 交易可以承载来自多个通道的帧

在下一行中,圆角框表示从通道中提取的各个 排序器批量。四个蓝色/紫色/粉色是从通道 A 推导出来的,而其他是从通道 B 推导出来的。这些批量在此处以它们从批量中解码出来的顺序表示(在这种情况下,B 首先被解码)。

请注意 此处的标题说“首先看到通道 B,并将首先将其解码为批量”,但这并非必要条件。例如,对于一个实现来说,也可以窥视通道并首先解码包含最旧批量的通道。

该图的其余部分在概念上与第一部分不同,它说明了在通道被重新排序后进行的 L2 链推导。

第一行显示 batcher 交易。请注意,在这种情况下,存在批量的排序,该排序使通道中的所有帧都连续出现。这通常不是真的。例如,在第二笔交易中,A1B0 的位置可以反转以获得完全相同的结果-无需更改图的其余部分。

第二行显示按正确顺序重建的通道。第三行显示从通道中提取的批量。因为通道是有序的,并且通道中的批量是顺序的,所以这意味着批量也是有序的。第四行显示从每个批量推导出的 L2 块。请注意,我们在此处具有 1-1 批量到块的映射,但是,正如我们稍后将看到的那样,如果 L1 上发布的批量中存在“间隙”,则可以插入不映射到批量的空块。

第五行显示 L1 属性存款交易,该交易在每个 L2 块中记录有关与 L2 块的 epoch 匹配的 L1 块的信息。第一个数字表示 epoch/L1x 编号,而第二个数字(“序列号”)表示 epoch 中的位置。

最后,第六行显示从先前提到的 存款合约 事件推导出的 用户存款交易

请注意该图右下角的 101-0 L1 属性交易。仅当帧 B2 指示它是通道中的最后一个帧,并且 (2) 不得插入空块时,才可能存在该交易。

该图未指定使用的排序窗口大小,但由此我们可以推断出它必须至少为 4 个块,因为通道 A 的最后一帧出现在块 102 中,但属于 epoch 99。

至于关于“安全类型”的注释,它解释了 L1 和 L2 上使用的块的分类。

这些安全级别映射到与 execution-engine API 交互时传输的 headBlockHashsafeBlockHashfinalizedBlockHash 值。

Batcher 交易格式

Batcher 交易被编码为 version_byte ++ rollup_payload(其中 ++ 表示连接)。

version_byte rollup_payload
0 frame ... (一个或多个帧,已连接)

未知的版本使 Batcher 交易无效(它必须被 rollup 节点忽略)。 Batcher 交易中的所有帧都必须可解析。如果任何一个帧无法解析,则该交易中的所有帧都将被拒绝。

通过验证交易的 to 地址是否与 Batcher 收件箱地址匹配,以及 from 地址是否与从 系统配置 中加载的 Data Sender 地址匹配来验证批量交易,数据来自交易数据从中读取的 L1 块。

帧格式

channel 帧 被编码为:

frame = channel_id ++ frame_number ++ frame_data_length ++ frame_data ++ is_last

channel_id        = bytes16
frame_number      = uint16
frame_data_length = uint32
frame_data        = bytes
is_last           = bool

其中 uint32uint16 都是大端无符号整数。类型名称应根据 Solidity ABI 进行解释和编码。

帧中的所有数据都是固定大小的,除了 frame_data。固定开销为 16 + 2 + 4 + 1 = 23 字节。 固定大小的帧元数据避免了与目标总数据长度的循环依赖关系,从而简化了具有不同内容长度的帧的打包。

其中:

  • channel_id 是通道的不透明标识符。不应重复使用,建议使用随机标识符;但是,在超时规则之外,不会检查其有效性
  • frame_number 标识帧在通道中的索引
  • frame_data_lengthframe_data 的长度,以字节为单位。它上限为 1,000,000 字节。
  • frame_data 是属于通道的字节序列,在逻辑上位于先前帧的字节之后
  • is_last 是一个单字节,如果帧是通道中的最后一个帧,则值为 1,如果通道中有帧,则值为 0。任何其他值都使帧无效(它必须被 rollup 节点忽略)。

通道格式

通道被编码为 channel_encoding,定义为:

rlp_batches = []
for batch in batches:
    rlp_batches.append(batch)
channel_encoding = compress(rlp_batches)

其中:

  • batches 是输入,即按照下一节(“批量编码”)的规定进行字节编码的一系列批量
  • rlp_batches 是 RLP 编码批量的串联
  • compress 是使用 ZLIB 算法(如 RFC-1950 中指定的)执行压缩的函数,不带字典
  • channel_encodingrlp_batches 的压缩版本

在解压缩通道时,我们将解压缩数据量限制为 MAX_RLP_BYTES_PER_CHANNEL(当前为 10,000,000 字节),以避免“zip-bomb”类型的攻击(其中小的压缩输入解压缩为大量数据)。如果解压缩的数据超过限制,则处理方式如同通道仅包含前 MAX_RLP_BYTES_PER_CHANNEL 解压缩字节。该限制设置在 RLP 解码上,因此即使通道的大小大于 MAX_RLP_BYTES_PER_CHANNEL,也可以接受在 MAX_RLP_BYTES_PER_CHANNEL 中可以解码的所有批量。确切的要求是 length(input) &lt;= MAX_RLP_BYTES_PER_CHANNEL

虽然上面的伪代码暗示所有批量都是预先知道的,但是可以对 RLP 编码的批量执行流式压缩和解压缩。这意味着我们可以在知道通道将包含多少批量(以及多少帧)之前,开始在 batcher 交易 中包含通道帧。

批量格式

回想一下,批量包含要包含在特定 L2 块中的交易列表。

批量编码为 batch_version ++ content,其中 content 取决于 batch_version

batch_version content
0 rlp_encode([parent_hash, epoch_number, epoch_hash, timestamp, transaction_list])

其中:

  • batch_version 是一个单字节,位于 RLP 内容之前,类似于交易类型。
  • rlp_encode 是根据 [RLP 格式] 编码批量的函数,[x, y, z] 表示包含项目 xyz 的列表
  • parent_hash 是上一个 L2 块的区块哈希
  • epoch_numberepoch_hash 是与 L2 块的 排序 epoch 相对应的 L1 块的编号和哈希
  • timestamp 是 L2 块的时间戳
  • transaction_listEIP-2718 编码交易的 RLP 编码列表。

未知的版本使批量无效(它必须被 rollup 节点忽略),畸形的内容也是如此。

epoch_numbertimestamp 还必须遵守 批量队列 部分中列出的约束,否则该批量被视为无效并将被忽略。


架构

以上主要描述了 L2 链推导中使用的通用编码, 主要是在 batcher 交易 中如何编码批量。

本节介绍如何使用管道架构从 L1 批量生成 L2 链。

验证器可以以不同的方式实现此功能,但其语义必须等效,以免与 L2 链发生分歧。

L2 链推导管道

我们的架构将推导过程分解为由以下阶段组成的管道:

  1. L1 遍历
  2. L1 检索
  3. 帧队列
  4. 通道库
  5. 通道读取器 (批量解码)
  6. 批量队列
  7. Payload 属性推导
  8. 引擎队列

数据从管道的开始(外部)流向结束(内部)。 从最内层阶段,数据从最外层阶段提取。

但是,数据以相反的顺序处理。这意味着如果在最后一个阶段有任何要处理的数据,它 将首先被处理。处理按照可以在每个阶段执行的“步骤”进行。我们尝试执行尽可能多的步骤, 在最后一个(最内层)阶段执行操作,然后再在其外部阶段执行任何步骤,依此类推。

这确保我们在提取更多数据之前先使用已经拥有的数据,并最大程度地减少数据遍历 推导管道的延迟。

每个阶段都可以根据需要维护其自身的内部状态。特别是,每个阶段都维护对最新 L1 块的 L1 块引用(编号 + 哈希),以便已完全处理来自先前块的所有数据, 并且正在或已经处理来自该块的数据。这使最内层阶段可以考虑用于生成 L2 链的 L1 数据可用性的最终确定,以便在 L2 链的输入变得不可逆时反映在 L2 链 forkchoice 中。

让我们简要描述管道的每个阶段。

L1 遍历

在_L1 遍历_阶段,我们仅读取下一个 L1 块的标头。在正常操作中,这些将是创建的新 L1 块,尽管在同步时或在 L1 重组 的情况下,我们也可以读取旧块。

遍历 L1 块后,更新 L1 检索阶段使用的 系统配置 副本,以便 Data Sender 身份验证始终精确到该阶段读取的 L1 块。

L1 检索

在_L1 检索_阶段,我们读取从外部阶段(L1 遍历)获取的块,并从其 batcher 交易 中提取数据。Batcher 交易具有以下属性:

  • to 字段等于配置的 Batcher 收件箱地址。

  • 从交易签名 (vrs) 恢复的发件人是 从与数据的 L1 块匹配的系统配置中加载的 Data Sender 地址。

每个 Batcher 交易都已进行版本控制,并包含一系列 通道帧,以 供帧队列读取,请参见 批量提交线路格式。每个 Batcher 块中的交易都按其出现在块中的顺序进行处理,方法是将 calldata 传递到 下一阶段。

Ecotone: Blob检索

通过 Ecotone 升级,检索阶段得到了扩展,以支持额外的数据可用性来源: EIP-4844 blobs。在 Ecotone 升级之后,我们修改了对 Data Sender 交易的迭代,以 不同方式处理交易类型 == 0x03 (BLOB_TX_TYPE) 的交易。如果 Data Sender 交易是 blob 交易,那么即使存在 calldata,也必须忽略 calldata。相反:

  • 对于 blob_versioned_hashes 中的每个 blob hash,检索与之匹配的 blob。可以从许多不同的来源检索 blob。建议通过 /eth/v1/beacon/blob_sidecars/ 端点,使用 indices 过滤器跳过不相关的 blob,从本地信标节点检索。对于每个检索到的 blob:
    • 应该(如果是不可信的来源则必须)对该 blob 进行密码学验证,以验证其版本化哈希。
    • 如果 blob 具有 有效的编码,将其解码为连续的字节字符串,并将其传递到下一阶段。否则,将忽略该 blob。

请注意,blob 类型的 batcher 交易必须与其他 batcher 交易在同一循环中处理,以保持批次始终按照它们在区块中出现的顺序处理的不变性。我们忽略 blob 交易中的 calldata,以便将来可以将其用于批量元数据或其他目的。

Blob 编码

EIP-4844 交易中的每个 blob 实际上由 FIELD_ELEMENTS_PER_BLOB = 4096 个字段元素组成。

每个字段元素都是一个素数域中的数字,该素数域为 BLS_MODULUS = 52435875175126190479447740508185965837690552500527637822603658699938581184513。 这个数字并不代表一个完整的 uint256math.log2(BLS_MODULUS) = 254.8570894...

L1 共识规范 描述了多项式的编码。 字段元素被编码为大端整数 (KZG_ENDIANNESS = big)。

为了节省计算开销,每个字段元素仅使用 254 位用于 rollup 数据。

每次将 127 字节的应用层 rollup 数据编码到 blob 的 4 个相邻字段元素中:

## read(N): 从应用层 rollup-data 中读取接下来的 N 个字节。下一次读取从上次停止的地方开始。
## write(V): 将 V(一个或多个字节)附加到原始 blob。
bytes tailA = read(31)
byte x = read(1)
byte A = x & 0b0011_1111
write(A)
write(tailA)

bytes tailB = read(31)
byte y = read(1)
byte B = (y & 0b0000_1111) | (x & 0b1100_0000) >> 2)
write(B)
write(tailB)

bytes tailC = read(31)
byte z = read(1)
byte C = z & 0b0011_1111
write(C)
write(tailC)

bytes tailD = read(31)
byte D = ((z & 0b1100_0000) >> 2) | ((y & 0b1111_0000) >> 4)
write(D)
write(tailD)

每个写入的字段元素看起来像这样:

  • 以准备好的 6 位左填充字节值之一开头,以使字段元素保持在有效范围内。
  • 后跟 31 字节的应用层数据,以填充字段元素的低 31 字节。

写入的输出应如下所示:

&lt;----- 元素 0 ----->&lt;----- 元素 1 ----->&lt;----- 元素 2 ----->&lt;----- 元素 3 ----->
| 字节 A |  tailA...  || 字节 B |  tailB...  || 字节 C |  tailC...  || 字节 D |  tailD...  |

上述过程重复 1024 次,以填充所有 4096 个元素, 总共有 (4 * 31 + 3) * 1024 = 130048 字节的数据。

当解码 blob 时,每个字段元素的最上面的两位必须为 0, 以使编码/解码是双射的。

rollup 数据的第一个字节(第一个字段元素中的第二个字节)用作版本字节。

在版本 0 中,接下来的 3 个字节用于将 rollup 数据的长度编码为大端 uint24。 长度分隔符之后的任何尾随数据都必须为 0,以保持编码/解码是双射的。 如果长度大于 130048 - 4,则 blob 无效。

如果任何编码无效,则必须忽略整个 blob。

Frame Queue (帧队列)

Frame Queue (帧队列)一次缓冲一个数据交易, 解码为 channel frames(通道帧),以供下一阶段使用。 请参阅 Batcher transaction format(Batcher 交易格式)Frame format(帧格式) 规范。

Channel Bank(通道银行)

Channel Bank 阶段负责管理从 L1 检索阶段写入的通道库中的缓冲。通道银行阶段中的一个步骤尝试从“准备好”的通道中读取数据。

目前,通道会被完全缓冲,直到被读取或丢弃。 未来的 ChannelBank 版本可能会支持流式通道。

为了限制资源使用,Channel Bank 根据通道大小进行清理,并使旧通道超时。

通道按照 FIFO 顺序记录在一个名为 channel queue(通道队列) 的结构中。当第一次看到属于该通道的帧时,该通道会被添加到通道队列中。

Pruning(清理)

成功插入新帧后,会对 ChannelBank 进行清理: 通道会按照 FIFO 顺序被丢弃,直到 total_size &lt;= MAX_CHANNEL_BANK_SIZE,其中:

  • total_size 是每个通道大小的总和,即通道的所有缓冲帧数据的总和, 外加每个帧 200 字节的帧开销。
  • MAX_CHANNEL_BANK_SIZE 是一个协议常量,为 100,000,000 字节。
Timeouts(超时)

通道在其打开时所在的 L1 来源会与通道一起被跟踪,为 channel.open_l1_block, 并确定通道数据在被清理之前保留的最大 L1 区块跨度。

如果 current_l1_block.number > channel.open_l1_block.number + CHANNEL_TIMEOUT,则通道会超时,其中:

  • current_l1_block 是该阶段当前遍历的 L1 来源。
  • CHANNEL_TIMEOUT 是一个 rollup 可配置的参数,以 L1 区块的数量表示。

超时通道的新帧将被丢弃,而不是被缓冲。

Reading(读取)

在读取时,如果第一个打开的通道已超时,则将其从通道银行中移除。

在 Canyon 网络升级之前,一旦第一个打开的通道(如果有)未超时且准备就绪, 则会读取该通道并将其从通道银行中移除。Canyon 网络升级之后,整个通道银行 将按照 FIFO 顺序(按打开时间)扫描,并且将返回第一个准备就绪(即未超时)的通道。

当来自 L1 区块的 timestamp 大于或等于 canyon 时间的帧首次进入通道队列时,canyon 行为将激活。

如果满足以下条件,则通道已准备就绪:

  • 通道已关闭
  • 通道具有直到关闭帧的连续帧序列

如果没有通道准备就绪,则会读取下一个帧并将其提取到通道银行中。

Loading frames(加载帧)

当帧引用的通道 ID 尚未存在于 Channel Bank 中时, 将打开一个新通道,标记为当前 L1 区块,并附加到通道队列中。

帧插入条件:

  • 与尚未从通道银行中清理掉的超时通道匹配的新帧将被丢弃。
  • 对于尚未从通道银行中清理掉的帧,重复帧(按帧号)将被丢弃。
  • 重复关闭(新帧 is_last == 1,但通道已经看到一个关闭帧并且尚未从通道银行中清理掉)将被丢弃。

如果一个帧正在关闭(is_last == 1),则会从通道中移除任何现有更高编号的帧。

请注意,虽然这允许在从通道银行中清理掉通道 ID 后重复使用它们,但建议 batcher 实现使用唯一的通道 ID。

Channel Reader (Batch Decoding)(通道读取器(批次解码))

在此阶段,我们解压缩从上一阶段提取的通道,然后从解压缩的字节流中解析 批次

请参阅 Batch Format (批次格式),了解解压缩和解码规范。

Batch Queue(批次队列)

Batch Buffering(批次缓冲)阶段,我们按时间戳重新排序批次。如果某些 time slots (Slot) 缺少批次, 并且存在具有更高时间戳的有效批次,则此阶段还会生成空批次以填充空白。

只要有一个紧随当前 safe L2 head (安全 L2 头) 的时间戳的连续批次(可以从规范 L1 链中派生出最后一个区块),批次就会被推送到下一阶段。 批次的父哈希也必须与当前安全 L2 头的哈希匹配。

请注意,从 L1 派生的批次中存在任何空白意味着此阶段需要缓冲整个 sequencing window (排序窗口),然后才能生成空批次(因为在最坏的情况下,缺失的批次可能在窗口的最后一个 L1 区块中存在数据)。

一个批次可以有 4 种不同的有效性形式:

  • drop:批次无效,并且将始终在将来,除非我们进行重组。可以从缓冲区中删除它。
  • accept:批次有效,应进行处理。
  • undecided:我们缺少 L1 信息,直到我们可以进行批次过滤。
  • future:批次可能有效,但尚未处理,应稍后再次检查。

批次的处理顺序是包含在 L1 上的顺序:如果可以 accept 多个批次,则应用第一个批次。 实现可以将 future 批次推迟到以后的派生步骤,以减少验证工作。

批次的有效性派生如下:

定义:

  • batchBatch format section (批次格式部分) 中定义。
  • epoch = safe_l2_head.l1_origin 是一个与批次耦合的 L1 origin (L1 来源),具有以下属性: number(L1 区块号)、hash(L1 区块哈希)和 timestamp(L1 区块时间戳)。
  • inclusion_block_number 是首次_完全_派生 batch 时的 L1 区块号, 即由上一阶段解码和输出。
  • next_timestamp = safe_l2_head.timestamp + block_time 是下一个批次应具有的预期 L2 时间戳, 请参阅 block time information (区块时间信息)
  • next_epoch 可能尚未知,但如果可用,则将是 epoch 之后的 L1 区块。
  • batch_originepochnext_epoch,具体取决于验证。

请注意,批次的处理可以推迟到 batch.timestamp &lt;= next_timestamp, 因为无论如何都必须保留 future 批次。

规则,按验证顺序:

  • batch.timestamp > next_timestamp -> future:即批次必须准备好处理。
  • batch.timestamp &lt; next_timestamp -> drop:即批次不能太旧。
  • batch.parent_hash != safe_l2_head.hash -> drop:即父哈希必须等于 L2 安全头区块哈希。
  • batch.epoch_num + sequence_window_size &lt; inclusion_block_number -> drop:即批次必须及时包含。
  • batch.epoch_num &lt; epoch.number -> drop:即批次来源不能早于 L2 安全头。
  • batch.epoch_num == epoch.number:将 batch_origin 定义为 epoch
  • batch.epoch_num == epoch.number+1
    • 如果 next_epoch 未知 -> undecided: 即,更改 L1 来源的批次在获得 L1 来源数据之前无法处理。
    • 如果已知,则将 batch_origin 定义为 next_epoch
  • batch.epoch_num > epoch.number+1 -> drop:即,每个 L2 区块的 L1 来源更改不能超过一个 L1 区块。
  • batch.epoch_hash != batch_origin.hash -> drop:即,批次必须引用规范 L1 来源, 以防止批次被重放到意外的 L1 链上。
  • batch.timestamp &lt; batch_origin.time -> drop:强制执行最小 L2 时间戳规则。
  • batch.timestamp > batch_origin.time + max_sequencer_drift:强制执行 L2 时间戳漂移规则, 但存在例外,以保留上述最小 L2 时间戳不变性:
    • len(batch.transactions) == 0
    • epoch.number == batch.epoch_num: 这意味着批次尚未提前 L1 来源,因此必须根据 next_epoch 进行检查。
      • 如果 next_epoch 未知 -> undecided: 如果没有下一个 L1 来源,我们尚且无法确定是否可以保持时间不变。
      • 如果 batch.timestamp >= next_epoch.time -> drop: 批次本可以采用下一个 L1 来源而不会破坏 L2 时间 >= L1 时间 不变性。
    • len(batch.transactions) > 0:-> drop: 当超过排序器时间漂移时,永远不允许排序器包含交易。
  • batch.transactions:如果 batch.transactions 列表包含无效或仅通过其他方式派生的交易,则 drop

如果没有批次可以 accept,并且该阶段已完成对可以从 epoch.number + sequence_window_size 高度的 L1 区块中完全读取的所有批次的缓冲,并且 next_epoch 可用, 则可以使用以下属性派生一个空批次:

  • parent_hash = safe_l2_head.hash
  • timestamp = next_timestamp
  • transactions 为空,即没有排序器交易。存款交易可能会在下一阶段添加。
  • 如果 next_timestamp &lt; next_epoch.time:将重复当前 L1 来源,以保留 L2 时间不变性。
    • epoch_num = epoch.number
    • epoch_hash = epoch.hash
  • 如果该批次是 epoch 的第一个批次,则使用该 epoch 而不是推进 epoch,以确保 每个 epoch 至少有一个 L2 区块。
    • epoch_num = epoch.number
    • epoch_hash = epoch.hash
  • 否则,
    • epoch_num = next_epoch.number
    • epoch_hash = next_epoch.hash

Payload Attributes Derivation (有效负载属性推导)

Payload Attributes Derivation(有效负载属性推导)阶段,我们将从上一阶段获得的批次转换为 PayloadAttributes 结构的实例。这种结构编码了需要出现在 区块中的交易,以及其他区块输入(时间戳、费用接收者等)。有效负载属性推导在 Deriving Payload Attributes section (推导有效负载属性部分) 中详细介绍。

此阶段维护其自己的 system configuration (系统配置) 副本,独立于 L1 检索阶段。 每当批次输入引用的 L1 epoch 发生变化时,系统配置都会使用 L1 日志事件进行更新。

Engine Queue (引擎队列)

Engine Queue(引擎队列)阶段,先前导出的 PayloadAttributes 结构被缓冲并发送到 execution engine (执行引擎),以执行并转换为适当的 L2 区块。

该阶段维护对三个 L2 区块的引用:

此外,它还会缓冲一个最近处理的安全 L2 区块的简短引用历史记录,以及每个区块从中派生的 L1 区块的引用。 此历史记录不必完整,但可以将以后的 L1 最终确定性信号转换为 L2 最终确定性。

Engine API usage(引擎 API 用法)

要与引擎交互,使用 execution engine API (执行引擎 API),具有以下 JSON-RPC 方法:

Bedrock, Canyon, Delta:API 用法
  • engine_forkchoiceUpdatedV2 — 如果不同,则将 forkchoice(即链头)更新为 headBlockHash,并且 如果有效负载属性参数不为 null,则指示引擎开始构建执行有效负载。
  • engine_getPayloadV2 — 检索先前请求的执行有效负载构建。
  • engine_newPayloadV2 — 执行执行有效负载以创建区块。
Ecotone:API 用法
  • engine_forkchoiceUpdatedV3 — 如果不同,则将 forkchoice(即链头)更新为 headBlockHash,并且 如果有效负载属性参数不为 null,则指示引擎开始构建执行有效负载。
  • engine_getPayloadV3 — 检索先前请求的执行有效负载构建。
  • engine_newPayload

由于 engine_newPayloadV3 仅支持 Ecotone 执行有效负载,因此当前版本的 op-node 使用 v3 引擎 API RPC 方法以及 engine_newPayloadV2engine_forkchoiceUpdatedV3engine_getPayloadV3 都向后兼容 Bedrock、Canyon 和 Delta 有效负载。

先前版本的 op-node 使用 v2v1 方法。

执行有效负载是 ExecutionPayloadV3 类型的对象。

ExecutionPayload 具有以下要求:

  • Bedrock
    • withdrawals 字段必须为 nil
    • blob gas used 字段必须为 nil
    • blob gas limit 字段必须为 nil
  • Canyon, Delta
    • withdrawals 字段必须为非 nil
    • withdrawals 字段必须为空列表
    • blob gas used 字段必须为 nil
    • blob gas limit 字段必须为 nil
  • Ecotone
    • withdrawals 字段必须为非 nil
    • withdrawals 字段必须为空列表
    • blob gas used 字段必须为 0
    • blob gas limit 字段必须为 0
Forkchoice synchronization(分叉选择同步)

如果在派生或处理其他输入之前有任何分叉选择更新要应用,则首先将这些更新应用于引擎。

以下情况可能会发生此同步:

  • L1 最终确定性信号最终确定一个或多个 L2 区块:更新“finalized”L2 区块。
  • 成功合并不安全的 L2 区块:更新“safe”L2 区块。
  • 派生管道重置后的第一件事,以确保一致的执行引擎分叉选择状态。

通过在引擎 API 上调用 fork choice updated(分叉选择已更新) 来应用新的分叉选择状态。 在分叉选择状态有效性错误时,必须重置派生管道以恢复到一致状态。

L1-consolidation:payload attributes matching(有效负载属性匹配)

如果不安全头领先于安全头,则会尝试 consolidation(合并),验证 现有不安全 L2 链是否与从规范 L1 数据派生的 L2 输入匹配。

在合并期间,我们考虑最旧的不安全 L2 区块,即直接位于安全头之后的不安全 L2 区块。如果 有效负载属性与此最旧的不安全 L2 区块匹配,则该区块可以被认为是“safe”区块,并成为新的 安全头。

检查派生的 L2 有效负载属性的以下字段与 L2 区块是否相等:

  • Bedrock、Canyon、Delta、Ecotone 区块
    • parent_hash
    • timestamp
    • randao
    • fee_recipient
    • transactions_list(首先是长度,然后是每个编码交易的相等性,包括存款)
    • gas_limit
  • Canyon、Delta、Ecotone 区块
    • withdrawals(首先是存在性,然后是长度,然后是每个编码提款的相等性)
  • Ecotone 区块
    • parent_beacon_block_root

如果合并成功,则如上节所述,分叉选择更改将同步。

如果合并失败,则 L2 有效负载属性将立即处理,如下节所述。 选择有效负载属性是为了支持先前的不安全 L2 区块,从而在当前安全区块之上创建 L2 链重组。立即处理新的替代属性使像 go-ethereum 这样的执行引擎能够 实施更改,因为可能不支持链顶部的线性回滚。

L1-sync:payload attributes processing(有效负载属性处理)

如果安全 L2 头和不安全 L2 头相同(无论是由于合并失败还是其他原因),我们会将 L2 有效负载 属性发送到执行引擎,以构建为适当的 L2 区块。 然后,此 L2 区块将成为新的 L2 安全头和不安全头。

如果由于验证错误而无法将从批次创建的有效负载属性插入到链中(即 区块中存在无效交易或状态转换),则应删除该批次,并且不应推进安全头。引擎队列将尝试使用来自批次队列的该时间戳的下一个批次。如果找不到有效批次,则汇总节点将创建一个仅包含存款的批次,该批次应始终通过验证,因为存款始终有效。

与执行引擎的交互通过执行引擎 API 在 Communication with the Execution Engine (与执行引擎的通信) 部分中详细介绍。

然后,使用以下顺序处理有效负载属性:

引擎 API 错误处理:

  • 在 RPC 类型错误时,应在将来的步骤中重新尝试有效负载属性处理。
  • 在有效负载处理错误时,必须删除属性,并且不得更改分叉选择状态。
    • 最终,派生管道将生成备用有效负载属性,无论有无批次。
    • 如果有效负载属性仅包含存款,则如果这些存款无效,则这是一个严重的派生错误。
  • 在分叉选择状态有效性错误时,必须重置派生管道以恢复到一致状态。
Processing unsafe payload attributes(处理不安全的有效负载属性)

如果没有剩余的分叉选择更新或 L1 数据要处理,并且如果可以通过诸如排序器通过 p2p 网络发布它的不安全源获得下一个可能的 L2 区块,则会乐观地将其处理为 “unsafe”区块。这减少了以后的派生工作,仅在顺利运行的情况下与 L1 合并,并使用户能够比 L1 确认 L2 批次更快地看到 L2 链的头部。

要处理不安全的有效负载,有效负载必须:

  • 具有高于当前安全 L2 头的区块号。
    • 仅由于 L1 重组才能重组安全 L2 头。
  • 具有与当前不安全 L2 头匹配的父区块哈希。
    • 这可以防止执行引擎单独同步不安全 L2 链中的较大间隙。
    • 这可以防止不安全的 L2 区块重组其他先前验证的 L2 区块。
    • 此检查可能会在将来的版本中更改,以例如采用 L1 快照同步协议。

然后,使用以下顺序处理有效负载:

  • Bedrock/Canyon/Delta 有效负载
    • engine_newPayloadV2:处理有效负载。它尚未成为规范。
    • engine_forkchoiceUpdatedV2:使有效负载成为规范的不安全 L2 头,并保留安全/最终确定的 L2 头。
  • Ecotone 有效负载
    • engine_newPayloadV3:处理有效负载。它尚未成为规范。
    • engine_forkchoiceUpdatedV3:使有效负载成为规范的不安全 L2 头,并保留安全/最终确定的 L2 头。

引擎 API 错误处理:

  • 在 RPC 类型错误时,应在将来的步骤中重新尝试有效负载处理。
  • 在有效负载处理错误时,必须删除有效负载,并且不应将其标记为规范。
  • 在分叉选择状态有效性错误时,必须重置派生管道以恢复到一致状态。

Resetting the Pipeline(重置管道)

可以重置管道,例如,如果我们检测到 L1 reorg (重组) 这使汇总节点能够处理 L1 链重组事件。

重置会将管道恢复为一种状态,该状态产生与完整 L2 派生过程相同的输出, 但是从现有的 L2 链开始,该链被向后遍历到足以与当前的 L1 链协调。

请注意,此算法涵盖了几个重要的用例:

  • 初始化管道而不从 0 开始,例如,当汇总节点使用现有引擎实例重新启动时。
  • 如果管道与执行引擎链不一致,则恢复管道,例如,当引擎同步/更改时。
  • 当 L1 链重组时恢复管道,例如,延迟的 L1 区块被孤立,或者发生了较大的证明失败。
  • 初始化管道以使用先前的 L1 和 L2 历史记录在容错程序中派生有争议的 L2 区块。

处理这些情况也意味着可以将节点配置为急切地同步具有 0 个确认的 L1 数据, 因为如果 L1 以后确实将数据识别为规范数据,则可以撤消更改,从而实现安全的低延迟使用。

首先重置引擎队列,以确定继续派生的 L1 和 L2 起始点。 在此之后,其他阶段将彼此独立地重置。

Finding the sync starting point(查找同步起点)

要找到起点,相对于链的头部向后遍历,有几个步骤:

  1. 查找当前 L2 分叉选择状态
    • 如果找不到 finalized 区块,则从 Bedrock 创世区块开始。
    • 如果找不到 safe 区块,则回退到 finalized 区块。
    • unsafe 区块应始终可用并且与上述一致 (在罕见的引擎损坏恢复情况下,它可能不存在,这正在审查中)。
  2. 查找第一个 L2 区块,该区块具有与新 unsafe 起始点相关的合理 L1 参考, 从先前的 unsafe 开始,回到 finalized 且不再向后。
    • 如果且仅当:L2 区块的 L1 来源已知且为规范,或者未知并具有领先于 L1 的区块号。
  3. 查找第一个 L2 区块,该区块具有早于排序窗口的 L1 参考,作为新的 safe 起始点, 从上述合理的 unsafe 头部开始,回到 finalized 且不再向后。
    • 如果在任何时候 L1 来源已知但不是规范,则 unsafe 头部将修订为当前头的父级。
    • 具有已知规范 L1 来源的最高 L2 区块将记住为 highest
    • 如果在任何时候区块中的 L1 来源与派生规则相比是损坏的,则出现错误。损坏包括:
      • L1 来源区块号或父哈希与父 L1 来源不一致
      • L1 序列号不一致(对于 L1 来源更改始终更改为 0,否则如果不更改则递增 1
    • 如果 L2 区块 n 的 L1 来源比 highest 的 L1 来源早于排序窗口, 并且 n.sequence_number == 0,则 n 的父 L2 区块将是 safe 起始点。
  4. finalized L2 区块将一直保持作为 finalized 起始点。
  5. 查找第一个 L2 区块,该区块具有早于通道超时的 L1 参考
    • 此区块引用的 L1 来源(我们称之为 l2base)将是 L2 管道派生的 base: 通过从此处开始,各阶段可以缓冲任何必要的数据,同时删除不完整的派生输出,直到 L1 遍历赶上实际的 L2 安全头部。

在向后遍历 L2 链时,实现可以健全性检查,与现有的分叉选择状态相比,起始点是否设置得太远, 以避免由于错误配置而导致的大规模重组。

实施者请注意:步骤 1-4 称为 FindL2Heads。步骤 5 目前是引擎队列重置的一部分。 这可能会更改为将起始点搜索与裸重置逻辑隔离。

Resetting derivation stages(重置派生阶段)
  1. L1 Traversal(L1 遍历):从 L1 base 作为下一个阶段要提取的第一个区块开始。
  2. L1 Retrieval(L1 检索):清空先前的数据,并获取 base L1 数据,或者将获取工作推迟到以后的管道步骤。
  3. Frame Queue(帧队列):清空队列。
  4. Channel Bank(通道银行):清空通道银行。
  5. Channel Reader(通道读取器):重置任何批次解码状态。
  6. Batch Queue(批次队列):清空批次队列,使用 base 作为初始 L1 参考点。
  7. Payload Attributes Derivation(有效负载属性派生):清空任何批次/属性状态。
  8. Engine Queue(引擎队列):
    • 使用同步起始点状态初始化 L2 分叉选择状态。(finalized/safe/unsafe
    • 将此阶段的 L1 参考点初始化为 base
    • 需要一个分叉选择更新作为第一个任务
    • 重置任何最终确定性数据 在必要时,从 base 开始的阶段可以从 l2base 块中编码的数据初始化它们的 system-config。
关于合并后(Post-Merge)的重组(reorgs)

请注意,在 合并 之后,重组的深度将受到 L1 最终性延迟 的限制 (2 个 L1 信标周期,或大约 13 分钟,除非超过 1/3 的网络持续不同意)。 新的 L1 区块可能会在每个 L1 信标周期(大约 6.4 分钟)内最终确定,并且取决于这些 最终性信号和批量包含,导出的 L2 链也将变得不可逆。

请注意,这种形式的最终确定仅影响输入,节点可以通过从这些不可逆的输入和设定的协议规则与参数重现链,从而主观地认为该链是不可逆的。

然而,这与发布在 L1 上的输出完全无关,后者需要一种证明形式,如故障证明或 zk-proof 来最终确定。像 L1 上的提款这样的乐观 Rollup 输出只有在经过一周 没有争议(故障证明挑战窗口)后才被标记为“最终确定”,这与权益证明最终确定(proof-of-stake finalization)的名称发生冲突。


推导 Payload 属性

对于每个从 L1 数据推导出的 L2 区块,我们需要构建 payload 属性, 它由 PayloadAttributesV2 对象的 扩展版本 表示, 其中包括额外的 transactionsnoTxPool 字段。

这个过程发生在由验证器节点运行的 payloads-attributes 队列中,以及由排序器节点运行的区块生产过程中 (如果交易是批量提交的,则排序器可以启用 tx-pool 的使用)。

推导交易列表

对于排序器要创建的每个 L2 区块,我们从与目标 L2 区块号匹配的 排序器批次 开始。 如果 L1 链没有包含目标 L2 区块的批次,这可能是一个空的自动生成的批次。记住,批次包含一个 排序 epoch 编号、一个 L2 时间戳和一个交易列表。

这个区块是 排序 epoch 的一部分, 其编号与 L1 区块的编号相匹配(它的 L1 源 g-l1-origin)。 此 L1 区块用于推导 L1 属性,并且(对于 epoch 中的第一个 L2 区块)用户存款。

因此,PayloadAttributesV2 对象必须包含以下交易:

  • 一个或多个 已存款交易,有两种类型:
    • 从 L1 源推导出的单个 L1 属性已存款交易 g-l1-attr-deposit
    • 对于 epoch 中的第一个 L2 区块,从 L1 源的 收据 推导出的零个或多个 用户已存款交易 g-user-deposited
  • 零个或多个 [网络升级自动化交易]:用于执行网络升级的特殊交易。
  • 零个或多个 排序交易 g-sequencing:由 L2 用户签名的常规交易,包含在排序器批次中。

交易必须按此顺序出现在 payload 属性中。

L1 属性从 L1 区块头读取,而存款从 L1 区块的 收据 中读取。 有关存款如何编码为日志条目的详细信息,请参阅 存款合约规范

网络升级自动化交易

一些网络升级需要在特定区块进行自动化的合约更改或部署。 为了自动化这些,而不在执行层添加持久性更改, 可以插入特殊交易作为推导过程的一部分。

Ecotone

Ecotone 硬分叉激活区块,按以下顺序包含以下交易:

  • L1 属性交易,使用 pre-Ecotone setL1BlockValues
  • 来自 L1 的用户存款
  • 网络升级交易
    • L1Block 部署
    • GasPriceOracle 部署
    • 更新 L1Block 代理 ERC-1967 实现槽
    • 更新 GasPriceOracle 代理 ERC-1967 实现槽
    • GasPriceOracle 启用 Ecotone
    • 信标区块根合约部署 (EIP-4788)

为了不修改或中断 gas 计算的系统行为,此区块将不包含任何排序交易,通过设置 noTxPool: true

L1Block 部署

L1Block 合约已升级,以处理新的 Ecotone L1-data-fee 参数和 L1 blob 基本费用。

存款交易通过以下属性派生:

  • from: 0x4210000000000000000000000000000000000000
  • to: null
  • mint: 0
  • value: 0
  • gasLimit: 375,000
  • data: 0x60806040523480156100105... (完整字节码)
  • sourceHash: 0x877a6077205782ea15a6dc8699fa5ebcec5e0f4389f09cb8eda09488231346f8, 使用“Upgrade-deposited”类型计算,其中 intent = "Ecotone: L1 Block Deployment"

这导致 Ecotone L1Block 合约被部署到 0x07dbe8500fc591d1852B76feE44d5a05e13097Ff,以验证:

cast compute-address --nonce=0 0x4210000000000000000000000000000000000000
Computed Address: 0x07dbe8500fc591d1852B76feE44d5a05e13097Ff

验证 sourceHash:

cast keccak $(cast concat-hex 0x0000000000000000000000000000000000000000000000000000000000000002 $(cast keccak "Ecotone: L1 Block Deployment"))
## 0x877a6077205782ea15a6dc8699fa5ebcec5e0f4389f09cb8eda09488231346f8

验证 data:

git checkout 5996d0bc1a4721f2169ba4366a014532f31ea932
pnpm clean && pnpm install && pnpm build
jq -r ".bytecode.object" packages/contracts-bedrock/forge-artifacts/L1Block.sol/L1Block.json

此交易必须部署一个具有以下代码哈希的合约: 0xc88a313aa75dc4fbf0b6850d9f9ae41e04243b7008cf3eadb29256d4a71c1dfd

GasPriceOracle 部署

GasPriceOracle 合约已升级,以支持新的 Ecotone L1-data-fee 参数。分叉后,此合约 将使用 blob 基本费用来计算 L1-data-fee 交易的 gas 价格。

存款交易通过以下属性派生:

  • from: 0x4210000000000000000000000000000000000001
  • to: null,
  • mint: 0
  • value: 0
  • gasLimit: 1,000,000
  • data: 0x60806040523480156100... (完整字节码)
  • sourceHash: 0xa312b4510adf943510f05fcc8f15f86995a5066bd83ce11384688ae20e6ecf42 使用“Upgrade-deposited”类型计算,其中 intent = "Ecotone: Gas Price Oracle Deployment"

这导致 Ecotone GasPriceOracle 合约被部署到 0xb528D11cC114E026F138fE568744c6D45ce6Da7A, 以验证:

cast compute-address --nonce=0 0x4210000000000000000000000000000000000001
Computed Address: 0xb528D11cC114E026F138fE568744c6D45ce6Da7A

验证 sourceHash:

❯ cast keccak $(cast concat-hex 0x0000000000000000000000000000000000000000000000000000000000000002 $(cast keccak "Ecotone: Gas Price Oracle Deployment"))
## 0xa312b4510adf943510f05fcc8f15f86995a5066bd83ce11384688ae20e6ecf42

验证 data:

git checkout 5996d0bc1a4721f2169ba4366a014532f31ea932
pnpm clean && pnpm install && pnpm build
jq -r ".bytecode.object" packages/contracts-bedrock/forge-artifacts/GasPriceOracle.sol/GasPriceOracle.json

此交易必须部署一个具有以下代码哈希的合约: 0x8b71360ea773b4cfaf1ae6d2bd15464a4e1e2e360f786e475f63aeaed8da0ae5

L1Block 代理更新

此交易更新 L1Block 代理 ERC-1967 实现槽,以指向新的 L1Block 部署。

存款交易通过以下属性派生:

  • from: 0x0000000000000000000000000000000000000000
  • to: 0x4200000000000000000000000000000000000015 (L1Block Proxy)
  • mint: 0
  • value: 0
  • gasLimit: 50,000
  • data: 0x3659cfe600000000000000000000000007dbe8500fc591d1852b76fee44d5a05e13097ff
  • sourceHash: 0x18acb38c5ff1c238a7460ebc1b421fa49ec4874bdf1e0a530d234104e5e67dbc 使用“Upgrade-deposited”类型计算,其中 intent = "Ecotone: L1 Block Proxy Update"

验证 data:

cast concat-hex $(cast sig "upgradeTo(address)") $(cast abi-encode "upgradeTo(address)" 0x07dbe8500fc591d1852B76feE44d5a05e13097Ff)
0x3659cfe600000000000000000000000007dbe8500fc591d1852b76fee44d5a05e13097ff

验证 sourceHash:

cast keccak $(cast concat-hex 0x0000000000000000000000000000000000000000000000000000000000000002 $(cast keccak "Ecotone: L1 Block Proxy Update"))
## 0x18acb38c5ff1c238a7460ebc1b421fa49ec4874bdf1e0a530d234104e5e67dbc
GasPriceOracle 代理更新

此交易更新 GasPriceOracle 代理 ERC-1967 实现槽,以指向新的 GasPriceOracle 部署。

存款交易通过以下属性派生:

  • from: 0x0000000000000000000000000000000000000000
  • to: 0x420000000000000000000000000000000000000F (Gas Price Oracle Proxy)
  • mint: 0
  • value: 0
  • gasLimit: 50,000
  • data: 0x3659cfe6000000000000000000000000b528d11cc114e026f138fe568744c6d45ce6da7a
  • sourceHash: 0xee4f9385eceef498af0be7ec5862229f426dec41c8d42397c7257a5117d9230a 使用“Upgrade-deposited”类型计算,其中 intent = "Ecotone: Gas Price Oracle Proxy Update"

验证 data:

cast concat-hex $(cast sig "upgradeTo(address)") $(cast abi-encode "upgradeTo(address)" 0xb528D11cC114E026F138fE568744c6D45ce6Da7A)
0x3659cfe6000000000000000000000000b528d11cc114e026f138fe568744c6d45ce6da7a

验证 sourceHash:

cast keccak $(cast concat-hex 0x0000000000000000000000000000000000000000000000000000000000000002 $(cast keccak "Ecotone: Gas Price Oracle Proxy Update"))
## 0xee4f9385eceef498af0be7ec5862229f426dec41c8d42397c7257a5117d9230a
GasPriceOracle 启用 Ecotone

此交易通知 GasPriceOracle 开始使用 Ecotone gas 计算公式。

存款交易通过以下属性派生:

  • from: 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001 (Depositer Account)
  • to: 0x420000000000000000000000000000000000000F (Gas Price Oracle Proxy)
  • mint: 0
  • value: 0
  • gasLimit: 80,000
  • data: 0x22b90ab3
  • sourceHash: 0x0c1cb38e99dbc9cbfab3bb80863380b0905290b37eb3d6ab18dc01c1f3e75f93, 使用“Upgrade-deposited”类型计算,其中 intent = "Ecotone: Gas Price Oracle Set Ecotone"

验证 data:

cast sig "setEcotone()"
0x22b90ab3

验证 sourceHash:

cast keccak $(cast concat-hex 0x0000000000000000000000000000000000000000000000000000000000000002 $(cast keccak "Ecotone: Gas Price Oracle Set Ecotone"))
## 0x0c1cb38e99dbc9cbfab3bb80863380b0905290b37eb3d6ab18dc01c1f3e75f93
信标区块根合约部署(EIP-4788)

EIP-4788 引入了一个“信标区块根”合约,用于处理和公开信标区块根值。 地址为 BEACON_ROOTS_ADDRESS = 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02

对于部署,EIP-4788 定义了一个 pre-EIP-155 传统交易,从一个密钥发送,该密钥的派生方式使得 交易签名有效性绑定到消息哈希,消息哈希绑定到包含 init-code 的输入数据。

但是,这种类型的交易需要手动部署和 gas 支付。 并且由于处理是链处理的一个组成部分,并且必须为每个 OP-Stack 链重复执行, 因此这里的部署方法有所不同。

某些链可能已经有用户提交的 EIP-4788 交易实例。 这在密码学上保证是正确的,但可能会导致升级交易 使用下一个 nonce 部署第二个合约。可以忽略此部署的结果。

存款交易通过以下属性派生:

  • from: 0x0B799C86a49DEeb90402691F1041aa3AF2d3C875,如 EIP 中指定。
  • to: null
  • mint: 0
  • value: 0
  • gasLimit: 0x3d090,如 EIP 中指定。
  • isCreation: true
  • data: 0x60618060095f395ff33373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500
  • isSystemTx: false,根据 Regolith 升级,即使是系统生成的交易也会花费 gas。
  • sourceHash: 0x69b763c48478b9dc2f65ada09b3d92133ec592ea715ec65ad6e7f3dc519dc00c, 使用“Upgrade-deposited”类型计算,其中 intent = "Ecotone: beacon block roots contract deployment"

部署时,合约地址计算为 rlp([sender, nonce]),它将等于:

  • 如果已部署,则为 BEACON_ROOTS_ADDRESS
  • 如果 nonce = 1,则为不同的地址 (0xE3aE1Ae551eeEda337c0BfF6C4c7cbA98dce353B): 当用户在升级之前已经提交了 EIP 交易时。

验证 BEACON_ROOTS_ADDRESS:

cast compute-address --nonce=0 0x0B799C86a49DEeb90402691F1041aa3AF2d3C875
## Computed Address: 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02

验证 sourceHash:

cast keccak $(cast concat-hex 0x0000000000000000000000000000000000000000000000000000000000000002 $(cast keccak "Ecotone: beacon block roots contract deployment"))
## 0x69b763c48478b9dc2f65ada09b3d92133ec592ea715ec65ad6e7f3dc519dc00c

构建单独的 Payload 属性

在推导出交易列表后,rollup 节点按如下方式构造一个 PayloadAttributesV2

  • timestamp 设置为批次的时间戳。
  • random 设置为 prev_randao L1 区块属性。
  • suggestedFeeRecipient 设置为排序器费用金库地址。 参阅 费用金库(Fee Vaults) 规范。
  • transactions 是派生的交易数组:存款交易和排序交易,都使用 EIP-2718 编码。
  • noTxPool 设置为 true,以便在构造区块时使用上述确切的 transactions 列表。
  • gasLimit 设置为此 payload 的 系统配置 中的当前 gasLimit 值。
  • withdrawals 在 Canyon 之前设置为 nil,在 Canyon 之后设置为空数组
  • 原文链接: github.com/ethereum-opti...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
ethereum-optimism
ethereum-optimism
江湖只有他的大名,没有他的介绍。