区块已死,Blob 万岁 — zk-s[nt]arks

文章详细介绍了 Block-in-Blobs (BiB) 提案,旨在通过将交易数据以 RLP 格式编码进 Blob 中,使以太坊验证者在 zkEVM 时代无需下载完整交易即可通过采样确保数据可用性。同时,文章还提出了“统一数据 Gas”模型,建议将执行 Gas 和 Blob Gas 合并为单一的数据维度,以简化资源核算并优化费用市场。

感谢 KevFrancescosoispokeAndersJihoon 的反馈和审阅。

摘要: Block in Blobs (BiB) 采用 RLP 格式的交易,并将其编码为 Blob(类似于 type-3-txs)。这在 zkEVM 世界中非常有益,因为不再需要每个人都下载每笔交易——从而降低了带宽需求。

随着 zkEVM 的到来,验证者将不再需要执行交易来验证区块:一个简洁的证明就足够了。但这改变了数据可用性 (DA) 的运作方式。

今天,DA 是隐式强制执行的:如果不下载并执行其中的交易,你就无法验证一个区块。在 zkEVM 下,验证者验证的是证明,而不是直接验证交易。构建者可能会发布一个有效的区块和证明,同时扣留底层的交易数据。该区块将通过共识,但没有人能够重新执行它、为其建立索引或可靠地在其基础上进行构建。

Block-in-Blobs (BiB) 通过将交易数据编码到 Blob 中来解决这个问题,使 DA 成为共识层面的要求。验证者随后可以进行 采样 (sample) 而不是下载:同样的保证,更少的带宽,更强的扩展性。

在接下来的内容中,我想梳理一下设计空间以及帮助我理解这一点的相关内容。


背景:区块、有效负载 (Payload) 与交易

要理解 BiB,我们首先需要理清一些术语。以太坊分为两层:共识层 (CL)执行层 (EL)。这种分离是有意为之且强大的,但它引入了概念上的摩擦,特别是关于“区块”或“有效负载 (Payload)”到底是什么。

区块 vs. 执行有效负载 (Execution Payloads)

从共识层的角度来看,规范对象是 信标区块 (beacon block)。信标区块可能包含一个 ExecutionPayload,但它本身并不是执行区块。

ExecutionPayload 是 CL 和 EL 之间通过 Engine API 交换的对象:

  • 区块构建 期间,EL 构建一个 ExecutionPayload 并将其交给 CL。
  • 验证和证明 (attestation) 期间,CL 将有效负载交回给 EL 进行执行。

ExecutionPayload 不是 EL 的原生区块格式。相反,它包含了 EL 在内部重构和验证执行区块所需的确切信息。

ExecutionPayload 的大小随着区块 Gas 限制的增加而增长,但也取决于 Calldata 定价和其他因素。随着区块 Gas 限制的提高,区块大小也会增加,带宽需求也随之增加。幸运的是,有两种解决方案。

首先,效仿 Flashblocks 的例子,我们可以将有效负载拆分为固定大小的片段,使其能够独立处理——从而有效地隐藏延迟。

其次,我们可以考虑让验证者不下载所有交易,但仍能确定这些交易已被发布的方法——即 Block in Blobs (BiB)。虽然这篇文章描述了第一种解决方案,但在接下来的内容中,我想重点关注第二种。

BiB 提议将区块(可能还包括 区块级访问列表 (BAL))编码为固定大小的 Blob。类似于 type-3-tx Blob,验证者届时将不再需要下载完整的 ExecutionPayload,而只需下载他们负责托管的列。这释放了扩展效益,因为并非每个验证者都需要下载所有内容,在不削弱安全/可用性保证的情况下降低了带宽需求。

所以当人们说“将区块放入 Blob 中”时,他们的真实意思是将 CL 的 执行有效负载 放入 Blob 中……即便如此,这也不是全部事实。

BiB 到底编码了什么

尽管名字如此,BiB 并没有将整个区块,甚至整个执行有效负载都放入 Blob 中。在其最简单的形式中,该协议只需要确保 交易数据的可用性

ExecutionPayload 内部的交易已经具备了正确的形式:

  • 每笔交易已经是 RLP 编码的字节。
  • 有效负载包含这些字节的列表。

BiB 获取序列化的 RLP 编码交易,将其分块并打包到 Blob 中。

其他所有与执行相关的内容(EL 区块头字段、执行输出根等)要么移入信标区块,要么带有一个在信标区块中包含承诺的单独 Sidecar。BiB 保证了 重新执行所需的信息是可用的

区块级访问列表 (BAL) 可能会与交易面临相同的命运。由于 BAL 的使用者可能与交易的使用者不同,将 BAL 也放入 Blob 中但与交易分开可能是有意义的。


