github.com/prysmaticlabs/prysm@v1.4.4/validator/client/validator.go (about) 1 // Package client represents a gRPC polling-based implementation 2 // of an Ethereum validator client. 3 package client 4 5 import ( 6 "bytes" 7 "context" 8 "encoding/binary" 9 "encoding/hex" 10 "fmt" 11 "io" 12 "strconv" 13 "strings" 14 "sync" 15 "time" 16 17 "github.com/dgraph-io/ristretto" 18 lru "github.com/hashicorp/golang-lru" 19 "github.com/pkg/errors" 20 types "github.com/prysmaticlabs/eth2-types" 21 "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" 22 ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" 23 "github.com/prysmaticlabs/prysm/shared/bytesutil" 24 "github.com/prysmaticlabs/prysm/shared/event" 25 "github.com/prysmaticlabs/prysm/shared/featureconfig" 26 "github.com/prysmaticlabs/prysm/shared/hashutil" 27 "github.com/prysmaticlabs/prysm/shared/params" 28 "github.com/prysmaticlabs/prysm/shared/slotutil" 29 accountsiface "github.com/prysmaticlabs/prysm/validator/accounts/iface" 30 "github.com/prysmaticlabs/prysm/validator/accounts/wallet" 31 "github.com/prysmaticlabs/prysm/validator/client/iface" 32 vdb "github.com/prysmaticlabs/prysm/validator/db" 33 "github.com/prysmaticlabs/prysm/validator/db/kv" 34 "github.com/prysmaticlabs/prysm/validator/graffiti" 35 "github.com/prysmaticlabs/prysm/validator/keymanager" 36 slashingiface "github.com/prysmaticlabs/prysm/validator/slashing-protection/iface" 37 "github.com/sirupsen/logrus" 38 "go.opencensus.io/trace" 39 "google.golang.org/protobuf/proto" 40 "google.golang.org/protobuf/types/known/emptypb" 41 ) 42 43 // reconnectPeriod is the frequency that we try to restart our 44 // slasher connection when the slasher client connection is not ready. 45 var reconnectPeriod = 5 * time.Second 46 47 // keyFetchPeriod is the frequency that we try to refetch validating keys 48 // in case no keys were fetched previously. 49 var keyRefetchPeriod = 30 * time.Second 50 51 var ( 52 msgCouldNotFetchKeys = "could not fetch validating keys" 53 msgNoKeysFetched = "No validating keys fetched. Trying again" 54 ) 55 56 type validator struct { 57 logValidatorBalances bool 58 useWeb bool 59 emitAccountMetrics bool 60 logDutyCountDown bool 61 domainDataLock sync.Mutex 62 attLogsLock sync.Mutex 63 aggregatedSlotCommitteeIDCacheLock sync.Mutex 64 prevBalanceLock sync.RWMutex 65 slashableKeysLock sync.RWMutex 66 walletInitializedFeed *event.Feed 67 blockFeed *event.Feed 68 genesisTime uint64 69 highestValidSlot types.Slot 70 domainDataCache *ristretto.Cache 71 aggregatedSlotCommitteeIDCache *lru.Cache 72 ticker slotutil.Ticker 73 prevBalance map[[48]byte]uint64 74 duties *ethpb.DutiesResponse 75 startBalances map[[48]byte]uint64 76 attLogs map[[32]byte]*attSubmitted 77 node ethpb.NodeClient 78 keyManager keymanager.IKeymanager 79 beaconClient ethpb.BeaconChainClient 80 validatorClient ethpb.BeaconNodeValidatorClient 81 protector slashingiface.Protector 82 db vdb.Database 83 graffiti []byte 84 voteStats voteStats 85 graffitiStruct *graffiti.Graffiti 86 graffitiOrderedIndex uint64 87 eipImportBlacklistedPublicKeys map[[48]byte]bool 88 } 89 90 type validatorStatus struct { 91 publicKey []byte 92 status *ethpb.ValidatorStatusResponse 93 index types.ValidatorIndex 94 } 95 96 // Done cleans up the validator. 97 func (v *validator) Done() { 98 v.ticker.Done() 99 } 100 101 // WaitForWalletInitialization checks if the validator needs to wait for 102 func (v *validator) WaitForWalletInitialization(ctx context.Context) error { 103 // This function should only run if we are using managing the 104 // validator client using the Prysm web UI. 105 if !v.useWeb { 106 return nil 107 } 108 if v.keyManager != nil { 109 return nil 110 } 111 walletChan := make(chan *wallet.Wallet) 112 sub := v.walletInitializedFeed.Subscribe(walletChan) 113 defer sub.Unsubscribe() 114 for { 115 select { 116 case w := <-walletChan: 117 keyManager, err := w.InitializeKeymanager(ctx, accountsiface.InitKeymanagerConfig{ListenForChanges: true}) 118 if err != nil { 119 return errors.Wrap(err, "could not read keymanager") 120 } 121 v.keyManager = keyManager 122 return nil 123 case <-ctx.Done(): 124 return errors.New("context canceled") 125 case <-sub.Err(): 126 log.Error("Subscriber closed, exiting goroutine") 127 return nil 128 } 129 } 130 } 131 132 // WaitForChainStart checks whether the beacon node has started its runtime. That is, 133 // it calls to the beacon node which then verifies the ETH1.0 deposit contract logs to check 134 // for the ChainStart log to have been emitted. If so, it starts a ticker based on the ChainStart 135 // unix timestamp which will be used to keep track of time within the validator client. 136 func (v *validator) WaitForChainStart(ctx context.Context) error { 137 ctx, span := trace.StartSpan(ctx, "validator.WaitForChainStart") 138 defer span.End() 139 // First, check if the beacon chain has started. 140 stream, err := v.validatorClient.WaitForChainStart(ctx, &emptypb.Empty{}) 141 if err != nil { 142 return errors.Wrap( 143 iface.ErrConnectionIssue, 144 errors.Wrap(err, "could not setup beacon chain ChainStart streaming client").Error(), 145 ) 146 } 147 148 log.Info("Waiting for beacon chain start log from the ETH 1.0 deposit contract") 149 chainStartRes, err := stream.Recv() 150 if err != io.EOF { 151 if ctx.Err() == context.Canceled { 152 return errors.Wrap(ctx.Err(), "context has been canceled so shutting down the loop") 153 } 154 if err != nil { 155 return errors.Wrap( 156 iface.ErrConnectionIssue, 157 errors.Wrap(err, "could not receive ChainStart from stream").Error(), 158 ) 159 } 160 v.genesisTime = chainStartRes.GenesisTime 161 curGenValRoot, err := v.db.GenesisValidatorsRoot(ctx) 162 if err != nil { 163 return errors.Wrap(err, "could not get current genesis validators root") 164 } 165 if len(curGenValRoot) == 0 { 166 if err := v.db.SaveGenesisValidatorsRoot(ctx, chainStartRes.GenesisValidatorsRoot); err != nil { 167 return errors.Wrap(err, "could not save genesis validator root") 168 } 169 } else { 170 if !bytes.Equal(curGenValRoot, chainStartRes.GenesisValidatorsRoot) { 171 log.Errorf("The genesis validators root received from the beacon node does not match what is in " + 172 "your validator database. This could indicate that this is a database meant for another network. If " + 173 "you were previously running this validator database on another network, please run --clear-db to " + 174 "clear the database. If not, please file an issue at https://github.com/prysmaticlabs/prysm/issues") 175 return fmt.Errorf( 176 "genesis validators root from beacon node (%#x) does not match root saved in validator db (%#x)", 177 chainStartRes.GenesisValidatorsRoot, 178 curGenValRoot, 179 ) 180 } 181 } 182 } else { 183 return iface.ErrConnectionIssue 184 } 185 186 // Once the ChainStart log is received, we update the genesis time of the validator client 187 // and begin a slot ticker used to track the current slot the beacon node is in. 188 v.ticker = slotutil.NewSlotTicker(time.Unix(int64(v.genesisTime), 0), params.BeaconConfig().SecondsPerSlot) 189 log.WithField("genesisTime", time.Unix(int64(v.genesisTime), 0)).Info("Beacon chain started") 190 return nil 191 } 192 193 // WaitForSync checks whether the beacon node has sync to the latest head. 194 func (v *validator) WaitForSync(ctx context.Context) error { 195 ctx, span := trace.StartSpan(ctx, "validator.WaitForSync") 196 defer span.End() 197 198 s, err := v.node.GetSyncStatus(ctx, &emptypb.Empty{}) 199 if err != nil { 200 return errors.Wrap(iface.ErrConnectionIssue, errors.Wrap(err, "could not get sync status").Error()) 201 } 202 if !s.Syncing { 203 return nil 204 } 205 206 for { 207 select { 208 // Poll every half slot. 209 case <-time.After(slotutil.DivideSlotBy(2 /* twice per slot */)): 210 s, err := v.node.GetSyncStatus(ctx, &emptypb.Empty{}) 211 if err != nil { 212 return errors.Wrap(iface.ErrConnectionIssue, errors.Wrap(err, "could not get sync status").Error()) 213 } 214 if !s.Syncing { 215 return nil 216 } 217 log.Info("Waiting for beacon node to sync to latest chain head") 218 case <-ctx.Done(): 219 return errors.New("context has been canceled, exiting goroutine") 220 } 221 } 222 } 223 224 // SlasherReady checks if slasher that was configured as external protection 225 // is reachable. 226 func (v *validator) SlasherReady(ctx context.Context) error { 227 ctx, span := trace.StartSpan(ctx, "validator.SlasherReady") 228 defer span.End() 229 if featureconfig.Get().SlasherProtection { 230 err := v.protector.Status() 231 if err == nil { 232 return nil 233 } 234 ticker := time.NewTicker(reconnectPeriod) 235 defer ticker.Stop() 236 for { 237 select { 238 case <-ticker.C: 239 log.WithError(err).Info("Slasher connection wasn't ready. Trying again") 240 err = v.protector.Status() 241 if err != nil { 242 continue 243 } 244 log.Info("Slasher connection is ready") 245 return nil 246 case <-ctx.Done(): 247 log.Debug("Context closed, exiting reconnect external protection") 248 return ctx.Err() 249 } 250 } 251 } 252 return nil 253 } 254 255 // ReceiveBlocks starts a gRPC client stream listener to obtain 256 // blocks from the beacon node. Upon receiving a block, the service 257 // broadcasts it to a feed for other usages to subscribe to. 258 func (v *validator) ReceiveBlocks(ctx context.Context, connectionErrorChannel chan<- error) { 259 stream, err := v.beaconClient.StreamBlocks(ctx, ðpb.StreamBlocksRequest{VerifiedOnly: true}) 260 if err != nil { 261 log.WithError(err).Error("Failed to retrieve blocks stream, " + iface.ErrConnectionIssue.Error()) 262 connectionErrorChannel <- errors.Wrap(iface.ErrConnectionIssue, err.Error()) 263 return 264 } 265 266 for { 267 if ctx.Err() == context.Canceled { 268 log.WithError(ctx.Err()).Error("Context canceled - shutting down blocks receiver") 269 return 270 } 271 res, err := stream.Recv() 272 if err != nil { 273 log.WithError(err).Error("Could not receive blocks from beacon node, " + iface.ErrConnectionIssue.Error()) 274 connectionErrorChannel <- errors.Wrap(iface.ErrConnectionIssue, err.Error()) 275 return 276 } 277 if res == nil || res.Block == nil { 278 continue 279 } 280 if res.Block.Slot > v.highestValidSlot { 281 v.highestValidSlot = res.Block.Slot 282 } 283 284 v.blockFeed.Send(res) 285 } 286 } 287 288 func (v *validator) checkAndLogValidatorStatus(statuses []*validatorStatus) bool { 289 nonexistentIndex := types.ValidatorIndex(^uint64(0)) 290 var validatorActivated bool 291 for _, status := range statuses { 292 fields := logrus.Fields{ 293 "pubKey": fmt.Sprintf("%#x", bytesutil.Trunc(status.publicKey)), 294 "status": status.status.Status.String(), 295 } 296 if status.index != nonexistentIndex { 297 fields["index"] = status.index 298 } 299 log := log.WithFields(fields) 300 if v.emitAccountMetrics { 301 fmtKey := fmt.Sprintf("%#x", status.publicKey) 302 ValidatorStatusesGaugeVec.WithLabelValues(fmtKey).Set(float64(status.status.Status)) 303 } 304 switch status.status.Status { 305 case ethpb.ValidatorStatus_UNKNOWN_STATUS: 306 log.Info("Waiting for deposit to be observed by beacon node") 307 case ethpb.ValidatorStatus_DEPOSITED: 308 if status.status.PositionInActivationQueue != 0 { 309 log.WithField( 310 "positionInActivationQueue", status.status.PositionInActivationQueue, 311 ).Info("Deposit processed, entering activation queue after finalization") 312 } 313 case ethpb.ValidatorStatus_PENDING: 314 if status.status.ActivationEpoch == params.BeaconConfig().FarFutureEpoch { 315 log.WithFields(logrus.Fields{ 316 "positionInActivationQueue": status.status.PositionInActivationQueue, 317 }).Info("Waiting to be assigned activation epoch") 318 } else { 319 log.WithFields(logrus.Fields{ 320 "activationEpoch": status.status.ActivationEpoch, 321 }).Info("Waiting for activation") 322 } 323 case ethpb.ValidatorStatus_ACTIVE, ethpb.ValidatorStatus_EXITING: 324 validatorActivated = true 325 case ethpb.ValidatorStatus_EXITED: 326 log.Info("Validator exited") 327 case ethpb.ValidatorStatus_INVALID: 328 log.Warn("Invalid Eth1 deposit") 329 default: 330 log.WithFields(logrus.Fields{ 331 "activationEpoch": status.status.ActivationEpoch, 332 }).Info("Validator status") 333 } 334 } 335 return validatorActivated 336 } 337 338 func logActiveValidatorStatus(statuses []*validatorStatus) { 339 for _, s := range statuses { 340 if s.status.Status != ethpb.ValidatorStatus_ACTIVE { 341 continue 342 } 343 log.WithFields(logrus.Fields{ 344 "publicKey": fmt.Sprintf("%#x", bytesutil.Trunc(s.publicKey)), 345 "index": s.index, 346 }).Info("Validator activated") 347 } 348 } 349 350 // CanonicalHeadSlot returns the slot of canonical block currently found in the 351 // beacon chain via RPC. 352 func (v *validator) CanonicalHeadSlot(ctx context.Context) (types.Slot, error) { 353 ctx, span := trace.StartSpan(ctx, "validator.CanonicalHeadSlot") 354 defer span.End() 355 head, err := v.beaconClient.GetChainHead(ctx, &emptypb.Empty{}) 356 if err != nil { 357 return 0, errors.Wrap(iface.ErrConnectionIssue, err.Error()) 358 } 359 return head.HeadSlot, nil 360 } 361 362 // NextSlot emits the next slot number at the start time of that slot. 363 func (v *validator) NextSlot() <-chan types.Slot { 364 return v.ticker.C() 365 } 366 367 // SlotDeadline is the start time of the next slot. 368 func (v *validator) SlotDeadline(slot types.Slot) time.Time { 369 secs := time.Duration((slot + 1).Mul(params.BeaconConfig().SecondsPerSlot)) 370 return time.Unix(int64(v.genesisTime), 0 /*ns*/).Add(secs * time.Second) 371 } 372 373 // CheckDoppelGanger checks if the current actively provided keys have 374 // any duplicates active in the network. 375 func (v *validator) CheckDoppelGanger(ctx context.Context) error { 376 if !featureconfig.Get().EnableDoppelGanger { 377 return nil 378 } 379 pubkeys, err := v.keyManager.FetchValidatingPublicKeys(ctx) 380 if err != nil { 381 return err 382 } 383 log.WithField("keys", len(pubkeys)).Info("Running doppelganger check") 384 // Exit early if no validating pub keys are found. 385 if len(pubkeys) == 0 { 386 return nil 387 } 388 req := ðpb.DoppelGangerRequest{ValidatorRequests: []*ethpb.DoppelGangerRequest_ValidatorRequest{}} 389 for _, pkey := range pubkeys { 390 attRec, err := v.db.AttestationHistoryForPubKey(ctx, pkey) 391 if err != nil { 392 return err 393 } 394 if len(attRec) == 0 { 395 // If no history exists we simply send in a zero 396 // value for the request epoch and root. 397 req.ValidatorRequests = append(req.ValidatorRequests, 398 ðpb.DoppelGangerRequest_ValidatorRequest{ 399 PublicKey: pkey[:], 400 Epoch: 0, 401 SignedRoot: make([]byte, 32), 402 }) 403 continue 404 } 405 r := retrieveLatestRecord(attRec) 406 if pkey != r.PubKey { 407 return errors.New("attestation record mismatched public key") 408 } 409 req.ValidatorRequests = append(req.ValidatorRequests, 410 ðpb.DoppelGangerRequest_ValidatorRequest{ 411 PublicKey: r.PubKey[:], 412 Epoch: r.Target, 413 SignedRoot: r.SigningRoot[:], 414 }) 415 } 416 resp, err := v.validatorClient.CheckDoppelGanger(ctx, req) 417 if err != nil { 418 return err 419 } 420 // If nothing is returned by the beacon node, we return an 421 // error as it is unsafe for us to proceed. 422 if resp == nil || resp.Responses == nil || len(resp.Responses) == 0 { 423 return errors.New("beacon node returned 0 responses for doppelganger check") 424 } 425 return buildDuplicateError(resp.Responses) 426 } 427 428 func buildDuplicateError(respones []*ethpb.DoppelGangerResponse_ValidatorResponse) error { 429 duplicates := make([][]byte, 0) 430 for _, valRes := range respones { 431 if valRes.DuplicateExists { 432 duplicates = append(duplicates, valRes.PublicKey) 433 } 434 } 435 if len(duplicates) == 0 { 436 return nil 437 } 438 return errors.Errorf("Duplicate instances exists in the network for validator keys: %#x", duplicates) 439 } 440 441 // Ensures that the latest attestion history is retrieved. 442 func retrieveLatestRecord(recs []*kv.AttestationRecord) *kv.AttestationRecord { 443 if len(recs) == 0 { 444 return nil 445 } 446 lastSource := recs[len(recs)-1].Source 447 chosenRec := recs[len(recs)-1] 448 for i := len(recs) - 1; i >= 0; i-- { 449 // Exit if we are now on a different source 450 // as it is assumed that all source records are 451 // byte sorted. 452 if recs[i].Source != lastSource { 453 break 454 } 455 // If we have a smaller target, we do 456 // change our chosen record. 457 if chosenRec.Target < recs[i].Target { 458 chosenRec = recs[i] 459 } 460 } 461 return chosenRec 462 } 463 464 // UpdateDuties checks the slot number to determine if the validator's 465 // list of upcoming assignments needs to be updated. For example, at the 466 // beginning of a new epoch. 467 func (v *validator) UpdateDuties(ctx context.Context, slot types.Slot) error { 468 if slot%params.BeaconConfig().SlotsPerEpoch != 0 && v.duties != nil { 469 // Do nothing if not epoch start AND assignments already exist. 470 return nil 471 } 472 // Set deadline to end of epoch. 473 ss, err := helpers.StartSlot(helpers.SlotToEpoch(slot) + 1) 474 if err != nil { 475 return err 476 } 477 ctx, cancel := context.WithDeadline(ctx, v.SlotDeadline(ss)) 478 defer cancel() 479 ctx, span := trace.StartSpan(ctx, "validator.UpdateAssignments") 480 defer span.End() 481 482 validatingKeys, err := v.keyManager.FetchValidatingPublicKeys(ctx) 483 if err != nil { 484 return err 485 } 486 487 // Filter out the slashable public keys from the duties request. 488 filteredKeys := make([][48]byte, 0, len(validatingKeys)) 489 v.slashableKeysLock.RLock() 490 for _, pubKey := range validatingKeys { 491 if ok := v.eipImportBlacklistedPublicKeys[pubKey]; !ok { 492 filteredKeys = append(filteredKeys, pubKey) 493 } else { 494 log.WithField( 495 "publicKey", fmt.Sprintf("%#x", bytesutil.Trunc(pubKey[:])), 496 ).Warn("Not including slashable public key from slashing protection import " + 497 "in request to update validator duties") 498 } 499 } 500 v.slashableKeysLock.RUnlock() 501 502 req := ðpb.DutiesRequest{ 503 Epoch: types.Epoch(slot / params.BeaconConfig().SlotsPerEpoch), 504 PublicKeys: bytesutil.FromBytes48Array(filteredKeys), 505 } 506 507 // If duties is nil it means we have had no prior duties and just started up. 508 resp, err := v.validatorClient.GetDuties(ctx, req) 509 if err != nil { 510 v.duties = nil // Clear assignments so we know to retry the request. 511 log.Error(err) 512 return err 513 } 514 515 v.duties = resp 516 v.logDuties(slot, v.duties.CurrentEpochDuties) 517 518 // Non-blocking call for beacon node to start subscriptions for aggregators. 519 go func() { 520 if err := v.subscribeToSubnets(context.Background(), resp); err != nil { 521 log.WithError(err).Error("Failed to subscribe to subnets") 522 } 523 }() 524 525 return nil 526 } 527 528 // subscribeToSubnets iterates through each validator duty, signs each slot, and asks beacon node 529 // to eagerly subscribe to subnets so that the aggregator has attestations to aggregate. 530 func (v *validator) subscribeToSubnets(ctx context.Context, res *ethpb.DutiesResponse) error { 531 subscribeSlots := make([]types.Slot, 0, len(res.CurrentEpochDuties)+len(res.NextEpochDuties)) 532 subscribeCommitteeIndices := make([]types.CommitteeIndex, 0, len(res.CurrentEpochDuties)+len(res.NextEpochDuties)) 533 subscribeIsAggregator := make([]bool, 0, len(res.CurrentEpochDuties)+len(res.NextEpochDuties)) 534 alreadySubscribed := make(map[[64]byte]bool) 535 536 for _, duty := range res.CurrentEpochDuties { 537 pk := bytesutil.ToBytes48(duty.PublicKey) 538 if duty.Status == ethpb.ValidatorStatus_ACTIVE || duty.Status == ethpb.ValidatorStatus_EXITING { 539 attesterSlot := duty.AttesterSlot 540 committeeIndex := duty.CommitteeIndex 541 542 alreadySubscribedKey := validatorSubscribeKey(attesterSlot, committeeIndex) 543 if _, ok := alreadySubscribed[alreadySubscribedKey]; ok { 544 continue 545 } 546 547 aggregator, err := v.isAggregator(ctx, duty.Committee, attesterSlot, pk) 548 if err != nil { 549 return errors.Wrap(err, "could not check if a validator is an aggregator") 550 } 551 if aggregator { 552 alreadySubscribed[alreadySubscribedKey] = true 553 } 554 555 subscribeSlots = append(subscribeSlots, attesterSlot) 556 subscribeCommitteeIndices = append(subscribeCommitteeIndices, committeeIndex) 557 subscribeIsAggregator = append(subscribeIsAggregator, aggregator) 558 } 559 } 560 561 for _, duty := range res.NextEpochDuties { 562 if duty.Status == ethpb.ValidatorStatus_ACTIVE || duty.Status == ethpb.ValidatorStatus_EXITING { 563 attesterSlot := duty.AttesterSlot 564 committeeIndex := duty.CommitteeIndex 565 566 alreadySubscribedKey := validatorSubscribeKey(attesterSlot, committeeIndex) 567 if _, ok := alreadySubscribed[alreadySubscribedKey]; ok { 568 continue 569 } 570 571 aggregator, err := v.isAggregator(ctx, duty.Committee, attesterSlot, bytesutil.ToBytes48(duty.PublicKey)) 572 if err != nil { 573 return errors.Wrap(err, "could not check if a validator is an aggregator") 574 } 575 if aggregator { 576 alreadySubscribed[alreadySubscribedKey] = true 577 } 578 579 subscribeSlots = append(subscribeSlots, attesterSlot) 580 subscribeCommitteeIndices = append(subscribeCommitteeIndices, committeeIndex) 581 subscribeIsAggregator = append(subscribeIsAggregator, aggregator) 582 } 583 } 584 585 _, err := v.validatorClient.SubscribeCommitteeSubnets(ctx, ðpb.CommitteeSubnetsSubscribeRequest{ 586 Slots: subscribeSlots, 587 CommitteeIds: subscribeCommitteeIndices, 588 IsAggregator: subscribeIsAggregator, 589 }) 590 591 return err 592 } 593 594 // RolesAt slot returns the validator roles at the given slot. Returns nil if the 595 // validator is known to not have a roles at the slot. Returns UNKNOWN if the 596 // validator assignments are unknown. Otherwise returns a valid ValidatorRole map. 597 func (v *validator) RolesAt(ctx context.Context, slot types.Slot) (map[[48]byte][]iface.ValidatorRole, error) { 598 rolesAt := make(map[[48]byte][]iface.ValidatorRole) 599 for _, duty := range v.duties.Duties { 600 var roles []iface.ValidatorRole 601 602 if duty == nil { 603 continue 604 } 605 if len(duty.ProposerSlots) > 0 { 606 for _, proposerSlot := range duty.ProposerSlots { 607 if proposerSlot != 0 && proposerSlot == slot { 608 roles = append(roles, iface.RoleProposer) 609 break 610 } 611 } 612 } 613 if duty.AttesterSlot == slot { 614 roles = append(roles, iface.RoleAttester) 615 616 aggregator, err := v.isAggregator(ctx, duty.Committee, slot, bytesutil.ToBytes48(duty.PublicKey)) 617 if err != nil { 618 return nil, errors.Wrap(err, "could not check if a validator is an aggregator") 619 } 620 if aggregator { 621 roles = append(roles, iface.RoleAggregator) 622 } 623 624 } 625 if len(roles) == 0 { 626 roles = append(roles, iface.RoleUnknown) 627 } 628 629 var pubKey [48]byte 630 copy(pubKey[:], duty.PublicKey) 631 rolesAt[pubKey] = roles 632 } 633 return rolesAt, nil 634 } 635 636 // GetKeymanager returns the underlying validator's keymanager. 637 func (v *validator) GetKeymanager() keymanager.IKeymanager { 638 return v.keyManager 639 } 640 641 // isAggregator checks if a validator is an aggregator of a given slot and committee, 642 // it uses a modulo calculated by validator count in committee and samples randomness around it. 643 func (v *validator) isAggregator(ctx context.Context, committee []types.ValidatorIndex, slot types.Slot, pubKey [48]byte) (bool, error) { 644 modulo := uint64(1) 645 if len(committee)/int(params.BeaconConfig().TargetAggregatorsPerCommittee) > 1 { 646 modulo = uint64(len(committee)) / params.BeaconConfig().TargetAggregatorsPerCommittee 647 } 648 649 slotSig, err := v.signSlotWithSelectionProof(ctx, pubKey, slot) 650 if err != nil { 651 return false, err 652 } 653 654 b := hashutil.Hash(slotSig) 655 656 return binary.LittleEndian.Uint64(b[:8])%modulo == 0, nil 657 } 658 659 // UpdateDomainDataCaches by making calls for all of the possible domain data. These can change when 660 // the fork version changes which can happen once per epoch. Although changing for the fork version 661 // is very rare, a validator should check these data every epoch to be sure the validator is 662 // participating on the correct fork version. 663 func (v *validator) UpdateDomainDataCaches(ctx context.Context, slot types.Slot) { 664 for _, d := range [][]byte{ 665 params.BeaconConfig().DomainRandao[:], 666 params.BeaconConfig().DomainBeaconAttester[:], 667 params.BeaconConfig().DomainBeaconProposer[:], 668 params.BeaconConfig().DomainSelectionProof[:], 669 params.BeaconConfig().DomainAggregateAndProof[:], 670 } { 671 _, err := v.domainData(ctx, helpers.SlotToEpoch(slot), d) 672 if err != nil { 673 log.WithError(err).Errorf("Failed to update domain data for domain %v", d) 674 } 675 } 676 } 677 678 // AllValidatorsAreExited informs whether all validators have already exited. 679 func (v *validator) AllValidatorsAreExited(ctx context.Context) (bool, error) { 680 validatingKeys, err := v.keyManager.FetchValidatingPublicKeys(ctx) 681 if err != nil { 682 return false, errors.Wrap(err, "could not fetch validating keys") 683 } 684 if len(validatingKeys) == 0 { 685 return false, nil 686 } 687 var publicKeys [][]byte 688 for _, key := range validatingKeys { 689 copyKey := key 690 publicKeys = append(publicKeys, copyKey[:]) 691 } 692 request := ðpb.MultipleValidatorStatusRequest{ 693 PublicKeys: publicKeys, 694 } 695 response, err := v.validatorClient.MultipleValidatorStatus(ctx, request) 696 if err != nil { 697 return false, err 698 } 699 if len(response.Statuses) != len(request.PublicKeys) { 700 return false, errors.New("number of status responses did not match number of requested keys") 701 } 702 for _, status := range response.Statuses { 703 if status.Status != ethpb.ValidatorStatus_EXITED { 704 return false, nil 705 } 706 } 707 return true, nil 708 } 709 710 func (v *validator) domainData(ctx context.Context, epoch types.Epoch, domain []byte) (*ethpb.DomainResponse, error) { 711 v.domainDataLock.Lock() 712 defer v.domainDataLock.Unlock() 713 714 req := ðpb.DomainRequest{ 715 Epoch: epoch, 716 Domain: domain, 717 } 718 719 key := strings.Join([]string{strconv.FormatUint(uint64(req.Epoch), 10), hex.EncodeToString(req.Domain)}, ",") 720 721 if val, ok := v.domainDataCache.Get(key); ok { 722 return proto.Clone(val.(proto.Message)).(*ethpb.DomainResponse), nil 723 } 724 725 res, err := v.validatorClient.DomainData(ctx, req) 726 if err != nil { 727 return nil, err 728 } 729 730 v.domainDataCache.Set(key, proto.Clone(res), 1) 731 732 return res, nil 733 } 734 735 func (v *validator) logDuties(slot types.Slot, duties []*ethpb.DutiesResponse_Duty) { 736 attesterKeys := make([][]string, params.BeaconConfig().SlotsPerEpoch) 737 for i := range attesterKeys { 738 attesterKeys[i] = make([]string, 0) 739 } 740 proposerKeys := make([]string, params.BeaconConfig().SlotsPerEpoch) 741 slotOffset := slot - (slot % params.BeaconConfig().SlotsPerEpoch) 742 var totalAttestingKeys uint64 743 for _, duty := range duties { 744 validatorNotTruncatedKey := fmt.Sprintf("%#x", duty.PublicKey) 745 if v.emitAccountMetrics { 746 ValidatorStatusesGaugeVec.WithLabelValues(validatorNotTruncatedKey).Set(float64(duty.Status)) 747 } 748 749 // Only interested in validators who are attesting/proposing. 750 // Note that SLASHING validators will have duties but their results are ignored by the network so we don't bother with them. 751 if duty.Status != ethpb.ValidatorStatus_ACTIVE && duty.Status != ethpb.ValidatorStatus_EXITING { 752 continue 753 } 754 755 validatorKey := fmt.Sprintf("%#x", bytesutil.Trunc(duty.PublicKey)) 756 attesterIndex := duty.AttesterSlot - slotOffset 757 if attesterIndex >= params.BeaconConfig().SlotsPerEpoch { 758 log.WithField("duty", duty).Warn("Invalid attester slot") 759 } else { 760 attesterKeys[duty.AttesterSlot-slotOffset] = append(attesterKeys[duty.AttesterSlot-slotOffset], validatorKey) 761 totalAttestingKeys++ 762 if v.emitAccountMetrics { 763 ValidatorNextAttestationSlotGaugeVec.WithLabelValues(validatorNotTruncatedKey).Set(float64(duty.AttesterSlot)) 764 } 765 } 766 767 for _, proposerSlot := range duty.ProposerSlots { 768 proposerIndex := proposerSlot - slotOffset 769 if proposerIndex >= params.BeaconConfig().SlotsPerEpoch { 770 log.WithField("duty", duty).Warn("Invalid proposer slot") 771 } else { 772 proposerKeys[proposerIndex] = validatorKey 773 } 774 if v.emitAccountMetrics { 775 ValidatorNextProposalSlotGaugeVec.WithLabelValues(validatorNotTruncatedKey).Set(float64(proposerSlot)) 776 } 777 } 778 } 779 for i := types.Slot(0); i < params.BeaconConfig().SlotsPerEpoch; i++ { 780 if len(attesterKeys[i]) > 0 { 781 log.WithFields(logrus.Fields{ 782 "slot": slotOffset + i, 783 "slotInEpoch": (slotOffset + i) % params.BeaconConfig().SlotsPerEpoch, 784 "attesterDutiesAtSlot": len(attesterKeys[i]), 785 "totalAttestersInEpoch": totalAttestingKeys, 786 "pubKeys": attesterKeys[i], 787 }).Info("Attestation schedule") 788 } 789 if proposerKeys[i] != "" { 790 log.WithField("slot", slotOffset+i).WithField("pubKey", proposerKeys[i]).Info("Proposal schedule") 791 } 792 } 793 } 794 795 // This constructs a validator subscribed key, it's used to track 796 // which subnet has already been pending requested. 797 func validatorSubscribeKey(slot types.Slot, committeeID types.CommitteeIndex) [64]byte { 798 return bytesutil.ToBytes64(append(bytesutil.Bytes32(uint64(slot)), bytesutil.Bytes32(uint64(committeeID))...)) 799 } 800 801 // This tracks all validators' voting status. 802 type voteStats struct { 803 startEpoch types.Epoch 804 includedAttestedCount uint64 805 totalAttestedCount uint64 806 totalDistance types.Slot 807 correctSources uint64 808 totalSources uint64 809 correctTargets uint64 810 totalTargets uint64 811 correctHeads uint64 812 totalHeads uint64 813 }