Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(server): start grpc server and register services when starting in standalone mode (v0.47) #18109

4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 & API server in standalone mode

### Improvements

* (baseapp) [#17954](https://github.com/cosmos/cosmos-sdk/issues/17954) Add `Mempool()` method on `BaseApp` to allow access to the mempool.
Expand Down
7 changes: 7 additions & 0 deletions docs/docs/run-node/01-run-node.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
212 changes: 137 additions & 75 deletions server/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -142,7 +144,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)
})
}

Expand Down Expand Up @@ -206,7 +208,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)
Expand All @@ -229,11 +231,64 @@ func startStandAlone(ctx *Context, appCreator types.AppCreator) error {
return err
}

_, err = startTelemetry(config)
// Add the tx service to the gRPC router. We only need to register this
// 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")
julienrbrt marked this conversation as resolved.
Show resolved Hide resolved
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)
app.RegisterNodeService(clientCtx)
}

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
}

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)
Expand All @@ -244,18 +299,16 @@ func startStandAlone(ctx *Context, appCreator types.AppCreator) error {
err = svr.Start()
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
return err
}

defer func() {
if err = svr.Stop(); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
_ = svr.Stop()
p-offtermatt marked this conversation as resolved.
Show resolved Hide resolved

_ = app.Close()

if err = app.Close(); err != nil {
fmt.Println(err.Error())
os.Exit(1)
if apiSrv != nil {
_ = apiSrv.Close()
}
}()

Expand Down Expand Up @@ -354,70 +407,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 (
Expand Down Expand Up @@ -523,6 +515,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
Expand Down
Loading