交易如何变成 Blob

在确立了“是什么”和“为什么”之后,让我们看看“如何做”。Blob 编码遵循 EIP-4844 模型:

  1. 序列化交易字节
    • 获取 payload.transactions(每个已经是 RLP 编码的)
    • 对列表进行规范编码(通常是交易字节列表的 RLP 编码)
  2. 将字节打包进 Blob
    • 将字节流拆分为 31 字节的分块 (chunks)
    • 每个分块成为 32 字节字段元素的前 31 个字节
    • 第一个字节设置为 0x00,以便该元素保持在 BLS 模数之下
    • 4096 个字段元素组成一个 Blob
  3. 承诺 (Commit)
    • 将 Blob 解释为一个多项式
    • 计算 KZG 承诺
    • 信标区块引用这些承诺
  4. 边车 (Sidecar)
    • 类似于 type-3-tx Sidecar,或者 ePBS 中的 ExecutionPayloadEnvelope,有效负载 Blob 以 Sidecar 的形式在网络中传输
    • 除了有效负载 Blob 之外,每个 Sidecar 还带有一个索引、KZG 证明、Slot 编号和信标区块根
  5. 托管 (Custody)
    • 最初,验证者可以在不利用 DAS 的情况下存储所有有效负载 Blob
    • 验证者可以下载有效负载 Blob(这几乎等同于在 ePBS 下下载 ExecutionPayloadEnvelope)并从中本地构建 ExecutionPayload
    • 在稍后阶段,可以引入部分托管和 DA 采样,利用新引入的机制进行扩展(不要求每个人下载所有交易)
    • 这同样适用于纠删码和单元级消息传递

关键见解:在 zkEVM 下,验证者不需要交易来验证承诺。他们只需要承诺来进行共识,而 DA 采样则确保 Blob 内容在网络上可用。

zkEVM 证明必须绑定什么

为了让 BiB 对 不下载所有有效负载 Blob 的 zk 证明者起作用,证明必须保证三件事:

  1. 执行正确性$pre\_state$$post\_state$ 的状态转换是有效的。
  2. 正确的交易到 Blob 编码 确切的规范交易字节被打包到了 Blob 中。
  3. 承诺绑定 信标区块引用的 Blob 承诺与这些有效负载 Blob 相对应。

这些要求共同将数据可用性从“通过重新执行实现的隐式”转变为“通过 Blob 承诺 + DAS 实现的显式”。

注意BiB 和 zkEVM/强制证明是正交的:

BiB 可以迭代推出,首先将交易(可能还有 BAL)放入 Blob 中,而暂不引入执行和 Blob 编码的强制 zk 证明。此外,部分数据托管也不需要从一开始就执行。BiB 在其最小形式下,只需获取 RLP 编码的交易,并将其编码为 Blob。从 EL 的角度来看,没有任何变化——在收到后,CL 从收到的 Blob 中构建 ExecutionPayload,并从中构建 EL 区块——就像今天一样。在这一点上,这些 Blob 已经可以被消耗可选证明的 zk 证明者使用,这些证明计划在针对有限的一组证明者推出强制证明之前推出。

有效负载的其余部分去哪了

BiB 不会将整个执行有效负载移入 Blob:只移动交易数据。

在 ePBS 的基础上,信标区块携带 执行区块头(或承诺/元数据),而 有效负载 则通过 Blob 单独提供

Blob 承载沉重的字节;出价 (Bids) 承载紧凑的承诺。

bib

这种设计会使未来转向 Slot 拍卖变得复杂。如果我们想保留该选项,我们可能仍然需要在 Sidecar 之外保留 ePBS 的 ExecutionPayloadEnvelope,或者也将 EL 请求移入有效负载 Blob 中。


有效负载需要多少个 Blob?

最后,让我们立足于具体的数字。如今 60M Gas 限制下的执行有效负载平均约为 ~150 KiB(约等于 1-2 个有效负载 Blob),最大大小约为 ~5.7 MiB(取决于 EL 数据定价)。

有效负载 Blob 的最大数量取决于 Gas 限制和 Calldata 定价。观察最近的区块,60M Gas 限制偶尔会产生 3-4 个 Blob 的区块:

image

在最坏情况下,假设 EIP-7976(针对 Calldata 密集型交易,每字节 64 Gas),我们将需要约 7 个填满 Calldata 的 Blob。按照今天的 Calldata 定价(10/40 Gas),这个数字将跃升至 45 个 Blob,进一步推广开来,我们得到以下公式来确定所需的最大 Blob 计数:

