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 := ð.ListBlocksRequest{QueryFilter: ð.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 := ð.ListBlocksRequest{QueryFilter: ð.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 := ð.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 := ð.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 := ð.VoluntaryExit{ 281 Epoch: chainHead.HeadEpoch, 282 ValidatorIndex: exitedIndex, 283 } 284 req := ð.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 := ð.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 := ð.GetValidatorRequest{ 312 QueryFilter: ð.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 := ð.ListBlocksRequest{QueryFilter: ð.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