Skip to content

fix(ssv_types): accept pre-Gloas aggregator versions that share a decode shape - #1326

Merged
shane-moore merged 1 commit into
sigp:epbsfrom
shane-moore:fix/aggregator-version-shape
Sep 28, 2026
Merged

shane-moore merged 1 commit into
sigp:epbsfrom
shane-moore:fix/aggregator-version-shape

Conversation

@shane-moore

Copy link
Copy Markdown
Member

Problem, Evidence, and Context

In a mixed cluster (2 Anchor + 2 go-ssv, Fulu, SSV Boole fork not active), Anchor rejected every per-validator aggregator value led by go-ssv with wrong data version: expected fork Electra, got Fulu. The two clients label the same Electra-shaped aggregate differently: Anchor labels it Electra (the shape's representative fork) and go-ssv labels it Fulu (the beacon node's version). Since #1215, the aggregator check has required the leader's label to equal our own. With both Anchors rejecting, the cluster could not reach quorum and waited for a round change. In a local ssv-mini run this hit slots 71, 75, 83, 95, 99, 102, 103, 126 and 127, every pre-Gloas aggregate with a go-ssv round-1 leader. Slot 71 was published at +10.0s instead of about +8s. No errors occurred after the Gloas fork.

This affects any network running epbs with Boole not active before Gloas, for example Sepolia mixed clusters until Sepolia's Gloas fork. unstable has no such check, so mainnet is unaffected.

Change Overview

The aggregator check now compares decode shapes. The leader's label must select the same shape as the fork scheduled at the duty slot, the same reference the proposer check already uses. Electra and Fulu both select the Electra shape, so both labels are accepted before Gloas. At Gloas slots only Gloas is accepted, so the SIP-94 §2 rule still holds: a leader cannot make the cluster decode a Gloas aggregate as Electra and sign the old signing root. A rejection now reports the scheduled fork (expected Fulu) instead of our own label.

Intentionally unchanged:

  • Anchor still labels its own aggregates Electra. go-ssv accepts that label, and Lighthouse parses an electra header on a Fulu slot as the same type. In the same run, go-ssv submitted all 8 Anchor-led pre-Gloas aggregates without a failure.
  • The Boole aggregator-committee check is unchanged. Both clients label it with the scheduled fork.

Risks, Trade-offs, and Mitigations

The check now accepts every label that shares the scheduled fork's shape. Only Electra and Fulu labels can differ this way on live networks, and the decoder already treats them as one shape. The only other behavior change: a Heze label now fails as an unsupported fork instead of as a version mismatch. It is still rejected.

Validation

  • New tests: on a Fulu slot, Fulu and Electra labels are both accepted, whichever label our own value carries. On a Gloas slot, Fulu and Electra labels are rejected even when our own value is labelled Electra, while the same bytes labelled Gloas are accepted. On a Fulu slot, a Deneb label is rejected.
  • With the old check restored, the two Fulu-slot acceptance tests and the Gloas-slot rejection test fail. The Deneb rejection test passes both before and after, because it covers behavior both versions share.
  • cargo test -p ssv_types --release: 140 passed, plus 2 doc-tests.
  • make cargo-fmt-check and make lint: passed.
  • No new devnet run.

Rollback

Revert the commit. No configuration or data migration is required.

…ode shape

Anchor labels a Fulu-slot aggregate Electra while go-ssv labels it Fulu,
so the raw label equality added in sigp#1215 rejected every go-ssv-led
pre-Boole aggregator round in mixed clusters. Bind the leader's label to
the decode shape of the fork scheduled at the duty slot instead, which
accepts both labels before Gloas and still requires Gloas at Gloas slots
(SIP-94 section 2).
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.31250% with 3 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (epbs@9f9b183). Learn more about missing BASE report.

Files with missing lines Patch % Lines
anchor/common/ssv_types/src/consensus.rs 95.31% 3 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             epbs    #1326   +/-   ##
=======================================
  Coverage        ?   80.83%           
=======================================
  Files           ?      179           
  Lines           ?    41321           
  Branches        ?        0           
=======================================
  Hits            ?    33400           
  Misses          ?     7921           
  Partials        ?        0           
Flag Coverage Δ
rust 80.83% <95.31%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@shane-moore

Copy link
Copy Markdown
Member Author

Review from Claude Opus 5.5 (effort: high)

No findings.

  • The Gloas binding still holds. expected comes from fork_name_at_slot(duty.slot), and the slot is pinned by the earlier SlotMismatch check. Only ForkName::Gloas maps to the Gloas shape, so Electra and Fulu labels fail at Gloas slots whatever our own value is labelled.
  • Accepting more labels before Gloas doesn't reach signing. After the decision, sign_single_aggregate_and_proof uses data.version only to pick the decode shape, and the signing domain comes from the target epoch.
  • cargo test -p ssv_types --release --locked at 2fb7ff7: 140 passed plus 2 doc-tests. With the old value.version != our_value.version check restored, the two Fulu-slot acceptance tests and the Gloas-slot rejection test fail and the Deneb test passes, as the description says.

Not independently verified: the ssv-mini mixed-cluster results.

@shane-moore
shane-moore marked this pull request as ready for review September 28, 2026 19:39
@shane-moore
shane-moore merged commit 6224324 into sigp:epbs Sep 28, 2026
35 checks passed
@shane-moore
shane-moore deleted the fix/aggregator-version-shape branch September 28, 2026 19:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants