From d5be6b5d6868321c8928eb82586256601c6d75d2 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 18 Oct 2021 18:10:27 -0600 Subject: [PATCH 1/3] remove prepare_payload in favor of a unification with notify_forkchoice_updated --- setup.py | 9 ++- specs/merge/fork-choice.md | 21 ++++++- specs/merge/validator.md | 118 ++++++++++++++++++++++--------------- 3 files changed, 96 insertions(+), 52 deletions(-) diff --git a/setup.py b/setup.py index 863761daa..fa396ab50 100644 --- a/setup.py +++ b/setup.py @@ -497,7 +497,7 @@ class MergeSpecBuilder(AltairSpecBuilder): return super().imports(preset_name) + f''' from typing import Protocol from eth2spec.altair import {preset_name} as altair -from eth2spec.utils.ssz.ssz_typing import Bytes20, ByteList, ByteVector, uint256, Union +from eth2spec.utils.ssz.ssz_typing import Bytes8, Bytes20, ByteList, ByteVector, uint256, Union ''' @classmethod @@ -527,7 +527,10 @@ class NoopExecutionEngine(ExecutionEngine): def execute_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: return True - def notify_forkchoice_updated(self: ExecutionEngine, head_block_hash: Hash32, finalized_block_hash: Hash32) -> None: + def notify_forkchoice_updated(self: ExecutionEngine, + head_block_hash: Hash32, + finalized_block_hash: Hash32, + payload_attributes: Optional[PayloadAttributes]) -> None: pass def prepare_payload(self: ExecutionEngine, @@ -683,7 +686,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', - 'Bytes1', 'Bytes4', 'Bytes20', 'Bytes32', 'Bytes48', 'Bytes96', 'Bitlist', 'Bitvector', + 'Bytes1', 'Bytes4', 'Bytes8', 'Bytes20', 'Bytes32', 'Bytes48', 'Bytes96', 'Bitlist', 'Bitvector', 'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256', 'bytes', 'byte', 'ByteList', 'ByteVector', 'Dict', 'dict', 'field', 'ceillog2', 'floorlog2', 'Set', diff --git a/specs/merge/fork-choice.md b/specs/merge/fork-choice.md index ea4a5588a..44e729104 100644 --- a/specs/merge/fork-choice.md +++ b/specs/merge/fork-choice.md @@ -12,6 +12,7 @@ - [`ExecutionEngine`](#executionengine) - [`notify_forkchoice_updated`](#notify_forkchoice_updated) - [Helpers](#helpers) + - [`PayloadAttributes`](#payloadattributes) - [`PowBlock`](#powblock) - [`get_pow_block`](#get_pow_block) - [`is_valid_terminal_pow_block`](#is_valid_terminal_pow_block) @@ -43,8 +44,14 @@ This function performs two actions *atomically*: * Applies finality to the execution state: it irreversibly persists the chain of all execution payloads and corresponding state, up to and including `finalized_block_hash`. +Additionally, if `payload_attributes` is provided, this function sets in motion a payload build process on top of +`head_block_hash` with the result to be gathered by a followup call to `get_payload`. + ```python -def notify_forkchoice_updated(self: ExecutionEngine, head_block_hash: Hash32, finalized_block_hash: Hash32) -> None: +def notify_forkchoice_updated(self: ExecutionEngine, + head_block_hash: Hash32, + finalized_block_hash: Hash32, + payload_attributes: Optional[PayloadAttributes]) -> None: ... ``` @@ -52,6 +59,18 @@ def notify_forkchoice_updated(self: ExecutionEngine, head_block_hash: Hash32, fi ## Helpers +### `PayloadAttributes` + +Used to signal to initiate the payload build process via `notify_forkchoice_updated`. + +```python +@dataclass +class PayloadAttributes(object): + timestamp: uint64 + random: Bytes32 + fee_recipient: ExecutionAddress +``` + ### `PowBlock` ```python diff --git a/specs/merge/validator.md b/specs/merge/validator.md index 2de4ef6b1..cbca6eeda 100644 --- a/specs/merge/validator.md +++ b/specs/merge/validator.md @@ -11,9 +11,12 @@ - [Introduction](#introduction) - [Prerequisites](#prerequisites) - [Custom types](#custom-types) +- [Helpers](#helpers) + - [`get_pow_block_at_terminal_total_difficulty`](#get_pow_block_at_terminal_total_difficulty) + - [`get_terminal_pow_block`](#get_terminal_pow_block) + - [`get_payload_id`](#get_payload_id) - [Protocols](#protocols) - [`ExecutionEngine`](#executionengine) - - [`prepare_payload`](#prepare_payload) - [`get_payload`](#get_payload) - [Beacon chain responsibilities](#beacon-chain-responsibilities) - [Block proposal](#block-proposal) @@ -29,7 +32,7 @@ This document represents the changes to be made in the code of an "honest valida ## Prerequisites -This document is an extension of the [Altair -- Honest Validator](../altair/validator.md) guide. +This documens is an extension of the [Altair -- Honest Validator](../altair/validator.md) guide. All behaviors and definitions defined in this document, and documents it extends, carry over unless explicitly noted or overridden. All terminology, constants, functions, and protocol mechanics defined in the updated Beacon Chain doc of [The Merge](./beacon-chain.md) are requisite for this document and used throughout. @@ -39,7 +42,59 @@ Please see related Beacon Chain doc before continuing and use them as a referenc | Name | SSZ equivalent | Description | | - | - | - | -| `PayloadId` | `uint64` | Identifier of a payload building process | +| `PayloadId` | `Bytes8` | Identifier of a payload building process | + +## Helpers + +### `get_pow_block_at_terminal_total_difficulty` + +```python +def get_pow_block_at_terminal_total_difficulty(pow_chain: Sequence[PowBlock]) -> Optional[PowBlock]: + # `pow_chain` abstractly represents all blocks in the PoW chain + for block in pow_chain: + parent = get_pow_block(block.parent_hash) + block_reached_ttd = block.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY + parent_reached_ttd = parent.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY + if block_reached_ttd and not parent_reached_ttd: + return block + + return None +``` + +### `get_terminal_pow_block` + +```python +def get_terminal_pow_block(pow_chain: Sequence[PowBlock]) -> Optional[PowBlock]: + if TERMINAL_BLOCK_HASH != Hash32(): + # Terminal block hash override takes precedence over terminal total difficulty + pow_block_overrides = [block for block in pow_chain if block.block_hash == TERMINAL_BLOCK_HASH] + if not any(pow_block_overrides): + return None + return pow_block_overrides[0] + + return get_pow_block_at_terminal_total_difficulty(pow_chain) +``` + +### `get_payload_id` + +Given the `head_block_hash` and the `payload_attributes` that were used to +initiate the build process via `notify_forkchoice_updated`, `get_payload_id()` +returns the `payload_id` used to retrieve the payload via `get_payload`. + +```python +def get_payload_id(parent_hash: Hash32, payload_attributes: PayloadAttributes) -> PayloadId: + return PayloadId( + hash( + parent_hash + + uint_to_bytes(payload_attributes.timestamp) + + payload_attributes.random + + payload_attributes.fee_recipient + )[0:8] + ) +``` + +*Note*: This function does *not* use simple serialize `hash_tree_root` as to +avoid requiring simple serialize hashing capabilities in the Execution Layer. ## Protocols @@ -50,27 +105,10 @@ Please see related Beacon Chain doc before continuing and use them as a referenc The body of each of these functions is implementation dependent. The Engine API may be used to implement them with an external execution engine. -#### `prepare_payload` - -Given the set of execution payload attributes, `prepare_payload` initiates a process of building an execution payload -on top of the execution chain tip identified by `parent_hash`. - -```python -def prepare_payload(self: ExecutionEngine, - parent_hash: Hash32, - timestamp: uint64, - random: Bytes32, - fee_recipient: ExecutionAddress) -> PayloadId: - """ - Return ``payload_id`` that is used to obtain the execution payload in a subsequent ``get_payload`` call. - """ - ... -``` - #### `get_payload` Given the `payload_id`, `get_payload` returns the most recent version of the execution payload that -has been built since the corresponding call to `prepare_payload` method. +has been built since the corresponding call to `notify_forkchoice_updated` method. ```python def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> ExecutionPayload: @@ -92,38 +130,17 @@ All validator responsibilities remain unchanged other than those noted below. Na To obtain an execution payload, a block proposer building a block on top of a `state` must take the following actions: -1. Set `payload_id = prepare_execution_payload(state, pow_chain, fee_recipient, execution_engine)`, where: +1. Set `payload_id = prepare_execution_payload(state, pow_chain, finalized_block_hash, fee_recipient, execution_engine)`, where: * `state` is the state object after applying `process_slots(state, slot)` transition to the resulting state of the parent block processing * `pow_chain` is a list that abstractly represents all blocks in the PoW chain + * `finalized_block_hash` is the hash of the latest finalized execution payload (`Hash32()` if none yet finalized) * `fee_recipient` is the value suggested to be used for the `coinbase` field of the execution payload ```python -def get_pow_block_at_terminal_total_difficulty(pow_chain: Sequence[PowBlock]) -> Optional[PowBlock]: - # `pow_chain` abstractly represents all blocks in the PoW chain - for block in pow_chain: - parent = get_pow_block(block.parent_hash) - block_reached_ttd = block.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY - parent_reached_ttd = parent.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY - if block_reached_ttd and not parent_reached_ttd: - return block - - return None - - -def get_terminal_pow_block(pow_chain: Sequence[PowBlock]) -> Optional[PowBlock]: - if TERMINAL_BLOCK_HASH != Hash32(): - # Terminal block hash override takes precedence over terminal total difficulty - pow_block_overrides = [block for block in pow_chain if block.block_hash == TERMINAL_BLOCK_HASH] - if not any(pow_block_overrides): - return None - return pow_block_overrides[0] - - return get_pow_block_at_terminal_total_difficulty(pow_chain) - - def prepare_execution_payload(state: BeaconState, pow_chain: Sequence[PowBlock], + finalized_block_hash: Hash32, fee_recipient: ExecutionAddress, execution_engine: ExecutionEngine) -> Optional[PayloadId]: if not is_merge_complete(state): @@ -137,9 +154,14 @@ def prepare_execution_payload(state: BeaconState, # Post-merge, normal payload parent_hash = state.latest_execution_payload_header.block_hash - timestamp = compute_timestamp_at_slot(state, state.slot) - random = get_randao_mix(state, get_current_epoch(state)) - return execution_engine.prepare_payload(parent_hash, timestamp, random, fee_recipient) + # Set the forkchoice head and initiate the payload build process + payload_attributes = PayloadAttributes( + timestamp=compute_timestamp_at_slot(state, state.slot), + random=get_randao_mix(state, get_current_epoch(state)), + fee_recipient=fee_recipient, + ) + execution_engine.notify_forkchoice_updated(parent_hash, finalized_block_hash, payload_attributes) + return get_payload_id(parent_hash, payload_attributes) ``` 2. Set `block.body.execution_payload = get_execution_payload(payload_id, execution_engine)`, where: From 34335e0334763bc0d3f7f56a8c1707add053aac7 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 19 Oct 2021 15:30:49 +0800 Subject: [PATCH 2/3] Remove `prepare_payload` leftover --- setup.py | 7 ------- specs/merge/validator.md | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/setup.py b/setup.py index fa396ab50..6ca46c636 100644 --- a/setup.py +++ b/setup.py @@ -533,13 +533,6 @@ class NoopExecutionEngine(ExecutionEngine): payload_attributes: Optional[PayloadAttributes]) -> None: pass - def prepare_payload(self: ExecutionEngine, - parent_hash: Hash32, - timestamp: uint64, - random: Bytes32, - feeRecipient: ExecutionAddress) -> PayloadId: - raise NotImplementedError("no default block production") - def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> ExecutionPayload: raise NotImplementedError("no default block production") diff --git a/specs/merge/validator.md b/specs/merge/validator.md index cbca6eeda..379687dc4 100644 --- a/specs/merge/validator.md +++ b/specs/merge/validator.md @@ -100,7 +100,7 @@ avoid requiring simple serialize hashing capabilities in the Execution Layer. ### `ExecutionEngine` -*Note*: `prepare_payload` and `get_payload` functions are added to the `ExecutionEngine` protocol for use as a validator. +*Note*: `get_payload` function is added to the `ExecutionEngine` protocol for use as a validator. The body of each of these functions is implementation dependent. The Engine API may be used to implement them with an external execution engine. From 8023edc94bd13c1f8cb396d1c6dc774dedc4d71b Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 19 Oct 2021 09:28:52 -0600 Subject: [PATCH 3/3] cleanup some copy relatedto removal of prepare_payload --- specs/merge/validator.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/merge/validator.md b/specs/merge/validator.md index 379687dc4..1e90b6b38 100644 --- a/specs/merge/validator.md +++ b/specs/merge/validator.md @@ -32,7 +32,7 @@ This document represents the changes to be made in the code of an "honest valida ## Prerequisites -This documens is an extension of the [Altair -- Honest Validator](../altair/validator.md) guide. +This document is an extension of the [Altair -- Honest Validator](../altair/validator.md) guide. All behaviors and definitions defined in this document, and documents it extends, carry over unless explicitly noted or overridden. All terminology, constants, functions, and protocol mechanics defined in the updated Beacon Chain doc of [The Merge](./beacon-chain.md) are requisite for this document and used throughout. @@ -102,8 +102,8 @@ avoid requiring simple serialize hashing capabilities in the Execution Layer. *Note*: `get_payload` function is added to the `ExecutionEngine` protocol for use as a validator. -The body of each of these functions is implementation dependent. -The Engine API may be used to implement them with an external execution engine. +The body of this function is implementation dependent. +The Engine API may be used to implement it with an external execution engine. #### `get_payload`