Gloas 注解:Gossip

  • potuz_
  • 发布于 2026-01-30 20:37
  • 阅读 24

本文深入探讨了 Gloas 分叉中 gossip 协议上的对象验证机制,涵盖了 Attestations、Beacon Blocks、Execution Payload、Proposer preferences、Payload bids 和 Blob data 等关键对象的验证流程和规则。

带注释的 Gloas 发布订阅

目录

这是关于 Gloas 分叉的系列文章的第二篇注释。在这篇文章中,我们将讨论对 gossip 对象的验证。这些路径通常在规范上是不可执行的,并为客户端设计其验证流水线留下了更多的余地。例如,改变验证顺序会导致客户端忽略其他客户端会拒绝的消息。

这篇文章的结构与信标链的不同。我们将把文章分成每个对象的部分。DataColumnSidecar 将会特别关注,因为这种验证很可能会改变

感谢 Manu Nalepa 不断发送更正。

证明

Gloas 中对证明的唯一更改是,当证明一个旧区块时,委员会索引被重载以指示负载内容。因此,我们添加了以下两个验证

  • [拒绝] aggregate.data.index < 2
  • [拒绝] 如果 block.slot == aggregate.data.slot,则 aggregate.data.index == 0

强制索引可以是 01,并且对于相同 slot 的证明,它必须为零。

在聚合和证明主题以及单个证明主题中,都需要完全相同的更改。请注意,我们删除了强制此索引为零的 Electra 条件。

信标区块

Gloas 中的信标区块要轻得多,因为缺少负载。相反,它们仅通过签名竞标来承诺未来的负载。因此,所有关于执行负载的验证都被删除。执行负载的一些信息仍然可以在签名竞标中找到,因此这些验证现在都在竞标中。

  • 如果执行节点对区块的执行负载父级的 execution_payload 验证已完成
    • [拒绝] 区块的执行负载父级(由 bid.parent_block_hash 定义)通过所有验证。
  • [拒绝] 竞标的父级(由 bid.parent_block_root 定义)等于区块的父级(由 block.parent_root 定义)。

如果我们将完整的 KZG 承诺移至签名竞标,则需要将以下规则添加到信标区块 gossip 验证中:

  • [拒绝] KZG 承诺的长度小于或等于共识层中定义的限制——即验证 len(bid.blob_kzg_commitments) <= get_blob_parameters(get_current_epoch(state)).max_blobs_per_block

执行负载

执行负载验证现在是一种 gossip 验证,因为负载作为 SignedExecutionPayloadEnvelope 独立传输。检查如下:

- _[忽略]_ envelope 的区块根 `envelope.block_root` 已经被看到(通过
  gossip 或非 gossip 来源)(客户端可以在检索到该区块后,将负载排队以进行处理)。

也就是说,我们忽略尚未看到的区块的负载。在实现中,这种待处理负载的缓存实际上很难处理,这就是为什么将 slot 添加到负载 envelope 以简化识别可能丢失的区块的原因。如果负载本质上是空的,则对于联合广播的自构建区块,负载早于信标区块到达的情况可能会发生在本地环境中。

- _[忽略]_ 节点尚未从此构建者看到此区块根的另一个有效的
  `SignedExecutionPayloadEnvelope`。
- _[忽略]_ envelope 来自大于或等于最新最终确定 slot 的 slot —— 即验证
  `envelope.slot >= compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)`

这些都是标准的。

当我们看到具有给定信标区块根的 block 时,以下是有效性条件

- _[拒绝]_ `block` 通过验证。
- _[拒绝]_ `block.slot` 等于 `envelope.slot`。
- _[拒绝]_ `envelope.builder_index == bid.builder_index`
- _[拒绝]_ `payload.block_hash == bid.block_hash`
- _[拒绝]_ `signed_execution_payload_envelope.signature` 对于
  构建者的公钥有效。

第一个是不接受无效区块的负载并惩罚发送这些负载的节点。另外三个是确保负载与承诺的竞标一致,没有关于父区块的检查,因为这些检查是在处理负载时完成的。

请注意,构建者方面可能会有含糊不清的情况,例如,执行请求包含在 envelope 中,并且未进行检查。长度也是如此。因此,仅转发一个负载的条件是必要的。

