From 162711ea564fb376c848f2b839140160f8f6722e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 15 Oct 2021 21:08:21 +0800 Subject: [PATCH] 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