Skip to content

fix(scan): stop chain-walk fanout + route /rpc to correct host#56

Merged
github-actions[bot] merged 3 commits into
mainfrom
fix/scan-rpc-fanout-and-host
May 11, 2026
Merged

fix(scan): stop chain-walk fanout + route /rpc to correct host#56
github-actions[bot] merged 3 commits into
mainfrom
fix/scan-rpc-fanout-and-host

Conversation

@satyakwok
Copy link
Copy Markdown
Member

Summary

Deep audit on 2026-05-11 caught scan v1 firing ~110 eth_getLogs requests per page mount at the wrong host (api.sentrixchain.com/rpc — no CORS, Caddy passthrough). Total per home page load: 202 network requests, 6 skeleton placeholders that never resolved after 30 seconds.

Root cause

Three connected bugs:

  1. Wrong host — three call sites in `api.ts` composed `${apiBase}/rpc` (where apiBase = api.sentrixchain.com). The actual JSON-RPC endpoint is rpc.sentrixchain.com. The api host happened to proxy /rpc but stripped CORS headers, breaking testnet entirely and burning extra round-trips.

  2. Chain walk on every mount — `fetchEvmTokensFromFactory` walks the deploy-block → tip range in 5K-block chunks looking for TokenCreated events. For mainnet that's ~110 eth_getLogs per call. `labels.tsx` calls `fetchTokens` on every page mount → walk fires every page transition.

  3. No deduplication — two simultaneous mounts (e.g. concurrent tabs, or a fast nav burst) doubled the walk.

Fixes

# Where Change
1 `chain.ts` New `getRpcUrl(network)` helper — single source of truth for the JSON-RPC endpoint
2 `api.ts` × 3 Route raw `fetch(\`${base}/rpc\`)` sites through `getRpcUrl(network)`
3 `api.ts` 5-minute TTL cache + in-flight dedup on `fetchEvmTokensFromFactory`
4 `scripts/audit-static.sh` New Rule 10 catches future `${apiBase}/rpc` regressions

Cache TTL of 5 minutes is the right balance: the token list is append-only and changes maybe a few times per hour at most, so users get fresh-enough data without re-walking on every visit. In-flight dedup means concurrent mounts share the walk.

New audit tooling (also committed)

  • `scripts/scan-deep-audit.mjs` — playwright crawl of every public route, surfaces stuck skeletons + console errors + empty bodies
  • `scripts/scan-network-trace.mjs` — captures every network request a single page makes, breaks /rpc POSTs by JSON-RPC method so fan-out bugs are obvious in seconds

Verification

  • `pnpm --filter @sentriscloud/scan typecheck` clean
  • `bash scripts/audit-static.sh` 0 hard errors (Rule 10 catches the bad pattern but main now uses good pattern → clean)
  • Live re-trace will drop home page from ~200 requests → <20 once deployed and the cache warms

Scope NOT in this PR (separate workstreams)

  • Testnet API+RPC origin is timing out at 10s — operator-side ops issue (origin or Caddy upstream broken), not a scan code bug. Surfaces as 429s + hung requests in the audit but only for testnet routes.
  • Cross-network probes hang when testnet is dead — block-detail/tx-detail page does both-networks fetch for "is this also on the other chain". 8s viem default timeout is too long; should be 1.5s with explicit cancellation. Follow-up PR.
  • Indexer-side token tracking — long-term answer is the indexer subscribing to factory events and serving `/tokens` directly. Currently `/tokens` returns empty so scan compensates with the chain walk. Separate workstream in indexer repo.

Test plan

  • CI green
  • After deploy: `node scripts/scan-network-trace.mjs https://scan.sentrixchain.com/ 4000` shows < 20 requests on second-mount (cache warm)
  • Home page renders with all data populated within 5s, no stuck skeletons
  • Network switch mainnet → testnet doesn't re-fire the walk (separate cache key per network)

satyakwok added 3 commits May 11, 2026 02:10
V2 (scan.sentriscloud.com + scan-testnet.sentriscloud.com) was missing
from the live audit surface. Both stay live alongside V1 per ops
direction; the audit needs to monitor both. Renamed v1 entries with
explicit `-v1` suffix for symmetry.

Verified locally: clean across all 17 surface URLs.
Before: 64-hex regex matched but only ran probeTx() on both networks.
A user pasting a block hash → both probes fail → "Transaction not
found" error message even though the hash IS a valid block hash.

After: 4 parallel probes (tx + block on current + other network).
Block hits resolve through getBlock({blockHash}) and route to
/blocks/<resolved-height> since the block route is height-keyed.
Tx still wins precedence — they're disjoint hash spaces in practice
but the explicit ordering keeps semantics deterministic.

Cost: 4 RPC calls instead of 2, but they run concurrently → wall
time stays bounded by the slowest single probe (~50ms).
Found via deep audit on 2026-05-11: every page mount fired ~110
eth_getLogs requests at api.sentrixchain.com/rpc — the wrong host
(no CORS), composed by `${apiBase}/rpc`. Total per home page load:
202 requests, 6 stuck skeletons that never resolved after 30s.

Three fixes, one PR:

1. **chain.ts: getRpcUrl(network) helper** — single source of truth
   for the JSON-RPC endpoint. Reads NEXT_PUBLIC_(MAINNET|TESTNET)_RPC
   with sane defaults to rpc.sentrixchain.com.

2. **api.ts: route 3 raw fetch sites through getRpcUrl** — stops the
   `${apiBase}/rpc` composition that was the wrong host plus CORS-
   stripped on testnet. Affected calls: eth_blockNumber tip read in
   fetchEvmTokensFromFactory, eth_call probes for ERC20 metadata,
   eth_getLogs in fetchEventLogs.

3. **api.ts: 5-min TTL cache + in-flight dedup for token-factory walk**
   — the underlying scan is ~110 chunked eth_getLogs (deploy-block to
   tip in 5K-block windows). Token list is append-only and changes
   maybe a few times per hour at most, so caching is safe. In-flight
   dedup prevents two simultaneous mounts from doubling the walk.

Plus an audit-static rule (Rule 10) to catch any future
`${apiBase}/rpc` regression at lint time.

Verified locally:
- typecheck clean
- audit-static.sh: 0 hard errors
- scan-network-trace.mjs (new tool): expect home page to drop from
  ~200 requests → <20 once the cache warms
@github-actions github-actions Bot enabled auto-merge (squash) May 11, 2026 00:55
@github-actions github-actions Bot merged commit e8ce951 into main May 11, 2026
5 checks passed
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.

1 participant