Skip to content

fix(indexer): keep typed source errors in IndexError - #135

Merged
valoryyaa-byte merged 1 commit into
RWA-ToolKit:mainfrom
lemarjohnny781:fix/indexer-typed-errors
Jul 28, 2026
Merged

fix(indexer): keep typed source errors in IndexError#135
valoryyaa-byte merged 1 commit into
RWA-ToolKit:mainfrom
lemarjohnny781:fix/indexer-typed-errors

Conversation

@lemarjohnny781

Copy link
Copy Markdown
Contributor

Closes #111.

Problem

IndexError flattened its causes into String (Xdr, Strkey, Decode, and
Rpc), so the underlying typed error was gone by the time a caller saw it. The
only structure left was the coarse Http | Rpc split that is_transient used
for retry decisions — and Rpc(String) lumped together three genuinely
different situations: a JSON-RPC error object from the node, a simulation that
the node ran and rejected, and a response missing a field we need.

build_invoke_envelope was the worst case: map_err(|_| IndexError::Xdr(...))
discarded the xdr::Error entirely and replaced it with a hand-written string.

Change

  • Xdr, Strkey, Decode carry their cause via #[from]
    (xdr::Error, stellar_strkey::DecodeError, serde_json::Error). The manual
    From<xdr::Error> for IndexError impl that stringified is removed, and seven
    .map_err(|e| ...e.to_string()) call sites collapse to ?.
  • Rpc(String) splits into:
    • Rpc { code: Option<i64>, message: String } — the JSON-RPC error object,
      with the error code now captured instead of dropped;
    • Simulation(String) — the node ran the simulation and it failed;
    • MissingField(&'static str) — a well-formed response missing result or
      results[0].
  • Envelope { context, source } replaces the ad-hoc Xdr(String) values built
    while encoding the invoke envelope, keeping both the typed xdr::Error and
    which part failed (function name / call arguments / operation list).
  • UnsupportedAddress(ScAddress) holds the address instead of a Debug dump
    stuffed into Decode.
  • is_transient is now an exhaustive match, so a new variant has to classify
    itself rather than silently defaulting to non-transient.

Behaviour

Deliberately unchanged.

  • Retries: everything previously classified as Rpc — now Rpc,
    Simulation, MissingField — is still transient. Narrowing this (e.g.
    treating Simulation as permanent, since a trapped contract call will trap
    again) is now possible, but it's a behaviour change and doesn't belong in a
    typing fix.
  • Logs: each Display string still embeds its cause, so existing
    error = %e output reads as before. Rpc additionally renders the code when
    the node sends one: rpc returned an error (code -32602): invalid params.

Tests

Verification

cargo test (10/10 pass), cargo clippy --all-targets, and
rustfmt --check — the last shows no new diffs beyond the three that already
exist on main.

One note: main does not currently compile on its own — api/Cargo.toml is
missing rand, metrics and metrics-exporter-prometheus, several imports are
absent, AppState derives Clone over a non-Clone ArcSwap, and
routes::cache_headers calls a missing AppState::last_indexed_ledger. All of
that predates this branch and is untouched here; I stubbed it locally to get a
build for verification. Happy to send a separate PR for it if useful.

IndexError flattened its causes into String, so the underlying error was
unavailable to callers and only the coarse Http|Rpc split survived for
retry decisions.

- Xdr, Strkey and Decode now carry their cause via #[from]; the manual
  From<xdr::Error> impl that stringified it is gone.
- Rpc splits into Rpc { code, message } (a JSON-RPC error object),
  Simulation (the node ran the simulation and it failed) and
  MissingField (a well-formed response missing a field we need), so a
  node-level error is distinguishable from an empty result.
- Envelope { context, source } replaces the ad-hoc Xdr(String) values
  built while encoding the invoke envelope, keeping both the typed cause
  and which part failed.
- UnsupportedAddress carries the ScAddress rather than a Debug string.
- is_transient now matches exhaustively, so a new variant has to
  classify itself.

Retry behaviour is unchanged: everything previously classified as Rpc
(now Rpc, Simulation and MissingField) is still transient, and Display
still embeds the cause so `error = %e` logs read exactly as before.

Tests: reworked the transient-classification test for the new variants,
and added coverage for source-chain preservation and for the Rpc code
rendering.
@drips-wave

drips-wave Bot commented Jul 25, 2026

Copy link
Copy Markdown

@lemarjohnny781 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

@valoryyaa-byte
valoryyaa-byte merged commit 8a19948 into RWA-ToolKit:main Jul 28, 2026
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.

api: IndexError variants are stringly-typed (Xdr/Decode/Rpc hold String) — loses structured cause

2 participants