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  }