github.com/prysmaticlabs/prysm@v1.4.4/validator/client/service.go (about) 1 package client 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 "time" 8 9 "github.com/dgraph-io/ristretto" 10 middleware "github.com/grpc-ecosystem/go-grpc-middleware" 11 grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry" 12 grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing" 13 grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" 14 lru "github.com/hashicorp/golang-lru" 15 "github.com/pkg/errors" 16 types "github.com/prysmaticlabs/eth2-types" 17 ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" 18 "github.com/prysmaticlabs/prysm/shared/bytesutil" 19 "github.com/prysmaticlabs/prysm/shared/event" 20 "github.com/prysmaticlabs/prysm/shared/grpcutils" 21 "github.com/prysmaticlabs/prysm/shared/params" 22 accountsiface "github.com/prysmaticlabs/prysm/validator/accounts/iface" 23 "github.com/prysmaticlabs/prysm/validator/accounts/wallet" 24 "github.com/prysmaticlabs/prysm/validator/client/iface" 25 "github.com/prysmaticlabs/prysm/validator/db" 26 "github.com/prysmaticlabs/prysm/validator/graffiti" 27 "github.com/prysmaticlabs/prysm/validator/keymanager" 28 "github.com/prysmaticlabs/prysm/validator/keymanager/imported" 29 slashingiface "github.com/prysmaticlabs/prysm/validator/slashing-protection/iface" 30 "go.opencensus.io/plugin/ocgrpc" 31 "google.golang.org/grpc" 32 "google.golang.org/grpc/credentials" 33 "google.golang.org/protobuf/types/known/emptypb" 34 ) 35 36 // SyncChecker is able to determine if a beacon node is currently 37 // going through chain synchronization. 38 type SyncChecker interface { 39 Syncing(ctx context.Context) (bool, error) 40 } 41 42 // GenesisFetcher can retrieve genesis information such as 43 // the genesis time and the validator deposit contract address. 44 type GenesisFetcher interface { 45 GenesisInfo(ctx context.Context) (*ethpb.Genesis, error) 46 } 47 48 // ValidatorService represents a service to manage the validator client 49 // routine. 50 type ValidatorService struct { 51 useWeb bool 52 emitAccountMetrics bool 53 logValidatorBalances bool 54 logDutyCountDown bool 55 conn *grpc.ClientConn 56 grpcRetryDelay time.Duration 57 grpcRetries uint 58 maxCallRecvMsgSize int 59 walletInitializedFeed *event.Feed 60 cancel context.CancelFunc 61 db db.Database 62 dataDir string 63 withCert string 64 endpoint string 65 validator iface.Validator 66 protector slashingiface.Protector 67 ctx context.Context 68 keyManager keymanager.IKeymanager 69 grpcHeaders []string 70 graffiti []byte 71 graffitiStruct *graffiti.Graffiti 72 } 73 74 // Config for the validator service. 75 type Config struct { 76 UseWeb bool 77 LogValidatorBalances bool 78 EmitAccountMetrics bool 79 LogDutyCountDown bool 80 WalletInitializedFeed *event.Feed 81 GrpcRetriesFlag uint 82 GrpcRetryDelay time.Duration 83 GrpcMaxCallRecvMsgSizeFlag int 84 Protector slashingiface.Protector 85 Endpoint string 86 Validator iface.Validator 87 ValDB db.Database 88 KeyManager keymanager.IKeymanager 89 GraffitiFlag string 90 CertFlag string 91 DataDir string 92 GrpcHeadersFlag string 93 GraffitiStruct *graffiti.Graffiti 94 } 95 96 // NewValidatorService creates a new validator service for the service 97 // registry. 98 func NewValidatorService(ctx context.Context, cfg *Config) (*ValidatorService, error) { 99 ctx, cancel := context.WithCancel(ctx) 100 return &ValidatorService{ 101 ctx: ctx, 102 cancel: cancel, 103 endpoint: cfg.Endpoint, 104 withCert: cfg.CertFlag, 105 dataDir: cfg.DataDir, 106 graffiti: []byte(cfg.GraffitiFlag), 107 keyManager: cfg.KeyManager, 108 logValidatorBalances: cfg.LogValidatorBalances, 109 emitAccountMetrics: cfg.EmitAccountMetrics, 110 maxCallRecvMsgSize: cfg.GrpcMaxCallRecvMsgSizeFlag, 111 grpcRetries: cfg.GrpcRetriesFlag, 112 grpcRetryDelay: cfg.GrpcRetryDelay, 113 grpcHeaders: strings.Split(cfg.GrpcHeadersFlag, ","), 114 protector: cfg.Protector, 115 validator: cfg.Validator, 116 db: cfg.ValDB, 117 walletInitializedFeed: cfg.WalletInitializedFeed, 118 useWeb: cfg.UseWeb, 119 graffitiStruct: cfg.GraffitiStruct, 120 logDutyCountDown: cfg.LogDutyCountDown, 121 }, nil 122 } 123 124 // Start the validator service. Launches the main go routine for the validator 125 // client. 126 func (v *ValidatorService) Start() { 127 dialOpts := ConstructDialOptions( 128 v.maxCallRecvMsgSize, 129 v.withCert, 130 v.grpcRetries, 131 v.grpcRetryDelay, 132 ) 133 if dialOpts == nil { 134 return 135 } 136 137 v.ctx = grpcutils.AppendHeaders(v.ctx, v.grpcHeaders) 138 139 conn, err := grpc.DialContext(v.ctx, v.endpoint, dialOpts...) 140 if err != nil { 141 log.Errorf("Could not dial endpoint: %s, %v", v.endpoint, err) 142 return 143 } 144 if v.withCert != "" { 145 log.Info("Established secure gRPC connection") 146 } 147 148 v.conn = conn 149 cache, err := ristretto.NewCache(&ristretto.Config{ 150 NumCounters: 1920, // number of keys to track. 151 MaxCost: 192, // maximum cost of cache, 1 item = 1 cost. 152 BufferItems: 64, // number of keys per Get buffer. 153 }) 154 if err != nil { 155 panic(err) 156 } 157 158 aggregatedSlotCommitteeIDCache, err := lru.New(int(params.BeaconConfig().MaxCommitteesPerSlot)) 159 if err != nil { 160 log.Errorf("Could not initialize cache: %v", err) 161 return 162 } 163 164 sPubKeys, err := v.db.EIPImportBlacklistedPublicKeys(v.ctx) 165 if err != nil { 166 log.Errorf("Could not read slashable public keys from disk: %v", err) 167 return 168 } 169 slashablePublicKeys := make(map[[48]byte]bool) 170 for _, pubKey := range sPubKeys { 171 slashablePublicKeys[pubKey] = true 172 } 173 174 graffitiOrderedIndex, err := v.db.GraffitiOrderedIndex(v.ctx, v.graffitiStruct.Hash) 175 if err != nil { 176 log.Errorf("Could not read graffiti ordered index from disk: %v", err) 177 return 178 } 179 180 v.validator = &validator{ 181 db: v.db, 182 validatorClient: ethpb.NewBeaconNodeValidatorClient(v.conn), 183 beaconClient: ethpb.NewBeaconChainClient(v.conn), 184 node: ethpb.NewNodeClient(v.conn), 185 keyManager: v.keyManager, 186 graffiti: v.graffiti, 187 logValidatorBalances: v.logValidatorBalances, 188 emitAccountMetrics: v.emitAccountMetrics, 189 startBalances: make(map[[48]byte]uint64), 190 prevBalance: make(map[[48]byte]uint64), 191 attLogs: make(map[[32]byte]*attSubmitted), 192 domainDataCache: cache, 193 aggregatedSlotCommitteeIDCache: aggregatedSlotCommitteeIDCache, 194 protector: v.protector, 195 voteStats: voteStats{startEpoch: types.Epoch(^uint64(0))}, 196 useWeb: v.useWeb, 197 walletInitializedFeed: v.walletInitializedFeed, 198 blockFeed: new(event.Feed), 199 graffitiStruct: v.graffitiStruct, 200 graffitiOrderedIndex: graffitiOrderedIndex, 201 eipImportBlacklistedPublicKeys: slashablePublicKeys, 202 logDutyCountDown: v.logDutyCountDown, 203 } 204 go run(v.ctx, v.validator) 205 go v.recheckKeys(v.ctx) 206 } 207 208 // Stop the validator service. 209 func (v *ValidatorService) Stop() error { 210 v.cancel() 211 log.Info("Stopping service") 212 if v.conn != nil { 213 return v.conn.Close() 214 } 215 return nil 216 } 217 218 // Status of the validator service. 219 func (v *ValidatorService) Status() error { 220 if v.conn == nil { 221 return errors.New("no connection to beacon RPC") 222 } 223 return nil 224 } 225 226 func (v *ValidatorService) recheckKeys(ctx context.Context) { 227 var validatingKeys [][48]byte 228 var err error 229 if v.useWeb { 230 initializedChan := make(chan *wallet.Wallet) 231 sub := v.walletInitializedFeed.Subscribe(initializedChan) 232 cleanup := sub.Unsubscribe 233 defer cleanup() 234 w := <-initializedChan 235 keyManager, err := w.InitializeKeymanager(ctx, accountsiface.InitKeymanagerConfig{ListenForChanges: true}) 236 if err != nil { 237 // log.Fatalf will prevent defer from being called 238 cleanup() 239 log.Fatalf("Could not read keymanager for wallet: %v", err) 240 } 241 v.keyManager = keyManager 242 } 243 validatingKeys, err = v.keyManager.FetchValidatingPublicKeys(ctx) 244 if err != nil { 245 log.WithError(err).Debug("Could not fetch validating keys") 246 } 247 if err := v.db.UpdatePublicKeysBuckets(validatingKeys); err != nil { 248 log.WithError(err).Debug("Could not update public keys buckets") 249 } 250 go recheckValidatingKeysBucket(ctx, v.db, v.keyManager) 251 for _, key := range validatingKeys { 252 log.WithField( 253 "publicKey", fmt.Sprintf("%#x", bytesutil.Trunc(key[:])), 254 ).Info("Validating for public key") 255 } 256 } 257 258 // ConstructDialOptions constructs a list of grpc dial options 259 func ConstructDialOptions( 260 maxCallRecvMsgSize int, 261 withCert string, 262 grpcRetries uint, 263 grpcRetryDelay time.Duration, 264 extraOpts ...grpc.DialOption, 265 ) []grpc.DialOption { 266 var transportSecurity grpc.DialOption 267 if withCert != "" { 268 creds, err := credentials.NewClientTLSFromFile(withCert, "") 269 if err != nil { 270 log.Errorf("Could not get valid credentials: %v", err) 271 return nil 272 } 273 transportSecurity = grpc.WithTransportCredentials(creds) 274 } else { 275 transportSecurity = grpc.WithInsecure() 276 log.Warn("You are using an insecure gRPC connection. If you are running your beacon node and " + 277 "validator on the same machines, you can ignore this message. If you want to know " + 278 "how to enable secure connections, see: https://docs.prylabs.network/docs/prysm-usage/secure-grpc") 279 } 280 281 if maxCallRecvMsgSize == 0 { 282 maxCallRecvMsgSize = 10 * 5 << 20 // Default 50Mb 283 } 284 285 dialOpts := []grpc.DialOption{ 286 transportSecurity, 287 grpc.WithDefaultCallOptions( 288 grpc.MaxCallRecvMsgSize(maxCallRecvMsgSize), 289 grpc_retry.WithMax(grpcRetries), 290 grpc_retry.WithBackoff(grpc_retry.BackoffLinear(grpcRetryDelay)), 291 ), 292 grpc.WithStatsHandler(&ocgrpc.ClientHandler{}), 293 grpc.WithUnaryInterceptor(middleware.ChainUnaryClient( 294 grpc_opentracing.UnaryClientInterceptor(), 295 grpc_prometheus.UnaryClientInterceptor, 296 grpc_retry.UnaryClientInterceptor(), 297 grpcutils.LogRequests, 298 )), 299 grpc.WithChainStreamInterceptor( 300 grpcutils.LogStream, 301 grpc_opentracing.StreamClientInterceptor(), 302 grpc_prometheus.StreamClientInterceptor, 303 grpc_retry.StreamClientInterceptor(), 304 ), 305 grpc.WithResolvers(&multipleEndpointsGrpcResolverBuilder{}), 306 } 307 308 dialOpts = append(dialOpts, extraOpts...) 309 return dialOpts 310 } 311 312 // Syncing returns whether or not the beacon node is currently synchronizing the chain. 313 func (v *ValidatorService) Syncing(ctx context.Context) (bool, error) { 314 nc := ethpb.NewNodeClient(v.conn) 315 resp, err := nc.GetSyncStatus(ctx, &emptypb.Empty{}) 316 if err != nil { 317 return false, err 318 } 319 return resp.Syncing, nil 320 } 321 322 // GenesisInfo queries the beacon node for the chain genesis info containing 323 // the genesis time along with the validator deposit contract address. 324 func (v *ValidatorService) GenesisInfo(ctx context.Context) (*ethpb.Genesis, error) { 325 nc := ethpb.NewNodeClient(v.conn) 326 return nc.GetGenesis(ctx, &emptypb.Empty{}) 327 } 328 329 // to accounts changes in the keymanager, then updates those keys' 330 // buckets in bolt DB if a bucket for a key does not exist. 331 func recheckValidatingKeysBucket(ctx context.Context, valDB db.Database, km keymanager.IKeymanager) { 332 importedKeymanager, ok := km.(*imported.Keymanager) 333 if !ok { 334 return 335 } 336 validatingPubKeysChan := make(chan [][48]byte, 1) 337 sub := importedKeymanager.SubscribeAccountChanges(validatingPubKeysChan) 338 defer func() { 339 sub.Unsubscribe() 340 close(validatingPubKeysChan) 341 }() 342 for { 343 select { 344 case keys := <-validatingPubKeysChan: 345 if err := valDB.UpdatePublicKeysBuckets(keys); err != nil { 346 log.WithError(err).Debug("Could not update public keys buckets") 347 continue 348 } 349 case <-ctx.Done(): 350 return 351 case <-sub.Err(): 352 log.Error("Subscriber closed, exiting goroutine") 353 return 354 } 355 } 356 }