Skip to content

SSZ source generator: emit InlineArray for fixed-size byte vectors (commitments, proofs, hashes) #11525

@LukaszRozmej

Description

@LukaszRozmej

Context

Discussed in #11301. The SSZ source generator currently emits wrapper containers like SszKzgCommitment and SszBlob as plain structs containing a byte[] field:

[SszContainer]
public partial struct SszKzgCommitment
{
    [SszVector(48)] public byte[]? Bytes { get; set; }
}

[SszContainer]
public partial struct SszBlob
{
    [SszVector(0x2_0000)] public byte[]? Bytes { get; set; }   // 131,072 bytes
}

Per blob bundle that's up to 4096 SszKzgCommitment instances (V1) or more (V2), each carrying its own byte[48] heap allocation. The outer array adds another. So encoding one V2 blobs-bundle costs 4096 × 2 + outer arrays ≈ 8000+ small allocations for the proofs alone.

For the small fixed-size cases (32-byte Hash256, 48-byte commitments/proofs), C# 12's [InlineArray(N)] would let the generator emit an inline-stored struct:

[InlineArray(48)]
public struct SszKzgCommitment
{
    private byte _e0;
}

Then SszKzgCommitment[] is a single contiguous heap allocation of count * 48 bytes (no per-element header) and the wire format is identical (SSZ for a fixed-size byte vector is just the raw bytes).

What's in scope

Generator-side support for [InlineArray(N)] byte-vector backing on small fixed-size byte-vector containers:

  • SszKzgCommitment (48) — commitments, proofs in BlobsBundleV{1,2}
  • Hash arrays in SszCodec.HashesFromWire (32 each) — currently allocates byte[32] per hash
  • Any future fixed-size 32/48/64/96-byte byte-vector container

What's NOT in scope

  • SszBlob (131,072 bytes) — too large to InlineArray. A 4096-blob bundle would be ~512 MiB inline; stack/struct copies become catastrophic. Keeps byte[] backing.
  • Variable-length lists ([SszList]) — InlineArray is fixed-size by definition.
  • Bitvectors / bitlists — different shape.

Threshold

Suggest a generator-side threshold (e.g., a [SszInline] opt-in attribute, or auto-promote when [SszVector(N)] and N <= 96). The threshold should be conservative — small fixed sizes only.

Why now

#11301 introduces SSZ-REST transport with raw blob payloads on the hot CL→EL path. Per-request allocations in BlobsBundleV{1,2}Wire encoding are a measurable contributor at high blob counts (post-Osaka with MAX_BLOB_COMMITMENTS_PER_BLOCK = 4096).

Acceptance criteria

  • Generator detects opt-in (or small-fixed-size auto) and emits [InlineArray(N)] struct.
  • Encode/decode/merkleize paths work for inline-array-backed types — bytes are accessed as MemoryMarshal.CreateSpan.
  • BlobsBundle encoding path measurably reduces allocations on a benchmark (e.g., 4096-commitment BlobsBundle encode).
  • Existing SszBlob (131,072) keeps byte[] — explicit non-conversion.
  • All existing SSZ generator tests pass.

Notes

  • C# 12+ / net8.0+ required for [InlineArray]. Repo targets net10.0
  • The user-facing wrapper types (SszKzgCommitment, etc.) are internal to Nethermind.Merge.Plugin.SszRest and the generator — no external consumers.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions