From 334d6c6bc73eee2daf45f75c53a62a8cd5a4066a Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 27 Aug 2019 14:58:28 +0100 Subject: [PATCH] Revamp 1_shard-data-chains.md WIP! * Significant simplifications * A few bug fixes * Lots of cleanups and reorganising (making it consistent with `0_beacon-chain.md`) * Likely a few bugs introduced --- specs/core/1_shard-data-chains.md | 641 ++++++++++++++---------------- 1 file changed, 295 insertions(+), 346 deletions(-) diff --git a/specs/core/1_shard-data-chains.md b/specs/core/1_shard-data-chains.md index 8e1532f17..9335c1803 100644 --- a/specs/core/1_shard-data-chains.md +++ b/specs/core/1_shard-data-chains.md @@ -17,31 +17,37 @@ - [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) - - [`ShardBlock`](#shardblock) + - [Containers](#containers) - [`ShardBlockSignatures`](#shardblocksignatures) - - [`ShardBlockCore`](#shardblockcore) - - [`ExtendedShardBlockCore`](#extendedshardblockcore) + - [`ShardBlock`](#shardblock) + - [`ShardBlockHeader`](#shardblockheader) - [`ShardState`](#shardstate) - [`ShardReceiptDelta`](#shardreceiptdelta) - [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) + - [Misc](#misc-1) + - [`pad`](#pad) + - [`compute_slot_of_shard_slot`](#compute_slot_of_shard_slot) + - [`compute_epoch_of_shard_slot`](#compute_epoch_of_shard_slot) + - [`compute_period_start_epoch`](#compute_period_start_epoch) + - [`compute_flat_shard_header`](#compute_flat_shard_header) + - [`compute_crosslink_data_root`](#compute_crosslink_data_root) + - [State accessors](#state-accessors) + - [`get_period_committee`](#get_period_committee) + - [`get_persistent_committee`](#get_persistent_committee) + - [`get_shard_proposer_index`](#get_shard_proposer_index) + - [`get_default_shard_state`](#get_default_shard_state) + - [`get_shard_base_reward`](#get_shard_base_reward) + - [State mutators](#state-mutators) + - [`add_fee`](#add_fee) + - [`add_reward`](#add_reward) + - [Shard state transition function](#shard-state-transition-function) + - [Period processing](#period-processing) + - [Block processing](#block-processing) + - [Block header](#block-header) + - [Attestations](#attestations) + - [Block data fees](#block-data-fees) - [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) - [Shard fork choice rule](#shard-fork-choice-rule) @@ -53,11 +59,9 @@ This document describes the shard data layer and the shard fork choice rule in P ## 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,7 +69,7 @@ We define the following Python custom types for type hinting and readability: | Name | Value | | - | - | -| `SHARD_SLOTS_PER_BEACON_SLOT` | `2**1` (= 2) | +| `SHARD_SLOTS_PER_EPOCH` | `2**7` (= 128) | | `TARGET_PERSISTENT_COMMITTEE_SIZE` | `2**7` (= 128) | | `SHARD_HEADER_SIZE` | `2**9` (= 512) | | `SHARD_BLOCK_SIZE_TARGET` | `2**14` (= 16,384) | @@ -76,7 +80,6 @@ We define the following Python custom types for type hinting and readability: | Name | Value | | - | - | | `PHASE_1_FORK_EPOCH` | **TBD** | -| `PHASE_1_FORK_SLOT` | **TBD** | ### Time parameters @@ -96,75 +99,53 @@ We define the following Python custom types for type hinting and readability: | Name | Value | | - | - | | `BASEFEE_ADJUSTMENT_FACTOR` | `2**3` (= 8) | -| `REWARD_COEFFICIENT_BASE` | `2**20` ( = 1,048,576) | +| `REWARD_COEFFICIENT_BASE` | `2**20` (= 1,048,576) | ### 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 +## Containers -| 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` +### `ShardBlockSignatures` ```python -class ShardBlockHeader(Container): - core: ShardBlockCore - signatures: ShardBlockSignatures +class ShardBlockSignatures(Container): + attesters: BLSSignature + proposer: BLSSignature ``` ### `ShardBlock` ```python class ShardBlock(Container): - core: ExtendedShardBlockCore + slot: ShardSlot + beacon_chain_root: Hash + parent_root: Hash + state_root: Hash + aggregation_bits: Bitvector[TARGET_PERSISTENT_COMMITTEE_SIZE * 2] + total_bytes: uint64 + body: Bytes[SHARD_BLOCK_SIZE_LIMIT - SHARD_HEADER_SIZE] + padding: Bytes[32] signatures: ShardBlockSignatures ``` -### `ShardBlockSignatures` +### `ShardBlockHeader` ```python -class ShardBlockSignatures(Container): - attestation_signature: BLSSignature - proposer_signature: BLSSignature -``` - -### `ShardBlockCore` - -```python -class ShardBlockCore(Container): +class ShardBlockHeader(Container): slot: ShardSlot beacon_chain_root: Hash parent_root: Hash - data_root: Hash state_root: Hash + aggregation_bits: Bitvector[TARGET_PERSISTENT_COMMITTEE_SIZE * 2] total_bytes: uint64 - attester_bitfield: Bitvector[TARGET_PERSISTENT_COMMITTEE_SIZE * 2] -``` - -### `ExtendedShardBlockCore` - -```python -class ExtendedShardBlockCore(Container): - slot: ShardSlot - beacon_chain_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 + padding: Bytes[32] + signatures: ShardBlockSignatures ``` ### `ShardState` @@ -179,7 +160,7 @@ class ShardState(Container): basefee: Gwei slot: ShardSlot shard: Shard - most_recent_block_core: ShardBlockCore + latest_block_header: ShardBlockHeader receipt_root: Hash total_bytes: uint64 ``` @@ -195,34 +176,67 @@ class ShardReceiptDelta(Container): ## Helper functions -### `compute_slot_of_shard_slot` +### Misc + +#### `pad` ```python -def compute_slot_of_shard_slot(slot: ShardSlot) -> Epoch: - return Epoch(slot // SHARD_SLOTS_PER_BEACON_SLOT) +def pad(x: bytes, length: uint64) -> bytes: + assert len(x) <= length + return x + b'\x00' * (length - len(x)) ``` -### `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 compute_epoch_of_slot(compute_slot_of_shard_slot(slot)) ``` -### `get_shard_period_start_epoch` +#### `compute_period_start_epoch` ```python -def get_shard_period_start_epoch(epoch: Epoch, lookback: int=0) -> Epoch: +def compute_period_start_epoch(epoch: Epoch, lookback: Epoch=0) -> Epoch: return Epoch(epoch - (epoch % EPOCHS_PER_SHARD_PERIOD) - lookback * EPOCHS_PER_SHARD_PERIOD) ``` -### `get_period_committee` +#### `compute_flat_shard_header` + +```python +def compute_flat_shard_header(block: ShardBlock) -> Bytes[SHARD_HEADER_SIZE]: + """ + Return a flat serialisation of the ``block`` header which preserves hash tree root. + """ + return ( + pad(int_to_bytes(block.slot, length=8), 32) + + block.beacon_chain_root + + block.parent_root + + hash_tree_root(block.body) + + block.state_root + + pad(int_to_bytes(block.total_bytes, length=8), 32) + + bytes([sum([block.aggregation_bits[i + j] << j for j in range(8)]) for i in range(0, 256, 8)]) + + block.padding + + pad(block.signatures.attesters, 128) + + pad(block.signatures.proposer, 128) + ) +``` + +#### `compute_crosslink_data_root` + +```python +def compute_crosslink_data_root(blocks: Sequence[ShardBlock]) -> Hash: + headers = b''.join([compute_flat_shard_header(block) for block in blocks]) + bodies = b''.join([block.body for block in blocks]) + MAX_SIZE = SHARD_BLOCK_SIZE_LIMIT * SHARD_SLOTS_PER_EPOCH * MAX_EPOCHS_PER_CROSSLINK + return hash_tree_root(BytesN[MAX_SIZE](pad(headers + bodies, MAX_SIZE))) +``` + +### 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), @@ -233,19 +247,12 @@ def get_period_committee(state: BeaconState, epoch: Epoch, shard: Shard) -> Sequ return full_committee[:TARGET_PERSISTENT_COMMITTEE_SIZE] ``` -### `get_persistent_committee` +#### `get_persistent_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) +def get_persistent_committee(state: BeaconState, shard: Shard, epoch: Epoch) -> Sequence[ValidatorIndex]: + earlier_committee = get_period_committee(state, compute_period_start_epoch(epoch, lookback=2), shard) + later_committee = get_period_committee(state, compute_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 @@ -255,22 +262,17 @@ def get_persistent_committee(state: BeaconState, )) ``` -### `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) - - active_indices = [i for i in persistent_committee if is_active_validator(state.validators[i], current_epoch)] - if not any(active_indices): - return None +def get_shard_proposer_index(state: BeaconState, shard: Shard, slot: ShardSlot) -> ValidatorIndex: + epoch = get_current_epoch(state) + persistent_committee = list(get_persistent_committee(state, shard, epoch)) + active_indices = [i for i in persistent_committee if is_active_validator(state.validators[i], epoch)] + assert len(active_indices) > 0 MAX_RANDOM_BYTE = 2**8 - 1 - seed = hash(get_seed(state, current_epoch) + int_to_bytes(shard, length=8) + int_to_bytes(slot, length=8)) + seed = hash(get_seed(state, epoch) + 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)] @@ -281,68 +283,7 @@ def get_shard_block_proposer_index(state: BeaconState, i += 1 ``` -### `get_shard_header` - -```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` +#### `get_default_shard_state` ```python def get_default_shard_state(beacon_state: BeaconState, shard: Shard) -> ShardState: @@ -359,7 +300,7 @@ def get_default_shard_state(beacon_state: BeaconState, shard: Shard) -> ShardSta return ShardState( basefee=1, shard=shard, - slot=PHASE_1_FORK_SLOT, + slot=ShardSlot(PHASE_1_FORK_EPOCH * SHARD_SLOTS_PER_EPOCH), 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))], @@ -367,195 +308,206 @@ def get_default_shard_state(beacon_state: BeaconState, shard: Shard) -> ShardSta ) ``` +#### `get_shard_base_reward` + +```python +def get_shard_base_reward(beacon_state: BeaconState) -> Gwei: + total_balance_root = integer_squareroot(get_total_active_balance(beacon_state)) + return Gwei(REWARD_COEFFICIENT_BASE * BASE_REWARD_FACTOR // total_balance_root // BASE_REWARDS_PER_EPOCH) +``` + +### State mutators + +#### `add_reward` + +```python +def add_reward(state: ShardState, beacon_state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: + epoch = compute_epoch_of_shard_slot(state.slot) + earlier_committee = get_period_committee(beacon_state, compute_period_start_epoch(epoch, lookback=2), state.shard) + later_committee = get_period_committee(beacon_state, compute_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 +``` + +#### `add_fee` + +```python +def add_fee(state: ShardState, beacon_state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None: + epoch = compute_epoch_of_shard_slot(state.slot) + earlier_committee = get_period_committee(beacon_state, compute_period_start_epoch(epoch, lookback=2), state.shard) + later_committee = get_period_committee(beacon_state, compute_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 +``` + +## Shard state transition function + +The post-state corresponding to a pre-state `state`, a beacon state `beacon_state`, and a block `block` is defined as `shard_state_transition(state, beacon_state, block)`. State transitions that trigger an unhandled exception (e.g. a failed `assert` or an out-of-range list access) are considered invalid. + +```python +def shard_state_transition(state: ShardState, + beacon_state: BeaconState, + block: ShardBlock, + validate_state_root: bool=False) -> ShardState: + # Process slots (including those with no blocks) since block + process_shard_slots(state, beacon_state, block.slot) + # Process block + process_shard_block(state, beacon_state, block) + # Validate state root (`validate_state_root == True` in production) + if validate_state_root: + assert block.state_root == hash_tree_root(state) + # Return post-state + return state +``` + +```python +def process_shard_slots(state: ShardState, beacon_state: BeaconState, slot: ShardSlot) -> None: + assert state.slot <= slot + while state.slot < slot: + process_shard_slot(state) + # Process period on the start slot of the next period + if (state.slot + 1) % (SHARD_SLOTS_PER_EPOCH * EPOCHS_PER_SHARD_PERIOD) == 0: + process_shard_period(state) + state.slot += ShardSlot(1) +``` + +```python +def process_shard_slot(state: ShardState, beacon_state: BeaconState, slot: ShardSlot) -> None: + # Cache state root + if state.latest_block_header.state_root == Hash(): + state.latest_block_header.state_root = hash_tree_root(state) + # Save state roots in history accumulator + depth = 0 + state_root = hash_tree_root(state) + while state.slot % 2**depth == 0 and depth < HISTORY_ACCUMULATOR_VECTOR: + state.history_accumulator[depth] = state_root + depth += 1 +``` + +### Period processing + +```python +def process_shard_period(state: ShardState, beacon_state: BeaconState) -> None: + epoch = compute_epoch_of_shard_slot(state.slot) + earlier_committee = get_period_committee( + beacon_state, + compute_period_start_epoch(epoch, lookback=2), + state.shard, + ) + later_committee = get_period_committee( + beacon_state, + compute_period_start_epoch(epoch, lookback=1), + state.shard, + ) + + state.receipt_root = hash_tree_root(List[ShardReceiptDelta, TARGET_PERSISTENT_COMMITTEE_SIZE]([ + ShardReceiptDelta(validator_index, state.earlier_committee_rewards[i], 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))] +``` + +### Block processing + +```python +def process_shard_block(state: ShardState, beacon_state: BeaconState, block: ShardBlock) -> None: + process_shard_block_header(state, beacon_state, block) + process_shard_attestations(state, beacon_state, block + process_shard_block_data_fees(state, beacon_state, block) +``` + +#### Block header + +```python +def process_shard_block_header(state: ShardState, beacon_state: BeaconState, block: ShardBlock) -> None: + # Verify that the slots match + assert block.slot == state.slot + # Verify that the parent matches + if block.parent_root != Hash(): + assert block.parent_root == signing_root(state.latest_block_header) + # Save current block as the new latest block + state.latest_block_header = ShardBlockHeader( + slot=block.slot, + beacon_chain_root=block.beacon_chain_root, + parent_root=block.parent_root, + # `state_root` is zeroed and overwritten in the next `process_shard_slot` call + aggregation_bits=block.aggregation_bits, + total_bytes=block.total_bytes, + body_root=hash_tree_root(block.body), + # `signatures` is zeroed + ) + # Verify proposer signature + proposer_index = get_shard_proposer_index(beacon_state, state.shard, block.slot) + pubkey = beacon_state.validators[proposer_index].pubkey + domain = get_domain(beacon_state, DOMAIN_SHARD_PROPOSER, compute_epoch_of_shard_slot(block.slot)) + assert bls_verify(pubkey, signing_root(block), block.signatures.proposer, domain) + # Verify total bytes count + state.total_bytes += len(block.body) + assert block.total_bytes == state.total_bytes +``` + +#### Attestations + +```python +def process_shard_attestations(state: ShardState, beacon_state: BeaconState, block: ShardBlock) -> None: + persistent_committee = get_persistent_committee(beacon_state, state.shard, block.slot) + pubkeys = [] + attestation_count = 0 + base_reward = get_shard_base_reward(beacon_state) + for i, validator_index in enumerate(persistent_committee): + if block.aggregation_bits[i]: + pubkeys.append(beacon_state.validators[validator_index].pubkey) + add_reward(state, beacon_state, validator_index, base_reward) + attestation_count += 1 + for i in range(len(persistent_committee), TARGET_PERSISTENT_COMMITTEE_SIZE): + assert block.aggregation_bits[i] is False or block.aggregation_bits[i] == 0 # TODO: Fix Bitvector + # Verify aggregate signature + domain = get_domain(beacon_state, DOMAIN_SHARD_ATTESTER, compute_epoch_of_shard_slot(block.slot)) + assert bls_verify(bls_aggregate_pubkeys(pubkeys), block.parent_root, block.signatures.attesters, domain) + # Proposer micro-rewards + add_reward(state, beacon_state, proposer_index, attestation_count * get_shard_base_reward(beacon_state) // PROPOSER_REWARD_QUOTIENT) +``` + +#### Block data fees + +```python +def process_shard_block_data_fees(state: ShardState, beacon_state: BeaconState, block: ShardBlock) -> None: + base_reward = get_shard_base_reward(beacon_state) + add_fee(state, beacon_state, proposer_index, state.basefee * len(block.body) // SHARD_BLOCK_SIZE_LIMIT) + QUOTIENT = SHARD_BLOCK_SIZE_LIMIT * BASEFEE_ADJUSTMENT_FACTOR + if len(block.body) > SHARD_BLOCK_SIZE_TARGET: + state.basefee += Gwei(max(1, state.basefee * (len(block.body) - SHARD_BLOCK_SIZE_TARGET) // QUOTIENT)) + elif len(block.body) < SHARD_BLOCK_SIZE_TARGET: + state.basefee -= Gwei(max(1, state.basefee * (len(block.body) - SHARD_BLOCK_SIZE_TARGET) // QUOTIENT)) + state.basefee = Gwei(max(1, min( EFFECTIVE_BALANCE_INCREMENT // EPOCHS_PER_SHARD_PERIOD // SHARD_SLOTS_PER_EPOCH, + state.basefee, + ))) +``` + ## 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)`. +* Either `block.parent_root == Hash()` or a block `parent` such that `signing_root(parent) == block.parent_root` has already been accepted. +* `block.beacon_chain_root == get_block_root(head_beacon_state, compute_epoch_of_shard_slot(parent.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.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.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.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 +- `pre_state` be 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 @@ -594,12 +546,9 @@ def is_valid_beacon_attestation(pre_state: ShardState, blocks.append(shard_blocks_or_state_roots[slot]) else: blocks.append(ShardBlock( - core=ExtendedShardBlockCore( - slot=slot, - state_root=shard_blocks_or_state_roots[slot], - total_bytes=pre_state.total_bytes, - ), - signatures=ShardBlockSignatures(), + slot=slot, + state_root=shard_blocks_or_state_roots[slot], + total_bytes=pre_state.total_bytes, )) assert candidate.data.crosslink.data_root == compute_crosslink_data_root(blocks)