diff --git a/specs/gloas/beacon-chain.md b/specs/gloas/beacon-chain.md index f10eaa3f5d..546d2d7651 100644 --- a/specs/gloas/beacon-chain.md +++ b/specs/gloas/beacon-chain.md @@ -194,6 +194,7 @@ class ExecutionPayloadBid(Container): parent_block_hash: Hash32 parent_block_root: Root block_hash: Hash32 + prev_randao: Bytes32 fee_recipient: ExecutionAddress gas_limit: uint64 builder_index: ValidatorIndex @@ -994,6 +995,7 @@ def process_execution_payload_bid(state: BeaconState, block: BeaconBlock) -> Non # Verify that the bid is for the right parent block assert bid.parent_block_hash == state.latest_block_hash assert bid.parent_block_root == block.parent_root + assert bid.prev_randao == get_randao_mix(state, get_current_epoch(state)) # Record the pending payment if there is some payment if amount > 0: @@ -1310,6 +1312,7 @@ def process_execution_payload( committed_bid = state.latest_execution_payload_bid assert envelope.builder_index == committed_bid.builder_index assert committed_bid.blob_kzg_commitments_root == hash_tree_root(envelope.blob_kzg_commitments) + assert committed_bid.prev_randao == payload.prev_randao # Verify the withdrawals root assert hash_tree_root(payload.withdrawals) == state.latest_withdrawals_root @@ -1320,8 +1323,6 @@ def process_execution_payload( assert committed_bid.block_hash == payload.block_hash # Verify consistency of the parent hash with respect to the previous execution payload assert payload.parent_hash == state.latest_block_hash - # Verify prev_randao - assert payload.prev_randao == get_randao_mix(state, get_current_epoch(state)) # Verify timestamp assert payload.timestamp == compute_time_at_slot(state, state.slot) # Verify commitments are under limit diff --git a/tests/core/pyspec/eth2spec/test/gloas/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/gloas/block_processing/test_process_execution_payload.py index 39d260576d..b61c223aa4 100644 --- a/tests/core/pyspec/eth2spec/test/gloas/block_processing/test_process_execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/gloas/block_processing/test_process_execution_payload.py @@ -169,7 +169,7 @@ def prepare_execution_payload_envelope( ) -def setup_state_with_payload_bid(spec, state, builder_index=None, value=None): +def setup_state_with_payload_bid(spec, state, builder_index=None, value=None, prev_randao=None): """ Helper to setup state with a committed execution payload bid. This simulates the state after process_execution_payload_bid has run. @@ -180,12 +180,16 @@ def setup_state_with_payload_bid(spec, state, builder_index=None, value=None): if value is None: value = spec.Gwei(0) + if prev_randao is None: + prev_randao = spec.get_randao_mix(state, spec.get_current_epoch(state)) + # Create and set the latest execution payload bid kzg_list = spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK]() bid = spec.ExecutionPayloadBid( parent_block_hash=state.latest_block_hash, parent_block_root=state.latest_block_header.hash_tree_root(), block_hash=spec.Hash32(), + prev_randao=prev_randao, fee_recipient=spec.ExecutionAddress(), gas_limit=spec.uint64(60000000), builder_index=builder_index, @@ -837,6 +841,38 @@ def test_process_execution_payload_wrong_prev_randao(spec, state): yield from run_execution_payload_processing(spec, state, signed_envelope, valid=False) +@with_gloas_and_later +@spec_state_test +@always_bls +def test_process_execution_payload_bid_prev_randao_mismatch(spec, state): + """ + Test that committed_bid.prev_randao must equal payload.prev_randao + """ + proposer_index = spec.get_beacon_proposer_index(state) + # Use a different validator as builder + builder_index = (proposer_index + 1) % len(state.validators) + make_validator_builder(spec, state, builder_index) + + # Setup bid with one prev_randao value + bid_prev_randao = spec.Bytes32(b"\x11" * 32) + setup_state_with_payload_bid( + spec, state, builder_index, spec.Gwei(2300000), prev_randao=bid_prev_randao + ) + + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.block_hash = state.latest_execution_payload_bid.block_hash + execution_payload.gas_limit = state.latest_execution_payload_bid.gas_limit + execution_payload.parent_hash = state.latest_block_hash + # Set payload with a different prev_randao value + execution_payload.prev_randao = spec.Bytes32(b"\x22" * 32) + + signed_envelope = prepare_execution_payload_envelope( + spec, state, builder_index=builder_index, execution_payload=execution_payload + ) + + yield from run_execution_payload_processing(spec, state, signed_envelope, valid=False) + + @with_gloas_and_later @spec_state_test @always_bls diff --git a/tests/core/pyspec/eth2spec/test/gloas/block_processing/test_process_execution_payload_bid.py b/tests/core/pyspec/eth2spec/test/gloas/block_processing/test_process_execution_payload_bid.py index 533e248304..bddac4fde3 100644 --- a/tests/core/pyspec/eth2spec/test/gloas/block_processing/test_process_execution_payload_bid.py +++ b/tests/core/pyspec/eth2spec/test/gloas/block_processing/test_process_execution_payload_bid.py @@ -47,6 +47,7 @@ def prepare_signed_execution_payload_bid( gas_limit=None, block_hash=None, blob_kzg_commitments_root=None, + prev_randao=None, valid_signature=True, ): """ @@ -88,10 +89,14 @@ def prepare_signed_execution_payload_bid( kzg_list = spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK]() blob_kzg_commitments_root = kzg_list.hash_tree_root() + if prev_randao is None: + prev_randao = spec.get_randao_mix(state, spec.get_current_epoch(state)) + bid = spec.ExecutionPayloadBid( parent_block_hash=parent_block_hash, parent_block_root=parent_block_root, block_hash=block_hash, + prev_randao=prev_randao, fee_recipient=fee_recipient, gas_limit=gas_limit, builder_index=builder_index, @@ -821,3 +826,30 @@ def test_process_execution_payload_bid_wrong_parent_block_root(spec, state): block.body.signed_execution_payload_bid = signed_bid yield from run_execution_payload_bid_processing(spec, state, block, valid=False) + + +@with_gloas_and_later +@spec_state_test +def test_process_execution_payload_bid_wrong_prev_randao(spec, state): + """ + Test wrong prev_randao fails (bid.prev_randao != get_randao_mix) + """ + proposer_index = spec.get_beacon_proposer_index(state) + + # Create block first to advance slot + block = build_empty_block_for_next_slot(spec, state) + + # Create bid with wrong prev_randao + wrong_prev_randao = spec.Bytes32(b"\x42" * 32) + signed_bid = prepare_signed_execution_payload_bid( + spec, + state, + builder_index=proposer_index, + slot=block.slot, + parent_block_root=block.parent_root, + prev_randao=wrong_prev_randao, + ) + + block.body.signed_execution_payload_bid = signed_bid + + yield from run_execution_payload_bid_processing(spec, state, block, valid=False) diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index b0c9c4f62a..545594dd2e 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -323,10 +323,12 @@ def build_empty_post_gloas_execution_payload_bid(spec, state): # to distinguish it from the genesis block hash and have # is_parent_node_full correctly return False empty_payload_hash = spec.Hash32(b"\x01" + b"\x00" * 31) + prev_randao = spec.get_randao_mix(state, spec.get_current_epoch(state)) return spec.ExecutionPayloadBid( parent_block_hash=state.latest_block_hash, parent_block_root=parent_block_root, block_hash=empty_payload_hash, + prev_randao=prev_randao, fee_recipient=spec.ExecutionAddress(), gas_limit=spec.uint64(0), builder_index=builder_index, @@ -362,15 +364,16 @@ def build_empty_execution_payload(spec, state, randao_mix=None): if is_post_gloas(spec): latest = state.latest_execution_payload_bid parent_hash = latest.parent_block_hash + if randao_mix is None: + randao_mix = state.latest_execution_payload_bid.prev_randao else: latest = state.latest_execution_payload_header parent_hash = latest.block_hash + if randao_mix is None: + randao_mix = spec.get_randao_mix(state, spec.get_current_epoch(state)) timestamp = spec.compute_time_at_slot(state, state.slot) empty_txs = spec.List[spec.Transaction, spec.MAX_TRANSACTIONS_PER_PAYLOAD]() - if randao_mix is None: - randao_mix = spec.get_randao_mix(state, spec.get_current_epoch(state)) - payload = spec.ExecutionPayload( parent_hash=parent_hash, fee_recipient=spec.ExecutionAddress(),