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 := ðpb.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, ðpb.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 }