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

Commit

Permalink
Update validate_voluntary_exit (#421)
Browse files Browse the repository at this point in the history
* Disallow duplicate voluntary exit

* Fix #413

* Fix `test_process_voluntary_exits`

* Clean up tests

* PR feedback
  • Loading branch information
hwwhww authored Mar 19, 2019
1 parent b682e75 commit 61c1e55
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 56 deletions.
1 change: 1 addition & 0 deletions eth2/beacon/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
('ACTIVATION_EXIT_DELAY', int),
('EPOCHS_PER_ETH1_VOTING_PERIOD', int),
('MIN_VALIDATOR_WITHDRAWABILITY_DELAY', int),
('PERSISTENT_COMMITTEE_PERIOD', int),
# Reward and penalty quotients
('BASE_REWARD_QUOTIENT', int),
('WHISTLEBLOWER_REWARD_QUOTIENT', int),
Expand Down
69 changes: 45 additions & 24 deletions eth2/beacon/state_machines/forks/serenity/block_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@
from eth2.beacon.configs import (
CommitteeConfig,
)
from eth2.beacon.constants import (
FAR_FUTURE_EPOCH,
)
from eth2.beacon.enums import (
SignatureDomain,
)
from eth2.beacon.helpers import (
get_block_root,
get_epoch_start_slot,
get_delayed_activation_exit_epoch,
get_domain,
is_double_vote,
is_surround_vote,
Expand Down Expand Up @@ -711,55 +713,74 @@ def validate_slashable_attestation(state: 'BeaconState',
def validate_voluntary_exit(state: 'BeaconState',
voluntary_exit: 'VoluntaryExit',
slots_per_epoch: int,
activation_exit_delay: int) -> None:
persistent_committee_period: int) -> None:
validator = state.validator_registry[voluntary_exit.validator_index]
current_epoch = state.current_epoch(slots_per_epoch)

validate_voluntary_exit_validator_exit_epoch(
state,
validator,
current_epoch,
slots_per_epoch=slots_per_epoch,
activation_exit_delay=activation_exit_delay,
)
validate_voluntary_exit_validator_exit_epoch(validator)

validate_voluntary_exit_initiated_exit(validator)

validate_voluntary_exit_epoch(voluntary_exit, current_epoch)

validate_voluntary_exit_persistent(validator, current_epoch, persistent_committee_period)

validate_voluntary_exit_signature(state, voluntary_exit, validator)


def validate_voluntary_exit_validator_exit_epoch(state: 'BeaconState',
validator: 'ValidatorRecord',
current_epoch: Epoch,
slots_per_epoch: int,
activation_exit_delay: int) -> None:
current_epoch = state.current_epoch(slots_per_epoch)
def validate_voluntary_exit_validator_exit_epoch(validator: 'ValidatorRecord') -> None:
"""
Verify the validator has not yet exited.
"""
if validator.exit_epoch != FAR_FUTURE_EPOCH:
raise ValidationError(
f"validator.exit_epoch ({validator.exit_epoch}) should be equal to "
f"FAR_FUTURE_EPOCH ({FAR_FUTURE_EPOCH})"
)

# Verify the validator has not yet exited
delayed_activation_exit_epoch = get_delayed_activation_exit_epoch(
current_epoch,
activation_exit_delay,
)
if validator.exit_epoch <= delayed_activation_exit_epoch:

def validate_voluntary_exit_initiated_exit(validator: 'ValidatorRecord') -> None:
"""
Verify the validator has not initiated an exit.
"""
if validator.initiated_exit is True:
raise ValidationError(
f"validator.exit_epoch ({validator.exit_epoch}) should be greater than "
f"delayed_activation_exit_epoch ({delayed_activation_exit_epoch})"
f"validator.initiated_exit ({validator.initiated_exit}) should be False"
)


def validate_voluntary_exit_epoch(voluntary_exit: 'VoluntaryExit',
current_epoch: Epoch) -> None:
# Exits must specify an epoch when they become valid; they are not valid before then
"""
Exits must specify an epoch when they become valid; they are not valid before then.
"""
if current_epoch < voluntary_exit.epoch:
raise ValidationError(
f"voluntary_exit.epoch ({voluntary_exit.epoch}) should be less than or equal to "
f"current epoch ({current_epoch})"
)


def validate_voluntary_exit_persistent(validator: 'ValidatorRecord',
current_epoch: Epoch,
persistent_committee_period: int) -> None:
"""
# Must have been in the validator set long enough
"""
if current_epoch - validator.activation_epoch < persistent_committee_period:
raise ValidationError(
"current_epoch - validator.activation_epoch "
f"({current_epoch} - {validator.activation_epoch}) should be greater than or equal to "
f"PERSISTENT_COMMITTEE_PERIOD ({persistent_committee_period})"
)


def validate_voluntary_exit_signature(state: 'BeaconState',
voluntary_exit: 'VoluntaryExit',
validator: 'ValidatorRecord') -> None:
"""
Verify signature.
"""
domain = get_domain(state.fork, voluntary_exit.epoch, SignatureDomain.DOMAIN_EXIT)
is_valid_signature = bls.verify(
pubkey=validator.pubkey,
Expand Down
1 change: 1 addition & 0 deletions eth2/beacon/state_machines/forks/serenity/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
ACTIVATION_EXIT_DELAY=2**2, # (= 4) epochs
EPOCHS_PER_ETH1_VOTING_PERIOD=2**4, # (= 16) epochs
MIN_VALIDATOR_WITHDRAWABILITY_DELAY=2**8, # (= 256) epochs
PERSISTENT_COMMITTEE_PERIOD=2**11, # (= 2,048) epochs
# Reward and penalty quotients
BASE_REWARD_QUOTIENT=2**10, # (= 1,024)
WHISTLEBLOWER_REWARD_QUOTIENT=2**9, # (= 512)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def process_voluntary_exits(state: BeaconState,
state,
voluntary_exit,
config.SLOTS_PER_EPOCH,
config.ACTIVATION_EXIT_DELAY,
config.PERSISTENT_COMMITTEE_PERIOD,
)
# Run the exit
state = initiate_validator_exit(state, voluntary_exit.validator_index)
Expand Down
7 changes: 7 additions & 0 deletions tests/eth2/beacon/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,11 @@ def min_validator_withdrawability_delay():
return SERENITY_CONFIG.MIN_VALIDATOR_WITHDRAWABILITY_DELAY


@pytest.fixture
def persistent_committee_period():
return SERENITY_CONFIG.PERSISTENT_COMMITTEE_PERIOD


@pytest.fixture
def base_reward_quotient():
return SERENITY_CONFIG.BASE_REWARD_QUOTIENT
Expand Down Expand Up @@ -718,6 +723,7 @@ def config(
activation_exit_delay,
epochs_per_eth1_voting_period,
min_validator_withdrawability_delay,
persistent_committee_period,
base_reward_quotient,
whistleblower_reward_quotient,
attestation_inclusion_reward_quotient,
Expand Down Expand Up @@ -759,6 +765,7 @@ def config(
ACTIVATION_EXIT_DELAY=activation_exit_delay,
EPOCHS_PER_ETH1_VOTING_PERIOD=epochs_per_eth1_voting_period,
MIN_VALIDATOR_WITHDRAWABILITY_DELAY=min_validator_withdrawability_delay,
PERSISTENT_COMMITTEE_PERIOD=persistent_committee_period,
BASE_REWARD_QUOTIENT=base_reward_quotient,
WHISTLEBLOWER_REWARD_QUOTIENT=whistleblower_reward_quotient,
ATTESTATION_INCLUSION_REWARD_QUOTIENT=attestation_inclusion_reward_quotient,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@
ValidationError,
)

from eth2.beacon.constants import (
FAR_FUTURE_EPOCH,
)
from eth2.beacon.helpers import (
get_epoch_start_slot,
)
from eth2.beacon.state_machines.forks.serenity.block_validation import (
validate_voluntary_exit,
validate_voluntary_exit_epoch,
validate_voluntary_exit_initiated_exit,
validate_voluntary_exit_persistent,
validate_voluntary_exit_signature,
validate_voluntary_exit_validator_exit_epoch,
)
Expand All @@ -24,27 +29,41 @@
'num_validators',
'slots_per_epoch',
'target_committee_size',
'activation_exit_delay',
'persistent_committee_period',
),
[
(40, 2, 2, 2),
(40, 2, 2, 16),
]
)
def test_validate_voluntary_exit(
genesis_state,
keymap,
slots_per_epoch,
activation_exit_delay,
persistent_committee_period,
config):
state = genesis_state
state = genesis_state.copy(
slot=get_epoch_start_slot(
config.GENESIS_EPOCH + persistent_committee_period,
slots_per_epoch
),
)
validator_index = 0
validator = state.validator_registry[validator_index].copy(
activation_epoch=config.GENESIS_EPOCH,
)
state = state.update_validator_registry(validator_index, validator)
valid_voluntary_exit = create_mock_voluntary_exit(
state,
config,
keymap,
validator_index,
)
validate_voluntary_exit(state, valid_voluntary_exit, slots_per_epoch, activation_exit_delay)
validate_voluntary_exit(
state,
valid_voluntary_exit,
slots_per_epoch,
persistent_committee_period,
)


@pytest.mark.parametrize(
Expand All @@ -61,26 +80,19 @@ def test_validate_voluntary_exit(
)
@pytest.mark.parametrize(
(
'activation_exit_delay',
'current_epoch',
'validator_exit_epoch',
'success',
),
[
(4, 8, 4 + 8 + 1 + 1, True),
(4, 8, 4 + 8 + 1, False),
(FAR_FUTURE_EPOCH, True),
(FAR_FUTURE_EPOCH - 1, False),
]
)
def test_validate_voluntary_validator_exit_epoch(
genesis_state,
current_epoch,
validator_exit_epoch,
slots_per_epoch,
activation_exit_delay,
success):
state = genesis_state.copy(
slot=get_epoch_start_slot(current_epoch, slots_per_epoch),
)
state = genesis_state

validator_index = 0

Expand All @@ -89,22 +101,39 @@ def test_validate_voluntary_validator_exit_epoch(
)

if success:
validate_voluntary_exit_validator_exit_epoch(
state,
validator,
current_epoch,
slots_per_epoch,
activation_exit_delay,
)
validate_voluntary_exit_validator_exit_epoch(validator)
else:
with pytest.raises(ValidationError):
validate_voluntary_exit_validator_exit_epoch(
state,
validator,
current_epoch,
slots_per_epoch,
activation_exit_delay,
)
validate_voluntary_exit_validator_exit_epoch(validator)


@pytest.mark.parametrize(
(
'initiated_exit',
'success',
),
[
(False, True),
(True, False),
]
)
def test_validate_voluntary_exit_initiated_exit(
genesis_state,
initiated_exit,
success):
state = genesis_state

validator_index = 0

validator = state.validator_registry[validator_index].copy(
initiated_exit=initiated_exit,
)

if success:
validate_voluntary_exit_initiated_exit(validator)
else:
with pytest.raises(ValidationError):
validate_voluntary_exit_initiated_exit(validator)


@pytest.mark.parametrize(
Expand Down Expand Up @@ -158,6 +187,53 @@ def test_validate_voluntary_exit_epoch(
validate_voluntary_exit_epoch(voluntary_exit, state.current_epoch(slots_per_epoch))


@pytest.mark.parametrize(
(
'current_epoch',
'persistent_committee_period',
'activation_epoch',
'success',
),
[
(16, 4, 16 - 4, True),
(16, 4, 16 - 4 + 1, False),
]
)
def test_validate_voluntary_exit_persistent(
genesis_state,
keymap,
current_epoch,
activation_epoch,
slots_per_epoch,
persistent_committee_period,
success):
state = genesis_state.copy(
slot=get_epoch_start_slot(
current_epoch,
slots_per_epoch
),
)
validator_index = 0
validator = state.validator_registry[validator_index].copy(
activation_epoch=activation_epoch,
)
state = state.update_validator_registry(validator_index, validator)

if success:
validate_voluntary_exit_persistent(
validator,
state.current_epoch(slots_per_epoch),
persistent_committee_period,
)
else:
with pytest.raises(ValidationError):
validate_voluntary_exit_persistent(
validator,
state.current_epoch(slots_per_epoch),
persistent_committee_period,
)


@pytest.mark.parametrize(
(
'num_validators',
Expand Down
Loading

0 comments on commit 61c1e55

Please sign in to comment.