This repository contains the Ethereum Proof-of-Stake Consensus Specifications. It serves as:
- Formal specifications in human-readable markdown with embedded Python
- Executable reference implementation (Python code generated from markdown)
- Reference test generator for client implementations
- Protocol development platform organized by network upgrades (forks)
The specifications define how Ethereum's consensus layer (beacon chain) operates.
/specs/
phase0/ # The genesis specs
altair/ # The 1st upgrade (starts with A)
bellatrix/ # The 2nd upgrade (starts with B)
capella/ # The 3rd upgrade (starts with C)
...
_features/ # Features which have not been scheduled for inclusion
/tests/
core/pyspec/eth_consensus_specs/
<fork>/ # Assembled pyspec (do not edit)
test/<fork>/ # Test cases organized by fork
block_processing/
epoch_processing/
sanity/
...
test/helpers/ # Shared test helpers
<fork>/ # Fork-specific test helpers
generators/ # Reference test generators
formats/ # Test format specifications
Everything is done through the Makefile. Run make help verbose=true for full
documentation.
make lintThis command runs all linters, formatters, and checks for the repository. It covers Python code style, markdown formatting, table of contents validation, and spec-specific checks. Always run this before committing to ensure changes meet the project standards.
See .claude/skills/run-tests/SKILL.md.
make cleanThis command deletes all untracked files in the repository. Any untracked files
that should be preserved must be staged with git add before running this
command.
Reference tests generate test vectors that client implementations use. Prefer reference tests when possible since they benefit the entire ecosystem.
Unittests are internal-only tests that don't produce reference files. Use
these when reference tests are not feasible (e.g., testing internal helpers or
edge cases that do not map to client behavior). Unittests are located in
unittests directories.
Reference test format specifications are located in tests/formats/. These
define the directory and file structure for generated reference tests,
documenting the expected inputs, outputs, and file organization for each test
category (e.g., operations/, sanity/, epoch_processing/). Client
implementations use these specifications to parse and run the reference tests.
When writing tests, use these decorators:
@with_all_phases- Run on all forks@with_phases([DENEB, FULU])- Run on specific forks@with_deneb_and_later- Run on Deneb and all subsequent forks@with_electra_and_later- Run on Electra and all subsequent forks@spec_state_test- State transition test@spec_test- General spec test@always_bls- Always enable BLS verification
Tests yield their outputs for reference test generation:
@with_all_phases
@spec_state_test
def test_example(spec, state):
# Setup
yield "pre", state
# Execute
block = build_empty_block_for_next_slot(spec, state)
signed_block = state_transition_and_sign_block(spec, state, block)
yield "blocks", [signed_block]
yield "post", state- Add the Python function to the appropriate spec markdown file
- Add tests in
tests/core/pyspec/eth_consensus_specs/test/ - Run
make lintto run checks
- Find the function in the spec markdown
- Make the necessary changes, adding "fork comments" above changed lines
- Run
make lintto run checks
- Add field to container definition in spec markdown
- Update any functions that construct or use the container
- Update preset/config if needed
- Run
make lintto run checks
Adding a new fork (e.g., "foobar") requires updates to many files:
1. Build system:
Makefile- Add toALL_EXECUTABLE_SPEC_NAMES
2. GitHub automation:
.github/labeler.yml- Add label config for auto-labeling PRs.github/release-drafter.yml- Add category for release notes
3. Spec generation (pysetup/):
pysetup/constants.py- AddFOOBAR = "foobar"pysetup/md_doc_paths.py- Import constant, add toPREVIOUS_FORK_OFpysetup/spec_builders/foobar.py- Create SpecBuilder classpysetup/spec_builders/__init__.py- Import and register the SpecBuilder
4. Test infrastructure (tests/core/pyspec/eth_consensus_specs/test/):
helpers/constants.py- Add constant, updateALL_PHASES,PREVIOUS_FORK_OF,POST_FORK_OFhelpers/forks.py- Addis_post_foobar(spec)functioncontext.py- Addwith_foobar_and_laterdecorator
5. Spec files:
specs/foobar/- For scheduled forksspecs/_features/eipNNNN/- For experimental features (must start with "eip")
6. Presets (if the fork has preset values):
presets/mainnet/foobar.yamlpresets/minimal/foobar.yaml
This is an evolving specification. Do not rely on prior knowledge or cached context when modifying the spec. Always read the current spec files to verify how functions, containers, and logic actually behave before making changes.
Each fork inherits all specs from the previous fork. The chain is defined in
pysetup/md_doc_paths.py via PREVIOUS_FORK_OF. When generating a fork's spec,
all markdown files from ancestor forks are loaded first.
When adding to a new fork:
- Reference the previous fork at the top of the spec
- Only include new or modified sections
- Include an
upgrade_to_<fork>function that converts the previous fork'sBeaconStateto the new fork's state
Changes to an older fork (functions, containers, constants, etc.) may require updates to newer forks as well, if those elements are used or modified in later forks.