Merge branch 'dev'

This commit is contained in:
Danny Ryan 2021-09-24 15:44:21 -06:00
commit 91f5e5684c
No known key found for this signature in database
GPG Key ID: 2765A792E42CE07A
22 changed files with 243 additions and 216 deletions

View File

@ -173,7 +173,7 @@ define run_generator
echo "generator $(1) finished"
endef
# The tests dir itself is simply build by creating the directory (recursively creating deeper directories if necessary)
# The tests dir itself is simply built by creating the directory (recursively creating deeper directories if necessary)
$(TEST_VECTOR_DIR):
$(info creating test output directory, for generators: ${GENERATOR_TARGETS})
mkdir -p $@

View File

@ -45,7 +45,7 @@ The merge is still actively in development. The exact specification has not been
* [Merge fork](specs/merge/fork.md)
* [Fork Choice changes](specs/merge/fork-choice.md)
* [Validator additions](specs/merge/validator.md)
* [Client settings](specs/merge/client_settings.md)
* [Client settings](specs/merge/client-settings.md)
### Sharding

View File

@ -3,6 +3,12 @@
# Extends the mainnet preset
PRESET_BASE: 'mainnet'
# Transition
# ---------------------------------------------------------------
# TBD, 2**256-1 is a placeholder
TERMINAL_TOTAL_DIFFICULTY: 115792089237316195423570985008687907853269984665640564039457584007913129639935
# Genesis
# ---------------------------------------------------------------
# `2**14` (= 16,384)
@ -31,9 +37,6 @@ MERGE_FORK_EPOCH: 18446744073709551615
SHARDING_FORK_VERSION: 0x03000000
SHARDING_FORK_EPOCH: 18446744073709551615
# TBD, 2**32 is a placeholder. Merge transition approach is in active R&D.
MIN_ANCHOR_POW_BLOCK_DIFFICULTY: 4294967296
# Time parameters
# ---------------------------------------------------------------

View File

@ -3,6 +3,12 @@
# Extends the minimal preset
PRESET_BASE: 'minimal'
# Transition
# ---------------------------------------------------------------
# TBD, 2**256-1 is a placeholder
TERMINAL_TOTAL_DIFFICULTY: 115792089237316195423570985008687907853269984665640564039457584007913129639935
# Genesis
# ---------------------------------------------------------------
# [customized]
@ -30,9 +36,6 @@ MERGE_FORK_EPOCH: 18446744073709551615
SHARDING_FORK_VERSION: 0x03000001
SHARDING_FORK_EPOCH: 18446744073709551615
# TBD, 2**32 is a placeholder. Merge transition approach is in active R&D.
MIN_ANCHOR_POW_BLOCK_DIFFICULTY: 4294967296
# Time parameters
# ---------------------------------------------------------------

View File

@ -32,7 +32,7 @@ except ImportError:
from marko.block import Heading, FencedCode, LinkRefDef, BlankLine
from marko.inline import CodeSpan
from marko.ext.gfm import gfm
from marko.ext.gfm.elements import Table, Paragraph
from marko.ext.gfm.elements import Table
# Definitions in context.py
@ -509,8 +509,7 @@ ExecutionState = Any
def get_pow_block(hash: Bytes32) -> PowBlock:
return PowBlock(block_hash=hash, parent_hash=Bytes32(), is_valid=True, is_processed=True,
total_difficulty=uint256(0), difficulty=uint256(0))
return PowBlock(block_hash=hash, parent_hash=Bytes32(), total_difficulty=uint256(0), difficulty=uint256(0))
def get_execution_state(execution_state_root: Bytes32) -> ExecutionState:
@ -523,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 assemble_block(self, block_hash: Hash32, timestamp: uint64, random: Bytes32) -> ExecutionPayload:
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")

View File

