fix: add RPC fallback with timeout for on-chain reads#154
fix: add RPC fallback with timeout for on-chain reads#1540x-SquidSol wants to merge 1 commit intodcccrypto:mainfrom
Conversation
All on-chain RPC calls (fetchSlab, getSlot) use a single Connection
from getConnection() with no failover. If the primary Solana RPC is
down, rate-limited, or hung, /markets/:slab, /api/adl/rankings, and
/health fail with no recovery path.
This adds:
- withRpcTimeout() — Promise.race deadline (10s routes, 5s health)
- withRpcFallback() — tries primary, on ANY error tries FALLBACK_RPC_URL
- Each attempt gets its own independent timeout budget
- Only activates when FALLBACK_RPC_URL is explicitly set (prevents
silent failover to the devnet default in @percolator/shared)
- RpcTimeoutError class for 504 Gateway Timeout responses
- FALLBACK_RPC_URL documented in .env.example
Configurable via env: RPC_TIMEOUT_MS, HEALTH_RPC_TIMEOUT_MS, FALLBACK_RPC_URL
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 20 minutes and 49 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Summary
fetchSlab,getSlot) use a singleConnectionfromgetConnection()with no failover. If the primary Solana RPC is down, rate-limited, or hung,/markets/:slab,/api/adl/rankings, and/healthfail with no recovery path.withRpcFallback()— tries primary connection with a timeout, on ANY error triesFALLBACK_RPC_URLwith its own independent timeout. Also addswithRpcTimeout()usingPromise.raceto prevent indefinite hangs.Changes
src/utils/rpc-timeout.tswithRpcTimeout(),RpcTimeoutError, env-configurable defaultssrc/utils/rpc-fallback.tswithRpcFallback()with primary→fallback failoversrc/routes/markets.tsfetchSlab()withwithRpcFallback, returns 504 on timeoutsrc/routes/adl.tsfetchSlab()withwithRpcFallback, returns 504 on timeoutsrc/routes/health.tsgetSlot()withwithRpcFallback(5s timeout).env.exampleFALLBACK_RPC_URLDesign decisions
Promise.race—fetchSlab/getSlotdon't acceptAbortSignalrateLimitedCall. Connection failures, timeouts, 5xx all trigger failover. Better for read-only API.getFallbackConnection()silently defaults to devnet whenFALLBACK_RPC_URLis unset. We checkBoolean(process.env.FALLBACK_RPC_URL)before attempting fallback to prevent silent devnet routing on mainnet.RPC_TIMEOUT_MS(default 10s),HEALTH_RPC_TIMEOUT_MS(default 5s),FALLBACK_RPC_URLNote
This PR supersedes #153 (timeout-only fix) by including both timeout AND fallback in one cohesive change.
Test plan
tsc --noEmitpasses (zero type errors)vitest runpasses (186/186 tests)FALLBACK_RPC_URLis set and primary failsFALLBACK_RPC_URLis unset🤖 Generated with Claude Code