From e4fac790ac0e6cb4938326ed7362db9d63134ea7 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 26 Jan 2023 09:14:08 +0100 Subject: [PATCH] Spec draft for the verge Co-Authored-By: Dankrad Feist --- specs/verge/beacon-chain.md | 208 ++++++++++++++++++++++++++++++++++++ specs/verge/fork.md | 143 +++++++++++++++++++++++++ 2 files changed, 351 insertions(+) create mode 100644 specs/verge/beacon-chain.md create mode 100644 specs/verge/fork.md diff --git a/specs/verge/beacon-chain.md b/specs/verge/beacon-chain.md new file mode 100644 index 0000000000..2f380a38e8 --- /dev/null +++ b/specs/verge/beacon-chain.md @@ -0,0 +1,208 @@ +# The Verge -- The Beacon Chain + +## Table of contents + + + + + +- [Introduction](#introduction) +- [Custom types](#custom-types) +- [Preset](#preset) + - [Execution](#execution) +- [Containers](#containers) + - [Extended containers](#extended-containers) + - [`ExecutionPayload`](#executionpayload) + - [`ExecutionPayloadHeader`](#executionpayloadheader) + - [New containers](#new-containers) + - [`SuffixStateDiff`](#suffixstatediff) + - [`StemStateDiff`](#stemstatediff) + - [`IPAProof`](#ipaproof) + - [`VerkleProof`](#verkleproof) + - [`ExecutionWitness`](#executionwitness) +- [Beacon chain state transition function](#beacon-chain-state-transition-function) + - [Execution engine](#execution-engine) + - [`notify_new_payload`](#notify_new_payload) + - [Block processing](#block-processing) + - [Execution payload](#execution-payload) + - [`process_execution_payload`](#process_execution_payload) +- [Testing](#testing) + + + + +## Introduction + +This upgrade adds transaction execution to the beacon chain as part of the Verge upgrade. + +## Custom types + +| Name | SSZ equivalent | Description | +| - | - | - | +| `StateDiff` | `List[StemStateDiff, MAX_STEMS]` | Only valid if list is sorted by stems | +| `BandersnatchGroupElement` | `Bytes32` | | +| `BandersnatchFieldElement` | `Bytes32` | | +| `Stem` | `Bytes31` | | + +## Preset + +### Execution + +| Name | Value | +| - | - | +| `MAX_STEMS` | `2**16` | +| `MAX_COMMITMENTS_PER_STEM` | `33` | +| `VERKLE_WIDTH` | `256` | +| `IPA_PROOF_DEPTH` | `8` | + +## Containers + +### Extended containers + +#### `ExecutionPayload` + +```python +class ExecutionPayload(Container): + # Execution block header fields + parent_hash: Hash32 + fee_recipient: ExecutionAddress # 'beneficiary' in the yellow paper + state_root: Bytes32 + receipts_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + prev_randao: Bytes32 # 'difficulty' in the yellow paper + block_number: uint64 # 'number' in the yellow paper + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + block_hash: Hash32 # Hash of execution block + # Extra payload field + execution_witness: ExecutionWitness + transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] +``` + +#### `ExecutionPayloadHeader` + +```python +class ExecutionPayloadHeader(Container): + # Execution block header fields + parent_hash: Hash32 + fee_recipient: ExecutionAddress + state_root: Bytes32 + receipts_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + prev_randao: Bytes32 + block_number: uint64 + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + block_hash: Hash32 # Hash of execution block + transactions_root: Root + # Extra payload fields + execution_witness: ExecutionWitness +``` + +### New containers + +#### `SuffixStateDiff` + +```python +class SuffixStateDiff(Container): + suffix: Byte + + # Null means not currently present + current_value: Union[Null, Bytes32] + + # Null means value not updated + new_value: Union[Null, Bytes32] +``` + +*Note*: on the Kaustinen testnet, `new_value` is ommitted from the container. + +#### `StemStateDiff` + +```python +class StemStateDiff(Container): + stem: Stem + # Valid only if list is sorted by suffixes + suffix_diffs: List[SuffixStateDiff, VERKLE_WIDTH] +``` + +```python +# Valid only if list is sorted by stems +StateDiff = List[StemStateDiff, MAX_STEMS] +``` + +#### `IPAProof` + +```python +class IpaProof(Container): + C_L = Vector[BandersnatchGroupElement, IPA_PROOF_DEPTH] + C_R = Vector[BandersnatchGroupElement, IPA_PROOF_DEPTH] + final_evaluation = BandersnatchFieldElement +``` + +#### `VerkleProof` + +```python +class VerkleProof(Container): + other_stems: List[Bytes32, MAX_STEMS] + depth_extension_present: List[uint8, MAX_STEMS] + commitments_by_path: List[BandersnatchGroupElement, MAX_STEMS * MAX_COMMITMENTS_PER_STEM] + D: BandersnatchGroupElement + ipa_proof: IpaProof +``` + +#### `ExecutionWitness` + +```python +class ExecutionWitness(container): + state_diff: StateDiff + verkle_proof: VerkleProof +``` + +## Beacon chain state transition function + +### Block processing + +#### Execution payload + +##### `process_execution_payload` + +```python +def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None: + # Verify consistency of the parent hash with respect to the previous execution payload header + if is_merge_transition_complete(state): + assert payload.parent_hash == state.latest_execution_payload_header.block_hash + # Verify prev_randao + assert payload.prev_randao == 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.notify_new_payload(payload) + # Cache execution payload header + state.latest_execution_payload_header = ExecutionPayloadHeader( + parent_hash=payload.parent_hash, + fee_recipient=payload.fee_recipient, + state_root=payload.state_root, + receipts_root=payload.receipts_root, + logs_bloom=payload.logs_bloom, + prev_randao=payload.prev_randao, + block_number=payload.block_number, + 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), + execution_witness=payload.execution_witness, + ) +``` + +## Testing + +TBD \ No newline at end of file diff --git a/specs/verge/fork.md b/specs/verge/fork.md new file mode 100644 index 0000000000..627c1183c0 --- /dev/null +++ b/specs/verge/fork.md @@ -0,0 +1,143 @@ +# The Verge -- Fork Logic + +## Table of contents + + + + +- [Introduction](#introduction) +- [Configuration](#configuration) +- [Helper functions](#helper-functions) + - [Misc](#misc) + - [Modified `compute_fork_version`](#modified-compute_fork_version) +- [Fork to the Verge](#fork-to-capella) + - [Fork trigger](#fork-trigger) + - [Upgrading the state](#upgrading-the-state) + + + +## Introduction + +This document describes the process of the Verge upgrade. + +## Configuration + +Warning: this configuration is not definitive. + +| Name | Value | +| - | - | +| `VERGE_FORK_VERSION` | `Version('0x05000000')` | +| `VERGE_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** | + + +## Helper functions + +### Misc + +#### Modified `compute_fork_version` + +```python +def compute_fork_version(epoch: Epoch) -> Version: + """ + Return the fork version at the given ``epoch``. + """ + if epoch >= VERGE_FORK_EPOCH: + return VERGE_FORK_VERSION + if epoch >= CAPELLA_FORK_EPOCH: + return CAPELLA_FORK_VERSION + if epoch >= BELLATRIX_FORK_EPOCH: + return BELLATRIX_FORK_VERSION + if epoch >= ALTAIR_FORK_EPOCH: + return ALTAIR_FORK_VERSION + return GENESIS_FORK_VERSION +``` + +## Fork to the Verge + +### Fork trigger + +The fork is triggered at epoch `VERGE_FORK_EPOCH`. + +Note that for the pure verge networks, we don't apply `upgrade_to_verge` since it starts with the Verge version logic. + +### Upgrading the state + +If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == VERGE_FORK_EPOCH`, +an irregular state change is made to upgrade to the Verge. + +The upgrade occurs after the completion of the inner loop of `process_slots` that sets `state.slot` equal to `VERGE_FORK_EPOCH * SLOTS_PER_EPOCH`. +Care must be taken when transitioning through the fork boundary as implementations will need a modified [state transition function](../phase0/beacon-chain.md#beacon-chain-state-transition-function) that deviates from the Phase 0 document. +In particular, the outer `state_transition` function defined in the Phase 0 document will not expose the precise fork slot to execute the upgrade in the presence of skipped slots at the fork boundary. Instead, the logic must be within `process_slots`. + +```python +def upgrade_to_verge(pre: capella.BeaconState) -> BeaconState: + epoch = capella.get_current_epoch(pre) + latest_execution_payload_header = ExecutionPayloadHeader( + parent_hash=pre.latest_execution_payload_header.parent_hash, + fee_recipient=pre.latest_execution_payload_header.fee_recipient, + state_root=pre.latest_execution_payload_header.state_root, + receipts_root=pre.latest_execution_payload_header.receipts_root, + logs_bloom=pre.latest_execution_payload_header.logs_bloom, + prev_randao=pre.latest_execution_payload_header.prev_randao, + block_number=pre.latest_execution_payload_header.block_number, + gas_limit=pre.latest_execution_payload_header.gas_limit, + gas_used=pre.latest_execution_payload_header.gas_used, + timestamp=pre.latest_execution_payload_header.timestamp, + extra_data=pre.latest_execution_payload_header.extra_data, + base_fee_per_gas=pre.latest_execution_payload_header.base_fee_per_gas, + block_hash=pre.latest_execution_payload_header.block_hash, + transactions_root=pre.latest_execution_payload_header.transactions_root, + withdrawals_root=pre.latest_execution_payload_header.withdrawals_root, + execution_witness=ExecutionWitness([], []) # New in the Verge + ) + post = BeaconState( + # Versioning + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + current_version=VERGE_FORK_VERSION, + epoch=epoch, + ), + # History + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + # Eth1 + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + # Registry + validators=pre.validators, + balances=pre.balances, + # Randomness + randao_mixes=pre.randao_mixes, + # Slashings + slashings=pre.slashings, + # Participation + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + # Finality + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + # Inactivity + inactivity_scores=pre.inactivity_scores, + # Sync + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + # Execution-layer + latest_execution_payload_header=latest_execution_payload_header, + # Withdrawals + next_withdrawal_index=pre.next_withdrawal_index, + next_withdrawal_validator_index=pre.next_withdrawal_validator_index, + # Deep history valid from Capella onwards + # FIXME most likely wrong + historical_summaries=List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT]([]), # [New in Capella] + ) + + return post +```