门票化 Blob Mempool 设计

本文提出了一种基于门票的 blob mempool 设计,通过垂直分片 mempool 和门票分配机制,实现 DoS 防护和提前采样,从而降低峰值带宽需求,并避免单独的 blob 包含层。门票通过 1559 机制出售,并可为上链交易退款。文章讨论了多种设计方案和权衡,包括签名、KZG 证明验证和哈希校验,并分析了在基于汇总的未来可能面临的挑战。

===== 开始 Markdown 内容 ===== 一年前修改一年前修改

Blob 内存池票证

介绍

是什么

核心思想是:

  • 镜像内存池采样:使用垂直分片内存池,节点下载所有 blob 交易,但只下载相关 blob 的样本,所选索引与区块的列采样镜像,即下载第 \(i\) 列的节点也会下载内存池中所有 blob 交易的第 \(i\) 个样本。
  • 内存池空间分配:为避免分片内存池上的拒绝服务攻击问题,内存池限制为每个 Slot 固定的 blob 容量,这种稀缺的内存池写入权限通过以内存池票证形式出售来分配。

为什么

  • 这是一种相对简单的方法,可以实现对垂直分片内存池的必要限制,额外的好处是使内存池吞吐量完全受限于所需的网络吞吐量。
  • 对于所有通过内存池的 blob,采样已经可以在那里进行,并且时间线更宽松。这意味着:
    • 峰值带宽不再是主要约束。
    • 我们可以更安全地使用基于拉取的 gossip(就像今天的 blob 内存池已经做的那样)。这减少了冗余,因为每个 blob 平均每个节点只发送和接收一次。
    • CL 和 EL 之间没有带宽重复,只有内存池传播。
  • 不需要单独的 blob IL 机制:不必通过随机选举验证者进入 IL 委员会来分配制作 blob IL 的权利,我们可以将每个内存池票证视为也在 blob IL 委员会中分配了一个席位。换句话说,内存池中的每个 blob 交易本身就可以充当 blob IL。由于这类交易数量很少,验证者不需要将它们打包成 IL,我们可以依赖直接的内存池观察(尽管仍然需要不运行完整内存池的证明者提供某种可用性信号,比如委员会的投票。参见此处)。

内存池

现在暂时不担心如何分配票证,只关注如何利用它们构建 blob 内存池。为此,我们只需要共识层以某种方式知道这些票证。假设 BeaconState 在循环数组 state.blob_tickets_record 中记录接下来 SLOTS_PER_EPOCH 个 Slot 的票证持有者。具体来说,state.blob_tickets_record[slot % SLOTS_PER_EPOCH] 以 BLS 公钥的形式记录 slot 的票证持有者。

class BeaconState(Container):
    ...
    blob_tickets_record: Vector[List[BLSPubkey, MAX_BLOBS_PER_BLOCK], SLOTS_PER_EPOCH]

Blob 内存池完全存在于 CL p2p 网络上,包含一个全局主题 blob_transaction_envelope,用于共享 blob 交易,以及 NUMBER_OF_COLUMNSmempool_cells 主题,用于共享与这些交易相关的 blob 样本及证明。在 Slot N 发送这些主题中的消息需要持有 Slot N 的票证,可以根据记录的公钥进行检查,即 pubkeys = state.blob_tickets_record[N % SLOTS_PER_EPOCH]。具体来说,消息必须包含 ticket_index 并使用 pubkeys[ticket_index] 签名。

Blob 交易主题

blob_transaction_envelope

class BlobTransactionEnvelope(Container):
    transaction: Transaction
    kzg_commitments: List[KZGCommitments, MAX_BLOBS_PER_TRANSACTION]
    slot: Slot
    ticket_indices: List[uint8, MAX_BLOBS_PER_TRANSACTION]

class SignedBlobTransactionEnvelope(Container):
    message: BlobTransactionEnvelope
    signature: BLSSignature

