From 94ae72dc1b8dc4f5c08cc033d074bf715829a8e0 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Fri, 13 Oct 2023 11:15:51 +0200 Subject: [PATCH 1/9] Start grpc server and register services when starting in standalone mode --- server/start.go | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/server/start.go b/server/start.go index 061b736cf6a5..3b4f1da2bd1d 100644 --- a/server/start.go +++ b/server/start.go @@ -142,7 +142,7 @@ is performed. Note, when enabled, gRPC will also be automatically enabled. if !withTM { serverCtx.Logger.Info("starting ABCI without Tendermint") return wrapCPUProfile(serverCtx, func() error { - return startStandAlone(serverCtx, appCreator) + return startStandAlone(serverCtx, clientCtx, appCreator) }) } @@ -206,7 +206,7 @@ is performed. Note, when enabled, gRPC will also be automatically enabled. return cmd } -func startStandAlone(ctx *Context, appCreator types.AppCreator) error { +func startStandAlone(ctx *Context, clientCtx client.Context, appCreator types.AppCreator) error { addr := ctx.Viper.GetString(flagAddress) transport := ctx.Viper.GetString(flagTransport) home := ctx.Viper.GetString(flags.FlagHome) @@ -229,11 +229,45 @@ func startStandAlone(ctx *Context, appCreator types.AppCreator) error { return err } + // Add the tx service to the gRPC router. We only need to register this + // service if gRPC is enabled + if config.GRPC.Enable { + // use the clientCtx provided to start command + app.RegisterTxService(clientCtx) + app.RegisterTendermintService(clientCtx) + app.RegisterNodeService(clientCtx) + } + _, err = startTelemetry(config) if err != nil { return err } + var ( + grpcSrv *grpc.Server + grpcWebSrv *http.Server + ) + + if config.GRPC.Enable { + grpcSrv, err = servergrpc.StartGRPCServer(clientCtx, app, config.GRPC) + if err != nil { + return err + } + defer grpcSrv.Stop() + if config.GRPCWeb.Enable { + grpcWebSrv, err = servergrpc.StartGRPCWeb(grpcSrv, config) + if err != nil { + ctx.Logger.Error("failed to start grpc-web http server: ", err) + return err + } + defer func() { + if err := grpcWebSrv.Close(); err != nil { + ctx.Logger.Error("failed to close grpc-web http server: ", err) + } + }() + } + } + svr, err := server.NewServer(addr, transport, app) if err != nil { return fmt.Errorf("error creating listener: %v", err) From 07a7a4e3a1c1bb66065d15b0d331bdb923eb4641 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Fri, 13 Oct 2023 13:40:46 +0200 Subject: [PATCH 2/9] Create new client from context in standalone mode --- server/start.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/server/start.go b/server/start.go index 3b4f1da2bd1d..b00a9e7a9100 100644 --- a/server/start.go +++ b/server/start.go @@ -35,6 +35,8 @@ import ( "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/mempool" + + rpchttp "github.com/cometbft/cometbft/rpc/client/http" ) const ( @@ -232,6 +234,14 @@ func startStandAlone(ctx *Context, clientCtx client.Context, appCreator types.Ap // Add the tx service to the gRPC router. We only need to register this // service if gRPC is enabled if config.GRPC.Enable { + // create tendermint client + rpcclient, err := rpchttp.New(ctx.Config.RPC.ListenAddress, "/websocket") + if err != nil { + return err + } + // re-assign for making the client available below + // do not use := to avoid shadowing clientCtx + clientCtx = clientCtx.WithClient(rpcclient) // use the clientCtx provided to start command app.RegisterTxService(clientCtx) app.RegisterTendermintService(clientCtx) From 3f90a1de79d538f3acfc53dc5045f6c321183b0f Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Fri, 13 Oct 2023 13:42:27 +0200 Subject: [PATCH 3/9] Add comment to explain listen address --- server/start.go | 1 + 1 file changed, 1 insertion(+) diff --git a/server/start.go b/server/start.go index b00a9e7a9100..e1da6e67dca5 100644 --- a/server/start.go +++ b/server/start.go @@ -235,6 +235,7 @@ func startStandAlone(ctx *Context, clientCtx client.Context, appCreator types.Ap // service if gRPC is enabled if config.GRPC.Enable { // create tendermint client + // assumes the rpc listen address is where tendermint has its rpc server rpcclient, err := rpchttp.New(ctx.Config.RPC.ListenAddress, "/websocket") if err != nil { return err From 2a72ca086539b5b80d7bd7295b00a8aeecc0d442 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Fri, 13 Oct 2023 16:50:57 +0200 Subject: [PATCH 4/9] Add paragraph about standalone CometBFT to README --- docs/docs/run-node/01-run-node.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/docs/run-node/01-run-node.md b/docs/docs/run-node/01-run-node.md index cce2bed5fa65..fdda4411c862 100644 --- a/docs/docs/run-node/01-run-node.md +++ b/docs/docs/run-node/01-run-node.md @@ -136,6 +136,13 @@ The previous command allow you to run a single node. This is enough for the next The naive way would be to run the same commands again in separate terminal windows. This is possible, however in the Cosmos SDK, we leverage the power of [Docker Compose](https://docs.docker.com/compose/) to run a localnet. If you need inspiration on how to set up your own localnet with Docker Compose, you can have a look at the Cosmos SDK's [`docker-compose.yml`](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc1/docker-compose.yml). +### Standalone App/CometBFT + +By default, the Cosmos SDK runs CometBFT in-process with the application +If you want to run the application and CometBFT in separate processes, +start the application with the `--with-comet=false` flag +and set `rpc.laddr` in `config.toml` to the CometBFT node's RPC address. + ## Logging Logging provides a way to see what is going on with a node. By default the info level is set. This is a global level and all info logs will be outputted to the terminal. If you would like to filter specific logs to the terminal instead of all, then setting `module:log_level` is how this can work. From 18bdec5ec32f81c57a797349cac0e763a5e80e14 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Mon, 16 Oct 2023 09:10:55 +0200 Subject: [PATCH 5/9] Add changelog entry for standalone gRPC --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02203d95b8b8..228e58586c63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,10 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## [Unreleased] +### Features + +* (server) [#18110](https://github.com/cosmos/cosmos-sdk/pull/18110) Start gRPC server in standalone mode + ## [v0.47.5](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.5) - 2023-09-01 ### Features From b56e95ea93c4e2cbd4e8afab9da7866293335394 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Mon, 16 Oct 2023 10:15:11 +0200 Subject: [PATCH 6/9] Change exit outside of deferred func to error return --- server/start.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/start.go b/server/start.go index e1da6e67dca5..9633c9b25c18 100644 --- a/server/start.go +++ b/server/start.go @@ -289,7 +289,7 @@ func startStandAlone(ctx *Context, clientCtx client.Context, appCreator types.Ap err = svr.Start() if err != nil { fmt.Println(err.Error()) - os.Exit(1) + return err } defer func() { From 4ef3398d775ac695d587bdd3da87e5b1bba44349 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Mon, 16 Oct 2023 15:02:41 +0200 Subject: [PATCH 7/9] Start the API server in standalone mode --- server/start.go | 160 ++++++++++++++++++++++++++---------------------- 1 file changed, 88 insertions(+), 72 deletions(-) diff --git a/server/start.go b/server/start.go index 9633c9b25c18..cee95b65d438 100644 --- a/server/start.go +++ b/server/start.go @@ -249,7 +249,16 @@ func startStandAlone(ctx *Context, clientCtx client.Context, appCreator types.Ap app.RegisterNodeService(clientCtx) } - _, err = startTelemetry(config) + metrics, err := startTelemetry(config) + if err != nil { + return err + } + + cfg := ctx.Config + + genDocProvider := node.DefaultGenesisDocProviderFunc(cfg) + + apiSrv, err := startAPIserver(config, genDocProvider, clientCtx, home, ctx, app, metrics) if err != nil { return err } @@ -293,14 +302,12 @@ func startStandAlone(ctx *Context, clientCtx client.Context, appCreator types.Ap } defer func() { - if err = svr.Stop(); err != nil { - fmt.Println(err.Error()) - os.Exit(1) - } + _ = svr.Stop() + + _ = app.Close() - if err = app.Close(); err != nil { - fmt.Println(err.Error()) - os.Exit(1) + if apiSrv != nil { + _ = apiSrv.Close() } }() @@ -399,70 +406,9 @@ func startInProcess(ctx *Context, clientCtx client.Context, appCreator types.App return err } - var apiSrv *api.Server - if config.API.Enable { - genDoc, err := genDocProvider() - if err != nil { - return err - } - - clientCtx := clientCtx.WithHomeDir(home).WithChainID(genDoc.ChainID) - - if config.GRPC.Enable { - _, port, err := net.SplitHostPort(config.GRPC.Address) - if err != nil { - return err - } - - maxSendMsgSize := config.GRPC.MaxSendMsgSize - if maxSendMsgSize == 0 { - maxSendMsgSize = serverconfig.DefaultGRPCMaxSendMsgSize - } - - maxRecvMsgSize := config.GRPC.MaxRecvMsgSize - if maxRecvMsgSize == 0 { - maxRecvMsgSize = serverconfig.DefaultGRPCMaxRecvMsgSize - } - - grpcAddress := fmt.Sprintf("127.0.0.1:%s", port) - - // If grpc is enabled, configure grpc client for grpc gateway. - grpcClient, err := grpc.Dial( - grpcAddress, - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithDefaultCallOptions( - grpc.ForceCodec(codec.NewProtoCodec(clientCtx.InterfaceRegistry).GRPCCodec()), - grpc.MaxCallRecvMsgSize(maxRecvMsgSize), - grpc.MaxCallSendMsgSize(maxSendMsgSize), - ), - ) - if err != nil { - return err - } - - clientCtx = clientCtx.WithGRPCClient(grpcClient) - ctx.Logger.Debug("grpc client assigned to client context", "target", grpcAddress) - } - - apiSrv = api.New(clientCtx, ctx.Logger.With("module", "api-server")) - app.RegisterAPIRoutes(apiSrv, config.API) - if config.Telemetry.Enabled { - apiSrv.SetTelemetry(metrics) - } - errCh := make(chan error) - - go func() { - if err := apiSrv.Start(config); err != nil { - errCh <- err - } - }() - - select { - case err := <-errCh: - return err - - case <-time.After(types.ServerStartTime): // assume server started successfully - } + apiSrv, err := startAPIserver(config, genDocProvider, clientCtx, home, ctx, app, metrics) + if err != nil { + return err } var ( @@ -568,6 +514,76 @@ func startInProcess(ctx *Context, clientCtx client.Context, appCreator types.App return WaitForQuitSignals() } +func startAPIserver(config serverconfig.Config, genDocProvider node.GenesisDocProvider, clientCtx client.Context, home string, ctx *Context, app types.Application, metrics *telemetry.Metrics) (*api.Server, error) { + // If grpc is enabled, configure grpc client for grpc gateway. + // assume server started successfully + var apiSrv *api.Server + if config.API.Enable { + genDoc, err := genDocProvider() + if err != nil { + return nil, err + } + + clientCtx := clientCtx.WithHomeDir(home).WithChainID(genDoc.ChainID) + + if config.GRPC.Enable { + _, port, err := net.SplitHostPort(config.GRPC.Address) + if err != nil { + return nil, err + } + + maxSendMsgSize := config.GRPC.MaxSendMsgSize + if maxSendMsgSize == 0 { + maxSendMsgSize = serverconfig.DefaultGRPCMaxSendMsgSize + } + + maxRecvMsgSize := config.GRPC.MaxRecvMsgSize + if maxRecvMsgSize == 0 { + maxRecvMsgSize = serverconfig.DefaultGRPCMaxRecvMsgSize + } + + grpcAddress := fmt.Sprintf("127.0.0.1:%s", port) + + grpcClient, err := grpc.Dial( + grpcAddress, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithDefaultCallOptions( + grpc.ForceCodec(codec.NewProtoCodec(clientCtx.InterfaceRegistry).GRPCCodec()), + grpc.MaxCallRecvMsgSize(maxRecvMsgSize), + grpc.MaxCallSendMsgSize(maxSendMsgSize), + ), + ) + if err != nil { + return nil, err + } + + clientCtx = clientCtx.WithGRPCClient(grpcClient) + ctx.Logger.Debug("grpc client assigned to client context", "target", grpcAddress) + } + + apiSrv = api.New(clientCtx, ctx.Logger.With("module", "api-server")) + app.RegisterAPIRoutes(apiSrv, config.API) + if config.Telemetry.Enabled { + apiSrv.SetTelemetry(metrics) + } + errCh := make(chan error) + + go func() { + if err := apiSrv.Start(config); err != nil { + errCh <- err + } + }() + + select { + case err := <-errCh: + return nil, err + + case <-time.After(types.ServerStartTime): + } + } + return apiSrv, nil +} + func startTelemetry(cfg serverconfig.Config) (*telemetry.Metrics, error) { if !cfg.Telemetry.Enabled { return nil, nil From 8b49a986a77ff1e891624d8902fc68c9475a4ec5 Mon Sep 17 00:00:00 2001 From: Philip Offtermatt Date: Wed, 18 Oct 2023 12:40:26 +0200 Subject: [PATCH 8/9] Also register services if API server is started --- server/start.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/server/start.go b/server/start.go index cee95b65d438..31deb30745dc 100644 --- a/server/start.go +++ b/server/start.go @@ -232,8 +232,9 @@ func startStandAlone(ctx *Context, clientCtx client.Context, appCreator types.Ap } // Add the tx service to the gRPC router. We only need to register this - // service if gRPC is enabled - if config.GRPC.Enable { + // service if API or gRPC is enabled, and avoid doing so in the general + // case, because it spawns a new local CometBFT RPC client. + if config.API.Enable || config.GRPC.Enable { // create tendermint client // assumes the rpc listen address is where tendermint has its rpc server rpcclient, err := rpchttp.New(ctx.Config.RPC.ListenAddress, "/websocket") From f4cedc19881d9b1f7cfbe6acab93fda64d7617c1 Mon Sep 17 00:00:00 2001 From: Marko Date: Thu, 19 Oct 2023 10:22:20 +0200 Subject: [PATCH 9/9] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72c82ebd0b18..b496daa03093 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,7 +39,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features -* (server) [#18110](https://github.com/cosmos/cosmos-sdk/pull/18110) Start gRPC server in standalone mode +* (server) [#18110](https://github.com/cosmos/cosmos-sdk/pull/18110) Start gRPC & API server in standalone mode ### Improvements