Skip to content

fix(ingest): Handle Horizon rate-limit (429) responses with backoff instead of treating them as a hard error #97

Description

@Emmyt24

Description

HorizonPayments::payments_after in crates/ingest/src/horizon.rs treats any non-2xx, non-404 response (including a 429 Too Many Requests, which public Horizon instances issue under load) identically to a genuine server error — both map to HorizonError::Request, and Ingestor::run's retry loop just waits the normal poll interval before trying again with no awareness that the previous failure was specifically a rate limit that Horizon expects callers to back off from (often advertised via a Retry-After header).

Requirements and Context

  • Add a HorizonError::RateLimited { retry_after: Option<Duration> } variant, populated by reading the Retry-After header (seconds, per HTTP spec) when Horizon returns 429.
  • In Ingestor::run's poll loop, when poll_once fails with IngestError::Horizon wrapping a rate-limit specifically, sleep for the advertised retry_after (or a sane default backoff like 5s if the header is absent/unparseable) instead of just falling through to the normal poll interval — this may require IngestError to carry enough information to distinguish this case, or for the check to happen inside run directly against a richer return type.

Suggested Execution

Branch: fix/ingest/handle-horizon-429-backoff

Implement Changes

  • Update HorizonError in crates/ingest/src/horizon.rs and the response-status handling in payments_after.
  • Update IngestError in crates/ingest/src/lib.rs if needed to propagate the distinction, and update Ingestor::run's loop accordingly.

Test and Commit

  • payments_after_maps_429_to_rate_limited_with_retry_after_header.
  • payments_after_defaults_retry_after_when_header_is_absent_or_unparseable.
  • run_backs_off_by_the_advertised_duration_on_rate_limit (using a mock Horizon server, asserting the loop's next request happens no sooner than the advertised backoff).
  • Run cargo test -p octo-ingest locally before committing.

Example Commit Message

fix(ingest): back off on Horizon 429 responses using the advertised Retry-After

A Horizon rate-limit response was previously indistinguishable from any
other server error, so the ingest loop just retried at its normal poll
interval — the opposite of what a 429 asks callers to do. Adds a distinct
RateLimited variant honoring Retry-After, with a sane default backoff when
the header is missing.

Guidelines

  • Use a mock HTTP server (see the ingest mock-server testing issue for the same pattern) to simulate the 429 + Retry-After header rather than relying on the live public Horizon rate limit, which is not reliably triggerable in CI.
  • Reference this issue with Closes #<issue-number> in the PR description.

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial Campaign | FWC26Campaign: Official Campaign | FWC26area/backendBackend crates: api, store, ingest, webhooks, bin/serverdifficulty/mediumMedium difficulty

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions