Alert Source Discuss
Standards Track: Core

EIP-4844: 分片 Blob 交易

分片 Blob 交易以一种简单且向前兼容的方式扩展了以太坊的数据可用性。

Authors Vitalik Buterin (@vbuterin), Dankrad Feist (@dankrad), Diederik Loerakker (@protolambda), George Kadianakis (@asn-d6), Matt Garnett (@lightclient), Mofi Taiwo (@Inphi), Ansgar Dietrichs (@adietrichs)
Created 2022-02-25
Requires EIP-1559, EIP-2718, EIP-2930, EIP-4895

摘要

引入一种新的“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 的限制。

规范

参数

Constant Value
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

类型别名

Type Base type Additional checks
Blob ByteVector[BYTES_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB]  
VersionedHash Bytes32  
KZGCommitment Bytes48 执行 IETF BLS 签名 “KeyValidate” 检查,但允许标识点
KZGProof Bytes48 KZGCommitment 相同

密码学助手函数

在本提案中,我们使用了相应的 共识 4844 规范 中定义的密码学方法和类。

具体来说,我们使用了 polynomial-commitments.md 中的以下方法:

助手函数

def kzg_to_versioned_hash(commitment: KZGCommitment) -> VersionedHash:
    return VERSIONED_HASH_VERSION_KZG + sha256(commitment)[1:]

使用泰勒展开逼近 factor * e ** (numerator / 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

Blob 交易

我们引入了一种新的 EIP-2718 交易类型“blob 交易”,其中 TransactionTypeBLOB_TX_TYPETransactionPayload 是以下 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_idnoncemax_priority_fee_per_gasmax_fee_per_gasgas_limitvaluedataaccess_list 遵循与 EIP-1559 相同的语义。

字段 to 与语义略有偏差,但必须不是 nil,因此必须始终表示 20 字节的地址。这意味着 blob 交易不能具有创建交易的形式。

字段 max_fee_per_blob_gas 是一个 uint256,字段 blob_versioned_hashes 表示来自 kzg_to_versioned_hash 的哈希输出列表。

此交易的 EIP-2718 ReceiptPayloadrlp([status, cumulative_transaction_gas_used, logs_bloom, logs])

签名

签名值 y_parityrs 是通过构造一个基于以下摘要的 secp256k1 签名来计算的:

keccak256(BLOB_TX_TYPE || 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]))

Header 扩展

当前的 header 编码扩展了两个新的 64 位无符号整数字段:

  • blob_gas_used 是块内交易消耗的总 blob gas 量。
  • excess_blob_gas 是在块之前消耗的超过目标的 blob gas 的运行总计。高于目标 blob gas 消耗的块会增加此值,低于目标 blob gas 消耗的块会减小此值(限制为 0)。

因此,header 的结果 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 的值可以使用父 header 计算。

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

对于第一个 post-fork 块,parent.blob_gas_usedparent.excess_blob_gas 都被评估为 0

Gas 记账

我们引入 blob gas 作为一种新型 gas。它独立于正常 gas,并遵循其自身的目标规则,类似于 EIP-1559。 我们使用 excess_blob_gas header 字段来存储计算 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 检查(请参阅下面的 执行层验证 部分)。

通过 calc_blob_fee 计算的实际 blob_fee 在交易执行之前从发送者余额中扣除并销毁,并且在交易失败的情况下不予退还。

获取版本化哈希的操作码

我们添加了一个指令 BLOBHASH (操作码为 HASH_OPCODE_BYTE),它从堆栈顶部读取 index 作为大端 uint256,并在 index < len(tx.blob_versioned_hashes) 时将其替换为堆栈上的 tx.blob_versioned_hashes[index],否则替换为零 bytes32 值。 该操作码的 gas 成本为 HASH_OPCODE_GAS

Point Evaluation 预编译合约

POINT_EVALUATION_PRECOMPILE_ADDRESS 添加一个预编译合约,用于验证 KZG 证明,该证明声明 blob(由承诺表示)在给定的点评估为给定的值。

预编译合约的成本为 POINT_EVALUATION_PRECOMPILE_GAS,并执行以下逻辑:

def point_evaluation_precompile(input: Bytes) -> Bytes:
    """
    验证 p(z) = y,给定对应于多项式 p(x) 的承诺和 KZG 证明。
    还要验证提供的承诺是否与提供的 versioned_hash 匹配。
    """
    # 数据编码如下: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]

    # 验证 Commitment 是否与 versioned_hash 匹配
    assert kzg_to_versioned_hash(commitment) == versioned_hash

    # 以大端格式验证 z 和 y 的 KZG 证明
    assert verify_kzg_proof(commitment, z, y, proof)

    # 返回 FIELD_ELEMENTS_PER_BLOB 和 BLS_MODULUS 作为填充的 32 字节大端值
    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 中涉及的以下共识层更改:

  • 信标链:处理更新的信标块并确保 blob 可用。
  • P2P 网络:gossip 和同步更新的信标块类型和新的 blob sidecars。
  • 诚实验证者:生成带有 blob 的信标块;签署并发布关联的 blob sidecars。

执行层验证

在执行层,块的有效性条件扩展如下:

def validate_block(block: Block) -> None:
    ...

    # 检查 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:
        ...

        # 修改对足够余额的检查
        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

        ...

        # 添加特定于 blob 交易的有效性逻辑
        if get_tx_type(tx) == BLOB_TX_TYPE:
            # 必须至少有一个 blob
            assert len(tx.blob_versioned_hashes) > 0

            # 所有 versioned blob hashes 必须以 VERSIONED_HASH_VERSION_KZG 开头
            for h in tx.blob_versioned_hashes:
                assert h[0] == VERSIONED_HASH_VERSION_KZG

            # 确保用户愿意至少支付当前的 blob 基础费用
            assert tx.max_fee_per_blob_gas >= get_base_fee_per_blob_gas(block.header)

            # 跟踪块中花费的总 blob gas
            blob_gas_used += get_total_blob_gas(tx)

    # 确保花费的总 blob gas 最多等于限制
    assert blob_gas_used <= MAX_BLOB_GAS_PER_BLOCK

    # 确保 blob_gas_used 与 header 匹配
    assert block.header.blob_gas_used == blob_gas_used

网络

Blob 交易有两种网络表示形式。在交易 gossip 响应 (PooledTransactions) 期间,blob 交易的 EIP-2718 TransactionPayload 被包装为:

rlp([tx_payload_body, blobs, commitments, proofs])

每个元素的定义如下:

  • tx_payload_body - 是标准 EIP-2718 blob 交易TransactionPayloadBody
  • blobs - Blob 项目列表
  • commitments - 相应 blobsKZGCommitment 列表
  • proofs - 相应 blobscommitmentsKZGProof 列表

节点必须验证 tx_payload_body 并针对它验证包装的数据。为此,请确保:

  • tx_payload_body.blob_versioned_hashesblobscommitmentsproofs 的数量相等。
  • KZG commitments 哈希到版本化哈希,即 kzg_to_versioned_hash(commitments[i]) == tx_payload_body.blob_versioned_hashes[i]
  • KZG commitments 匹配相应的 blobsproofs。(注意:可以使用 verify_blob_kzg_proof_batch 进行优化,每个 blob 都有一个证明,用于在从每个 blob 的承诺和 blob 数据导出的点进行随机评估)

对于 body 检索响应 (BlockBodies),使用标准 EIP-2718 blob 交易 TransactionPayload

节点不得自动将 blob 交易广播给其对等节点。 相反,这些交易仅使用 NewPooledTransactionHashes 消息进行声明,然后可以通过 GetPooledTransactions 手动请求。

理由

在分片的道路上

本 EIP 以与预期在最终分片规范中存在的格式相同的格式引入 blob 交易。 这通过允许 Rollup 最初扩展到每个 slot 0.375 MB,从而为 Rollup 提供了一个临时但显着的扩展缓解, 具有单独的费用市场,允许在对此系统的使用受到限制时费用非常低。

