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

Commit

Permalink
Implement process_exit_queue (#385)
Browse files Browse the repository at this point in the history
* Add `process_exit_queue`

* Add tests

* Fix mypy checks

* Fix tests

* Update eth2/beacon/state_machines/forks/serenity/epoch_processing.py

Co-Authored-By: hwwhww <hwwang156@gmail.com>
  • Loading branch information
hwwhww committed Mar 9, 2019
1 parent 166d1b6 commit 9071ada
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 4 deletions.
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),
]
)
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

0 comments on commit 9071ada

Please sign in to comment.