Repository navigation
feat(beacon): --builder and register_validator - #696
MegaRedHand wants to merge 6 commits into
Conversation
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.
🤖 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 |
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>BuilderClientat startup, so a malformed URL stops the node there. A one-offGET /eth/v1/builder/statusruns in the background and only warns, since the builder may come up after the node does. The user agent is the node'sCLIENT_VERSION--builder-header-timeout <ms>1000, at least1, requires--builderPOST /eth/v1/validator/register_validatorlean_beacon_builder_registrations_total{outcome}forwarded,failed,dropped; pluscall="status"onlean_beacon_builder_request_secondsregister_validatorepoch < 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 forward, so they hold even when the builder fails (as Lighthouse does).[]out to every relay.--builderthe answer is 500, so a validator client configured for a builder hears that it has none (as in Lighthouse and Lodestar);prepare_beacon_proposer.Tests
The tests run against the mock relay from #695:
make lintpasses, as do the rpc lib tests and the binary's tests.