From 841240a970e10a8e69c6a408dc3168cca7b630d6 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 7 Mar 2019 20:15:37 +0800 Subject: [PATCH 1/5] Add `process_exit_queue` --- .../forks/serenity/epoch_processing.py | 46 +++++++++++++++++-- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/eth2/beacon/state_machines/forks/serenity/epoch_processing.py b/eth2/beacon/state_machines/forks/serenity/epoch_processing.py index cc58025590..3651830494 100644 --- a/eth2/beacon/state_machines/forks/serenity/epoch_processing.py +++ b/eth2/beacon/state_machines/forks/serenity/epoch_processing.py @@ -63,6 +63,7 @@ from eth2.beacon.validator_status_helpers import ( activate_validator, exit_validator, + prepare_validator_for_withdrawal, ) from eth2.beacon._utils.hash import ( hash_eth2, @@ -1087,14 +1088,11 @@ def process_validator_registry(state: BeaconState, state = process_slashings(state, config) - # TODO: state = process_exit_queue(state, config) + state = process_exit_queue(state, config) return state -# -# Final updates -# def _update_latest_active_index_roots(state: BeaconState, committee_config: CommitteeConfig) -> BeaconState: """ @@ -1202,6 +1200,46 @@ def process_slashings(state: BeaconState, return state +def process_exit_queue(state: BeaconState, + config: BeaconConfig) -> BeaconState: + """ + Process the exit queue. + """ + def eligible(index): + validator = state.validator_registry[index] + # Filter out dequeued validators + if validator.withdrawable_epoch != FAR_FUTURE_EPOCH: + return False + # Dequeue if the minimum amount of time has passed + else: + return ( + state.current_epoch(config.SLOTS_PER_EPOCH) >= + validator.exit_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + ) + + eligible_indices = filter(eligible, list(range(len(state.validator_registry)))) + # Sort in order of exit epoch, and validators that exit withinthe same epoch exit + # in order of validator index + sorted_indices = sorted( + eligible_indices, + key=lambda index: state.validator_registry[index].exit_epoch, + ) + for dequeues, index in enumerate(sorted_indices): + if dequeues >= config.MAX_EXIT_DEQUEUES_PER_EPOCH: + break + state = prepare_validator_for_withdrawal( + state, + index, + slots_per_epoch=config.SLOTS_PER_EPOCH, + min_validator_withdrawability_delay=config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY, + ) + + return state + + +# +# Final updates +# def process_final_updates(state: BeaconState, config: BeaconConfig) -> BeaconState: current_epoch = state.current_epoch(config.SLOTS_PER_EPOCH) From 3931aa886dbae07c48086e52a65c144b90d406e8 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 8 Mar 2019 19:07:12 +0800 Subject: [PATCH 2/5] Add tests --- .../forks/serenity/epoch_processing.py | 2 +- .../forks/test_serenity_epoch_processing.py | 139 ++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) diff --git a/eth2/beacon/state_machines/forks/serenity/epoch_processing.py b/eth2/beacon/state_machines/forks/serenity/epoch_processing.py index 3651830494..d5b08515c5 100644 --- a/eth2/beacon/state_machines/forks/serenity/epoch_processing.py +++ b/eth2/beacon/state_machines/forks/serenity/epoch_processing.py @@ -1217,7 +1217,7 @@ def eligible(index): validator.exit_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY ) - eligible_indices = filter(eligible, list(range(len(state.validator_registry)))) + eligible_indices = filter(eligible, tuple(range(len(state.validator_registry)))) # Sort in order of exit epoch, and validators that exit withinthe same epoch exit # in order of validator index sorted_indices = sorted( diff --git a/tests/eth2/beacon/state_machines/forks/test_serenity_epoch_processing.py b/tests/eth2/beacon/state_machines/forks/test_serenity_epoch_processing.py index 600a8baef8..96425f519c 100644 --- a/tests/eth2/beacon/state_machines/forks/test_serenity_epoch_processing.py +++ b/tests/eth2/beacon/state_machines/forks/test_serenity_epoch_processing.py @@ -64,6 +64,7 @@ _update_latest_active_index_roots, process_crosslinks, process_ejections, + process_exit_queue, process_final_updates, process_justification, process_slashings, @@ -1288,6 +1289,144 @@ def test_process_slashings(genesis_state, assert penalty == expected_penalty +@pytest.mark.parametrize( + ( + 'num_validators', + 'slots_per_epoch', + 'genesis_slot', + 'current_epoch', + ), + [ + (10, 4, 8, 8) + ] +) +@pytest.mark.parametrize( + ( + 'min_validator_withdrawability_delay', + 'withdrawable_epoch', + 'exit_epoch', + 'is_eligible', + ), + [ + # current_epoch == validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY + (4, FAR_FUTURE_EPOCH, 4, True), + # withdrawable_epoch != FAR_FUTURE_EPOCH + (4, 8, 4, False), + # current_epoch < validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY + (4, FAR_FUTURE_EPOCH, 5, False), + ] +) +def test_process_exit_queue_eligible(genesis_state, + config, + current_epoch, + min_validator_withdrawability_delay, + withdrawable_epoch, + exit_epoch, + is_eligible): + state = genesis_state.copy( + slot=get_epoch_start_slot(current_epoch, config.SLOTS_PER_EPOCH) + ) + validator_index = 0 + + # Set eligible validators + state = state.update_validator_registry( + validator_index, + state.validator_registry[validator_index].copy( + withdrawable_epoch=withdrawable_epoch, + exit_epoch=exit_epoch, + ) + ) + + result_state = process_exit_queue(state, config) + + if is_eligible: + # Check if they got prepared for withdrawal + assert ( + result_state.validator_registry[validator_index].withdrawable_epoch == + current_epoch + min_validator_withdrawability_delay + ) + else: + assert ( + result_state.validator_registry[validator_index].withdrawable_epoch == + state.validator_registry[validator_index].withdrawable_epoch + ) + + +@pytest.mark.parametrize( + ( + 'num_validators', + 'slots_per_epoch', + 'genesis_slot', + 'current_epoch', + 'min_validator_withdrawability_delay' + ), + [ + (10, 4, 4, 16, 4) + ] +) +@pytest.mark.parametrize( + ( + 'max_exit_dequeues_per_epoch', + 'num_eligible_validators', + 'validator_exit_epochs', + ), + [ + # no eligible validator + (4, 0, ()), + # max_exit_dequeues_per_epoch == num_eligible_validators + (4, 4, (4, 5, 6, 7)), + # max_exit_dequeues_per_epoch > num_eligible_validators + (5, 4, (4, 5, 6, 7)), + # max_exit_dequeues_per_epoch < num_eligible_validators + (3, 4, (4, 5, 6, 7)), + (3, 4, (7, 6, 5, 4)), + ] +) +def test_process_exit(genesis_state, + config, + current_epoch, + num_validators, + max_exit_dequeues_per_epoch, + min_validator_withdrawability_delay, + num_eligible_validators, + validator_exit_epochs): + state = genesis_state.copy( + slot=get_epoch_start_slot(current_epoch, config.SLOTS_PER_EPOCH) + ) + + # Set eligible validators + assert num_eligible_validators <= num_validators + for i in range(num_eligible_validators): + state = state.update_validator_registry( + i, + state.validator_registry[i].copy( + exit_epoch=validator_exit_epochs[i], + ) + ) + + result_state = process_exit_queue(state, config) + + # Exit queue is sorted + sorted_indices = sorted( + range(num_eligible_validators), + key=lambda i: validator_exit_epochs[i], + ) + filtered_indices = sorted_indices[:min(max_exit_dequeues_per_epoch, num_eligible_validators)] + + for i in range(filtered_indices): + if i in set(filtered_indices): + # Check if they got prepared for withdrawal + assert ( + result_state.validator_registry[i].withdrawable_epoch == + current_epoch + min_validator_withdrawability_delay + ) + else: + assert ( + result_state.validator_registry[i].withdrawable_epoch == + FAR_FUTURE_EPOCH + ) + + # # Final updates # From 22ff93753ac6e9139c51860c5e0d05bf27ff147d Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 8 Mar 2019 20:40:39 +0800 Subject: [PATCH 3/5] Fix mypy checks --- .../state_machines/forks/serenity/epoch_processing.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/eth2/beacon/state_machines/forks/serenity/epoch_processing.py b/eth2/beacon/state_machines/forks/serenity/epoch_processing.py index d5b08515c5..68462f4541 100644 --- a/eth2/beacon/state_machines/forks/serenity/epoch_processing.py +++ b/eth2/beacon/state_machines/forks/serenity/epoch_processing.py @@ -1205,7 +1205,7 @@ def process_exit_queue(state: BeaconState, """ Process the exit queue. """ - def eligible(index): + def eligible(index: ValidatorIndex) -> bool: validator = state.validator_registry[index] # Filter out dequeued validators if validator.withdrawable_epoch != FAR_FUTURE_EPOCH: @@ -1217,7 +1217,10 @@ def eligible(index): validator.exit_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY ) - eligible_indices = filter(eligible, tuple(range(len(state.validator_registry)))) + eligible_indices = filter( + eligible, + tuple([ValidatorIndex(i) for i in range(len(state.validator_registry))]) + ) # Sort in order of exit epoch, and validators that exit withinthe same epoch exit # in order of validator index sorted_indices = sorted( @@ -1229,7 +1232,7 @@ def eligible(index): break state = prepare_validator_for_withdrawal( state, - index, + ValidatorIndex(index), slots_per_epoch=config.SLOTS_PER_EPOCH, min_validator_withdrawability_delay=config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY, ) From c2718c37631f5176da035363863ef7bc7bd202c9 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 8 Mar 2019 20:44:23 +0800 Subject: [PATCH 4/5] Fix tests --- .../forks/test_serenity_epoch_processing.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/eth2/beacon/state_machines/forks/test_serenity_epoch_processing.py b/tests/eth2/beacon/state_machines/forks/test_serenity_epoch_processing.py index 96425f519c..a4f79890bf 100644 --- a/tests/eth2/beacon/state_machines/forks/test_serenity_epoch_processing.py +++ b/tests/eth2/beacon/state_machines/forks/test_serenity_epoch_processing.py @@ -1382,14 +1382,14 @@ def test_process_exit_queue_eligible(genesis_state, (3, 4, (7, 6, 5, 4)), ] ) -def test_process_exit(genesis_state, - config, - current_epoch, - num_validators, - max_exit_dequeues_per_epoch, - min_validator_withdrawability_delay, - num_eligible_validators, - validator_exit_epochs): +def test_process_exit_queue(genesis_state, + config, + current_epoch, + num_validators, + max_exit_dequeues_per_epoch, + min_validator_withdrawability_delay, + num_eligible_validators, + validator_exit_epochs): state = genesis_state.copy( slot=get_epoch_start_slot(current_epoch, config.SLOTS_PER_EPOCH) ) @@ -1413,7 +1413,7 @@ def test_process_exit(genesis_state, ) filtered_indices = sorted_indices[:min(max_exit_dequeues_per_epoch, num_eligible_validators)] - for i in range(filtered_indices): + for i in range(num_validators): if i in set(filtered_indices): # Check if they got prepared for withdrawal assert ( From 64bf27595ae07160d3f217aa80629fa24bf7319a Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Sat, 9 Mar 2019 08:25:02 +0800 Subject: [PATCH 5/5] Update eth2/beacon/state_machines/forks/serenity/epoch_processing.py Co-Authored-By: hwwhww --- eth2/beacon/state_machines/forks/serenity/epoch_processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth2/beacon/state_machines/forks/serenity/epoch_processing.py b/eth2/beacon/state_machines/forks/serenity/epoch_processing.py index 68462f4541..ce425e0a09 100644 --- a/eth2/beacon/state_machines/forks/serenity/epoch_processing.py +++ b/eth2/beacon/state_machines/forks/serenity/epoch_processing.py @@ -1221,7 +1221,7 @@ def eligible(index: ValidatorIndex) -> bool: eligible, tuple([ValidatorIndex(i) for i in range(len(state.validator_registry))]) ) - # Sort in order of exit epoch, and validators that exit withinthe same epoch exit + # Sort in order of exit epoch, and validators that exit within the same epoch exit # in order of validator index sorted_indices = sorted( eligible_indices,