Skip to content

Fix unguarded /outspends response parsing in MempoolSpaceIndexerApi - #973

Merged
dangershony merged 1 commit into
mainfrom
fix/indexer-outspends-error-handling
Sep 23, 2026
Merged

dangershony merged 1 commit into
mainfrom
fix/indexer-outspends-error-handling

Conversation

@dangershony

Copy link
Copy Markdown
Member

Problem

FetchUtxoAsync and GetIsSpentOutputsOnTransactionAsync in MempoolSpaceIndexerApi.cs called the indexer's /outspends endpoint and immediately deserialized the response as JSON without checking IsSuccessStatusCode first — unlike every other call site in this file, which all guard with the standard pattern:

_networkService.CheckAndHandleError(response);
if (!response.IsSuccessStatusCode)
    throw new InvalidOperationException($"Indexer {IndexerHost(client)} returned an error: {response.ReasonPhrase}");

When the indexer returns a plain-text error body (e.g. Internal Server Error) instead of JSON — which happens under load — this crashed with a confusing:

System.Text.Json.JsonException: 'I' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0.

...instead of a clear, actionable indexer error.

How this was found

While chasing an intermittent UAT failure in SendFundsTest (three wallets sending funds to each other in rounds against the Angornet test indexer), SendAmount failed with the JSON parse exception above while test.indexer.angor.io was under load.

Fix

Added the same IsSuccessStatusCode guard (and _networkService.CheckAndHandleError) before deserializing in both call sites, matching the existing pattern used everywhere else in this file.

After the fix, the same underlying indexer issue now surfaces as a clear, diagnosable error instead of a crash:

Indexer test.indexer.angor.io returned an error: Internal Server Error

This doesn't fix indexer instability under load (that's a separate, ongoing infra concern — see #971), but it ensures failures are always surfaced with a meaningful message instead of being masked by an unrelated JSON parse exception.

Testing

  • dotnet test src/shared/Angor.Shared.Tests/Angor.Shared.Tests.csproj — 160/160 passed.
  • Re-ran App.Test.Uat/SendFundsTest before/after: confirmed the JSON crash is gone and replaced with the clear indexer error message. The test itself still intermittently fails due to indexer load, tracked separately.

FetchUtxoAsync and GetIsSpentOutputsOnTransactionAsync called the
indexer's /outspends endpoint and immediately deserialized the
response as JSON without checking IsSuccessStatusCode first, unlike
every other call site in this file. When the indexer returns a
plain-text error body (e.g. "Internal Server Error" under load), this
crashed with a confusing System.Text.Json.JsonException:
'I' is an invalid start of a value, instead of a clear indexer error.

Discovered via SendFundsTest UAT failures where SendAmount failed with
this exact JsonException while test.indexer.angor.io was under load.
After the fix, the same failure now surfaces as:
"Indexer test.indexer.angor.io returned an error: Internal Server
Error" — matching the existing error-handling pattern used everywhere
else in the file.

This doesn't fix indexer instability under load, but ensures failures
are diagnosable instead of masked by an unrelated parse exception.
@dangershony
dangershony merged commit 80f2522 into main Sep 23, 2026
1 check passed
@dangershony
dangershony deleted the fix/indexer-outspends-error-handling branch September 23, 2026 22:27
dangershony added a commit that referenced this pull request Sep 24, 2026
#975)

Found while re-running the full UAT suite after merging #973/#974:
SendFundsTest.ThreeUsersSendToEachOther reproducibly failed (2/2 runs)
at the final sweep-all verification with:

  Expected sweptA to be less than 4.0 because A receives C's balance
  minus the network fee (fee is subtracted from the swept amount),
  but found 4.0.

Root cause: FundsViewModel.TotalBalance formats the displayed balance
with "F4" (4 decimal places). At the 2 sat/vB fee rate used by this
test, a single-input sweep transaction costs roughly 200-300 sats
(~0.000002-0.000003 BTC) — three orders of magnitude below what F4
can represent. The fee is genuinely subtracted on-chain; it just
rounds away completely at this display precision, so the strict
BeLessThan assertion was never reliable at this fee rate. This wasn't
caught before because earlier runs failed on unrelated indexer/faucet
issues before ever reaching this check; now that #973/#974 fixed
those, the test reaches this point reliably and exposed the latent
assertion bug.

Fix: relax to BeLessThanOrEqualTo, since "fee not visible at 4-decimal
display precision" is expected, correct behavior, not a bug.

Verified: re-ran SendFundsTest after the fix — passes reliably.
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.

1 participant