diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index 5a5200c9e..36abac85f 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -1,4 +1,4 @@ -# Ethereum 2.0 Phase 1 -- The Beacon Chain for Shards +# Ethereum 2.0 Phase 1 -- The Beacon Chain with Shards **Notice**: This document is a work-in-progress for researchers and implementers. diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index 05f986000..09e603c58 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -11,7 +11,9 @@ - [Introduction](#introduction) - [Helper functions](#helper-functions) - [Shard block verification functions](#shard-block-verification-functions) -- [Shard state transition](#shard-state-transition) + - [`verify_shard_block_message`](#verify_shard_block_message) + - [`verify_shard_block_signature`](#verify_shard_block_signature) +- [Shard state transition function](#shard-state-transition-function) - [Fraud proofs](#fraud-proofs) - [Verifying the proof](#verifying-the-proof) @@ -25,6 +27,8 @@ This document describes the shard transition function and fraud proofs as part o ### Shard block verification functions +#### `verify_shard_block_message` + ```python def verify_shard_block_message(beacon_parent_state: BeaconState, shard_parent_state: ShardState, @@ -49,6 +53,8 @@ def verify_shard_block_message(beacon_parent_state: BeaconState, return True ``` +#### `verify_shard_block_signature` + ```python def verify_shard_block_signature(beacon_parent_state: BeaconState, signed_block: SignedShardBlock) -> bool: @@ -58,7 +64,9 @@ def verify_shard_block_signature(beacon_parent_state: BeaconState, return bls.Verify(proposer.pubkey, signing_root, signed_block.signature) ``` -## Shard state transition +## Shard state transition function + +The post-state corresponding to a pre-state `shard_state` and a signed block `signed_block` is defined as `shard_state_transition(state, signed_block)`. State transitions that trigger an unhandled exception (e.g. a failed `assert` or an out-of-range list access) are considered invalid. State transitions that cause a `uint64` overflow or underflow are also considered invalid. ```python def shard_state_transition(shard_state: ShardState, diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py b/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py index d10d1ee7b..6e508e397 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_transitions.py @@ -35,3 +35,8 @@ def get_shard_transition_of_committee(spec, state, committee_index, shard_blocks shard = spec.compute_shard_from_committee_index(state, committee_index, state.slot) shard_transition = spec.get_shard_transition(state, shard, shard_blocks=shard_blocks) return shard_transition + + +def is_full_crosslink(spec, state): + epoch = spec.compute_epoch_at_slot(state.slot) + return spec.get_committee_count_per_slot(state, epoch) >= spec.get_active_shard_count(state) diff --git a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py index b14b836c0..866831686 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py +++ b/tests/core/pyspec/eth2spec/test/phase1/block_processing/test_process_shard_transition.py @@ -8,7 +8,10 @@ from eth2spec.test.helpers.attestations import ( get_valid_on_time_attestation, run_attestation_processing, ) -from eth2spec.test.helpers.shard_transitions import run_shard_transitions_processing +from eth2spec.test.helpers.shard_transitions import ( + run_shard_transitions_processing, + is_full_crosslink, +) from eth2spec.test.helpers.shard_block import ( build_shard_block, get_shard_transitions, @@ -42,11 +45,6 @@ def get_attestations_and_shard_transitions(spec, state, shard_block_dict): return attestations, shard_transitions -def is_full_crosslink(spec, state): - epoch = spec.compute_epoch_at_slot(state.slot) - return spec.get_committee_count_per_slot(state, epoch) >= spec.get_active_shard_count(state) - - def run_successful_crosslink_tests(spec, state, target_len_offset_slot): state, shard, target_shard_slot = get_initial_env(spec, state, target_len_offset_slot) init_slot = state.slot diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py index aa24bfc1e..3cb2fe6c0 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_blocks.py @@ -9,15 +9,17 @@ from eth2spec.test.helpers.attestations import get_valid_on_time_attestation from eth2spec.test.helpers.block import build_empty_block from eth2spec.test.helpers.shard_block import ( build_shard_block, + get_sample_shard_block_body, get_shard_transitions, ) +from eth2spec.test.helpers.shard_transitions import is_full_crosslink from eth2spec.test.helpers.state import state_transition_and_sign_block, transition_to_valid_shard_slot, transition_to def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, committee_index, shard, valid=True): transition_to(spec, state, state.slot + target_len_offset_slot) - body = b'\x56' * spec.MAX_SHARD_BLOCK_SIZE + body = get_sample_shard_block_body(spec, is_max=True) shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, signed=True) shard_block_dict: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]} @@ -40,13 +42,16 @@ def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, comm pre_gasprice = state.shard_states[shard].gasprice pre_shard_states = state.shard_states.copy() yield 'pre', state.copy() - yield 'block', beacon_block - state_transition_and_sign_block(spec, state, beacon_block) - if valid: - yield 'post', state - else: + + if not valid: + state_transition_and_sign_block(spec, state, beacon_block, expect_fail=True) + yield 'block', beacon_block yield 'post', None return + else: + state_transition_and_sign_block(spec, state, beacon_block) + yield 'block', beacon_block + yield 'post', None for shard in range(spec.get_active_shard_count(state)): post_shard_state = state.shard_states[shard] @@ -67,6 +72,10 @@ def run_beacon_block_with_shard_blocks(spec, state, target_len_offset_slot, comm @spec_state_test def test_process_beacon_block_with_normal_shard_transition(spec, state): # NOTE: this test is only for full crosslink (minimal config), not for mainnet + if not is_full_crosslink(spec, state): + # skip + return + state = transition_to_valid_shard_slot(spec, state) target_len_offset_slot = 1 @@ -81,6 +90,10 @@ def test_process_beacon_block_with_normal_shard_transition(spec, state): @spec_state_test def test_process_beacon_block_with_empty_proposal_transition(spec, state): # NOTE: this test is only for full crosslink (minimal config), not for mainnet + if not is_full_crosslink(spec, state): + # skip + return + state = transition_to_valid_shard_slot(spec, state) target_len_offset_slot = 1 diff --git a/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py new file mode 100644 index 000000000..40d7e0650 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase1/sanity/test_shard_blocks.py @@ -0,0 +1,151 @@ +from eth2spec.test.context import ( + PHASE0, + always_bls, + expect_assertion_error, + spec_state_test, + with_all_phases_except, +) +from eth2spec.test.helpers.shard_block import ( + build_shard_block, + sign_shard_block, +) +from eth2spec.test.helpers.shard_transitions import is_full_crosslink +from eth2spec.test.helpers.state import transition_to_valid_shard_slot + + +def run_shard_blocks(spec, shard_state, signed_shard_block, + beacon_parent_state, + validate=True, valid=True): + yield 'pre', shard_state.copy() + yield 'signed_shard_block', signed_shard_block + yield 'validate', validate + yield 'beacon_parent_state', beacon_parent_state + + if validate is False: + beacon_parent_state = None + + if not valid: + expect_assertion_error(lambda: spec.shard_state_transition( + shard_state, signed_shard_block, validate=validate, beacon_parent_state=beacon_parent_state) + ) + yield 'post', None + else: + spec.shard_state_transition(shard_state, signed_shard_block, + validate=validate, beacon_parent_state=beacon_parent_state) + yield 'post', shard_state + + +@with_all_phases_except([PHASE0]) +@spec_state_test +@always_bls +def test_valid_shard_block(spec, state): + if not is_full_crosslink(spec, state): + # skip + return + + beacon_state = transition_to_valid_shard_slot(spec, state) + shard = 0 + shard_state = beacon_state.shard_states[shard] + signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True) + + yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state) + + +# +# verify_shard_block_message +# + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_invalid_shard_parent_root(spec, state): + if not is_full_crosslink(spec, state): + # skip + return + + beacon_state = transition_to_valid_shard_slot(spec, state) + shard = 0 + shard_state = beacon_state.shard_states[shard] + signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True) + signed_shard_block.message.shard_parent_root = b'\x12' * 32 + sign_shard_block(spec, beacon_state, shard, signed_shard_block) + + yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False) + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_invalid_beacon_parent_root(spec, state): + if not is_full_crosslink(spec, state): + # skip + return + + beacon_state = transition_to_valid_shard_slot(spec, state) + shard = 0 + shard_state = beacon_state.shard_states[shard] + signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True) + signed_shard_block.message.beacon_parent_root = b'\x12' * 32 + sign_shard_block(spec, beacon_state, shard, signed_shard_block) + + yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False) + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_invalid_slot(spec, state): + if not is_full_crosslink(spec, state): + # skip + return + + beacon_state = transition_to_valid_shard_slot(spec, state) + shard = 0 + shard_state = beacon_state.shard_states[shard] + signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True) + signed_shard_block.message.slot = beacon_state.slot + 1 + proposer_index = spec.get_shard_proposer_index(beacon_state, signed_shard_block.message.slot, shard) + sign_shard_block(spec, beacon_state, shard, signed_shard_block, proposer_index=proposer_index) + + yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False) + + +@with_all_phases_except([PHASE0]) +@spec_state_test +def test_invalid_proposer_index(spec, state): + if not is_full_crosslink(spec, state): + # skip + return + + beacon_state = transition_to_valid_shard_slot(spec, state) + shard = 0 + shard_state = beacon_state.shard_states[shard] + signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=True) + active_validator_indices = spec.get_active_validator_indices(beacon_state, spec.get_current_epoch(beacon_state)) + proposer_index = ( + (spec.get_shard_proposer_index(beacon_state, signed_shard_block.message.slot, shard) + 1) + % len(active_validator_indices) + ) + signed_shard_block.message.proposer_index = proposer_index + sign_shard_block(spec, beacon_state, shard, signed_shard_block, proposer_index=proposer_index) + + yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False) + + +# +# verify_shard_block_signature +# + + +@with_all_phases_except([PHASE0]) +@spec_state_test +@always_bls +def test_invalid_signature(spec, state): + if not is_full_crosslink(spec, state): + # skip + return + + beacon_state = transition_to_valid_shard_slot(spec, state) + shard = 0 + shard_state = beacon_state.shard_states[shard] + signed_shard_block = build_shard_block(spec, beacon_state, shard, slot=beacon_state.slot, signed=False) + + yield from run_shard_blocks(spec, shard_state, signed_shard_block, beacon_state, valid=False)