diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md new file mode 100644 index 000000000..a235dd518 --- /dev/null +++ b/specs/merge/beacon-chain.md @@ -0,0 +1,257 @@ +# Ethereum 2.0 The Merge + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + + +- [Introduction](#introduction) +- [Constants](#constants) + - [Execution](#execution) +- [Containers](#containers) + - [Extended containers](#extended-containers) + - [`BeaconBlockBody`](#beaconblockbody) + - [`BeaconState`](#beaconstate) + - [New containers](#new-containers) + - [`Transaction`](#transaction) + - [`ApplicationPayload`](#applicationpayload) +- [Helper functions](#helper-functions) + - [Misc](#misc) + - [`compute_randao_mix`](#compute_randao_mix) + - [`compute_time_at_slot`](#compute_time_at_slot) + - [Beacon state accessors](#beacon-state-accessors) + - [`get_recent_beacon_block_roots`](#get_recent_beacon_block_roots) + - [`get_evm_beacon_block_roots`](#get_evm_beacon_block_roots) + - [Block processing](#block-processing) + - [Modified `process_eth1_data`](#modified-process_eth1_data) + - [Application payload processing](#application-payload-processing) + - [`BeaconChainData`](#beaconchaindata) + - [`get_application_state`](#get_application_state) + - [`application_state_transition`](#application_state_transition) + - [`process_application_payload`](#process_application_payload) + + + + +## Introduction + +This is a patch implementing executable beacon chain proposal. +It enshrines application execution and validity as a first class citizen at the core of the beacon chain. + +## Constants + +### Execution + +| Name | Value | +| - | - | +| `MAX_BYTES_PER_TRANSACTION_PAYLOAD` | `2**20` | +| `MAX_APPLICATION_TRANSACTIONS` | `2**14` | +| `BYTES_PER_LOGS_BLOOM` | `2**8` | +| `EVM_BLOCK_ROOTS_SIZE` | `2**8` | + + +## 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 `application_payload`. + +```python +class BeaconBlockBody(phase0.BeaconBlockBody): + application_payload: ApplicationPayload # User execution payload +``` + +#### `BeaconState` + +*Note*: `BeaconState` fields remain unchanged other than the removal of `eth1_data_votes` and addition of `application_state_root`. +The latter stores the root hash of ethereum application state. + +```python +class BeaconState(Container): + # Versioning + genesis_time: uint64 + genesis_validators_root: Root + slot: Slot + fork: Fork + # History + latest_block_header: BeaconBlockHeader + block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] + # Eth1 + eth1_data: Eth1Data + # [removed] eth1_data_votes + eth1_deposit_index: uint64 + # [new] Hash of the root of application state + application_state_root: Bytes32 + # [new] Hash of recent application block + application_block_hash: Bytes32 + # Registry + validators: List[Validator, VALIDATOR_REGISTRY_LIMIT] + balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT] + # Randomness + randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR] + # Slashings + slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances + # Attestations + previous_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH] + current_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH] + # Finality + justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch + previous_justified_checkpoint: Checkpoint # Previous epoch snapshot + current_justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint +``` + +### New containers + +#### `Transaction` + +Application transaction fields structured as an SSZ object for inclusion in an `ApplicationPayload` contained within a `BeaconBlock`. + +```python +class Transaction(Container): + nonce: uint64 + gas_price: uint256 + gas_limit: uint64 + recipient: Bytes20 + value: uint256 + input: List[Bytes1, MAX_BYTES_PER_TRANSACTION_PAYLOAD] + v: uint256 + r: uint256 + s: uint256 +``` + +#### `ApplicationPayload` + +The application payload included in a `BeaconBlock`. + +```python +class ApplicationPayload(Container): + block_hash: Bytes32 # Hash of application block + coinbase: Bytes20 + state_root: Bytes32 + gas_limit: uint64 + gas_used: uint64 + receipt_root: Bytes32 + logs_bloom: Vector[Bytes1, BYTES_PER_LOGS_BLOOM] + difficulty: uint64 # Temporary field, will be removed later on + transactions: List[Transaction, MAX_APPLICATION_TRANSACTIONS] +``` + +## Helper functions + +### Misc + +#### `compute_randao_mix` + +```python +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)) +``` + +#### `compute_time_at_slot` + +```python +def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64: + return uint64(state.genesis_time + slot * SECONDS_PER_SLOT) +``` + +### Beacon state accessors + +#### `get_recent_beacon_block_roots` + +```python +def get_recent_beacon_block_roots(state: BeaconState, qty: uint64) -> Sequence[Bytes32]: + return [get_block_root_at_slot(state.slot - i) if GENESIS_SLOT + i < state.slot else Bytes32() for i in reversed(range(1, qty + 1))] +``` + +#### `get_evm_beacon_block_roots` + +```python +def get_evm_beacon_block_roots(state: BeaconState) -> Sequence[Bytes32]: + num_block_roots = min(BLOCK_ROOTS_FOR_EVM_SIZE, SLOTS_PER_HISTORICAL_ROOT) + return get_recent_beacon_block_roots(state, num_block_roots) +``` + +### Block processing + +```python +def process_block(state: BeaconState, block: BeaconBlock) -> None: + process_block_header(state, block) + process_randao(state, block.body) + process_eth1_data(state, block.body) # [Modified in The Merge] + process_operations(state, block.body) + process_application_payload(state, block.body) # [New in The Merge] +``` + +#### Modified `process_eth1_data` + +*Note*: The function `process_eth1_data` is modified to update `state.eth1_data` with `eth1_data` of each block. + +```python +def process_eth1_data(state: BeaconState, body: BeaconBlockBody) -> None: + state.eth1_data = body.eth1_data +``` + +#### Application payload processing + +##### `BeaconChainData` + +*Note*: `BeaconChainData` contains beacon state data that is used by the application state transition function. + +```python +class BeaconChainData(Container): + slot: Slot + randao_mix: Bytes32 + timestamp: uint64 + recent_block_roots: Sequence[Bytes32] +``` + +##### `get_application_state` + +*Note*: `ApplicationState` class is an abstract class representing ethereum application state. + +Let `get_application_state(application_state_root: Bytes32) -> ApplicationState` be the function that given the root hash returns a copy of ethereum application state. +The body of the function is implementation dependant. + +##### `application_state_transition` + +Let `application_state_transition(application_state: ApplicationState, beacon_chain_data: BeaconChainData, application_payload: ApplicationPayload) -> None` be the transition function of ethereum application state. +The body of the function is implementation dependant. + +*Note*: `application_state_transition` must throw `AssertionError` if either transition itself or post-transition verifications has failed. + +##### `process_application_payload` + +```python +def process_application_payload(state: BeaconState, body: BeaconBlockBody) -> None: + """ + Note: This function is designed to be able to be run in parallel with + the other `process_block` sub-functions + """ + + # Utilizes `compute_randao_mix` to avoid any assumptions about + # the processing of other `process_block` sub-functions + beacon_chain_data = BeaconChainData( + slot=state.slot, + randao_mix=compute_randao_mix(state, body.randao_reveal), + timestamp=compute_time_at_slot(state.genesis_time, state.slot), + recent_block_roots=get_evm_beacon_block_roots(state) + ) + + application_state = get_application_state(state.application_state_root) + application_state_transition(application_state, beacon_chain_data, body.application_payload) + + state.application_state_root = body.application_payload.state_root + state.application_block_hash = body.application_payload.block_hash +``` diff --git a/specs/merge/fork-choice.md b/specs/merge/fork-choice.md new file mode 100644 index 000000000..9d8f175d0 --- /dev/null +++ b/specs/merge/fork-choice.md @@ -0,0 +1,107 @@ +# Ethereum 2.0 The Merge + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + +- [Introduction](#introduction) + - [Helpers](#helpers) + - [`get_eth1_data`](#get_eth1_data) + - [`is_valid_eth1_data`](#is_valid_eth1_data) + - [Updated fork-choice handlers](#updated-fork-choice-handlers) + - [`on_block`](#on_block) + + + + +## Introduction + +This is the modification of the fork choice according to the executable beacon chain proposal. + +*Note*: It introduces the following change. `Eth1Data` included in a block must correspond to the application state produced by the parent block. This acts as an additional filter on the block subtree under consideration for the beacon block fork choice. + +### Helpers + +#### `get_eth1_data` + +Let `get_eth1_data(state: BeaconState) -> Eth1Data` be the function that returns the `Eth1Data` obtained from the beacon state. + +*Note*: This is mostly a function of the state of the beacon chain deposit contract. It can be read from the application state and/or logs. The `block_hash` value of `Eth1Data` must be set to `state.application_block_hash`. + +#### `is_valid_eth1_data` + +Used by fork-choice handler, `on_block` + +```python +def is_valid_eth1_data(store: Store, block: BeaconBlock) -> boolean: + parent_state = store.block_states[block.parent_root] + expected_eth1_data = get_eth1_data(parent_state) + actual_eth1_data = block.body.eth1_data + + is_correct_root = expected_eth1_data.deposit_root == actual_eth1_data.deposit_root + is_correct_count = expected_eth1_data.deposit_count == actual_eth1_data.deposit_count + is_correct_block_hash = expected_eth1_data.block_hash == actual_eth1_data.block_hash + return is_correct_root and is_correct_count and is_correct_block_hash +``` + +### Updated fork-choice handlers + +#### `on_block` + +*Note*: The only modification is the addition of the `Eth1Data` validity assumption. + +```python +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 + # Make a copy of the state to avoid mutability issues + pre_state = copy(store.block_states[block.parent_root]) + # Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past. + assert get_current_slot(store) >= block.slot + + # Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor) + finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + assert block.slot > finalized_slot + # 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 + + # [Added] Check that Eth1 data is correct + assert is_valid_eth1_data(store, block) + + # Check the block is valid and compute the post-state + state = pre_state.copy() + state_transition(state, signed_block, True) + # Add new block to the store + store.blocks[hash_tree_root(block)] = block + # Add new state for this block to the store + store.block_states[hash_tree_root(block)] = state + + # Update justified checkpoint + if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: + if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch: + store.best_justified_checkpoint = state.current_justified_checkpoint + if should_update_justified_checkpoint(store, state.current_justified_checkpoint): + store.justified_checkpoint = state.current_justified_checkpoint + + # Update finalized checkpoint + if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch: + store.finalized_checkpoint = state.finalized_checkpoint + + # Potentially update justified if different from store + if store.justified_checkpoint != state.current_justified_checkpoint: + # Update justified if new justified is later than store justified + if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: + store.justified_checkpoint = state.current_justified_checkpoint + return + + # Update justified if store justified is not in chain with finalized checkpoint + finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + ancestor_at_finalized_slot = get_ancestor(store, store.justified_checkpoint.root, finalized_slot) + if ancestor_at_finalized_slot != store.finalized_checkpoint.root: + store.justified_checkpoint = state.current_justified_checkpoint +``` + diff --git a/specs/merge/validator.md b/specs/merge/validator.md new file mode 100644 index 000000000..d55ec1c8f --- /dev/null +++ b/specs/merge/validator.md @@ -0,0 +1,77 @@ +# Ethereum 2.0 Phase 1 -- Honest Validator + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + + +- [Introduction](#introduction) +- [Prerequisites](#prerequisites) +- [Beacon chain responsibilities](#beacon-chain-responsibilities) + - [Block proposal](#block-proposal) + - [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody) + - [Eth1 data](#eth1-data) + - [`get_eth1_data`](#get_eth1_data) + - [Application Payload](#application-payload) + - [`produce_application_payload`](#produce_application_payload) + + + + +## Introduction + +This document represents the changes to be made in the code of an "honest validator" to implement executable beacon chain proposal. + +## Prerequisites + +This document is an extension of the [Phase 0 -- Validator](../phase0/validator.md). All behaviors and definitions defined in the Phase 0 doc 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. Please see related Beacon Chain doc before continuing and use them as a reference throughout. + +## Beacon chain responsibilities + +All validator responsibilities remain unchanged other than those noted below. Namely, the modification of `Eth1Data` and the addition of `ApplicationPayload`. + +### Block proposal + +#### Constructing the `BeaconBlockBody` + +##### Eth1 data + +The `block.body.eth1_data` field is for block proposers to publish recent Eth1 data. This recent data contains deposit root (as calculated by the get_deposit_root() method of the deposit contract) and deposit count after processing of the parent block. The fork choice verifies Eth1 data of a block, then `state.eth1_data` updates immediately allowing new deposits to be processed. Each deposit in `block.body.deposits` must verify against `state.eth1_data.deposit_root`. + +###### `get_eth1_data` + +Let `get_eth1_data(state: BeaconState) -> Eth1Data` be the function that returns the `Eth1Data` obtained from the beacon state. + +*Note*: This is mostly a function of the state of the beacon chain deposit contract. It can be read from the application state and/or logs. The `block_hash` value of `Eth1Data` must be set to `state.application_block_hash`. + +Set `block.body.eth1_data = get_eth1_data(state)`. + + +##### Application Payload + +###### `produce_application_payload` + +Let `produce_application_payload(parent_hash: Bytes32, beacon_chain_data: BeaconChainData) -> ApplicationPayload` be the function that produces new instance of application payload. + + +* Let `randao_reveal` be `block.body.randao_reveal` of the block that is being produced +* Set `block.body.application_payload = get_application_payload(state, randao_reveal)` where: + +```python +def get_application_payload(state: BeaconState, randao_reveal: BLSSignature) -> ApplicationPayload: + application_parent_hash = state.application_block_hash + beacon_chain_data = BeaconChainData( + slot=state.slot, + randao_mix=compute_randao_mix(state, randao_reveal), + timestamp=compute_time_at_slot(state.genesis_time, state.slot), + recent_block_roots=get_evm_beacon_block_roots(state) + ) + + return produce_application_payload(application_parent_hash, beacon_chain_data) +``` +