提议者偏好

添加此主题是为了使提议者可以向构建者广播有关其即将到来的提议 slot 的信息。

提议者提前一个 epoch 广播(提前查看两个 epoch),以下对象

class ProposerPreferences(Container):
    proposal_slot: Slot
    validator_index: ValidatorIndex
    fee_recipient: ExecutionAddress
    gas_limit: uint64
class SignedProposerPreferences(Container):
    message: ProposerPreferences
    signature: BLSSignature

这些只是发送费用接收者和 gas 限制作为可调整的偏好,以及此提议者的预期提议 slot 和用于签名验证的验证者索引。我们可以添加其他偏好,例如提议者将从外部构建者接受的最低竞标等等,但这些被认为是不成熟的,特别是因为它们可能会破坏 p2p 构建者集合,使其无法成为在无法进行自构建时良好的后备机制。

此主题中的 gossip 验证指定为

- _[忽略]_ `preferences.proposal_slot` 在下一个 epoch 中 —— 即
  `compute_epoch_at_slot(preferences.proposal_slot) == get_current_epoch(state) + 1`。
- _[拒绝]_ `preferences.validator_index` 出现在 `state.proposer_lookahead` 的下一个 epoch 部分中的正确 slot 中 —— 即
  `is_valid_proposal_slot(state, preferences)` 返回 `True`。
- _[忽略]_ `signed_proposer_preferences` 是从验证者收到的第一个有效消息,其索引为 `preferences.validator_index` 和给定的 slot。
- _[拒绝]_ `signed_proposer_preferences.signature` 对于验证者的公钥有效。

我们只是检查提议者是否确实在给定的 slot 中进行提议,并且该提议在下一个 epoch 中,以便在 N epoch 开始时,提议者已经发现他们将在 N+1 中进行提议,他们可以开始提交这些消息。

预计此主题的消息非常少,因为其中只有 32 个可能有效。

我假设此 PR 将合并。

负载竞标

这个主题尚未完全指定,可能会发生许多变化。主要是尝试限制并在全局主题上避免 DOS,在该主题中,任何构建者都可以发送消息。

不应将此主题视为拍卖。如果集中的构建者市场无法正常工作、进行审查(或被审查)等,则此主题充当一种后备机制。因此,验证应侧重于保护网络免受 DOS 攻击,而不是添加规则以尝试使其在允许构建者有效地竞标以赢得此 p2p 主题中的区块的意义上是“公平的”。

在撰写本文时,这些规则分为两组:

- _[忽略]_ `bid.slot` 是当前 slot 或下一个 slot。
- _[忽略]_ 已经看到 `SignedProposerPreferences`,其中 `preferences.proposal_slot`
  等于 `bid.slot`。
- _[拒绝]_ `bid.builder_index` 是一个有效的/活跃的构建者索引 —— 即
  `is_active_builder(state, bid.builder_index)` 返回 `True`。
- _[拒绝]_ `bid.execution_payment` 为零。
- _[拒绝]_ `bid.fee_recipient` 匹配与 `bid.slot` 关联的提议者的
  `SignedProposerPreferences` 中的 `fee_recipient`。
- _[拒绝]_ `bid.gas_limit` 匹配与 `bid.slot` 关联的提议者的
  `SignedProposerPreferences` 中的 `gas_limit`。
- _[忽略]_ 这是为此 slot 从给定构建者收到的第一个具有有效签名的已签名竞标。
- _[忽略]_ 此竞标是为相应 slot 和给定父区块哈希看到的最高价值竞标。
- _[忽略]_ `bid.value` 小于或等于构建者的剩余余额 ——
  即 `can_builder_cover_bid(state, builder_index, amount)` 返回 `True`。
- _[忽略]_ `bid.parent_block_hash` 是分叉选择中已知执行负载的区块哈希。
- _[忽略]_ `bid.parent_block_root` 是分叉选择中已知信标区块的哈希树根。
- _[拒绝]_ `signed_execution_payload_bid.signature` 对于
  `bid.builder_index` 有效。

这些是验证规则,而 DOS 防御则作为实现的细节留给客户端:

