github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/rpc/prysm/v1alpha1/beacon/validators_stream.go (about) 1 package beacon 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "math/big" 9 "sort" 10 "sync" 11 "time" 12 13 "github.com/patrickmn/go-cache" 14 "github.com/prometheus/client_golang/prometheus" 15 "github.com/prometheus/client_golang/prometheus/promauto" 16 types "github.com/prysmaticlabs/eth2-types" 17 "github.com/prysmaticlabs/prysm/beacon-chain/blockchain" 18 "github.com/prysmaticlabs/prysm/beacon-chain/cache/depositcache" 19 "github.com/prysmaticlabs/prysm/beacon-chain/core/feed" 20 statefeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/state" 21 "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" 22 "github.com/prysmaticlabs/prysm/beacon-chain/db" 23 "github.com/prysmaticlabs/prysm/beacon-chain/powchain" 24 iface "github.com/prysmaticlabs/prysm/beacon-chain/state/interface" 25 ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" 26 "github.com/prysmaticlabs/prysm/shared/bytesutil" 27 "github.com/prysmaticlabs/prysm/shared/event" 28 "github.com/prysmaticlabs/prysm/shared/params" 29 "google.golang.org/grpc/codes" 30 "google.golang.org/grpc/status" 31 ) 32 33 // infostream is a struct for each instance of the infostream created by a client connection. 34 type infostream struct { 35 ctx context.Context 36 headFetcher blockchain.HeadFetcher 37 depositFetcher depositcache.DepositFetcher 38 blockFetcher powchain.POWBlockFetcher 39 beaconDB db.ReadOnlyDatabase 40 pubKeys [][]byte 41 pubKeysMutex *sync.RWMutex 42 stateChannel chan *feed.Event 43 stateSub event.Subscription 44 eth1Deposits *cache.Cache 45 eth1DepositsMutex *sync.RWMutex 46 eth1Blocktimes *cache.Cache 47 eth1BlocktimesMutex *sync.RWMutex 48 currentEpoch types.Epoch 49 stream ethpb.BeaconChain_StreamValidatorsInfoServer 50 genesisTime uint64 51 } 52 53 // eth1Deposit contains information about a deposit made on the Ethereum 1 chain. 54 type eth1Deposit struct { 55 block *big.Int 56 data *ethpb.Deposit_Data 57 } 58 59 var ( 60 eth1DepositCacheHits = promauto.NewCounter( 61 prometheus.CounterOpts{ 62 Name: "infostream_eth1_deposit_cache_hits", 63 Help: "The number of times the infostream Ethereum 1 deposit cache is hit.", 64 }, 65 ) 66 eth1DepositCacheMisses = promauto.NewCounter( 67 prometheus.CounterOpts{ 68 Name: "infostream_eth1_deposit_cache_misses", 69 Help: "The number of times the infostream Ethereum 1 deposit cache is missed.", 70 }, 71 ) 72 eth1BlocktimeCacheHits = promauto.NewCounter( 73 prometheus.CounterOpts{ 74 Name: "infostream_eth1_blocktime_cache_hits", 75 Help: "The number of times the infostream Ethereum 1 block time cache is hit.", 76 }, 77 ) 78 eth1BlocktimeCacheMisses = promauto.NewCounter( 79 prometheus.CounterOpts{ 80 Name: "infostream_eth1_blocktime_cache_misses", 81 Help: "The number of times the infostream Ethereum 1 block time cache is missed.", 82 }, 83 ) 84 ) 85 86 // StreamValidatorsInfo returns a stream of information for given validators. 87 // Validators are supplied dynamically by the client, and can be added, removed and reset at any time. 88 // Information about the current set of validators is supplied as soon as the end-of-epoch accounting has been processed, 89 // providing a near real-time view of the state of the validators. 90 // Note that this will stream information whilst syncing; this is intended, to allow for complete validator state capture 91 // over time. If this is not required then the client can either wait until the beacon node is synced, or filter results 92 // based on the epoch value in the returned validator info. 93 func (bs *Server) StreamValidatorsInfo(stream ethpb.BeaconChain_StreamValidatorsInfoServer) error { 94 stateChannel := make(chan *feed.Event, params.BeaconConfig().SlotsPerEpoch) 95 epochDuration := time.Duration(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().SecondsPerSlot)) * time.Second 96 97 // Fetch our current epoch. 98 headState, err := bs.HeadFetcher.HeadState(bs.Ctx) 99 if err != nil { 100 return status.Error(codes.Internal, "Could not access head state") 101 } 102 if headState == nil || headState.IsNil() { 103 return status.Error(codes.Internal, "Not ready to serve information") 104 } 105 106 // Create an infostream struct. This will track relevant state for the stream. 107 infostream := &infostream{ 108 ctx: bs.Ctx, 109 headFetcher: bs.HeadFetcher, 110 depositFetcher: bs.DepositFetcher, 111 blockFetcher: bs.BlockFetcher, 112 beaconDB: bs.BeaconDB, 113 pubKeys: make([][]byte, 0), 114 pubKeysMutex: &sync.RWMutex{}, 115 stateChannel: stateChannel, 116 stateSub: bs.StateNotifier.StateFeed().Subscribe(stateChannel), 117 eth1Deposits: cache.New(epochDuration, epochDuration*2), 118 eth1DepositsMutex: &sync.RWMutex{}, 119 eth1Blocktimes: cache.New(epochDuration*12, epochDuration*24), 120 eth1BlocktimesMutex: &sync.RWMutex{}, 121 currentEpoch: types.Epoch(headState.Slot() / params.BeaconConfig().SlotsPerEpoch), 122 stream: stream, 123 genesisTime: headState.GenesisTime(), 124 } 125 defer infostream.stateSub.Unsubscribe() 126 127 return infostream.handleConnection() 128 } 129 130 // handleConnection handles the two-way connection between client and server. 131 func (is *infostream) handleConnection() error { 132 // Handle messages from client. 133 go func() { 134 for { 135 msg, err := is.stream.Recv() 136 if errors.Is(err, io.EOF) { 137 return 138 } 139 if err != nil { 140 // Errors handle elsewhere 141 select { 142 case <-is.stream.Context().Done(): 143 return 144 case <-is.ctx.Done(): 145 return 146 case <-is.stateSub.Err(): 147 return 148 default: 149 } 150 log.WithError(err).Debug("Receive from validators stream listener failed; client probably closed connection") 151 return 152 } 153 is.handleMessage(msg) 154 } 155 }() 156 // Send responses at the end of every epoch. 157 for { 158 select { 159 case stateEvent := <-is.stateChannel: 160 if stateEvent.Type == statefeed.BlockProcessed { 161 is.handleBlockProcessed() 162 } 163 case <-is.stateSub.Err(): 164 return status.Error(codes.Aborted, "Subscriber closed") 165 case <-is.ctx.Done(): 166 return status.Error(codes.Canceled, "Service context canceled") 167 case <-is.stream.Context().Done(): 168 return status.Error(codes.Canceled, "Stream context canceled") 169 } 170 } 171 } 172 173 // handleMessage handles a message from the infostream client, updating the list of keys. 174 func (is *infostream) handleMessage(msg *ethpb.ValidatorChangeSet) { 175 var err error 176 switch msg.Action { 177 case ethpb.SetAction_ADD_VALIDATOR_KEYS: 178 err = is.handleAddValidatorKeys(msg.PublicKeys) 179 case ethpb.SetAction_REMOVE_VALIDATOR_KEYS: 180 is.handleRemoveValidatorKeys(msg.PublicKeys) 181 case ethpb.SetAction_SET_VALIDATOR_KEYS: 182 err = is.handleSetValidatorKeys(msg.PublicKeys) 183 } 184 if err != nil { 185 log.WithError(err).Debug("Error handling request; closing stream") 186 is.stream.Context().Done() 187 } 188 } 189 190 // handleAddValidatorKeys handles a request to add validator keys. 191 func (is *infostream) handleAddValidatorKeys(reqPubKeys [][]byte) error { 192 is.pubKeysMutex.Lock() 193 // Create existence map to ensure we don't duplicate keys. 194 pubKeysMap := make(map[[48]byte]bool, len(is.pubKeys)) 195 for _, pubKey := range is.pubKeys { 196 pubKeysMap[bytesutil.ToBytes48(pubKey)] = true 197 } 198 addedPubKeys := make([][]byte, 0, len(reqPubKeys)) 199 for _, pubKey := range reqPubKeys { 200 if _, exists := pubKeysMap[bytesutil.ToBytes48(pubKey)]; !exists { 201 is.pubKeys = append(is.pubKeys, pubKey) 202 addedPubKeys = append(addedPubKeys, pubKey) 203 } 204 } 205 is.pubKeysMutex.Unlock() 206 // Send immediate info for the new validators. 207 return is.sendValidatorsInfo(addedPubKeys) 208 } 209 210 // handleSetValidatorKeys handles a request to set validator keys. 211 func (is *infostream) handleSetValidatorKeys(reqPubKeys [][]byte) error { 212 is.pubKeysMutex.Lock() 213 is.pubKeys = make([][]byte, 0, len(reqPubKeys)) 214 is.pubKeys = append(is.pubKeys, reqPubKeys...) 215 is.pubKeysMutex.Unlock() 216 // Send immediate info for the new validators. 217 return is.sendValidatorsInfo(is.pubKeys) 218 } 219 220 // handleRemoveValidatorKeys handles a request to remove validator keys. 221 func (is *infostream) handleRemoveValidatorKeys(reqPubKeys [][]byte) { 222 is.pubKeysMutex.Lock() 223 // Create existence map to track what we have to delete. 224 pubKeysMap := make(map[[48]byte]bool, len(reqPubKeys)) 225 for _, pubKey := range reqPubKeys { 226 pubKeysMap[bytesutil.ToBytes48(pubKey)] = true 227 } 228 max := len(is.pubKeys) 229 for i := 0; i < max; i++ { 230 if _, exists := pubKeysMap[bytesutil.ToBytes48(is.pubKeys[i])]; exists { 231 copy(is.pubKeys[i:], is.pubKeys[i+1:]) 232 is.pubKeys = is.pubKeys[:len(is.pubKeys)-1] 233 i-- 234 max-- 235 } 236 } 237 is.pubKeysMutex.Unlock() 238 } 239 240 // sendValidatorsInfo sends validator info for a specific set of public keys. 241 func (is *infostream) sendValidatorsInfo(pubKeys [][]byte) error { 242 validators, err := is.generateValidatorsInfo(pubKeys) 243 if err != nil { 244 return err 245 } 246 for _, validator := range validators { 247 if err := is.stream.Send(validator); err != nil { 248 return err 249 } 250 } 251 return nil 252 } 253 254 // generateValidatorsInfo generates the validator info for a set of public keys. 255 func (is *infostream) generateValidatorsInfo(pubKeys [][]byte) ([]*ethpb.ValidatorInfo, error) { 256 if is.headFetcher == nil { 257 return nil, status.Error(codes.Internal, "No head fetcher") 258 } 259 headState, err := is.headFetcher.HeadState(is.ctx) 260 if err != nil { 261 return nil, status.Error(codes.Internal, "Could not access head state") 262 } 263 if headState == nil || headState.IsNil() { 264 return nil, status.Error(codes.Internal, "Not ready to serve information") 265 } 266 epoch := types.Epoch(headState.Slot() / params.BeaconConfig().SlotsPerEpoch) 267 if epoch == 0 { 268 // Not reporting, but no error. 269 return nil, nil 270 } 271 // We are reporting on the state at the end of the *previous* epoch. 272 epoch-- 273 274 res := make([]*ethpb.ValidatorInfo, 0, len(pubKeys)) 275 for _, pubKey := range pubKeys { 276 i, e := headState.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubKey)) 277 if !e { 278 return nil, errors.New("could not find public key") 279 } 280 v, err := headState.ValidatorAtIndexReadOnly(i) 281 if err != nil { 282 return nil, status.Errorf(codes.Internal, "Could not retrieve validator: %v", err) 283 284 } 285 info, err := is.generateValidatorInfo(pubKey, v, headState, epoch) 286 if err != nil { 287 return nil, err 288 } 289 res = append(res, info) 290 } 291 292 // Calculate activation time for pending validators (if there are any). 293 if err := is.calculateActivationTimeForPendingValidators(res, headState, epoch); err != nil { 294 return nil, err 295 } 296 297 return res, nil 298 } 299 300 // generateValidatorInfo generates the validator info for a public key. 301 func (is *infostream) generateValidatorInfo( 302 pubKey []byte, 303 validator iface.ReadOnlyValidator, 304 headState iface.ReadOnlyBeaconState, 305 epoch types.Epoch, 306 ) (*ethpb.ValidatorInfo, error) { 307 info := ðpb.ValidatorInfo{ 308 PublicKey: pubKey, 309 Epoch: epoch, 310 Status: ethpb.ValidatorStatus_UNKNOWN_STATUS, 311 } 312 313 // Index 314 var ok bool 315 info.Index, ok = headState.ValidatorIndexByPubkey(bytesutil.ToBytes48(pubKey)) 316 if !ok { 317 // We don't know of this validator; it's either a pending deposit or totally unknown. 318 return is.generatePendingValidatorInfo(info) 319 } 320 // Status and progression timestamp 321 info.Status, info.TransitionTimestamp = is.calculateStatusAndTransition(validator, helpers.CurrentEpoch(headState)) 322 323 // Balance 324 info.Balance = headState.Balances()[info.Index] 325 326 // Effective balance (for attesting states) 327 if info.Status == ethpb.ValidatorStatus_ACTIVE || 328 info.Status == ethpb.ValidatorStatus_SLASHING || 329 info.Status == ethpb.ValidatorStatus_EXITING { 330 info.EffectiveBalance = validator.EffectiveBalance() 331 } 332 333 return info, nil 334 } 335 336 // generatePendingValidatorInfo generates the validator info for a pending (or unknown) key. 337 func (is *infostream) generatePendingValidatorInfo(info *ethpb.ValidatorInfo) (*ethpb.ValidatorInfo, error) { 338 key := string(info.PublicKey) 339 var deposit *eth1Deposit 340 is.eth1DepositsMutex.Lock() 341 if fetchedDeposit, exists := is.eth1Deposits.Get(key); exists { 342 eth1DepositCacheHits.Inc() 343 var ok bool 344 deposit, ok = fetchedDeposit.(*eth1Deposit) 345 if !ok { 346 is.eth1DepositsMutex.Unlock() 347 return nil, errors.New("cached eth1 deposit is not type *eth1Deposit") 348 } 349 } else { 350 eth1DepositCacheMisses.Inc() 351 fetchedDeposit, eth1BlockNumber := is.depositFetcher.DepositByPubkey(is.ctx, info.PublicKey) 352 if fetchedDeposit == nil { 353 deposit = ð1Deposit{} 354 is.eth1Deposits.Set(key, deposit, cache.DefaultExpiration) 355 } else { 356 deposit = ð1Deposit{ 357 block: eth1BlockNumber, 358 data: fetchedDeposit.Data, 359 } 360 is.eth1Deposits.Set(key, deposit, cache.DefaultExpiration) 361 } 362 } 363 is.eth1DepositsMutex.Unlock() 364 if deposit.block != nil { 365 info.Status = ethpb.ValidatorStatus_DEPOSITED 366 if queueTimestamp, err := is.depositQueueTimestamp(deposit.block); err != nil { 367 log.WithError(err).Error("Could not obtain queue activation timestamp") 368 } else { 369 info.TransitionTimestamp = queueTimestamp 370 } 371 info.Balance = deposit.data.Amount 372 } 373 return info, nil 374 } 375 376 func (is *infostream) calculateActivationTimeForPendingValidators(res []*ethpb.ValidatorInfo, headState iface.ReadOnlyBeaconState, epoch types.Epoch) error { 377 // pendingValidatorsMap is map from the validator pubkey to the index in our return array 378 pendingValidatorsMap := make(map[[48]byte]int) 379 for i, info := range res { 380 if info.Status == ethpb.ValidatorStatus_PENDING { 381 pendingValidatorsMap[bytesutil.ToBytes48(info.PublicKey)] = i 382 } 383 } 384 if len(pendingValidatorsMap) == 0 { 385 // Nothing to do. 386 return nil 387 } 388 389 // Fetch the list of pending validators; count the number of attesting validators. 390 numAttestingValidators := uint64(0) 391 pendingValidators := make([]types.ValidatorIndex, 0, headState.NumValidators()) 392 393 err := headState.ReadFromEveryValidator(func(idx int, val iface.ReadOnlyValidator) error { 394 if val.IsNil() { 395 return errors.New("nil validator in state") 396 } 397 if helpers.IsEligibleForActivationUsingTrie(headState, val) { 398 pubKey := val.PublicKey() 399 validatorIndex, ok := headState.ValidatorIndexByPubkey(pubKey) 400 if ok { 401 pendingValidators = append(pendingValidators, validatorIndex) 402 } 403 } 404 if helpers.IsActiveValidatorUsingTrie(val, epoch) { 405 numAttestingValidators++ 406 } 407 return nil 408 }) 409 if err != nil { 410 return err 411 } 412 413 sortableIndices := &indicesSorter{ 414 indices: pendingValidators, 415 } 416 sort.Sort(sortableIndices) 417 418 sortedIndices := sortableIndices.indices 419 420 // Loop over epochs, roughly simulating progression. 421 for curEpoch := epoch + 1; len(sortedIndices) > 0 && len(pendingValidators) > 0; curEpoch++ { 422 toProcess, err := helpers.ValidatorChurnLimit(numAttestingValidators) 423 if err != nil { 424 log.WithError(err).Error("Could not determine validator churn limit") 425 } 426 if toProcess > uint64(len(sortedIndices)) { 427 toProcess = uint64(len(sortedIndices)) 428 } 429 for i := uint64(0); i < toProcess; i++ { 430 validator, err := headState.ValidatorAtIndexReadOnly(sortedIndices[i]) 431 if err != nil { 432 return err 433 } 434 if index, exists := pendingValidatorsMap[validator.PublicKey()]; exists { 435 res[index].TransitionTimestamp = is.epochToTimestamp(helpers.ActivationExitEpoch(curEpoch)) 436 delete(pendingValidatorsMap, validator.PublicKey()) 437 } 438 numAttestingValidators++ 439 } 440 sortedIndices = sortedIndices[toProcess:] 441 } 442 443 return nil 444 } 445 446 // handleBlockProcessed handles the situation where a block has been processed by the Prysm server. 447 func (is *infostream) handleBlockProcessed() { 448 headState, err := is.headFetcher.HeadState(is.ctx) 449 if err != nil { 450 log.Warn("Could not access head state for infostream") 451 return 452 } 453 if headState == nil || headState.IsNil() { 454 // We aren't ready to serve information 455 return 456 } 457 blockEpoch := types.Epoch(headState.Slot() / params.BeaconConfig().SlotsPerEpoch) 458 if blockEpoch == is.currentEpoch { 459 // Epoch hasn't changed, nothing to report yet. 460 return 461 } 462 is.currentEpoch = blockEpoch 463 if err := is.sendValidatorsInfo(is.pubKeys); err != nil { 464 // Client probably disconnected. 465 log.WithError(err).Debug("Could not send infostream response") 466 } 467 } 468 469 type indicesSorter struct { 470 indices []types.ValidatorIndex 471 } 472 473 // Len is the number of elements in the collection. 474 func (s indicesSorter) Len() int { return len(s.indices) } 475 476 // Swap swaps the elements with indexes i and j. 477 func (s indicesSorter) Swap(i, j int) { s.indices[i], s.indices[j] = s.indices[j], s.indices[i] } 478 479 // Less reports whether the element with index i must sort before the element with index j. 480 func (s indicesSorter) Less(i, j int) bool { 481 return s.indices[i] < s.indices[j] 482 } 483 484 func (is *infostream) calculateStatusAndTransition(validator iface.ReadOnlyValidator, currentEpoch types.Epoch) (ethpb.ValidatorStatus, uint64) { 485 farFutureEpoch := params.BeaconConfig().FarFutureEpoch 486 487 if validator.IsNil() { 488 return ethpb.ValidatorStatus_UNKNOWN_STATUS, 0 489 } 490 491 if currentEpoch < validator.ActivationEligibilityEpoch() { 492 if helpers.IsEligibleForActivationQueueUsingTrie(validator) { 493 return ethpb.ValidatorStatus_DEPOSITED, is.epochToTimestamp(validator.ActivationEligibilityEpoch()) 494 } 495 return ethpb.ValidatorStatus_DEPOSITED, 0 496 } 497 if currentEpoch < validator.ActivationEpoch() { 498 return ethpb.ValidatorStatus_PENDING, is.epochToTimestamp(validator.ActivationEpoch()) 499 } 500 if validator.ExitEpoch() == farFutureEpoch { 501 return ethpb.ValidatorStatus_ACTIVE, 0 502 } 503 if currentEpoch < validator.ExitEpoch() { 504 if validator.Slashed() { 505 return ethpb.ValidatorStatus_SLASHING, is.epochToTimestamp(validator.ExitEpoch()) 506 } 507 return ethpb.ValidatorStatus_EXITING, is.epochToTimestamp(validator.ExitEpoch()) 508 } 509 return ethpb.ValidatorStatus_EXITED, is.epochToTimestamp(validator.WithdrawableEpoch()) 510 } 511 512 // epochToTimestamp converts an epoch number to a timestamp. 513 func (is *infostream) epochToTimestamp(epoch types.Epoch) uint64 { 514 return is.genesisTime + params.BeaconConfig().SecondsPerSlot*uint64(params.BeaconConfig().SlotsPerEpoch.Mul(uint64(epoch))) 515 } 516 517 // depositQueueTimestamp calculates the timestamp for exit of the validator from the deposit queue. 518 func (is *infostream) depositQueueTimestamp(eth1BlockNumber *big.Int) (uint64, error) { 519 var blockTimestamp uint64 520 key := fmt.Sprintf("%v", eth1BlockNumber) 521 is.eth1BlocktimesMutex.Lock() 522 if cachedTimestamp, exists := is.eth1Blocktimes.Get(key); exists { 523 eth1BlocktimeCacheHits.Inc() 524 var ok bool 525 blockTimestamp, ok = cachedTimestamp.(uint64) 526 if !ok { 527 is.eth1BlocktimesMutex.Unlock() 528 return 0, errors.New("cached timestamp is not type uint64") 529 } 530 } else { 531 eth1BlocktimeCacheMisses.Inc() 532 var err error 533 blockTimestamp, err = is.blockFetcher.BlockTimeByHeight(is.ctx, eth1BlockNumber) 534 if err != nil { 535 is.eth1BlocktimesMutex.Unlock() 536 return 0, err 537 } 538 is.eth1Blocktimes.Set(key, blockTimestamp, cache.DefaultExpiration) 539 } 540 is.eth1BlocktimesMutex.Unlock() 541 542 followTime := time.Duration(params.BeaconConfig().Eth1FollowDistance*params.BeaconConfig().SecondsPerETH1Block) * time.Second 543 eth1UnixTime := time.Unix(int64(blockTimestamp), 0).Add(followTime) 544 545 period := uint64(params.BeaconConfig().EpochsPerEth1VotingPeriod.Mul(uint64(params.BeaconConfig().SlotsPerEpoch))) 546 votingPeriod := time.Duration(period*params.BeaconConfig().SecondsPerSlot) * time.Second 547 activationTime := eth1UnixTime.Add(votingPeriod) 548 eth2Genesis := time.Unix(int64(is.genesisTime), 0) 549 550 if eth2Genesis.After(activationTime) { 551 return is.genesisTime, nil 552 } 553 return uint64(activationTime.Unix()), nil 554 }