diff --git a/specs/gloas/builder.md b/specs/gloas/builder.md index 462c4b2928..af465eede4 100644 --- a/specs/gloas/builder.md +++ b/specs/gloas/builder.md @@ -123,8 +123,11 @@ to include. They produce a `SignedExecutionPayloadBid` as follows. `get_proposer_dependent_root(parent_state, compute_epoch_at_slot(bid.slot))`, where `parent_state` is the post-state of `bid.parent_block_root`. 07. Set `bid.gas_limit` to be the gas limit of the constructed payload, which - must match the `gas_limit` in the `SignedProposerPreferences` referenced in - step 6. + **MUST** satisfy + `is_gas_limit_target_compatible(parent_gas_limit, bid.gas_limit, target_gas_limit)`, + where `parent_gas_limit` is the `gas_limit` of the parent execution payload + and `target_gas_limit` is the `target_gas_limit` in the + `SignedProposerPreferences` referenced in step 6. 08. Set `bid.builder_index` to be the index of the builder performing these actions. 09. Set `bid.slot` to be the slot for which this bid is aimed. This slot diff --git a/specs/gloas/fork.md b/specs/gloas/fork.md index c1182d89b8..75dbb1a376 100644 --- a/specs/gloas/fork.md +++ b/specs/gloas/fork.md @@ -182,6 +182,7 @@ def upgrade_to_gloas(pre: fulu.BeaconState) -> BeaconState: # [New in Gloas:EIP7732] latest_execution_payload_bid=ExecutionPayloadBid( block_hash=pre.latest_execution_payload_header.block_hash, + gas_limit=pre.latest_execution_payload_header.gas_limit, execution_requests_root=hash_tree_root(ExecutionRequests()), ), # [New in Gloas:EIP7732] diff --git a/specs/gloas/p2p-interface.md b/specs/gloas/p2p-interface.md index b81edab548..5b4ead9244 100644 --- a/specs/gloas/p2p-interface.md +++ b/specs/gloas/p2p-interface.md @@ -90,7 +90,7 @@ class ProposerPreferences(Container): proposal_slot: Slot validator_index: ValidatorIndex fee_recipient: ExecutionAddress - gas_limit: uint64 + target_gas_limit: uint64 ``` #### New `SignedProposerPreferences` @@ -347,7 +347,6 @@ where `parent_state` is the post-state of `bid.parent_block_root`, and the alias `is_active_builder(state, bid.builder_index)` returns `True`. - _[REJECT]_ `bid.execution_payment == 0`. - _[REJECT]_ `bid.fee_recipient == proposer_preferences.fee_recipient`. -- _[REJECT]_ `bid.gas_limit == proposer_preferences.gas_limit`. - _[REJECT]_ The length of KZG commitments is less than or equal to the limitation defined in the consensus layer -- i.e. validate that `len(bid.blob_kzg_commitments) <= get_blob_parameters(compute_epoch_at_slot(bid.slot)).max_blobs_per_block`. @@ -358,12 +357,34 @@ where `parent_state` is the post-state of `bid.parent_block_root`, and the alias - _[IGNORE]_ `bid.value` is less or equal than the builder's excess balance -- i.e. `can_builder_cover_bid(state, builder_index, amount)` returns `True`. - _[IGNORE]_ `bid.parent_block_hash` is the block hash of a known execution - payload in fork choice. + payload in fork choice and + `is_gas_limit_target_compatible(parent_gas_limit, bid.gas_limit, proposer_preferences.target_gas_limit)` + is `True` where `parent_gas_limit` is the `gas_limit` of that execution + payload. - _[IGNORE]_ `bid.parent_block_root` is the hash tree root of a known beacon block in fork choice. - _[REJECT]_ `signed_execution_payload_bid.signature` is valid with respect to the `bid.builder_index`. +```python +def is_gas_limit_target_compatible( + parent_gas_limit: uint64, gas_limit: uint64, target_gas_limit: uint64 +) -> bool: + """ + Check if ``gas_limit`` is compatible with ``target_gas_limit`` under the + EIP-1559 transition rule from ``parent_gas_limit``. + """ + max_gas_limit_difference = max(parent_gas_limit // 1024, 1) - 1 + min_gas_limit = parent_gas_limit - max_gas_limit_difference + max_gas_limit = parent_gas_limit + max_gas_limit_difference + + if target_gas_limit >= min_gas_limit and target_gas_limit <= max_gas_limit: + return gas_limit == target_gas_limit + if target_gas_limit > max_gas_limit: + return gas_limit == max_gas_limit + return gas_limit == min_gas_limit +``` + *Note*: Implementations SHOULD include DoS prevention measures to mitigate spam from malicious builders submitting numerous bids with minimal value increments. Possible strategies include: (1) only forwarding bids that exceed the current @@ -376,7 +397,7 @@ bid at regular time intervals. This topic is used to propagate signed proposer preferences as `SignedProposerPreferences`. These messages allow validators to communicate -their preferred `fee_recipient` and `gas_limit` to builders. +their preferred `fee_recipient` and `target_gas_limit` to builders. The following validations MUST pass before forwarding the `signed_proposer_preferences` on the network, assuming the alias diff --git a/specs/gloas/validator.md b/specs/gloas/validator.md index bfb8149a1c..c7caaace69 100644 --- a/specs/gloas/validator.md +++ b/specs/gloas/validator.md @@ -128,9 +128,9 @@ A validator MAY broadcast `SignedProposerPreferences` messages to the `get_upcoming_proposal_slots(state, validator_index)`. These include any future proposal slots within the proposer lookahead, i.e. the current epoch up to `MIN_SEED_LOOKAHEAD` epochs ahead. This allows builders to construct execution -payloads with the validator's preferred `fee_recipient` and `gas_limit`. If a -validator does not broadcast a `SignedProposerPreferences` message, this implies -that the validator will not accept any trustless bids for that slot. +payloads with the validator's preferred `fee_recipient` and `target_gas_limit`. +If a validator does not broadcast a `SignedProposerPreferences` message, this +implies that the validator will not accept any trustless bids for that slot. ```python def get_upcoming_proposal_slots( @@ -162,8 +162,8 @@ To construct each `SignedProposerPreferences`: 4. Set `preferences.validator_index` to the validator's index. 5. Set `preferences.fee_recipient` to the execution address where the validator wishes to receive the builder payment. -6. Set `preferences.gas_limit` to the validator's preferred gas limit for this - execution payload. +6. Set `preferences.target_gas_limit` to the validator's preferred gas limit for + this execution payload. 7. Instantiate a new `SignedProposerPreferences` object as `signed_preferences`. 8. Set `signed_preferences.message` to `preferences`. 9. Set `signed_preferences.signature` to the result of diff --git a/tests/core/pyspec/eth_consensus_specs/test/gloas/unittests/test_is_gas_limit_target_compatible.py b/tests/core/pyspec/eth_consensus_specs/test/gloas/unittests/test_is_gas_limit_target_compatible.py new file mode 100644 index 0000000000..46d011ec32 --- /dev/null +++ b/tests/core/pyspec/eth_consensus_specs/test/gloas/unittests/test_is_gas_limit_target_compatible.py @@ -0,0 +1,58 @@ +from eth_consensus_specs.test.context import ( + single_phase, + spec_test, + with_gloas_and_later, +) + + +@with_gloas_and_later +@spec_test +@single_phase +def test_increase_within_limit(spec): + assert spec.is_gas_limit_target_compatible(60_000_000, 60_000_100, 60_000_100) + + +@with_gloas_and_later +@spec_test +@single_phase +def test_increase_exceeding_limit(spec): + # max_gas_limit_difference = 60_000_000 // 1024 - 1 = 58_592 + assert spec.is_gas_limit_target_compatible(60_000_000, 60_058_592, 100_000_000) + + +@with_gloas_and_later +@spec_test +@single_phase +def test_increase_exceeding_limit_off_by_one_fails(spec): + # gas_limit one above max_gas_limit (= 60_058_592) must fail (off by one) + assert not spec.is_gas_limit_target_compatible(60_000_000, 60_058_593, 100_000_000) + + +@with_gloas_and_later +@spec_test +@single_phase +def test_decrease_within_limit(spec): + assert spec.is_gas_limit_target_compatible(60_000_000, 59_999_990, 59_999_990) + + +@with_gloas_and_later +@spec_test +@single_phase +def test_decrease_exceeding_limit(spec): + # max_gas_limit_difference = 60_000_000 // 1024 - 1 = 58_592 + assert spec.is_gas_limit_target_compatible(60_000_000, 59_941_408, 30_000_000) + + +@with_gloas_and_later +@spec_test +@single_phase +def test_target_equals_parent(spec): + assert spec.is_gas_limit_target_compatible(60_000_000, 60_000_000, 60_000_000) + + +@with_gloas_and_later +@spec_test +@single_phase +def test_parent_gas_limit_underflows(spec): + # parent_gas_limit // 1024 = 0; guard clamps to max(0, 1) - 1 = 0 (no underflow) + assert spec.is_gas_limit_target_compatible(1023, 1023, 60_000_000)