feat(client): validate SSV builder definitions constraints at startup - #1283
Conversation
|
@claude, please take a look. just the top commit, as described in pr description |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## epbs #1283 +/- ##
=======================================
Coverage ? 80.50%
=======================================
Files ? 181
Lines ? 41414
Branches ? 0
=======================================
Hits ? 33342
Misses ? 8072
Partials ? 0
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Claude finished @shane-moore's task in 24m 7s —— View job Review — PR #1283 (top commit
|
shane-moore
left a comment
There was a problem hiding this comment.
Reviewed the top commit, 85596357, against its direct parent, d1a56cdd.
The SSV entry cap, zero-length auth validation, startup ordering, parser drift guard, and derivation vectors match the current SIP-94, go-ssv, and pinned Lighthouse behavior. I found two issues to resolve before undrafting:
builder_pubkeysis missing its startup bound. Anchor's new pass does not inspect the list length, and Lighthouse's load validator also leaves theVecunbounded. A definition with 65 valid keys passes startup;builder_configlater logs and omits that builder because the wire list is capped at 64. Please enforce this in Lighthouse and re-pin, or check it here, then add a 65-key test and document the limit.- The security guidance should not say
auth_datais non-secret or imply credentials in URLs are safe. The merged keymanager schema explicitly permitsauth_datato be a shared secret, while the pinned store logs raw builder URLs andBuilderUrl::Displaydoes not redact them. Treat custom auth data and URL credentials as sensitive. Until those Lighthouse logs are redacted, advise operators not to embed credentials in builder URLs.
All CI checks pass at this head, including the local testnet. The existing draft gates on the Lighthouse stack and issue #1280 remain.
Reviewed by gpt-5.6-sol max.
Reject entries with more than MAX_BUILDER_PUBKEYS (64) keys at startup: Lighthouse's load validation never inspects the list, so an oversized entry loads fine and builder_config then omits the builder with only an error log at every proposal. Also drop the docs' claim that auth_data is not a secret (the keymanager schema permits shared secrets) and advise keeping credentials out of builder URLs, which appear verbatim in logs. Addresses both items from the review on sigp#1283.
|
Both review items addressed in 4ca6d8b: startup now rejects any entry with more than 64 |
Bump the Lighthouse pin to the Gloas builder-API stack head (sigp/lighthouse#9807) and implement the one new required trait method, sign_request_auth_v1, as a distributed threshold signing round: kind 9 (RequestAuth) riding Role::ProposerPreferences under the fixed builder-specs sigp#165 application domain, with a slot-aware collection bound (future slots 2 slots, current slot 1s fail-fast, elapsed slots declined without broadcast). Wire the BuilderStore and RequestAuthCache the new BlockServiceBuilder requires at startup, decline elapsed-slot proposer-preferences signing after restarts, and migrate the one test broken by the pin (private attestation-due fields).
Review follow-ups: apply the nightly rustfmt reflow check-fmt requires on the request_auth_collection_bound doc comment, and carry the blst 0.3.16 -> 0.3.17 lockfile hardening (Pippenger divide-by-zero, blst sigp#283) that Lighthouse #9869 took upstream but Anchor's lock did not inherit.
Spawn Lighthouse's BuilderPreferencesService inside the Gloas-scheduled gate so builder preferences publish ahead of time for current- and next-epoch proposal duties, and hoist a single RequestAuthCache shared via Arc-backed clones with the block service; the service's per-slot tick is the cache's only prune caller at the Lighthouse pin.
Enforce the SIP-94 section 5 limits Lighthouse cannot know on <data_dir>/builder_definitions.yml before any service spawns: at most 8 enabled entries (the SSV policy sub-cap of the beacon-API's 64) and no entry resolving to zero-length auth data. Excess entries would otherwise be dropped silently and per-peer nondeterministically at every proposal by the gossip root budget. Also adds the operator docs section for direct builder connections.
Reject entries with more than MAX_BUILDER_PUBKEYS (64) keys at startup: Lighthouse's load validation never inspects the list, so an oversized entry loads fine and builder_config then omits the builder with only an error log at every proposal. Also drop the docs' claim that auth_data is not a secret (the keymanager schema permits shared secrets) and advise keeping credentials out of builder URLs, which appear verbatim in logs. Addresses both items from the review on sigp#1283.
The builder rejects auth data it did not agree to and drops itself from that proposal. The failure is per builder rather than per file, so nothing is refused at startup and the entry looks valid. Live validation on ssv-mini showed a builder answering 400 for exactly this case. Omitting the field stays the interoperable default: it resolves to the URL, which is what a builder that agreed nothing out of band expects.
0966425 to
3f01d4a
Compare
…efinitions Resolve the client wiring, manifest, and lockfile conflicts on the PR side (the epbs squash of sigp#1285 carries the store open this PR hoists earlier). Also fold in three review fixes: - client: keep the parser line/column and io ErrorKind in redacted startup errors; they are structural and cannot echo input - logging: add builder_store to the Lighthouse log allowlist so a builder omitted at proposal time is logged, and drop the docs caveat about it - client: remove the redundant bls dev-dependency Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
shane-moore
left a comment
There was a problem hiding this comment.
Review by Claude Fable 5.1 (Claude Code), against head 3f01d4a8 with epbs at 6c98a389 and the Lighthouse pin fdcc8658. Findings were folded into the merge commit 9e0d3f65, so this is the record rather than a request.
Scope checked
- Global entries: at the pin, Lighthouse
BuilderConfigFile::validateskips disabled entries and, for enabled ones, checks only URL shape and scheme, derivable default auth, and(url, auth)duplicates. It does not reject an explicit emptyauth_dataand does not boundbuilder_pubkeys. The per-entry loop here is unfiltered byenabled, so both are caught for every global entry. - Per-validator overrides: Lighthouse
ValidatorBuilderConfig::validatealready bounds the list at 64, rejects oversized pubkeys and empty auth, and dedups. This PR adds only the SSV cap of 8 on present lists;Noneinherits the already-capped enabled globals andSome([])disables, matchingresolved_for. - SIP-94 at PR #94 head
fc4f965d: 8 entries per validator, lowercase ASCII hostname default, zero-length data invalid. go-ssv #2901 head6fe7a951: still defaults auth to the raw URL bytes and caps at 8, so the mixed-client migration section is accurate as of today. - Docs claims against the pin: template file on first start, 2048-byte URL and 4096-byte auth bounds, no
enabledon override entries, override replaces globals, disabled globals need no derivable hostname. All hold. yaml_serderesolves to a single0.10.4on both sides. Startup placement is unconditional, asopen_or_createalready was on the base, so no new failure class on non-Gloas networks.
No correctness or safety defects found. The two items from the 08-27 review (builder_pubkeys bound, auth_data treated as a credential) are addressed at this head.
Three minor items, fixed in 9e0d3f65
- Startup errors redacted safe metadata along with the sensitive payload: parse errors lost the parser's line and column, open errors lost the
io::ErrorKind. Both are structural and cannot echo input, so they are now rendered while URLs, auth bytes, and parser messages stay out. - The docs told operators not to rely on Lighthouse
builder_storeomission logs being visible. That was true because the crate was missing from the logging allowlist, and one path had no other logger: the 200 ms request-auth sign timeout on a cache miss at proposal time, which dropped the builder silently.builder_storeis now on the allowlist (its only log sites areerror!) and the caveat is removed. blswas added to[dev-dependencies]while already a regular dependency.
Considered and dropped
- Entry cap versus the 8-root gossip budget under overrides: an override list replaces globals, so a validator resolves to at most 8 entries and at most 8 roots.
- The two-read window between the store load and this validation: unchanged from the earlier review; no Anchor writer, no runtime reload, and only a Lighthouse accessor over loaded definitions would close it. The module doc already names that as the deletion path for the wrapper.
Closes #1279. Based on
epbs, which now carries #1282 and #1285; this PR is the last piece of that stack. Deploy only the complete stack.Problem, Evidence, and Context
Lighthouse accepts up to 64 wire entries, but SSV permits only eight configured builders per validator. Its new per-validator overrides would bypass the original global-only Anchor validation. Excess auth roots can fragment signing quorum even when compilation and startup succeed.
Change Overview
Validate at startup before services run:
builder_storecrate is added to the logging allowlist, so a builder omitted at proposal time (for example a request-auth signing timeout on a cache miss) is logged rather than dropped silently.Risks, Trade-offs, and Mitigations
Reuse Lighthouse's exported definition types and YAML parser. A small private file wrapper remains because the public store API cannot enumerate every raw override. Validation rereads the file after the store loads it; this existing external-write race is documented, not claimed eliminated.
Startup errors preserve safe categories plus position and error-kind metadata without rendering credentials or auth data. A parser-generated regression test also prevents a second-read YAML error from reflecting a sensitive scalar while asserting the position survives. Signing logs record auth length rather than contents.
Document per-validator configuration, inheritance/disabling and the hostname-default migration. For mixed clients or rolling upgrades, configure explicit identical auth bytes on every operator. Explicitly configuring the standard hostname bytes is valid without negotiating a new custom token; arbitrary custom values still require builder agreement. SIP-94 commit 27fb510 aligns the normative default with this Lighthouse pin. That specification update does not replace mixed-client implementation and runtime verification.
Validation
cargo test -p client --lib builder_definitions::tests --locked: 11 passed.cargo test --release -p client --lib builder_definitions::tests --locked: 11 passed.cargo check --workspace --all-targets --locked,make cargo-fmt-check,make lint,make sort,make audit-CI,git diff --check: passed.No keymanager mutation route is exposed by Anchor, so file startup is the reachable configuration boundary. No runtime reload, auth normalization in the signer, batching, stateless production or envelope-fold implementation is added. See #1285 for the integrated local runtime results and their limitations.
Rollback
Removing this validation reopens the SSV entry-cap and silent-filtering failure modes. Do not deploy a partial revert with direct builders configured; use a previously validated complete build and compatible configuration. This PR adds no database migration; configuration edits apply on restart.
Blockers / Dependencies
#1282 and #1285 are merged. Release the complete stack together. Hosted CI must pass on the refreshed head. The local stub run is not proof of real-builder signature acceptance, bid selection, delivery or mixed-client interoperability.
🤖 Generated with Claude Code