Appendices

Reference

Running the spec

Introduction

Being written in Python, the spec itself is executable. This is wonderful for generating test cases and there is a whole infrastructure in the specs repo for doing just that.

We can also run the spec ourselves to do interesting things. In this exercise we will calculate the minimum and maximum sizes of the various containers defined by the spec. The following code is from Protolambda, lightly modified to simplify and update it.

from inspect import getmembers, isclass
from eth2spec.utils.ssz.ssz_typing import Container
from eth2spec.capella import mainnet

def get_spec_ssz_types():
    return [
        value for (_, value) in getmembers(mainnet, isclass)
        if issubclass(value, Container) and value != Container  # only the subclasses, not the imported base class
    ]

type_bounds = {
    value.__name__: ({
        'size': value.type_byte_length()
    } if value.is_fixed_byte_length() else {
        'min_size': value.min_byte_length(),
        'max_size': value.max_byte_length(),
    }) for value in get_spec_ssz_types()
}
import json
print(json.dumps(type_bounds))

Set up

In the below, if you are using Ubuntu you might need to run sudo apt install python3-pip first. If not, then you probably need to use python rather than python3.

The specs installation is much easier than it used to be.

> git clone https://github.com/ethereum/consensus-specs.git
Cloning into 'consensus-specs'...
...
> cd consensus-specs/
> python3 -m pip install .
... lots of output ...
Successfully installed...
> make pyspec
... lots more output ...

All being well, this will create a directory for each of the spec versions under tests/core/pyspec/eth2spec/: altair, bellatrix, capella and so on. Each directory contains the complete executable specification for that version, built automatically from the markdown source. There's a mainnet version for each one, and a minimal version that runs with lower resource requirements. All this magic is performed by the scripts in pysetup.

Run

Finally, we can simply run the Python script from above. Copy it into a file called sizes.py and run it as follows.

> source venv/bin/activate
(venv) > python sizes.py | jq
{
  "AggregateAndProof": {
    "min_size": 337,
    "max_size": 593
  },
...

The pipe to jq is optional, you will just get less pretty output without it.

Full output

Values are bytes. Don't be alarmed that the maximum size of BeaconState turns out to be 139 TiB, or that BeaconBlockBody can be enormous. These sizes are based on the notional maximum SSZ list lengths they contain, and are not realistic in practice.

{
  "AggregateAndProof": {
    "min_size": 337,
    "max_size": 593
  },
  "Attestation": {
    "min_size": 229,
    "max_size": 485
  },
  "AttestationData": {
    "size": 128
  },
  "AttesterSlashing": {
    "min_size": 464,
    "max_size": 33232
  },
  "BLSToExecutionChange": {
    "size": 76
  },
  "BeaconBlock": {
    "min_size": 984,
    "max_size": 1125899911198752
  },
  "BeaconBlockBody": {
    "min_size": 900,
    "max_size": 1125899911198668
  },
  "BeaconBlockHeader": {
    "size": 112
  },
  "BeaconState": {
    "min_size": 2737221,
    "max_size": 152833729758309
  },
  "Checkpoint": {
    "size": 40
  },
  "ContributionAndProof": {
    "size": 264
  },
  "Deposit": {
    "size": 1240
  },
  "DepositData": {
    "size": 184
  },
  "DepositMessage": {
    "size": 88
  },
  "Eth1Block": {
    "size": 48
  },
  "Eth1Data": {
    "size": 72
  },
  "ExecutionPayload": {
    "min_size": 512,
    "max_size": 1125899911038176
  },
  "ExecutionPayloadHeader": {
    "min_size": 568,
    "max_size": 600
  },
  "Fork": {
    "size": 16
  },
  "ForkData": {
    "size": 36
  },
  "HistoricalBatch": {
    "size": 524288
  },
  "HistoricalSummary": {
    "size": 64
  },
  "IndexedAttestation": {
    "min_size": 228,
    "max_size": 16612
  },
  "LightClientBootstrap": {
    "min_size": 25600,
    "max_size": 25632
  },
  "LightClientFinalityUpdate": {
    "min_size": 1992,
    "max_size": 2056
  },
  "LightClientHeader": {
    "min_size": 812,
    "max_size": 844
  },
  "LightClientOptimisticUpdate": {
    "min_size": 984,
    "max_size": 1016
  },
  "LightClientUpdate": {
    "min_size": 26776,
    "max_size": 26840
  },
  "PendingAttestation": {
    "min_size": 149,
    "max_size": 405
  },
  "PowBlock": {
    "size": 96
  },
  "ProposerSlashing": {
    "size": 416
  },
  "SignedAggregateAndProof": {
    "min_size": 437,
    "max_size": 693
  },
  "SignedBLSToExecutionChange": {
    "size": 172
  },
  "SignedBeaconBlock": {
    "min_size": 1084,
    "max_size": 1125899911198852
  },
  "SignedBeaconBlockHeader": {
    "size": 208
  },
  "SignedContributionAndProof": {
    "size": 360
  },
  "SignedVoluntaryExit": {
    "size": 112
  },
  "SigningData": {
    "size": 64
  },
  "SyncAggregate": {
    "size": 160
  },
  "SyncAggregatorSelectionData": {
    "size": 16
  },
  "SyncCommittee": {
    "size": 24624
  },
  "SyncCommitteeContribution": {
    "size": 160
  },
  "SyncCommitteeMessage": {
    "size": 144
  },
  "Validator": {
    "size": 121
  },
  "VoluntaryExit": {
    "size": 16
  },
  "Withdrawal": {
    "size": 44
  }
}

See also

Hsiao-Wei Wang gave a Lightning Talk on the consensus Pyspec at Devcon VI. She swiftly covers how it is structured, how to run it, and how to build test cases. The presentation slides are available.

Created by Ben Edgington. Licensed under CC BY-SA 4.0. Published 2025-09-01 08:19 UTC. Commit d013cb9.