From 0b64db5e21ba207a031a4e9e22d158f3545b47a4 Mon Sep 17 00:00:00 2001 From: Ivan Martinez Date: Thu, 28 May 2020 22:51:00 -0400 Subject: [PATCH] Add voluntary exit processing to E2E (#6016) * Add voluntary exit to E2E * Fix long urnning e2e * Fix for comments Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com> --- endtoend/evaluators/BUILD.bazel | 2 + endtoend/evaluators/operations.go | 233 ++++++++++++++++++++++++++++++ endtoend/evaluators/validator.go | 143 +----------------- endtoend/long_minimal_e2e_test.go | 2 + endtoend/minimal_e2e_test.go | 2 + shared/params/config.go | 1 + 6 files changed, 245 insertions(+), 138 deletions(-) create mode 100644 endtoend/evaluators/operations.go diff --git a/endtoend/evaluators/BUILD.bazel b/endtoend/evaluators/BUILD.bazel index 486056b541..95a41cd8c8 100644 --- a/endtoend/evaluators/BUILD.bazel +++ b/endtoend/evaluators/BUILD.bazel @@ -7,6 +7,7 @@ go_library( "finality.go", "metrics.go", "node.go", + "operations.go", "slashing.go", "validator.go", ], @@ -26,5 +27,6 @@ go_library( "@com_github_prysmaticlabs_ethereumapis//eth/v1alpha1:go_default_library", "@com_github_prysmaticlabs_go_bitfield//:go_default_library", "@org_golang_google_grpc//:go_default_library", + "@org_golang_x_exp//rand:go_default_library", ], ) diff --git a/endtoend/evaluators/operations.go b/endtoend/evaluators/operations.go new file mode 100644 index 0000000000..f3782561c4 --- /dev/null +++ b/endtoend/evaluators/operations.go @@ -0,0 +1,233 @@ +package evaluators + +import ( + "context" + "fmt" + + ptypes "github.com/gogo/protobuf/types" + "github.com/pkg/errors" + eth "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" + "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" + e2e "github.com/prysmaticlabs/prysm/endtoend/params" + "github.com/prysmaticlabs/prysm/endtoend/types" + "github.com/prysmaticlabs/prysm/shared/params" + "github.com/prysmaticlabs/prysm/shared/testutil" + "golang.org/x/exp/rand" + "google.golang.org/grpc" +) + +// exitedIndice holds the exited indice from ProposeVoluntaryExit in memory so other functions don't confuse it +// for a normal validator. +var exitedIndice uint64 + +// valExited is used to know if exitedIndice is set, since default value is 0. +var valExited bool + +// ProcessesDepositedValidators ensures the expected amount of validator deposits are processed into the state. +var ProcessesDepositedValidators = types.Evaluator{ + Name: "processes_deposit_validators_epoch_%d", + Policy: isBetweenEpochs(8, 21), //Choosing 8-21 because of the churn limit of 4 per epoch for 256 vals / 4 beacon nodes = 64 deposits. ) + Evaluation: processesDepositedValidators, +} + +// DepositedValidatorsAreActive ensures the expected amount of validators are active after their deposits are processed. +var DepositedValidatorsAreActive = types.Evaluator{ + Name: "deposited_validators_are_active_epoch_%d", + Policy: afterNthEpoch(22), + Evaluation: depositedValidatorsAreActive, +} + +// ProposeVoluntaryExit sends a voluntary exit from randomly selected validator in the genesis set. +var ProposeVoluntaryExit = types.Evaluator{ + Name: "propose_voluntary_exit_epoch_%d", + Policy: onEpoch(5), + Evaluation: proposeVoluntaryExit, +} + +// ValidatorHasExited checks the beacon state for the exited validator and ensures its marked as exited. +var ValidatorHasExited = types.Evaluator{ + Name: "voluntary_has_exited_%d", + Policy: onEpoch(6), + Evaluation: validatorIsExited, +} + +// Not including first epoch because of issues with genesis. +func isBetweenEpochs(fromEpoch uint64, toEpoch uint64) func(uint64) bool { + return func(currentEpoch uint64) bool { + return fromEpoch < currentEpoch && currentEpoch > toEpoch + } +} + +func processesDepositedValidators(conns ...*grpc.ClientConn) error { + conn := conns[0] + client := eth.NewBeaconChainClient(conn) + validatorRequest := ð.ListValidatorsRequest{ + PageSize: int32(params.BeaconConfig().MinGenesisActiveValidatorCount), + PageToken: "1", + } + validators, err := client.ListValidators(context.Background(), validatorRequest) + if err != nil { + return errors.Wrap(err, "failed to get validators") + } + + expectedCount := params.BeaconConfig().MinGenesisActiveValidatorCount / uint64(e2e.TestParams.BeaconNodeCount) + receivedCount := uint64(len(validators.ValidatorList)) + if expectedCount != receivedCount { + return fmt.Errorf("expected validator count to be %d, recevied %d", expectedCount, receivedCount) + } + + churnLimit, err := helpers.ValidatorChurnLimit(params.BeaconConfig().MinGenesisActiveValidatorCount + uint64(len(validators.ValidatorList))) + if err != nil { + return errors.Wrap(err, "failed to calculate churn limit") + } + var effBalanceLowCount, exitEpochWrongCount, withdrawEpochWrongCount uint64 + var activeEpoch10Count, activeEpoch11Count, activeEpoch12Count, activeEpoch13Count uint64 + for _, item := range validators.ValidatorList { + switch item.Validator.ActivationEpoch { + case 10: + activeEpoch10Count++ + case 11: + activeEpoch11Count++ + case 12: + activeEpoch12Count++ + case 13: + activeEpoch13Count++ + } + + if item.Validator.EffectiveBalance < params.BeaconConfig().MaxEffectiveBalance { + effBalanceLowCount++ + } + if item.Validator.ExitEpoch != params.BeaconConfig().FarFutureEpoch { + exitEpochWrongCount++ + } + if item.Validator.WithdrawableEpoch != params.BeaconConfig().FarFutureEpoch { + withdrawEpochWrongCount++ + } + } + + if effBalanceLowCount > 0 { + return fmt.Errorf( + "%d validators did not have genesis validator effective balance of %d", + effBalanceLowCount, + params.BeaconConfig().MaxEffectiveBalance, + ) + } else if activeEpoch10Count != churnLimit { + return fmt.Errorf("%d validators did not have activation epoch of 10", activeEpoch10Count) + } else if activeEpoch11Count != churnLimit { + return fmt.Errorf("%d validators did not have activation epoch of 11", activeEpoch11Count) + } else if activeEpoch12Count != churnLimit { + return fmt.Errorf("%d validators did not have activation epoch of 12", activeEpoch12Count) + } else if activeEpoch13Count != churnLimit { + return fmt.Errorf("%d validators did not have activation epoch of 13", activeEpoch13Count) + } else if exitEpochWrongCount > 0 { + return fmt.Errorf("%d validators did not have an exit epoch of far future epoch", exitEpochWrongCount) + } else if withdrawEpochWrongCount > 0 { + return fmt.Errorf("%d validators did not have a withdrawable epoch of far future epoch", withdrawEpochWrongCount) + } + return nil +} + +func depositedValidatorsAreActive(conns ...*grpc.ClientConn) error { + conn := conns[0] + client := eth.NewBeaconChainClient(conn) + validatorRequest := ð.ListValidatorsRequest{ + PageSize: int32(params.BeaconConfig().MinGenesisActiveValidatorCount), + PageToken: "1", + } + validators, err := client.ListValidators(context.Background(), validatorRequest) + if err != nil { + return errors.Wrap(err, "failed to get validators") + } + + expectedCount := params.BeaconConfig().MinGenesisActiveValidatorCount / uint64(e2e.TestParams.BeaconNodeCount) + receivedCount := uint64(len(validators.ValidatorList)) + if expectedCount != receivedCount { + return fmt.Errorf("expected validator count to be %d, recevied %d", expectedCount, receivedCount) + } + + chainHead, err := client.GetChainHead(context.Background(), &ptypes.Empty{}) + if err != nil { + return errors.Wrap(err, "failed to get chain head") + } + + inactiveCount := 0 + for _, item := range validators.ValidatorList { + if !helpers.IsActiveValidator(item.Validator, chainHead.HeadEpoch) { + inactiveCount++ + } + } + + if inactiveCount > 0 { + return fmt.Errorf( + "%d validators were not active, expected %d active validators from deposits", + inactiveCount, + params.BeaconConfig().MinGenesisActiveValidatorCount, + ) + } + return nil +} + +func proposeVoluntaryExit(conns ...*grpc.ClientConn) error { + conn := conns[0] + valClient := eth.NewBeaconNodeValidatorClient(conn) + beaconClient := eth.NewBeaconChainClient(conn) + + ctx := context.Background() + chainHead, err := beaconClient.GetChainHead(ctx, &ptypes.Empty{}) + if err != nil { + return errors.Wrap(err, "could not get chain head") + } + + _, privKeys, err := testutil.DeterministicDepositsAndKeys(params.BeaconConfig().MinGenesisActiveValidatorCount) + if err != nil { + return err + } + + exitedIndice = rand.Uint64() % params.BeaconConfig().MinGenesisActiveValidatorCount + valExited = true + + voluntaryExit := ð.VoluntaryExit{ + Epoch: chainHead.HeadEpoch, + ValidatorIndex: exitedIndice, + } + req := ð.DomainRequest{ + Epoch: chainHead.HeadEpoch, + Domain: params.BeaconConfig().DomainVoluntaryExit[:], + } + domain, err := valClient.DomainData(ctx, req) + if err != nil { + return err + } + signingData, err := helpers.ComputeSigningRoot(voluntaryExit, domain.SignatureDomain) + if err != nil { + return err + } + signature := privKeys[exitedIndice].Sign(signingData[:]) + signedExit := ð.SignedVoluntaryExit{ + Exit: voluntaryExit, + Signature: signature.Marshal(), + } + + if _, err = valClient.ProposeExit(ctx, signedExit); err != nil { + return errors.Wrap(err, "could not propose exit") + } + return nil +} + +func validatorIsExited(conns ...*grpc.ClientConn) error { + conn := conns[0] + client := eth.NewBeaconChainClient(conn) + validatorRequest := ð.GetValidatorRequest{ + QueryFilter: ð.GetValidatorRequest_Index{ + Index: exitedIndice, + }, + } + validator, err := client.GetValidator(context.Background(), validatorRequest) + if err != nil { + return errors.Wrap(err, "failed to get validators") + } + if validator.ExitEpoch == params.BeaconConfig().FarFutureEpoch { + return fmt.Errorf("expected validator %d to be submitted for exit", exitedIndice) + } + return nil +} diff --git a/endtoend/evaluators/validator.go b/endtoend/evaluators/validator.go index 3210259bdd..ad3a9374c4 100644 --- a/endtoend/evaluators/validator.go +++ b/endtoend/evaluators/validator.go @@ -4,17 +4,14 @@ import ( "context" "fmt" - ptypes "github.com/gogo/protobuf/types" "github.com/pkg/errors" eth "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" - "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" - e2e "github.com/prysmaticlabs/prysm/endtoend/params" "github.com/prysmaticlabs/prysm/endtoend/types" "github.com/prysmaticlabs/prysm/shared/params" "google.golang.org/grpc" ) -var expectedParticipation = 1 // 100% participation. +var expectedParticipation = 0.98 // 98% participation to make room for small fluctuations. // ValidatorsAreActive ensures the expected amount of validators are active. var ValidatorsAreActive = types.Evaluator{ @@ -30,20 +27,6 @@ var ValidatorsParticipating = types.Evaluator{ Evaluation: validatorsParticipating, } -// ProcessesDepositedValidators ensures the expected amount of validator deposits are processed into the state. -var ProcessesDepositedValidators = types.Evaluator{ - Name: "processes_deposit_validators_epoch_%d", - Policy: isBetweenEpochs(8, 21), //Choosing 8-21 because of the churn limit of 4 per epoch for 256 vals / 4 beacon nodes = 64 deposits. ) - Evaluation: processesDepositedValidators, -} - -// DepositedValidatorsAreActive ensures the expected amount of validators are active after their deposits are processed. -var DepositedValidatorsAreActive = types.Evaluator{ - Name: "deposited_validators_are_active_epoch_%d", - Policy: afterNthEpoch(22), - Evaluation: depositedValidatorsAreActive, -} - // Not including first epoch because of issues with genesis. func afterNthEpoch(afterEpoch uint64) func(uint64) bool { return func(currentEpoch uint64) bool { @@ -51,13 +34,6 @@ func afterNthEpoch(afterEpoch uint64) func(uint64) bool { } } -// Not including first epoch because of issues with genesis. -func isBetweenEpochs(fromEpoch uint64, toEpoch uint64) func(uint64) bool { - return func(currentEpoch uint64) bool { - return fromEpoch < currentEpoch && currentEpoch > toEpoch - } -} - // All epochs. func allEpochs(currentEpoch uint64) bool { return true @@ -69,6 +45,7 @@ func validatorsAreActive(conns ...*grpc.ClientConn) error { // Balances actually fluctuate but we just want to check initial balance. validatorRequest := ð.ListValidatorsRequest{ PageSize: int32(params.BeaconConfig().MinGenesisActiveValidatorCount), + Active: true, } validators, err := client.ListValidators(context.Background(), validatorRequest) if err != nil { @@ -86,6 +63,9 @@ func validatorsAreActive(conns ...*grpc.ClientConn) error { exitEpochWrongCount := 0 withdrawEpochWrongCount := 0 for _, item := range validators.ValidatorList { + if valExited && item.Index == exitedIndice { + continue + } if item.Validator.EffectiveBalance < params.BeaconConfig().MaxEffectiveBalance { effBalanceLowCount++ } @@ -139,116 +119,3 @@ func validatorsParticipating(conns ...*grpc.ClientConn) error { } return nil } - -func processesDepositedValidators(conns ...*grpc.ClientConn) error { - conn := conns[0] - client := eth.NewBeaconChainClient(conn) - validatorRequest := ð.ListValidatorsRequest{ - PageSize: int32(params.BeaconConfig().MinGenesisActiveValidatorCount), - PageToken: "1", - } - validators, err := client.ListValidators(context.Background(), validatorRequest) - if err != nil { - return errors.Wrap(err, "failed to get validators") - } - - expectedCount := params.BeaconConfig().MinGenesisActiveValidatorCount / uint64(e2e.TestParams.BeaconNodeCount) - receivedCount := uint64(len(validators.ValidatorList)) - if expectedCount != receivedCount { - return fmt.Errorf("expected validator count to be %d, recevied %d", expectedCount, receivedCount) - } - - churnLimit, err := helpers.ValidatorChurnLimit(params.BeaconConfig().MinGenesisActiveValidatorCount + uint64(len(validators.ValidatorList))) - if err != nil { - return errors.Wrap(err, "failed to calculate churn limit") - } - effBalanceLowCount := 0 - activeEpoch10Count := 0 - activeEpoch11Count := 0 - activeEpoch12Count := 0 - activeEpoch13Count := 0 - exitEpochWrongCount := 0 - withdrawEpochWrongCount := 0 - for _, item := range validators.ValidatorList { - if item.Validator.EffectiveBalance < params.BeaconConfig().MaxEffectiveBalance { - effBalanceLowCount++ - } - if item.Validator.ActivationEpoch == 10 { - activeEpoch10Count++ - } else if item.Validator.ActivationEpoch == 11 { - activeEpoch11Count++ - } else if item.Validator.ActivationEpoch == 12 { - activeEpoch12Count++ - } else if item.Validator.ActivationEpoch == 13 { - activeEpoch13Count++ - } - - if item.Validator.ExitEpoch != params.BeaconConfig().FarFutureEpoch { - exitEpochWrongCount++ - } - if item.Validator.WithdrawableEpoch != params.BeaconConfig().FarFutureEpoch { - withdrawEpochWrongCount++ - } - } - - if effBalanceLowCount > 0 { - return fmt.Errorf( - "%d validators did not have genesis validator effective balance of %d", - effBalanceLowCount, - params.BeaconConfig().MaxEffectiveBalance, - ) - } else if activeEpoch10Count != int(churnLimit) { - return fmt.Errorf("%d validators did not have activation epoch of 10", activeEpoch10Count) - } else if activeEpoch11Count != int(churnLimit) { - return fmt.Errorf("%d validators did not have activation epoch of 11", activeEpoch11Count) - } else if activeEpoch12Count != int(churnLimit) { - return fmt.Errorf("%d validators did not have activation epoch of 12", activeEpoch12Count) - } else if activeEpoch13Count != int(churnLimit) { - return fmt.Errorf("%d validators did not have activation epoch of 13", activeEpoch13Count) - } else if exitEpochWrongCount > 0 { - return fmt.Errorf("%d validators did not have an exit epoch of far future epoch", exitEpochWrongCount) - } else if withdrawEpochWrongCount > 0 { - return fmt.Errorf("%d validators did not have a withdrawable epoch of far future epoch", withdrawEpochWrongCount) - } - return nil -} - -func depositedValidatorsAreActive(conns ...*grpc.ClientConn) error { - conn := conns[0] - client := eth.NewBeaconChainClient(conn) - validatorRequest := ð.ListValidatorsRequest{ - PageSize: int32(params.BeaconConfig().MinGenesisActiveValidatorCount), - PageToken: "1", - } - validators, err := client.ListValidators(context.Background(), validatorRequest) - if err != nil { - return errors.Wrap(err, "failed to get validators") - } - - expectedCount := params.BeaconConfig().MinGenesisActiveValidatorCount / uint64(e2e.TestParams.BeaconNodeCount) - receivedCount := uint64(len(validators.ValidatorList)) - if expectedCount != receivedCount { - return fmt.Errorf("expected validator count to be %d, recevied %d", expectedCount, receivedCount) - } - - chainHead, err := client.GetChainHead(context.Background(), &ptypes.Empty{}) - if err != nil { - return errors.Wrap(err, "failed to get chain head") - } - - inactiveCount := 0 - for _, item := range validators.ValidatorList { - if !helpers.IsActiveValidator(item.Validator, chainHead.HeadEpoch) { - inactiveCount++ - } - } - - if inactiveCount > 0 { - return fmt.Errorf( - "%d validators were not active, expected %d active validators from deposits", - inactiveCount, - params.BeaconConfig().MinGenesisActiveValidatorCount, - ) - } - return nil -} diff --git a/endtoend/long_minimal_e2e_test.go b/endtoend/long_minimal_e2e_test.go index 329433ef6f..5ef502e79d 100644 --- a/endtoend/long_minimal_e2e_test.go +++ b/endtoend/long_minimal_e2e_test.go @@ -43,7 +43,9 @@ func TestEndToEnd_Long_MinimalConfig(t *testing.T) { ev.FinalizationOccurs, ev.MetricsCheck, ev.ProcessesDepositedValidators, + ev.ProposeVoluntaryExit, ev.DepositedValidatorsAreActive, + ev.ValidatorHasExited, }, } if err := e2eParams.Init(4); err != nil { diff --git a/endtoend/minimal_e2e_test.go b/endtoend/minimal_e2e_test.go index 83c53cfd15..90ff4c2efc 100644 --- a/endtoend/minimal_e2e_test.go +++ b/endtoend/minimal_e2e_test.go @@ -27,6 +27,8 @@ func TestEndToEnd_MinimalConfig(t *testing.T) { ev.ValidatorsParticipating, ev.FinalizationOccurs, ev.MetricsCheck, + ev.ProposeVoluntaryExit, + ev.ValidatorHasExited, }, } if err := e2eParams.Init(2); err != nil { diff --git a/shared/params/config.go b/shared/params/config.go index d192cdf7d4..4c888497bd 100644 --- a/shared/params/config.go +++ b/shared/params/config.go @@ -316,6 +316,7 @@ func E2ETestConfig() *BeaconChainConfig { e2eConfig.SecondsPerSlot = 8 e2eConfig.SecondsPerETH1Block = 2 e2eConfig.Eth1FollowDistance = 4 + e2eConfig.PersistentCommitteePeriod = 4 return e2eConfig }