github.com/prysmaticlabs/prysm@v1.4.4/endtoend/evaluators/operations.go (about)

     1  package evaluators
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"math"
     8  
     9  	"github.com/pkg/errors"
    10  	types "github.com/prysmaticlabs/eth2-types"
    11  	corehelpers "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
    12  	"github.com/prysmaticlabs/prysm/endtoend/helpers"
    13  	e2e "github.com/prysmaticlabs/prysm/endtoend/params"
    14  	"github.com/prysmaticlabs/prysm/endtoend/policies"
    15  	e2etypes "github.com/prysmaticlabs/prysm/endtoend/types"
    16  	eth "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    17  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    18  	"github.com/prysmaticlabs/prysm/shared/params"
    19  	"github.com/prysmaticlabs/prysm/shared/testutil"
    20  	"golang.org/x/exp/rand"
    21  	"google.golang.org/grpc"
    22  	"google.golang.org/protobuf/types/known/emptypb"
    23  )
    24  
    25  // exitedIndex holds the exited index from ProposeVoluntaryExit in memory so other functions don't confuse it
    26  // for a normal validator.
    27  var exitedIndex types.ValidatorIndex
    28  
    29  // valExited is used to know if exitedIndex is set, since default value is 0.
    30  var valExited bool
    31  
    32  // churnLimit is normally 4 unless the validator set is extremely large.
    33  var churnLimit = uint64(4)
    34  var depositValCount = e2e.DepositCount
    35  
    36  // Deposits should be processed in twice the length of the epochs per eth1 voting period.
    37  var depositsInBlockStart = types.Epoch(math.Floor(float64(params.E2ETestConfig().EpochsPerEth1VotingPeriod) * 2))
    38  
    39  // deposits included + finalization + MaxSeedLookahead for activation.
    40  var depositActivationStartEpoch = depositsInBlockStart + 2 + params.E2ETestConfig().MaxSeedLookahead
    41  var depositEndEpoch = depositActivationStartEpoch + types.Epoch(math.Ceil(float64(depositValCount)/float64(churnLimit)))
    42  
    43  // ProcessesDepositsInBlocks ensures the expected amount of deposits are accepted into blocks.
    44  var ProcessesDepositsInBlocks = e2etypes.Evaluator{
    45  	Name:       "processes_deposits_in_blocks_epoch_%d",
    46  	Policy:     policies.OnEpoch(depositsInBlockStart), // We expect all deposits to enter in one epoch.
    47  	Evaluation: processesDepositsInBlocks,
    48  }
    49  
    50  // VerifyBlockGraffiti ensures the block graffiti is one of the random list.
    51  var VerifyBlockGraffiti = e2etypes.Evaluator{
    52  	Name:       "verify_graffiti_in_blocks_epoch_%d",
    53  	Policy:     policies.AfterNthEpoch(0),
    54  	Evaluation: verifyGraffitiInBlocks,
    55  }
    56  
    57  // ActivatesDepositedValidators ensures the expected amount of validator deposits are activated into the state.
    58  var ActivatesDepositedValidators = e2etypes.Evaluator{
    59  	Name:       "processes_deposit_validators_epoch_%d",
    60  	Policy:     policies.BetweenEpochs(depositActivationStartEpoch, depositEndEpoch),
    61  	Evaluation: activatesDepositedValidators,
    62  }
    63  
    64  // DepositedValidatorsAreActive ensures the expected amount of validators are active after their deposits are processed.
    65  var DepositedValidatorsAreActive = e2etypes.Evaluator{
    66  	Name:       "deposited_validators_are_active_epoch_%d",
    67  	Policy:     policies.AfterNthEpoch(depositEndEpoch),
    68  	Evaluation: depositedValidatorsAreActive,
    69  }
    70  
    71  // ProposeVoluntaryExit sends a voluntary exit from randomly selected validator in the genesis set.
    72  var ProposeVoluntaryExit = e2etypes.Evaluator{
    73  	Name:       "propose_voluntary_exit_epoch_%d",
    74  	Policy:     policies.OnEpoch(7),
    75  	Evaluation: proposeVoluntaryExit,
    76  }
    77  
    78  // ValidatorHasExited checks the beacon state for the exited validator and ensures its marked as exited.
    79  var ValidatorHasExited = e2etypes.Evaluator{
    80  	Name:       "voluntary_has_exited_%d",
    81  	Policy:     policies.OnEpoch(8),
    82  	Evaluation: validatorIsExited,
    83  }
    84  
    85  // ValidatorsVoteWithTheMajority verifies whether validator vote for eth1data using the majority algorithm.
    86  var ValidatorsVoteWithTheMajority = e2etypes.Evaluator{
    87  	Name:       "validators_vote_with_the_majority_%d",
    88  	Policy:     policies.AfterNthEpoch(0),
    89  	Evaluation: validatorsVoteWithTheMajority,
    90  }
    91  
    92  func processesDepositsInBlocks(conns ...*grpc.ClientConn) error {
    93  	conn := conns[0]
    94  	client := eth.NewBeaconChainClient(conn)
    95  
    96  	chainHead, err := client.GetChainHead(context.Background(), &emptypb.Empty{})
    97  	if err != nil {
    98  		return errors.Wrap(err, "failed to get chain head")
    99  	}
   100  
   101  	req := &eth.ListBlocksRequest{QueryFilter: &eth.ListBlocksRequest_Epoch{Epoch: chainHead.HeadEpoch - 1}}
   102  	blks, err := client.ListBlocks(context.Background(), req)
   103  	if err != nil {
   104  		return errors.Wrap(err, "failed to get blocks from beacon-chain")
   105  	}
   106  	var deposits uint64
   107  	for _, blk := range blks.BlockContainers {
   108  		fmt.Printf(
   109  			"Slot: %d with %d deposits, Eth1 block %#x with %d deposits\n",
   110  			blk.Block.Block.Slot,
   111  			len(blk.Block.Block.Body.Deposits),
   112  			blk.Block.Block.Body.Eth1Data.BlockHash, blk.Block.Block.Body.Eth1Data.DepositCount,
   113  		)
   114  		deposits += uint64(len(blk.Block.Block.Body.Deposits))
   115  	}
   116  	if deposits != depositValCount {
   117  		return fmt.Errorf("expected %d deposits to be processed, received %d", depositValCount, deposits)
   118  	}
   119  	return nil
   120  }
   121  
   122  func verifyGraffitiInBlocks(conns ...*grpc.ClientConn) error {
   123  	conn := conns[0]
   124  	client := eth.NewBeaconChainClient(conn)
   125  
   126  	chainHead, err := client.GetChainHead(context.Background(), &emptypb.Empty{})
   127  	if err != nil {
   128  		return errors.Wrap(err, "failed to get chain head")
   129  	}
   130  
   131  	req := &eth.ListBlocksRequest{QueryFilter: &eth.ListBlocksRequest_Epoch{Epoch: chainHead.HeadEpoch - 1}}
   132  	blks, err := client.ListBlocks(context.Background(), req)
   133  	if err != nil {
   134  		return errors.Wrap(err, "failed to get blocks from beacon-chain")
   135  	}
   136  	for _, blk := range blks.BlockContainers {
   137  		var e bool
   138  		for _, graffiti := range helpers.Graffiti {
   139  			if bytes.Equal(bytesutil.PadTo([]byte(graffiti), 32), blk.Block.Block.Body.Graffiti) {
   140  				e = true
   141  				break
   142  			}
   143  		}
   144  		if !e && blk.Block.Block.Slot != 0 {
   145  			return errors.New("could not get graffiti from the list")
   146  		}
   147  	}
   148  
   149  	return nil
   150  }
   151  
   152  func activatesDepositedValidators(conns ...*grpc.ClientConn) error {
   153  	conn := conns[0]
   154  	client := eth.NewBeaconChainClient(conn)
   155  
   156  	chainHead, err := client.GetChainHead(context.Background(), &emptypb.Empty{})
   157  	if err != nil {
   158  		return errors.Wrap(err, "failed to get chain head")
   159  	}
   160  
   161  	validatorRequest := &eth.ListValidatorsRequest{
   162  		PageSize:  int32(params.BeaconConfig().MinGenesisActiveValidatorCount),
   163  		PageToken: "1",
   164  	}
   165  	validators, err := client.ListValidators(context.Background(), validatorRequest)
   166  	if err != nil {
   167  		return errors.Wrap(err, "failed to get validators")
   168  	}
   169  
   170  	expectedCount := depositValCount
   171  	receivedCount := uint64(len(validators.ValidatorList))
   172  	if expectedCount != receivedCount {
   173  		return fmt.Errorf("expected validator count to be %d, recevied %d", expectedCount, receivedCount)
   174  	}
   175  
   176  	epoch := chainHead.HeadEpoch
   177  	depositsInEpoch := uint64(0)
   178  	var effBalanceLowCount, exitEpochWrongCount, withdrawEpochWrongCount uint64
   179  	for _, item := range validators.ValidatorList {
   180  		if item.Validator.ActivationEpoch == epoch {
   181  			depositsInEpoch++
   182  			if item.Validator.EffectiveBalance < params.BeaconConfig().MaxEffectiveBalance {
   183  				effBalanceLowCount++
   184  			}
   185  			if item.Validator.ExitEpoch != params.BeaconConfig().FarFutureEpoch {
   186  				exitEpochWrongCount++
   187  			}
   188  			if item.Validator.WithdrawableEpoch != params.BeaconConfig().FarFutureEpoch {
   189  				withdrawEpochWrongCount++
   190  			}
   191  		}
   192  	}
   193  	if depositsInEpoch != churnLimit {
   194  		return fmt.Errorf("expected %d deposits to be processed in epoch %d, received %d", churnLimit, epoch, depositsInEpoch)
   195  	}
   196  
   197  	if effBalanceLowCount > 0 {
   198  		return fmt.Errorf(
   199  			"%d validators did not have genesis validator effective balance of %d",
   200  			effBalanceLowCount,
   201  			params.BeaconConfig().MaxEffectiveBalance,
   202  		)
   203  	} else if exitEpochWrongCount > 0 {
   204  		return fmt.Errorf("%d validators did not have an exit epoch of far future epoch", exitEpochWrongCount)
   205  	} else if withdrawEpochWrongCount > 0 {
   206  		return fmt.Errorf("%d validators did not have a withdrawable epoch of far future epoch", withdrawEpochWrongCount)
   207  	}
   208  	return nil
   209  }
   210  
   211  func depositedValidatorsAreActive(conns ...*grpc.ClientConn) error {
   212  	conn := conns[0]
   213  	client := eth.NewBeaconChainClient(conn)
   214  	validatorRequest := &eth.ListValidatorsRequest{
   215  		PageSize:  int32(params.BeaconConfig().MinGenesisActiveValidatorCount),
   216  		PageToken: "1",
   217  	}
   218  	validators, err := client.ListValidators(context.Background(), validatorRequest)
   219  	if err != nil {
   220  		return errors.Wrap(err, "failed to get validators")
   221  	}
   222  
   223  	expectedCount := depositValCount
   224  	receivedCount := uint64(len(validators.ValidatorList))
   225  	if expectedCount != receivedCount {
   226  		return fmt.Errorf("expected validator count to be %d, recevied %d", expectedCount, receivedCount)
   227  	}
   228  
   229  	chainHead, err := client.GetChainHead(context.Background(), &emptypb.Empty{})
   230  	if err != nil {
   231  		return errors.Wrap(err, "failed to get chain head")
   232  	}
   233  
   234  	inactiveCount, belowBalanceCount := 0, 0
   235  	for _, item := range validators.ValidatorList {
   236  		if !corehelpers.IsActiveValidator(item.Validator, chainHead.HeadEpoch) {
   237  			inactiveCount++
   238  		}
   239  		if item.Validator.EffectiveBalance < params.BeaconConfig().MaxEffectiveBalance {
   240  			belowBalanceCount++
   241  		}
   242  	}
   243  
   244  	if inactiveCount > 0 {
   245  		return fmt.Errorf(
   246  			"%d validators were not active, expected %d active validators from deposits",
   247  			inactiveCount,
   248  			params.BeaconConfig().MinGenesisActiveValidatorCount,
   249  		)
   250  	}
   251  	if belowBalanceCount > 0 {
   252  		return fmt.Errorf(
   253  			"%d validators did not have a proper balance, expected %d validators to have 32 ETH",
   254  			belowBalanceCount,
   255  			params.BeaconConfig().MinGenesisActiveValidatorCount,
   256  		)
   257  	}
   258  	return nil
   259  }
   260  
   261  func proposeVoluntaryExit(conns ...*grpc.ClientConn) error {
   262  	conn := conns[0]
   263  	valClient := eth.NewBeaconNodeValidatorClient(conn)
   264  	beaconClient := eth.NewBeaconChainClient(conn)
   265  
   266  	ctx := context.Background()
   267  	chainHead, err := beaconClient.GetChainHead(ctx, &emptypb.Empty{})
   268  	if err != nil {
   269  		return errors.Wrap(err, "could not get chain head")
   270  	}
   271  
   272  	_, privKeys, err := testutil.DeterministicDepositsAndKeys(params.BeaconConfig().MinGenesisActiveValidatorCount)
   273  	if err != nil {
   274  		return err
   275  	}
   276  
   277  	exitedIndex = types.ValidatorIndex(rand.Uint64() % params.BeaconConfig().MinGenesisActiveValidatorCount)
   278  	valExited = true
   279  
   280  	voluntaryExit := &eth.VoluntaryExit{
   281  		Epoch:          chainHead.HeadEpoch,
   282  		ValidatorIndex: exitedIndex,
   283  	}
   284  	req := &eth.DomainRequest{
   285  		Epoch:  chainHead.HeadEpoch,
   286  		Domain: params.BeaconConfig().DomainVoluntaryExit[:],
   287  	}
   288  	domain, err := valClient.DomainData(ctx, req)
   289  	if err != nil {
   290  		return err
   291  	}
   292  	signingData, err := corehelpers.ComputeSigningRoot(voluntaryExit, domain.SignatureDomain)
   293  	if err != nil {
   294  		return err
   295  	}
   296  	signature := privKeys[exitedIndex].Sign(signingData[:])
   297  	signedExit := &eth.SignedVoluntaryExit{
   298  		Exit:      voluntaryExit,
   299  		Signature: signature.Marshal(),
   300  	}
   301  
   302  	if _, err = valClient.ProposeExit(ctx, signedExit); err != nil {
   303  		return errors.Wrap(err, "could not propose exit")
   304  	}
   305  	return nil
   306  }
   307  
   308  func validatorIsExited(conns ...*grpc.ClientConn) error {
   309  	conn := conns[0]
   310  	client := eth.NewBeaconChainClient(conn)
   311  	validatorRequest := &eth.GetValidatorRequest{
   312  		QueryFilter: &eth.GetValidatorRequest_Index{
   313  			Index: exitedIndex,
   314  		},
   315  	}
   316  	validator, err := client.GetValidator(context.Background(), validatorRequest)
   317  	if err != nil {
   318  		return errors.Wrap(err, "failed to get validators")
   319  	}
   320  	if validator.ExitEpoch == params.BeaconConfig().FarFutureEpoch {
   321  		return fmt.Errorf("expected validator %d to be submitted for exit", exitedIndex)
   322  	}
   323  	return nil
   324  }
   325  
   326  func validatorsVoteWithTheMajority(conns ...*grpc.ClientConn) error {
   327  	conn := conns[0]
   328  	client := eth.NewBeaconChainClient(conn)
   329  
   330  	chainHead, err := client.GetChainHead(context.Background(), &emptypb.Empty{})
   331  	if err != nil {
   332  		return errors.Wrap(err, "failed to get chain head")
   333  	}
   334  
   335  	req := &eth.ListBlocksRequest{QueryFilter: &eth.ListBlocksRequest_Epoch{Epoch: chainHead.HeadEpoch - 1}}
   336  	blks, err := client.ListBlocks(context.Background(), req)
   337  	if err != nil {
   338  		return errors.Wrap(err, "failed to get blocks from beacon-chain")
   339  	}
   340  
   341  	for _, blk := range blks.BlockContainers {
   342  		slot, vote := blk.Block.Block.Slot, blk.Block.Block.Body.Eth1Data.BlockHash
   343  		slotsPerVotingPeriod := params.E2ETestConfig().SlotsPerEpoch.Mul(uint64(params.E2ETestConfig().EpochsPerEth1VotingPeriod))
   344  
   345  		// We treat epoch 1 differently from other epoch for two reasons:
   346  		// - this evaluator is not executed for epoch 0 so we have to calculate the first slot differently
   347  		// - for some reason the vote for the first slot in epoch 1 is 0x000... so we skip this slot
   348  		var isFirstSlotInVotingPeriod bool
   349  		if chainHead.HeadEpoch == 1 && slot%params.E2ETestConfig().SlotsPerEpoch == 0 {
   350  			continue
   351  		}
   352  		// We skipped the first slot so we treat the second slot as the starting slot of epoch 1.
   353  		if chainHead.HeadEpoch == 1 {
   354  			isFirstSlotInVotingPeriod = slot%params.E2ETestConfig().SlotsPerEpoch == 1
   355  		} else {
   356  			isFirstSlotInVotingPeriod = slot%slotsPerVotingPeriod == 0
   357  		}
   358  		if isFirstSlotInVotingPeriod {
   359  			expectedEth1DataVote = vote
   360  			return nil
   361  		}
   362  
   363  		if !bytes.Equal(vote, expectedEth1DataVote) {
   364  			return fmt.Errorf("incorrect eth1data vote for slot %d; expected: %#x vs voted: %#x",
   365  				slot, expectedEth1DataVote, vote)
   366  		}
   367  	}
   368  	return nil
   369  }
   370  
   371  var expectedEth1DataVote []byte