$$N = \left\lceil \frac{L}{c \cdot B} \right\rceil$$

其中

$L$ = 区块 Gas 限制

$c$ = Calldata Gas 成本 (Gas/字节)

$B$ = Blob 大小 (字节)

image


附录:统一数据 Gas (Unified Data Gas)

BiB 回答了 如何 通过 Blob 使交易数据可用。但它引出了一个更根本的问题:我们如何核算所有这些数据?

今天,以太坊在数据方面有两个独立的资源维度:

  1. 执行 Gas (Execution gas):由 Calldata 使用(每个零/非零字节 4/16 Gas,或者在 EIP-7623 底线价格下为 10/40 Gas)
  2. Blob Gas:由 type-3 交易 Blob 使用(每个 Blob 131,072 Blob Gas)

这些维度具有 独立的限制。区块 Gas 限制上限了执行 Gas;MAX_BLOB_GAS_PER_BLOCK 上限了 Blob Gas。一个区块可以同时达到这 两者 的最大值。

叠加的最坏情况

在 BiB 下,Calldata 变成了 Blob。但 type-3 交易 携带 Blob。如果不改变资源核算方式,我们将面临叠加的最坏情况:

$$N{worst} = N{calldata} + N_{type3} = \left\lceil \frac{L}{c \cdot B} \right\rceil + \frac{\text{MAX_BLOB_GAS}}{\text{GAS_PER_BLOB}}$$

在 60M Gas 限制、EIP-7976 定价(64 Gas/字节)以及 21 个 type-3 Blob 的情况下:

$$N_{worst} = 7 + 21 = 28 \text{ 个 Blob}$$

按照今天的 Calldata 定价(10/40 Gas),这等于 $11 + 21 = 32$ 个 Blob,且全部不可压缩。

统一数据 Gas:所有 DA 的单一维度

优雅的解决方案是将来自 Calldata 和 Blob 数据的数据压缩成一个单一的 数据维度。核心原则是:

所有需要被提供的数据都应计入同一个限制。

碎片化仅存在于 EL,在那里我们有:

## 当前:两个独立的检查
assert tx.gas <= block_gas_limit - block.gas_used         # 执行 Gas (execution gas)
assert tx.blob_gas <= MAX_BLOB_GAS - block.blob_gas_used  # Blob Gas (blob gas)

在 BiB 下,交易和 type-3 Blob 都变成了同一种东西:Blob,核算方式也应反映这一点。

设计:统一数据 Gas

将二维模型替换为单一的 数据字节 (data bytes) 维度:

BYTES_PER_BLOB = 131_072                      # 128 KiB
MAX_DATA_BYTES = max_blobs * BYTES_PER_BLOB   # 例如:28 个 Blob ≈ 3.5 MiB

def data_bytes(tx):
    # 完整编码的交易进入有效负载 Blob
    tx_bytes = len(rlp_encode(tx))

    # type-3 Blob Sidecar 增加了更多 DA
    if tx.blob_versioned_hashes:
        tx_bytes += len(tx.blob_versioned_hashes) * BYTES_PER_BLOB

    return tx_bytes

区块级强制执行变为一次统一检查:

## 一次统一检查(取代原本独立的 Gas + Blob_Gas 检查)
if data_bytes(tx) > MAX_DATA_BYTES - block.data_bytes_used:
    invalid("data limit exceeded")

不再有叠加的最坏情况。一个区块可以拥有 28 个 Blob 的数据量,无论是 28 个 type-3 Blob、28 个编码交易的 Blob,还是两者的任何混合。

将执行与 DA 分离

Calldata 目前支付的执行 Gas 隐式地涵盖了计算 数据可用性。通过统一数据 Gas,我们可以清晰地分离这些关注点:

def intrinsic_cost(tx):
    # 执行 Gas:纯计算(不含 Calldata 成本!)
    exec_gas = TX_BASE_COST + access_list_cost + create_cost + auth_cost

    # 数据字节:完整交易 + Blob Sidecar
    data = len(rlp_encode(tx))
    if tx.blob_versioned_hashes:
        data += len(tx.blob_versioned_hashes) * BYTES_PER_BLOB

    return exec_gas, data

注意缺失了什么:没有 Calldata Gas 成本,没有 EIP-7623 底线价格

为什么 Calldata 底线价格变得不再必要

EIP-7623 引入了 Calldata 底线价格,以防止包含大量 Calldata 但没有执行的庞大区块,确保 Calldata 密集型交易不会因为它们带来的带宽负担而支付过低的费用,同时不消耗任何其他资源。但在统一的数据费用市场中,这种保护是 免费 提供的:

