Skip to content

Commit dce511c

Browse files
hlongvuzsfelfoldirjl493456442
authored
eth/filters, cmd: add config of eth_getLogs address limit (#32327)
Add cli configurable limit for the number of addresses allowed in eth_getLogs filter criteria: #32264 Key changes: - Added --rpc.getlogmaxaddrs CLI flag (default: 1000) to configure the maximum number of addresses - Updated ethconfig.Config with FilterMaxAddresses field for configuration management - Modified filter system to use the configurable limit instead of the hardcoded maxAddresses constant - Enhanced test coverage with new test cases for address limit validation - Removed hardcoded validation from JSON unmarshaling, moving it to runtime validation Please notice that I remove the check at FilterCriteria UnmarshalJSON because the runtime config can not pass into this validation. Please help review this change! --------- Co-authored-by: zsfelfoldi <[email protected]> Co-authored-by: rjl493456442 <[email protected]>
1 parent 2a82964 commit dce511c

File tree

8 files changed

+148
-39
lines changed

8 files changed

+148
-39
lines changed

cmd/geth/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ var (
182182
utils.RPCGlobalGasCapFlag,
183183
utils.RPCGlobalEVMTimeoutFlag,
184184
utils.RPCGlobalTxFeeCapFlag,
185+
utils.RPCGlobalLogQueryLimit,
185186
utils.AllowUnprotectedTxs,
186187
utils.BatchRequestLimit,
187188
utils.BatchResponseMaxSize,

cmd/utils/flags.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,12 @@ var (
600600
Value: ethconfig.Defaults.RPCTxFeeCap,
601601
Category: flags.APICategory,
602602
}
603+
RPCGlobalLogQueryLimit = &cli.IntFlag{
604+
Name: "rpc.logquerylimit",
605+
Usage: "Maximum number of alternative addresses or topics allowed per search position in eth_getLogs filter criteria (0 = no cap)",
606+
Value: ethconfig.Defaults.LogQueryLimit,
607+
Category: flags.APICategory,
608+
}
603609
// Authenticated RPC HTTP settings
604610
AuthListenFlag = &cli.StringFlag{
605611
Name: "authrpc.addr",
@@ -1699,6 +1705,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
16991705
if ctx.IsSet(CacheLogSizeFlag.Name) {
17001706
cfg.FilterLogCacheSize = ctx.Int(CacheLogSizeFlag.Name)
17011707
}
1708+
if ctx.IsSet(RPCGlobalLogQueryLimit.Name) {
1709+
cfg.LogQueryLimit = ctx.Int(RPCGlobalLogQueryLimit.Name)
1710+
}
17021711
if !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 {
17031712
// If snap-sync is requested, this flag is also required
17041713
if cfg.SyncMode == ethconfig.SnapSync {
@@ -2017,7 +2026,8 @@ func RegisterGraphQLService(stack *node.Node, backend ethapi.Backend, filterSyst
20172026
// RegisterFilterAPI adds the eth log filtering RPC API to the node.
20182027
func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconfig.Config) *filters.FilterSystem {
20192028
filterSystem := filters.NewFilterSystem(backend, filters.Config{
2020-
LogCacheSize: ethcfg.FilterLogCacheSize,
2029+
LogCacheSize: ethcfg.FilterLogCacheSize,
2030+
LogQueryLimit: ethcfg.LogQueryLimit,
20212031
})
20222032
stack.RegisterAPIs([]rpc.API{{
20232033
Namespace: "eth",

eth/ethconfig/config.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ var Defaults = Config{
6262
TrieTimeout: 60 * time.Minute,
6363
SnapshotCache: 102,
6464
FilterLogCacheSize: 32,
65+
LogQueryLimit: 1000,
6566
Miner: miner.DefaultConfig,
6667
TxPool: legacypool.DefaultConfig,
6768
BlobPool: blobpool.DefaultConfig,
@@ -131,6 +132,10 @@ type Config struct {
131132
// This is the number of blocks for which logs will be cached in the filter system.
132133
FilterLogCacheSize int
133134

135+
// This is the maximum number of addresses or topics allowed in filter criteria
136+
// for eth_getLogs.
137+
LogQueryLimit int
138+
134139
// Mining options
135140
Miner miner.Config
136141

eth/ethconfig/gen_config.go

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

eth/filters/api.go

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,10 @@ var (
4242
errBlockHashWithRange = errors.New("can't specify fromBlock/toBlock with blockHash")
4343
errPendingLogsUnsupported = errors.New("pending logs are not supported")
4444
errExceedMaxTopics = errors.New("exceed max topics")
45-
errExceedMaxAddresses = errors.New("exceed max addresses")
45+
errExceedLogQueryLimit = errors.New("exceed max addresses or topics per search position")
4646
)
4747

4848
const (
49-
// The maximum number of addresses allowed in a filter criteria
50-
maxAddresses = 1000
5149
// The maximum number of topic criteria allowed, vm.LOG4 - vm.LOG0
5250
maxTopics = 4
5351
// The maximum number of allowed topics within a topic criteria
@@ -70,20 +68,22 @@ type filter struct {
7068
// FilterAPI offers support to create and manage filters. This will allow external clients to retrieve various
7169
// information related to the Ethereum protocol such as blocks, transactions and logs.
7270
type FilterAPI struct {
73-
sys *FilterSystem
74-
events *EventSystem
75-
filtersMu sync.Mutex
76-
filters map[rpc.ID]*filter
77-
timeout time.Duration
71+
sys *FilterSystem
72+
events *EventSystem
73+
filtersMu sync.Mutex
74+
filters map[rpc.ID]*filter
75+
timeout time.Duration
76+
logQueryLimit int
7877
}
7978

8079
// NewFilterAPI returns a new FilterAPI instance.
8180
func NewFilterAPI(system *FilterSystem) *FilterAPI {
8281
api := &FilterAPI{
83-
sys: system,
84-
events: NewEventSystem(system),
85-
filters: make(map[rpc.ID]*filter),
86-
timeout: system.cfg.Timeout,
82+
sys: system,
83+
events: NewEventSystem(system),
84+
filters: make(map[rpc.ID]*filter),
85+
timeout: system.cfg.Timeout,
86+
logQueryLimit: system.cfg.LogQueryLimit,
8787
}
8888
go api.timeoutLoop(system.cfg.Timeout)
8989

@@ -347,8 +347,15 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type
347347
if len(crit.Topics) > maxTopics {
348348
return nil, errExceedMaxTopics
349349
}
350-
if len(crit.Addresses) > maxAddresses {
351-
return nil, errExceedMaxAddresses
350+
if api.logQueryLimit != 0 {
351+
if len(crit.Addresses) > api.logQueryLimit {
352+
return nil, errExceedLogQueryLimit
353+
}
354+
for _, topics := range crit.Topics {
355+
if len(topics) > api.logQueryLimit {
356+
return nil, errExceedLogQueryLimit
357+
}
358+
}
352359
}
353360

354361
var filter *Filter
@@ -545,9 +552,6 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error {
545552
// raw.Address can contain a single address or an array of addresses
546553
switch rawAddr := raw.Addresses.(type) {
547554
case []interface{}:
548-
if len(rawAddr) > maxAddresses {
549-
return errExceedMaxAddresses
550-
}
551555
for i, addr := range rawAddr {
552556
if strAddr, ok := addr.(string); ok {
553557
addr, err := decodeAddress(strAddr)

eth/filters/api_test.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package filters
1919
import (
2020
"encoding/json"
2121
"fmt"
22-
"strings"
2322
"testing"
2423

2524
"github.com/ethereum/go-ethereum/common"
@@ -183,15 +182,4 @@ func TestUnmarshalJSONNewFilterArgs(t *testing.T) {
183182
if len(test7.Topics[2]) != 0 {
184183
t.Fatalf("expected 0 topics, got %d topics", len(test7.Topics[2]))
185184
}
186-
187-
// multiple address exceeding max
188-
var test8 FilterCriteria
189-
addresses := make([]string, maxAddresses+1)
190-
for i := 0; i < maxAddresses+1; i++ {
191-
addresses[i] = fmt.Sprintf(`"%s"`, common.HexToAddress(fmt.Sprintf("0x%x", i)).Hex())
192-
}
193-
vector = fmt.Sprintf(`{"address": [%s]}`, strings.Join(addresses, ", "))
194-
if err := json.Unmarshal([]byte(vector), &test8); err != errExceedMaxAddresses {
195-
t.Fatal("expected errExceedMaxAddresses, got", err)
196-
}
197185
}

eth/filters/filter_system.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ import (
4141

4242
// Config represents the configuration of the filter system.
4343
type Config struct {
44-
LogCacheSize int // maximum number of cached blocks (default: 32)
45-
Timeout time.Duration // how long filters stay active (default: 5min)
44+
LogCacheSize int // maximum number of cached blocks (default: 32)
45+
Timeout time.Duration // how long filters stay active (default: 5min)
46+
LogQueryLimit int // maximum number of addresses allowed in filter criteria (default: 1000)
4647
}
4748

4849
func (cfg Config) withDefaults() Config {
@@ -291,8 +292,15 @@ func (es *EventSystem) SubscribeLogs(crit ethereum.FilterQuery, logs chan []*typ
291292
if len(crit.Topics) > maxTopics {
292293
return nil, errExceedMaxTopics
293294
}
294-
if len(crit.Addresses) > maxAddresses {
295-
return nil, errExceedMaxAddresses
295+
if es.sys.cfg.LogQueryLimit != 0 {
296+
if len(crit.Addresses) > es.sys.cfg.LogQueryLimit {
297+
return nil, errExceedLogQueryLimit
298+
}
299+
for _, topics := range crit.Topics {
300+
if len(topics) > es.sys.cfg.LogQueryLimit {
301+
return nil, errExceedLogQueryLimit
302+
}
303+
}
296304
}
297305
var from, to rpc.BlockNumber
298306
if crit.FromBlock == nil {

eth/filters/filter_system_test.go

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
"github.com/ethereum/go-ethereum/internal/ethapi"
3737
"github.com/ethereum/go-ethereum/params"
3838
"github.com/ethereum/go-ethereum/rpc"
39+
"github.com/ethereum/go-ethereum/triedb"
3940
)
4041

4142
type testBackend struct {
@@ -424,7 +425,7 @@ func TestInvalidLogFilterCreation(t *testing.T) {
424425

425426
var (
426427
db = rawdb.NewMemoryDatabase()
427-
_, sys = newTestFilterSystem(db, Config{})
428+
_, sys = newTestFilterSystem(db, Config{LogQueryLimit: 1000})
428429
api = NewFilterAPI(sys)
429430
)
430431

@@ -435,7 +436,7 @@ func TestInvalidLogFilterCreation(t *testing.T) {
435436
1: {FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(100)},
436437
2: {FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), ToBlock: big.NewInt(100)},
437438
3: {Topics: [][]common.Hash{{}, {}, {}, {}, {}}},
438-
4: {Addresses: make([]common.Address, maxAddresses+1)},
439+
4: {Addresses: make([]common.Address, api.logQueryLimit+1)},
439440
}
440441

441442
for i, test := range testCases {
@@ -455,7 +456,7 @@ func TestInvalidGetLogsRequest(t *testing.T) {
455456
BaseFee: big.NewInt(params.InitialBaseFee),
456457
}
457458
db, blocks, _ = core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 10, func(i int, gen *core.BlockGen) {})
458-
_, sys = newTestFilterSystem(db, Config{})
459+
_, sys = newTestFilterSystem(db, Config{LogQueryLimit: 10})
459460
api = NewFilterAPI(sys)
460461
blockHash = blocks[0].Hash()
461462
unknownBlockHash = common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111")
@@ -500,8 +501,8 @@ func TestInvalidGetLogsRequest(t *testing.T) {
500501
err: errExceedMaxTopics,
501502
},
502503
{
503-
f: FilterCriteria{BlockHash: &blockHash, Addresses: make([]common.Address, maxAddresses+1)},
504-
err: errExceedMaxAddresses,
504+
f: FilterCriteria{BlockHash: &blockHash, Addresses: make([]common.Address, api.logQueryLimit+1)},
505+
err: errExceedLogQueryLimit,
505506
},
506507
}
507508

@@ -528,6 +529,92 @@ func TestInvalidGetRangeLogsRequest(t *testing.T) {
528529
}
529530
}
530531

532+
// TestExceedLogQueryLimit tests getLogs with too many addresses or topics
533+
func TestExceedLogQueryLimit(t *testing.T) {
534+
t.Parallel()
535+
536+
// Test with custom config (LogQueryLimit = 5 for easier testing)
537+
var (
538+
db = rawdb.NewMemoryDatabase()
539+
backend, sys = newTestFilterSystem(db, Config{LogQueryLimit: 5})
540+
api = NewFilterAPI(sys)
541+
gspec = &core.Genesis{
542+
Config: params.TestChainConfig,
543+
Alloc: types.GenesisAlloc{},
544+
BaseFee: big.NewInt(params.InitialBaseFee),
545+
}
546+
)
547+
548+
_, err := gspec.Commit(db, triedb.NewDatabase(db, nil))
549+
if err != nil {
550+
t.Fatal(err)
551+
}
552+
chain, _ := core.GenerateChain(gspec.Config, gspec.ToBlock(), ethash.NewFaker(), db, 1000, func(i int, gen *core.BlockGen) {})
553+
554+
options := core.DefaultConfig().WithStateScheme(rawdb.HashScheme)
555+
options.TxLookupLimit = 0 // index all txs
556+
bc, err := core.NewBlockChain(db, gspec, ethash.NewFaker(), options)
557+
if err != nil {
558+
t.Fatal(err)
559+
}
560+
_, err = bc.InsertChain(chain[:600])
561+
if err != nil {
562+
t.Fatal(err)
563+
}
564+
565+
backend.startFilterMaps(200, false, filtermaps.RangeTestParams)
566+
defer backend.stopFilterMaps()
567+
568+
addresses := make([]common.Address, 6)
569+
for i := range addresses {
570+
addresses[i] = common.HexToAddress("0x1234567890123456789012345678901234567890")
571+
}
572+
573+
topics := make([]common.Hash, 6)
574+
for i := range topics {
575+
topics[i] = common.HexToHash("0x123456789012345678901234567890123456789001234567890012345678901234")
576+
}
577+
578+
// Test that 5 addresses do not result in error
579+
// Add FromBlock and ToBlock to make it similar to other invalid tests
580+
if _, err := api.GetLogs(context.Background(), FilterCriteria{
581+
FromBlock: big.NewInt(0),
582+
ToBlock: big.NewInt(100),
583+
Addresses: addresses[:5],
584+
}); err != nil {
585+
t.Errorf("Expected GetLogs with 5 addresses to return with no error, got: %v", err)
586+
}
587+
588+
// Test that 6 addresses fails with correct error
589+
if _, err := api.GetLogs(context.Background(), FilterCriteria{
590+
FromBlock: big.NewInt(0),
591+
ToBlock: big.NewInt(100),
592+
Addresses: addresses,
593+
}); err != errExceedLogQueryLimit {
594+
t.Errorf("Expected GetLogs with 6 addresses to return errExceedLogQueryLimit, got: %v", err)
595+
}
596+
597+
// Test that 5 topics at one position do not result in error
598+
if _, err := api.GetLogs(context.Background(), FilterCriteria{
599+
FromBlock: big.NewInt(0),
600+
ToBlock: big.NewInt(100),
601+
Addresses: addresses[:1],
602+
Topics: [][]common.Hash{topics[:5]},
603+
}); err != nil {
604+
t.Errorf("Expected GetLogs with 5 topics at one position to return with no error, got: %v", err)
605+
}
606+
607+
// Test that 6 topics at one position fails with correct error
608+
if _, err := api.GetLogs(context.Background(), FilterCriteria{
609+
FromBlock: big.NewInt(0),
610+
ToBlock: big.NewInt(100),
611+
Addresses: addresses[:1],
612+
Topics: [][]common.Hash{topics},
613+
}); err != errExceedLogQueryLimit {
614+
t.Errorf("Expected GetLogs with 6 topics at one position to return errExceedLogQueryLimit, got: %v", err)
615+
}
616+
}
617+
531618
// TestLogFilter tests whether log filters match the correct logs that are posted to the event feed.
532619
func TestLogFilter(t *testing.T) {
533620
t.Parallel()

0 commit comments

Comments
 (0)