Skip to content

fix(indexer): mark reads stale and surface per-network status when indexer is unconfigured (#238) - #252

Merged
Miracle656 merged 3 commits into
Miracle656:mainfrom
Stanley471:fix/issue-238-mainnet-indexer-staleness
Oct 18, 2026
Merged

Miracle656 merged 3 commits into
Miracle656:mainfrom
Stanley471:fix/issue-238-mainnet-indexer-staleness

Conversation

@Stanley471

Copy link
Copy Markdown
Contributor

Closes #238

Summary of Changes

  1. Explicit Staleness Marking on Unconfigured/Stopped Networks:

    • In \src/api.ts, the stale read middleware now checks whether the network's indexer is stopped or unconfigured via \isIndexerStopped(net)\ alongside \checkRpcHealth(net).
    • When the indexer loop for a network is not running, read responses serve whatever data is in the database with explicit staleness headers and body attributes:
      • Header: \X-Data-Stale: true, \X-Indexer-Running: false, and \X-As-Of-Ledger: .
      • JSON body: \stale: true, \indexer_running: false, and \�s_of_ledger: .
    • Rationale for Answering with Explicit Staleness Markers: Answering with an explicit staleness marker rather than refusing outright is essential so existing clients, dashboards, and wallet history views can still display previous historical transactions rather than encountering hard 500/503 errors or receiving an empty list that masquerades as zero transactions. The presence of \stale: true\ and \indexer_running: false\ allows callers and dashboards to unmistakably distinguish 'no transfers' from 'indexer stopped/unconfigured'.
  2. **Per-Network Indexer State in /status**:

    • Track per-network indexer status in \src/indexer.ts\ (
      unningNetworks, \isIndexerRunning, \isIndexerStopped, \getIndexerUnstartedReason).
    • In \src/api.ts, the /status\ endpoint now reports every enabled network in the
      etworks\ map with
      unning: boolean, \lastIndexedLedger, \latestLedger, \lagLedgers, and
      eason\ (when stopped/unstarted due to missing configuration).
  3. Validation & Unconfigured-RPC Path Tests:

    • Added unit test in \src/tests/multiNetworkIndexer.test.ts\ covering the unconfigured-RPC path when \startAllIndexers\ encounters a network missing required RPC config (e.g. \SOROBAN_RPC_URL_MAINNET).
    • Added unit tests in \src/tests/staleReads.test.ts\ verifying that reads for a stopped indexer return explicit staleness markers and that /status\ surfaces per-network running status, lag, and unstarted reasons.
  4. Documentation:

    • Updated \README.md\ to document the per-network RPC variables (\SOROBAN_RPC_URL_TESTNET, \SOROBAN_RPC_URL_MAINNET), their resolution order, and behavior when an enabled network indexer is unconfigured.

@drips-wave

drips-wave Bot commented Oct 16, 2026

Copy link
Copy Markdown

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

…fields

`the committed openapi.json matches the generator output` was the only red:
this PR adds fields to the status schema in src/openapi/schemas.ts without
re-running the generator, so the committed document described the previous
shape.

    npm run docs:openapi

The generator is deterministic, so this is mechanical — but the test
exists because a spec that silently lags the code is worse than no spec:
callers build against the document, not the handler.

openapiCoverage.test.ts: 7 passed.

Claude-Session: https://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB

@Miracle656 Miracle656 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behaviour is right and this closes a real gap: a network whose indexer never started served database rows with no indication they were frozen. "Stale" and "there is genuinely nothing new" are indistinguishable to a caller unless we say which, and an indexer that is not running is the case where the distinction matters most — the data does not get older-looking, it just stops moving.

Marking those reads stale and reporting per-network state with a reason is the right shape. A /status that says a network is not running without saying why sends someone to the logs; getIndexerUnstartedReason answers it in the response.

Switching the status listing from runningNetworks() to enabledNetworks() is the subtle fix inside the obvious one. Listing only running networks means a network that failed to start is absent from /status rather than reported as down — and absent reads as "not configured", which is exactly the wrong conclusion.

I pushed one commit to your branch. The only red was:

● OpenAPI route coverage (W085) › the committed openapi.json matches the generator output

You added fields to the status schema without re-running npm run docs:openapi, so the committed document described the previous shape. Regenerated — deterministic, so it is mechanical. openapiCoverage.test.ts: 7 passed, full suite 704.

One nit for next time, not worth another round. These guards are dead:

typeof isIndexerStopped === "function" ? isIndexerStopped(net) : false

All three functions are static exports from ./indexer on this same branch — isIndexerRunning at line 217, isIndexerStopped at 221, getIndexerUnstartedReason at 225 — so the typeof can never be false and the fallbacks are unreachable. Two reasons to drop them: the : false default means "not stopped", which is the wrong way for a staleness check to fail; and the isIndexerRunning fallback is a second implementation of the same question, which can drift from the real one without any test noticing.

Integration tests fails identically on main and is not yours.

Merging.

@Miracle656
Miracle656 merged commit 6a7457d into Miracle656:main Oct 18, 2026
1 check passed
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.

The mainnet indexer never starts, and reads serve stale data without saying so

2 participants