关注点 EIP-7623 解决方案 统一数据 Gas 解决方案
防止垃圾信息 底线 Gas 成本(每字节 10/40) 数据费用市场(上涨的 $data\_base\_fee$
价格信号 隐含在执行 Gas 中 显式的每字节 $data\_base\_fee$
适应性 固定底线 动态(EIP-1559 式调整)

如果有人试图用 Calldata 填满区块,$data\_base\_fee$ 就会上涨,就像区块填满时 $base\_fee\_per\_gas$ 会上涨一样。市场会自动处理垃圾信息防范。

更简洁的模型:

  • 执行 Gas = 纯计算 (TX_BASE_COST + 访问列表 + auth + create + 执行期间的操作码)
  • 数据字节 = 所有 DA(完整编码的交易字节 + Blob Sidecar 字节)

没有重复计算。没有底线。一份计算费,一份数据费。

数据层面的 EIP-1559

正如 Blob Gas 拥有自己的 EIP-1559 式费用市场一样,统一数据字节也将拥有:

## 数据层面的 EIP-1559 式定价(机制与当今的 Blob Gas 相同)
data_base_fee = fake_exponential(MIN_DATA_PRICE, excess_data_bytes, UPDATE_FRACTION)

## 超额追踪(与 Blob Gas 相同)
excess = max(0, parent.excess_data_bytes + parent.data_bytes_used - TARGET_DATA_BYTES)

一种新的交易类型引入了显式的数据费用上限,让用户可以更精细地控制他们愿意为数据支付多少费用:

## 新交易类型字段(类似于 type-3 对 Blob 的处理)
tx.max_fee_per_data_byte   # 显式数据费用上限
tx.blob_versioned_hashes   # 用于 Blob Sidecar

完整的验证流程如下所示:

def validate(tx, block):
    exec_gas, data = intrinsic_cost(tx)

    if has_explicit_data_fee(tx):
      # 新交易类型:显式的各维度上限
      max_cost = tx.gas * tx.max_fee_per_gas + data * tx.max_fee_per_data_byte
      assert tx.max_fee_per_data_byte >= data_base_fee
    else:
      # 传统类型:单一的总预算 (EIP-7999 风格)
      max_cost = get_max_fee(tx)  # gas_price × gas_limit
      required = exec_gas * base_fee_per_gas + data * data_base_fee
      assert required <= max_cost

    assert exec_gas <= tx.gas
    assert data <= MAX_DATA_BYTES - block.data_bytes_used
    assert sender.balance >= max_cost + tx.value

向后兼容性

$max\_fee\_per\_data\_byte$ 字段在新的交易类型中引入。但现有的交易通过 Gas 限制分区 (gas limit partitioning)(详见 EIP-7999)保持完全兼容:

def partition_gas_limit(tx):
    if has_explicit_data_fee(tx):  # 新交易类型
        return tx.gas, data_bytes(tx)
    else:  # 传统类型
        calldata_tokens = zero_bytes + 4 * non_zero_bytes
        execution_gas = tx.gas - STANDARD_TOKEN_COST * calldata_tokens
        return execution_gas, data_bytes(tx)

关键见解:传统交易的 $gas\_price \times gas\_limit$ 已经提供了一个总预算。这个预算现在涵盖了执行 Gas 和数据 Gas:Calldata 成本只是从一个维度移到了另一个维度。不需要额外的资金。

区块头变更

区块头追踪的是数据使用情况,而不是 Blob Gas:

## 替换这些字段:
blob_gas_used       →  data_bytes_used      # 此区块中的总 DA
excess_blob_gas     →  excess_data_bytes    # 用于 EIP-1559 定价

统一数据 Gas 规范的初稿可以在这里找到。


两个正交的提议

关键见解如下:BiB统一数据 Gas (Unified Data Gas) 解决的是不同的问题。

BiB 统一数据 Gas
解决的问题 如何使 Calldata 可进行 DA 采样 如何限制总 DA 需求
机制 编码(交易 → Blob) 核算(所有数据统一限制)
影响层级 CL(Blob Sidecar、承诺) EL(Gas 计量、固有成本)
能独立存在吗?

没有统一数据 Gas 的 BiB 仍然可行:你只需接受叠加的最坏情况或引入临时限制。这与当今协议的运作方式并无不同。

没有 BiB 的统一数据 Gas 同样可行:它限制了总数据量,但并没有为交易开启 DAS。

两者结合会产生协同效应:BiB 提供“编码”,统一数据 Gas 提供“核算”。

  • 原文链接: ethresear.ch/t/blocks-ar...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
以太坊中文
以太坊中文
以太坊中文, 用中文传播以太坊的最新进展