@ -14,6 +14,7 @@
- [Execution](#execution)
- [Configuration](#configuration)
- [Genesis testing settings](#genesis-testing-settings)
- [Transition settings](#transition-settings)
- [Containers](#containers)
- [Extended containers](#extended-containers)
- [`BeaconBlockBody`](#beaconblockbody)
@ -30,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)
@ -52,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
@ -64,6 +67,7 @@ This patch adds transaction execution to the beacon chain as part of the Merge f
| `BYTES_PER_LOGS_BLOOM` | `uint64(2**8)` (= 256) |
| `GAS_LIMIT_DENOMINATOR` | `uint64(2**10)` (= 1,024) |
| `MIN_GAS_LIMIT` | `uint64(5000)` (= 5,000) |
| `MAX_EXTRA_DATA_BYTES` | `2**5` (= 32) |
## Configuration
@ -76,6 +80,12 @@ This patch adds transaction execution to the beacon chain as part of the Merge f
| `GENESIS_GAS_LIMIT` | `uint64(30000000)` (= 30,000,000) |
| `GENESIS_BASE_FEE_PER_GAS` | `Bytes32('0x00ca9a3b00000000000000000000000000000000000000000000000000000000')` (= 1,000,000,000) |
### Transition settings
| Name | Value |
| - | - |
| `TERMINAL_TOTAL_DIFFICULTY` | **TBD** |
## Containers
### Extended containers
@ -150,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]
@ -159,6 +169,7 @@ class ExecutionPayload(Container):
gas_limit: uint64
gas_used: uint64
timestamp: uint64
extra_data: ByteList[MAX_EXTRA_DATA_BYTES]
base_fee_per_gas: Bytes32 # base fee introduced in EIP-1559, little-endian serialized
# Extra payload fields
block_hash: Hash32 # Hash of execution block
@ -171,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]
@ -180,6 +191,7 @@ class ExecutionPayloadHeader(Container):
gas_limit: uint64
gas_used: uint64
timestamp: uint64
extra_data: ByteList[MAX_EXTRA_DATA_BYTES]
base_fee_per_gas: Bytes32
# Extra payload fields
block_hash: Hash32 # Hash of execution block
@ -230,31 +242,52 @@ 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
*Note*: The call to the `process_execution_payload` must happen before the call to the `process_randao` as the former depends on the `randao_mix` computed with the reveal of the previous block.
```python
def process_block(state: BeaconState, block: BeaconBlock) -> None:
process_block_header(state, block)
if is_execution_enabled(state, block.body):
process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [New in Merge]
process_randao(state, block.body)
process_eth1_data(state, block.body)
process_operations(state, block.body)
process_sync_aggregate(state, block.body.sync_aggregate)
if is_execution_enabled(state, block.body):
process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [New in Merge]
```
### Execution payload processing
@ -284,20 +317,20 @@ def is_valid_gas_limit(payload: ExecutionPayload, parent: ExecutionPayloadHeader
#### `process_execution_payload`
*Note:* This function depends on `process_randao` function call as it retrieves the most recent randao mix from the `state`. Implementations that are considering parallel processing of execution payload with respect to beacon chain state transition function should work around this dependency.
```python
def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None:
# Verify consistency of the parent hash, block number, random, base fee per gas and gas limit
# Verify consistency of the parent hash, block number, base fee per gas and gas limit
# with respect to the previous execution payload header
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)
assert payload.random == get_randao_mix(state, get_current_epoch(state))
assert is_valid_gas_limit(payload, state.latest_execution_payload_header)
# Verify random
assert payload.random == get_randao_mix(state, get_current_epoch(state))
# 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,
@ -310,6 +343,7 @@ def process_execution_payload(state: BeaconState, payload: ExecutionPayload, exe
gas_limit=payload.gas_limit,
gas_used=payload.gas_used,
timestamp=payload.timestamp,
extra_data=payload.extra_data,
base_fee_per_gas=payload.base_fee_per_gas,
block_hash=payload.block_hash,
transactions_root=hash_tree_root(payload.transactions),

View File

@ -0,0 +1,21 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [The Merge -- Client Settings](#the-merge----client-settings)
- [Override terminal total difficulty](#override-terminal-total-difficulty)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# The Merge -- Client Settings
**Notice**: This document is a work-in-progress for researchers and implementers.
This document specifies configurable settings that clients must implement for the Merge.
### Override terminal total difficulty
To coordinate manual overrides to [`TERMINAL_TOTAL_DIFFICULTY`](./beacon-chain.md#Transition-settings) parameter, clients must provide `--terminal-total-difficulty-override` as a configurable setting. The value provided by this setting must take precedence over pre-configured `TERMINAL_TOTAL_DIFFICULTY` parameter.
Except under exceptional scenarios, this setting is expected to not be used. Sufficient warning to the user about this exceptional configurable setting should be provided.

View File

@ -1,26 +0,0 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [The Merge -- Client Settings](#the-merge----client-settings)
- [Override terminal total difficulty](#override-terminal-total-difficulty)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# The Merge -- Client Settings
**Notice**: This document is a work-in-progress for researchers and implementers.
This document specifies configurable settings that clients must implement for the Merge.
### Override terminal total difficulty
To coordinate manual overrides to [`terminal_total_difficulty`](fork-choice.md#transitionstore), clients
must provide `--terminal-total-difficulty-override` as a configurable setting.
If `TransitionStore` has already [been initialized](./fork.md#initializing-transition-store), this alters the previously initialized value of
`TransitionStore.terminal_total_difficulty`, otherwise this setting initializes `TransitionStore` with the specified, bypassing `compute_terminal_total_difficulty` and the use of an `anchor_pow_block`.
`terminal_total_difficulty`.
Except under exceptional scenarios, this setting is expected to not be used, and `terminal_total_difficulty` will operate with [default functionality](./fork.md#initializing-transition-store). Sufficient warning to the user about this exceptional configurable setting should be provided.
[here](fork.md#initializing-transition-store).

View File

@ -10,10 +10,8 @@
- [Introduction](#introduction)
- [Protocols](#protocols)
- [`ExecutionEngine`](#executionengine)
- [`set_head`](#set_head)
- [`finalize_block`](#finalize_block)
- [`notify_forkchoice_updated`](#notify_forkchoice_updated)
- [Helpers](#helpers)
- [`TransitionStore`](#transitionstore)
- [`PowBlock`](#powblock)
- [`get_pow_block`](#get_pow_block)
- [`is_valid_terminal_pow_block`](#is_valid_terminal_pow_block)
@ -33,49 +31,27 @@ 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.
#### `notify_forkchoice_updated`
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 set_head(self: ExecutionEngine, block_hash: Hash32) -> bool:
"""
Returns True if the ``block_hash`` was successfully set as head of the execution payload chain.
"""
def notify_forkchoice_updated(self: ExecutionEngine, head_block_hash: Hash32, finalized_block_hash: Hash32) -> None:
...
```
#### `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.
```python
def finalize_block(self: ExecutionEngine, block_hash: Hash32) -> bool:
"""
Returns True if the data up to and including ``block_hash`` was successfully finalized.
"""
...
```
*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
### `TransitionStore`
```python
@dataclass
class TransitionStore(object):
terminal_total_difficulty: uint256
```
### `PowBlock`
```python
@ -83,8 +59,6 @@ class TransitionStore(object):
class PowBlock(object):
block_hash: Hash32
parent_hash: Hash32
is_processed: boolean
is_valid: boolean
total_difficulty: uint256
difficulty: uint256
```
@ -93,17 +67,17 @@ class PowBlock(object):
Let `get_pow_block(block_hash: Hash32) -> PowBlock` be the function that given the hash of the PoW block returns its data.
*Note*: The `eth_getBlockByHash` JSON-RPC method does not distinguish invalid blocks from blocks that haven't been processed yet. Either extending this existing method or implementing a new one is required.
*Note*: The `eth_getBlockByHash` JSON-RPC method may be used to pull this information from an execution client.
### `is_valid_terminal_pow_block`
Used by fork-choice handler, `on_block`.
```python
def is_valid_terminal_pow_block(transition_store: TransitionStore, block: PowBlock, parent: PowBlock) -> bool:
is_total_difficulty_reached = block.total_difficulty >= transition_store.terminal_total_difficulty
is_parent_total_difficulty_valid = parent.total_difficulty < transition_store.terminal_total_difficulty
return block.is_valid and is_total_difficulty_reached and is_parent_total_difficulty_valid
def is_valid_terminal_pow_block(block: PowBlock, parent: PowBlock) -> bool:
is_total_difficulty_reached = block.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY
is_parent_total_difficulty_valid = parent.total_difficulty < TERMINAL_TOTAL_DIFFICULTY
return is_total_difficulty_reached and is_parent_total_difficulty_valid
```
## Updated fork-choice handlers
@ -113,7 +87,7 @@ def is_valid_terminal_pow_block(transition_store: TransitionStore, block: PowBlo
*Note*: The only modification is the addition of the verification of transition block conditions.
```python
def on_block(store: Store, signed_block: SignedBeaconBlock, transition_store: TransitionStore=None) -> None:
def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
block = signed_block.message
# Parent block must be known
assert block.parent_root in store.block_states
@ -128,17 +102,16 @@ def on_block(store: Store, signed_block: SignedBeaconBlock, transition_store: Tr
# Check block is a descendant of the finalized block at the checkpoint finalized slot
assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root
# [New in Merge]
if (transition_store is not None) and is_merge_block(pre_state, block.body):
# Delay consideration of block until PoW block is processed by the PoW node
pow_block = get_pow_block(block.body.execution_payload.parent_hash)
pow_parent = get_pow_block(pow_block.parent_hash)
assert pow_block.is_processed
assert is_valid_terminal_pow_block(transition_store, pow_block, pow_parent)
# Check the block is valid and compute the post-state
state = pre_state.copy()
state_transition(state, signed_block, True)
# [New in Merge]
if is_merge_block(pre_state, block.body):
pow_block = get_pow_block(block.body.execution_payload.parent_hash)
pow_parent = get_pow_block(pow_block.parent_hash)
assert is_valid_terminal_pow_block(pow_block, pow_parent)
# Add new block to the store
store.blocks[hash_tree_root(block)] = block
# Add new state for this block to the store

View File

@ -12,7 +12,6 @@
- [Fork to Merge](#fork-to-merge)
- [Fork trigger](#fork-trigger)
- [Upgrading the state](#upgrading-the-state)
- [Initializing transition store](#initializing-transition-store)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@ -28,8 +27,6 @@ Warning: this configuration is not definitive.
| - | - |
| `MERGE_FORK_VERSION` | `Version('0x02000000')` |
| `MERGE_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** |
| `MIN_ANCHOR_POW_BLOCK_DIFFICULTY` | **TBD** |
| `TARGET_SECONDS_TO_MERGE` | `uint64(7 * 86400)` = (604,800) |
## Fork to Merge
@ -37,8 +34,6 @@ Warning: this configuration is not definitive.
TBD. Social consensus, along with state conditions such as epoch boundary, finality, deposits, active validator count, etc. may be part of the decision process to trigger the fork. For now we assume the condition will be triggered at epoch `MERGE_FORK_EPOCH`.
Since the Merge transition process relies on `Eth1Data` in the beacon state we do want to make sure that this data is fresh. This is achieved by forcing `MERGE_FORK_EPOCH` to point to eth1 voting period boundary, i.e. `MERGE_FORK_EPOCH` should satisfy the following condition `MERGE_FORK_EPOCH % EPOCHS_PER_ETH1_VOTING_PERIOD == 0`.
Note that for the pure Merge networks, we don't apply `upgrade_to_merge` since it starts with Merge version logic.
### Upgrading the state
@ -100,33 +95,3 @@ def upgrade_to_merge(pre: altair.BeaconState) -> BeaconState:
return post
```
### Initializing transition store
If `state.slot % SLOTS_PER_EPOCH == 0`, `compute_epoch_at_slot(state.slot) == MERGE_FORK_EPOCH`, and the transition store has not already been initialized, a transition store is initialized to be further utilized by the transition process of the Merge.
Transition store initialization occurs after the state has been modified by corresponding `upgrade_to_merge` function.
```python
def compute_terminal_total_difficulty(anchor_pow_block: PowBlock) -> uint256:
seconds_per_voting_period = EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH * SECONDS_PER_SLOT
pow_blocks_per_voting_period = seconds_per_voting_period // SECONDS_PER_ETH1_BLOCK
pow_blocks_to_merge = TARGET_SECONDS_TO_MERGE // SECONDS_PER_ETH1_BLOCK
pow_blocks_after_anchor_block = ETH1_FOLLOW_DISTANCE + pow_blocks_per_voting_period + pow_blocks_to_merge
anchor_difficulty = max(MIN_ANCHOR_POW_BLOCK_DIFFICULTY, anchor_pow_block.difficulty)
return anchor_pow_block.total_difficulty + anchor_difficulty * pow_blocks_after_anchor_block
def get_transition_store(anchor_pow_block: PowBlock) -> TransitionStore:
terminal_total_difficulty = compute_terminal_total_difficulty(anchor_pow_block)
return TransitionStore(terminal_total_difficulty=terminal_total_difficulty)
def initialize_transition_store(state: BeaconState) -> TransitionStore:
pow_block = get_pow_block(state.eth1_data.block_hash)
return get_transition_store(pow_block)
```
*Note*: Transition store can also be initialized at client startup by [overriding terminal total
difficulty](client_settings.md#override-terminal-total-difficulty).

View File

@ -68,14 +68,10 @@ See the Merge [state transition document](./beacon-chain.md#beaconblockbody) for
In addition to the gossip validations for this topic from prior specifications,
the following validations MUST pass before forwarding the `signed_beacon_block` on the network.
Alias `block = signed_beacon_block.message`, `execution_payload = block.body.execution_payload`.
- If the merge is complete with respect to the head state -- i.e. `is_merge_complete(state)` --
then validate the following:
- _[REJECT]_ The block's execution payload must be non-empty --
i.e. `execution_payload != ExecutionPayload()`
- If the execution is enabled for the block -- i.e. `is_execution_enabled(state, block.body)`
then validate the following:
- _[REJECT]_ The block's execution payload timestamp is correct with respect to the slot
-- i.e. `execution_payload.timestamp == compute_time_at_slot(state, block.slot)`.
-- i.e. `execution_payload.timestamp == compute_timestamp_at_slot(state, block.slot)`.
- _[REJECT]_ Gas used is less than the gas limit --
i.e. `execution_payload.gas_used <= execution_payload.gas_limit`.
- _[REJECT]_ The execution payload block hash is not equal to the parent hash --
@ -89,7 +85,7 @@ Alias `block = signed_beacon_block.message`, `execution_payload = block.body.exe
### Transitioning the gossip
See gossip transition details found in the [Altair document](../altair/p2p) for
See gossip transition details found in the [Altair document](../altair/p2p-interface.md#transitioning-the-gossip) for
details on how to handle transitioning gossip topics for the Merge.
## The Req/Resp domain

View File

@ -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 -->
@ -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.
"""
...
```
@ -60,9 +88,14 @@ All validator responsibilities remain unchanged other than those noted below. Na
#### Constructing the `BeaconBlockBody`
##### Execution Payload
##### ExecutionPayload
* Set `block.body.execution_payload = get_execution_payload(state, transition_store, execution_engine, pow_chain)` where:
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:
* `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]:
@ -75,35 +108,37 @@ def get_pow_block_at_total_difficulty(total_difficulty: uint256, pow_chain: Sequ
return None
def compute_randao_mix(state: BeaconState, randao_reveal: BLSSignature) -> Bytes32:
epoch = get_current_epoch(state)
return xor(get_randao_mix(state, epoch), hash(randao_reveal))
def produce_execution_payload(state: BeaconState,
parent_hash: Hash32,
randao_reveal: BLSSignature,
execution_engine: ExecutionEngine) -> ExecutionPayload:
timestamp = compute_timestamp_at_slot(state, state.slot)
randao_mix = compute_randao_mix(state, randao_reveal)
return execution_engine.assemble_block(parent_hash, timestamp, randao_mix)
def get_execution_payload(state: BeaconState,
transition_store: TransitionStore,
randao_reveal: BLSSignature,
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(transition_store.terminal_total_difficulty, pow_chain)
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, randao_reveal, 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, randao_reveal, 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.

View File

@ -270,8 +270,6 @@ class ShardBlobBody(Container):
degree_proof: BLSCommitment
# The actual data. Should match the commitment and degree proof.
data: List[BLSPoint, POINTS_PER_SAMPLE * MAX_SAMPLES_PER_BLOB]
# Latest block root of the Beacon Chain, before shard_blob.slot
beacon_block_root: Root
# fee payment fields (EIP 1559 like)
# TODO: express in MWei instead?
max_priority_fee_per_sample: Gwei
@ -293,8 +291,6 @@ class ShardBlobBodySummary(Container):
degree_proof: BLSCommitment
# Hash-tree-root as summary of the data field
data_root: Root
# Latest block root of the Beacon Chain, before shard_blob.slot
beacon_block_root: Root
# fee payment fields (EIP 1559 like)
# TODO: express in MWei instead?
max_priority_fee_per_sample: Gwei
@ -552,12 +548,12 @@ def compute_committee_index_from_shard(state: BeaconState, slot: Slot, shard: Sh
```python
def process_block(state: BeaconState, block: BeaconBlock) -> None:
process_block_header(state, block)
# is_execution_enabled is omitted, execution is enabled by default.
process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE)
process_randao(state, block.body)
process_eth1_data(state, block.body)
process_operations(state, block.body) # [Modified in Sharding]
process_sync_aggregate(state, block.body.sync_aggregate)
# is_execution_enabled is omitted, execution is enabled by default.
process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE)
```
#### Operations
@ -695,10 +691,6 @@ def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeade
committees_per_slot = get_committee_count_per_slot(state, header_epoch)
assert committee_index <= committees_per_slot
# Verify that the block root matches,
# to ensure the header will only be included in this specific Beacon Chain sub-tree.
assert header.body_summary.beacon_block_root == get_block_root_at_slot(state, slot - 1)
# Check that this data is still pending
committee_work = state.shard_buffer[slot % SHARD_STATE_MEMORY_SLOTS][shard]
assert committee_work.status.selector == SHARD_WORK_PENDING

View File

@ -98,7 +98,7 @@ on the horizontal subnet or creating samples for it. Alias `blob = signed_blob.m
- _[REJECT]_ The blob signature, `signed_blob.signature`, is valid for the aggregate of proposer and builder --
i.e. `bls.FastAggregateVerify([builder_pubkey, proposer_pubkey], blob_signing_root, signed_blob.signature)`.
- _[REJECT]_ The blob is proposed by the expected `proposer_index` for the blob's `slot` and `shard`,
in the context of the current shuffling (defined by `blob.body.beacon_block_root`/`slot`).
in the context of the current shuffling (defined by the current node head state and `blob.slot`).
If the `proposer_index` cannot immediately be verified against the expected shuffling,
the blob MAY be queued for later processing while proposers for the blob's branch are calculated --
in such a case _do not_ `REJECT`, instead `IGNORE` this message.
@ -133,7 +133,7 @@ The following validations MUST pass before forwarding the `signed_blob_header` o
- _[REJECT]_ The header signature, `signed_blob_header.signature`, is valid for the aggregate of proposer and builder --
i.e. `bls.FastAggregateVerify([builder_pubkey, proposer_pubkey], blob_signing_root, signed_blob_header.signature)`.
- _[REJECT]_ The header is proposed by the expected `proposer_index` for the blob's `header.slot` and `header.shard`
in the context of the current shuffling (defined by `header.body_summary.beacon_block_root`/`slot`).
in the context of the current shuffling (defined by the current node head state and `header.slot`).
If the `proposer_index` cannot immediately be verified against the expected shuffling,
the blob MAY be queued for later processing while proposers for the blob's branch are calculated --
in such a case _do not_ `REJECT`, instead `IGNORE` this message.
@ -161,7 +161,7 @@ The following validations MUST pass before forwarding the `signed_blob_header` o
- _[REJECT]_ The header signature, `signed_blob_header.signature`, is valid for ONLY the builder --
i.e. `bls.Verify(builder_pubkey, blob_signing_root, signed_blob_header.signature)`. The signature is not an aggregate with the proposer.
- _[REJECT]_ The header is designated for proposal by the expected `proposer_index` for the blob's `header.slot` and `header.shard`
in the context of the current shuffling (defined by `header.body_summary.beacon_block_root`/`slot`).
in the context of the current shuffling (defined by the current node head state and `header.slot`).
If the `proposer_index` cannot immediately be verified against the expected shuffling,
the blob MAY be queued for later processing while proposers for the blob's branch are calculated --
in such a case _do not_ `REJECT`, instead `IGNORE` this message.

View File

@ -1 +1 @@
1.1.0-beta.4
1.1.0-beta.5

View File

@ -9,9 +9,6 @@ from eth2spec.test.helpers.merkle import build_proof
@with_phases([ALTAIR])
@spec_state_test
def test_next_sync_committee_tree(spec, state):
state.next_sync_committee: object = spec.SyncCommittee(
pubkeys=[state.validators[i]for i in range(spec.SYNC_COMMITTEE_SIZE)]
)
next_sync_committee_branch = build_proof(state.get_backing(), spec.NEXT_SYNC_COMMITTEE_INDEX)
assert spec.is_valid_merkle_branch(
leaf=state.next_sync_committee.hash_tree_root(),

View File

@ -52,7 +52,7 @@ def test_process_light_client_update_not_updated(spec, state):
sync_committee_signature = compute_aggregate_sync_committee_signature(
spec,
state,
block.slot,
block_header.slot,
committee,
)
next_sync_committee_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_INDEX))]

View File

@ -99,8 +99,7 @@ def build_empty_block(spec, state, slot=None):
empty_block.body.sync_aggregate.sync_committee_signature = spec.G2_POINT_AT_INFINITY
if is_post_merge(spec):
randao_mix = spec.compute_randao_mix(state, empty_block.body.randao_reveal)
empty_block.body.execution_payload = build_empty_execution_payload(spec, state, randao_mix)
empty_block.body.execution_payload = build_empty_execution_payload(spec, state)
return empty_block

View File

@ -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?
@ -20,6 +20,7 @@ def build_empty_execution_payload(spec, state, randao_mix=None):
gas_limit=latest.gas_limit, # retain same limit
gas_used=0, # empty block, 0 gas
timestamp=timestamp,
extra_data=spec.ByteList[spec.MAX_EXTRA_DATA_BYTES](),
base_fee_per_gas=latest.base_fee_per_gas, # retain same base_fee
block_hash=spec.Hash32(),
transactions=empty_txs,
@ -42,6 +43,7 @@ def get_execution_payload_header(spec, execution_payload):
gas_limit=execution_payload.gas_limit,
gas_used=execution_payload.gas_used,
timestamp=execution_payload.timestamp,
extra_data=execution_payload.extra_data,
base_fee_per_gas=execution_payload.base_fee_per_gas,
block_hash=execution_payload.block_hash,
transactions_root=spec.hash_tree_root(execution_payload.transactions)

View File

@ -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
@ -144,6 +144,34 @@ def test_bad_parent_hash_regular_payload(spec, state):
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
@with_merge_and_later
@spec_state_test
def test_bad_random_first_payload(spec, state):
# pre-state
state = build_state_with_incomplete_transition(spec, state)
next_slot(spec, state)
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.random = b'\x42' * 32
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
@with_merge_and_later
@spec_state_test
def test_bad_random_regular_payload(spec, state):
# pre-state
state = build_state_with_complete_transition(spec, state)
next_slot(spec, state)
# execution payload
execution_payload = build_empty_execution_payload(spec, state)
execution_payload.random = b'\x04' * 32
yield from run_execution_payload_processing(spec, state, execution_payload, valid=False)
@with_merge_and_later
@spec_state_test
def test_bad_number_regular_payload(spec, state):

View File

@ -143,6 +143,9 @@ def process_and_sign_block_without_header_validations(spec, state, block):
state_root=spec.Bytes32(),
body_root=block.body.hash_tree_root(),
)
if is_post_merge(spec):
if spec.is_execution_enabled(state, block.body):
spec.process_execution_payload(state, block.body.execution_payload, spec.EXECUTION_ENGINE)
# Perform rest of process_block transitions
spec.process_randao(state, block.body)
@ -150,9 +153,6 @@ def process_and_sign_block_without_header_validations(spec, state, block):
spec.process_operations(state, block.body)
if is_post_altair(spec):
spec.process_sync_aggregate(state, block.body.sync_aggregate)
if is_post_merge(spec):
if spec.is_execution_enabled(state, block.body):
spec.process_execution_payload(state, block.body.execution_payload, spec.EXECUTION_ENGINE)
# Insert post-state rot
block.state_root = state.hash_tree_root()
@ -196,8 +196,7 @@ def test_parent_from_same_slot(spec, state):
child_block.parent_root = state.latest_block_header.hash_tree_root()
if is_post_merge(spec):
randao_mix = spec.compute_randao_mix(state, child_block.body.randao_reveal)
child_block.body.execution_payload = build_empty_execution_payload(spec, state, randao_mix)
child_block.body.execution_payload = build_empty_execution_payload(spec, state)
# Show that normal path through transition fails
failed_state = state.copy()

View File

@ -45,8 +45,8 @@ MESSAGES = [
SAMPLE_MESSAGE = b'\x12' * 32
PRIVKEYS = [
# Curve order is 256 so private keys are 32 bytes at most.
# Also not all integers is a valid private key, so using pre-generated keys
# Curve order is 256, so private keys use 32 bytes at most.
# Also, not all integers are valid private keys. Therefore, using pre-generated keys.
hex_to_int('0x00000000000000000000000000000000263dbd792f5b1be47ed85f8938c0f29586af0d3ac7b977f21c278fe1462040e3'),
hex_to_int('0x0000000000000000000000000000000047b8192d77bf871b62e87859d653922725724a5c031afeabc60bcef5ff665138'),
hex_to_int('0x00000000000000000000000000000000328388aff0d4a5b7dc9205abd374e7e98f3cd9f3418edb4eafda5fb16473d216'),