EIP-7916 - SSZ ProgressiveList

Created 2025-03-24
Status Draft
Category Core
Type Standards Track
Authors

Abstract

This EIP introduces a new Simple Serialize (SSZ) type, ProgressiveList[T], to represent lists of arbitrary length with stable merkleization. Unlike the existing List[T, N] type, which imposes a fixed capacity N, ProgressiveList[T] supports unbounded growth using a recursive tree structure during merkleization to efficiently handle lists of any size while maintaining stable generalized indices (gindices) for individual elements. This enables reduced hash overhead for small lists and avoids arbitrary predefined limits.

Motivation

Current SSZ List[T, N] types require a predefined capacity N, which leads to several issues:

ProgressiveList[T] addresses these by:

This is particularly valuable for execution-layer (EL) data like receipt logs and calldata, where list sizes vary widely, and for consensus-layer (CL) structures where unbounded growth avoids artificial caps.

Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

ProgressiveList[T]

ProgressiveList[T] defines an ordered, homogeneous collection of elements of type T, where T is any valid SSZ type (e.g., uint64, Container, etc.).

Serialization

Serialization of ProgressiveList[T] is identical to List[T, N].

Merkleization

ProgressiveList[T] is represented as a recursive Merkle tree following this process:

ProgressiveList[T]

       V4   0 (terminator)
        \  /
     V3  \/
      \  /
   V2  \/
    \  /
 V1  \/
  \  /
   \/  LEN
    \  /
     \/
    ROOT

V1: Vector[T, 1]
V2: Vector[T, 4]
V3: Vector[T, 16]
V4: Vector[T, 64]
def merkleize_progressive_list(chunks, base_size=1, scaling_factor=4):
    if len(chunks) <= base_size:
        return mix_in_aux(merkleize(chunks + [Bytes32()] * (base_size - len(chunks))), Bytes32())
    else:
        next_size = base_size * scaling_factor
        subtree = chunks[:base_size]
        successor = chunks[base_size:]
        subtree_root = merkleize(subtree)
        successor_root = merkleize_progressive_list(successor, next_size, scaling_factor)
        return mix_in_aux(subtree_root, successor_root)
# Layer size (chunks) Total capacity (chunks)
1 1 1
2 4 5
3 16 21
4 64 85
5 256 341
6 1'024 1'365
7 4'096 5'461
8 16'384 21'845
9 65'536 87'381
10 262'144 349'525
11 1'048'576 1'398'101
12 4'194'304 5'592'405
13 16'777'216 22'369'621
14 67'108'864 89'478'485
15 268'435'456 357'913'941

ProgressiveByteList

For convenience ProgressiveByteList is defined as an alias to ProgressiveList[byte].

# Layer size (bytes) Total capacity (bytes)
1 32 32
2 128 160
3 512 672
4 2'048 2'720
5 8'192 10'912
6 32'768 43'680
7 131'072 174'752
8 524'288 699'040
9 2'097'152 2'796'192
10 8'388'608 11'184'800
11 33'554'432 44'739'232
12 134'217'728 178'956'960
13 536'870'912 715'827'872
14 2'147'483'648 2'863'311'520
15 8'589'934'592 11'453'246'112

Rationale

Why a Recursive Structure?

Why Not Dynamic Depth?

Dynamic-depth Merkleization destabilizes gindices:

Mixing in successor subtrees ensures predictable gindices and proof sizes.

Why Not Fixed-Capacity Lists?

List[T, N]:

ProgressiveList[T] offers a scalable, efficient alternative.

Why Are Base Size and Scaling Factors Not Exposed Parameters?

Backwards Compatibility

ProgressiveList[T] is a new SSZ type, coexisting with List[T, N] and other types without conflict. Its List-equivalent serialization ensures compatibility with existing serializers.

Security Considerations

Copyright

Copyright and related rights waived via CC0.