Repository navigation
Description
Type: design / tracking → implementation plan (upstream refs re-checked 2026-08-25)
Related: #2901 (node-side ePBS impl — phases 1–3 are implemented there), ssvlabs/SIPs#94, ssvlabs/ssv-spec#632
Update (2026-08-25): the upstream surface this issue tracks has merged, and the merged shape differs from the draft this plan was first written against — so the plan below is reconciled in-place. Key moves: beacon-APIs#625 (the draft phase-2 gate) was closed and superseded by beacon-APIs#630, merged; builder-specs#165 merged (renaming the request-auth objects); keymanager-APIs#87 was superseded by keymanager-APIs#88, merged. Phases 2–3 are therefore now implemented on the #2901 branch, atop the phase-1 reconcile (they were genuinely gated before). The request-auth signing design held; the naming, the config shape, the transport header, and design questions C1/C2 all changed — see the inline
⚠️ markers.
Context
#2901 implements node-side ePBS §2–§6. For block production (§4) it uses the merged, GET produce endpoint
GET /eth/v4/validator/blocks/{slot}?…&include_payload=false (beacon-APIs#580) with no builder preferences and no builder authentication — external-builder selection is entirely beacon-node-internal, and SSV only observes the outcome via selfBuild() (BuilderIndex == BUILDER_INDEX_SELF_BUILD). Phase 2 migrates this to the POST produce now merged in beacon-APIs#630.
The spec line that adds a validator→builder authentication handshake and per-builder bid preferences has now merged:
- beacon-APIs#630 (merged; supersedes the closed beacon-APIs#625) — replaces GET produceBlockV4 with a POST carrying a required
BuilderConfigbody (abuilderslist ofBuilderEntry, plus top-levelmin_bid/builder_boost_factorfor p2p bids), adds the BN-mediatedsubmitBuilderPreferencesendpoint, and defines theEth-Builder-Urlblock-forwarding echo. - builder-specs#165 (merged) — defines
BuilderRequestAuth/SignedBuilderRequestAuth(the signed auth; renamed fromRequestAuthV1/SignedRequestAuthV1),BuilderPreferences(max_execution_payment), thesubmitBuilderPreferencesbuilder endpoint, andDOMAIN_BUILDER_REQUEST_AUTH. - keymanager-APIs#88 (merged; supersedes keymanager-APIs#87) — the per-key builder config vocabulary (
BuilderEntry{url, auth_data, builder_pubkeys, max_execution_payment, min_bid, builder_boost_factor}wrapped in aBuilderConfigwith top-levelmin_bid/builder_boost_factordefaults). SSV doesn't serve the keymanager API, but D2 adopts its vocabulary.
SIP-94's upstream watchlist anticipated exactly this work: "If adopted, update §4's endpoint and specify the distributed request-auth signing duty once builder-specs#165 settles the signed object and forwarding contract." This issue started as the design-question capture; as of 2026-08-25 the upstream specs have merged, the design questions below are resolved (resolutions annotated inline), and the work is phased in the implementation plan at the bottom.
This is an opt-in, off-protocol overlay
Everything in this issue lives in beacon-APIs / builder-specs, not the consensus spec — by design: trusted/off-protocol payments and direct-builder auth are deliberately kept out of consensus and handled at the client/builder-API layer ("trusted payments don't need to be enshrined into consensus", per the ePBS trustless-payments discussion). The enshrined flow — p2p bids from staked builders, trustless on-chain payment, with local self-build as the always-available floor — is self-sufficient, and #2901 already produces valid Gloas proposals on it.
So this whole surface is an opt-in enhancement, not on the critical path for ePBS support: a cluster that never adopts it still proposes valid blocks — trustless bids or self-build — just without direct-builder connections or trusted (off-protocol) payments, and the enshrined path stays the fallback whenever the overlay fails or a builder is unavailable. Prioritize accordingly (phase-2 value capture).
Don't confuse the four "preferences" objects
| Object | Hop | Fields | Signed? | Status in SSV |
|---|---|---|---|---|
ProposerPreferences (consensus-spec) |
gossip topic | fee_recipient, gas_limit, … | yes (own domain) | done — §5 in #2901 (deprecates ValidatorRegistrationV1) |
BuilderConfig / BuilderEntry (beacon-APIs#630) |
VC → beacon node (produceBlockV4 POST) | top-level min_bid, builder_boost_factor (p2p) + per-entry BuilderEntry{url, auth, builder_pubkeys, max_execution_payment, min_bid, builder_boost_factor} |
only the nested auth |
this issue (phase 2) |
BuilderPreferences (builder-specs#165) |
VC → beacon node → builder (submitBuilderPreferences) |
max_execution_payment (+ auth) |
only the nested auth |
this issue (phase 3) |
BuilderEntry (keymanager-APIs#88) |
operator → VC (config, not wire) | url, auth_data, builder_pubkeys, max_execution_payment, min_bid, builder_boost_factor |
no | this issue — D2's config vocabulary (phase 1) |
max_trusted_bid field is gone in the merged specs; max_execution_payment is the trusted-payment cap. And there is no empty-url "default entry" — the merged BuilderEntry.url is required/non-empty; the "one config for all / p2p" role is served by the top-level BuilderConfig knobs.
Why the distributed-validator case is special
The only validator-signed artifact in this whole surface is SignedBuilderRequestAuth, and its signed pre-image is just BuilderRequestAuth{ data, slot } — not the boost factor, bid caps, or max_execution_payment. The builder verifies that signature against the (group) validator_pubkey, so in SSV it must be a threshold BLS signature reconstructed from operator partials.
Consequences that shape every question below:
- Reconstruction needs a quorum (
2f+1; 3-of-4, 5-of-7, …) of partials over a byte-identical signing root.datais aByteList, so any divergence changes the root and the partials won't combine. - The unsigned fields (
builder_boost_factor,min_bid,max_execution_payment,url,builder_pubkeys) need no signing round — each operator's BN applies them locally — but they still want cluster-consistent config for deterministic QBFT and coherent economics. - The token is a public label (by default), not a secret or credential: it binds the signature to a builder+slot, but the BLS signature is the actual authenticator, so possession of
dataalone grants nothing. It can live in ordinary — even on-chain — config.
Design questions
A. The auth data token
A1. What kind of value is data?
- ✅ Resolved (builder-specs#165, merged): opaque bytes agreed out of band, signed exactly as serialized — decoupled from the URL, with a spec-recommended default when nothing was agreed: the UTF-8 bytes of the builder's advertised URL, exactly as advertised. The authenticity-not-confidentiality reading was confirmed (anti-DDoS/replay; bid/payload privacy is the separate commit-reveal + PTC path), so the token stays a public label in the default case; a builder that insists on minting a custom (even secret) value is a spec-supported variant, and for SSV it's the same config-distribution problem either way (see A2/D2).
A2. How do all operators produce byte-identical data? (Hard requirement: data is hashed into the signing root, so any divergence breaks reconstruction.)
- ✅ Resolved (builder-specs#165, merged): no canonicalization anywhere — the earlier canonical-byte-form ask is superseded. Byte-identity is achieved by distributing the exact per-builder
databytes to all operators as config (opaque bytes signed as-is turn a silent cross-implementation canonicalization hazard into plain config discipline), backed by byte-preserving forwarding MUSTs at every hop (VC → BN → builder). D2's on-chain single-source-of-truth remains a legitimate distribution channel for those exact bytes.
B. Threshold-signing mechanics
B1. When/where does the auth get signed, and by which runner?
- Decided (SSV-side; implemented in ePBS (EIP-7732 / Gloas) — SIP-94 #2901): ride the §5 dispatcher — same
RoleProposerPreferences, a newRequestAuthPartialSigpartial-sig type (ssv-spec#632 extension). The §5 slot sub-runner freezes oneBuilderRequestAuth{data, proposal_slot}per configured builder next to its preference, broadcasts one single-root partial per distinct auth root (one per builder unless entries shareauth_data, preserving the §5 single-message wire rule), collects them in a separate partial-sig container, and on quorum reconstructs into a per-proposal-slot auth cache consumed by §4 (phase 2) and the AOT submit (phase 3). Rationale: the cadence is inherently identical (proposer-lookahead-driven, epoch-ahead, reorg/indices-change rechecks), so riding §5 reuses the handler's emission machinery and the dispatcher's stash/replay + re-emission idempotency wholesale, with zero new scheduler, role, queue, or tracer surface and only +1 wire constant for Anchor to match; a dedicated role would duplicate all of that for no cadence or semantic gain. Isolation is kept by construction: the §5 duty outcome stays preference-defined, auth partials live in their own container, and auth failures surface via metrics (reconstruction + E1 consumption), never in the §5 path. This is also the SIP-94-conformant shape: single-entry packets keep §7's non-committee one-entry rule (REJECT above 1) intact, and the §7 validation-table row forRoleProposerPreferencesgains a second admitted partial-sig type with its own distinct-root budget — a delta to the existing §7 row, not a new role row (the §5 cap of 4 distinct preference roots is SIP-normative and stays).
B2. What is the exact signing root/domain, and what wire plumbing does it need?
- ✅ Resolved (builder-specs#165, merged):
DOMAIN_BUILDER_REQUEST_AUTH = DomainType('0x0B000001')on builder-specs master — value unchanged; renamed fromDOMAIN_REQUEST_AUTH. Signing/verification iscompute_signing_root(BuilderRequestAuth, compute_domain(DOMAIN_BUILDER_REQUEST_AUTH)); the merged pseudocode callscompute_domainwith no fork version, i.e. genesis-style (networkGENESIS_FORK_VERSION, zero genesis-validators-root), likeDOMAIN_APPLICATION_BUILDER. So the BLS signing domain is fork-agnostic, and the phase-1 ekm arm (grouped withDomainApplicationBuilderinbeacon/goclient/signing.go) is correct as implemented. ⚠️ Nuance flipped: the request-auth type is nonetheless fork-versioned for wire decoding — validator.md/builder.md require theEth-Consensus-Versionheader on any hop carrying the body (see C3). "Genesis-style signing domain" and "fork-versioned wire type" coexist; don't conflate them. The original "not fork-versioned" note was right about the signing domain but wrong about the transport header.- Plumbing: ssv-spec#632's
DomainBuilderRequestAuth(renamed fromDomainRequestAuthfor builder-specs parity; the0x0B000001value is what matters for cross-client compat) +RequestAuthPartialSig = PartialSigMsgType(9). ekm/ssvsigner get the genesis-style domain arm besideDomainApplicationBuilder(non-slashable signing path).⚠️ 0x0B000001is one byte from the beacon domain0x0B000000(DOMAIN_BEACON_BUILDER, SIP-94 §6) — keep the loud comment.
B3. What slot goes in BuilderRequestAuth.slot?
- ✅ Resolved (builder-specs#165, merged): the proposal slot, explicitly — "not the slot at which the request is signed or sent". Proposer duties are known an epoch ahead so the auth can be pre-signed off the hot path, and one auth covers both channels (
datais not endpoint-tied):submitBuilderPreferencesrejects auths whose slot has already passed (anti-replay/rollback),getExecutionPayloadBidrequires slot == the proposal-slot path param.
B4. Quorum & the sub-quorum failure mode.
- ✅ Resolved (builder-specs#165, merged): the auth body is required on
getExecutionPayloadBid(400 without it) — there is no "unauthenticated bid" degrade tier. Sub-quorum on a builder's auth ⇒ that builder is simply not contactable for the slot; the fallback is the enshrined floor (gossip bids / self-build), and the proposal is never blocked on the overlay. The all-nrequirement stands: every operator holds the token config (not just a bare quorum) soffault-tolerance is preserved on the auth path too.
C. API adoption
C1. produceBlockV4 GET → POST (beacon-APIs#630, merged). §4 currently uses GET. The POST carries a required BuilderConfig body: top-level min_bid / builder_boost_factor plus a builders list of BuilderEntry (each with the reconstructed auth).
- ✅ Resolved (gossip vs "contacted"): the earlier open question — how the knobs govern gossip-sourced enshrined bids for a trustless-only cluster — is answered by the merged shape: the top-level
min_bid/builder_boost_factorapply to p2p bids, and eachBuilderEntry's own knobs apply to that builder's bid. A local-preferred build is an emptybuilderslist with top-levelbuilder_boost_factor = 0. - ✅ Resolved (auth-optionality): the draft's "requests without authentication" wording is gone —
BuilderEntry.authis required, so an entry with no reconstructed quorum auth is simply omitted for the slot (degrades to p2p/self-build). - Implementation: build the
BuilderConfigfrom cluster builder config + the epoch-ahead auth cache; sendinclude_payload(required query param) andEth-Consensus-Version(required header). Two-level GET fallback: the POST is taken only when the cluster configured the overlay (any entry, or just a top-level knob) — an unconfigured cluster keeps the enshrined GET and imposes no defaultbuilder_boost_factor— and even when configured the GET stays a per-BN fallback until that BN ships the POST. Who calls (SSV): all operators, each POSTing produce to its own BN — so each BN independently solicits bids from the configured builders with the same reconstructed auth, and a builder sees up tonauthenticated bid requests per slot (spec-legal: the auth binds proposer+slot, not the requester — the same all-operators redundancy as C2's submit).
C2. submitBuilderPreferences (beacon-APIs#630, merged).
- ✅ Resolved (channel): BN-mediated. The endpoint is a beacon-API call,
POST /eth/v1/validator/builder_preferences— the VC posts a flat list ofBuilderPreferencesEntry{proposer_pubkey, url, auth, max_execution_payment}to its own beacon node, which forwards each to the builder'ssubmitBuilderPreferences. The VC does not call builders directly. Our long-standing "direct vs BN-mediated" question is thus settled BN-mediated — simpler for SSV (no direct-to-builder HTTP/TLS client, noDate-Milliseconds/X-Timeout-Ms). - Implementation: phase 3; reuse the same cached auth (same
{data, proposal_slot}) as the bid request; submit in the epoch prior (proposer_lookahead), batched across the operator's local proposers. Who submits (SSV): all operators, each via its own BN — not "redundant at the builder" from one submit, but N BNs each forwarding the identical(proposer_pubkey, auth, max_execution_payment); the builder stores per-proposer-per-slot andmax_execution_paymentis required-identical (D1/D2), so this is consistent.
C3. Transport.
- ✅ Resolved (builder-specs#165 + beacon-APIs#630, merged): required
SignedBuilderRequestAuthbody, JSON or SSZ.⚠️ Eth-Consensus-Versionis now required on hops carrying the body (the type is fork-versioned for decoding, B2) — a flip from the earlier "no header" note.Date-Milliseconds/X-Timeout-Msare on the BN→builder bid hop (not SSV's concern under BN-mediation). The beacon-API shape (beacon-APIs#630) is merged, so nothing here is pending.
D. Non-signed config & governance
D1. Selection knobs (builder_boost_factor, min_bid) and max_execution_payment are cluster policy — a shared risk/reward decision.
- Candidate: cluster-level config, required consistent across operators for deterministic QBFT and coherent economics, even though signing doesn't force it.
- Value formula (now pinned, beacon-APIs#630): a bid is valued at
value + min(execution_payment, max_execution_payment); below the applicablemin_bidit is rejected; survivors are weighted bybuilder_boost_factorand compared against the local build weighted by100(ties → local). - Finding (code-checked): divergence is consensus-safe — the proposer value-check (
proposerChecker.CheckValueinprotocol/v2/ssv/value_check.go) validates only role/validator/slot + slashing, never the knobs or the bid, so non-leaders ratify the leader's block regardless of their own config. Divergence doesn't stall consensus; it makes the effective policy non-deterministic ("whoever leads"), andmax_execution_paymentresolves last-write-wins at the builder (C2). "Required consistent" is thus for determinism/economics, not safety — contingent on the current lenient check: if a future Gloas value-check starts validating bids against these knobs, divergence flips to a consensus-stall risk, so tightening that check must be paired with enforced config consistency. - Builder-side default (builder-specs#165): without stored preferences a builder MAY pay anything; the proposer's locally configured
max_execution_paymentcap is the backstop (applied at bid valuation). This raises the stakes on cluster-consistent caps but doesn't change the safety analysis above.
D2. Config surface & distribution.
- Decided (phase 1, implemented in ePBS (EIP-7732 / Gloas) — SIP-94 #2901): cluster-level operator config adopting keymanager-APIs#88's
BuilderEntryvocabulary (url,auth_data,builder_pubkeys,max_execution_payment,min_bid,builder_boost_factor), entries identified by(url, auth_data)(url compared exactly, auth_data by decoded bytes),auth_datadefaulting to the URL bytes when omitted; documented as required-identical across all n operators (B4), with a shared entry cap (MaxBuilderEntries) that also bounds the message-validation budget. ⚠️ Reconcile against merged shape:builder_pubkeysis a list (empty = accept any builder), not a single optionalpubkey; there is nomax_trusted_bid; and the "one config for all / p2p" role belongs to top-levelBuilderConfigknobs, not an empty-urlentry (per keymanager-APIs#88 the top-level knobs also serve as per-entry defaults, resolved VC-side before the wire call). The spec cap isMAX_BUILDER_ENTRIES = 64; SSV'sMaxBuilderEntries = 8is a legitimate sub-cap (policy choice), but it should be framed as SSV's own, not "the spec cap".- Finding: since the token is non-secret in the default case (A1), an on-chain / contract-derived value remains a legitimate single-source-of-truth for the exact
databytes — the strongest long-term answer to A2's byte-identity requirement; revisit after phase 1.
E. Observability / fallback
E1. Distinguish self-build causes. selfBuild() today can't tell "self-build because economics" from "self-build because auth/quorum failed."
- Implemented (ePBS (EIP-7732 / Gloas) — SIP-94 #2901), in a two-signal shape: the §4 build-source telemetry is an outcome enum (
local/builder) at submit, plus a produce-timerequest_auth.unavailablecounter — configured builders with no reconstructed auth for the slot, omitted from the produceBlockV4 body. The originally planned three-way reason (no-bid/economics/auth-unavailable) proved underivable: the decided block cannot reveal why the BN self-built, so the auth dimension is surfaced where it is knowable (produce time, fed by the per-slot auth cache) instead. Silent degradation stays the main risk — a cluster can propose fine while quietly never reaching its preferred builder; the counter is the watch signal.
Load-bearing assumptions (revisit if these change)
Proposal-slot semantics (B3) · signing domain/root (B2) · proposer-derived— all resolved by the merges; the resolutions are annotated inline above (✅ markers).data(A1/A2) · body transport (C1/C3) · required-auth survival (B4)- Value-check stays lenient (D1). Unchanged: divergent unsigned knobs are consensus-safe only while the proposer value-check ignores them; tightening it turns divergence into a stall risk.
- Fork-versioning split (B2/C3). New load-bearing fact: the signing domain is genesis-style (fork-agnostic) but the wire type is fork-versioned (
Eth-Consensus-Versionrequired on transport). If a future fork changes theBuilderRequestAuthschema, the header selects the decode; the signing domain stays genesis-style. - Auth-availability is observable (E1). Holds as implemented: the submit-time outcome enum plus the produce-time
request_auth.unavailablecounter —selfBuild()is no longer a bare bool. - Unconfigured clusters GET produce first (phase 2). An unconfigured cluster sends the plain enshrined GET with no POST retry — correct while beacon nodes still predate beacon-APIs#630, but the moment a CL ships the beacon-APIs#630 POST and drops the GET (the spec already replaced it), produce breaks for exactly the clusters that never opted in. Action when that lands: flip §4 to POST-first — unconfigured clusters send the empty
BuilderConfig(builders: [], neutral knobs), and the per-node GET stays only as the older-BN fallback; revisit at the latest before Gloas ships on a public network. Watch: ChainSafe/lodestar#9832 (the reference beacon-APIs#630 impl) merging.
Implementation plan (SSV)
All phases land on epbs-gloas (bundled into #2901). Wire constants go through ssv-spec#632 (the established spectypes pattern; ekm/ssvsigner reach signing domains only via spectypes).
Phase 1 — implemented in #2901 and reconciled to the merged specs. The signing/reconstruction vertical is done and spec-correct (B2); the reconcile items below — renames, config reshape, and the optional ssv-spec#632 constant rename — are all applied:
- ssv-spec#632 extension:
DomainRequestAuth = 0x0B000001+RequestAuthPartialSig = PartialSigMsgType(9)+ golden tests. ✅ The constant was renamed toDomainBuilderRequestAuthon the ssv-spec#632 branch (value0x0B000001unchanged) and adopted node-side; ssv-spec#632 is still open (a ePBS (EIP-7732 / Gloas) — SIP-94 #2901 merge-blocker). - Wire types:
BuilderRequestAuth{data ByteList[MAX_BUILDER_AUTH_DATA_SIZE=4096], slot}/SignedBuilderRequestAuthinprotocol/v2/types/gloas(SSZ + JSON).⚠️ Rename fromRequestAuthV1/SignedRequestAuthV1andMaxRequestAuthDataSize→MAX_BUILDER_AUTH_DATA_SIZE; use builder-specs'examples/gloas/signed_builder_request_auth.jsonas the round-trip/HTR fixture. - Cluster builder config (D2): operator-YAML section in keymanager-APIs#88 vocabulary; parse/validate (
auth_datadefaults to URL bytes;MaxBuilderEntriescap);docs/EXTERNAL_BUILDERS.mddocuments the all-nrequired-identical rule.⚠️ Reconcile: dropMaxTrustedBid;PubKey→BuilderPubKeyslist; drop the empty-urldefault entry in favor of a top-levelBuilderConfigwrapper (min_bid/builder_boost_factor); refresh beacon-APIs#625→beacon-APIs#630, keymanager-APIs#87→keymanager-APIs#88 comment refs; adopt keymanager-APIs#88's resolution rule — top-levelmin_bid/builder_boost_factordouble as per-entry defaults (an entry that omits its own takes them) and as the p2p knobs, resolved SSV-side before the wire call, with the resolved values under the same required-identical rule. - ekm/ssvsigner: genesis-style
DOMAIN_BUILDER_REQUEST_AUTHarm besideDomainApplicationBuilder(non-slashable) — correct as implemented (beacon/goclient/signing.go); local + remote key managers; Web3Signer gap recorded with the other Gloas domains. (TheEth-Consensus-Versiontransport header is a phase-2/3 concern, not a signing change.) - §5 dispatcher extension (B1): per-builder frozen
BuilderRequestAuths, one single-rootRequestAuthPartialSigbroadcast per distinct auth root (one per builder unless entries shareauth_data), separate partial-sig container, reconstruct → per-proposal-slot auth cache (evicted with the sub-runners); dispatcher stash/replay generalized by msg type. Message validation: admit the new type underRoleProposerPreferenceswith its own distinct-root budget (SSV'sMaxRequestAuthDistinctRoots = MaxBuilderEntries; note the SSV sub-cap vs the spec's 64) and the same future-slot allowance as §5. - E1: build-source reason enum replacing the bool.
- Design record — the full reconciled prose of the distributed request-auth duty (§5 signing-duty subsection, §7 validation-table changes, the
auth_data-divergence security consideration, watchlist update) has been adopted into SIPs#94 (design-record note below); the values it pins are matched there and in ssv-spec#632 for Anchor parity.
Phase 2 — implemented (#2901); e2e gated on a beacon-APIs#630 beacon node: §4 produce GET→POST (beacon/goclient Gloas proposer path), taken when the cluster set any direct-builder config — entries or just the top-level p2p knobs (min_bid/builder_boost_factor), else the enshrined GET (imposing no proposer knobs on an unconfigured cluster): BuilderConfig body assembled from config + the auth cache (entries without a reconstructed auth are omitted — auth is required), include_payload (required query) sent explicitly, Eth-Consensus-Version header; add the Eth-Builder-Url block-forwarding echo on publish, gated so only the operator whose produced block won QBFT echoes it (mirrors #2901's §6 envelope-owner match); GET fallback retained until BNs ship the POST — now off-spec (beacon-APIs#630 removed the GET from master), so transitional only, auto-engaged per BN on a 404/405 to the POST (the fallback GET still carries builder_boost_factor — the sole knob beacon-APIs#580's GET honors; min_bid and the per-builder inputs are POST-only); wire auth-unavailable into E1.
Phase 3 — implemented (#2901), BN-mediated; e2e gated on a beacon-APIs#630 beacon node: POST /eth/v1/validator/builder_preferences to the operator's own BN (not direct SSV→builder), a flat list of BuilderPreferencesEntry reusing the cached auth (epoch-prior window, same as §5 emission); all-operators submit each via its own BN; batch-level submit metrics — one call per reconstructed auth root (a beacon-APIs#630 partial 400 books the call a failure; the per-entry IndexedErrorMessage rides the warn log).
Testing: unit + golden vectors per phase — SSZ/HTR/domain against builder-specs' merged examples/gloas/* fixtures; beacon-APIs ships no fixtures for BuilderConfig/BuilderEntry/BuilderPreferencesEntry, so those are pinned by SSV's own round-trip vectors. §5 message-validation suite extended for the second type. E2E deferred until a CL ships the beacon-APIs#630 POST (reference impl: ChainSafe/lodestar#9832, an open draft) and a Gloas builder exists — devnet e2e rides the #2920 successors (#2920 is closed: #2953, #2954, ssvlabs/aetheria#139, ssvlabs/aetheria#141) plus the builders-configured local_testnet_gloas run tracked on #2901.
Action — Aetheria local_testnet_gloas coverage of the overlay (#2901 code): extend the suite to exercise the direct-builder code toward as-full-as-possible coverage, split by what today's CL can serve. Exercisable now (no beacon-APIs#630 BN): the §5 request-auth signing + threshold reconstruction (partials → per-slot auth cache), the produce GET fallback carrying builder_boost_factor (today's Lodestar serves the beacon-APIs#580 GET), the Configured() POST-vs-GET gate + config parse/validate, the remote-signer entry-drop, and the build-source / request-auth-unavailable / builder-preferences metrics. Gated on a beacon-APIs#630 BN + a Gloas builder (ChainSafe/lodestar#9832, open draft): the produceBlockV4 POST being accepted, builder-API bid solicitation, submitBuilderPreferences forwarding, and the Eth-Builder-Url echo — deferred with the rest of e2e (ssvlabs/aetheria#139 / #141). Remote-signing paths for the overlay stay bounded to ≤ f until #3000 lands.
Out of scope (already in #2901)
§4 GET produce/publish + selfBuild() detection; §6 self-build execution-payload envelope duty; §5 ProposerPreferences gossip (fee-recipient/gas, ValidatorRegistrationV1 deprecation); §3 PTC; §2 Gloas attestations; fork gating/retiming. (Phase 1 of this issue — the request-auth signing vertical — is also in #2901.)
Design record: adopted into SIPs#94
The full SSV-side design record of the distributed request-auth duty — the §5 signing-duty subsection, the §7 validation-table changes, the auth_data-divergence security consideration, and the produceBlockV4 watchlist update — has been adopted into SIPs#94 (feat/epbs, sips/epbs_support.md), now the normative copy, so this issue no longer reproduces it. The SIP matches the implementation (#2901) and ssv-spec#632 in shape and in the values pinned for Anchor parity:
DomainBuilderRequestAuth = 0x0B000001(mirrors builder-specs'DOMAIN_BUILDER_REQUEST_AUTH; genesis-style domain, one byte fromDOMAIN_BEACON_BUILDER0x0B000000);RequestAuthPartialSig = PartialSigMsgType(9)- rides
RoleProposerPreferencesas a second partial-sig type — no new beacon/runner role orMapDutyToRunnerRoleentry - SSV builder-entry sub-cap 8 (beacon-API
MAX_BUILDER_ENTRIES = 64); request-auth distinct-root budget 8; one single-entry packet per distinct auth root per proposal slot
References
- SSV: #2901 (ePBS impl), SIPs#94, ssv-spec#632
- beacon-APIs: #630 (merged — POST produceBlockV4 +
BuilderConfig, BN-mediatedsubmitBuilderPreferences,Eth-Builder-Urlforwarding), #625 (closed, superseded by beacon-APIs#630), #580 (merged Gloas produce), #620 (motivation) - builder-specs: #165 (merged — request-auth clarifications), specs/gloas/validator.md, specs/gloas/builder.md (
DOMAIN_BUILDER_REQUEST_AUTH) - keymanager-APIs: #88 (merged — per-key builder config, supersedes #87)
- reference impl: ChainSafe/lodestar#9832 (implements the merged Gloas builder flow; open draft)
- EIP-7732
Activity
types: add the builder request-auth domain and partial-sig type
gloas: re-pin ssv-spec for the request-auth constants; absorb the Env…
gloas: request-auth wire types and the BuilderEntry config vocabulary (…
gloas: DomainRequestAuth signing arms (#2962)
gloas: #2962 phase 1 — threshold request-auth rounds on the §5 dispat…
iurii-ssv commentedon Jul 28, 2026
Superseded — the SIP-94 amendment draft that lived here was reconciled to the merged specs and moved into the issue body (now the "Design record" section there). The pre-merge text remains in this comment's edit history.
gloas: #2962 review follow-ups — clock-anchored auth cache, shared-to…
gloas: #2962 follow-ups — hard-fail auth partials without local build…
gloas: #2962 review round 3 — replay stash into concluded re-emission…
gloas: re-pin ssv-spec for the request-auth constants; absorb the Env…
gloas: request-auth wire types and the BuilderEntry config vocabulary (…
gloas: DomainRequestAuth signing arms (#2962)
gloas: #2962 phase 1 — threshold request-auth rounds on the §5 dispat…
gloas: #2962 review follow-ups — clock-anchored auth cache, shared-to…
73 remaining items
gloas: request-auth wire types and the BuilderEntry config vocabulary (…
gloas: DomainRequestAuth signing arms (#2962)
gloas: #2962 phase 1 — threshold request-auth rounds on the §5 dispat…
gloas: #2962 review follow-ups — clock-anchored auth cache, shared-to…
gloas: #2962 follow-ups — hard-fail auth partials without local build…
gloas: #2962 review round 3 — replay stash into concluded re-emission…
gloas: reconcile the request-auth overlay to the merged Gloas builder…
gloas: adopt the produceBlockV4 POST for the direct-builder overlay (…
gloas: submit ahead-of-time builder preferences (phase 3)
gloas: code-review follow-ups — POST on a knobs-only config; comment/…
gloas: carry builder_boost_factor on the produce GET fallback
gloas: polish the parse-once follow-ups (docs, receiver, lint)
iurii-ssv commentedon Sep 22, 2026
Closing: the three phases this issue planned are implemented on #2901 and reconciled to the merged upstream specs (builder-specs#165, beacon-APIs#630, keymanager-APIs#88), and the design record is adopted into the ssvlabs/SIPs#94 text. What the August snapshot above still listed as open has either moved on or has its own tracker:
local_testnet_gloasfollow-up in ePBS (EIP-7732 / Gloas) — SIP-94 #2901's checklist.The body stays as the design record;
docs/EXTERNAL_BUILDERS.mdpoints here for it and at ssvlabs/aetheria#173 for the coverage.types: add ePBS (Gloas) domains, roles, and partial-sig types (#632)