From 4e8a7ff1156dc3a12ccb718476782bc685de3a4f Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 6 Apr 2020 19:30:32 +0800 Subject: [PATCH] [squashed] shard transition wip Fix the wrong `get_shard_proposer_index` parameters order Phase 1 WIP Add shard transition basic test Fix lint error Fix --- specs/phase1/beacon-chain.md | 63 ++--- specs/phase1/fraud-proofs.md | 229 ++++++++++++++---- .../eth2spec/test/helpers/attestations.py | 40 ++- .../eth2spec/test/helpers/crosslinks.py | 28 +++ .../eth2spec/test/helpers/phase1/__init__.py | 0 .../test/helpers/phase1/attestations.py | 63 ----- .../test/helpers/phase1/shard_block.py | 71 ------ .../test/helpers/phase1/shard_state.py | 18 -- .../eth2spec/test/helpers/shard_block.py | 47 ++++ .../test_process_crosslink.py | 52 ++++ 10 files changed, 374 insertions(+), 237 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/helpers/crosslinks.py delete mode 100644 tests/core/pyspec/eth2spec/test/helpers/phase1/__init__.py delete mode 100644 tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py delete mode 100644 tests/core/pyspec/eth2spec/test/helpers/phase1/shard_block.py delete mode 100644 tests/core/pyspec/eth2spec/test/helpers/phase1/shard_state.py create mode 100644 tests/core/pyspec/eth2spec/test/helpers/shard_block.py create mode 100644 tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 7c3ae786b..4b22f8633 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -460,16 +460,17 @@ def get_online_validator_indices(state: BeaconState) -> Set[ValidatorIndex]: ```python def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) -> Sequence[ValidatorIndex]: - source_epoch = epoch - epoch % SHARD_COMMITTEE_PERIOD + source_epoch = epoch - epoch % SHARD_COMMITTEE_PERIOD if source_epoch > 0: source_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_shards_count = get_active_shard_count(beacon_state) return compute_committee( indices=active_validator_indices, seed=seed, index=shard, - count=get_active_shard_count(beacon_state) + count=active_shards_count, ) ``` @@ -712,29 +713,35 @@ def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTr ) assert transition.start_slot == offset_slots[0] - # Reconstruct shard headers headers = [] + header = ShardBlockHeader() proposers = [] - shard_parent_root = state.shard_states[shard].latest_block_root - for i in range(len(offset_slots)): - if any(transition.shard_data_roots): - headers.append(ShardBlockHeader( - shard_parent_root=shard_parent_root, - beacon_parent_root=get_block_root_at_slot(state, get_previous_slot(state.slot)), - slot=offset_slots[i], - body_root=transition.shard_data_roots[i] - )) - proposers.append(get_shard_proposer_index(state, shard, offset_slots[i])) - shard_parent_root = hash_tree_root(headers[-1]) - - # Verify correct calculation of gas prices and slots prev_gasprice = state.shard_states[shard].gasprice + shard_parent_root = state.shard_states[shard].latest_block_root + beacon_parent_root = get_block_root_at_slot(state, get_previous_slot(state.slot)) for i in range(len(offset_slots)): + shard_block_length = transition.shard_block_lengths[i] + is_empty_proposal = (shard_block_length == 0) shard_state = transition.shard_states[i] - block_length = transition.shard_block_lengths[i] - assert shard_state.gasprice == get_updated_gasprice(prev_gasprice, block_length) - assert shard_state.slot == offset_slots[i] - prev_gasprice = shard_state.gasprice + proposal_index = get_shard_proposer_index(state, offset_slots[i], shard) + # Reconstruct shard headers + header = ShardBlockHeader( + shard_parent_root=shard_parent_root, + beacon_parent_root=beacon_parent_root, + proposer_index=proposal_index, + slot=offset_slots[i], + body_root=transition.shard_data_roots[i] + ) + shard_parent_root = hash_tree_root(header) + + if not is_empty_proposal: + # Only add non-empty signature + headers.append(header) + proposers.append(proposal_index) + # Verify correct calculation of gas prices and slots + assert shard_state.gasprice == get_updated_gasprice(prev_gasprice, shard_block_length) + assert shard_state.slot == offset_slots[i] + prev_gasprice = shard_state.gasprice pubkeys = [state.validators[proposer].pubkey for proposer in proposers] signing_roots = [ @@ -745,7 +752,8 @@ def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTr assert bls.AggregateVerify(zip(pubkeys, signing_roots), signature=transition.proposer_signature_aggregate) # Save updated state - state.shard_states[shard] = transition.shard_states[-1] + state.shard_states[shard] = transition.shard_states[len(transition.shard_states) - 1] + assert state.slot > 0 state.shard_states[shard].slot = state.slot - 1 ``` @@ -779,7 +787,9 @@ def process_crosslink_for_shard(state: BeaconState, # Attestation <-> shard transition consistency assert shard_transition_root == hash_tree_root(shard_transition) - assert attestation.data.head_shard_root == shard_transition.shard_data_roots[-1] + assert attestation.data.head_shard_root == shard_transition.shard_data_roots[ + len(shard_transition.shard_data_roots) - 1 + ] # Apply transition apply_shard_transition(state, shard, shard_transition) @@ -790,11 +800,11 @@ def process_crosslink_for_shard(state: BeaconState, increase_balance(state, beacon_proposer_index, proposer_reward) states_slots_lengths = zip( shard_transition.shard_states, - get_offset_slots(state, get_latest_slot_for_shard(state, shard)), + get_offset_slots(state, shard), shard_transition.shard_block_lengths ) for shard_state, slot, length in states_slots_lengths: - proposer_index = get_shard_proposer_index(state, shard, slot) + proposer_index = get_shard_proposer_index(state, slot, shard) decrease_balance(state, proposer_index, shard_state.gasprice * length) # Return winning transition root @@ -913,14 +923,13 @@ def process_light_client_signatures(state: BeaconState, block_body: BeaconBlockB total_reward += get_base_reward(state, participant_index) increase_balance(state, get_beacon_proposer_index(state), Gwei(total_reward // PROPOSER_REWARD_QUOTIENT)) - + slot = get_previous_slot(state.slot) - signing_root = compute_signing_root(get_block_root_at_slot(state, slot), + signing_root = compute_signing_root(get_block_root_at_slot(state, slot), get_domain(state, DOMAIN_LIGHT_CLIENT, compute_epoch_at_slot(slot))) assert bls.FastAggregateVerify(signer_pubkeys, signing_root, signature=block_body.light_client_signature) ``` - ### Epoch transition This epoch transition overrides the phase0 epoch transition: diff --git a/specs/phase1/fraud-proofs.md b/specs/phase1/fraud-proofs.md index e72a8bb2a..30b94d810 100644 --- a/specs/phase1/fraud-proofs.md +++ b/specs/phase1/fraud-proofs.md @@ -9,6 +9,8 @@ - [Shard state transition function](#shard-state-transition-function) - [Verifying the proof](#verifying-the-proof) - [Honest committee member behavior](#honest-committee-member-behavior) + - [Helper functions](#helper-functions) + - [Make attestations](#make-attestations) @@ -30,76 +32,76 @@ This document describes the shard transition function and fraud proofs as part o ## Fraud proofs -TODO. The intent is to have a single universal fraud proof type, which contains the following parts: - -1. An on-time attestation on some `shard` signing a `ShardTransition` -2. An index `index` of a particular position to focus on -3. The `ShardTransition` itself -4. The full body of the block `ShardBlock` -5. A Merkle proof to the `shard_states` in the parent block `parent_block` the attestation is referencing - ### Shard state transition function ```python def shard_state_transition(beacon_state: BeaconState, shard: Shard, slot: Slot, - pre_state: Root, - previous_beacon_root: Root, - proposer_index: ValidatorIndex, - signed_block: SignedShardBlock, - validate_result: bool=True) -> Root: - # We will add something more substantive in phase 2 - - # Verify the proposer_index and signature - assert proposer_index == signed_block.message.proposer_index - if validate_result: - assert verify_shard_block_signature(beacon_state, signed_block) - - return hash(pre_state + hash_tree_root(previous_beacon_root) + hash_tree_root(signed_block.message.data)) + shard_state: ShardState, + beacon_parent_root: Root, + signed_block: SignedShardBlock) -> None: + # Update shard state + shard_state.data = hash( + hash_tree_root(shard_state) + hash_tree_root(beacon_parent_root) + hash_tree_root(signed_block.message.body) + ) + shard_state.slot = slot + shard_state.latest_block_root = hash_tree_root(signed_block.message) ``` ```python def verify_shard_block_signature(beacon_state: BeaconState, signed_block: SignedShardBlock) -> bool: proposer = beacon_state.validators[signed_block.message.proposer_index] - signing_root = compute_signing_root(signed_block.message, get_domain(beacon_state, DOMAIN_SHARD_PROPOSAL)) + domain = get_domain(beacon_state, DOMAIN_SHARD_PROPOSAL, compute_epoch_at_slot(signed_block.message.slot)) + signing_root = compute_signing_root(signed_block.message, domain) return bls.Verify(proposer.pubkey, signing_root, signed_block.signature) ``` ### Verifying the proof +TODO. The intent is to have a single universal fraud proof type, which contains the following parts: + +1. An on-time attestation `attestation` on some shard `shard` signing a `transition: ShardTransition` +2. An index `offset_index` of a particular position to focus on +3. The `transition: ShardTransition` itself +4. The full body of the shard block `shard_block` +5. A Merkle proof to the `shard_states` in the parent block the attestation is referencing + +Call the following function to verify the proof: + ```python def verify_fraud_proof(beacon_state: BeaconState, - subkey: BLSPubkey, attestation: Attestation, - index: uint64, + offset_index: uint64, transition: ShardTransition, signed_block: SignedShardBlock, - parent_block: ShardBlock) -> bool: - # 1. Check if `custody_bits[index][j] != generate_custody_bit(subkey, block_contents)` for any `j` + subkey: BLSPubkey, + beacon_parent_block: BeaconBlock) -> bool: + # 1. Check if `custody_bits[offset_index][j] != generate_custody_bit(subkey, block_contents)` for any `j`. shard = get_shard(beacon_state, attestation) slot = attestation.data.slot custody_bits = attestation.custody_bits_blocks - for j in range(custody_bits[index]): - if custody_bits[index][j] != generate_custody_bit(subkey, signed_block): + for j in range(custody_bits[offset_index]): + if custody_bits[offset_index][j] != generate_custody_bit(subkey, signed_block): return True - # 2. Verify the shard state transition - if index == 0: - parent_data = parent_block.shard_states[shard][-1].data + # 2. Check if the shard state transition result is wrong between + # `transition.shard_states[offset_index - 1]` to `transition.shard_states[offset_index]`. + if offset_index == 0: + shard_state = beacon_parent_block.shard_transitions[shard][-1] else: - parent_data = parent_block.shard_states[shard][index].data + shard_state = transition.shard_states[offset_index - 1].copy() # Not doing the actual state updates here. - if shard_state_transition( - beacon_state, - shard, - slot, - transition.shard_states[index - 1].data, - hash_tree_root(parent_block), - get_shard_proposer_index(beacon_state, slot, shard), - signed_block, - ) != parent_data: + shard_state_transition( + beacon_state=beacon_state, + shard=shard, + slot=slot, + shard_state=shard_state, + beacon_parent_root=hash_tree_root(beacon_parent_block), + signed_block=signed_block, + ) + if shard_state.latest_block_root != transition.shard_states[offset_index].data: return True return False @@ -113,14 +115,141 @@ def generate_custody_bit(subkey: BLSPubkey, block: ShardBlock) -> bool: ## Honest committee member behavior -Suppose you are a committee member on shard `shard` at slot `current_slot`. Let `state` be the head beacon state you are building on, and let `QUARTER_PERIOD = SECONDS_PER_SLOT // 4`. `2 * QUARTER_PERIOD` seconds into slot `slot`, run the following procedure: +### Helper functions -* Initialize `proposals = []`, `shard_states = []`, `shard_state = state.shard_states[shard][-1]`, `start_slot = shard_state.slot`. -* For `slot in get_offset_slots(state, start_slot)`, do the following: - * Look for all valid proposals for `slot`; that is, a SignedShardBlock `proposal` where `shard_state_transition(shard, slot, shard_state, get_block_root_at_slot(state, state.slot - 1), get_shard_proposer_index(state, slot, shard), proposal)` returns a result and does not throw an exception. Let `choices` be the set of non-empty valid proposals you discover. - * If `len(choices) == 0`, do `proposals.append(make_empty_proposal(shard_state, slot))` - * If `len(choices) == 1`, do `proposals.append(choices[0])` - * If `len(choices) > 1`, let `winning_proposal` be the proposal with the largest number of total attestations from slots in `state.shard_next_slots[shard]....slot-1` supporting it or any of its descendants, breaking ties by choosing the first proposal locally seen. Do `proposals.append(winning_proposal)`. - * If `proposals.message.data[-1]` is NOT an empty proposal, set `shard_state = shard_state_transition(shard, slot, shard_state, get_block_root_at_slot(state, state.slot - 1), get_shard_proposer_index(state, slot, shard), proposals[-1])` and do `shard_states.append(shard_state)`. If it is an empty proposal, leave `shard_state` unchanged. +```python +def get_winning_proposal(beacon_state: BeaconState, proposals: Sequence[SignedShardBlock]) -> SignedShardBlock: + # TODO: Let `winning_proposal` be the proposal with the largest number of total attestationsfrom slots in + # `state.shard_next_slots[shard]....slot-1` supporting it or any of its descendants, breaking ties by choosing + # the first proposal locally seen. Do `proposals.append(winning_proposal)`. + return proposals[-1] # stub +``` -Make an attestation using `shard_data_roots = [hash_tree_root(proposal.message.data) for proposal in proposals]` and `shard_state_roots = shard_states`. +```python +def get_empty_body_block(shard_parent_root: Root, + beacon_parent_root: Root, + slot: Slot, + proposer_index: ValidatorIndex) -> ShardBlock: + return ShardBlock( + shard_parent_root=shard_parent_root, + beacon_parent_root=beacon_parent_root, + slot=slot, + proposer_index=proposer_index, + ) +``` + +```python +def is_empty_body(proposal: ShardBlock) -> bool: + # TODO + return len(proposal.body) == 0 +``` + +```python +def compute_shard_data_roots(proposals: Sequence[SignedShardBlock]) -> Sequence[Root]: + return [hash_tree_root(proposal.message.body) for proposal in proposals] +``` + +### Make attestations + +Suppose you are a committee member on shard `shard` at slot `current_slot` and you have received shard blocks `shard_blocks`. Let `state` be the head beacon state you are building on, and let `QUARTER_PERIOD = SECONDS_PER_SLOT // 4`. `2 * QUARTER_PERIOD` seconds into slot `current_slot`, run `get_shard_transition(beacon_state, shard, shard_blocks)` to get `shard_transition`. + +```python +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: + shard_block_lengths.append(len(proposal.message.body)) + if proposal.signature != BLSSignature(): + proposer_signatures.append(proposal.signature) + + proposer_signature_aggregate = bls.Aggregate(proposer_signatures) + + return ShardTransition( + start_slot=start_slot, + shard_block_lengths=shard_block_lengths, + shard_data_roots=shard_data_roots, + shard_states=shard_states, + proposer_signature_aggregate=proposer_signature_aggregate, + ) +``` + +```python +def get_shard_state_transition_result( + beacon_state: BeaconState, + shard: Shard, + shard_blocks: Sequence[SignedShardBlock], + validate_result: bool=True, +) -> Tuple[Sequence[SignedShardBlock], Sequence[ShardState], Sequence[Root]]: + proposals = [] + shard_states = [] + shard_state = beacon_state.shard_states[shard].copy() + shard_parent_root = beacon_state.shard_states[shard].latest_block_root + for slot in get_offset_slots(beacon_state, shard): + choices = [] + beacon_parent_root = get_block_root_at_slot(beacon_state, get_previous_slot(beacon_state.slot)) + proposer_index = get_shard_proposer_index(beacon_state, slot, shard) + shard_blocks_at_slot = [block for block in shard_blocks if block.message.slot == slot] + for block in shard_blocks_at_slot: + temp_shard_state = shard_state.copy() # Not doing the actual state updates here. + # Try to apply state transition to temp_shard_state. + try: + # Verify the proposer_index and signature + assert block.message.proposer_index == proposer_index + if validate_result: + assert verify_shard_block_signature(beacon_state, block) + + shard_state_transition( + beacon_state=beacon_state, + shard=shard, + slot=slot, + shard_state=temp_shard_state, + beacon_parent_root=beacon_parent_root, + signed_block=block, + ) + except Exception: + pass # TODO: throw error in the test helper + else: + choices.append(block) + + if len(choices) == 0: + block_header = get_empty_body_block( + shard_parent_root=shard_parent_root, + beacon_parent_root=beacon_parent_root, + slot=slot, + proposer_index=proposer_index, + ) + block = SignedShardBlock(message=block_header) + proposals.append(block) + elif len(choices) == 1: + proposals.append(choices[0]) + else: + proposals.append(get_winning_proposal(beacon_state, choices)) + + shard_parent_root = hash_tree_root(proposals[-1].message) + + if not is_empty_body(proposals[-1].message): + # Apply state transition to shard_state. + shard_state_transition( + beacon_state=beacon_state, + shard=shard, + slot=slot, + shard_state=shard_state, + beacon_parent_root=beacon_parent_root, + signed_block=block, + ) + + shard_states.append(shard_state) + + shard_data_roots = compute_shard_data_roots(proposals) + + return proposals, shard_states, shard_data_roots +``` diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index 8215f5c5b..c203b737a 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -1,6 +1,6 @@ from typing import List -from eth2spec.test.context import expect_assertion_error, PHASE0 +from eth2spec.test.context import expect_assertion_error, PHASE0, PHASE1 from eth2spec.test.helpers.state import state_transition_and_sign_block from eth2spec.test.helpers.block import build_empty_block_for_next_slot from eth2spec.test.helpers.keys import privkeys @@ -43,7 +43,7 @@ def run_attestation_processing(spec, state, attestation, valid=True): yield 'post', state -def build_attestation_data(spec, state, slot, index): +def build_attestation_data(spec, state, slot, index, shard_transition=None): assert state.slot >= slot if slot == state.slot: @@ -66,12 +66,21 @@ def build_attestation_data(spec, state, slot, index): source_epoch = state.current_justified_checkpoint.epoch source_root = state.current_justified_checkpoint.root + if spec.fork == PHASE1 and shard_transition is not None: + shard_transition_root = shard_transition.hash_tree_root() + head_shard_root = shard_transition.shard_data_roots[len(shard_transition.shard_data_roots) - 1] + else: + shard_transition_root = spec.Root() + head_shard_root = spec.Root() + return spec.AttestationData( slot=slot, index=index, beacon_block_root=block_root, source=spec.Checkpoint(epoch=source_epoch, root=source_root), target=spec.Checkpoint(epoch=spec.compute_epoch_at_slot(slot), root=epoch_boundary_root), + head_shard_root=head_shard_root, + shard_transition_root=shard_transition_root, ) @@ -89,7 +98,7 @@ def convert_to_valid_on_time_attestation(spec, state, attestation, signed=False) return attestation -def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=False): +def get_valid_on_time_attestation(spec, state, slot=None, index=None, shard_transition=None, signed=False): ''' Construct on-time attestation for next slot ''' @@ -98,7 +107,15 @@ def get_valid_on_time_attestation(spec, state, slot=None, index=None, signed=Fal if index is None: index = 0 - return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=True) + return get_valid_attestation( + spec, + state, + slot=slot, + index=index, + shard_transition=shard_transition, + signed=signed, + on_time=True, + ) def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False): @@ -113,13 +130,20 @@ def get_valid_late_attestation(spec, state, slot=None, index=None, signed=False) return get_valid_attestation(spec, state, slot=slot, index=index, signed=signed, on_time=False) -def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signed=False, on_time=True): +def get_valid_attestation(spec, + state, + slot=None, + index=None, + shard_transition=None, + empty=False, + signed=False, + on_time=True): if slot is None: slot = state.slot if index is None: index = 0 - attestation_data = build_attestation_data(spec, state, slot, index) + attestation_data = build_attestation_data(spec, state, slot=slot, index=index, shard_transition=shard_transition) beacon_committee = spec.get_beacon_committee( state, @@ -138,7 +162,7 @@ def get_valid_attestation(spec, state, slot=None, index=None, empty=False, signe if signed: sign_attestation(spec, state, attestation) - if spec.fork == 'phase1' and on_time: + if spec.fork == PHASE1 and on_time: attestation = convert_to_valid_on_time_attestation(spec, state, attestation, signed) return attestation @@ -210,7 +234,7 @@ def get_attestation_custody_signature(spec, state, attestation_data, block_index def sign_attestation(spec, state, attestation): - if spec.fork == 'phase1' and any(attestation.custody_bits_blocks): + if spec.fork == PHASE1 and any(attestation.custody_bits_blocks): sign_on_time_attestation(spec, state, attestation) return diff --git a/tests/core/pyspec/eth2spec/test/helpers/crosslinks.py b/tests/core/pyspec/eth2spec/test/helpers/crosslinks.py new file mode 100644 index 000000000..ea5da89d9 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/crosslinks.py @@ -0,0 +1,28 @@ +from eth2spec.test.context import expect_assertion_error + + +def run_crosslinks_processing(spec, state, shard_transitions, attestations, valid=True): + """ + Run ``process_attestation``, yielding: + - pre-state ('pre') + - shard_transitions ('shard_transitions') + - attestations ('attestations') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + # yield pre-state + yield 'pre', state + yield 'shard_transitions', shard_transitions + yield 'attestations', attestations + + # 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)) + yield 'post', None + return + + # process crosslinks + spec.process_crosslinks(state, shard_transitions, attestations) + + # yield post-state + yield 'post', state diff --git a/tests/core/pyspec/eth2spec/test/helpers/phase1/__init__.py b/tests/core/pyspec/eth2spec/test/helpers/phase1/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py deleted file mode 100644 index 0e16e1fac..000000000 --- a/tests/core/pyspec/eth2spec/test/helpers/phase1/attestations.py +++ /dev/null @@ -1,63 +0,0 @@ -from eth2spec.utils.ssz.ssz_typing import Bitlist -from eth2spec.utils import bls - -from eth2spec.test.helpers.keys import privkeys -import eth2spec.test.helpers.attestations as phase0_attestations - - -def get_valid_on_time_attestation(spec, state, index=None, signed=False): - ''' - Construct on-time attestation for next slot - ''' - if index is None: - index = 0 - - attestation = phase0_attestations.get_valid_attestation(spec, state, state.slot, index, False) - shard = spec.get_shard(state, attestation) - offset_slots = spec.compute_offset_slots(spec.get_latest_slot_for_shard(state, shard), state.slot + 1) - - for _ in offset_slots: - attestation.custody_bits_blocks.append( - Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]([0 for _ in attestation.aggregation_bits]) - ) - - if signed: - sign_attestation(spec, state, attestation) - - return attestation - - -def sign_attestation(spec, state, attestation): - if not any(attestation.custody_bits_blocks): - phase0_attestations.sign_attestation(spec, state, attestation) - return - - committee = spec.get_beacon_committee(state, attestation.data.slot, attestation.data.index) - signatures = [] - for block_index, custody_bits in enumerate(attestation.custody_bits_blocks): - for participant, abit, cbit in zip(committee, attestation.aggregation_bits, custody_bits): - if not abit: - continue - signatures.append(get_attestation_custody_signature( - spec, - state, - attestation.data, - block_index, - cbit, - privkeys[participant] - )) - - attestation.signature = bls.Aggregate(signatures) - - -def get_attestation_custody_signature(spec, state, attestation_data, block_index, bit, privkey): - domain = spec.get_domain(state, spec.DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch) - signing_root = spec.compute_signing_root( - spec.AttestationCustodyBitWrapper( - attestation_data.hash_tree_root(), - block_index, - bit, - ), - domain, - ) - return bls.Sign(privkey, signing_root) diff --git a/tests/core/pyspec/eth2spec/test/helpers/phase1/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/phase1/shard_block.py deleted file mode 100644 index 6ef0cf79b..000000000 --- a/tests/core/pyspec/eth2spec/test/helpers/phase1/shard_block.py +++ /dev/null @@ -1,71 +0,0 @@ -from eth2spec.test.helpers.keys import privkeys -from eth2spec.utils import bls -from eth2spec.utils.bls import only_with_bls -from eth2spec.utils.ssz.ssz_impl import ( - hash_tree_root, -) - -from .attestations import ( - sign_shard_attestation, -) - - -@only_with_bls() -def sign_shard_block(spec, beacon_state, shard_state, block, proposer_index=None): - if proposer_index is None: - proposer_index = spec.get_shard_proposer_index(beacon_state, shard_state.shard, block.slot) - - privkey = privkeys[proposer_index] - domain = spec.get_domain(beacon_state, spec.DOMAIN_SHARD_PROPOSER, spec.compute_epoch_of_shard_slot(block.slot)) - signing_root = spec.compute_signing_root(block, domain) - block.signature = bls.Sign(privkey, signing_root) - - -def build_empty_shard_block(spec, - beacon_state, - shard_state, - slot, - signed=False, - full_attestation=False): - if slot is None: - slot = shard_state.slot - - previous_beacon_header = beacon_state.latest_block_header.copy() - if previous_beacon_header.state_root == spec.Bytes32(): - previous_beacon_header.state_root = beacon_state.hash_tree_root() - beacon_block_root = hash_tree_root(previous_beacon_header) - - previous_block_header = shard_state.latest_block_header.copy() - if previous_block_header.state_root == spec.Bytes32(): - previous_block_header.state_root = shard_state.hash_tree_root() - parent_root = hash_tree_root(previous_block_header) - - block = spec.ShardBlock( - shard=shard_state.shard, - slot=slot, - beacon_block_root=beacon_block_root, - parent_root=parent_root, - block_size_sum=shard_state.block_size_sum + spec.SHARD_HEADER_SIZE, - ) - - if full_attestation: - shard_committee = spec.get_shard_committee(beacon_state, shard_state.shard, block.slot) - block.aggregation_bits = list( - (True,) * len(shard_committee) + - (False,) * (spec.MAX_PERIOD_COMMITTEE_SIZE * 2 - len(shard_committee)) - ) - else: - shard_committee = [] - - block.attestations = sign_shard_attestation( - spec, - beacon_state, - shard_state, - block, - participants=shard_committee, - ) - - if signed: - sign_shard_block(spec, beacon_state, shard_state, block) - - return block diff --git a/tests/core/pyspec/eth2spec/test/helpers/phase1/shard_state.py b/tests/core/pyspec/eth2spec/test/helpers/phase1/shard_state.py deleted file mode 100644 index 24240b5fa..000000000 --- a/tests/core/pyspec/eth2spec/test/helpers/phase1/shard_state.py +++ /dev/null @@ -1,18 +0,0 @@ -from eth2spec.test.helpers.phase1.shard_block import sign_shard_block - - -def configure_shard_state(spec, beacon_state, shard=0): - beacon_state.slot = spec.Slot(spec.SHARD_GENESIS_EPOCH * spec.SLOTS_PER_EPOCH) - shard_state = spec.get_genesis_shard_state(spec.Shard(shard)) - shard_state.slot = spec.ShardSlot(spec.SHARD_GENESIS_EPOCH * spec.SHARD_SLOTS_PER_EPOCH) - return beacon_state, shard_state - - -def shard_state_transition_and_sign_block(spec, beacon_state, shard_state, block): - """ - Shard state transition via the provided ``block`` - then package the block with the state root and signature. - """ - spec.shard_state_transition(beacon_state, shard_state, block) - block.state_root = shard_state.hash_tree_root() - sign_shard_block(spec, beacon_state, shard_state, block) diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py new file mode 100644 index 000000000..0cb8a3dfd --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py @@ -0,0 +1,47 @@ +from eth2spec.test.helpers.keys import privkeys +from eth2spec.utils import bls +from eth2spec.utils.bls import only_with_bls + + +@only_with_bls() +def sign_shard_block(spec, beacon_state, shard, block, proposer_index=None): + slot = block.message.slot + if proposer_index is None: + proposer_index = spec.get_shard_proposer_index(beacon_state, slot, shard) + + privkey = privkeys[proposer_index] + domain = spec.get_domain(beacon_state, spec.DOMAIN_SHARD_PROPOSAL, spec.compute_epoch_at_slot(slot)) + signing_root = spec.compute_signing_root(block.message, domain) + block.signature = bls.Sign(privkey, signing_root) + + +def build_empty_shard_block(spec, + beacon_state, + shard, + slot, + body=None, + signed=False): + shard_state = beacon_state.shard_states[shard] + if slot is None: + slot = shard_state.slot + + if body is None: + body = [] + + proposer_index = spec.get_shard_proposer_index(beacon_state, slot, shard) + + block = spec.ShardBlock( + shard_parent_root=shard_state.latest_block_root, + beacon_parent_root=spec.get_block_root_at_slot(beacon_state, spec.get_previous_slot(beacon_state.slot)), + slot=slot, + proposer_index=proposer_index, + body=body, + ) + signed_block = spec.SignedShardBlock( + message=block, + ) + + if signed: + sign_shard_block(spec, beacon_state, shard, signed_block, proposer_index=proposer_index) + + return signed_block 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 new file mode 100644 index 000000000..1d5fe7c02 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase_1/block_processing/test_process_crosslink.py @@ -0,0 +1,52 @@ +from eth2spec.test.context import ( + with_all_phases_except, + spec_state_test, + always_bls, +) +from eth2spec.test.helpers.attestations import ( + get_valid_on_time_attestation, +) +from eth2spec.test.helpers.crosslinks import ( + run_crosslinks_processing, +) +from eth2spec.test.helpers.shard_block import build_empty_shard_block +from eth2spec.test.helpers.state import next_epoch, next_slot + + +@with_all_phases_except(['phase0']) +@spec_state_test +@always_bls +def test_basic_crosslinks(spec, state): + next_epoch(spec, state) + next_epoch(spec, state) + state = spec.upgrade_to_phase1(state) + next_slot(spec, state) + + committee_index = spec.CommitteeIndex(0) + shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) + shard_block = build_empty_shard_block(spec, state, shard, body=b'\x12' * 10, slot=state.slot, signed=True) + shard_blocks = [shard_block] + + next_slot(spec, state) + + shard_transition = spec.get_shard_transition(state, shard, shard_blocks) + shard_transitions = [spec.ShardTransition()] * len(state.shard_states) + shard_transitions[shard] = shard_transition + + attestation = get_valid_on_time_attestation( + spec, + state, + slot=state.slot, + index=committee_index, + shard_transition=shard_transition, + signed=True, + ) + attestations = [attestation] + + offset_slots = spec.get_offset_slots(state, shard) + + yield from run_crosslinks_processing(spec, state, shard_transitions, attestations) + + shard_state = state.shard_states[shard] + assert shard_state.slot == offset_slots[-1] + assert shard_state.latest_block_root == shard_block.message.hash_tree_root()