Repository navigation
feat(beacon): --builder and register_validator - #696
MegaRedHand wants to merge 10 commits into
Conversation
🤖 Kimi Code ReviewI'll review this PR which adds builder API integration (MEV-Boost support) for validator registrations. Let me analyze the code carefully. OverviewThis PR adds:
Issues Found1. Race Condition in Fee Recipients Update (Security/Correctness)File: {
let mut recipients = fee_recipients.lock().expect("fee recipient lock poisoned");
for (index, registration) in &kept {
recipients.insert(*index, registration.message.fee_recipient);
}
}
let kept: Vec<SignedValidatorRegistrationV1> = kept
.into_iter()
.map(|(_, registration)| registration)
.collect();The fee recipients are updated before the builder accepts the registrations. If the builder returns a 400 (bad signature), the fee recipients are still set (confirmed by test at line 442). This is intentional per the test, but the comment at line 15-16 says "a block built locally after all still pays the address the validator registered" — this is reasonable for liveness, but consider documenting this design choice more prominently since it affects validator rewards. However, there's a subtle issue: if two concurrent registration requests for the same validator arrive with different fee recipients, the lock protects the HashMap, but the 2. Unbounded
|
| Priority | Item | Action |
|---|---|---|
| Low | 6 | Clarify startup log about "until it does" vs per-proposal fallback |
| Low | 7 | Use as_ref() instead of clone() in startup check |
| Low | 2 | Document or bound fee recipient map growth |
| Style | — | Add comment explaining why fee recipients are set even on builder failure |
The code is generally well-structured and correct for consensus-layer considerations. No critical security vulnerabilities found.
Automated review by Kimi (Moonshot AI) · kimi-k2.6 · custom prompt
🤖 Claude Code ReviewReview of PR 696: builder flag and
|
🤖 Codex Code ReviewOverall this looks solid. The builder wiring is reasonably contained, validates URLs at startup, keeps request-size bounds in the client, and the registration handler’s “drop unknown/exited, don’t fail whole request” behavior matches the Beacon API intent well.
Potential issue:
Nit:
No other specific bugs found in the touched code. Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
23a5138 to
afba2c8
Compare
builder-specs' status endpoint, so the node can check its builder once at startup and a wrong --builder shows before the first proposal rather than as a missing bid at it.
The client is built at startup, so a malformed URL stops the node there, and its status is checked once in the background, warning when the builder is not up yet rather than failing a node that would only lose MEV until it is. --builder-header-timeout bounds the wait for a bid and is sent to the builder as X-Timeout-Ms.
A validator client's registrations reach the builder only for validators the head state holds as active or pending, as the beacon-APIs require; the rest are counted, not refused. Each kept registration also sets its validator's fee recipient, so a block that falls back to local building still pays the registered address. A builder's 400 is passed through and any other failure is a 500, so a misconfigured validator client hears its registrations went nowhere.
The head-state read and the walk over every validator now run on a blocking task, as the duty endpoints' walks do, and the forward to the builder runs on its own task so a validator client hanging up cannot cancel it. Exit is judged against the wall clock too, a malformed body is a 400 with or without a builder, the route takes the bodies a validator client sending all its keys at once needs, and a zero header timeout is refused.
The outcome counter and its log move into the task that outlives the request, and a test pins exit to the wall clock as well as the head.
afba2c8 to
19ba324
Compare
🤖 Kimi Code ReviewAutomated review by Kimi (Moonshot AI) |
🤖 Kimi Code ReviewAutomated review by Kimi (Moonshot AI) |
🤖 Claude Code ReviewReview: PR 696 —
|
🤖 Codex Code Review
Tests could not run because rustup attempted to write to a read-only directory. Automated review by OpenAI Codex · gpt-6.1-sol · custom prompt |
🤖 Gemini Code ReviewThis PR looks solid and well-tested. The CLI flag validation, startup One minor suggestion:
Automated review by Gemini · gemini-4-argon · custom prompt |
🤖 Claude Code ReviewReview: PR 696,
|
🤖 Codex Code Review
Tests could not run because Rust toolchain initialization requires writing outside the permitted filesystem. Automated review by OpenAI Codex · gpt-6.1-sol · custom prompt |
🤖 Gemini Code ReviewThis PR is clean, well-structured, and thoroughly tested. The CLI flag validation, startup builder status check, registration filtering against One minor suggestion:
Automated review by Gemini · gemini-4-argon · custom prompt |
🤖 DeepSeek Code ReviewAutomated review by DeepSeek |
🤖 Grok Code ReviewThe registration path is well structured. It decodes before the builder check, scans the registry once on
Automated review by Grok (xAI) · grok-4.7 · custom prompt |
🤖 Grok Code ReviewThe registration path updates local fee recipients from unverified input. That is a reward-theft bug on any beacon API that is not strictly VC-only. The rest of the wiring (CLI, status probe, exit filtering, metrics, tests) is in good shape.
Exit filtering itself looks right: Automated review by Grok (xAI) · grok-4.7 · custom prompt |
🤖 DeepSeek Code ReviewOverall, this PR is well-structured and test-heavy. The main blocker is a security-sensitive ordering issue in the new Critical
Minor / clarity
Automated review by DeepSeek · deepseek-v4-pro · custom prompt |
|
Security read of this PR (and the client it inherits from #695). No critical or high issues; the trust model is the same class as 1. Dedup registrations by pubkey before forwarding ( The route takes 2. Short-circuit an empty body
Smaller
Tests I'd add: duplicate pubkeys in one request; an empty Checked and clean: the builder URL never reaches errors or logs ( |
- Record fee recipients before looking for a builder, so they hold with no --builder too, as Lighthouse does. - Keep one registration per validator: its newest by timestamp, the later one on a tie, as builders order them. The rest are counted as `duplicate` in lean_beacon_builder_registrations_total. - Answer an empty list without reading the head state (still a 500 with no builder). - Stop following builder redirects unless --builder-follow-redirects is set. A refused redirect fails the call with an error naming its target, minus any user or password, and is counted as `redirected`. - Check --builder's URL while the flags are parsed. - Reword the startup status log: nothing waits on that probe. - Tests: duplicate registrations and timestamp ties, empty lists with and without a builder, a slashed validator that has not exited, the fee recipient after prepare_beacon_proposer and register_validator in turn, and redirects refused, followed and shown without credentials. Unreachable builders use a released local port instead of port 1, and the pending-validator test pins the wall clock to slot 0. - Note the per-call registry walk as a future improvement. Claude-Session: https://claude.ai/code/session_011vghNjQ8uikd9KL5Jo5at7
|
Thanks for the reviews. 6fb5e57 addresses most of them, and the updated description has the details:
The trust note is in #778. These are the points we did not take, and why. Verifying registration signatures before setting fee recipients (Codex, Grok, DeepSeek, Claude, Kimi)
A body limit of its own (pablodeymo, Claude, Kimi)
The registry walk on every call (Claude)
A 503 while the head lags (Grok) Timestamp checks beyond the dedup (Grok)
The startup probe (Claude, Kimi)
Doesn't apply
|
|
/review |
🤖 Gemini Code ReviewThis PR is clean, well-structured, and thoroughly tested. A few highlights of what looks good:
No issues found — LGTM. Automated review by Gemini · gemini-4-argon · custom prompt |
🤖 Claude Code ReviewReview of PR 696: builder registrations, redirect policy and status probeOverall this is well structured and tested. The redirect-off default, the credential-stripped redirect target, the dedup by timestamp and the Correctness and security
Minor / nits
TestsThe coverage is solid: dedup tie-break, wall-clock exit, pending and slashed validators, redirect credentials, and the fee-recipient interplay with Automated review by Claude · claude-opus-5-5 · custom prompt |
🤖 Codex Code Review
Tests could not run because rustup attempted to write to a read-only directory. Automated review by OpenAI Codex · gpt-6.1-sol · custom prompt |
🤖 Kimi Code Review
The CLI validation, secure default of refusing redirects, and otherwise extensive test coverage look good. Automated review by Kimi (Moonshot AI) · kimi-k3.1-pro · custom prompt |
🤖 Grok Code ReviewThe registration path and redirect defaults are in good shape: malformed bodies stay 400, redirects are off unless asked, credential stripping is tested, and the forward is detached so a client hang-up does not drop it. A few correctness and safety gaps are worth fixing before this is relied on for local block production.
That runs before the builder answers, and a later request with an older
The unbounded full-registry walk on every Automated review by Grok (xAI) · grok-4.7 · custom prompt |
🤖 DeepSeek Code ReviewOverall the builder URL handling, redirect policy, status probe, and registration endpoint are well documented and well tested. No consensus state-transition or SSZ changes are touched, so I don’t see fork-choice/finality issues in this PR. A few concerns and nits:
Automated review by DeepSeek · deepseek-v4-pro · custom prompt |
…alks Address the bot reviews of the register_validator change: - A redirect's Location is resolved against the URL that answered it and shown without user or password. A scheme-relative target such as `//user:pass@host/x` used to fail to parse and be shown raw, credentials included; one that does not resolve, or resolves to a URL with no host, is now a fixed placeholder, never the raw value. - --builder-follow-redirects follows only 307 and 308, up to ten hops, never from https to http. reqwest's default policy also followed 301, 302 and 303, which resend a POST as a body-less GET, and followed downgrades. A redirect not followed is stopped rather than errored, so it still surfaces as BuilderError::Redirected naming its target. - Only 301, 302, 303, 307 and 308 count as redirects; any other 3xx (304, 300) is an ordinary unexpected status. - register_validator's registry walks take a permit from a two-permit semaphore before spawn_blocking, so concurrent requests queue as suspended tasks instead of filling the blocking pool. The permit moves into the walk, so a client hanging up does not release it early. - BuilderClient's Debug is written out and strips --builder credentials. - Comment why an all-dropped request is a 500 without a builder and a 200 with one. Claude-Session: https://claude.ai/code/session_011vghNneoZ1Ta9W6JNqzjxm
|
Thanks for the second round. The changes are in f33971d. I checked each point against Lighthouse, Prysm, Teku, Lodestar and Nimbus. Changed
Checked, not changed
|
Third step of MEV-Boost builder support. The beacon node can now be pointed at a builder, and it serves
register_validator. Stacked on #695.Scope: block production is unchanged in this PR and still builds every block locally.
--builder-header-timeoutis parsed and stored, but nothing reads it until the next PR, which adds theproduceBlockV3builder path andblinded_blocks. The flag docs already describe that behavior.What it adds
--builder <URL>GET /eth/v1/builder/statusruns in the background and only logs: nothing waits on it, and every later call to the builder is tried on its own. The user agent is the node'sCLIENT_VERSION--builder-header-timeout <ms>1000, at least1, requires--builder--builder-follow-redirects--builder. See RedirectsPOST /eth/v1/validator/register_validatorlean_beacon_builder_registrations_total{outcome}forwarded,failed,dropped,duplicate; pluscall="status"andoutcome="redirected"onlean_beacon_builder_request_secondsregister_validator--builder, as any other body is.timestamp, the later one on a tie. That is the one a builder keeps (builder-specs'process_registration). The rest are counted asduplicate, so one key repeated through a large body is forwarded once.epoch < exit_epoch). The epoch is the later of the head's and the wall clock's.prepare_beacon_proposerdoes, so a block built locally still pays the registered address. They are written before the builder is even looked up, so they hold when the builder fails or refuses, and with no--builderat all. That is Lighthouse's behavior. Prysm, Teku, Lodestar, Nimbus and Grandine set the local fee recipient only fromprepare_beacon_proposer.[]out to every relay.--builderthe answer is 500, so a validator client configured for a builder hears that it has none (as in Lighthouse, Teku and Lodestar);prepare_beacon_proposer. docs(rpc): say what the HTTP servers trust their callers with #778 documents what the HTTP servers trust their callers with.Redirects
The builder client no longer follows redirects. #695's client followed up to ten, reqwest's default. Now a call goes only where
--builderpoints, so registrations and signed blocks cannot be carried somewhere else. A redirect (301, 302, 303, 307 or 308) fails the call withBuilderError::Redirected. Its message names the target and the flag, for example:The target is resolved against the request URL and shown without any user or password; one that cannot be resolved to a URL with a host is shown as a placeholder, never as sent.
--builder-follow-redirectsfollows 307 and 308 only, the redirects that keep the method and body, up to ten hops and never from https to http. A 301, 302 or 303 would resend a POST as a body-less GET, so it fails the call even with the flag. Lighthouse's pre-gloas builder client and Prysm's relay client follow with their libraries' defaults; Lighthouse's gloas client follows none.Tests
The tests run against the mock relay from #695, which can now also answer with a redirect:
prepare_beacon_proposerandregister_validatoroverwriting each other's fee recipient;--builderURL and--builder-follow-redirects.cargo fmt --checkand clippy (-D warnings, the rpc crate and the binary, all targets) are clean, and the rpc lib tests (387) and the binary's CLI tests pass.