From e48a6760fc947f92ce16d74e2ae338b3479e5f5c Mon Sep 17 00:00:00 2001 From: Amir Deris Date: Wed, 24 Jun 2026 09:29:51 -0700 Subject: [PATCH] Added batch request limit and batch response size limit to config --- evmrpc/config/config.go | 30 ++++++++++++++++++++++++++++ evmrpc/config/config_test.go | 38 ++++++++++++++++++++++++++++++++++++ evmrpc/server.go | 9 +++++++-- 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/evmrpc/config/config.go b/evmrpc/config/config.go index cb4b8612a8..d12eb48065 100644 --- a/evmrpc/config/config.go +++ b/evmrpc/config/config.go @@ -166,6 +166,14 @@ type Config struct { // IPRateLimitBurst is the maximum per-IP burst size. IPRateLimitBurst int `mapstructure:"ip_rate_limit_burst"` + + // BatchRequestLimit is the maximum number of requests allowed in a single + // JSON-RPC batch (HTTP and WebSocket). Set to 0 to disable the limit. + BatchRequestLimit int `mapstructure:"batch_request_limit"` + + // BatchResponseMaxSize is the maximum number of bytes returned from a + // batched JSON-RPC call (HTTP and WebSocket). Set to 0 to disable the limit. + BatchResponseMaxSize int `mapstructure:"batch_response_max_size"` } var DefaultConfig = Config{ @@ -213,6 +221,8 @@ var DefaultConfig = Config{ TraceBakeSnapshotWindow: 64, IPRateLimitRPS: 200, IPRateLimitBurst: 400, + BatchRequestLimit: 1000, + BatchResponseMaxSize: 25 * 1000 * 1000, // 25MB } const ( @@ -256,6 +266,8 @@ const ( flagTraceBakeSnapshotWindow = "evm.trace_bake_snapshot_window" flagIPRateLimitRPS = "evm.ip_rate_limit_rps" flagIPRateLimitBurst = "evm.ip_rate_limit_burst" + flagBatchRequestLimit = "evm.batch_request_limit" + flagBatchResponseMaxSize = "evm.batch_response_max_size" ) func ReadConfig(opts servertypes.AppOptions) (Config, error) { @@ -461,6 +473,16 @@ func ReadConfig(opts servertypes.AppOptions) (Config, error) { return cfg, err } } + if v := opts.Get(flagBatchRequestLimit); v != nil { + if cfg.BatchRequestLimit, err = cast.ToIntE(v); err != nil { + return cfg, err + } + } + if v := opts.Get(flagBatchResponseMaxSize); v != nil { + if cfg.BatchResponseMaxSize, err = cast.ToIntE(v); err != nil { + return cfg, err + } + } return cfg, nil } @@ -657,4 +679,12 @@ ip_rate_limit_rps = {{ .EVM.IPRateLimitRPS }} # ip_rate_limit_burst is the maximum per-IP burst above the sustained rate. ip_rate_limit_burst = {{ .EVM.IPRateLimitBurst }} +# batch_request_limit is the maximum number of requests allowed in a single +# JSON-RPC batch (HTTP and WebSocket). Set to 0 to disable the limit. +batch_request_limit = {{ .EVM.BatchRequestLimit }} + +# batch_response_max_size is the maximum number of bytes returned from a +# batched JSON-RPC call (HTTP and WebSocket). Set to 0 to disable the limit. +batch_response_max_size = {{ .EVM.BatchResponseMaxSize }} + ` diff --git a/evmrpc/config/config_test.go b/evmrpc/config/config_test.go index fb37e237bf..cbba55ee7f 100644 --- a/evmrpc/config/config_test.go +++ b/evmrpc/config/config_test.go @@ -42,6 +42,8 @@ type opts struct { workerQueueSize interface{} ipRateLimitRPS interface{} ipRateLimitBurst interface{} + batchRequestLimit interface{} + batchResponseMaxSize interface{} } func (o *opts) Get(k string) interface{} { @@ -156,6 +158,12 @@ func (o *opts) Get(k string) interface{} { if k == "evm.ip_rate_limit_burst" { return o.ipRateLimitBurst } + if k == "evm.batch_request_limit" { + return o.batchRequestLimit + } + if k == "evm.batch_response_max_size" { + return o.batchResponseMaxSize + } panic("unknown key") } @@ -195,6 +203,8 @@ func getDefaultOpts() opts { 1000, 200.0, 400, + 1000, + 25 * 1000 * 1000, } } @@ -321,6 +331,17 @@ func TestReadConfig(t *testing.T) { _, err = config.ReadConfig(&badOpts) require.NotNil(t, err) + // Test bad types for batch limit config + badOpts = goodOpts + badOpts.batchRequestLimit = "bad" + _, err = config.ReadConfig(&badOpts) + require.NotNil(t, err) + + badOpts = goodOpts + badOpts.batchResponseMaxSize = "bad" + _, err = config.ReadConfig(&badOpts) + require.NotNil(t, err) + } // Test worker pool configuration values @@ -380,6 +401,23 @@ func TestReadConfigWorkerPool(t *testing.T) { } } +func TestReadConfigBatchLimits(t *testing.T) { + // Defaults flow through when not overridden. + cfg, err := config.ReadConfig(&opts{}) + require.NoError(t, err) + require.Equal(t, config.DefaultConfig.BatchRequestLimit, cfg.BatchRequestLimit) + require.Equal(t, config.DefaultConfig.BatchResponseMaxSize, cfg.BatchResponseMaxSize) + + // Custom values (including 0 to disable) flow through. + o := getDefaultOpts() + o.batchRequestLimit = 50 + o.batchResponseMaxSize = 0 + cfg, err = config.ReadConfig(&o) + require.NoError(t, err) + require.Equal(t, 50, cfg.BatchRequestLimit) + require.Equal(t, 0, cfg.BatchResponseMaxSize) +} + func TestReadConfigEnableParallelizedBlockTrace(t *testing.T) { opts := getDefaultOpts() opts.enableParallelizedBlockTrace = true diff --git a/evmrpc/server.go b/evmrpc/server.go index 4c04adaa21..f7a93b944f 100644 --- a/evmrpc/server.go +++ b/evmrpc/server.go @@ -220,12 +220,15 @@ func NewEVMHTTPServer( logger.Info("Disabling Test EVM APIs", "liveChainID", evmCfg.IsLiveChainID(ctx), "enableTestAPI", config.EnableTestAPI) } - if err := httpServer.EnableRPC(apis, HTTPConfig{ + httpConfig := HTTPConfig{ CorsAllowedOrigins: strings.Split(config.CORSOrigins, ","), Vhosts: []string{"*"}, DenyList: config.DenyList, SeiLegacyAllowlist: seiLegacyAllowlist, - }); err != nil { + } + httpConfig.batchItemLimit = config.BatchRequestLimit + httpConfig.batchResponseSizeLimit = config.BatchResponseMaxSize + if err := httpServer.EnableRPC(apis, httpConfig); err != nil { return nil, err } @@ -329,6 +332,8 @@ func NewEVMWebSocketServer( wsConfig := WsConfig{Origins: strings.Split(config.WSOrigins, ",")} wsConfig.readLimit = DefaultWebsocketMaxMessageSize + wsConfig.batchItemLimit = config.BatchRequestLimit + wsConfig.batchResponseSizeLimit = config.BatchResponseMaxSize if err := httpServer.EnableWS(apis, wsConfig); err != nil { return nil, err }