github.com/palisadeinc/bor@v0.0.0-20230615125219-ab7196213d15/internal/cli/server/server.go (about) 1 package server 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "math/big" 8 "net" 9 "net/http" 10 "os" 11 "runtime" 12 "strings" 13 "time" 14 15 "github.com/mattn/go-colorable" 16 "github.com/mattn/go-isatty" 17 "go.opentelemetry.io/otel" 18 "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" 19 "go.opentelemetry.io/otel/propagation" 20 "go.opentelemetry.io/otel/sdk/resource" 21 sdktrace "go.opentelemetry.io/otel/sdk/trace" 22 semconv "go.opentelemetry.io/otel/semconv/v1.4.0" 23 "google.golang.org/grpc" 24 25 "github.com/ethereum/go-ethereum/accounts" 26 "github.com/ethereum/go-ethereum/accounts/keystore" 27 "github.com/ethereum/go-ethereum/consensus/beacon" //nolint:typecheck 28 "github.com/ethereum/go-ethereum/consensus/bor" //nolint:typecheck 29 "github.com/ethereum/go-ethereum/consensus/clique" 30 "github.com/ethereum/go-ethereum/eth" 31 "github.com/ethereum/go-ethereum/eth/tracers" 32 "github.com/ethereum/go-ethereum/ethstats" 33 "github.com/ethereum/go-ethereum/graphql" 34 "github.com/ethereum/go-ethereum/internal/cli/server/pprof" 35 "github.com/ethereum/go-ethereum/internal/cli/server/proto" 36 "github.com/ethereum/go-ethereum/log" 37 "github.com/ethereum/go-ethereum/metrics" 38 "github.com/ethereum/go-ethereum/metrics/influxdb" 39 "github.com/ethereum/go-ethereum/metrics/prometheus" 40 "github.com/ethereum/go-ethereum/node" 41 42 // Force-load the tracer engines to trigger registration 43 _ "github.com/ethereum/go-ethereum/eth/tracers/js" 44 _ "github.com/ethereum/go-ethereum/eth/tracers/native" 45 ) 46 47 type Server struct { 48 proto.UnimplementedBorServer 49 node *node.Node 50 backend *eth.Ethereum 51 grpcServer *grpc.Server 52 tracer *sdktrace.TracerProvider 53 config *Config 54 55 // tracerAPI to trace block executions 56 tracerAPI *tracers.API 57 } 58 59 type serverOption func(srv *Server, config *Config) error 60 61 var glogger *log.GlogHandler 62 63 func init() { 64 glogger = log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) 65 glogger.Verbosity(log.LvlInfo) 66 log.Root().SetHandler(glogger) 67 } 68 69 func WithGRPCAddress() serverOption { 70 return func(srv *Server, config *Config) error { 71 return srv.gRPCServerByAddress(config.GRPC.Addr) 72 } 73 } 74 75 func WithGRPCListener(lis net.Listener) serverOption { 76 return func(srv *Server, _ *Config) error { 77 return srv.gRPCServerByListener(lis) 78 } 79 } 80 81 func VerbosityIntToString(verbosity int) string { 82 mapIntToString := map[int]string{ 83 5: "trace", 84 4: "debug", 85 3: "info", 86 2: "warn", 87 1: "error", 88 0: "crit", 89 } 90 91 return mapIntToString[verbosity] 92 } 93 94 func VerbosityStringToInt(loglevel string) int { 95 mapStringToInt := map[string]int{ 96 "trace": 5, 97 "debug": 4, 98 "info": 3, 99 "warn": 2, 100 "error": 1, 101 "crit": 0, 102 } 103 104 return mapStringToInt[loglevel] 105 } 106 107 //nolint:gocognit 108 func NewServer(config *Config, opts ...serverOption) (*Server, error) { 109 // start pprof 110 if config.Pprof.Enabled { 111 pprof.SetMemProfileRate(config.Pprof.MemProfileRate) 112 pprof.SetSetBlockProfileRate(config.Pprof.BlockProfileRate) 113 pprof.StartPProf(fmt.Sprintf("%s:%d", config.Pprof.Addr, config.Pprof.Port)) 114 } 115 116 runtime.SetMutexProfileFraction(5) 117 118 srv := &Server{ 119 config: config, 120 } 121 122 // start the logger 123 setupLogger(VerbosityIntToString(config.Verbosity), *config.Logging) 124 125 var err error 126 127 for _, opt := range opts { 128 err = opt(srv, config) 129 if err != nil { 130 return nil, err 131 } 132 } 133 134 // load the chain genesis 135 if err = config.loadChain(); err != nil { 136 return nil, err 137 } 138 139 // create the node/stack 140 nodeCfg, err := config.buildNode() 141 if err != nil { 142 return nil, err 143 } 144 145 stack, err := node.New(nodeCfg) 146 if err != nil { 147 return nil, err 148 } 149 150 // setup account manager (only keystore) 151 // create a new account manager, only for the scope of this function 152 accountManager := accounts.NewManager(&accounts.Config{}) 153 154 // register backend to account manager with keystore for signing 155 keydir := stack.KeyStoreDir() 156 157 n, p := keystore.StandardScryptN, keystore.StandardScryptP 158 if config.Accounts.UseLightweightKDF { 159 n, p = keystore.LightScryptN, keystore.LightScryptP 160 } 161 162 // proceed to authorize the local account manager in any case 163 accountManager.AddBackend(keystore.NewKeyStore(keydir, n, p)) 164 165 // flag to set if we're authorizing consensus here 166 authorized := false 167 168 // check if personal wallet endpoints are disabled or not 169 // nolint:nestif 170 if !config.Accounts.DisableBorWallet { 171 // add keystore globally to the node's account manager if personal wallet is enabled 172 stack.AccountManager().AddBackend(keystore.NewKeyStore(keydir, n, p)) 173 174 // register the ethereum backend 175 ethCfg, err := config.buildEth(stack, stack.AccountManager()) 176 if err != nil { 177 return nil, err 178 } 179 180 backend, err := eth.New(stack, ethCfg) 181 if err != nil { 182 return nil, err 183 } 184 185 srv.backend = backend 186 } else { 187 // register the ethereum backend (with temporary created account manager) 188 ethCfg, err := config.buildEth(stack, accountManager) 189 if err != nil { 190 return nil, err 191 } 192 193 backend, err := eth.New(stack, ethCfg) 194 if err != nil { 195 return nil, err 196 } 197 198 srv.backend = backend 199 200 // authorize only if mining or in developer mode 201 if config.Sealer.Enabled || config.Developer.Enabled { 202 // get the etherbase 203 eb, err := srv.backend.Etherbase() 204 if err != nil { 205 log.Error("Cannot start mining without etherbase", "err", err) 206 207 return nil, fmt.Errorf("etherbase missing: %v", err) 208 } 209 210 // Authorize the clique consensus (if chosen) to sign using wallet signer 211 var cli *clique.Clique 212 if c, ok := srv.backend.Engine().(*clique.Clique); ok { 213 cli = c 214 } else if cl, ok := srv.backend.Engine().(*beacon.Beacon); ok { 215 if c, ok := cl.InnerEngine().(*clique.Clique); ok { 216 cli = c 217 } 218 } 219 if cli != nil { 220 wallet, err := accountManager.Find(accounts.Account{Address: eb}) 221 if wallet == nil || err != nil { 222 log.Error("Etherbase account unavailable locally", "err", err) 223 return nil, fmt.Errorf("signer missing: %v", err) 224 } 225 226 cli.Authorize(eb, wallet.SignData) 227 authorized = true 228 } 229 230 // Authorize the bor consensus (if chosen) to sign using wallet signer 231 if bor, ok := srv.backend.Engine().(*bor.Bor); ok { 232 wallet, err := accountManager.Find(accounts.Account{Address: eb}) 233 if wallet == nil || err != nil { 234 log.Error("Etherbase account unavailable locally", "err", err) 235 return nil, fmt.Errorf("signer missing: %v", err) 236 } 237 238 bor.Authorize(eb, wallet.SignData) 239 authorized = true 240 } 241 } 242 } 243 244 // set the auth status in backend 245 srv.backend.SetAuthorized(authorized) 246 247 // debug tracing is enabled by default 248 stack.RegisterAPIs(tracers.APIs(srv.backend.APIBackend)) 249 srv.tracerAPI = tracers.NewAPI(srv.backend.APIBackend) 250 251 // graphql is started from another place 252 if config.JsonRPC.Graphql.Enabled { 253 if err := graphql.New(stack, srv.backend.APIBackend, config.JsonRPC.Graphql.Cors, config.JsonRPC.Graphql.VHost); err != nil { 254 return nil, fmt.Errorf("failed to register the GraphQL service: %v", err) 255 } 256 } 257 258 // register ethash service 259 if config.Ethstats != "" { 260 if err := ethstats.New(stack, srv.backend.APIBackend, srv.backend.Engine(), config.Ethstats); err != nil { 261 return nil, err 262 } 263 } 264 265 // sealing (if enabled) or in dev mode 266 if config.Sealer.Enabled || config.Developer.Enabled { 267 if err := srv.backend.StartMining(1); err != nil { 268 return nil, err 269 } 270 } 271 272 if err := srv.setupMetrics(config.Telemetry, config.Identity); err != nil { 273 return nil, err 274 } 275 276 // Set the node instance 277 srv.node = stack 278 279 // start the node 280 if err := srv.node.Start(); err != nil { 281 return nil, err 282 } 283 284 return srv, nil 285 } 286 287 func (s *Server) Stop() { 288 if s.node != nil { 289 s.node.Close() 290 } 291 292 if s.grpcServer != nil { 293 s.grpcServer.Stop() 294 } 295 296 // shutdown the tracer 297 if s.tracer != nil { 298 if err := s.tracer.Shutdown(context.Background()); err != nil { 299 log.Error("Failed to shutdown open telemetry tracer") 300 } 301 } 302 } 303 304 func (s *Server) setupMetrics(config *TelemetryConfig, serviceName string) error { 305 // Check the global metrics if they're matching with the provided config 306 if metrics.Enabled != config.Enabled || metrics.EnabledExpensive != config.Expensive { 307 log.Warn( 308 "Metric misconfiguration, some of them might not be visible", 309 "metrics", metrics.Enabled, 310 "config.metrics", config.Enabled, 311 "expensive", metrics.EnabledExpensive, 312 "config.expensive", config.Expensive, 313 ) 314 } 315 316 // Update the values anyways (for services which don't need immediate attention) 317 metrics.Enabled = config.Enabled 318 metrics.EnabledExpensive = config.Expensive 319 320 if !metrics.Enabled { 321 // metrics are disabled, do not set up any sink 322 return nil 323 } 324 325 log.Info("Enabling metrics collection") 326 327 if metrics.EnabledExpensive { 328 log.Info("Enabling expensive metrics collection") 329 } 330 331 // influxdb 332 if v1Enabled, v2Enabled := config.InfluxDB.V1Enabled, config.InfluxDB.V2Enabled; v1Enabled || v2Enabled { 333 if v1Enabled && v2Enabled { 334 return fmt.Errorf("both influx v1 and influx v2 cannot be enabled") 335 } 336 337 cfg := config.InfluxDB 338 tags := cfg.Tags 339 endpoint := cfg.Endpoint 340 341 if v1Enabled { 342 log.Info("Enabling metrics export to InfluxDB (v1)") 343 344 go influxdb.InfluxDBWithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, cfg.Database, cfg.Username, cfg.Password, "geth.", tags) 345 } 346 if v2Enabled { 347 log.Info("Enabling metrics export to InfluxDB (v2)") 348 349 go influxdb.InfluxDBV2WithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, cfg.Token, cfg.Bucket, cfg.Organization, "geth.", tags) 350 } 351 } 352 353 // Start system runtime metrics collection 354 go metrics.CollectProcessMetrics(3 * time.Second) 355 356 if config.PrometheusAddr != "" { 357 358 prometheusMux := http.NewServeMux() 359 360 prometheusMux.Handle("/debug/metrics/prometheus", prometheus.Handler(metrics.DefaultRegistry)) 361 362 promServer := &http.Server{ 363 Addr: config.PrometheusAddr, 364 Handler: prometheusMux, 365 } 366 367 go func() { 368 if err := promServer.ListenAndServe(); err != nil { 369 log.Error("Failure in running Prometheus server", "err", err) 370 } 371 }() 372 373 log.Info("Enabling metrics export to prometheus", "path", fmt.Sprintf("http://%s/debug/metrics/prometheus", config.PrometheusAddr)) 374 375 } 376 377 if config.OpenCollectorEndpoint != "" { 378 // setup open collector tracer 379 ctx := context.Background() 380 381 res, err := resource.New(ctx, 382 resource.WithAttributes( 383 // the service name used to display traces in backends 384 semconv.ServiceNameKey.String(serviceName), 385 ), 386 ) 387 if err != nil { 388 return fmt.Errorf("failed to create open telemetry resource for service: %v", err) 389 } 390 391 // Set up a trace exporter 392 traceExporter, err := otlptracegrpc.New( 393 ctx, 394 otlptracegrpc.WithInsecure(), 395 otlptracegrpc.WithEndpoint(config.OpenCollectorEndpoint), 396 ) 397 if err != nil { 398 return fmt.Errorf("failed to create open telemetry tracer exporter for service: %v", err) 399 } 400 401 // Register the trace exporter with a TracerProvider, using a batch 402 // span processor to aggregate spans before export. 403 bsp := sdktrace.NewBatchSpanProcessor(traceExporter) 404 tracerProvider := sdktrace.NewTracerProvider( 405 sdktrace.WithSampler(sdktrace.AlwaysSample()), 406 sdktrace.WithResource(res), 407 sdktrace.WithSpanProcessor(bsp), 408 ) 409 otel.SetTracerProvider(tracerProvider) 410 411 // set global propagator to tracecontext (the default is no-op). 412 otel.SetTextMapPropagator(propagation.TraceContext{}) 413 414 // set the tracer 415 s.tracer = tracerProvider 416 417 log.Info("Open collector tracing started", "address", config.OpenCollectorEndpoint) 418 } 419 420 return nil 421 } 422 423 func (s *Server) gRPCServerByAddress(addr string) error { 424 lis, err := net.Listen("tcp", addr) 425 if err != nil { 426 return err 427 } 428 429 return s.gRPCServerByListener(lis) 430 } 431 432 func (s *Server) gRPCServerByListener(listener net.Listener) error { 433 s.grpcServer = grpc.NewServer(s.withLoggingUnaryInterceptor()) 434 proto.RegisterBorServer(s.grpcServer, s) 435 436 go func() { 437 if err := s.grpcServer.Serve(listener); err != nil { 438 log.Error("failed to serve grpc server", "err", err) 439 } 440 }() 441 442 log.Info("GRPC Server started", "addr", listener.Addr()) 443 444 return nil 445 } 446 447 func (s *Server) withLoggingUnaryInterceptor() grpc.ServerOption { 448 return grpc.UnaryInterceptor(s.loggingServerInterceptor) 449 } 450 451 func (s *Server) loggingServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 452 start := time.Now() 453 h, err := handler(ctx, req) 454 455 log.Trace("Request", "method", info.FullMethod, "duration", time.Since(start), "error", err) 456 457 return h, err 458 } 459 460 func setupLogger(logLevel string, loggingInfo LoggingConfig) { 461 var ostream log.Handler 462 463 output := io.Writer(os.Stderr) 464 465 if loggingInfo.Json { 466 ostream = log.StreamHandler(output, log.JSONFormat()) 467 } else { 468 usecolor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb" 469 if usecolor { 470 output = colorable.NewColorableStderr() 471 } 472 ostream = log.StreamHandler(output, log.TerminalFormat(usecolor)) 473 } 474 475 glogger.SetHandler(ostream) 476 477 // logging 478 lvl, err := log.LvlFromString(strings.ToLower(logLevel)) 479 if err == nil { 480 glogger.Verbosity(lvl) 481 } else { 482 glogger.Verbosity(log.LvlInfo) 483 } 484 485 if loggingInfo.Vmodule != "" { 486 if err := glogger.Vmodule(loggingInfo.Vmodule); err != nil { 487 log.Error("failed to set Vmodule", "err", err) 488 } 489 } 490 491 log.PrintOrigins(loggingInfo.Debug) 492 493 if loggingInfo.Backtrace != "" { 494 if err := glogger.BacktraceAt(loggingInfo.Backtrace); err != nil { 495 log.Error("failed to set BacktraceAt", "err", err) 496 } 497 } 498 499 log.Root().SetHandler(glogger) 500 } 501 502 func (s *Server) GetLatestBlockNumber() *big.Int { 503 return s.backend.BlockChain().CurrentBlock().Number() 504 } 505 506 func (s *Server) GetGrpcAddr() string { 507 return s.config.GRPC.Addr[1:] 508 }