Skip to content

ci: run Lean Hive simulators in CI #2263

ci: run Lean Hive simulators in CI

ci: run Lean Hive simulators in CI #2263

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: ["**"]
workflow_dispatch:
# Cancel in-progress runs when a new commit is pushed to the same PR or branch
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
env:
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
CARGO_NET_RETRY: "10"
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Setup Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: "1.97.1"
components: rustfmt, clippy
- name: Setup cache
# Tools under tooling/ are separate Cargo workspaces with their own
# target dir and Cargo.lock, so they need listing explicitly or their
# builds are neither cached nor reflected in the cache key.
uses: Swatinem/rust-cache@v2
with:
workspaces: |
.
tooling/event-monitor
- name: Check formatting
run: cargo fmt --all -- --check
- name: Cargo check
run: cargo check --workspace --all-targets
- name: Clippy
run: cargo clippy --workspace --all-targets -- -D warnings
# tooling/event-monitor declares its own [workspace] table, so every step
# above stops at the root workspace members and never reaches it. Its
# tests run in this job rather than in `test` because clippy has already
# compiled the test targets, and because they need none of that job's
# leanSpec fixtures.
# `--locked` so the committed Cargo.lock is actually enforced: without it
# cargo silently resolves and rewrites the lockfile in CI, and a stale or
# missing entry never fails the build.
- name: Lint tooling
working-directory: tooling/event-monitor
run: |
cargo fmt --all -- --check
cargo clippy --locked --all-targets -- -D warnings
- name: Test tooling
working-directory: tooling/event-monitor
run: cargo test --locked
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- 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.fixtures-release.outputs.sha }}
- name: Download leanSpec fixtures release
id: download-fixtures
if: steps.cache-fixtures.outputs.cache-hit != 'true'
run: |
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.download-fixtures.outcome == 'success'
uses: actions/cache/save@v5
with:
path: leanSpec/fixtures
key: ${{ steps.cache-fixtures.outputs.cache-primary-key }}
# Ensure make sees fixtures as up-to-date (its timestamp must be
# newer than leanSpec/, which intermediate steps may have modified).
- name: Mark fixtures as up-to-date
run: touch leanSpec/fixtures
- name: Setup Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: "1.92.0"
- name: Setup cache
uses: Swatinem/rust-cache@v2
- name: Run tests
run: make test
docker_build:
name: Build Docker
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build ethlambda Docker image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
build-args: |
GIT_COMMIT=${{ github.sha }}
GIT_BRANCH=${{ github.ref_name }}
push: false
tags: |
ghcr.io/lambdaclass/ethlambda:devnet4
ghcr.io/lambdaclass/ethlambda:devnet5
outputs: type=docker,dest=/tmp/ethlambda_image.tar
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Upload image artifacts
uses: actions/upload-artifact@v7
with:
name: ethlambda_image
path: /tmp/ethlambda_image.tar
run-hive:
name: Hive - ${{ matrix.name }}
runs-on: ubuntu-latest
permissions:
contents: read
needs: docker_build
strategy:
fail-fast: false
matrix:
include:
- name: "Lean RPC Compat"
limit: "rpc-compat"
artifact_prefix: lean_rpc_compat
- name: "Lean Sync"
limit: "sync"
artifact_prefix: lean_sync
- name: "Lean Client Interop"
limit: "client-interop"
artifact_prefix: lean_client_interop
- name: "Lean Validation"
limit: "validation"
artifact_prefix: lean_validation
- name: "Lean Gossip"
limit: "gossip"
artifact_prefix: lean_gossip
- name: "Lean ReqResp"
limit: "reqresp"
artifact_prefix: lean_reqresp
- name: "Lean Fork Choice Spec Tests"
limit: "lean-spec-tests-fork-choice"
artifact_prefix: lean_spec_fork_choice
- name: "Lean State Transition Spec Tests"
limit: "lean-spec-tests-state-transition"
artifact_prefix: lean_spec_state_transition
- name: "Lean Verify Signatures Spec Tests"
limit: "lean-spec-tests-verify-signatures"
artifact_prefix: lean_spec_verify_signatures
steps:
- uses: actions/checkout@v6
- name: Download ethlambda image artifact
uses: actions/download-artifact@v6
with:
name: ethlambda_image
path: /tmp
- name: Load image
run: docker load --input /tmp/ethlambda_image.tar
- name: Load hive client config
id: client-config
shell: bash
run: |
{
echo "config<<EOF"
cat .github/config/hive/clients.yaml
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Determine hive flags
id: hive-flags
shell: bash
env:
SIM_LIMIT: ${{ matrix.limit }}
run: |
FLAGS="--sim.parallelism 4 --sim.loglevel 3"
if [[ -n "$SIM_LIMIT" ]]; then
escaped_limit=${SIM_LIMIT//\'/\'\\\'\'}
FLAGS+=" --sim.limit '$escaped_limit'"
fi
echo "flags=$FLAGS" >> "$GITHUB_OUTPUT"
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version: "1.24"
cache: false
- name: Checkout hive
uses: actions/checkout@v6
with:
repository: ethereum/hive
ref: dde4f59d04ff0ff8b6585670b08cea1b6c8ab65c
path: src
- name: Patch Hive Lean Dockerfile
run: python3 .github/scripts/patch-hive-lean-dockerfile.py src/simulators/lean/Dockerfile
- name: Build Hive
working-directory: src
run: go build -o hive .
- name: Create Hive results directory
working-directory: src
run: mkdir -p results
- name: Write Hive client config
working-directory: src
run: |
cat > client-config.yaml <<'EOF'
${{ steps.client-config.outputs.config }}
EOF
- name: Run Hive Simulation
working-directory: src
run: |
set -x
(./hive \
--sim lean \
--client ethlambda_devnet5 \
--results-root results \
--client-file=client-config.yaml \
${{ steps.hive-flags.outputs.flags }} \
2>&1 || true) | tee hive.log
if tail -n 10 hive.log | grep -q "simulation .* finished"; then
exit 0
fi
exit 1
- name: Check Hive Results For Failures
id: verify-hive-results
if: ${{ success() }}
shell: bash
run: bash ./.github/scripts/check-hive-results.sh src/results
- name: Upload Hive Failure Logs
if: ${{ failure() && steps.verify-hive-results.conclusion == 'failure' }}
uses: actions/upload-artifact@v7
with:
name: hive_failed_logs_${{ matrix.artifact_prefix }}
path: src/results/failed_logs
if-no-files-found: warn
- name: Upload Hive Run Artifacts
if: ${{ failure() }}
uses: actions/upload-artifact@v7
with:
name: hive_run_artifacts_${{ matrix.artifact_prefix }}
path: |
src/hive.log
src/results
if-no-files-found: warn