diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 750bf55927..b08c1b3809 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -180,6 +180,7 @@ var ( utils.RPCGlobalGasCapFlag, utils.RPCGlobalEVMTimeoutFlag, utils.RPCGlobalTxFeeCapFlag, + utils.EthGetLogMaxAddressFlag, utils.AllowUnprotectedTxs, utils.BatchRequestLimit, utils.BatchResponseMaxSize, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index a134ea4308..8f27eaf9bb 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -590,6 +590,12 @@ var ( Value: ethconfig.Defaults.RPCTxFeeCap, Category: flags.APICategory, } + EthGetLogMaxAddressFlag = &cli.Uint64Flag{ + Name: "rpc.getlogmaxaddrs", + Usage: "Maximum number of addresses allowed in eth_getLogs filter criteria", + Value: 1000, + Category: flags.APICategory, + } // Authenticated RPC HTTP settings AuthListenFlag = &cli.StringFlag{ Name: "authrpc.addr", @@ -1689,6 +1695,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.IsSet(CacheLogSizeFlag.Name) { cfg.FilterLogCacheSize = ctx.Int(CacheLogSizeFlag.Name) } + if ctx.IsSet(EthGetLogMaxAddressFlag.Name) { + cfg.FilterMaxAddresses = int(ctx.Uint64(EthGetLogMaxAddressFlag.Name)) + } if !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 { // If snap-sync is requested, this flag is also required if cfg.SyncMode == ethconfig.SnapSync { @@ -1998,6 +2007,7 @@ func RegisterGraphQLService(stack *node.Node, backend ethapi.Backend, filterSyst func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconfig.Config) *filters.FilterSystem { filterSystem := filters.NewFilterSystem(backend, filters.Config{ LogCacheSize: ethcfg.FilterLogCacheSize, + MaxAddresses: ethcfg.FilterMaxAddresses, }) stack.RegisterAPIs([]rpc.API{{ Namespace: "eth", diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index dc77141081..c43a11afef 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -62,6 +62,7 @@ var Defaults = Config{ TrieTimeout: 60 * time.Minute, SnapshotCache: 102, FilterLogCacheSize: 32, + FilterMaxAddresses: 1000, Miner: miner.DefaultConfig, TxPool: legacypool.DefaultConfig, BlobPool: blobpool.DefaultConfig, @@ -131,6 +132,9 @@ type Config struct { // This is the number of blocks for which logs will be cached in the filter system. FilterLogCacheSize int + // This is the maximum number of addresses allowed in filter criteria for eth_getLogs. + FilterMaxAddresses int + // Mining options Miner miner.Config diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index 2fdd219dee..10349ffb97 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -44,6 +44,7 @@ func (c Config) MarshalTOML() (interface{}, error) { SnapshotCache int Preimages bool FilterLogCacheSize int + FilterMaxAddresses int Miner miner.Config TxPool legacypool.Config BlobPool blobpool.Config @@ -86,6 +87,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.SnapshotCache = c.SnapshotCache enc.Preimages = c.Preimages enc.FilterLogCacheSize = c.FilterLogCacheSize + enc.FilterMaxAddresses = c.FilterMaxAddresses enc.Miner = c.Miner enc.TxPool = c.TxPool enc.BlobPool = c.BlobPool @@ -132,6 +134,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { SnapshotCache *int Preimages *bool FilterLogCacheSize *int + FilterMaxAddresses *int Miner *miner.Config TxPool *legacypool.Config BlobPool *blobpool.Config @@ -231,6 +234,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.FilterLogCacheSize != nil { c.FilterLogCacheSize = *dec.FilterLogCacheSize } + if dec.FilterMaxAddresses != nil { + c.FilterMaxAddresses = *dec.FilterMaxAddresses + } if dec.Miner != nil { c.Miner = *dec.Miner } diff --git a/eth/filters/api.go b/eth/filters/api.go index c929810a12..fe5fe1b25e 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -46,8 +46,6 @@ var ( ) const ( - // The maximum number of addresses allowed in a filter criteria - maxAddresses = 1000 // The maximum number of topic criteria allowed, vm.LOG4 - vm.LOG0 maxTopics = 4 // The maximum number of allowed topics within a topic criteria @@ -70,20 +68,22 @@ type filter struct { // FilterAPI offers support to create and manage filters. This will allow external clients to retrieve various // information related to the Ethereum protocol such as blocks, transactions and logs. type FilterAPI struct { - sys *FilterSystem - events *EventSystem - filtersMu sync.Mutex - filters map[rpc.ID]*filter - timeout time.Duration + sys *FilterSystem + events *EventSystem + filtersMu sync.Mutex + filters map[rpc.ID]*filter + timeout time.Duration + maxAddresses int } // NewFilterAPI returns a new FilterAPI instance. func NewFilterAPI(system *FilterSystem) *FilterAPI { api := &FilterAPI{ - sys: system, - events: NewEventSystem(system), - filters: make(map[rpc.ID]*filter), - timeout: system.cfg.Timeout, + sys: system, + events: NewEventSystem(system), + filters: make(map[rpc.ID]*filter), + timeout: system.cfg.Timeout, + maxAddresses: system.cfg.MaxAddresses, } go api.timeoutLoop(system.cfg.Timeout) @@ -347,7 +347,7 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type if len(crit.Topics) > maxTopics { return nil, errExceedMaxTopics } - if len(crit.Addresses) > maxAddresses { + if len(crit.Addresses) > api.maxAddresses { return nil, errExceedMaxAddresses } @@ -545,9 +545,6 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error { // raw.Address can contain a single address or an array of addresses switch rawAddr := raw.Addresses.(type) { case []interface{}: - if len(rawAddr) > maxAddresses { - return errExceedMaxAddresses - } for i, addr := range rawAddr { if strAddr, ok := addr.(string); ok { addr, err := decodeAddress(strAddr) diff --git a/eth/filters/api_test.go b/eth/filters/api_test.go index 2eb3ee97b3..822bc826f6 100644 --- a/eth/filters/api_test.go +++ b/eth/filters/api_test.go @@ -19,7 +19,6 @@ package filters import ( "encoding/json" "fmt" - "strings" "testing" "github.com/ethereum/go-ethereum/common" @@ -183,15 +182,4 @@ func TestUnmarshalJSONNewFilterArgs(t *testing.T) { if len(test7.Topics[2]) != 0 { t.Fatalf("expected 0 topics, got %d topics", len(test7.Topics[2])) } - - // multiple address exceeding max - var test8 FilterCriteria - addresses := make([]string, maxAddresses+1) - for i := 0; i < maxAddresses+1; i++ { - addresses[i] = fmt.Sprintf(`"%s"`, common.HexToAddress(fmt.Sprintf("0x%x", i)).Hex()) - } - vector = fmt.Sprintf(`{"address": [%s]}`, strings.Join(addresses, ", ")) - if err := json.Unmarshal([]byte(vector), &test8); err != errExceedMaxAddresses { - t.Fatal("expected errExceedMaxAddresses, got", err) - } } diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go index 751cd417e8..7f9132f801 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -43,6 +43,7 @@ import ( type Config struct { LogCacheSize int // maximum number of cached blocks (default: 32) Timeout time.Duration // how long filters stay active (default: 5min) + MaxAddresses int // maximum number of addresses allowed in filter criteria (default: 1000) } func (cfg Config) withDefaults() Config { @@ -52,6 +53,9 @@ func (cfg Config) withDefaults() Config { if cfg.LogCacheSize == 0 { cfg.LogCacheSize = 32 } + if cfg.MaxAddresses == 0 { + cfg.MaxAddresses = 1000 + } return cfg } @@ -291,7 +295,7 @@ func (es *EventSystem) SubscribeLogs(crit ethereum.FilterQuery, logs chan []*typ if len(crit.Topics) > maxTopics { return nil, errExceedMaxTopics } - if len(crit.Addresses) > maxAddresses { + if len(crit.Addresses) > es.sys.cfg.MaxAddresses { return nil, errExceedMaxAddresses } var from, to rpc.BlockNumber diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index 013c1ae527..121678093a 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -435,7 +435,7 @@ func TestInvalidLogFilterCreation(t *testing.T) { 1: {FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(100)}, 2: {FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), ToBlock: big.NewInt(100)}, 3: {Topics: [][]common.Hash{{}, {}, {}, {}, {}}}, - 4: {Addresses: make([]common.Address, maxAddresses+1)}, + 4: {Addresses: make([]common.Address, api.maxAddresses+1)}, } for i, test := range testCases { @@ -500,8 +500,8 @@ func TestInvalidGetLogsRequest(t *testing.T) { err: errExceedMaxTopics, }, { - f: FilterCriteria{BlockHash: &blockHash, Addresses: make([]common.Address, maxAddresses+1)}, - err: errExceedMaxAddresses, + f: FilterCriteria{BlockHash: &blockHash, Addresses: make([]common.Address, api.logQueryLimit+1)}, + err: errExceedLogQueryLimit, }, } @@ -528,6 +528,54 @@ func TestInvalidGetRangeLogsRequest(t *testing.T) { } } +// TestInvalidAddressLengthRequest tests getLogs with too many addresses +func TestInvalidAddressLengthRequest(t *testing.T) { + t.Parallel() + + // Test with custom config (MaxAddresses = 5 for easier testing) + var ( + db = rawdb.NewMemoryDatabase() + _, sys = newTestFilterSystem(db, Config{MaxAddresses: 5}) + api = NewFilterAPI(sys) + ) + + // Test that 6 addresses fails with correct error + invalidAddresses := make([]common.Address, 6) + for i := range invalidAddresses { + invalidAddresses[i] = common.HexToAddress("0x1234567890123456789012345678901234567890") + } + + // Add FromBlock and ToBlock to make it similar to other invalid tests + if _, err := api.GetLogs(context.Background(), FilterCriteria{ + FromBlock: big.NewInt(0), + ToBlock: big.NewInt(100), + Addresses: invalidAddresses, + }); err != errExceedMaxAddresses { + t.Errorf("Expected GetLogs with 6 addresses to return errExceedMaxAddresses, but got: %v", err) + } + + // Test with default config should reject 1001 addresses + var ( + db2 = rawdb.NewMemoryDatabase() + _, sys2 = newTestFilterSystem(db2, Config{}) // Uses default MaxAddresses = 1000 + api2 = NewFilterAPI(sys2) + ) + + // Test that 1001 addresses fails with correct error + tooManyAddresses := make([]common.Address, 1001) + for i := range tooManyAddresses { + tooManyAddresses[i] = common.HexToAddress("0x1234567890123456789012345678901234567890") + } + + if _, err := api2.GetLogs(context.Background(), FilterCriteria{ + FromBlock: big.NewInt(0), + ToBlock: big.NewInt(100), + Addresses: tooManyAddresses, + }); err != errExceedMaxAddresses { + t.Errorf("Expected GetLogs with 1001 addresses to return errExceedMaxAddresses, but got: %v", err) + } +} + // TestLogFilter tests whether log filters match the correct logs that are posted to the event feed. func TestLogFilter(t *testing.T) { t.Parallel()