From 776196e66d593cb988474549fc5b6c13fb393043 Mon Sep 17 00:00:00 2001 From: dankrad Date: Wed, 6 Mar 2019 14:46:52 +0100 Subject: [PATCH 01/24] Add tuple lengths Adding tuple lengths in BeaconState objects (this changes merkleization/serialization as no length mixin required) --- specs/core/0_beacon-chain.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 6bff0f705..2771d19fa 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -522,7 +522,7 @@ The following data structures are defined as [SimpleSerialize (SSZ)](https://git 'validator_registry_update_epoch': 'uint64', # Randomness and committees - 'latest_randao_mixes': ['bytes32'], + 'latest_randao_mixes': ['bytes32', LATEST_RANDAO_MIXES_LENGTH], 'previous_shuffling_start_shard': 'uint64', 'current_shuffling_start_shard': 'uint64', 'previous_shuffling_epoch': 'uint64', @@ -537,10 +537,10 @@ The following data structures are defined as [SimpleSerialize (SSZ)](https://git 'finalized_epoch': 'uint64', # Recent state - 'latest_crosslinks': [Crosslink], - 'latest_block_roots': ['bytes32'], - 'latest_active_index_roots': ['bytes32'], - 'latest_slashed_balances': ['uint64'], # Balances slashed at every withdrawal period + 'latest_crosslinks': [Crosslink, SHARD_COUNT], + 'latest_block_roots': ['bytes32', SLOTS_PER_HISTORICAL_ROOT], + 'latest_active_index_roots': ['bytes32', LATEST_ACTIVE_INDEX_ROOTS_LENGTH], + 'latest_slashed_balances': ['uint64', LATEST_SLASHED_EXIT_LENGTH], # Balances slashed at every withdrawal period 'latest_attestations': [PendingAttestation], 'batched_block_roots': ['bytes32'], From eece029cdfdf6672a929a9b9d8c960a53bd2fac1 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Wed, 6 Mar 2019 17:46:40 +0100 Subject: [PATCH 02/24] Array spec to [type] and tuple to [type, N]. Also make notation consistent: use "base" for base types --- specs/simple-serialize.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/specs/simple-serialize.md b/specs/simple-serialize.md index a452213b5..bb15bb6da 100644 --- a/specs/simple-serialize.md +++ b/specs/simple-serialize.md @@ -10,8 +10,8 @@ This is a **work in progress** describing typing, serialization and Merkleizatio - [Composite types](#composite-types) - [Aliases](#aliases) - [Serialization](#serialization) - - [`uintN`](#uintn) - - [`bool`](#bool) + - [`"uintN"`](#uintn) + - [`"bool"`](#bool) - [Tuples, containers, lists](#tuples-containers-lists) - [Deserialization](#deserialization) - [Merkleization](#merkleization) @@ -28,29 +28,29 @@ This is a **work in progress** describing typing, serialization and Merkleizatio ## Typing ### Basic types -* `uintN`: `N`-bit unsigned integer (where `N in [8, 16, 32, 64, 128, 256]`) -* `bool`: `True` or `False` +* `"uintN"`: `N`-bit unsigned integer (where `N in [8, 16, 32, 64, 128, 256]`) +* `"bool"`: `True` or `False` ### Composite types * **container**: ordered heterogenous collection of values * key-pair curly bracket notation `{}`, e.g. `{'foo': "uint64", 'bar': "bool"}` * **tuple**: ordered fixed-length homogeneous collection of values - * angle bracket notation `[N]`, e.g. `uint64[N]` + * angle bracket notation `[type, N]`, e.g. `["uint64", N]` * **list**: ordered variable-length homogenous collection of values - * angle bracket notation `[]`, e.g. `uint64[]` + * angle bracket notation `[type]`, e.g. `["uint64"]` ### Aliases For convenience we alias: -* `byte` to `uint8` -* `bytes` to `byte[]` -* `bytesN` to `byte[N]` +* `"byte"` to `"uint8"` +* `"bytes"` to `["byte"]` +* `"bytesN"` to `["byte", N]` ## Serialization -We recursively define the `serialize` function which consumes an object `value` (of the type specified) and returns a bytestring of type `bytes`. +We recursively define the `serialize` function which consumes an object `value` (of the type specified) and returns a bytestring of type `"bytes"`. *Note*: In the function definitions below (`serialize`, `hash_tree_root`, `signed_root`, etc.) objects implicitly carry their type. @@ -95,7 +95,7 @@ We first define helper functions: * `pack`: Given ordered objects of the same basic type, serialize them, pack them into `BYTES_PER_CHUNK`-byte chunks, right-pad the last chunk with zero bytes, and return the chunks. * `merkleize`: Given ordered `BYTES_PER_CHUNK`-byte chunks, if necessary append zero chunks so that the number of chunks is a power of two, Merkleize the chunks, and return the root. -* `mix_in_length`: Given a Merkle root `root` and a length `length` (`uint256` little-endian serialization) return `hash(root + length)`. +* `mix_in_length`: Given a Merkle root `root` and a length `length` (`"uint256"` little-endian serialization) return `hash(root + length)`. We now define Merkleization `hash_tree_root(value)` of an object `value` recursively: @@ -106,7 +106,7 @@ We now define Merkleization `hash_tree_root(value)` of an object `value` recursi ## Self-signed containers -Let `value` be a self-signed container object. The convention is that the signature (e.g. a `bytes96` BLS12-381 signature) be the last field of `value`. Further, the signed message for `value` is `signed_root(value) = hash_tree_root(truncate_last(value))` where `truncate_last` truncates the last element of `value`. +Let `value` be a self-signed container object. The convention is that the signature (e.g. a `"bytes96"` BLS12-381 signature) be the last field of `value`. Further, the signed message for `value` is `signed_root(value) = hash_tree_root(truncate_last(value))` where `truncate_last` truncates the last element of `value`. ## Implementations From 81f48ea1c3bc16c5b48429da7eb1dce57463b095 Mon Sep 17 00:00:00 2001 From: Dankrad Feist Date: Wed, 6 Mar 2019 21:11:50 +0100 Subject: [PATCH 03/24] Also fix length of Deposit.proof --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 2031587cf..b50f4c36a 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -387,7 +387,7 @@ The following data structures are defined as [SimpleSerialize (SSZ)](https://git ```python { # Branch in the deposit tree - 'proof': ['bytes32'], + 'proof': ['bytes32', DEPOSIT_CONTRACT_TREE_DEPTH], # Index in the deposit tree 'index': 'uint64', # Data From 987c741bea582fce0839bc2ad2e14abe50a15d66 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 7 Mar 2019 09:44:55 -0700 Subject: [PATCH 04/24] fix state types --- specs/core/0_beacon-chain.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index d4739173e..796305953 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -543,8 +543,7 @@ The following data structures are defined as [SimpleSerialize (SSZ)](https://git 'latest_active_index_roots': ['bytes32', LATEST_ACTIVE_INDEX_ROOTS_LENGTH], 'latest_slashed_balances': ['uint64', LATEST_SLASHED_EXIT_LENGTH], # Balances slashed at every withdrawal period 'latest_block_header': BeaconBlockHeader, # `latest_block_header.state_root == ZERO_HASH` temporarily - 'latest_attestations': [PendingAttestation], - 'batched_block_roots': ['bytes32'], + 'historical_roots': ['bytes32'], # Ethereum 1.0 chain data 'latest_eth1_data': Eth1Data, From 250455a67e18e51abe5566a0ea129873922c7427 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 7 Mar 2019 10:03:25 -0700 Subject: [PATCH 05/24] Apply suggestions from code review Co-Authored-By: djrtwo --- specs/core/0_beacon-chain.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 796305953..002770913 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1899,7 +1899,7 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: data=attestation.data, aggregation_bitfield=attestation.aggregation_bitfield, custody_bitfield=attestation.custody_bitfield, - inclusion_slot=state.slot + inclusion_slot=state.slot, ) if slot_to_epoch(attestation.data.slot) == get_current_epoch(state): state.current_epoch_attestations.append(pending_attestation) @@ -2057,7 +2057,7 @@ def get_winning_root_and_participants(state: BeaconState, shard: Shard) -> Tuple if len(all_roots) == 0: return ZERO_HASH, [] - def get_attestations_for(root) -> List[PendingAttestation]: + def get_attestations_for(root: Bytes32) -> List[PendingAttestation]: return [a for a in valid_attestations if a.data.crosslink_data_root == root] # Winning crosslink root is the root with the most votes for it, ties broken in favor of @@ -2134,7 +2134,7 @@ Run the following function: ```python def process_crosslinks(state: BeaconState) -> None: current_epoch = get_current_epoch(state) - previous_epoch = current_epoch - 1 + previous_epoch = get_previous_epoch(state) next_epoch = current_epoch + 1 for slot in range(get_epoch_start_slot(previous_epoch), get_epoch_start_slot(next_epoch)): for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot): @@ -2144,7 +2144,7 @@ def process_crosslinks(state: BeaconState) -> None: if 3 * participating_balance >= 2 * total_balance: state.latest_crosslinks[shard] = Crosslink( epoch=slot_to_epoch(slot), - crosslink_data_root=winning_root + crosslink_data_root=winning_root, ) ``` From 8dec7d805cf7673f77b762522532fe15aa944d0c Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 7 Mar 2019 11:02:13 -0700 Subject: [PATCH 06/24] reorder ssz types topologically --- specs/core/0_beacon-chain.md | 395 +++++++++++++++++------------------ 1 file changed, 190 insertions(+), 205 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 002770913..898fc3c86 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -21,36 +21,31 @@ - [Max transactions per block](#max-transactions-per-block) - [Signature domains](#signature-domains) - [Data structures](#data-structures) - - [Beacon chain transactions](#beacon-chain-transactions) - - [Proposer slashings](#proposer-slashings) - - [`ProposerSlashing`](#proposerslashing) - - [Attester slashings](#attester-slashings) - - [`AttesterSlashing`](#attesterslashing) - - [`SlashableAttestation`](#slashableattestation) - - [Attestations](#attestations) - - [`Attestation`](#attestation) - - [`AttestationData`](#attestationdata) - - [`AttestationDataAndCustodyBit`](#attestationdataandcustodybit) - - [Deposits](#deposits) - - [`Deposit`](#deposit) - - [`DepositData`](#depositdata) - - [`DepositInput`](#depositinput) - - [Voluntary exits](#voluntary-exits) - - [`VoluntaryExit`](#voluntaryexit) - - [Transfers](#transfers) - - [`Transfer`](#transfer) - - [Beacon chain blocks](#beacon-chain-blocks) - - [`BeaconBlock`](#beaconblock) - - [`BeaconBlockHeader`](#beaconblockheader) - - [`BeaconBlockBody`](#beaconblockbody) - - [Beacon chain state](#beacon-chain-state) - - [`BeaconState`](#beaconstate) - - [`Validator`](#validator) - - [`Crosslink`](#crosslink) - - [`PendingAttestation`](#pendingattestation) + - [Misc dependencies](#misc-dependencies) - [`Fork`](#fork) + - [`Crosslink`](#crosslink) - [`Eth1Data`](#eth1data) - [`Eth1DataVote`](#eth1datavote) + - [`AttestationData`](#attestationdata) + - [`AttestationDataAndCustodyBit`](#attestationdataandcustodybit) + - [`SlashableAttestation`](#slashableattestation) + - [`DepositInput`](#depositinput) + - [`DepositData`](#depositdata) + - [`BeaconBlockHeader`](#beaconblockheader) + - [`Validator`](#validator) + - [`PendingAttestation`](#pendingattestation) + - [Beacon transactions](#beacon-transactions) + - [`ProposerSlashing`](#proposerslashing) + - [`AttesterSlashing`](#attesterslashing) + - [`Attestation`](#attestation) + - [`Deposit`](#deposit) + - [`VoluntaryExit`](#voluntaryexit) + - [`Transfer`](#transfer) + - [Beacon blocks](#beacon-blocks) + - [`BeaconBlockBody`](#beaconblockbody) + - [`BeaconBlock`](#beaconblock) + - [Beacon state](#beacon-state) + - [`BeaconState`](#beaconstate) - [Custom Types](#custom-types) - [Helper functions](#helper-functions) - [`xor`](#xor) @@ -116,8 +111,6 @@ - [Beacon chain fork choice rule](#beacon-chain-fork-choice-rule) - [Beacon chain state transition function](#beacon-chain-state-transition-function) - [Per-slot processing](#per-slot-processing) - - [Slot](#slot) - - [Block roots](#block-roots) - [Per-block processing](#per-block-processing) - [Block header](#block-header) - [RANDAO](#randao) @@ -136,8 +129,8 @@ - [Eth1 data](#eth1-data-1) - [Rewards and penalties](#rewards-and-penalties) - [Justification and finalization](#justification-and-finalization) - - [Attestation inclusion](#attestation-inclusion) - [Crosslinks](#crosslinks-1) + - [Apply rewards](#apply-rewards) - [Ejections](#ejections) - [Validator registry and shuffling seed data](#validator-registry-and-shuffling-seed-data) - [Slashings and exit queue](#slashings-and-exit-queue) @@ -286,69 +279,57 @@ Code snippets appearing in `this style` are to be interpreted as Python code. The following data structures are defined as [SimpleSerialize (SSZ)](https://github.com/ethereum/eth2.0-specs/blob/master/specs/simple-serialize.md) objects. -### Beacon chain transactions +The types are defined topologically to aid in facilitating an executable version of the spec. -#### Proposer slashings +### Misc dependencies -##### `ProposerSlashing` +#### `Fork` ```python { - # Proposer index - 'proposer_index': 'uint64', - # First block header - 'header_1': BeaconBlockHeader, - # Second block header - 'header_2': BeaconBlockHeader, + # Previous fork version + 'previous_version': 'uint64', + # Current fork version + 'current_version': 'uint64', + # Fork epoch number + 'epoch': 'uint64', } ``` -#### Attester slashings - -##### `AttesterSlashing` +#### `Crosslink` ```python { - # First slashable attestation - 'slashable_attestation_1': SlashableAttestation, - # Second slashable attestation - 'slashable_attestation_2': SlashableAttestation, + # Epoch number + 'epoch': 'uint64', + # Shard data since the previous crosslink + 'crosslink_data_root': 'bytes32', } ``` -##### `SlashableAttestation` +#### `Eth1Data` ```python { - # Validator indices - 'validator_indices': ['uint64'], - # Attestation data - 'data': AttestationData, - # Custody bitfield - 'custody_bitfield': 'bytes', - # Aggregate signature - 'aggregate_signature': 'bytes96', + # Root of the deposit tree + 'deposit_root': 'bytes32', + # Block hash + 'block_hash': 'bytes32', } ``` -#### Attestations - -##### `Attestation` +#### `Eth1DataVote` ```python { - # Attester aggregation bitfield - 'aggregation_bitfield': 'bytes', - # Attestation data - 'data': AttestationData, - # Custody bitfield - 'custody_bitfield': 'bytes', - # BLS aggregate signature - 'aggregate_signature': 'bytes96', + # Data being voted for + 'eth1_data': Eth1Data, + # Vote count + 'vote_count': 'uint64', } ``` -##### `AttestationData` +#### `AttestationData` ```python { @@ -371,7 +352,7 @@ The following data structures are defined as [SimpleSerialize (SSZ)](https://git } ``` -##### `AttestationDataAndCustodyBit` +#### `AttestationDataAndCustodyBit` ```python { @@ -382,35 +363,22 @@ The following data structures are defined as [SimpleSerialize (SSZ)](https://git } ``` -#### Deposits - -##### `Deposit` +#### `SlashableAttestation` ```python { - # Branch in the deposit tree - 'proof': ['bytes32', DEPOSIT_CONTRACT_TREE_DEPTH], - # Index in the deposit tree - 'index': 'uint64', - # Data - 'deposit_data': DepositData, + # Validator indices + 'validator_indices': ['uint64'], + # Attestation data + 'data': AttestationData, + # Custody bitfield + 'custody_bitfield': 'bytes', + # Aggregate signature + 'aggregate_signature': 'bytes96', } ``` -##### `DepositData` - -```python -{ - # Amount in Gwei - 'amount': 'uint64', - # Timestamp from deposit contract - 'timestamp': 'uint64', - # Deposit input - 'deposit_input': DepositInput, -} -``` - -##### `DepositInput` +#### `DepositInput` ```python { @@ -423,9 +391,122 @@ The following data structures are defined as [SimpleSerialize (SSZ)](https://git } ``` -#### Voluntary exits +#### `DepositData` -##### `VoluntaryExit` +```python +{ + # Amount in Gwei + 'amount': 'uint64', + # Timestamp from deposit contract + 'timestamp': 'uint64', + # Deposit input + 'deposit_input': DepositInput, +} +``` + +#### `BeaconBlockHeader` + +```python +{ + 'slot': 'uint64', + 'previous_block_root': 'bytes32', + 'state_root': 'bytes32', + 'block_body_root': 'bytes32', + 'signature': 'bytes96', +} +``` + +#### `Validator` + +```python +{ + # BLS public key + 'pubkey': 'bytes48', + # Withdrawal credentials + 'withdrawal_credentials': 'bytes32', + # Epoch when validator activated + 'activation_epoch': 'uint64', + # Epoch when validator exited + 'exit_epoch': 'uint64', + # Epoch when validator is eligible to withdraw + 'withdrawable_epoch': 'uint64', + # Did the validator initiate an exit + 'initiated_exit': 'bool', + # Was the validator slashed + 'slashed': 'bool', +} +``` + +#### `PendingAttestation` + +```python +{ + # Attester aggregation bitfield + 'aggregation_bitfield': 'bytes', + # Attestation data + 'data': AttestationData, + # Custody bitfield + 'custody_bitfield': 'bytes', + # Inclusion slot + 'inclusion_slot': 'uint64', +} +``` + +### Beacon transactions + +#### `ProposerSlashing` + +```python +{ + # Proposer index + 'proposer_index': 'uint64', + # First block header + 'header_1': BeaconBlockHeader, + # Second block header + 'header_2': BeaconBlockHeader, +} +``` + +#### `AttesterSlashing` + +```python +{ + # First slashable attestation + 'slashable_attestation_1': SlashableAttestation, + # Second slashable attestation + 'slashable_attestation_2': SlashableAttestation, +} +``` + +#### `Attestation` + +```python +{ + # Attester aggregation bitfield + 'aggregation_bitfield': 'bytes', + # Attestation data + 'data': AttestationData, + # Custody bitfield + 'custody_bitfield': 'bytes', + # BLS aggregate signature + 'aggregate_signature': 'bytes96', +} +``` + +#### `Deposit` + +```python +{ + # Branch in the deposit tree + 'proof': ['bytes32', DEPOSIT_CONTRACT_TREE_DEPTH], + # Index in the deposit tree + 'index': 'uint64', + # Data + 'deposit_data': DepositData, +} +``` + +#### `VoluntaryExit` ```python { @@ -438,9 +519,7 @@ The following data structures are defined as [SimpleSerialize (SSZ)](https://git } ``` -#### Transfers - -##### `Transfer` +#### `Transfer` ```python { @@ -461,32 +540,7 @@ The following data structures are defined as [SimpleSerialize (SSZ)](https://git } ``` -### Beacon chain blocks - -#### `BeaconBlock` - -```python -{ - # Header - 'slot': 'uint64', - 'previous_block_root': 'bytes32', - 'state_root': 'bytes32', - 'body': BeaconBlockBody, - 'signature': 'bytes96', -} -``` - -#### `BeaconBlockHeader` - -```python -{ - 'slot': 'uint64', - 'previous_block_root': 'bytes32', - 'state_root': 'bytes32', - 'block_body_root': 'bytes32', - 'signature': 'bytes96', -} -``` +### Beacon blocks #### `BeaconBlockBody` @@ -503,7 +557,20 @@ The following data structures are defined as [SimpleSerialize (SSZ)](https://git } ``` -### Beacon chain state +#### `BeaconBlock` + +```python +{ + # Header + 'slot': 'uint64', + 'previous_block_root': 'bytes32', + 'state_root': 'bytes32', + 'body': BeaconBlockBody, + 'signature': 'bytes96', +} +``` + +### Beacon state #### `BeaconState` @@ -552,88 +619,6 @@ The following data structures are defined as [SimpleSerialize (SSZ)](https://git } ``` -#### `Validator` - -```python -{ - # BLS public key - 'pubkey': 'bytes48', - # Withdrawal credentials - 'withdrawal_credentials': 'bytes32', - # Epoch when validator activated - 'activation_epoch': 'uint64', - # Epoch when validator exited - 'exit_epoch': 'uint64', - # Epoch when validator is eligible to withdraw - 'withdrawable_epoch': 'uint64', - # Did the validator initiate an exit - 'initiated_exit': 'bool', - # Was the validator slashed - 'slashed': 'bool', -} -``` - -#### `Crosslink` - -```python -{ - # Epoch number - 'epoch': 'uint64', - # Shard data since the previous crosslink - 'crosslink_data_root': 'bytes32', -} -``` - -#### `PendingAttestation` - -```python -{ - # Attester aggregation bitfield - 'aggregation_bitfield': 'bytes', - # Attestation data - 'data': AttestationData, - # Custody bitfield - 'custody_bitfield': 'bytes', - # Inclusion slot - 'inclusion_slot': 'uint64', -} -``` - -#### `Fork` - -```python -{ - # Previous fork version - 'previous_version': 'uint64', - # Current fork version - 'current_version': 'uint64', - # Fork epoch number - 'epoch': 'uint64', -} -``` - -#### `Eth1Data` - -```python -{ - # Root of the deposit tree - 'deposit_root': 'bytes32', - # Block hash - 'block_hash': 'bytes32', -} -``` - -#### `Eth1DataVote` - -```python -{ - # Data being voted for - 'eth1_data': Eth1Data, - # Vote count - 'vote_count': 'uint64', -} -``` - ## Custom Types We define the following Python custom types for type hinting and readability: From 6cf14884a83474ef164bb482f4d976dca818c08c Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 7 Mar 2019 12:05:34 -0700 Subject: [PATCH 07/24] epoch transition at start of epoch --- specs/core/0_beacon-chain.md | 687 ++++++++++++++++++----------------- 1 file changed, 351 insertions(+), 336 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 002770913..281acfa79 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -115,9 +115,20 @@ - [Beacon chain processing](#beacon-chain-processing) - [Beacon chain fork choice rule](#beacon-chain-fork-choice-rule) - [Beacon chain state transition function](#beacon-chain-state-transition-function) + - [Per-epoch processing](#per-epoch-processing) + - [Helper functions](#helper-functions-1) + - [Justification](#justification) + - [Crosslinks](#crosslinks) + - [Eth1 data](#eth1-data-1) + - [Rewards and penalties](#rewards-and-penalties) + - [Justification and finalization](#justification-and-finalization) + - [Crosslinks](#crosslinks-1) + - [Apply rewards](#apply-rewards) + - [Ejections](#ejections) + - [Validator registry and shuffling seed data](#validator-registry-and-shuffling-seed-data) + - [Slashings and exit queue](#slashings-and-exit-queue) + - [Final updates](#final-updates) - [Per-slot processing](#per-slot-processing) - - [Slot](#slot) - - [Block roots](#block-roots) - [Per-block processing](#per-block-processing) - [Block header](#block-header) - [RANDAO](#randao) @@ -129,20 +140,7 @@ - [Deposits](#deposits-1) - [Voluntary exits](#voluntary-exits-1) - [Transfers](#transfers-1) - - [Per-epoch processing](#per-epoch-processing) - - [Helper functions](#helper-functions-1) - - [Justification](#justification) - - [Crosslinks](#crosslinks) - - [Eth1 data](#eth1-data-1) - - [Rewards and penalties](#rewards-and-penalties) - - [Justification and finalization](#justification-and-finalization) - - [Attestation inclusion](#attestation-inclusion) - - [Crosslinks](#crosslinks-1) - - [Ejections](#ejections) - - [Validator registry and shuffling seed data](#validator-registry-and-shuffling-seed-data) - - [Slashings and exit queue](#slashings-and-exit-queue) - - [Final updates](#final-updates) - - [State root verification](#state-root-verification) + - [State root verification](#state-root-verification) - [References](#references) - [Normative](#normative) - [Informative](#informative) @@ -1674,327 +1672,38 @@ def lmd_ghost(store: Store, start_state: BeaconState, start_block: BeaconBlock) ## Beacon chain state transition function -We now define the state transition function. At a high level the state transition is made up of three parts: +We now define the state transition function. At a high level the state transition is made up of four parts: -1. The per-slot transitions, which happens at the start of every slot. -2. The per-block transitions, which happens at every block. -3. The per-epoch transitions, which happens at the end of the last slot of every epoch (i.e. `(state.slot + 1) % SLOTS_PER_EPOCH == 0`). +1. State-root caching, which happens at the start of every slot. +2. The per-epoch transitions, which happens at the start of the first slot of every epoch. +3. The per-slot transitions, which happens at every slot. +4. The per-block transitions, which happens at every block. -The per-slot transitions focus on the slot counter and block roots records updates; the per-block transitions generally focus on verifying aggregate signatures and saving temporary records relating to the per-block activity in the `BeaconState`; the per-epoch transitions focus on the [validator](#dfn-validator) registry, including adjusting balances and activating and exiting [validators](#dfn-validator), as well as processing crosslinks and managing block justification/finalization. +The state-root caching, caches the state root of the previous slot; +The per-epoch transitions focus on the [validator](#dfn-validator) registry, including adjusting balances and activating and exiting [validators](#dfn-validator), as well as processing crosslinks and managing block justification/finalization; +The per-slot transitions focus on the slot counter and block roots records updates; +The per-block transitions generally focus on verifying aggregate signatures and saving temporary records relating to the per-block activity in the `BeaconState`. Beacon blocks that trigger unhandled Python exceptions (e.g. out-of-range list accesses) and failed `assert`s during the state transition are considered invalid. -_Note_: If there are skipped slots between a block and its parent block, run the steps in the [per-slot](#per-slot-processing) and [per-epoch](#per-epoch-processing) sections once for each skipped slot and then once for the slot containing the new block. +_Note_: If there are skipped slots between a block and its parent block, run the steps in the [per-epoch](#per-epoch-processing) and [per-slot](#per-slot-processing) sections once for each skipped slot and then once for the slot containing the new block. -### Per-slot processing +### State-root caching At every `slot > GENESIS_SLOT` run the following function: ```python -def advance_slot(state: BeaconState) -> None: +def store_state_root(state: BeaconState) -> None: state.latest_state_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = hash_tree_root(state) - state.slot += 1 - if state.latest_block_header.state_root == ZERO_HASH: - state.latest_block_header.state_root = get_state_root(state, state.slot - 1) - state.latest_block_roots[(state.slot - 1) % SLOTS_PER_HISTORICAL_ROOT] = hash_tree_root(state.latest_block_header) -``` - -### Per-block processing - -For every `block` except the genesis block, run `process_block_header(state, block)`, `process_randao(state, block)` and `process_eth1_data(state, block)`. - -#### Block header - -```python -def process_block_header(state: BeaconState, block: BeaconBlock) -> None: - # Verify that the slots match - assert block.slot == state.slot - # Verify that the parent matches - assert block.previous_block_root == hash_tree_root(state.latest_block_header) - # Save current block as the new latest block - state.latest_block_header = get_temporary_block_header(block) - # Verify proposer signature - proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)] - assert bls_verify( - pubkey=proposer.pubkey, - message_hash=signed_root(block), - signature=block.signature, - domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_BEACON_BLOCK) - ) -``` - -#### RANDAO - -```python -def process_randao(state: BeaconState, block: BeaconBlock) -> None: - proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)] - # Verify that the provided randao value is valid - assert bls_verify( - pubkey=proposer.pubkey, - message_hash=hash_tree_root(get_current_epoch(state)), - signature=block.body.randao_reveal, - domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_RANDAO) - ) - # Mix it in - state.latest_randao_mixes[get_current_epoch(state) % LATEST_RANDAO_MIXES_LENGTH] = ( - xor(get_randao_mix(state, get_current_epoch(state)), - hash(block.body.randao_reveal)) - ) -``` - -#### Eth1 data - -```python -def process_eth1_data(state: BeaconState, block: BeaconBlock) -> None: - for eth1_data_vote in state.eth1_data_votes: - # If someone else has already voted for the same hash, add to its counter - if eth1_data_vote.eth1_data == block.body.eth1_data: - eth1_data_vote.vote_count += 1 - return - # If we're seeing this hash for the first time, make a new counter - state.eth1_data_votes.append(Eth1DataVote(eth1_data=block.body.eth1_data, vote_count=1)) -``` - -#### Transactions - -##### Proposer slashings - -Verify that `len(block.body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS`. - -For each `proposer_slashing` in `block.body.proposer_slashings`, run the following function: - -```python -def process_proposer_slashing(state: BeaconState, - proposer_slashing: ProposerSlashing) -> None: - """ - Process ``ProposerSlashing`` transaction. - Note that this function mutates ``state``. - """ - proposer = state.validator_registry[proposer_slashing.proposer_index] - # Verify that the slot is the same - assert proposer_slashing.header_1.slot == proposer_slashing.header_2.slot - # But the roots are different! - assert hash_tree_root(proposer_slashing.header_1) != hash_tree_root(proposer_slashing.header_2) - # Proposer is not yet slashed - assert proposer.slashed is False - # Signatures are valid - for header in (proposer_slashing.header_1, proposer_slashing.header_2): - assert bls_verify( - pubkey=proposer.pubkey, - message_hash=signed_root(header), - signature=header.signature, - domain=get_domain(state.fork, slot_to_epoch(header.slot), DOMAIN_BEACON_BLOCK) - ) - slash_validator(state, proposer_slashing.proposer_index) -``` - -##### Attester slashings - -Verify that `len(block.body.attester_slashings) <= MAX_ATTESTER_SLASHINGS`. - -For each `attester_slashing` in `block.body.attester_slashings`, run the following function: - -```python -def process_attester_slashing(state: BeaconState, - attester_slashing: AttesterSlashing) -> None: - """ - Process ``AttesterSlashing`` transaction. - Note that this function mutates ``state``. - """ - attestation1 = attester_slashing.slashable_attestation_1 - attestation2 = attester_slashing.slashable_attestation_2 - # Check that the attestations are conflicting - assert attestation1.data != attestation2.data - assert ( - is_double_vote(attestation1.data, attestation2.data) or - is_surround_vote(attestation1.data, attestation2.data) - ) - assert verify_slashable_attestation(state, attestation1) - assert verify_slashable_attestation(state, attestation2) - slashable_indices = [ - index for index in attestation1.validator_indices - if ( - index in attestation2.validator_indices and - state.validator_registry[index].slashed is False - ) - ] - assert len(slashable_indices) >= 1 - for index in slashable_indices: - slash_validator(state, index) -``` - -##### Attestations - -Verify that `len(block.body.attestations) <= MAX_ATTESTATIONS`. - -For each `attestation` in `block.body.attestations`, run the following function: - -```python -def process_attestation(state: BeaconState, attestation: Attestation) -> None: - """ - Process ``Attestation`` transaction. - Note that this function mutates ``state``. - """ - # Can't submit attestations that are too far in history (or in prehistory) - assert attestation.data.slot >= GENESIS_SLOT - assert state.slot < attestation.data.slot + SLOTS_PER_EPOCH - # Can't submit attestations too quickly - assert attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot - # Verify that the justified epoch is correct, case 1: current epoch attestations - if slot_to_epoch(attestation.data.slot + 1) >= get_current_epoch(state): - assert attestation.data.justified_epoch == state.justified_epoch - # Case 2: previous epoch attestations - else: - assert attestation.data.justified_epoch == state.previous_justified_epoch - # Check that the justified block root is correct - assert attestation.data.justified_block_root == get_block_root( - state, get_epoch_start_slot(attestation.data.justified_epoch) - ) - # Check that the crosslink data is valid - acceptable_crosslink_data = { - # Case 1: Latest crosslink matches the one in the state - attestation.data.latest_crosslink, - # Case 2: State has already been updated, state's latest crosslink matches the crosslink - # the attestation is trying to create - Crosslink( - crosslink_data_root=attestation.data.crosslink_data_root, - epoch=slot_to_epoch(attestation.data.slot) - ) - } - assert state.latest_crosslinks[attestation.data.shard] in acceptable_crosslink_data - # Attestation must be nonempty! - assert attestation.aggregation_bitfield != b'\x00' * len(attestation.aggregation_bitfield) - # Custody must be empty (to be removed in phase 1) - assert attestation.custody_bitfield == b'\x00' * len(attestation.custody_bitfield) - # Get the committee for the specific shard that this attestation is for - crosslink_committee = [ - committee for committee, shard in get_crosslink_committees_at_slot(state, attestation.data.slot) - if shard == attestation.data.shard - ][0] - # Custody bitfield must be a subset of the attestation bitfield - for i in range(len(crosslink_committee)): - if get_bitfield_bit(attestation.aggregation_bitfield, i) == 0b0: - assert get_bitfield_bit(attestation.custody_bitfield, i) == 0b0 - # Verify aggregate signature - participants = get_attestation_participants(state, attestation.data, attestation.aggregation_bitfield) - custody_bit_1_participants = get_attestation_participants(state, attestation.data, attestation.custody_bitfield) - custody_bit_0_participants = [i for i in participants if i not in custody_bit_1_participants] - - assert bls_verify_multiple( - pubkeys=[ - bls_aggregate_pubkeys([state.validator_registry[i].pubkey for i in custody_bit_0_participants]), - bls_aggregate_pubkeys([state.validator_registry[i].pubkey for i in custody_bit_1_participants]), - ], - message_hashes=[ - hash_tree_root(AttestationDataAndCustodyBit(data=attestation.data, custody_bit=0b0)), - hash_tree_root(AttestationDataAndCustodyBit(data=attestation.data, custody_bit=0b1)), - ], - signature=attestation.aggregate_signature, - domain=get_domain(state.fork, slot_to_epoch(attestation.data.slot), DOMAIN_ATTESTATION), - ) - # Crosslink data root is zero (to be removed in phase 1) - assert attestation.data.crosslink_data_root == ZERO_HASH - # Apply the attestation - pending_attestation = PendingAttestation( - data=attestation.data, - aggregation_bitfield=attestation.aggregation_bitfield, - custody_bitfield=attestation.custody_bitfield, - inclusion_slot=state.slot, - ) - if slot_to_epoch(attestation.data.slot) == get_current_epoch(state): - state.current_epoch_attestations.append(pending_attestation) - elif slot_to_epoch(attestation.data.slot) == get_previous_epoch(state): - state.previous_epoch_attestations.append(pending_attestation) -``` - -##### Deposits - -Verify that `len(block.body.deposits) <= MAX_DEPOSITS`. - -For each `deposit` in `block.body.deposits`, run `process_deposit(state, deposit)`. - -##### Voluntary exits - -Verify that `len(block.body.voluntary_exits) <= MAX_VOLUNTARY_EXITS`. - -For each `exit` in `block.body.voluntary_exits`, run the following function: - -```python -def process_exit(state: BeaconState, exit: VoluntaryExit) -> None: - """ - Process ``VoluntaryExit`` transaction. - Note that this function mutates ``state``. - """ - validator = state.validator_registry[exit.validator_index] - # Verify the validator has not yet exited - assert validator.exit_epoch > get_delayed_activation_exit_epoch(get_current_epoch(state)) - # Exits must specify an epoch when they become valid; they are not valid before then - assert get_current_epoch(state) >= exit.epoch - # Verify signature - assert bls_verify( - pubkey=validator.pubkey, - message_hash=signed_root(exit), - signature=exit.signature, - domain=get_domain(state.fork, exit.epoch, DOMAIN_VOLUNTARY_EXIT) - ) - # Run the exit - initiate_validator_exit(state, exit.validator_index) -``` - -##### Transfers - -Note: Transfers are a temporary functionality for phases 0 and 1, to be removed in phase 2. - -Verify that `len(block.body.transfers) <= MAX_TRANSFERS` and that all transfers are distinct. - -For each `transfer` in `block.body.transfers`, run the following function: - -```python -def process_transfer(state: BeaconState, transfer: Transfer) -> None: - """ - Process ``Transfer`` transaction. - Note that this function mutates ``state``. - """ - # Verify the amount and fee aren't individually too big (for anti-overflow purposes) - assert state.validator_balances[transfer.sender] >= max(transfer.amount, transfer.fee) - # Verify that we have enough ETH to send, and that after the transfer the balance will be either - # exactly zero or at least MIN_DEPOSIT_AMOUNT - assert ( - state.validator_balances[transfer.sender] == transfer.amount + transfer.fee or - state.validator_balances[transfer.sender] >= transfer.amount + transfer.fee + MIN_DEPOSIT_AMOUNT - ) - # A transfer is valid in only one slot - assert state.slot == transfer.slot - # Only withdrawn or not-yet-deposited accounts can transfer - assert ( - get_current_epoch(state) >= state.validator_registry[transfer.sender].withdrawable_epoch or - state.validator_registry[transfer.sender].activation_epoch == FAR_FUTURE_EPOCH - ) - # Verify that the pubkey is valid - assert ( - state.validator_registry[transfer.sender].withdrawal_credentials == - BLS_WITHDRAWAL_PREFIX_BYTE + hash(transfer.pubkey)[1:] - ) - # Verify that the signature is valid - assert bls_verify( - pubkey=transfer.pubkey, - message_hash=signed_root(transfer), - signature=transfer.signature, - domain=get_domain(state.fork, slot_to_epoch(transfer.slot), DOMAIN_TRANSFER) - ) - # Process the transfer - state.validator_balances[transfer.sender] -= transfer.amount + transfer.fee - state.validator_balances[transfer.recipient] += transfer.amount - state.validator_balances[get_beacon_proposer_index(state, state.slot)] += transfer.fee ``` ### Per-epoch processing -The steps below happen when `(state.slot + 1) % SLOTS_PER_EPOCH == 0`. +The steps below happen when `state.slot > GENESIS_SLOT and (state.slot + 1) % SLOTS_PER_EPOCH == 0`. #### Helper functions - -We define some helper functions: + +We define some helper functions utilized when processing an epoch transition: ```python def get_current_total_balance(state: BeaconState) -> Gwei: @@ -2022,24 +1731,24 @@ def get_attesting_balance(state: BeaconState, attestations: List[PendingAttestat ```python def get_current_epoch_boundary_attestations(state: BeaconState) -> List[PendingAttestation]: return [ - a for a in state.current_epoch_attestations if - a.data.epoch_boundary_root == get_block_root(state, get_epoch_start_slot(get_current_epoch(state))) + a for a in state.current_epoch_attestations + if a.data.epoch_boundary_root == get_block_root(state, get_epoch_start_slot(get_current_epoch(state))) ] ``` ```python def get_previous_epoch_boundary_attestations(state: BeaconState) -> List[PendingAttestation]: return [ - a for a in state.previous_epoch_attestations if - a.data.epoch_boundary_root == get_block_root(state, get_epoch_start_slot(get_previous_epoch(state))) + a for a in state.previous_epoch_attestations + if a.data.epoch_boundary_root == get_block_root(state, get_epoch_start_slot(get_previous_epoch(state))) ] ``` ```python def get_previous_epoch_matching_head_attestations(state: BeaconState) -> List[PendingAttestation]: return [ - a for a in state.previous_epoch_attestations if - a.data.beacon_block_root == get_block_root(state, a.data.slot) + a for a in state.previous_epoch_attestations + if a.data.beacon_block_root == get_block_root(state, a.data.slot) ] ``` @@ -2057,7 +1766,7 @@ def get_winning_root_and_participants(state: BeaconState, shard: Shard) -> Tuple if len(all_roots) == 0: return ZERO_HASH, [] - def get_attestations_for(root: Bytes32) -> List[PendingAttestation]: + def get_attestations_for(root) -> List[PendingAttestation]: return [a for a in valid_attestations if a.data.crosslink_data_root == root] # Winning crosslink root is the root with the most votes for it, ties broken in favor of @@ -2105,7 +1814,7 @@ def update_justification_and_finalization(state: BeaconState) -> None: if current_boundary_attesting_balance * 3 >= get_current_total_balance(state) * 2: new_justified_epoch = get_current_epoch(state) state.justification_bitfield |= 1 - + # Process finalizations bitfield = state.justification_bitfield current_epoch = get_current_epoch(state) @@ -2118,10 +1827,10 @@ def update_justification_and_finalization(state: BeaconState) -> None: # The 1st/2nd/3rd most recent epochs are all justified, the 1st using the 3rd as source if (bitfield >> 0) % 8 == 0b111 and state.justified_epoch == current_epoch - 2: state.finalized_epoch = state.justified_epoch - # The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source + # The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source if (bitfield >> 0) % 4 == 0b11 and state.justified_epoch == current_epoch - 1: state.finalized_epoch = state.justified_epoch - + # Rotate justified epochs state.previous_justified_epoch = state.justified_epoch state.justified_epoch = new_justified_epoch @@ -2134,7 +1843,7 @@ Run the following function: ```python def process_crosslinks(state: BeaconState) -> None: current_epoch = get_current_epoch(state) - previous_epoch = get_previous_epoch(state) + previous_epoch = current_epoch - 1 next_epoch = current_epoch + 1 for slot in range(get_epoch_start_slot(previous_epoch), get_epoch_start_slot(next_epoch)): for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot): @@ -2144,7 +1853,7 @@ def process_crosslinks(state: BeaconState) -> None: if 3 * participating_balance >= 2 * total_balance: state.latest_crosslinks[shard] = Crosslink( epoch=slot_to_epoch(slot), - crosslink_data_root=winning_root, + crosslink_data_root=winning_root ) ``` @@ -2244,7 +1953,7 @@ def compute_normal_justification_and_finalization_deltas(state: BeaconState) -> ``` When blocks are not finalizing normally... - + ```python def compute_inactivity_leak_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]: # deltas[0] for rewards @@ -2328,7 +2037,7 @@ def apply_rewards(state: BeaconState) -> None: #### Ejections -* Run `process_ejections(state)`. +Run `process_ejections(state)`. ```python def process_ejections(state: BeaconState) -> None: @@ -2515,9 +2224,315 @@ def finish_epoch_update(state: BeaconState) -> None: state.current_epoch_attestations = [] ``` -### State root verification +### Per-slot processing -Verify `block.state_root == hash_tree_root(state)` if there exists a `block` for the slot being processed. +At every `slot > GENESIS_SLOT` run the following function: + +```python +def advance_slot(state: BeaconState) -> None: + state.slot += 1 + if state.latest_block_header.state_root == ZERO_HASH: + state.latest_block_header.state_root = get_state_root(state, state.slot - 1) + state.latest_block_roots[(state.slot - 1) % SLOTS_PER_HISTORICAL_ROOT] = hash_tree_root(state.latest_block_header) +``` + +### Per-block processing + +For every `block` except the genesis block, run `process_block_header(state, block)`, `process_randao(state, block)` and `process_eth1_data(state, block)`. + +#### Block header + +```python +def process_block_header(state: BeaconState, block: BeaconBlock) -> None: + # Verify that the slots match + assert block.slot == state.slot + # Verify that the parent matches + assert block.previous_block_root == hash_tree_root(state.latest_block_header) + # Save current block as the new latest block + state.latest_block_header = get_temporary_block_header(block) + # Verify proposer signature + proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)] + assert bls_verify( + pubkey=proposer.pubkey, + message_hash=signed_root(block), + signature=block.signature, + domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_BEACON_BLOCK) + ) +``` + +#### RANDAO + +```python +def process_randao(state: BeaconState, block: BeaconBlock) -> None: + proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)] + # Verify that the provided randao value is valid + assert bls_verify( + pubkey=proposer.pubkey, + message_hash=hash_tree_root(get_current_epoch(state)), + signature=block.body.randao_reveal, + domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_RANDAO) + ) + # Mix it in + state.latest_randao_mixes[get_current_epoch(state) % LATEST_RANDAO_MIXES_LENGTH] = ( + xor(get_randao_mix(state, get_current_epoch(state)), + hash(block.body.randao_reveal)) + ) +``` + +#### Eth1 data + +```python +def process_eth1_data(state: BeaconState, block: BeaconBlock) -> None: + for eth1_data_vote in state.eth1_data_votes: + # If someone else has already voted for the same hash, add to its counter + if eth1_data_vote.eth1_data == block.body.eth1_data: + eth1_data_vote.vote_count += 1 + return + # If we're seeing this hash for the first time, make a new counter + state.eth1_data_votes.append(Eth1DataVote(eth1_data=block.body.eth1_data, vote_count=1)) +``` + +#### Transactions + +##### Proposer slashings + +Verify that `len(block.body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS`. + +For each `proposer_slashing` in `block.body.proposer_slashings`, run the following function: + +```python +def process_proposer_slashing(state: BeaconState, + proposer_slashing: ProposerSlashing) -> None: + """ + Process ``ProposerSlashing`` transaction. + Note that this function mutates ``state``. + """ + proposer = state.validator_registry[proposer_slashing.proposer_index] + # Verify that the slot is the same + assert proposer_slashing.header_1.slot == proposer_slashing.header_2.slot + # But the roots are different! + assert hash_tree_root(proposer_slashing.header_1) != hash_tree_root(proposer_slashing.header_2) + # Proposer is not yet slashed + assert proposer.slashed is False + # Signatures are valid + for header in (proposer_slashing.header_1, proposer_slashing.header_2): + assert bls_verify( + pubkey=proposer.pubkey, + message_hash=signed_root(header), + signature=header.signature, + domain=get_domain(state.fork, slot_to_epoch(header.slot), DOMAIN_BEACON_BLOCK) + ) + slash_validator(state, proposer_slashing.proposer_index) +``` + +##### Attester slashings + +Verify that `len(block.body.attester_slashings) <= MAX_ATTESTER_SLASHINGS`. + +For each `attester_slashing` in `block.body.attester_slashings`, run the following function: + +```python +def process_attester_slashing(state: BeaconState, + attester_slashing: AttesterSlashing) -> None: + """ + Process ``AttesterSlashing`` transaction. + Note that this function mutates ``state``. + """ + attestation1 = attester_slashing.slashable_attestation_1 + attestation2 = attester_slashing.slashable_attestation_2 + # Check that the attestations are conflicting + assert attestation1.data != attestation2.data + assert ( + is_double_vote(attestation1.data, attestation2.data) or + is_surround_vote(attestation1.data, attestation2.data) + ) + assert verify_slashable_attestation(state, attestation1) + assert verify_slashable_attestation(state, attestation2) + slashable_indices = [ + index for index in attestation1.validator_indices + if ( + index in attestation2.validator_indices and + state.validator_registry[index].slashed is False + ) + ] + assert len(slashable_indices) >= 1 + for index in slashable_indices: + slash_validator(state, index) +``` + +##### Attestations + +Verify that `len(block.body.attestations) <= MAX_ATTESTATIONS`. + +For each `attestation` in `block.body.attestations`, run the following function: + +```python +def process_attestation(state: BeaconState, attestation: Attestation) -> None: + """ + Process ``Attestation`` transaction. + Note that this function mutates ``state``. + """ + # Can't submit attestations that are too far in history (or in prehistory) + assert attestation.data.slot >= GENESIS_SLOT + assert state.slot < attestation.data.slot + SLOTS_PER_EPOCH + # Can't submit attestations too quickly + assert attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot + # Verify that the justified epoch is correct, case 1: current epoch attestations + if slot_to_epoch(attestation.data.slot) >= get_current_epoch(state): + assert attestation.data.justified_epoch == state.justified_epoch + # Case 2: previous epoch attestations + else: + assert attestation.data.justified_epoch == state.previous_justified_epoch + # Check that the justified block root is correct + assert attestation.data.justified_block_root == get_block_root( + state, get_epoch_start_slot(attestation.data.justified_epoch) + ) + # Check that the crosslink data is valid + acceptable_crosslink_data = { + # Case 1: Latest crosslink matches the one in the state + attestation.data.latest_crosslink, + # Case 2: State has already been updated, state's latest crosslink matches the crosslink + # the attestation is trying to create + Crosslink( + crosslink_data_root=attestation.data.crosslink_data_root, + epoch=slot_to_epoch(attestation.data.slot) + ) + } + assert state.latest_crosslinks[attestation.data.shard] in acceptable_crosslink_data + # Attestation must be nonempty! + assert attestation.aggregation_bitfield != b'\x00' * len(attestation.aggregation_bitfield) + # Custody must be empty (to be removed in phase 1) + assert attestation.custody_bitfield == b'\x00' * len(attestation.custody_bitfield) + # Get the committee for the specific shard that this attestation is for + crosslink_committee = [ + committee for committee, shard in get_crosslink_committees_at_slot(state, attestation.data.slot) + if shard == attestation.data.shard + ][0] + # Custody bitfield must be a subset of the attestation bitfield + for i in range(len(crosslink_committee)): + if get_bitfield_bit(attestation.aggregation_bitfield, i) == 0b0: + assert get_bitfield_bit(attestation.custody_bitfield, i) == 0b0 + # Verify aggregate signature + participants = get_attestation_participants(state, attestation.data, attestation.aggregation_bitfield) + custody_bit_1_participants = get_attestation_participants(state, attestation.data, attestation.custody_bitfield) + custody_bit_0_participants = [i for i in participants if i not in custody_bit_1_participants] + + assert bls_verify_multiple( + pubkeys=[ + bls_aggregate_pubkeys([state.validator_registry[i].pubkey for i in custody_bit_0_participants]), + bls_aggregate_pubkeys([state.validator_registry[i].pubkey for i in custody_bit_1_participants]), + ], + message_hashes=[ + hash_tree_root(AttestationDataAndCustodyBit(data=attestation.data, custody_bit=0b0)), + hash_tree_root(AttestationDataAndCustodyBit(data=attestation.data, custody_bit=0b1)), + ], + signature=attestation.aggregate_signature, + domain=get_domain(state.fork, slot_to_epoch(attestation.data.slot), DOMAIN_ATTESTATION), + ) + # Crosslink data root is zero (to be removed in phase 1) + assert attestation.data.crosslink_data_root == ZERO_HASH + # Apply the attestation + pending_attestation = PendingAttestation( + data=attestation.data, + aggregation_bitfield=attestation.aggregation_bitfield, + custody_bitfield=attestation.custody_bitfield, + inclusion_slot=state.slot + ) + if slot_to_epoch(attestation.data.slot) == get_current_epoch(state): + state.current_epoch_attestations.append(pending_attestation) + elif slot_to_epoch(attestation.data.slot) == get_previous_epoch(state): + state.previous_epoch_attestations.append(pending_attestation) +``` + +##### Deposits + +Verify that `len(block.body.deposits) <= MAX_DEPOSITS`. + +For each `deposit` in `block.body.deposits`, run `process_deposit(state, deposit)`. + +##### Voluntary exits + +Verify that `len(block.body.voluntary_exits) <= MAX_VOLUNTARY_EXITS`. + +For each `exit` in `block.body.voluntary_exits`, run the following function: + +```python +def process_exit(state: BeaconState, exit: VoluntaryExit) -> None: + """ + Process ``VoluntaryExit`` transaction. + Note that this function mutates ``state``. + """ + validator = state.validator_registry[exit.validator_index] + # Verify the validator has not yet exited + assert validator.exit_epoch > get_delayed_activation_exit_epoch(get_current_epoch(state)) + # Exits must specify an epoch when they become valid; they are not valid before then + assert get_current_epoch(state) >= exit.epoch + # Verify signature + assert bls_verify( + pubkey=validator.pubkey, + message_hash=signed_root(exit), + signature=exit.signature, + domain=get_domain(state.fork, exit.epoch, DOMAIN_VOLUNTARY_EXIT) + ) + # Run the exit + initiate_validator_exit(state, exit.validator_index) +``` + +##### Transfers + +Note: Transfers are a temporary functionality for phases 0 and 1, to be removed in phase 2. + +Verify that `len(block.body.transfers) <= MAX_TRANSFERS` and that all transfers are distinct. + +For each `transfer` in `block.body.transfers`, run the following function: + +```python +def process_transfer(state: BeaconState, transfer: Transfer) -> None: + """ + Process ``Transfer`` transaction. + Note that this function mutates ``state``. + """ + # Verify the amount and fee aren't individually too big (for anti-overflow purposes) + assert state.validator_balances[transfer.sender] >= max(transfer.amount, transfer.fee) + # Verify that we have enough ETH to send, and that after the transfer the balance will be either + # exactly zero or at least MIN_DEPOSIT_AMOUNT + assert ( + state.validator_balances[transfer.sender] == transfer.amount + transfer.fee or + state.validator_balances[transfer.sender] >= transfer.amount + transfer.fee + MIN_DEPOSIT_AMOUNT + ) + # A transfer is valid in only one slot + assert state.slot == transfer.slot + # Only withdrawn or not-yet-deposited accounts can transfer + assert ( + get_current_epoch(state) >= state.validator_registry[transfer.sender].withdrawable_epoch or + state.validator_registry[transfer.sender].activation_epoch == FAR_FUTURE_EPOCH + ) + # Verify that the pubkey is valid + assert ( + state.validator_registry[transfer.sender].withdrawal_credentials == + BLS_WITHDRAWAL_PREFIX_BYTE + hash(transfer.pubkey)[1:] + ) + # Verify that the signature is valid + assert bls_verify( + pubkey=transfer.pubkey, + message_hash=signed_root(transfer), + signature=transfer.signature, + domain=get_domain(state.fork, slot_to_epoch(transfer.slot), DOMAIN_TRANSFER) + ) + # Process the transfer + state.validator_balances[transfer.sender] -= transfer.amount + transfer.fee + state.validator_balances[transfer.recipient] += transfer.amount + state.validator_balances[get_beacon_proposer_index(state, state.slot)] += transfer.fee +``` + +#### State root verification + +Verify the block's `state_root` by running the following function: + +```python +def verify_block_state_root(state: BeaconState, block: BeaconBlock) -> None: + assert block.state_root == hash_tree_root(state) +``` # References From e57bfaab7c4096cf022d3948f78cf553f9e1a75d Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 7 Mar 2019 13:36:22 -0700 Subject: [PATCH 08/24] clean up state transition notes --- specs/core/0_beacon-chain.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 740ace0df..144262d8b 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1667,14 +1667,15 @@ We now define the state transition function. At a high level the state transitio 3. The per-slot transitions, which happens at every slot. 4. The per-block transitions, which happens at every block. -The state-root caching, caches the state root of the previous slot; -The per-epoch transitions focus on the [validator](#dfn-validator) registry, including adjusting balances and activating and exiting [validators](#dfn-validator), as well as processing crosslinks and managing block justification/finalization; -The per-slot transitions focus on the slot counter and block roots records updates; -The per-block transitions generally focus on verifying aggregate signatures and saving temporary records relating to the per-block activity in the `BeaconState`. +Transition section notes: +* The state-root caching, caches the state root of the previous slot; +* The per-epoch transitions focus on the [validator](#dfn-validator) registry, including adjusting balances and activating and exiting [validators](#dfn-validator), as well as processing crosslinks and managing block justification/finalization; +* The per-slot transitions focus on the slot counter and block roots records updates; +* The per-block transitions generally focus on verifying aggregate signatures and saving temporary records relating to the per-block activity in the `BeaconState`. Beacon blocks that trigger unhandled Python exceptions (e.g. out-of-range list accesses) and failed `assert`s during the state transition are considered invalid. -_Note_: If there are skipped slots between a block and its parent block, run the steps in the [per-epoch](#per-epoch-processing) and [per-slot](#per-slot-processing) sections once for each skipped slot and then once for the slot containing the new block. +_Note_: If there are skipped slots between a block and its parent block, run the steps in the [state-root](#state-root-caching), [per-epoch](#per-epoch-processing), and [per-slot](#per-slot-processing) sections once for each skipped slot and then once for the slot containing the new block. ### State-root caching From 2d9724dbfc30315f947ca583956f3b5bad4dc764 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 7 Mar 2019 23:13:06 +0100 Subject: [PATCH 09/24] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 144262d8b..36cb0cc6f 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -2148,9 +2148,8 @@ def process_slashings(state: BeaconState) -> None: total_balance = get_total_balance(state, active_validator_indices) # Compute `total_penalties` - epoch_index = current_epoch % LATEST_SLASHED_EXIT_LENGTH - total_at_start = state.latest_slashed_balances[(epoch_index + 1) % LATEST_SLASHED_EXIT_LENGTH] - total_at_end = state.latest_slashed_balances[epoch_index] + total_at_start = state.latest_slashed_balances[(current_epoch + 1) % LATEST_SLASHED_EXIT_LENGTH] + total_at_end = state.latest_slashed_balances[current_epoch % LATEST_SLASHED_EXIT_LENGTH] total_penalties = total_at_end - total_at_start for index, validator in enumerate(state.validator_registry): From 339a7fb63b098b4b91cf31b390d98a4f76c02f76 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 7 Mar 2019 23:14:47 +0100 Subject: [PATCH 10/24] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 36cb0cc6f..ed92cec23 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1682,7 +1682,7 @@ _Note_: If there are skipped slots between a block and its parent block, run the At every `slot > GENESIS_SLOT` run the following function: ```python -def store_state_root(state: BeaconState) -> None: +def cache_state_root(state: BeaconState) -> None: state.latest_state_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = hash_tree_root(state) ``` From e74c79e353a63ac43ec3d3367f2de272169e01b6 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 8 Mar 2019 09:08:30 +0100 Subject: [PATCH 11/24] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index ed92cec23..c0b5bfd8b 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -2218,10 +2218,10 @@ At every `slot > GENESIS_SLOT` run the following function: ```python def advance_slot(state: BeaconState) -> None: - state.slot += 1 if state.latest_block_header.state_root == ZERO_HASH: - state.latest_block_header.state_root = get_state_root(state, state.slot - 1) - state.latest_block_roots[(state.slot - 1) % SLOTS_PER_HISTORICAL_ROOT] = hash_tree_root(state.latest_block_header) + state.latest_block_header.state_root = state.latest_block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] + state.latest_block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = hash_tree_root(state.latest_block_header) + state.slot += 1 ``` ### Per-block processing From 8dcc1ba930332980a88a1745539cea61e5c07d87 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Fri, 8 Mar 2019 02:58:38 -0600 Subject: [PATCH 12/24] Break LMD GHOST ties in favor of higher hash tree roots (#737) --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 898fc3c86..c51effc9a 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1654,7 +1654,7 @@ def lmd_ghost(store: Store, start_state: BeaconState, start_block: BeaconBlock) children = get_children(store, head) if len(children) == 0: return head - head = max(children, key=get_vote_count) + head = max(children, key=lambda x: (get_vote_count(x), hash_tree_root(x))) ``` ## Beacon chain state transition function From 12695425c9ba8ef705665d36788cf7751be15b39 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 8 Mar 2019 10:32:40 +0100 Subject: [PATCH 13/24] Use hash_tree_root everywhere And get rid of merkle_root. This is possible because of SSZ tuples. --- specs/core/0_beacon-chain.md | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index c51effc9a..f79140379 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -73,7 +73,6 @@ - [`get_active_index_root`](#get_active_index_root) - [`generate_seed`](#generate_seed) - [`get_beacon_proposer_index`](#get_beacon_proposer_index) - - [`merkle_root`](#merkle_root) - [`verify_merkle_branch`](#verify_merkle_branch) - [`get_attestation_participants`](#get_attestation_participants) - [`is_power_of_two`](#is_power_of_two) @@ -1005,20 +1004,6 @@ def get_beacon_proposer_index(state: BeaconState, return first_committee[slot % len(first_committee)] ``` -### `merkle_root` - -```python -def merkle_root(values: List[Bytes32]) -> Bytes32: - """ - Merkleize ``values`` (where ``len(values)`` is a power of two) and return the Merkle root. - Note that the leaves are not hashed. - """ - o = [0] * len(values) + values - for i in range(len(values) - 1, 0, -1): - o[i] = hash(o[i * 2] + o[i * 2 + 1]) - return o[1] -``` - ### `verify_merkle_branch` ```python @@ -2494,7 +2479,7 @@ def finish_epoch_update(state: BeaconState) -> None: state.latest_randao_mixes[next_epoch % LATEST_RANDAO_MIXES_LENGTH] = get_randao_mix(state, current_epoch) # Set historical root accumulator if next_epoch % (SLOTS_PER_HISTORICAL_ROOT // SLOTS_PER_EPOCH) == 0: - state.historical_roots.append(merkle_root(state.latest_block_roots + state.latest_state_roots)) + state.historical_roots.append(hash_tree_root(state.latest_block_roots + state.latest_state_roots)) # Rotate current/previous epoch attestations state.previous_epoch_attestations = state.current_epoch_attestations state.current_epoch_attestations = [] From 11414673495b0fdf5df7223f710ded05bfad9cd9 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 8 Mar 2019 17:28:00 +0100 Subject: [PATCH 14/24] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 971a521dc..679b5a65b 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -2191,7 +2191,7 @@ def finish_epoch_update(state: BeaconState) -> None: state.latest_randao_mixes[next_epoch % LATEST_RANDAO_MIXES_LENGTH] = get_randao_mix(state, current_epoch) # Set historical root accumulator if next_epoch % (SLOTS_PER_HISTORICAL_ROOT // SLOTS_PER_EPOCH) == 0: - state.historical_roots.append(merkle_root(state.latest_block_roots + state.latest_state_roots)) + state.historical_roots.append(hash_tree_root(state.latest_block_roots + state.latest_state_roots)) # Rotate current/previous epoch attestations state.previous_epoch_attestations = state.current_epoch_attestations state.current_epoch_attestations = [] From f081f23c318e0dfc190b784988ff05ce38813eea Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 8 Mar 2019 09:48:46 -0700 Subject: [PATCH 15/24] cache more than just state root at start of state transition --- specs/core/0_beacon-chain.md | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index a339d5058..286855aef 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -110,7 +110,7 @@ - [Beacon chain processing](#beacon-chain-processing) - [Beacon chain fork choice rule](#beacon-chain-fork-choice-rule) - [Beacon chain state transition function](#beacon-chain-state-transition-function) - - [State-root caching](#state-root-caching) + - [State caching](#state-caching) - [Per-epoch processing](#per-epoch-processing) - [Helper functions](#helper-functions-1) - [Justification](#justification) @@ -1662,28 +1662,38 @@ def lmd_ghost(store: Store, start_state: BeaconState, start_block: BeaconBlock) We now define the state transition function. At a high level the state transition is made up of four parts: -1. State-root caching, which happens at the start of every slot. +1. State caching, which happens at the start of every slot. 2. The per-epoch transitions, which happens at the start of the first slot of every epoch. 3. The per-slot transitions, which happens at every slot. 4. The per-block transitions, which happens at every block. Transition section notes: -* The state-root caching, caches the state root of the previous slot; -* The per-epoch transitions focus on the [validator](#dfn-validator) registry, including adjusting balances and activating and exiting [validators](#dfn-validator), as well as processing crosslinks and managing block justification/finalization; -* The per-slot transitions focus on the slot counter and block roots records updates; +* The state caching, caches the state root of the previous slot. +* The per-epoch transitions focus on the [validator](#dfn-validator) registry, including adjusting balances and activating and exiting [validators](#dfn-validator), as well as processing crosslinks and managing block justification/finalization. +* The per-slot transitions focus on the slot counter and block roots records updates. * The per-block transitions generally focus on verifying aggregate signatures and saving temporary records relating to the per-block activity in the `BeaconState`. Beacon blocks that trigger unhandled Python exceptions (e.g. out-of-range list accesses) and failed `assert`s during the state transition are considered invalid. -_Note_: If there are skipped slots between a block and its parent block, run the steps in the [state-root](#state-root-caching), [per-epoch](#per-epoch-processing), and [per-slot](#per-slot-processing) sections once for each skipped slot and then once for the slot containing the new block. +_Note_: If there are skipped slots between a block and its parent block, run the steps in the [state-root](#state-caching), [per-epoch](#per-epoch-processing), and [per-slot](#per-slot-processing) sections once for each skipped slot and then once for the slot containing the new block. -### State-root caching +### State caching At every `slot > GENESIS_SLOT` run the following function: ```python -def cache_state_root(state: BeaconState) -> None: - state.latest_state_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = hash_tree_root(state) +def cache_state(state: BeaconState) -> None: + previous_slot_state_root = hash_tree_root(state) + + # store the previous slot's post state transition root + state.latest_state_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_slot_state_root + + # cache state root in stored latest_block_header if empty + if state.latest_block_header.state_root == ZERO_HASH: + state.latest_block_header.state_root = previous_slot_state_root + + # store latest known block for previous slot + state.latest_block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = hash_tree_root(state.latest_block_header) ``` ### Per-epoch processing @@ -2218,9 +2228,6 @@ At every `slot > GENESIS_SLOT` run the following function: ```python def advance_slot(state: BeaconState) -> None: - if state.latest_block_header.state_root == ZERO_HASH: - state.latest_block_header.state_root = state.latest_block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] - state.latest_block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = hash_tree_root(state.latest_block_header) state.slot += 1 ``` From de60533d7238c4010439152176e9a10828d50fa2 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 8 Mar 2019 18:13:05 +0100 Subject: [PATCH 16/24] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 679b5a65b..cc7cc5b18 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -34,6 +34,7 @@ - [`BeaconBlockHeader`](#beaconblockheader) - [`Validator`](#validator) - [`PendingAttestation`](#pendingattestation) + - [`HistoricalBatch`](#historicalbatch) - [Beacon transactions](#beacon-transactions) - [`ProposerSlashing`](#proposerslashing) - [`AttesterSlashing`](#attesterslashing) @@ -452,6 +453,17 @@ The types are defined topologically to aid in facilitating an executable version } ``` +#### `HistoricalBatch` + +```python +{ + // Block roots + 'block_roots': ['bytes32', SLOTS_PER_HISTORICAL_ROOT], + // State roots + 'state_roots': ['bytes32', SLOTS_PER_HISTORICAL_ROOT], +} +``` + ### Beacon transactions #### `ProposerSlashing` @@ -2191,7 +2203,11 @@ def finish_epoch_update(state: BeaconState) -> None: state.latest_randao_mixes[next_epoch % LATEST_RANDAO_MIXES_LENGTH] = get_randao_mix(state, current_epoch) # Set historical root accumulator if next_epoch % (SLOTS_PER_HISTORICAL_ROOT // SLOTS_PER_EPOCH) == 0: - state.historical_roots.append(hash_tree_root(state.latest_block_roots + state.latest_state_roots)) + historical_batch = HistoricalBatch( + block_roots=state.latest_block_roots, + state_roots=state.latest_state_roots, + ) + state.historical_roots.append(hash_tree_root(historical_batch)) # Rotate current/previous epoch attestations state.previous_epoch_attestations = state.current_epoch_attestations state.current_epoch_attestations = [] From f180eb5e9e2bcf33325c8aece7655adb76bf0561 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 8 Mar 2019 18:14:00 +0100 Subject: [PATCH 17/24] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index cc7cc5b18..8c9aafd38 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -457,9 +457,9 @@ The types are defined topologically to aid in facilitating an executable version ```python { - // Block roots + # Block roots 'block_roots': ['bytes32', SLOTS_PER_HISTORICAL_ROOT], - // State roots + # State roots 'state_roots': ['bytes32', SLOTS_PER_HISTORICAL_ROOT], } ``` From f253feeacf6639f62e1260eee8c2dab80628a4bc Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 8 Mar 2019 18:34:51 +0100 Subject: [PATCH 18/24] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index a339d5058..f33a827b4 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -130,12 +130,12 @@ - [RANDAO](#randao) - [Eth1 data](#eth1-data) - [Transactions](#transactions) - - [Proposer slashings](#proposer-slashings-1) - - [Attester slashings](#attester-slashings-1) - - [Attestations](#attestations-1) - - [Deposits](#deposits-1) - - [Voluntary exits](#voluntary-exits-1) - - [Transfers](#transfers-1) + - [Proposer slashings](#proposer-slashings) + - [Attester slashings](#attester-slashings) + - [Attestations](#attestations) + - [Deposits](#deposits) + - [Voluntary exits](#voluntary-exits) + - [Transfers](#transfers) - [State root verification](#state-root-verification) - [References](#references) - [Normative](#normative) @@ -2298,8 +2298,8 @@ def process_proposer_slashing(state: BeaconState, proposer = state.validator_registry[proposer_slashing.proposer_index] # Verify that the slot is the same assert proposer_slashing.header_1.slot == proposer_slashing.header_2.slot - # But the roots are different! - assert hash_tree_root(proposer_slashing.header_1) != hash_tree_root(proposer_slashing.header_2) + # But the headers are different + assert proposer_slashing.header_1 != proposer_slashing.header_2 # Proposer is not yet slashed assert proposer.slashed is False # Signatures are valid From f88db44e81b7f9fa44c7bfdf4d146ded2fb90442 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 8 Mar 2019 12:07:57 -0700 Subject: [PATCH 19/24] skip proposer bonus if no attestation for v index --- specs/core/0_beacon-chain.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 286855aef..3065dbfd2 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1946,8 +1946,9 @@ def compute_normal_justification_and_finalization_deltas(state: BeaconState) -> else: deltas[1][index] += get_base_reward(state, index) # Proposer bonus - proposer_index = get_beacon_proposer_index(state, inclusion_slot(state, index)) - deltas[0][proposer_index] += get_base_reward(state, index) // ATTESTATION_INCLUSION_REWARD_QUOTIENT + if index in get_attesting_indices(state, state.previous_epoch_attestations): + proposer_index = get_beacon_proposer_index(state, inclusion_slot(state, index)) + deltas[0][proposer_index] += get_base_reward(state, index) // ATTESTATION_INCLUSION_REWARD_QUOTIENT return deltas ``` From 30e64d7de6bd72d20d3cfe91a9434bc980e113a0 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 8 Mar 2019 12:14:21 -0700 Subject: [PATCH 20/24] fix get_inactivity_penalty function signature --- specs/core/0_beacon-chain.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 3065dbfd2..44b96272a 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1885,8 +1885,7 @@ def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: ``` ```python -def get_inactivity_penalty(state: BeaconState, index: ValidatorIndex) -> Gwei: - epochs_since_finality = get_current_epoch(state) + 1 - state.finalized_epoch +def get_inactivity_penalty(state: BeaconState, index: ValidatorIndex, epochs_since_finality: int) -> Gwei: return ( get_base_reward(state, index) + get_effective_balance(state, index) * epochs_since_finality // INACTIVITY_PENALTY_QUOTIENT // 2 From 902e65e072bbb4ca825f610962aafbc6c37acb8e Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 8 Mar 2019 15:16:06 -0700 Subject: [PATCH 21/24] add min persistent committee period resitriction on exits --- specs/core/0_beacon-chain.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index c88155f77..43e159ad2 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -229,8 +229,9 @@ Code snippets appearing in `this style` are to be interpreted as Python code. | `MIN_SEED_LOOKAHEAD` | `2**0` (= 1) | epochs | 6.4 minutes | | `ACTIVATION_EXIT_DELAY` | `2**2` (= 4) | epochs | 25.6 minutes | | `EPOCHS_PER_ETH1_VOTING_PERIOD` | `2**4` (= 16) | epochs | ~1.7 hours | -| `SLOTS_PER_HISTORICAL_ROOT` | `2**13` (= 8,192) | slots | ~13 hours | +| `SLOTS_PER_HISTORICAL_ROOT` | `2**13` (= 8,192) | slots | ~13 hours | | `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `2**8` (= 256) | epochs | ~27 hours | +| `PERSISTENT_COMMITTEE_PERIOD` | `2**11` (= 2,048) | epochs | 9 days | ### State list lengths @@ -2463,6 +2464,8 @@ def process_exit(state: BeaconState, exit: VoluntaryExit) -> None: assert validator.exit_epoch > get_delayed_activation_exit_epoch(get_current_epoch(state)) # Exits must specify an epoch when they become valid; they are not valid before then assert get_current_epoch(state) >= exit.epoch + # Must have been in the validator set long enough + assert get_current_epoch(state) - validator.activation_epoch >= PERSISTENT_COMMITTEE_PERIOD # Verify signature assert bls_verify( pubkey=validator.pubkey, From b7376aea5ca8b3c772f4b69f193724ea2a881106 Mon Sep 17 00:00:00 2001 From: Justin Date: Sun, 10 Mar 2019 13:49:37 +0100 Subject: [PATCH 22/24] Disallow duplicate voluntary exits Stricter processing of voluntary exits to remove an edge case --- specs/core/0_beacon-chain.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index c88155f77..4979eb91a 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -2460,7 +2460,9 @@ def process_exit(state: BeaconState, exit: VoluntaryExit) -> None: """ validator = state.validator_registry[exit.validator_index] # Verify the validator has not yet exited - assert validator.exit_epoch > get_delayed_activation_exit_epoch(get_current_epoch(state)) + assert validator.exit_epoch == FAR_FUTURE_EPOCH + # Verify the validator has not initiated an exit + assert validator.initiated_exit is False # Exits must specify an epoch when they become valid; they are not valid before then assert get_current_epoch(state) >= exit.epoch # Verify signature From d425ea26e2cb5cabc5e6288dc2ca2f8c77753923 Mon Sep 17 00:00:00 2001 From: Justin Date: Sun, 10 Mar 2019 13:50:28 +0100 Subject: [PATCH 23/24] Update 0_beacon-chain.md --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 4979eb91a..0de38f219 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -2453,7 +2453,7 @@ Verify that `len(block.body.voluntary_exits) <= MAX_VOLUNTARY_EXITS`. For each `exit` in `block.body.voluntary_exits`, run the following function: ```python -def process_exit(state: BeaconState, exit: VoluntaryExit) -> None: +def process_voluntary_exit(state: BeaconState, exit: VoluntaryExit) -> None: """ Process ``VoluntaryExit`` transaction. Note that this function mutates ``state``. From 2d3d7e33b3fed03b44f6bd0950b62e273099d037 Mon Sep 17 00:00:00 2001 From: Justin Date: Sun, 10 Mar 2019 21:58:32 +0100 Subject: [PATCH 24/24] Weaken criterion for attestation inclusion The invariant that `get_current_epoch(state) in [get_current_epoch(state), get_previous_epoch(state)]` is preserved, as well as symmetry/fairness across blocks. --- specs/core/0_beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 317d929c9..8bfc52993 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -2370,7 +2370,7 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: """ # Can't submit attestations that are too far in history (or in prehistory) assert attestation.data.slot >= GENESIS_SLOT - assert state.slot < attestation.data.slot + SLOTS_PER_EPOCH + assert state.slot <= attestation.data.slot + SLOTS_PER_EPOCH # Can't submit attestations too quickly assert attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot # Verify that the justified epoch is correct, case 1: current epoch attestations