From 56309342c05d324e359591b902abc355fba82fb1 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Thu, 21 May 2020 15:33:47 +0200 Subject: [PATCH 01/31] Use `Bytes32` for `error_message` `ErrorMessage.error_message` is a leftover from an older version of SSZ that was able to encode unbounded lists. This is no longer the case - all collection types now have a fixed upper bound on length. In general, the `error_message`, just like the `graffitti` field, should not be interpreted in any particular way except for debugging and vanity - as such, using the same type, a `Bytes32`, seems reasonable. An alternative would be `List[byte, 256]` which maybe could be "reasonably backwards compatible" with whatever clients are are doing now - depending on how they are dealing with this field type that no longer exists in the SSZ spec :) It would however be the only place where `List[uintN, N]` is used in the current spec. As an exercise, this could be considered a security issue since it's essentially unbounded and undefined behaviour. --- specs/phase0/p2p-interface.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 118be8839..84db361c9 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -391,11 +391,11 @@ The `ErrorMessage` schema is: ``` ( - error_message: String + error_message: Bytes32 ) ``` -*Note*: The String type is encoded as UTF-8 bytes without NULL terminator when SSZ-encoded. As the `ErrorMessage` is not an SSZ-container, only the UTF-8 bytes will be sent when SSZ-encoded. +*Note*: By convention, the `error_message` is a sequence of bytes that can be interpreted as a UTF-8 string up to 32 bytes - a 0 byte shortens the string in this interpretation. Clients MUST treat as valid any bytes. ### Encoding strategies From ca489630325aebffca2a425fe1a5b69573140e6e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 28 May 2020 21:38:11 +0800 Subject: [PATCH 02/31] Rename `head_shard_root` to `shard_head_root` --- specs/phase1/beacon-chain.md | 4 ++-- tests/core/pyspec/eth2spec/test/helpers/attestations.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 0ae4bae2b..bc5a6385c 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -130,7 +130,7 @@ class AttestationData(Container): source: Checkpoint target: Checkpoint # Current-slot shard block root - head_shard_root: Root + shard_head_root: Root # Shard transition root shard_transition_root: Root ``` @@ -823,7 +823,7 @@ def process_crosslink_for_shard(state: BeaconState, for attestation in transition_attestations: participants = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) transition_participants = transition_participants.union(participants) - assert attestation.data.head_shard_root == shard_transition.shard_data_roots[ + assert attestation.data.shard_head_root == shard_transition.shard_data_roots[ len(shard_transition.shard_data_roots) - 1 ] diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 4d246c8c6..c533182ef 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -78,7 +78,7 @@ def build_attestation_data(spec, state, slot, index, shard_transition=None, on_t if spec.fork == PHASE1: if shard_transition is not None: lastest_shard_data_root_index = len(shard_transition.shard_data_roots) - 1 - attestation_data.head_shard_root = shard_transition.shard_data_roots[lastest_shard_data_root_index] + attestation_data.shard_head_root = shard_transition.shard_data_roots[lastest_shard_data_root_index] attestation_data.shard_transition_root = shard_transition.hash_tree_root() else: # No shard transition @@ -88,10 +88,10 @@ def build_attestation_data(spec, state, slot, index, shard_transition=None, on_t next_slot(spec, temp_state) shard_transition = spec.get_shard_transition(temp_state, shard, []) lastest_shard_data_root_index = len(shard_transition.shard_data_roots) - 1 - attestation_data.head_shard_root = shard_transition.shard_data_roots[lastest_shard_data_root_index] + attestation_data.shard_head_root = shard_transition.shard_data_roots[lastest_shard_data_root_index] attestation_data.shard_transition_root = shard_transition.hash_tree_root() else: - attestation_data.head_shard_root = state.shard_states[shard].transition_digest + attestation_data.shard_head_root = state.shard_states[shard].transition_digest attestation_data.shard_transition_root = spec.Root() return attestation_data From 8c9bbc48d8720ed621ba54ab7ae12696ca5279e8 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 28 May 2020 21:49:36 +0800 Subject: [PATCH 03/31] Rework `is_shard_attestation` Change it to `is_on_time_attestation` so that it could be reused in `validate_attestation`. --- specs/phase1/beacon-chain.md | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index bc5a6385c..4f4713f70 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -53,7 +53,7 @@ - [`get_offset_slots`](#get_offset_slots) - [Predicates](#predicates) - [Updated `is_valid_indexed_attestation`](#updated-is_valid_indexed_attestation) - - [`is_shard_attestation`](#is_shard_attestation) + - [`is_on_time_attestation`](#is_on_time_attestation) - [`is_winning_attestation`](#is_winning_attestation) - [`optional_aggregate_verify`](#optional_aggregate_verify) - [`optional_fast_aggregate_verify`](#optional_fast_aggregate_verify) @@ -602,20 +602,16 @@ def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: Indexe return bls.AggregateVerify(all_pubkeys, all_signing_roots, signature=attestation.signature) ``` -#### `is_shard_attestation` +#### `is_on_time_attestation` ```python -def is_shard_attestation(state: BeaconState, - attestation: Attestation, - committee_index: CommitteeIndex) -> bool: - if not ( - attestation.data.index == committee_index - and attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY == state.slot # Must be on-time attestation - # TODO: MIN_ATTESTATION_INCLUSION_DELAY should always be 1 - ): - return False - - return True +def is_on_time_attestation(state: BeaconState, + attestation: Attestation) -> bool: + """ + Check if the given attestation is on-time. + """ + # TODO: MIN_ATTESTATION_INCLUSION_DELAY should always be 1 + return attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY == state.slot ``` #### `is_winning_attestation` @@ -730,7 +726,7 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None: # Type 1: on-time attestations, the custody bits should be non-empty. if attestation.custody_bits_blocks != []: # Ensure on-time attestation - assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY == state.slot + assert is_on_time_attestation(state, attestation) # Correct data root count assert len(attestation.custody_bits_blocks) == len(get_offset_slots(state, shard)) # Correct parent block root @@ -875,7 +871,7 @@ def process_crosslinks(state: BeaconState, shard_transition = shard_transitions[shard] shard_attestations = [ attestation for attestation in attestations - if is_shard_attestation(state, attestation, committee_index) + if is_on_time_attestation(state, attestation) and attestation.data.index == committee_index ] winning_root = process_crosslink_for_shard(state, committee_index, shard_transition, shard_attestations) From 19262888e4997659f1a6c015562284213526d0fa Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 28 May 2020 21:55:49 +0800 Subject: [PATCH 04/31] Rename `verify_shard_transition_false_positives` to `verify_empty_shard_transition` --- specs/phase1/beacon-chain.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 4f4713f70..e536ad6ef 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -66,7 +66,7 @@ - [`process_crosslinks`](#process_crosslinks) - [`process_attestation`](#process_attestation) - [New Attester slashing processing](#new-attester-slashing-processing) - - [Shard transition false positives](#shard-transition-false-positives) + - [Verify empty shard transition](#verify-empty-shard-transition) - [Light client processing](#light-client-processing) - [Epoch transition](#epoch-transition) - [Custody game updates](#custody-game-updates) @@ -671,7 +671,7 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: process_eth1_data(state, block.body) process_light_client_signatures(state, block.body) process_operations(state, block.body) - verify_shard_transition_false_positives(state, block.body) + verify_empty_shard_transition(state, block.body) ``` #### Operations @@ -943,11 +943,13 @@ def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSla assert slashed_any ``` -#### Shard transition false positives +#### Verify empty shard transition ```python -def verify_shard_transition_false_positives(state: BeaconState, block_body: BeaconBlockBody) -> None: - # Verify that a `shard_transition` in a block is empty if an attestation was not processed for it +def verify_empty_shard_transition(state: BeaconState, block_body: BeaconBlockBody) -> None: + """ + Verify that a `shard_transition` in a block is empty if an attestation was not processed for it. + """ for shard in range(get_active_shard_count(state)): if state.shard_states[shard].slot != compute_previous_slot(state.slot): assert block_body.shard_transitions[shard] == ShardTransition() From 7509ecb742173e0df6768d18fdc4d07686ae5184 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 28 May 2020 22:39:52 +0800 Subject: [PATCH 05/31] Add comments, minor refactoring --- specs/phase1/beacon-chain.md | 75 ++++++++++++++++++-------------- specs/phase1/shard-transition.md | 6 +-- 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 0ae4bae2b..89dd6aaac 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -64,7 +64,7 @@ - [`apply_shard_transition`](#apply_shard_transition) - [`process_crosslink_for_shard`](#process_crosslink_for_shard) - [`process_crosslinks`](#process_crosslinks) - - [`process_attestation`](#process_attestation) + - [Updated `process_attestation`](#updated-process_attestation) - [New Attester slashing processing](#new-attester-slashing-processing) - [Shard transition false positives](#shard-transition-false-positives) - [Light client processing](#light-client-processing) @@ -153,6 +153,7 @@ class PendingAttestation(Container): data: AttestationData inclusion_delay: Slot proposer_index: ValidatorIndex + # Phase 1 crosslink_success: boolean ``` @@ -445,13 +446,13 @@ def compute_offset_slots(start_slot: Slot, end_slot: Slot) -> Sequence[Slot]: #### `compute_updated_gasprice` ```python -def compute_updated_gasprice(prev_gasprice: Gwei, length: uint8) -> Gwei: - if length > TARGET_SHARD_BLOCK_SIZE: - delta = (prev_gasprice * (length - TARGET_SHARD_BLOCK_SIZE) +def compute_updated_gasprice(prev_gasprice: Gwei, shard_block_length: uint8) -> Gwei: + if shard_block_length > TARGET_SHARD_BLOCK_SIZE: + delta = (prev_gasprice * (shard_block_length - TARGET_SHARD_BLOCK_SIZE) // TARGET_SHARD_BLOCK_SIZE // GASPRICE_ADJUSTMENT_COEFFICIENT) return min(prev_gasprice + delta, MAX_GASPRICE) else: - delta = (prev_gasprice * (TARGET_SHARD_BLOCK_SIZE - length) + delta = (prev_gasprice * (TARGET_SHARD_BLOCK_SIZE - shard_block_length) // TARGET_SHARD_BLOCK_SIZE // GASPRICE_ADJUSTMENT_COEFFICIENT) return max(prev_gasprice, MIN_GASPRICE + delta) - delta ``` @@ -477,9 +478,12 @@ def get_online_validator_indices(state: BeaconState) -> Set[ValidatorIndex]: ```python def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) -> Sequence[ValidatorIndex]: + """ + Return the shard committee of the given ``epoch`` of the given ``shard``. + """ source_epoch = epoch - epoch % SHARD_COMMITTEE_PERIOD if source_epoch >= SHARD_COMMITTEE_PERIOD: - source_epoch -= SHARD_COMMITTEE_PERIOD + source_epoch -= SHARD_COMMITTEE_PERIOD # `SHARD_COMMITTEE_PERIOD` epochs lookahead active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) seed = get_seed(beacon_state, source_epoch, DOMAIN_SHARD_COMMITTEE) active_shard_count = get_active_shard_count(beacon_state) @@ -495,9 +499,12 @@ def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) - ```python def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: + """ + Return the light client committee that no more than ``TARGET_COMMITTEE_SIZE`` validators. + """ source_epoch = epoch - epoch % LIGHT_CLIENT_COMMITTEE_PERIOD if source_epoch >= LIGHT_CLIENT_COMMITTEE_PERIOD: - source_epoch -= LIGHT_CLIENT_COMMITTEE_PERIOD + source_epoch -= LIGHT_CLIENT_COMMITTEE_PERIOD # `LIGHT_CLIENT_COMMITTEE_PERIOD` epochs lookahead active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) seed = get_seed(beacon_state, source_epoch, DOMAIN_LIGHT_CLIENT) return compute_committee( @@ -554,7 +561,10 @@ def get_latest_slot_for_shard(state: BeaconState, shard: Shard) -> Slot: ```python def get_offset_slots(state: BeaconState, shard: Shard) -> Sequence[Slot]: - return compute_offset_slots(state.shard_states[shard].slot, state.slot) + """ + Return the offset slots of the given ``shard`` between that latest included slot and current slot. + """ + return compute_offset_slots(get_latest_slot_for_shard(state, shard), state.slot) ``` ### Predicates @@ -569,36 +579,38 @@ def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: Indexe Check if ``indexed_attestation`` has valid indices and signature. """ # Verify aggregate signature - all_pubkeys = [] - all_signing_roots = [] attestation = indexed_attestation.attestation - domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch) aggregation_bits = attestation.aggregation_bits if not any(aggregation_bits) or len(aggregation_bits) != len(indexed_attestation.committee): return False + domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch) + all_pubkeys = [] + all_signing_roots = [] if len(attestation.custody_bits_blocks) == 0: # fall back on phase0 behavior if there is no shard data. - for participant, abit in zip(indexed_attestation.committee, aggregation_bits): - if abit: + for participant, aggregation_bit in zip(indexed_attestation.committee, aggregation_bits): + if aggregation_bit: all_pubkeys.append(state.validators[participant].pubkey) signing_root = compute_signing_root(indexed_attestation.attestation.data, domain) return bls.FastAggregateVerify(all_pubkeys, signing_root, signature=attestation.signature) else: - for i, custody_bits in enumerate(attestation.custody_bits_blocks): + for block_index, custody_bits in enumerate(attestation.custody_bits_blocks): assert len(custody_bits) == len(indexed_attestation.committee) - for participant, abit, cbit in zip(indexed_attestation.committee, aggregation_bits, custody_bits): - if abit: + for participant, aggregation_bit, custody_bit in zip( + indexed_attestation.committee, aggregation_bits, custody_bits + ): + if aggregation_bit: all_pubkeys.append(state.validators[participant].pubkey) # Note: only 2N distinct message hashes attestation_wrapper = AttestationCustodyBitWrapper( attestation_data_root=hash_tree_root(attestation.data), - block_index=i, - bit=cbit + block_index=block_index, + bit=custody_bit ) all_signing_roots.append(compute_signing_root(attestation_wrapper, domain)) else: - assert not cbit + assert not custody_bit return bls.AggregateVerify(all_pubkeys, all_signing_roots, signature=attestation.signature) ``` @@ -767,22 +779,22 @@ def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTr proposers = [] prev_gasprice = state.shard_states[shard].gasprice shard_parent_root = state.shard_states[shard].latest_block_root - for i in range(len(offset_slots)): + for i, offset_slot in enumerate(offset_slots): shard_block_length = transition.shard_block_lengths[i] shard_state = transition.shard_states[i] # Verify correct calculation of gas prices and slots assert shard_state.gasprice == compute_updated_gasprice(prev_gasprice, shard_block_length) - assert shard_state.slot == offset_slots[i] + assert shard_state.slot == offset_slot # Collect the non-empty proposals result is_empty_proposal = shard_block_length == 0 if not is_empty_proposal: - proposal_index = get_shard_proposer_index(state, offset_slots[i], shard) + proposal_index = get_shard_proposer_index(state, offset_slot, shard) # Reconstruct shard headers header = ShardBlockHeader( shard_parent_root=shard_parent_root, - beacon_parent_root=get_block_root_at_slot(state, offset_slots[i]), + beacon_parent_root=get_block_root_at_slot(state, offset_slot), proposer_index=proposal_index, - slot=offset_slots[i], + slot=offset_slot, body_root=transition.shard_data_roots[i] ) shard_parent_root = hash_tree_root(header) @@ -872,13 +884,12 @@ def process_crosslinks(state: BeaconState, for committee_index in map(CommitteeIndex, range(committee_count)): shard = compute_shard_from_committee_index(state, committee_index, state.slot) # All attestations in the block for this committee/shard and current slot - shard_transition = shard_transitions[shard] shard_attestations = [ attestation for attestation in attestations if is_shard_attestation(state, attestation, committee_index) ] - winning_root = process_crosslink_for_shard(state, committee_index, shard_transition, shard_attestations) + winning_root = process_crosslink_for_shard(state, committee_index, shard_transitions[shard], shard_attestations) if winning_root != Root(): # Mark relevant pending attestations as creating a successful crosslink for pending_attestation in state.current_epoch_attestations: @@ -886,7 +897,7 @@ def process_crosslinks(state: BeaconState, pending_attestation.crosslink_success = True ``` -###### `process_attestation` +###### Updated `process_attestation` ```python def process_attestation(state: BeaconState, attestation: Attestation) -> None: @@ -910,11 +921,9 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: ```python def get_indices_from_committee( committee: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE], - bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE]: + bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> Sequence[ValidatorIndex]: assert len(bits) == len(committee) - return List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE]( - [validator_index for i, validator_index in enumerate(committee) if bits[i]] - ) + return ([validator_index for i, validator_index in enumerate(committee) if bits[i]]) ``` ```python @@ -1018,7 +1027,9 @@ def process_online_tracking(state: BeaconState) -> None: ```python def process_light_client_committee_updates(state: BeaconState) -> None: - # Update light client committees + """ + Update light client committees. + """ if get_current_epoch(state) % LIGHT_CLIENT_COMMITTEE_PERIOD == 0: state.current_light_committee = state.next_light_committee new_committee = get_light_client_committee(state, get_current_epoch(state) + LIGHT_CLIENT_COMMITTEE_PERIOD) diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index da3aa648c..1a8277066 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -268,12 +268,8 @@ def get_shard_transition(beacon_state: BeaconState, shard: Shard, shard_blocks: Sequence[SignedShardBlock]) -> ShardTransition: offset_slots = get_offset_slots(beacon_state, shard) - start_slot = offset_slots[0] proposals, shard_states, shard_data_roots = get_shard_state_transition_result(beacon_state, shard, shard_blocks) - assert len(proposals) > 0 - assert len(shard_data_roots) > 0 - shard_block_lengths = [] proposer_signatures = [] for proposal in proposals: @@ -287,7 +283,7 @@ def get_shard_transition(beacon_state: BeaconState, proposer_signature_aggregate = NO_SIGNATURE return ShardTransition( - start_slot=start_slot, + start_slot=offset_slots[0], shard_block_lengths=shard_block_lengths, shard_data_roots=shard_data_roots, shard_states=shard_states, From 8ae7f5b6fa46c6423798ba56dee3c7192db8b232 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 29 May 2020 01:24:17 +0800 Subject: [PATCH 06/31] Refactor `is_valid_indexed_attestation`: extract `verify_attestation_custody` --- specs/phase1/beacon-chain.md | 57 +++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 89dd6aaac..ee282f203 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -52,6 +52,7 @@ - [`get_latest_slot_for_shard`](#get_latest_slot_for_shard) - [`get_offset_slots`](#get_offset_slots) - [Predicates](#predicates) + - [`verify_attestation_custody`](#verify_attestation_custody) - [Updated `is_valid_indexed_attestation`](#updated-is_valid_indexed_attestation) - [`is_shard_attestation`](#is_shard_attestation) - [`is_winning_attestation`](#is_winning_attestation) @@ -414,7 +415,7 @@ def unpack_compact_validator(compact_validator: uint64) -> Tuple[ValidatorIndex, ```python def committee_to_compact_committee(state: BeaconState, committee: Sequence[ValidatorIndex]) -> CompactCommittee: """ - Given a state and a list of validator indices, outputs the CompactCommittee representing them. + Given a state and a list of validator indices, outputs the ``CompactCommittee`` representing them. """ validators = [state.validators[i] for i in committee] compact_validators = [ @@ -569,6 +570,37 @@ def get_offset_slots(state: BeaconState, shard: Shard) -> Sequence[Slot]: ### Predicates +#### `verify_attestation_custody` + +```python +def verify_attestation_custody(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool: + """ + Check if ``indexed_attestation`` has valid signature against non-empty custody bits. + """ + attestation = indexed_attestation.attestation + aggregation_bits = attestation.aggregation_bits + domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch) + all_pubkeys = [] + all_signing_roots = [] + for block_index, custody_bits in enumerate(attestation.custody_bits_blocks): + assert len(custody_bits) == len(indexed_attestation.committee) + for participant, aggregation_bit, custody_bit in zip( + indexed_attestation.committee, aggregation_bits, custody_bits + ): + if aggregation_bit: + all_pubkeys.append(state.validators[participant].pubkey) + # Note: only 2N distinct message hashes + attestation_wrapper = AttestationCustodyBitWrapper( + attestation_data_root=hash_tree_root(attestation.data), + block_index=block_index, + bit=custody_bit, + ) + all_signing_roots.append(compute_signing_root(attestation_wrapper, domain)) + else: + assert not custody_bit + return bls.AggregateVerify(all_pubkeys, all_signing_roots, signature=attestation.signature) +``` + #### Updated `is_valid_indexed_attestation` Note that this replaces the Phase 0 `is_valid_indexed_attestation`. @@ -584,34 +616,17 @@ def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: Indexe if not any(aggregation_bits) or len(aggregation_bits) != len(indexed_attestation.committee): return False - domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch) - all_pubkeys = [] - all_signing_roots = [] if len(attestation.custody_bits_blocks) == 0: # fall back on phase0 behavior if there is no shard data. + domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.data.target.epoch) + all_pubkeys = [] for participant, aggregation_bit in zip(indexed_attestation.committee, aggregation_bits): if aggregation_bit: all_pubkeys.append(state.validators[participant].pubkey) signing_root = compute_signing_root(indexed_attestation.attestation.data, domain) return bls.FastAggregateVerify(all_pubkeys, signing_root, signature=attestation.signature) else: - for block_index, custody_bits in enumerate(attestation.custody_bits_blocks): - assert len(custody_bits) == len(indexed_attestation.committee) - for participant, aggregation_bit, custody_bit in zip( - indexed_attestation.committee, aggregation_bits, custody_bits - ): - if aggregation_bit: - all_pubkeys.append(state.validators[participant].pubkey) - # Note: only 2N distinct message hashes - attestation_wrapper = AttestationCustodyBitWrapper( - attestation_data_root=hash_tree_root(attestation.data), - block_index=block_index, - bit=custody_bit - ) - all_signing_roots.append(compute_signing_root(attestation_wrapper, domain)) - else: - assert not custody_bit - return bls.AggregateVerify(all_pubkeys, all_signing_roots, signature=attestation.signature) + return verify_attestation_custody(state, indexed_attestation) ``` #### `is_shard_attestation` From bd9f983eeae34b11ebb84df2d000ab7f22ab3d34 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 29 May 2020 02:20:38 +0800 Subject: [PATCH 07/31] Minor fix --- specs/phase1/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index ee282f203..ce47ef585 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -938,7 +938,7 @@ def get_indices_from_committee( committee: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE], bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> Sequence[ValidatorIndex]: assert len(bits) == len(committee) - return ([validator_index for i, validator_index in enumerate(committee) if bits[i]]) + return [validator_index for i, validator_index in enumerate(committee) if bits[i]] ``` ```python From b16e6d7a8623d6d90c90ea0fb64cc29a993952b5 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 29 May 2020 12:58:19 +0800 Subject: [PATCH 08/31] PR feedback from Danny Co-authored-by: Danny Ryan --- specs/phase1/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index e536ad6ef..f087247f4 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -948,7 +948,7 @@ def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSla ```python def verify_empty_shard_transition(state: BeaconState, block_body: BeaconBlockBody) -> None: """ - Verify that a `shard_transition` in a block is empty if an attestation was not processed for it. + Verify that ``shard_transitions`` are empty if a crosslink was not formed for the associated shard in this slot. """ for shard in range(get_active_shard_count(state)): if state.shard_states[shard].slot != compute_previous_slot(state.slot): From 2dc041807af913f16e54cf44a46fef07f80dbe2e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 29 May 2020 23:50:18 +0800 Subject: [PATCH 09/31] Implement `get_start_shard` --- setup.py | 15 ++++- specs/phase1/beacon-chain.md | 61 ++++++++++++++++++- specs/phase1/phase1-fork.md | 1 + .../test_process_crosslink.py | 2 +- .../test/phase_1/sanity/test_blocks.py | 4 +- 5 files changed, 74 insertions(+), 9 deletions(-) diff --git a/setup.py b/setup.py index 4f3f2b872..d0f063b33 100644 --- a/setup.py +++ b/setup.py @@ -140,7 +140,7 @@ SUNDRY_CONSTANTS_FUNCTIONS = ''' def ceillog2(x: uint64) -> int: return (x - 1).bit_length() ''' -SUNDRY_FUNCTIONS = ''' +PHASE0_SUNDRY_FUNCTIONS = ''' # Monkey patch hash cache _hash = hash hash_cache: Dict[bytes, Bytes32] = {} @@ -220,6 +220,13 @@ get_attesting_indices = cache_this( _get_attesting_indices, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3)''' +PHASE1_SUNDRY_FUNCTIONS = ''' +_get_start_shard = get_start_shard +get_start_shard = cache_this( + lambda state, slot: (state.validators.hash_tree_root(), slot), + _get_start_shard, lru_size=SLOTS_PER_EPOCH * 3)''' + + def objects_to_spec(spec_object: SpecObject, imports: str, fork: str) -> str: """ Given all the objects that constitute a spec, combine them into a single pyfile. @@ -250,9 +257,11 @@ def objects_to_spec(spec_object: SpecObject, imports: str, fork: str) -> str: + '\n\n' + CONFIG_LOADER + '\n\n' + ssz_objects_instantiation_spec + '\n\n' + functions_spec - + '\n' + SUNDRY_FUNCTIONS - + '\n' + + '\n' + PHASE0_SUNDRY_FUNCTIONS ) + if fork == 'phase1': + spec += '\n' + PHASE1_SUNDRY_FUNCTIONS + spec += '\n' return spec diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 39a73c0aa..124442b59 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -47,6 +47,7 @@ - [`get_light_client_committee`](#get_light_client_committee) - [`get_shard_proposer_index`](#get_shard_proposer_index) - [`get_indexed_attestation`](#get_indexed_attestation) + - [`get_committee_count_delta`](#get_committee_count_delta) - [`get_start_shard`](#get_start_shard) - [`get_shard`](#get_shard) - [`get_latest_slot_for_shard`](#get_latest_slot_for_shard) @@ -69,6 +70,7 @@ - [Shard transition false positives](#shard-transition-false-positives) - [Light client processing](#light-client-processing) - [Epoch transition](#epoch-transition) + - [Phase 1 final updates](#phase-1-final-updates) - [Custody game updates](#custody-game-updates) - [Online-tracking](#online-tracking) - [Light client committee updates](#light-client-committee-updates) @@ -280,6 +282,7 @@ class BeaconState(Container): current_justified_checkpoint: Checkpoint finalized_checkpoint: Checkpoint # Phase 1 + current_epoch_start_shard: Shard shard_states: List[ShardState, MAX_SHARDS] online_countdown: List[OnlineEpochs, VALIDATOR_REGISTRY_LIMIT] # not a raw byte array, considered its large size. current_light_committee: CompactCommittee @@ -530,18 +533,53 @@ def get_indexed_attestation(beacon_state: BeaconState, attestation: Attestation) ) ``` +#### `get_committee_count_delta` + +```python +def get_committee_count_delta(state: BeaconState, start_slot: Slot, stop_slot: Slot) -> uint64: + """ + Return the sum of committee counts between ``[start_slot, stop_slot)``. + """ + committee_sum = 0 + for slot in range(start_slot, stop_slot): + count = get_committee_count_at_slot(state, Slot(slot)) + committee_sum += count + return committee_sum +``` + #### `get_start_shard` ```python def get_start_shard(state: BeaconState, slot: Slot) -> Shard: - # TODO: implement start shard logic - return Shard(0) + """ + Return the start shard at ``slot``. + """ + current_epoch_start_slot = compute_start_slot_at_epoch(get_current_epoch(state)) + active_shard_count = get_active_shard_count(state) + if current_epoch_start_slot == slot: + return state.current_epoch_start_shard + elif current_epoch_start_slot > slot: + # Current epoch or the next epoch lookahead + shard_delta = get_committee_count_delta(state, start_slot=current_epoch_start_slot, stop_slot=slot) + return Shard((state.current_epoch_start_shard + shard_delta) % active_shard_count) + else: + # Previous epoch + shard_delta = get_committee_count_delta(state, start_slot=slot, stop_slot=current_epoch_start_slot) + max_committees_per_epoch = MAX_COMMITTEES_PER_SLOT * SLOTS_PER_EPOCH + return Shard( + # Ensure positive + (state.current_epoch_start_shard + max_committees_per_epoch * active_shard_count - shard_delta) + % active_shard_count + ) ``` #### `get_shard` ```python def get_shard(state: BeaconState, attestation: Attestation) -> Shard: + """ + Return the shard that the given attestation is attesting. + """ return compute_shard_from_committee_index(state, attestation.data.index, attestation.data.slot) ``` @@ -549,6 +587,9 @@ def get_shard(state: BeaconState, attestation: Attestation) -> Shard: ```python def get_latest_slot_for_shard(state: BeaconState, shard: Shard) -> Slot: + """ + Return the latest slot number of the given shard. + """ return state.shard_states[shard].slot ``` @@ -556,7 +597,11 @@ def get_latest_slot_for_shard(state: BeaconState, shard: Shard) -> Slot: ```python def get_offset_slots(state: BeaconState, shard: Shard) -> Sequence[Slot]: - return compute_offset_slots(state.shard_states[shard].slot, state.slot) + """ + Return the offset slots of the given shard. + The offset slot are after the latest slot and before current slot. + """ + return compute_offset_slots(get_latest_slot_for_shard(state, shard), state.slot) ``` ### Predicates @@ -993,9 +1038,19 @@ def process_epoch(state: BeaconState) -> None: process_reveal_deadlines(state) process_slashings(state) process_final_updates(state) + process_phase_1_final_updates(state) +``` + +#### Phase 1 final updates + +```python +def process_phase_1_final_updates(state: BeaconState) -> None: process_custody_final_updates(state) process_online_tracking(state) process_light_client_committee_updates(state) + + # Update current_epoch_start_shard + state.current_epoch_start_shard = get_start_shard(state, state.slot) ``` #### Custody game updates diff --git a/specs/phase1/phase1-fork.md b/specs/phase1/phase1-fork.md index cc7d8f33e..d362ed633 100644 --- a/specs/phase1/phase1-fork.md +++ b/specs/phase1/phase1-fork.md @@ -99,6 +99,7 @@ def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState: current_justified_checkpoint=pre.current_justified_checkpoint, finalized_checkpoint=pre.finalized_checkpoint, # Phase 1 + current_epoch_start_shard=Shard(0), shard_states=List[ShardState, MAX_SHARDS]( ShardState( slot=pre.slot, diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py index 1f066b344..af0ff1a90 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py @@ -18,7 +18,7 @@ def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True): # At the beginning, let `x = state.slot`, `state.shard_states[shard].slot == x - 1` slot_x = state.slot committee_index = spec.CommitteeIndex(0) - shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) + shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot) assert state.shard_states[shard].slot == slot_x - 1 # Create SignedShardBlock diff --git a/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py index 60af35d45..1499d34cd 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py @@ -67,7 +67,7 @@ def test_process_beacon_block_with_normal_shard_transition(spec, state): target_len_offset_slot = 1 committee_index = spec.CommitteeIndex(0) - shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) + shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot) assert state.shard_states[shard].slot == state.slot - 1 pre_gasprice = state.shard_states[shard].gasprice @@ -93,7 +93,7 @@ def test_process_beacon_block_with_empty_proposal_transition(spec, state): target_len_offset_slot = 1 committee_index = spec.CommitteeIndex(0) - shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) + shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot) assert state.shard_states[shard].slot == state.slot - 1 # No new shard block From f70224b84e0e2eebf7e89ff36fc218ea217d3569 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Sat, 30 May 2020 03:09:42 +0800 Subject: [PATCH 10/31] Extract `compute_committee_source_epoch` --- specs/phase1/beacon-chain.md | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index ce47ef585..054868847 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -40,6 +40,7 @@ - [`compute_shard_from_committee_index`](#compute_shard_from_committee_index) - [`compute_offset_slots`](#compute_offset_slots) - [`compute_updated_gasprice`](#compute_updated_gasprice) + - [`compute_committee_source_epoch`](#compute_committee_source_epoch) - [Beacon state accessors](#beacon-state-accessors) - [`get_active_shard_count`](#get_active_shard_count) - [`get_online_validator_indices`](#get_online_validator_indices) @@ -458,6 +459,19 @@ def compute_updated_gasprice(prev_gasprice: Gwei, shard_block_length: uint8) -> return max(prev_gasprice, MIN_GASPRICE + delta) - delta ``` +#### `compute_committee_source_epoch` + +```python +def compute_committee_source_epoch(epoch: Epoch, period: uint64) -> Epoch: + """ + Return the source epoch for computing the committee. + """ + source_epoch = epoch - epoch % period + if source_epoch >= period: + source_epoch -= period # `period` epochs lookahead + return source_epoch +``` + ### Beacon state accessors #### `get_active_shard_count` @@ -482,9 +496,7 @@ def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) - """ Return the shard committee of the given ``epoch`` of the given ``shard``. """ - source_epoch = epoch - epoch % SHARD_COMMITTEE_PERIOD - if source_epoch >= SHARD_COMMITTEE_PERIOD: - source_epoch -= SHARD_COMMITTEE_PERIOD # `SHARD_COMMITTEE_PERIOD` epochs lookahead + source_epoch = compute_committee_source_epoch(epoch, SHARD_COMMITTEE_PERIOD) active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) seed = get_seed(beacon_state, source_epoch, DOMAIN_SHARD_COMMITTEE) active_shard_count = get_active_shard_count(beacon_state) @@ -503,9 +515,7 @@ def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Seque """ Return the light client committee that no more than ``TARGET_COMMITTEE_SIZE`` validators. """ - source_epoch = epoch - epoch % LIGHT_CLIENT_COMMITTEE_PERIOD - if source_epoch >= LIGHT_CLIENT_COMMITTEE_PERIOD: - source_epoch -= LIGHT_CLIENT_COMMITTEE_PERIOD # `LIGHT_CLIENT_COMMITTEE_PERIOD` epochs lookahead + source_epoch = compute_committee_source_epoch(epoch, LIGHT_CLIENT_COMMITTEE_PERIOD) active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) seed = get_seed(beacon_state, source_epoch, DOMAIN_LIGHT_CLIENT) return compute_committee( From cceeab265769cb9a39362fe81de42436b74e3f90 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 29 May 2020 03:14:43 +0800 Subject: [PATCH 11/31] Combine `process_crosslinks` and `verify_empty_shard_transition` into `process_shard_transitions` --- specs/phase1/beacon-chain.md | 24 +++++++++++++++---- .../{crosslinks.py => shard_transitions.py} | 8 +++---- ...nk.py => test_process_shard_transition.py} | 4 ++-- tests/generators/README.md | 2 +- 4 files changed, 26 insertions(+), 12 deletions(-) rename tests/core/pyspec/eth2spec/test/helpers/{crosslinks.py => shard_transitions.py} (65%) rename tests/core/pyspec/eth2spec/test/phase_1/block_processing/{test_process_crosslink.py => test_process_shard_transition.py} (93%) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index f087247f4..853160292 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -64,6 +64,7 @@ - [`apply_shard_transition`](#apply_shard_transition) - [`process_crosslink_for_shard`](#process_crosslink_for_shard) - [`process_crosslinks`](#process_crosslinks) + - [`process_shard_transitions`](#process_shard_transitions) - [`process_attestation`](#process_attestation) - [New Attester slashing processing](#new-attester-slashing-processing) - [Verify empty shard transition](#verify-empty-shard-transition) @@ -671,7 +672,6 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: process_eth1_data(state, block.body) process_light_client_signatures(state, block.body) process_operations(state, block.body) - verify_empty_shard_transition(state, block.body) ``` #### Operations @@ -695,7 +695,7 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: # See custody game spec. process_custody_game_operations(state, body) - process_crosslinks(state, body.shard_transitions, body.attestations) + process_shard_transitions(state, body.shard_transitions, body.attestations) # TODO process_operations(body.shard_receipt_proofs, process_shard_receipt_proofs) ``` @@ -882,6 +882,18 @@ def process_crosslinks(state: BeaconState, pending_attestation.crosslink_success = True ``` +###### `process_shard_transitions` + +```python +def process_shard_transitions(state: BeaconState, + shard_transitions: Sequence[ShardTransition], + attestations: Sequence[Attestation]) -> None: + # Process crosslinks + process_crosslinks(state, shard_transitions, attestations) + # Verify the empty proposal shard states + assert verify_empty_shard_transition(state, shard_transitions) +``` + ###### `process_attestation` ```python @@ -893,7 +905,7 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: data=attestation.data, inclusion_delay=state.slot - attestation.data.slot, proposer_index=get_beacon_proposer_index(state), - crosslink_success=False, # To be filled in during process_crosslinks + crosslink_success=False, # To be filled in during process_shard_transitions ) if attestation.data.target.epoch == get_current_epoch(state): state.current_epoch_attestations.append(pending_attestation) @@ -946,13 +958,15 @@ def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSla #### Verify empty shard transition ```python -def verify_empty_shard_transition(state: BeaconState, block_body: BeaconBlockBody) -> None: +def verify_empty_shard_transition(state: BeaconState, shard_transitions: Sequence[ShardTransition]) -> bool: """ Verify that ``shard_transitions`` are empty if a crosslink was not formed for the associated shard in this slot. """ for shard in range(get_active_shard_count(state)): if state.shard_states[shard].slot != compute_previous_slot(state.slot): - assert block_body.shard_transitions[shard] == ShardTransition() + if shard_transitions[shard] != ShardTransition(): + return False + return True ``` #### Light client processing diff --git a/tests/core/pyspec/eth2spec/test/helpers/crosslinks.py b/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py similarity index 65% rename from tests/core/pyspec/eth2spec/test/helpers/crosslinks.py rename to tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py index ea5da89d9..4ac0ddcfb 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/crosslinks.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py @@ -1,9 +1,9 @@ from eth2spec.test.context import expect_assertion_error -def run_crosslinks_processing(spec, state, shard_transitions, attestations, valid=True): +def run_shard_transitions_processing(spec, state, shard_transitions, attestations, valid=True): """ - Run ``process_attestation``, yielding: + Run ``process_shard_transitions``, yielding: - pre-state ('pre') - shard_transitions ('shard_transitions') - attestations ('attestations') @@ -17,12 +17,12 @@ def run_crosslinks_processing(spec, state, shard_transitions, attestations, vali # If the attestation is invalid, processing is aborted, and there is no post-state. if not valid: - expect_assertion_error(lambda: spec.process_crosslinks(state, shard_transitions, attestations)) + expect_assertion_error(lambda: spec.process_shard_transitions(state, shard_transitions, attestations)) yield 'post', None return # process crosslinks - spec.process_crosslinks(state, shard_transitions, attestations) + spec.process_shard_transitions(state, shard_transitions, attestations) # yield post-state yield 'post', state diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py similarity index 93% rename from tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py rename to tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py index 1f066b344..b4721da5e 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py @@ -4,7 +4,7 @@ from eth2spec.test.context import ( spec_state_test, always_bls, ) -from eth2spec.test.helpers.crosslinks import run_crosslinks_processing +from eth2spec.test.helpers.shard_transitions import run_shard_transitions_processing from eth2spec.test.helpers.shard_block import ( build_attestation_with_shard_transition, build_shard_block, @@ -46,7 +46,7 @@ def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True): transition_to(spec, state, state.slot + target_len_offset_slot) pre_shard_state = state.shard_states[shard] - yield from run_crosslinks_processing(spec, state, shard_transitions, [attestation], valid=valid) + yield from run_shard_transitions_processing(spec, state, shard_transitions, [attestation], valid=valid) if valid: # After state transition, diff --git a/tests/generators/README.md b/tests/generators/README.md index 77a50606b..9446551fb 100644 --- a/tests/generators/README.md +++ b/tests/generators/README.md @@ -184,7 +184,7 @@ def create_provider(handler_name: str, tests_src, config_name: str) -> gen_typin if __name__ == "__main__": gen_runner.run_generator("epoch_processing", [ - create_provider('crosslinks', test_process_crosslinks, 'minimal'), + create_provider('final_updates', test_process_final_updates, 'minimal'), ... ]) From 327deb40b276ecf1e8c180d2f2c33b4f5196483c Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 29 May 2020 03:20:36 +0800 Subject: [PATCH 12/31] Adjust function blocks --- specs/phase1/beacon-chain.md | 73 +++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 853160292..d939d1c9f 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -61,13 +61,14 @@ - [Operations](#operations) - [New Attestation processing](#new-attestation-processing) - [`validate_attestation`](#validate_attestation) + - [Updated `process_attestation`](#updated-process_attestation) + - [Shard transition processing](#shard-transition-processing) - [`apply_shard_transition`](#apply_shard_transition) - [`process_crosslink_for_shard`](#process_crosslink_for_shard) - [`process_crosslinks`](#process_crosslinks) + - [`verify_empty_shard_transition`](#verify_empty_shard_transition) - [`process_shard_transitions`](#process_shard_transitions) - - [`process_attestation`](#process_attestation) - [New Attester slashing processing](#new-attester-slashing-processing) - - [Verify empty shard transition](#verify-empty-shard-transition) - [Light client processing](#light-client-processing) - [Epoch transition](#epoch-transition) - [Custody game updates](#custody-game-updates) @@ -742,6 +743,27 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None: assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) ``` +###### Updated `process_attestation` + +```python +def process_attestation(state: BeaconState, attestation: Attestation) -> None: + validate_attestation(state, attestation) + # Store pending attestation for epoch processing + pending_attestation = PendingAttestation( + aggregation_bits=attestation.aggregation_bits, + data=attestation.data, + inclusion_delay=state.slot - attestation.data.slot, + proposer_index=get_beacon_proposer_index(state), + crosslink_success=False, # To be filled in during process_shard_transitions + ) + if attestation.data.target.epoch == get_current_epoch(state): + state.current_epoch_attestations.append(pending_attestation) + else: + state.previous_epoch_attestations.append(pending_attestation) +``` + +##### Shard transition processing + ###### `apply_shard_transition` ```python @@ -882,6 +904,20 @@ def process_crosslinks(state: BeaconState, pending_attestation.crosslink_success = True ``` +###### `verify_empty_shard_transition` + +```python +def verify_empty_shard_transition(state: BeaconState, shard_transitions: Sequence[ShardTransition]) -> bool: + """ + Verify that a `shard_transition` in a block is empty if an attestation was not processed for it. + """ + for shard in range(get_active_shard_count(state)): + if state.shard_states[shard].slot != compute_previous_slot(state.slot): + if shard_transitions[shard] != ShardTransition(): + return False + return True +``` + ###### `process_shard_transitions` ```python @@ -894,25 +930,6 @@ def process_shard_transitions(state: BeaconState, assert verify_empty_shard_transition(state, shard_transitions) ``` -###### `process_attestation` - -```python -def process_attestation(state: BeaconState, attestation: Attestation) -> None: - validate_attestation(state, attestation) - # Store pending attestation for epoch processing - pending_attestation = PendingAttestation( - aggregation_bits=attestation.aggregation_bits, - data=attestation.data, - inclusion_delay=state.slot - attestation.data.slot, - proposer_index=get_beacon_proposer_index(state), - crosslink_success=False, # To be filled in during process_shard_transitions - ) - if attestation.data.target.epoch == get_current_epoch(state): - state.current_epoch_attestations.append(pending_attestation) - else: - state.previous_epoch_attestations.append(pending_attestation) -``` - ##### New Attester slashing processing ```python @@ -955,20 +972,6 @@ def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSla assert slashed_any ``` -#### Verify empty shard transition - -```python -def verify_empty_shard_transition(state: BeaconState, shard_transitions: Sequence[ShardTransition]) -> bool: - """ - Verify that ``shard_transitions`` are empty if a crosslink was not formed for the associated shard in this slot. - """ - for shard in range(get_active_shard_count(state)): - if state.shard_states[shard].slot != compute_previous_slot(state.slot): - if shard_transitions[shard] != ShardTransition(): - return False - return True -``` - #### Light client processing ```python From 437b2ebb90d3f26b2fb92d23792c560f38208603 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Fri, 29 May 2020 18:16:06 -0700 Subject: [PATCH 13/31] Update fork choice spec comment for clarity I think this change more clearly specifies the intended behavior. Given the terseness of the spec's code representation, I think we should aim for as much clarity as possible. --- specs/phase0/fork-choice.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 4ed2733e1..baf7b7b7b 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -150,7 +150,7 @@ def get_ancestor(store: Store, root: Root, slot: Slot) -> Root: elif block.slot == slot: return root else: - # root is older than queried slot, thus a skip slot. Return earliest root prior to slot + # root is older than queried slot, thus a skip slot. Return most recent root prior to slot return root ``` From 33e115f8c65407357852f1605399bfaf79f933cb Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Sat, 30 May 2020 20:59:12 +0200 Subject: [PATCH 14/31] Clean up remaining `[]` List syntax ..and use List[byte, 256] for error message --- specs/phase0/p2p-interface.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 84db361c9..a77f1851a 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -150,6 +150,7 @@ This section outlines constants that are used in this spec. | Name | Value | Description | |---|---|---| | `GOSSIP_MAX_SIZE` | `2**20` (= 1048576, 1 MiB) | The maximum allowed size of uncompressed gossip messages. | +| `MAX_REQUEST_BLOCKS` | `2**10` (= 1024) | Maximum number of blocks in a single request | | `MAX_CHUNK_SIZE` | `2**20` (1048576, 1 MiB) | The maximum allowed size of uncompressed req/resp chunked responses. | | `TTFB_TIMEOUT` | `5s` | The maximum time to wait for first byte of request response (time-to-first-byte). | | `RESP_TIMEOUT` | `10s` | The maximum time for complete response transfer. | @@ -391,11 +392,11 @@ The `ErrorMessage` schema is: ``` ( - error_message: Bytes32 + error_message: List[byte, 256] ) ``` -*Note*: By convention, the `error_message` is a sequence of bytes that can be interpreted as a UTF-8 string up to 32 bytes - a 0 byte shortens the string in this interpretation. Clients MUST treat as valid any bytes. +*Note*: By convention, the `error_message` is a sequence of bytes that MAY be interpreted as a UTF-8 string (for debugging purposes). Clients MUST treat as valid any byte sequences. ### Encoding strategies @@ -443,9 +444,9 @@ In case of an invalid input (header or payload), a reader MUST: All messages that contain only a single field MUST be encoded directly as the type of that field and MUST NOT be encoded as an SSZ container. -Responses that are SSZ-lists (for example `[]SignedBeaconBlock`) send their +Responses that are SSZ-lists (for example `List[SignedBeaconBlock, ...]`) send their constituents individually as `response_chunk`s. For example, the -`[]SignedBeaconBlock` response type sends zero or more `response_chunk`s. Each _successful_ `response_chunk` contains a single `SignedBeaconBlock` payload. +`List[SignedBeaconBlock, ...]` response type sends zero or more `response_chunk`s. Each _successful_ `response_chunk` contains a single `SignedBeaconBlock` payload. ### Messages @@ -528,7 +529,7 @@ Request Content: Response Content: ``` ( - []SignedBeaconBlock + List[SignedBeaconBlock, MAX_REQUEST_BLOCKS] ) ``` @@ -545,7 +546,7 @@ The response MUST consist of zero or more `response_chunk`. Each _successful_ `r Clients MUST keep a record of signed blocks seen since the since the start of the weak subjectivity period and MUST support serving requests of blocks up to their own `head_block_root`. -Clients MUST respond with at least the first block that exists in the range, if they have it. +Clients MUST respond with at least the first block that exists in the range, if they have it, and no more than `MAX_REQUEST_BLOCKS` blocks. The following blocks, where they exist, MUST be send in consecutive order. @@ -568,7 +569,7 @@ Request Content: ``` ( - []Root + List[Root, MAX_REQUEST_BLOCKS] ) ``` @@ -576,12 +577,14 @@ Response Content: ``` ( - []SignedBeaconBlock + List[SignedBeaconBlock, MAX_REQUEST_BLOCKS] ) ``` Requests blocks by block root (= `hash_tree_root(SignedBeaconBlock.message)`). The response is a list of `SignedBeaconBlock` whose length is less than or equal to the number of requested blocks. It may be less in the case that the responding peer is missing blocks. +No more than `MAX_REQUEST_BLOCKS` may be requested at a time. + `BeaconBlocksByRoot` is primarily used to recover recent blocks (e.g. when receiving a block or attestation whose parent is unknown). The request MUST be encoded as an SSZ-field. From 6317bd68aa7476d82064daf70d7dbd71236ae3e1 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 1 Jun 2020 17:47:47 +0800 Subject: [PATCH 15/31] PR feedback from Danny Co-authored-by: Danny Ryan --- specs/phase1/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 6c792432f..308af4ee3 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -515,7 +515,7 @@ def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) - ```python def get_light_client_committee(beacon_state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]: """ - Return the light client committee that no more than ``TARGET_COMMITTEE_SIZE`` validators. + Return the light client committee of no more than ``TARGET_COMMITTEE_SIZE`` validators. """ source_epoch = compute_committee_source_epoch(epoch, LIGHT_CLIENT_COMMITTEE_PERIOD) active_validator_indices = get_active_validator_indices(beacon_state, source_epoch) From 92db6da50854071eecea138576e9771a5004a596 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 1 Jun 2020 17:56:22 +0800 Subject: [PATCH 16/31] Apply suggestions from Terence Co-authored-by: terence tsao --- specs/phase1/beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 124442b59..1367d029b 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -578,7 +578,7 @@ def get_start_shard(state: BeaconState, slot: Slot) -> Shard: ```python def get_shard(state: BeaconState, attestation: Attestation) -> Shard: """ - Return the shard that the given attestation is attesting. + Return the shard that the given ``attestation`` is attesting. """ return compute_shard_from_committee_index(state, attestation.data.index, attestation.data.slot) ``` @@ -588,7 +588,7 @@ def get_shard(state: BeaconState, attestation: Attestation) -> Shard: ```python def get_latest_slot_for_shard(state: BeaconState, shard: Shard) -> Slot: """ - Return the latest slot number of the given shard. + Return the latest slot number of the given ``shard``. """ return state.shard_states[shard].slot ``` @@ -598,7 +598,7 @@ def get_latest_slot_for_shard(state: BeaconState, shard: Shard) -> Slot: ```python def get_offset_slots(state: BeaconState, shard: Shard) -> Sequence[Slot]: """ - Return the offset slots of the given shard. + Return the offset slots of the given ``shard``. The offset slot are after the latest slot and before current slot. """ return compute_offset_slots(get_latest_slot_for_shard(state, shard), state.slot) From 30f72dd69646d037a06943d026e2d91ef2b9c2c5 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 1 Jun 2020 23:15:16 +0800 Subject: [PATCH 17/31] Fix `get_shard` and `compute_shard_from_committee_index` calls --- specs/phase1/beacon-chain.md | 25 ++++++------- .../eth2spec/test/helpers/attestations.py | 37 +++++++++---------- .../eth2spec/test/helpers/shard_block.py | 2 +- .../test/helpers/shard_transitions.py | 15 ++++++++ .../test/phase_0/sanity/test_blocks.py | 20 +++++++--- .../test_process_shard_transition.py | 9 ++--- .../test/phase_1/sanity/test_blocks.py | 4 +- 7 files changed, 67 insertions(+), 45 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 228cd888e..52b6f2afb 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -578,7 +578,7 @@ def get_start_shard(state: BeaconState, slot: Slot) -> Shard: active_shard_count = get_active_shard_count(state) if current_epoch_start_slot == slot: return state.current_epoch_start_shard - elif current_epoch_start_slot > slot: + elif slot > current_epoch_start_slot: # Current epoch or the next epoch lookahead shard_delta = get_committee_count_delta(state, start_slot=current_epoch_start_slot, stop_slot=slot) return Shard((state.current_epoch_start_shard + shard_delta) % active_shard_count) @@ -693,8 +693,7 @@ def is_on_time_attestation(state: BeaconState, """ Check if the given attestation is on-time. """ - # TODO: MIN_ATTESTATION_INCLUSION_DELAY should always be 1 - return attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY == state.slot + return attestation.data.slot == compute_previous_slot(state.slot) ``` #### `is_winning_attestation` @@ -803,20 +802,19 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None: else: assert attestation.data.source == state.previous_justified_checkpoint - shard = get_shard(state, attestation) - # Type 1: on-time attestations, the custody bits should be non-empty. if attestation.custody_bits_blocks != []: # Ensure on-time attestation assert is_on_time_attestation(state, attestation) # Correct data root count + shard = get_shard(state, attestation) assert len(attestation.custody_bits_blocks) == len(get_offset_slots(state, shard)) # Correct parent block root assert data.beacon_block_root == get_block_root_at_slot(state, compute_previous_slot(state.slot)) # Type 2: no shard transition, no custody bits else: # Ensure delayed attestation - assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY < state.slot + assert data.slot < compute_previous_slot(state.slot) # Late attestations cannot have a shard transition root assert data.shard_transition_root == Root() @@ -911,9 +909,10 @@ def process_crosslink_for_shard(state: BeaconState, committee_index: CommitteeIndex, shard_transition: ShardTransition, attestations: Sequence[Attestation]) -> Root: - committee = get_beacon_committee(state, state.slot, committee_index) + on_time_attestation_slot = compute_previous_slot(state.slot) + committee = get_beacon_committee(state, on_time_attestation_slot, committee_index) online_indices = get_online_validator_indices(state) - shard = compute_shard_from_committee_index(state, committee_index, state.slot) + shard = compute_shard_from_committee_index(state, committee_index, on_time_attestation_slot) # Loop over all shard transition roots shard_transition_roots = set([a.data.shard_transition_root for a in attestations]) @@ -968,15 +967,15 @@ def process_crosslink_for_shard(state: BeaconState, def process_crosslinks(state: BeaconState, shard_transitions: Sequence[ShardTransition], attestations: Sequence[Attestation]) -> None: - committee_count = get_committee_count_at_slot(state, state.slot) + on_time_attestation_slot = compute_previous_slot(state.slot) + committee_count = get_committee_count_at_slot(state, on_time_attestation_slot) for committee_index in map(CommitteeIndex, range(committee_count)): - shard = compute_shard_from_committee_index(state, committee_index, state.slot) # All attestations in the block for this committee/shard and current slot shard_attestations = [ attestation for attestation in attestations if is_on_time_attestation(state, attestation) and attestation.data.index == committee_index ] - + shard = compute_shard_from_committee_index(state, committee_index, on_time_attestation_slot) winning_root = process_crosslink_for_shard(state, committee_index, shard_transitions[shard], shard_attestations) if winning_root != Root(): # Mark relevant pending attestations as creating a successful crosslink @@ -1083,7 +1082,7 @@ def process_epoch(state: BeaconState) -> None: process_registry_updates(state) process_reveal_deadlines(state) process_slashings(state) - process_final_updates(state) + process_final_updates(state) # phase 0 final updates process_phase_1_final_updates(state) ``` @@ -1096,7 +1095,7 @@ def process_phase_1_final_updates(state: BeaconState) -> None: process_light_client_committee_updates(state) # Update current_epoch_start_shard - state.current_epoch_start_shard = get_start_shard(state, state.slot) + state.current_epoch_start_shard = get_start_shard(state, Slot(state.slot + 1)) ``` #### Custody game updates diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index c533182ef..1372b0654 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -1,8 +1,9 @@ from typing import List from eth2spec.test.context import expect_assertion_error, PHASE0, PHASE1 -from eth2spec.test.helpers.state import state_transition_and_sign_block, next_epoch, next_slot, transition_to +from eth2spec.test.helpers.state import state_transition_and_sign_block, next_epoch, next_slot from eth2spec.test.helpers.block import build_empty_block_for_next_slot +from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls from eth2spec.utils.ssz.ssz_typing import Bitlist @@ -81,12 +82,12 @@ def build_attestation_data(spec, state, slot, index, shard_transition=None, on_t attestation_data.shard_head_root = shard_transition.shard_data_roots[lastest_shard_data_root_index] attestation_data.shard_transition_root = shard_transition.hash_tree_root() else: - # No shard transition + # No shard transition -> no shard block shard = spec.get_shard(state, spec.Attestation(data=attestation_data)) if on_time: temp_state = state.copy() next_slot(spec, temp_state) - shard_transition = spec.get_shard_transition(temp_state, shard, []) + shard_transition = spec.get_shard_transition(temp_state, shard, shard_blocks=[]) lastest_shard_data_root_index = len(shard_transition.shard_data_roots) - 1 attestation_data.shard_head_root = shard_transition.shard_data_roots[lastest_shard_data_root_index] attestation_data.shard_transition_root = shard_transition.hash_tree_root() @@ -180,7 +181,7 @@ def get_valid_attestation(spec, fill_aggregate_attestation(spec, state, attestation, signed=signed, filter_participant_set=filter_participant_set) if spec.fork == PHASE1 and on_time: - attestation = convert_to_valid_on_time_attestation(spec, state, attestation, signed) + attestation = convert_to_valid_on_time_attestation(spec, state, attestation, signed=signed) return attestation @@ -317,7 +318,19 @@ def next_epoch_with_attestations(spec, committees_per_slot = spec.get_committee_count_at_slot(state, slot_to_attest) if slot_to_attest >= spec.compute_start_slot_at_epoch(spec.get_current_epoch(post_state)): for index in range(committees_per_slot): - cur_attestation = get_valid_attestation(spec, post_state, slot_to_attest, index=index, signed=True) + if spec.fork == PHASE1: + shard = spec.compute_shard_from_committee_index(post_state, index, slot_to_attest) + shard_transition = get_shard_transition_of_committee( + spec, post_state, index, slot=slot_to_attest + ) + block.body.shard_transitions[shard] = shard_transition + else: + shard_transition = None + + cur_attestation = get_valid_attestation( + spec, post_state, slot_to_attest, + shard_transition=shard_transition, index=index, signed=True, on_time=True + ) block.body.attestations.append(cur_attestation) if fill_prev_epoch: @@ -328,9 +341,6 @@ def next_epoch_with_attestations(spec, spec, post_state, slot_to_attest, index=index, signed=True, on_time=False) block.body.attestations.append(prev_attestation) - if spec.fork == PHASE1: - fill_block_shard_transitions_by_attestations(spec, post_state, block) - signed_block = state_transition_and_sign_block(spec, post_state, block) signed_blocks.append(signed_block) @@ -396,14 +406,3 @@ def cached_prepare_state_with_attestations(spec, state): # Put the LRU cache result into the state view, as if we transitioned the original view state.set_backing(_prep_state_cache_dict[key]) - - -def fill_block_shard_transitions_by_attestations(spec, state, block): - block.body.shard_transitions = [spec.ShardTransition()] * spec.MAX_SHARDS - for attestation in block.body.attestations: - shard = spec.get_shard(state, attestation) - if attestation.data.slot == state.slot: - temp_state = state.copy() - transition_to(spec, temp_state, slot=block.slot) - shard_transition = spec.get_shard_transition(temp_state, shard, []) - block.body.shard_transitions[shard] = shard_transition diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py index 805b955f7..58efada83 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py @@ -70,7 +70,7 @@ def build_shard_transitions_till_slot(spec, state, shard_blocks, on_time_slot): return shard_transitions -def build_attestation_with_shard_transition(spec, state, index, on_time_slot, shard_transition=None): +def build_attestation_with_shard_transition(spec, state, index, on_time_slot, shard_transition): temp_state = state.copy() transition_to(spec, temp_state, on_time_slot - 1) attestation = get_valid_on_time_attestation( diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py b/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py index 4ac0ddcfb..abb5e7278 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py @@ -1,4 +1,5 @@ from eth2spec.test.context import expect_assertion_error +from eth2spec.test.helpers.state import transition_to def run_shard_transitions_processing(spec, state, shard_transitions, attestations, valid=True): @@ -26,3 +27,17 @@ def run_shard_transitions_processing(spec, state, shard_transitions, attestation # yield post-state yield 'post', state + + +def get_shard_transition_of_committee(spec, state, committee_index, slot=None, shard_blocks=None): + if shard_blocks is None: + shard_blocks = [] + + if slot is None: + slot = state.slot + + shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) + temp_state = state.copy() + transition_to(spec, temp_state, slot + 1) + shard_transition = spec.get_shard_transition(temp_state, shard, shard_blocks=shard_blocks) + return shard_transition diff --git a/tests/core/pyspec/eth2spec/test/phase_0/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase_0/sanity/test_blocks.py index f0cfc462e..1e057e5a9 100644 --- a/tests/core/pyspec/eth2spec/test/phase_0/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase_0/sanity/test_blocks.py @@ -16,8 +16,9 @@ from eth2spec.test.helpers.attester_slashings import ( get_indexed_attestation_participants, ) from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing, check_proposer_slashing_effect -from eth2spec.test.helpers.attestations import get_valid_attestation, fill_block_shard_transitions_by_attestations +from eth2spec.test.helpers.attestations import get_valid_attestation from eth2spec.test.helpers.deposits import prepare_state_and_deposit +from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee from eth2spec.test.context import ( spec_state_test, with_all_phases, expect_assertion_error, always_bls, with_phases, @@ -687,14 +688,23 @@ def test_attestation(spec, state): yield 'pre', state - attestation = get_valid_attestation(spec, state, signed=True, on_time=True) + attestation_block = build_empty_block(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + + index = 0 + if spec.fork == PHASE1: + shard = spec.compute_shard_from_committee_index(state, index, state.slot) + shard_transition = get_shard_transition_of_committee(spec, state, index) + attestation_block.body.shard_transitions[shard] = shard_transition + else: + shard_transition = None + + attestation = get_valid_attestation( + spec, state, shard_transition=shard_transition, index=index, signed=True, on_time=True + ) # Add to state via block transition pre_current_attestations_len = len(state.current_epoch_attestations) - attestation_block = build_empty_block(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) attestation_block.body.attestations.append(attestation) - if spec.fork == PHASE1: - fill_block_shard_transitions_by_attestations(spec, state, attestation_block) signed_attestation_block = state_transition_and_sign_block(spec, state, attestation_block) assert len(state.current_epoch_attestations) == pre_current_attestations_len + 1 diff --git a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py index ba408cd48..00ffbe0a8 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_shard_transition.py @@ -15,11 +15,10 @@ from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True): state = transition_to_valid_shard_slot(spec, state) - # At the beginning, let `x = state.slot`, `state.shard_states[shard].slot == x - 1` - slot_x = state.slot + init_slot = state.slot committee_index = spec.CommitteeIndex(0) - shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot) - assert state.shard_states[shard].slot == slot_x - 1 + shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot - 1) + assert state.shard_states[shard].slot == state.slot - 1 # Create SignedShardBlock body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE @@ -50,7 +49,7 @@ def run_basic_crosslink_tests(spec, state, target_len_offset_slot, valid=True): if valid: # After state transition, - assert state.slot == slot_x + target_len_offset_slot + assert state.slot == init_slot + target_len_offset_slot shard_state = state.shard_states[shard] assert shard_state != pre_shard_state assert shard_state == shard_transition.shard_states[len(shard_transition.shard_states) - 1] diff --git a/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py index 1499d34cd..0175bd40d 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/sanity/test_blocks.py @@ -67,7 +67,7 @@ def test_process_beacon_block_with_normal_shard_transition(spec, state): target_len_offset_slot = 1 committee_index = spec.CommitteeIndex(0) - shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot) + shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot - 1) assert state.shard_states[shard].slot == state.slot - 1 pre_gasprice = state.shard_states[shard].gasprice @@ -93,7 +93,7 @@ def test_process_beacon_block_with_empty_proposal_transition(spec, state): target_len_offset_slot = 1 committee_index = spec.CommitteeIndex(0) - shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot) + shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot + target_len_offset_slot - 1) assert state.shard_states[shard].slot == state.slot - 1 # No new shard block From 5f10ac13bf91c181152a5469562dfb9d89383926 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 1 Jun 2020 23:22:59 +0800 Subject: [PATCH 18/31] PR feedback from Terence and Danny: refactor `get_committee_count_delta` --- specs/phase1/beacon-chain.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 52b6f2afb..484b24cf1 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -558,13 +558,9 @@ def get_indexed_attestation(beacon_state: BeaconState, attestation: Attestation) ```python def get_committee_count_delta(state: BeaconState, start_slot: Slot, stop_slot: Slot) -> uint64: """ - Return the sum of committee counts between ``[start_slot, stop_slot)``. + Return the sum of committee counts in range ``[start_slot, stop_slot)``. """ - committee_sum = 0 - for slot in range(start_slot, stop_slot): - count = get_committee_count_at_slot(state, Slot(slot)) - committee_sum += count - return committee_sum + return sum(get_committee_count_at_slot(state, Slot(slot)) for slot in range(start_slot, stop_slot)) ``` #### `get_start_shard` From 2a218520a1338aca2601cd90ffe2d31f64475495 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Mon, 1 Jun 2020 14:31:45 -0700 Subject: [PATCH 19/31] Tidying up `shard_state_transition` --- specs/phase1/shard-transition.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index ce6d289bd..f9de34b31 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -71,21 +71,22 @@ def verify_shard_block_signature(beacon_state: BeaconState, def shard_state_transition(beacon_state: BeaconState, shard_state: ShardState, block: ShardBlock) -> None: - # Update shard state + """ + Update ``shard_state`` with shard ``block`` and ``beacon_state`. + """ + shard_state.slot = block.slot prev_gasprice = shard_state.gasprice - if len(block.body) == 0: - latest_block_root = shard_state.latest_block_root - else: - latest_block_root = hash_tree_root(block) - + shard_state.gasprice = compute_updated_gasprice(prev_gasprice, len(block.body)) shard_state.transition_digest = compute_shard_transition_digest( beacon_state, shard_state, block.beacon_parent_root, hash_tree_root(block.body), ) - shard_state.gasprice = compute_updated_gasprice(prev_gasprice, len(block.body)) - shard_state.slot = block.slot + if len(block.body) == 0: + latest_block_root = shard_state.latest_block_root + else: + latest_block_root = hash_tree_root(block) shard_state.latest_block_root = latest_block_root ``` From c6aac1650600407a832fa9646362873ebb785d72 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 2 Jun 2020 17:16:25 +1000 Subject: [PATCH 20/31] Reword fork choice comment --- specs/phase0/fork-choice.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index baf7b7b7b..3f1c20c0a 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -285,7 +285,7 @@ def validate_on_attestation(store: Store, attestation: Attestation) -> None: # Attestations must not be for blocks in the future. If not, the attestation should not be considered assert store.blocks[attestation.data.beacon_block_root].slot <= attestation.data.slot - # FFG and LMD vote must be consistent with each other + # LMD vote must be consistent with FFG target target_slot = compute_start_slot_at_epoch(target.epoch) assert target.root == get_ancestor(store, attestation.data.beacon_block_root, target_slot) From 2a125e8497c37b8685ba5478be254105df2542d3 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 2 Jun 2020 17:22:33 +1000 Subject: [PATCH 21/31] Update fork-choice.md --- specs/phase0/fork-choice.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 3f1c20c0a..b9d8ecd3c 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -285,7 +285,7 @@ def validate_on_attestation(store: Store, attestation: Attestation) -> None: # Attestations must not be for blocks in the future. If not, the attestation should not be considered assert store.blocks[attestation.data.beacon_block_root].slot <= attestation.data.slot - # LMD vote must be consistent with FFG target + # LMD vote must be consistent with FFG vote target target_slot = compute_start_slot_at_epoch(target.epoch) assert target.root == get_ancestor(store, attestation.data.beacon_block_root, target_slot) From 24427947b17060623047fca2fbc0b49d8b636fe7 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Tue, 2 Jun 2020 08:09:43 -0700 Subject: [PATCH 22/31] Moved transition_digest to last --- specs/phase1/shard-transition.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index f9de34b31..e6221a980 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -77,17 +77,17 @@ def shard_state_transition(beacon_state: BeaconState, shard_state.slot = block.slot prev_gasprice = shard_state.gasprice shard_state.gasprice = compute_updated_gasprice(prev_gasprice, len(block.body)) + if len(block.body) == 0: + latest_block_root = shard_state.latest_block_root + else: + latest_block_root = hash_tree_root(block) + shard_state.latest_block_root = latest_block_root shard_state.transition_digest = compute_shard_transition_digest( beacon_state, shard_state, block.beacon_parent_root, hash_tree_root(block.body), ) - if len(block.body) == 0: - latest_block_root = shard_state.latest_block_root - else: - latest_block_root = hash_tree_root(block) - shard_state.latest_block_root = latest_block_root ``` We have a pure function `get_post_shard_state` for describing the fraud proof verification and honest validator behavior. From 34412130c5a8df907680649adbae58ae45f3c0bc Mon Sep 17 00:00:00 2001 From: Raw Pong Ghmoa <58883403+q9f@users.noreply.github.com> Date: Mon, 1 Jun 2020 13:59:03 +0200 Subject: [PATCH 23/31] phase0: enable a tunable genesis time --- specs/phase0/beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 1be32da44..e4de2d38b 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -218,7 +218,7 @@ The following values are (non-configurable) constants used throughout the specif | Name | Value | Unit | Duration | | - | - | :-: | :-: | -| `MIN_GENESIS_DELAY` | `86400` | seconds | 1 day | +| `GENESIS_DELAY` | `172800` | seconds | 2 days | | `SECONDS_PER_SLOT` | `12` | seconds | 12 seconds | | `SECONDS_PER_ETH1_BLOCK` | `14` | seconds | 14 seconds | | `MIN_ATTESTATION_INCLUSION_DELAY` | `2**0` (= 1) | slots | 12 seconds | @@ -1137,7 +1137,7 @@ Before the Ethereum 2.0 genesis has been triggered, and for every Ethereum 1.0 b - `eth1_timestamp` is the Unix timestamp corresponding to `eth1_block_hash` - `deposits` is the sequence of all deposits, ordered chronologically, up to (and including) the block with hash `eth1_block_hash` -Eth1 blocks must only be considered once they are at least `SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE` seconds old (i.e. `eth1_timestamp + SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE <= current_unix_time`). Due to this constraint, if `MIN_GENESIS_DELAY < SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE`, then the `genesis_time` can happen before the time/state is first known. Values should be configured to avoid this case. +Eth1 blocks must only be considered once they are at least `SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE` seconds old (i.e. `eth1_timestamp + SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE <= current_unix_time`). Due to this constraint, if `GENESIS_DELAY < SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE`, then the `genesis_time` can happen before the time/state is first known. Values should be configured to avoid this case. ```python def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, @@ -1149,7 +1149,7 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, epoch=GENESIS_EPOCH, ) state = BeaconState( - genesis_time=eth1_timestamp - eth1_timestamp % MIN_GENESIS_DELAY + 2 * MIN_GENESIS_DELAY, + genesis_time=eth1_timestamp + GENESIS_DELAY, fork=fork, eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=len(deposits)), latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), From b3f2d81ad5063f728c4620ed5a9435b5dfbe8853 Mon Sep 17 00:00:00 2001 From: Raw Pong Ghmoa <58883403+q9f@users.noreply.github.com> Date: Mon, 1 Jun 2020 14:02:29 +0200 Subject: [PATCH 24/31] reflect changes in mainnet.yaml --- configs/mainnet.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 42845c235..3631b7045 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -76,8 +76,8 @@ BLS_WITHDRAWAL_PREFIX: 0x00 # Time parameters # --------------------------------------------------------------- -# 86400 seconds (1 day) -MIN_GENESIS_DELAY: 86400 +# 172800 seconds (2 days) +GENESIS_DELAY: 172800 # 12 seconds SECONDS_PER_SLOT: 12 # 2**0 (= 1) slots 12 seconds From d5ed78e974aa97e22c4695d25f82165a5cc46310 Mon Sep 17 00:00:00 2001 From: Raw Pong Ghmoa <58883403+q9f@users.noreply.github.com> Date: Mon, 1 Jun 2020 14:03:07 +0200 Subject: [PATCH 25/31] reflect changes in minimal.yaml --- configs/minimal.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/minimal.yaml b/configs/minimal.yaml index d8e346ffa..5abf6c93c 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -77,7 +77,7 @@ BLS_WITHDRAWAL_PREFIX: 0x00 # Time parameters # --------------------------------------------------------------- # [customized] Faster to spin up testnets, but does not give validator reasonable warning time for genesis -MIN_GENESIS_DELAY: 300 +GENESIS_DELAY: 300 # [customized] Faster for testing purposes SECONDS_PER_SLOT: 6 # 2**0 (= 1) slots 6 seconds From 671fae6efe83e0fe7e0e1256d2c0199daf7c42ed Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 2 Jun 2020 11:09:42 -0600 Subject: [PATCH 26/31] change note about genesis delay in p2p spec to match new GENESIS_DELAY config value; fix tests --- specs/phase0/p2p-interface.md | 2 +- .../core/pyspec/eth2spec/test/genesis/test_initialization.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index a994b0c66..e47fafed0 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -1052,7 +1052,7 @@ discv5 uses ENRs and we will presumably need to: Although client software might very well be running locally prior to the solidification of the eth2 genesis state and block, clients cannot form valid ENRs prior to this point. ENRs contain `fork_digest` which utilizes the `genesis_validators_root` for a cleaner separation between chains so prior to knowing genesis, we cannot use `fork_digest` to cleanly find peers on our intended chain. Once genesis data is known, we can then form ENRs and safely find peers. -When using an eth1 deposit contract for deposits, `fork_digest` will be known at least `MIN_GENESIS_DELAY` (24 hours in mainnet configuration) before `genesis_time`, providing ample time to find peers and form initial connections and gossip subnets prior to genesis. +When using an eth1 deposit contract for deposits, `fork_digest` will be known `GENESIS_DELAY` (48hours in mainnet configuration) before `genesis_time`, providing ample time to find peers and form initial connections and gossip subnets prior to genesis. ## Compression/Encoding diff --git a/tests/core/pyspec/eth2spec/test/genesis/test_initialization.py b/tests/core/pyspec/eth2spec/test/genesis/test_initialization.py index 882821337..faade2d17 100644 --- a/tests/core/pyspec/eth2spec/test/genesis/test_initialization.py +++ b/tests/core/pyspec/eth2spec/test/genesis/test_initialization.py @@ -21,7 +21,7 @@ def test_initialize_beacon_state_from_eth1(spec): # initialize beacon_state state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits) - assert state.genesis_time == eth1_timestamp - eth1_timestamp % spec.MIN_GENESIS_DELAY + 2 * spec.MIN_GENESIS_DELAY + assert state.genesis_time == eth1_timestamp + spec.GENESIS_DELAY assert len(state.validators) == deposit_count assert state.eth1_data.deposit_root == deposit_root assert state.eth1_data.deposit_count == deposit_count @@ -57,7 +57,7 @@ def test_initialize_beacon_state_some_small_balances(spec): # initialize beacon_state state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits) - assert state.genesis_time == eth1_timestamp - eth1_timestamp % spec.MIN_GENESIS_DELAY + 2 * spec.MIN_GENESIS_DELAY + assert state.genesis_time == eth1_timestamp + spec.GENESIS_DELAY assert len(state.validators) == small_deposit_count assert state.eth1_data.deposit_root == deposit_root assert state.eth1_data.deposit_count == len(deposits) From 06dfff022bf05f63e703e1f0d1031d530edf4fb4 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 2 Jun 2020 11:21:00 -0600 Subject: [PATCH 27/31] add comment about default of 0x00 for genesis finalized checkpoint in p2p spec --- specs/phase0/p2p-interface.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index a994b0c66..c7d61d8f1 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -468,9 +468,9 @@ The fields are, as seen by the client at the time of sending the message: - `fork_digest`: The node's `ForkDigest` (`compute_fork_digest(current_fork_version, genesis_validators_root)`) where - `current_fork_version` is the fork version at the node's current epoch defined by the wall-clock time (not necessarily the epoch to which the node is sync) - `genesis_validators_root` is the static `Root` found in `state.genesis_validators_root` -- `finalized_root`: `state.finalized_checkpoint.root` for the state corresponding to the head block. +- `finalized_root`: `state.finalized_checkpoint.root` for the state corresponding to the head block (Note this defaults to `Root(b'\x00' * 32)` for the genesis finalized checkpoint). - `finalized_epoch`: `state.finalized_checkpoint.epoch` for the state corresponding to the head block. -- `head_root`: The hash_tree_root root of the current head block. +- `head_root`: The `hash_tree_root` root of the current head block. - `head_slot`: The slot of the block corresponding to the `head_root`. The dialing client MUST send a `Status` request upon connection. From cf7b9993b5ea1124ddd87579dc27a78ebf092e92 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 2 Jun 2020 13:51:43 -0600 Subject: [PATCH 28/31] clarify `head_root` is for a `BeaconBlock` Co-authored-by: Diederik Loerakker --- specs/phase0/p2p-interface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index c7d61d8f1..e98070b6f 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -470,7 +470,7 @@ The fields are, as seen by the client at the time of sending the message: - `genesis_validators_root` is the static `Root` found in `state.genesis_validators_root` - `finalized_root`: `state.finalized_checkpoint.root` for the state corresponding to the head block (Note this defaults to `Root(b'\x00' * 32)` for the genesis finalized checkpoint). - `finalized_epoch`: `state.finalized_checkpoint.epoch` for the state corresponding to the head block. -- `head_root`: The `hash_tree_root` root of the current head block. +- `head_root`: The `hash_tree_root` root of the current head block (`BeaconBlock`). - `head_slot`: The slot of the block corresponding to the `head_root`. The dialing client MUST send a `Status` request upon connection. From 314dea97a5018740c5166e4884777c936f5d5150 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 2 Jun 2020 17:26:26 -0600 Subject: [PATCH 29/31] bump VERSION.txt to 0.12.1 --- tests/core/pyspec/eth2spec/VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/VERSION.txt b/tests/core/pyspec/eth2spec/VERSION.txt index d33c3a212..aac2dacab 100644 --- a/tests/core/pyspec/eth2spec/VERSION.txt +++ b/tests/core/pyspec/eth2spec/VERSION.txt @@ -1 +1 @@ -0.12.0 \ No newline at end of file +0.12.1 \ No newline at end of file From 68e934bf1552517346c1b31f1fb9e181b51d82cb Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 3 Jun 2020 23:08:38 +0800 Subject: [PATCH 30/31] Add `get_start_shard` unittests and update minimal config 1. Add unittests for testing `get_start_shard` with better granularity 2. Change `INITIAL_ACTIVE_SHARDS` from `4` to `2` for tight crosslinking --- configs/minimal.yaml | 2 +- .../test/phase_1/unittests/__init__.py | 0 .../phase_1/unittests/test_get_start_shard.py | 71 +++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase_1/unittests/__init__.py create mode 100644 tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py diff --git a/configs/minimal.yaml b/configs/minimal.yaml index d8e346ffa..bb46294f5 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -165,7 +165,7 @@ PHASE_1_FORK_VERSION: 0x01000001 # [customized] for testing PHASE_1_GENESIS_SLOT: 8 # [customized] reduced for testing -INITIAL_ACTIVE_SHARDS: 4 +INITIAL_ACTIVE_SHARDS: 2 # Phase 1: General diff --git a/tests/core/pyspec/eth2spec/test/phase_1/unittests/__init__.py b/tests/core/pyspec/eth2spec/test/phase_1/unittests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py b/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py new file mode 100644 index 000000000..f9f605d9f --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py @@ -0,0 +1,71 @@ +from eth2spec.test.context import ( + PHASE0, + with_all_phases_except, + spec_state_test, +) +from eth2spec.test.helpers.state import next_epoch + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_get_committee_count_delta(spec, state): + assert spec.get_committee_count_delta(state, 0, 0) == 0 + assert spec.get_committee_count_at_slot(state, 0) != 0 + assert spec.get_committee_count_delta(state, 0, 1) == spec.get_committee_count_at_slot(state, 0) + assert spec.get_committee_count_delta(state, 1, 2) == spec.get_committee_count_at_slot(state, 1) + assert spec.get_committee_count_delta(state, 0, 2) == ( + spec.get_committee_count_at_slot(state, 0) + spec.get_committee_count_at_slot(state, 1) + ) + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_get_start_shard_current_epoch_start(spec, state): + assert state.current_epoch_start_shard == 0 + next_epoch(spec, state) + active_shard_count = spec.get_active_shard_count(state) + assert state.current_epoch_start_shard == ( + spec.get_committee_count_delta(state, 0, spec.SLOTS_PER_EPOCH) % active_shard_count + ) + current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)) + + slot = current_epoch_start_slot + start_shard = spec.get_start_shard(state, slot) + assert start_shard == state.current_epoch_start_shard + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_get_start_shard_next_slot(spec, state): + next_epoch(spec, state) + active_shard_count = spec.get_active_shard_count(state) + current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)) + + slot = current_epoch_start_slot + 1 + start_shard = spec.get_start_shard(state, slot) + + current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)) + expected_start_shard = ( + state.current_epoch_start_shard + + spec.get_committee_count_delta(state, start_slot=current_epoch_start_slot, stop_slot=slot) + ) % active_shard_count + assert start_shard == expected_start_shard + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_get_start_shard_previous_slot(spec, state): + next_epoch(spec, state) + active_shard_count = spec.get_active_shard_count(state) + current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)) + + slot = current_epoch_start_slot - 1 + start_shard = spec.get_start_shard(state, slot) + + current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)) + expected_start_shard = ( + state.current_epoch_start_shard + + spec.MAX_COMMITTEES_PER_SLOT * spec.SLOTS_PER_EPOCH * active_shard_count + - spec.get_committee_count_delta(state, start_slot=slot, stop_slot=current_epoch_start_slot) + ) % active_shard_count + assert start_shard == expected_start_shard From a685be3bbecb18d7bf336af911a638d68e6b64f9 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 4 Jun 2020 00:42:08 +0800 Subject: [PATCH 31/31] PR feedback from Danny Co-authored-by: Danny Ryan --- .../eth2spec/test/phase_1/unittests/test_get_start_shard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py b/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py index f9f605d9f..27afd4a4e 100644 --- a/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py +++ b/tests/core/pyspec/eth2spec/test/phase_1/unittests/test_get_start_shard.py @@ -46,8 +46,8 @@ def test_get_start_shard_next_slot(spec, state): current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)) expected_start_shard = ( - state.current_epoch_start_shard + - spec.get_committee_count_delta(state, start_slot=current_epoch_start_slot, stop_slot=slot) + state.current_epoch_start_shard + + spec.get_committee_count_delta(state, start_slot=current_epoch_start_slot, stop_slot=slot) ) % active_shard_count assert start_shard == expected_start_shard