github.com/prysmaticlabs/prysm@v1.4.4/validator/client/wait_for_activation.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"time"
     7  
     8  	"github.com/pkg/errors"
     9  	ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    10  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    11  	"github.com/prysmaticlabs/prysm/shared/mathutil"
    12  	"github.com/prysmaticlabs/prysm/shared/params"
    13  	"github.com/prysmaticlabs/prysm/shared/slotutil"
    14  	"github.com/prysmaticlabs/prysm/shared/traceutil"
    15  	"github.com/prysmaticlabs/prysm/validator/keymanager/remote"
    16  	"go.opencensus.io/trace"
    17  )
    18  
    19  // WaitForActivation checks whether the validator pubkey is in the active
    20  // validator set. If not, this operation will block until an activation message is
    21  // received. This method also monitors the keymanager for updates while waiting for an activation
    22  // from the gRPC server.
    23  //
    24  // If the channel parameter is nil, WaitForActivation creates and manages its own channel.
    25  func (v *validator) WaitForActivation(ctx context.Context, accountsChangedChan chan [][48]byte) error {
    26  	// Monitor the key manager for updates.
    27  	if accountsChangedChan == nil {
    28  		accountsChangedChan = make(chan [][48]byte, 1)
    29  		sub := v.GetKeymanager().SubscribeAccountChanges(accountsChangedChan)
    30  		defer func() {
    31  			sub.Unsubscribe()
    32  			close(accountsChangedChan)
    33  		}()
    34  	}
    35  
    36  	return v.waitForActivation(ctx, accountsChangedChan)
    37  }
    38  
    39  // waitForActivation performs the following:
    40  // 1) While the key manager is empty, poll the key manager until some validator keys exist.
    41  // 2) Open a server side stream for activation events against the given keys.
    42  // 3) In another go routine, the key manager is monitored for updates and emits an update event on
    43  // the accountsChangedChan. When an event signal is received, restart the waitForActivation routine.
    44  // 4) If the stream is reset in error, restart the routine.
    45  // 5) If the stream returns a response indicating one or more validators are active, exit the routine.
    46  func (v *validator) waitForActivation(ctx context.Context, accountsChangedChan <-chan [][48]byte) error {
    47  	ctx, span := trace.StartSpan(ctx, "validator.WaitForActivation")
    48  	defer span.End()
    49  
    50  	validatingKeys, err := v.keyManager.FetchValidatingPublicKeys(ctx)
    51  	if err != nil {
    52  		return errors.Wrap(err, "could not fetch validating keys")
    53  	}
    54  	if len(validatingKeys) == 0 {
    55  		log.Warn(msgNoKeysFetched)
    56  
    57  		ticker := time.NewTicker(keyRefetchPeriod)
    58  		defer ticker.Stop()
    59  		for {
    60  			select {
    61  			case <-ticker.C:
    62  				validatingKeys, err = v.keyManager.FetchValidatingPublicKeys(ctx)
    63  				if err != nil {
    64  					return errors.Wrap(err, msgCouldNotFetchKeys)
    65  				}
    66  				if len(validatingKeys) == 0 {
    67  					log.Warn(msgNoKeysFetched)
    68  					continue
    69  				}
    70  			case <-ctx.Done():
    71  				log.Debug("Context closed, exiting fetching validating keys")
    72  				return ctx.Err()
    73  			}
    74  			break
    75  		}
    76  	}
    77  
    78  	req := &ethpb.ValidatorActivationRequest{
    79  		PublicKeys: bytesutil.FromBytes48Array(validatingKeys),
    80  	}
    81  	stream, err := v.validatorClient.WaitForActivation(ctx, req)
    82  	if err != nil {
    83  		traceutil.AnnotateError(span, err)
    84  		attempts := streamAttempts(ctx)
    85  		log.WithError(err).WithField("attempts", attempts).
    86  			Error("Stream broken while waiting for activation. Reconnecting...")
    87  		// Reconnection attempt backoff, up to 60s.
    88  		time.Sleep(time.Second * time.Duration(mathutil.Min(uint64(attempts), 60)))
    89  		return v.waitForActivation(incrementRetries(ctx), accountsChangedChan)
    90  	}
    91  
    92  	remoteKm, ok := v.keyManager.(remote.RemoteKeymanager)
    93  	if ok {
    94  		for {
    95  			select {
    96  			case <-accountsChangedChan:
    97  				// Accounts (keys) changed, restart the process.
    98  				return v.waitForActivation(ctx, accountsChangedChan)
    99  			case <-v.NextSlot():
   100  				if ctx.Err() == context.Canceled {
   101  					return errors.Wrap(ctx.Err(), "context canceled, not waiting for activation anymore")
   102  				}
   103  
   104  				validatingKeys, err = remoteKm.ReloadPublicKeys(ctx)
   105  				if err != nil {
   106  					return errors.Wrap(err, msgCouldNotFetchKeys)
   107  				}
   108  				statusRequestKeys := make([][]byte, len(validatingKeys))
   109  				for i := range validatingKeys {
   110  					statusRequestKeys[i] = validatingKeys[i][:]
   111  				}
   112  				resp, err := v.validatorClient.MultipleValidatorStatus(ctx, &ethpb.MultipleValidatorStatusRequest{
   113  					PublicKeys: statusRequestKeys,
   114  				})
   115  				if err != nil {
   116  					return err
   117  				}
   118  				statuses := make([]*validatorStatus, len(resp.Statuses))
   119  				for i, s := range resp.Statuses {
   120  					statuses[i] = &validatorStatus{
   121  						publicKey: resp.PublicKeys[i],
   122  						status:    s,
   123  						index:     resp.Indices[i],
   124  					}
   125  				}
   126  
   127  				valActivated := v.checkAndLogValidatorStatus(statuses)
   128  				if valActivated {
   129  					logActiveValidatorStatus(statuses)
   130  				} else {
   131  					continue
   132  				}
   133  			}
   134  			break
   135  		}
   136  	} else {
   137  		for {
   138  			select {
   139  			case <-accountsChangedChan:
   140  				// Accounts (keys) changed, restart the process.
   141  				return v.waitForActivation(ctx, accountsChangedChan)
   142  			default:
   143  				res, err := stream.Recv()
   144  				// If the stream is closed, we stop the loop.
   145  				if errors.Is(err, io.EOF) {
   146  					break
   147  				}
   148  				// If context is canceled we return from the function.
   149  				if ctx.Err() == context.Canceled {
   150  					return errors.Wrap(ctx.Err(), "context has been canceled so shutting down the loop")
   151  				}
   152  				if err != nil {
   153  					traceutil.AnnotateError(span, err)
   154  					attempts := streamAttempts(ctx)
   155  					log.WithError(err).WithField("attempts", attempts).
   156  						Error("Stream broken while waiting for activation. Reconnecting...")
   157  					// Reconnection attempt backoff, up to 60s.
   158  					time.Sleep(time.Second * time.Duration(mathutil.Min(uint64(attempts), 60)))
   159  					return v.waitForActivation(incrementRetries(ctx), accountsChangedChan)
   160  				}
   161  
   162  				statuses := make([]*validatorStatus, len(res.Statuses))
   163  				for i, s := range res.Statuses {
   164  					statuses[i] = &validatorStatus{
   165  						publicKey: s.PublicKey,
   166  						status:    s.Status,
   167  						index:     s.Index,
   168  					}
   169  				}
   170  
   171  				valActivated := v.checkAndLogValidatorStatus(statuses)
   172  				if valActivated {
   173  					logActiveValidatorStatus(statuses)
   174  				} else {
   175  					continue
   176  				}
   177  			}
   178  			break
   179  		}
   180  	}
   181  
   182  	v.ticker = slotutil.NewSlotTicker(time.Unix(int64(v.genesisTime), 0), params.BeaconConfig().SecondsPerSlot)
   183  	return nil
   184  }
   185  
   186  // Preferred way to use context keys is with a non built-in type. See: RVV-B0003
   187  type waitForActivationContextKey string
   188  
   189  const waitForActivationAttemptsContextKey = waitForActivationContextKey("WaitForActivation-attempts")
   190  
   191  func streamAttempts(ctx context.Context) int {
   192  	attempts, ok := ctx.Value(waitForActivationAttemptsContextKey).(int)
   193  	if !ok {
   194  		return 1
   195  	}
   196  	return attempts
   197  }
   198  
   199  func incrementRetries(ctx context.Context) context.Context {
   200  	attempts := streamAttempts(ctx)
   201  	return context.WithValue(ctx, waitForActivationAttemptsContextKey, attempts+1)
   202  }