diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/run_epoch_process_base.py b/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/phase0/epoch_processing/run_epoch_process_base.py rename to tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py diff --git a/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py b/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py new file mode 100644 index 000000000..b7b2381e3 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py @@ -0,0 +1,33 @@ +from eth2spec.test.helpers.keys import privkeys +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.utils import bls + + +def compute_sync_committee_signature(spec, state, slot, privkey): + domain = spec.get_domain(state, spec.DOMAIN_SYNC_COMMITTEE, spec.compute_epoch_at_slot(slot)) + if slot == state.slot: + block_root = build_empty_block_for_next_slot(spec, state).parent_root + else: + block_root = spec.get_block_root_at_slot(state, slot) + signing_root = spec.compute_signing_root(block_root, domain) + return bls.Sign(privkey, signing_root) + + +def compute_aggregate_sync_committee_signature(spec, state, slot, participants): + if len(participants) == 0: + return spec.G2_POINT_AT_INFINITY + + signatures = [] + for validator_index in participants: + privkey = privkeys[validator_index] + signatures.append( + compute_sync_committee_signature( + spec, + state, + slot, + privkey, + ) + ) + return bls.Aggregate(signatures) diff --git a/tests/core/pyspec/eth2spec/test/lightclient_patch/__init__.py b/tests/core/pyspec/eth2spec/test/lightclient_patch/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/core/pyspec/eth2spec/test/lightclient_patch/block_processing/__init__.py b/tests/core/pyspec/eth2spec/test/lightclient_patch/block_processing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/core/pyspec/eth2spec/test/lightclient_patch/block_processing/test_process_sync_committee.py b/tests/core/pyspec/eth2spec/test/lightclient_patch/block_processing/test_process_sync_committee.py new file mode 100644 index 000000000..37cd0992b --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/lightclient_patch/block_processing/test_process_sync_committee.py @@ -0,0 +1,232 @@ +import random +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, + transition_unsigned_block, +) +from eth2spec.test.helpers.state import ( + state_transition_and_sign_block, + transition_to, +) +from eth2spec.test.helpers.sync_committee import ( + compute_aggregate_sync_committee_signature, +) +from eth2spec.test.context import ( + PHASE0, PHASE1, + expect_assertion_error, + with_all_phases_except, + spec_state_test, +) + + +@with_all_phases_except([PHASE0, PHASE1]) +@spec_state_test +def test_invalid_signature_missing_participant(spec, state): + committee = spec.get_sync_committee_indices(state, spec.get_current_epoch(state)) + random_participant = random.choice(committee) + + yield 'pre', state + + block = build_empty_block_for_next_slot(spec, state) + # Exclude one participant whose signature was included. + block.body.sync_committee_bits = [index != random_participant for index in committee] + block.body.sync_committee_signature = compute_aggregate_sync_committee_signature( + spec, + state, + block.slot - 1, + committee, # full committee signs + ) + + yield 'blocks', [block] + expect_assertion_error(lambda: spec.process_sync_committee(state, block.body)) + yield 'post', None + + +@with_all_phases_except([PHASE0, PHASE1]) +@spec_state_test +def test_invalid_signature_extra_participant(spec, state): + committee = spec.get_sync_committee_indices(state, spec.get_current_epoch(state)) + random_participant = random.choice(committee) + + yield 'pre', state + + block = build_empty_block_for_next_slot(spec, state) + # Exclude one signature even though the block claims the entire committee participated. + block.body.sync_committee_bits = [True] * len(committee) + block.body.sync_committee_signature = compute_aggregate_sync_committee_signature( + spec, + state, + block.slot - 1, + [index for index in committee if index != random_participant], + ) + + yield 'blocks', [block] + expect_assertion_error(lambda: spec.process_sync_committee(state, block.body)) + yield 'post', None + + +def compute_sync_committee_participant_reward(spec, state, participant_index, active_validator_count, committee_size): + base_reward = spec.get_base_reward(state, participant_index) + proposer_reward = spec.get_proposer_reward(state, participant_index) + max_participant_reward = base_reward - proposer_reward + return max_participant_reward * active_validator_count // committee_size // spec.SLOTS_PER_EPOCH + + +@with_all_phases_except([PHASE0, PHASE1]) +@spec_state_test +def test_sync_committee_rewards(spec, state): + committee = spec.get_sync_committee_indices(state, spec.get_current_epoch(state)) + committee_size = len(committee) + active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state))) + + yield 'pre', state + + pre_balances = state.balances.copy() + + block = build_empty_block_for_next_slot(spec, state) + block.body.sync_committee_bits = [True] * committee_size + block.body.sync_committee_signature = compute_aggregate_sync_committee_signature( + spec, + state, + block.slot - 1, + committee, + ) + + signed_block = state_transition_and_sign_block(spec, state, block) + + yield 'blocks', [signed_block] + yield 'post', state + + for index in range(len(state.validators)): + expected_reward = 0 + + if index == block.proposer_index: + expected_reward += sum([spec.get_proposer_reward(state, index) for index in committee]) + + if index in committee: + expected_reward += compute_sync_committee_participant_reward( + spec, + state, + index, + active_validator_count, + committee_size + ) + + assert state.balances[index] == pre_balances[index] + expected_reward + + +@with_all_phases_except([PHASE0, PHASE1]) +@spec_state_test +def test_invalid_signature_past_block(spec, state): + committee = spec.get_sync_committee_indices(state, spec.get_current_epoch(state)) + + yield 'pre', state + + blocks = [] + for _ in range(2): + # NOTE: need to transition twice to move beyond the degenerate case at genesis + block = build_empty_block_for_next_slot(spec, state) + # Valid sync committee signature here... + block.body.sync_committee_bits = [True] * len(committee) + block.body.sync_committee_signature = compute_aggregate_sync_committee_signature( + spec, + state, + block.slot - 1, + committee, + ) + + signed_block = state_transition_and_sign_block(spec, state, block) + blocks.append(signed_block) + + invalid_block = build_empty_block_for_next_slot(spec, state) + # Invalid signature from a slot other than the previous + invalid_block.body.sync_committee_bits = [True] * len(committee) + invalid_block.body.sync_committee_signature = compute_aggregate_sync_committee_signature( + spec, + state, + invalid_block.slot - 2, + committee, + ) + blocks.append(invalid_block) + + expect_assertion_error(lambda: transition_unsigned_block(spec, state, invalid_block)) + + yield 'blocks', blocks + yield 'post', None + + +@with_all_phases_except([PHASE0, PHASE1]) +@spec_state_test +def test_invalid_signature_previous_committee(spec, state): + # NOTE: the `state` provided is at genesis and the process to select + # sync committees currently returns the same committee for the first and second + # periods at genesis. + # To get a distinct committee so we can generate an "old" signature, we need to advance + # 2 EPOCHS_PER_SYNC_COMMITTEE_PERIOD periods. + current_epoch = spec.get_current_epoch(state) + previous_committee = state.next_sync_committee + + epoch_in_future_sync_commitee_period = current_epoch + 2 * spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD + slot_in_future_sync_committee_period = epoch_in_future_sync_commitee_period * spec.SLOTS_PER_EPOCH + transition_to(spec, state, slot_in_future_sync_committee_period) + + pubkeys = [validator.pubkey for validator in state.validators] + committee = [pubkeys.index(pubkey) for pubkey in previous_committee.pubkeys] + + yield 'pre', state + + block = build_empty_block_for_next_slot(spec, state) + block.body.sync_committee_bits = [True] * len(committee) + block.body.sync_committee_signature = compute_aggregate_sync_committee_signature( + spec, + state, + block.slot - 1, + committee, + ) + + yield 'blocks', [block] + expect_assertion_error(lambda: spec.process_sync_committee(state, block.body)) + yield 'post', None + + +@with_all_phases_except([PHASE0, PHASE1]) +@spec_state_test +def test_valid_signature_future_committee(spec, state): + # NOTE: the `state` provided is at genesis and the process to select + # sync committees currently returns the same committee for the first and second + # periods at genesis. + # To get a distinct committee so we can generate an "old" signature, we need to advance + # 2 EPOCHS_PER_SYNC_COMMITTEE_PERIOD periods. + current_epoch = spec.get_current_epoch(state) + old_current_sync_committee = state.current_sync_committee + old_next_sync_committee = state.next_sync_committee + + epoch_in_future_sync_committee_period = current_epoch + 2 * spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD + slot_in_future_sync_committee_period = epoch_in_future_sync_committee_period * spec.SLOTS_PER_EPOCH + transition_to(spec, state, slot_in_future_sync_committee_period) + + sync_committee = state.current_sync_committee + + expected_sync_committee = spec.get_sync_committee(state, epoch_in_future_sync_committee_period) + + assert sync_committee == expected_sync_committee + assert sync_committee != old_current_sync_committee + assert sync_committee != old_next_sync_committee + + pubkeys = [validator.pubkey for validator in state.validators] + committee_indices = [pubkeys.index(pubkey) for pubkey in sync_committee.pubkeys] + + yield 'pre', state + + block = build_empty_block_for_next_slot(spec, state) + block.body.sync_committee_bits = [True] * len(committee_indices) + block.body.sync_committee_signature = compute_aggregate_sync_committee_signature( + spec, + state, + block.slot - 1, + committee_indices, + ) + + signed_block = state_transition_and_sign_block(spec, state, block) + + yield 'blocks', [signed_block] + yield 'post', state diff --git a/tests/core/pyspec/eth2spec/test/lightclient_patch/epoch_processing/__init__.py b/tests/core/pyspec/eth2spec/test/lightclient_patch/epoch_processing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/core/pyspec/eth2spec/test/lightclient_patch/epoch_processing/test_process_final_updates.py b/tests/core/pyspec/eth2spec/test/lightclient_patch/epoch_processing/test_process_final_updates.py new file mode 100644 index 000000000..012438cd8 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/lightclient_patch/epoch_processing/test_process_final_updates.py @@ -0,0 +1,37 @@ +from eth2spec.test.context import ( + PHASE0, PHASE1, + with_all_phases_except, + spec_state_test, +) +from eth2spec.test.helpers.state import transition_to +from eth2spec.test.helpers.epoch_processing import ( + run_epoch_processing_with, +) + + +@with_all_phases_except([PHASE0, PHASE1]) +@spec_state_test +def test_sync_committees_progress(spec, state): + current_epoch = spec.get_current_epoch(state) + # NOTE: if not in the genesis epoch, period math below needs to be + # adjusted relative to the current epoch + assert current_epoch == 0 + + first_sync_committee = state.current_sync_committee + second_sync_committee = state.next_sync_committee + + slot_at_end_of_current_period = spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, slot_at_end_of_current_period) + + # Ensure assignments have not changed: + assert state.current_sync_committee == first_sync_committee + assert state.next_sync_committee == second_sync_committee + + yield from run_epoch_processing_with(spec, state, 'process_final_updates') + + # Can compute the third committee having computed final balances in the last epoch + # of this `EPOCHS_PER_SYNC_COMMITTEE_PERIOD` + third_sync_committee = spec.get_sync_committee(state, 2 * spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD) + + assert state.current_sync_committee == second_sync_committee + assert state.next_sync_committee == third_sync_committee diff --git a/tests/core/pyspec/eth2spec/test/lightclient_patch/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/lightclient_patch/sanity/test_blocks.py index 4fbdfc371..9033a0f15 100644 --- a/tests/core/pyspec/eth2spec/test/lightclient_patch/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/lightclient_patch/sanity/test_blocks.py @@ -1,6 +1,4 @@ import random -from eth2spec.test.helpers.keys import privkeys -from eth2spec.utils import bls from eth2spec.test.helpers.state import ( state_transition_and_sign_block, next_epoch, @@ -8,6 +6,9 @@ from eth2spec.test.helpers.state import ( from eth2spec.test.helpers.block import ( build_empty_block_for_next_slot, ) +from eth2spec.test.helpers.sync_committee import ( + compute_aggregate_sync_committee_signature, +) from eth2spec.test.context import ( PHASE0, PHASE1, with_all_phases_except, @@ -15,34 +16,6 @@ from eth2spec.test.context import ( ) -def compute_sync_committee_signature(spec, state, slot, privkey): - domain = spec.get_domain(state, spec.DOMAIN_SYNC_COMMITTEE, spec.compute_epoch_at_slot(slot)) - if slot == state.slot: - block_root = build_empty_block_for_next_slot(spec, state).parent_root - else: - block_root = spec.get_block_root_at_slot(state, slot) - signing_root = spec.compute_signing_root(block_root, domain) - return bls.Sign(privkey, signing_root) - - -def compute_aggregate_sync_committee_signature(spec, state, slot, participants): - if len(participants) == 0: - return spec.G2_POINT_AT_INFINITY - - signatures = [] - for validator_index in participants: - privkey = privkeys[validator_index] - signatures.append( - compute_sync_committee_signature( - spec, - state, - slot, - privkey, - ) - ) - return bls.Aggregate(signatures) - - def run_sync_committee_sanity_test(spec, state, fraction_full=1.0): committee = spec.get_sync_committee_indices(state, spec.get_current_epoch(state)) participants = random.sample(committee, int(len(committee) * fraction_full)) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_final_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_final_updates.py index 27676a59a..f9b5c872b 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_final_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_final_updates.py @@ -1,5 +1,5 @@ from eth2spec.test.context import spec_state_test, with_all_phases -from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import ( +from eth2spec.test.helpers.epoch_processing import ( run_epoch_processing_with, run_epoch_processing_to ) from eth2spec.test.helpers.state import transition_to diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py index f8504fc4f..921289f5b 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py @@ -1,5 +1,5 @@ from eth2spec.test.context import spec_state_test, with_all_phases -from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import ( +from eth2spec.test.helpers.epoch_processing import ( run_epoch_processing_with ) from eth2spec.test.helpers.state import transition_to diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py index c734010f3..ee40984a6 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py @@ -1,7 +1,7 @@ from eth2spec.test.helpers.deposits import mock_deposit from eth2spec.test.helpers.state import next_epoch, next_slots from eth2spec.test.context import spec_state_test, with_all_phases -from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import run_epoch_processing_with +from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with def run_process_registry_updates(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py index dd3cbb791..8560b66a8 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py @@ -19,7 +19,7 @@ from eth2spec.test.helpers.attestations import ( ) from eth2spec.test.helpers.rewards import leaking from eth2spec.test.helpers.attester_slashings import get_indexed_attestation_participants -from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import run_epoch_processing_with +from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with from random import Random diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py index 3e96f8b58..2e09f5c8a 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py @@ -1,5 +1,5 @@ from eth2spec.test.context import spec_state_test, with_all_phases -from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import ( +from eth2spec.test.helpers.epoch_processing import ( run_epoch_processing_with, run_epoch_processing_to ) from eth2spec.test.helpers.state import next_epoch diff --git a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_challenge_deadlines.py b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_challenge_deadlines.py index 0350324a5..1d8adecbc 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_challenge_deadlines.py +++ b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_challenge_deadlines.py @@ -15,7 +15,7 @@ from eth2spec.test.context import ( with_configs, ) from eth2spec.test.phase0.block_processing.test_process_attestation import run_attestation_processing -from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import run_epoch_processing_with +from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with from eth2spec.test.phase1.block_processing.test_process_chunk_challenge import ( run_chunk_challenge_processing, diff --git a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py index 5994306d9..82ecde7ab 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_custody_final_updates.py @@ -17,7 +17,7 @@ from eth2spec.test.context import ( spec_state_test, ) from eth2spec.test.phase0.block_processing.test_process_attestation import run_attestation_processing -from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import run_epoch_processing_with +from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with from eth2spec.test.phase1.block_processing.test_process_chunk_challenge import ( run_chunk_challenge_processing, diff --git a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_reveal_deadlines.py b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_reveal_deadlines.py index 3c2060ba5..b95082491 100644 --- a/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_reveal_deadlines.py +++ b/tests/core/pyspec/eth2spec/test/phase1/epoch_processing/test_process_reveal_deadlines.py @@ -10,7 +10,7 @@ from eth2spec.test.context import ( with_configs, spec_state_test, ) -from eth2spec.test.phase0.epoch_processing.run_epoch_process_base import run_epoch_processing_with +from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with from eth2spec.test.phase1.block_processing.test_process_custody_key_reveal import run_custody_key_reveal_processing