注意:如果我们有 SSZ 编码的交易,可以从 BlobTransactionEnvelope 中移除 kzg_commitments,因为我们可以直接访问版本化哈希,并对照 cell 中的 KZG 承诺进行检查。如果没有,我们还需要将对应于 kzg_commitments 的版本化哈希提供给 EL,并让其检查它们是否与 transaction 中的一致。

Gossip 检查

blob_tickets = state.blob_tickets[slot % SLOTS_PER_EPOCH]。在转发之前,我们基本上只检查消息是否正确由未使用票证的持有者签署,要求:

  • 所有对应于 ticket_indices 的票证持有者是同一人,即对于 ticket_indices 中的所有 ticket_indexblob_tickets[ticket_index].pubkey 相同。
  • 对于 ticket_indices 中的每个 ticket_index,这是为该 ticket_index 收到的第一条有效消息。
  • signature 是由 pubkeymessage 的有效签名,其中 pubkey 是拥有所有票证的唯一公钥,即 blob_tickets[ticket_index].pubkey(对于 ticket_indices 中的任意 ticket_index)。

样本主题

mempool_cells_{subnet_id}

class CellSidecar(Container)
    cell: Cell
    index: CellIndex
    kzg_proof: KZGProof
    kzg_commitment: KZGCommitment

class MempoolCells(Container):
    cell_sidecars: List[CellSidecar, MAX_BLOBS_PER_TRANSACTION]
    slot: Slot
    ticket_indices: List[uint8, MAX_BLOBS_PER_TRANSACTION]

class SignedMempoolCells(Container):
    message: MempoolCells
    signature: BLSSignature

注意:与 CellSidecar 相比,开销为 105 字节(主要来自签名),当 NUM_COLUMNS = 128 时约为 5%,当 NUM_COLUMNS = 256 时约为 10%。与在单个(签名)消息中发送带证明的 blob 相比,开销仅来自每个 cell 中的 kzg_commitmentcell_indexslotticket_index,每个 cell 总共 65 字节。

Gossip 检查

在转发之前,我们执行与 BlobTransactionEnvelope 主题完全相同的检查,基于 slotticket_indices,相当于检查消息是否正确由该 Slot 未使用票证的持有者签署。我们还要检查子网是否正确:

  • compute_subnet_for_cell_sidecar(cell_sidecar.cell_index) == subnet_id
有效性

在 gossip 时,我们不要求以下检查,但要使 MempoolCells 对象有效(连同 gossip 检查),则需要这些检查:

  • 验证 cell_sidecar.cell 是通过 cell_sidecar.kzg_proofcell_sidecar.cell_index 处打开 cell_sidecar.kzg_commitment 的结果。

换句话说,我们不要求验证 cell sidecar 本身。原因在于,验证单个 cell 虽然相当便宜(约 3ms),但效率远低于批量验证(例如,整个 blob 约 16ms)。为了防止 DoS,检查签名就足够了。完整的验证可以在以后进行,一旦某个 blob 的所有 cell 都已检索到(或者,客户端可以自由安排验证时间)。只有当证明被验证后,这些 cell 才最终被视为有效。

替代设计

由于验证签名比验证 cell 证明“仅”快约 2-3 倍,另一种设计可能是不对 MempoolCells 进行签名(节省 96 KB,约 10%),而是立即验证证明,无需等待批量验证。但是,节点只有在知道对应的 BlobTransactionEnvelope 时才会验证并转发 MempoolCells 对象,因为在这种设计中,签名验证(即通过票证限制内存池)只会发生在该对象中。

另一种设计是重新引入少量带宽开销,但完全消除验证开销:在 BlobTransactionEnvelope 中包含一个字段 mempool_cells_hashes: List[Root, NUMBER_OF_COLUMNS],存储与交易关联的所有 MempoolCells 对象的哈希。这样,一旦节点拥有了 BlobTransactionEnvelope,它就可以转发任何哈希匹配的 CellSidecar,而无需等待 KZG 证明验证,验证可以在方便批量处理时进行。这样就没有验证开销,而带宽开销仅为每个 cell 32 字节(假设最坏情况是单个 blob 的交易),比签名方式少 3 倍。

