Indexer rate limiter runs at 1/RPS of intended rate: per_second(rps) sets the replenish period, not requests-per-second #355
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:implement
agent:implement
agent:open_issues
agent:ready
agent:research
agent:security_audit
agent:verify
architecture
backend
blocker:hybrid
blocker:launch
blocker:limit-orders
blocker:v2
block:log_only
block:security
bug
ci
contracts
correctness
deploy
dev
devops
docs
documentation
duplicate
e2e
enhancement
epic
feature
frontend
functional-completion
gas
good first issue
governance
help wanted
high-risk
hooks
hybrid
indexer
infra
infrastructure
integrators
invalid
launch-blocker
limit-orders
localnet
localterra
low priority
missing-implementation
needs-design
ops
performance
priority
high
priority
medium
product
qa
QA
question
ready
ready
research
scripts
security
security-hardening
smartcontracts
tech-debt
testing
ux
UX
v2
verification
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-dex-terraclassic#355
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
apply_rate_limit_layer(indexer/src/api/mod.rs:118-145) builds the governor with.per_second(rps).burst_size(rps*2). In tower_governor 0.8.0,per_second(n)means "replenish ONE token every n seconds" — it is the period, not the rate. Every configured limit therefore serves a burst of 2xRPS and then ONE request per RPS seconds:Live proof (LocalTerra,
83dc192; RATE_LIMIT_RPS=0 so only the lcd-heavy layer was active)x-ratelimit-limit: 20.Impact
Fix direction
Use the per-second semantics intentionally — e.g. construct the quota at
Quota::per_second(rps)equivalents or.per_millisecond(1000 / rps)— keep burst_size, and add a test asserting sustained throughput ~= RPS over a few seconds. The existing 429 tests only assert that bursts get limited, which passes under both semantics, so they never caught the inversion.Severity: High for production readiness (self-inflicted API starvation under normal dapp usage). Found working the SW/TR rows of #337.
cc @PlasticDigits
mentioned in issue #350
mentioned in issue #337
mentioned in commit
bdfeeef671mentioned in merge request !852
Implementation
MR opened on
local1/355-impl-rate-limiter-rps.Rate limiter now uses
period(1s / RPS)instead ofper_second(RPS). Added sustained-throughput test and documented that both env vars must be 0 to disable in dev.Verification checklist
/tradepair switch + book polling — no 429s under normal usecargo test --test security rate_limit— all 4 tests passmentioned in commit
0ee39be2a8Verified the merged fix (!852) live on
e198dcb. Rebuilt the indexer and ran it with RATE_LIMIT_LCD_HEAVY_RPS=10: after draining the burst and waiting ~2s, 10/10 requests in a ~1s window returned 200, where the pre-fix per_second-as-period bug would have allowed ~0-1 (1 token / 10s). The limiter now replenishes at true RPS. Confirmed lcd-heavy is a separate layer from the general RATE_LIMIT_RPS, as noted.mentioned in merge request !873
mentioned in issue #363
mentioned in issue #694