Skip to content

feat(ratelimiter): add RateLimiterRegistry + evmrpc config fields#3507

Open
amir-deris wants to merge 27 commits into
mainfrom
amir/plt-411-rate-limiter-registry-for-rpc
Open

feat(ratelimiter): add RateLimiterRegistry + evmrpc config fields#3507
amir-deris wants to merge 27 commits into
mainfrom
amir/plt-411-rate-limiter-registry-for-rpc

Conversation

@amir-deris

@amir-deris amir-deris commented May 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds ratelimiter package with Registry: per-IP token-bucket rate limiter backed by an expirable LRU (50k entries, 1h TTL) using golang.org/x/time/rate
  • Allow(ctx, ip, plane, method) enforces the limit and increments the rpc_rate_limit_rejected_total{plane, method} OTel counter on rejection
  • IPFromHTTPRequest / IPFromGRPCContext extract the real client IP, honouring X-Forwarded-For only when RemoteAddr / peer address is within a configured trusted proxy CIDR
  • Adds rate_limiting_enabled, ip_rate_limit_rps, and ip_rate_limit_burst fields to evmrpc/config (defaults: enabled, 200 RPS, 400 burst); trusted_proxy_cidrs will be added when request-path wiring lands; existing nodes without these fields in app.toml fall back to defaults safely
  • No request-path wiring in this PR; EVM, CometBFT, and gRPC plane wiring follow in future PRs.

@cursor

cursor Bot commented May 26, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Client IP derivation and trusted-proxy CIDRs affect who gets limited once wired; misconfiguration could weaken limits or bucket the wrong peers. No live enforcement yet, so operational impact is limited until follow-up wiring lands.

Overview
Introduces a new ratelimiter package with a Registry that enforces per-IP token-bucket limits (expirable LRU, IPv6 /64 bucketing) and records rejections on rpc_rate_limit_rejected_total{plane}. Allow is a no-op when RPS or burst is ≤0; helpers resolve client IPs from HTTP and gRPC using rightmost untrusted X-Forwarded-For only when the peer is in configured trusted proxy CIDRs (default config trusts none).

evmrpc/config gains ip_rate_limit_rps and ip_rate_limit_burst (defaults 200 / 400, template + ReadConfig), with config tests for bad types. Request-path middleware is not wired in this PR—only the library and EVM config surface.

Reviewed by Cursor Bugbot for commit 57e9ff3. Bugbot is set up for automated code reviews on this repo. Configure here.

@amir-deris amir-deris changed the title Added rate limiter registry and test feat(ratelimiter): add RateLimiterRegistry + evmrpc config fields (PLT-411 Phase 1a) May 26, 2026
@github-actions

github-actions Bot commented May 26, 2026

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedJun 12, 2026, 11:25 PM

@github-actions

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedMay 26, 2026, 11:18 PM

@amir-deris amir-deris changed the title feat(ratelimiter): add RateLimiterRegistry + evmrpc config fields (PLT-411 Phase 1a) feat(ratelimiter): add RateLimiterRegistry + evmrpc config fields May 26, 2026
@amir-deris amir-deris requested review from bdchatham and masih May 26, 2026 23:20
Comment thread ratelimiter/registry.go
Comment thread ratelimiter/registry.go
@codecov

codecov Bot commented May 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 58.35%. Comparing base (57bba16) to head (57e9ff3).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3507      +/-   ##
==========================================
- Coverage   59.22%   58.35%   -0.87%     
==========================================
  Files        2214     2140      -74     
  Lines      183402   174855    -8547     
==========================================
- Hits       108619   102040    -6579     
+ Misses      64994    63725    -1269     
+ Partials     9789     9090     -699     
Flag Coverage Δ
sei-db 70.41% <ø> (ø)
sei-db-state-db ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
evmrpc/config/config.go 70.43% <ø> (ø)

... and 74 files with indirect coverage changes

🚀 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 evmrpc/config/config.go Outdated
Comment thread ratelimiter/registry.go
Comment thread ratelimiter/registry.go Outdated
Comment thread ratelimiter/registry.go Outdated
Comment thread ratelimiter/registry.go
Comment thread evmrpc/config/config.go Outdated
Comment thread ratelimiter/registry.go
Comment thread ratelimiter/registry.go
Comment thread ratelimiter/registry.go
@bdchatham

Copy link
Copy Markdown
Contributor

Build-vs-buy on ratelimiter — keep the limiter core, lean on a library for the IP plumbing