*注意*:实现应包括 DoS 防御措施,以缓解来自恶意构建者的垃圾邮件,这些构建者提交了许多价值增量最小的竞标。
可能的策略包括:(1)仅转发超过当前最高竞标达到最小阈值的竞标,或(2)仅以固定的时间间隔转发观察到的最高竞标。

将此留给客户端的想法是,只要它们实现合理的默认值并且足够不同,这些规则将很难由于客户端多样性而被利用。

在验证列表中,我们看到我们只接受当前和下一个 slot 的竞标。预计 Vanilla 客户端会在执行完先前的负载并构建下一个区块后立即提交竞标,这可能会在先前的 slot 中非常早。一些更复杂的构建者仍然不想打开服务器进行直接连接,他们可能希望尽可能地等待并在 slot 期间提交竞标,在 p2p 网络中看到先前的竞标之后。

其他验证是标准的,它们主要是检查竞标是否与之前必须看到的提议者的偏好兼容。其中一个问题是构建者激活。由于构建者仅在分叉时从待处理的存款中加入,因此他们不太可能在分叉后立即处于活动状态(他们的存款需要已经最终确定,如果分叉时存在大量的待处理队列并且构建者提前两个 epoch 进行了存款,则可能会发生这种情况)。

我们检查构建者实际上是否有能力支付竞标,特别是,我们只允许在此主题中使用无信任付款,包括受信任的付款将毫无用处,因为任何人都可以承诺最大价值。我们只允许构建在已知区块之上的竞标。请注意,我们没有对竞标引用的 EL 区块哈希进行任何检查。原则上,我们可以强制执行父哈希是已知的,或者对应于父区块的竞标或父区块的父哈希。但是,这些检查是在处理时完成的,并且在 gossip 中进行这些检查本身会很慢。

请注意,我们允许每个构建者在此主题中发送一条消息。此规则可能会发生更改,因为有些人认为这会导致构建者因无法在拍卖式中更新其竞标而受到伤害。

Blob 数据

数据列侧车验证的主题正在不断变化。我将在此处讨论当前状态下的设计决策以及由于此未解决的问题 而可能发生的变化。

当前的验证机制

目前,签名竞标包括一个对 blob KZG 承诺的隐藏承诺,方法是包含它们的哈希而不是完整列表。此承诺目前是无用的,它的存在主要是为了避免构建者在发送侧车时产生歧义。想法是,始终希望在信标区块之后接收数据列侧车,但不一定在负载 envelope 之后。数据列侧车和负载 envelope 都包含 KZG 承诺的完整列表。前者的原因是,无论是否看到负载,都可以对其进行验证。事实上,单个数据列侧车可能比负载小得多,并且构建者可以选择比负载更早地开始广播它们。因此,我们非常希望经常在负载之前看到它们。负载 envelope 也包含它们,以便它可以独立于已看到有效数据列侧车进行验证:需要 KZG 承诺才能在负载验证时将版本化的哈希发送到引擎。因此,当前的方法要求已看到信标区块才能验证负载 envelope 和数据列侧车。但是,构建者发送的这些对象可以独立于首先看到其中任何一个进行验证。

一个提议的变体

已经提出将 KZG 承诺的完整列表移动到签名竞标中,而不是它们的哈希。这允许从负载和每个数据列侧车中删除此列表。因此,它将仅在区块中包含一次,并且可以从 slot 中的广播数据中删除多达 129 次。

这种机制允许的是,N+1 的证明者只要看到 N 的信标区块,就可以开始从引擎请求 blob,甚至在 N 的构建者甚至还没有开始组装数据列侧车之前!这可能会大大降低带宽消耗。客户端的另一个重大收获是实现简单性,因为目前对于 Fulu 和 Deneb 而言,这些承诺都假定在信标区块主体中。

进行此更改的代价是,具有 72 个 blob 的签名竞标从 316 字节变为 3772 字节。这可能会使 p2p 竞标机制上的 DOS 防御策略更加严格,并且可能会通过增加与提议者的直接连接到中心化构建者拍卖的延迟来发挥作用。

请注意,这也没有改变在验证数据列侧车时需要看到信标区块的要求。我们可以从侧车中删除 KZG 承诺,因为侧车具有信标区块根,因此我们可以从包含的竞标中获取承诺。负载 envelope 也会发生类似的情况。

