Repository navigation
feat(rpc): a builder API client (getHeader, registerValidator, submitBlindedBlockV2) - #695
MegaRedHand wants to merge 7 commits into
Conversation
Registrations and bids are signed under DOMAIN_APPLICATION_BUILDER at the genesis fork version and a zero validators root, the way deposits are. The known-answer test pins mainnet's value, so a call that used get_domain's current fork and real validators root instead would fail here rather than at every relay.
The first builder API call the produceBlockV3 builder path needs. One client per URL, SSZ first, and JSON for good once the URL refuses SSZ; responses are read by their own content type, since relays commonly answer JSON without refusing. An SSZ bid must name its fork, and only electra and fulu bids are accepted, as separate errors so the caller can count them. The mock relay is what the produceBlockV3 tests will drive next.
A 406 no longer retries getHeader, since a retry restarts the clock the proposer is waiting on; the next call asks for JSON instead. Answers are capped at 8 MiB and kept error text at 1 KiB, a malformed URL fails when the client is built, and transport errors drop the URL so credentials in it cannot reach a log. The mock relay now leaves Eth-Consensus-Version off JSON answers, as MEV-Boost does.
Url::parse reads `localhost:18550` as scheme `localhost`, so a missing `http://` would only surface as an unreachable builder at the first proposal. The truncation test now checks the cut length exactly, so an empty body would fail it.
registerValidator in one JSON request, keeping the builder's 400 message so the Beacon API can pass it through. JSON because MEV-Boost forwards a registration's body to every relay as is and reports success if any accepts it, so SSZ would be lost on the relays that do not take it. The mock relay verifies registration signatures under the builder domain, the same check a real relay makes.
submitBlindedBlockV2, the last fulu call. The builder publishes the full block, so only its 202 counts as success. SSZ goes first, since MEV-Boost converts it for relays that lack SSZ; a relay used directly that refuses it gets the block again in JSON, an immediate retry that costs the proposer nothing.
The module doc still described one SSZ-until-refused policy, which the review fixes replaced with a choice per call. The mock relay's doc now covers registrations and submissions, and BuilderError no longer claims every error becomes a 500.
🤖 Claude Code ReviewReview of PR 695: builder API client and builder signing domainThe change is well scoped, and nothing in it constructs the client yet, so it can't affect a running node. I read the diff only; I did not build it or run the tests. Looks good
Points to consider
No blocking issues. Point 1 is the most important to settle in the follow-up PR. Automated review by Claude (Anthropic) · sonnet · custom prompt |
🤖 Codex Code Review
Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
🤖 Kimi Code ReviewI'll review this PR which adds a builder API client for MEV-Boost/relay communication. Let me analyze the code systematically. Overall AssessmentThis is a well-structured PR adding builder-specs client functionality with good test coverage. However, I've identified several issues ranging from minor correctness problems to potential security and reliability concerns. Security & Correctness Issues1. Race condition in
|
| Priority | Item | File | Line | Issue |
|---|---|---|---|---|
| High | 1 | mock_relay.rs |
199-201 | Race condition in header() handler |
| High | 6 | mod.rs |
324-340 | Unbounded chunk size in read_body |
| Medium | 5 | error.rs |
52-59 | Timeout classification ambiguity |
| Medium | 3 | mod.rs |
399-402 | unix_millis() clock skew handling |
| Medium | 13 | Cargo.toml |
— | reqwest feature bloat |
| Low | 9 | signing.rs |
79-80 | Documentation typo ("gloas's") |
| Low | 10 | tests.rs |
35-48 | Confusing zero pubkey in test helper |
| Low | 11 | mod.rs |
158-160 | URL path construction fragile |
| Low | 16 | mock_relay.rs |
12 | Comment grammar |
Automated review by Kimi (Moonshot AI) · kimi-k2.6 · custom prompt
Second step of MEV-Boost builder support: an HTTP client for the builder API, and the builder signing domain. Stacked on #694. Nothing constructs the client yet; the next PR adds
--builderand wires it intoproduceBlockV3,register_validatorandblinded_blocks.What it adds
BuilderClient:get_header,register_validators,submit_blinded_block;VersionedBid;BuilderErrorrpc/src/beacon/builder_client/(new), exported fromethlambda_rpccompute_builder_domain(genesis_fork_version), with a mainnet known-answer testtypes/src/beacon/signing.rslean_beacon_builder_request_seconds{call, outcome}rpc/src/metrics.rs,docs/metrics.mdreqwest(the workspace pin, rustls) andthiserroras direct dependenciesrpc/Cargo.toml#[cfg(test)]axum mock relay, reused by the next PR's endpoint testsbuilder_client/mock_relay.rsEncoding, call by call
Each call picks its encoding from how MEV-Boost actually handles it:
getHeaderContent-Type. A 406 fails that call with no retry; later calls ask for JSONregisterValidatorsubmitBlindedBlockV2Strict choices and bounds
Eth-Consensus-Versionis an error, since builder-specs requires the header on SSZ answers.UnsupportedForkerror, so the caller can count it.submitBlindedBlockV2succeeds on 202 only.getHeaderuses the configured header timeout, which is also sent asX-Timeout-MsalongsideDate-Milliseconds.httpandhttpsbuilder URLs are accepted, which catches a missing scheme at startup.Tests
The client has 27 tests against the mock relay. The mock relay:
Accept, answering slowly, leaving out headers, or giving a chosen status.make lintpasses, as do theethlambda-rpcandethlambda-typeslib tests.