eth/filters, cmd: add config of eth_getLogs address limit #33320 #32327 (#1961)

* eth/filters: change error code for invalid parameter errors #33320

* eth/filters, cmd: add config of eth_getLogs address limit #32327
This commit is contained in:
Daniel Liu 2026-01-29 13:56:45 +08:00 committed by GitHub
parent 84aedaa7bf
commit 4768d00e1e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 84 additions and 51 deletions

View file

@ -162,6 +162,7 @@ var (
utils.IPCDisabledFlag, utils.IPCDisabledFlag,
utils.IPCPathFlag, utils.IPCPathFlag,
utils.RPCGlobalTxFeeCapFlag, utils.RPCGlobalTxFeeCapFlag,
utils.RPCGlobalLogQueryLimit,
utils.AllowUnprotectedTxsFlag, utils.AllowUnprotectedTxsFlag,
utils.BatchRequestLimitFlag, utils.BatchRequestLimitFlag,
utils.BatchResponseMaxSizeFlag, utils.BatchResponseMaxSizeFlag,

View file

@ -407,6 +407,12 @@ var (
Value: ethconfig.Defaults.RPCTxFeeCap, Value: ethconfig.Defaults.RPCTxFeeCap,
Category: flags.APICategory, Category: flags.APICategory,
} }
RPCGlobalLogQueryLimit = &cli.IntFlag{
Name: "rpc.logquerylimit",
Usage: "Maximum number of alternative addresses or topics allowed per search position in eth_getLogs filter criteria (0 = no cap)",
Value: ethconfig.Defaults.LogQueryLimit,
Category: flags.APICategory,
}
// Authenticated RPC HTTP settings // Authenticated RPC HTTP settings
AuthListenFlag = &cli.StringFlag{ AuthListenFlag = &cli.StringFlag{
Name: "authrpc-addr", Name: "authrpc-addr",
@ -1523,6 +1529,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(RPCGlobalLogQueryLimit.Name) {
cfg.LogQueryLimit = ctx.Int(RPCGlobalLogQueryLimit.Name)
}
if ctx.IsSet(VMEnableDebugFlag.Name) { if ctx.IsSet(VMEnableDebugFlag.Name) {
// TODO(fjl): force-enable this in --dev mode // TODO(fjl): force-enable this in --dev mode
cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name) cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name)
@ -1838,7 +1847,8 @@ func WalkMatch(root, pattern string) ([]string, error) {
// RegisterFilterAPI adds the eth log filtering RPC API to the node. // RegisterFilterAPI adds the eth log filtering RPC API to the node.
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,
LogQueryLimit: ethcfg.LogQueryLimit,
}) })
stack.RegisterAPIs([]rpc.API{{ stack.RegisterAPIs([]rpc.API{{
Namespace: "eth", Namespace: "eth",

View file

@ -50,13 +50,13 @@ var Defaults = Config{
TrieDirtyCache: 256, TrieDirtyCache: 256,
TrieTimeout: 5 * time.Minute, TrieTimeout: 5 * time.Minute,
FilterLogCacheSize: 32, FilterLogCacheSize: 32,
LogQueryLimit: 1000,
GasPrice: big.NewInt(0.25 * params.Shannon), GasPrice: big.NewInt(0.25 * params.Shannon),
TxPool: legacypool.DefaultConfig,
TxPool: legacypool.DefaultConfig, RPCGasCap: 50000000,
RPCGasCap: 50000000, RPCEVMTimeout: 5 * time.Second,
RPCEVMTimeout: 5 * time.Second, GPO: FullNodeGPO,
GPO: FullNodeGPO, RPCTxFeeCap: 1, // 1 ether
RPCTxFeeCap: 1, // 1 ether
} }
//go:generate go run github.com/fjl/gencodec -type Config -field-override configMarshaling -formats toml -out gen_config.go //go:generate go run github.com/fjl/gencodec -type Config -field-override configMarshaling -formats toml -out gen_config.go
@ -92,6 +92,10 @@ 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 or topics allowed in filter criteria
// for eth_getLogs.
LogQueryLimit int
// Mining-related options // Mining-related options
Etherbase common.Address `toml:",omitempty"` Etherbase common.Address `toml:",omitempty"`
MinerThreads int `toml:",omitempty"` MinerThreads int `toml:",omitempty"`

View file

@ -35,6 +35,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
TrieTimeout time.Duration TrieTimeout time.Duration
Preimages bool Preimages bool
FilterLogCacheSize int FilterLogCacheSize int
LogQueryLimit int
Etherbase common.Address `toml:",omitempty"` Etherbase common.Address `toml:",omitempty"`
MinerThreads int `toml:",omitempty"` MinerThreads int `toml:",omitempty"`
ExtraData hexutil.Bytes `toml:",omitempty"` ExtraData hexutil.Bytes `toml:",omitempty"`
@ -65,6 +66,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.TrieTimeout = c.TrieTimeout enc.TrieTimeout = c.TrieTimeout
enc.Preimages = c.Preimages enc.Preimages = c.Preimages
enc.FilterLogCacheSize = c.FilterLogCacheSize enc.FilterLogCacheSize = c.FilterLogCacheSize
enc.LogQueryLimit = c.LogQueryLimit
enc.Etherbase = c.Etherbase enc.Etherbase = c.Etherbase
enc.MinerThreads = c.MinerThreads enc.MinerThreads = c.MinerThreads
enc.ExtraData = c.ExtraData enc.ExtraData = c.ExtraData
@ -99,6 +101,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
TrieTimeout *time.Duration TrieTimeout *time.Duration
Preimages *bool Preimages *bool
FilterLogCacheSize *int FilterLogCacheSize *int
LogQueryLimit *int
Etherbase *common.Address `toml:",omitempty"` Etherbase *common.Address `toml:",omitempty"`
MinerThreads *int `toml:",omitempty"` MinerThreads *int `toml:",omitempty"`
ExtraData *hexutil.Bytes `toml:",omitempty"` ExtraData *hexutil.Bytes `toml:",omitempty"`
@ -164,6 +167,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.LogQueryLimit != nil {
c.LogQueryLimit = *dec.LogQueryLimit
}
if dec.Etherbase != nil { if dec.Etherbase != nil {
c.Etherbase = *dec.Etherbase c.Etherbase = *dec.Etherbase
} }

View file

@ -33,18 +33,27 @@ import (
) )
var ( var (
errInvalidTopic = errors.New("invalid topic(s)") errInvalidTopic = invalidParamsErr("invalid topic(s)")
errFilterNotFound = errors.New("filter not found") errInvalidBlockRange = invalidParamsErr("invalid block range params")
errInvalidBlockRange = errors.New("invalid block range params") errBlockHashWithRange = invalidParamsErr("can't specify fromBlock/toBlock with blockHash")
errUnknownBlock = errors.New("unknown block") errUnknownBlock = errors.New("unknown block")
errBlockHashWithRange = errors.New("can't specify fromBlock/toBlock with blockHash") errFilterNotFound = errors.New("filter not found")
errExceedMaxTopics = errors.New("exceed max topics") errExceedMaxTopics = errors.New("exceed max topics")
errExceedMaxAddresses = errors.New("exceed max addresses") errExceedLogQueryLimit = errors.New("exceed max addresses or topics per search position")
) )
type invalidParamsError struct {
err error
}
func (e invalidParamsError) Error() string { return e.err.Error() }
func (e invalidParamsError) ErrorCode() int { return -32602 }
func invalidParamsErr(format string, args ...any) error {
return invalidParamsError{fmt.Errorf(format, args...)}
}
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
) )
@ -64,20 +73,22 @@ type filter struct {
// FilterAPI offers support to create and manage filters. This will allow external clients to retrieve various // FilterAPI offers support to create and manage filters. This will allow external clients to retrieve various
// information related to the Ethereum protocol such als blocks, transactions and logs. // information related to the Ethereum protocol such als blocks, transactions and logs.
type FilterAPI struct { type FilterAPI struct {
sys *FilterSystem sys *FilterSystem
events *EventSystem events *EventSystem
filtersMu sync.Mutex filtersMu sync.Mutex
filters map[rpc.ID]*filter filters map[rpc.ID]*filter
timeout time.Duration timeout time.Duration
logQueryLimit int
} }
// NewFilterAPI returns a new FilterAPI instance. // NewFilterAPI returns a new FilterAPI instance.
func NewFilterAPI(system *FilterSystem, lightMode bool) *FilterAPI { func NewFilterAPI(system *FilterSystem, lightMode bool) *FilterAPI {
api := &FilterAPI{ api := &FilterAPI{
sys: system, sys: system,
events: NewEventSystem(system, lightMode), events: NewEventSystem(system, lightMode),
filters: make(map[rpc.ID]*filter), filters: make(map[rpc.ID]*filter),
timeout: system.cfg.Timeout, timeout: system.cfg.Timeout,
logQueryLimit: system.cfg.LogQueryLimit,
} }
go api.timeoutLoop(system.cfg.Timeout) go api.timeoutLoop(system.cfg.Timeout)
@ -341,8 +352,15 @@ 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 api.logQueryLimit != 0 {
return nil, errExceedMaxAddresses if len(crit.Addresses) > api.logQueryLimit {
return nil, errExceedLogQueryLimit
}
for _, topics := range crit.Topics {
if len(topics) > api.logQueryLimit {
return nil, errExceedLogQueryLimit
}
}
} }
var filter *Filter var filter *Filter
@ -526,9 +544,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/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/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

@ -41,8 +41,9 @@ import (
// Config represents the configuration of the filter system. // Config represents the configuration of the filter system.
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)
LogQueryLimit int // maximum number of addresses allowed in filter criteria (default: 1000)
} }
func (cfg Config) withDefaults() Config { func (cfg Config) withDefaults() Config {
@ -303,8 +304,15 @@ 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 es.sys.cfg.LogQueryLimit != 0 {
return nil, errExceedMaxAddresses if len(crit.Addresses) > es.sys.cfg.LogQueryLimit {
return nil, errExceedLogQueryLimit
}
for _, topics := range crit.Topics {
if len(topics) > es.sys.cfg.LogQueryLimit {
return nil, errExceedLogQueryLimit
}
}
} }
var from, to rpc.BlockNumber var from, to rpc.BlockNumber
if crit.FromBlock == nil { if crit.FromBlock == nil {

View file

@ -343,7 +343,7 @@ func TestInvalidLogFilterCreation(t *testing.T) {
var ( var (
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
_, sys = newTestFilterSystem(t, db, Config{}) _, sys = newTestFilterSystem(t, db, Config{LogQueryLimit: 1000})
api = NewFilterAPI(sys, false) api = NewFilterAPI(sys, false)
) )
@ -354,7 +354,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.logQueryLimit+1)},
} }
for i, test := range testCases { for i, test := range testCases {
@ -374,7 +374,7 @@ func TestInvalidGetLogsRequest(t *testing.T) {
BaseFee: big.NewInt(params.InitialBaseFee), BaseFee: big.NewInt(params.InitialBaseFee),
} }
db, blocks, _ = core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 10, func(i int, gen *core.BlockGen) {}) db, blocks, _ = core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 10, func(i int, gen *core.BlockGen) {})
_, sys = newTestFilterSystem(t, db, Config{}) _, sys = newTestFilterSystem(t, db, Config{LogQueryLimit: 10})
api = NewFilterAPI(sys, false) api = NewFilterAPI(sys, false)
blockHash = blocks[0].Hash() blockHash = blocks[0].Hash()
unknownBlockHash = common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111") unknownBlockHash = common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111")
@ -419,10 +419,11 @@ 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,
}, },
} }
for i, test := range testCases { for i, test := range testCases {
_, err := api.GetLogs(context.Background(), test.f) _, err := api.GetLogs(context.Background(), test.f)
if !errors.Is(err, test.err) { if !errors.Is(err, test.err) {