Skip to content
This repository has been archived by the owner on Jul 1, 2021. It is now read-only.

Implement process_exit_queue #385

Merged
merged 5 commits into from
Mar 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 45 additions & 4 deletions eth2/beacon/state_machines/forks/serenity/epoch_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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:
"""
Expand Down Expand Up @@ -1202,6 +1200,49 @@ def process_slashings(state: BeaconState,
return state


def process_exit_queue(state: BeaconState,
config: BeaconConfig) -> BeaconState:
"""
Process the exit queue.
"""
def eligible(index: ValidatorIndex) -> bool:
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,
tuple([ValidatorIndex(i) for i in range(len(state.validator_registry))])
)
# 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,
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,
ValidatorIndex(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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
_update_latest_active_index_roots,
process_crosslinks,
process_ejections,
process_exit_queue,
process_final_updates,
process_justification,
process_slashings,
Expand Down Expand Up @@ -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),
]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like the test coverage here 👍

)
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_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)
)

# 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(num_validators):
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
#
Expand Down