存款交易
本文档介绍了在L1上发起并在L2上执行的Deposit交易类型,包括其结构、在L1上的发起方式以及在L2上的验证和授权条件,以及两种类型的 Deposit 交易:L1 属性 Deposit 交易和用户 Deposit 交易。还详细说明了 L1 属性预部署合约和用户 Deposit 交易的 Deposit 合约。
存款
<!-- 本文件中所有的术语表参考。 --> [g-transaction-type]: glossary.md#transaction-type [g-derivation]: glossary.md#L2-chain-derivation [g-deposited]: glossary.md#deposited [g-deposits]: glossary.md#deposits [g-l1-attr-deposit]: glossary.md#l1-attributes-deposited-transaction [g-user-deposited]: glossary.md#user-deposited-transaction [g-eoa]: glossary.md#eoa [g-exec-engine]: glossary.md#execution-engine
[已存款的交易][g-deposited],也称为[存款][g-deposits],是在 L1 上发起并在 L2 上执行的交易。本文档概述了一种新的[交易类型][g-transaction-type]用于存款。它还描述了如何在 L1 上发起存款,以及 L2 上的授权和验证条件。
词汇提示:已存款的交易 特指 L2 交易,而 存款 可以指交易的各个阶段(例如,在 L1 上存款时)。
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- 不要编辑此部分,请重新运行 doctoc 以更新 --> 目录
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
已存款的交易类型
[已存款的交易][g-deposited] 与现有交易类型有以下显著区别:
- 它们源自 Layer 1 区块,并且必须作为协议的一部分包含在内。
- 它们不包括签名验证(有关原因,请参见用户已存款的交易)。
- 它们在 L1 上购买其 L2 gas,因此,L2 gas 不可退还。
我们定义一个新的 EIP-2718 兼容交易类型,前缀为 0x7E,以表示存款交易。
一个存款具有以下字段(按照它们在此处出现的顺序进行 RLP 编码):
bytes32 sourceHash:source-hash,唯一标识存款的来源。address from:发送方账户的地址。address to:接收方账户的地址,如果已存款的交易是合约创建,则为 null(零长度)地址。uint256 mint:要在 L2 上铸造的 ETH 值。uint256 value:要发送到接收方账户的 ETH 值。uint64 gas:L2 交易的 gas 限制。bool isSystemTx:如果为 true,则交易不与 L2 区块 gas 池交互。- 注意:从 Regolith 升级开始,布尔值被禁用(强制为
false)。
- 注意:从 Regolith 升级开始,布尔值被禁用(强制为
bytes data:调用数据。
与 EIP-155 交易相比,此交易类型:
- 不包括
nonce,因为它由sourceHash标识。 API 响应仍然包括nonce属性:- 在 Regolith 之前:
nonce始终为0 - 使用 Regolith:
nonce设置为相应交易收据的depositNonce属性。
- 在 Regolith 之前:
- 不包括签名信息,并显式指定
from地址。 API 响应包含零签名的v、r、s值,以实现向后兼容性。 - 包括新的
sourceHash、from、mint和isSystemTx属性。 API 响应将这些作为附加字段包含在内。
我们选择 0x7E 是因为当前允许交易类型标识符高达 0x7F。选择较高的标识符可以最大限度地降低标识符在未来被 L1 链上的另一种交易类型声明的风险。我们不选择 0x7F 本身,以防它被用于可变长度编码方案。
源哈希计算
存款交易的 sourceHash 基于来源计算:
- 用户已存款:
keccak256(bytes32(uint256(0)), keccak256(l1BlockHash, bytes32(uint256(l1LogIndex))))。 其中l1BlockHash和l1LogIndex均指 L1 上存款日志事件的包含。l1LogIndex是区块日志事件组合列表中的存款事件日志的索引。 - L1 属性已存款:
keccak256(bytes32(uint256(1)), keccak256(l1BlockHash, bytes32(uint256(seqNumber))))。 其中l1BlockHash指的是 info 属性被存入的 L1 区块哈希值。 并且seqNumber = l2BlockNum - l2EpochStartBlockNum, 其中l2BlockNum是在 L2 中包含存款交易的 L2 区块号, 并且l2EpochStartBlockNum是 epoch 中第一个 L2 区块的 L2 区块号。
如果没有存款中的 sourceHash,则两个不同的已存款交易可能具有完全相同的哈希值。
外部 keccak256 使用域对实际的唯一标识信息进行哈希处理,以避免不同类型的来源之间发生冲突。
我们不使用发送方的 nonce 来确保唯一性,因为这需要在区块推导期间从[执行引擎][g-exec-engine]额外读取 L2 EVM 状态。
已存款的交易种类
虽然我们只定义了一种新的交易类型,但我们可以根据它们在 L2 区块中的位置来区分两种已存款的交易:
- 第一个交易 必须 是一个 L1 属性已存款的交易,后跟
- 零个或多个 用户已存款的交易 数组,这些交易已提交到 L1 上的存款 feed 合约(称为
OptimismPortal)。 用户已存款的交易仅存在于 L2 epoch 的第一个区块中。
我们仅定义一种新的交易类型,以最大限度地减少对 L1 客户端软件的修改,并降低总体复杂性。
已存款的交易的验证和授权
如上所述,已存款的交易类型不包括用于验证的签名。相反,授权由 [L2 链推导][g-derivation] 过程处理,该过程在正确应用时,只会推导出 from 地址由 L1 存款合约 的日志证明的交易。
执行
为了执行已存款的交易:
首先,from 账户的余额 必须 增加 mint 的金额。
这是无条件的,并且不会因存款失败而回滚。
然后,根据交易的属性初始化已存款的交易的执行环境,其方式与 EIP-155 交易完全相同。
存款交易的处理方式与类型 -3 (EIP-1559) 交易完全相同,但以下情况除外:
- 不验证费用字段:存款没有任何费用,因为它在 L1 上支付 gas 费用。
- 不验证
nonce字段:存款没有任何 nonce,它由其sourceHash唯一标识。 - 不处理访问列表:存款没有访问列表,因此将其视为访问列表为空进行处理。
- 不检查
from是否为外部拥有帐户 (EOA):通过 L1 地址确保存款不是 EOA 掩蔽,这可能会在未来的 L1 合约部署中发生变化,例如启用类似帐户抽象的机制。 - 在 Regolith 升级之前:
- 执行输出声明了非标准的 gas 使用量:
- 如果
isSystemTx为 false:执行输出声明它使用gasLimitgas。 - 如果
isSystemTx为 true:执行输出声明它使用0gas。
- 如果
- 执行输出声明了非标准的 gas 使用量:
- 不会将 gas 作为 ETH 退还。(要么不退还,要么利用存款的 gas 价格为
0这一事实) - 不收取交易优先级费用。不向区块费用接收者付款。
- 不收取 L1 成本费用,因为存款来自 L1,因此不必作为数据提交回 L1。
- 不收取基本费用。基本费用总额的核算不会改变。
请注意,这包括与常规交易一样的合约部署行为,并且 gas 计量是相同的(除了上述与费用相关的更改之外),包括内部 gas 的计量。
EVM 执行发出的任何非 EVM 状态转换错误都将以特殊方式处理:
- 它被转换为 EVM 错误:
即,存款将始终被包括在内,但如果它遇到非 EVM 状态转换错误,则其收据将表明失败
例如,由于帐户余额不足而无法转移指定的
valueETH 金额。 - 世界状态将回滚到 EVM 处理开始时,即存款的铸造部分之后。
- 世界状态中
from的 nonce 递增 1,使错误等同于本机 EVM 故障。 请注意,之前的 nonce 递增可能发生在 EVM 处理期间,但会首先回滚。
最后,在上述处理之后,执行后处理的运行方式相同:
即,gas 池和收据的处理方式与常规交易相同。
但是,从 Regolith 升级开始,存款交易的收据将扩展一个额外的 depositNonce 值,用于存储在 EVM 处理 之前 注册的 from 发送方的 nonce 值。
请注意,执行输出声明的已用 gas 量将从 gas 池中扣除, 但在 Regolith 升级之前的此执行输出值具有特殊的边缘情况。
应用程序开发人员请注意:由于 CALLER 和 ORIGIN 设置为 from,因此
使用 tx.origin == msg.sender 检查的语义将无法确定在存款交易期间调用者是否为 EOA。相反,该检查只能用于
识别 L2 存款交易中的第一个调用。但是,此检查仍然满足常见情况,即开发人员使用此检查来确保 CALLER 无法
在调用之前和之后执行代码。
Nonce 处理
尽管缺少签名验证,但我们仍然会在执行存款交易时递增 from 帐户的 nonce。在仅限存款回滚的上下文中,对于交易排序或重放预防而言,这不是必需的
,但是它可以保持与在合约创建期间使用 nonce 的一致性。它还可以简化与下游的集成
工具(例如钱包和区块浏览器)。
存款收据
交易收据使用 EIP-2718 中的标准键入。
存款交易收据类型等于常规收据,
但扩展了一个可选的 depositNonce 字段。
RLP 编码的共识强制字段为:
postStateOrStatus(标准):这包含交易状态,请参见 EIP-658。cumulativeGasUsed(标准):到目前为止,区块中使用的 gas,包括此交易。- 实际使用的 gas 来自与先前交易的
CumulativeGasUsed的差值。 - 从 Regolith 开始,这说明了存款的实际 gas 使用量,如常规交易一样。
- 实际使用的 gas 来自与先前交易的
bloom(标准):交易日志的布隆过滤器。logs(标准):EVM 处理发出的日志事件。depositNonce(唯一扩展):可选字段。存款交易保留执行期间使用的 nonce。- 在 Regolith 之前,必须始终省略此
depositNonce字段。 - 使用 Regolith,必须始终包括此
depositNonce字段。
- 在 Regolith 之前,必须始终省略此
从 Regolith 开始,收据 API 响应利用收据更改来获得更准确的响应数据:
depositNonce包含在 API 响应的收据 JSON 数据中- 对于合约部署(当
to == null时),depositNonce有助于推导出正确的contractAddress元数据, 而不是假设 nonce 为零。 cumulativeGasUsed说明了实际的 gas 使用量,如 EVM 处理中所计量。
L1 属性已存款的交易
[L1 属性已存款的交易][g-l1-attr-deposit] 是发送到 L1 属性预部署合约 的存款交易。
此交易 必须 具有以下值:
from是0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001(L1 属性存款人账户的地址)to是0x4200000000000000000000000000000000000015(L1 属性预部署合约的地址)。mint是0value是0gasLimit设置为 150,000,000。isSystemTx设置为true。data是对 L1 属性预部署合约 的 ABI 编码调用setL1BlockValues()函数,其中包含与相应 L1 区块关联的正确值(参见 参考实现)。
如果 Regolith 升级已激活,则某些字段将被覆盖:
gasLimit设置为 1,000,000isSystemTx设置为false
此系统发起的 L1 属性交易不收取任何 ETH 用于其分配的 gasLimit,
因为它实际上是状态转换处理的一部分。
L2 上的特殊账户
L1 属性存款交易涉及两个特殊用途账户:
- L1 属性存款人账户
- L1 属性预部署合约
L1 属性存款人账户
存款人账户是一个没有已知私钥的 [EOA][g-eoa]。它的地址是 0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001。它的值由在 L1 属性已存款的交易执行期间由 CALLER 和 ORIGIN 操作码返回。
L1 属性预部署合约
一个在地址 0x4200000000000000000000000000000000000015 上预部署的 L2 合约,它在存储中保存来自相应 L1 区块的某些区块变量,以便可以在后续的已存款交易执行期间访问它们。
预部署存储以下值:
- L1 区块属性:
number(uint64)timestamp(uint64)basefee(uint256)hash(bytes32)
sequenceNumber(uint64):这等于相对于 epoch 开始的 L2 区块号, 即 L2 区块距离 L1 属性上次更改的 L2 区块高度, 并在新 epoch 开始时重置为 0。- 与 L1 区块绑定的系统可配置项,请参见系统配置规范:
batcherHash(bytes32):当抢跑的批量提交者的版本控制承诺。overhead(uint256):应用于此 L2 区块中交易的 L1 成本计算的 L1 费用开销。scalar(uint256):应用于此 L2 区块中交易的 L1 成本计算的 L1 费用标量。
该合约实现了一种授权方案,因此它仅接受来自 存款人账户 的状态更改调用。
该合约具有以下 Solidity 接口,并且可以根据 合约 ABI 规范与之交互。
L1 属性预部署合约:参考实现
L1 属性预部署合约的参考实现可以在 L1Block.sol 中找到。
在 packages/contracts 目录中运行 pnpm build 后,要添加到 genesis
文件的字节码将位于 /packages/contracts/artifacts/contracts/L2/L1Block.sol/L1Block.json 处的构建工件文件的 deployedBytecode 字段中。
用户已存款的交易
[用户已存款的交易][g-user-deposited] 是由 [L2 链推导][g-derivation] 流程生成的已存款的交易。每个用户已存款的交易的内容
由 存款合约 在 L1 上发出的相应 TransactionDeposited 事件确定。
from与发出的值保持不变(尽管它可能 已在OptimismPortal(存款 feed 合约)中转换为别名)。to是任何 20 字节的地址(包括零地址)- 在合约创建的情况下(参见
isCreation),此地址设置为null。
- 在合约创建的情况下(参见
mint设置为发出的值。value设置为发出的值。gaslimit与发出的值保持不变。它必须至少为 21000。- 如果交易是合约创建,则
isCreation设置为true,否则设置为false。 data与发出的值保持不变。根据isCreation的值,它被处理为调用数据或合约初始化代码。isSystemTx由回滚节点为某些具有未计量执行的交易设置。 对于用户存款的交易,它为false
存款合约
存款合约已部署到 L1。已存款的交易来自由
存款合约发出的 TransactionDeposited 事件中的值。
存款合约负责维护保证 gas 市场, 收取存款以用于 L2 上的 gas,并确保单个 L1 区块中的保证 gas 总量不超过 L2 区块 gas 限制。
存款合约处理两种特殊情况:
- 合约创建存款,通过将
isCreation标志设置为true来指示。 如果to地址为非零,则合约将回滚。 - 来自合约账户的调用,在这种情况下,
from值将转换为其 L2 别名。
地址别名
如果调用者是一个合约,则将通过将
0x1111000000000000000000000000000000001111 添加到其中来转换地址。该数学运算是 unchecked 并且在
Solidity uint160 上完成,因此该值将溢出。这可以防止 L1 上的合约与 L2 上的合约具有相同地址但没有相同代码的攻击。我们可以安全地忽略此
,因为保证 EOA 具有相同的“代码”(即根本没有代码)。当 Sequencer 处于关闭状态时,这也使得用户可以与 L2 上的合约进行交互。
存款合约实现:Optimism Portal
存款合约的参考实现可以在 OptimismPortal.sol 中找到。
- 原文链接: github.com/ethereum-opti...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~