Implements distributed rate limiting for the tiered API limiter using Redis when REDIS_URL is configured. This removes the bypass where limits lived only in process memory (restart or horizontal scaling cleared or split enforcement).
Issue #116: in-memory rate limiting could be bypassed by restarting the server or routing traffic across multiple instances. Production deployments need a shared store so limits are consistent and durable across replicas.
TieredRateLimiter(backend/src/middleware/rateLimiter.ts):- When Redis is enabled (
REDIS_URL), uses Redis sorted sets for a sliding window of request timestamps and a separate counter key for queue depth. - Uses Lua scripts so check/consume and status reads are atomic and safe under concurrency.
- Keeps the previous in-memory behavior when Redis is not configured (e.g. local dev/tests without Redis).
- Exposes
resetTimeas ISO strings aligned withTierRateLimitStatus. - Middleware is async, forwards limiter errors via
next(error)instead of failing silently.
- When Redis is enabled (
- Monitoring (
backend/src/routes/monitoring.ts):GET /api/monitoring/rate-limit-statusawaits asyncgetStatus().
- Set
REDIS_URLin environments where multiple app instances run or restarts must not reset limits (same variable as existing Redis pub/sub for WebSockets). - Keys use the prefix
rl:tier:requests:andrl:tier:queue:per user id derived fromx-user-idor IP.
- With Redis: Point
REDIS_URLat a Redis instance, start the API, and hit/api/payment(or any route behindtieredRateLimiter) until429/ queued responses; repeat from another client or after restart — counts should continue from shared state, not reset per process. - Without Redis: Unset
REDIS_URL— behavior should match prior in-memory limiting for a single process. - Monitoring:
GET /api/monitoring/rate-limit-statuswithx-user-idshould return tier + limit fields without throwing.
None intended. Response shapes and HTTP status codes for rate limit / queue paths are unchanged; Redis is additive behind configuration.
Closes nathydre21#116
-
Implementation scoped to tiered limiter + monitoring route
-
CI / full backend build (repo may have pre-existing TS issues outside this change)
-
Redis connectivity verified in staging This PR resolves three related reliability issues in one delivery:
-
#120Missing Error Recovery Mechanism -
#121No Offline Payment Queue Persistence -
#128Missing Network Congestion Handling
The implementation focuses on resilient payment execution, durable offline queueing, and dynamic congestion-aware fee/retry behavior while preserving existing API contracts.
Payment and transaction submission failures were not consistently retried, forcing users to manually restart flows after transient failures (timeouts, temporary network issues, rate spikes).
Offline queued actions could be fragile across browser sessions and lacked robust retry metadata, creating risk that scheduled/offline payment intents are lost or repeatedly fail without traceability.
Fee selection and retry strategy were not tuned to real-time network congestion, causing avoidable stuck or failed transactions during high-fee periods.
- Added bounded exponential-backoff retry logic for backend payment execution in
backend/src/payment-service.ts. - Added retry classification for transient failures (network, timeout, 429/503 patterns) and congestion-related failures.
- Added frontend transaction submission retry loop in
frontend/src/App.tsxto improve user-facing resilience.
- Hardened IndexedDB queue implementation in
frontend/src/services/offlineQueueService.ts:- added retry metadata (
retryCount,lastError) - added safer transaction wrapper utilities
- preserved deterministic queue ordering.
- added retry metadata (
- Improved queue synchronization in
frontend/src/hooks/useOfflineSync.ts:- resilient behavior when history endpoint is unavailable
- exponential backoff for retryable sync failures
- metadata updates per retry attempt and failure cause
- startup sync trigger when already online.
- Fixed network listener cleanup bug in
frontend/src/utils/networkStatus.tsby using stable listener references. - Added persistence/restore for scheduled payment tasks in
frontend/src/components/scheduledPaymentService.tsusing IndexedDB so tasks survive browser restarts.
- Updated fee estimation in
frontend/src/services/feeEstimation.tsto use Horizon fee stats and compute dynamic recommended fees based on congestion level. - Integrated congestion signals into retry behavior (longer backoff profile where needed) in
backend/src/payment-service.ts.
- Existing endpoints and request/response shapes were kept unchanged.
- Changes are additive around retry logic, queue metadata, and fee estimation internals.
- No migration or breaking API contract changes required.
-
Transient failure retry (
#120)- Trigger payment flow while simulating intermittent network failures/timeouts.
- Verify the client/backend retry automatically and recover without manual restart.
- Expected: payment eventually succeeds or fails after bounded retries with clear error response.
-
Offline queue persistence (
#121)- Go offline and queue payment/scheduled payment actions.
- Close and reopen browser/app, then reconnect network.
- Expected: queued items are restored from IndexedDB and replayed automatically.
-
Congestion handling (
#128)- Simulate elevated network fee environment (or mock fee stats with high p90 values).
- Verify recommended fee increases dynamically and retry path adapts.
- Expected: fewer stuck submissions and improved completion under congestion.
-
Listener cleanup / no duplicate sync
- Toggle online/offline repeatedly and navigate routes.
- Expected: sync triggers correctly without accumulating duplicate listeners.
- Full local automated verification was limited in this environment due to missing toolchain commands (
tsc/playwrightnot available at runtime). - Manual validation steps above should be run in CI/dev environment with full dependencies.
- None.
Closes nathydre21#120 Closes nathydre21#121 Closes nathydre21#128
- Code builds successfully
- Tests added/updated
- No console errors
- Documentation updated