Skip to content

fix(rpc): cap log filter address/topic lists and honor context cancellation in eth_getLogs - #1109

Open
JayT106 wants to merge 8 commits into
developfrom
fix/getlogs-filter-limits
Open

JayT106 wants to merge 8 commits into
developfrom
fix/getlogs-filter-limits

Conversation

@JayT106

@JayT106 JayT106 commented Sep 24, 2026 •

Copy link
Copy Markdown

What

Bound address/topic lists in eth_getLogs, eth_newFilter and the logs WebSocket subscription, stop the range scan on context cancel, and match blocks against precomputed bloom bit positions.

Issue

  • No size check on crit.Addresses / crit.Topics. The WebSocket parser builds criteria by hand, so go-ethereum's UnmarshalJSON topic limits don't apply there either.
  • blockLogs ran one keccak per address per block. 100k addresses (fits in the 5MB body limit) × 10k blocks ≈ 5 min CPU per request.
  • Filter.Logs ignored ctx, so the scan kept running after the client disconnected.
  • NewRangeFilter already precomputed bloomFilters [][]BloomIV but nothing read it.

RPC-node DoS only.

Solution

  • ValidateCriteria: max 4 topic positions, 1000 addresses, 1000 alternatives per position (go-ethereum defaults). Called from GetLogs, NewFilter, subscribeLogs. Returns -32602.
  • Filter.Logs checks ctx.Err() before each block fetch.
  • blockLogs tests bloom bytes via bloomMatches using the precomputed IVs; keccak-per-lookup bloomFilter deleted. Bloom is a prefilter only, exact match still in FilterLogs.

1000 addresses × 10k blocks: ~3s → 5.4ms.

…lation in eth_getLogs

Address and topic lists in eth_getLogs, eth_newFilter and the logs
WebSocket subscription were unbounded, and the range scan ignored the
request context, so one oversized query could keep a core busy for
minutes after the caller disconnected.

Mirror go-ethereum: 4 topic positions, 1000 entries per list. Check
ctx.Err() before each block fetch.
@JayT106
JayT106 requested a review from a team as a code owner September 24, 2026 08:54
@JayT106
JayT106 requested review from randy-cro and thomas-nguy and removed request for a team September 24, 2026 08:54
@github-actions

This comment has been minimized.

@JayT106

JayT106 commented Sep 24, 2026

Copy link
Copy Markdown
Author

@claude review

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Claude finished @JayT106's task in 2m 1s —— View job


Code Review — fix(rpc): cap log filter address/topic lists and honor context cancellation

  • Read changed files and gather context
  • Review filters/api.go - ValidateCriteria and integration
  • Review filters/filters.go - context cancellation changes
  • Review filters/filters_test.go - test coverage
  • Review rpc/websockets.go and rpc/websockets_test.go
  • Review CHANGELOG.md
  • Provide final review summary

Summary

This PR addresses a real RPC-node DoS vector: unbounded address/topic lists in eth_getLogs / eth_newFilter / WebSocket logs subscriptions, and a context leak in the range scan loop. The fix is well-scoped, follows go-ethereum conventions, and includes solid test coverage. No consensus-layer or state-breaking changes detected.


Findings

1. Sentinel errors are unexported — external callers can't use errors.Is

errExceedMaxTopics and errExceedLogQueryLimit are package-level var pointers in api.go (lines 82–84). External packages that want to distinguish these errors from other InvalidParamsError values can't use errors.Is; they can only use errors.As(err, new(*types.InvalidParamsError)). This is actually acceptable Go style (check the type, not the value), but it is worth documenting. If external packages (e.g., tests in rpc/) ever need to assert on the specific sub-error, exporting them (or defining string constants for the messages) would help.

2. subscribeLogs builds full criteria before validating

In websockets.go lines 678–754, addresses and topics are fully parsed into crit before ValidateCriteria is called on line 757. If a client sends 2001 addresses in a WS message, all 2001 get passed through common.HexToAddress before validation rejects the request. The parsing is O(n) and CPU-cheap per entry, so this is not a meaningful DoS vector on its own. Still, an early size-check during address parsing (e.g., bail out as soon as len(crit.Addresses) exceeds the limit) would be more defensive. Not a blocker.

