configurable address limit

This commit is contained in:
Long Vu 2025-07-31 21:46:35 +07:00 committed by zsfelfoldi
parent b340103e9d
commit 22580d2237
8 changed files with 89 additions and 31 deletions

View file

@ -180,6 +180,7 @@ var (
utils.RPCGlobalGasCapFlag, utils.RPCGlobalGasCapFlag,
utils.RPCGlobalEVMTimeoutFlag, utils.RPCGlobalEVMTimeoutFlag,
utils.RPCGlobalTxFeeCapFlag, utils.RPCGlobalTxFeeCapFlag,
utils.EthGetLogMaxAddressFlag,
utils.AllowUnprotectedTxs, utils.AllowUnprotectedTxs,
utils.BatchRequestLimit, utils.BatchRequestLimit,
utils.BatchResponseMaxSize, utils.BatchResponseMaxSize,

View file

@ -590,6 +590,12 @@ var (
Value: ethconfig.Defaults.RPCTxFeeCap, Value: ethconfig.Defaults.RPCTxFeeCap,
Category: flags.APICategory, 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 // Authenticated RPC HTTP settings
AuthListenFlag = &cli.StringFlag{ AuthListenFlag = &cli.StringFlag{
Name: "authrpc.addr", Name: "authrpc.addr",
@ -1689,6 +1695,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if ctx.IsSet(CacheLogSizeFlag.Name) { if ctx.IsSet(CacheLogSizeFlag.Name) {
cfg.FilterLogCacheSize = ctx.Int(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 !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 {
// If snap-sync is requested, this flag is also required // If snap-sync is requested, this flag is also required
if cfg.SyncMode == ethconfig.SnapSync { 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 { func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconfig.Config) *filters.FilterSystem {
filterSystem := filters.NewFilterSystem(backend, filters.Config{ filterSystem := filters.NewFilterSystem(backend, filters.Config{
LogCacheSize: ethcfg.FilterLogCacheSize, LogCacheSize: ethcfg.FilterLogCacheSize,
MaxAddresses: ethcfg.FilterMaxAddresses,
}) })
stack.RegisterAPIs([]rpc.API{{ stack.RegisterAPIs([]rpc.API{{
Namespace: "eth", Namespace: "eth",

View file

@ -62,6 +62,7 @@ var Defaults = Config{
TrieTimeout: 60 * time.Minute, TrieTimeout: 60 * time.Minute,
SnapshotCache: 102, SnapshotCache: 102,
FilterLogCacheSize: 32, FilterLogCacheSize: 32,
FilterMaxAddresses: 1000,
Miner: miner.DefaultConfig, Miner: miner.DefaultConfig,
TxPool: legacypool.DefaultConfig, TxPool: legacypool.DefaultConfig,
BlobPool: blobpool.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. // This is the number of blocks for which logs will be cached in the filter system.
FilterLogCacheSize int FilterLogCacheSize int
// This is the maximum number of addresses allowed in filter criteria for eth_getLogs.
FilterMaxAddresses int
// Mining options // Mining options
Miner miner.Config Miner miner.Config

View file

@ -44,6 +44,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
SnapshotCache int SnapshotCache int
Preimages bool Preimages bool
FilterLogCacheSize int FilterLogCacheSize int
FilterMaxAddresses int
Miner miner.Config Miner miner.Config
TxPool legacypool.Config TxPool legacypool.Config
BlobPool blobpool.Config BlobPool blobpool.Config
@ -86,6 +87,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.SnapshotCache = c.SnapshotCache enc.SnapshotCache = c.SnapshotCache
enc.Preimages = c.Preimages enc.Preimages = c.Preimages
enc.FilterLogCacheSize = c.FilterLogCacheSize enc.FilterLogCacheSize = c.FilterLogCacheSize
enc.FilterMaxAddresses = c.FilterMaxAddresses
enc.Miner = c.Miner enc.Miner = c.Miner
enc.TxPool = c.TxPool enc.TxPool = c.TxPool
enc.BlobPool = c.BlobPool enc.BlobPool = c.BlobPool
@ -132,6 +134,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
SnapshotCache *int SnapshotCache *int
Preimages *bool Preimages *bool
FilterLogCacheSize *int FilterLogCacheSize *int
FilterMaxAddresses *int
Miner *miner.Config Miner *miner.Config
TxPool *legacypool.Config TxPool *legacypool.Config
BlobPool *blobpool.Config BlobPool *blobpool.Config
@ -231,6 +234,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.FilterLogCacheSize != nil { if dec.FilterLogCacheSize != nil {
c.FilterLogCacheSize = *dec.FilterLogCacheSize c.FilterLogCacheSize = *dec.FilterLogCacheSize
} }
if dec.FilterMaxAddresses != nil {
c.FilterMaxAddresses = *dec.FilterMaxAddresses
}
if dec.Miner != nil { if dec.Miner != nil {
c.Miner = *dec.Miner c.Miner = *dec.Miner
} }

View file

@ -46,8 +46,6 @@ var (
) )
const ( const (
// The maximum number of addresses allowed in a filter criteria
maxAddresses = 1000
// The maximum number of topic criteria allowed, vm.LOG4 - vm.LOG0 // The maximum number of topic criteria allowed, vm.LOG4 - vm.LOG0
maxTopics = 4 maxTopics = 4
// The maximum number of allowed topics within a topic criteria // The maximum number of allowed topics within a topic criteria
@ -75,6 +73,7 @@ type FilterAPI struct {
filtersMu sync.Mutex filtersMu sync.Mutex
filters map[rpc.ID]*filter filters map[rpc.ID]*filter
timeout time.Duration timeout time.Duration
maxAddresses int
} }
// NewFilterAPI returns a new FilterAPI instance. // NewFilterAPI returns a new FilterAPI instance.
@ -84,6 +83,7 @@ func NewFilterAPI(system *FilterSystem) *FilterAPI {
events: NewEventSystem(system), events: NewEventSystem(system),
filters: make(map[rpc.ID]*filter), filters: make(map[rpc.ID]*filter),
timeout: system.cfg.Timeout, timeout: system.cfg.Timeout,
maxAddresses: system.cfg.MaxAddresses,
} }
go api.timeoutLoop(system.cfg.Timeout) 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 { if len(crit.Topics) > maxTopics {
return nil, errExceedMaxTopics return nil, errExceedMaxTopics
} }
if len(crit.Addresses) > maxAddresses { if len(crit.Addresses) > api.maxAddresses {
return nil, errExceedMaxAddresses 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 // raw.Address can contain a single address or an array of addresses
switch rawAddr := raw.Addresses.(type) { switch rawAddr := raw.Addresses.(type) {
case []interface{}: case []interface{}:
if len(rawAddr) > maxAddresses {
return errExceedMaxAddresses
}
for i, addr := range rawAddr { for i, addr := range rawAddr {
if strAddr, ok := addr.(string); ok { if strAddr, ok := addr.(string); ok {
addr, err := decodeAddress(strAddr) addr, err := decodeAddress(strAddr)

View file

@ -19,7 +19,6 @@ package filters
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -183,15 +182,4 @@ func TestUnmarshalJSONNewFilterArgs(t *testing.T) {
if len(test7.Topics[2]) != 0 { if len(test7.Topics[2]) != 0 {
t.Fatalf("expected 0 topics, got %d topics", len(test7.Topics[2])) 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)
}
} }

View file

@ -43,6 +43,7 @@ import (
type Config struct { type Config struct {
LogCacheSize int // maximum number of cached blocks (default: 32) LogCacheSize int // maximum number of cached blocks (default: 32)
Timeout time.Duration // how long filters stay active (default: 5min) 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 { func (cfg Config) withDefaults() Config {
@ -52,6 +53,9 @@ func (cfg Config) withDefaults() Config {
if cfg.LogCacheSize == 0 { if cfg.LogCacheSize == 0 {
cfg.LogCacheSize = 32 cfg.LogCacheSize = 32
} }
if cfg.MaxAddresses == 0 {
cfg.MaxAddresses = 1000
}
return cfg return cfg
} }
@ -291,7 +295,7 @@ func (es *EventSystem) SubscribeLogs(crit ethereum.FilterQuery, logs chan []*typ
if len(crit.Topics) > maxTopics { if len(crit.Topics) > maxTopics {
return nil, errExceedMaxTopics return nil, errExceedMaxTopics
} }
if len(crit.Addresses) > maxAddresses { if len(crit.Addresses) > es.sys.cfg.MaxAddresses {
return nil, errExceedMaxAddresses return nil, errExceedMaxAddresses
} }
var from, to rpc.BlockNumber var from, to rpc.BlockNumber

View file

@ -435,7 +435,7 @@ func TestInvalidLogFilterCreation(t *testing.T) {
1: {FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(100)}, 1: {FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(100)},
2: {FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), ToBlock: big.NewInt(100)}, 2: {FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), ToBlock: big.NewInt(100)},
3: {Topics: [][]common.Hash{{}, {}, {}, {}, {}}}, 3: {Topics: [][]common.Hash{{}, {}, {}, {}, {}}},
4: {Addresses: make([]common.Address, maxAddresses+1)}, 4: {Addresses: make([]common.Address, api.maxAddresses+1)},
} }
for i, test := range testCases { for i, test := range testCases {
@ -500,8 +500,8 @@ func TestInvalidGetLogsRequest(t *testing.T) {
err: errExceedMaxTopics, err: errExceedMaxTopics,
}, },
{ {
f: FilterCriteria{BlockHash: &blockHash, Addresses: make([]common.Address, maxAddresses+1)}, f: FilterCriteria{BlockHash: &blockHash, Addresses: make([]common.Address, api.logQueryLimit+1)},
err: errExceedMaxAddresses, 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. // TestLogFilter tests whether log filters match the correct logs that are posted to the event feed.
func TestLogFilter(t *testing.T) { func TestLogFilter(t *testing.T) {
t.Parallel() t.Parallel()