The rate-limiting core here is solid and appropriately simple — I'd keep it:

  • Token bucketgolang.org/x/time/rate ✅ already used, nothing to reinvent.
  • Per-IP storehashicorp/golang-lru/v2/expirable ✅ already used, and it's the canonical in-memory Go LRU.

The part worth not hand-rolling is client-IP extraction — IPFromHTTPRequest / IPFromGRPCContext / rightmostUntrustedIP / isTrustedProxy / parseCIDRs. XFF + trusted-proxy parsing is a well-known footgun (spoofing, malformed entries, IPv6, multi-header), and there's a community reference implementation for exactly this:

  • HTTPrealclientip-go. Its RightmostTrustedRangeStrategy is the algorithm implemented here ("the rightmost valid IP not in a set of trusted ranges"), but battle-tested and spec-aligned. Tiny, stateless, dependency-light.
  • gRPC → feed the metadata x-forwarded-for through the same strategy via a small http.Header adapter, or use grpc-ecosystem/.../interceptors/realip.

This would replace roughly the lower half of registry.go (the IP/XFF/CIDR functions) with a library call, and directly retire the trust-model concerns:

  • No dangerous default trust set — the strategy takes the trusted ranges from the caller, so there's no broad RFC-1918/fc00::/7 default to ship. Pass exactly your ingress CIDRs at wiring time; default to none.
  • Fail-loud on bad CIDRs — its constructor returns an error, vs. the current silent skip.
  • Spoof-resistant rightmost walk + malformed-entry handling — handled and tested upstream.

One thing no extraction library solves, so it stays in-house: IPv6 should be bucketed by prefix — mask the extracted IP to /64 before it becomes the limiter key, otherwise a client with a /64 rotates addresses for a fresh bucket per request. That's a one-liner in the keying step.

Net: keep x/time/rate + golang-lru + the Allow/getOrCreate core (small, conventional); delete the bespoke XFF/CIDR code in favor of realclientip-go; add the v6 /64 mask. Happy to pair on the swap.

Comment thread evmrpc/config/config.go Outdated
Comment thread ratelimiter/registry.go
@amir-deris

amir-deris commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

Build-vs-buy on ratelimiter — keep the limiter core, lean on a library for the IP plumbing

The rate-limiting core here is solid and appropriately simple — I'd keep it:

  • Token bucketgolang.org/x/time/rate ✅ already used, nothing to reinvent.
  • Per-IP storehashicorp/golang-lru/v2/expirable ✅ already used, and it's the canonical in-memory Go LRU.

The part worth not hand-rolling is client-IP extraction — IPFromHTTPRequest / IPFromGRPCContext / rightmostUntrustedIP / isTrustedProxy / parseCIDRs. XFF + trusted-proxy parsing is a well-known footgun (spoofing, malformed entries, IPv6, multi-header), and there's a community reference implementation for exactly this:

  • HTTPrealclientip-go. Its RightmostTrustedRangeStrategy is the algorithm implemented here ("the rightmost valid IP not in a set of trusted ranges"), but battle-tested and spec-aligned. Tiny, stateless, dependency-light.
  • gRPC → feed the metadata x-forwarded-for through the same strategy via a small http.Header adapter, or use grpc-ecosystem/.../interceptors/realip.

This would replace roughly the lower half of registry.go (the IP/XFF/CIDR functions) with a library call, and directly retire the trust-model concerns:

  • No dangerous default trust set — the strategy takes the trusted ranges from the caller, so there's no broad RFC-1918/fc00::/7 default to ship. Pass exactly your ingress CIDRs at wiring time; default to none.
  • Fail-loud on bad CIDRs — its constructor returns an error, vs. the current silent skip.
  • Spoof-resistant rightmost walk + malformed-entry handling — handled and tested upstream.

One thing no extraction library solves, so it stays in-house: IPv6 should be bucketed by prefix — mask the extracted IP to /64 before it becomes the limiter key, otherwise a client with a /64 rotates addresses for a fresh bucket per request. That's a one-liner in the keying step.

Net: keep x/time/rate + golang-lru + the Allow/getOrCreate core (small, conventional); delete the bespoke XFF/CIDR code in favor of realclientip-go; add the v6 /64 mask. Happy to pair on the swap.

@bdchatham Thanks for great feedback.

About using the external library for the IP extraction part, I believe that is not necessary as the library realclientip-go doesn't seem well maintained (one contributor, only a handful of PRs, issues filed from years ago, etc. From Claude pov:

What we have is ~60 lines of readable, tested code. Adding an external dependency for it isn't a clear win — realclientip-go is niche, not stdlib-grade

The other issues are very valid, I will work on fixing them!

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f3c25e1. Configure here.

Comment thread ratelimiter/registry.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants