diff --git a/specs/gloas/beacon-chain.md b/specs/gloas/beacon-chain.md index 6b3b40fc99..de35a52f76 100644 --- a/specs/gloas/beacon-chain.md +++ b/specs/gloas/beacon-chain.md @@ -11,6 +11,7 @@ - [Domains](#domains) - [Misc](#misc) - [Withdrawal prefixes](#withdrawal-prefixes) + - [Builder versions](#builder-versions) - [Execution-layer triggered requests](#execution-layer-triggered-requests) - [Preset](#preset) - [Misc](#misc-1) @@ -174,6 +175,12 @@ future validator withdrawal prefix may reuse this value. | --------------------------- | ---------------- | ------------------------------------------ | | `BUILDER_WITHDRAWAL_PREFIX` | `Bytes1('0x03')` | Withdrawal credential prefix for a builder | +### Builder versions + +| Name | Value | Description | +| ------------------------- | ---------- | ---------------------------------------- | +| `PAYLOAD_BUILDER_VERSION` | `uint8(0)` | Version for an execution payload builder | + ### Execution-layer triggered requests | Name | Value | @@ -1538,6 +1545,8 @@ def process_execution_payload_bid( else: # Verify that the builder is active assert is_active_builder(state, builder_index) + # Verify that the builder is a payload builder + assert state.builders[builder_index].version == PAYLOAD_BUILDER_VERSION # Verify that the builder has funds to cover the bid assert can_builder_cover_bid(state, builder_index, amount) # Verify that the bid signature is valid diff --git a/specs/gloas/builder.md b/specs/gloas/builder.md index d033fbfdb2..2f3edd8b45 100644 --- a/specs/gloas/builder.md +++ b/specs/gloas/builder.md @@ -44,7 +44,8 @@ deposit contract on the execution layer, as defined in - `pubkey`: The builder's BLS public key. - `withdrawal_credentials`: The withdrawal credentials, where the first byte is the builder version and the last 20 bytes are the execution-layer address that - will receive withdrawals. + will receive withdrawals. For the version, execution payload builders should + use `PAYLOAD_BUILDER_VERSION`. - `amount`: At least `MIN_DEPOSIT_AMOUNT` gwei. - `signature`: BLS proof of possession over the corresponding `DepositMessage` under `DOMAIN_BUILDER_DEPOSIT`. diff --git a/specs/gloas/fork.md b/specs/gloas/fork.md index 1083af7ee1..6705da9458 100644 --- a/specs/gloas/fork.md +++ b/specs/gloas/fork.md @@ -106,7 +106,7 @@ def onboard_builders_from_pending_deposits(state: BeaconState) -> None: add_builder_to_registry( state, deposit.pubkey, - uint8(deposit.withdrawal_credentials[0]), + PAYLOAD_BUILDER_VERSION, ExecutionAddress(deposit.withdrawal_credentials[12:]), deposit.amount, deposit.slot, diff --git a/specs/gloas/p2p-interface.md b/specs/gloas/p2p-interface.md index 69c3ec7a9b..ff56e5c599 100644 --- a/specs/gloas/p2p-interface.md +++ b/specs/gloas/p2p-interface.md @@ -369,6 +369,8 @@ where `parent_state` is the post-state of `bid.parent_block_root`, and the alias - _[IGNORE]_ The matching `signed_proposer_preferences` has been seen. - _[REJECT]_ `bid.builder_index` is a valid/active builder index -- i.e. `is_active_builder(state, bid.builder_index)` returns `True`. +- _[REJECT]_ The builder version is `PAYLOAD_BUILDER_VERSION` -- i.e. + `state.builders[bid.builder_index].version == PAYLOAD_BUILDER_VERSION`. - _[REJECT]_ `bid.execution_payment == 0`. - _[REJECT]_ `bid.fee_recipient == proposer_preferences.fee_recipient`. - _[REJECT]_ The length of KZG commitments is less than or equal to the diff --git a/tests/core/pyspec/eth_consensus_specs/test/gloas/block_processing/test_process_execution_payload_bid.py b/tests/core/pyspec/eth_consensus_specs/test/gloas/block_processing/test_process_execution_payload_bid.py index bb34872720..ba2cdd8417 100644 --- a/tests/core/pyspec/eth_consensus_specs/test/gloas/block_processing/test_process_execution_payload_bid.py +++ b/tests/core/pyspec/eth_consensus_specs/test/gloas/block_processing/test_process_execution_payload_bid.py @@ -268,6 +268,43 @@ def test_process_execution_payload_bid_inactive_builder_exiting(spec, state): 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_non_payload_builder_version(spec, state): + """ + Test that a bid from a builder whose version is not PAYLOAD_BUILDER_VERSION fails. + """ + next_epoch_with_full_participation(spec, state) + next_epoch_with_full_participation(spec, state) + next_epoch_with_full_participation(spec, state) + next_epoch_with_full_participation(spec, state) + assert state.finalized_checkpoint.epoch == 2 + + block, builder_index = prepare_block_with_non_proposer_builder(spec, state) + assert spec.is_active_builder(state, builder_index) + + # Mark the builder as a non-payload builder, leaving every other condition valid + state.builders[builder_index].version = spec.uint8(spec.PAYLOAD_BUILDER_VERSION + 1) + assert state.builders[builder_index].version != spec.PAYLOAD_BUILDER_VERSION + + # The builder can cover the bid, so the version check is the only failing condition + value = spec.Gwei(1000000) # 0.001 ETH + assert spec.can_builder_cover_bid(state, builder_index, value) + + signed_bid = prepare_signed_execution_payload_bid( + spec, + state, + builder_index=builder_index, + value=value, + slot=block.slot, + parent_block_root=block.parent_root, + ) + + 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_self_build_non_zero_value(spec, state): diff --git a/tests/core/pyspec/eth_consensus_specs/test/gloas/fork/test_gloas_fork_onboard_builders.py b/tests/core/pyspec/eth_consensus_specs/test/gloas/fork/test_gloas_fork_onboard_builders.py index 1e85289516..8773c52002 100644 --- a/tests/core/pyspec/eth_consensus_specs/test/gloas/fork/test_gloas_fork_onboard_builders.py +++ b/tests/core/pyspec/eth_consensus_specs/test/gloas/fork/test_gloas_fork_onboard_builders.py @@ -120,6 +120,30 @@ def test_fork_single_builder_deposit(spec, phases, state): assert len(post_state.pending_deposits) == 0 +@with_phases(phases=[FULU], other_phases=[GLOAS]) +@spec_test +@with_state +@with_meta_tags(GLOAS_FORK_TEST_META_TAGS) +def test_fork_builder_deposit_version(spec, phases, state): + """ + Test that a builder onboarded at the fork is registered as a payload builder. + """ + post_spec = phases[GLOAS] + amount = post_spec.MIN_DEPOSIT_AMOUNT + + # Create a pending deposit with builder credentials + builder_pubkey = builder_pubkeys[0] + pending_deposit = create_pending_deposit_for_builder(post_spec, builder_pubkey, amount) + state.pending_deposits = [pending_deposit] + + post_state = yield from run_fork_test(post_spec, state) + + # The onboarded builder is registered with the payload builder version + assert len(post_state.builders) == 1 + assert post_state.builders[0].pubkey == builder_pubkey + assert post_state.builders[0].version == post_spec.PAYLOAD_BUILDER_VERSION + + @with_phases(phases=[FULU], other_phases=[GLOAS]) @spec_test @with_state