Repository navigation
feat(backend): harden Horizon retry manager and sanitize exchange rate - #1600
arandomogg wants to merge 1 commit into
Conversation
…e oracle cache Add jittered exponential backoff with Retry-After support and a time budget, a per-process concurrency limiter, Redis-backed cluster retry slots and rate-limit cooldown, strict validation and allow-list sanitization for exchange rate quotes, and integration and stress suites.
|
@arandomogg is attempting to deploy a commit to the Emmanuel's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@arandomogg Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| - | - | Generic High Entropy Secret | f2ae1d5 | backend/src/lib/exchange-rate-oracle-sanitizer.test.js | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secret safely. Learn here the best practices.
- Revoke and rotate this secret.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
Automated retry with exponential backoff (#1439)
horizon-client/backoff.js: jittered exponential backoff by default (full, equal or none). An explicitretryDelaysarray keeps the old fixed schedule.maxRetryAfterMs. A total time budget (maxElapsedMs) covers every wait.retryable: falseare never retried. Every failure carriesattemptsandretryStopReason.code(for exampleconnect ECONNREFUSED), so they were never retried.isRetryableErrornow also checks the message anderr.cause.code.Distributed concurrency control and locking (#1440)
ConcurrencyLimiter: caps in-flight Horizon calls per process and holds the rest in a bounded FIFO queue. When the queue is full or a wait times out, the caller gets a 503 (HORIZON_CLIENT_QUEUE_FULL/HORIZON_CLIENT_QUEUE_TIMEOUT).HorizonRetryCoordinator: retry slots shared across the cluster, stored as Redis keys taken withSET NX PXand released with compare-and-delete. It also publishes a shared rate-limit cooldown after a 429 or 503, so many instances cannot turn one Horizon incident into a retry storm.configureHorizonRetryCoordination()inapp.js.Integration and stress test suite (#1441)
tests/integration/horizon-retry-manager.test.js: runs the real HorizonClient and Stellar SDK against a local HTTP Horizon stub. Covers retries, 404s, running out of retries, the time budget, connection refused, the concurrency cap, load shedding, and the shared cooldown and retry slots across instances.load-tests/horizon-retry-manager-stress.test.js: 2,000 operations with 30% transient failures, 8 instances retrying against a Horizon that is down, everyone holding through the shared 429 cooldown, queue saturation, Redis flapping, and 10,000 successful operations. Each test also checks that nothing leaks.Payload sanitization and strict validation for the Exchange Rate Oracle Cache (#1442)
exchange-rate-oracle-sanitizer.js:sanitizeQuoteRequest()validates and normalizes asset codes, issuers and the source account (full StrKey checksum), amounts (positive, at most 7 decimals, within int64 stroops) and slippage. Bad input gets a 400ExchangeRateValidationErrorthat is never retried, and the route returns{ error, field }.sanitizeQuote()rebuilds every quote from a fixed list of allowed fields, both for Horizon results and for entries read from the shared Redis store. Unknown fields and prototype keys are dropped, and a poisoned entry is treated as a cache miss.Notes
retryDelayskeeps the exact old schedule.resolveAssetor at Horizon. Some existing test fixtures used made-up keys with bad checksums; I replaced them with valid generated keys and added issuers where they were missing.src/lib/stellar/horizon-client/metrics.test.js,src/lib/stellar.test.js,src/lib/stellar-metrics.test.js,tests/integration/exchange-rate-cache.test.js(401 from auth setup) andload-tests/path-payment-load.test.js.Docs
backend/docs/HORIZON_CLIENT_RETRY_MANAGER.md(new)backend/docs/EXCHANGE_RATE_ORACLE_CACHE.md(new section)closes #1439
closes #1440
closes #1441
closes #1442