From b861207783ea71adf526d3163ca977db869b3e9c Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 14 Oct 2021 21:45:33 +0800 Subject: [PATCH] PR feedback --- .../test_process_inactivity_updates.py | 15 ++++++- .../transition/test_activations_and_exits.py | 35 ++++++++-------- .../test/altair/transition/test_leaking.py | 35 +--------------- .../test/altair/transition/test_slashing.py | 21 +++++----- .../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, 94 insertions(+), 100 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 f262bcdd2d..1ac622dd97 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.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 50e97d24ee..35d6b9aed2 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.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 @@ -205,9 +211,6 @@ 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 - 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, @@ -217,10 +220,10 @@ def test_transition_with_deposit_at_fork(state, fork_epoch, spec, post_spec, pre 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 + 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 086e43a93e..6cdac16610 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 86b78bd3ec..d41f5489cc 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 @@ 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,10 @@ 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) + for block in blocks: + print('block.slot', block.message.slot) + print('len(blocks)', len(blocks)) yield "blocks", blocks yield "post", state @@ -132,7 +133,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 ea5419d0dd..277428bcfb 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 02aec71053..29f9038a8b 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 cd662d09d2..4086431c09 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 0a9080db35..efe00995ef 100644 --- a/tests/generators/transition/main.py +++ b/tests/generators/transition/main.py @@ -3,7 +3,7 @@ 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 cases_fn() -> Iterable[gen_typing.TestCase]: 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), )