Skip to content

compliance fork-choice tests: gloas SSZ types diverge from consensus-specs master #9295

@parithosh

Description

@parithosh

Context

Discovered while wiring up the consensus-specs fork-choice compliance test runner in #9290. With the runner in place, 1280 / 2944 compliance tests fail at SSZ load time before any logic runs. All affected cases are gloas suites except block_cover_test, which doesn't include the divergent fixture files.

Root cause

Lodestar's packages/beacon-node/test/spec-tests-version.json pins consensus-specs to v1.7.0-alpha.5 — the latest available release. The compliance test artifact, however, is generated by the consensus-specs Compliance Tests workflow which builds from master (e.g. commit ccc96eb). Master is ahead of any released alpha, so the gloas SSZ container shapes have drifted vs Lodestar's @lodestar/types definitions.

v1.7.0-alpha.5 was released 2026-04-19; everything between then and master HEAD is unreleased.

Concrete divergences

Bisected via a small probe script (below) that snappy-decompresses each .ssz_snappy file in a failing case and tries to deserialize it with the type the runner registers.

Test case used: spec-tests-compliance/small/tests/minimal/gloas/fork_choice_compliance/attester_slashing_test/pyspec_tests/attester_slashing_test_0_10950258_0

File Lodestar type Status Error
anchor_block.ssz_snappy ssz.gloas.BeaconBlock FAIL First offset must equal to fixedEnd 336 != 396 (+60 bytes)
block_<root>.ssz_snappy ssz.gloas.SignedBeaconBlock FAIL same 336 != 396
attestation_<root>.ssz_snappy ssz.gloas.Attestation FAIL 229 != 236 (+7 bytes)
execution_payload_envelope_<root>.ssz_snappy ssz.gloas.SignedExecutionPayloadEnvelope FAIL 80 != 48 (-32 bytes)
anchor_state.ssz_snappy ssz.gloas.BeaconState FAIL Offset is outside the bounds of the DataView (cascading from one of the above)
attester_slashing_<root>.ssz_snappy ssz.gloas.AttesterSlashing OK
payload_attestation_<root>.ssz_snappy none registered SKIP new gloas type not yet in @lodestar/types

The gaps are different across types, so this isn't a single field rename — multiple gloas containers have changed on master.

Probe script

Drop into packages/beacon-node/probe.mjs and run with node probe.mjs after extracting compliance test data. Useful for re-checking once a new spec release lands and spec-tests-version.json is bumped.

import fs from "node:fs";
import path from "node:path";
import {uncompress} from "snappyjs";
import {ssz} from "@lodestar/types";

const CASE = "spec-tests-compliance/small/tests/minimal/gloas/fork_choice_compliance/attester_slashing_test/pyspec_tests/attester_slashing_test_0_10950258_0";

const types = {
  anchor_state: ssz.gloas.BeaconState,
  anchor_block: ssz.gloas.BeaconBlock,
  "^(block)_([0-9a-zA-Z]+)$": ssz.gloas.SignedBeaconBlock,
  "^(execution_payload_envelope)_([0-9a-zA-Z]+)$": ssz.gloas.SignedExecutionPayloadEnvelope,
  "^(attestation)_([0-9a-zA-Z])+$": ssz.gloas.Attestation,
  "^(attester_slashing)_([0-9a-zA-Z])+$": ssz.gloas.AttesterSlashing,
};
const matches = (name, k) => k.startsWith("^") ? name.match(k) : name === k;

for (const f of fs.readdirSync(CASE).filter((f) => f.endsWith(".ssz_snappy")).sort()) {
  const inputName = f.replace(".ssz_snappy", "");
  const k = Object.keys(types).find((k) => matches(inputName, k));
  if (!k) { console.log(`SKIP  ${f}  (no registered type)`); continue; }
  const bytes = uncompress(fs.readFileSync(path.join(CASE, f)));
  try {
    types[k].deserialize(bytes);
    console.log(`OK    ${f}  type=${types[k].typeName}`);
  } catch (e) {
    console.log(`FAIL  ${f}  type=${types[k].typeName}  -- ${e.message}`);
  }
}

Suggested resolution

  1. Wait for the next consensus-specs alpha release (v1.7.0-alpha.6 or later) that captures these gloas changes.
  2. Bump packages/beacon-node/test/spec-tests-version.json to that release.
  3. Update @lodestar/types for any container shape changes the new alpha includes (BeaconBlock/BeaconBlockBody, Attestation, ExecutionPayloadEnvelope, plus the new PayloadAttestation type).
  4. Re-run ./scripts/compliance-fc-report.sh and confirm the 1280x SSZ-deserialize failure class is gone.

Attempting (3) ahead of a release would be premature — the spec is still moving on master and our types could go stale within days.

Impact

Until resolved, ~13% (gloas) of the compliance pass rate is gated on this. Fulu side is unaffected and currently passes ~21% — see #9290 for the full per-suite breakdown.

Metadata

Metadata

Assignees

No one assigned

    Labels

    spec-gloasIssues targeting the Glamsterdam spec version

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions