文章提出将以太坊执行层区块拆分为独立传播的固定 Gas 分块(Chunks),以实现流式下载和并行验证。该方案通过在共识层引入 Sidecars 机制,旨在降低区块传播延迟,优化带宽利用率,并为未来的 ZK 证明并行化和无状态性提供技术基础。
tl;dr: 将一个 EL 区块 ($= \text{payload}$) 拆分为多个具有固定 Gas 预算(例如 $2^{24} = 16.77\text{M}$)的迷你区块(“chunks”),这些小块作为 sidecar 独立传播。每个 chunk 都携带其无状态执行所需的预状态(pre-state),并提交其后置状态差异(post-state diff)。Chunk 是有序的,但可以完全独立地并行执行。CL 提交 chunk 头的集合;sidecar 携带主体和包含证明(inclusion proofs)。
验证过程变得更像是一种连续的流。
目前的区块是大型的单体对象,未来还会变得更大。验证要求在执行开始前接收到完整的区块。这在区块传播和执行中造成了延迟瓶颈。
在通过 p2p 网络接收到区块后,交易是顺序执行的。我们无法在下载的同时开始验证,也无法并行执行。

p2p 层上的消息通常使用 Snappy 进行压缩。以太坊上使用的 Snappy 的 block-format 无法进行流式传输。因此,我们需要在压缩之前将区块切分成 chunk。
有了 EIP-7928: Block-level Access Lists,情况有所改善,但我们仍然需要等待下载完成后才能开始区块验证。使用 $4$ 个核心,我们会得到以下甘特图:

相反,我们可以将区块作为 chunk 进行流式传输:

这使验证从“下载完整区块,然后处理”转变为“在接收剩余部分的同时进行处理”。
我们扩展 EL 区块格式以支持分块:
class ELHeader:
parent_hash: Hash32
fee_recipient: Address
block_number: uint64
gas_limit: uint64
timestamp: uint64
extra_data: ByteList[MAX_EXTRA_DATA_BYTES]
prev_randao: Bytes32
base_fee_per_gas: uint256
parent_beacon_block_root: Root
blob_gas_used: uint64
excess_blob_gas: uint64
transactions_root: Root
state_root: Root
receipts_root: Root
logs_bloom: Bloom
gas_used: Uint
withdrawals_root: Root
block_access_list_hash: Bytes32
# 新字段
chunk_count: int # >= 0
EL 区块头中没有对单个 chunk 的承诺。我们只向其中添加了 chunk 计数。执行输出(state_root、logs_bloom、receipts_root、gas_used)必须与最后一个 chunk 中的值相同(适用于状态根和提款根),或者是聚合 chunk 值后的根(适用于交易、收据、日志、已用 Gas 和区块访问列表)。
Chunk 永远不会上链;仅提交它们的根。
Chunk 包含我们通常在 EL 区块主体中预期的字段。交易被拆分到多个 chunk 中,每个 chunk 的限制为 $2^{24}$ Gas。提款必须仅包含在最后一个 chunk 中。镜像区块级访问列表,chunk 带有自己的 chunk 访问列表,并且还可以向 chunk 添加预状态值,从而实现无状态化。
class Chunk:
header: ChunkHeader
transactions: List[Tx]
withdrawals: List[Withdrawal] # 仅在索引为 -1 的 chunk 中
chunk_access_list: List[ChunkAccessList]
pre_state_values: List[(Key, Value)] # 可选
每个 chunk 都有一个包含 chunk 索引的头。交易按 chunk.header.index 及其在 chunk 中的索引排序。每个 chunk 执行输出的承诺都包含在头中。
class ChunkHeader:
index: int
txs_root: Root
post_state_root: Root
receipts_root: Root
logs_bloom: Bloom
gas_used: uint64
withdrawals_root: Root
chunk_access_list_root: Root
pre_state_values_root: Root # 可选
为了防止提议者将其区块拆分为过多的 chunk,协议可以强制要求 chunk 必须至少达到半满($\ge \frac{\text{chunk_gas_limit}}{2}$)或者满足 chunk.header.index == len(beaconBlock.chunk_roots)(= 该区块中的最后一个 chunk)。

信标区块通过新字段跟踪 chunk:
class BeaconBlockBody:
...
chunk_roots: List[ChunkRoot, MAX_CHUNKS_PER_BLOCK] # chunk 的 SSZ 根
class ExecutionPayloadHeader:
...
chunk_count: int
CL 通过一个新的 ChunkBundle 容器从 EL 接收执行 chunk,该容器包括 EL 区块头和 chunk(=类似于 blob)。
CL 使用 SSZ 的 hash_tree_root 计算 chunk 根,并将其放入信标区块主体中。
Chunk 携带在 sidecar 中:
class ExecutionChunkSidecar:
index: uint64 # chunk 索引
chunk: ByteList[MAX_CHUNK_SIZE] # 不透明的 chunk 数据
signed_block_header: SignedBeaconBlockHeader
chunk_root_inclusion_proof: Vector[Bytes32, PROOF_DEPTH]
共识层确保所有 chunk 都是可用的,并通过针对 chunk_roots 的 Merkle 证明正确地链接到信标区块主体(=类似于 blob)。
提议者仅在正常的 beacon_block 主题上广播带有承诺(chunk_count、chunk_headers_root)的轻量级信标区块,而庞大的执行数据则作为 ExecutionChunkSidecar 通过 $X$ 个并行子网(beacon_chunk_sidecar_{0..X})分别流式传输,并通过 (block_root, index) 进行去重。
最初,所有节点必须订阅所有子网并托管所有 chunk。虽然这目前还不会减少带宽/存储要求,但它能立即带来并行化的好处。一旦基本机制得到验证且/或 zk 证明变得可行,就可以在未来的升级中添加部分托管。

分叉选择要求在区块被视为有效之前,所有 sidecar 都必须可用且成功通过验证。带有 chunk_roots 的信标区块传播速度很快,但只有在接收到每个 chunk 并针对根进行了包含证明后,该区块才具有分叉选择资格。信标区块仍然包含带有所有必要承诺的 EL 区块头(=提交到父区块和执行输出)。在 EL 上我们所熟知的“区块主体”在此设计中保持为空。
$2^{24}$ Gas($\approx 16.7\text{M}$)成为了一个自然的 chunk 大小:
ExecutionChunkBundle 将其传递给 CL。构建者可以在完成构建 chunk 时立即发布,验证者甚至可以在收到信标区块之前就开始验证它们。由于 chunk 包含已签名的信标区块头和针对它的包含证明,人们可以在 chunk 到达时验证(=执行)它们,并信任它们的来源(=提议者)。
几何级数增加 chunk 大小($2^{22}, 2^{23}, \dots, 2^{25}$)的想法似乎有益,但增加了复杂性。第一个 chunk 可以较小($5\text{M}$ Gas)并带有预状态值以便立即执行,而后面的 chunk 较大。这仍是一个待实验的领域。
虽然最初的实现要求完全托管,但该架构自然支持部分托管:
初步看来,所提议的设计似乎与 EIP-7732 和 EIP-7886 都兼容。在 ePBS 下,chunk 根可能会移动到 ExecutionPayloadEnvelope 中,我们会在 ExecutionPayloadHeader 中针对 chunk 根放置一个额外的根。PTC 不仅需要检查单个 EL 有效载荷是否可用,还需要检查所有 chunk 是否可用。这与 blob 没有太大区别。
区块分块和独立验证的优势随着 Gas 限制的提高而扩大,并可能有助于减少节点带宽消耗的峰值。
- 原文链接: ethresear.ch/t/payload-c...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!