github.com/prysmaticlabs/prysm@v1.4.4/validator/client/runner.go (about) 1 package client 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "time" 8 9 "github.com/pkg/errors" 10 types "github.com/prysmaticlabs/eth2-types" 11 "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" 12 "github.com/prysmaticlabs/prysm/shared/bytesutil" 13 "github.com/prysmaticlabs/prysm/shared/featureconfig" 14 "github.com/prysmaticlabs/prysm/shared/params" 15 "github.com/prysmaticlabs/prysm/validator/client/iface" 16 "github.com/prysmaticlabs/prysm/validator/keymanager/remote" 17 "go.opencensus.io/trace" 18 "google.golang.org/grpc/codes" 19 "google.golang.org/grpc/status" 20 ) 21 22 // time to wait before trying to reconnect with beacon node. 23 var backOffPeriod = 10 * time.Second 24 25 // Run the main validator routine. This routine exits if the context is 26 // canceled. 27 // 28 // Order of operations: 29 // 1 - Initialize validator data 30 // 2 - Wait for validator activation 31 // 3 - Wait for the next slot start 32 // 4 - Update assignments 33 // 5 - Determine role at current slot 34 // 6 - Perform assigned role, if any 35 func run(ctx context.Context, v iface.Validator) { 36 cleanup := v.Done 37 defer cleanup() 38 if err := v.WaitForWalletInitialization(ctx); err != nil { 39 // log.Fatalf will prevent defer from being called 40 cleanup() 41 log.Fatalf("Wallet is not ready: %v", err) 42 } 43 if featureconfig.Get().SlasherProtection { 44 if err := v.SlasherReady(ctx); err != nil { 45 log.Fatalf("Slasher is not ready: %v", err) 46 } 47 } 48 ticker := time.NewTicker(backOffPeriod) 49 defer ticker.Stop() 50 51 var headSlot types.Slot 52 firstTime := true 53 for { 54 if !firstTime { 55 if ctx.Err() != nil { 56 log.Info("Context canceled, stopping validator") 57 return // Exit if context is canceled. 58 } 59 <-ticker.C 60 } else { 61 firstTime = false 62 } 63 err := v.WaitForChainStart(ctx) 64 if isConnectionError(err) { 65 log.Warnf("Could not determine if beacon chain started: %v", err) 66 continue 67 } 68 if err != nil { 69 log.Fatalf("Could not determine if beacon chain started: %v", err) 70 } 71 err = v.WaitForSync(ctx) 72 if isConnectionError(err) { 73 log.Warnf("Could not determine if beacon chain started: %v", err) 74 continue 75 } 76 if err != nil { 77 log.Fatalf("Could not determine if beacon node synced: %v", err) 78 } 79 err = v.WaitForActivation(ctx, nil /* accountsChangedChan */) 80 if isConnectionError(err) { 81 log.Warnf("Could not wait for validator activation: %v", err) 82 continue 83 } 84 if err != nil { 85 log.Fatalf("Could not wait for validator activation: %v", err) 86 } 87 err = v.CheckDoppelGanger(ctx) 88 if isConnectionError(err) { 89 log.Warnf("Could not wait for checking doppelganger: %v", err) 90 continue 91 } 92 if err != nil { 93 log.Fatalf("Could not succeed with doppelganger check: %v", err) 94 } 95 headSlot, err = v.CanonicalHeadSlot(ctx) 96 if isConnectionError(err) { 97 log.Warnf("Could not get current canonical head slot: %v", err) 98 continue 99 } 100 if err != nil { 101 log.Fatalf("Could not get current canonical head slot: %v", err) 102 } 103 break 104 } 105 106 connectionErrorChannel := make(chan error, 1) 107 go v.ReceiveBlocks(ctx, connectionErrorChannel) 108 if err := v.UpdateDuties(ctx, headSlot); err != nil { 109 handleAssignmentError(err, headSlot) 110 } 111 112 accountsChangedChan := make(chan [][48]byte, 1) 113 sub := v.GetKeymanager().SubscribeAccountChanges(accountsChangedChan) 114 for { 115 slotCtx, cancel := context.WithCancel(ctx) 116 ctx, span := trace.StartSpan(ctx, "validator.processSlot") 117 118 select { 119 case <-ctx.Done(): 120 log.Info("Context canceled, stopping validator") 121 span.End() 122 cancel() 123 sub.Unsubscribe() 124 close(accountsChangedChan) 125 return // Exit if context is canceled. 126 case blocksError := <-connectionErrorChannel: 127 if blocksError != nil { 128 log.WithError(blocksError).Warn("block stream interrupted") 129 go v.ReceiveBlocks(ctx, connectionErrorChannel) 130 continue 131 } 132 case newKeys := <-accountsChangedChan: 133 anyActive, err := v.HandleKeyReload(ctx, newKeys) 134 if err != nil { 135 log.WithError(err).Error("Could not properly handle reloaded keys") 136 } 137 if !anyActive { 138 log.Info("No active keys found. Waiting for activation...") 139 err := v.WaitForActivation(ctx, accountsChangedChan) 140 if err != nil { 141 log.Fatalf("Could not wait for validator activation: %v", err) 142 } 143 } 144 case slot := <-v.NextSlot(): 145 span.AddAttributes(trace.Int64Attribute("slot", int64(slot))) 146 147 remoteKm, ok := v.GetKeymanager().(remote.RemoteKeymanager) 148 if ok { 149 _, err := remoteKm.ReloadPublicKeys(ctx) 150 if err != nil { 151 log.WithError(err).Error(msgCouldNotFetchKeys) 152 } 153 } 154 155 allExited, err := v.AllValidatorsAreExited(ctx) 156 if err != nil { 157 log.WithError(err).Error("Could not check if validators are exited") 158 } 159 if allExited { 160 log.Info("All validators are exited, no more work to perform...") 161 continue 162 } 163 164 deadline := v.SlotDeadline(slot) 165 slotCtx, cancel = context.WithDeadline(ctx, deadline) 166 log := log.WithField("slot", slot) 167 log.WithField("deadline", deadline).Debug("Set deadline for proposals and attestations") 168 169 // Keep trying to update assignments if they are nil or if we are past an 170 // epoch transition in the beacon node's state. 171 if err := v.UpdateDuties(ctx, slot); err != nil { 172 handleAssignmentError(err, slot) 173 cancel() 174 span.End() 175 continue 176 } 177 178 // Start fetching domain data for the next epoch. 179 if helpers.IsEpochEnd(slot) { 180 go v.UpdateDomainDataCaches(ctx, slot+1) 181 } 182 183 var wg sync.WaitGroup 184 185 allRoles, err := v.RolesAt(ctx, slot) 186 if err != nil { 187 log.WithError(err).Error("Could not get validator roles") 188 span.End() 189 continue 190 } 191 for pubKey, roles := range allRoles { 192 wg.Add(len(roles)) 193 for _, role := range roles { 194 go func(role iface.ValidatorRole, pubKey [48]byte) { 195 defer wg.Done() 196 switch role { 197 case iface.RoleAttester: 198 v.SubmitAttestation(slotCtx, slot, pubKey) 199 case iface.RoleProposer: 200 v.ProposeBlock(slotCtx, slot, pubKey) 201 case iface.RoleAggregator: 202 v.SubmitAggregateAndProof(slotCtx, slot, pubKey) 203 case iface.RoleUnknown: 204 log.WithField("pubKey", fmt.Sprintf("%#x", bytesutil.Trunc(pubKey[:]))).Trace("No active roles, doing nothing") 205 default: 206 log.Warnf("Unhandled role %v", role) 207 } 208 }(role, pubKey) 209 } 210 } 211 // Wait for all processes to complete, then report span complete. 212 213 go func() { 214 wg.Wait() 215 // Log this client performance in the previous epoch 216 v.LogAttestationsSubmitted() 217 if err := v.LogValidatorGainsAndLosses(slotCtx, slot); err != nil { 218 log.WithError(err).Error("Could not report validator's rewards/penalties") 219 } 220 if err := v.LogNextDutyTimeLeft(slot); err != nil { 221 log.WithError(err).Error("Could not report next count down") 222 } 223 span.End() 224 }() 225 } 226 } 227 } 228 229 func isConnectionError(err error) bool { 230 return err != nil && errors.Is(err, iface.ErrConnectionIssue) 231 } 232 233 func handleAssignmentError(err error, slot types.Slot) { 234 if errCode, ok := status.FromError(err); ok && errCode.Code() == codes.NotFound { 235 log.WithField( 236 "epoch", slot/params.BeaconConfig().SlotsPerEpoch, 237 ).Warn("Validator not yet assigned to epoch") 238 } else { 239 log.WithField("error", err).Error("Failed to update assignments") 240 } 241 }