Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
aa95254
feat(fork-choice): expose getViableHeads() for compliance suite
GrapeBaBa May 1, 2026
5df6c34
feat(beacon-node): integrate fork_choice_compliance spec runner
GrapeBaBa May 1, 2026
014ef47
fix(fork-choice): restrict getViableHeads to justified subtree, dedup…
GrapeBaBa Jul 1, 2026
4d1bcfa
test(spec): align fork-choice runner with spec test-format semantics
GrapeBaBa Jul 1, 2026
c0a24d8
refactor(fork-choice): collapse justified-slot lookup in getViableHeads
GrapeBaBa Jul 6, 2026
118ef89
fix: correct compliance runner semantics for alpha.12 vectors
ensi321 Jul 16, 2026
a4bc419
feat: add opt-in fork-choice compliance vector download + test:comptest
ensi321 Jul 16, 2026
8d24860
test: fail test:comptest loudly when compliance fixtures are absent
ensi321 Jul 16, 2026
49f6716
test: unit-test comptests downloader merge safety
ensi321 Jul 16, 2026
22f4c1c
test: unit-test getViableHeads leaf selection and gloas variant identity
ensi321 Jul 16, 2026
d374c36
refactor: pin comptests download to the standard spec-tests version
ensi321 Jul 16, 2026
b9e8e58
refactor: dedicated vitest project for compliance suite, drop env-var…
ensi321 Jul 16, 2026
3682329
test: skip compliance case hitting stale-head proposer boost bug
ensi321 Jul 16, 2026
31bd1db
fix: payload-attestation slot mismatch is a no-op success per spec
ensi321 Jul 16, 2026
05908df
refactor: plain-text comptests version marker
ensi321 Jul 16, 2026
d27b1d2
refactor: comptests fixtures in a standalone sibling directory
ensi321 Jul 16, 2026
04b9ac3
ci: nightly fork-choice compliance test workflow
ensi321 Jul 16, 2026
3f73bb3
fix: satisfy ProtoBlock discriminated union in getViableHeads test
ensi321 Jul 16, 2026
bcae53b
refactor: comptests config block in spec-tests-version.json
ensi321 Jul 16, 2026
48f1f5f
chore: polish comments in compliance suite code
ensi321 Jul 16, 2026
dd38054
fix: use clock slot for pre-tick attestation precondition
ensi321 Jul 17, 2026
7d9d1eb
Merge branch 'unstable' into nc/fork-choice-compliance
GrapeBaBa Jul 21, 2026
3f03982
refactor: address review - reuse isDescendant in getViableHeads, TODO…
ensi321 Jul 23, 2026
a82cbed
Merge remote-tracking branch 'origin/unstable' into nc/fork-choice-co…
ensi321 Jul 23, 2026
5ad9361
chore: link issue #9694 in proposer boost normalization TODO
ensi321 Jul 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/comptests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Fork-choice compliance tests

# Don't cancel an in-flight run when the next cron fires
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false

permissions:
contents: read

on:
schedule:
- cron: "0 3 * * *" # Runs at 3am UTC everyday
workflow_dispatch:

jobs:
comptests:
name: Fork-choice compliance tests
# Don't run scheduled copies of this workflow on forks.
if: ${{ github.event_name != 'schedule' || github.repository == 'ChainSafe/lodestar' }}
runs-on: warp-ubuntu-2204-x64-4x
# Single vitest worker (one test file): ~10 min per fork locally, 6 pre-gloas forks.
timeout-minutes: 150
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: "./.github/actions/setup-and-build"
with:
node: 24

- name: Restore compliance vectors cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: packages/beacon-node/spec-tests-comptests
key: comptest-data-${{ hashFiles('spec-tests-version.json') }}
- name: Download compliance vectors
run: pnpm download-comptests

- name: Fork-choice compliance tests
run: pnpm test:comptest
working-directory: packages/beacon-node
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ pnpm download-spec-tests 2026-04-14 # latest successf
pnpm download-spec-tests latest <owner>/consensus-specs # fork
pnpm download-spec-tests latest <owner>/consensus-specs <ref> # fork + branch

# Fork-choice compliance tests (model-generated vectors, standalone flow;
# runs nightly in CI, not per-PR). Same version pin as the spec tests.
pnpm download-comptests
pnpm test:comptest
SPEC_FILTER_FORK=deneb pnpm test:comptest # single fork (from packages/beacon-node)

# Run e2e tests (requires docker environment)
./scripts/run_e2e_env.sh start
pnpm test:e2e
Expand Down
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ pnpm vitest run --project unit test/unit/path/to/test.test.ts
pnpm download-spec-tests
pnpm test:spec

# Fork-choice compliance tests (standalone, nightly CI; download first)
pnpm download-comptests
pnpm test:comptest

