diff --git a/beacon_node/beacon_chain/src/payload_bid_verification/gossip_verified_bid.rs b/beacon_node/beacon_chain/src/payload_bid_verification/gossip_verified_bid.rs index 851bf821de7..8ee17fdedca 100644 --- a/beacon_node/beacon_chain/src/payload_bid_verification/gossip_verified_bid.rs +++ b/beacon_node/beacon_chain/src/payload_bid_verification/gossip_verified_bid.rs @@ -432,9 +432,15 @@ impl GossipVerifiedPayloadBid { // [REJECT] `bid.prev_randao` is the correct RANDAO mix -- i.e. validate that // `bid.prev_randao == get_randao_mix(parent_state, get_current_epoch(parent_state))` - if signed_bid.message.prev_randao - != *head_state.get_randao_mix(current_slot.epoch(E::slots_per_epoch()))? - { + // TODO: Enable head-parent RANDAO gossip spec coverage: + // https://github.com/ethereum/consensus-specs/pull/5645 + let expected_randao = if bid_parent_block_root == cached_head.head_block_root() { + cached_head.head_random()? + } else { + // Head compatibility also permits a bid on the head's parent. + cached_head.parent_random()? + }; + if signed_bid.message.prev_randao != expected_randao { return Err(PayloadBidError::InvalidPrevRandao { slot: bid_slot }); } diff --git a/beacon_node/beacon_chain/src/payload_bid_verification/tests.rs b/beacon_node/beacon_chain/src/payload_bid_verification/tests.rs index 4a7d4f2c217..329fe024cc1 100644 --- a/beacon_node/beacon_chain/src/payload_bid_verification/tests.rs +++ b/beacon_node/beacon_chain/src/payload_bid_verification/tests.rs @@ -1111,6 +1111,42 @@ fn valid_bid_after_empty_genesis_uses_parent_payload_gas_limit() { ); } +#[test] +fn valid_bid_with_parent_in_previous_epoch() { + if !fork_name_from_env().is_some_and(|fork| fork.gloas_enabled()) { + return; + } + let epoch_start = E::slots_per_epoch(); + for (current_slot, bid_slot) in [ + (epoch_start - 1, epoch_start), + (epoch_start, epoch_start), + (epoch_start + 1, epoch_start + 1), + ] { + let ctx = TestContext::new(); + ctx.slot_clock.set_slot(current_slot); + let slot = Slot::new(bid_slot); + seed_preferences(&ctx, slot, Address::ZERO, 30_000_000); + let parent_state = &ctx.canonical_head.cached_head().snapshot.beacon_state; + let bid = ctx.sign_bid(ExecutionPayloadBid { + slot, + builder_index: 0, + fee_recipient: Address::ZERO, + gas_limit: 30_000_000, + parent_block_root: ctx.genesis_block_root, + parent_block_hash: ctx.execution_parent_hash(), + prev_randao: *parent_state + .get_randao_mix(parent_state.current_epoch()) + .unwrap(), + ..ExecutionPayloadBid::default() + }); + let result = GossipVerifiedPayloadBid::new(bid, &ctx.gossip_ctx()); + assert!( + result.is_ok(), + "clock {current_slot}, bid {bid_slot}: {result:?}" + ); + } +} + #[test] fn two_builders_coexist_in_cache() { if !fork_name_from_env().is_some_and(|f| f.gloas_enabled()) {