From 42abb259cbed8ee03f395645e0b05cce75847b20 Mon Sep 17 00:00:00 2001 From: Freddy Caceres Date: Wed, 10 Aug 2022 08:26:51 -0400 Subject: [PATCH] Add support for `MaxOpenConnections` (#1229) --- CHANGELOG.md | 4 ++++ rpc/websockets.go | 2 +- server/config/config.go | 33 ++++++++++++++++++++------------- server/config/toml.go | 4 ++++ server/flags/flags.go | 1 + server/json_rpc.go | 9 +++++++-- server/start.go | 3 ++- server/util.go | 21 ++++++++++++++++++++- testutil/network/util.go | 2 +- 9 files changed, 60 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55ee0ec2..b40f676d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,10 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## Unreleased +### Improvements + +* (rpc) [#1229](https://github.com/evmos/ethermint/pull/1229) Add support for configuring RPC `MaxOpenConnections` + ### State Machine Breaking * (deps) [\#1159](https://github.com/evmos/ethermint/pull/1159) Bump Geth version to `v1.10.19`. diff --git a/rpc/websockets.go b/rpc/websockets.go index 97a57acd..c501ace3 100644 --- a/rpc/websockets.go +++ b/rpc/websockets.go @@ -74,7 +74,7 @@ type websocketsServer struct { logger log.Logger } -func NewWebsocketsServer(clientCtx client.Context, logger log.Logger, tmWSClient *rpcclient.WSClient, cfg config.Config) WebsocketsServer { +func NewWebsocketsServer(clientCtx client.Context, logger log.Logger, tmWSClient *rpcclient.WSClient, cfg *config.Config) WebsocketsServer { logger = logger.With("api", "websocket-server") _, port, _ := net.SplitHostPort(cfg.JSONRPC.Address) diff --git a/server/config/config.go b/server/config/config.go index a4ba48a9..407b2d0b 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -49,6 +49,8 @@ const ( DefaultHTTPIdleTimeout = 120 * time.Second // DefaultAllowUnprotectedTxs value is false DefaultAllowUnprotectedTxs = false + // DefaultMaxOpenConnections represents the amount of open connections (unlimited = 0) + DefaultMaxOpenConnections = 0 ) var evmTracers = []string{"json", "markdown", "struct", "access_list"} @@ -103,6 +105,9 @@ type JSONRPCConfig struct { // AllowUnprotectedTxs restricts unprotected (non EIP155 signed) transactions to be submitted via // the node's RPC when global parameter is disabled. AllowUnprotectedTxs bool `mapstructure:"allow-unprotected-txs"` + // MaxOpenConnections sets the maximum number of simultaneous connections + // for the server listener. + MaxOpenConnections int `mapstructure:"max-open-connections"` } // TLSConfig defines the certificate and matching private key for the server. @@ -202,6 +207,7 @@ func DefaultJSONRPCConfig() *JSONRPCConfig { HTTPTimeout: DefaultHTTPTimeout, HTTPIdleTimeout: DefaultHTTPIdleTimeout, AllowUnprotectedTxs: DefaultAllowUnprotectedTxs, + MaxOpenConnections: DefaultMaxOpenConnections, } } @@ -292,19 +298,20 @@ func GetConfig(v *viper.Viper) Config { MaxTxGasWanted: v.GetUint64("evm.max-tx-gas-wanted"), }, JSONRPC: JSONRPCConfig{ - Enable: v.GetBool("json-rpc.enable"), - API: v.GetStringSlice("json-rpc.api"), - Address: v.GetString("json-rpc.address"), - WsAddress: v.GetString("json-rpc.ws-address"), - GasCap: v.GetUint64("json-rpc.gas-cap"), - FilterCap: v.GetInt32("json-rpc.filter-cap"), - FeeHistoryCap: v.GetInt32("json-rpc.feehistory-cap"), - TxFeeCap: v.GetFloat64("json-rpc.txfee-cap"), - EVMTimeout: v.GetDuration("json-rpc.evm-timeout"), - LogsCap: v.GetInt32("json-rpc.logs-cap"), - BlockRangeCap: v.GetInt32("json-rpc.block-range-cap"), - HTTPTimeout: v.GetDuration("json-rpc.http-timeout"), - HTTPIdleTimeout: v.GetDuration("json-rpc.http-idle-timeout"), + Enable: v.GetBool("json-rpc.enable"), + API: v.GetStringSlice("json-rpc.api"), + Address: v.GetString("json-rpc.address"), + WsAddress: v.GetString("json-rpc.ws-address"), + GasCap: v.GetUint64("json-rpc.gas-cap"), + FilterCap: v.GetInt32("json-rpc.filter-cap"), + FeeHistoryCap: v.GetInt32("json-rpc.feehistory-cap"), + TxFeeCap: v.GetFloat64("json-rpc.txfee-cap"), + EVMTimeout: v.GetDuration("json-rpc.evm-timeout"), + LogsCap: v.GetInt32("json-rpc.logs-cap"), + BlockRangeCap: v.GetInt32("json-rpc.block-range-cap"), + HTTPTimeout: v.GetDuration("json-rpc.http-timeout"), + HTTPIdleTimeout: v.GetDuration("json-rpc.http-idle-timeout"), + MaxOpenConnections: v.GetInt("json-rpc.max-open-connections"), }, TLS: TLSConfig{ CertificatePath: v.GetString("tls.certificate-path"), diff --git a/server/config/toml.go b/server/config/toml.go index 93d3285a..d8861c83 100644 --- a/server/config/toml.go +++ b/server/config/toml.go @@ -66,6 +66,10 @@ http-idle-timeout = "{{ .JSONRPC.HTTPIdleTimeout }}" # the node's RPC when the global parameter is disabled. allow-unprotected-txs = {{ .JSONRPC.AllowUnprotectedTxs }} +# MaxOpenConnections sets the maximum number of simultaneous connections +# for the server listener. +max-open-connections = {{ .JSONRPC.MaxOpenConnections }} + ############################################################################### ### TLS Configuration ### ############################################################################### diff --git a/server/flags/flags.go b/server/flags/flags.go index 67fb0cc7..5422988e 100644 --- a/server/flags/flags.go +++ b/server/flags/flags.go @@ -47,6 +47,7 @@ const ( JSONRPCHTTPTimeout = "json-rpc.http-timeout" JSONRPCHTTPIdleTimeout = "json-rpc.http-idle-timeout" JSONRPCAllowUnprotectedTxs = "json-rpc.allow-unprotected-txs" + JSONRPCMaxOpenConnections = "json-rpc.max-open-connections" ) // EVM flags diff --git a/server/json_rpc.go b/server/json_rpc.go index c27399b8..bf6b8306 100644 --- a/server/json_rpc.go +++ b/server/json_rpc.go @@ -18,7 +18,7 @@ import ( ) // StartJSONRPC starts the JSON-RPC server -func StartJSONRPC(ctx *server.Context, clientCtx client.Context, tmRPCAddr, tmEndpoint string, config config.Config) (*http.Server, chan struct{}, error) { +func StartJSONRPC(ctx *server.Context, clientCtx client.Context, tmRPCAddr, tmEndpoint string, config *config.Config) (*http.Server, chan struct{}, error) { tmWsClient := ConnectTmWS(tmRPCAddr, tmEndpoint, ctx.Logger) logger := ctx.Logger.With("module", "geth") @@ -70,10 +70,15 @@ func StartJSONRPC(ctx *server.Context, clientCtx client.Context, tmRPCAddr, tmEn } httpSrvDone := make(chan struct{}, 1) + ln, err := Listen(httpSrv.Addr, config) + if err != nil { + return nil, nil, err + } + errCh := make(chan error) go func() { ctx.Logger.Info("Starting JSON-RPC server", "address", config.JSONRPC.Address) - if err := httpSrv.ListenAndServe(); err != nil { + if err := httpSrv.Serve(ln); err != nil { if err == http.ErrServerClosed { close(httpSrvDone) return diff --git a/server/start.go b/server/start.go index c4e979f3..6957d5be 100644 --- a/server/start.go +++ b/server/start.go @@ -164,6 +164,7 @@ which accepts a path for the resulting pprof file. cmd.Flags().Bool(srvflags.JSONRPCAllowUnprotectedTxs, config.DefaultAllowUnprotectedTxs, "Allow for unprotected (non EIP155 signed) transactions to be submitted via the node's RPC when the global parameter is disabled") cmd.Flags().Int32(srvflags.JSONRPCLogsCap, config.DefaultLogsCap, "Sets the max number of results can be returned from single `eth_getLogs` query") cmd.Flags().Int32(srvflags.JSONRPCBlockRangeCap, config.DefaultBlockRangeCap, "Sets the max block range allowed for `eth_getLogs` query") + cmd.Flags().Int(srvflags.JSONRPCMaxOpenConnections, config.DefaultMaxOpenConnections, "Sets the maximum number of simultaneous connections for the server listener") cmd.Flags().String(srvflags.EVMTracer, config.DefaultEVMTracer, "the EVM tracer type to collect execution traces from the EVM transaction execution (json|struct|access_list|markdown)") cmd.Flags().Uint64(srvflags.EVMMaxTxGasWanted, config.DefaultMaxTxGasWanted, "the gas wanted for each eth tx returned in ante handler in check tx mode") @@ -426,7 +427,7 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty tmEndpoint := "/websocket" tmRPCAddr := cfg.RPC.ListenAddress - httpSrv, httpSrvDone, err = StartJSONRPC(ctx, clientCtx, tmRPCAddr, tmEndpoint, config) + httpSrv, httpSrvDone, err = StartJSONRPC(ctx, clientCtx, tmRPCAddr, tmEndpoint, &config) if err != nil { return err } diff --git a/server/util.go b/server/util.go index 885f657d..b608e7b6 100644 --- a/server/util.go +++ b/server/util.go @@ -1,12 +1,15 @@ package server import ( + "net" "net/http" "time" + "github.com/evmos/ethermint/server/config" "github.com/gorilla/mux" "github.com/improbable-eng/grpc-web/go/grpcweb" "github.com/spf13/cobra" + "golang.org/x/net/netutil" sdkserver "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/server/types" @@ -17,7 +20,7 @@ import ( rpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client" ) -// add server commands +// AddCommands adds server commands func AddCommands(rootCmd *cobra.Command, defaultNodeHome string, appCreator types.AppCreator, appExport types.AppExporter, addStartFlags types.ModuleInitFlags) { tendermintCmd := &cobra.Command{ Use: "tendermint", @@ -97,3 +100,19 @@ func MountGRPCWebServices( }) } } + +// Listen starts a net.Listener on the tcp network on the given address. +// If there is a specified MaxOpenConnections in the config, it will also set the limitListener. +func Listen(addr string, config *config.Config) (net.Listener, error) { + if addr == "" { + addr = ":http" + } + ln, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + if config.JSONRPC.MaxOpenConnections > 0 { + ln = netutil.LimitListener(ln, config.JSONRPC.MaxOpenConnections) + } + return ln, err +} diff --git a/testutil/network/util.go b/testutil/network/util.go index 05dcbb5f..487a6bdf 100644 --- a/testutil/network/util.go +++ b/testutil/network/util.go @@ -131,7 +131,7 @@ func startInProcess(cfg Config, val *Validator) error { tmEndpoint := "/websocket" tmRPCAddr := val.RPCAddress - val.jsonrpc, val.jsonrpcDone, err = server.StartJSONRPC(val.Ctx, val.ClientCtx, tmRPCAddr, tmEndpoint, *val.AppConfig) + val.jsonrpc, val.jsonrpcDone, err = server.StartJSONRPC(val.Ctx, val.ClientCtx, tmRPCAddr, tmEndpoint, val.AppConfig) if err != nil { return err }