汇总扩展临时措施的核心目标是提供临时扩展缓解,而不会给 Rollup 带来额外的开发负担来利用这种缓解。 如今,Rollup 使用 calldata。将来,Rollup 将别无选择,只能使用分片数据(也称为“blob”),因为分片数据会便宜得多。 因此,Rollup 至少需要进行一次重大升级,以改变它们处理数据的方式。 但是,我们可以做的是确保 Rollup 只需要升级一次。 这立即意味着临时措施只有两种可能性:(i) 降低现有 calldata 的 gas 成本, 以及 (ii) 提前使用将用于分片数据的格式,但尚未实际对其进行分片。 之前的 EIP 都是第 (i) 类别的解决方案;此 EIP 是第 (ii) 类别的解决方案。

在设计此 EIP 时,主要的权衡是在现在实现更多还是以后必须实现更多: 我们是在实现完整分片的 25% 的工作,还是 50% 或 75%?

本 EIP 中已完成的工作包括:

  • 一种新的交易类型,其格式与“完整分片”中需要存在的格式完全相同
  • 完整分片所需的_所有_执行层逻辑
  • 完整分片所需的_所有_执行/共识交叉验证逻辑
  • BeaconBlock 验证和数据可用性采样 blob 之间的层分离
  • 完整分片所需的大部分 BeaconBlock 逻辑
  • blob 的自我调整的独立基础费用

要实现完整分片,仍需完成的工作包括:

  • 共识层中 commitments 的低度扩展,以允许 2D 采样
  • 数据可用性采样的实际实施
  • PBS(提议者/构建者分离),以避免要求单个验证者在一个 slot 中处理 32 MB 的数据
  • 托管证明或类似的协议内要求,每个验证者都验证每个块中分片数据的特定部分

此 EIP 还为更长期的协议清理奠定了基础。例如,其(更简洁的)gas 基础费用更新规则可以应用于主基础费用计算。

Rollup 将如何运作

Rollup 无需将 Rollup 块数据放入交易 calldata 中,Rollup 会期望 Rollup 块提交者将数据放入 blob 中。这保证了可用性(这是 Rollup 所需要的),但比 calldata 便宜得多。 Rollup 需要数据可用一次,时间足够长,以确保诚实的行为者可以构建 Rollup 状态,但不是永远。

只有在提交欺诈证明时,Optimistic Rollup 才需要实际提供底层数据。 欺诈证明可以分小步验证转换,每次通过 calldata 最多加载 blob 的几个值。 对于每个值,它将提供一个 KZG 证明,并使用 point evaluation 预编译合约根据之前提交的版本化哈希验证该值, 然后像今天一样对该数据执行欺诈证明验证。

ZK Rollup 将向其交易或状态增量数据提供两个承诺: blob 承诺(协议确保指向可用数据)和 ZK Rollup 自己的承诺,使用 Rollup 在内部使用的任何证明系统。 他们将使用等效协议的证明,使用 point evaluation 预编译合约, 以证明这两个承诺引用相同的数据。

版本化哈希 & 预编译返回数据

我们在执行层中使用版本化哈希(而不是承诺)作为对 blob 的引用,以确保与未来更改的前向兼容性。 例如,如果出于量子安全原因我们需要切换到 Merkle 树 + STARK,那么我们将添加一个新版本, 允许 point evaluation 预编译合约与新格式一起使用。 Rollup 无需对它们的工作方式进行任何 EVM 级别的更改; 排序器只需在适当的时候切换到使用新的交易类型。

但是,point evaluation 发生在有限域内,并且只有在知道域模数的情况下才能很好地定义它。智能合约可以包含一个将承诺版本映射到模数的表,但这将不允许智能合约考虑尚未知的模数的未来升级。通过允许在 EVM 内部访问模数,可以构建智能合约,以便它可以使用未来的承诺和证明,而无需升级。

为了不添加另一个预编译合约,我们直接从 point evaluation 预编译合约返回模数和多项式度数。然后可以由调用者使用它。它也是“免费的”,因为调用者可以忽略返回值的这一部分而不会产生额外的成本——在可预见的未来保持可升级性的系统可能会暂时使用此路由。

每个 blob gas 的基础费用更新规则

