2019-10-31 03:26:55 +00:00
|
|
|
// Package service defines the service used to retrieve slashings proofs and
|
|
|
|
// feed attestations and block headers into the slasher db.
|
|
|
|
package service
|
2019-09-26 16:29:10 +00:00
|
|
|
|
|
|
|
import (
|
2019-12-13 07:31:37 +00:00
|
|
|
"context"
|
2019-09-26 16:29:10 +00:00
|
|
|
"fmt"
|
|
|
|
"net"
|
2019-10-31 03:26:55 +00:00
|
|
|
"os"
|
|
|
|
"os/signal"
|
|
|
|
"path"
|
|
|
|
"sync"
|
|
|
|
"syscall"
|
|
|
|
|
2019-12-13 07:31:37 +00:00
|
|
|
middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
|
|
|
recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
|
|
|
|
grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
|
2019-10-31 03:26:55 +00:00
|
|
|
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
|
2020-01-29 01:44:51 +00:00
|
|
|
"github.com/pkg/errors"
|
2019-12-13 07:31:37 +00:00
|
|
|
eth "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
2019-12-21 03:47:00 +00:00
|
|
|
slashpb "github.com/prysmaticlabs/prysm/proto/slashing"
|
2019-10-31 03:26:55 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/shared/cmd"
|
|
|
|
"github.com/prysmaticlabs/prysm/shared/debug"
|
|
|
|
"github.com/prysmaticlabs/prysm/shared/version"
|
2020-02-10 20:09:14 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/slasher/cache"
|
2019-12-13 07:31:37 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/slasher/db"
|
2020-01-22 05:39:21 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/slasher/flags"
|
2019-10-31 03:26:55 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/slasher/rpc"
|
2019-12-13 07:31:37 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2019-10-31 03:26:55 +00:00
|
|
|
"github.com/urfave/cli"
|
|
|
|
"go.opencensus.io/plugin/ocgrpc"
|
2019-12-13 07:31:37 +00:00
|
|
|
"google.golang.org/grpc"
|
2019-10-31 03:26:55 +00:00
|
|
|
"google.golang.org/grpc/credentials"
|
|
|
|
"google.golang.org/grpc/reflection"
|
2019-09-26 16:29:10 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var log logrus.FieldLogger
|
|
|
|
|
2019-10-31 03:26:55 +00:00
|
|
|
const slasherDBName = "slasherdata"
|
|
|
|
|
2020-02-10 20:09:14 +00:00
|
|
|
var committeesCache = cache.NewCommitteesCache()
|
|
|
|
|
2019-09-26 16:29:10 +00:00
|
|
|
func init() {
|
|
|
|
log = logrus.WithField("prefix", "slasherRPC")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Service defining an RPC server for the slasher service.
|
|
|
|
type Service struct {
|
|
|
|
slasherDb *db.Store
|
|
|
|
grpcServer *grpc.Server
|
2020-01-29 01:44:51 +00:00
|
|
|
slasher *rpc.Server
|
2019-12-13 07:31:37 +00:00
|
|
|
port int
|
2019-09-26 16:29:10 +00:00
|
|
|
withCert string
|
|
|
|
withKey string
|
|
|
|
listener net.Listener
|
|
|
|
credentialError error
|
|
|
|
failStatus error
|
2019-10-31 03:26:55 +00:00
|
|
|
ctx *cli.Context
|
|
|
|
lock sync.RWMutex
|
|
|
|
stop chan struct{} // Channel to wait for termination notifications.
|
2019-12-13 07:31:37 +00:00
|
|
|
context context.Context
|
|
|
|
beaconConn *grpc.ClientConn
|
|
|
|
beaconProvider string
|
|
|
|
beaconCert string
|
|
|
|
beaconClient eth.BeaconChainClient
|
|
|
|
started bool
|
2019-09-26 16:29:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Config options for the slasher server.
|
|
|
|
type Config struct {
|
2019-12-13 07:31:37 +00:00
|
|
|
Port int
|
|
|
|
CertFlag string
|
|
|
|
KeyFlag string
|
|
|
|
SlasherDb *db.Store
|
|
|
|
BeaconProvider string
|
|
|
|
BeaconCert string
|
2019-09-26 16:29:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewRPCService creates a new instance of a struct implementing the SlasherService
|
|
|
|
// interface.
|
2019-10-31 03:26:55 +00:00
|
|
|
func NewRPCService(cfg *Config, ctx *cli.Context) (*Service, error) {
|
|
|
|
s := &Service{
|
2019-12-13 07:31:37 +00:00
|
|
|
slasherDb: cfg.SlasherDb,
|
|
|
|
port: cfg.Port,
|
|
|
|
withCert: cfg.CertFlag,
|
|
|
|
withKey: cfg.KeyFlag,
|
|
|
|
ctx: ctx,
|
|
|
|
stop: make(chan struct{}),
|
|
|
|
beaconProvider: cfg.BeaconProvider,
|
|
|
|
beaconCert: cfg.BeaconCert,
|
2019-10-31 03:26:55 +00:00
|
|
|
}
|
|
|
|
if err := s.startDB(s.ctx); err != nil {
|
|
|
|
return nil, err
|
2019-09-26 16:29:10 +00:00
|
|
|
}
|
2020-01-29 01:44:51 +00:00
|
|
|
s.slasher = &rpc.Server{
|
|
|
|
SlasherDB: s.slasherDb,
|
|
|
|
}
|
2019-10-31 03:26:55 +00:00
|
|
|
return s, nil
|
2019-09-26 16:29:10 +00:00
|
|
|
}
|
|
|
|
|
2020-02-10 05:57:20 +00:00
|
|
|
func (s *Service) startDB(ctx *cli.Context) error {
|
|
|
|
baseDir := ctx.GlobalString(cmd.DataDirFlag.Name)
|
|
|
|
dbPath := path.Join(baseDir, slasherDBName)
|
|
|
|
cfg := &db.Config{SpanCacheEnabled: ctx.GlobalBool(flags.UseSpanCacheFlag.Name)}
|
|
|
|
d, err := db.NewDB(dbPath, cfg)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if s.ctx.GlobalBool(cmd.ClearDB.Name) {
|
|
|
|
if err := d.ClearDB(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
d, err = db.NewDB(dbPath, cfg)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log.WithField("path", dbPath).Info("Checking db")
|
|
|
|
s.slasherDb = d
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-09-26 16:29:10 +00:00
|
|
|
// Start the gRPC server.
|
|
|
|
func (s *Service) Start() {
|
2019-10-31 03:26:55 +00:00
|
|
|
s.lock.Lock()
|
|
|
|
log.WithFields(logrus.Fields{
|
|
|
|
"version": version.GetVersion(),
|
|
|
|
}).Info("Starting hash slinging slasher node")
|
2019-12-13 07:31:37 +00:00
|
|
|
s.context = context.Background()
|
2019-10-31 03:26:55 +00:00
|
|
|
s.startSlasher()
|
2020-01-29 01:44:51 +00:00
|
|
|
if s.beaconClient == nil {
|
|
|
|
if err := s.startBeaconClient(); err != nil {
|
|
|
|
log.WithError(err).Errorf("failed to start beacon client")
|
|
|
|
s.failStatus = err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2019-10-31 03:26:55 +00:00
|
|
|
stop := s.stop
|
2020-02-10 20:09:14 +00:00
|
|
|
if err := s.historicalAttestationFeeder(s.context); err != nil {
|
2020-01-29 01:44:51 +00:00
|
|
|
err = errors.Wrap(err, "couldn't start attestation feeder from archive endpoint. please use "+
|
|
|
|
"--beacon-rpc-provider flag value if you are not running a beacon chain service with "+
|
|
|
|
"--archive flag on the local machine.")
|
2020-02-10 05:57:20 +00:00
|
|
|
log.Error(err)
|
2020-01-29 01:44:51 +00:00
|
|
|
s.failStatus = err
|
|
|
|
return
|
|
|
|
}
|
2020-02-10 05:57:20 +00:00
|
|
|
go func() {
|
2020-02-10 20:09:14 +00:00
|
|
|
if err := s.attestationFeeder(s.context); err != nil {
|
2020-02-10 05:57:20 +00:00
|
|
|
log.Error(err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
go func() {
|
2020-02-10 20:09:14 +00:00
|
|
|
if err := s.finalizedChangeUpdater(s.context); err != nil {
|
2020-02-10 05:57:20 +00:00
|
|
|
log.Error(err)
|
|
|
|
}
|
|
|
|
}()
|
2019-10-31 03:26:55 +00:00
|
|
|
s.lock.Unlock()
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
sigc := make(chan os.Signal, 1)
|
|
|
|
signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
defer signal.Stop(sigc)
|
|
|
|
<-sigc
|
|
|
|
log.Info("Got interrupt, shutting down...")
|
|
|
|
debug.Exit(s.ctx) // Ensure trace and CPU profile data are flushed.
|
|
|
|
go s.Close()
|
|
|
|
for i := 10; i > 0; i-- {
|
|
|
|
<-sigc
|
|
|
|
if i > 1 {
|
|
|
|
log.Info("Already shutting down, interrupt more to panic", "times", i-1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
panic("Panic closing the hash slinging slasher node")
|
|
|
|
}()
|
2019-12-13 07:31:37 +00:00
|
|
|
s.started = true
|
2019-10-31 03:26:55 +00:00
|
|
|
// Wait for stop channel to be closed.
|
2019-12-13 07:31:37 +00:00
|
|
|
<-stop
|
2019-10-31 03:26:55 +00:00
|
|
|
}
|
2019-12-13 07:31:37 +00:00
|
|
|
|
2019-10-31 03:26:55 +00:00
|
|
|
func (s *Service) startSlasher() {
|
|
|
|
log.Info("Starting service on port: ", s.port)
|
2019-12-13 07:31:37 +00:00
|
|
|
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", s.port))
|
2019-09-26 16:29:10 +00:00
|
|
|
if err != nil {
|
2019-12-13 07:31:37 +00:00
|
|
|
log.Errorf("Could not listen to port in Start() :%d: %v", s.port, err)
|
2019-09-26 16:29:10 +00:00
|
|
|
}
|
|
|
|
s.listener = lis
|
|
|
|
log.WithField("port", s.port).Info("Listening on port")
|
|
|
|
|
|
|
|
opts := []grpc.ServerOption{
|
|
|
|
grpc.StatsHandler(&ocgrpc.ServerHandler{}),
|
|
|
|
grpc.StreamInterceptor(middleware.ChainStreamServer(
|
|
|
|
recovery.StreamServerInterceptor(),
|
|
|
|
grpc_prometheus.StreamServerInterceptor,
|
|
|
|
)),
|
|
|
|
grpc.UnaryInterceptor(middleware.ChainUnaryServer(
|
|
|
|
recovery.UnaryServerInterceptor(),
|
|
|
|
grpc_prometheus.UnaryServerInterceptor,
|
|
|
|
)),
|
|
|
|
}
|
|
|
|
// TODO(#791): Utilize a certificate for secure connections
|
|
|
|
// between beacon nodes and validator clients.
|
|
|
|
if s.withCert != "" && s.withKey != "" {
|
|
|
|
creds, err := credentials.NewServerTLSFromFile(s.withCert, s.withKey)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Could not load TLS keys: %s", err)
|
|
|
|
s.credentialError = err
|
|
|
|
}
|
|
|
|
opts = append(opts, grpc.Creds(creds))
|
|
|
|
} else {
|
|
|
|
log.Warn("You are using an insecure gRPC connection! Provide a certificate and key to connect securely")
|
|
|
|
}
|
|
|
|
s.grpcServer = grpc.NewServer(opts...)
|
2019-10-17 03:12:26 +00:00
|
|
|
slasherServer := rpc.Server{
|
2019-11-12 17:24:56 +00:00
|
|
|
SlasherDB: s.slasherDb,
|
2019-10-17 03:12:26 +00:00
|
|
|
}
|
2020-01-22 05:39:21 +00:00
|
|
|
if s.ctx.GlobalBool(flags.RebuildSpanMapsFlag.Name) {
|
2020-02-04 18:48:51 +00:00
|
|
|
s.loadSpanMaps(slasherServer)
|
2020-01-22 05:39:21 +00:00
|
|
|
}
|
2019-11-27 05:08:18 +00:00
|
|
|
slashpb.RegisterSlasherServer(s.grpcServer, &slasherServer)
|
2019-09-26 16:29:10 +00:00
|
|
|
|
|
|
|
// Register reflection service on gRPC server.
|
|
|
|
reflection.Register(s.grpcServer)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
if s.listener != nil {
|
|
|
|
if err := s.grpcServer.Serve(s.listener); err != nil {
|
|
|
|
log.Errorf("Could not serve gRPC: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2020-01-29 01:44:51 +00:00
|
|
|
func (s *Service) startBeaconClient() error {
|
2019-12-13 07:31:37 +00:00
|
|
|
var dialOpt grpc.DialOption
|
|
|
|
|
|
|
|
if s.beaconCert != "" {
|
|
|
|
creds, err := credentials.NewClientTLSFromFile(s.beaconCert, "")
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Could not get valid credentials: %v", err)
|
|
|
|
}
|
|
|
|
dialOpt = grpc.WithTransportCredentials(creds)
|
|
|
|
} else {
|
|
|
|
dialOpt = grpc.WithInsecure()
|
|
|
|
log.Warn("You are using an insecure gRPC connection to beacon chain! Please provide a certificate and key to use a secure connection.")
|
|
|
|
}
|
|
|
|
beaconOpts := []grpc.DialOption{
|
|
|
|
dialOpt,
|
|
|
|
grpc.WithStatsHandler(&ocgrpc.ClientHandler{}),
|
|
|
|
grpc.WithStreamInterceptor(middleware.ChainStreamClient(
|
|
|
|
grpc_opentracing.StreamClientInterceptor(),
|
|
|
|
grpc_prometheus.StreamClientInterceptor,
|
|
|
|
)),
|
|
|
|
grpc.WithUnaryInterceptor(middleware.ChainUnaryClient(
|
|
|
|
grpc_opentracing.UnaryClientInterceptor(),
|
|
|
|
grpc_prometheus.UnaryClientInterceptor,
|
|
|
|
)),
|
|
|
|
}
|
|
|
|
conn, err := grpc.DialContext(s.context, s.beaconProvider, beaconOpts...)
|
|
|
|
if err != nil {
|
2020-01-29 01:44:51 +00:00
|
|
|
return fmt.Errorf("could not dial endpoint: %s, %v", s.beaconProvider, err)
|
2019-12-13 07:31:37 +00:00
|
|
|
}
|
|
|
|
log.Info("Successfully started gRPC connection")
|
|
|
|
s.beaconConn = conn
|
|
|
|
s.beaconClient = eth.NewBeaconChainClient(s.beaconConn)
|
2020-01-29 01:44:51 +00:00
|
|
|
return nil
|
2019-12-13 07:31:37 +00:00
|
|
|
}
|
|
|
|
|
2020-02-10 05:57:20 +00:00
|
|
|
func (s *Service) loadSpanMaps(slasherServer rpc.Server) {
|
|
|
|
latestTargetEpoch, err := slasherServer.SlasherDB.LatestIndexedAttestationsTargetEpoch()
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Could not extract latest target epoch from indexed attestations store: %v", err)
|
|
|
|
}
|
|
|
|
for epoch := uint64(0); epoch < latestTargetEpoch; epoch++ {
|
|
|
|
idxAtts, err := slasherServer.SlasherDB.IdxAttsForTarget(epoch)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Got error while trying to retrieve indexed attestations from db: %v", err)
|
|
|
|
}
|
|
|
|
for _, ia := range idxAtts {
|
|
|
|
if err := slasherServer.UpdateSpanMaps(s.context, ia); err != nil {
|
|
|
|
log.Errorf("Unexpected error updating span maps: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
log.Infof("Update span maps for epoch: %d", epoch)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-26 16:29:10 +00:00
|
|
|
// Stop the service.
|
|
|
|
func (s *Service) Stop() error {
|
|
|
|
log.Info("Stopping service")
|
2019-10-31 03:26:55 +00:00
|
|
|
if s.slasherDb != nil {
|
2020-02-04 18:48:51 +00:00
|
|
|
if err := s.slasherDb.Close(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-10-31 03:26:55 +00:00
|
|
|
}
|
2019-09-26 16:29:10 +00:00
|
|
|
if s.listener != nil {
|
|
|
|
s.grpcServer.GracefulStop()
|
|
|
|
log.Debug("Initiated graceful stop of gRPC server")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-10-31 03:26:55 +00:00
|
|
|
// Close handles graceful shutdown of the system.
|
|
|
|
func (s *Service) Close() {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
log.Info("Stopping hash slinging slasher")
|
2020-02-04 18:48:51 +00:00
|
|
|
if err := s.Stop(); err != nil {
|
2019-12-13 07:31:37 +00:00
|
|
|
log.Panicf("Could not stop the slasher service: %v", err)
|
|
|
|
}
|
2020-01-22 05:39:21 +00:00
|
|
|
if err := s.slasherDb.SaveCachedSpansMaps(); err != nil {
|
|
|
|
log.Fatal("Didn't save span map cache to db. if span cache is enabled please restart with --%s", flags.RebuildSpanMapsFlag.Name)
|
|
|
|
}
|
2019-10-31 03:26:55 +00:00
|
|
|
if err := s.slasherDb.Close(); err != nil {
|
|
|
|
log.Errorf("Failed to close slasher database: %v", err)
|
|
|
|
}
|
2019-12-13 07:31:37 +00:00
|
|
|
s.context.Done()
|
2019-10-31 03:26:55 +00:00
|
|
|
close(s.stop)
|
|
|
|
}
|
|
|
|
|
2019-09-26 16:29:10 +00:00
|
|
|
// Status returns nil, credentialError or fail status.
|
2019-12-13 07:31:37 +00:00
|
|
|
func (s *Service) Status() (bool, error) {
|
2019-09-26 16:29:10 +00:00
|
|
|
if s.credentialError != nil {
|
2019-12-13 07:31:37 +00:00
|
|
|
return false, s.credentialError
|
2019-09-26 16:29:10 +00:00
|
|
|
}
|
|
|
|
if s.failStatus != nil {
|
2019-12-13 07:31:37 +00:00
|
|
|
return false, s.failStatus
|
2019-09-26 16:29:10 +00:00
|
|
|
}
|
2019-12-13 07:31:37 +00:00
|
|
|
return s.started, nil
|
2019-10-31 03:26:55 +00:00
|
|
|
}
|