Skip to content
Merged
118 changes: 37 additions & 81 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,100 +47,56 @@ jobs:
steps:
- uses: actions/checkout@v6

# Read the pinned leanSpec commit from the Makefile (single source of truth)
- name: Get leanSpec pinned commit
id: lean-spec
run: echo "commit=$(sed -n 's/^LEAN_SPEC_COMMIT_HASH:= *//p' Makefile)" >> $GITHUB_OUTPUT
- name: Get leanSpec fixtures release info
id: fixtures-release
run: |
api_url="https://api.github.com/repos/leanEthereum/leanSpec/releases/latest"
json=$(curl -sL "$api_url")
fixtures_url=$(echo "$json" | python3 -c "import sys,json; j=json.load(sys.stdin); print(next(a.get('browser_download_url') for a in j.get('assets',[]) if a.get('name')=='fixtures-prod-scheme.tar.gz'))")
sha_url=$(echo "$json" | python3 -c "import sys,json; j=json.load(sys.stdin); print(next(a.get('browser_download_url') for a in j.get('assets',[]) if a.get('name')=='fixtures-prod-scheme.tar.gz.sha256'))")
sha=$(curl -sL "$sha_url" | cut -d' ' -f1)
{
echo "url=$fixtures_url"
echo "sha_url=$sha_url"
echo "sha=$sha"
} >> $GITHUB_OUTPUT

- name: Restore test fixtures cache
id: cache-fixtures
uses: actions/cache/restore@v5
with:
path: leanSpec/fixtures
key: leanspec-fixtures-${{ steps.lean-spec.outputs.commit }}

# All fixture generation steps are skipped when the cache hits
- name: Checkout leanSpec at pinned commit
if: steps.cache-fixtures.outputs.cache-hit != 'true'
uses: actions/checkout@v6
with:
repository: leanEthereum/leanSpec
ref: ${{ steps.lean-spec.outputs.commit }}
path: leanSpec

- name: Install uv and Python 3.14
if: steps.cache-fixtures.outputs.cache-hit != 'true'
uses: astral-sh/setup-uv@v4
with:
enable-cache: true
cache-dependency-glob: "leanSpec/pyproject.toml"
python-version: "3.14"

- name: Sync leanSpec dependencies
if: steps.cache-fixtures.outputs.cache-hit != 'true'
working-directory: leanSpec
run: uv sync --no-progress
key: leanspec-fixtures-${{ steps.fixtures-release.outputs.sha }}

- name: Get production keys URL hash
- name: Download leanSpec fixtures release
id: download-fixtures
if: steps.cache-fixtures.outputs.cache-hit != 'true'
id: prod-keys-url
working-directory: leanSpec
run: |
URL=$(uv run python -c "from consensus_testing.keys import KEY_DOWNLOAD_URLS; print(KEY_DOWNLOAD_URLS['prod'])")
HASH=$(echo -n "$URL" | sha256sum | awk '{print $1}')
echo "hash=$HASH" >> $GITHUB_OUTPUT

- name: Restore production keys cache
if: steps.cache-fixtures.outputs.cache-hit != 'true'
id: cache-prod-keys
uses: actions/cache/restore@v5
with:
path: leanSpec/packages/testing/src/consensus_testing/test_keys/prod_scheme
key: prod-keys-${{ steps.prod-keys-url.outputs.hash }}

- name: Download production keys
if: steps.cache-fixtures.outputs.cache-hit != 'true' && steps.cache-prod-keys.outputs.cache-hit != 'true'
working-directory: leanSpec
run: uv run python -m consensus_testing.keys --download --scheme prod

# Save production keys even if a later step fails, so a re-run does
# not have to re-download. See: https://github.com/actions/cache/tree/main/save#always-save-cache
#
# `cache-hit == 'false'` (rather than `!= 'true'`) only matches when
# the restore step actually ran and missed: when fixtures were already
# cached, the restore was skipped and `cache-hit` is empty, so save
# is skipped too.
- name: Save production keys cache
if: always() && steps.cache-prod-keys.outputs.cache-hit == 'false'
uses: actions/cache/save@v5
with:
path: leanSpec/packages/testing/src/consensus_testing/test_keys/prod_scheme
key: ${{ steps.cache-prod-keys.outputs.cache-primary-key }}

# `-n 1` (not `-n auto`) runs a single leanVM prover at a time. The
# devnet5 prover peaks at ~12 GiB per proof (measured), so more workers
# blow past the 4-vCPU/16 GiB runner's RAM: `-n auto` (4 provers) hard
# OOM-killed the runner, `-n 2` thrashed and lost its heartbeat so GitHub
# cancelled the job. One prover peaks ~12.4 GiB with 0 swap and completes
# in ~2h36m. The Makefile keeps `-n auto` for local machines with more RAM.
- name: Generate test fixtures
id: generate-fixtures
if: steps.cache-fixtures.outputs.cache-hit != 'true'
working-directory: leanSpec
run: uv run fill --fork Lstar -n 1 --scheme prod -o fixtures

