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 }