From de9b4f2d6d64e42fe00c070dd98e77cd6f37d24e Mon Sep 17 00:00:00 2001 From: vbuterin Date: Mon, 29 Jul 2019 09:47:35 -0400 Subject: [PATCH] Attestation changes + persistent committee changes (#1294) * Minimal attestation simplification * minor fix * Make the tests pass * Decrease `PLACEHOLDER`, Use `compute_epoch_of_shard_slot` * Fix proposer signature name and use get_seed() to calculate current_shuffling_seed * Fix linter error * Add the WIP `test_is_valid_shard_block` * Add `get_shard_block_attester_committee` * Simplified committee selection * Added some helpers and simplified * Update specs/core/1_shard-data-chains.md * Update 1_shard-data-chains.md * Simplified switchover epochs, changed block structure, changed crosslink structure * Update 1_shard-data-chains.md * Moved balance dependency to proposer selection * Update specs/core/1_shard-data-chains.md Co-Authored-By: Danny Ryan * Update specs/core/1_shard-data-chains.md Co-Authored-By: Danny Ryan * Update specs/core/1_shard-data-chains.md Co-Authored-By: Danny Ryan * Update specs/core/1_shard-data-chains.md Co-Authored-By: Danny Ryan * Update specs/core/1_shard-data-chains.md Co-Authored-By: Danny Ryan * Update specs/core/1_shard-data-chains.md Co-Authored-By: Danny Ryan * Update specs/core/1_shard-data-chains.md Co-Authored-By: Danny Ryan * Update specs/core/1_shard-data-chains.md * Fixed shard header flattening * Update specs/core/1_shard-data-chains.md * Minor fixes * Update specs/core/1_shard-data-chains.md * Update specs/core/1_shard-data-chains.md Co-Authored-By: Hsiao-Wei Wang * cleanup testing and lint * return none if not active validators in persistent committee * only allow active validators as shard proposer --- scripts/build_spec.py | 3 +- specs/core/1_shard-data-chains.md | 380 +++++++++--------- .../eth2spec/test/helpers/phase1/__init__.py | 0 .../test/helpers/phase1/shard_block.py | 47 +++ .../shard_data_chain/test_shard_block.py | 26 ++ 5 files changed, 263 insertions(+), 193 deletions(-) create mode 100644 test_libs/pyspec/eth2spec/test/helpers/phase1/__init__.py create mode 100644 test_libs/pyspec/eth2spec/test/helpers/phase1/shard_block.py create mode 100644 test_libs/pyspec/eth2spec/test/phase_1/shard_data_chain/test_shard_block.py diff --git a/scripts/build_spec.py b/scripts/build_spec.py index 8b541ff50..96866cc8a 100644 --- a/scripts/build_spec.py +++ b/scripts/build_spec.py @@ -48,11 +48,10 @@ from dataclasses import ( from eth2spec.utils.ssz.ssz_impl import ( hash_tree_root, signing_root, - serialize, is_empty, ) from eth2spec.utils.ssz.ssz_typing import ( - bit, boolean, Container, List, Vector, Bytes, uint64, + uint64, bit, boolean, Container, List, Vector, Bytes, BytesN, Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, Bitlist, Bitvector, ) from eth2spec.utils.bls import ( diff --git a/specs/core/1_shard-data-chains.md b/specs/core/1_shard-data-chains.md index 00737a87a..fc839930f 100644 --- a/specs/core/1_shard-data-chains.md +++ b/specs/core/1_shard-data-chains.md @@ -9,6 +9,7 @@ - [Ethereum 2.0 Phase 1 -- Shard Data Chains](#ethereum-20-phase-1----shard-data-chains) - [Table of contents](#table-of-contents) - [Introduction](#introduction) + - [Custom types](#custom-types) - [Configuration](#configuration) - [Misc](#misc) - [Initial values](#initial-values) @@ -16,21 +17,25 @@ - [Signature domain types](#signature-domain-types) - [TODO PLACEHOLDER](#todo-placeholder) - [Data structures](#data-structures) - - [`ShardBlockBody`](#shardblockbody) - - [`ShardAttestation`](#shardattestation) - - [`ShardBlock`](#shardblock) - [`ShardBlockHeader`](#shardblockheader) + - [`ShardBlock`](#shardblock) + - [`ShardBlockSignatures`](#shardblocksignatures) + - [`ShardBlockCore`](#shardblockcore) + - [`ExtendedShardBlockCore`](#extendedshardblockcore) - [Helper functions](#helper-functions) + - [`compute_epoch_of_shard_slot`](#compute_epoch_of_shard_slot) + - [`compute_slot_of_shard_slot`](#compute_slot_of_shard_slot) + - [`get_shard_period_start_epoch`](#get_shard_period_start_epoch) - [`get_period_committee`](#get_period_committee) - - [`get_switchover_epoch`](#get_switchover_epoch) - [`get_persistent_committee`](#get_persistent_committee) - - [`get_shard_proposer_index`](#get_shard_proposer_index) + - [`get_shard_block_proposer_index`](#get_shard_block_proposer_index) + - [`get_shard_block_attester_committee`](#get_shard_block_attester_committee) - [`get_shard_header`](#get_shard_header) - - [`verify_shard_attestation_signature`](#verify_shard_attestation_signature) + - [`pad`](#pad) + - [`flatten_shard_header`](#flatten_shard_header) - [`compute_crosslink_data_root`](#compute_crosslink_data_root) - [Object validity](#object-validity) - [Shard blocks](#shard-blocks) - - [Shard attestations](#shard-attestations) - [Beacon attestations](#beacon-attestations) - [Shard fork choice rule](#shard-fork-choice-rule) @@ -40,14 +45,24 @@ This document describes the shard data layer and the shard fork choice rule in 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 | + ## Configuration ### Misc | Name | Value | | - | - | -| `BYTES_PER_SHARD_BLOCK_BODY` | `2**14` (= 16,384) | -| `MAX_SHARD_ATTESTIONS` | `2**4` (= 16) | +| `SHARD_HEADER_SIZE` | `2**9` (= 512) | +| `SHARD_BLOCK_SIZE_LIMIT` | `2**16` (= 65,536) | +| `SHARD_SLOTS_PER_BEACON_SLOT` | `2**1` (= 2) | +| `MAX_PERSISTENT_COMMITTEE_SIZE` | `2**7` (= 128) | ### Initial values @@ -61,7 +76,8 @@ This document describes the shard data layer and the shard fork choice rule in P | Name | Value | Unit | Duration | | - | - | :-: | :-: | -| `CROSSLINK_LOOKBACK` | `2**0` (= 1) | epochs | 6.2 minutes | +| `CROSSLINK_LOOKBACK` | `2**0` (= 1) | epochs | 6.4 minutes | +| `EPOCHS_PER_SHARD_PERIOD` | `2**8` (= 256) | epochs | ~27 hours | ### Signature domain types @@ -76,85 +92,102 @@ The following types are defined, mapping into `DomainType` (little endian): | Name | Value | | - | - | -| `PLACEHOLDER` | `2**32` | +| `PLACEHOLDER` | `2**3` | ## Data structures -### `ShardBlockBody` +_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 ShardBlockBody(Container): - data: Vector[Bytes[PLACEHOLDER], BYTES_PER_SHARD_BLOCK_BODY] -``` - -### `ShardAttestation` - -```python -class ShardAttestation(Container): - class data(Container): - slot: Slot - shard: Shard - shard_block_root: Hash - aggregation_bits: Bitlist[PLACEHOLDER] - aggregate_signature: BLSSignature +class ShardBlockHeader(Container): + core: ShardBlockCore + signatures: ShardBlockSignatures ``` ### `ShardBlock` ```python class ShardBlock(Container): - slot: Slot - shard: Shard - beacon_chain_root: Hash - parent_root: Hash - data: ShardBlockBody - state_root: Hash - attestations: List[ShardAttestation, PLACEHOLDER] - signature: BLSSignature + core: ExtendedShardBlockCore + signatures: ShardBlockSignatures ``` -### `ShardBlockHeader` +### `ShardBlockSignatures` ```python -class ShardBlockHeader(Container): - slot: Slot - shard: Shard +class ShardBlockSignatures(Container): + attestation_signature: BLSSignature + proposer_signature: BLSSignature +``` + +### `ShardBlockCore` + +```python +class ShardBlockCore(Container): + slot: ShardSlot beacon_chain_root: Hash parent_root: Hash - body_root: Hash + data_root: Hash state_root: Hash - attestations: List[ShardAttestation, PLACEHOLDER] - signature: BLSSignature + total_bytes: uint64 + attester_bitfield: Bitvector[MAX_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[MAX_PERSISTENT_COMMITTEE_SIZE * 2] ``` ## Helper functions +### `compute_slot_of_shard_slot` + +```python +def compute_slot_of_shard_slot(slot: ShardSlot) -> Epoch: + return Epoch(slot // SHARD_SLOTS_PER_BEACON_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) +``` + +### `get_shard_period_start_epoch` + +```python +def get_shard_period_start_epoch(epoch: Epoch, lookback: Epoch=Epoch(0)) -> Epoch: + return Epoch(epoch - (epoch % EPOCHS_PER_SHARD_PERIOD) - lookback * EPOCHS_PER_SHARD_PERIOD) +``` + ### `get_period_committee` ```python def get_period_committee(state: BeaconState, epoch: Epoch, - shard: Shard, - index: uint64, - count: uint64) -> Sequence[ValidatorIndex]: + shard: Shard) -> List[ValidatorIndex, MAX_PERSISTENT_COMMITTEE_SIZE]: """ Return committee for a period. Used to construct persistent committees. """ - return compute_committee( + full_committee = compute_committee( indices=get_active_validator_indices(state, epoch), seed=get_seed(state, epoch), - index=shard * count + index, - count=SHARD_COUNT * count, + index=shard, + count=SHARD_COUNT, ) -``` -### `get_switchover_epoch` - -```python -def get_switchover_epoch(state: BeaconState, epoch: Epoch, index: ValidatorIndex) -> int: - earlier_start_epoch = Epoch(epoch - (epoch % PERSISTENT_COMMITTEE_PERIOD) - PERSISTENT_COMMITTEE_PERIOD * 2) - return (bytes_to_int(hash(get_seed(state, earlier_start_epoch) + int_to_bytes(index, length=3)[0:8])) - % PERSISTENT_COMMITTEE_PERIOD) + return full_committee[:MAX_PERSISTENT_COMMITTEE_SIZE] ``` ### `get_persistent_committee` @@ -162,52 +195,47 @@ def get_switchover_epoch(state: BeaconState, epoch: Epoch, index: ValidatorIndex ```python def get_persistent_committee(state: BeaconState, shard: Shard, - slot: Slot) -> Sequence[ValidatorIndex]: + slot: ShardSlot) -> Sequence[ValidatorIndex]: """ Return the persistent committee for the given ``shard`` at the given ``slot``. """ - epoch = compute_epoch_of_slot(slot) - earlier_start_epoch = Epoch(epoch - (epoch % PERSISTENT_COMMITTEE_PERIOD) - PERSISTENT_COMMITTEE_PERIOD * 2) - later_start_epoch = Epoch(epoch - (epoch % PERSISTENT_COMMITTEE_PERIOD) - PERSISTENT_COMMITTEE_PERIOD) + epoch = compute_epoch_of_shard_slot(slot) - committee_count = max( - len(get_active_validator_indices(state, earlier_start_epoch)) // - (SHARD_COUNT * TARGET_COMMITTEE_SIZE), - len(get_active_validator_indices(state, later_start_epoch)) // - (SHARD_COUNT * TARGET_COMMITTEE_SIZE), - ) + 1 - - index = slot % committee_count - earlier_committee = get_period_committee(state, earlier_start_epoch, shard, index, committee_count) - later_committee = get_period_committee(state, later_start_epoch, shard, index, committee_count) + earlier_committee = get_period_committee(state, get_shard_period_start_epoch(epoch, lookback=Epoch(2)), shard) + later_committee = get_period_committee(state, get_shard_period_start_epoch(epoch, lookback=Epoch(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(list(set( - [i for i in earlier_committee if epoch % PERSISTENT_COMMITTEE_PERIOD < get_switchover_epoch(state, epoch, i)] - + [i for i in later_committee if epoch % PERSISTENT_COMMITTEE_PERIOD >= get_switchover_epoch(state, epoch, i)] - ))) + 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] + )) ``` -### `get_shard_proposer_index` +### `get_shard_block_proposer_index` ```python -def get_shard_proposer_index(state: BeaconState, - shard: Shard, - slot: Slot) -> Optional[ValidatorIndex]: +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)) - seed = hash(state.current_shuffling_seed + int_to_bytes(shard, length=8) + int_to_bytes(slot, length=8)) - random_index = bytes_to_int(seed[0:8]) % len(persistent_committee) - persistent_committee = persistent_committee[random_index:] + persistent_committee[:random_index] + current_epoch = get_current_epoch(state) - # Search for an active proposer - for index in persistent_committee: - if is_active_validator(state.validators[index], get_current_epoch(state)): - return index + active_indices = [i for i in persistent_committee if is_active_validator(state.validators[i], current_epoch)] + if not any(active_indices): + return None - # No block can be proposed if no validator is active - return None + 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)) + 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 ``` ### `get_shard_header` @@ -215,35 +243,49 @@ def get_shard_proposer_index(state: BeaconState, ```python def get_shard_header(block: ShardBlock) -> ShardBlockHeader: return ShardBlockHeader( - slot=block.slot, - shard=block.shard, - beacon_chain_root=block.beacon_chain_root, - parent_root=block.parent_root, - body_root=hash_tree_root(block.body), - state_root=block.state_root, - attestations=block.attestations, - signature=block.signature, + 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 ) ``` -### `verify_shard_attestation_signature` +### `pad` ```python -def verify_shard_attestation_signature(state: BeaconState, - attestation: ShardAttestation) -> None: - data = attestation.data - persistent_committee = get_persistent_committee(state, data.shard, data.slot) - pubkeys = [] - for i, index in enumerate(persistent_committee): - if attestation.aggregation_bits[i]: - validator = state.validators[index] - assert is_active_validator(validator, get_current_epoch(state)) - pubkeys.append(validator.pubkey) - assert bls_verify( - pubkey=bls_aggregate_pubkeys(pubkeys), - message_hash=data.shard_block_root, - signature=attestation.aggregate_signature, - domain=get_domain(state, DOMAIN_SHARD_ATTESTER, compute_epoch_of_slot(data.slot)) +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) ) ``` @@ -251,32 +293,10 @@ def verify_shard_attestation_signature(state: BeaconState, ```python def compute_crosslink_data_root(blocks: Sequence[ShardBlock]) -> Hash: - def is_power_of_two(value: uint64) -> bool: - return (value > 0) and (value & (value - 1) == 0) - - def pad_to_power_of_2(values: MutableSequence[bytes]) -> Sequence[bytes]: - while not is_power_of_two(len(values)): - values.append(b'\x00' * BYTES_PER_SHARD_BLOCK_BODY) - return values - - def hash_tree_root_of_bytes(data: bytes) -> Hash: - return hash_tree_root([data[i:i + 32] for i in range(0, len(data), 32)]) - - def zpad(data: bytes, length: uint64) -> bytes: - return data + b'\x00' * (length - len(data)) - - return hash( - # TODO untested code. - # Need to either pass a typed list to hash-tree-root, or merkleize_chunks(values, pad_to=2**x) - hash_tree_root(pad_to_power_of_2([ - hash_tree_root_of_bytes( - zpad(serialize(get_shard_header(block)), BYTES_PER_SHARD_BLOCK_BODY) - ) for block in blocks - ])) - + hash_tree_root(pad_to_power_of_2([ - hash_tree_root_of_bytes(block.body) for block in blocks - ])) - ) + 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))) ``` ## Object validity @@ -287,12 +307,14 @@ Let: - `beacon_blocks` be the `BeaconBlock` list such that `beacon_blocks[slot]` is the canonical `BeaconBlock` at slot `slot` - `beacon_state` be the canonical `BeaconState` after processing `beacon_blocks[-1]` +- `shard` is the shard ID - `valid_shard_blocks` be the list of valid `ShardBlock`, recursively defined - `candidate` be a candidate `ShardBlock` for which validity is to be determined by running `is_valid_shard_block` ```python -def is_valid_shard_block(beacon_blocks: Sequence[BeaconBlock], - beacon_state: BeaconState, +def is_valid_shard_block(beacon_state: BeaconState, + beacon_blocks: Sequence[BeaconBlock], + shard: Shard, valid_shard_blocks: Sequence[ShardBlock], candidate: ShardBlock) -> bool: # Check if block is already determined valid @@ -301,80 +323,56 @@ def is_valid_shard_block(beacon_blocks: Sequence[BeaconBlock], return True # Check slot number - assert candidate.slot >= PHASE_1_FORK_SLOT - - # Check shard number - assert candidate.shard <= SHARD_COUNT + assert compute_slot_of_shard_slot(candidate.core.slot) >= PHASE_1_FORK_SLOT # Check beacon block - beacon_block = beacon_blocks[candidate.slot] - assert candidate.beacon_block_root == signing_root(beacon_block) - assert beacon_block.slot <= candidate.slot + beacon_block_slot = compute_start_slot_of_epoch(compute_epoch_of_shard_slot(candidate.core.slot)) + beacon_block = beacon_blocks[beacon_block_slot] + assert candidate.core.beacon_block_root == signing_root(beacon_block) + assert beacon_block.slot <= candidate.core.slot # Check state root - assert candidate.state_root == Hash() # [to be removed in phase 2] + assert candidate.core.state_root == Hash() # [to be removed in phase 2] # Check parent block - if candidate.slot == PHASE_1_FORK_SLOT: - assert candidate.parent_root == Hash() - else: + if candidate.core.parent_root != Hash(): parent_block = next( - (block for block in valid_shard_blocks if signing_root(block) == candidate.parent_root), + (block for block in valid_shard_blocks if hash_tree_root(block.core) == candidate.core.parent_root), None ) assert parent_block is not None - assert parent_block.shard == candidate.shard - assert parent_block.slot < candidate.slot - assert signing_root(beacon_blocks[parent_block.slot]) == parent_block.beacon_chain_root + assert parent_block.core.slot < candidate.core.slot + parent_beacon_block_slot = compute_start_slot_of_epoch(compute_epoch_of_shard_slot(parent_block.core.slot)) + assert signing_root(beacon_blocks[parent_beacon_block_slot]) == parent_block.core.beacon_chain_root # Check attestations - assert len(candidate.attestations) <= MAX_SHARD_ATTESTIONS - for _, attestation in enumerate(candidate.attestations): - assert max(GENESIS_SHARD_SLOT, candidate.slot - SLOTS_PER_EPOCH) <= attestation.data.slot - assert attestation.data.slot <= candidate.slot - MIN_ATTESTATION_INCLUSION_DELAY - assert attestation.data.crosslink.shard == candidate.shard - verify_shard_attestation_signature(beacon_state, attestation) + attester_committee = get_persistent_committee(beacon_state, shard, block.core.slot) + pubkeys = [] + for i, index in enumerate(attester_committee): + if block.core.attester_bitfield[i]: + pubkeys.append(beacon_state.validators[index].pubkey) + for i in range(len(attester_committee), MAX_PERSISTENT_COMMITTEE_SIZE * 2): + assert block.attester_bitfield[i] is False + assert bls_verify( + pubkey=bls_aggregate_pubkeys(pubkeys), + message_hash=candidate.core.parent_root, + signature=candidate.signatures.attestation_signature, + domain=get_domain(beacon_state, DOMAIN_SHARD_ATTESTER, compute_epoch_of_shard_slot(candidate.core.slot)) + ) - # Check signature - proposer_index = get_shard_proposer_index(beacon_state, candidate.shard, candidate.slot) + # Check proposer + proposer_index = get_shard_block_proposer_index(beacon_state, shard, candidate.core.slot) assert proposer_index is not None assert bls_verify( pubkey=beacon_state.validators[proposer_index].pubkey, - message_hash=signing_root(candidate), - signature=candidate.signature, - domain=get_domain(beacon_state, DOMAIN_SHARD_PROPOSER, compute_epoch_of_slot(candidate.slot)), + message_hash=hash_tree_root(candidate.core), + signature=candidate.signatures.proposer_signature, + domain=get_domain(beacon_state, DOMAIN_SHARD_PROPOSER, compute_epoch_of_shard_slot(candidate.core.slot)), ) return True ``` -### Shard attestations - -Let: - -- `valid_shard_blocks` be the list of valid `ShardBlock` -- `beacon_state` be the canonical `BeaconState` -- `candidate` be a candidate `ShardAttestation` for which validity is to be determined by running `is_valid_shard_attestation` - -```python -def is_valid_shard_attestation(valid_shard_blocks: Sequence[ShardBlock], - beacon_state: BeaconState, - candidate: ShardAttestation) -> bool: - # Check shard block - shard_block = next( - (block for block in valid_shard_blocks if signing_root(block) == candidate.data.shard_block_root), - None, - ) - assert shard_block is not None - assert shard_block.slot == candidate.data.slot - assert shard_block.shard == candidate.data.shard - - # Check signature - verify_shard_attestation_signature(beacon_state, candidate) - - return True -``` - ### Beacon attestations Let: diff --git a/test_libs/pyspec/eth2spec/test/helpers/phase1/__init__.py b/test_libs/pyspec/eth2spec/test/helpers/phase1/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test_libs/pyspec/eth2spec/test/helpers/phase1/shard_block.py b/test_libs/pyspec/eth2spec/test/helpers/phase1/shard_block.py new file mode 100644 index 000000000..4e1981727 --- /dev/null +++ b/test_libs/pyspec/eth2spec/test/helpers/phase1/shard_block.py @@ -0,0 +1,47 @@ +from eth2spec.test.helpers.keys import privkeys +from eth2spec.utils.bls import ( + bls_sign, + only_with_bls, +) +from eth2spec.utils.ssz.ssz_impl import ( + signing_root, +) + + +@only_with_bls() +def sign_shard_block(spec, state, block, shard, proposer_index=None): + if proposer_index is None: + proposer_index = spec.get_shard_block_proposer_index(state, shard, block.core.slot) + + privkey = privkeys[proposer_index] + + block.signatures.proposer_signature = bls_sign( + message_hash=signing_root(block), + privkey=privkey, + domain=spec.get_domain( + state, + spec.DOMAIN_SHARD_PROPOSER, + spec.compute_epoch_of_shard_slot(block.core.slot), + ) + ) + + +def build_empty_shard_block(spec, state, slot, shard, parent_root, signed=False): + if slot is None: + slot = state.slot + block = spec.ShardBlock( + core=spec.ExtendedShardBlockCore( + slot=slot, + beacon_chain_root=state.block_roots[state.slot % spec.SLOTS_PER_HISTORICAL_ROOT], + parent_root=parent_root, + ), + signatures=spec.ShardBlockSignatures( + attestation_signature=b'\x12' * 96, + proposer_signature=b'\x25' * 96, + ) + ) + + if signed: + sign_shard_block(spec, state, block, shard) + + return block 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 new file mode 100644 index 000000000..359350d39 --- /dev/null +++ b/test_libs/pyspec/eth2spec/test/phase_1/shard_data_chain/test_shard_block.py @@ -0,0 +1,26 @@ +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_is_valid_shard_block(spec, state): + block = build_empty_shard_block( + spec, + state, + slot=spec.Slot(spec.PERSISTENT_COMMITTEE_PERIOD * 100), + shard=spec.Shard(1), + parent_root=spec.Hash(), + signed=True, + ) + + # TODO: test `is_valid_shard_block` + + yield 'blocks', (block,)