# Save fixtures only when generation actually SUCCEEDED. A bare
# `always()` here previously saved the (empty) fixtures dir when
# generation was cancelled or OOM-killed mid-run, poisoning the cache:
# later runs hit the empty cache, skipped generation, and the Rust tests
# failed with no fixtures. Gating on the generate step's outcome keeps
# the "save even if the later Rust test step fails" intent without ever
# persisting a partial fixture set.
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT
fixtures_url="${{ steps.fixtures-release.outputs.url }}"
sha_url="${{ steps.fixtures-release.outputs.sha_url }}"
echo "Downloading fixtures from $fixtures_url"
curl -L -f -o "$tmpdir/fixtures-prod-scheme.tar.gz" "$fixtures_url"
curl -L -f -o "$tmpdir/fixtures-prod-scheme.tar.gz.sha256" "$sha_url"
expected=$(cut -d' ' -f1 "$tmpdir/fixtures-prod-scheme.tar.gz.sha256")
actual=$(sha256sum "$tmpdir/fixtures-prod-scheme.tar.gz" | awk '{print $1}')
if [ "$expected" != "$actual" ]; then
echo "SHA256 mismatch: expected $expected, got $actual"
exit 1
fi
rm -rf leanSpec/fixtures
mkdir -p leanSpec/fixtures
tar -xzf "$tmpdir/fixtures-prod-scheme.tar.gz" -C leanSpec/fixtures --strip-components=1

# Save fixtures only when the download actually SUCCEEDED, so a
# cancelled or failed download never persists a partial fixture set,
# while still saving even if the later Rust test step fails.
- name: Save test fixtures cache
if: >-
always()
&& steps.cache-fixtures.outputs.cache-hit != 'true'
&& steps.generate-fixtures.outcome == 'success'
&& steps.download-fixtures.outcome == 'success'
uses: actions/cache/save@v5
with:
path: leanSpec/fixtures
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Not to be confused with Ethereum consensus clients AKA Beacon Chain clients AKA

**Main branch:** `main`
**Rust version:** 1.92.0 (edition 2024)
**Test fixtures commit:** Check `LEAN_SPEC_COMMIT_HASH` in Makefile
**Test fixtures release:** Download latest production fixtures from leanSpec releases

## Codebase Structure (10 crates)

Expand Down Expand Up @@ -81,7 +81,7 @@ make test # All tests + forkchoice spec tests
### Common Operations
```bash
.claude/skills/test-pr-devnet/scripts/test-branch.sh # Test branch in multi-client devnet
rm -rf leanSpec && make leanSpec/fixtures # Regenerate test fixtures (requires uv)
rm -rf leanSpec && make leanSpec/fixtures # Download latest released test fixtures
make docker-build # Build Docker image (DOCKER_TAG=local)
make run-devnet # Run local devnet with lean-quickstart
```
Expand Down
26 changes: 17 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,23 @@ shadow-docker-build: ## 👻🐳 Build a Shadow-compatible Docker image
-t ghcr.io/lambdaclass/ethlambda:$(DOCKER_TAG)-shadow .
@echo

# 2026-06-03
LEAN_SPEC_COMMIT_HASH:=30ffb6cab54ca6d2e2e1c82e8e2713ebb9a8fa3f

leanSpec:
git clone https://github.com/leanEthereum/leanSpec.git --single-branch
cd leanSpec && git checkout $(LEAN_SPEC_COMMIT_HASH)

leanSpec/fixtures: leanSpec
cd leanSpec && uv run fill --fork Lstar -n auto --scheme prod -o fixtures
LEAN_SPEC_FIXTURES_URL ?= https://github.com/leanEthereum/leanSpec/releases/latest/download/fixtures-prod-scheme.tar.gz
LEAN_SPEC_FIXTURES_SHA_URL ?= $(LEAN_SPEC_FIXTURES_URL).sha256

leanSpec/fixtures:
tmpdir=$$(mktemp -d); \
trap 'rm -rf "$$tmpdir"' EXIT; \
curl -L -f -o "$$tmpdir/fixtures-prod-scheme.tar.gz" "$(LEAN_SPEC_FIXTURES_URL)"; \
curl -L -f -o "$$tmpdir/fixtures-prod-scheme.tar.gz.sha256" "$(LEAN_SPEC_FIXTURES_SHA_URL)"; \
expected=$$(cut -d' ' -f1 "$$tmpdir/fixtures-prod-scheme.tar.gz.sha256"); \
actual=$$(sha256sum "$$tmpdir/fixtures-prod-scheme.tar.gz" | awk '{print $$1}'); \
if [ "$$expected" != "$$actual" ]; then \
echo "SHA256 mismatch: expected $$expected, got $$actual" >&2; \
exit 1; \
fi; \
rm -rf leanSpec/fixtures; \
mkdir -p leanSpec/fixtures; \
tar -xzf "$$tmpdir/fixtures-prod-scheme.tar.gz" -C leanSpec/fixtures --strip-components=1

lean-quickstart:
git clone https://github.com/blockblaz/lean-quickstart.git --depth 1 --single-branch
Expand Down