每个 blob gas 的基础费用更新规则旨在近似公式 base_fee_per_blob_gas = MIN_BASE_FEE_PER_BLOB_GAS * e**(excess_blob_gas / BLOB_BASE_FEE_UPDATE_FRACTION), 其中 excess_blob_gas 是链相对于“目标”数量(每个块 TARGET_BLOB_GAS_PER_BLOCK)消耗的总“额外”blob gas 量。 与 EIP-1559 类似,这是一个自我纠正的公式:随着 excess 值越来越高,base_fee_per_blob_gas 会呈指数增长,从而减少使用量,并最终迫使 excess 值回落。

逐块行为大致如下。 如果块 N 消耗 X blob gas,则在块 N+1 中,excess_blob_gas 增加 X - TARGET_BLOB_GAS_PER_BLOCK, 因此块 N+1base_fee_per_blob_gas 增加 e**((X - TARGET_BLOB_GAS_PER_BLOCK) / BLOB_BASE_FEE_UPDATE_FRACTION) 倍。 因此,它具有与现有 EIP-1559 类似的效果,但更“稳定”,因为它以相同的方式响应相同的总使用量,而不管其分布如何。

参数 BLOB_BASE_FEE_UPDATE_FRACTION 控制每个 blob gas 的基础费用的最大变化率。选择它的目标是每个块的最大变化率为 e**(TARGET_BLOB_GAS_PER_BLOCK / BLOB_BASE_FEE_UPDATE_FRACTION) ≈ 1.125

吞吐量

TARGET_BLOB_GAS_PER_BLOCKMAX_BLOB_GAS_PER_BLOCK 的值被选择为对应于每个块 3 个 blob (0.375 MB) 的目标和最多 6 个 blob (0.75 MB) 的最大值。这些小的初始限制旨在最大限度地减少此 EIP 对网络造成的压力,并预计在未来的升级中会随着网络在更大的块下表现出可靠性而增加。

向后兼容性

Blob 不可访问性

此 EIP 引入了一种交易类型,该类型具有不同的 mempool 版本和执行有效负载版本, 并且两者之间只有单向可转换性。blob 位于网络表示中,而不是共识表示中; 相反,它们与信标块耦合。这意味着现在有一部分交易将无法从 web3 API 访问。

Mempool 问题

Blob 交易在 mempool 层具有较大的数据大小,这会带来 mempool DoS 风险, 尽管并非史无前例,因为这也适用于具有大量 calldata 的交易。

通过仅广播 blob 交易的声明,接收节点将可以控制接收哪些交易以及接收多少交易, 从而允许它们将吞吐量限制在可接受的水平。 EIP-5793 将通过扩展 NewPooledTransactionHashes 声明消息以包括交易类型和大小,从而为节点提供更精细的控制。

此外,我们建议在 mempool 交易替换规则中包括 1.1 倍的每个 blob gas 的基础费用增长要求。

测试用例

此 EIP 的执行层测试用例可以在 ethereum/execution-spec-tests 存储库的 eip4844_blobs 中找到。共识层测试用例可以在 这里 找到。

安全注意事项

此 EIP 将每个信标块的带宽要求最多增加约 0.75 MB。 这比今天一个块的理论最大大小大 40%(30M gas / 每个 calldata 字节 16 gas = 1.875M 字节),因此不会大大增加最坏情况下的带宽。 合并后,块时间是静态的,而不是不可预测的泊松分布,从而为大型块的传播提供了有保证的时间段。

即使 calldata 受到限制,此 EIP 的_持续_负载也比降低 calldata 成本的替代方案低得多, 因为不期望 blob 需要像执行有效负载一样长时间存储。 这使得可以实施一项策略,即这些 blob 必须至少保留一段时间。选择的特定值为 MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS epoch,大约为 18 天, 与提议的(但尚未实施)执行有效负载历史记录的一年轮换时间相比,延迟要短得多。

版权

版权及相关权利通过 CC0 放弃。

Citation

Please cite this document as:

Vitalik Buterin (@vbuterin), Dankrad Feist (@dankrad), Diederik Loerakker (@protolambda), George Kadianakis (@asn-d6), Matt Garnett (@lightclient), Mofi Taiwo (@Inphi), Ansgar Dietrichs (@adietrichs), "EIP-4844: 分片 Blob 交易," Ethereum Improvement Proposals, no. 4844, February 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-4844.