From a61bb8b54a2947592c9a517690c1165bc8f6138e Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 8 Jun 2021 14:34:38 +0100 Subject: [PATCH 01/14] polish merge/beacon-chain.md --- specs/merge/beacon-chain.md | 197 +++++++++++++++++------------------- 1 file changed, 91 insertions(+), 106 deletions(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index ada6c1a258..614ca60694 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -1,6 +1,6 @@ # Ethereum 2.0 The Merge -**Warning:** This document is currently based on [Phase 0](../phase0/beacon-chain.md) but will be rebased to [Altair](../altair/beacon-chain.md) once the latter is shipped. +**Warning**: This document is currently based on [Phase 0](../phase0/beacon-chain.md) and will be rebased for [Altair](../altair/beacon-chain.md). **Notice**: This document is a work-in-progress for researchers and implementers. @@ -15,7 +15,7 @@ - [Constants](#constants) - [Execution](#execution) - [Configuration](#configuration) - - [Transition](#transition) + - [Merge](#merge) - [Containers](#containers) - [Extended containers](#extended-containers) - [`BeaconBlockBody`](#beaconblockbody) @@ -23,34 +23,32 @@ - [New containers](#new-containers) - [`ExecutionPayload`](#executionpayload) - [`ExecutionPayloadHeader`](#executionpayloadheader) -- [Protocols](#protocols) - - [`ExecutionEngine`](#executionengine) - - [`new_block`](#new_block) - [Helper functions](#helper-functions) - - [Misc](#misc) + - [Predicates](#predicates) + - [`is_merge_complete`](#is_merge_complete) + - [`is_merge_block`](#is_merge_block) - [`is_execution_enabled`](#is_execution_enabled) - - [`is_transition_completed`](#is_transition_completed) - - [`is_transition_block`](#is_transition_block) - - [`compute_time_at_slot`](#compute_time_at_slot) + - [Misc](#misc) + - [`compute_timestamp_at_slot`](#compute_timestamp_at_slot) +- [Execution engine](#execution-engine) + - [`on_payload`](#on_payload) +- [Beacon chain state transition function](#beacon-chain-state-transition-function) - [Block processing](#block-processing) - - [Execution payload processing](#execution-payload-processing) - - [`process_execution_payload`](#process_execution_payload) + - [Execution payload processing](#execution-payload-processing) + - [`process_execution_payload`](#process_execution_payload) ## Introduction -This is a patch implementing the executable beacon chain proposal. -It enshrines transaction execution and validity as a first class citizen at the core of the beacon chain. +This patch adds transaction execution to the beacon chain as part of the merge. ## Custom types -We define the following Python custom types for type hinting and readability: - | Name | SSZ equivalent | Description | | - | - | - | -| `OpaqueTransaction` | `ByteList[MAX_BYTES_PER_OPAQUE_TRANSACTION]` | a byte-list containing a single [typed transaction envelope](https://eips.ethereum.org/EIPS/eip-2718#opaque-byte-array-rather-than-an-rlp-array) structured as `TransactionType \|\| TransactionPayload` | +| `OpaqueTransaction` | `ByteList[MAX_BYTES_PER_OPAQUE_TRANSACTION]` | a [typed transaction envelope](https://eips.ethereum.org/EIPS/eip-2718#opaque-byte-array-rather-than-an-rlp-array) structured as `TransactionType \|\| TransactionPayload` | ## Constants @@ -59,44 +57,37 @@ We define the following Python custom types for type hinting and readability: | Name | Value | | - | - | | `MAX_BYTES_PER_OPAQUE_TRANSACTION` | `uint64(2**20)` (= 1,048,576) | -| `MAX_EXECUTION_TRANSACTIONS` | `uint64(2**14)` (= 16,384) | +| `MAX_OPAQUE_TRANSACTIONS` | `uint64(2**14)` (= 16,384) | | `BYTES_PER_LOGS_BLOOM` | `uint64(2**8)` (= 256) | ## Configuration -Warning: this configuration is not definitive. +### Merge -### Transition +*Note*: The configuration value `MERGE_FORK_EPOCH` is not final. | Name | Value | | - | - | | `MERGE_FORK_VERSION` | `Version('0x02000000')` | -| `MERGE_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** | -| `TRANSITION_TOTAL_DIFFICULTY` | **TBD** | +| `MERGE_FORK_EPOCH` | `Epoch(18446744073709551615)` | ## Containers ### Extended containers -*Note*: Extended SSZ containers inherit all fields from the parent in the original -order and append any additional fields to the end. - #### `BeaconBlockBody` -*Note*: `BeaconBlockBody` fields remain unchanged other than the addition of `execution_payload`. - ```python class BeaconBlockBody(phase0.BeaconBlockBody): + # Execution execution_payload: ExecutionPayload # [New in Merge] ``` #### `BeaconState` -*Note*: `BeaconState` fields remain unchanged other than addition of `latest_execution_payload_header`. - ```python class BeaconState(phase0.BeaconState): - # Execution-layer + # Execution latest_execution_payload_header: ExecutionPayloadHeader # [New in Merge] ``` @@ -104,146 +95,140 @@ class BeaconState(phase0.BeaconState): #### `ExecutionPayload` -The execution payload included in a `BeaconBlockBody`. - ```python class ExecutionPayload(Container): - block_hash: Hash32 # Hash of execution block + # Execution block header fields parent_hash: Hash32 - coinbase: Bytes20 + coinbase: Bytes20 # 'beneficiary' in the yellow paper state_root: Bytes32 - number: uint64 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + receipt_root: Bytes32 # 'receipts root' in the yellow paper + block_number: uint64 # 'number' in the yellow paper gas_limit: uint64 gas_used: uint64 timestamp: uint64 - receipt_root: Bytes32 - logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - transactions: List[OpaqueTransaction, MAX_EXECUTION_TRANSACTIONS] + # Extra payload fields + block_hash: Hash32 # Hash of execution block + opaque_transactions: List[OpaqueTransaction, MAX_OPAQUE_TRANSACTIONS] ``` #### `ExecutionPayloadHeader` -The execution payload header included in a `BeaconState`. - -*Note:* Holds execution payload data without transaction bodies. - ```python class ExecutionPayloadHeader(Container): - block_hash: Hash32 # Hash of execution block + # Execution block header fields parent_hash: Hash32 coinbase: Bytes20 state_root: Bytes32 - number: uint64 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + receipt_root: Bytes32 + block_number: uint64 gas_limit: uint64 gas_used: uint64 timestamp: uint64 - receipt_root: Bytes32 - logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - transactions_root: Root + # Extra payload fields + block_hash: Hash32 # Hash of execution block + opaque_transactions_root: Root ``` -## Protocols - -### `ExecutionEngine` - -The `ExecutionEngine` protocol separates the consensus and execution sub-systems. -The consensus implementation references an instance of this sub-system with `EXECUTION_ENGINE`. - -The following methods are added to the `ExecutionEngine` protocol for use in the state transition: - -#### `new_block` +## Helper functions -Verifies the given `execution_payload` with respect to execution state transition, and persists changes if valid. +### Predicates -The body of this function is implementation dependent. -The Consensus API may be used to implement this with an external execution engine. +#### `is_merge_complete` ```python -def new_block(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: - """ - Returns True if the ``execution_payload`` was verified and processed successfully, False otherwise. - """ - ... +def is_merge_complete(state: BeaconState) -> bool: + return state.latest_execution_payload_header != ExecutionPayloadHeader() ``` -## Helper functions +#### `is_merge_block` -### Misc +```python +def is_merge_block(state: BeaconState, body: BeaconBlockBody) -> bool: + return not is_merge_complete(state) and body.execution_payload != ExecutionPayload() +``` #### `is_execution_enabled` ```python -def is_execution_enabled(state: BeaconState, block: BeaconBlock) -> bool: - return is_transition_completed(state) or is_transition_block(state, block) +def is_execution_enabled(state: BeaconState, body: BeaconBlockBody) -> bool: + return is_merge_block(state, body) or is_merge_complete(state) ``` -#### `is_transition_completed` +### Misc -```python -def is_transition_completed(state: BeaconState) -> bool: - return state.latest_execution_payload_header != ExecutionPayloadHeader() -``` +#### `compute_timestamp_at_slot` -#### `is_transition_block` +*Note*: This function is unsafe with respect to overflows and underflows. ```python -def is_transition_block(state: BeaconState, block: BeaconBlock) -> bool: - return not is_transition_completed(state) and block.body.execution_payload != ExecutionPayload() +def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64: + slots_since_genesis = slot - GENESIS_SLOT + return uint64(state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT) ``` -#### `compute_time_at_slot` +## Execution engine -*Note*: This function is unsafe with respect to overflows and underflows. +The implementation-dependent `ExecutionEngine` protocol encapsulates the execution sub-system logic via: + +* a state object `self.execution_state` of type `ExecutionState` +* a state transition function `self.on_payload` which mutates `self.execution_state` + +### `on_payload` ```python -def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64: - slots_since_genesis = slot - GENESIS_SLOT - return uint64(state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT) +def on_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: + """ + Returns ``True`` iff ``execution_payload`` is valid with respect to ``self.execution_state``. + """ + # [...] ``` +The above function is accessed through the `execution_engine` module which instantiates the `ExecutionEngine` protocol. + +## Beacon chain state transition function + ### Block processing ```python -def process_block(state: BeaconState, block: BeaconBlock) -> None: +def process_block(state: BeaconState, block: BeaconBlock, execution_state: ExecutionState) -> None: process_block_header(state, block) process_randao(state, block.body) process_eth1_data(state, block.body) process_operations(state, block.body) - # Pre-merge, skip execution payload processing - if is_execution_enabled(state, block): - process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [New in Merge] + process_execution_payload(state, block.body) # [New in Merge] ``` -#### Execution payload processing +### Execution payload processing -##### `process_execution_payload` +#### `process_execution_payload` ```python -def process_execution_payload(state: BeaconState, - execution_payload: ExecutionPayload, - execution_engine: ExecutionEngine) -> None: - """ - Note: This function is designed to be able to be run in parallel with the other `process_block` sub-functions - """ - if is_transition_completed(state): - assert execution_payload.parent_hash == state.latest_execution_payload_header.block_hash - assert execution_payload.number == state.latest_execution_payload_header.number + 1 - - assert execution_payload.timestamp == compute_time_at_slot(state, state.slot) - - assert execution_engine.new_block(execution_payload) - +def process_execution_payload(state: BeaconState, body: BeaconBlockBody) -> None: + # Skip execution payload processing if execution is not enabled + if not is_execution_enabled(state, body): + return + # Verify consistency of the parent hash and block number + if is_merge_complete(state): + assert body.execution_payload.parent_hash == state.latest_execution_payload_header.block_hash + assert body.execution_payload.block_number == state.latest_execution_payload_header.block_number + uint64(1) + # Verify timestamp + assert body.execution_payload.timestamp == compute_timestamp_at_slot(state, state.slot) + # Verify the execution payload is valid + assert execution_engine.on_payload(body.execution_payload) + # Cache execution payload state.latest_execution_payload_header = ExecutionPayloadHeader( - block_hash=execution_payload.block_hash, parent_hash=execution_payload.parent_hash, coinbase=execution_payload.coinbase, state_root=execution_payload.state_root, - number=execution_payload.number, + logs_bloom=execution_payload.logs_bloom, + receipt_root=execution_payload.receipt_root, + block_number=execution_payload.block_number, gas_limit=execution_payload.gas_limit, gas_used=execution_payload.gas_used, timestamp=execution_payload.timestamp, - receipt_root=execution_payload.receipt_root, - logs_bloom=execution_payload.logs_bloom, - transactions_root=hash_tree_root(execution_payload.transactions), + block_hash=execution_payload.block_hash, + opaque_transactions_root=hash_tree_root(execution_payload.opaque_transactions), ) ``` From 7c3a93e9f60344e1b22ff5086e5546471853c616 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 8 Jun 2021 14:40:14 +0100 Subject: [PATCH 02/14] Update beacon-chain.md --- 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 614ca60694..adee0f413e 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -192,7 +192,7 @@ The above function is accessed through the `execution_engine` module which insta ### Block processing ```python -def process_block(state: BeaconState, block: BeaconBlock, execution_state: ExecutionState) -> None: +def process_block(state: BeaconState, block: BeaconBlock) -> None: process_block_header(state, block) process_randao(state, block.body) process_eth1_data(state, block.body) From a55021f0198e7fa7489db7f42c0c3bbffca63ad9 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 8 Jun 2021 15:42:41 +0100 Subject: [PATCH 03/14] Update specs/merge/beacon-chain.md Co-authored-by: Danny Ryan --- 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 adee0f413e..b97ebcaf3d 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -182,7 +182,7 @@ def on_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bo """ Returns ``True`` iff ``execution_payload`` is valid with respect to ``self.execution_state``. """ - # [...] + ... ``` The above function is accessed through the `execution_engine` module which instantiates the `ExecutionEngine` protocol. From cbadac9400d941db0b7a9ae10e042774bae91414 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 8 Jun 2021 15:43:21 +0100 Subject: [PATCH 04/14] Update beacon-chain.md --- 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 b97ebcaf3d..1af68e1f63 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -1,6 +1,6 @@ # Ethereum 2.0 The Merge -**Warning**: This document is currently based on [Phase 0](../phase0/beacon-chain.md) and will be rebased for [Altair](../altair/beacon-chain.md). +**Warning**: This document is currently based on [Phase 0](../phase0/beacon-chain.md) and will be rebased on [Altair](../altair/beacon-chain.md). **Notice**: This document is a work-in-progress for researchers and implementers. From b1bb6163e38fe8cecacc64b49eaaa14442e5902a Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 8 Jun 2021 15:50:15 +0100 Subject: [PATCH 05/14] Update beacon-chain.md --- specs/merge/beacon-chain.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 1af68e1f63..d2c6b6fc51 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -30,9 +30,9 @@ - [`is_execution_enabled`](#is_execution_enabled) - [Misc](#misc) - [`compute_timestamp_at_slot`](#compute_timestamp_at_slot) +- [Beacon chain state transition function](#beacon-chain-state-transition-function) - [Execution engine](#execution-engine) - [`on_payload`](#on_payload) -- [Beacon chain state transition function](#beacon-chain-state-transition-function) - [Block processing](#block-processing) - [Execution payload processing](#execution-payload-processing) - [`process_execution_payload`](#process_execution_payload) @@ -49,6 +49,7 @@ This patch adds transaction execution to the beacon chain as part of the merge. | Name | SSZ equivalent | Description | | - | - | - | | `OpaqueTransaction` | `ByteList[MAX_BYTES_PER_OPAQUE_TRANSACTION]` | a [typed transaction envelope](https://eips.ethereum.org/EIPS/eip-2718#opaque-byte-array-rather-than-an-rlp-array) structured as `TransactionType \|\| TransactionPayload` | +| `Transaction` | **TODO**: define as `Union` type with `OpaqueTransaction` | an execution transaction | ## Constants @@ -57,7 +58,7 @@ This patch adds transaction execution to the beacon chain as part of the merge. | Name | Value | | - | - | | `MAX_BYTES_PER_OPAQUE_TRANSACTION` | `uint64(2**20)` (= 1,048,576) | -| `MAX_OPAQUE_TRANSACTIONS` | `uint64(2**14)` (= 16,384) | +| `MAX_TRANSACTIONS_PER_PAYLOAD` | `uint64(2**14)` (= 16,384) | | `BYTES_PER_LOGS_BLOOM` | `uint64(2**8)` (= 256) | ## Configuration @@ -109,7 +110,7 @@ class ExecutionPayload(Container): timestamp: uint64 # Extra payload fields block_hash: Hash32 # Hash of execution block - opaque_transactions: List[OpaqueTransaction, MAX_OPAQUE_TRANSACTIONS] + transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] ``` #### `ExecutionPayloadHeader` @@ -128,7 +129,7 @@ class ExecutionPayloadHeader(Container): timestamp: uint64 # Extra payload fields block_hash: Hash32 # Hash of execution block - opaque_transactions_root: Root + transactions_root: Root ``` ## Helper functions @@ -168,14 +169,16 @@ def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64: return uint64(state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT) ``` -## Execution engine +## Beacon chain state transition function + +### Execution engine The implementation-dependent `ExecutionEngine` protocol encapsulates the execution sub-system logic via: * a state object `self.execution_state` of type `ExecutionState` * a state transition function `self.on_payload` which mutates `self.execution_state` -### `on_payload` +#### `on_payload` ```python def on_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: @@ -187,8 +190,6 @@ def on_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bo The above function is accessed through the `execution_engine` module which instantiates the `ExecutionEngine` protocol. -## Beacon chain state transition function - ### Block processing ```python @@ -229,6 +230,6 @@ def process_execution_payload(state: BeaconState, body: BeaconBlockBody) -> None gas_used=execution_payload.gas_used, timestamp=execution_payload.timestamp, block_hash=execution_payload.block_hash, - opaque_transactions_root=hash_tree_root(execution_payload.opaque_transactions), + transactions_root=hash_tree_root(execution_payload.transactions), ) ``` From 29272dd4b649a851498e4fbbe54b0b5d379f6a6c Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 8 Jun 2021 21:02:31 +0100 Subject: [PATCH 06/14] Update beacon-chain.md --- specs/merge/beacon-chain.md | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index d2c6b6fc51..00743c5cf5 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -188,7 +188,7 @@ def on_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bo ... ``` -The above function is accessed through the `execution_engine` module which instantiates the `ExecutionEngine` protocol. +The above function is accessed through the `EXECUTION_ENGINE` module which instantiates the `ExecutionEngine` protocol. ### Block processing @@ -198,7 +198,8 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: process_randao(state, block.body) process_eth1_data(state, block.body) process_operations(state, block.body) - process_execution_payload(state, block.body) # [New in Merge] + if is_execution_enabled(state, block.body): + process_execution_payload(state, block.body, EXECUTION_ENGINE) # [New in Merge] ``` ### Execution payload processing @@ -206,10 +207,7 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: #### `process_execution_payload` ```python -def process_execution_payload(state: BeaconState, body: BeaconBlockBody) -> None: - # Skip execution payload processing if execution is not enabled - if not is_execution_enabled(state, body): - return +def process_execution_payload(state: BeaconState, body: BeaconBlockBody, execution_engine: ExecutionEngine) -> None: # Verify consistency of the parent hash and block number if is_merge_complete(state): assert body.execution_payload.parent_hash == state.latest_execution_payload_header.block_hash @@ -220,16 +218,16 @@ def process_execution_payload(state: BeaconState, body: BeaconBlockBody) -> None assert execution_engine.on_payload(body.execution_payload) # Cache execution payload state.latest_execution_payload_header = ExecutionPayloadHeader( - parent_hash=execution_payload.parent_hash, - coinbase=execution_payload.coinbase, - state_root=execution_payload.state_root, - logs_bloom=execution_payload.logs_bloom, - receipt_root=execution_payload.receipt_root, - block_number=execution_payload.block_number, - gas_limit=execution_payload.gas_limit, - gas_used=execution_payload.gas_used, - timestamp=execution_payload.timestamp, - block_hash=execution_payload.block_hash, - transactions_root=hash_tree_root(execution_payload.transactions), + parent_hash=body.execution_payload.parent_hash, + coinbase=body.execution_payload.coinbase, + state_root=body.execution_payload.state_root, + logs_bloom=body.execution_payload.logs_bloom, + receipt_root=body.execution_payload.receipt_root, + block_number=body.execution_payload.block_number, + gas_limit=body.execution_payload.gas_limit, + gas_used=body.execution_payload.gas_used, + timestamp=body.execution_payload.timestamp, + block_hash=body.execution_payload.block_hash, + transactions_root=hash_tree_root(body.execution_payload.transactions), ) ``` From a25a8b04b5bbd90b996ea4822fd260866d84a32f Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 9 Jun 2021 11:31:28 +0100 Subject: [PATCH 07/14] address HW's comments :) --- specs/merge/beacon-chain.md | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 00743c5cf5..dd3b39fec9 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -46,10 +46,12 @@ This patch adds transaction execution to the beacon chain as part of the merge. ## Custom types +*Note*: The `Transaction` type is a stub which is not final. + | Name | SSZ equivalent | Description | | - | - | - | | `OpaqueTransaction` | `ByteList[MAX_BYTES_PER_OPAQUE_TRANSACTION]` | a [typed transaction envelope](https://eips.ethereum.org/EIPS/eip-2718#opaque-byte-array-rather-than-an-rlp-array) structured as `TransactionType \|\| TransactionPayload` | -| `Transaction` | **TODO**: define as `Union` type with `OpaqueTransaction` | an execution transaction | +| `Transaction` | `Union[OpaqueTransaction]` | a transaction | ## Constants @@ -209,25 +211,26 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: ```python def process_execution_payload(state: BeaconState, body: BeaconBlockBody, execution_engine: ExecutionEngine) -> None: # Verify consistency of the parent hash and block number + payload = body.execution_payload if is_merge_complete(state): - assert body.execution_payload.parent_hash == state.latest_execution_payload_header.block_hash - assert body.execution_payload.block_number == state.latest_execution_payload_header.block_number + uint64(1) + assert payload.parent_hash == state.latest_execution_payload_header.block_hash + assert payload.block_number == state.latest_execution_payload_header.block_number + uint64(1) # Verify timestamp - assert body.execution_payload.timestamp == compute_timestamp_at_slot(state, state.slot) + assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) # Verify the execution payload is valid - assert execution_engine.on_payload(body.execution_payload) + assert execution_engine.on_payload(payload) # Cache execution payload state.latest_execution_payload_header = ExecutionPayloadHeader( - parent_hash=body.execution_payload.parent_hash, - coinbase=body.execution_payload.coinbase, - state_root=body.execution_payload.state_root, - logs_bloom=body.execution_payload.logs_bloom, - receipt_root=body.execution_payload.receipt_root, - block_number=body.execution_payload.block_number, - gas_limit=body.execution_payload.gas_limit, - gas_used=body.execution_payload.gas_used, - timestamp=body.execution_payload.timestamp, - block_hash=body.execution_payload.block_hash, - transactions_root=hash_tree_root(body.execution_payload.transactions), + parent_hash=payload.parent_hash, + coinbase=payload.coinbase, + state_root=payload.state_root, + logs_bloom=payload.logs_bloom, + receipt_root=payload.receipt_root, + block_number=payload.block_number, + gas_limit=payload.gas_limit, + gas_used=payload.gas_used, + timestamp=payload.timestamp, + block_hash=payload.block_hash, + transactions_root=hash_tree_root(payload.transactions), ) ``` From 728aeffc595b809d2c60215d9b9b5a64bf4e9741 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 9 Jun 2021 19:24:05 +0800 Subject: [PATCH 08/14] Make pyspec builder recognize custom Union type --- setup.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 2f28a75eed..8f22118afb 100644 --- a/setup.py +++ b/setup.py @@ -223,7 +223,7 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str]) -> if not _is_constant_id(name): # Check for short type declarations - if value.startswith("uint") or value.startswith("Bytes") or value.startswith("ByteList"): + if value.startswith("uint") or value.startswith("Bytes") or value.startswith("ByteList") or value.startswith("Union"): custom_types[name] = value continue @@ -495,7 +495,7 @@ def imports(cls, preset_name: str): return super().imports(preset_name) + f''' from typing import Protocol from eth2spec.phase0 import {preset_name} as phase0 -from eth2spec.utils.ssz.ssz_typing import Bytes20, ByteList, ByteVector, uint256 +from eth2spec.utils.ssz.ssz_typing import Bytes20, ByteList, ByteVector, uint256, Union ''' @classmethod @@ -553,6 +553,10 @@ def hardcoded_custom_type_dep_constants(cls) -> str: } +def is_spec_defined_type(value: str) -> bool: + return value.startswith('ByteList') or value.startswith('Union') + + def objects_to_spec(preset_name: str, spec_object: SpecObject, builder: SpecBuilder, @@ -565,15 +569,15 @@ def objects_to_spec(preset_name: str, [ f"class {key}({value}):\n pass\n" for key, value in spec_object.custom_types.items() - if not value.startswith('ByteList') + if not is_spec_defined_type(value) ] ) - + ('\n\n' if len([key for key, value in spec_object.custom_types.items() if value.startswith('ByteList')]) > 0 else '') + + ('\n\n' if len([key for key, value in spec_object.custom_types.items() if is_spec_defined_type(value)]) > 0 else '') + '\n\n'.join( [ f"{key} = {value}\n" for key, value in spec_object.custom_types.items() - if value.startswith('ByteList') + if is_spec_defined_type(value) ] ) ) @@ -673,7 +677,7 @@ def combine_dicts(old_dict: Dict[str, T], new_dict: Dict[str, T]) -> Dict[str, T ignored_dependencies = [ - 'bit', 'boolean', 'Vector', 'List', 'Container', 'BLSPubkey', 'BLSSignature', + 'bit', 'boolean', 'Vector', 'List', 'Container', 'BLSPubkey', 'BLSSignature', 'Transaction', 'Bytes1', 'Bytes4', 'Bytes20', 'Bytes32', 'Bytes48', 'Bytes96', 'Bitlist', 'Bitvector', 'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256', 'bytes', 'byte', 'ByteList', 'ByteVector', From 714083813d567f73a2c02c0bd97c5e837cf63111 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 9 Jun 2021 19:26:58 +0800 Subject: [PATCH 09/14] Minor revert --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8f22118afb..1a255cd8af 100644 --- a/setup.py +++ b/setup.py @@ -677,7 +677,7 @@ def combine_dicts(old_dict: Dict[str, T], new_dict: Dict[str, T]) -> Dict[str, T ignored_dependencies = [ - 'bit', 'boolean', 'Vector', 'List', 'Container', 'BLSPubkey', 'BLSSignature', 'Transaction', + 'bit', 'boolean', 'Vector', 'List', 'Container', 'BLSPubkey', 'BLSSignature', 'Bytes1', 'Bytes4', 'Bytes20', 'Bytes32', 'Bytes48', 'Bytes96', 'Bitlist', 'Bitvector', 'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256', 'bytes', 'byte', 'ByteList', 'ByteVector', From 835f83956947206d1bcb37dfa67db03c8cad3c25 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 15 Jun 2021 11:06:04 +0100 Subject: [PATCH 10/14] Pass payload to process_execution_payload --- specs/merge/beacon-chain.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index 7a65cfd30e..e06e074ff3 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -189,7 +189,7 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: process_eth1_data(state, block.body) process_operations(state, block.body) if is_execution_enabled(state, block.body): - process_execution_payload(state, block.body, EXECUTION_ENGINE) # [New in Merge] + process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [New in Merge] ``` ### Execution payload processing @@ -197,9 +197,8 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: #### `process_execution_payload` ```python -def process_execution_payload(state: BeaconState, body: BeaconBlockBody, execution_engine: ExecutionEngine) -> None: +def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None: # Verify consistency of the parent hash and block number - payload = body.execution_payload 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) From 9d8943873a1869437980cd8dd4d5578aa922180b Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 15 Jun 2021 11:46:28 +0100 Subject: [PATCH 11/14] Update beacon-chain.md --- specs/merge/beacon-chain.md | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md index e06e074ff3..4a9949a6a3 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -34,14 +34,14 @@ - [Block processing](#block-processing) - [Execution payload processing](#execution-payload-processing) - [`process_execution_payload`](#process_execution_payload) -- [Initialize state for pure Merge testnets and test vectors](#initialize-state-for-pure-merge-testnets-and-test-vectors) +- [Testing](#testing) ## Introduction -This patch adds transaction execution to the beacon chain as part of the merge. +This patch adds transaction execution to the beacon chain as part of the Merge fork. ## Custom types @@ -222,11 +222,11 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe ) ``` -## Initialize state for pure Merge testnets and test vectors +## Testing -This helper function is only for initializing the state for pure Merge testnets and tests. +*Note*: The function `initialize_beacon_state_from_eth1` is modified for pure Merge testing only. -*Note*: The function `initialize_beacon_state_from_eth1` is modified: (1) using `MERGE_FORK_VERSION` as the current fork version, (2) utilizing the Merge `BeaconBlockBody` when constructing the initial `latest_block_header`, and (3) adding initial `latest_execution_payload_header`. +*Note*: The function `initialize_beacon_state_from_eth1` is modified to use `MERGE_FORK_VERSION` and initialize `latest_execution_payload_header`. ```python def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, @@ -263,21 +263,9 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, # Set genesis validators root for domain separation and chain versioning state.genesis_validators_root = hash_tree_root(state.validators) - # [New in Merge] Construct execution payload header - # Note: initialized with zero block height - state.latest_execution_payload_header = ExecutionPayloadHeader( - block_hash=eth1_block_hash, - parent_hash=Hash32(), - coinbase=Bytes20(), - state_root=Bytes32(), - number=uint64(0), - gas_limit=uint64(0), - gas_used=uint64(0), - timestamp=eth1_timestamp, - receipt_root=Bytes32(), - logs_bloom=ByteVector[BYTES_PER_LOGS_BLOOM](), - transactions_root=Root(), - ) + # Initialize the execution payload header (with block number set to 0) + state.latest_execution_payload_header.block_hash = eth1_block_hash # [New in Merge] + state.latest_execution_payload_header.timestamp = eth1_timestamp # [New in Merge] return state ``` From 1edd78fa484602a453d7eb0116b17762fcef0d63 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 15 Jun 2021 15:11:23 +0100 Subject: [PATCH 12/14] polish as per Mikhail --- 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 4a9949a6a3..562adaab4d 100644 --- a/specs/merge/beacon-chain.md +++ b/specs/merge/beacon-chain.md @@ -263,9 +263,9 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, # Set genesis validators root for domain separation and chain versioning state.genesis_validators_root = hash_tree_root(state.validators) - # Initialize the execution payload header (with block number set to 0) - state.latest_execution_payload_header.block_hash = eth1_block_hash # [New in Merge] - state.latest_execution_payload_header.timestamp = eth1_timestamp # [New in Merge] + # [New in Merge] Initialize the execution payload header (with block number set to 0) + state.latest_execution_payload_header.block_hash = eth1_block_hash + state.latest_execution_payload_header.timestamp = eth1_timestamp return state ``` From b17005d56713840c4ef7a60ff871d676c7931602 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 15 Jun 2021 23:51:11 +0800 Subject: [PATCH 13/14] Bump `remerkleable` to v0.1.21 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b124069007..7415a3f291 100644 --- a/setup.py +++ b/setup.py @@ -1024,7 +1024,7 @@ def run(self): "py_ecc==5.2.0", "milagro_bls_binding==1.6.3", "dataclasses==0.6", - "remerkleable==0.1.20", + "remerkleable==0.1.21", RUAMEL_YAML_VERSION, "lru-dict==1.1.6", MARKO_VERSION, From a33284970b3a7d2af510bd0f668a8afd53d0435c Mon Sep 17 00:00:00 2001 From: Justin Drake Date: Thu, 17 Jun 2021 09:28:49 +0100 Subject: [PATCH 14/14] Fix tests --- setup.py | 2 +- .../eth2spec/test/helpers/execution_payload.py | 18 +++++++++--------- .../test_process_execution_payload.py | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/setup.py b/setup.py index 7415a3f291..4430ccec0d 100644 --- a/setup.py +++ b/setup.py @@ -523,7 +523,7 @@ def get_pow_chain_head() -> PowBlock: class NoopExecutionEngine(ExecutionEngine): - def new_block(self, execution_payload: ExecutionPayload) -> bool: + def on_payload(self, execution_payload: ExecutionPayload) -> bool: return True def set_head(self, block_hash: Hash32) -> bool: diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index 7774aa4d9f..238e2e00f5 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -4,19 +4,19 @@ def build_empty_execution_payload(spec, state): """ latest = state.latest_execution_payload_header timestamp = spec.compute_time_at_slot(state, state.slot) - empty_txs = spec.List[spec.OpaqueTransaction, spec.MAX_EXECUTION_TRANSACTIONS]() + empty_txs = spec.List[spec.Transaction, spec.MAX_TRANSACTIONS_PER_PAYLOAD]() payload = spec.ExecutionPayload( - block_hash=spec.Hash32(), parent_hash=latest.block_hash, coinbase=spec.Bytes20(), state_root=latest.state_root, # no changes to the state - number=latest.number + 1, + logs_bloom=spec.ByteVector[spec.BYTES_PER_LOGS_BLOOM](), # TODO: zeroed logs bloom for empty logs ok? + receipt_root=b"no receipts here" + b"\x00" * 16, # TODO: root of empty MPT may be better. + block_number=latest.block_number + 1, gas_limit=latest.gas_limit, # retain same limit gas_used=0, # empty block, 0 gas timestamp=timestamp, - receipt_root=b"no receipts here" + b"\x00" * 16, # TODO: root of empty MPT may be better. - logs_bloom=spec.ByteVector[spec.BYTES_PER_LOGS_BLOOM](), # TODO: zeroed logs bloom for empty logs ok? + block_hash=spec.Hash32(), transactions=empty_txs, ) # TODO: real RLP + block hash logic would be nice, requires RLP and keccak256 dependency however. @@ -27,16 +27,16 @@ def build_empty_execution_payload(spec, state): def get_execution_payload_header(spec, execution_payload): return spec.ExecutionPayloadHeader( - block_hash=execution_payload.block_hash, parent_hash=execution_payload.parent_hash, coinbase=execution_payload.coinbase, state_root=execution_payload.state_root, - number=execution_payload.number, + logs_bloom=execution_payload.logs_bloom, + receipt_root=execution_payload.receipt_root, + block_number=execution_payload.block_number, gas_limit=execution_payload.gas_limit, gas_used=execution_payload.gas_used, timestamp=execution_payload.timestamp, - receipt_root=execution_payload.receipt_root, - logs_bloom=execution_payload.logs_bloom, + block_hash=execution_payload.block_hash, transactions_root=spec.hash_tree_root(execution_payload.transactions) ) diff --git a/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py index 5edd319603..4c68034d4a 100644 --- a/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py @@ -25,7 +25,7 @@ def run_execution_payload_processing(spec, state, execution_payload, valid=True, called_new_block = False class TestEngine(spec.NoopExecutionEngine): - def new_block(self, payload) -> bool: + def on_payload(self, payload) -> bool: nonlocal called_new_block, execution_valid called_new_block = True assert payload == execution_payload @@ -153,7 +153,7 @@ def test_bad_number_regular_payload(spec, state): # execution payload execution_payload = build_empty_execution_payload(spec, state) - execution_payload.number = execution_payload.number + 1 + execution_payload.block_number = execution_payload.block_number + 1 yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) @@ -168,7 +168,7 @@ def test_bad_everything_regular_payload(spec, state): # execution payload execution_payload = build_empty_execution_payload(spec, state) execution_payload.parent_hash = spec.Hash32() - execution_payload.number = execution_payload.number + 1 + execution_payload.block_number = execution_payload.block_number + 1 yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)