Skip to content

Add structured error handling for Horizon and anchor failures - #257

Merged
K1NGD4VID merged 3 commits into
mergepay:mainfrom
kingAkinwale:fix-structured-error-handling
Sep 2, 2026
Merged

Add structured error handling for Horizon and anchor failures#257
K1NGD4VID merged 3 commits into
mergepay:mainfrom
kingAkinwale:fix-structured-error-handling

Conversation

@kingAkinwale

Copy link
Copy Markdown
Contributor

Closes #179

Summary

Gives callers and operators consistent, safe error behavior when Stellar Horizon or SEP-24 anchor requests fail. Provider failures are converted to typed, categorized errors inside src/services/*, mapped to the repository's standard { code, message, requestId } envelope by the existing central error handler, and classified for worker retry decisions — without exposing tokens, authorization headers, signed XDRs, or raw upstream payloads.

Changes

New: ProviderError (src/lib/provider-error.ts)

  • Extends AppError with a normalized category: timeout | transport | rate_limited | unavailable | malformed | rejected
  • Carries stable provider/operation identifiers (e.g. horizon, Anchor.getChallenge) for debugging
  • Only validated identifier tokens (Horizon result codes such as tx_bad_seq) may be appended to messages/details; everything else from an upstream error is dropped
  • New PROVIDER_REJECTED error code (502) so a permanent transaction rejection is machine-distinguishable from a transient dependency failure (UPSTREAM_ERROR)

Normalization helper (src/services/timeout.ts)

  • toProviderError() maps anything a provider call threw: TimeoutError → timeout, TransportError → transport, Horizon result codes → rejected, upstream 429 → rate_limited, 5xx → unavailable, other 4xx → rejected, unknown → unavailable

Services (all I/O stays inside src/services/*, mockable)

  • anchor.ts: SEP-10/SEP-24 fetch paths throw categorized ProviderErrors; unguarded res.json() on non-JSON bodies (previously an untyped 500) is now malformed; pollTransaction results carry their failure category for the worker
  • stellar.ts: loadAccount/submit/lookup failures normalized; replaced console.error with Pino logging only safe fields (operation, category, resultCodes)
  • network.ts: fee-stats failures normalized the same way

Error handler (src/plugins/error-handler.ts)

  • Escaping timeout/transport failures answer a safe 502 instead of the generic 500; unexpected errors still reach the generic INTERNAL_ERROR path unchanged

Worker retry decisions (src/services/job-retry.ts, src/worker/index.ts)

  • classifyJobFailure consults the typed category first: timeout → indeterminate (ledger check before resubmit), rate-limit/outage → transient, rejection → permanent — no message-text guessing
  • Anchor reconciliation persists the poll's category as the session's errorCategory

Status codes

Case Status Code
Invalid user input / malformed XDR 400 unchanged
Authorization 401/403 unchanged
Dependency unavailable / timeout / rate-limited / malformed 502 UPSTREAM_ERROR
Provider rejected the transaction 502 PROVIDER_REJECTED
Unexpected server error 500 INTERNAL_ERROR

Successful response shapes are unchanged.

Testing

  • New tests/provider-errors.test.ts (21 tests): mocked-fetch coverage of timeout, non-success HTTP status, non-JSON and schema-mismatched responses, provider rejection with result codes, successful shapes, worker classification, and route-level envelopes via app.inject (simulated provider outage)
  • npm run build ✅ · npm test ✅ (757 passed) · npm run lint ✅ (0 errors)

Introduce ProviderError (src/lib/provider-error.ts), a typed AppError
carrying a normalized failure category (timeout, transport, rate_limited,
unavailable, malformed, rejected) plus safe provider/operation
identifiers. All Horizon and SEP-24 anchor I/O under src/services/* now
converts provider failures through toProviderError so Fastify handlers
and the worker see categories instead of raw SDK exceptions.

- Routes: dependency failures answer 502 UPSTREAM_ERROR; provider
  rejections answer 502 PROVIDER_REJECTED; escaping timeout/transport
  errors get a safe 502 instead of a generic 500. Successful response
  shapes are unchanged.
- Safety: client messages and logs never carry tokens, authorization
  headers, signed XDRs, or raw upstream payloads — only validated
  identifier tokens (e.g. Horizon result codes). stellar.ts now logs via
  Pino with safe fields only.
- Workers: classifyJobFailure reads the typed category first (timeout →
  indeterminate ledger check, rate-limit/outage → transient, rejection →
  permanent); anchor poll results persist their category for retry
  decisions.
- Untyped failures handled without throwing: timeouts, non-success HTTP
  statuses, non-JSON bodies, and schema-mismatched responses are all
  categorized.

Closes mergepay#179
@drips-wave

drips-wave Bot commented Aug 26, 2026

Copy link
Copy Markdown

@kingAkinwale 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! 🚀

Learn more about application limits

@mergekeeper

mergekeeper Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

MergeKeeper review

Scope: in scope for linked issue #179.
Verdict: clean

The pull request correctly implements structured error handling for Stellar Horizon and SEP-24 anchor failures in accordance with the issue requirements.

Reviewed commit: 3bcaf914840a2557a883dd8f132155f93da6b809.
CI and merge eligibility are checked separately.

@mergekeeper

mergekeeper Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

MergeKeeper merge status

Status: blocked
PR state: open
Mergeability: unknown
Checked commit: 3bcaf914840a2557a883dd8f132155f93da6b809.

Reason: One or more required CI checks failed.

Failing checks:

Next steps:

  1. Open the failing check details above and fix the reported error.
  2. Run the same checks locally where possible.
  3. Commit and push the fix.
  4. MergeKeeper will automatically re-review the updated PR.

@K1NGD4VID
K1NGD4VID merged commit 95a8d1d into mergepay:main Sep 2, 2026
1 check failed
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.

Add structured error handling for Horizon and anchor failures

2 participants