From 84db0fb77386a870a034e092c9ef55353f989f88 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Tue, 24 Feb 2026 11:55:17 +0800 Subject: [PATCH] fix: reject invalid block ranges when creating NewFilter --- CHANGELOG.md | 3 +- rpc/namespaces/ethereum/eth/filters/api.go | 47 +++++++++++-------- .../ethereum/eth/filters/api_test.go | 41 ++++++++++++++++ 3 files changed, 70 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65863ab60..a260bfb3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ ### BUG FIXES - [\#965](https://github.com/cosmos/evm/pull/965) Fix gas double charging on EVM calls in IBCOnTimeoutPacketCallback. +- [\#965](https://github.com/cosmos/evm/pull/965) Fix gas double charging on EVM calls in IBCOnTimeoutPacketCallback. - [\#869](https://github.com/cosmos/evm/pull/869) Fix erc20 IBC callbacks to check for native token transfer before parsing recipient. - [\#860](https://github.com/cosmos/evm/pull/860) Fix EIP-712 signature verification to use configured EVM chain ID instead of parsing cosmos chain ID string and replace legacytx.StdSignBytes with the aminojson sign mode handler. - [\#794](https://github.com/cosmos/evm/pull/794) Fix mempool.max-txs flag not using desired default of 0 @@ -43,6 +43,7 @@ - [\#967](https://github.com/cosmos/evm/pull/967) Fix return value of erc20 ibcv2 middleware to properly reflect application success and middleware failure. - [\#992](https://github.com/cosmos/evm/pull/992) Respect the provided `gasCap` in `CallEVMWithData` instead of always used the default cap. - [\#993](https://github.com/cosmos/evm/pull/993) Enforce `src_callback` contract address to match the packet sender for IBC acknowledgement and timeout callbacks to prevent arbitrary contract execution. +- [\#1016](https://github.com/cosmos/evm/pull/1016) Reject invalid block ranges when creating NewFilter. ## v0.5.0 diff --git a/rpc/namespaces/ethereum/eth/filters/api.go b/rpc/namespaces/ethereum/eth/filters/api.go index fedf94ff2..1389cbc00 100644 --- a/rpc/namespaces/ethereum/eth/filters/api.go +++ b/rpc/namespaces/ethereum/eth/filters/api.go @@ -3,6 +3,7 @@ package filters import ( "context" "fmt" + "math/big" "sync" "time" @@ -31,6 +32,24 @@ var ( tracer = otel.Tracer("evm/rpc/namespaces/ethereum/eth/filters") ) +func getBlockRange(fromBlock, toBlock *big.Int) (int64, int64, error) { + // Convert the RPC block numbers into internal representations + begin := rpc.LatestBlockNumber.Int64() + if fromBlock != nil { + begin = fromBlock.Int64() + } + end := rpc.LatestBlockNumber.Int64() + if toBlock != nil { + end = toBlock.Int64() + } + // Block numbers below 0 are special cases. + // for more info, https://github.com/ethereum/go-ethereum/blob/v1.15.11/eth/filters/api.go#L360 + if begin > 0 && end > 0 && begin > end { + return 0, 0, errInvalidBlockRange + } + return begin, end, nil +} + // FilterAPI gathers type FilterAPI interface { NewPendingTransactionFilter() rpc.ID @@ -205,6 +224,9 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, error) { api.filtersMu.Lock() defer api.filtersMu.Unlock() + if _, _, err := getBlockRange(criteria.FromBlock, criteria.ToBlock); err != nil { + return "", err + } if len(api.filters) >= int(api.backend.RPCFilterCap()) { return "", fmt.Errorf("error creating filter: max limit reached") @@ -233,18 +255,8 @@ func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit filters.FilterCrit // Block filter requested, construct a single-shot filter filter = NewBlockFilter(api.logger, api.backend, crit) } else { - // Convert the RPC block numbers into internal representations - begin := rpc.LatestBlockNumber.Int64() - if crit.FromBlock != nil { - begin = crit.FromBlock.Int64() - } - end := rpc.LatestBlockNumber.Int64() - if crit.ToBlock != nil { - end = crit.ToBlock.Int64() - } - // Block numbers below 0 are special cases. - // for more info, https://github.com/ethereum/go-ethereum/blob/v1.15.11/eth/filters/api.go#L360 - if begin > 0 && end > 0 && begin > end { + begin, end, err := getBlockRange(crit.FromBlock, crit.ToBlock) + if err != nil { return nil, errInvalidBlockRange } // Construct the range filter @@ -298,14 +310,9 @@ func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) (_ []* // Block filter requested, construct a single-shot filter filter = NewBlockFilter(api.logger, api.backend, f.crit) } else { - // Convert the RPC block numbers into internal representations - begin := rpc.LatestBlockNumber.Int64() - if f.crit.FromBlock != nil { - begin = f.crit.FromBlock.Int64() - } - end := rpc.LatestBlockNumber.Int64() - if f.crit.ToBlock != nil { - end = f.crit.ToBlock.Int64() + begin, end, err := getBlockRange(f.crit.FromBlock, f.crit.ToBlock) + if err != nil { + return nil, errInvalidBlockRange } // Construct the range filter filter = NewRangeFilter(api.logger, api.backend, begin, end, f.crit.Addresses, f.crit.Topics) diff --git a/rpc/namespaces/ethereum/eth/filters/api_test.go b/rpc/namespaces/ethereum/eth/filters/api_test.go index be15dc763..7c595e3c7 100644 --- a/rpc/namespaces/ethereum/eth/filters/api_test.go +++ b/rpc/namespaces/ethereum/eth/filters/api_test.go @@ -1,6 +1,8 @@ package filters import ( + "context" + "math/big" "sync" "testing" "time" @@ -10,6 +12,45 @@ import ( "github.com/stretchr/testify/require" ) +func TestInvalidBlockRange(t *testing.T) { + invalidCriteria := filters.FilterCriteria{ + FromBlock: big.NewInt(0x20), + ToBlock: big.NewInt(0x10), + } + t.Run("NewFilter", func(t *testing.T) { + api := &PublicFilterAPI{ + filters: make(map[rpc.ID]*filter), + } + id, err := api.NewFilter(invalidCriteria) + require.Equal(t, rpc.ID(""), id) + require.Error(t, err) + require.Len(t, api.filters, 0) + require.ErrorIs(t, err, errInvalidBlockRange) + }) + + t.Run("GetFilterLogs", func(t *testing.T) { + id := rpc.NewID() + api := &PublicFilterAPI{ + filters: map[rpc.ID]*filter{ + id: { + typ: filters.LogsSubscription, + crit: invalidCriteria, + }, + }, + } + logs, err := api.GetFilterLogs(context.Background(), id) + require.Nil(t, logs) + require.ErrorIs(t, err, errInvalidBlockRange) + }) + + t.Run("GetLogs", func(t *testing.T) { + api := &PublicFilterAPI{} + logs, err := api.GetLogs(context.Background(), invalidCriteria) + require.Nil(t, logs) + require.ErrorIs(t, err, errInvalidBlockRange) + }) +} + func TestTimeoutLoop_PanicOnNilCancel(t *testing.T) { api := &PublicFilterAPI{ filters: make(map[rpc.ID]*filter),