You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Tracking issue for the ssv-spec side of ePBS (EIP-7732 / Gloas, Glamsterdam fork), per SIP-94 (ssvlabs/SIPs#94). Node-side counterpart: ssvlabs/ssv#2901, with the direct-builder extension in ssvlabs/ssv#2962.
PR structure:#632 (wire constants, targets main) → #633 (reference implementation, stacked on #632; accumulates the slices below and auto-retargets to main when #632 merges) → #643 (SSZ backend migration to pk910/dynamic-ssz for the progressive Gloas + consensus-data types, stacked on #633).
⚠️ §6 amendment (SIP-94, 2026-09): envelope signing reworked from consensus to dissemination
After the branch tip (2026-08-27), SIP-94 rewrote §6 (2026-09-01→09-03, ssvlabs/SIPs#94 commits "Rewrite envelope signing without consensus" … "Fold the envelope-source rationale into the SHOULD"): once §4 decides, bid.block_hash pins exactly one valid envelope, so there is nothing to negotiate. Envelope signing is now one dissemination round + one threshold-signing round, no QBFT. This reopens the §6 slice below and supersedes the EnvelopeConsensusData / EnvelopeValueCheckF / consensus+postconsensus-suite parts of the landed work. §1–§5 are unaffected. Detailed plan in the reopened §6 slice below.
Landed
Wire constants (types: add ePBS (Gloas) domains, roles, and partial-sig types #632 — green, awaiting review): Gloas domains 0x0B/0x0C/0x0D + builder-specs' DomainBuilderRequestAuth0x0B000001, beacon/runner roles 7/8/9, partial-sig types 7/8/9, and a golden test pinning the exact values. (§6 rework appends two more wire constants here — see below.)
Type layer (on types: ePBS (Gloas / SIP-94) reference implementation — WIP #633): types/gloas package (§3/§4/§5/§6 containers, DataVersionGloas placeholder), flat GloasBeaconVote / EnvelopeConsensusData, fork machinery, six encoding-vector suites in new anchor-safe package prefixes. (§6 rework removes EnvelopeConsensusData + SignedBlindedExecutionPayloadEnvelope and replaces the envelopeconsensusdata suite — see below.)
§2 attestations (on types: ePBS (Gloas / SIP-94) reference implementation — WIP #633, activated): fork-aware committee-runner path (Gloas vote carries the BN-supplied 0/1 payload-status index), aggregator-committee Gloas arms (Electra shape under the Gloas version tag), GloasBeaconVoteValueCheckF + six value-check vectors; SupportedAttestationVersions / SupportedAggregatorVersions include Gloas.
Verification state: full generate + test green on #633; fixture delta vs base: 567 Gloas subtests added, none removed, 3 pre-Gloas changed (details in #633's description). (Superseded once the §6 rework lands and vectors regenerate.)
§3 PTC runner (depends only on the type layer; reorderable) — VR-shaped, no QBFT: partial signatures over PayloadAttestationData under DomainPTCAttester; joins the preconsensus multi-tests; PTC-specific vectors (convergence quorum, minority split no-reconstruct, abstain, wrong root, dutyexe role checks).
§5 ProposerPreferences runner + ValidatorRegistration deprecation (reorderable) — per-slot-state runner, gas-limit config plumbing, VR rejected at Gloas slots (unchanged pre-Gloas); vectors include concurrent lookahead slots and re-emission-replaces-slot.
Removed: EnvelopeProposerRunner's QBFT decide()/post-consensus path, EnvelopeConsensusData, EnvelopeValueCheckF, SignedBlindedExecutionPayloadEnvelope, the valcheckenvelope suite, and the envelope entries in the consensus/postconsensus suites.
Type + runner (types: ePBS (Gloas / SIP-94) reference implementation — WIP #633): EnvelopeDissemination{Slot, Envelope} carrier; non-QBFT runner — the builder operator disseminates the blinded envelope, every operator content-selects the first that binds to its §4 decision (4 checks: block root, parent root, self-build index, hash_tree_root(ExecutionRequests) vs the bid), threshold-signs under DomainBeaconBuilder via the pre-consensus container, and the builder operator publishes.
Design decisions resolved: (1) linkage store expanded to ProposedBlocks{BlockRoot, ParentRoot, ExecutionRequestsRoot}; (2) ProcessEnvelopeDissemination added to the Runner interface (other runners return an unsupported-message error, mirroring the pre/post-consensus stubs); (3) the mock BeaconNode publishes the blinded envelope + reconstructed signature (the full SignedExecutionPayloadEnvelope/Contents stay node-side). Note: gloas.TestingBeaconBlock now commits a real execution-requests root so §6 binding holds — this changes Gloas §4/§6 fixtures only, pre-Gloas untouched.
Type (types: ePBS (Gloas / SIP-94) reference implementation — WIP #633): types/gloas.BuilderRequestAuth{Data, Slot} / SignedBuilderRequestAuth. Data is the token agreed with the builder out of band (defaulting to the advertised URL bytes; zero-length invalid), signed byte-for-byte under the chain-independent DomainBuilderRequestAuth (genesis compute_domain, like the deprecated ValidatorRegistrationV1); Slot is the proposal slot and carries no dependent root, so re-emissions reproduce a byte-identical signing root.
Runner (types: ePBS (Gloas / SIP-94) reference implementation — WIP #633): BuilderEntry config (per-validator, deduped by data, capped at MaxBuilderEntries = 8); the proposer-preferences slot runner freezes one auth per distinct entry, signs them in one multi-root RequestAuthPartialSig container, and reconstructs + submits each on per-root quorum via SubmitBuilderRequestAuth. The round rides the duty but collects independently of the preference round — neither gates the other.
dynssz (types: migrate Gloas + consensus-data SSZ to pk910/dynamic-ssz #643): BuilderRequestAuth / SignedBuilderRequestAuth migrated to pk910/dynamic-ssz (gloas generate.yaml), replacing the fastssz encoders; the runner's expected-roots helper returns the narrow types.HashRoot.
(Optional, post-epic) cleanups: go list -m-based includes for the pre-existing sszgen directives, doc touch-ups.
Wiring checklist for each new runner (§3/§5/§6): DutyRunners construction, testingutils ConstructBaseRunner* case (single-share), message-ID helpers + DutyRunnerForMsgID, per-runner ssv_msgs_*.go, overrideStateComparison + run_test.go mapping. §6 no longer wires a QBFT config/controller (the runner is non-QBFT after the rework); instead it wires the new dissemination message class through the validator and the spectest framework.
Externally gated / watch list
Progressive HTR (EIP-7688) — positional roots are shipped deliberately (devnet-6 reality; 7688 is only CFI for Glamsterdam). The §4-block/§6-envelope signing-root goldens and the progressive-container encoding suites stay held until the devnet-7 fork scope settles; prefer go-eth2-client's progressive-SSZ work over hand-rolling the block-tree cascade.
go-eth2-client Gloas support — when it ships, swap the DataVersionGloas placeholder for the upstream enum (values match by construction; the (unknown) fixture-name artifact resolves).
Consensus-spec pin bump (follow-up) — SIP-94 now pins ethereum/consensus-specs@a5a1bc630 (2026-08-26); the types/gloas containers were vendored against an earlier snapshot. Re-verify field-for-field on the next re-vendor.
Node-side lifecycle (ePBS (EIP-7732 / Gloas) — SIP-94 ssv#2901) — the spec model keeps ProposerPreferencesRunner.BySlot and the shared §4→§6 linkage store unbounded (pruning is declared node-side in the code comments); the node implementation owns pruning both and the preferences runner's linear HasRunningDuty() scan.
(Optional) index-aware mock slashability store, so the §2 cross-index equivocation rule becomes vector-assertable.
Tracking issue for the ssv-spec side of ePBS (EIP-7732 / Gloas, Glamsterdam fork), per SIP-94 (ssvlabs/SIPs#94). Node-side counterpart: ssvlabs/ssv#2901, with the direct-builder extension in ssvlabs/ssv#2962.
PR structure: #632 (wire constants, targets
main) → #633 (reference implementation, stacked on #632; accumulates the slices below and auto-retargets tomainwhen #632 merges) → #643 (SSZ backend migration topk910/dynamic-sszfor the progressive Gloas + consensus-data types, stacked on #633).Landed
0x0B/0x0C/0x0D+ builder-specs'DomainBuilderRequestAuth0x0B000001, beacon/runner roles 7/8/9, partial-sig types 7/8/9, and a golden test pinning the exact values. (§6 rework appends two more wire constants here — see below.)types/gloaspackage (§3/§4/§5/§6 containers,DataVersionGloasplaceholder), flatGloasBeaconVote/EnvelopeConsensusData, fork machinery, six encoding-vector suites in new anchor-safe package prefixes. (§6 rework removesEnvelopeConsensusData+SignedBlindedExecutionPayloadEnvelopeand replaces theenvelopeconsensusdatasuite — see below.)GloasBeaconVoteValueCheckF+ six value-check vectors;SupportedAttestationVersions/SupportedAggregatorVersionsinclude Gloas.ProposerValueCheckFdecodesgloas.BeaconBlockand pins block slot == duty slot;typesstays free of concrete Gloas decoding.Verification state: full generate + test green on #633; fixture delta vs base: 567 Gloas subtests added, none removed, 3 pre-Gloas changed (details in #633's description). (Superseded once the §6 rework lands and vectors regenerate.)
Process
main)main)AggregatorCommitteeConsensusDataGloas arm (currently on types: ePBS (Gloas / SIP-94) reference implementation — WIP #633). Options: (a) merge types: ePBS (Gloas / SIP-94) reference implementation — WIP #633 at the current §2-complete checkpoint and continue the runners in a follow-up PR; (b) split the §2 arm into a micro-PR; (c) the tag waits for the full epic. This decides whether ssv#2901 can land onstagebefore the remaining slices exist.gloasbeaconvote,payloadattestationdata,payloadattestationmessage,proposerpreferences,signedproposerpreferences, and the §6 dissemination suite that replacesenvelopeconsensusdata) — Gloas fixtures live only in new packages so anchor's prefix dispatcher never runs pre-Gloas decoders on them; ping again after the §4 slice and after the §6 rework.Remaining implementation slices (landing order)
SupportedBlockVersions += Gloas,TestingGloasBeaconBlockfixtures;valcheckproposerGloas cases + Gloas full-happy-flow (everySupportedBlockVersionsiterator picks up a Gloas case). Closes anchor's spec_tests: fork-gate proposer block decode for Gloas sigp/anchor#1073 acceptance criterion.PayloadAttestationDataunderDomainPTCAttester; joins the preconsensus multi-tests; PTC-specific vectors (convergence quorum, minority split no-reconstruct, abstain, wrong root, dutyexe role checks).EnvelopeProposerRunner's QBFTdecide()/post-consensus path,EnvelopeConsensusData,EnvelopeValueCheckF,SignedBlindedExecutionPayloadEnvelope, thevalcheckenvelopesuite, and the envelope entries in the consensus/postconsensus suites.SSVEnvelopeDisseminationMsgType MsgType = 3andEnvelopePartialSig PartialSigMsgType = 10; accepted inSSVMessage.Validate(); golden test extended.EnvelopeDissemination{Slot, Envelope}carrier; non-QBFT runner — the builder operator disseminates the blinded envelope, every operator content-selects the first that binds to its §4 decision (4 checks: block root, parent root, self-build index,hash_tree_root(ExecutionRequests)vs the bid), threshold-signs underDomainBeaconBuildervia the pre-consensus container, and the builder operator publishes.SSVMessage.Validate(), theRunnerinterface (ProcessEnvelopeDissemination),Validator.ProcessMessage, and the reference p2p validator.envelopedisseminationencoding suite (replacingenvelopeconsensusdata); the envelope runner moved from the consensus/postconsensus suites to the preconsensus and non-QBFT newduty suites alongside PTC (full parity);envelopeproposerduty tests rewritten (builder happy-flow, non-builder no-publish, no-decision silent-wait, real §4→§6 root-linkage);valcheckenvelopedropped.EnvelopeDisseminationand the blinded envelope migrated to dynssz (generate_cd.yaml/ gloasgenerate.yaml), replacing the fastssz encoders.ProposedBlocks{BlockRoot, ParentRoot, ExecutionRequestsRoot}; (2)ProcessEnvelopeDisseminationadded to theRunnerinterface (other runners return an unsupported-message error, mirroring the pre/post-consensus stubs); (3) the mockBeaconNodepublishes the blinded envelope + reconstructed signature (the fullSignedExecutionPayloadEnvelope/Contentsstay node-side). Note:gloas.TestingBeaconBlocknow commits a real execution-requests root so §6 binding holds — this changes Gloas §4/§6 fixtures only, pre-Gloas untouched.types/gloas.BuilderRequestAuth{Data, Slot}/SignedBuilderRequestAuth.Datais the token agreed with the builder out of band (defaulting to the advertised URL bytes; zero-length invalid), signed byte-for-byte under the chain-independentDomainBuilderRequestAuth(genesis compute_domain, like the deprecatedValidatorRegistrationV1);Slotis the proposal slot and carries no dependent root, so re-emissions reproduce a byte-identical signing root.BuilderEntryconfig (per-validator, deduped by data, capped atMaxBuilderEntries = 8); the proposer-preferences slot runner freezes one auth per distinct entry, signs them in one multi-rootRequestAuthPartialSigcontainer, and reconstructs + submits each on per-root quorum viaSubmitBuilderRequestAuth. The round rides the duty but collects independently of the preference round — neither gates the other.BuilderRequestAuth/SignedBuilderRequestAuthmigrated topk910/dynamic-ssz(gloasgenerate.yaml), replacing the fastssz encoders; the runner's expected-roots helper returns the narrowtypes.HashRoot.go list -m-based includes for the pre-existing sszgen directives, doc touch-ups.Wiring checklist for each new runner (§3/§5/§6):
DutyRunnersconstruction, testingutilsConstructBaseRunner*case (single-share), message-ID helpers +DutyRunnerForMsgID, per-runnerssv_msgs_*.go,overrideStateComparison+run_test.gomapping. §6 no longer wires a QBFT config/controller (the runner is non-QBFT after the rework); instead it wires the new dissemination message class through the validator and the spectest framework.Externally gated / watch list
DataVersionGloasplaceholder for the upstream enum (values match by construction; the(unknown)fixture-name artifact resolves).ethereum/consensus-specs@a5a1bc630(2026-08-26); thetypes/gloascontainers were vendored against an earlier snapshot. Re-verify field-for-field on the next re-vendor.ProposerPreferencesRunner.BySlotand the shared §4→§6 linkage store unbounded (pruning is declared node-side in the code comments); the node implementation owns pruning both and the preferences runner's linearHasRunningDuty()scan.