diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md new file mode 100644 index 000000000..ce6df0dd9 --- /dev/null +++ b/specs/merge/beacon-chain.md @@ -0,0 +1,175 @@ +# 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. + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + + +- [Introduction](#introduction) +- [Constants](#constants) + - [Transition](#transition) + - [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) + - [`is_transition_completed`](#is_transition_completed) + - [`is_transition_block`](#is_transition_block) + - [Block processing](#block-processing) + - [Application payload processing](#application-payload-processing) + - [`get_application_state`](#get_application_state) + - [`application_state_transition`](#application_state_transition) + - [`process_application_payload`](#process_application_payload) + + + + +## Introduction + +This is a patch implementing the executable beacon chain proposal. +It enshrines application-layer execution and validity as a first class citizen at the core of the beacon chain. + +## Custom types + +We define the following Python custom types for type hinting and readability: + +| 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` | + +## Constants + +### Transition + +| Name | Value | +| - | - | +| `TRANSITION_TOTAL_DIFFICULTY` | **TBD** | + +### Execution + +| Name | Value | +| - | - | +| `MAX_BYTES_PER_OPAQUE_TRANSACTION` | `uint64(2**20)` (= 1,048,576) | +| `MAX_APPLICATION_TRANSACTIONS` | `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 `application_payload`. + +```python +class BeaconBlockBody(phase0.BeaconBlockBody): + application_payload: ApplicationPayload # [New in Merge] application payload +``` + +#### `BeaconState` + +*Note*: `BeaconState` fields remain unchanged other than addition of `application_state_root` and `application_block_hash`. + +```python +class BeaconState(phase0.BeaconState): + # Application-layer + application_state_root: Bytes32 # [New in Merge] + application_block_hash: Bytes32 # [New in Merge] +``` + +### New containers + +#### `ApplicationPayload` + +The application payload included in a `BeaconBlockBody`. + +```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: ByteVector[BYTES_PER_LOGS_BLOOM] + transactions: List[OpaqueTransaction, MAX_APPLICATION_TRANSACTIONS] +``` + +## Helper functions + +### Misc + +#### `is_transition_completed` + +```python +def is_transition_completed(state: BeaconState) -> boolean: + return state.application_block_hash != Bytes32() +``` + +#### `is_transition_block` + +```python +def is_transition_block(state: BeaconState, block_body: BeaconBlockBody) -> boolean: + return state.application_block_hash == Bytes32() and block_body.application_payload.block_hash != Bytes32() +``` + +### 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) + process_operations(state, block.body) + process_application_payload(state, block.body) # [New in Merge] +``` + +#### Application payload processing + +##### `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 dependent. + +##### `application_state_transition` + +Let `application_state_transition(application_state: ApplicationState, application_payload: ApplicationPayload) -> None` be the transition function of ethereum application state. +The body of the function is implementation dependent. + +*Note*: `application_state_transition` must throw `AssertionError` if either the transition itself or one of the 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 + """ + + if is_transition_completed(state): + application_state = get_application_state(state.application_state_root) + application_state_transition(application_state, body.application_payload) + + state.application_state_root = body.application_payload.state_root + state.application_block_hash = body.application_payload.block_hash + elif is_transition_block(state, body): + assert body.application_payload == ApplicationPayload(block_hash=body.application_payload.block_hash) + state.application_block_hash = body.application_payload.block_hash + else: + assert body.application_payload == ApplicationPayload() +``` diff --git a/specs/merge/fork-choice.md b/specs/merge/fork-choice.md new file mode 100644 index 000000000..430128c12 --- /dev/null +++ b/specs/merge/fork-choice.md @@ -0,0 +1,116 @@ +# Ethereum 2.0 The Merge + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + +- [Introduction](#introduction) + - [Helpers](#helpers) + - [`PowBlock`](#powblock) + - [`get_pow_block`](#get_pow_block) + - [`is_valid_transition_block`](#is_valid_transition_block) + - [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 process of transition from the last PoW block to the first PoS block. + +### Helpers + +#### `PowBlock` + +```python +class PowBlock(Container): + block_hash: Bytes32 + is_processed: boolean + is_valid: boolean + total_difficulty: uint256 +``` + +#### `get_pow_block` + +Let `get_pow_block(hash: Bytes32) -> 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. + +#### `is_valid_transition_block` + +Used by fork-choice handler, `on_block`. + +```python +def is_valid_transition_block(block: PowBlock) -> boolean: + is_total_difficulty_reached = block.total_difficulty >= TRANSITION_TOTAL_DIFFICULTY + return block.is_valid and is_total_difficulty_reached +``` + +### Updated fork-choice handlers + +#### `on_block` + +*Note*: The only modification is the addition of the verification of transition block conditions. + +```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 + + # [New in Merge] + if is_transition_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.application_payload.block_hash) + assert pow_block.is_processed + assert is_valid_transition_block(pow_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..49f7f6137 --- /dev/null +++ b/specs/merge/validator.md @@ -0,0 +1,70 @@ +# Ethereum 2.0 The Merge + +**Warning:** This document is currently based on [Phase 0](../phase0/validator.md) but will be rebased to [Altair](../altair/validator.md) once the latter is shipped. + +**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) + - [Application Payload](#application-payload) + - [`get_pow_chain_head`](#get_pow_chain_head) + - [`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 transition block handling and the addition of `ApplicationPayload`. + +### Block proposal + +#### Constructing the `BeaconBlockBody` + +##### Application Payload + +###### `get_pow_chain_head` + +Let `get_pow_chain_head() -> PowBlock` be the function that returns the head of the PoW chain. The body of the function is implementation specific. + +###### `produce_application_payload` + +Let `produce_application_payload(parent_hash: Bytes32) -> ApplicationPayload` be the function that produces new instance of application payload. +The body of this function is implementation dependent. + +* Set `block.body.application_payload = get_application_payload(state)` where: + +```python +def get_application_payload(state: BeaconState) -> ApplicationPayload: + if not is_transition_completed(state): + pow_block = get_pow_chain_head() + if pow_block.total_difficulty < TRANSITION_TOTAL_DIFFICULTY: + # Pre-merge, empty payload + return ApplicationPayload() + else: + # Signify merge via last PoW block_hash and an otherwise empty payload + return ApplicationPayload(block_hash=pow_block.block_hash) + + # Post-merge, normal payload + application_parent_hash = state.application_block_hash + return produce_application_payload(state.application_block_hash) +```