Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove prepare_payload #2683

Merged
merged 3 commits into from
Oct 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 6 additions & 10 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ def imports(cls, preset_name: str):
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
Expand Down Expand Up @@ -527,16 +527,12 @@ 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,
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")

Expand Down Expand Up @@ -683,7 +679,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',
Expand Down
21 changes: 20 additions & 1 deletion specs/merge/fork-choice.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -43,15 +44,33 @@ 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:
...
```

*Note*: The call of the `notify_forkchoice_updated` function maps on the `POS_FORKCHOICE_UPDATED` event defined in the [EIP-3675](https://eips.ethereum.org/EIPS/eip-3675#definitions).

## 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
Expand Down
116 changes: 69 additions & 47 deletions specs/merge/validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -39,38 +42,73 @@ 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 |

## Protocols
## Helpers

### `ExecutionEngine`
### `get_pow_block_at_terminal_total_difficulty`
Copy link
Contributor Author

@djrtwo djrtwo Oct 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved get_pow_block_at_terminal_total_difficulty and get_terminal_pow_block up to helpers section.

No changes made to contents of the functions.


```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
```

*Note*: `prepare_payload` and `get_payload` functions are added to the `ExecutionEngine` protocol for use as a validator.
### `get_terminal_pow_block`

The body of each of these functions is implementation dependent.
The Engine API may be used to implement them with an external execution engine.
```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]

#### `prepare_payload`
return get_pow_block_at_terminal_total_difficulty(pow_chain)
```

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`.
### `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 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.
"""
...
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

### `ExecutionEngine`

*Note*: `get_payload` function is added to the `ExecutionEngine` protocol for use as a validator.

The body of this function is implementation dependent.
The Engine API may be used to implement it with an external execution engine.

#### `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:
Expand All @@ -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)
djrtwo marked this conversation as resolved.
Show resolved Hide resolved
* `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):
Expand All @@ -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:
Expand Down