# Docs lint (markdown)
pnpm docs:lint
pnpm docs:lint:fix
Expand Down
23 changes: 23 additions & 0 deletions configs/vitest.config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const specProjectMinimal = defineProject({
test: {
name: "spec-minimal",
include: ["**/test/spec/**/*.test.ts"],
exclude: ["**/test/spec/comptest/**"],
setupFiles: [
path.join(__dirname, "../scripts/vitest/setupFiles/customMatchers.ts"),
path.join(__dirname, "../scripts/vitest/setupFiles/dotenv.ts"),
Expand All @@ -26,6 +27,7 @@ export const specProjectMainnet = defineProject({
test: {
name: "spec-mainnet",
include: ["**/test/spec/**/*.test.ts"],
exclude: ["**/test/spec/comptest/**"],
setupFiles: [
path.join(__dirname, "../scripts/vitest/setupFiles/customMatchers.ts"),
path.join(__dirname, "../scripts/vitest/setupFiles/dotenv.ts"),
Expand All @@ -42,3 +44,24 @@ export const specProjectMainnet = defineProject({
},
},
});

// Fork-choice compliance suite (`pnpm test:comptest`) — deliberately its own project so the
// regular spec projects never pick it up.
// Fixtures via `pnpm download-comptests`.
export const specProjectComptest = defineProject({
test: {
name: "comptest",
include: ["**/test/spec/comptest/**/*.test.ts"],
setupFiles: [
path.join(__dirname, "../scripts/vitest/setupFiles/customMatchers.ts"),
path.join(__dirname, "../scripts/vitest/setupFiles/dotenv.ts"),
path.join(__dirname, "../scripts/vitest/setupFiles/lodestarPreset.ts"),
],
testTimeout: 1000 * 60 * 15,
hookTimeout: 1000 * 60 * 15,
pool: "forks",
env: {
LODESTAR_PRESET: "minimal",
},
},
});
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
"test:browsers": "vitest run --project browser",
"test:e2e": "vitest run --project e2e --project e2e-mainnet",
"download-spec-tests": "pnpm -r download-spec-tests",
"download-comptests": "pnpm -r download-comptests",
"test:comptest": "vitest run --project comptest",
"test:spec": "vitest run --project spec-minimal --project spec-mainnet",
"benchmark": "pnpm benchmark:files 'packages/*/test/perf/**/*.test.ts'",
"benchmark:files": "NODE_OPTIONS='--max-old-space-size=8192 --loader=ts-node/esm' benchmark --config .benchrc.yaml --defaultBranch unstable",
Expand Down
2 changes: 2 additions & 0 deletions packages/beacon-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@
"test:sim": "vitest run test/sim/**/*.test.ts",
"test:sim:blobs": "vitest run test/sim/4844-interop.test.ts",
"download-spec-tests": "node --loader=ts-node/esm test/spec/downloadTests.ts",
"download-comptests": "node --loader=ts-node/esm test/spec/downloadComptests.ts",
"test:comptest": "vitest run --project comptest",
"test:spec:bls": "vitest run --project spec-minimal test/spec/bls/",
"test:spec:general": "vitest run --project spec-minimal test/spec/general/",
"test:spec:minimal": "vitest run --project spec-minimal test/spec/presets/",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import fs from "node:fs";
import path from "node:path";
import {ACTIVE_PRESET} from "@lodestar/params";
import {comptestsSpecTests} from "../specTestVersioning.js";
import {forkChoiceTestRunner} from "../utils/forkChoiceTestRunner.js";
import {specTestIterator} from "../utils/specTestIterator.js";
import {RunnerType} from "../utils/types.js";

// Fork-choice compliance suite (`pnpm test:comptest`) — a standalone flow parallel to the
// standard spec tests: its own vitest project, its own fixture directory
// (`pnpm download-comptests`), sharing only the fork-choice test runner.

const presetDir = path.join(comptestsSpecTests.outputDir, "tests", ACTIVE_PRESET);
const hasComplianceFixtures =
fs.existsSync(presetDir) &&
fs.readdirSync(presetDir).some((fork) => fs.existsSync(path.join(presetDir, fork, "fork_choice_compliance")));
if (!hasComplianceFixtures) {
throw Error("No fork_choice_compliance fixtures found — run `pnpm download-comptests` first");
}

specTestIterator(presetDir, {
fork_choice_compliance: {type: RunnerType.default, fn: forkChoiceTestRunner({onlyPredefinedResponses: false})},
});
7 changes: 7 additions & 0 deletions packages/beacon-node/test/spec/downloadComptests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {downloadTests} from "@lodestar/spec-test-util/downloadTests";
import {comptestsSpecTests} from "./specTestVersioning.js";

await downloadTests(comptestsSpecTests, console.log).catch((e: Error) => {
console.error(e);
process.exit(1);
});
Loading
Loading