3. No context check in the block-hash path

Filter.Logs has two branches: hash-lookup (lines 108–137 in filters.go) and range-scan (line 186+). The context-cancellation check (ctx.Err()) was added only to the range loop. The hash path makes two backend calls (TendermintBlockByHash + TendermintBlockResultByNumber) and is bounded, so omitting the check is acceptable. Worth a brief comment to make the intent explicit.

4. ValidateCriteria is not called in GetFilterLogs

GetFilterLogs (api.go line 306) uses criteria stored from a previous NewFilter call (which does validate). This is correct as implemented, but if a future refactor adds a code path that populates filter.crit without going through NewFilter, the validation would be silently skipped. A defensive call to ValidateCriteria on the stored f.crit would be belt-and-suspenders, at the cost of a tiny extra check. Not required.

5. Error message minor imprecision

errExceedMaxTopics has message "exceed max topics" (line 82). This fires when len(crit.Topics) > 4, i.e., too many topic positions, not too many alternatives at a position. The message could be "too many topic positions" or "exceed max topic positions" to avoid confusion with errExceedLogQueryLimit ("exceed max addresses or topics per search position"). go-ethereum uses "too many topics". Very minor — doesn't affect behavior.


What's Done Well

  • ValidateCriteria is centralized and exported, making it easy for downstream callers (test suites, middleware) to call it independently.
  • Test TestFilterLogs_StopsOnCancelledContext correctly validates both "cancelled before scan starts" and "cancelled mid-scan" scenarios with a countingBackend — the test precisely asserts the number of backend calls.
  • ctx.Err() is checked at loop start (before the block fetch), matching go-ethereum's implementation pattern.
  • NewFilter validates before taking a filter slot (line 224 before line 232), so oversized criteria can never consume a filter slot — the test on line 322 (require.Empty(t, api.filters)) verifies this invariant.
  • Constants MaxTopics = 4 and MaxLogQueryEntries = 1000 match go-ethereum defaults and are clearly documented.
  • No consensus or state-breaking changes — this is pure RPC-layer with no impact on the EVM state machine or Cosmos modules.

No Blocking Issues

All findings above are minor (style/defense-in-depth). The core logic is correct, the limits are properly enforced at all three entry points (GetLogs, NewFilter, subscribeLogs), and the context-cancellation fix is a one-line change with clear before/after behavior validated by tests.

Comment thread rpc/namespaces/ethereum/eth/filters/api.go Outdated
Comment thread rpc/namespaces/ethereum/eth/filters/filters.go
Comment thread rpc/websockets.go
NewRangeFilter already hashed every address and topic into BloomIV but
blockLogs ignored them and re-ran keccak per entry per block. Compute
the IVs in newFilter for both constructors and test the bloom bytes
directly. 1000 addresses over 10000 blocks: ~3s -> 5.4ms.
@codecov

codecov Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.67442% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 47.43%. Comparing base (e9c85d5) to head (f8f129b).

Files with missing lines Patch % Lines
rpc/namespaces/ethereum/eth/filters/filters.go 96.42% 0 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #1109      +/-   ##
===========================================
+ Coverage    47.08%   47.43%   +0.34%     
===========================================
  Files          200      200              
  Lines        17648    17657       +9     
===========================================
+ Hits          8310     8376      +66     
+ Misses        8476     8417      -59     
- Partials       862      864       +2     
Files with missing lines Coverage Δ
rpc/namespaces/ethereum/eth/filters/api.go 31.17% <100.00%> (+5.69%) ⬆️
rpc/namespaces/ethereum/eth/filters/utils.go 22.85% <ø> (+3.98%) ⬆️
rpc/websockets.go 15.27% <100.00%> (+2.71%) ⬆️
rpc/namespaces/ethereum/eth/filters/filters.go 65.33% <96.42%> (+25.47%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread rpc/namespaces/ethereum/eth/filters/api.go Outdated
Comment thread rpc/namespaces/ethereum/eth/filters/api.go Outdated

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants