From 9a1a30c3d469e1ab55234df28a8ee97b994d0180 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Mon, 26 Jul 2021 19:30:50 +0600 Subject: [PATCH 1/7] Rebase Merge spec with London --- specs/merge/beacon-chain.md | 55 +++++++++++++++++++ .../test/helpers/execution_payload.py | 5 +- .../pyspec/eth2spec/test/helpers/genesis.py | 1 + 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 01c54e017..1a9c933d6 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -59,6 +59,10 @@ This patch adds transaction execution to the beacon chain as part of the Merge f | `MAX_BYTES_PER_OPAQUE_TRANSACTION` | `uint64(2**20)` (= 1,048,576) | | `MAX_TRANSACTIONS_PER_PAYLOAD` | `uint64(2**14)` (= 16,384) | | `BYTES_PER_LOGS_BLOOM` | `uint64(2**8)` (= 256) | +| `GAS_LIMIT_DIVISOR` | `uint64(2**10)` (= 1,024) | +| `MIN_GAS_LIMIT` | `uint64(5000)` (= 5,000) | +| `BASE_FEE_MAX_CHANGE_DENOMINATOR` | `uint64(2**3)` (= 8) | +| `ELASTICITY_MULTIPLIER` | `uint64(2**1)` (= 2) | ## Containers @@ -141,6 +145,7 @@ class ExecutionPayload(Container): gas_limit: uint64 gas_used: uint64 timestamp: uint64 + base_fee_per_gas: uint64 # base fee introduced in eip-1559 # Extra payload fields block_hash: Hash32 # Hash of execution block transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] @@ -161,6 +166,7 @@ class ExecutionPayloadHeader(Container): gas_limit: uint64 gas_used: uint64 timestamp: uint64 + base_fee_per_gas: uint64 # Extra payload fields block_hash: Hash32 # Hash of execution block transactions_root: Root @@ -239,6 +245,51 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: ### Execution payload processing +#### `is_valid_gas_limit` + +```python +def is_valid_gas_limit(payload: ExecutionPayload, parent: ExecutionPayloadHeader) -> bool: + parent_gas_limit = parent.gas_limit + + # check if the payload used too much gas + if payload.gas_used > payload.gas_limit: + return False + + # check if the payload changed the gas limit too much + if payload.gas_limit >= parent_gas_limit + parent_gas_limit // GAS_LIMIT_DIVISOR: + return False + if payload.gas_limit <= parent_gas_limit - parent_gas_limit // GAS_LIMIT_DIVISOR: + return False + + # check if the gas limit is at least the minimum gas limit + if payload.gas_limit < MIN_GAS_LIMIT: + return False + + return True +``` + +#### `compute_base_fee_per_gas` + +```python +def compute_base_fee_per_gas(payload: ExecutionPayload, parent: ExecutionPayloadHeader) -> uint64: + parent_gas_target = parent.gas_limit // ELASTICITY_MULTIPLIER + parent_base_fee_per_gas = parent.base_fee_per_gas + parent_gas_used = payload.gas_used + + if parent_gas_used == parent_gas_target: + return parent_base_fee_per_gas + elif parent_gas_used > parent_gas_target: + gas_used_delta = parent_gas_used - parent_gas_target + base_fee_per_gas_delta = \ + max(parent_base_fee_per_gas * gas_used_delta // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR, 1) + return parent_base_fee_per_gas + base_fee_per_gas_delta + else: + gas_used_delta = parent_gas_target - parent_gas_used + base_fee_per_gas_delta = \ + parent_base_fee_per_gas * gas_used_delta // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR + return max(parent_base_fee_per_gas - base_fee_per_gas_delta, 0) +``` + #### `process_execution_payload` *Note:* This function depends on `process_randao` function call as it retrieves the most recent randao mix from the `state`. Implementations that are considering parallel processing of execution payload with respect to beacon chain state transition function should work around this dependency. @@ -250,6 +301,8 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe assert payload.parent_hash == state.latest_execution_payload_header.block_hash assert payload.block_number == state.latest_execution_payload_header.block_number + uint64(1) assert payload.random == get_randao_mix(state, get_current_epoch(state)) + assert payload.base_fee_per_gas == compute_base_fee_per_gas(payload, state.latest_execution_payload_header) + assert is_valid_gas_limit(payload, state.latest_execution_payload_header) # Verify timestamp assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) # Verify the execution payload is valid @@ -266,6 +319,7 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe gas_limit=payload.gas_limit, gas_used=payload.gas_used, timestamp=payload.timestamp, + base_fee_per_gas=payload.base_fee_per_gas, block_hash=payload.block_hash, transactions_root=hash_tree_root(payload.transactions), ) @@ -321,6 +375,7 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, state.latest_execution_payload_header.block_hash = eth1_block_hash state.latest_execution_payload_header.timestamp = eth1_timestamp state.latest_execution_payload_header.random = eth1_block_hash + state.latest_execution_payload_header.gas_limit = MIN_GAS_LIMIT return state ``` diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index c41a05079..5c7eb6de0 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -17,12 +17,14 @@ def build_empty_execution_payload(spec, state, randao_mix=None): logs_bloom=spec.ByteVector[spec.BYTES_PER_LOGS_BLOOM](), # TODO: zeroed logs bloom for empty logs ok? block_number=latest.block_number + 1, random=randao_mix, - gas_limit=latest.gas_limit, # retain same limit + gas_limit=max(latest.gas_limit, spec.MIN_GAS_LIMIT), gas_used=0, # empty block, 0 gas timestamp=timestamp, + base_fee_per_gas=spec.uint64(0), block_hash=spec.Hash32(), transactions=empty_txs, ) + payload.base_fee_per_gas = spec.compute_base_fee_per_gas(latest, payload) # TODO: real RLP + block hash logic would be nice, requires RLP and keccak256 dependency however. payload.block_hash = spec.Hash32(spec.hash(payload.hash_tree_root() + b"FAKE RLP HASH")) @@ -41,6 +43,7 @@ def get_execution_payload_header(spec, execution_payload): gas_limit=execution_payload.gas_limit, gas_used=execution_payload.gas_used, timestamp=execution_payload.timestamp, + base_fee_per_gas=execution_payload.base_fee_per_gas, block_hash=execution_payload.block_hash, transactions_root=spec.hash_tree_root(execution_payload.transactions) ) diff --git a/tests/core/pyspec/eth2spec/test/helpers/genesis.py b/tests/core/pyspec/eth2spec/test/helpers/genesis.py index a9eb59f67..fc14c0aef 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/genesis.py +++ b/tests/core/pyspec/eth2spec/test/helpers/genesis.py @@ -78,5 +78,6 @@ def create_genesis_state(spec, validator_balances, activation_threshold): # Initialize the execution payload header (with block number and genesis time set to 0) state.latest_execution_payload_header.block_hash = eth1_block_hash state.latest_execution_payload_header.random = eth1_block_hash + state.latest_execution_payload_header.gas_limit = spec.MIN_GAS_LIMIT return state From 789e10ea7c890a909aeaaf7028a49aee9d795ff1 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Mon, 26 Jul 2021 20:09:57 +0600 Subject: [PATCH 2/7] Update toc --- specs/merge/beacon-chain.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 1a9c933d6..1a99a1227 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -31,6 +31,8 @@ - [`on_payload`](#on_payload) - [Block processing](#block-processing) - [Execution payload processing](#execution-payload-processing) + - [`is_valid_gas_limit`](#is_valid_gas_limit) + - [`compute_base_fee_per_gas`](#compute_base_fee_per_gas) - [`process_execution_payload`](#process_execution_payload) - [Testing](#testing) From c311712bcad2c795b71a34e8a81d47cb1b38f3ae Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Fri, 30 Jul 2021 12:36:02 +0600 Subject: [PATCH 3/7] Apply suggestions from code review Co-authored-by: Hsiao-Wei Wang --- specs/merge/beacon-chain.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 1a99a1227..08d357c3c 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -147,7 +147,7 @@ class ExecutionPayload(Container): gas_limit: uint64 gas_used: uint64 timestamp: uint64 - base_fee_per_gas: uint64 # base fee introduced in eip-1559 + base_fee_per_gas: uint64 # base fee introduced in EIP-1559 # Extra payload fields block_hash: Hash32 # Hash of execution block transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] @@ -253,17 +253,17 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: def is_valid_gas_limit(payload: ExecutionPayload, parent: ExecutionPayloadHeader) -> bool: parent_gas_limit = parent.gas_limit - # check if the payload used too much gas + # Check if the payload used too much gas if payload.gas_used > payload.gas_limit: return False - # check if the payload changed the gas limit too much + # Check if the payload changed the gas limit too much if payload.gas_limit >= parent_gas_limit + parent_gas_limit // GAS_LIMIT_DIVISOR: return False if payload.gas_limit <= parent_gas_limit - parent_gas_limit // GAS_LIMIT_DIVISOR: return False - # check if the gas limit is at least the minimum gas limit + # Check if the gas limit is at least the minimum gas limit if payload.gas_limit < MIN_GAS_LIMIT: return False @@ -282,13 +282,16 @@ def compute_base_fee_per_gas(payload: ExecutionPayload, parent: ExecutionPayload return parent_base_fee_per_gas elif parent_gas_used > parent_gas_target: gas_used_delta = parent_gas_used - parent_gas_target - base_fee_per_gas_delta = \ - max(parent_base_fee_per_gas * gas_used_delta // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR, 1) + base_fee_per_gas_delta = max( + parent_base_fee_per_gas * gas_used_delta // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR, + 1, + ) return parent_base_fee_per_gas + base_fee_per_gas_delta else: gas_used_delta = parent_gas_target - parent_gas_used - base_fee_per_gas_delta = \ + base_fee_per_gas_delta = ( parent_base_fee_per_gas * gas_used_delta // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR + ) return max(parent_base_fee_per_gas - base_fee_per_gas_delta, 0) ``` From 5d5a9e392b0925460d2f189920745bddf84edd02 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Fri, 30 Jul 2021 12:43:54 +0600 Subject: [PATCH 4/7] Rename GAS_LIMIT_DIVISOR to GAS_LIMIT_DENOMINATOR --- specs/merge/beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 08d357c3c..03784ffa8 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -61,7 +61,7 @@ This patch adds transaction execution to the beacon chain as part of the Merge f | `MAX_BYTES_PER_OPAQUE_TRANSACTION` | `uint64(2**20)` (= 1,048,576) | | `MAX_TRANSACTIONS_PER_PAYLOAD` | `uint64(2**14)` (= 16,384) | | `BYTES_PER_LOGS_BLOOM` | `uint64(2**8)` (= 256) | -| `GAS_LIMIT_DIVISOR` | `uint64(2**10)` (= 1,024) | +| `GAS_LIMIT_DENOMINATOR` | `uint64(2**10)` (= 1,024) | | `MIN_GAS_LIMIT` | `uint64(5000)` (= 5,000) | | `BASE_FEE_MAX_CHANGE_DENOMINATOR` | `uint64(2**3)` (= 8) | | `ELASTICITY_MULTIPLIER` | `uint64(2**1)` (= 2) | @@ -258,9 +258,9 @@ def is_valid_gas_limit(payload: ExecutionPayload, parent: ExecutionPayloadHeader return False # Check if the payload changed the gas limit too much - if payload.gas_limit >= parent_gas_limit + parent_gas_limit // GAS_LIMIT_DIVISOR: + if payload.gas_limit >= parent_gas_limit + parent_gas_limit // GAS_LIMIT_DENOMINATOR: return False - if payload.gas_limit <= parent_gas_limit - parent_gas_limit // GAS_LIMIT_DIVISOR: + if payload.gas_limit <= parent_gas_limit - parent_gas_limit // GAS_LIMIT_DENOMINATOR: return False # Check if the gas limit is at least the minimum gas limit From d58ffc7dfc69a11305444f8db23d0352920a3316 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Fri, 30 Jul 2021 16:10:33 +0600 Subject: [PATCH 5/7] Add genesis settings section --- specs/merge/beacon-chain.md | 16 +++++++++++++++- .../eth2spec/test/helpers/execution_payload.py | 4 ++-- .../core/pyspec/eth2spec/test/helpers/genesis.py | 3 ++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 03784ffa8..1341cc9cb 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -12,6 +12,8 @@ - [Custom types](#custom-types) - [Constants](#constants) - [Execution](#execution) +- [Configuration](#configuration) + - [Genesis settings](#genesis-settings) - [Containers](#containers) - [Extended containers](#extended-containers) - [`BeaconBlockBody`](#beaconblockbody) @@ -66,6 +68,17 @@ This patch adds transaction execution to the beacon chain as part of the Merge f | `BASE_FEE_MAX_CHANGE_DENOMINATOR` | `uint64(2**3)` (= 8) | | `ELASTICITY_MULTIPLIER` | `uint64(2**1)` (= 2) | +## Configuration + +### Genesis settings + +*Note*: These configuration settings do not apply to the mainnet and are utilized only by pure Merge testing. + +| Name | Value | +| - | - | +| `GENESIS_GAS_LIMIT` | `uint64(30000000)` (= 30,000,000) | +| `GENESIS_BASE_FEE_PER_GAS` | `uint64(1000000000)` (= 1,000,000,000) | + ## Containers ### Extended containers @@ -380,7 +393,8 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, state.latest_execution_payload_header.block_hash = eth1_block_hash state.latest_execution_payload_header.timestamp = eth1_timestamp state.latest_execution_payload_header.random = eth1_block_hash - state.latest_execution_payload_header.gas_limit = MIN_GAS_LIMIT + state.latest_execution_payload_header.gas_limit = GENESIS_GAS_LIMIT + state.latest_execution_payload_header.base_fee_per_gas = GENESIS_BASE_FEE_PER_GAS return state ``` diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index 5c7eb6de0..ce653a986 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -17,14 +17,14 @@ def build_empty_execution_payload(spec, state, randao_mix=None): logs_bloom=spec.ByteVector[spec.BYTES_PER_LOGS_BLOOM](), # TODO: zeroed logs bloom for empty logs ok? block_number=latest.block_number + 1, random=randao_mix, - gas_limit=max(latest.gas_limit, spec.MIN_GAS_LIMIT), + gas_limit=latest.gas_limit, # retain same limit gas_used=0, # empty block, 0 gas timestamp=timestamp, base_fee_per_gas=spec.uint64(0), block_hash=spec.Hash32(), transactions=empty_txs, ) - payload.base_fee_per_gas = spec.compute_base_fee_per_gas(latest, payload) + payload.base_fee_per_gas = spec.compute_base_fee_per_gas(payload, latest) # TODO: real RLP + block hash logic would be nice, requires RLP and keccak256 dependency however. payload.block_hash = spec.Hash32(spec.hash(payload.hash_tree_root() + b"FAKE RLP HASH")) diff --git a/tests/core/pyspec/eth2spec/test/helpers/genesis.py b/tests/core/pyspec/eth2spec/test/helpers/genesis.py index fc14c0aef..0e9af4cff 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/genesis.py +++ b/tests/core/pyspec/eth2spec/test/helpers/genesis.py @@ -78,6 +78,7 @@ def create_genesis_state(spec, validator_balances, activation_threshold): # Initialize the execution payload header (with block number and genesis time set to 0) state.latest_execution_payload_header.block_hash = eth1_block_hash state.latest_execution_payload_header.random = eth1_block_hash - state.latest_execution_payload_header.gas_limit = spec.MIN_GAS_LIMIT + state.latest_execution_payload_header.gas_limit = spec.GENESIS_GAS_LIMIT + state.latest_execution_payload_header.base_fee_per_gas = spec.GENESIS_BASE_FEE_PER_GAS return state From f1982d4fc3ff2f6320c7f47fcd96c302fc91e95a Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Fri, 30 Jul 2021 16:18:05 +0600 Subject: [PATCH 6/7] Replace underflow check with respective comment --- specs/merge/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 1341cc9cb..590e2bc4f 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -305,7 +305,7 @@ def compute_base_fee_per_gas(payload: ExecutionPayload, parent: ExecutionPayload base_fee_per_gas_delta = ( parent_base_fee_per_gas * gas_used_delta // parent_gas_target // BASE_FEE_MAX_CHANGE_DENOMINATOR ) - return max(parent_base_fee_per_gas - base_fee_per_gas_delta, 0) + return parent_base_fee_per_gas - base_fee_per_gas_delta # This subtraction can't underflow ``` #### `process_execution_payload` From ef71a4af1d35c0882a4dd584267b1d4d285976a3 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Tue, 10 Aug 2021 17:15:07 +0600 Subject: [PATCH 7/7] Polishing as per code review --- specs/merge/beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 590e2bc4f..8837c0ea3 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -13,7 +13,7 @@ - [Constants](#constants) - [Execution](#execution) - [Configuration](#configuration) - - [Genesis settings](#genesis-settings) + - [Genesis testing settings](#genesis-testing-settings) - [Containers](#containers) - [Extended containers](#extended-containers) - [`BeaconBlockBody`](#beaconblockbody) @@ -70,7 +70,7 @@ This patch adds transaction execution to the beacon chain as part of the Merge f ## Configuration -### Genesis settings +### Genesis testing settings *Note*: These configuration settings do not apply to the mainnet and are utilized only by pure Merge testing. @@ -314,7 +314,7 @@ def compute_base_fee_per_gas(payload: ExecutionPayload, parent: ExecutionPayload ```python def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None: - # Verify consistency of the parent hash, block number and random + # Verify consistency of the parent hash, block number, random, base fee per gas and gas limit if is_merge_complete(state): assert payload.parent_hash == state.latest_execution_payload_header.block_hash assert payload.block_number == state.latest_execution_payload_header.block_number + uint64(1)