diff --git a/command/server/config/config.go b/command/server/config/config.go index a1b6f4523b..d9e4ae913a 100644 --- a/command/server/config/config.go +++ b/command/server/config/config.go @@ -14,22 +14,24 @@ import ( // Config defines the server configuration params type Config struct { - GenesisPath string `json:"chain_config" yaml:"chain_config"` - SecretsConfigPath string `json:"secrets_config" yaml:"secrets_config"` - DataDir string `json:"data_dir" yaml:"data_dir"` - BlockGasTarget string `json:"block_gas_target" yaml:"block_gas_target"` - GRPCAddr string `json:"grpc_addr" yaml:"grpc_addr"` - JSONRPCAddr string `json:"jsonrpc_addr" yaml:"jsonrpc_addr"` - Telemetry *Telemetry `json:"telemetry" yaml:"telemetry"` - Network *Network `json:"network" yaml:"network"` - ShouldSeal bool `json:"seal" yaml:"seal"` - TxPool *TxPool `json:"tx_pool" yaml:"tx_pool"` - LogLevel string `json:"log_level" yaml:"log_level"` - RestoreFile string `json:"restore_file" yaml:"restore_file"` - BlockTime uint64 `json:"block_time_s" yaml:"block_time_s"` - IBFTBaseTimeout uint64 `json:"ibft_base_time_s" yaml:"ibft_base_time_s"` - Headers *Headers `json:"headers" yaml:"headers"` - LogFilePath string `json:"log_to" yaml:"log_to"` + GenesisPath string `json:"chain_config" yaml:"chain_config"` + SecretsConfigPath string `json:"secrets_config" yaml:"secrets_config"` + DataDir string `json:"data_dir" yaml:"data_dir"` + BlockGasTarget string `json:"block_gas_target" yaml:"block_gas_target"` + GRPCAddr string `json:"grpc_addr" yaml:"grpc_addr"` + JSONRPCAddr string `json:"jsonrpc_addr" yaml:"jsonrpc_addr"` + Telemetry *Telemetry `json:"telemetry" yaml:"telemetry"` + Network *Network `json:"network" yaml:"network"` + ShouldSeal bool `json:"seal" yaml:"seal"` + TxPool *TxPool `json:"tx_pool" yaml:"tx_pool"` + LogLevel string `json:"log_level" yaml:"log_level"` + RestoreFile string `json:"restore_file" yaml:"restore_file"` + BlockTime uint64 `json:"block_time_s" yaml:"block_time_s"` + IBFTBaseTimeout uint64 `json:"ibft_base_time_s" yaml:"ibft_base_time_s"` + Headers *Headers `json:"headers" yaml:"headers"` + LogFilePath string `json:"log_to" yaml:"log_to"` + JSONRPCBatchRequestLimit uint64 `json:"json_rpc_batch_request_limit" yaml:"json_rpc_batch_request_limit"` + JSONRPCBlockRangeLimit uint64 `json:"json_rpc_block_range_limit" yaml:"json_rpc_block_range_limit"` } // Telemetry holds the config details for metric services. @@ -69,6 +71,12 @@ const ( // Multiplier to get IBFT timeout from block time // timeout is calculated when IBFT timeout is not specified BlockTimeMultiplierForTimeout uint64 = 5 + + // maximum length allowed for json_rpc batch requests + DefaultJSONRPCBatchRequestLimit uint64 = 20 + + // maximum block range allowed for json_rpc requests with fromBlock/toBlock values (e.g. eth_getLogs) + DefaultJSONRPCBlockRangeLimit uint64 = 1000 ) // DefaultConfig returns the default server configuration @@ -102,7 +110,9 @@ func DefaultConfig() *Config { Headers: &Headers{ AccessControlAllowOrigins: []string{"*"}, }, - LogFilePath: "", + LogFilePath: "", + JSONRPCBatchRequestLimit: DefaultJSONRPCBatchRequestLimit, + JSONRPCBlockRangeLimit: DefaultJSONRPCBlockRangeLimit, } } diff --git a/command/server/params.go b/command/server/params.go index 416643e7b3..2baa9c769d 100644 --- a/command/server/params.go +++ b/command/server/params.go @@ -14,28 +14,30 @@ import ( ) const ( - configFlag = "config" - genesisPathFlag = "chain" - dataDirFlag = "data-dir" - libp2pAddressFlag = "libp2p" - prometheusAddressFlag = "prometheus" - natFlag = "nat" - dnsFlag = "dns" - sealFlag = "seal" - maxPeersFlag = "max-peers" - maxInboundPeersFlag = "max-inbound-peers" - maxOutboundPeersFlag = "max-outbound-peers" - priceLimitFlag = "price-limit" - maxSlotsFlag = "max-slots" - blockGasTargetFlag = "block-gas-target" - secretsConfigFlag = "secrets-config" - restoreFlag = "restore" - blockTimeFlag = "block-time" - ibftBaseTimeoutFlag = "ibft-base-timeout" - devIntervalFlag = "dev-interval" - devFlag = "dev" - corsOriginFlag = "access-control-allow-origins" - logFileLocationFlag = "log-to" + configFlag = "config" + genesisPathFlag = "chain" + dataDirFlag = "data-dir" + libp2pAddressFlag = "libp2p" + prometheusAddressFlag = "prometheus" + natFlag = "nat" + dnsFlag = "dns" + sealFlag = "seal" + maxPeersFlag = "max-peers" + maxInboundPeersFlag = "max-inbound-peers" + maxOutboundPeersFlag = "max-outbound-peers" + priceLimitFlag = "price-limit" + jsonRPCBatchRequestLimitFlag = "json-rpc-batch-request-limit" + jsonRPCBlockRangeLimitFlag = "json-rpc-block-range-limit" + maxSlotsFlag = "max-slots" + blockGasTargetFlag = "block-gas-target" + secretsConfigFlag = "secrets-config" + restoreFlag = "restore" + blockTimeFlag = "block-time" + ibftBaseTimeoutFlag = "ibft-base-timeout" + devIntervalFlag = "dev-interval" + devFlag = "dev" + corsOriginFlag = "access-control-allow-origins" + logFileLocationFlag = "log-to" ) const ( @@ -73,6 +75,9 @@ type serverParams struct { corsAllowedOrigins []string + jsonRPCBatchLengthLimit uint64 + jsonRPCBlockRangeLimit uint64 + genesisConfig *chain.Chain secretsConfig *secrets.SecretsManagerConfig @@ -134,6 +139,8 @@ func (p *serverParams) generateConfig() *server.Config { JSONRPC: &server.JSONRPC{ JSONRPCAddr: p.jsonRPCAddress, AccessControlAllowOrigin: p.corsAllowedOrigins, + BatchLengthLimit: p.jsonRPCBatchLengthLimit, + BlockRangeLimit: p.jsonRPCBlockRangeLimit, }, GRPCAddr: p.grpcAddress, LibP2PAddr: p.libp2pAddress, diff --git a/command/server/server.go b/command/server/server.go index 71d3ef3a8e..d1e4922064 100644 --- a/command/server/server.go +++ b/command/server/server.go @@ -204,6 +204,21 @@ func setFlags(cmd *cobra.Command) { "the CORS header indicating whether any JSON-RPC response can be shared with the specified origin", ) + cmd.Flags().Uint64Var( + ¶ms.jsonRPCBatchLengthLimit, + jsonRPCBatchRequestLimitFlag, + defaultConfig.JSONRPCBatchRequestLimit, + "the max length to be considered when handling json-rpc batch requests", + ) + + //nolint:lll + cmd.Flags().Uint64Var( + ¶ms.jsonRPCBlockRangeLimit, + jsonRPCBlockRangeLimitFlag, + defaultConfig.JSONRPCBlockRangeLimit, + "the max block range to be considered when executing json-rpc requests that consider fromBlock/toBlock values (e.g. eth_getLogs)", + ) + cmd.Flags().StringVar( ¶ms.rawConfig.LogFilePath, logFileLocationFlag, diff --git a/jsonrpc/dispatcher.go b/jsonrpc/dispatcher.go index 1225b2713c..cc979392d2 100644 --- a/jsonrpc/dispatcher.go +++ b/jsonrpc/dispatcher.go @@ -39,23 +39,32 @@ type endpoints struct { // Dispatcher handles all json rpc requests by delegating // the execution flow to the corresponding service type Dispatcher struct { - logger hclog.Logger - serviceMap map[string]*serviceData - filterManager *FilterManager - endpoints endpoints - chainID uint64 - priceLimit uint64 + logger hclog.Logger + serviceMap map[string]*serviceData + filterManager *FilterManager + endpoints endpoints + chainID uint64 + priceLimit uint64 + jsonRPCBatchLengthLimit uint64 } -func newDispatcher(logger hclog.Logger, store JSONRPCStore, chainID uint64, priceLimit uint64) *Dispatcher { +func newDispatcher( + logger hclog.Logger, + store JSONRPCStore, + chainID uint64, + priceLimit uint64, + jsonRPCBatchLengthLimit uint64, + blockRangeLimit uint64, +) *Dispatcher { d := &Dispatcher{ - logger: logger.Named("dispatcher"), - chainID: chainID, - priceLimit: priceLimit, + logger: logger.Named("dispatcher"), + chainID: chainID, + priceLimit: priceLimit, + jsonRPCBatchLengthLimit: jsonRPCBatchLengthLimit, } if store != nil { - d.filterManager = NewFilterManager(logger, store) + d.filterManager = NewFilterManager(logger, store, blockRangeLimit) go d.filterManager.Run() } @@ -253,6 +262,11 @@ func (d *Dispatcher) Handle(reqBody []byte) ([]byte, error) { return NewRPCResponse(nil, "2.0", nil, NewInvalidRequestError("Invalid json request")).Bytes() } + // avoid handling long batch requests + if len(requests) > int(d.jsonRPCBatchLengthLimit) { + return NewRPCResponse(nil, "2.0", nil, NewInvalidRequestError("Batch request length too long")).Bytes() + } + responses := make([]Response, 0) for _, req := range requests { diff --git a/jsonrpc/dispatcher_test.go b/jsonrpc/dispatcher_test.go index 4968c371b7..674b9bc08c 100644 --- a/jsonrpc/dispatcher_test.go +++ b/jsonrpc/dispatcher_test.go @@ -62,7 +62,7 @@ func TestDispatcher_HandleWebsocketConnection_EthSubscribe(t *testing.T) { t.Parallel() store := newMockStore() - dispatcher := newDispatcher(hclog.NewNullLogger(), store, 0, 0) + dispatcher := newDispatcher(hclog.NewNullLogger(), store, 0, 0, 20, 1000) mockConnection := &mockWsConn{ msgCh: make(chan []byte, 1), @@ -96,7 +96,7 @@ func TestDispatcher_HandleWebsocketConnection_EthSubscribe(t *testing.T) { func TestDispatcher_WebsocketConnection_RequestFormats(t *testing.T) { store := newMockStore() - dispatcher := newDispatcher(hclog.NewNullLogger(), store, 0, 0) + dispatcher := newDispatcher(hclog.NewNullLogger(), store, 0, 0, 20, 1000) mockConnection := &mockWsConn{ msgCh: make(chan []byte, 1), @@ -200,7 +200,7 @@ func (m *mockService) Filter(f LogQuery) (interface{}, error) { func TestDispatcherFuncDecode(t *testing.T) { srv := &mockService{msgCh: make(chan interface{}, 10)} - dispatcher := newDispatcher(hclog.NewNullLogger(), newMockStore(), 0, 0) + dispatcher := newDispatcher(hclog.NewNullLogger(), newMockStore(), 0, 0, 20, 1000) dispatcher.registerService("mock", srv) handleReq := func(typ string, msg string) interface{} { @@ -266,25 +266,95 @@ func TestDispatcherFuncDecode(t *testing.T) { } func TestDispatcherBatchRequest(t *testing.T) { - dispatcher := newDispatcher(hclog.NewNullLogger(), newMockStore(), 0, 0) + handle := func(dispatcher *Dispatcher, reqBody []byte) []byte { + res, _ := dispatcher.Handle(reqBody) - // test with leading whitespace (" \t\n\n\r") - leftBytes := []byte{0x20, 0x20, 0x09, 0x0A, 0x0A, 0x0D} - resp, err := dispatcher.Handle(append(leftBytes, []byte(`[ - {"id":1,"jsonrpc":"2.0","method":"eth_getBalance","params":["0x1", true]}, - {"id":2,"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x2", true]}, - {"id":3,"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x3", true]}, - {"id":4,"jsonrpc":"2.0","method": "web3_sha3","params": ["0x68656c6c6f20776f726c64"]} -]`)...)) - assert.NoError(t, err) - - var res []SuccessResponse - - assert.NoError(t, expectBatchJSONResult(resp, &res)) - assert.Len(t, res, 4) + return res + } - jsonerr := &ObjectError{Code: -32602, Message: "Invalid Params"} + cases := []struct { + name string + desc string + dispatcher *Dispatcher + reqBody []byte + err *ObjectError + batchResponse []*SuccessResponse + }{ + { + "leading-whitespace", + "test with leading whitespace (\" \\t\\n\\n\\r\\)", + newDispatcher(hclog.NewNullLogger(), newMockStore(), 0, 0, 20, 1000), + append([]byte{0x20, 0x20, 0x09, 0x0A, 0x0A, 0x0D}, []byte(`[ + {"id":1,"jsonrpc":"2.0","method":"eth_getBalance","params":["0x1", true]}, + {"id":2,"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x2", true]}, + {"id":3,"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x3", true]}, + {"id":4,"jsonrpc":"2.0","method": "web3_sha3","params": ["0x68656c6c6f20776f726c64"]}]`)...), + nil, + []*SuccessResponse{ + {Error: &ObjectError{Code: -32602, Message: "Invalid Params"}}, + {Error: nil}, + {Error: nil}, + {Error: nil}}, + }, + { + "valid-batch-req", + "test with batch req length within batchRequestLengthLimit", + newDispatcher(hclog.NewNullLogger(), newMockStore(), 0, 0, 10, 1000), + []byte(`[ + {"id":1,"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest", true]}, + {"id":2,"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest", true]}, + {"id":3,"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest", true]}, + {"id":4,"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest", true]}, + {"id":5,"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest", true]}, + {"id":6,"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest", true]}]`), + nil, + []*SuccessResponse{ + {Error: nil}, + {Error: nil}, + {Error: nil}, + {Error: nil}, + {Error: nil}, + {Error: nil}}, + }, + { + "invalid-batch-req", + "test with batch req length exceeding batchRequestLengthLimit", + newDispatcher(hclog.NewNullLogger(), newMockStore(), 0, 0, 3, 1000), + []byte(`[ + {"id":1,"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest", true]}, + {"id":2,"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest", true]}, + {"id":3,"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest", true]}, + {"id":4,"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest", true]}, + {"id":5,"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest", true]}, + {"id":6,"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest", true]}]`), + &ObjectError{Code: -32600, Message: "Batch request length too long"}, + nil, + }, + } - assert.Equal(t, res[0].Error, jsonerr) - assert.Nil(t, res[3].Error) + for _, c := range cases { + res := handle(c.dispatcher, c.reqBody) + + if c.err != nil { + var resp ErrorResponse + + assert.NoError(t, expectBatchJSONResult(res, &resp)) + assert.Equal(t, resp.Error, c.err) + } else { + var batchResp []SuccessResponse + assert.NoError(t, expectBatchJSONResult(res, &batchResp)) + + if c.name == "leading-whitespace" { + assert.Len(t, batchResp, 4) + for index, resp := range batchResp { + assert.Equal(t, resp.Error, c.batchResponse[index].Error) + } + } else if c.name == "valid-batch-req" { + assert.Len(t, batchResp, 6) + for index, resp := range batchResp { + assert.Equal(t, resp.Error, c.batchResponse[index].Error) + } + } + } + } } diff --git a/jsonrpc/filter_manager.go b/jsonrpc/filter_manager.go index 1d89966e93..cef8abd8cc 100644 --- a/jsonrpc/filter_manager.go +++ b/jsonrpc/filter_manager.go @@ -22,6 +22,7 @@ var ( ErrCastingFilterToLogFilter = errors.New("casting filter object to logFilter error") ErrBlockNotFound = errors.New("block not found") ErrIncorrectBlockRange = errors.New("incorrect range") + ErrBlockRangeTooHigh = errors.New("block range too high") ErrPendingBlockNumber = errors.New("pending block number is not supported") ErrNoWSConnection = errors.New("no websocket connection") ) @@ -244,9 +245,10 @@ type FilterManager struct { timeout time.Duration - store filterManagerStore - subscription blockchain.Subscription - blockStream *blockStream + store filterManagerStore + subscription blockchain.Subscription + blockStream *blockStream + blockRangeLimit uint64 filters map[string]filter timeouts timeHeapImpl @@ -255,16 +257,17 @@ type FilterManager struct { closeCh chan struct{} } -func NewFilterManager(logger hclog.Logger, store filterManagerStore) *FilterManager { +func NewFilterManager(logger hclog.Logger, store filterManagerStore, blockRangeLimit uint64) *FilterManager { m := &FilterManager{ - logger: logger.Named("filter"), - timeout: defaultTimeout, - store: store, - blockStream: &blockStream{}, - filters: make(map[string]filter), - timeouts: timeHeapImpl{}, - updateCh: make(chan struct{}), - closeCh: make(chan struct{}), + logger: logger.Named("filter"), + timeout: defaultTimeout, + store: store, + blockStream: &blockStream{}, + blockRangeLimit: blockRangeLimit, + filters: make(map[string]filter), + timeouts: timeHeapImpl{}, + updateCh: make(chan struct{}), + closeCh: make(chan struct{}), } // start blockstream with the current header @@ -434,6 +437,11 @@ func (f *FilterManager) getLogsFromBlocks(query *LogQuery) ([]*Log, error) { from = 1 } + // avoid handling large block ranges + if to-from > f.blockRangeLimit { + return nil, ErrBlockRangeTooHigh + } + logs := make([]*Log, 0) for i := from; i <= to; i++ { diff --git a/jsonrpc/filter_manager_test.go b/jsonrpc/filter_manager_test.go index 11cd5b14e0..2b6a05579b 100644 --- a/jsonrpc/filter_manager_test.go +++ b/jsonrpc/filter_manager_test.go @@ -80,6 +80,16 @@ func Test_GetLogsForQuery(t *testing.T) { 0, ErrIncorrectBlockRange, }, + { + "Block range too high", + &LogQuery{ + fromBlock: 10, + toBlock: 1012, + Topics: topics, + }, + 0, + ErrBlockRangeTooHigh, + }, } // setup test @@ -112,7 +122,7 @@ func Test_GetLogsForQuery(t *testing.T) { store.appendBlocksToStore(blocks) - f := NewFilterManager(hclog.NewNullLogger(), store) + f := NewFilterManager(hclog.NewNullLogger(), store, 1000) t.Cleanup(func() { defer f.Close() @@ -144,7 +154,7 @@ func Test_GetLogFilterFromID(t *testing.T) { store := newMockStore() - m := NewFilterManager(hclog.NewNullLogger(), store) + m := NewFilterManager(hclog.NewNullLogger(), store, 1000) defer m.Close() go m.Run() @@ -167,7 +177,7 @@ func TestFilterLog(t *testing.T) { store := newMockStore() - m := NewFilterManager(hclog.NewNullLogger(), store) + m := NewFilterManager(hclog.NewNullLogger(), store, 1000) defer m.Close() go m.Run() @@ -231,7 +241,7 @@ func TestFilterBlock(t *testing.T) { store := newMockStore() - m := NewFilterManager(hclog.NewNullLogger(), store) + m := NewFilterManager(hclog.NewNullLogger(), store, 1000) defer m.Close() go m.Run() @@ -296,7 +306,7 @@ func TestFilterTimeout(t *testing.T) { store := newMockStore() - m := NewFilterManager(hclog.NewNullLogger(), store) + m := NewFilterManager(hclog.NewNullLogger(), store, 1000) defer m.Close() m.timeout = 2 * time.Second @@ -320,7 +330,7 @@ func TestRemoveFilterByWebsocket(t *testing.T) { msgCh: make(chan []byte, 1), } - m := NewFilterManager(hclog.NewNullLogger(), store) + m := NewFilterManager(hclog.NewNullLogger(), store, 1000) defer m.Close() go m.Run() @@ -342,7 +352,7 @@ func TestFilterWebsocket(t *testing.T) { msgCh: make(chan []byte, 1), } - m := NewFilterManager(hclog.NewNullLogger(), store) + m := NewFilterManager(hclog.NewNullLogger(), store, 1000) defer m.Close() go m.Run() @@ -431,7 +441,7 @@ func TestClosedFilterDeletion(t *testing.T) { store := newMockStore() - m := NewFilterManager(hclog.NewNullLogger(), store) + m := NewFilterManager(hclog.NewNullLogger(), store, 1000) defer m.Close() go m.Run() diff --git a/jsonrpc/jsonrpc.go b/jsonrpc/jsonrpc.go index 92ce18f8f7..1009effbab 100644 --- a/jsonrpc/jsonrpc.go +++ b/jsonrpc/jsonrpc.go @@ -61,14 +61,17 @@ type Config struct { ChainID uint64 AccessControlAllowOrigin []string PriceLimit uint64 + BatchLengthLimit uint64 + BlockRangeLimit uint64 } // NewJSONRPC returns the JSONRPC http server func NewJSONRPC(logger hclog.Logger, config *Config) (*JSONRPC, error) { srv := &JSONRPC{ - logger: logger.Named("jsonrpc"), - config: config, - dispatcher: newDispatcher(logger, config.Store, config.ChainID, config.PriceLimit), + logger: logger.Named("jsonrpc"), + config: config, + dispatcher: newDispatcher(logger, config.Store, config.ChainID, config.PriceLimit, + config.BatchLengthLimit, config.BlockRangeLimit), } // start http server diff --git a/jsonrpc/web3_endpoint_test.go b/jsonrpc/web3_endpoint_test.go index 11df4c830b..a5266fe8a3 100644 --- a/jsonrpc/web3_endpoint_test.go +++ b/jsonrpc/web3_endpoint_test.go @@ -11,7 +11,7 @@ import ( ) func TestWeb3EndpointSha3(t *testing.T) { - dispatcher := newDispatcher(hclog.NewNullLogger(), newMockStore(), 0, 0) + dispatcher := newDispatcher(hclog.NewNullLogger(), newMockStore(), 0, 0, 20, 1000) resp, err := dispatcher.Handle([]byte(`{ "method": "web3_sha3", @@ -26,7 +26,7 @@ func TestWeb3EndpointSha3(t *testing.T) { } func TestWeb3EndpointClientVersion(t *testing.T) { - dispatcher := newDispatcher(hclog.NewNullLogger(), newMockStore(), 0, 0) + dispatcher := newDispatcher(hclog.NewNullLogger(), newMockStore(), 0, 0, 20, 1000) resp, err := dispatcher.Handle([]byte(`{ "method": "web3_clientVersion", diff --git a/server/config.go b/server/config.go index f941e6e792..703d4b4cdc 100644 --- a/server/config.go +++ b/server/config.go @@ -50,4 +50,6 @@ type Telemetry struct { type JSONRPC struct { JSONRPCAddr *net.TCPAddr AccessControlAllowOrigin []string + BatchLengthLimit uint64 + BlockRangeLimit uint64 } diff --git a/server/server.go b/server/server.go index 2b18344f48..7212f7503e 100644 --- a/server/server.go +++ b/server/server.go @@ -553,6 +553,8 @@ func (s *Server) setupJSONRPC() error { ChainID: uint64(s.config.Chain.Params.ChainID), AccessControlAllowOrigin: s.config.JSONRPC.AccessControlAllowOrigin, PriceLimit: s.config.PriceLimit, + BatchLengthLimit: s.config.JSONRPC.BatchLengthLimit, + BlockRangeLimit: s.config.JSONRPC.BlockRangeLimit, } srv, err := jsonrpc.NewJSONRPC(s.logger, conf)