总结,至少有三种可能的设计,权衡如下:

验证开销 带宽开销 需要信封才能转发 cell
对 cell 签名 1ms 每个 cell 96 KB
验证 KZG 证明 2-3ms 0
对照信封中的哈希检查 0 每个 cell 32 KB

票证分配

1559 销售

票证可以通过 EL 上的系统合约出售,实现类似 1559 的机制,很像 EIP-7002(EL 触发的提款)使用的机制。然而,可能更适合批量出售票证,时间跨度超过一个 Slot。例如,我们可以出售

目标和最大值可以设置为与 blob 交易费用市场相同的值,因为我们希望内存池有足够的容量来支持网络吞吐量,但不超过:理想情况下,我们不希望那些通过内存池但最终没有上链的交易。例如,最终我们可能每个 Slot 有 128 张票证的目标,以及 256 张的最大值。

注意:这种方法的一个弱点是“如果某个 Slot 被错过,IL 票证就被浪费了。我们可以为错过的 Slot 退款票证,但那样区块提议者可能会做诸如购买所有票证并故意错过该 Slot 之类的事情,如果它无法以足够利润转售它们的话。”

退款

票证分配机制必须确保 DoS 抵抗性:我们只允许有限数量的 blob 交易进入内存池,因此只能分配有限数量的票证。如果我们能提前知道哪些发送者会将 blob 上链,我们就可以将票证分配给他们。但实际中我们当然没有这种先见之明,可能不得不为票证定价。

为了避免阻止 blob 交易发送者使用内存池,我们可以对上链的票证进行退款,同时不放弃 DoS 抵抗性:只有当你将相应的交易上链时,票证才是免费的,此时交易已经支付了费用,并且我们允许它进入内存池是可以接受的。然而,每当其交易上链时就退款票证可能会被滥用:通常每个 epoch 提交少量 blob 交易的人可以在一段时间内购买所有内存池票证,远远超出其自身需求,然后通过将其(真实的)blob 交易直接发送给构建者来慢慢获得退款。虽然这不算严重的攻击,但它仍然允许某人暂时阻止其他人访问内存池,这并不理想。为了防止这种情况,我们可以缩短退款期限。如果设置为一个 Slot,意味着你只有在 Slot N+1 上链交易时才能获得 Slot N 票证的退款,此时攻击向量就不再存在。本质上,你只有在证明自己确实有正当理由在 Slot N 使用内存池时才能获得退款。

为什么不是 blob 票证

先前的帖子探讨了协议出售 blob 票证的想法,这既可以限制对 blob 内存池的访问,也可以授予包含权利。想法是一石二鸟:获得一个抗 DoS 的垂直分片 blob 内存池,并确保它用于提前对 blob 交易进行采样,而不是在区块传播的关键路径上。

这个解决方案似乎非常适合当前的世界,其中排序主要由 L1 之外完成,因此 blob 提交对时间不敏感,因为它只是在已经存在的 Rollup 层面上确认链上信息。然而,在一个充满 Based Rollup 的未来,尤其是基于原生的 Rollup 不断交互的情况下,事情可能会变得更加复杂。到那时,blob 交易允许访问 L1 和所有此类 L2 的统一状态,并且 blob 交易的包含时间和排序在这个扩展状态中与今天常规 L1 状态一样重要,例如,一个区块中的第一笔 blob 交易可能套利 L1 和多个 L2 上的 AMM。

在这样的世界中,提前出售 blob 交易的包含权利将类似于现在将某种部分区块构建神圣化,因为每个 blob 本质上是统一状态的部分区块。对于部分区块构建,由于状态争用导致的潜在问题范围很大,而且系统是否会崩溃回一个购买大多数票证的超级构建者尚不清楚。此外,时间游戏现在也适用于 blob 交易(再次强调,它们基本上就是这个统一状态的交易),因此几乎没有理由期望内存池传播会在整个 Slot 内发生,而不是尽可能晚地发生。 ===== 结束 Markdown 内容 =====

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

0 条评论

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