该文介绍了以太坊的一种新型“携带Blob的交易”格式,旨在为Rollups提供临时的扩容解决方案,直到完全分片实现。这种新格式允许交易携带大量不可被EVM直接访问但其承诺可被访问的数据,从而大幅降低数据成本,提高Rollups的效率。
markdown
引入一种新的交易格式,用于“blob-carrying transactions”(携带 Blob 的交易),这种交易包含大量 EVM 执行无法访问但其承诺可以访问的数据。 该格式旨在与未来全分片中将使用的格式完全兼容。
Rollup 在短期、中期,甚至可能在长期,都是以太坊唯一的无需信任的扩容解决方案。 L1 上的交易费用已经持续数月居高不下,现在更迫切地需要采取一切必要措施,以促进全生态系统向 Rollup 迁移。 Rollup 显著降低了许多以太坊用户的费用:Optimism 和 Arbitrum 提供的费用通常比以太坊基础层本身低约 3-8 倍, 而 ZK Rollup 具有更好的数据压缩能力,可以避免包含签名,其费用比基础层低约 40-100 倍。
然而,即使是这些费用,对许多用户来说也过于昂贵。Rollup 自身长期不足的长期解决方案一直是数据分片,它将为链增加每个区块约 16 MB 的专用数据空间供 Rollup 使用。 然而,数据分片仍需相当长的时间才能完成实现和部署。
本 EIP 提供了一个过渡解决方案,通过实现分片中将使用的交易格式,但实际上不分片这些交易。 相反,这种交易格式的数据只是信标链的一部分,并由所有共识节点完全下载(但可以在相对较短的延迟后删除)。 与完整数据分片相比,本 EIP 对可包含的此类交易数量设定了更低的上限,目标是每个区块约 0.375 MB,上限为约 0.75 MB。
| 常量 | 值 |
|---|---|
BLOB_TX_TYPE |
Bytes1(0x03) |
BYTES_PER_FIELD_ELEMENT |
32 |
FIELD_ELEMENTS_PER_BLOB |
4096 |
BLS_MODULUS |
52435875175126190479447740508185965837690552500527637822603658699938581184513 |
VERSIONED_HASH_VERSION_KZG |
Bytes1(0x01) |
POINT_EVALUATION_PRECOMPILE_ADDRESS |
Bytes20(0x0A) |
POINT_EVALUATION_PRECOMPILE_GAS |
50000 |
MAX_BLOB_GAS_PER_BLOCK |
786432 |
TARGET_BLOB_GAS_PER_BLOCK |
393216 |
MIN_BASE_FEE_PER_BLOB_GAS |
1 |
BLOB_BASE_FEE_UPDATE_FRACTION |
3338477 |
GAS_PER_BLOB |
2**17 |
HASH_OPCODE_BYTE |
Bytes1(0x49) |
HASH_OPCODE_GAS |
3 |
MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS |
4096 |
| 类型 | 基本类型 | 附加检查 |
|---|---|---|
Blob |
ByteVector[BYTES_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB] |
|
VersionedHash |
Bytes32 |
|
KZGCommitment |
Bytes48 |
执行 IETF BLS 签名 "KeyValidate" 检查,但允许单位点 |
KZGProof |
Bytes48 |
同 KZGCommitment |
在本提案中,我们使用相应 consensus 4844 specs 中定义的加密方法和类。
具体来说,我们使用 polynomial-commitments.md 中的以下方法:
def kzg_to_versioned_hash(commitment: KZGCommitment) -> VersionedHash:
return VERSIONED_HASH_VERSION_KZG + sha256(commitment)[1:]
使用泰勒展开近似 $factor \cdot e^{\text{numerator} / \text{denominator}}$:
def fake_exponential(factor: int, numerator: int, denominator: int) -> int:
i = 1
output = 0
numerator_accum = factor * denominator
while numerator_accum > 0:
output += numerator_accum
numerator_accum = (numerator_accum * numerator) // (denominator * i)
i += 1
return output // denominator
我们引入一种新型的 EIP-2718 交易,“blob 交易”,其中 TransactionType 是 BLOB_TX_TYPE,TransactionPayload 是以下 TransactionPayloadBody 的 RLP 序列化:
[chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, data, access_list, max_fee_per_blob_gas, blob_versioned_hashes, y_parity, r, s]
字段 chain_id、nonce、max_priority_fee_per_gas、max_fee_per_gas、gas_limit、value、data 和 access_list 遵循与 EIP-1559 相同的语义。
字段 to 略微偏离语义,例外是它不能为 nil,因此必须始终表示一个 20 字节的地址。这意味着 blob 交易不能采用创建交易的形式。
字段 max_fee_per_blob_gas 是一个 uint256,字段 blob_versioned_hashes 表示来自 kzg_to_versioned_hash 的哈希输出列表。
此交易的 EIP-2718 ReceiptPayload 是 rlp([status, cumulative_transaction_gas_used, logs_bloom, logs])。
签名值 $y_{parity}$、$r$ 和 $s$ 通过对以下摘要构造 secp256k1 签名来计算:
$\text{keccak256}(\text{BLOB_TX_TYPE} \ || \ \text{rlp}([\text{chain_id}, \text{nonce}, \text{max_priority_fee_per_gas}, \text{max_fee_per_gas}, \text{gas_limit}, \text{to}, \text{value}, \text{data}, \text{access_list}, \text{max_fee_per_blob_gas}, \text{blob_versioned_hashes}]))$
当前的头部编码扩展了两个新的 64 位无符号整数字段:
blob_gas_used 是区块内交易消耗的 blob gas 总量。excess_blob_gas 是在区块之前,超过目标消耗的 blob gas 的运行总量。超过目标 blob gas 消耗的区块会增加此值,低于目标 blob gas 消耗的区块会减少此值(下限为 0)。因此,头部的 RLP 编码为:
rlp([
parent_hash,
0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347, # ommers hash
coinbase,
state_root,
txs_root,
receipts_root,
logs_bloom,
0, # difficulty
number,
gas_limit,
gas_used,
timestamp,
extradata,
prev_randao,
0x0000000000000000, # nonce
base_fee_per_gas,
withdrawals_root,
blob_gas_used,
excess_blob_gas,
])
excess_blob_gas 的值可以使用父头部计算。
def calc_excess_blob_gas(parent: Header) -> int:
if parent.excess_blob_gas + parent.blob_gas_used < TARGET_BLOB_GAS_PER_BLOCK:
return 0
else:
return parent.excess_blob_gas + parent.blob_gas_used - TARGET_BLOB_GAS_PER_BLOCK
对于分叉后的第一个区块,parent.blob_gas_used 和 parent.excess_blob_gas 都被评估为 0。
我们引入 blob gas 作为一种新型 gas。它独立于普通 gas,并遵循自己的目标规则,类似于 EIP-1559。
我们使用 excess_blob_gas 头部字段来存储计算 blob gas 基础费用所需的持久数据。目前,只有 blob 以 blob gas 定价。
def calc_blob_fee(header: Header, tx: Transaction) -> int:
return get_total_blob_gas(tx) * get_base_fee_per_blob_gas(header)
def get_total_blob_gas(tx: Transaction) -> int:
return GAS_PER_BLOB * len(tx.blob_versioned_hashes)
def get_base_fee_per_blob_gas(header: Header) -> int:
return fake_exponential(
MIN_BASE_FEE_PER_BLOB_GAS,
header.excess_blob_gas,
BLOB_BASE_FEE_UPDATE_FRACTION
)
区块有效性条件已修改,以包括 blob gas 检查(参见下面的 Execution layer validation 部分)。
通过 calc_blob_fee 计算的实际 $\text{blob_fee}$ 在交易执行前从发送者余额中扣除并销毁,如果交易失败则不予退还。
我们添加一个指令 BLOBHASH(操作码为 HASH_OPCODE_BYTE),它从堆栈顶部读取 $index$ 作为大端 uint256,
然后将其替换为 tx.blob_versioned_hashes[index](如果 $index < \text{len}(\text{tx.blob_versioned_hashes})$),
否则替换为归零的 bytes32 值。
该操作码的 gas 成本为 HASH_OPCODE_GAS。
在 POINT_EVALUATION_PRECOMPILE_ADDRESS 处添加一个预编译,用于验证 KZG 证明,该证明声称一个 blob
(由承诺表示)在给定点处评估为给定值。
该预编译的成本为 POINT_EVALUATION_PRECOMPILE_GAS,并执行以下逻辑:
def point_evaluation_precompile(input: Bytes) -> Bytes:
"""
Verify p(z) = y given commitment that corresponds to the polynomial p(x) and a KZG proof.
Also verify that the provided commitment matches the provided versioned_hash.
"""
# The data is encoded as follows: versioned_hash | z | y | commitment | proof | with z and y being padded 32 byte big endian values
# 数据编码如下:versioned_hash | z | y | commitment | proof | 其中 z 和 y 是填充的 32 字节大端值
assert len(input) == 192
versioned_hash = input[:32]
z = input[32:64]
y = input[64:96]
commitment = input[96:144]
proof = input[144:192]
# Verify commitment matches versioned_hash
# 验证承诺与 versioned_hash 匹配
assert kzg_to_versioned_hash(commitment) == versioned_hash
# Verify KZG proof with z and y in big endian format
# 使用大端格式的 z 和 y 验证 KZG 证明
assert verify_kzg_proof(commitment, z, y, proof)
# Return FIELD_ELEMENTS_PER_BLOB and BLS_MODULUS as padded 32 byte big endian values
# 以填充的 32 字节大端值返回 FIELD_ELEMENTS_PER_BLOB 和 BLS_MODULUS
return Bytes(U256(FIELD_ELEMENTS_PER_BLOB).to_be_bytes32() + U256(BLS_MODULUS).to_be_bytes32())
预编译必须拒绝非规范的字段元素(即,提供的字段元素必须严格小于 BLS_MODULUS)。
在共识层,blob 在信标区块体中被引用,但没有完全编码。 blob 不会完整嵌入到区块体中,而是作为“sidecars”单独传播。
这种“sidecar”设计通过“黑盒化” is_data_available() 为未来数据量的增加提供了向前兼容性:
通过全分片,is_data_available() 可以被数据可用性采样(DAS)取代,从而避免所有信标节点下载网络中的所有 blob。
请注意,共识层负责持久化 blob 以实现数据可用性,执行层不负责。
ethereum/consensus-specs 仓库定义了本 EIP 中涉及的以下共识层变更:
在执行层,区块有效性条件扩展如下:
def validate_block(block: Block) -> None:
...
# check that the excess blob gas was updated correctly
# 检查 excess blob gas 是否正确更新
assert block.header.excess_blob_gas == calc_excess_blob_gas(block.parent.header)
blob_gas_used = 0
for tx in block.transactions:
...
# modify the check for sufficient balance
# 修改对充足余额的检查
max_total_fee = tx.gas * tx.max_fee_per_gas
if get_tx_type(tx) == BLOB_TX_TYPE:
max_total_fee += get_total_blob_gas(tx) * tx.max_fee_per_blob_gas
assert signer(tx).balance >= max_total_fee
...
# add validity logic specific to blob txs
# 添加 blob 交易特有的有效性逻辑
if get_tx_type(tx) == BLOB_TX_TYPE:
# there must be at least one blob
# 必须至少有一个 blob
assert len(tx.blob_versioned_hashes) > 0
# all versioned blob hashes must start with VERSIONED_HASH_VERSION_KZG
# 所有版本化的 blob 哈希必须以 VERSIONED_HASH_VERSION_KZG 开头
for h in tx.blob_versioned_hashes:
assert h[0] == VERSIONED_HASH_VERSION_KZG
# ensure that the user was willing to at least pay the current blob base fee
# 确保用户至少愿意支付当前的 blob 基础费用
assert tx.max_fee_per_blob_gas >= get_base_fee_per_blob_gas(block.header)
# keep track of total blob gas spent in the block
# 跟踪区块中消耗的 blob gas 总量
blob_gas_used += get_total_blob_gas(tx)
# ensure the total blob gas spent is at most equal to the limit
# 确保消耗的 blob gas 总量至多等于限制
assert blob_gas_used <= MAX_BLOB_GAS_PER_BLOCK
# ensure blob_gas_used matches header
# 确保 blob_gas_used 与头部匹配
assert block.header.blob_gas_used == blob_gas_used
Blob 交易有两种网络表示形式。在交易流言响应 (PooledTransactions) 期间,blob 交易的 EIP-2718 TransactionPayload 被包装成:
rlp([tx_payload_body, blobs, commitments, proofs])
这些元素的定义如下:
tx_payload_body - 是标准 EIP-2718 blob 交易 的 TransactionPayloadBodyblobs - Blob 项目列表commitments - 相应 blobs 的 KZGCommitment 列表proofs - 相应 blobs 的 KZGProof 列表节点必须验证 tx_payload_body 并对照其验证包装的数据。为此,请确保:
tx_payload_body.blob_versioned_hashes、blobs、commitments 和 proofs 的数量相等。commitments 哈希到版本化哈希,即 kzg_to_versioned_hash(commitments[i]) == tx_payload_body.blob_versioned_hashes[i]commitments 与相应的 blobs 和 proofs 匹配。(注意:这可以通过使用 verify_blob_kzg_proof_batch 进行优化,对于每个 blob,使用从承诺和 blob 数据派生出的随机评估点进行证明)对于区块体检索响应 (BlockBodies),使用标准的 EIP-2718 blob 交易 TransactionPayload。
节点不得自动将 blob 交易广播给其对等节点。
相反,这些交易仅使用 NewPooledTransactionHashes 消息进行宣告,然后可以通过 GetPooledTransactions 手动请求。
本 EIP 引入的 blob 交易采用与最终分片规范中预期的完全相同的格式。 这为 Rollup 提供了一个临时但显著的扩容缓解方案,允许它们最初扩展到每个插槽 0.375 MB, 并有一个独立的费用市场,使得在系统使用量有限的情况下费用可以非常低。
Rollup 扩容过渡方案的核心目标是提供临时的扩容缓解, 同时不给 Rollup 带来额外的开发负担来利用这种缓解。 如今,Rollup 使用 calldata。未来,Rollup 将别无选择,只能使用分片数据(也称为“blobs”), 因为分片数据将便宜得多。 因此,Rollup 无法避免在某个阶段至少进行一次关于数据处理方式的重大升级。 但我们可以做的是确保 Rollup 只需要升级一次。 这立即意味着过渡方案只有两种可能性:(i) 降低现有 calldata 的 gas 成本, 以及 (ii) 提前引入用于分片数据的格式,但尚未实际进行分片。 以前的 EIP 都属于类别 (i) 的解决方案;本 EIP 属于类别 (ii) 的解决方案。
设计本 EIP 的主要权衡是“现在实现更多”与“以后实现更多”之间: 我们是实现完整分片工作的 25%,还是 50%,抑或是 75%?
本 EIP 中已完成的工作包括:
BeaconBlock 验证与数据可用性采样 blob 之间的层分离BeaconBlock 逻辑要实现完整分片,仍需完成的工作包括:
commitments 进行低度扩展以允许 2D 采样本 EIP 也为更长期的协议清理奠定了基础。例如,其(更清晰的)gas 基础费用更新规则可以应用于主要的基础费用计算。
Rollup 将不再把 Rollup 区块数据放在交易 calldata 中,而是期望 Rollup 区块提交者 将数据放入 blob 中。这保证了可用性(这是 Rollup 所需要的),但会比 calldata 便宜得多。 Rollup 需要数据可用一次,时间长度足以确保诚实参与者可以构建 Rollup 状态,但不需要永久可用。
Optimistic Rollup 只有在提交欺诈证明时才需要实际提供底层数据。 欺诈证明可以通过 calldata 分步验证状态转换,一次最多加载 blob 的几个值。 对于每个值,它会提供一个 KZG 证明,并使用点评估预编译来根据之前提交的版本化哈希验证该值, 然后像今天一样对该数据执行欺诈证明验证。
ZK Rollup 将提供两个对其交易或状态差异数据的承诺: blob 承诺(协议确保其指向可用数据)和 ZK Rollup 自己的承诺(使用 Rollup 内部使用的任何证明系统)。 它们将使用等价性证明协议,通过点评估预编译, 来证明这两个承诺指向相同的数据。
我们在执行层中使用版本化哈希(而不是承诺)作为 blob 的引用,以确保与未来更改的向前兼容性。 例如,如果我们需要为了量子安全而切换到 Merkle 树 + STARKs,那么我们将添加一个新版本, 允许点评估预编译使用新格式。 Rollup 不必对其工作方式进行任何 EVM 级别的更改; 排序器只需在适当的时候切换到使用新的交易类型。
然而,点评估发生在有限域内,只有当域模数已知时才定义良好。智能合约可以包含一个将承诺版本映射到模数的表,但这将不允许智能合约考虑尚未知道的未来模数升级。通过允许在 EVM 内部访问模数,可以构建智能合约,使其能够使用未来的承诺和证明,而无需升级。
为了避免添加另一个预编译,我们直接从点评估预编译返回模数和多项式度。然后调用者可以使用它。这也是“免费的”,因为调用者可以忽略返回值的这一部分而不会产生额外成本——在可预见的未来保持可升级的系统可能会暂时使用此路径。
Blob Gas 基础费用更新规则旨在近似以下公式:
$$ \text{base_fee_per_blob_gas} = \text{MIN_BASE_FEE_PER_BLOB_GAS} \cdot e^{\text{excess_blob_gas} / \text{BLOB_BASE_FEE_UPDATE_FRACTION}} $$
其中 $\text{excess_blob_gas}$ 是链相对于“目标”数量(每个区块 TARGET_BLOB_GAS_PER_BLOCK)消耗的 blob gas 总“额外”量。
与 EIP-1559 类似,它是一个自修正公式:随着超额的增加,$\text{base_fee_per_blob_gas}$ 以指数方式增加,从而减少使用并最终迫使超额回落。
逐块行为大致如下。 如果区块 $N$ 消耗 $X$ blob gas,则在区块 $N+1$ 中,$\text{excess_blob_gas}$ 增加 $X - \text{TARGET_BLOB_GAS_PER_BLOCK}$, 因此区块 $N+1$ 的 $\text{base_fee_per_blob_gas}$ 增加一个因子 $e^{((X - \text{TARGET_BLOB_GAS_PER_BLOCK}) / \text{BLOB_BASE_FEE_UPDATE_FRACTION})}$。 因此,它具有与现有 EIP-1559 类似的效果,但更“稳定”,因为它以相同的方式响应相同的总使用量,无论其分布如何。
参数 BLOB_BASE_FEE_UPDATE_FRACTION 控制每个 blob gas 基础费用的最大变化率。它的选择是为了使最大变化率目标为每个区块 $e^{(\text{TARGET_BLOB_GAS_PER_BLOCK} / \text{BLOB_BASE_FEE_UPDATE_FRACTION})} \approx 1.125$。
TARGET_BLOB_GAS_PER_BLOCK 和 MAX_BLOB_GAS_PER_BLOCK 的值选择对应于每个区块目标 3 个 blob (0.375 MB)和最大 6 个 blob (0.75 MB)。这些小的初始限制旨在最大限度地减少本 EIP 给网络带来的压力,预计未来随着网络在大区块下展示可靠性,这些限制将会增加。
本 EIP 引入了一种具有独立内存池版本和执行负载版本的新交易类型, 两者之间只有单向转换。Blob 存在于网络表示中,而非共识表示中; 相反,它们与信标区块耦合。这意味着现在交易中有一部分将无法从 web3 API 访问。
Blob 交易在内存池层具有较大的数据大小,这构成了内存池 DoS 风险, 尽管这不是前所未有的,因为这也适用于包含大量 calldata 的交易。
通过仅广播 blob 交易的公告,接收节点将能够控制接收哪些交易以及接收多少交易,
从而允许它们将吞吐量限制在可接受的水平。
EIP-5793 将通过扩展 NewPooledTransactionHashes 公告消息以包含交易类型和大小,从而进一步为节点提供细粒度控制。
此外,我们建议在内存池交易替换规则中包含 1.1 倍 的每个 blob gas 基础费用上调要求。
本 EIP 的执行层测试用例可在 ethereum/execution-spec-tests 仓库的 eip4844_blobs 中找到。共识层测试用例可在此处找到:here。
本 EIP 将每个信标区块的带宽需求增加了最大约 0.75 MB。 这比目前区块的理论最大大小($30\text{M gas} / 16\text{ gas per calldata byte} = 1.875\text{M bytes}$)大 40%,因此它不会大幅增加最坏情况下的带宽。 合并后,区块时间是静态的,而不是不可预测的泊松分布,为大型区块的传播提供了有保证的时间段。
本 EIP 的持续负载远低于降低 calldata 成本的替代方案,即使 calldata 受限,
因为没有预期 blob 需要像执行负载那样长时间存储。
这使得可以实施一项策略,即这些 blob 必须至少保留一定时期。选择的具体值是 MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS 个 epoch,大约是 18 天,
与提议的(但尚未实施的)执行负载历史一年轮换时间相比,这是一个短得多的延迟。
通过 CC0 放弃版权及相关权利。
- 原文链接: github.com/nerolation/EI...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!