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

polish merge/beacon-chain.md #2472

Merged
merged 15 commits into from
Jun 18, 2021
14 changes: 9 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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)
]
)
)
Expand Down
219 changes: 97 additions & 122 deletions specs/merge/beacon-chain.md
Original file line number Diff line number Diff line change
@@ -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 on [Altair](../altair/beacon-chain.md).

**Notice**: This document is a work-in-progress for researchers and implementers.

Expand All @@ -21,35 +21,36 @@
- [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)
- [Beacon chain state transition function](#beacon-chain-state-transition-function)
- [Execution engine](#execution-engine)
- [`on_payload`](#on_payload)
- [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)
- [Execution payload processing](#execution-payload-processing)
- [`process_execution_payload`](#process_execution_payload)
- [Testing](#testing)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->

## 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 fork.

## Custom types

We define the following Python custom types for type hinting and readability:
*Note*: The `Transaction` type is a stub which is not final.

| 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` |
| `Transaction` | `Union[OpaqueTransaction]` | a transaction |

## Constants

Expand All @@ -58,136 +59,127 @@ 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_TRANSACTIONS_PER_PAYLOAD` | `uint64(2**14)` (= 16,384) |
| `BYTES_PER_LOGS_BLOOM` | `uint64(2**8)` (= 256) |

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

### New containers

#### `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
transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD]
JustinDrake marked this conversation as resolved.
Show resolved Hide resolved
```

#### `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]
# Extra payload fields
block_hash: Hash32 # Hash of execution block
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:
JustinDrake marked this conversation as resolved.
Show resolved Hide resolved
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:
JustinDrake marked this conversation as resolved.
Show resolved Hide resolved
return not is_transition_completed(state) and block.body.execution_payload != ExecutionPayload()
def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64:
djrtwo marked this conversation as resolved.
Show resolved Hide resolved
slots_since_genesis = slot - GENESIS_SLOT
return uint64(state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT)
```

#### `compute_time_at_slot`
## Beacon chain state transition function

*Note*: This function is unsafe with respect to overflows and underflows.
### 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`

```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``.
mkalinin marked this conversation as resolved.
Show resolved Hide resolved
"""
...
```

The above function is accessed through the `EXECUTION_ENGINE` module which instantiates the `ExecutionEngine` protocol.

### Block processing

```python
Expand All @@ -196,50 +188,45 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None:
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):
if is_execution_enabled(state, block.body):
process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [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, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None:
# Verify consistency of the parent hash and block number
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)
# Verify timestamp
assert payload.timestamp == compute_timestamp_at_slot(state, state.slot)
# Verify the execution payload is valid
assert execution_engine.on_payload(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,
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),
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),
)
```

## Initialize state for pure Merge testnets and test vectors
## Testing
djrtwo marked this conversation as resolved.
Show resolved Hide resolved

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,
Expand Down Expand Up @@ -276,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)
JustinDrake marked this conversation as resolved.
Show resolved Hide resolved
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
```
Loading