Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement new initiate validator exit function #2366

Merged
merged 11 commits into from
Apr 26, 2019
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,12 @@ test_cases:
slashable_attestation_1_validator_indices: [1, 2, 3, 4, 5, 6, 7, 51]
slashable_attestation_2_custody_bitfield: !binary "F"
slashable_attestation_2_validator_indices: [1, 2, 3, 4, 5, 6, 7, 51]
validator_exits:
- epoch: 144115188075855872
validator_index: 45 # At slot 9223372036854775868, validator at index 45 triggers a voluntary exit
# TODO(2307): Validator rotation logic is getting revamped piece by piece,
# we'll add the Exit test case back when state transition aligns spec v0.6.
results:
slot: 9223372036854775872
num_validators: 67
penalized_validators: [50, 51] # We test that the validators at indices were indeed penalized
exited_validators: [45] # We confirm the indices of validators that willingly exited the registry
# TODO(1387): Waiting for spec to stable to proceed with this test case
# - config:
# skip_slots: [10, 20]
Expand Down
1 change: 1 addition & 0 deletions beacon-chain/core/blocks/block_operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,7 @@ func ProcessValidatorExits(
func verifyExit(beaconState *pb.BeaconState, exit *pb.VoluntaryExit, verifySignatures bool) error {
validator := beaconState.ValidatorRegistry[exit.ValidatorIndex]
currentEpoch := helpers.CurrentEpoch(beaconState)

delayedActivationExitEpoch := helpers.DelayedActivationExitEpoch(currentEpoch)
if validator.ExitEpoch <= delayedActivationExitEpoch {
return fmt.Errorf(
Expand Down
5 changes: 3 additions & 2 deletions beacon-chain/core/blocks/block_operations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1504,7 +1504,8 @@ func TestProcessValidatorExits_AppliesCorrectStatus(t *testing.T) {
t.Fatalf("Could not process exits: %v", err)
}
newRegistry := newState.ValidatorRegistry
if newRegistry[0].StatusFlags == pb.Validator_INITIAL {
t.Error("Expected validator status to change, remained INITIAL")
if newRegistry[0].ExitEpoch != helpers.DelayedActivationExitEpoch(state.Slot/params.BeaconConfig().SlotsPerEpoch) {
t.Errorf("Expected validator exit epoch to be %d, got %d",
helpers.DelayedActivationExitEpoch(state.Slot/params.BeaconConfig().SlotsPerEpoch), newRegistry[0].ExitEpoch)
}
}
59 changes: 55 additions & 4 deletions beacon-chain/core/validators/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,63 @@ func ActivateValidator(state *pb.BeaconState, idx uint64, genesis bool) (*pb.Bea
//
// Spec pseudocode definition:
// def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None:
// """
// Initiate the validator of the given ``index``.
// Note that this function mutates ``state``.
// """
// # Return if validator already initiated exit
// validator = state.validator_registry[index]
// validator.status_flags |= INITIATED_EXIT
// if validator.exit_epoch != FAR_FUTURE_EPOCH:
// return
//
// # Compute exit queue epoch
// exit_epochs = [v.exit_epoch for v in state.validator_registry if v.exit_epoch != FAR_FUTURE_EPOCH]
// exit_queue_epoch = max(exit_epochs + [get_delayed_activation_exit_epoch(get_current_epoch(state))])
// exit_queue_churn = len([v for v in state.validator_registry if v.exit_epoch == exit_queue_epoch])
// if exit_queue_churn >= get_churn_limit(state):
// exit_queue_epoch += 1
//
// # Set validator exit epoch and withdrawable epoch
// validator.exit_epoch = exit_queue_epoch
// validator.withdrawable_epoch = validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY
func InitiateValidatorExit(state *pb.BeaconState, idx uint64) *pb.BeaconState {
state.ValidatorRegistry[idx].StatusFlags |=
pb.Validator_INITIATED_EXIT
v := state.ValidatorRegistry[idx]

// Return if validator already initiated exit.
// According to the spec, this is not an assert condition and
// shouldn't fail beacon block state transition.
if v.ExitEpoch != params.BeaconConfig().FarFutureEpoch {
return state
}

// Find the highest exit epoch among exited validators.
highestExitEpoch := helpers.DelayedActivationExitEpoch(helpers.CurrentEpoch(state))
for i := 0; i < len(state.ValidatorRegistry); i++ {
if state.ValidatorRegistry[i].ExitEpoch != params.BeaconConfig().FarFutureEpoch {
if highestExitEpoch < state.ValidatorRegistry[i].ExitEpoch {
highestExitEpoch = state.ValidatorRegistry[i].ExitEpoch
}
}
}

// Find the total number of validators exiting same epoch as
// input validator. If the number is greater than churn limit, postpone
// exit epoch to the next epoch.
var currentExitQueueLength uint64
for i := 0; i < len(state.ValidatorRegistry); i++ {
if state.ValidatorRegistry[i].ExitEpoch == highestExitEpoch {
currentExitQueueLength++
}
}

if currentExitQueueLength >= helpers.ChurnLimit(state) {
highestExitEpoch++
}

v.ExitEpoch = highestExitEpoch
v.WithdrawableEpoch = v.ExitEpoch + params.BeaconConfig().MinValidatorWithdrawalDelay

state.ValidatorRegistry[idx] = v
return state
}

Expand Down Expand Up @@ -307,7 +359,6 @@ func UpdateRegistry(state *pb.BeaconState) (*pb.BeaconState, error) {
currentEpoch := helpers.CurrentEpoch(state)
updatedEpoch := helpers.DelayedActivationExitEpoch(currentEpoch)
activeValidatorIndices := helpers.ActiveValidatorIndices(state, currentEpoch)

totalBalance := helpers.TotalBalance(state, activeValidatorIndices)

// The maximum balance churn in Gwei (for deposits and exits separately).
Expand Down
55 changes: 47 additions & 8 deletions beacon-chain/core/validators/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,14 +398,53 @@ func TestActivateValidator_OK(t *testing.T) {
}
}

func TestInitiateValidatorExit_OK(t *testing.T) {
state := &pb.BeaconState{ValidatorRegistry: []*pb.Validator{{}, {}, {}}}
newState := InitiateValidatorExit(state, 2)
if newState.ValidatorRegistry[0].StatusFlags != pb.Validator_INITIAL {
t.Errorf("Wanted flag INITIAL, got %v", newState.ValidatorRegistry[0].StatusFlags)
}
if newState.ValidatorRegistry[2].StatusFlags != pb.Validator_INITIATED_EXIT {
t.Errorf("Wanted flag ACTIVE_PENDING_EXIT, got %v", newState.ValidatorRegistry[0].StatusFlags)
func TestInitiateValidatorExit_AlreadyExited(t *testing.T) {
exitEpoch := uint64(199)
state := &pb.BeaconState{ValidatorRegistry: []*pb.Validator{{
ExitEpoch: exitEpoch},
}}
newState := InitiateValidatorExit(state, 0)
if newState.ValidatorRegistry[0].ExitEpoch != exitEpoch {
t.Errorf("Already exited, wanted exit epoch %d, got %d",
exitEpoch, newState.ValidatorRegistry[0].ExitEpoch)
}
}

func TestInitiateValidatorExit_ProperExit(t *testing.T) {
exitedEpoch := uint64(100)
idx := uint64(3)
state := &pb.BeaconState{ValidatorRegistry: []*pb.Validator{
{ExitEpoch: exitedEpoch},
{ExitEpoch: exitedEpoch + 1},
{ExitEpoch: exitedEpoch + 2},
{ExitEpoch: params.BeaconConfig().FarFutureEpoch},
}}
newState := InitiateValidatorExit(state, idx)
if newState.ValidatorRegistry[idx].ExitEpoch != exitedEpoch+2 {
t.Errorf("Exit epoch was not the highest, wanted exit epoch %d, got %d",
exitedEpoch+2, newState.ValidatorRegistry[idx].ExitEpoch)
}
}

func TestInitiateValidatorExit_ChurnOverflow(t *testing.T) {
exitedEpoch := uint64(100)
idx := uint64(4)
state := &pb.BeaconState{ValidatorRegistry: []*pb.Validator{
{ExitEpoch: exitedEpoch + 2},
{ExitEpoch: exitedEpoch + 2},
{ExitEpoch: exitedEpoch + 2},
{ExitEpoch: exitedEpoch + 2}, //over flow here
{ExitEpoch: params.BeaconConfig().FarFutureEpoch},
}}
newState := InitiateValidatorExit(state, idx)

// Because of exit queue overflow,
// validator who init exited has to wait one more epoch.
wantedEpoch := state.ValidatorRegistry[0].ExitEpoch + 1

if newState.ValidatorRegistry[idx].ExitEpoch != wantedEpoch {
t.Errorf("Exit epoch did not cover overflow case, wanted exit epoch %d, got %d",
wantedEpoch, newState.ValidatorRegistry[idx].ExitEpoch)
}
}

Expand Down