sec: indexer API4 — cap /gt/events, LCD-heavy progress + blacklist-check (RE-01/02/03) #694
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#694
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?
Gate
P1 availability / OWASP API4. Internal audit
INTERNAL_GROK46_1787908099findings RE-01, RE-02, RE-03 (Chain G2). No fund-loss path. Request-count governors already exist (RATE_LIMIT_RPS=60,RATE_LIMIT_LCD_HEAVY_RPS=10, prod zero-clamp). This ticket is per-request cost: one HTTP hit must not do unbounded SQL or unbounded LCD work.Related (do not reopen): #646 GT adapters, #485 progress poll, #379 / #363 / #355 RPS governors, #278 CG/CMC orderbook on LCD-heavy, #308 blacklist.
Current codebase
Rate limits are request-count (
tower_governor,PeerIpKeyExtractoronly — no XFF). LCD-heavy router (10 RPS) includes/api/v1/route/solve,/best, limit-book, CG/CMC orderbook. Everything else, including the three surfaces below, sits on the global 60 RPS router aftermerge(lcd_heavy_router).GET /gt/eventsMAX_EVENT_BLOCK_SPAN = 2000thenSELECTswaps + liquidity with noLIMIT, thenSELECT pair_id, reserve_0, reserve_1 FROM pair_reserves(all rows) to stamp current reserves. Range rejection only intests/api_gt.rs.GET /api/v1/route/solve/progressresolve_discount_bps, which LCD-GetDiscountwhentrader/senderis set andfee_discount_addressis configured. dAppuseRouteSolveProgresspolls ~1 Hz while a sim quote is in flight. Solve itself is cached (512 / 12s–90s); progress is not on LCD-heavy and has no 429 test.GET /api/v1/compliance/blacklist-checktokensandpairsare comma-split with no max length, then forwarded to factoryBlacklistCheckon LCD. Global governor only. Tests: sanitized 502 only.Honest Swap/Trade tabs already amplify RE-02 (progress +
refetchInterval). Behind NAT, one peer IP shares a bucket.Why the new implementation is needed
OWASP API4 (unrestricted resource consumption): limits that count requests miss work per request. A busy 2k-block
/gt/eventswindow can return a multi-MB JSON payload and scan every reserve row. Progress + liveGetDiscountat 60 RPS (distincttradervalues) amplifies public LCD. Unbounded blacklist lists do the same to the factory query. Saturation looks like “DEX down” even though contracts still execute.Listing partners (GeckoTerminal) and the official dApp are friendly load. An unauthenticated public API must bound both.
Constraints / guardrails
X-Forwarded-For/CF-Connecting-IP(#282). KeepPeerIpKeyExtractor.ALLOW_ZERO_RATE_LIMITS=1. Prod clamp stays.EXCLUDED_CW20/ L639-2) stays. Reserves stay current snapshot, not historical reconstruction.You Receive(#484). Progress stays advisory.LCD_UPSTREAM_GATEWAY_MSG).traderon progress if discount is already on the in-flight quote, and/or back off after N consecutive progress failures. Do not rewrite allrefetchIntervals.Relevant files
indexer/src/api/gt.rs/gt/eventsqueries + fullpair_reservesscanindexer/src/api/mod.rslcd_heavy_routervs globalindexer/src/api/route_solve_progress.rsindexer/src/api/route_solver.rsresolve_discount_bpsLCDGetDiscountindexer/src/api/compliance.rstokens/pairsindexer/src/config.rsDEFAULT_RATE_LIMIT_*, prod clampindexer/tests/security.rsindexer/tests/api_gt.rsindexer/tests/api_route_solve.rsfrontend-dapp/src/hooks/useRouteSolveProgress.tstraderfrontend-dapp/src/utils/quoteDebounce.tsdocs/indexer-invariants.mdRecommended direction
MAX_GT_EVENT_ROWS(recommend 5000 combined swap+liq). If the window would exceed, return 400 with a stable message (or return the first N and set a documentedtruncatedfield — pick one and test it). Load reserves only forpair_ids present in the window (WHERE pair_id = ANY($ids)), never the full table./api/v1/route/solve/progressontolcd_heavy_router(same 10 RPS as solve). Cachediscount_bpsper(trader, sender)with the existing 12s route-solve TTL (or reuse the progress registry entry) so a 1 Hz poll does not LCD every tick. Skip LCD when both trader fields are absent (already returns 0).tokensat 16 andpairsat 8 (after trim/empty-drop). Oversize → 400. Move the route ontolcd_heavy_router.useRouteSolveProgress— omittraderwhen the parent quote already resolved discount; abort/backoff after consecutive failures. Do not change quote math.security.rs(and GT/progress files) per test plan.make verify-issue-<this>greps the caps + router membership + new tests.Acceptance criteria
/gt/eventsnever returns more than the documented row cap; over-cap is 400 or explicit truncation (documented)./gt/eventsdoes notSELECTall ofpair_reserves./route/solve).traderset does not LCD-GetDiscounton every poll within the cache TTL.tokens/pairsover the cap → 400; at-cap still queries factory; factory error still sanitized 502.indexer/tests/security.rscovers 429 on progress + blacklist-check (and solve/bestif missing) and the new 400s.make verify-issue-<N>exists and is wired in the Makefile / AGENTS.md indexer table.docs/indexer-invariants.mdrecords the new caps and which routes are LCD-heavy.Test plan — happy / bad path
/gt/eventssmall window, few rows/gt/eventsto-from+1 > 2000/gt/eventswindow with >cap rows (seed)/gt/eventsmany pairs in windowGET /route/solve+/progresswithout tradertraderset, 2+ polls inside TTLGetDiscount(mock LCD call count)blacklist-check1 wallet + 2 tokens + 1 pairtokenswith 17 addressespairswith 9 addressesRATE_LIMIT_*=0Test plan — attack / abuse vectors
/gt/eventsat 60 RPStradersblacklist-check?tokens=a,a,a…(thousands)Verification criteria
cd indexer && cargo test --test security -- --test-threads=1includes the new 400/429 cases.make verify-issue-<N>green: grepsMAX_GT_EVENT_ROWS(or equivalent), progress + blacklist-check onlcd_heavy_router, list caps, and the new test names.curlmax-span/gt/eventsdoes not grow without bound; progress with a registered trader does not 1:1 LCD (log or mock).INTERNAL_GROK46_1787908099follow-up when this lands.marked as related to #646
marked as related to #485
mentioned in commit
8ca32dc679mentioned in merge request !1193
mentioned in commit
3c4060aba0Merge sanity (!1193 landed on main)
make verify-issue-694passed on the MR tip (5/5). All in-repo acceptance criteria are implemented: GT row cap 5000 → 400 (no truncation), nopair_reservesSELECT, progress + blacklist-check onlcd_heavy_router, 12sGetDiscountcache, blacklist tokens≤16 / pairs≤8 fail-closed 400, factory errors stay sanitized 502, frontend omitstraderwhendiscount_bpsis known and backs off after progress failures.Do not reopen this issue unless a merged invariant is wrong. Operator leftovers moved to a post-merge ticket.
Residual (non-blocking, already out of MR scope)
/gt/eventsstays bounded and progress+trader is not 1:1 LCD.INTERNAL_GROK46_1787908099after prod evidence.api_gt.rs(notsecurity.rs); functionally covered by the verify harness./gt/eventsis not one transaction (theoretical over-cap race).useRouteSolveProgress(Swap only; documented as optional).mentioned in issue #698
marked as related to #698
mentioned in issue #699
marked as related to #699
marked as related to #700
mentioned in issue #700
Post-merge leftovers tracked on #700.
mentioned in issue #717