Skip to content

Commit

Permalink
Merge pull request #2613 from mkalinin/execution-engine-update
Browse files Browse the repository at this point in the history
Update execution engine calls
  • Loading branch information
djrtwo authored Sep 24, 2021
2 parents 3f885c6 + 11840ce commit 940d6b1
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 70 deletions.
19 changes: 13 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,16 +522,23 @@ def get_pow_chain_head() -> PowBlock:
class NoopExecutionEngine(ExecutionEngine):
def on_payload(self, execution_payload: ExecutionPayload) -> bool:
def execute_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool:
return True
def set_head(self, block_hash: Hash32) -> bool:
return True
def notify_consensus_validated(self: ExecutionEngine, block_hash: Hash32, valid: bool) -> None:
pass
def finalize_block(self, block_hash: Hash32) -> bool:
return True
def notify_forkchoice_updated(self: ExecutionEngine, head_block_hash: Hash32, finalized_block_hash: Hash32) -> None:
pass
def prepare_payload(self: ExecutionEngine,
parent_hash: Hash32,
timestamp: uint64,
random: Bytes32,
feeRecipient: ExecutionAddress) -> PayloadId:
raise NotImplementedError("no default block production")
def assemble_block(self, block_hash: Hash32, timestamp: uint64, random: Bytes32) -> ExecutionPayload:
def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> ExecutionPayload:
raise NotImplementedError("no default block production")
Expand Down
37 changes: 29 additions & 8 deletions specs/merge/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
- [`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)
- [`execute_payload`](#execute_payload)
- [`notify_consensus_validated`](#notify_consensus_validated)
- [Block processing](#block-processing)
- [Execution payload processing](#execution-payload-processing)
- [`is_valid_gas_limit`](#is_valid_gas_limit)
Expand All @@ -53,6 +54,7 @@ This patch adds transaction execution to the beacon chain as part of the Merge f
| - | - | - |
| `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` | `Union[OpaqueTransaction]` | a transaction |
| `ExecutionAddress` | `Bytes20` | Address of account on the execution layer |

## Constants

Expand Down Expand Up @@ -158,7 +160,7 @@ class BeaconState(Container):
class ExecutionPayload(Container):
# Execution block header fields
parent_hash: Hash32
coinbase: Bytes20 # 'beneficiary' in the yellow paper
coinbase: ExecutionAddress # 'beneficiary' in the yellow paper
state_root: Bytes32
receipt_root: Bytes32 # 'receipts root' in the yellow paper
logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM]
Expand All @@ -180,7 +182,7 @@ class ExecutionPayload(Container):
class ExecutionPayloadHeader(Container):
# Execution block header fields
parent_hash: Hash32
coinbase: Bytes20
coinbase: ExecutionAddress
state_root: Bytes32
receipt_root: Bytes32
logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM]
Expand Down Expand Up @@ -240,19 +242,38 @@ def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64:
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`
* a state transition function `self.execute_payload` which applies changes to the `self.execution_state`
* a function `self.notify_consensus_validated` which signals that the beacon block containing the execution payload
is valid with respect to the consensus rule set

#### `on_payload`
*Note*: `execute_payload` and `notify_consensus_validated` are functions accessed through the `EXECUTION_ENGINE` module which instantiates the `ExecutionEngine` protocol.

The body of each of these functions is implementation dependent.
The Engine API may be used to implement them with an external execution engine.

#### `execute_payload`

```python
def on_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool:
def execute_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.
#### `notify_consensus_validated`

```python
def notify_consensus_validated(self: ExecutionEngine, block_hash: Hash32, valid: bool) -> None:
...
```

The inputs to this function depend on the result of the state transition. A call to `notify_consensus_validated` must be made after the [`state_transition`](../phase0/beacon-chain.md#beacon-chain-state-transition-function) function finishes. The value of the `valid` parameter must be set as follows:

* `True` if `state_transition` function call succeeds
* `False` if `state_transition` function call fails

*Note*: The call of the `notify_consensus_validated` function with `valid = True` maps on the `POS_CONSENSUS_VALIDATED` event defined in the [EIP-3675](https://eips.ethereum.org/EIPS/eip-3675#definitions).

### Block processing

Expand Down Expand Up @@ -309,7 +330,7 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe
# Verify timestamp
assert payload.timestamp == compute_timestamp_at_slot(state, state.slot)
# Verify the execution payload is valid
assert execution_engine.on_payload(payload)
assert execution_engine.execute_payload(payload)
# Cache execution payload header
state.latest_execution_payload_header = ExecutionPayloadHeader(
parent_hash=payload.parent_hash,
Expand Down
37 changes: 11 additions & 26 deletions specs/merge/fork-choice.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
- [Introduction](#introduction)
- [Protocols](#protocols)
- [`ExecutionEngine`](#executionengine)
- [`set_head`](#set_head)
- [`finalize_block`](#finalize_block)
- [`notify_forkchoice_updated`](#notify_forkchoice_updated)
- [Helpers](#helpers)
- [`PowBlock`](#powblock)
- [`get_pow_block`](#get_pow_block)
Expand All @@ -32,39 +31,25 @@ This is the modification of the fork choice according to the executable beacon c

### `ExecutionEngine`

The following methods are added to the `ExecutionEngine` protocol for use in the fork choice:

#### `set_head`

Re-organizes the execution payload chain and corresponding state to make `block_hash` the head.
*Note*: The `notify_forkchoice_updated` function is added to the `ExecutionEngine` protocol to signal the fork choice updates.

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

```python
def set_head(self: ExecutionEngine, block_hash: Hash32) -> bool:
"""
Returns True if the ``block_hash`` was successfully set as head of the execution payload chain.
"""
...
```
#### `notify_forkchoice_updated`

#### `finalize_block`

Applies finality to the execution state: it irreversibly persists the chain of all execution payloads
and corresponding state, up to and including `block_hash`.

The body of this function is implementation dependent.
The Consensus API may be used to implement this with an external execution engine.
This function performs two actions *atomically*:
* Re-organizes the execution payload chain and corresponding state to make `head_block_hash` the head.
* 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`.

```python
def finalize_block(self: ExecutionEngine, block_hash: Hash32) -> bool:
"""
Returns True if the data up to and including ``block_hash`` was successfully finalized.
"""
def notify_forkchoice_updated(self: ExecutionEngine, head_block_hash: Hash32, finalized_block_hash: Hash32) -> 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

### `PowBlock`
Expand Down
99 changes: 71 additions & 28 deletions specs/merge/validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@

- [Introduction](#introduction)
- [Prerequisites](#prerequisites)
- [Custom types](#custom-types)
- [Protocols](#protocols)
- [`ExecutionEngine`](#executionengine)
- [`assemble_block`](#assemble_block)
- [`prepare_payload`](#prepare_payload)
- [`get_payload`](#get_payload)
- [Beacon chain responsibilities](#beacon-chain-responsibilities)
- [Block proposal](#block-proposal)
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
- [Execution Payload](#execution-payload)
- [ExecutionPayload](#executionpayload)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
Expand All @@ -33,22 +35,48 @@ All behaviors and definitions defined in this document, and documents it extends
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.
Please see related Beacon Chain doc before continuing and use them as a reference throughout.

## Custom types

| Name | SSZ equivalent | Description |
| - | - | - |
| `PayloadId` | `uint64` | Identifier of a payload building process |

## Protocols

### `ExecutionEngine`

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

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

Produces a new instance of an execution payload, with the specified `timestamp`,
on top of the execution payload chain tip identified by `block_hash`.
#### `prepare_payload`

The body of this function is implementation dependent.
The Consensus API may be used to implement this with an external execution engine.
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 assemble_block(self: ExecutionEngine, block_hash: Hash32, timestamp: uint64, random: Bytes32) -> ExecutionPayload:
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.

```python
def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> ExecutionPayload:
"""
Return ``execution_payload`` object.
"""
...
```

Expand All @@ -60,9 +88,14 @@ All validator responsibilities remain unchanged other than those noted below. Na

#### Constructing the `BeaconBlockBody`

##### Execution Payload
##### ExecutionPayload

To obtain an execution payload, a block proposer building a block on top of a `state` must take the following actions:

* Set `block.body.execution_payload = get_execution_payload(state, execution_engine, pow_chain)` where:
1. Set `payload_id = prepare_execution_payload(state, pow_chain, 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
* `fee_recipient` is the value suggested to be used for the `coinbase` field of the execution payload

```python
def get_pow_block_at_total_difficulty(total_difficulty: uint256, pow_chain: Sequence[PowBlock]) -> Optional[PowBlock]:
Expand All @@ -75,27 +108,37 @@ def get_pow_block_at_total_difficulty(total_difficulty: uint256, pow_chain: Sequ
return None


def produce_execution_payload(state: BeaconState,
parent_hash: Hash32,
execution_engine: ExecutionEngine) -> ExecutionPayload:
timestamp = compute_timestamp_at_slot(state, state.slot)
randao_mix = get_randao_mix(state, get_current_epoch(state))
return execution_engine.assemble_block(parent_hash, timestamp, randao_mix)


def get_execution_payload(state: BeaconState,
execution_engine: ExecutionEngine,
pow_chain: Sequence[PowBlock]) -> ExecutionPayload:
def prepare_execution_payload(state: BeaconState,
pow_chain: Sequence[PowBlock],
fee_recipient: ExecutionAddress,
execution_engine: ExecutionEngine) -> Optional[PayloadId]:
if not is_merge_complete(state):
terminal_pow_block = get_pow_block_at_total_difficulty(TERMINAL_TOTAL_DIFFICULTY, pow_chain)
if terminal_pow_block is None:
# Pre-merge, empty payload
return ExecutionPayload()
# Pre-merge, no prepare payload call is needed
return None
else:
# Signify merge via producing on top of the last PoW block
return produce_execution_payload(state, terminal_pow_block.block_hash, execution_engine)
parent_hash = terminal_pow_block.block_hash
else:
# Post-merge, normal payload
parent_hash = state.latest_execution_payload_header.block_hash

# Post-merge, normal payload
parent_hash = state.latest_execution_payload_header.block_hash
return produce_execution_payload(state, parent_hash, execution_engine)
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)
```

2. Set `block.body.execution_payload = get_execution_payload(payload_id, execution_engine)`, where:

```python
def get_execution_payload(payload_id: Optional[PayloadId], execution_engine: ExecutionEngine) -> ExecutionPayload:
if payload_id is None:
# Pre-merge, empty payload
return ExecutionPayload()
else:
return execution_engine.get_payload(payload_id)
```

*Note*: It is recommended for a validator to call `prepare_execution_payload` as soon as input parameters become known,
and make subsequent calls to this function when any of these parameters gets updated.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def build_empty_execution_payload(spec, state, randao_mix=None):

payload = spec.ExecutionPayload(
parent_hash=latest.block_hash,
coinbase=spec.Bytes20(),
coinbase=spec.ExecutionAddress(),
state_root=latest.state_root, # no changes to the state
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?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def run_execution_payload_processing(spec, state, execution_payload, valid=True,
called_new_block = False

class TestEngine(spec.NoopExecutionEngine):
def on_payload(self, payload) -> bool:
def execute_payload(self, payload) -> bool:
nonlocal called_new_block, execution_valid
called_new_block = True
assert payload == execution_payload
Expand Down

0 comments on commit 940d6b1

Please sign in to comment.