Merkle 包含证明

原则上,构建者可以在数据列侧车上包含相同的 Merkle 系统,该系统表明侧车已在与提议者签名相关联的信标区块中提交。这将使得可以验证数据列侧车而无需看到信标区块。如果构建者通过直接连接从提议者处获取区块并立即广播数据列,则实际上可能会发生这种情况。N+1 的证明者可以在获取 N 的信标区块之前接收这些列。但是,由于这些证明者有一个完整的 slot 来验证数据,因此使用此包含机制并无缘无故地膨胀带宽并没有多大意义。数据列验证不再位于验证的热路径中,只有下一个 slot 的证明者感兴趣,而不是当前 slot 的证明者。

实际规范

当前结构已修改为

class DataColumnSidecar(Container):
    index: ColumnIndex
    column: List[Cell, MAX_BLOB_COMMITMENTS_PER_BLOCK]
    kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]
    kzg_proofs: List[KZGProof, MAX_BLOB_COMMITMENTS_PER_BLOCK]
    # [在 Gloas:EIP7732 中修改]
    # 删除 `signed_block_header`
    # [在 Gloas:EIP7732 中修改]
    # 删除 `kzg_commitments_inclusion_proof`
    # [在 Gloas:EIP7732 中新增]
    slot: Slot
    # [在 Gloas:EIP7732 中新增]
    beacon_block_root: Root

如果我们确实将 KZG 承诺移动到竞标中,我们将从该结构中删除它们。请注意,由于上述原因,我们已经删除了 Merkle 证明。

gossip 验证如下

- _[忽略]_ 已经由有效的签名执行负载竞标看到了侧车的 `beacon_block_root`。客户端可以在检索到区块后将侧车排队以进行处理。
- _[拒绝]_ 侧车的 `slot` 匹配根为 `beacon_block_root` 的区块的 slot。
- _[拒绝]_ 侧车的 `kzg_commitments` 的哈希匹配对应构建者在 `sidecar.beacon_block_root` 的竞标中的
  `blob_kzg_commitments_root`。
- _[拒绝]_ 侧车是有效的,由 `verify_data_column_sidecar(sidecar)` 验证。
- _[拒绝]_ 侧车适用于正确的子网 —— 即
  `compute_subnet_for_data_column_sidecar(sidecar.index) == subnet_id`。
- _[拒绝]_ 侧车的列数据是有效的,由 `verify_data_column_sidecar_kzg_proofs(sidecar)` 验证。
- _[忽略]_ 侧车是元组 `(sidecar.beacon_block_root, sidecar.index)` 的第一个具有有效 kzg 证明的侧车。

因此,我们明确要求信标区块是已知的,并且 kzg 承诺哈希与竞标中提交的哈希匹配。verify_data_column_sidecar 修改如下

def verify_data_column_sidecar(sidecar: DataColumnSidecar) -> bool:
    """
    验证数据列侧车是否有效。
    """
    # 侧车索引必须在有效范围内
    if sidecar.index >= NUMBER_OF_COLUMNS:
        return False

    # 零 blob 的侧车无效
    if len(sidecar.kzg_commitments) == 0:
        return False

    # [在 Gloas:EIP7732 中修改]
    # 检查侧车是否遵守 blob 限制
    epoch = compute_epoch_at_slot(sidecar.slot)
    if len(sidecar.kzg_commitments) > get_blob_parameters(epoch).max_blobs_per_block:
        return False

    # 列长度必须等于承诺/证明的数量
    if len(sidecar.column) != len(sidecar.kzg_commitments) or len(sidecar.column) != len(
        sidecar.kzg_proofs
    ):
        return False

    return True

我们现在需要使用侧车的 slot 而不是不再存在的区块头来检查侧车的 KZG 承诺是否遵守 blob 限制。

如果我们按照上述说明进行操作以从侧车中删除 KZG 承诺,则此帮助程序会将它们作为输入,从信标区块的已提交竞标中传递。

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

0 条评论

请先 登录 后评论
potuz_
potuz_
江湖只有他的大名,没有他的介绍。