github.com/prysmaticlabs/prysm@v1.4.4/slasher/node/node.go (about)

     1  // Package node is the main process which handles the lifecycle of
     2  // the runtime services in a slasher process, gracefully shutting
     3  // everything down upon close.
     4  package node
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"os"
    10  	"os/signal"
    11  	"path"
    12  	"sync"
    13  	"syscall"
    14  
    15  	"github.com/pkg/errors"
    16  	"github.com/prysmaticlabs/prysm/cmd/slasher/flags"
    17  	"github.com/prysmaticlabs/prysm/shared"
    18  	"github.com/prysmaticlabs/prysm/shared/backuputil"
    19  	"github.com/prysmaticlabs/prysm/shared/cmd"
    20  	"github.com/prysmaticlabs/prysm/shared/debug"
    21  	"github.com/prysmaticlabs/prysm/shared/event"
    22  	"github.com/prysmaticlabs/prysm/shared/featureconfig"
    23  	"github.com/prysmaticlabs/prysm/shared/params"
    24  	"github.com/prysmaticlabs/prysm/shared/prereq"
    25  	"github.com/prysmaticlabs/prysm/shared/prometheus"
    26  	"github.com/prysmaticlabs/prysm/shared/tracing"
    27  	"github.com/prysmaticlabs/prysm/shared/version"
    28  	"github.com/prysmaticlabs/prysm/slasher/beaconclient"
    29  	"github.com/prysmaticlabs/prysm/slasher/db"
    30  	"github.com/prysmaticlabs/prysm/slasher/db/kv"
    31  	"github.com/prysmaticlabs/prysm/slasher/detection"
    32  	"github.com/prysmaticlabs/prysm/slasher/rpc"
    33  	"github.com/sirupsen/logrus"
    34  	"github.com/urfave/cli/v2"
    35  )
    36  
    37  // SlasherNode defines a struct that handles the services running a slashing detector
    38  // for Ethereum. It handles the lifecycle of the entire system and registers
    39  // services to a service registry.
    40  type SlasherNode struct {
    41  	cliCtx                *cli.Context
    42  	ctx                   context.Context
    43  	cancel                context.CancelFunc
    44  	lock                  sync.RWMutex
    45  	services              *shared.ServiceRegistry
    46  	proposerSlashingsFeed *event.Feed
    47  	attesterSlashingsFeed *event.Feed
    48  	stop                  chan struct{} // Channel to wait for termination notifications.
    49  	db                    db.Database
    50  }
    51  
    52  // New creates a new node instance, sets up configuration options,
    53  // and registers every required service.
    54  func New(cliCtx *cli.Context) (*SlasherNode, error) {
    55  	if err := tracing.Setup(
    56  		"slasher", // Service name.
    57  		cliCtx.String(cmd.TracingProcessNameFlag.Name),
    58  		cliCtx.String(cmd.TracingEndpointFlag.Name),
    59  		cliCtx.Float64(cmd.TraceSampleFractionFlag.Name),
    60  		cliCtx.Bool(cmd.EnableTracingFlag.Name),
    61  	); err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	// Warn if user's platform is not supported
    66  	prereq.WarnIfPlatformNotSupported(cliCtx.Context)
    67  
    68  	if cliCtx.Bool(flags.EnableHistoricalDetectionFlag.Name) {
    69  		// Set the max RPC size to 4096 as configured by --historical-slasher-node for optimal historical detection.
    70  		cmdConfig := cmd.Get()
    71  		cmdConfig.MaxRPCPageSize = int(params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().MaxAttestations))
    72  		cmd.Init(cmdConfig)
    73  	}
    74  
    75  	featureconfig.ConfigureSlasher(cliCtx)
    76  	cmd.ConfigureSlasher(cliCtx)
    77  	registry := shared.NewServiceRegistry()
    78  
    79  	ctx, cancel := context.WithCancel(cliCtx.Context)
    80  	slasher := &SlasherNode{
    81  		cliCtx:                cliCtx,
    82  		ctx:                   ctx,
    83  		cancel:                cancel,
    84  		proposerSlashingsFeed: new(event.Feed),
    85  		attesterSlashingsFeed: new(event.Feed),
    86  		services:              registry,
    87  		stop:                  make(chan struct{}),
    88  	}
    89  
    90  	if err := slasher.startDB(); err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	if !cliCtx.Bool(cmd.DisableMonitoringFlag.Name) {
    95  		if err := slasher.registerPrometheusService(cliCtx); err != nil {
    96  			return nil, err
    97  		}
    98  	}
    99  
   100  	if err := slasher.registerBeaconClientService(); err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	if err := slasher.registerDetectionService(); err != nil {
   105  		return nil, err
   106  	}
   107  
   108  	if err := slasher.registerRPCService(); err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	return slasher, nil
   113  }
   114  
   115  // Start the slasher and kick off every registered service.
   116  func (n *SlasherNode) Start() {
   117  	n.lock.Lock()
   118  	n.services.StartAll()
   119  	n.lock.Unlock()
   120  
   121  	log.WithFields(logrus.Fields{
   122  		"version": version.Version(),
   123  	}).Info("Starting slasher client")
   124  
   125  	stop := n.stop
   126  	go func() {
   127  		sigc := make(chan os.Signal, 1)
   128  		signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
   129  		defer signal.Stop(sigc)
   130  		<-sigc
   131  		log.Info("Got interrupt, shutting down...")
   132  		debug.Exit(n.cliCtx) // Ensure trace and CPU profile data are flushed.
   133  		go n.Close()
   134  		for i := 10; i > 0; i-- {
   135  			<-sigc
   136  			if i > 1 {
   137  				log.WithField("times", i-1).Info("Already shutting down, interrupt more to panic")
   138  			}
   139  		}
   140  		panic("Panic closing the beacon node")
   141  	}()
   142  
   143  	// Wait for stop channel to be closed.
   144  	<-stop
   145  }
   146  
   147  // Close handles graceful shutdown of the system.
   148  func (n *SlasherNode) Close() {
   149  	n.lock.Lock()
   150  	defer n.lock.Unlock()
   151  
   152  	log.Info("Stopping hash slinging slasher")
   153  	n.services.StopAll()
   154  	if err := n.db.Close(); err != nil {
   155  		log.Errorf("Failed to close database: %v", err)
   156  	}
   157  	n.cancel()
   158  	close(n.stop)
   159  }
   160  
   161  func (n *SlasherNode) registerPrometheusService(cliCtx *cli.Context) error {
   162  	var additionalHandlers []prometheus.Handler
   163  	if cliCtx.IsSet(cmd.EnableBackupWebhookFlag.Name) {
   164  		additionalHandlers = append(
   165  			additionalHandlers,
   166  			prometheus.Handler{
   167  				Path:    "/db/backup",
   168  				Handler: backuputil.BackupHandler(n.db, cliCtx.String(cmd.BackupWebhookOutputDir.Name)),
   169  			},
   170  		)
   171  	}
   172  	service := prometheus.NewService(
   173  		fmt.Sprintf("%s:%d", n.cliCtx.String(cmd.MonitoringHostFlag.Name), n.cliCtx.Int(flags.MonitoringPortFlag.Name)),
   174  		n.services,
   175  		additionalHandlers...,
   176  	)
   177  	logrus.AddHook(prometheus.NewLogrusCollector())
   178  	return n.services.RegisterService(service)
   179  }
   180  
   181  func (n *SlasherNode) startDB() error {
   182  	baseDir := n.cliCtx.String(cmd.DataDirFlag.Name)
   183  	clearDB := n.cliCtx.Bool(cmd.ClearDB.Name)
   184  	forceClearDB := n.cliCtx.Bool(cmd.ForceClearDB.Name)
   185  	dbPath := path.Join(baseDir, kv.SlasherDbDirName)
   186  	spanCacheSize := n.cliCtx.Int(flags.SpanCacheSize.Name)
   187  	highestAttCacheSize := n.cliCtx.Int(flags.HighestAttCacheSize.Name)
   188  	cfg := &kv.Config{SpanCacheSize: spanCacheSize, HighestAttestationCacheSize: highestAttCacheSize}
   189  	log.Infof("Span cache size has been set to: %d", spanCacheSize)
   190  	d, err := db.NewDB(dbPath, cfg)
   191  	if err != nil {
   192  		return err
   193  	}
   194  	clearDBConfirmed := false
   195  	if clearDB && !forceClearDB {
   196  		actionText := "This will delete your slasher database stored in your data directory. " +
   197  			"Your database backups will not be removed - do you want to proceed? (Y/N)"
   198  		deniedText := "Database will not be deleted. No changes have been made."
   199  		clearDBConfirmed, err = cmd.ConfirmAction(actionText, deniedText)
   200  		if err != nil {
   201  			return err
   202  		}
   203  	}
   204  	if clearDBConfirmed || forceClearDB {
   205  		log.Warning("Removing database")
   206  		if err := d.Close(); err != nil {
   207  			return errors.Wrap(err, "could not close db prior to clearing")
   208  		}
   209  		if err := d.ClearDB(); err != nil {
   210  			return err
   211  		}
   212  		d, err = db.NewDB(dbPath, cfg)
   213  		if err != nil {
   214  			return err
   215  		}
   216  	}
   217  	log.WithField("database-path", baseDir).Info("Checking DB")
   218  	n.db = d
   219  	return nil
   220  }
   221  
   222  func (n *SlasherNode) registerBeaconClientService() error {
   223  	beaconCert := n.cliCtx.String(flags.BeaconCertFlag.Name)
   224  	beaconProvider := n.cliCtx.String(flags.BeaconRPCProviderFlag.Name)
   225  	if beaconProvider == "" {
   226  		beaconProvider = flags.BeaconRPCProviderFlag.Value
   227  	}
   228  
   229  	bs, err := beaconclient.NewService(n.ctx, &beaconclient.Config{
   230  		BeaconCert:            beaconCert,
   231  		SlasherDB:             n.db,
   232  		BeaconProvider:        beaconProvider,
   233  		AttesterSlashingsFeed: n.attesterSlashingsFeed,
   234  		ProposerSlashingsFeed: n.proposerSlashingsFeed,
   235  	})
   236  	if err != nil {
   237  		return errors.Wrap(err, "failed to initialize beacon client")
   238  	}
   239  	return n.services.RegisterService(bs)
   240  }
   241  
   242  func (n *SlasherNode) registerDetectionService() error {
   243  	var bs *beaconclient.Service
   244  	if err := n.services.FetchService(&bs); err != nil {
   245  		panic(err)
   246  	}
   247  	ds := detection.NewService(n.ctx, &detection.Config{
   248  		Notifier:              bs,
   249  		SlasherDB:             n.db,
   250  		BeaconClient:          bs,
   251  		ChainFetcher:          bs,
   252  		AttesterSlashingsFeed: n.attesterSlashingsFeed,
   253  		ProposerSlashingsFeed: n.proposerSlashingsFeed,
   254  		HistoricalDetection:   n.cliCtx.Bool(flags.EnableHistoricalDetectionFlag.Name),
   255  	})
   256  	return n.services.RegisterService(ds)
   257  }
   258  
   259  func (n *SlasherNode) registerRPCService() error {
   260  	var detectionService *detection.Service
   261  	if err := n.services.FetchService(&detectionService); err != nil {
   262  		return err
   263  	}
   264  	var bs *beaconclient.Service
   265  	if err := n.services.FetchService(&bs); err != nil {
   266  		panic(err)
   267  	}
   268  	host := n.cliCtx.String(flags.RPCHost.Name)
   269  	port := n.cliCtx.String(flags.RPCPort.Name)
   270  	cert := n.cliCtx.String(flags.CertFlag.Name)
   271  	key := n.cliCtx.String(flags.KeyFlag.Name)
   272  	rpcService := rpc.NewService(n.ctx, &rpc.Config{
   273  		Host:         host,
   274  		Port:         port,
   275  		CertFlag:     cert,
   276  		KeyFlag:      key,
   277  		Detector:     detectionService,
   278  		SlasherDB:    n.db,
   279  		BeaconClient: bs,
   280  	})
   281  
   282  	return n.services.RegisterService(rpcService)
   283  }