diff --git a/CHANGELOG.md b/CHANGELOG.md index cce684e237..b60368fc45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (ledger) [#1277](https://github.com/evmos/ethermint/pull/1277) Add Ledger preprocessing transaction hook for EIP-712-signed Cosmos payloads. * (rpc) [#1296](https://github.com/evmos/ethermint/pull/1296) Add RPC Backend unit tests. * (rpc) [#1352](https://github.com/evmos/ethermint/pull/1352) Make the grpc queries run concurrently, don't block the consensus state machine. +* (cli) [#1360](https://github.com/evmos/ethermint/pull/1360) Introduce a new `grpc-only` flag, such that when enabled, will start the node in a query-only mode. Note, gRPC MUST be enabled with this flag. * (rpc) [#1378](https://github.com/evmos/ethermint/pull/1378) Add support for EVM RPC metrics ### Bug Fixes diff --git a/server/flags/flags.go b/server/flags/flags.go index 1fd0d6af82..475e363c86 100644 --- a/server/flags/flags.go +++ b/server/flags/flags.go @@ -20,6 +20,7 @@ const ( // GRPC-related flags. const ( + GRPCOnly = "grpc-only" GRPCEnable = "grpc.enable" GRPCAddress = "grpc.address" GRPCWebEnable = "grpc-web.enable" diff --git a/server/start.go b/server/start.go index 85b3b31a9c..a476f5ffb3 100644 --- a/server/start.go +++ b/server/start.go @@ -150,6 +150,7 @@ which accepts a path for the resulting pprof file. cmd.Flags().Uint64(server.FlagMinRetainBlocks, 0, "Minimum block height offset during ABCI commit to prune Tendermint blocks") cmd.Flags().String(srvflags.AppDBBackend, "", "The type of database for application and snapshots databases") + cmd.Flags().Bool(srvflags.GRPCOnly, false, "Start the node in gRPC query only mode without Tendermint process") cmd.Flags().Bool(srvflags.GRPCEnable, true, "Define if the gRPC server should be enabled") cmd.Flags().String(srvflags.GRPCAddress, serverconfig.DefaultGRPCAddress, "the gRPC server address to listen on") cmd.Flags().Bool(srvflags.GRPCWebEnable, true, "Define if the gRPC-Web server should be enabled. (Note: gRPC must also be enabled.)") @@ -158,7 +159,7 @@ which accepts a path for the resulting pprof file. cmd.Flags().Bool(srvflags.RPCEnable, false, "Defines if Cosmos-sdk REST server should be enabled") cmd.Flags().Bool(srvflags.EnabledUnsafeCors, false, "Defines if CORS should be enabled (unsafe - use it at your own risk)") - cmd.Flags().Bool(srvflags.JSONRPCEnable, true, "Define if the gRPC server should be enabled") + cmd.Flags().Bool(srvflags.JSONRPCEnable, true, "Define if the JSON-RPC server should be enabled") cmd.Flags().StringSlice(srvflags.JSONRPCAPI, config.GetDefaultAPINamespaces(), "Defines a list of JSON-RPC namespaces that should be enabled") cmd.Flags().String(srvflags.JSONRPCAddress, config.DefaultJSONRPCAddress, "the JSON-RPC server address to listen on") cmd.Flags().String(srvflags.JSONWsAddress, config.DefaultJSONRPCWsAddress, "the JSON-RPC WS server address to listen on") @@ -239,8 +240,6 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty cfg := ctx.Config home := cfg.RootDir logger := ctx.Logger - var cpuProfileCleanup func() error - if cpuProfile := ctx.Viper.GetString(srvflags.CPUProfile); cpuProfile != "" { fp, err := ethdebug.ExpandHome(cpuProfile) if err != nil { @@ -257,15 +256,13 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty return err } - cpuProfileCleanup = func() error { + defer func() { ctx.Logger.Info("stopping CPU profiler", "profile", cpuProfile) pprof.StopCPUProfile() if err := f.Close(); err != nil { logger.Error("failed to close CPU profiler file", "error", err.Error()) - return err } - return nil - } + }() } traceWriterFile := ctx.Viper.GetString(srvflags.TraceStore) @@ -313,30 +310,47 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty } genDocProvider := node.DefaultGenesisDocProviderFunc(cfg) - tmNode, err := node.NewNode( - cfg, - pvm.LoadOrGenFilePV(cfg.PrivValidatorKeyFile(), cfg.PrivValidatorStateFile()), - nodeKey, - proxy.NewLocalClientCreator(app), - genDocProvider, - node.DefaultDBProvider, - node.DefaultMetricsProvider(cfg.Instrumentation), - ctx.Logger.With("server", "node"), + var ( + tmNode *node.Node + gRPCOnly = ctx.Viper.GetBool(srvflags.GRPCOnly) ) - if err != nil { - logger.Error("failed init node", "error", err.Error()) - return err - } + if gRPCOnly { + ctx.Logger.Info("starting node in query only mode; Tendermint is disabled") + config.GRPC.Enable = true + config.JSONRPC.EnableIndexer = false + } else { + tmNode, err = node.NewNode( + cfg, + pvm.LoadOrGenFilePV(cfg.PrivValidatorKeyFile(), cfg.PrivValidatorStateFile()), + nodeKey, + proxy.NewLocalClientCreator(app), + genDocProvider, + node.DefaultDBProvider, + node.DefaultMetricsProvider(cfg.Instrumentation), + ctx.Logger.With("server", "node"), + ) + if err != nil { + logger.Error("failed init node", "error", err.Error()) + return err + } - if err := tmNode.Start(); err != nil { - logger.Error("failed start tendermint server", "error", err.Error()) - return err + if err := tmNode.Start(); err != nil { + logger.Error("failed start tendermint server", "error", err.Error()) + return err + } + + defer func() { + if tmNode.IsRunning() { + _ = tmNode.Stop() + } + logger.Info("Bye!") + }() } // Add the tx service to the gRPC router. We only need to register this // service if API or gRPC or JSONRPC is enabled, and avoid doing so in the general // case, because it spawns a new local tendermint RPC client. - if config.API.Enable || config.GRPC.Enable || config.JSONRPC.Enable || config.JSONRPC.EnableIndexer { + if (config.API.Enable || config.GRPC.Enable || config.JSONRPC.Enable || config.JSONRPC.EnableIndexer) && tmNode != nil { clientCtx = clientCtx.WithClient(local.New(tmNode)) app.RegisterTxService(clientCtx) @@ -429,7 +443,6 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty apiSrv = api.New(clientCtx, ctx.Logger.With("server", "api")) app.RegisterAPIRoutes(apiSrv, config.API) errCh := make(chan error) - go func() { if err := apiSrv.Start(config.Config); err != nil { errCh <- err @@ -441,6 +454,7 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty return err case <-time.After(types.ServerStartTime): // assume server started successfully } + defer apiSrv.Close() } var ( @@ -452,13 +466,60 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty if err != nil { return err } + defer grpcSrv.Stop() if config.GRPCWeb.Enable { grpcWebSrv, err = servergrpc.StartGRPCWeb(grpcSrv, config.Config) if err != nil { ctx.Logger.Error("failed to start grpc-web http server", "error", err) return err } + defer func() { + if err := grpcWebSrv.Close(); err != nil { + logger.Error("failed to close the grpcWebSrc", "error", err.Error()) + } + }() + } + } + + var ( + httpSrv *http.Server + httpSrvDone chan struct{} + ) + + if config.JSONRPC.Enable { + genDoc, err := genDocProvider() + if err != nil { + return err } + + clientCtx := clientCtx.WithChainID(genDoc.ChainID) + + tmEndpoint := "/websocket" + tmRPCAddr := cfg.RPC.ListenAddress + httpSrv, httpSrvDone, err = StartJSONRPC(ctx, clientCtx, tmRPCAddr, tmEndpoint, &config, idxer) + if err != nil { + return err + } + defer func() { + shutdownCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) + defer cancelFn() + if err := httpSrv.Shutdown(shutdownCtx); err != nil { + logger.Error("HTTP server shutdown produced a warning", "error", err.Error()) + } else { + logger.Info("HTTP server shut down, waiting 5 sec") + select { + case <-time.Tick(5 * time.Second): + case <-httpSrvDone: + } + } + }() + } + + // At this point it is safe to block the process if we're in query only mode as + // we do not need to start Rosetta or handle any Tendermint related processes. + if gRPCOnly { + // wait for signal capture and gracefully return + return server.WaitForQuitSignals() } var rosettaSrv crgserver.Server @@ -496,68 +557,6 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty case <-time.After(types.ServerStartTime): // assume server started successfully } } - - var ( - httpSrv *http.Server - httpSrvDone chan struct{} - ) - - if config.JSONRPC.Enable { - genDoc, err := genDocProvider() - if err != nil { - return err - } - - clientCtx := clientCtx.WithChainID(genDoc.ChainID) - - tmEndpoint := "/websocket" - tmRPCAddr := cfg.RPC.ListenAddress - httpSrv, httpSrvDone, err = StartJSONRPC(ctx, clientCtx, tmRPCAddr, tmEndpoint, &config, idxer) - if err != nil { - return err - } - } - - defer func() { - if tmNode.IsRunning() { - _ = tmNode.Stop() - } - - if cpuProfileCleanup != nil { - _ = cpuProfileCleanup() - } - - if apiSrv != nil { - _ = apiSrv.Close() - } - - if grpcSrv != nil { - grpcSrv.Stop() - if grpcWebSrv != nil { - if err := grpcWebSrv.Close(); err != nil { - logger.Error("failed to close the grpcWebSrc", "error", err.Error()) - } - } - } - - if httpSrv != nil { - shutdownCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) - defer cancelFn() - - if err := httpSrv.Shutdown(shutdownCtx); err != nil { - logger.Error("HTTP server shutdown produced a warning", "error", err.Error()) - } else { - logger.Info("HTTP server shut down, waiting 5 sec") - select { - case <-time.Tick(5 * time.Second): - case <-httpSrvDone: - } - } - } - - logger.Info("Bye!") - }() - // Wait for SIGINT or SIGTERM signal return server.WaitForQuitSignals() }