server: sync start command with cosmos-sdk v0.43-rc2 (#341)

* sync start command with cosmos-sdk 0.43

Closes #340

- add grpc web and rosetta server
- add tx/tm services
- add standalone mode
- update cosmos-sdk to 0.43.0-rc2, which fixed a bug in service startup
- add EnableUnsafeCORS option to evm rpc server

* set keyring options in root cmd and use viper prefix

* fix linter and pr suggestions

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
yihuang 2021-07-23 20:31:59 +08:00 committed by GitHub
parent 3f136bf5d6
commit e61594e10a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 309 additions and 156 deletions

View File

@ -68,12 +68,14 @@ func DefaultEVMConfig() *EVMRPCConfig {
// EVMRPCConfig defines configuration for the EVM RPC server. // EVMRPCConfig defines configuration for the EVM RPC server.
type EVMRPCConfig struct { type EVMRPCConfig struct {
// RPCAddress defines the HTTP server to listen on
RPCAddress string `mapstructure:"address"`
// WsAddress defines the WebSocket server to listen on
WsAddress string `mapstructure:"ws-address"`
// Enable defines if the EVM RPC server should be enabled. // Enable defines if the EVM RPC server should be enabled.
Enable bool `mapstructure:"enable"` Enable bool `mapstructure:"enable"`
// Address defines the HTTP server to listen on // EnableUnsafeCORS defines if CORS should be enabled (unsafe - use it at your own risk)
RPCAddress string `mapstructure:"address"` EnableUnsafeCORS bool `mapstructure:"enable-unsafe-cors"`
// Address defines the WebSocket server to listen on
WsAddress string `mapstructure:"ws-address"`
} }
// Config defines the server's top level configuration. It includes the default app config // Config defines the server's top level configuration. It includes the default app config

View File

@ -35,10 +35,13 @@ import (
"github.com/tharsis/ethermint/app" "github.com/tharsis/ethermint/app"
ethermintclient "github.com/tharsis/ethermint/client" ethermintclient "github.com/tharsis/ethermint/client"
ethermintconfig "github.com/tharsis/ethermint/cmd/ethermintd/config" ethermintconfig "github.com/tharsis/ethermint/cmd/ethermintd/config"
"github.com/tharsis/ethermint/crypto/hd"
"github.com/tharsis/ethermint/encoding" "github.com/tharsis/ethermint/encoding"
"github.com/tharsis/ethermint/server" "github.com/tharsis/ethermint/server"
) )
const EnvPrefix = "ETHERMINT"
// NewRootCmd creates a new root command for simd. It is called once in the // NewRootCmd creates a new root command for simd. It is called once in the
// main function. // main function.
func NewRootCmd() (*cobra.Command, params.EncodingConfig) { func NewRootCmd() (*cobra.Command, params.EncodingConfig) {
@ -52,7 +55,8 @@ func NewRootCmd() (*cobra.Command, params.EncodingConfig) {
WithAccountRetriever(types.AccountRetriever{}). WithAccountRetriever(types.AccountRetriever{}).
WithBroadcastMode(flags.BroadcastBlock). WithBroadcastMode(flags.BroadcastBlock).
WithHomeDir(app.DefaultNodeHome). WithHomeDir(app.DefaultNodeHome).
WithViper("") // In simapp, we don't use any prefix for env variables. WithKeyringOptions(hd.EthSecp256k1Option()).
WithViper(EnvPrefix)
rootCmd := &cobra.Command{ rootCmd := &cobra.Command{
Use: "ethermintd", Use: "ethermintd",

2
go.mod
View File

@ -11,7 +11,7 @@ require (
github.com/bugsnag/bugsnag-go v2.1.0+incompatible // indirect github.com/bugsnag/bugsnag-go v2.1.0+incompatible // indirect
github.com/bugsnag/panicwrap v1.3.2 // indirect github.com/bugsnag/panicwrap v1.3.2 // indirect
github.com/cespare/cp v1.1.1 // indirect github.com/cespare/cp v1.1.1 // indirect
github.com/cosmos/cosmos-sdk v0.43.0-rc1 github.com/cosmos/cosmos-sdk v0.43.0-rc2
github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/go-bip39 v1.0.0
github.com/cosmos/ibc-go v1.0.0-beta1 github.com/cosmos/ibc-go v1.0.0-beta1
github.com/deckarep/golang-set v1.7.1 // indirect github.com/deckarep/golang-set v1.7.1 // indirect

4
go.sum
View File

@ -213,8 +213,8 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cosmos/cosmos-sdk v0.43.0-beta1/go.mod h1:rpCPaC3MnityU4Io4CDZqZB4GMtPqNeYXxPk8iRqmYM= github.com/cosmos/cosmos-sdk v0.43.0-beta1/go.mod h1:rpCPaC3MnityU4Io4CDZqZB4GMtPqNeYXxPk8iRqmYM=
github.com/cosmos/cosmos-sdk v0.43.0-rc1 h1:3QGgMqwLmzW+015P4ZEIQ+wRj7TrVU063D2QpHc2Syw= github.com/cosmos/cosmos-sdk v0.43.0-rc2 h1:9xww4vDnsNyZyF1p9U4zpc8tc5Ctx763WQWLccddP8A=
github.com/cosmos/cosmos-sdk v0.43.0-rc1/go.mod h1:ctcrTEAhei9s8O3KSNvL0dxe+fVQGp07QyRb/7H9JYE= github.com/cosmos/cosmos-sdk v0.43.0-rc2/go.mod h1:ctcrTEAhei9s8O3KSNvL0dxe+fVQGp07QyRb/7H9JYE=
github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y=
github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY=
github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw=

80
server/evmrpc.go Normal file
View File

@ -0,0 +1,80 @@
package server
import (
"net"
"net/http"
"time"
"github.com/gorilla/mux"
"github.com/rs/cors"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/server/types"
ethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/tharsis/ethermint/cmd/ethermintd/config"
"github.com/tharsis/ethermint/ethereum/rpc"
)
// StartEVMRPC start evm rpc server
func StartEVMRPC(ctx *server.Context, clientCtx client.Context, tmRPCAddr string, tmEndpoint string, config config.Config) (*http.Server, chan struct{}, error) {
tmWsClient := ConnectTmWS(tmRPCAddr, tmEndpoint)
rpcServer := ethrpc.NewServer()
apis := rpc.GetRPCAPIs(ctx, clientCtx, tmWsClient)
for _, api := range apis {
if err := rpcServer.RegisterName(api.Namespace, api.Service); err != nil {
ctx.Logger.Error(
"failed to register service in EVM RPC namespace",
"namespace", api.Namespace,
"service", api.Service,
)
return nil, nil, err
}
}
r := mux.NewRouter()
r.HandleFunc("/", rpcServer.ServeHTTP).Methods("POST")
handlerWithCors := cors.Default()
if config.EVMRPC.EnableUnsafeCORS {
handlerWithCors = cors.AllowAll()
}
httpSrv := &http.Server{
Addr: config.EVMRPC.RPCAddress,
Handler: handlerWithCors.Handler(r),
}
httpSrvDone := make(chan struct{}, 1)
errCh := make(chan error)
go func() {
ctx.Logger.Info("Starting EVM RPC server", "address", config.EVMRPC.RPCAddress)
if err := httpSrv.ListenAndServe(); err != nil {
if err == http.ErrServerClosed {
close(httpSrvDone)
return
}
ctx.Logger.Error("failed to start EVM RPC server", "error", err.Error())
errCh <- err
}
}()
select {
case err := <-errCh:
ctx.Logger.Error("failed to boot EVM RPC server", "error", err.Error())
return nil, nil, err
case <-time.After(types.ServerStartTime): // assume EVM RPC server started successfully
}
ctx.Logger.Info("Starting EVM WebSocket server", "address", config.EVMRPC.WsAddress)
_, port, _ := net.SplitHostPort(config.EVMRPC.RPCAddress)
// allocate separate WS connection to Tendermint
tmWsClient = ConnectTmWS(tmRPCAddr, tmEndpoint)
wsSrv := rpc.NewWebsocketsServer(ctx.Logger, tmWsClient, "localhost:"+port, config.EVMRPC.WsAddress)
wsSrv.Start()
return httpSrv, httpSrvDone, nil
}

View File

@ -2,22 +2,22 @@ package server
import ( import (
"context" "context"
"fmt"
"io" "io"
"net"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"runtime/pprof" "runtime/pprof"
"time" "time"
"github.com/gorilla/mux" "github.com/cosmos/cosmos-sdk/codec"
"github.com/improbable-eng/grpc-web/go/grpcweb"
"github.com/rs/cors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/xlab/closer"
"google.golang.org/grpc" "google.golang.org/grpc"
abciserver "github.com/tendermint/tendermint/abci/server"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
tmos "github.com/tendermint/tendermint/libs/os"
"github.com/tendermint/tendermint/node" "github.com/tendermint/tendermint/node"
"github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/p2p"
pvm "github.com/tendermint/tendermint/privval" pvm "github.com/tendermint/tendermint/privval"
@ -25,26 +25,28 @@ import (
"github.com/tendermint/tendermint/rpc/client/local" "github.com/tendermint/tendermint/rpc/client/local"
dbm "github.com/tendermint/tm-db" dbm "github.com/tendermint/tm-db"
"github.com/cosmos/cosmos-sdk/server/rosetta"
crgserver "github.com/cosmos/cosmos-sdk/server/rosetta/lib/server"
"github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/server/api" "github.com/cosmos/cosmos-sdk/server/api"
sdkconfig "github.com/cosmos/cosmos-sdk/server/config" serverconfig "github.com/cosmos/cosmos-sdk/server/config"
servergrpc "github.com/cosmos/cosmos-sdk/server/grpc" servergrpc "github.com/cosmos/cosmos-sdk/server/grpc"
"github.com/cosmos/cosmos-sdk/server/types" "github.com/cosmos/cosmos-sdk/server/types"
storetypes "github.com/cosmos/cosmos-sdk/store/types" storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
ethlog "github.com/ethereum/go-ethereum/log" ethlog "github.com/ethereum/go-ethereum/log"
ethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/tharsis/ethermint/cmd/ethermintd/config" "github.com/tharsis/ethermint/cmd/ethermintd/config"
"github.com/tharsis/ethermint/ethereum/rpc"
ethdebug "github.com/tharsis/ethermint/ethereum/rpc/namespaces/debug" ethdebug "github.com/tharsis/ethermint/ethereum/rpc/namespaces/debug"
) )
// Tendermint full-node start flags // Tendermint full-node start flags
const ( const (
flagWithTendermint = "with-tendermint"
flagAddress = "address" flagAddress = "address"
flagTransport = "transport" flagTransport = "transport"
flagTraceStore = "trace-store" flagTraceStore = "trace-store"
@ -61,6 +63,7 @@ const (
FlagPruningKeepRecent = "pruning-keep-recent" FlagPruningKeepRecent = "pruning-keep-recent"
FlagPruningKeepEvery = "pruning-keep-every" FlagPruningKeepEvery = "pruning-keep-every"
FlagPruningInterval = "pruning-interval" FlagPruningInterval = "pruning-interval"
FlagIndexEvents = "index-events"
FlagMinRetainBlocks = "min-retain-blocks" FlagMinRetainBlocks = "min-retain-blocks"
) )
@ -71,6 +74,9 @@ const (
flagEVMRPCEnable = "evm-rpc.enable" flagEVMRPCEnable = "evm-rpc.enable"
flagEVMRPCAddress = "evm-rpc.address" flagEVMRPCAddress = "evm-rpc.address"
flagEVMWSAddress = "evm-rpc.ws-address" flagEVMWSAddress = "evm-rpc.ws-address"
flagEVMEnableUnsafeCORS = "evm-rpc.enable-unsafe-cors"
flagGRPCWebEnable = "grpc-web.enable"
flagGRPCWebAddress = "grpc-web.address"
) )
// State sync-related flags. // State sync-related flags.
@ -122,17 +128,33 @@ which accepts a path for the resulting pprof file.
}, },
RunE: func(cmd *cobra.Command, _ []string) error { RunE: func(cmd *cobra.Command, _ []string) error {
serverCtx := server.GetServerContextFromCmd(cmd) serverCtx := server.GetServerContextFromCmd(cmd)
clientCtx := client.GetClientContextFromCmd(cmd) clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
withTM, _ := cmd.Flags().GetBool(flagWithTendermint)
if !withTM {
serverCtx.Logger.Info("starting ABCI without Tendermint")
return startStandAlone(serverCtx, appCreator)
}
serverCtx.Logger.Info("starting ABCI with Tendermint") serverCtx.Logger.Info("starting ABCI with Tendermint")
// amino is needed here for backwards compatibility of REST routes // amino is needed here for backwards compatibility of REST routes
err := startInProcess(serverCtx, clientCtx, appCreator) err = startInProcess(serverCtx, clientCtx, appCreator)
errCode, ok := err.(server.ErrorCode)
if !ok {
return err return err
}
serverCtx.Logger.Debug(fmt.Sprintf("received quit signal: %d", errCode.Code))
return nil
}, },
} }
cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory") cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
cmd.Flags().Bool(flagWithTendermint, true, "Run abci app embedded in-process with tendermint")
cmd.Flags().String(flagAddress, "tcp://0.0.0.0:26658", "Listen address") cmd.Flags().String(flagAddress, "tcp://0.0.0.0:26658", "Listen address")
cmd.Flags().String(flagTransport, "socket", "Transport protocol: socket, grpc") cmd.Flags().String(flagTransport, "socket", "Transport protocol: socket, grpc")
cmd.Flags().String(flagTraceStore, "", "Enable KVStore tracing to an output file") cmd.Flags().String(flagTraceStore, "", "Enable KVStore tracing to an output file")
@ -151,11 +173,15 @@ which accepts a path for the resulting pprof file.
cmd.Flags().Uint64(FlagMinRetainBlocks, 0, "Minimum block height offset during ABCI commit to prune Tendermint blocks") cmd.Flags().Uint64(FlagMinRetainBlocks, 0, "Minimum block height offset during ABCI commit to prune Tendermint blocks")
cmd.Flags().Bool(flagGRPCEnable, true, "Define if the gRPC server should be enabled") cmd.Flags().Bool(flagGRPCEnable, true, "Define if the gRPC server should be enabled")
cmd.Flags().String(flagGRPCAddress, config.DefaultGRPCAddress, "the gRPC server address to listen on") cmd.Flags().String(flagGRPCAddress, serverconfig.DefaultGRPCAddress, "the gRPC server address to listen on")
cmd.Flags().Bool(flagGRPCWebEnable, true, "Define if the gRPC-Web server should be enabled. (Note: gRPC must also be enabled.)")
cmd.Flags().String(flagGRPCWebAddress, serverconfig.DefaultGRPCWebAddress, "The gRPC-Web server address to listen on")
cmd.Flags().Bool(flagEVMRPCEnable, true, "Define if the gRPC server should be enabled") cmd.Flags().Bool(flagEVMRPCEnable, true, "Define if the gRPC server should be enabled")
cmd.Flags().String(flagEVMRPCAddress, config.DefaultEVMAddress, "the EVM RPC server address to listen on") cmd.Flags().String(flagEVMRPCAddress, config.DefaultEVMAddress, "the EVM RPC server address to listen on")
cmd.Flags().String(flagEVMWSAddress, config.DefaultEVMWSAddress, "the EVM WS server address to listen on") cmd.Flags().String(flagEVMWSAddress, config.DefaultEVMWSAddress, "the EVM WS server address to listen on")
cmd.Flags().Bool(flagEVMEnableUnsafeCORS, false, "Define if the EVM RPC server should enabled CORS (unsafe - use it at your own risk)")
cmd.Flags().Uint64(FlagStateSyncSnapshotInterval, 0, "State sync snapshot interval") cmd.Flags().Uint64(FlagStateSyncSnapshotInterval, 0, "State sync snapshot interval")
cmd.Flags().Uint32(FlagStateSyncSnapshotKeepRecent, 2, "State sync snapshot to keep") cmd.Flags().Uint32(FlagStateSyncSnapshotKeepRecent, 2, "State sync snapshot to keep")
@ -165,10 +191,70 @@ which accepts a path for the resulting pprof file.
return cmd return cmd
} }
func startStandAlone(ctx *server.Context, appCreator types.AppCreator) error {
addr := ctx.Viper.GetString(flagAddress)
transport := ctx.Viper.GetString(flagTransport)
home := ctx.Viper.GetString(flags.FlagHome)
db, err := openDB(home)
if err != nil {
return err
}
traceWriterFile := ctx.Viper.GetString(flagTraceStore)
traceWriter, err := openTraceWriter(traceWriterFile)
if err != nil {
return err
}
app := appCreator(ctx.Logger, db, traceWriter, ctx.Viper)
svr, err := abciserver.NewServer(addr, transport, app)
if err != nil {
return fmt.Errorf("error creating listener: %v", err)
}
svr.SetLogger(ctx.Logger.With("server", "abci"))
err = svr.Start()
if err != nil {
tmos.Exit(err.Error())
}
defer func() {
if err = svr.Stop(); err != nil {
tmos.Exit(err.Error())
}
}()
// Wait for SIGINT or SIGTERM signal
return server.WaitForQuitSignals()
}
// legacyAminoCdc is used for the legacy REST API
func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator types.AppCreator) error { func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator types.AppCreator) error {
cfg := ctx.Config cfg := ctx.Config
home := cfg.RootDir home := cfg.RootDir
logger := ctx.Logger logger := ctx.Logger
var cpuProfileCleanup func()
if cpuProfile := ctx.Viper.GetString(flagCPUProfile); cpuProfile != "" {
f, err := os.Create(ethdebug.ExpandHome(cpuProfile))
if err != nil {
return err
}
ctx.Logger.Info("starting CPU profiler", "profile", cpuProfile)
if err := pprof.StartCPUProfile(f); err != nil {
return err
}
cpuProfileCleanup = func() {
ctx.Logger.Info("stopping CPU profiler", "profile", cpuProfile)
pprof.StopCPUProfile()
f.Close()
}
}
traceWriterFile := ctx.Viper.GetString(flagTraceStore) traceWriterFile := ctx.Viper.GetString(flagTraceStore)
db, err := openDB(home) db, err := openDB(home)
@ -183,6 +269,13 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty
return err return err
} }
config := config.GetConfig(ctx.Viper)
if err := config.ValidateBasic(); err != nil {
ctx.Logger.Error("WARNING: The minimum-gas-prices config in app.toml is set to the empty string. " +
"This defaults to 0 in the current version, but will error in the next version " +
"(SDK v0.44). Please explicitly put the desired minimum-gas-prices in your app.toml.")
}
app := appCreator(ctx.Logger, db, traceWriter, ctx.Viper) app := appCreator(ctx.Logger, db, traceWriter, ctx.Viper)
nodeKey, err := p2p.LoadOrGenNodeKey(cfg.NodeKeyFile()) nodeKey, err := p2p.LoadOrGenNodeKey(cfg.NodeKeyFile())
@ -200,7 +293,7 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty
genDocProvider, genDocProvider,
node.DefaultDBProvider, node.DefaultDBProvider,
node.DefaultMetricsProvider(cfg.Instrumentation), node.DefaultMetricsProvider(cfg.Instrumentation),
ctx.Logger.With("module", "node"), ctx.Logger.With("server", "node"),
) )
if err != nil { if err != nil {
logger.Error("failed init node", "error", err.Error()) logger.Error("failed init node", "error", err.Error())
@ -212,153 +305,121 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty
return err return err
} }
// 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 tendermint RPC client.
if config.API.Enable || config.GRPC.Enable {
clientCtx = clientCtx.WithClient(local.New(tmNode))
app.RegisterTxService(clientCtx)
app.RegisterTendermintService(clientCtx)
}
var apiSrv *api.Server
if config.API.Enable {
genDoc, err := genDocProvider() genDoc, err := genDocProvider()
if err != nil { if err != nil {
return err return err
} }
clientCtx = clientCtx. clientCtx := clientCtx.
WithHomeDir(home). WithHomeDir(home).
WithChainID(genDoc.ChainID). WithChainID(genDoc.ChainID)
WithClient(local.New(tmNode))
var apiSrv *api.Server apiSrv = api.New(clientCtx, ctx.Logger.With("server", "api"))
config := config.GetConfig(ctx.Viper) app.RegisterAPIRoutes(apiSrv, config.API)
errCh := make(chan error)
var grpcSrv *grpc.Server go func() {
if err := apiSrv.Start(config.Config); err != nil {
errCh <- err
}
}()
select {
case err := <-errCh:
return err
case <-time.After(types.ServerStartTime): // assume server started successfully
}
}
var (
grpcSrv *grpc.Server
grpcWebSrv *http.Server
)
if config.GRPC.Enable { if config.GRPC.Enable {
grpcSrv, err = servergrpc.StartGRPCServer(clientCtx, app, config.GRPC.Address) grpcSrv, err = servergrpc.StartGRPCServer(clientCtx, app, config.GRPC.Address)
if err != nil { if err != nil {
logger.Error("failed to boot GRPC server", "error", err.Error()) return err
}
if config.GRPCWeb.Enable {
grpcWebSrv, err = servergrpc.StartGRPCWeb(grpcSrv, config.Config)
if err != nil {
ctx.Logger.Error("failed to start grpc-web http server: ", err)
return err return err
} }
} }
}
var httpSrv *http.Server var rosettaSrv crgserver.Server
var httpSrvDone = make(chan struct{}, 1) if config.Rosetta.Enable {
var wsSrv rpc.WebsocketsServer offlineMode := config.Rosetta.Offline
if !config.GRPC.Enable { // If GRPC is not enabled rosetta cannot work in online mode, so it works in offline mode.
offlineMode = true
}
conf := &rosetta.Config{
Blockchain: config.Rosetta.Blockchain,
Network: config.Rosetta.Network,
TendermintRPC: ctx.Config.RPC.ListenAddress,
GRPCEndpoint: config.GRPC.Address,
Addr: config.Rosetta.Address,
Retries: config.Rosetta.Retries,
Offline: offlineMode,
}
conf.WithCodec(clientCtx.InterfaceRegistry, clientCtx.Codec.(*codec.ProtoCodec))
rosettaSrv, err = rosetta.ServerFromConfig(conf)
if err != nil {
return err
}
errCh := make(chan error)
go func() {
if err := rosettaSrv.Start(); err != nil {
errCh <- err
}
}()
select {
case err := <-errCh:
return err
case <-time.After(types.ServerStartTime): // assume server started successfully
}
}
ethlog.Root().SetHandler(ethlog.StdoutHandler) ethlog.Root().SetHandler(ethlog.StdoutHandler)
var (
httpSrv *http.Server
httpSrvDone chan struct{}
)
if config.EVMRPC.Enable { if config.EVMRPC.Enable {
genDoc, err := genDocProvider()
if err != nil {
return err
}
clientCtx := clientCtx.WithChainID(genDoc.ChainID)
tmEndpoint := "/websocket" tmEndpoint := "/websocket"
tmRPCAddr := cfg.RPC.ListenAddress tmRPCAddr := cfg.RPC.ListenAddress
logger.Info("EVM RPC Connecting to Tendermint WebSocket at", "address", tmRPCAddr+tmEndpoint) httpSrv, httpSrvDone, err = StartEVMRPC(ctx, clientCtx, tmRPCAddr, tmEndpoint, config)
tmWsClient := ConnectTmWS(tmRPCAddr, tmEndpoint)
rpcServer := ethrpc.NewServer()
apis := rpc.GetRPCAPIs(ctx, clientCtx, tmWsClient)
for _, api := range apis {
if err := rpcServer.RegisterName(api.Namespace, api.Service); err != nil {
logger.Error(
"failed to register service in EVM RPC namespace",
"namespace", api.Namespace,
"service", api.Service,
)
return err
}
}
r := mux.NewRouter()
r.HandleFunc("/", rpcServer.ServeHTTP).Methods("POST")
if grpcSrv != nil {
grpcWeb := grpcweb.WrapServer(grpcSrv)
MountGRPCWebServices(r, grpcWeb, grpcweb.ListGRPCResources(grpcSrv))
}
handlerWithCors := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{
http.MethodHead,
http.MethodGet,
http.MethodPost,
http.MethodPut,
http.MethodPatch,
http.MethodDelete,
},
AllowedHeaders: []string{"*"},
AllowCredentials: false,
OptionsPassthrough: false,
})
httpSrv = &http.Server{
Addr: config.EVMRPC.RPCAddress,
Handler: handlerWithCors.Handler(r),
}
errCh := make(chan error)
go func() {
logger.Info("Starting EVM RPC server", "address", config.EVMRPC.RPCAddress)
if err := httpSrv.ListenAndServe(); err != nil {
if err == http.ErrServerClosed {
close(httpSrvDone)
return
}
logger.Error("failed to start EVM RPC server", "error", err.Error())
errCh <- err
}
}()
select {
case err := <-errCh:
logger.Error("failed to boot EVM RPC server", "error", err.Error())
return err
case <-time.After(1 * time.Second): // assume EVM RPC server started successfully
}
logger.Info("Starting EVM WebSocket server", "address", config.EVMRPC.WsAddress)
_, port, _ := net.SplitHostPort(config.EVMRPC.RPCAddress)
// allocate separate WS connection to Tendermint
tmWsClient = ConnectTmWS(tmRPCAddr, tmEndpoint)
wsSrv = rpc.NewWebsocketsServer(logger, tmWsClient, "localhost:"+port, config.EVMRPC.WsAddress)
go wsSrv.Start()
}
sdkcfg := sdkconfig.GetConfig(ctx.Viper)
sdkcfg.API = config.API
if sdkcfg.API.Enable {
apiSrv = api.New(clientCtx, ctx.Logger.With("module", "api-server"))
app.RegisterAPIRoutes(apiSrv, sdkcfg.API)
errCh := make(chan error)
go func() {
if err := apiSrv.Start(sdkcfg); err != nil {
errCh <- err
}
}()
select {
case err := <-errCh:
logger.Error("failed to boot API server", "error", err.Error())
return err
case <-time.After(5 * time.Second): // assume server started successfully
}
}
var cpuProfileCleanup func()
if cpuProfile := ctx.Viper.GetString(flagCPUProfile); cpuProfile != "" {
f, err := os.Create(ethdebug.ExpandHome(cpuProfile))
if err != nil { if err != nil {
logger.Error("failed to create CPU profile", "error", err.Error())
return err return err
} }
logger.Info("starting CPU profiler", "profile", cpuProfile)
if err := pprof.StartCPUProfile(f); err != nil {
return err
} }
cpuProfileCleanup = func() { defer func() {
logger.Info("stopping CPU profiler", "profile", cpuProfile)
pprof.StopCPUProfile()
f.Close()
}
}
closer.Bind(func() {
if tmNode.IsRunning() { if tmNode.IsRunning() {
_ = tmNode.Stop() _ = tmNode.Stop()
} }
@ -367,6 +428,17 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty
cpuProfileCleanup() cpuProfileCleanup()
} }
if apiSrv != nil {
_ = apiSrv.Close()
}
if grpcSrv != nil {
grpcSrv.Stop()
if grpcWebSrv != nil {
grpcWebSrv.Close()
}
}
if httpSrv != nil { if httpSrv != nil {
shutdownCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) shutdownCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
defer cancelFn() defer cancelFn()
@ -382,16 +454,11 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty
} }
} }
if grpcSrv != nil {
grpcSrv.Stop()
}
logger.Info("Bye!") logger.Info("Bye!")
}) }()
closer.Hold() // Wait for SIGINT or SIGTERM signal
return server.WaitForQuitSignals()
return nil
} }
func openDB(rootDir string) (dbm.DB, error) { func openDB(rootDir string) (dbm.DB, error) {