diff --git a/specs/core/0_beacon-chain.md b/specs/core/0_beacon-chain.md index f0f90e7fe..a834a1cde 100644 --- a/specs/core/0_beacon-chain.md +++ b/specs/core/0_beacon-chain.md @@ -2089,16 +2089,21 @@ def update_validator_registry(state: BeaconState) -> None: activate_validator(state, index, is_genesis=False) # Exit validators within the allowable balance churn - balance_churn = 0 - for index, validator in enumerate(state.validator_registry): - if validator.exit_epoch == FAR_FUTURE_EPOCH and validator.initiated_exit: - # Check the balance churn would be within the allowance - balance_churn += get_effective_balance(state, index) - if balance_churn > max_balance_churn: - break + if current_epoch < state.validator_registry_update_epoch + LATEST_SLASHED_EXIT_LENGTH: + balance_churn = ( + state.latest_slashed_balances[state.validator_registry_update_epoch % LATEST_SLASHED_EXIT_LENGTH] - + state.latest_slashed_balances[current_epoch % LATEST_SLASHED_EXIT_LENGTH] + ) - # Exit validator - exit_validator(state, index) + for index, validator in enumerate(state.validator_registry): + if validator.exit_epoch == FAR_FUTURE_EPOCH and validator.initiated_exit: + # Check the balance churn would be within the allowance + balance_churn += get_effective_balance(state, index) + if balance_churn > max_balance_churn: + break + + # Exit validator + exit_validator(state, index) state.validator_registry_update_epoch = current_epoch ``` diff --git a/tests/phase0/test_sanity.py b/tests/phase0/test_sanity.py index d1811cd00..8c7e7d28b 100644 --- a/tests/phase0/test_sanity.py +++ b/tests/phase0/test_sanity.py @@ -316,12 +316,18 @@ def test_attestation(state, pubkeys, privkeys): def test_voluntary_exit(state, pubkeys, privkeys): pre_state = deepcopy(state) - validator_index = get_active_validator_indices(pre_state.validator_registry, get_current_epoch(pre_state))[-1] + validator_index = get_active_validator_indices( + pre_state.validator_registry, + get_current_epoch(pre_state) + )[-1] # move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit pre_state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH # artificially trigger registry update at next epoch transition - pre_state.validator_registry_update_epoch -= 1 + pre_state.finalized_epoch = get_current_epoch(pre_state) - 1 + for crosslink in pre_state.latest_crosslinks: + crosslink.epoch = pre_state.finalized_epoch + pre_state.validator_registry_update_epoch = pre_state.finalized_epoch - 1 post_state = deepcopy(pre_state) @@ -363,6 +369,44 @@ def test_voluntary_exit(state, pubkeys, privkeys): return pre_state, [initiate_exit_block, exit_block], post_state +def test_no_exit_too_long_since_change(state): + pre_state = deepcopy(state) + validator_index = get_active_validator_indices( + pre_state.validator_registry, + get_current_epoch(pre_state) + )[-1] + + # + # 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 + # artificially trigger registry update at next epoch transition + pre_state.finalized_epoch = get_current_epoch(pre_state) - 1 + for crosslink in pre_state.latest_crosslinks: + crosslink.epoch = pre_state.finalized_epoch + # make epochs since registry update greater than LATEST_SLASHED_EXIT_LENGTH + pre_state.validator_registry_update_epoch = ( + get_current_epoch(pre_state) - spec.LATEST_SLASHED_EXIT_LENGTH + ) + # set validator to have previously initiated exit + pre_state.validator_registry[validator_index].initiated_exit = True + + post_state = deepcopy(pre_state) + + # + # Process registry change but ensure no exit + # + block = build_empty_block_for_next_slot(post_state) + block.slot += spec.SLOTS_PER_EPOCH + state_transition(post_state, block) + + assert post_state.validator_registry_update_epoch == get_current_epoch(post_state) - 1 + assert post_state.validator_registry[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH + + return pre_state, [block], post_state + + def test_transfer(state, pubkeys, privkeys): pre_state = deepcopy(state) current_epoch = get_current_epoch(pre_state)