Repository navigation
feat(rpc): honor skip_randao_verification on block production - #678
MegaRedHand wants to merge 31 commits into
Conversation
🤖 Kimi Code ReviewI'll review this PR which adds Security & CorrectnessCritical:
|
| Priority | Issue | Location |
|---|---|---|
| Medium | Inconsistent HTTP error status for invalid infinity reveal (fulu: 500, gloas: 400) | proposal.rs vs gloas_proposal.rs |
| Low | assemble_gloas_block_on_bid lacks infinity reveal test |
gloas_block_production.rs |
| Low | BlockProcessingOptions should be #[non_exhaustive] |
stf/block.rs:209 |
| Low | randao_verification naming |
proposal.rs:284 |
| Nit | super::super:: test import path |
proposal.rs:636 |
The security-critical design is correct: state_transition always verifies, production paths can skip with proper validation. No critical bugs found.
Automated review by Kimi (Moonshot AI) · kimi-k2.6 · custom prompt
🤖 Codex Code ReviewLooks good overall. The change is narrowly scoped, preserves the default verification path, and adds good tests around the new flag behavior.
Findings
Notes
If you want, I can turn this into a ready-to-post GitHub review comment. Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
🤖 Claude Code ReviewReview: PR 678, honor
|
…-64-633-636-638-gloas-live Also brings #669's sync with beacon-chain-integration (#645, #675). Semantic resolutions: - #665's `active_balances` and #678's `options` both kept on every `process_block`, `options` last; `assemble_*` keep tmp's inputs (operations, sync aggregate) plus `options`. - fork_choice `CountingView` test impl takes #658 as it landed on bci (forwards `contains`). - #678's v3 tests use tmp's fixtures: `SharedSyncCommitteePool`, `OwnVersion`, `fake_execution_client(0)`. - p2p scoring test sets `SwarmConfig::agent_version` (#675).
…ences, from JSON `Builder` serialized `version`, `balance`, `deposit_epoch` and `withdrawable_epoch` as bare numbers, which the Beacon API wants quoted (this also affected the gloas state's JSON). Give it the same `quoted_or_bare` adapters the other containers use, and `Deserialize`, along with the proposer preferences containers a builder-market endpoint takes in a request body. Round-trip tests cover the bid, the preferences and the builder, and the example bid and preferences events from beacon-APIs' event stream parse.
…and in parallel Adds the shared `BuilderMarket`, the `execution_payload_bid` and `proposer_preferences` gossip rule modules, the p2p triage, verdict and publish plumbing, the `RpcToP2P` publish methods, and the Beacon API route modules, all with the final signatures and placeholder bodies: every rule answers `Ignore(NoConsumer)`, the market holds nothing, and no route is registered yet. Splitting the wiring from the logic lets the rules and market, the p2p handling and the Beacon API be filled in on separate branches without touching each other's files. New reason variants, `Validated` arms and `RpcToP2P` methods sit beside the envelope ones, and everything else is a new file, so a parallel sync committee branch merges cleanly.
Bids, proposer preferences and revealed execution payloads are judged and consumed from three places (gossip rules on blocking threads, the Beacon API and block production), so they live in one shared object rather than in the chain actor. The market keeps the spec's seen sets and best-bid bar apart from the pooled bids: the pool is truncated to the top values per parent, but the bar a new bid must strictly beat is not. Known payloads are a bounded LRU, and every collection is bounded so spam cannot grow memory. Also adds the test-utils fixtures (builder registry state, bid and preference signing, envelopes) the gossip rules, p2p and rpc tests build on.
… blocks on a bid Gives the builder market its rules: the spec's execution_payload_bid and proposer_preferences gossip validation, split into cheap and stateful halves like the other topics, plus block assembly on a builder's bid. The rules read cached states only and never queue, so a verdict gossipsub waits on is never stalled by a replay. Two shortcuts keep that honest: the parent's own state stands in for the advanced one within its epoch (gloas's process_slot touches none of the fields the later rules read), and a proposer's lookahead comes from the head state when it shares the dependent root, since a dependent block about an epoch old is usually out of the state cache. Preference signatures verify under the fork versions on both sides of the gloas boundary, because clients disagree there. assemble_gloas_block_on_bid and bid_is_includable let produceBlockV4 commit a block to another builder's bid; the pre-filter keeps a bid that would fail process_block from being tried. Both gossip vector handlers now run (44 and 26 mainnet cases pass). The runner records a delivered envelope the way the spec generators do, without re-verifying it, and applies the fixture's finalized-checkpoint override to the stored states, which is how those generators activate builders. docs/spec_deviations.md lists what departs from the spec.
…nces Subscribe the execution_payload_bid and proposer_preferences topics under gloas digests only, decode them, triage them (size cap, decode, cheap rules) and hand the rest to the blocking pool on a permit pool of their own so a bid burst cannot starve blocks, columns or attestations. Neither type reaches the chain actor; accepted ones are recorded in the shared builder market. Envelopes accepted from gossip, and envelopes this node publishes itself, are recorded as known payloads, since bid validation reads them and gossip never echoes a node's own message. Bids and preferences are published on the digest of their own slot, so preferences for the first gloas epoch go out on the gloas digest during the epoch before the fork.
An engine that wants its own payload used whatever a builder pays says so with this flag; dropping it would let a bid comparison override that.
Add POST execution_payload_bids, POST proposer_preferences and POST
states/{id}/builders. Bids and preferences run through the gossip rules and
land in the shared builder market that p2p also fills.
produceBlockV4 now reads the real BuilderConfig, builds locally when it has an
engine and takes the best pooled bid when the config's min_bid and boost factor
say so (the local payload wins ties, and shouldOverrideBuilder keeps it). A
bid-won block comes back bare and caches nothing, since the builder reveals the
payload. A self-built payload takes its fee recipient and gas target from the
proposer's signed preferences when the market holds them.
…o end Cover the three endpoints, the verdicts they turn into 400s, idempotent resubmission, SSZ bodies, produceBlockV4 choosing between a stand-in engine's payload and a pooled bid, the preferences-driven fee recipient and gas target, and the envelope endpoints' behaviour for a block built on a bid.
CLAUDE.md still said gloas added two topics and that duties were self-build only.
The spec ignores a bid whose value does not exceed the best recorded for the same (slot, parent hash, parent root), so a value-10 bid after a value-10 one answered NotHighestBid, not Accept. The fixture was wrong; the rule is spec.
…zed epoch The scene put a funded, active builder in an epoch-1 state: it is active only once the finalized epoch passes its deposit epoch, but a chain never finalizes past the previous epoch, so no real chain holds that state. Advancing it through the end of epoch 1 underflows get_previous_epoch - finalized epoch, which the spec treats as invalid and a stricter get_finality_delay now errors on. scene_at builds the parent in a later epoch by advancing with nothing finalized and finalizing epoch 1 afterwards. The epoch-crossing bid test uses it with its slots and epochs shifted by one epoch; its assertions are unchanged.
The bid and preferences scenes hardcoded mainnet slots (parent 32, bid 34, epochs at 64 and 96), but their base state sits at SLOTS_PER_EPOCH. On the minimal preset that is slot 8, so every bid slot fell outside the parent's two-epoch proposer lookahead and 43 tests panicked or hit the wrong rule. Express each slot through SLOTS_PER_EPOCH and the scene's own PARENT_SLOT, as the other gossip tests do, and place the stray and stateless dependent blocks before the dependent slot so they reach the rule they test.
beacon-APIs defines `skip_randao_verification` on produceBlockV3 and produceBlockV4 as a flag with no value that asks the node to build on the point-at-infinity reveal without checking it. We accepted the flag on v4 and ignored it, and v3 did not read it, so a request carrying it with the infinity reveal was refused (v4: 400 `invalid randao_reveal`, v3: 500), where Lighthouse and Prysm build the block. The beacon state transition now takes a `BlockProcessingOptions` whose `RandaoVerification` lets block production mix in a reveal it did not check. `state_transition` always passes the default, so import still verifies every reveal, and every other check on a produced block stays. Parsing follows Lighthouse: a value on the flag, or the flag with a reveal other than the point at infinity, is a 400.
4100d21 to
1c36c33
Compare
…verride The builder market gossip vectors finalize epoch 1 through the runner's finalized_checkpoint override, then walk the head from the justified root. Since main's #627, moving finality prunes LiveChain below the finalized block, which left the genesis justified checkpoint pointing at a pruned row: every bid and preferences case failed with "computing the head: SpecAssert(\"block_root in store.blocks\")". A store's justified checkpoint is never behind its finalized one, so for those cases the override now moves it up too.
…rification Brings in main (the gloas stack) through the parent, which also now holds #669 as a squash. Merged against that squash as the base, since this branch already carries feat/beacon-gloas-builder-market, whose tree the squash matches: no conflicts.
# Conflicts: # crates/net/rpc/src/beacon/gloas_proposal.rs
# Conflicts: # crates/net/rpc/src/beacon/gloas_proposal.rs # crates/net/rpc/src/beacon/proposal.rs
# Conflicts: # crates/net/rpc/src/beacon/builder_market_tests.rs
Why
beacon-APIs (
v5.0.0-beta.0) definesskip_randao_verificationon produceBlockV3 and produceBlockV4 as a flag with no value: the caller sends the point-at-infinity reveal and asks the node not to check it. We accepted the flag on v4 and ignored it, and v3 did not read it, so a request carrying it was refused (v4: 400invalid randao_reveal, v3: 500the block failed to build). Lighthouse and Prysm build the block. The assertoorgloas-api-checkplaybook (patched to v5.0.0-beta.0) hit this: lighthouse and prysm answered 200, ethlambda 400.What
skip_randao_verification/skip_randao_verification=+ infinity reveal=true, ...)skip_randao_verification does not take a valuerandao_reveal must be the point at infinity when skip_randao_verification is setParsing follows Lighthouse (
get_randao_verification), which matches the spec's wording; Prysm instead accepts=trueand silently replaces any reveal with infinity.BlockProcessingOptions { randao: RandaoVerification }(stf/block.rs), last parameter of theprocess_blockdispatcher, every fork'sprocess_blockandprocess_randao.Skiponly drops the reveal's BLS check; the mix is updated as before.state_transitionalways passes the default, so import never skips.assemble_block,assemble_gloas_block,assemble_gloas_block_on_bidtake the options and forward them.randao_verificationhelper inbeacon/proposal.rs, called right after the query parses (a 400 on any node, ahead of the engine check). v4 still gives its earlyinvalid randao_revealwhen not skipping.Tests
Skipmixes in the infinity reveal;Verifyrejects it.Skip, and re-applying the block withSkiplands on itsstate_root.Ran:
cargo test --profile release-fast --libforethlambda-state-transition(548 passed) andethlambda-rpc(238 passed), clippy-D warningson both,cargo fmt --check. The beacon spec-test suites were not run locally.Open question
v3 without the flag still answers 500 for a reveal that does not verify (the STF rejects it), where v4 answers 400 early. Not changed here.