fix(indexer): keep typed source errors in IndexError - #135
Merged
valoryyaa-byte merged 1 commit intoJul 28, 2026
Merged
Conversation
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.
|
@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! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #111.
Problem
IndexErrorflattened its causes intoString(Xdr,Strkey,Decode, andRpc), so the underlying typed error was gone by the time a caller saw it. Theonly structure left was the coarse
Http | Rpcsplit thatis_transientusedfor retry decisions — and
Rpc(String)lumped together three genuinelydifferent 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_envelopewas the worst case:map_err(|_| IndexError::Xdr(...))discarded the
xdr::Errorentirely and replaced it with a hand-written string.Change
Xdr,Strkey,Decodecarry their cause via#[from](
xdr::Error,stellar_strkey::DecodeError,serde_json::Error). The manualFrom<xdr::Error> for IndexErrorimpl 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-RPCerrorobject,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 missingresultorresults[0].Envelope { context, source }replaces the ad-hocXdr(String)values builtwhile encoding the invoke envelope, keeping both the typed
xdr::Errorandwhich part failed (function name / call arguments / operation list).
UnsupportedAddress(ScAddress)holds the address instead of aDebugdumpstuffed into
Decode.is_transientis now an exhaustivematch, so a new variant has to classifyitself rather than silently defaulting to non-transient.
Behaviour
Deliberately unchanged.
Rpc— nowRpc,Simulation,MissingField— is still transient. Narrowing this (e.g.treating
Simulationas permanent, since a trapped contract call will trapagain) is now possible, but it's a behaviour change and doesn't belong in a
typing fix.
Displaystring still embeds its cause, so existingerror = %eoutput reads as before.Rpcadditionally renders the code whenthe node sends one:
rpc returned an error (code -32602): invalid params.Tests
only_http_and_rpc_errors_are_transient→only_transport_and_node_errors_are_transient, covering every new variant.errors_keep_their_typed_source: assertsError::source()is presentand downcasts to the original
serde_json::Error/DecodeError/xdr::Error— i.e. the thing api:IndexErrorvariants are stringly-typed (Xdr/Decode/Rpc hold String) — loses structured cause #111 asked for.rpc_error_display_includes_the_code_when_present.Verification
cargo test(10/10 pass),cargo clippy --all-targets, andrustfmt --check— the last shows no new diffs beyond the three that alreadyexist on
main.One note:
maindoes not currently compile on its own —api/Cargo.tomlismissing
rand,metricsandmetrics-exporter-prometheus, several imports areabsent,
AppStatederivesCloneover a non-CloneArcSwap, androutes::cache_headerscalls a missingAppState::last_indexed_ledger. All ofthat 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.