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>
This commit is contained in:
Ivan Martinez
2020-05-28 22:51:00 -04:00
committed by GitHub
parent 88aaee36bf
commit 0b64db5e21
6 changed files with 245 additions and 138 deletions

View File

@@ -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",
],
)

View File

@@ -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 := &eth.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 := &eth.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 := &eth.VoluntaryExit{
Epoch: chainHead.HeadEpoch,
ValidatorIndex: exitedIndice,
}
req := &eth.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 := &eth.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 := &eth.GetValidatorRequest{
QueryFilter: &eth.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
}

View File

@@ -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 := &eth.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 := &eth.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 := &eth.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
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -316,6 +316,7 @@ func E2ETestConfig() *BeaconChainConfig {
e2eConfig.SecondsPerSlot = 8
e2eConfig.SecondsPerETH1Block = 2
e2eConfig.Eth1FollowDistance = 4
e2eConfig.PersistentCommitteePeriod = 4
return e2eConfig
}