From 37004404d04e0e6d4d7eec95e7097e844fa40106 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Sun, 14 Apr 2019 09:13:53 +1000 Subject: [PATCH] add exit queue test --- specs/core/0_beacon-chain.md | 29 ++-- .../block_processing/test_process_deposit.py | 4 +- .../block_processing/test_voluntary_exit.py | 155 ++++++++++-------- 3 files changed, 106 insertions(+), 82 deletions(-) diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index 67eb7b000..40363a666 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -1311,23 +1311,26 @@ def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: Note that this function mutates ``state``. """ validator = state.validator_registry[index] + # Operation is a no-op if validator is already in the queue - if validator.exit_epoch == FAR_FUTURE_EPOCH: - # Update exit queue counters - delayed_activation_exit_epoch = get_delayed_activation_exit_epoch(get_current_epoch(state)) - if state.exit_epoch < delayed_activation_exit_epoch: - state.exit_epoch = delayed_activation_exit_epoch + if validator.exit_epoch != FAR_FUTURE_EPOCH: + return - if state.exit_queue_filled >= MAX_EXITS_PER_EPOCH: - state.exit_epoch += 1 - state.exit_queue_filled = 0 + # Update exit queue counters + delayed_activation_exit_epoch = get_delayed_activation_exit_epoch(get_current_epoch(state)) + if state.exit_epoch < delayed_activation_exit_epoch: + state.exit_epoch = delayed_activation_exit_epoch - # Set validator exit epoch and withdrawable epoch - validator.exit_epoch = state.exit_epoch - validator.withdrawable_epoch = validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY + if state.exit_queue_filled >= MAX_EXITS_PER_EPOCH: + state.exit_epoch += 1 + state.exit_queue_filled = 0 - # Extend queue - state.exit_queue_filled += 1 + # Set validator exit epoch and withdrawable epoch + validator.exit_epoch = state.exit_epoch + validator.withdrawable_epoch = validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY + + # Extend queue + state.exit_queue_filled += 1 ``` #### `slash_validator` diff --git a/tests/phase0/block_processing/test_process_deposit.py b/tests/phase0/block_processing/test_process_deposit.py index 0726dddef..0c3447d4e 100644 --- a/tests/phase0/block_processing/test_process_deposit.py +++ b/tests/phase0/block_processing/test_process_deposit.py @@ -15,8 +15,8 @@ from tests.phase0.helpers import ( ) -# mark entire file as 'voluntary_exits' -pytestmark = pytest.mark.voluntary_exits +# mark entire file as 'deposits' +pytestmark = pytest.mark.deposits def test_success(state): diff --git a/tests/phase0/block_processing/test_voluntary_exit.py b/tests/phase0/block_processing/test_voluntary_exit.py index 7627f1f0b..2f0693454 100644 --- a/tests/phase0/block_processing/test_voluntary_exit.py +++ b/tests/phase0/block_processing/test_voluntary_exit.py @@ -18,124 +18,145 @@ from tests.phase0.helpers import ( pytestmark = pytest.mark.voluntary_exits -def test_success(state): - pre_state = deepcopy(state) - # - # setup pre_state - # - # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit - pre_state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH +def run_voluntary_exit_processing(state, voluntary_exit, valid=True): + """ + Run ``process_voluntary_exit`` returning the pre and post state. + If ``valid == False``, run expecting ``AssertionError`` + """ + post_state = deepcopy(state) - # - # build voluntary exit - # - current_epoch = get_current_epoch(pre_state) - validator_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0] - privkey = pubkey_to_privkey[pre_state.validator_registry[validator_index].pubkey] + if not valid: + with pytest.raises(AssertionError): + process_voluntary_exit(post_state, voluntary_exit) + return state, None + + process_voluntary_exit(post_state, voluntary_exit) + + validator_index = voluntary_exit.validator_index + assert state.validator_registry[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH + assert post_state.validator_registry[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH + + return state, post_state + + +def test_success(state): + # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + current_epoch = get_current_epoch(state) + validator_index = get_active_validator_indices(state.validator_registry, current_epoch)[0] + privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey] voluntary_exit = build_voluntary_exit( - pre_state, + state, current_epoch, validator_index, privkey, ) - post_state = deepcopy(pre_state) + pre_state, post_state = run_voluntary_exit_processing(state, voluntary_exit) + return pre_state, voluntary_exit, post_state - # - # test valid exit - # - process_voluntary_exit(post_state, voluntary_exit) - assert pre_state.validator_registry[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH - assert post_state.validator_registry[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH +def test_success_exit_queue(state): + # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + current_epoch = get_current_epoch(state) + + # exit `MAX_EXITS_PER_EPOCH` + initial_indices = get_active_validator_indices(state.validator_registry,current_epoch)[:spec.MAX_EXITS_PER_EPOCH] + post_state = state + for index in initial_indices: + privkey = pubkey_to_privkey[state.validator_registry[index].pubkey] + voluntary_exit = build_voluntary_exit( + state, + current_epoch, + index, + privkey, + ) + + _, post_state = run_voluntary_exit_processing(post_state, voluntary_exit) + + # exit an additional validator + validator_index = get_active_validator_indices(state.validator_registry,current_epoch)[-1] + privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey] + voluntary_exit = build_voluntary_exit( + state, + current_epoch, + validator_index, + privkey, + ) + + pre_state, post_state = run_voluntary_exit_processing(post_state, voluntary_exit) + + assert ( + post_state.validator_registry[validator_index].exit_epoch == + post_state.validator_registry[initial_indices[0]].exit_epoch + 1 + ) return pre_state, voluntary_exit, post_state def test_validator_not_active(state): - pre_state = deepcopy(state) - current_epoch = get_current_epoch(pre_state) - validator_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0] - privkey = pubkey_to_privkey[pre_state.validator_registry[validator_index].pubkey] + current_epoch = get_current_epoch(state) + validator_index = get_active_validator_indices(state.validator_registry, current_epoch)[0] + privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey] - # - # setup pre_state - # - pre_state.validator_registry[validator_index].activation_epoch = spec.FAR_FUTURE_EPOCH + state.validator_registry[validator_index].activation_epoch = spec.FAR_FUTURE_EPOCH # # build and test voluntary exit # voluntary_exit = build_voluntary_exit( - pre_state, + state, current_epoch, validator_index, privkey, ) - with pytest.raises(AssertionError): - process_voluntary_exit(pre_state, voluntary_exit) - - return pre_state, voluntary_exit, None + pre_state, post_state = run_voluntary_exit_processing(state, voluntary_exit, False) + return pre_state, voluntary_exit, post_state def test_validator_already_exited(state): - pre_state = deepcopy(state) - # - # setup pre_state - # # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow validator able to exit - pre_state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH - current_epoch = get_current_epoch(pre_state) - validator_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0] - privkey = pubkey_to_privkey[pre_state.validator_registry[validator_index].pubkey] + current_epoch = get_current_epoch(state) + validator_index = get_active_validator_indices(state.validator_registry, current_epoch)[0] + privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey] # but validator already has exited - pre_state.validator_registry[validator_index].exit_epoch = current_epoch + 2 + state.validator_registry[validator_index].exit_epoch = current_epoch + 2 - # - # build voluntary exit - # voluntary_exit = build_voluntary_exit( - pre_state, + state, current_epoch, validator_index, privkey, ) - with pytest.raises(AssertionError): - process_voluntary_exit(pre_state, voluntary_exit) - - return pre_state, voluntary_exit, None + pre_state, post_state = run_voluntary_exit_processing(state, voluntary_exit, False) + return pre_state, voluntary_exit, post_state def test_validator_not_active_long_enough(state): - pre_state = deepcopy(state) - # - # setup pre_state - # - current_epoch = get_current_epoch(pre_state) - validator_index = get_active_validator_indices(pre_state.validator_registry, current_epoch)[0] - privkey = pubkey_to_privkey[pre_state.validator_registry[validator_index].pubkey] + current_epoch = get_current_epoch(state) + validator_index = get_active_validator_indices(state.validator_registry, current_epoch)[0] + privkey = pubkey_to_privkey[state.validator_registry[validator_index].pubkey] - # - # build voluntary exit - # voluntary_exit = build_voluntary_exit( - pre_state, + state, current_epoch, validator_index, privkey, ) assert ( - current_epoch - pre_state.validator_registry[validator_index].activation_epoch < + current_epoch - state.validator_registry[validator_index].activation_epoch < spec.PERSISTENT_COMMITTEE_PERIOD ) - with pytest.raises(AssertionError): - process_voluntary_exit(pre_state, voluntary_exit) - - return pre_state, voluntary_exit, None + pre_state, post_state = run_voluntary_exit_processing(state, voluntary_exit, False) + return pre_state, voluntary_exit, post_state