EIP-7916: SSZ 渐进列表
用于高效哈希短列表的 SSZ 类型
| Authors | Zsolt Felföldi (@zsfelfoldi), Cayman (@wemeetagain), Etan Kissling (@etan-status) |
|---|---|
| Created | 2025-03-24 |
| Discussion Link | https://ethereum-magicians.org/t/eip-7916-ssz-progressivebytelist/23254 |
Table of Contents
摘要
此 EIP 引入了一种新的 Merkle 树形结构,用于 Simple Serialize (SSZ),当仅使用少量叶子节点时,可以减少哈希次数。新的树形结构随着叶子节点数量的增加而逐渐增长,并且不再具有有界容量。它还提供了向前兼容性:给定的块索引始终被分配相同的稳定 generalized index (gindex),而与叶子节点数量无关。
定义了新的类型以使用渐进式 Merkle 树形结构:ProgressiveList[type] 和 ProgressiveBitlist。这些新类型表示具有稳定 merkleization 的任意长度的列表,从而减少了小型列表的哈希开销,并避免了任意容量限制。
动机
当前的 SSZ List[type, N] 类型需要预定义的容量 N,这导致了以下几个问题:
- 哈希效率低下:列表通常包含远少于其最大容量的元素(例如,
Transaction),从而导致不必要的零填充和数十个额外的哈希计算。当嵌套List[type, N]时,这种情况会更加恶化,例如,在每个交易最多X个访问列表的设计中,每个访问列表最多有Z个存储槽。 - 任意限制:容量
N通常是任意选择的(例如,MAX_BYTES_PER_TRANSACTION,MAX_TRANSACTIONS_PER_PAYLOAD),并设置为人为的大值,以预测未来并不总是正确的设计空间。 - 不稳定的证明:跨分叉修改
N(例如,MAX_ATTESTER_SLASHINGS_ELECTRA,MAX_ATTESTATIONS_ELECTRA)会更改 gindex,从而破坏下游验证器。
渐进式 Merkle 树形结构通过以下方式解决这些问题:
- 使用递归树结构,该结构随着实际叶子节点数量的增加而逐渐增长,且开销最小
- 放弃最大容量的概念,而是依赖于实际的限制,例如,SSZ 的 4 GB 变量偏移上限,网络负载限制,gas 限制,签名数量的限制。
- 为每个元素维护稳定的 gindex,确保证明器在叶子节点数量更改时保持有效。
规范
本文档中的关键词“必须”,“禁止”,“需要”,“应该”,“不应该”,“推荐”,“不推荐”,“可以”和“可选”应解释为 RFC 2119 和 RFC 8174 中所述。
渐进式 Merkle 树
SSZ Merkleization specification) 通过辅助函数进行扩展:
merkleize_progressive(chunks, num_leaves=1): 给定排序后的BYTES_PER_CHUNK字节块:- merkleization 取决于输入块的数量,并以递归方式定义:
- 如果
len(chunks) == 0:根是一个零值,Bytes32()。 - 否则:使用
hash(a, b)计算根a: 使用merkleize_progressive(chunks[num_leaves:], num_leaves * 4)递归地 merkleize 超出num_leaves的块。b: 使用merkleize(chunks[:num_leaves], num_leaves)将前num_leaves个块 merkleize 为二叉树。
- 如果
- merkleization 取决于输入块的数量,并以递归方式定义:
这导致一个以 0 结尾的二叉子树序列,其叶子节点数量不断增加。最深的子树用零填充的块填充(实际上是为了提高内存效率)。
root
/\
/ \
/\ 1: chunks[0 ..< 1]
/ \
/\ 4: chunks[1 ..< 5]
/ \
/\ 16: chunks[5 ..< 21]
/ \
0 64: chunks[21 ..< 85]
| 深度 | 添加的块 | 总块数 | 总容量 (字节) |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 1 | 1 | 1 | 32 |
| 2 | 4 | 5 | 160 |
| 3 | 16 | 21 | 672 |
| 4 | 64 | 85 | 2’720 |
| 5 | 256 | 341 | 10’912 |
| 6 | 1’024 | 1’365 | 43’680 |
| 7 | 4’096 | 5’461 | 174’752 |
| 8 | 16’384 | 21’845 | 699’040 |
| 9 | 65’536 | 87’381 | 2’796’192 |
| 10 | 262’144 | 349’525 | 11’184’800 |
| 11 | 1’048’576 | 1’398’101 | 44’739’232 |
| 12 | 4’194’304 | 5’592’405 | 178’956’960 |
| 13 | 16’777’216 | 22’369’621 | 715’827’872 |
| 14 | 67’108’864 | 89’478’485 | 2’863’311’520 |
| 15 | 268’435’456 | 357’913’941 | 11’453’246’112 |
ProgressiveList[type] 和 ProgressiveBitlist
定义了两种新的 SSZ 组合类型):
- 渐进列表:有序的可变长度同构集合
- 符号
ProgressiveList[type],例如ProgressiveList[uint64]
- 符号
- 渐进位列表:
boolean值的有序的可变长度集合- 符号
ProgressiveBitlist
- 符号
新类型被认为是“可变大小的”。
为了方便起见,我们别名:
ProgressiveByteList到ProgressiveList[byte]
默认值 定义为:
| 类型 | 默认值 |
|---|---|
ProgressiveList[type] |
[] |
ProgressiveBitlist |
[] |
序列化
ProgressiveBitlist 的序列化、反序列化和 JSON 映射与 Bitlist[N] 相同。
ProgressiveList[type] 的序列化、反序列化和 JSON 映射与 List[type, N] 相同。
Merkle化
Merkleization 定义已扩展。
- 如果
value是基本对象的渐进列表,则mix_in_length(merkleize_progressive(pack(value)), len(value))。 - 如果
value是渐进位列表,则mix_in_length(merkleize_progressive(pack_bits(value)), len(value))。 - 如果
value是复合对象的渐进列表,则mix_in_length(merkleize_progressive([hash_tree_root(element) for element in value]), len(value))。
理由
为什么是递归结构?
- 效率:小型列表使用更少的哈希(例如,16 个元素的子树中的 3 个项目的列表比 1024 个元素的
List[type, N]浪费更少的哈希)。 - 稳定性:固定的子树大小可确保稳定的 gindex,从而避免了动态深度调整或多次查询的需求。
- 可伸缩性:递归子树允许任意增长而没有硬编码的限制,仅受实际限制的约束(例如,网络负载限制,验证规则)。
为什么没有动态深度?
动态深度 Merkleization 会破坏 gindex:
- 需要两步查询(长度,然后是 gindex),从而增加了延迟和重组风险。
- 使带有语义查找的证明复杂化。
混合后继子树可确保可预测的 gindex 和证明大小。
为什么没有固定容量列表?
List[type, N]:
- 施加任意限制,从而阻碍了可伸缩性。
- 在重新定义时会破坏稳定性。
- 使用填充来浪费哈希(例如,对于 1 项列表,容量为 1024 个元素)。 (仅浪费 log(N) 个哈希)
ProgressiveList[type] 提供了一种可伸缩,高效的替代方案。
为什么初始叶子节点计数和缩放因子不是公开的参数?
- 简单性:固定值(初始叶子节点计数 1,缩放因子 4)提供了一个合理的默认值,该默认值平衡了效率和可用性,与 SSZ 的简单性目标相符。
- 未来可扩展性:如果特定用例需要不同的值,则将来的 EIP 可能会引入参数化。目前,固定值减少了采用障碍,并符合“足够好”的默认值原则。
向后兼容性
新的 SSZ 类型与现有类型共存而没有冲突,并共享其序列化逻辑。
测试用例
请参阅 EIP assets。
参考实现
请参阅 EIP assets,基于 protolambda/remerkleable。
安全考虑
- 资源限制:可变长度偏移量的
uint32限制实际上引入了 ~4GB 的上限,当在另一个复杂类型中包括ProgressiveList[type]或ProgressiveBitlist时,但实际限制(例如,10MB libp2p 消息)适用。 实现应强制执行特定于上下文的边界。 - 可变证明大小:递归遍历可能会增加大索引的证明大小,但由于缩放因子,它在列表大小中是对数的。
版权
在 CC0 下放弃版权和相关权利。
Citation
Please cite this document as:
Zsolt Felföldi (@zsfelfoldi), Cayman (@wemeetagain), Etan Kissling (@etan-status), "EIP-7916: SSZ 渐进列表 [DRAFT]," Ethereum Improvement Proposals, no. 7916, March 2025. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7916.