From 4dd8b7c98ae0de5a5de37787c4aee969c442e828 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 12 Oct 2021 20:35:18 +0800 Subject: [PATCH 01/17] [WIP] Add new transition tests --- .../test_process_inactivity_updates.py | 20 +- .../test/altair/transition/test_transition.py | 409 +++++++++++++++++- .../test/helpers/inactivity_scores.py | 23 + .../pyspec/eth2spec/test/helpers/random.py | 5 + 4 files changed, 438 insertions(+), 19 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py index 2a971f4f0..f262bcdd2 100644 --- a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py +++ b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py @@ -1,7 +1,11 @@ from random import Random from eth2spec.test.context import spec_state_test, with_altair_and_later -from eth2spec.test.helpers.inactivity_scores import randomize_inactivity_scores, zero_inactivity_scores +from eth2spec.test.helpers.inactivity_scores import ( + randomize_inactivity_scores, + zero_inactivity_scores, + slash_some_validators_for_inactivity_scores_test, +) from eth2spec.test.helpers.state import ( next_epoch, next_epoch_via_block, @@ -201,20 +205,6 @@ def test_random_inactivity_scores_full_participation_leaking(spec, state): assert spec.is_in_inactivity_leak(state) -def slash_some_validators_for_inactivity_scores_test(spec, state, rng=Random(40404040)): - # ``run_inactivity_scores_test`` runs at the next epoch from `state`. - # We retrieve the proposer of this future state to avoid - # accidentally slashing that validator - future_state = state.copy() - next_epoch_via_block(spec, future_state) - - proposer_index = spec.get_beacon_proposer_index(future_state) - # Slash ~1/4 of validaors - for validator_index in range(len(state.validators)): - if rng.choice(range(4)) == 0 and validator_index != proposer_index: - spec.slash_validator(state, validator_index) - - @with_altair_and_later @spec_state_test def test_some_slashed_zero_scores_full_participation(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py index 62740df4e..9996a1278 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py @@ -1,12 +1,24 @@ import random from eth2spec.test.context import fork_transition_test from eth2spec.test.helpers.constants import PHASE0, ALTAIR -from eth2spec.test.helpers.state import state_transition_and_sign_block, next_slot, next_epoch_via_signed_block +from eth2spec.test.helpers.state import ( + next_epoch_via_signed_block, + next_slot, + state_transition_and_sign_block, + transition_to, +) from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_empty_block, sign_block +from eth2spec.test.helpers.deposits import ( + prepare_state_and_deposit, +) from eth2spec.test.helpers.attestations import next_slots_with_attestations +from eth2spec.test.helpers.random import set_some_new_deposits +from eth2spec.test.helpers.inactivity_scores import ( + slash_some_validators_for_inactivity_scores_test, +) -def _state_transition_and_sign_block_at_slot(spec, state): +def _state_transition_and_sign_block_at_slot(spec, state, deposits=None): """ Cribbed from ``transition_unsigned_block`` helper where the early parts of the state transition have already @@ -15,6 +27,9 @@ def _state_transition_and_sign_block_at_slot(spec, state): Used to produce a block during an irregular state transition. """ block = build_empty_block(spec, state) + # FIXME: not just passing `deposits` + if deposits is not None: + block.body.deposits = deposits assert state.latest_block_header.slot < block.slot assert state.slot == block.slot @@ -62,7 +77,29 @@ def _state_transition_across_slots(spec, state, to_slot, block_filter=_all_block next_slot(spec, state) -def _do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True): +def _state_transition_across_slots_with_ignoring_proposers(spec, state, to_slot, ignoring_proposers): + """ + The slashed validators can't be proposers. Here we ignore the given `ignoring_proposers` + and ensure that the result state was computed with a block with slot >= to_slot. + """ + assert state.slot < to_slot + + found_valid = False + while state.slot < to_slot or not found_valid: + future_state = state.copy() + next_slot(spec, future_state) + proposer_index = spec.get_beacon_proposer_index(future_state) + if proposer_index not in ignoring_proposers: + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + yield signed_block + if state.slot >= to_slot: + found_valid = True + else: + next_slot(spec, state) + + +def _do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True, deposits=None): spec.process_slots(state, state.slot + 1) assert state.slot % spec.SLOTS_PER_EPOCH == 0 @@ -75,11 +112,25 @@ def _do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True): assert state.fork.current_version == post_spec.config.ALTAIR_FORK_VERSION if with_block: - return state, _state_transition_and_sign_block_at_slot(post_spec, state) + return state, _state_transition_and_sign_block_at_slot(post_spec, state, deposits=deposits) else: return state, None +def _set_validators_exit_epoch(spec, state, exit_epoch, rng=random.Random(40404040), fraction=0.25): + """ + Set some valdiators' exit_epoch. + """ + selected_count = int(len(state.validators) * fraction) + selected_indices = rng.sample(range(len(state.validators)), selected_count) + for validator_index in selected_indices: + state.validators[validator_index].exit_epoch = exit_epoch + state.validators[validator_index].withdrawable_epoch = ( + exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + ) + return selected_indices + + @fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) def test_normal_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ @@ -434,3 +485,353 @@ def test_transition_with_no_attestations_until_after_fork(state, fork_epoch, spe yield "blocks", blocks yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=1) +def test_transition_with_one_fourth_slashed_active_validators_pre_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + 1/4 validators are slashed but still active at the fork transition. + """ + # slash 1/4 validators + selected_indices = slash_some_validators_for_inactivity_scores_test( + spec, state, rng=random.Random(5566), fraction=0.25) + assert len(selected_indices) > 0 + + # check if some validators are slashed but still active + for validator_index in selected_indices: + validator = state.validators[validator_index] + assert validator.slashed + assert spec.is_active_validator(validator, spec.get_current_epoch(state)) + assert not spec.is_in_inactivity_leak(state) + + # regular state transition until fork: + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, to_slot) + + assert spec.get_current_epoch(state) < fork_epoch + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # continue regular state transition with new spec into next epoch + to_slot = post_spec.SLOTS_PER_EPOCH + state.slot + # since the proposer might have been slashed, here we only create blocks with non-slashed proposers + blocks.extend([ + post_tag(block) for block in + _state_transition_across_slots_with_ignoring_proposers(post_spec, state, to_slot, selected_indices) + ]) + + # check post state + for validator in state.validators: + assert post_spec.is_active_validator(validator, post_spec.get_current_epoch(state)) + assert not post_spec.is_in_inactivity_leak(state) + + yield "blocks", blocks + yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +def test_transition_with_one_fourth_exiting_validators_exit_post_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + 1/4 exiting but still active validators at the fork transition. + """ + exited_indices = _set_validators_exit_epoch(spec, state, exit_epoch=10, rng=random.Random(5566), fraction=0.25) + + # regular state transition until fork: + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, to_slot) + + # check pre state + assert len(exited_indices) > 0 + for index in exited_indices: + validator = state.validators[index] + assert not validator.slashed + assert fork_epoch < validator.exit_epoch < spec.FAR_FUTURE_EPOCH + assert spec.is_active_validator(validator, spec.get_current_epoch(state)) + assert not spec.is_in_inactivity_leak(state) + assert spec.get_current_epoch(state) < fork_epoch + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # ensure that some of the current sync committee members are exiting + exited_pubkeys = [state.validators[index].pubkey for index in exited_indices] + assert any(set(exited_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) + + # continue regular state transition with new spec into next epoch + to_slot = post_spec.SLOTS_PER_EPOCH + state.slot + blocks.extend([ + post_tag(block) for block in + _state_transition_across_slots(post_spec, state, to_slot) + ]) + + # check state + for index in exited_indices: + validator = state.validators[index] + assert not validator.slashed + assert post_spec.is_active_validator(validator, post_spec.get_current_epoch(state)) + assert not post_spec.is_in_inactivity_leak(state) + + yield "blocks", blocks + yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +def test_transition_with_one_fourth_exiting_validators_exit_at_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + 1/4 exiting but still active validators at the fork transition. + """ + exited_indices = _set_validators_exit_epoch(spec, state, exit_epoch=2, rng=random.Random(5566), fraction=0.25) + + # regular state transition until fork: + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, to_slot) + + # check pre state + assert len(exited_indices) > 0 + for index in exited_indices: + validator = state.validators[index] + assert not validator.slashed + assert fork_epoch == validator.exit_epoch < spec.FAR_FUTURE_EPOCH + assert spec.is_active_validator(validator, spec.get_current_epoch(state)) + assert not spec.is_in_inactivity_leak(state) + assert spec.get_current_epoch(state) < fork_epoch + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # check post transition state + for index in exited_indices: + validator = state.validators[index] + assert not validator.slashed + assert not post_spec.is_active_validator(validator, post_spec.get_current_epoch(state)) + assert not post_spec.is_in_inactivity_leak(state) + + # ensure that none of the current sync committee members are exited validators + exited_pubkeys = [state.validators[index].pubkey for index in exited_indices] + assert not any(set(exited_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) + + # continue regular state transition with new spec into next epoch + to_slot = post_spec.SLOTS_PER_EPOCH + state.slot + blocks.extend([ + post_tag(block) for block in + _state_transition_across_slots(post_spec, state, to_slot) + ]) + + yield "blocks", blocks + yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=7) +def test_transition_with_leaking_pre_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2). + The leaking starts before the fork transition in this case. + """ + # regular state transition until fork: + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, to_slot) + + assert spec.is_in_inactivity_leak(state) + assert spec.get_current_epoch(state) < fork_epoch + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # check post transition state + assert spec.is_in_inactivity_leak(state) + + # continue regular state transition with new spec into next epoch + to_slot = post_spec.SLOTS_PER_EPOCH + state.slot + blocks.extend([ + post_tag(block) for block in + _state_transition_across_slots(post_spec, state, to_slot) + ]) + + yield "blocks", blocks + yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=6) +def test_transition_with_leaking_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2). + The leaking starts at the fork transition in this case. + """ + # regular state transition until fork: + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, to_slot) + + assert not spec.is_in_inactivity_leak(state) + assert spec.get_current_epoch(state) < fork_epoch + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # check post transition state + assert spec.is_in_inactivity_leak(state) + + # continue regular state transition with new spec into next epoch + to_slot = post_spec.SLOTS_PER_EPOCH + state.slot + blocks.extend([ + post_tag(block) for block in + _state_transition_across_slots(post_spec, state, to_slot) + ]) + + yield "blocks", blocks + yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=5) +def test_transition_with_leaking_post_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2). + The leaking starts after the fork transition in this case. + """ + # regular state transition until fork: + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, to_slot) + + assert not spec.is_in_inactivity_leak(state) + assert spec.get_current_epoch(state) < fork_epoch + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # check post transition state + assert not spec.is_in_inactivity_leak(state) + + # continue regular state transition with new spec into next epoch + to_slot = post_spec.SLOTS_PER_EPOCH + state.slot + blocks.extend([ + post_tag(block) for block in + _state_transition_across_slots(post_spec, state, to_slot) + ]) + + # check state again + assert spec.is_in_inactivity_leak(state) + + yield "blocks", blocks + yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=10) +def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create some deposits before the transition + """ + # regular state transition until fork: + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, to_slot) + + _, queuing_indices = set_some_new_deposits(spec, state, rng=random.Random(5566)) + + assert spec.get_current_epoch(state) < fork_epoch + assert len(queuing_indices) > 0 + for validator_index in queuing_indices: + assert not spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state)) + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # continue regular state transition with new spec into next epoch + to_slot = post_spec.SLOTS_PER_EPOCH + state.slot + blocks.extend([ + post_tag(block) for block in + _state_transition_across_slots(post_spec, state, to_slot) + ]) + + yield "blocks", blocks + yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=10) +def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create a deposit at the transition + """ + # regular state transition until fork: + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, to_slot) + + yield "pre", state + + # create a new deposit + validator_index = len(state.validators) + amount = post_spec.MAX_EFFECTIVE_BALANCE + deposit = prepare_state_and_deposit(post_spec, state, validator_index, amount, signed=True) + + # irregular state transition to handle fork: + state, block = _do_altair_fork(state, spec, post_spec, fork_epoch, deposits=[deposit]) + blocks = [] + blocks.append(post_tag(block)) + + assert not post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state)) + + # continue regular state transition with new spec into next epoch + to_slot = post_spec.SLOTS_PER_EPOCH + state.slot + blocks.extend([ + post_tag(block) for block in + _state_transition_across_slots(post_spec, state, to_slot) + ]) + + # finalize activation_eligibility_epoch + _, blocks_in_epoch, state = next_slots_with_attestations( + post_spec, + state, + spec.SLOTS_PER_EPOCH * 2, + fill_cur_epoch=True, + fill_prev_epoch=True, + ) + blocks.extend([pre_tag(block) for block in blocks_in_epoch]) + assert state.finalized_checkpoint.epoch == state.validators[validator_index].activation_eligibility_epoch + + # continue regular state transition with new spec into next epoch + to_slot = post_spec.SLOTS_PER_EPOCH + state.slot + blocks.extend([ + post_tag(block) for block in + _state_transition_across_slots(post_spec, state, to_slot) + ]) + + assert state.validators[validator_index].activation_epoch < post_spec.FAR_FUTURE_EPOCH + + to_slot = state.validators[validator_index].activation_epoch * post_spec.SLOTS_PER_EPOCH + blocks.extend([ + post_tag(block) for block in + _state_transition_across_slots(post_spec, state, to_slot) + ]) + assert post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state)) + + yield "blocks", blocks + yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py b/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py index 29f9038a8..ff744c4ed 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py +++ b/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py @@ -1,5 +1,9 @@ from random import Random +from eth2spec.test.helpers.state import ( + next_epoch_via_block, +) + def randomize_inactivity_scores(spec, state, minimum=0, maximum=50000, rng=Random(4242)): state.inactivity_scores = [rng.randint(minimum, maximum) for _ in range(len(state.validators))] @@ -7,3 +11,22 @@ def randomize_inactivity_scores(spec, state, minimum=0, maximum=50000, rng=Rando def zero_inactivity_scores(spec, state, rng=None): state.inactivity_scores = [0] * len(state.validators) + + +def slash_some_validators_for_inactivity_scores_test(spec, state, rng=Random(40404040), fraction=0.25): + """ + ``run_inactivity_scores_test`` runs at the next epoch from `state`. + # We retrieve the proposer of this future state to avoid + # accidentally slashing that validator + """ + future_state = state.copy() + next_epoch_via_block(spec, future_state) + proposer_index = spec.get_beacon_proposer_index(future_state) + selected_count = int(len(state.validators) * fraction) + selected_indices = rng.sample(range(len(state.validators)), selected_count) + if proposer_index in selected_indices: + selected_indices.remove(proposer_index) + for validator_index in selected_indices: + spec.slash_validator(state, validator_index) + + return selected_indices diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index 8448b2424..e8b233e1e 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -7,6 +7,7 @@ from eth2spec.test.helpers.state import next_epoch def set_some_new_deposits(spec, state, rng): + eligible_indices = queuing_indices = [] num_validators = len(state.validators) # Set ~1/10 to just recently deposited for index in range(num_validators): @@ -18,6 +19,10 @@ def set_some_new_deposits(spec, state, rng): # Set ~half of selected to eligible for activation if rng.choice([True, False]): state.validators[index].activation_eligibility_epoch = spec.get_current_epoch(state) + eligible_indices.append(index) + else: + queuing_indices.append(index) + return eligible_indices, queuing_indices def exit_random_validators(spec, state, rng, fraction=None): From 95f940cc74fed73f24e042f5854e21ad15fc3d8b Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 12 Oct 2021 23:33:48 +0800 Subject: [PATCH 02/17] ensure that some of the current sync committee members are the slashed --- .../test/altair/transition/test_transition.py | 49 ++++++++----------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py index 9996a1278..b56197ab5 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py @@ -131,6 +131,11 @@ def _set_validators_exit_epoch(spec, state, exit_epoch, rng=random.Random(404040 return selected_indices +def _transition_until_fork(spec, state, fork_epoch): + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, to_slot) + + @fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) def test_normal_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ @@ -494,20 +499,18 @@ def test_transition_with_one_fourth_slashed_active_validators_pre_fork( 1/4 validators are slashed but still active at the fork transition. """ # slash 1/4 validators - selected_indices = slash_some_validators_for_inactivity_scores_test( + slashed_indices = slash_some_validators_for_inactivity_scores_test( spec, state, rng=random.Random(5566), fraction=0.25) - assert len(selected_indices) > 0 + assert len(slashed_indices) > 0 # check if some validators are slashed but still active - for validator_index in selected_indices: + for validator_index in slashed_indices: validator = state.validators[validator_index] assert validator.slashed assert spec.is_active_validator(validator, spec.get_current_epoch(state)) assert not spec.is_in_inactivity_leak(state) - # regular state transition until fork: - to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 - transition_to(spec, state, to_slot) + _transition_until_fork(spec, state, fork_epoch) assert spec.get_current_epoch(state) < fork_epoch @@ -518,12 +521,16 @@ def test_transition_with_one_fourth_slashed_active_validators_pre_fork( state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) blocks.append(post_tag(block)) + # ensure that some of the current sync committee members are the slashed + slashed_pubkeys = [state.validators[index].pubkey for index in slashed_indices] + assert any(set(slashed_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) + # continue regular state transition with new spec into next epoch to_slot = post_spec.SLOTS_PER_EPOCH + state.slot # since the proposer might have been slashed, here we only create blocks with non-slashed proposers blocks.extend([ post_tag(block) for block in - _state_transition_across_slots_with_ignoring_proposers(post_spec, state, to_slot, selected_indices) + _state_transition_across_slots_with_ignoring_proposers(post_spec, state, to_slot, slashed_indices) ]) # check post state @@ -543,9 +550,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_post_fork( """ exited_indices = _set_validators_exit_epoch(spec, state, exit_epoch=10, rng=random.Random(5566), fraction=0.25) - # regular state transition until fork: - to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 - transition_to(spec, state, to_slot) + _transition_until_fork(spec, state, fork_epoch) # check pre state assert len(exited_indices) > 0 @@ -594,9 +599,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork( """ exited_indices = _set_validators_exit_epoch(spec, state, exit_epoch=2, rng=random.Random(5566), fraction=0.25) - # regular state transition until fork: - to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 - transition_to(spec, state, to_slot) + _transition_until_fork(spec, state, fork_epoch) # check pre state assert len(exited_indices) > 0 @@ -643,9 +646,7 @@ def test_transition_with_leaking_pre_fork(state, fork_epoch, spec, post_spec, pr Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2). The leaking starts before the fork transition in this case. """ - # regular state transition until fork: - to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 - transition_to(spec, state, to_slot) + _transition_until_fork(spec, state, fork_epoch) assert spec.is_in_inactivity_leak(state) assert spec.get_current_epoch(state) < fork_epoch @@ -677,9 +678,7 @@ def test_transition_with_leaking_at_fork(state, fork_epoch, spec, post_spec, pre Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2). The leaking starts at the fork transition in this case. """ - # regular state transition until fork: - to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 - transition_to(spec, state, to_slot) + _transition_until_fork(spec, state, fork_epoch) assert not spec.is_in_inactivity_leak(state) assert spec.get_current_epoch(state) < fork_epoch @@ -711,9 +710,7 @@ def test_transition_with_leaking_post_fork(state, fork_epoch, spec, post_spec, p Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2). The leaking starts after the fork transition in this case. """ - # regular state transition until fork: - to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 - transition_to(spec, state, to_slot) + _transition_until_fork(spec, state, fork_epoch) assert not spec.is_in_inactivity_leak(state) assert spec.get_current_epoch(state) < fork_epoch @@ -747,9 +744,7 @@ def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, pos """ Create some deposits before the transition """ - # regular state transition until fork: - to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 - transition_to(spec, state, to_slot) + _transition_until_fork(spec, state, fork_epoch) _, queuing_indices = set_some_new_deposits(spec, state, rng=random.Random(5566)) @@ -781,9 +776,7 @@ def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre """ Create a deposit at the transition """ - # regular state transition until fork: - to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 - transition_to(spec, state, to_slot) + _transition_until_fork(spec, state, fork_epoch) yield "pre", state From f0980a4ab988bf8c59f24399a40af2a42a07a595 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 13 Oct 2021 00:26:23 +0800 Subject: [PATCH 03/17] Refactoring. Sort tests to specific files. --- .../test/altair/transition/test_activation.py | 210 ++++++++ .../test/altair/transition/test_leaking.py | 106 ++++ .../test/altair/transition/test_slashing.py | 61 +++ .../test/altair/transition/test_transition.py | 502 +----------------- .../eth2spec/test/helpers/fork_transition.py | 130 +++++ tests/generators/transition/main.py | 14 +- 6 files changed, 540 insertions(+), 483 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py create mode 100644 tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py create mode 100644 tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py create mode 100644 tests/core/pyspec/eth2spec/test/helpers/fork_transition.py diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py new file mode 100644 index 000000000..83933d751 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py @@ -0,0 +1,210 @@ +import random +from eth2spec.test.context import fork_transition_test +from eth2spec.test.helpers.constants import PHASE0, ALTAIR +from eth2spec.test.helpers.attestations import next_slots_with_attestations +from eth2spec.test.helpers.deposits import prepare_state_and_deposit +from eth2spec.test.helpers.fork_transition import ( + do_altair_fork, + set_validators_exit_epoch, + state_transition_across_slots, + transition_until_fork, +) +from eth2spec.test.helpers.random import set_some_new_deposits + + +# +# Exit +# + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +def test_transition_with_one_fourth_exiting_validators_exit_post_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + 1/4 exiting but still active validators at the fork transition. + """ + exited_indices = set_validators_exit_epoch(spec, state, exit_epoch=10, rng=random.Random(5566), fraction=0.25) + + transition_until_fork(spec, state, fork_epoch) + + # check pre state + assert len(exited_indices) > 0 + for index in exited_indices: + validator = state.validators[index] + assert not validator.slashed + assert fork_epoch < validator.exit_epoch < spec.FAR_FUTURE_EPOCH + assert spec.is_active_validator(validator, spec.get_current_epoch(state)) + assert not spec.is_in_inactivity_leak(state) + assert spec.get_current_epoch(state) < fork_epoch + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = do_altair_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # ensure that some of the current sync committee members are exiting + exited_pubkeys = [state.validators[index].pubkey for index in exited_indices] + assert any(set(exited_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) + + # continue regular state transition with new spec into next epoch + to_slot = post_spec.SLOTS_PER_EPOCH + state.slot + blocks.extend([ + post_tag(block) for block in + state_transition_across_slots(post_spec, state, to_slot) + ]) + + # check state + for index in exited_indices: + validator = state.validators[index] + assert not validator.slashed + assert post_spec.is_active_validator(validator, post_spec.get_current_epoch(state)) + assert not post_spec.is_in_inactivity_leak(state) + + yield "blocks", blocks + yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +def test_transition_with_one_fourth_exiting_validators_exit_at_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + 1/4 exiting but still active validators at the fork transition. + """ + exited_indices = set_validators_exit_epoch(spec, state, exit_epoch=2, rng=random.Random(5566), fraction=0.25) + + transition_until_fork(spec, state, fork_epoch) + + # check pre state + assert len(exited_indices) > 0 + for index in exited_indices: + validator = state.validators[index] + assert not validator.slashed + assert fork_epoch == validator.exit_epoch < spec.FAR_FUTURE_EPOCH + assert spec.is_active_validator(validator, spec.get_current_epoch(state)) + assert not spec.is_in_inactivity_leak(state) + assert spec.get_current_epoch(state) < fork_epoch + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = do_altair_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # check post transition state + for index in exited_indices: + validator = state.validators[index] + assert not validator.slashed + assert not post_spec.is_active_validator(validator, post_spec.get_current_epoch(state)) + assert not post_spec.is_in_inactivity_leak(state) + + # ensure that none of the current sync committee members are exited validators + exited_pubkeys = [state.validators[index].pubkey for index in exited_indices] + assert not any(set(exited_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) + + # continue regular state transition with new spec into next epoch + to_slot = post_spec.SLOTS_PER_EPOCH + state.slot + blocks.extend([ + post_tag(block) for block in + state_transition_across_slots(post_spec, state, to_slot) + ]) + + yield "blocks", blocks + yield "post", state + + +# +# Activation +# + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create some deposits before the transition + """ + transition_until_fork(spec, state, fork_epoch) + + _, queuing_indices = set_some_new_deposits(spec, state, rng=random.Random(5566)) + + assert spec.get_current_epoch(state) < fork_epoch + assert len(queuing_indices) > 0 + for validator_index in queuing_indices: + assert not spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state)) + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = do_altair_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # continue regular state transition with new spec into next epoch + to_slot = post_spec.SLOTS_PER_EPOCH + state.slot + blocks.extend([ + post_tag(block) for block in + state_transition_across_slots(post_spec, state, to_slot) + ]) + + yield "blocks", blocks + yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create a deposit at the transition + """ + transition_until_fork(spec, state, fork_epoch) + + yield "pre", state + + # create a new deposit + validator_index = len(state.validators) + amount = post_spec.MAX_EFFECTIVE_BALANCE + deposit = prepare_state_and_deposit(post_spec, state, validator_index, amount, signed=True) + + # irregular state transition to handle fork: + state, block = do_altair_fork(state, spec, post_spec, fork_epoch, deposits=[deposit]) + blocks = [] + blocks.append(post_tag(block)) + + assert not post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state)) + + # continue regular state transition with new spec into next epoch + to_slot = post_spec.SLOTS_PER_EPOCH + state.slot + blocks.extend([ + post_tag(block) for block in + state_transition_across_slots(post_spec, state, to_slot) + ]) + + # finalize activation_eligibility_epoch + _, blocks_in_epoch, state = next_slots_with_attestations( + post_spec, + state, + spec.SLOTS_PER_EPOCH * 2, + fill_cur_epoch=True, + fill_prev_epoch=True, + ) + blocks.extend([pre_tag(block) for block in blocks_in_epoch]) + assert state.finalized_checkpoint.epoch == state.validators[validator_index].activation_eligibility_epoch + + # continue regular state transition with new spec into next epoch + to_slot = post_spec.SLOTS_PER_EPOCH + state.slot + blocks.extend([ + post_tag(block) for block in + state_transition_across_slots(post_spec, state, to_slot) + ]) + + assert state.validators[validator_index].activation_epoch < post_spec.FAR_FUTURE_EPOCH + + to_slot = state.validators[validator_index].activation_epoch * post_spec.SLOTS_PER_EPOCH + blocks.extend([ + post_tag(block) for block in + state_transition_across_slots(post_spec, state, to_slot) + ]) + assert post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state)) + + yield "blocks", blocks + yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py new file mode 100644 index 000000000..37c0f80d1 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py @@ -0,0 +1,106 @@ +from eth2spec.test.context import fork_transition_test +from eth2spec.test.helpers.constants import PHASE0, ALTAIR +from eth2spec.test.helpers.fork_transition import ( + do_altair_fork, + state_transition_across_slots, + transition_until_fork, +) + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=7) +def test_transition_with_leaking_pre_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2). + The leaking starts before the fork transition in this case. + """ + transition_until_fork(spec, state, fork_epoch) + + assert spec.is_in_inactivity_leak(state) + assert spec.get_current_epoch(state) < fork_epoch + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = do_altair_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # check post transition state + assert spec.is_in_inactivity_leak(state) + + # continue regular state transition with new spec into next epoch + to_slot = post_spec.SLOTS_PER_EPOCH + state.slot + blocks.extend([ + post_tag(block) for block in + state_transition_across_slots(post_spec, state, to_slot) + ]) + + yield "blocks", blocks + yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=6) +def test_transition_with_leaking_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2). + The leaking starts at the fork transition in this case. + """ + transition_until_fork(spec, state, fork_epoch) + + assert not spec.is_in_inactivity_leak(state) + assert spec.get_current_epoch(state) < fork_epoch + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = do_altair_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # check post transition state + assert spec.is_in_inactivity_leak(state) + + # continue regular state transition with new spec into next epoch + to_slot = post_spec.SLOTS_PER_EPOCH + state.slot + blocks.extend([ + post_tag(block) for block in + state_transition_across_slots(post_spec, state, to_slot) + ]) + + yield "blocks", blocks + yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=5) +def test_transition_with_leaking_post_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2). + The leaking starts after the fork transition in this case. + """ + transition_until_fork(spec, state, fork_epoch) + + assert not spec.is_in_inactivity_leak(state) + assert spec.get_current_epoch(state) < fork_epoch + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = do_altair_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # check post transition state + assert not spec.is_in_inactivity_leak(state) + + # continue regular state transition with new spec into next epoch + to_slot = post_spec.SLOTS_PER_EPOCH + state.slot + blocks.extend([ + post_tag(block) for block in + state_transition_across_slots(post_spec, state, to_slot) + ]) + + # check state again + assert spec.is_in_inactivity_leak(state) + + yield "blocks", blocks + yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py new file mode 100644 index 000000000..d3b9c5b2f --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py @@ -0,0 +1,61 @@ +import random +from eth2spec.test.context import fork_transition_test +from eth2spec.test.helpers.constants import PHASE0, ALTAIR +from eth2spec.test.helpers.fork_transition import ( + do_altair_fork, + state_transition_across_slots_with_ignoring_proposers, + transition_until_fork, +) +from eth2spec.test.helpers.inactivity_scores import ( + slash_some_validators_for_inactivity_scores_test, +) + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=1) +def test_transition_with_one_fourth_slashed_active_validators_pre_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + 1/4 validators are slashed but still active at the fork transition. + """ + # slash 1/4 validators + slashed_indices = slash_some_validators_for_inactivity_scores_test( + spec, state, rng=random.Random(5566), fraction=0.25) + assert len(slashed_indices) > 0 + + # check if some validators are slashed but still active + for validator_index in slashed_indices: + validator = state.validators[validator_index] + assert validator.slashed + assert spec.is_active_validator(validator, spec.get_current_epoch(state)) + assert not spec.is_in_inactivity_leak(state) + + transition_until_fork(spec, state, fork_epoch) + + assert spec.get_current_epoch(state) < fork_epoch + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = do_altair_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # ensure that some of the current sync committee members are the slashed + slashed_pubkeys = [state.validators[index].pubkey for index in slashed_indices] + assert any(set(slashed_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) + + # continue regular state transition with new spec into next epoch + to_slot = post_spec.SLOTS_PER_EPOCH + state.slot + # since the proposer might have been slashed, here we only create blocks with non-slashed proposers + blocks.extend([ + post_tag(block) for block in + state_transition_across_slots_with_ignoring_proposers(post_spec, state, to_slot, slashed_indices) + ]) + + # check post state + for validator in state.validators: + assert post_spec.is_active_validator(validator, post_spec.get_current_epoch(state)) + assert not post_spec.is_in_inactivity_leak(state) + + yield "blocks", blocks + yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py index b56197ab5..c6dec8d3e 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py @@ -3,139 +3,17 @@ from eth2spec.test.context import fork_transition_test from eth2spec.test.helpers.constants import PHASE0, ALTAIR from eth2spec.test.helpers.state import ( next_epoch_via_signed_block, - next_slot, - state_transition_and_sign_block, - transition_to, -) -from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_empty_block, sign_block -from eth2spec.test.helpers.deposits import ( - prepare_state_and_deposit, ) from eth2spec.test.helpers.attestations import next_slots_with_attestations -from eth2spec.test.helpers.random import set_some_new_deposits -from eth2spec.test.helpers.inactivity_scores import ( - slash_some_validators_for_inactivity_scores_test, +from eth2spec.test.helpers.fork_transition import ( + do_altair_fork, + no_blocks, + only_at, + skip_slots, + state_transition_across_slots, ) -def _state_transition_and_sign_block_at_slot(spec, state, deposits=None): - """ - Cribbed from ``transition_unsigned_block`` helper - where the early parts of the state transition have already - been applied to ``state``. - - Used to produce a block during an irregular state transition. - """ - block = build_empty_block(spec, state) - # FIXME: not just passing `deposits` - if deposits is not None: - block.body.deposits = deposits - - assert state.latest_block_header.slot < block.slot - assert state.slot == block.slot - spec.process_block(state, block) - block.state_root = state.hash_tree_root() - return sign_block(spec, state, block) - - -def _all_blocks(_): - return True - - -def _skip_slots(*slots): - """ - Skip making a block if its slot is - passed as an argument to this filter - """ - def f(state_at_prior_slot): - return state_at_prior_slot.slot + 1 not in slots - return f - - -def _no_blocks(_): - return False - - -def _only_at(slot): - """ - Only produce a block if its slot is ``slot``. - """ - def f(state_at_prior_slot): - return state_at_prior_slot.slot + 1 == slot - return f - - -def _state_transition_across_slots(spec, state, to_slot, block_filter=_all_blocks): - assert state.slot < to_slot - while state.slot < to_slot: - should_make_block = block_filter(state) - if should_make_block: - block = build_empty_block_for_next_slot(spec, state) - signed_block = state_transition_and_sign_block(spec, state, block) - yield signed_block - else: - next_slot(spec, state) - - -def _state_transition_across_slots_with_ignoring_proposers(spec, state, to_slot, ignoring_proposers): - """ - The slashed validators can't be proposers. Here we ignore the given `ignoring_proposers` - and ensure that the result state was computed with a block with slot >= to_slot. - """ - assert state.slot < to_slot - - found_valid = False - while state.slot < to_slot or not found_valid: - future_state = state.copy() - next_slot(spec, future_state) - proposer_index = spec.get_beacon_proposer_index(future_state) - if proposer_index not in ignoring_proposers: - block = build_empty_block_for_next_slot(spec, state) - signed_block = state_transition_and_sign_block(spec, state, block) - yield signed_block - if state.slot >= to_slot: - found_valid = True - else: - next_slot(spec, state) - - -def _do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True, deposits=None): - spec.process_slots(state, state.slot + 1) - - assert state.slot % spec.SLOTS_PER_EPOCH == 0 - assert spec.get_current_epoch(state) == fork_epoch - - state = post_spec.upgrade_to_altair(state) - - assert state.fork.epoch == fork_epoch - assert state.fork.previous_version == post_spec.config.GENESIS_FORK_VERSION - assert state.fork.current_version == post_spec.config.ALTAIR_FORK_VERSION - - if with_block: - return state, _state_transition_and_sign_block_at_slot(post_spec, state, deposits=deposits) - else: - return state, None - - -def _set_validators_exit_epoch(spec, state, exit_epoch, rng=random.Random(40404040), fraction=0.25): - """ - Set some valdiators' exit_epoch. - """ - selected_count = int(len(state.validators) * fraction) - selected_indices = rng.sample(range(len(state.validators)), selected_count) - for validator_index in selected_indices: - state.validators[validator_index].exit_epoch = exit_epoch - state.validators[validator_index].withdrawable_epoch = ( - exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY - ) - return selected_indices - - -def _transition_until_fork(spec, state, fork_epoch): - to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 - transition_to(spec, state, to_slot) - - @fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) def test_normal_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ @@ -151,18 +29,18 @@ def test_normal_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag blocks = [] blocks.extend([ pre_tag(block) for block in - _state_transition_across_slots(spec, state, to_slot) + state_transition_across_slots(spec, state, to_slot) ]) # irregular state transition to handle fork: - state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) + state, block = do_altair_fork(state, spec, post_spec, fork_epoch) blocks.append(post_tag(block)) # continue regular state transition with new spec into next epoch to_slot = post_spec.SLOTS_PER_EPOCH + state.slot blocks.extend([ post_tag(block) for block in - _state_transition_across_slots(post_spec, state, to_slot) + state_transition_across_slots(post_spec, state, to_slot) ]) assert state.slot % post_spec.SLOTS_PER_EPOCH == 0 @@ -192,17 +70,17 @@ def test_transition_missing_first_post_block(state, fork_epoch, spec, post_spec, blocks = [] blocks.extend([ pre_tag(block) for block in - _state_transition_across_slots(spec, state, to_slot) + state_transition_across_slots(spec, state, to_slot) ]) # irregular state transition to handle fork: - state, _ = _do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False) + state, _ = do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False) # continue regular state transition with new spec into next epoch to_slot = post_spec.SLOTS_PER_EPOCH + state.slot blocks.extend([ post_tag(block) for block in - _state_transition_across_slots(post_spec, state, to_slot) + state_transition_across_slots(post_spec, state, to_slot) ]) assert state.slot % post_spec.SLOTS_PER_EPOCH == 0 @@ -234,18 +112,18 @@ def test_transition_missing_last_pre_fork_block(state, fork_epoch, spec, post_sp blocks = [] blocks.extend([ pre_tag(block) for block in - _state_transition_across_slots(spec, state, to_slot, block_filter=_skip_slots(last_slot_of_pre_fork)) + state_transition_across_slots(spec, state, to_slot, block_filter=skip_slots(last_slot_of_pre_fork)) ]) # irregular state transition to handle fork: - state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) + state, block = do_altair_fork(state, spec, post_spec, fork_epoch) blocks.append(post_tag(block)) # continue regular state transition with new spec into next epoch to_slot = post_spec.SLOTS_PER_EPOCH + state.slot blocks.extend([ post_tag(block) for block in - _state_transition_across_slots(post_spec, state, to_slot) + state_transition_across_slots(post_spec, state, to_slot) ]) assert state.slot % post_spec.SLOTS_PER_EPOCH == 0 @@ -277,18 +155,18 @@ def test_transition_only_blocks_post_fork(state, fork_epoch, spec, post_spec, pr blocks = [] blocks.extend([ pre_tag(block) for block in - _state_transition_across_slots(spec, state, to_slot, block_filter=_no_blocks) + state_transition_across_slots(spec, state, to_slot, block_filter=no_blocks) ]) # irregular state transition to handle fork: - state, _ = _do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False) + state, _ = do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False) # continue regular state transition with new spec into next epoch to_slot = post_spec.SLOTS_PER_EPOCH + state.slot last_slot = (fork_epoch + 1) * post_spec.SLOTS_PER_EPOCH blocks.extend([ post_tag(block) for block in - _state_transition_across_slots(post_spec, state, to_slot, block_filter=_only_at(last_slot)) + state_transition_across_slots(post_spec, state, to_slot, block_filter=only_at(last_slot)) ]) assert state.slot % post_spec.SLOTS_PER_EPOCH == 0 @@ -348,7 +226,7 @@ def _run_transition_test_with_attestations(state, assert (state.slot + 1) % spec.SLOTS_PER_EPOCH == 0 # irregular state transition to handle fork: - state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) + state, block = do_altair_fork(state, spec, post_spec, fork_epoch) blocks.append(post_tag(block)) # continue regular state transition with new spec into next epoch @@ -461,11 +339,11 @@ def test_transition_with_no_attestations_until_after_fork(state, fork_epoch, spe blocks = [] blocks.extend([ pre_tag(block) for block in - _state_transition_across_slots(spec, state, to_slot) + state_transition_across_slots(spec, state, to_slot) ]) # irregular state transition to handle fork: - state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) + state, block = do_altair_fork(state, spec, post_spec, fork_epoch) blocks.append(post_tag(block)) # continue regular state transition but add attestations @@ -490,341 +368,3 @@ def test_transition_with_no_attestations_until_after_fork(state, fork_epoch, spe yield "blocks", blocks yield "post", state - - -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=1) -def test_transition_with_one_fourth_slashed_active_validators_pre_fork( - state, fork_epoch, spec, post_spec, pre_tag, post_tag): - """ - 1/4 validators are slashed but still active at the fork transition. - """ - # slash 1/4 validators - slashed_indices = slash_some_validators_for_inactivity_scores_test( - spec, state, rng=random.Random(5566), fraction=0.25) - assert len(slashed_indices) > 0 - - # check if some validators are slashed but still active - for validator_index in slashed_indices: - validator = state.validators[validator_index] - assert validator.slashed - assert spec.is_active_validator(validator, spec.get_current_epoch(state)) - assert not spec.is_in_inactivity_leak(state) - - _transition_until_fork(spec, state, fork_epoch) - - assert spec.get_current_epoch(state) < fork_epoch - - yield "pre", state - - # irregular state transition to handle fork: - blocks = [] - state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) - blocks.append(post_tag(block)) - - # ensure that some of the current sync committee members are the slashed - slashed_pubkeys = [state.validators[index].pubkey for index in slashed_indices] - assert any(set(slashed_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) - - # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - # since the proposer might have been slashed, here we only create blocks with non-slashed proposers - blocks.extend([ - post_tag(block) for block in - _state_transition_across_slots_with_ignoring_proposers(post_spec, state, to_slot, slashed_indices) - ]) - - # check post state - for validator in state.validators: - assert post_spec.is_active_validator(validator, post_spec.get_current_epoch(state)) - assert not post_spec.is_in_inactivity_leak(state) - - yield "blocks", blocks - yield "post", state - - -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) -def test_transition_with_one_fourth_exiting_validators_exit_post_fork( - state, fork_epoch, spec, post_spec, pre_tag, post_tag): - """ - 1/4 exiting but still active validators at the fork transition. - """ - exited_indices = _set_validators_exit_epoch(spec, state, exit_epoch=10, rng=random.Random(5566), fraction=0.25) - - _transition_until_fork(spec, state, fork_epoch) - - # check pre state - assert len(exited_indices) > 0 - for index in exited_indices: - validator = state.validators[index] - assert not validator.slashed - assert fork_epoch < validator.exit_epoch < spec.FAR_FUTURE_EPOCH - assert spec.is_active_validator(validator, spec.get_current_epoch(state)) - assert not spec.is_in_inactivity_leak(state) - assert spec.get_current_epoch(state) < fork_epoch - - yield "pre", state - - # irregular state transition to handle fork: - blocks = [] - state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) - blocks.append(post_tag(block)) - - # ensure that some of the current sync committee members are exiting - exited_pubkeys = [state.validators[index].pubkey for index in exited_indices] - assert any(set(exited_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) - - # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - _state_transition_across_slots(post_spec, state, to_slot) - ]) - - # check state - for index in exited_indices: - validator = state.validators[index] - assert not validator.slashed - assert post_spec.is_active_validator(validator, post_spec.get_current_epoch(state)) - assert not post_spec.is_in_inactivity_leak(state) - - yield "blocks", blocks - yield "post", state - - -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) -def test_transition_with_one_fourth_exiting_validators_exit_at_fork( - state, fork_epoch, spec, post_spec, pre_tag, post_tag): - """ - 1/4 exiting but still active validators at the fork transition. - """ - exited_indices = _set_validators_exit_epoch(spec, state, exit_epoch=2, rng=random.Random(5566), fraction=0.25) - - _transition_until_fork(spec, state, fork_epoch) - - # check pre state - assert len(exited_indices) > 0 - for index in exited_indices: - validator = state.validators[index] - assert not validator.slashed - assert fork_epoch == validator.exit_epoch < spec.FAR_FUTURE_EPOCH - assert spec.is_active_validator(validator, spec.get_current_epoch(state)) - assert not spec.is_in_inactivity_leak(state) - assert spec.get_current_epoch(state) < fork_epoch - - yield "pre", state - - # irregular state transition to handle fork: - blocks = [] - state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) - blocks.append(post_tag(block)) - - # check post transition state - for index in exited_indices: - validator = state.validators[index] - assert not validator.slashed - assert not post_spec.is_active_validator(validator, post_spec.get_current_epoch(state)) - assert not post_spec.is_in_inactivity_leak(state) - - # ensure that none of the current sync committee members are exited validators - exited_pubkeys = [state.validators[index].pubkey for index in exited_indices] - assert not any(set(exited_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) - - # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - _state_transition_across_slots(post_spec, state, to_slot) - ]) - - yield "blocks", blocks - yield "post", state - - -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=7) -def test_transition_with_leaking_pre_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): - """ - Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2). - The leaking starts before the fork transition in this case. - """ - _transition_until_fork(spec, state, fork_epoch) - - assert spec.is_in_inactivity_leak(state) - assert spec.get_current_epoch(state) < fork_epoch - - yield "pre", state - - # irregular state transition to handle fork: - blocks = [] - state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) - blocks.append(post_tag(block)) - - # check post transition state - assert spec.is_in_inactivity_leak(state) - - # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - _state_transition_across_slots(post_spec, state, to_slot) - ]) - - yield "blocks", blocks - yield "post", state - - -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=6) -def test_transition_with_leaking_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): - """ - Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2). - The leaking starts at the fork transition in this case. - """ - _transition_until_fork(spec, state, fork_epoch) - - assert not spec.is_in_inactivity_leak(state) - assert spec.get_current_epoch(state) < fork_epoch - - yield "pre", state - - # irregular state transition to handle fork: - blocks = [] - state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) - blocks.append(post_tag(block)) - - # check post transition state - assert spec.is_in_inactivity_leak(state) - - # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - _state_transition_across_slots(post_spec, state, to_slot) - ]) - - yield "blocks", blocks - yield "post", state - - -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=5) -def test_transition_with_leaking_post_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): - """ - Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2). - The leaking starts after the fork transition in this case. - """ - _transition_until_fork(spec, state, fork_epoch) - - assert not spec.is_in_inactivity_leak(state) - assert spec.get_current_epoch(state) < fork_epoch - - yield "pre", state - - # irregular state transition to handle fork: - blocks = [] - state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) - blocks.append(post_tag(block)) - - # check post transition state - assert not spec.is_in_inactivity_leak(state) - - # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - _state_transition_across_slots(post_spec, state, to_slot) - ]) - - # check state again - assert spec.is_in_inactivity_leak(state) - - yield "blocks", blocks - yield "post", state - - -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=10) -def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, post_spec, pre_tag, post_tag): - """ - Create some deposits before the transition - """ - _transition_until_fork(spec, state, fork_epoch) - - _, queuing_indices = set_some_new_deposits(spec, state, rng=random.Random(5566)) - - assert spec.get_current_epoch(state) < fork_epoch - assert len(queuing_indices) > 0 - for validator_index in queuing_indices: - assert not spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state)) - - yield "pre", state - - # irregular state transition to handle fork: - blocks = [] - state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) - blocks.append(post_tag(block)) - - # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - _state_transition_across_slots(post_spec, state, to_slot) - ]) - - yield "blocks", blocks - yield "post", state - - -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=10) -def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): - """ - Create a deposit at the transition - """ - _transition_until_fork(spec, state, fork_epoch) - - yield "pre", state - - # create a new deposit - validator_index = len(state.validators) - amount = post_spec.MAX_EFFECTIVE_BALANCE - deposit = prepare_state_and_deposit(post_spec, state, validator_index, amount, signed=True) - - # irregular state transition to handle fork: - state, block = _do_altair_fork(state, spec, post_spec, fork_epoch, deposits=[deposit]) - blocks = [] - blocks.append(post_tag(block)) - - assert not post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state)) - - # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - _state_transition_across_slots(post_spec, state, to_slot) - ]) - - # finalize activation_eligibility_epoch - _, blocks_in_epoch, state = next_slots_with_attestations( - post_spec, - state, - spec.SLOTS_PER_EPOCH * 2, - fill_cur_epoch=True, - fill_prev_epoch=True, - ) - blocks.extend([pre_tag(block) for block in blocks_in_epoch]) - assert state.finalized_checkpoint.epoch == state.validators[validator_index].activation_eligibility_epoch - - # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - _state_transition_across_slots(post_spec, state, to_slot) - ]) - - assert state.validators[validator_index].activation_epoch < post_spec.FAR_FUTURE_EPOCH - - to_slot = state.validators[validator_index].activation_epoch * post_spec.SLOTS_PER_EPOCH - blocks.extend([ - post_tag(block) for block in - _state_transition_across_slots(post_spec, state, to_slot) - ]) - assert post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state)) - - yield "blocks", blocks - yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py new file mode 100644 index 000000000..3a9977326 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -0,0 +1,130 @@ +import random + +from eth2spec.test.helpers.state import ( + next_slot, + state_transition_and_sign_block, + transition_to, +) +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, + build_empty_block, + sign_block, +) + + +def _state_transition_and_sign_block_at_slot(spec, state, deposits=None): + """ + Cribbed from ``transition_unsigned_block`` helper + where the early parts of the state transition have already + been applied to ``state``. + + Used to produce a block during an irregular state transition. + """ + block = build_empty_block(spec, state) + # FIXME: not just passing `deposits` + if deposits is not None: + block.body.deposits = deposits + + assert state.latest_block_header.slot < block.slot + assert state.slot == block.slot + spec.process_block(state, block) + block.state_root = state.hash_tree_root() + return sign_block(spec, state, block) + + +def _all_blocks(_): + return True + + +def skip_slots(*slots): + """ + Skip making a block if its slot is + passed as an argument to this filter + """ + def f(state_at_prior_slot): + return state_at_prior_slot.slot + 1 not in slots + return f + + +def no_blocks(_): + return False + + +def only_at(slot): + """ + Only produce a block if its slot is ``slot``. + """ + def f(state_at_prior_slot): + return state_at_prior_slot.slot + 1 == slot + return f + + +def state_transition_across_slots(spec, state, to_slot, block_filter=_all_blocks): + assert state.slot < to_slot + while state.slot < to_slot: + should_make_block = block_filter(state) + if should_make_block: + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + yield signed_block + else: + next_slot(spec, state) + + +def state_transition_across_slots_with_ignoring_proposers(spec, state, to_slot, ignoring_proposers): + """ + The slashed validators can't be proposers. Here we ignore the given `ignoring_proposers` + and ensure that the result state was computed with a block with slot >= to_slot. + """ + assert state.slot < to_slot + + found_valid = False + while state.slot < to_slot or not found_valid: + future_state = state.copy() + next_slot(spec, future_state) + proposer_index = spec.get_beacon_proposer_index(future_state) + if proposer_index not in ignoring_proposers: + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + yield signed_block + if state.slot >= to_slot: + found_valid = True + else: + next_slot(spec, state) + + +def do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True, deposits=None): + spec.process_slots(state, state.slot + 1) + + assert state.slot % spec.SLOTS_PER_EPOCH == 0 + assert spec.get_current_epoch(state) == fork_epoch + + state = post_spec.upgrade_to_altair(state) + + assert state.fork.epoch == fork_epoch + assert state.fork.previous_version == post_spec.config.GENESIS_FORK_VERSION + assert state.fork.current_version == post_spec.config.ALTAIR_FORK_VERSION + + if with_block: + return state, _state_transition_and_sign_block_at_slot(post_spec, state, deposits=deposits) + else: + return state, None + + +def set_validators_exit_epoch(spec, state, exit_epoch, rng=random.Random(40404040), fraction=0.25): + """ + Set some valdiators' exit_epoch. + """ + selected_count = int(len(state.validators) * fraction) + selected_indices = rng.sample(range(len(state.validators)), selected_count) + for validator_index in selected_indices: + state.validators[validator_index].exit_epoch = exit_epoch + state.validators[validator_index].withdrawable_epoch = ( + exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + ) + return selected_indices + + +def transition_until_fork(spec, state, fork_epoch): + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, to_slot) diff --git a/tests/generators/transition/main.py b/tests/generators/transition/main.py index 2ded56a13..0a9080db3 100644 --- a/tests/generators/transition/main.py +++ b/tests/generators/transition/main.py @@ -1,7 +1,12 @@ from typing import Iterable from eth2spec.test.helpers.constants import ALTAIR, MINIMAL, MAINNET, PHASE0 -from eth2spec.test.altair.transition import test_transition as test_altair_transition +from eth2spec.test.altair.transition import ( + test_transition as test_altair_transition, + test_activation as test_altair_activation, + test_leaking as test_altair_leaking, + test_slashing as test_altair_slashing, +) from eth2spec.gen_helpers.gen_base import gen_runner, gen_typing from eth2spec.gen_helpers.gen_from_tests.gen import generate_from_tests @@ -25,7 +30,12 @@ def create_provider(tests_src, preset_name: str, pre_fork_name: str, post_fork_n return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn) -TRANSITION_TESTS = ((PHASE0, ALTAIR, test_altair_transition),) +TRANSITION_TESTS = ( + (PHASE0, ALTAIR, test_altair_transition), + (PHASE0, ALTAIR, test_altair_activation), + (PHASE0, ALTAIR, test_altair_leaking), + (PHASE0, ALTAIR, test_altair_slashing), +) if __name__ == "__main__": From 53d4fa51871e007b1362637824566d72d2fa3d0f Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 13 Oct 2021 02:01:13 +0800 Subject: [PATCH 04/17] Make operation (attester_slashing, proposer_slashing, voluntary_exit) at the fork block --- .../test/altair/transition/test_activation.py | 41 +++++++++- .../test/altair/transition/test_slashing.py | 82 ++++++++++++++++++- .../eth2spec/test/helpers/fork_transition.py | 16 ++-- 3 files changed, 130 insertions(+), 9 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py index 83933d751..91888b0c3 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py @@ -10,6 +10,10 @@ from eth2spec.test.helpers.fork_transition import ( transition_until_fork, ) from eth2spec.test.helpers.random import set_some_new_deposits +from eth2spec.test.helpers.state import ( + transition_to, +) +from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits # @@ -114,6 +118,39 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork( yield "post", state +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=260) +def test_transition_with_voluntary_exit_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create an attester slashing at the transition + """ + transition_to(spec, state, spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH) + transition_until_fork(spec, state, fork_epoch) + + yield "pre", state + + validator_index = 0 + signed_exits = prepare_signed_exits(spec, state, [validator_index]) + operation_dict = {'voluntary_exits': signed_exits} + + # irregular state transition to handle fork: + state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=operation_dict) + blocks = [] + blocks.append(post_tag(block)) + + validator = state.validators[validator_index] + assert validator.exit_epoch < post_spec.FAR_FUTURE_EPOCH + + # continue regular state transition with new spec into next epoch + to_slot = post_spec.SLOTS_PER_EPOCH + state.slot + blocks.extend([ + post_tag(block) for block in + state_transition_across_slots(post_spec, state, to_slot) + ]) + + yield "blocks", blocks + yield "post", state + + # # Activation # @@ -164,9 +201,9 @@ def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre validator_index = len(state.validators) amount = post_spec.MAX_EFFECTIVE_BALANCE deposit = prepare_state_and_deposit(post_spec, state, validator_index, amount, signed=True) - + operation_dict = {'deposits': [deposit]} # irregular state transition to handle fork: - state, block = do_altair_fork(state, spec, post_spec, fork_epoch, deposits=[deposit]) + state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=operation_dict) blocks = [] blocks.append(post_tag(block)) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py index d3b9c5b2f..018ce6781 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py @@ -1,8 +1,18 @@ import random -from eth2spec.test.context import fork_transition_test +from eth2spec.test.context import ( + always_bls, + fork_transition_test, +) from eth2spec.test.helpers.constants import PHASE0, ALTAIR +from eth2spec.test.helpers.attester_slashings import ( + get_valid_attester_slashing, +) +from eth2spec.test.helpers.proposer_slashings import ( + get_valid_proposer_slashing, +) from eth2spec.test.helpers.fork_transition import ( do_altair_fork, + state_transition_across_slots, state_transition_across_slots_with_ignoring_proposers, transition_until_fork, ) @@ -59,3 +69,73 @@ def test_transition_with_one_fourth_slashed_active_validators_pre_fork( yield "blocks", blocks yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@always_bls +def test_transition_with_attester_slashing_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create an attester slashing at the transition + """ + transition_until_fork(spec, state, fork_epoch) + + yield "pre", state + + # NOTE: it can only be created with pre spec + attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) + operation_dict = {'attester_slashings': [attester_slashing]} + + # irregular state transition to handle fork: + state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=operation_dict) + blocks = [] + blocks.append(post_tag(block)) + + indices = set(attester_slashing.attestation_1.attesting_indices).intersection( + attester_slashing.attestation_2.attesting_indices + ) + assert len(indices) > 0 + for validator_index in indices: + assert state.validators[validator_index].slashed + + # continue regular state transition with new spec into next epoch + to_slot = post_spec.SLOTS_PER_EPOCH + state.slot + blocks.extend([ + post_tag(block) for block in + state_transition_across_slots(post_spec, state, to_slot) + ]) + + yield "blocks", blocks + yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@always_bls +def test_transition_with_proposer_slashing_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create an attester slashing at the transition + """ + transition_until_fork(spec, state, fork_epoch) + + yield "pre", state + + # NOTE: it can only be created with pre spec + proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) + operation_dict = {'proposer_slashings': [proposer_slashing]} + + # irregular state transition to handle fork: + state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=operation_dict) + blocks = [] + blocks.append(post_tag(block)) + + slashed_proposer = state.validators[proposer_slashing.signed_header_1.message.proposer_index] + assert slashed_proposer.slashed + + # continue regular state transition with new spec into next epoch + to_slot = post_spec.SLOTS_PER_EPOCH + state.slot + blocks.extend([ + post_tag(block) for block in + state_transition_across_slots(post_spec, state, to_slot) + ]) + + yield "blocks", blocks + yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index 3a9977326..e0e7d028b 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -12,7 +12,10 @@ from eth2spec.test.helpers.block import ( ) -def _state_transition_and_sign_block_at_slot(spec, state, deposits=None): +def _state_transition_and_sign_block_at_slot(spec, + state, + *, + operation_dict=None): """ Cribbed from ``transition_unsigned_block`` helper where the early parts of the state transition have already @@ -21,9 +24,10 @@ def _state_transition_and_sign_block_at_slot(spec, state, deposits=None): Used to produce a block during an irregular state transition. """ block = build_empty_block(spec, state) - # FIXME: not just passing `deposits` - if deposits is not None: - block.body.deposits = deposits + # we can't just pass `body` because randao_reveal and eth1_data was set in `build_empty_block` + if operation_dict is not None: + for key, value in operation_dict.items(): + setattr(block.body, key, value) assert state.latest_block_header.slot < block.slot assert state.slot == block.slot @@ -93,7 +97,7 @@ def state_transition_across_slots_with_ignoring_proposers(spec, state, to_slot, next_slot(spec, state) -def do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True, deposits=None): +def do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True, operation_dict=None): spec.process_slots(state, state.slot + 1) assert state.slot % spec.SLOTS_PER_EPOCH == 0 @@ -106,7 +110,7 @@ def do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True, deposits assert state.fork.current_version == post_spec.config.ALTAIR_FORK_VERSION if with_block: - return state, _state_transition_and_sign_block_at_slot(post_spec, state, deposits=deposits) + return state, _state_transition_and_sign_block_at_slot(post_spec, state, operation_dict=operation_dict) else: return state, None From 67da1ba2bf24f0cec1a233a2bad50b189b65cec5 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 13 Oct 2021 02:35:47 +0800 Subject: [PATCH 05/17] Minor refactoring - sanity check: deposit operation is independent of spec fork versions - refactoring - add comments --- .../test/altair/transition/test_activation.py | 43 ++++++------------- .../test/altair/transition/test_leaking.py | 20 ++------- .../test/altair/transition/test_slashing.py | 14 ++---- .../test/altair/transition/test_transition.py | 19 ++------ .../eth2spec/test/helpers/fork_transition.py | 14 ++++-- 5 files changed, 34 insertions(+), 76 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py index 91888b0c3..df8ac6934 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py @@ -8,6 +8,7 @@ from eth2spec.test.helpers.fork_transition import ( set_validators_exit_epoch, state_transition_across_slots, transition_until_fork, + transition_to_next_epoch_and_append_blocks, ) from eth2spec.test.helpers.random import set_some_new_deposits from eth2spec.test.helpers.state import ( @@ -52,11 +53,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_post_fork( assert any(set(exited_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - state_transition_across_slots(post_spec, state, to_slot) - ]) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) # check state for index in exited_indices: @@ -108,11 +105,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork( assert not any(set(exited_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - state_transition_across_slots(post_spec, state, to_slot) - ]) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) yield "blocks", blocks yield "post", state @@ -121,7 +114,8 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork( @fork_transition_test(PHASE0, ALTAIR, fork_epoch=260) def test_transition_with_voluntary_exit_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ - Create an attester slashing at the transition + Create an attester slashing at the transition. + fork_epoch=260 because mainnet `SHARD_COMMITTEE_PERIOD` is 256 epochs. """ transition_to(spec, state, spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH) transition_until_fork(spec, state, fork_epoch) @@ -141,11 +135,7 @@ def test_transition_with_voluntary_exit_at_fork(state, fork_epoch, spec, post_sp assert validator.exit_epoch < post_spec.FAR_FUTURE_EPOCH # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - state_transition_across_slots(post_spec, state, to_slot) - ]) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) yield "blocks", blocks yield "post", state @@ -178,11 +168,7 @@ def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, pos blocks.append(post_tag(block)) # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - state_transition_across_slots(post_spec, state, to_slot) - ]) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) yield "blocks", blocks yield "post", state @@ -201,6 +187,9 @@ def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre validator_index = len(state.validators) amount = post_spec.MAX_EFFECTIVE_BALANCE deposit = prepare_state_and_deposit(post_spec, state, validator_index, amount, signed=True) + deposit_old = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True) + # sanity check: deposit operation is independent of spec fork versions + assert deposit_old == deposit operation_dict = {'deposits': [deposit]} # irregular state transition to handle fork: state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=operation_dict) @@ -210,11 +199,7 @@ def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre assert not post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state)) # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - state_transition_across_slots(post_spec, state, to_slot) - ]) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) # finalize activation_eligibility_epoch _, blocks_in_epoch, state = next_slots_with_attestations( @@ -228,11 +213,7 @@ def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre assert state.finalized_checkpoint.epoch == state.validators[validator_index].activation_eligibility_epoch # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - state_transition_across_slots(post_spec, state, to_slot) - ]) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) assert state.validators[validator_index].activation_epoch < post_spec.FAR_FUTURE_EPOCH diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py index 37c0f80d1..086e43a93 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py @@ -2,8 +2,8 @@ from eth2spec.test.context import fork_transition_test from eth2spec.test.helpers.constants import PHASE0, ALTAIR from eth2spec.test.helpers.fork_transition import ( do_altair_fork, - state_transition_across_slots, transition_until_fork, + transition_to_next_epoch_and_append_blocks, ) @@ -29,11 +29,7 @@ def test_transition_with_leaking_pre_fork(state, fork_epoch, spec, post_spec, pr assert spec.is_in_inactivity_leak(state) # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - state_transition_across_slots(post_spec, state, to_slot) - ]) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) yield "blocks", blocks yield "post", state @@ -61,11 +57,7 @@ def test_transition_with_leaking_at_fork(state, fork_epoch, spec, post_spec, pre assert spec.is_in_inactivity_leak(state) # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - state_transition_across_slots(post_spec, state, to_slot) - ]) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) yield "blocks", blocks yield "post", state @@ -93,11 +85,7 @@ def test_transition_with_leaking_post_fork(state, fork_epoch, spec, post_spec, p assert not spec.is_in_inactivity_leak(state) # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - state_transition_across_slots(post_spec, state, to_slot) - ]) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) # check state again assert spec.is_in_inactivity_leak(state) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py index 018ce6781..cc22f5c22 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py @@ -12,9 +12,9 @@ from eth2spec.test.helpers.proposer_slashings import ( ) from eth2spec.test.helpers.fork_transition import ( do_altair_fork, - state_transition_across_slots, state_transition_across_slots_with_ignoring_proposers, transition_until_fork, + transition_to_next_epoch_and_append_blocks, ) from eth2spec.test.helpers.inactivity_scores import ( slash_some_validators_for_inactivity_scores_test, @@ -98,11 +98,7 @@ def test_transition_with_attester_slashing_at_fork(state, fork_epoch, spec, post assert state.validators[validator_index].slashed # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - state_transition_across_slots(post_spec, state, to_slot) - ]) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) yield "blocks", blocks yield "post", state @@ -131,11 +127,7 @@ def test_transition_with_proposer_slashing_at_fork(state, fork_epoch, spec, post assert slashed_proposer.slashed # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - state_transition_across_slots(post_spec, state, to_slot) - ]) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) yield "blocks", blocks yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py index c6dec8d3e..fe6248c5f 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py @@ -11,6 +11,7 @@ from eth2spec.test.helpers.fork_transition import ( only_at, skip_slots, state_transition_across_slots, + transition_to_next_epoch_and_append_blocks, ) @@ -37,11 +38,7 @@ def test_normal_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag blocks.append(post_tag(block)) # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - state_transition_across_slots(post_spec, state, to_slot) - ]) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) assert state.slot % post_spec.SLOTS_PER_EPOCH == 0 assert post_spec.get_current_epoch(state) == fork_epoch + 1 @@ -77,11 +74,7 @@ def test_transition_missing_first_post_block(state, fork_epoch, spec, post_spec, state, _ = do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False) # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - state_transition_across_slots(post_spec, state, to_slot) - ]) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) assert state.slot % post_spec.SLOTS_PER_EPOCH == 0 assert post_spec.get_current_epoch(state) == fork_epoch + 1 @@ -120,11 +113,7 @@ def test_transition_missing_last_pre_fork_block(state, fork_epoch, spec, post_sp blocks.append(post_tag(block)) # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - state_transition_across_slots(post_spec, state, to_slot) - ]) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) assert state.slot % post_spec.SLOTS_PER_EPOCH == 0 assert post_spec.get_current_epoch(state) == fork_epoch + 1 diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index e0e7d028b..ea5419d0d 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -14,7 +14,6 @@ from eth2spec.test.helpers.block import ( def _state_transition_and_sign_block_at_slot(spec, state, - *, operation_dict=None): """ Cribbed from ``transition_unsigned_block`` helper @@ -24,7 +23,8 @@ def _state_transition_and_sign_block_at_slot(spec, Used to produce a block during an irregular state transition. """ block = build_empty_block(spec, state) - # we can't just pass `body` because randao_reveal and eth1_data was set in `build_empty_block` + # we can't just pass `body` and assign it because randao_reveal and eth1_data was set in `build_empty_block` + # thus use dict to pass operations. if operation_dict is not None: for key, value in operation_dict.items(): setattr(block.body, key, value) @@ -117,7 +117,7 @@ def do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True, operatio def set_validators_exit_epoch(spec, state, exit_epoch, rng=random.Random(40404040), fraction=0.25): """ - Set some valdiators' exit_epoch. + Set some valdiators' `exit_epoch` and `withdrawable_epoch`. """ selected_count = int(len(state.validators) * fraction) selected_indices = rng.sample(range(len(state.validators)), selected_count) @@ -132,3 +132,11 @@ def set_validators_exit_epoch(spec, state, exit_epoch, rng=random.Random(4040404 def transition_until_fork(spec, state, fork_epoch): to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 transition_to(spec, state, to_slot) + + +def transition_to_next_epoch_and_append_blocks(spec, state, post_tag, blocks): + to_slot = spec.SLOTS_PER_EPOCH + state.slot + blocks.extend([ + post_tag(block) for block in + state_transition_across_slots(spec, state, to_slot) + ]) From cbba5426fea2fe3b0532e71339044981d2cfe6ab Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 14 Oct 2021 20:07:07 +0800 Subject: [PATCH 06/17] PR feedback --- ...ation.py => test_activations_and_exits.py} | 25 ++++++++++++------- .../test/altair/transition/test_slashing.py | 9 +++++-- .../test/helpers/inactivity_scores.py | 4 +-- .../pyspec/eth2spec/test/helpers/random.py | 3 ++- 4 files changed, 27 insertions(+), 14 deletions(-) rename tests/core/pyspec/eth2spec/test/altair/transition/{test_activation.py => test_activations_and_exits.py} (88%) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py similarity index 88% rename from tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py rename to tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py index df8ac6934..50e97d24e 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activation.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py @@ -11,9 +11,6 @@ from eth2spec.test.helpers.fork_transition import ( transition_to_next_epoch_and_append_blocks, ) from eth2spec.test.helpers.random import set_some_new_deposits -from eth2spec.test.helpers.state import ( - transition_to, -) from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits @@ -22,8 +19,12 @@ from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits # @fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) -def test_transition_with_one_fourth_exiting_validators_exit_post_fork( - state, fork_epoch, spec, post_spec, pre_tag, post_tag): +def test_transition_with_one_fourth_exiting_validators_exit_post_fork(state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag): """ 1/4 exiting but still active validators at the fork transition. """ @@ -51,6 +52,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_post_fork( # ensure that some of the current sync committee members are exiting exited_pubkeys = [state.validators[index].pubkey for index in exited_indices] assert any(set(exited_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) + assert any(set(exited_pubkeys).difference(list(state.current_sync_committee.pubkeys))) # continue regular state transition with new spec into next epoch transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) @@ -67,8 +69,12 @@ def test_transition_with_one_fourth_exiting_validators_exit_post_fork( @fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) -def test_transition_with_one_fourth_exiting_validators_exit_at_fork( - state, fork_epoch, spec, post_spec, pre_tag, post_tag): +def test_transition_with_one_fourth_exiting_validators_exit_at_fork(state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag): """ 1/4 exiting but still active validators at the fork transition. """ @@ -117,7 +123,8 @@ def test_transition_with_voluntary_exit_at_fork(state, fork_epoch, spec, post_sp Create an attester slashing at the transition. fork_epoch=260 because mainnet `SHARD_COMMITTEE_PERIOD` is 256 epochs. """ - transition_to(spec, state, spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH) + # Fast forward to the future epoch so that validator can do voluntary exit + state.slot = spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH transition_until_fork(spec, state, fork_epoch) yield "pre", state @@ -209,7 +216,7 @@ def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre fill_cur_epoch=True, fill_prev_epoch=True, ) - blocks.extend([pre_tag(block) for block in blocks_in_epoch]) + blocks.extend([post_tag(block) for block in blocks_in_epoch]) assert state.finalized_checkpoint.epoch == state.validators[validator_index].activation_eligibility_epoch # continue regular state transition with new spec into next epoch diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py index cc22f5c22..86b78bd3e 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py @@ -22,8 +22,12 @@ from eth2spec.test.helpers.inactivity_scores import ( @fork_transition_test(PHASE0, ALTAIR, fork_epoch=1) -def test_transition_with_one_fourth_slashed_active_validators_pre_fork( - state, fork_epoch, spec, post_spec, pre_tag, post_tag): +def test_transition_with_one_fourth_slashed_active_validators_pre_fork(state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag): """ 1/4 validators are slashed but still active at the fork transition. """ @@ -53,6 +57,7 @@ def test_transition_with_one_fourth_slashed_active_validators_pre_fork( # ensure that some of the current sync committee members are the slashed slashed_pubkeys = [state.validators[index].pubkey for index in slashed_indices] assert any(set(slashed_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) + assert any(set(slashed_pubkeys).difference(list(state.current_sync_committee.pubkeys))) # continue regular state transition with new spec into next epoch to_slot = post_spec.SLOTS_PER_EPOCH + state.slot diff --git a/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py b/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py index ff744c4ed..02aec7105 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py +++ b/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py @@ -16,8 +16,8 @@ def zero_inactivity_scores(spec, state, rng=None): def slash_some_validators_for_inactivity_scores_test(spec, state, rng=Random(40404040), fraction=0.25): """ ``run_inactivity_scores_test`` runs at the next epoch from `state`. - # We retrieve the proposer of this future state to avoid - # accidentally slashing that validator + We retrieve the proposer of this future state to avoid + accidentally slashing that validator """ future_state = state.copy() next_epoch_via_block(spec, future_state) diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index e8b233e1e..cd662d09d 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -7,7 +7,8 @@ from eth2spec.test.helpers.state import next_epoch def set_some_new_deposits(spec, state, rng): - eligible_indices = queuing_indices = [] + eligible_indices = [] + queuing_indices = [] num_validators = len(state.validators) # Set ~1/10 to just recently deposited for index in range(num_validators): From 49bf78d43148241bcbec247a73f75d575b522134 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 14 Oct 2021 21:45:33 +0800 Subject: [PATCH 07/17] PR feedback --- .../test_process_inactivity_updates.py | 15 ++++++- .../transition/test_activations_and_exits.py | 30 ++++++++------ .../test/altair/transition/test_leaking.py | 35 +--------------- .../test/altair/transition/test_slashing.py | 18 ++++---- .../eth2spec/test/helpers/fork_transition.py | 20 ++++++--- .../test/helpers/inactivity_scores.py | 23 ----------- .../pyspec/eth2spec/test/helpers/random.py | 41 ++++++++++++++----- tests/generators/transition/main.py | 4 +- 8 files changed, 90 insertions(+), 96 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py index f262bcdd2..1ac622dd9 100644 --- a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py +++ b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py @@ -4,7 +4,6 @@ from eth2spec.test.context import spec_state_test, with_altair_and_later from eth2spec.test.helpers.inactivity_scores import ( randomize_inactivity_scores, zero_inactivity_scores, - slash_some_validators_for_inactivity_scores_test, ) from eth2spec.test.helpers.state import ( next_epoch, @@ -205,6 +204,20 @@ def test_random_inactivity_scores_full_participation_leaking(spec, state): assert spec.is_in_inactivity_leak(state) +def slash_some_validators_for_inactivity_scores_test(spec, state, rng=Random(40404040)): + # ``run_inactivity_scores_test`` runs at the next epoch from `state`. + # We retrieve the proposer of this future state to avoid + # accidentally slashing that validator + future_state = state.copy() + next_epoch_via_block(spec, future_state) + + proposer_index = spec.get_beacon_proposer_index(future_state) + # Slash ~1/4 of validaors + for validator_index in range(len(state.validators)): + if rng.choice(range(4)) == 0 and validator_index != proposer_index: + spec.slash_validator(state, validator_index) + + @with_altair_and_later @spec_state_test def test_some_slashed_zero_scores_full_participation(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py index 50e97d24e..573f7fba1 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py @@ -5,12 +5,14 @@ from eth2spec.test.helpers.attestations import next_slots_with_attestations from eth2spec.test.helpers.deposits import prepare_state_and_deposit from eth2spec.test.helpers.fork_transition import ( do_altair_fork, - set_validators_exit_epoch, state_transition_across_slots, transition_until_fork, transition_to_next_epoch_and_append_blocks, ) -from eth2spec.test.helpers.random import set_some_new_deposits +from eth2spec.test.helpers.random import ( + exit_random_validators, + set_some_new_deposits, +) from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits @@ -26,9 +28,11 @@ def test_transition_with_one_fourth_exiting_validators_exit_post_fork(state, pre_tag, post_tag): """ - 1/4 exiting but still active validators at the fork transition. + 1/4 validators initiated voluntary exit before the fork, + and are exiting but still active *after* the fork transition. """ - exited_indices = set_validators_exit_epoch(spec, state, exit_epoch=10, rng=random.Random(5566), fraction=0.25) + exited_indices = exit_random_validators( + spec, state, rng=random.Random(5566), fraction=0.25, exit_epoch=10, forward=False) transition_until_fork(spec, state, fork_epoch) @@ -55,7 +59,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_post_fork(state, assert any(set(exited_pubkeys).difference(list(state.current_sync_committee.pubkeys))) # continue regular state transition with new spec into next epoch - transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) # check state for index in exited_indices: @@ -76,9 +80,11 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork(state, pre_tag, post_tag): """ - 1/4 exiting but still active validators at the fork transition. + 1/4 validators initiated voluntary exit before the fork, + and being exited and inactive *right after* the fork transition. """ - exited_indices = set_validators_exit_epoch(spec, state, exit_epoch=2, rng=random.Random(5566), fraction=0.25) + exited_indices = exit_random_validators( + spec, state, rng=random.Random(5566), fraction=0.25, exit_epoch=fork_epoch, forward=False) transition_until_fork(spec, state, fork_epoch) @@ -111,7 +117,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork(state, assert not any(set(exited_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) # continue regular state transition with new spec into next epoch - transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) yield "blocks", blocks yield "post", state @@ -120,7 +126,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork(state, @fork_transition_test(PHASE0, ALTAIR, fork_epoch=260) def test_transition_with_voluntary_exit_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ - Create an attester slashing at the transition. + Create a voluntary exit at the transition. fork_epoch=260 because mainnet `SHARD_COMMITTEE_PERIOD` is 256 epochs. """ # Fast forward to the future epoch so that validator can do voluntary exit @@ -142,7 +148,7 @@ def test_transition_with_voluntary_exit_at_fork(state, fork_epoch, spec, post_sp assert validator.exit_epoch < post_spec.FAR_FUTURE_EPOCH # continue regular state transition with new spec into next epoch - transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) yield "blocks", blocks yield "post", state @@ -175,7 +181,7 @@ def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, pos blocks.append(post_tag(block)) # continue regular state transition with new spec into next epoch - transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) yield "blocks", blocks yield "post", state @@ -220,7 +226,7 @@ def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre assert state.finalized_checkpoint.epoch == state.validators[validator_index].activation_eligibility_epoch # continue regular state transition with new spec into next epoch - transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) assert state.validators[validator_index].activation_epoch < post_spec.FAR_FUTURE_EPOCH diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py index 086e43a93..6cdac1661 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py @@ -29,7 +29,7 @@ def test_transition_with_leaking_pre_fork(state, fork_epoch, spec, post_spec, pr assert spec.is_in_inactivity_leak(state) # continue regular state transition with new spec into next epoch - transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) yield "blocks", blocks yield "post", state @@ -57,38 +57,7 @@ def test_transition_with_leaking_at_fork(state, fork_epoch, spec, post_spec, pre assert spec.is_in_inactivity_leak(state) # continue regular state transition with new spec into next epoch - transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) - - yield "blocks", blocks - yield "post", state - - -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=5) -def test_transition_with_leaking_post_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): - """ - Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2). - The leaking starts after the fork transition in this case. - """ - transition_until_fork(spec, state, fork_epoch) - - assert not spec.is_in_inactivity_leak(state) - assert spec.get_current_epoch(state) < fork_epoch - - yield "pre", state - - # irregular state transition to handle fork: - blocks = [] - state, block = do_altair_fork(state, spec, post_spec, fork_epoch) - blocks.append(post_tag(block)) - - # check post transition state - assert not spec.is_in_inactivity_leak(state) - - # continue regular state transition with new spec into next epoch - transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) - - # check state again - assert spec.is_in_inactivity_leak(state) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) yield "blocks", blocks yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py index 86b78bd3e..6c9a8371b 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py @@ -16,8 +16,8 @@ from eth2spec.test.helpers.fork_transition import ( transition_until_fork, transition_to_next_epoch_and_append_blocks, ) -from eth2spec.test.helpers.inactivity_scores import ( - slash_some_validators_for_inactivity_scores_test, +from eth2spec.test.helpers.random import ( + slash_random_validators, ) @@ -32,8 +32,7 @@ def test_transition_with_one_fourth_slashed_active_validators_pre_fork(state, 1/4 validators are slashed but still active at the fork transition. """ # slash 1/4 validators - slashed_indices = slash_some_validators_for_inactivity_scores_test( - spec, state, rng=random.Random(5566), fraction=0.25) + slashed_indices = slash_random_validators(spec, state, rng=random.Random(5566), fraction=0.25) assert len(slashed_indices) > 0 # check if some validators are slashed but still active @@ -50,11 +49,9 @@ def test_transition_with_one_fourth_slashed_active_validators_pre_fork(state, yield "pre", state # irregular state transition to handle fork: - blocks = [] - state, block = do_altair_fork(state, spec, post_spec, fork_epoch) - blocks.append(post_tag(block)) + state, _ = do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False) - # ensure that some of the current sync committee members are the slashed + # ensure that some of the current sync committee members are slashed slashed_pubkeys = [state.validators[index].pubkey for index in slashed_indices] assert any(set(slashed_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) assert any(set(slashed_pubkeys).difference(list(state.current_sync_committee.pubkeys))) @@ -62,6 +59,7 @@ def test_transition_with_one_fourth_slashed_active_validators_pre_fork(state, # continue regular state transition with new spec into next epoch to_slot = post_spec.SLOTS_PER_EPOCH + state.slot # since the proposer might have been slashed, here we only create blocks with non-slashed proposers + blocks = [] blocks.extend([ post_tag(block) for block in state_transition_across_slots_with_ignoring_proposers(post_spec, state, to_slot, slashed_indices) @@ -103,7 +101,7 @@ def test_transition_with_attester_slashing_at_fork(state, fork_epoch, spec, post assert state.validators[validator_index].slashed # continue regular state transition with new spec into next epoch - transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) yield "blocks", blocks yield "post", state @@ -132,7 +130,7 @@ def test_transition_with_proposer_slashing_at_fork(state, fork_epoch, spec, post assert slashed_proposer.slashed # continue regular state transition with new spec into next epoch - transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) yield "blocks", blocks yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index ea5419d0d..277428bcf 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -21,11 +21,15 @@ def _state_transition_and_sign_block_at_slot(spec, been applied to ``state``. Used to produce a block during an irregular state transition. + + The optional `operation_dict` is a dict of {'': }. + This is used for assigning the block operations. + p.s. we can't just pass `body` and assign it because randao_reveal and eth1_data was set in `build_empty_block` + Thus use dict to pass operations. """ block = build_empty_block(spec, state) - # we can't just pass `body` and assign it because randao_reveal and eth1_data was set in `build_empty_block` - # thus use dict to pass operations. - if operation_dict is not None: + + if operation_dict: for key, value in operation_dict.items(): setattr(block.body, key, value) @@ -134,9 +138,15 @@ def transition_until_fork(spec, state, fork_epoch): transition_to(spec, state, to_slot) -def transition_to_next_epoch_and_append_blocks(spec, state, post_tag, blocks): +def transition_to_next_epoch_and_append_blocks(spec, state, post_tag, blocks, only_last_block=False): to_slot = spec.SLOTS_PER_EPOCH + state.slot + + if only_last_block: + block_filter = only_at(to_slot) + else: + block_filter = _all_blocks + blocks.extend([ post_tag(block) for block in - state_transition_across_slots(spec, state, to_slot) + state_transition_across_slots(spec, state, to_slot, block_filter=block_filter) ]) diff --git a/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py b/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py index 02aec7105..29f9038a8 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py +++ b/tests/core/pyspec/eth2spec/test/helpers/inactivity_scores.py @@ -1,9 +1,5 @@ from random import Random -from eth2spec.test.helpers.state import ( - next_epoch_via_block, -) - def randomize_inactivity_scores(spec, state, minimum=0, maximum=50000, rng=Random(4242)): state.inactivity_scores = [rng.randint(minimum, maximum) for _ in range(len(state.validators))] @@ -11,22 +7,3 @@ def randomize_inactivity_scores(spec, state, minimum=0, maximum=50000, rng=Rando def zero_inactivity_scores(spec, state, rng=None): state.inactivity_scores = [0] * len(state.validators) - - -def slash_some_validators_for_inactivity_scores_test(spec, state, rng=Random(40404040), fraction=0.25): - """ - ``run_inactivity_scores_test`` runs at the next epoch from `state`. - We retrieve the proposer of this future state to avoid - accidentally slashing that validator - """ - future_state = state.copy() - next_epoch_via_block(spec, future_state) - proposer_index = spec.get_beacon_proposer_index(future_state) - selected_count = int(len(state.validators) * fraction) - selected_indices = rng.sample(range(len(state.validators)), selected_count) - if proposer_index in selected_indices: - selected_indices.remove(proposer_index) - for validator_index in selected_indices: - spec.slash_validator(state, validator_index) - - return selected_indices diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index cd662d09d..4086431c0 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -26,29 +26,47 @@ def set_some_new_deposits(spec, state, rng): return eligible_indices, queuing_indices -def exit_random_validators(spec, state, rng, fraction=None): +def exit_random_validators(spec, state, rng, fraction=None, exit_epoch=None, withdrawable_epoch=None, forward=True): + """ + Set some validators' exit_epoch and withdrawable_epoch. + + If exit_epoch is configured, use the given exit_epoch. Otherwise, randomly set exit_epoch and withdrawable_epoch. + """ if fraction is None: # Exit ~1/2 fraction = 0.5 - if spec.get_current_epoch(state) < 5: - # Move epochs forward to allow for some validators already exited/withdrawable - for _ in range(5): - next_epoch(spec, state) + if forward: + if spec.get_current_epoch(state) < 5: + # Move epochs forward to allow for some validators already exited/withdrawable + for _ in range(5): + next_epoch(spec, state) current_epoch = spec.get_current_epoch(state) + exited_indices = [] for index in spec.get_active_validator_indices(state, current_epoch): sampled = rng.random() < fraction if not sampled: continue + exited_indices.append(index) validator = state.validators[index] - validator.exit_epoch = rng.choice([current_epoch, current_epoch - 1, current_epoch - 2, current_epoch - 3]) - # ~1/2 are withdrawable (note, unnatural span between exit epoch and withdrawable epoch) - if rng.choice([True, False]): - validator.withdrawable_epoch = current_epoch + if exit_epoch is None: + assert withdrawable_epoch is None + validator.exit_epoch = rng.choice([current_epoch, current_epoch - 1, current_epoch - 2, current_epoch - 3]) + # ~1/2 are withdrawable (note, unnatural span between exit epoch and withdrawable epoch) + if rng.choice([True, False]): + validator.withdrawable_epoch = current_epoch + else: + validator.withdrawable_epoch = current_epoch + 1 else: - validator.withdrawable_epoch = current_epoch + 1 + validator.exit_epoch = exit_epoch + if withdrawable_epoch is None: + validator.withdrawable_epoch = validator.exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + else: + validator.withdrawable_epoch = withdrawable_epoch + + return exited_indices def slash_random_validators(spec, state, rng, fraction=None): @@ -56,11 +74,14 @@ def slash_random_validators(spec, state, rng, fraction=None): # Slash ~1/2 of validators fraction = 0.5 + slashed_indices = [] for index in range(len(state.validators)): # slash at least one validator sampled = rng.random() < fraction if index == 0 or sampled: spec.slash_validator(state, index) + slashed_indices.append(index) + return slashed_indices def randomize_epoch_participation(spec, state, epoch, rng): diff --git a/tests/generators/transition/main.py b/tests/generators/transition/main.py index 0a9080db3..efe00995e 100644 --- a/tests/generators/transition/main.py +++ b/tests/generators/transition/main.py @@ -3,7 +3,7 @@ from typing import Iterable from eth2spec.test.helpers.constants import ALTAIR, MINIMAL, MAINNET, PHASE0 from eth2spec.test.altair.transition import ( test_transition as test_altair_transition, - test_activation as test_altair_activation, + test_activations_and_exits as test_altair_activations_and_exits, test_leaking as test_altair_leaking, test_slashing as test_altair_slashing, ) @@ -32,7 +32,7 @@ def create_provider(tests_src, preset_name: str, pre_fork_name: str, post_fork_n TRANSITION_TESTS = ( (PHASE0, ALTAIR, test_altair_transition), - (PHASE0, ALTAIR, test_altair_activation), + (PHASE0, ALTAIR, test_altair_activations_and_exits), (PHASE0, ALTAIR, test_altair_leaking), (PHASE0, ALTAIR, test_altair_slashing), ) From 3a242a1e0b7534bad0952d786c3235de95934b35 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 14 Oct 2021 23:29:20 +0800 Subject: [PATCH 08/17] Refactor and add test cases of having operation right before the fork --- .../transition/test_activations_and_exits.py | 89 --------- .../test/altair/transition/test_operations.py | 176 ++++++++++++++++++ .../test/altair/transition/test_slashing.py | 70 ------- .../eth2spec/test/helpers/fork_transition.py | 159 +++++++++++++++- tests/generators/transition/main.py | 2 + 5 files changed, 331 insertions(+), 165 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py index 573f7fba1..8f4cad2c5 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py @@ -1,11 +1,8 @@ import random from eth2spec.test.context import fork_transition_test from eth2spec.test.helpers.constants import PHASE0, ALTAIR -from eth2spec.test.helpers.attestations import next_slots_with_attestations -from eth2spec.test.helpers.deposits import prepare_state_and_deposit from eth2spec.test.helpers.fork_transition import ( do_altair_fork, - state_transition_across_slots, transition_until_fork, transition_to_next_epoch_and_append_blocks, ) @@ -13,7 +10,6 @@ from eth2spec.test.helpers.random import ( exit_random_validators, set_some_new_deposits, ) -from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits # @@ -123,37 +119,6 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork(state, yield "post", state -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=260) -def test_transition_with_voluntary_exit_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): - """ - Create a voluntary exit at the transition. - fork_epoch=260 because mainnet `SHARD_COMMITTEE_PERIOD` is 256 epochs. - """ - # Fast forward to the future epoch so that validator can do voluntary exit - state.slot = spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH - transition_until_fork(spec, state, fork_epoch) - - yield "pre", state - - validator_index = 0 - signed_exits = prepare_signed_exits(spec, state, [validator_index]) - operation_dict = {'voluntary_exits': signed_exits} - - # irregular state transition to handle fork: - state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=operation_dict) - blocks = [] - blocks.append(post_tag(block)) - - validator = state.validators[validator_index] - assert validator.exit_epoch < post_spec.FAR_FUTURE_EPOCH - - # continue regular state transition with new spec into next epoch - transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) - - yield "blocks", blocks - yield "post", state - - # # Activation # @@ -185,57 +150,3 @@ def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, pos yield "blocks", blocks yield "post", state - - -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) -def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): - """ - Create a deposit at the transition - """ - transition_until_fork(spec, state, fork_epoch) - - yield "pre", state - - # create a new deposit - validator_index = len(state.validators) - amount = post_spec.MAX_EFFECTIVE_BALANCE - deposit = prepare_state_and_deposit(post_spec, state, validator_index, amount, signed=True) - deposit_old = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True) - # sanity check: deposit operation is independent of spec fork versions - assert deposit_old == deposit - operation_dict = {'deposits': [deposit]} - # irregular state transition to handle fork: - state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=operation_dict) - blocks = [] - blocks.append(post_tag(block)) - - assert not post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state)) - - # continue regular state transition with new spec into next epoch - transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) - - # finalize activation_eligibility_epoch - _, blocks_in_epoch, state = next_slots_with_attestations( - post_spec, - state, - spec.SLOTS_PER_EPOCH * 2, - fill_cur_epoch=True, - fill_prev_epoch=True, - ) - blocks.extend([post_tag(block) for block in blocks_in_epoch]) - assert state.finalized_checkpoint.epoch == state.validators[validator_index].activation_eligibility_epoch - - # continue regular state transition with new spec into next epoch - transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) - - assert state.validators[validator_index].activation_epoch < post_spec.FAR_FUTURE_EPOCH - - to_slot = state.validators[validator_index].activation_epoch * post_spec.SLOTS_PER_EPOCH - blocks.extend([ - post_tag(block) for block in - state_transition_across_slots(post_spec, state, to_slot) - ]) - assert post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state)) - - yield "blocks", blocks - yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py new file mode 100644 index 000000000..09d3c25c5 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py @@ -0,0 +1,176 @@ +from eth2spec.test.context import ( + always_bls, + fork_transition_test, +) +from eth2spec.test.helpers.constants import PHASE0, ALTAIR +from eth2spec.test.helpers.fork_transition import ( + OperetionType, + run_transition_with_operation, +) + + +# +# PROPOSER_SLASHING +# + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@always_bls +def test_transition_with_proposer_slashing_right_after_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create an attester slashing right *after* the transition + """ + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperetionType.PROPOSER_SLASHING, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, + ) + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@always_bls +def test_transition_with_proposer_slashing_right_before_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create an attester slashing right *before* the transition + """ + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperetionType.PROPOSER_SLASHING, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1, + ) + + +# +# ATTESTER_SLASHING +# + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@always_bls +def test_transition_with_attester_slashing_right_after_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create an attester slashing right *after* the transition + """ + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperetionType.ATTESTER_SLASHING, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, + ) + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@always_bls +def test_transition_with_attester_slashing_right_before_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create an attester slashing right *after* the transition + """ + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperetionType.ATTESTER_SLASHING, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1, + ) + + +# +# DEPOSIT +# + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +def test_transition_with_deposit_right_after_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create a deposit right *after* the transition + """ + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperetionType.DEPOSIT, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, + ) + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +def test_transition_with_deposit_right_before_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create a deposit right *before* the transition + """ + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperetionType.DEPOSIT, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1, + ) + + +# +# VOLUNTARY_EXIT +# + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=260) +def test_transition_with_voluntary_exit_right_after_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create a voluntary exit right *after* the transition. + fork_epoch=260 because mainnet `SHARD_COMMITTEE_PERIOD` is 256 epochs. + """ + # Fast forward to the future epoch so that validator can do voluntary exit + state.slot = spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperetionType.VOLUNTARY_EXIT, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, + ) + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=260) +def test_transition_with_voluntary_exit_right_before_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create a voluntary exit right *before* the transition. + fork_epoch=260 because mainnet `SHARD_COMMITTEE_PERIOD` is 256 epochs. + """ + # Fast forward to the future epoch so that validator can do voluntary exit + state.slot = spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperetionType.VOLUNTARY_EXIT, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1, + ) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py index 6c9a8371b..0a5c7abeb 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py @@ -1,20 +1,12 @@ import random from eth2spec.test.context import ( - always_bls, fork_transition_test, ) from eth2spec.test.helpers.constants import PHASE0, ALTAIR -from eth2spec.test.helpers.attester_slashings import ( - get_valid_attester_slashing, -) -from eth2spec.test.helpers.proposer_slashings import ( - get_valid_proposer_slashing, -) from eth2spec.test.helpers.fork_transition import ( do_altair_fork, state_transition_across_slots_with_ignoring_proposers, transition_until_fork, - transition_to_next_epoch_and_append_blocks, ) from eth2spec.test.helpers.random import ( slash_random_validators, @@ -72,65 +64,3 @@ def test_transition_with_one_fourth_slashed_active_validators_pre_fork(state, yield "blocks", blocks yield "post", state - - -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) -@always_bls -def test_transition_with_attester_slashing_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): - """ - Create an attester slashing at the transition - """ - transition_until_fork(spec, state, fork_epoch) - - yield "pre", state - - # NOTE: it can only be created with pre spec - attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) - operation_dict = {'attester_slashings': [attester_slashing]} - - # irregular state transition to handle fork: - state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=operation_dict) - blocks = [] - blocks.append(post_tag(block)) - - indices = set(attester_slashing.attestation_1.attesting_indices).intersection( - attester_slashing.attestation_2.attesting_indices - ) - assert len(indices) > 0 - for validator_index in indices: - assert state.validators[validator_index].slashed - - # continue regular state transition with new spec into next epoch - transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) - - yield "blocks", blocks - yield "post", state - - -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) -@always_bls -def test_transition_with_proposer_slashing_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): - """ - Create an attester slashing at the transition - """ - transition_until_fork(spec, state, fork_epoch) - - yield "pre", state - - # NOTE: it can only be created with pre spec - proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) - operation_dict = {'proposer_slashings': [proposer_slashing]} - - # irregular state transition to handle fork: - state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=operation_dict) - blocks = [] - blocks.append(post_tag(block)) - - slashed_proposer = state.validators[proposer_slashing.signed_header_1.message.proposer_index] - assert slashed_proposer.slashed - - # continue regular state transition with new spec into next epoch - transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) - - yield "blocks", blocks - yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index 277428bcf..7f2b913e6 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -1,15 +1,41 @@ +from enum import Enum, auto import random -from eth2spec.test.helpers.state import ( - next_slot, - state_transition_and_sign_block, - transition_to, +from eth2spec.test.helpers.attester_slashings import ( + get_valid_attester_slashing, ) +from eth2spec.test.helpers.attestations import next_slots_with_attestations from eth2spec.test.helpers.block import ( build_empty_block_for_next_slot, build_empty_block, sign_block, ) +from eth2spec.test.helpers.deposits import ( + prepare_state_and_deposit, +) +from eth2spec.test.helpers.proposer_slashings import ( + get_valid_proposer_slashing, +) +from eth2spec.test.helpers.state import ( + next_slot, + state_transition_and_sign_block, + transition_to, +) +from eth2spec.test.helpers.voluntary_exits import ( + prepare_signed_exits, +) + + +class OperetionType(Enum): + PROPOSER_SLASHING = auto() + ATTESTER_SLASHING = auto() + DEPOSIT = auto() + VOLUNTARY_EXIT = auto() + + +def _set_operations_by_dict(block, operation_dict): + for key, value in operation_dict.items(): + setattr(block.body, key, value) def _state_transition_and_sign_block_at_slot(spec, @@ -30,8 +56,7 @@ def _state_transition_and_sign_block_at_slot(spec, block = build_empty_block(spec, state) if operation_dict: - for key, value in operation_dict.items(): - setattr(block.body, key, value) + _set_operations_by_dict(block, operation_dict) assert state.latest_block_header.slot < block.slot assert state.slot == block.slot @@ -138,6 +163,11 @@ def transition_until_fork(spec, state, fork_epoch): transition_to(spec, state, to_slot) +def _transition_until_fork_minus_one(spec, state, fork_epoch): + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 2 + transition_to(spec, state, to_slot) + + def transition_to_next_epoch_and_append_blocks(spec, state, post_tag, blocks, only_last_block=False): to_slot = spec.SLOTS_PER_EPOCH + state.slot @@ -150,3 +180,120 @@ def transition_to_next_epoch_and_append_blocks(spec, state, post_tag, blocks, on post_tag(block) for block in state_transition_across_slots(spec, state, to_slot, block_filter=block_filter) ]) + + +def run_transition_with_operation(state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type, + operation_at_slot): + is_at_fork = operation_at_slot == fork_epoch * spec.SLOTS_PER_EPOCH + is_right_before_fork = operation_at_slot == fork_epoch * spec.SLOTS_PER_EPOCH - 1 + assert is_at_fork or is_right_before_fork + + blocks = [] + + if is_at_fork: + transition_until_fork(spec, state, fork_epoch) + + if is_right_before_fork: + _transition_until_fork_minus_one(spec, state, fork_epoch) + + # prepare operation + selected_validator_index = None + if operation_type == OperetionType.PROPOSER_SLASHING: + proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) + operation_dict = {'proposer_slashings': [proposer_slashing]} + elif operation_type == OperetionType.ATTESTER_SLASHING: + attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) + operation_dict = {'attester_slashings': [attester_slashing]} + elif operation_type == OperetionType.DEPOSIT: + # create a new deposit + selected_validator_index = len(state.validators) + amount = post_spec.MAX_EFFECTIVE_BALANCE + deposit = prepare_state_and_deposit(post_spec, state, selected_validator_index, amount, signed=True) + operation_dict = {'deposits': [deposit]} + elif operation_type == OperetionType.VOLUNTARY_EXIT: + selected_validator_index = 0 + signed_exits = prepare_signed_exits(spec, state, [selected_validator_index]) + operation_dict = {'voluntary_exits': signed_exits} + + if is_right_before_fork: + # add a block with operation. + block = build_empty_block_for_next_slot(spec, state) + _set_operations_by_dict(block, operation_dict) + signed_block = state_transition_and_sign_block(spec, state, block) + blocks.append(pre_tag(signed_block)) + + def _check_state(): + if operation_type == OperetionType.PROPOSER_SLASHING: + slashed_proposer = state.validators[proposer_slashing.signed_header_1.message.proposer_index] + assert slashed_proposer.slashed + elif operation_type == OperetionType.ATTESTER_SLASHING: + indices = set(attester_slashing.attestation_1.attesting_indices).intersection( + attester_slashing.attestation_2.attesting_indices + ) + assert len(indices) > 0 + for validator_index in indices: + assert state.validators[validator_index].slashed + elif operation_type == OperetionType.DEPOSIT: + assert not post_spec.is_active_validator( + state.validators[selected_validator_index], + post_spec.get_current_epoch(state) + ) + elif operation_type == OperetionType.VOLUNTARY_EXIT: + validator = state.validators[selected_validator_index] + assert validator.exit_epoch < post_spec.FAR_FUTURE_EPOCH + + if is_right_before_fork: + _check_state() + + yield "pre", state + + # irregular state transition to handle fork: + _operation_at_slot = operation_dict if is_at_fork else None + state, block = do_altair_fork(state, spec, post_spec, fork_epoch, operation_dict=_operation_at_slot) + blocks.append(post_tag(block)) + + if is_at_fork: + _check_state() + + # after the fork + if operation_type == OperetionType.DEPOSIT: + _transition_until_active(post_spec, state, post_tag, blocks, selected_validator_index) + else: + # continue regular state transition with new spec into next epoch + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) + + yield "blocks", blocks + yield "post", state + + +def _transition_until_active(post_spec, state, post_tag, blocks, validator_index): + # continue regular state transition with new spec into next epoch + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) + # finalize activation_eligibility_epoch + _, blocks_in_epoch, state = next_slots_with_attestations( + post_spec, + state, + post_spec.SLOTS_PER_EPOCH * 2, + fill_cur_epoch=True, + fill_prev_epoch=True, + ) + blocks.extend([post_tag(block) for block in blocks_in_epoch]) + assert state.finalized_checkpoint.epoch >= state.validators[validator_index].activation_eligibility_epoch + + # continue regular state transition with new spec into next epoch + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) + + assert state.validators[validator_index].activation_epoch < post_spec.FAR_FUTURE_EPOCH + + to_slot = state.validators[validator_index].activation_epoch * post_spec.SLOTS_PER_EPOCH + blocks.extend([ + post_tag(block) for block in + state_transition_across_slots(post_spec, state, to_slot, block_filter=only_at(to_slot)) + ]) + assert post_spec.is_active_validator(state.validators[validator_index], post_spec.get_current_epoch(state)) diff --git a/tests/generators/transition/main.py b/tests/generators/transition/main.py index efe00995e..a850a7f45 100644 --- a/tests/generators/transition/main.py +++ b/tests/generators/transition/main.py @@ -6,6 +6,7 @@ from eth2spec.test.altair.transition import ( test_activations_and_exits as test_altair_activations_and_exits, test_leaking as test_altair_leaking, test_slashing as test_altair_slashing, + test_operations as test_altair_operations, ) from eth2spec.gen_helpers.gen_base import gen_runner, gen_typing @@ -35,6 +36,7 @@ TRANSITION_TESTS = ( (PHASE0, ALTAIR, test_altair_activations_and_exits), (PHASE0, ALTAIR, test_altair_leaking), (PHASE0, ALTAIR, test_altair_slashing), + (PHASE0, ALTAIR, test_altair_operations), ) From f62167c4abe6fec6784bc937b841aa77a1e5e9b0 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 Oct 2021 00:30:24 +0800 Subject: [PATCH 09/17] pr feedback --- .../test/altair/transition/test_activations_and_exits.py | 2 +- tests/core/pyspec/eth2spec/test/helpers/random.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py index 8f4cad2c5..86742042f 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py @@ -131,7 +131,7 @@ def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, pos """ transition_until_fork(spec, state, fork_epoch) - _, queuing_indices = set_some_new_deposits(spec, state, rng=random.Random(5566)) + queuing_indices = set_some_new_deposits(spec, state, rng=random.Random(5566)) assert spec.get_current_epoch(state) < fork_epoch assert len(queuing_indices) > 0 diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index 4086431c0..6d51d812e 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -7,7 +7,6 @@ from eth2spec.test.helpers.state import next_epoch def set_some_new_deposits(spec, state, rng): - eligible_indices = [] queuing_indices = [] num_validators = len(state.validators) # Set ~1/10 to just recently deposited @@ -20,10 +19,9 @@ def set_some_new_deposits(spec, state, rng): # Set ~half of selected to eligible for activation if rng.choice([True, False]): state.validators[index].activation_eligibility_epoch = spec.get_current_epoch(state) - eligible_indices.append(index) else: queuing_indices.append(index) - return eligible_indices, queuing_indices + return queuing_indices def exit_random_validators(spec, state, rng, fraction=None, exit_epoch=None, withdrawable_epoch=None, forward=True): From 52235a9e40eab6be47cdf3b5da13c80fc267a6d6 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 Oct 2021 00:42:14 +0800 Subject: [PATCH 10/17] minor: Use pre spec for running `prepare_state_and_deposit` --- .../core/pyspec/eth2spec/test/helpers/fork_transition.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index 7f2b913e6..47e0c803a 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -190,6 +190,10 @@ def run_transition_with_operation(state, post_tag, operation_type, operation_at_slot): + """ + Generate `operation_type` operation with the spec before fork. + The operation would be included into the block at `operation_at_slot`. + """ is_at_fork = operation_at_slot == fork_epoch * spec.SLOTS_PER_EPOCH is_right_before_fork = operation_at_slot == fork_epoch * spec.SLOTS_PER_EPOCH - 1 assert is_at_fork or is_right_before_fork @@ -213,8 +217,8 @@ def run_transition_with_operation(state, elif operation_type == OperetionType.DEPOSIT: # create a new deposit selected_validator_index = len(state.validators) - amount = post_spec.MAX_EFFECTIVE_BALANCE - deposit = prepare_state_and_deposit(post_spec, state, selected_validator_index, amount, signed=True) + amount = spec.MAX_EFFECTIVE_BALANCE + deposit = prepare_state_and_deposit(spec, state, selected_validator_index, amount, signed=True) operation_dict = {'deposits': [deposit]} elif operation_type == OperetionType.VOLUNTARY_EXIT: selected_validator_index = 0 From a4e5d50660f5af57e9c8b75987f7478f04e76098 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 Oct 2021 11:57:10 +0800 Subject: [PATCH 11/17] Fix/ignore mainnet preset cases --- .../transition/test_activations_and_exits.py | 7 ++++++- .../test/altair/transition/test_slashing.py | 3 +++ .../eth2spec/test/helpers/fork_transition.py | 15 ++++++++++----- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py index 86742042f..7ef11f629 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py @@ -1,5 +1,9 @@ import random -from eth2spec.test.context import fork_transition_test +from eth2spec.test.context import ( + MINIMAL, + fork_transition_test, + with_presets, +) from eth2spec.test.helpers.constants import PHASE0, ALTAIR from eth2spec.test.helpers.fork_transition import ( do_altair_fork, @@ -17,6 +21,7 @@ from eth2spec.test.helpers.random import ( # @fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@with_presets([MINIMAL], reason="only test with non-full committee") def test_transition_with_one_fourth_exiting_validators_exit_post_fork(state, fork_epoch, spec, diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py index 0a5c7abeb..20ab89c1a 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py @@ -1,6 +1,8 @@ import random from eth2spec.test.context import ( + MINIMAL, fork_transition_test, + with_presets, ) from eth2spec.test.helpers.constants import PHASE0, ALTAIR from eth2spec.test.helpers.fork_transition import ( @@ -14,6 +16,7 @@ from eth2spec.test.helpers.random import ( @fork_transition_test(PHASE0, ALTAIR, fork_epoch=1) +@with_presets([MINIMAL], reason="only test with non-full committee") def test_transition_with_one_fourth_slashed_active_validators_pre_fork(state, fork_epoch, spec, diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index 47e0c803a..63a92fce2 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -198,18 +198,21 @@ def run_transition_with_operation(state, is_right_before_fork = operation_at_slot == fork_epoch * spec.SLOTS_PER_EPOCH - 1 assert is_at_fork or is_right_before_fork - blocks = [] - if is_at_fork: transition_until_fork(spec, state, fork_epoch) - - if is_right_before_fork: + elif is_right_before_fork: _transition_until_fork_minus_one(spec, state, fork_epoch) # prepare operation selected_validator_index = None if operation_type == OperetionType.PROPOSER_SLASHING: - proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) + # avoid slashing the next proposer + future_state = state.copy() + next_slot(spec, future_state) + proposer_index = spec.get_beacon_proposer_index(future_state) + selected_validator_index = (proposer_index + 1) % len(state.validators) + proposer_slashing = get_valid_proposer_slashing( + spec, state, slashed_index=selected_validator_index, signed_1=True, signed_2=True) operation_dict = {'proposer_slashings': [proposer_slashing]} elif operation_type == OperetionType.ATTESTER_SLASHING: attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) @@ -225,6 +228,8 @@ def run_transition_with_operation(state, signed_exits = prepare_signed_exits(spec, state, [selected_validator_index]) operation_dict = {'voluntary_exits': signed_exits} + blocks = [] + if is_right_before_fork: # add a block with operation. block = build_empty_block_for_next_slot(spec, state) From 162711ea564fb376c848f2b839140160f8f6722e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 Oct 2021 21:08:21 +0800 Subject: [PATCH 12/17] PR feedback. Rework `transition_to_next_epoch_and_append_blocks` a bit --- .../transition/test_activations_and_exits.py | 25 +++-- .../test/altair/transition/test_operations.py | 18 ++-- .../test/altair/transition/test_slashing.py | 18 ++-- .../eth2spec/test/helpers/fork_transition.py | 97 ++++++++++++------- .../pyspec/eth2spec/test/helpers/random.py | 21 ++-- 5 files changed, 108 insertions(+), 71 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py index 7ef11f629..e91493250 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py @@ -21,7 +21,8 @@ from eth2spec.test.helpers.random import ( # @fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) -@with_presets([MINIMAL], reason="only test with non-full committee") +@with_presets([MINIMAL], + reason="only test with enough validators such that at lease one exited index is not in sync committee") def test_transition_with_one_fourth_exiting_validators_exit_post_fork(state, fork_epoch, spec, @@ -33,7 +34,13 @@ def test_transition_with_one_fourth_exiting_validators_exit_post_fork(state, and are exiting but still active *after* the fork transition. """ exited_indices = exit_random_validators( - spec, state, rng=random.Random(5566), fraction=0.25, exit_epoch=10, forward=False) + spec, + state, + rng=random.Random(5566), + fraction=0.25, + exit_epoch=10, + forward=False, + ) transition_until_fork(spec, state, fork_epoch) @@ -85,7 +92,13 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork(state, and being exited and inactive *right after* the fork transition. """ exited_indices = exit_random_validators( - spec, state, rng=random.Random(5566), fraction=0.25, exit_epoch=fork_epoch, forward=False) + spec, + state, + rng=random.Random(5566), + fraction=0.25, + exit_epoch=fork_epoch, + forward=False, + ) transition_until_fork(spec, state, fork_epoch) @@ -136,11 +149,11 @@ def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, pos """ transition_until_fork(spec, state, fork_epoch) - queuing_indices = set_some_new_deposits(spec, state, rng=random.Random(5566)) + deposited_indices = set_some_new_deposits(spec, state, rng=random.Random(5566)) assert spec.get_current_epoch(state) < fork_epoch - assert len(queuing_indices) > 0 - for validator_index in queuing_indices: + assert len(deposited_indices) > 0 + for validator_index in deposited_indices: assert not spec.is_active_validator(state.validators[validator_index], spec.get_current_epoch(state)) yield "pre", state diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py index 09d3c25c5..e19c57fb1 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py @@ -4,7 +4,7 @@ from eth2spec.test.context import ( ) from eth2spec.test.helpers.constants import PHASE0, ALTAIR from eth2spec.test.helpers.fork_transition import ( - OperetionType, + OperationType, run_transition_with_operation, ) @@ -26,7 +26,7 @@ def test_transition_with_proposer_slashing_right_after_fork(state, fork_epoch, s post_spec, pre_tag, post_tag, - operation_type=OperetionType.PROPOSER_SLASHING, + operation_type=OperationType.PROPOSER_SLASHING, operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, ) @@ -44,7 +44,7 @@ def test_transition_with_proposer_slashing_right_before_fork(state, fork_epoch, post_spec, pre_tag, post_tag, - operation_type=OperetionType.PROPOSER_SLASHING, + operation_type=OperationType.PROPOSER_SLASHING, operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1, ) @@ -67,7 +67,7 @@ def test_transition_with_attester_slashing_right_after_fork(state, fork_epoch, s post_spec, pre_tag, post_tag, - operation_type=OperetionType.ATTESTER_SLASHING, + operation_type=OperationType.ATTESTER_SLASHING, operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, ) @@ -85,7 +85,7 @@ def test_transition_with_attester_slashing_right_before_fork(state, fork_epoch, post_spec, pre_tag, post_tag, - operation_type=OperetionType.ATTESTER_SLASHING, + operation_type=OperationType.ATTESTER_SLASHING, operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1, ) @@ -107,7 +107,7 @@ def test_transition_with_deposit_right_after_fork(state, fork_epoch, spec, post_ post_spec, pre_tag, post_tag, - operation_type=OperetionType.DEPOSIT, + operation_type=OperationType.DEPOSIT, operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, ) @@ -124,7 +124,7 @@ def test_transition_with_deposit_right_before_fork(state, fork_epoch, spec, post post_spec, pre_tag, post_tag, - operation_type=OperetionType.DEPOSIT, + operation_type=OperationType.DEPOSIT, operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1, ) @@ -150,7 +150,7 @@ def test_transition_with_voluntary_exit_right_after_fork(state, fork_epoch, spec post_spec, pre_tag, post_tag, - operation_type=OperetionType.VOLUNTARY_EXIT, + operation_type=OperationType.VOLUNTARY_EXIT, operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, ) @@ -171,6 +171,6 @@ def test_transition_with_voluntary_exit_right_before_fork(state, fork_epoch, spe post_spec, pre_tag, post_tag, - operation_type=OperetionType.VOLUNTARY_EXIT, + operation_type=OperationType.VOLUNTARY_EXIT, operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1, ) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py index 20ab89c1a..975733eeb 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py @@ -7,7 +7,7 @@ from eth2spec.test.context import ( from eth2spec.test.helpers.constants import PHASE0, ALTAIR from eth2spec.test.helpers.fork_transition import ( do_altair_fork, - state_transition_across_slots_with_ignoring_proposers, + transition_to_next_epoch_and_append_blocks, transition_until_fork, ) from eth2spec.test.helpers.random import ( @@ -16,7 +16,8 @@ from eth2spec.test.helpers.random import ( @fork_transition_test(PHASE0, ALTAIR, fork_epoch=1) -@with_presets([MINIMAL], reason="only test with non-full committee") +@with_presets([MINIMAL], + reason="only test with enough validators such that at lease one exited index is not in sync committee") def test_transition_with_one_fourth_slashed_active_validators_pre_fork(state, fork_epoch, spec, @@ -52,13 +53,16 @@ def test_transition_with_one_fourth_slashed_active_validators_pre_fork(state, assert any(set(slashed_pubkeys).difference(list(state.current_sync_committee.pubkeys))) # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot # since the proposer might have been slashed, here we only create blocks with non-slashed proposers blocks = [] - blocks.extend([ - post_tag(block) for block in - state_transition_across_slots_with_ignoring_proposers(post_spec, state, to_slot, slashed_indices) - ]) + transition_to_next_epoch_and_append_blocks( + post_spec, + state, + post_tag, + blocks, + only_last_block=True, + ignoring_proposers=slashed_indices, + ) # check post state for validator in state.validators: diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index 63a92fce2..947954b80 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -1,8 +1,7 @@ from enum import Enum, auto -import random from eth2spec.test.helpers.attester_slashings import ( - get_valid_attester_slashing, + get_valid_attester_slashing_by_indices, ) from eth2spec.test.helpers.attestations import next_slots_with_attestations from eth2spec.test.helpers.block import ( @@ -26,7 +25,7 @@ from eth2spec.test.helpers.voluntary_exits import ( ) -class OperetionType(Enum): +class OperationType(Enum): PROPOSER_SLASHING = auto() ATTESTER_SLASHING = auto() DEPOSIT = auto() @@ -104,7 +103,11 @@ def state_transition_across_slots(spec, state, to_slot, block_filter=_all_blocks next_slot(spec, state) -def state_transition_across_slots_with_ignoring_proposers(spec, state, to_slot, ignoring_proposers): +def state_transition_across_slots_with_ignoring_proposers(spec, + state, + to_slot, + ignoring_proposers, + only_last_block=False): """ The slashed validators can't be proposers. Here we ignore the given `ignoring_proposers` and ensure that the result state was computed with a block with slot >= to_slot. @@ -113,6 +116,10 @@ def state_transition_across_slots_with_ignoring_proposers(spec, state, to_slot, found_valid = False while state.slot < to_slot or not found_valid: + if state.slot + 1 < to_slot and only_last_block: + next_slot(spec, state) + continue + future_state = state.copy() next_slot(spec, future_state) proposer_index = spec.get_beacon_proposer_index(future_state) @@ -144,20 +151,6 @@ def do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True, operatio return state, None -def set_validators_exit_epoch(spec, state, exit_epoch, rng=random.Random(40404040), fraction=0.25): - """ - Set some valdiators' `exit_epoch` and `withdrawable_epoch`. - """ - selected_count = int(len(state.validators) * fraction) - selected_indices = rng.sample(range(len(state.validators)), selected_count) - for validator_index in selected_indices: - state.validators[validator_index].exit_epoch = exit_epoch - state.validators[validator_index].withdrawable_epoch = ( - exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY - ) - return selected_indices - - def transition_until_fork(spec, state, fork_epoch): to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 transition_to(spec, state, to_slot) @@ -168,7 +161,12 @@ def _transition_until_fork_minus_one(spec, state, fork_epoch): transition_to(spec, state, to_slot) -def transition_to_next_epoch_and_append_blocks(spec, state, post_tag, blocks, only_last_block=False): +def transition_to_next_epoch_and_append_blocks(spec, + state, + post_tag, + blocks, + only_last_block=False, + ignoring_proposers=None): to_slot = spec.SLOTS_PER_EPOCH + state.slot if only_last_block: @@ -176,9 +174,20 @@ def transition_to_next_epoch_and_append_blocks(spec, state, post_tag, blocks, on else: block_filter = _all_blocks + if ignoring_proposers is None: + result_blocks = state_transition_across_slots(spec, state, to_slot, block_filter=block_filter) + else: + result_blocks = state_transition_across_slots_with_ignoring_proposers( + spec, + state, + to_slot, + ignoring_proposers, + only_last_block=only_last_block, + ) + blocks.extend([ post_tag(block) for block in - state_transition_across_slots(spec, state, to_slot, block_filter=block_filter) + result_blocks ]) @@ -203,27 +212,34 @@ def run_transition_with_operation(state, elif is_right_before_fork: _transition_until_fork_minus_one(spec, state, fork_epoch) + is_slashing_operation = operation_type in (OperationType.PROPOSER_SLASHING, OperationType.ATTESTER_SLASHING) # prepare operation selected_validator_index = None - if operation_type == OperetionType.PROPOSER_SLASHING: + if is_slashing_operation: # avoid slashing the next proposer future_state = state.copy() next_slot(spec, future_state) proposer_index = spec.get_beacon_proposer_index(future_state) selected_validator_index = (proposer_index + 1) % len(state.validators) - proposer_slashing = get_valid_proposer_slashing( - spec, state, slashed_index=selected_validator_index, signed_1=True, signed_2=True) - operation_dict = {'proposer_slashings': [proposer_slashing]} - elif operation_type == OperetionType.ATTESTER_SLASHING: - attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) - operation_dict = {'attester_slashings': [attester_slashing]} - elif operation_type == OperetionType.DEPOSIT: + if operation_type == OperationType.PROPOSER_SLASHING: + proposer_slashing = get_valid_proposer_slashing( + spec, state, slashed_index=selected_validator_index, signed_1=True, signed_2=True) + operation_dict = {'proposer_slashings': [proposer_slashing]} + else: + # operation_type == OperationType.ATTESTER_SLASHING: + attester_slashing = get_valid_attester_slashing_by_indices( + spec, state, + [selected_validator_index], + signed_1=True, signed_2=True, + ) + operation_dict = {'attester_slashings': [attester_slashing]} + elif operation_type == OperationType.DEPOSIT: # create a new deposit selected_validator_index = len(state.validators) amount = spec.MAX_EFFECTIVE_BALANCE deposit = prepare_state_and_deposit(spec, state, selected_validator_index, amount, signed=True) operation_dict = {'deposits': [deposit]} - elif operation_type == OperetionType.VOLUNTARY_EXIT: + elif operation_type == OperationType.VOLUNTARY_EXIT: selected_validator_index = 0 signed_exits = prepare_signed_exits(spec, state, [selected_validator_index]) operation_dict = {'voluntary_exits': signed_exits} @@ -238,22 +254,23 @@ def run_transition_with_operation(state, blocks.append(pre_tag(signed_block)) def _check_state(): - if operation_type == OperetionType.PROPOSER_SLASHING: + if operation_type == OperationType.PROPOSER_SLASHING: slashed_proposer = state.validators[proposer_slashing.signed_header_1.message.proposer_index] assert slashed_proposer.slashed - elif operation_type == OperetionType.ATTESTER_SLASHING: + elif operation_type == OperationType.ATTESTER_SLASHING: indices = set(attester_slashing.attestation_1.attesting_indices).intersection( attester_slashing.attestation_2.attesting_indices ) + assert selected_validator_index in indices assert len(indices) > 0 for validator_index in indices: assert state.validators[validator_index].slashed - elif operation_type == OperetionType.DEPOSIT: + elif operation_type == OperationType.DEPOSIT: assert not post_spec.is_active_validator( state.validators[selected_validator_index], post_spec.get_current_epoch(state) ) - elif operation_type == OperetionType.VOLUNTARY_EXIT: + elif operation_type == OperationType.VOLUNTARY_EXIT: validator = state.validators[selected_validator_index] assert validator.exit_epoch < post_spec.FAR_FUTURE_EPOCH @@ -271,11 +288,21 @@ def run_transition_with_operation(state, _check_state() # after the fork - if operation_type == OperetionType.DEPOSIT: + if operation_type == OperationType.DEPOSIT: _transition_until_active(post_spec, state, post_tag, blocks, selected_validator_index) else: + # avoid using the slashed validators as block proposers + ignoring_proposers = [selected_validator_index] if is_slashing_operation else None + # continue regular state transition with new spec into next epoch - transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) + transition_to_next_epoch_and_append_blocks( + post_spec, + state, + post_tag, + blocks, + only_last_block=True, + ignoring_proposers=ignoring_proposers, + ) yield "blocks", blocks yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index 6d51d812e..4f6139989 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -7,7 +7,7 @@ from eth2spec.test.helpers.state import next_epoch def set_some_new_deposits(spec, state, rng): - queuing_indices = [] + deposited_indices = [] num_validators = len(state.validators) # Set ~1/10 to just recently deposited for index in range(num_validators): @@ -16,24 +16,21 @@ def set_some_new_deposits(spec, state, rng): continue if rng.randrange(num_validators) < num_validators // 10: mock_deposit(spec, state, index) - # Set ~half of selected to eligible for activation if rng.choice([True, False]): + # Set ~half of selected to eligible for activation state.validators[index].activation_eligibility_epoch = spec.get_current_epoch(state) else: - queuing_indices.append(index) - return queuing_indices + # The validators that just made a deposit + deposited_indices.append(index) + return deposited_indices -def exit_random_validators(spec, state, rng, fraction=None, exit_epoch=None, withdrawable_epoch=None, forward=True): +def exit_random_validators(spec, state, rng, fraction=0.5, exit_epoch=None, withdrawable_epoch=None, forward=True): """ Set some validators' exit_epoch and withdrawable_epoch. If exit_epoch is configured, use the given exit_epoch. Otherwise, randomly set exit_epoch and withdrawable_epoch. """ - if fraction is None: - # Exit ~1/2 - fraction = 0.5 - if forward: if spec.get_current_epoch(state) < 5: # Move epochs forward to allow for some validators already exited/withdrawable @@ -67,11 +64,7 @@ def exit_random_validators(spec, state, rng, fraction=None, exit_epoch=None, wit return exited_indices -def slash_random_validators(spec, state, rng, fraction=None): - if fraction is None: - # Slash ~1/2 of validators - fraction = 0.5 - +def slash_random_validators(spec, state, rng, fraction=0.5): slashed_indices = [] for index in range(len(state.validators)): # slash at least one validator From be6d2017bb93c0d6c1a49b7653bdc56afdd7030a Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 Oct 2021 22:05:07 +0800 Subject: [PATCH 13/17] fix typo --- .../test/altair/transition/test_activations_and_exits.py | 2 +- .../pyspec/eth2spec/test/altair/transition/test_slashing.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py index e91493250..2ccecec1a 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py @@ -22,7 +22,7 @@ from eth2spec.test.helpers.random import ( @fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) @with_presets([MINIMAL], - reason="only test with enough validators such that at lease one exited index is not in sync committee") + reason="only test with enough validators such that at least one exited index is not in sync committee") def test_transition_with_one_fourth_exiting_validators_exit_post_fork(state, fork_epoch, spec, diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py index 975733eeb..211a4fbfe 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py @@ -17,7 +17,7 @@ from eth2spec.test.helpers.random import ( @fork_transition_test(PHASE0, ALTAIR, fork_epoch=1) @with_presets([MINIMAL], - reason="only test with enough validators such that at lease one exited index is not in sync committee") + reason="only test with enough validators such that at least one exited index is not in sync committee") def test_transition_with_one_fourth_slashed_active_validators_pre_fork(state, fork_epoch, spec, From 40869d6e39bd84cba0b371d23b8011b137bb7369 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 Oct 2021 22:14:30 +0800 Subject: [PATCH 14/17] PR feedback on `exit_random_validators` helper --- .../altair/transition/test_activations_and_exits.py | 4 ++-- tests/core/pyspec/eth2spec/test/helpers/random.py | 13 +++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py index 2ccecec1a..25a76a686 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py @@ -39,7 +39,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_post_fork(state, rng=random.Random(5566), fraction=0.25, exit_epoch=10, - forward=False, + from_epoch=spec.get_current_epoch(state), ) transition_until_fork(spec, state, fork_epoch) @@ -97,7 +97,7 @@ def test_transition_with_one_fourth_exiting_validators_exit_at_fork(state, rng=random.Random(5566), fraction=0.25, exit_epoch=fork_epoch, - forward=False, + from_epoch=spec.get_current_epoch(state), ) transition_until_fork(spec, state, fork_epoch) diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index 4f6139989..1332d1e90 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -25,17 +25,18 @@ def set_some_new_deposits(spec, state, rng): return deposited_indices -def exit_random_validators(spec, state, rng, fraction=0.5, exit_epoch=None, withdrawable_epoch=None, forward=True): +def exit_random_validators(spec, state, rng, fraction=0.5, exit_epoch=None, withdrawable_epoch=None, from_epoch=None): """ Set some validators' exit_epoch and withdrawable_epoch. If exit_epoch is configured, use the given exit_epoch. Otherwise, randomly set exit_epoch and withdrawable_epoch. """ - if forward: - if spec.get_current_epoch(state) < 5: - # Move epochs forward to allow for some validators already exited/withdrawable - for _ in range(5): - next_epoch(spec, state) + if from_epoch is None: + from_epoch = spec.MAX_SEED_LOOKAHEAD + 1 + epoch_diff = from_epoch - spec.get_current_epoch(state) + for _ in range(epoch_diff): + # NOTE: if `epoch_diff` is negative, then this loop body does not execute. + next_epoch(spec, state) current_epoch = spec.get_current_epoch(state) exited_indices = [] From b0b3733243c15a45e030b9830302b5097e63cbfe Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 Oct 2021 22:18:13 +0800 Subject: [PATCH 15/17] Fix `randomize_state` default params --- tests/core/pyspec/eth2spec/test/helpers/random.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index 1332d1e90..049bb1a0d 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -142,7 +142,7 @@ def randomize_attestation_participation(spec, state, rng=Random(8020)): randomize_epoch_participation(spec, state, spec.get_current_epoch(state), rng) -def randomize_state(spec, state, rng=Random(8020), exit_fraction=None, slash_fraction=None): +def randomize_state(spec, state, rng=Random(8020), exit_fraction=0.5, slash_fraction=0.5): set_some_new_deposits(spec, state, rng) exit_random_validators(spec, state, rng, fraction=exit_fraction) slash_random_validators(spec, state, rng, fraction=slash_fraction) From 7480fad8cd2cccea758902e6d8b89195fe60a926 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 Oct 2021 22:46:16 +0800 Subject: [PATCH 16/17] Add `test_transition_with_activation_at_fork_epoch` --- .../transition/test_activations_and_exits.py | 36 +++++++++++++++++++ .../pyspec/eth2spec/test/helpers/random.py | 20 +++++++++++ 2 files changed, 56 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py index 25a76a686..12aa815ad 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py @@ -12,6 +12,7 @@ from eth2spec.test.helpers.fork_transition import ( ) from eth2spec.test.helpers.random import ( exit_random_validators, + set_some_activations, set_some_new_deposits, ) @@ -168,3 +169,38 @@ def test_transition_with_non_empty_activation_queue(state, fork_epoch, spec, pos yield "blocks", blocks yield "post", state + + +@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +def test_transition_with_activation_at_fork_epoch(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Create some deposits before the transition + """ + transition_until_fork(spec, state, fork_epoch) + + selected_indices = set_some_activations(spec, state, rng=random.Random(5566), activation_epoch=fork_epoch) + + assert spec.get_current_epoch(state) < fork_epoch + assert len(selected_indices) > 0 + for validator_index in selected_indices: + validator = state.validators[validator_index] + assert not spec.is_active_validator(validator, spec.get_current_epoch(state)) + assert validator.activation_epoch == fork_epoch + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = do_altair_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # continue regular state transition with new spec into next epoch + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks, only_last_block=True) + + # now they are active + for validator_index in selected_indices: + validator = state.validators[validator_index] + assert post_spec.is_active_validator(validator, post_spec.get_current_epoch(state)) + + yield "blocks", blocks + yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index 049bb1a0d..bf603b2c8 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -6,6 +6,26 @@ from eth2spec.test.helpers.deposits import mock_deposit from eth2spec.test.helpers.state import next_epoch +def set_some_activations(spec, state, rng, activation_epoch=None): + if activation_epoch is None: + activation_epoch = spec.get_current_epoch(state) + num_validators = len(state.validators) + selected_indices = [] + for index in range(num_validators): + # If is slashed or exiting, skip + if state.validators[index].slashed or state.validators[index].exit_epoch != spec.FAR_FUTURE_EPOCH: + continue + # Set ~1/10 validators' activation_eligibility_epoch and activation_epoch + if rng.randrange(num_validators) < num_validators // 10: + state.validators[index].activation_eligibility_epoch = max( + int(activation_epoch) - int(spec.MAX_SEED_LOOKAHEAD) - 1, + spec.GENESIS_EPOCH, + ) + state.validators[index].activation_epoch = activation_epoch + selected_indices.append(index) + return selected_indices + + def set_some_new_deposits(spec, state, rng): deposited_indices = [] num_validators = len(state.validators) From e70ef11b4d32473caf989ea3f61d09d596800767 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 Oct 2021 22:49:43 +0800 Subject: [PATCH 17/17] Fix SSZ underflow --- tests/core/pyspec/eth2spec/test/helpers/random.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index bf603b2c8..0dc446d19 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -53,7 +53,7 @@ def exit_random_validators(spec, state, rng, fraction=0.5, exit_epoch=None, with """ if from_epoch is None: from_epoch = spec.MAX_SEED_LOOKAHEAD + 1 - epoch_diff = from_epoch - spec.get_current_epoch(state) + epoch_diff = int(from_epoch) - int(spec.get_current_epoch(state)) for _ in range(epoch_diff): # NOTE: if `epoch_diff` is negative, then this loop body does not execute. next_epoch(spec, state)