diff --git a/scripts/build_spec.py b/scripts/build_spec.py index 9df365124..f35332e64 100644 --- a/scripts/build_spec.py +++ b/scripts/build_spec.py @@ -37,7 +37,7 @@ from eth2spec.utils.bls import ( from eth2spec.utils.hash_function import hash ''' PHASE1_IMPORTS = '''from typing import ( - Any, Dict, Optional, Set, Sequence, MutableSequence, NewType, Tuple, Union, + Any, Dict, Set, Sequence, MutableSequence, NewType, Tuple, Union, ) from math import ( log2, @@ -57,7 +57,7 @@ from eth2spec.utils.ssz.ssz_typing import ( BasicValue, Elements, BaseBytes, BaseList, SSZType, Container, List, Vector, Bytes, BytesN, Bitlist, Bitvector, Bits, Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, - uint64, bit, boolean, + uint64, bit, boolean, byte, ) from eth2spec.utils.bls import ( bls_aggregate_pubkeys, diff --git a/scripts/function_puller.py b/scripts/function_puller.py index 26671bafc..b30e5b75c 100644 --- a/scripts/function_puller.py +++ b/scripts/function_puller.py @@ -81,7 +81,7 @@ def get_spec(file_name: str) -> SpecObject: if c not in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789': is_constant_def = False if is_constant_def: - constants[row[0]] = row[1].replace('**TBD**', '0x1234567890123456789012345678901234567890') + constants[row[0]] = row[1].replace('**TBD**', '2**32') elif row[1].startswith('uint') or row[1].startswith('Bytes'): custom_types[row[0]] = row[1] return functions, custom_types, constants, ssz_objects, inserts diff --git a/specs/core/1_beacon-chain-misc.md b/specs/core/1_beacon-chain-misc.md index 5bb0f6da0..6dd6e19c3 100644 --- a/specs/core/1_beacon-chain-misc.md +++ b/specs/core/1_beacon-chain-misc.md @@ -34,6 +34,7 @@ | `MAX_SHARD_RECEIPT_PROOFS` | `2**0` (= 1) | - | - | | `PERIOD_COMMITTEE_ROOT_LENGTH` | `2**8` (= 256) | periods | ~9 months | | `MINOR_REWARD_QUOTIENT` | `2**8` (=256) | - | - | +| `REWARD_COEFFICIENT_BASE` | **TBD** | - | - | ## Containers @@ -45,6 +46,16 @@ class CompactCommittee(Container): compact_validators: List[uint64, MAX_VALIDATORS_PER_COMMITTEE] ``` +#### `ShardReceiptDelta` + +```python +class ShardReceiptDelta(Container): + index: ValidatorIndex + reward_coefficient: uint64 + block_fee: Gwei +``` + + #### `ShardReceiptProof` ```python @@ -112,16 +123,17 @@ def verify_merkle_proof(leaf: Hash, proof: Sequence[Hash], index: GeneralizedInd ```python def compute_historical_state_generalized_index(earlier: ShardSlot, later: ShardSlot) -> GeneralizedIndex: """ - Computes the generalized index of the state root of slot `frm` based on the state root of slot `to`. - Relies on the `history_acc` in the `ShardState`, where `history_acc[i]` maintains the most recent 2**i'th - slot state. Works by tracing a `log(later-earlier)` step path from `later` to `earlier` through intermediate - blocks at the next available multiples of descending powers of two. + Computes the generalized index of the state root of slot `earlier` based on the state root of slot `later`. + Relies on the `history_accumulator` in the `ShardState`, where `history_accumulator[i]` maintains the most + recent 2**i'th slot state. Works by tracing a `log(later-earlier)` step path from `later` to `earlier` + through intermediate blocks at the next available multiples of descending powers of two. """ o = GeneralizedIndex(1) - for i in range(HISTORY_ACCUMULATOR_VECTOR - 1, -1, -1): + for i in range(HISTORY_ACCUMULATOR_DEPTH - 1, -1, -1): if (later - 1) & 2**i > (earlier - 1) & 2**i: later = later - ((later - 1) % 2**i) - 1 - o = concat_generalized_indices(o, GeneralizedIndex(get_generalized_index(ShardState, ['history_acc', i]))) + gindex = GeneralizedIndex(get_generalized_index(ShardState, ['history_accumulator', i])) + o = concat_generalized_indices(o, gindex) return o ``` @@ -133,7 +145,7 @@ def get_generalized_index_of_crosslink_header(index: int) -> GeneralizedIndex: Gets the generalized index for the root of the index'th header in a crosslink. """ MAX_CROSSLINK_SIZE = ( - SHARD_BLOCK_SIZE_LIMIT * SHARD_SLOTS_PER_BEACON_SLOT * SLOTS_PER_EPOCH * MAX_EPOCHS_PER_CROSSLINK + MAX_SHARD_BLOCK_SIZE * SHARD_SLOTS_PER_EPOCH * MAX_EPOCHS_PER_CROSSLINK ) assert MAX_CROSSLINK_SIZE == get_previous_power_of_two(MAX_CROSSLINK_SIZE) return GeneralizedIndex(MAX_CROSSLINK_SIZE // SHARD_HEADER_SIZE + index) @@ -146,10 +158,9 @@ def process_shard_receipt_proof(state: BeaconState, receipt_proof: ShardReceiptP """ Processes a ShardReceipt object. """ - SHARD_SLOTS_PER_EPOCH = SHARD_SLOTS_PER_BEACON_SLOT * SLOTS_PER_EPOCH receipt_slot = ( state.next_shard_receipt_period[receipt_proof.shard] * - SHARD_SLOTS_PER_BEACON_SLOT * SLOTS_PER_EPOCH * EPOCHS_PER_SHARD_PERIOD + SHARD_SLOTS_PER_EPOCH * EPOCHS_PER_SHARD_PERIOD ) first_slot_in_last_crosslink = state.current_crosslinks[receipt_proof.shard].start_epoch * SHARD_SLOTS_PER_EPOCH gindex = concat_generalized_indices( @@ -215,16 +226,18 @@ def update_period_committee(state: BeaconState) -> None: """ Updates period committee roots at boundary blocks. """ - if (get_current_epoch(state) + 1) % EPOCHS_PER_SHARD_PERIOD == 0: - period = (get_current_epoch(state) + 1) // EPOCHS_PER_SHARD_PERIOD - committees = Vector[CompactCommittee, SHARD_COUNT]([ - committee_to_compact_committee( - state, - get_period_committee(state, Epoch(get_current_epoch(state) + 1), Shard(shard)), - ) - for shard in range(SHARD_COUNT) - ]) - state.period_committee_roots[period % PERIOD_COMMITTEE_ROOT_LENGTH] = hash_tree_root(committees) + if (get_current_epoch(state) + 1) % EPOCHS_PER_SHARD_PERIOD != 0: + return + + period = (get_current_epoch(state) + 1) // EPOCHS_PER_SHARD_PERIOD + committees = Vector[CompactCommittee, SHARD_COUNT]([ + committee_to_compact_committee( + state, + get_period_committee(state, Shard(shard), Epoch(get_current_epoch(state) + 1)), + ) + for shard in range(SHARD_COUNT) + ]) + state.period_committee_roots[period % PERIOD_COMMITTEE_ROOT_LENGTH] = hash_tree_root(committees) ``` ### Shard receipt processing diff --git a/specs/core/1_shard-data-chains.md b/specs/core/1_shard-data-chains.md index f24c6f9c3..69962b6fe 100644 --- a/specs/core/1_shard-data-chains.md +++ b/specs/core/1_shard-data-chains.md @@ -17,47 +17,43 @@ - [State list lengths](#state-list-lengths) - [Rewards and penalties](#rewards-and-penalties) - [Signature domain types](#signature-domain-types) - - [TODO PLACEHOLDER](#todo-placeholder) - - [Data structures](#data-structures) - - [`ShardBlockHeader`](#shardblockheader) + - [Containers](#containers) - [`ShardBlock`](#shardblock) - - [`ShardBlockSignatures`](#shardblocksignatures) - - [`ShardBlockCore`](#shardblockcore) - - [`ExtendedShardBlockCore`](#extendedshardblockcore) + - [`ShardBlockHeader`](#shardblockheader) - [`ShardState`](#shardstate) - - [`ShardReceiptDelta`](#shardreceiptdelta) + - [`ShardAttestationData`](#ShardAttestationData) - [Helper functions](#helper-functions) - - [`compute_slot_of_shard_slot`](#compute_slot_of_shard_slot) - - [`compute_epoch_of_shard_slot`](#compute_epoch_of_shard_slot) - - [`get_shard_period_start_epoch`](#get_shard_period_start_epoch) - - [`get_period_committee`](#get_period_committee) - - [`get_persistent_committee`](#get_persistent_committee) - - [`get_shard_block_proposer_index`](#get_shard_block_proposer_index) - - [`get_shard_header`](#get_shard_header) - - [`pad`](#pad) - - [`flatten_shard_header`](#flatten_shard_header) - - [`compute_crosslink_data_root`](#compute_crosslink_data_root) - - [`get_default_shard_state`](#get_default_shard_state) - - [Object validity](#object-validity) - - [Shard block validation: preliminary](#shard-block-validation-preliminary) - - [Shard state transition function helpers](#shard-state-transition-function-helpers) - - [Shard state transition function](#shard-state-transition-function) - - [Beacon attestations](#beacon-attestations) + - [Misc](#misc-1) + - [`compute_epoch_of_shard_slot`](#compute_epoch_of_shard_slot) + - [`compute_shard_period_start_epoch`](#compute_shard_period_start_epoch) + - [Beacon state accessors](#beacon-state-accessors) + - [`get_period_committee`](#get_period_committee) + - [`get_shard_committee`](#get_shard_committee) + - [`get_shard_proposer_index`](#get_shard_proposer_index) + - [Shard state mutators](#shard-state-mutators) + - [`process_delta`](#process_delta) + - [Genesis](#genesis) + - [`get_genesis_shard_state`](#get_genesis_shard_state) + - [`get_genesis_shard_block`](#get_genesis_shard_block) + - [Shard state transition function](#shard-state-transition-function) + - [Period processing](#period-processing) + - [Block processing](#block-processing) + - [Block header](#block-header) + - [Attestations](#attestations) + - [Block body](#block-body) - [Shard fork choice rule](#shard-fork-choice-rule) ## Introduction -This document describes the shard data layer and the shard fork choice rule in Phase 1 of Ethereum 2.0. +This document describes the shard transition function (data layer only) and the shard fork choice rule as part of Phase 1 of Ethereum 2.0. ## Custom types -We define the following Python custom types for type hinting and readability: - | Name | SSZ equivalent | Description | | - | - | - | -| `ShardSlot` | `uint64` | a slot number in shard chain | +| `ShardSlot` | `uint64` | a shard slot number | ## Configuration @@ -65,548 +61,370 @@ We define the following Python custom types for type hinting and readability: | Name | Value | | - | - | -| `SHARD_SLOTS_PER_BEACON_SLOT` | `2**1` (= 2) | -| `TARGET_PERSISTENT_COMMITTEE_SIZE` | `2**7` (= 128) | -| `SHARD_HEADER_SIZE` | `2**9` (= 512) | +| `MIN_BLOCK_BODY_PRICE` | `2**0` (= 1) | +| `MAX_PERIOD_COMMITTEE_SIZE` | `2**7` (= 128) | +| `SHARD_HEADER_SIZE` | `2**10` (= 1024) | | `SHARD_BLOCK_SIZE_TARGET` | `2**14` (= 16,384) | -| `SHARD_BLOCK_SIZE_LIMIT` | `2**16` (= 65,536) | +| `MAX_SHARD_BLOCK_SIZE` | `2**16` (= 65,536) | ### Initial values | Name | Value | Unit | -| - | - | - | -| `PHASE_1_FORK_EPOCH` | **TBD** | Epoch | -| `PHASE_1_FORK_SLOT` | **TBD** | Slot | +| - | - | +| `SHARD_GENESIS_EPOCH` | **TBD** | Epoch | ### Time parameters | Name | Value | Unit | Duration | | - | - | :-: | :-: | -| `CROSSLINK_LOOKBACK` | `2**0` (= 1) | epochs | 6.4 minutes | +| `SHARD_SLOTS_PER_EPOCH` | `2**7` (= 128) | shard slots | 6.4 minutes | | `EPOCHS_PER_SHARD_PERIOD` | `2**8` (= 256) | epochs | ~27 hours | ### State list lengths -| Name | Value | Unit | -| - | - | :-: | -| `HISTORY_ACCUMULATOR_VECTOR` | `2**6` (= 64) | state tree maximum depth | +| Name | Value | +| - | - | +| `HISTORY_ACCUMULATOR_DEPTH` | `2**6` (= 64) | ### Rewards and penalties | Name | Value | | - | - | -| `BASEFEE_ADJUSTMENT_FACTOR` | `2**3` (= 8) | -| `REWARD_COEFFICIENT_BASE` | `2**20` ( = 1,048,576) | +| `BLOCK_BODY_PRICE_QUOTIENT` | `2**3` (= 8) | ### Signature domain types -The following types are defined, mapping into `DomainType` (little endian): - | Name | Value | | - | - | | `DOMAIN_SHARD_PROPOSER` | `128` | | `DOMAIN_SHARD_ATTESTER` | `129` | -### TODO PLACEHOLDER - -| Name | Value | -| - | - | -| `PLACEHOLDER` | `2**3` | - -## Data structures - -_Note: the shard block header structure is carefully designed so that all of the values have the same depth in a hash tree implementation, so `hash_tree_root(SSZ_partial(x)) == hash_tree_root(x)` (using the "left-to-right leaves" scheme [here](https://github.com/ethereum/eth2.0-specs/issues/1303)), which allows shard block headers to look like an SSZ object when in the crosslink structure. This is done by balancing it so that 7 or 8 items are on the left side (the "core") and two 96-byte (ie. 3*2 = 6 chunk) items are on the right side. Change with care._ - -### `ShardBlockHeader` - -```python -class ShardBlockHeader(Container): - core: ShardBlockCore - signatures: ShardBlockSignatures -``` +## Containers ### `ShardBlock` ```python class ShardBlock(Container): - core: ExtendedShardBlockCore - signatures: ShardBlockSignatures -``` - -### `ShardBlockSignatures` - -```python -class ShardBlockSignatures(Container): - attestation_signature: BLSSignature - proposer_signature: BLSSignature -``` - -### `ShardBlockCore` - -```python -class ShardBlockCore(Container): + shard: Shard slot: ShardSlot - beacon_chain_root: Hash + beacon_block_root: Hash parent_root: Hash - data_root: Hash state_root: Hash - total_bytes: uint64 - attester_bitfield: Bitvector[TARGET_PERSISTENT_COMMITTEE_SIZE * 2] + body: List[byte, MAX_SHARD_BLOCK_SIZE - SHARD_HEADER_SIZE] + block_size_sum: uint64 + aggregation_bits: Bitvector[2 * MAX_PERIOD_COMMITTEE_SIZE] + attestations: BLSSignature + signature: BLSSignature ``` -### `ExtendedShardBlockCore` +### `ShardBlockHeader` ```python -class ExtendedShardBlockCore(Container): +class ShardBlockHeader(Container): + shard: Shard slot: ShardSlot - beacon_chain_root: Hash + beacon_block_root: Hash parent_root: Hash - data: Bytes[SHARD_BLOCK_SIZE_LIMIT - SHARD_HEADER_SIZE] state_root: Hash - total_bytes: uint64 - attester_bitfield: Bitvector[TARGET_PERSISTENT_COMMITTEE_SIZE * 2] + body_root: Hash + block_size_sum: uint64 + aggregation_bits: Bitvector[2 * MAX_PERIOD_COMMITTEE_SIZE] + attestations: BLSSignature + signature: BLSSignature ``` ### `ShardState` ```python class ShardState(Container): - history_accumulator: Vector[Hash, HISTORY_ACCUMULATOR_VECTOR] - earlier_committee_rewards: List[uint64, TARGET_PERSISTENT_COMMITTEE_SIZE] - later_committee_rewards: List[uint64, TARGET_PERSISTENT_COMMITTEE_SIZE] - earlier_committee_fees: List[Gwei, TARGET_PERSISTENT_COMMITTEE_SIZE] - later_committee_fees: List[Gwei, TARGET_PERSISTENT_COMMITTEE_SIZE] - basefee: Gwei - slot: ShardSlot shard: Shard - most_recent_block_core: ShardBlockCore - receipt_root: Hash - total_bytes: uint64 + slot: ShardSlot + history_accumulator: Vector[Hash, HISTORY_ACCUMULATOR_DEPTH] + latest_block_header: ShardBlockHeader + block_size_sum: uint64 + # Fees and rewards + block_body_price: Gwei + older_committee_positive_deltas: Vector[Gwei, MAX_PERIOD_COMMITTEE_SIZE] + older_committee_negative_deltas: Vector[Gwei, MAX_PERIOD_COMMITTEE_SIZE] + newer_committee_positive_deltas: Vector[Gwei, MAX_PERIOD_COMMITTEE_SIZE] + newer_committee_negative_deltas: Vector[Gwei, MAX_PERIOD_COMMITTEE_SIZE] ``` -### `ShardReceiptDelta` +### `ShardAttestationData` ```python -class ShardReceiptDelta(Container): - index: ValidatorIndex - reward_coefficient: uint64 - block_fee: Gwei +class ShardAttestationData(Container): + slot: ShardSlot + parent_root: Hash ``` ## Helper functions -### `compute_slot_of_shard_slot` +### Misc -```python -def compute_slot_of_shard_slot(slot: ShardSlot) -> Epoch: - return Epoch(slot // SHARD_SLOTS_PER_BEACON_SLOT) -``` - -### `compute_epoch_of_shard_slot` +#### `compute_epoch_of_shard_slot` ```python def compute_epoch_of_shard_slot(slot: ShardSlot) -> Epoch: - return Epoch(slot // SHARD_SLOTS_PER_BEACON_SLOT // SLOTS_PER_EPOCH) + return Epoch(slot // SHARD_SLOTS_PER_EPOCH) ``` -### `get_shard_period_start_epoch` +#### `compute_shard_period_start_epoch` ```python -def get_shard_period_start_epoch(epoch: Epoch, lookback: int=0) -> Epoch: +def compute_shard_period_start_epoch(epoch: Epoch, lookback: uint64) -> Epoch: return Epoch(epoch - (epoch % EPOCHS_PER_SHARD_PERIOD) - lookback * EPOCHS_PER_SHARD_PERIOD) ``` -### `get_period_committee` +### Beacon state accessors + +#### `get_period_committee` ```python -def get_period_committee(state: BeaconState, epoch: Epoch, shard: Shard) -> Sequence[ValidatorIndex]: - """ - Return committee for a period. Used to construct persistent committees. - """ - full_committee = compute_committee( - indices=get_active_validator_indices(state, epoch), - seed=get_seed(state, epoch, DOMAIN_SHARD_ATTESTER), - index=shard, - count=SHARD_COUNT, - ) - - return full_committee[:TARGET_PERSISTENT_COMMITTEE_SIZE] +def get_period_committee(beacon_state: BeaconState, shard: Shard, epoch: Epoch) -> Sequence[ValidatorIndex]: + active_validator_indices = get_active_validator_indices(beacon_state, epoch) + seed = get_seed(beacon_state, epoch, DOMAIN_SHARD_ATTESTER) + return compute_committee(active_validator_indices, seed, shard, SHARD_COUNT)[:MAX_PERIOD_COMMITTEE_SIZE] ``` -### `get_persistent_committee` +#### `get_shard_committee` ```python -def get_persistent_committee(state: BeaconState, - shard: Shard, - slot: ShardSlot) -> Sequence[ValidatorIndex]: - """ - Return the persistent committee for the given ``shard`` at the given ``slot``. - """ - epoch = compute_epoch_of_shard_slot(slot) - - earlier_committee = get_period_committee(state, get_shard_period_start_epoch(epoch, lookback=2), shard) - later_committee = get_period_committee(state, get_shard_period_start_epoch(epoch, lookback=1), shard) - - # Take not-yet-cycled-out validators from earlier committee and already-cycled-in validators from - # later committee; return a sorted list of the union of the two, deduplicated - return sorted(set( - [i for i in earlier_committee if epoch % EPOCHS_PER_SHARD_PERIOD < i % EPOCHS_PER_SHARD_PERIOD] - + [i for i in later_committee if epoch % EPOCHS_PER_SHARD_PERIOD >= i % EPOCHS_PER_SHARD_PERIOD] - )) +def get_shard_committee(beacon_state: BeaconState, shard: Shard, epoch: Epoch) -> Sequence[ValidatorIndex]: + older_committee = get_period_committee(beacon_state, shard, compute_shard_period_start_epoch(epoch, 2)) + newer_committee = get_period_committee(beacon_state, shard, compute_shard_period_start_epoch(epoch, 1)) + # Every epoch cycle out validators from the older committee and cycle in validators from the newer committee + older_subcommittee = [i for i in older_committee if i % EPOCHS_PER_SHARD_PERIOD > epoch % EPOCHS_PER_SHARD_PERIOD] + newer_subcommittee = [i for i in newer_committee if i % EPOCHS_PER_SHARD_PERIOD <= epoch % EPOCHS_PER_SHARD_PERIOD] + return older_subcommittee + newer_subcommittee ``` -### `get_shard_block_proposer_index` +#### `get_shard_proposer_index` ```python -def get_shard_block_proposer_index(state: BeaconState, - shard: Shard, - slot: ShardSlot) -> Optional[ValidatorIndex]: - # Randomly shift persistent committee - persistent_committee = list(get_persistent_committee(state, shard, slot)) - current_epoch = get_current_epoch(state) +def get_shard_proposer_index(beacon_state: BeaconState, shard: Shard, slot: ShardSlot) -> ValidatorIndex: + epoch = get_current_epoch(beacon_state) + shard_committee = get_shard_committee(beacon_state, shard, epoch) + active_indices = [i for i in shard_committee if is_active_validator(beacon_state.validators[i], epoch)] + assert any(active_indices) - active_indices = [i for i in persistent_committee if is_active_validator(state.validators[i], current_epoch)] - if not any(active_indices): - return None - - MAX_RANDOM_BYTE = 2**8 - 1 - epoch_seed = get_seed(state, current_epoch, DOMAIN_SHARD_PROPOSER) - seed = hash(epoch_seed + int_to_bytes(shard, length=8) + int_to_bytes(slot, length=8)) - i = 0 - while True: - candidate_index = active_indices[(slot + i) % len(active_indices)] - random_byte = hash(seed + int_to_bytes(i // 32, length=8))[i % 32] - effective_balance = state.validators[candidate_index].effective_balance - if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte: - return ValidatorIndex(candidate_index) - i += 1 + epoch_seed = get_seed(beacon_state, epoch, DOMAIN_SHARD_PROPOSER) + seed = hash(epoch_seed + int_to_bytes(slot, length=8) + int_to_bytes(shard, length=8)) + return compute_proposer_index(beacon_state, active_indices, seed) ``` -### `get_shard_header` +### Shard state mutators + +#### `process_delta` ```python -def get_shard_header(block: ShardBlock) -> ShardBlockHeader: - return ShardBlockHeader( - core=ShardBlockCore( - slot=block.core.slot, - beacon_chain_root=block.core.beacon_chain_root, - parent_root=block.core.parent_root, - data_root=hash_tree_root(block.core.data), - state_root=block.core.state_root, - total_bytes=block.core.total_bytes, - attester_bitfield=block.core.attester_bitfield, - ), - signatures=block.signatures, - ) -``` - -### `pad` - -```python -def pad(x: bytes, length: int) -> bytes: - assert len(x) <= length - return x + b'\x00' * (length - len(x)) -``` - -### `flatten_shard_header` - -```python -def flatten_shard_header(header: ShardBlockHeader) -> Bytes[SHARD_HEADER_SIZE]: - """ - Converts a shard block header into a flat object with the same hash tree root. Used - in the crosslink construction. - """ - committee_size = len(header.core.attester_bitfield) - attester_bits = [header.core.attester_bitfield[i] if i < committee_size else 0 for i in range(256)] - attester_bytes = bytes([sum([attester_bits[i + j] << j for j in range(8)]) for i in range(0, 256, 8)]) - return ( - pad(int_to_bytes(header.core.slot, length=8), 32) + - header.core.beacon_chain_root + - header.core.parent_root + - header.core.data_root + - header.core.state_root + - pad(int_to_bytes(header.core.total_bytes, length=8), 32) + - attester_bytes + - b'\x00' * 32 + - pad(header.signatures.attestation_signature, 128) + - pad(header.signatures.proposer_signature, 128) - ) -``` - -### `compute_crosslink_data_root` - -```python -def compute_crosslink_data_root(blocks: Sequence[ShardBlock]) -> Hash: - header = b''.join([flatten_shard_header(get_shard_header(block)) for block in blocks]) - footer = b''.join([block.core.data for block in blocks]) - MAX_SIZE = SHARD_BLOCK_SIZE_LIMIT * SHARD_SLOTS_PER_BEACON_SLOT * SLOTS_PER_EPOCH * MAX_EPOCHS_PER_CROSSLINK - return hash_tree_root(BytesN[MAX_SIZE](pad(header + footer, MAX_SIZE))) -``` - -### `get_default_shard_state` - -```python -def get_default_shard_state(beacon_state: BeaconState, shard: Shard) -> ShardState: - earlier_committee = get_period_committee( - beacon_state, - Epoch(PHASE_1_FORK_EPOCH - EPOCHS_PER_SHARD_PERIOD * 2), - shard, - ) - later_committee = get_period_committee( - beacon_state, - Epoch(PHASE_1_FORK_EPOCH - EPOCHS_PER_SHARD_PERIOD), - shard, - ) - return ShardState( - basefee=1, - shard=shard, - slot=PHASE_1_FORK_SLOT * SHARD_SLOTS_PER_BEACON_SLOT, - earlier_committee_rewards=[REWARD_COEFFICIENT_BASE for _ in range(len(earlier_committee))], - later_committee_rewards=[REWARD_COEFFICIENT_BASE for _ in range(len(later_committee))], - earlier_committee_fees=[Gwei(0) for _ in range(len(earlier_committee))], - later_committee_fees=[Gwei(0) for _ in range(len(later_committee))], - ) -``` - -## Object validity - -### Shard block validation: preliminary - -Accept a shard block `block` only if all of the following are correct: - -* Either `block.core.parent_root == Hash()` or a block `parent` such that `hash_tree_root(parent.core) == block.core.parent_root` has already been accepted. -* `block.core.beacon_chain_root == get_block_root(head_beacon_state, compute_epoch_of_shard_slot(parent.core.slot))` where `head_beacon_state` is the current beacon chain head state. Alternatively phrased, a beacon chain block `beacon_ref` such that `signing_root(beacon_ref) == block.core.beacon_chain_root` has already been accepted and is part of the canonical chain, and no block with slot `beacon_ref.slot < slot <= compute_start_slot_of_epoch(compute_epoch_of_shard_slot(parent.core.slot))` is part of the canonical chain. -* Let `beacon_state` be the state where `beacon_ref.state_root == hash_tree_root(beacon_state)`. Let `prev_state` be the post-state of the `parent` if the `parent` exists, otherwise let it be `get_default_shard_state(beacon_state, shard)` (defined below). `block.core.state_root` must equal the `hash_tree_root` of the state after applying `shard_state_transition(prev_state, beacon_state, block)`. - -Note that these acceptance conditions depend on the canonical beacon chain; when the canonical beacon chain reorganizes, the eligibility of shard blocks should be re-evaluated. - -### Shard state transition function helpers - -```python -def add_reward(state: ShardState, beacon_state: BeaconState, index: ValidatorIndex, delta: int) -> None: - epoch = compute_epoch_of_shard_slot(state.slot) - earlier_committee = get_period_committee( - beacon_state, - get_shard_period_start_epoch(epoch, lookback=2), - state.shard, - ) - later_committee = get_period_committee(beacon_state, get_shard_period_start_epoch(epoch, lookback=1), state.shard) - if index in earlier_committee: - state.earlier_committee_rewards[earlier_committee.index(index)] += delta - elif index in later_committee: - state.later_committee_rewards[later_committee.index(index)] += delta - else: - raise Exception("Should never be here") -``` - -```python -def add_fee(state: ShardState, beacon_state: BeaconState, index: ValidatorIndex, delta: int) -> None: - epoch = compute_epoch_of_shard_slot(state.slot) - earlier_committee = get_period_committee(beacon_state, get_shard_period_start_epoch(epoch, lookback=2), state.shard) - later_committee = get_period_committee(beacon_state, get_shard_period_start_epoch(epoch, lookback=1), state.shard) - if index in earlier_committee: - state.earlier_committee_fees[earlier_committee.index(index)] += delta - elif index in later_committee: - state.later_committee_fees[later_committee.index(index)] += delta - else: - raise Exception("Should never be here") -``` - -### Shard state transition function - -```python -def shard_state_transition(state: ShardState, - beacon_state: BeaconState, - block: ShardBlock, - validate_state_root: bool=False) -> None: - assert block.core.slot > state.slot - for slot in range(state.slot, block.core.slot): - shard_slot_transition(state, beacon_state) - shard_block_transition(state, beacon_state, block, validate_state_root=validate_state_root) -``` - -```python -def shard_slot_transition(state: ShardState, beacon_state: BeaconState) -> None: - # Correct saved state root - if state.most_recent_block_core.state_root == Hash(): - state.most_recent_block_core.state_root = hash_tree_root(state) - - # Save states in history accumulator - depth = 0 - h = hash_tree_root(state) - while state.slot % 2**depth == 0 and depth < HISTORY_ACCUMULATOR_VECTOR: - state.history_accumulator[depth] = h - depth += 1 - - # Period transitions - if (state.slot + 1) % (SHARD_SLOTS_PER_BEACON_SLOT * SLOTS_PER_EPOCH * EPOCHS_PER_SHARD_PERIOD) == 0: - epoch = compute_epoch_of_shard_slot(state.slot) - earlier_committee = get_period_committee( - beacon_state, - get_shard_period_start_epoch(epoch, lookback=2), - state.shard, - ) - later_committee = get_period_committee( - beacon_state, - get_shard_period_start_epoch(epoch, lookback=1), - state.shard, - ) - state.receipt_root = hash_tree_root(List[ShardReceiptDelta, PLACEHOLDER]([ - ShardReceiptDelta( - index=validator_index, - reward_coefficient=state.earlier_committee_rewards[i], - block_fee=state.earlier_committee_fees[i], - ) - for i, validator_index in enumerate(earlier_committee) - ])) - state.earlier_committee_rewards = state.later_committee_rewards - state.earlier_committee_fees = state.later_committee_fees - state.later_committee_rewards = [REWARD_COEFFICIENT_BASE for _ in range(len(later_committee))], - state.later_committee_fees = [Gwei(0) for _ in range(len(later_committee))], - else: - state.receipt_root = Hash() - state.slot += ShardSlot(1) -``` - -```python -def shard_block_transition(state: ShardState, - beacon_state: BeaconState, - block: ShardBlock, - validate_state_root: bool) -> None: - # Check slot number - assert block.core.slot == state.slot - - # Check parent block - if block.core.parent_root != Hash(): - assert block.core.parent_root == hash_tree_root(state.most_recent_block_core) - - # Calculate base reward - total_balance = get_total_active_balance(beacon_state) - base_reward = ( - REWARD_COEFFICIENT_BASE * BASE_REWARD_FACTOR // integer_squareroot(total_balance) // BASE_REWARDS_PER_EPOCH - ) - # Check attestations - attester_committee = get_persistent_committee(beacon_state, state.shard, block.core.slot) - pubkeys = [] - attestations = 0 - - for i, validator_index in enumerate(attester_committee): - if block.core.attester_bitfield[i]: - pubkeys.append(beacon_state.validators[validator_index].pubkey) - add_reward(state, beacon_state, validator_index, base_reward) - attestations += 1 - - for i in range(len(attester_committee), TARGET_PERSISTENT_COMMITTEE_SIZE): - assert block.core.attester_bitfield[i] is False or block.core.attester_bitfield[i] == 0 # TODO: FIX Bitvector - - assert bls_verify( - pubkey=bls_aggregate_pubkeys(pubkeys), - message_hash=block.core.parent_root, - signature=block.signatures.attestation_signature, - domain=get_domain(beacon_state, DOMAIN_SHARD_ATTESTER, compute_epoch_of_shard_slot(block.core.slot)) - ) - - # Check proposer - proposer_index = get_shard_block_proposer_index(beacon_state, state.shard, block.core.slot) - assert proposer_index is not None - add_reward(state, beacon_state, proposer_index, attestations * base_reward // PROPOSER_REWARD_QUOTIENT) - assert bls_verify( - pubkey=beacon_state.validators[proposer_index].pubkey, - message_hash=hash_tree_root(block.core), - signature=block.signatures.proposer_signature, - domain=get_domain(beacon_state, DOMAIN_SHARD_PROPOSER, compute_epoch_of_shard_slot(block.core.slot)), - ) - - # Process and update block data fees - add_fee(state, beacon_state, proposer_index, state.basefee * len(block.core.data) // SHARD_BLOCK_SIZE_LIMIT) - QUOTIENT = SHARD_BLOCK_SIZE_LIMIT * BASEFEE_ADJUSTMENT_FACTOR - if len(block.core.data) > SHARD_BLOCK_SIZE_TARGET: - state.basefee += Gwei(max(1, state.basefee * (len(block.core.data) - SHARD_BLOCK_SIZE_TARGET) // QUOTIENT)) - elif len(block.core.data) < SHARD_BLOCK_SIZE_TARGET: - state.basefee -= Gwei(max(1, state.basefee * (len(block.core.data) - SHARD_BLOCK_SIZE_TARGET) // QUOTIENT)) - state.basefee = Gwei(max( - 1, - min( - EFFECTIVE_BALANCE_INCREMENT // EPOCHS_PER_SHARD_PERIOD // SHARD_SLOTS_PER_BEACON_SLOT * SLOTS_PER_EPOCH, - state.basefee, - ) - )) - - # Check total bytes - state.total_bytes += len(block.core.data) - assert block.core.total_bytes == state.total_bytes - - # Update in-state block header - state.most_recent_block_core = ShardBlockCore( - slot=block.core.slot, - beacon_chain_root=block.core.beacon_chain_root, - parent_root=block.core.parent_root, - data_root=hash_tree_root(block.core.data), - state_root=Hash(), - total_bytes=block.core.total_bytes, - attester_bitfield=block.core.attester_bitfield, - ) - - # Check state root - if validate_state_root: - assert block.core.state_root == hash_tree_root(state) -``` - -### Beacon attestations - -Let: - -- `pre_state` is the `ShardState` before processing any blocks -- `shard_blocks_or_state_roots` be the `Union[ShardBlock, Hash]` list such that `shard_blocks[slot]` is the canonical `ShardBlock` for shard `pre_state.shard` at slot `slot` if a block exists, or the post-state-root of processing state up to and including that slot if a block does not exist. -- `beacon_state` be the canonical `BeaconState` -- `valid_attestations` be the set of valid `Attestation` objects, recursively defined -- `candidate` be a candidate `Attestation` which is valid under Phase 0 rules, and for which validity is to be determined under Phase 1 rules by running `is_valid_beacon_attestation` - -```python -def is_valid_beacon_attestation(pre_state: ShardState, - shard_blocks_or_state_roots: Sequence[Union[ShardBlock, Hash]], - beacon_state: BeaconState, - valid_attestations: Set[Attestation], - candidate: Attestation) -> bool: - # Check if attestation is already determined valid - for attestation in valid_attestations: - if candidate == attestation: - return True - - # Check previous attestation - if candidate.data.previous_crosslink.epoch <= PHASE_1_FORK_EPOCH: - assert candidate.data.previous_crosslink.data_root == Hash() - else: - previous_attestation = next( - (attestation for attestation in valid_attestations - if attestation.data.crosslink.data_root == candidate.data.previous_crosslink.data_root), - None, - ) - assert previous_attestation is not None - assert candidate.data.previous_attestation.epoch < compute_epoch_of_slot(candidate.data.slot) - - # Check crosslink data root - start_epoch = beacon_state.crosslinks[pre_state.shard].epoch - end_epoch = min(compute_epoch_of_slot(candidate.data.slot) - CROSSLINK_LOOKBACK, - start_epoch + MAX_EPOCHS_PER_CROSSLINK) - blocks = [] - for slot in range(start_epoch * SLOTS_PER_EPOCH, end_epoch * SLOTS_PER_EPOCH): - if isinstance(shard_blocks_or_state_roots[slot], ShardBlock): - blocks.append(shard_blocks_or_state_roots[slot]) +def process_delta(beacon_state: BeaconState, + shard_state: ShardState, + index: ValidatorIndex, + delta: Gwei, + positive: bool=True) -> None: + epoch = compute_epoch_of_shard_slot(shard_state.slot) + older_committee = get_period_committee(beacon_state, shard_state.shard, compute_shard_period_start_epoch(epoch, 2)) + newer_committee = get_period_committee(beacon_state, shard_state.shard, compute_shard_period_start_epoch(epoch, 1)) + if index in older_committee: + if positive: + shard_state.older_committee_positive_deltas[older_committee.index(index)] += delta else: - blocks.append(ShardBlock( - core=ExtendedShardBlockCore( - slot=slot, - state_root=shard_blocks_or_state_roots[slot], - total_bytes=pre_state.total_bytes, - ), - signatures=ShardBlockSignatures(), - )) - assert candidate.data.crosslink.data_root == compute_crosslink_data_root(blocks) + shard_state.older_committee_negative_deltas[older_committee.index(index)] += delta + elif index in newer_committee: + if positive: + shard_state.newer_committee_positive_deltas[newer_committee.index(index)] += delta + else: + shard_state.newer_committee_negative_deltas[newer_committee.index(index)] += delta +``` - return True +## Genesis + +### `get_genesis_shard_state` + +```python +def get_genesis_shard_state(shard: Shard) -> ShardState: + return ShardState( + shard=shard, + slot=ShardSlot(SHARD_GENESIS_EPOCH * SHARD_SLOTS_PER_EPOCH), + latest_block_header=ShardBlockHeader( + shard=shard, + slot=ShardSlot(SHARD_GENESIS_EPOCH * SHARD_SLOTS_PER_EPOCH), + body_root=hash_tree_root(List[byte, MAX_SHARD_BLOCK_SIZE - SHARD_HEADER_SIZE]()), + ), + block_body_price=MIN_BLOCK_BODY_PRICE, + ) +``` + +### `get_genesis_shard_block` + +```python +def get_genesis_shard_block(shard: Shard) -> ShardBlock: + return ShardBlock( + shard=shard, + slot=ShardSlot(SHARD_GENESIS_EPOCH * SHARD_SLOTS_PER_EPOCH), + state_root=hash_tree_root(get_genesis_shard_state(shard)), + ) +``` + +## Shard state transition function + +```python +def shard_state_transition(beacon_state: BeaconState, + shard_state: ShardState, + block: ShardBlock, + validate_state_root: bool=False) -> ShardState: + # Process slots (including those with no blocks) since block + process_shard_slots(shard_state, block.slot) + # Process block + process_shard_block(beacon_state, shard_state, block) + # Validate state root (`validate_state_root == True` in production) + if validate_state_root: + assert block.state_root == hash_tree_root(shard_state) + # Return post-state + return shard_state +``` + +```python +def process_shard_slots(shard_state: ShardState, slot: ShardSlot) -> None: + assert shard_state.slot <= slot + while shard_state.slot < slot: + process_shard_slot(shard_state) + # Process shard period on the start slot of the next shard period + if (shard_state.slot + 1) % (SHARD_SLOTS_PER_EPOCH * EPOCHS_PER_SHARD_PERIOD) == 0: + process_shard_period(shard_state) + shard_state.slot += ShardSlot(1) +``` + +```python +def process_shard_slot(shard_state: ShardState) -> None: + # Cache state root + previous_state_root = hash_tree_root(shard_state) + if shard_state.latest_block_header.state_root == Bytes32(): + shard_state.latest_block_header.state_root = previous_state_root + # Cache state root in history accumulator + depth = 0 + while shard_state.slot % 2**depth == 0 and depth < HISTORY_ACCUMULATOR_DEPTH: + shard_state.history_accumulator[depth] = previous_state_root + depth += 1 +``` + +### Period processing + +```python +def process_shard_period(shard_state: ShardState) -> None: + # Rotate committee deltas + shard_state.older_committee_positive_deltas = shard_state.newer_committee_positive_deltas + shard_state.older_committee_negative_deltas = shard_state.newer_committee_negative_deltas + shard_state.newer_committee_positive_deltas = [Gwei(0) for _ in range(MAX_PERIOD_COMMITTEE_SIZE)] + shard_state.newer_committee_negative_deltas = [Gwei(0) for _ in range(MAX_PERIOD_COMMITTEE_SIZE)] +``` + +### Block processing + +```python +def process_shard_block(beacon_state: BeaconState, shard_state: ShardState, block: ShardBlock) -> None: + process_shard_block_header(beacon_state, shard_state, block) + process_shard_attestations(beacon_state, shard_state, block) + process_shard_block_body(beacon_state, shard_state, block) +``` + +#### Block header + +```python +def process_shard_block_header(beacon_state: BeaconState, shard_state: ShardState, block: ShardBlock) -> None: + # Verify the shard number + assert block.shard == shard_state.shard + # Verify the slot number + assert block.slot == shard_state.slot + # Verify the beacon chain root + epoch = compute_epoch_of_shard_slot(shard_state.slot) + assert epoch * SLOTS_PER_EPOCH == beacon_state.slot + beacon_block_header = BeaconBlockHeader( + slot=beacon_state.latest_block_header.slot, + parent_root=beacon_state.latest_block_header.parent_root, + state_root=beacon_state.latest_block_header.state_root, + body_root=beacon_state.latest_block_header.body_root, + ) + if beacon_block_header.state_root == Bytes32(): + beacon_block_header.state_root = hash_tree_root(beacon_state) + assert block.beacon_block_root == signing_root(beacon_block_header) + # Verify the parent root + assert block.parent_root == signing_root(shard_state.latest_block_header) + # Save current block as the new latest block + shard_state.latest_block_header = ShardBlockHeader( + shard=block.shard, + slot=block.slot, + beacon_block_root=block.beacon_block_root, + parent_root=block.parent_root, + # `state_root` is zeroed and overwritten in the next `process_shard_slot` call + body_root=hash_tree_root(block.body), + block_size_sum=block.block_size_sum, + aggregation_bits=block.aggregation_bits, + attestations=block.attestations, + # `signature` is zeroed + ) + # Verify the sum of the block sizes since genesis + shard_state.block_size_sum += SHARD_HEADER_SIZE + len(block.body) + assert block.block_size_sum == shard_state.block_size_sum + # Verify proposer is not slashed + proposer_index = get_shard_proposer_index(beacon_state, shard_state.shard, block.slot) + proposer = beacon_state.validators[proposer_index] + assert not proposer.slashed + # Verify proposer signature + domain = get_domain(beacon_state, DOMAIN_SHARD_PROPOSER, compute_epoch_of_shard_slot(block.slot)) + assert bls_verify(proposer.pubkey, signing_root(block), block.signature, domain) +``` + +#### Attestations + +```python +def process_shard_attestations(beacon_state: BeaconState, shard_state: ShardState, block: ShardBlock) -> None: + pubkeys = [] + attestation_count = 0 + shard_committee = get_shard_committee(beacon_state, shard_state.shard, block.slot) + for i, validator_index in enumerate(shard_committee): + if block.aggregation_bits[i]: + pubkeys.append(beacon_state.validators[validator_index].pubkey) + process_delta(beacon_state, shard_state, validator_index, get_base_reward(beacon_state, validator_index)) + attestation_count += 1 + # Verify there are no extraneous bits set beyond the shard committee + for i in range(len(shard_committee), 2 * MAX_PERIOD_COMMITTEE_SIZE): + assert block.aggregation_bits[i] == 0b0 + # Verify attester aggregate signature + domain = get_domain(beacon_state, DOMAIN_SHARD_ATTESTER, compute_epoch_of_shard_slot(block.slot)) + message = hash_tree_root(ShardAttestationData(slot=shard_state.slot, parent_root=block.parent_root)) + assert bls_verify(bls_aggregate_pubkeys(pubkeys), message, block.attestations, domain) + # Proposer micro-reward + proposer_index = get_shard_proposer_index(beacon_state, shard_state.shard, block.slot) + reward = attestation_count * get_base_reward(beacon_state, proposer_index) // PROPOSER_REWARD_QUOTIENT + process_delta(beacon_state, shard_state, proposer_index, Gwei(reward)) +``` + +#### Block body + +```python +def process_shard_block_body(beacon_state: BeaconState, shard_state: ShardState, block: ShardBlock) -> None: + # Verify block body size is a multiple of the header size + assert len(block.body) % SHARD_HEADER_SIZE == 0 + # Apply proposer block body fee + block_body_fee = shard_state.block_body_price * len(block.body) // MAX_SHARD_BLOCK_SIZE + proposer_index = get_shard_proposer_index(beacon_state, shard_state.shard, block.slot) + process_delta(beacon_state, shard_state, proposer_index, Gwei(block_body_fee), positive=False) # Burn + process_delta(beacon_state, shard_state, proposer_index, Gwei(block_body_fee // PROPOSER_REWARD_QUOTIENT)) # Reward + # Calculate new block body price + block_size = SHARD_HEADER_SIZE + len(block.body) + QUOTIENT = MAX_SHARD_BLOCK_SIZE * BLOCK_BODY_PRICE_QUOTIENT + if block_size > SHARD_BLOCK_SIZE_TARGET: + price_delta = Gwei(shard_state.block_body_price * (block_size - SHARD_BLOCK_SIZE_TARGET) // QUOTIENT) + # The maximum block body price caps the amount burnt on fees within a shard period + MAX_BLOCK_BODY_PRICE = MAX_EFFECTIVE_BALANCE // EPOCHS_PER_SHARD_PERIOD // SHARD_SLOTS_PER_EPOCH + shard_state.block_body_price = Gwei(min(MAX_BLOCK_BODY_PRICE, shard_state.block_body_price + price_delta)) + else: + price_delta = Gwei(shard_state.block_body_price * (SHARD_BLOCK_SIZE_TARGET - block_size) // QUOTIENT) + shard_state.block_body_price = Gwei(max(MIN_BLOCK_BODY_PRICE, shard_state.block_body_price + price_delta)) ``` ## Shard fork choice rule -The fork choice rule for any shard is LMD GHOST using the shard attestations of the persistent committee and the beacon chain attestations of the crosslink committee currently assigned to that shard, but instead of being rooted in the genesis it is rooted in the block referenced in the most recent accepted crosslink (i.e. `state.crosslinks[shard].shard_block_root`). Only blocks whose `beacon_chain_root` is the block in the main beacon chain at the specified `slot` should be considered. (If the beacon chain skips a slot, then the block at that slot is considered to be the block in the beacon chain at the highest slot lower than that slot.) +The fork choice rule for any shard is LMD GHOST using the shard attestations of the shard committee and the beacon chain attestations of the crosslink committee currently assigned to that shard, but instead of being rooted in the genesis it is rooted in the block referenced in the most recent accepted crosslink (i.e. `beacon_state.crosslinks[shard].shard_block_root`). Only blocks whose `beacon_block_root` is the block in the main beacon chain at the specified `slot` should be considered. (If the beacon chain skips a slot, then the block at that slot is considered to be the block in the beacon chain at the highest slot lower than that slot.) diff --git a/test_libs/pyspec/eth2spec/test/helpers/phase1/attestations.py b/test_libs/pyspec/eth2spec/test/helpers/phase1/attestations.py index 750ab5048..4f0a9fb0a 100644 --- a/test_libs/pyspec/eth2spec/test/helpers/phase1/attestations.py +++ b/test_libs/pyspec/eth2spec/test/helpers/phase1/attestations.py @@ -5,17 +5,20 @@ from eth2spec.utils.bls import ( ) -def sign_shard_attestation(spec, shard_state, beacon_state, block, participants): +def sign_shard_attestation(spec, beacon_state, shard_state, block, participants): signatures = [] - message_hash = block.core.parent_root - block_epoch = spec.compute_epoch_of_shard_slot(block.core.slot) + message_hash = spec.ShardAttestationData( + slot=block.slot, + parent_root=block.parent_root, + ).hash_tree_root() + block_epoch = spec.compute_epoch_of_shard_slot(block.slot) for validator_index in participants: privkey = privkeys[validator_index] signatures.append( get_attestation_signature( spec, - shard_state, beacon_state, + shard_state, message_hash, block_epoch, privkey, @@ -25,7 +28,7 @@ def sign_shard_attestation(spec, shard_state, beacon_state, block, participants) return bls_aggregate_signatures(signatures) -def get_attestation_signature(spec, shard_state, beacon_state, message_hash, block_epoch, privkey): +def get_attestation_signature(spec, beacon_state, shard_state, message_hash, block_epoch, privkey): return bls_sign( message_hash=message_hash, privkey=privkey, diff --git a/test_libs/pyspec/eth2spec/test/helpers/phase1/shard_block.py b/test_libs/pyspec/eth2spec/test/helpers/phase1/shard_block.py index b9c388a3f..7955c613e 100644 --- a/test_libs/pyspec/eth2spec/test/helpers/phase1/shard_block.py +++ b/test_libs/pyspec/eth2spec/test/helpers/phase1/shard_block.py @@ -1,3 +1,5 @@ +from copy import deepcopy + from eth2spec.test.helpers.keys import privkeys from eth2spec.utils.bls import ( bls_sign, @@ -13,69 +15,68 @@ from .attestations import ( @only_with_bls() -def sign_shard_block(spec, state, block, shard, proposer_index=None): +def sign_shard_block(spec, beacon_state, shard_state, block, proposer_index=None): if proposer_index is None: - proposer_index = spec.get_shard_block_proposer_index(state, shard, block.core.slot) + proposer_index = spec.get_shard_proposer_index(beacon_state, shard_state.shard, block.slot) privkey = privkeys[proposer_index] - block.signatures.proposer_signature = bls_sign( + block.signature = bls_sign( message_hash=signing_root(block), privkey=privkey, domain=spec.get_domain( - state, + beacon_state, spec.DOMAIN_SHARD_PROPOSER, - spec.compute_epoch_of_shard_slot(block.core.slot), + spec.compute_epoch_of_shard_slot(block.slot), ) ) def build_empty_shard_block(spec, - shard_state, beacon_state, + shard_state, slot, - parent_root, signed=False, full_attestation=False): if slot is None: slot = shard_state.slot + previous_beacon_header = deepcopy(beacon_state.latest_block_header) + if previous_beacon_header.state_root == spec.Bytes32(): + previous_beacon_header.state_root = beacon_state.hash_tree_root() + beacon_block_root = spec.signing_root(previous_beacon_header) + + previous_block_header = deepcopy(shard_state.latest_block_header) + if previous_block_header.state_root == spec.Bytes32(): + previous_block_header.state_root = shard_state.hash_tree_root() + parent_root = signing_root(previous_block_header) + block = spec.ShardBlock( - core=spec.ExtendedShardBlockCore( - slot=slot, - beacon_chain_root=beacon_state.block_roots[beacon_state.slot % spec.SLOTS_PER_HISTORICAL_ROOT], - parent_root=parent_root, - ), - signatures=spec.ShardBlockSignatures( - attestation_signature=b'\x00' * 96, - proposer_signature=b'\x25' * 96, - ) + shard=shard_state.shard, + slot=slot, + beacon_block_root=beacon_block_root, + parent_root=parent_root, + block_size_sum=shard_state.block_size_sum + spec.SHARD_HEADER_SIZE, ) - # attestation if full_attestation: - attester_committee = spec.get_persistent_committee(beacon_state, shard_state.shard, block.core.slot) - block.core.attester_bitfield = list( - (True,) * len(attester_committee) + - (False,) * (spec.TARGET_PERSISTENT_COMMITTEE_SIZE * 2 - len(attester_committee)) - ) - block.signatures.attestation_signature = sign_shard_attestation( - spec, - shard_state, - beacon_state, - block, - participants=attester_committee, + shard_committee = spec.get_shard_committee(beacon_state, shard_state.shard, block.slot) + block.aggregation_bits = list( + (True,) * len(shard_committee) + + (False,) * (spec.MAX_PERIOD_COMMITTEE_SIZE * 2 - len(shard_committee)) ) else: - block.signatures.attestation_signature = sign_shard_attestation( - spec, - shard_state, - beacon_state, - block, - participants=(), - ) + shard_committee = [] + + block.attestations = sign_shard_attestation( + spec, + beacon_state, + shard_state, + block, + participants=shard_committee, + ) if signed: - sign_shard_block(spec, beacon_state, block, shard_state.shard) + sign_shard_block(spec, beacon_state, shard_state, block) return block diff --git a/test_libs/pyspec/eth2spec/test/helpers/phase1/shard_state.py b/test_libs/pyspec/eth2spec/test/helpers/phase1/shard_state.py new file mode 100644 index 000000000..24240b5fa --- /dev/null +++ b/test_libs/pyspec/eth2spec/test/helpers/phase1/shard_state.py @@ -0,0 +1,18 @@ +from eth2spec.test.helpers.phase1.shard_block import sign_shard_block + + +def configure_shard_state(spec, beacon_state, shard=0): + beacon_state.slot = spec.Slot(spec.SHARD_GENESIS_EPOCH * spec.SLOTS_PER_EPOCH) + shard_state = spec.get_genesis_shard_state(spec.Shard(shard)) + shard_state.slot = spec.ShardSlot(spec.SHARD_GENESIS_EPOCH * spec.SHARD_SLOTS_PER_EPOCH) + return beacon_state, shard_state + + +def shard_state_transition_and_sign_block(spec, beacon_state, shard_state, block): + """ + Shard state transition via the provided ``block`` + then package the block with the state root and signature. + """ + spec.shard_state_transition(beacon_state, shard_state, block) + block.state_root = shard_state.hash_tree_root() + sign_shard_block(spec, beacon_state, shard_state, block) diff --git a/test_libs/pyspec/eth2spec/test/phase_1/sanity/test_shard_blocks.py b/test_libs/pyspec/eth2spec/test/phase_1/sanity/test_shard_blocks.py new file mode 100644 index 000000000..51575f2d5 --- /dev/null +++ b/test_libs/pyspec/eth2spec/test/phase_1/sanity/test_shard_blocks.py @@ -0,0 +1,177 @@ +from copy import deepcopy + +from eth2spec.test.helpers.phase1.shard_block import ( + build_empty_shard_block, + sign_shard_block, +) +from eth2spec.test.helpers.phase1.shard_state import ( + configure_shard_state, + shard_state_transition_and_sign_block, +) +from eth2spec.test.context import ( + always_bls, + expect_assertion_error, + spec_state_test, + with_all_phases_except, +) + + +@with_all_phases_except(['phase0']) +@spec_state_test +@always_bls +def test_process_empty_shard_block(spec, state): + beacon_state, shard_state = configure_shard_state(spec, state) + + block = build_empty_shard_block( + spec, + beacon_state, + shard_state, + slot=shard_state.slot + 1, + signed=True, + full_attestation=False, + ) + + yield 'pre', shard_state + yield 'beacon_state', beacon_state + + shard_state_transition_and_sign_block(spec, beacon_state, shard_state, block) + + yield 'blocks', [block] + yield 'post', shard_state + + +@with_all_phases_except(['phase0']) +@spec_state_test +@always_bls +def test_process_full_attestation_shard_block(spec, state): + beacon_state, shard_state = configure_shard_state(spec, state) + + block = build_empty_shard_block( + spec, + beacon_state, + shard_state, + slot=shard_state.slot + 1, + signed=True, + full_attestation=True, + ) + + yield 'pre', shard_state + yield 'beacon_state', beacon_state + + shard_state_transition_and_sign_block(spec, beacon_state, shard_state, block) + + yield 'blocks', [block] + yield 'post', shard_state + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_prev_slot_block_transition(spec, state): + beacon_state, shard_state = configure_shard_state(spec, state) + + # Go to clean slot + spec.process_shard_slots(shard_state, shard_state.slot + 1) + # Make a block for it + block = build_empty_shard_block(spec, beacon_state, shard_state, slot=shard_state.slot, signed=True) + # Transition to next slot, above block will not be invalid on top of new state. + spec.process_shard_slots(shard_state, shard_state.slot + 1) + + yield 'pre', shard_state + yield 'beacon_state', beacon_state + expect_assertion_error( + lambda: spec.shard_state_transition(beacon_state, shard_state, block) + ) + yield 'blocks', [block] + yield 'post', None + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_same_slot_block_transition(spec, state): + beacon_state, shard_state = configure_shard_state(spec, state) + + # Same slot on top of pre-state, but move out of slot 0 first. + spec.process_shard_slots(shard_state, shard_state.slot + 1) + block = build_empty_shard_block(spec, beacon_state, shard_state, slot=shard_state.slot, signed=True) + + yield 'pre', shard_state + yield 'beacon_state', beacon_state + + shard_state_transition_and_sign_block(spec, beacon_state, shard_state, block) + + yield 'blocks', [block] + yield 'post', shard_state + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_invalid_state_root(spec, state): + beacon_state, shard_state = configure_shard_state(spec, state) + + spec.process_shard_slots(shard_state, shard_state.slot + 1) + block = build_empty_shard_block(spec, beacon_state, shard_state, slot=shard_state.slot) + block.state_root = b'\x36' * 32 + sign_shard_block(spec, beacon_state, shard_state, block) + + yield 'pre', shard_state + yield 'beacon_state', beacon_state + expect_assertion_error( + lambda: spec.shard_state_transition(beacon_state, shard_state, block, validate_state_root=True) + ) + yield 'blocks', [block] + yield 'post', None + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_skipped_slots(spec, state): + beacon_state, shard_state = configure_shard_state(spec, state) + + block = build_empty_shard_block(spec, beacon_state, shard_state, slot=shard_state.slot + 3, signed=True) + + yield 'pre', shard_state + yield 'beacon_state', beacon_state + + shard_state_transition_and_sign_block(spec, beacon_state, shard_state, block) + + yield 'blocks', [block] + yield 'post', shard_state + + assert shard_state.slot == block.slot + latest_block_header = deepcopy(shard_state.latest_block_header) + latest_block_header.state_root = shard_state.hash_tree_root() + assert latest_block_header.signing_root() == block.signing_root() + + +@with_all_phases_except(['phase0']) +@spec_state_test +def test_empty_shard_period_transition(spec, state): + beacon_state, shard_state = configure_shard_state(spec, state) + + # modify some of the deltas to ensure the period transition works properly + stub_delta = 10 + shard_state.newer_committee_positive_deltas[0] = stub_delta + shard_state.newer_committee_negative_deltas[0] = stub_delta + + slot = shard_state.slot + spec.SHARD_SLOTS_PER_EPOCH * spec.EPOCHS_PER_SHARD_PERIOD + beacon_state.slot = spec.compute_epoch_of_shard_slot(slot) * spec.SLOTS_PER_EPOCH - 4 + spec.process_slots(beacon_state, spec.compute_epoch_of_shard_slot(slot) * spec.SLOTS_PER_EPOCH) + + # all validators get slashed for not revealing keys + # undo this to allow for a block proposal + for index in range(len(beacon_state.validators)): + beacon_state.validators[index].slashed = False + block = build_empty_shard_block(spec, beacon_state, shard_state, slot=slot, signed=True) + + yield 'pre', shard_state + yield 'beacon_state', beacon_state + + shard_state_transition_and_sign_block(spec, beacon_state, shard_state, block) + + yield 'blocks', [block] + yield 'post', shard_state + + shard_state.older_committee_positive_deltas[0] == stub_delta + shard_state.older_committee_negative_deltas[0] == stub_delta + shard_state.newer_committee_positive_deltas[0] == 0 + shard_state.newer_committee_negative_deltas[0] == 0 diff --git a/test_libs/pyspec/eth2spec/test/phase_1/shard_data_chain/test_beacon_attestation.py b/test_libs/pyspec/eth2spec/test/phase_1/shard_data_chain/test_beacon_attestation.py deleted file mode 100644 index aface905b..000000000 --- a/test_libs/pyspec/eth2spec/test/phase_1/shard_data_chain/test_beacon_attestation.py +++ /dev/null @@ -1,48 +0,0 @@ -from eth2spec.test.context import ( - with_all_phases_except, - spec_state_test, - always_bls, -) -from eth2spec.test.helpers.phase1.shard_block import ( - build_empty_shard_block, -) -from eth2spec.test.helpers.attestations import get_valid_attestation - - -@with_all_phases_except(['phase0']) -@always_bls -@spec_state_test -def test_process_empty_shard_block(spec, state): - beacon_state = state - - shard_slot = spec.PHASE_1_FORK_SLOT - beacon_state.slot = spec.Slot(spec.PHASE_1_FORK_EPOCH * spec.SLOTS_PER_EPOCH) - shard_state = spec.get_default_shard_state(beacon_state, shard=spec.Shard(0)) - shard_state.slot = shard_slot - - block = build_empty_shard_block( - spec, - shard_state, - beacon_state, - slot=shard_slot + 1, - parent_root=spec.Hash(), - signed=True, - full_attestation=True, - ) - - yield 'pre', shard_state - yield 'beacon_state', beacon_state - yield 'block', block - - beacon_attestation = get_valid_attestation(spec, beacon_state, signed=True) - yield 'beacon_attestation', beacon_attestation - - is_valid_beacon_attestation = spec.is_valid_beacon_attestation( - pre_state=shard_state, - shard_blocks_or_state_roots=(block,), - beacon_state=beacon_state, - valid_attestations=set([beacon_attestation]), - candidate=beacon_attestation, - ) - assert is_valid_beacon_attestation - yield 'is_valid_beacon_attestation', is_valid_beacon_attestation diff --git a/test_libs/pyspec/eth2spec/test/phase_1/shard_data_chain/test_shard_block.py b/test_libs/pyspec/eth2spec/test/phase_1/shard_data_chain/test_shard_block.py deleted file mode 100644 index 2bb0232f0..000000000 --- a/test_libs/pyspec/eth2spec/test/phase_1/shard_data_chain/test_shard_block.py +++ /dev/null @@ -1,68 +0,0 @@ -from eth2spec.test.helpers.phase1.shard_block import ( - build_empty_shard_block, -) -from eth2spec.test.context import ( - with_all_phases_except, - spec_state_test, - always_bls, -) - - -@with_all_phases_except(['phase0']) -@always_bls -@spec_state_test -def test_process_empty_shard_block(spec, state): - beacon_state = state - - shard_slot = spec.PHASE_1_FORK_SLOT - beacon_state.slot = spec.Slot(spec.PHASE_1_FORK_EPOCH * spec.SLOTS_PER_EPOCH) - shard_state = spec.get_default_shard_state(beacon_state, shard=spec.Shard(0)) - shard_state.slot = shard_slot - - block = build_empty_shard_block( - spec, - shard_state, - beacon_state, - slot=shard_slot + 1, - parent_root=spec.Hash(), - signed=True, - full_attestation=False, - ) - - yield 'pre', shard_state - yield 'beacon_state', beacon_state - yield 'block', block - - spec.shard_state_transition(shard_state, beacon_state, block) - - yield 'post', shard_state - - -@with_all_phases_except(['phase0']) -@always_bls -@spec_state_test -def test_process_full_attestation_shard_block(spec, state): - beacon_state = state - - shard_slot = spec.PHASE_1_FORK_SLOT - beacon_state.slot = spec.Slot(spec.PHASE_1_FORK_EPOCH * spec.SLOTS_PER_EPOCH) - shard_state = spec.get_default_shard_state(beacon_state, shard=spec.Shard(0)) - shard_state.slot = shard_slot - - block = build_empty_shard_block( - spec, - shard_state, - beacon_state, - slot=shard_slot + 1, - parent_root=spec.Hash(), - signed=True, - full_attestation=True, - ) - - yield 'pre', shard_state - yield 'beacon_state', beacon_state - yield 'block', block - - spec.shard_state_transition(shard_state, beacon_state, block) - - yield 'post', shard_state