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  }