Skip to content

ePBS/Gloas: external-builder authentication & per-builder bid preferences — design & implementation plan #2962

Closed

Description

@iurii-ssv

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 BuilderConfig body (a builders list of BuilderEntry, plus top-level min_bid / builder_boost_factor for p2p bids), adds the BN-mediated submitBuilderPreferences endpoint, and defines the Eth-Builder-Url block-forwarding echo.
  • builder-specs#165 (merged) — defines BuilderRequestAuth / SignedBuilderRequestAuth (the signed auth; renamed from RequestAuthV1 / SignedRequestAuthV1), BuilderPreferences (max_execution_payment), the submitBuilderPreferences builder endpoint, and DOMAIN_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 a BuilderConfig with top-level min_bid / builder_boost_factor defaults). 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)

⚠️ The draft 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. data is a ByteList, 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 data alone 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 data bytes 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 new RequestAuthPartialSig partial-sig type (ssv-spec#632 extension). The §5 slot sub-runner freezes one BuilderRequestAuth{data, proposal_slot} per configured builder next to its preference, broadcasts one single-root partial per distinct auth root (one per builder unless entries share auth_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 for RoleProposerPreferences gains 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 from DOMAIN_REQUEST_AUTH. Signing/verification is compute_signing_root(BuilderRequestAuth, compute_domain(DOMAIN_BUILDER_REQUEST_AUTH)); the merged pseudocode calls compute_domain with no fork version, i.e. genesis-style (network GENESIS_FORK_VERSION, zero genesis-validators-root), like DOMAIN_APPLICATION_BUILDER. So the BLS signing domain is fork-agnostic, and the phase-1 ekm arm (grouped with DomainApplicationBuilder in beacon/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 the Eth-Consensus-Version header 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 from DomainRequestAuth for builder-specs parity; the 0x0B000001 value is what matters for cross-client compat) + RequestAuthPartialSig = PartialSigMsgType(9). ekm/ssvsigner get the genesis-style domain arm beside DomainApplicationBuilder (non-slashable signing path). ⚠️ 0x0B000001 is one byte from the beacon domain 0x0B000000 (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 (data is not endpoint-tied): submitBuilderPreferences rejects auths whose slot has already passed (anti-replay/rollback), getExecutionPayloadBid requires 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-n requirement stands: every operator holds the token config (not just a bare quorum) so f fault-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_factor apply to p2p bids, and each BuilderEntry's own knobs apply to that builder's bid. A local-preferred build is an empty builders list with top-level builder_boost_factor = 0.
  • ✅ Resolved (auth-optionality): the draft's "requests without authentication" wording is gone — BuilderEntry.auth is required, so an entry with no reconstructed quorum auth is simply omitted for the slot (degrades to p2p/self-build).
  • Implementation: build the BuilderConfig from cluster builder config + the epoch-ahead auth cache; send include_payload (required query param) and Eth-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 default builder_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 to n authenticated 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 of BuilderPreferencesEntry{proposer_pubkey, url, auth, max_execution_payment} to its own beacon node, which forwards each to the builder's submitBuilderPreferences. 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, no Date-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 and max_execution_payment is required-identical (D1/D2), so this is consistent.

C3. Transport.

  • ✅ Resolved (builder-specs#165 + beacon-APIs#630, merged): required SignedBuilderRequestAuth body, JSON or SSZ. ⚠️ Eth-Consensus-Version is 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-Ms are 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 applicable min_bid it is rejected; survivors are weighted by builder_boost_factor and compared against the local build weighted by 100 (ties → local).
  • Finding (code-checked): divergence is consensus-safe — the proposer value-check (proposerChecker.CheckValue in protocol/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"), and max_execution_payment resolves 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_payment cap 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 BuilderEntry vocabulary (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_data defaulting 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_pubkeys is a list (empty = accept any builder), not a single optional pubkey; there is no max_trusted_bid; and the "one config for all / p2p" role belongs to top-level BuilderConfig knobs, not an empty-url entry (per keymanager-APIs#88 the top-level knobs also serve as per-entry defaults, resolved VC-side before the wire call). The spec cap is MAX_BUILDER_ENTRIES = 64; SSV's MaxBuilderEntries = 8 is 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 data bytes — 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-time request_auth.unavailable counter — 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 data (A1/A2) · body transport (C1/C3) · required-auth survival (B4) — all resolved by the merges; the resolutions are annotated inline above (✅ markers).
  • 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-Version required on transport). If a future fork changes the BuilderRequestAuth schema, 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.unavailable counter — 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:

  1. ssv-spec#632 extension: DomainRequestAuth = 0x0B000001 + RequestAuthPartialSig = PartialSigMsgType(9) + golden tests. ✅ The constant was renamed to DomainBuilderRequestAuth on the ssv-spec#632 branch (value 0x0B000001 unchanged) and adopted node-side; ssv-spec#632 is still open (a ePBS (EIP-7732 / Gloas) — SIP-94 #2901 merge-blocker).
  2. Wire types: BuilderRequestAuth{data ByteList[MAX_BUILDER_AUTH_DATA_SIZE=4096], slot} / SignedBuilderRequestAuth in protocol/v2/types/gloas (SSZ + JSON). ⚠️ Rename from RequestAuthV1/SignedRequestAuthV1 and MaxRequestAuthDataSize→MAX_BUILDER_AUTH_DATA_SIZE; use builder-specs' examples/gloas/signed_builder_request_auth.json as the round-trip/HTR fixture.
  3. Cluster builder config (D2): operator-YAML section in keymanager-APIs#88 vocabulary; parse/validate (auth_data defaults to URL bytes; MaxBuilderEntries cap); docs/EXTERNAL_BUILDERS.md documents the all-n required-identical rule. ⚠️ Reconcile: drop MaxTrustedBid; PubKey→BuilderPubKeys list; drop the empty-url default entry in favor of a top-level BuilderConfig wrapper (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-level min_bid/builder_boost_factor double 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.
  4. ekm/ssvsigner: genesis-style DOMAIN_BUILDER_REQUEST_AUTH arm beside DomainApplicationBuilder (non-slashable) — correct as implemented (beacon/goclient/signing.go); local + remote key managers; Web3Signer gap recorded with the other Gloas domains. (The Eth-Consensus-Version transport header is a phase-2/3 concern, not a signing change.)
  5. §5 dispatcher extension (B1): per-builder frozen BuilderRequestAuths, one single-root RequestAuthPartialSig broadcast per distinct auth root (one per builder unless entries share auth_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 under RoleProposerPreferences with its own distinct-root budget (SSV's MaxRequestAuthDistinctRoots = MaxBuilderEntries; note the SSV sub-cap vs the spec's 64) and the same future-slot allowance as §5.
  6. E1: build-source reason enum replacing the bool.
  7. 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 from DOMAIN_BEACON_BUILDER 0x0B000000); RequestAuthPartialSig = PartialSigMsgType(9)
  • rides RoleProposerPreferences as a second partial-sig type — no new beacon/runner role or MapDutyToRunnerRole entry
  • 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

Activity

  1. self-assigned this
    on Jul 21, 2026
  2. iurii-ssv commented on Jul 28, 2026

    @iurii-ssv
    ContributorAuthor

    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.

  3. added 8 commits that reference this issue on Jul 29, 2026
  4. 73 remaining items

  5. iurii-ssv commented on Sep 22, 2026

    @iurii-ssv
    ContributorAuthor

    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:

    The body stays as the design record; docs/EXTERNAL_BUILDERS.md points here for it and at ssvlabs/aetheria#173 for the coverage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions