github.com/shrimpyuk/bor@v0.2.15-0.20220224151350-fb4ec6020bae/internal/cli/server/server.go (about)

     1  package server
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"net"
     8  	"net/http"
     9  	"os"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/ethereum/go-ethereum/accounts/keystore"
    14  	"github.com/ethereum/go-ethereum/eth"
    15  	"github.com/ethereum/go-ethereum/eth/tracers"
    16  	"github.com/ethereum/go-ethereum/ethstats"
    17  	"github.com/ethereum/go-ethereum/graphql"
    18  	"github.com/ethereum/go-ethereum/internal/cli/server/proto"
    19  	"github.com/ethereum/go-ethereum/log"
    20  	"github.com/ethereum/go-ethereum/metrics"
    21  	"github.com/ethereum/go-ethereum/metrics/influxdb"
    22  	"github.com/ethereum/go-ethereum/metrics/prometheus"
    23  	"github.com/ethereum/go-ethereum/node"
    24  	"github.com/mattn/go-colorable"
    25  	"github.com/mattn/go-isatty"
    26  	"go.opentelemetry.io/otel"
    27  	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
    28  	"go.opentelemetry.io/otel/propagation"
    29  	"go.opentelemetry.io/otel/sdk/resource"
    30  	sdktrace "go.opentelemetry.io/otel/sdk/trace"
    31  	semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
    32  	"google.golang.org/grpc"
    33  )
    34  
    35  type Server struct {
    36  	proto.UnimplementedBorServer
    37  	node       *node.Node
    38  	backend    *eth.Ethereum
    39  	grpcServer *grpc.Server
    40  	tracer     *sdktrace.TracerProvider
    41  	config     *Config
    42  }
    43  
    44  func NewServer(config *Config) (*Server, error) {
    45  	srv := &Server{
    46  		config: config,
    47  	}
    48  
    49  	// start the logger
    50  	setupLogger(config.LogLevel)
    51  
    52  	if err := srv.setupGRPCServer(config.GRPC.Addr); err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	// load the chain genesis
    57  	if err := config.loadChain(); err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	// create the node/stack
    62  	nodeCfg, err := config.buildNode()
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	stack, err := node.New(nodeCfg)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	srv.node = stack
    71  
    72  	// setup account manager (only keystore)
    73  	{
    74  		keydir := stack.KeyStoreDir()
    75  		n, p := keystore.StandardScryptN, keystore.StandardScryptP
    76  		if config.Accounts.UseLightweightKDF {
    77  			n, p = keystore.LightScryptN, keystore.LightScryptP
    78  		}
    79  		stack.AccountManager().AddBackend(keystore.NewKeyStore(keydir, n, p))
    80  	}
    81  
    82  	// register the ethereum backend
    83  	ethCfg, err := config.buildEth(stack)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  
    88  	backend, err := eth.New(stack, ethCfg)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	srv.backend = backend
    93  
    94  	// debug tracing is enabled by default
    95  	stack.RegisterAPIs(tracers.APIs(backend.APIBackend))
    96  
    97  	// graphql is started from another place
    98  	if config.JsonRPC.Graphql.Enabled {
    99  		if err := graphql.New(stack, backend.APIBackend, config.JsonRPC.Cors, config.JsonRPC.VHost); err != nil {
   100  			return nil, fmt.Errorf("failed to register the GraphQL service: %v", err)
   101  		}
   102  	}
   103  
   104  	// register ethash service
   105  	if config.Ethstats != "" {
   106  		if err := ethstats.New(stack, backend.APIBackend, backend.Engine(), config.Ethstats); err != nil {
   107  			return nil, err
   108  		}
   109  	}
   110  
   111  	// sealing (if enabled) or in dev mode
   112  	if config.Sealer.Enabled || config.Developer.Enabled {
   113  		if err := backend.StartMining(1); err != nil {
   114  			return nil, err
   115  		}
   116  	}
   117  
   118  	if err := srv.setupMetrics(config.Telemetry, config.Name); err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	// start the node
   123  	if err := srv.node.Start(); err != nil {
   124  		return nil, err
   125  	}
   126  	return srv, nil
   127  }
   128  
   129  func (s *Server) Stop() {
   130  	s.node.Close()
   131  
   132  	// shutdown the tracer
   133  	if s.tracer != nil {
   134  		if err := s.tracer.Shutdown(context.Background()); err != nil {
   135  			log.Error("Failed to shutdown open telemetry tracer")
   136  		}
   137  	}
   138  }
   139  
   140  func (s *Server) setupMetrics(config *TelemetryConfig, serviceName string) error {
   141  	metrics.Enabled = config.Enabled
   142  	metrics.EnabledExpensive = config.Expensive
   143  
   144  	if !metrics.Enabled {
   145  		// metrics are disabled, do not set up any sink
   146  		return nil
   147  	}
   148  
   149  	log.Info("Enabling metrics collection")
   150  
   151  	// influxdb
   152  	if v1Enabled, v2Enabled := config.InfluxDB.V1Enabled, config.InfluxDB.V2Enabled; v1Enabled || v2Enabled {
   153  		if v1Enabled && v2Enabled {
   154  			return fmt.Errorf("both influx v1 and influx v2 cannot be enabled")
   155  		}
   156  
   157  		cfg := config.InfluxDB
   158  		tags := cfg.Tags
   159  		endpoint := cfg.Endpoint
   160  
   161  		if v1Enabled {
   162  			log.Info("Enabling metrics export to InfluxDB (v1)")
   163  			go influxdb.InfluxDBWithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, cfg.Database, cfg.Username, cfg.Password, "geth.", tags)
   164  		}
   165  		if v2Enabled {
   166  			log.Info("Enabling metrics export to InfluxDB (v2)")
   167  			go influxdb.InfluxDBV2WithTags(metrics.DefaultRegistry, 10*time.Second, endpoint, cfg.Token, cfg.Bucket, cfg.Organization, "geth.", tags)
   168  		}
   169  	}
   170  
   171  	// Start system runtime metrics collection
   172  	go metrics.CollectProcessMetrics(3 * time.Second)
   173  
   174  	if config.PrometheusAddr != "" {
   175  
   176  		prometheusMux := http.NewServeMux()
   177  
   178  		prometheusMux.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
   179  			prometheus.Handler(metrics.DefaultRegistry)
   180  		})
   181  
   182  		promServer := &http.Server{
   183  			Addr:    config.PrometheusAddr,
   184  			Handler: prometheusMux,
   185  		}
   186  
   187  		go func() {
   188  			if err := promServer.ListenAndServe(); err != nil {
   189  				log.Error("Failure in running Prometheus server", "err", err)
   190  			}
   191  		}()
   192  
   193  	}
   194  
   195  	if config.OpenCollectorEndpoint != "" {
   196  		// setup open collector tracer
   197  		ctx := context.Background()
   198  
   199  		res, err := resource.New(ctx,
   200  			resource.WithAttributes(
   201  				// the service name used to display traces in backends
   202  				semconv.ServiceNameKey.String(serviceName),
   203  			),
   204  		)
   205  		if err != nil {
   206  			return fmt.Errorf("failed to create open telemetry resource for service: %v", err)
   207  		}
   208  
   209  		// Set up a trace exporter
   210  		traceExporter, err := otlptracegrpc.New(
   211  			ctx,
   212  			otlptracegrpc.WithInsecure(),
   213  			otlptracegrpc.WithEndpoint(config.OpenCollectorEndpoint),
   214  		)
   215  		if err != nil {
   216  			return fmt.Errorf("failed to create open telemetry tracer exporter for service: %v", err)
   217  		}
   218  
   219  		// Register the trace exporter with a TracerProvider, using a batch
   220  		// span processor to aggregate spans before export.
   221  		bsp := sdktrace.NewBatchSpanProcessor(traceExporter)
   222  		tracerProvider := sdktrace.NewTracerProvider(
   223  			sdktrace.WithSampler(sdktrace.AlwaysSample()),
   224  			sdktrace.WithResource(res),
   225  			sdktrace.WithSpanProcessor(bsp),
   226  		)
   227  		otel.SetTracerProvider(tracerProvider)
   228  
   229  		// set global propagator to tracecontext (the default is no-op).
   230  		otel.SetTextMapPropagator(propagation.TraceContext{})
   231  
   232  		// set the tracer
   233  		s.tracer = tracerProvider
   234  	}
   235  
   236  	return nil
   237  }
   238  
   239  func (s *Server) setupGRPCServer(addr string) error {
   240  	s.grpcServer = grpc.NewServer(s.withLoggingUnaryInterceptor())
   241  	proto.RegisterBorServer(s.grpcServer, s)
   242  
   243  	lis, err := net.Listen("tcp", addr)
   244  	if err != nil {
   245  		return err
   246  	}
   247  
   248  	go func() {
   249  		if err := s.grpcServer.Serve(lis); err != nil {
   250  			log.Error("failed to serve grpc server", "err", err)
   251  		}
   252  	}()
   253  
   254  	log.Info("GRPC Server started", "addr", addr)
   255  	return nil
   256  }
   257  
   258  func (s *Server) withLoggingUnaryInterceptor() grpc.ServerOption {
   259  	return grpc.UnaryInterceptor(s.loggingServerInterceptor)
   260  }
   261  
   262  func (s *Server) loggingServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
   263  	start := time.Now()
   264  	h, err := handler(ctx, req)
   265  	log.Trace("Request", "method", info.FullMethod, "duration", time.Since(start), "error", err)
   266  	return h, err
   267  }
   268  
   269  func setupLogger(logLevel string) {
   270  	output := io.Writer(os.Stderr)
   271  	usecolor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb"
   272  	if usecolor {
   273  		output = colorable.NewColorableStderr()
   274  	}
   275  	ostream := log.StreamHandler(output, log.TerminalFormat(usecolor))
   276  	glogger := log.NewGlogHandler(ostream)
   277  
   278  	// logging
   279  	lvl, err := log.LvlFromString(strings.ToLower(logLevel))
   280  	if err == nil {
   281  		glogger.Verbosity(lvl)
   282  	} else {
   283  		glogger.Verbosity(log.LvlInfo)
   284  	}
   285  	log.Root().SetHandler(glogger)
   286  }