Skip to content

feat(indexer): reconcile indexed event counts against the RPC source - #550

Open
Miracle656 wants to merge 2 commits into
Telocel-Labs:devfrom
Miracle656:feature/511-rpc-count-reconciliation
Open

feat(indexer): reconcile indexed event counts against the RPC source#550
Miracle656 wants to merge 2 commits into
Telocel-Labs:devfrom
Miracle656:feature/511-rpc-count-reconciliation

Conversation

@Miracle656

Copy link
Copy Markdown

Closes #511

Problem

Nothing proved that what Trident indexed matches what the chain actually emitted — silent under-indexing was invisible, and the API returned confident, incomplete answers.

What this does

A reconciliation loop inside the indexer (crates/indexer/src/reconcile.rs): every RECONCILE_INTERVAL_MS (default 10 min) it re-fetches the most recent RECONCILE_LEDGER_SPAN settled ledgers (default 400 — deliberately the same window as the nightly testnet-correctness suite, i.e. a meaningful testnet window) sitting RECONCILE_TIP_MARGIN behind the tip and never past the ingest cursor, and compares per-ledger event counts against the database.

The comparison mirrors the ingest pipeline exactly — same server-side filter plan (allowlist + topics, incl. the degraded index-all fallback), the diagnostic gate, the failed-call skip, per-contract index_from — because indexed counts are not raw RPC counts and comparing unlike sets would make every report a false positive. The DB side counts soroban_events plus parse_errors (seen-but-undecodable is accounted for, not missing). A parity test pins the mirrored rules.

Discrepancies are reported with the specific ledger ranges involved (contiguous discrepant ledgers coalesced, each logged with both counts), in both directions — missing AND extra events. A page-cap-truncated walk clamps the compare window to the fully-walked prefix so un-walked ledgers can never surface as fake discrepancies. Six new trident_indexer_reconcile_* metrics (described/seeded) feed two alerts with runbook entries: TridentIndexerReconciliationMismatch (any discrepant ledger, sustained across passes) and TridentIndexerReconciliationFailing (passes keep aborting — silence is unknown, not clean).

Continuous vs on-demand — decided and documented: a slow continuous in-process loop, default ON (disabling logs a loud warning), because that surfaces under-indexing in minutes at negligible RPC cost; trident-backfill --dry-run remains the on-demand path for arbitrary historical ranges. RpcClient::get_latest_ledger loses its #[cfg(test)] gate — its own comment asked for exactly this production caller.

Done-when check

Integration tests (real Postgres + mock RPC) prove it: an under-indexed and an over-indexed ledger are reported as exactly the right ranges with the right counts; failed-call/diagnostic events and allowlist/index_from boundaries never read as missing; parse-error rows count as accounted for. Runs over a 400-ledger window continuously; choice documented. Full workspace suite green; promtool clean; env vars documented (CI-enforced).

@drips-wave

drips-wave Bot commented Aug 29, 2026

Copy link
Copy Markdown

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

Nothing proved that what Trident indexed matches what the chain actually
emitted: the streamer trusts its own poll loop, so a missed page, a
filter bug, or a skipped event was invisible — the API kept returning a
confident, incomplete answer.

A reconciliation loop now runs inside the indexer (crates/indexer/src/
reconcile.rs): every RECONCILE_INTERVAL_MS (default 10 minutes) it
re-fetches the most recent RECONCILE_LEDGER_SPAN settled ledgers
(default 400 — deliberately matching the nightly testnet-correctness
window, i.e. a meaningful testnet window rather than a handful of
ledgers) from getEvents, sitting RECONCILE_TIP_MARGIN ledgers behind
the tip and never past the ingest cursor, and compares per-ledger
event counts against the database.

Correctness of the comparison is the heart of it: indexed counts are
not raw RPC counts, so the RPC side applies exactly the ingest
pipeline's own selection rules — the same server-side filter plan
(allowlist + topic patterns, including the degraded index-all
fallback), the diagnostic gate, the failed-call skip, and per-contract
index_from boundaries — and the database side counts soroban_events
plus parse_errors (an event that was seen but undecodable is accounted
for, not missing). A parity test pins the mirrored rules so a rule
added to the parser without updating the reconciler fails the suite.

Discrepancies are reported with the specific ledger ranges involved:
contiguous discrepant ledgers coalesce into ranges, each logged with
both counts, and both directions are surfaced — missing events (silent
under-indexing) and extra events (over-indexing). Six new
trident_indexer_reconcile_* metrics (described and zero-seeded) feed
two new alerts: TridentIndexerReconciliationMismatch on any
discrepant ledger sustained across passes, and
TridentIndexerReconciliationFailing when passes keep aborting — while
that fires, mismatch silence is unknown, not clean. Runbook entries
cover triage and repair (idempotent trident-backfill over the reported
ranges; --dry-run for on-demand checks).

Continuous vs on-demand is decided and documented: a slow continuous
in-process loop (default on; disabling logs a loud warning), because
that is what surfaces under-indexing in minutes rather than at the
next incident and the RPC cost at this cadence is negligible — with
trident-backfill --dry-run as the existing on-demand path for
arbitrary historical ranges. RpcClient::get_latest_ledger loses its

Integration tests (Postgres + mock RPC) prove the acceptance scenario:
an under-indexed ledger and an over-indexed ledger are reported as
exactly the right ranges with the right counts, failed-call and
diagnostic events on the RPC side do not read as missing, parse-error
rows count as accounted for, and allowlist/index_from boundaries are
honored; unit tests cover range coalescing.

Closes Telocel-Labs#511
…tabase

The two reconcile tests wiped soroban_events globally and staged their
fixtures at ledgers 850-900 — a window dev's new commit_page test now
also seeds at ledger 900, so under the parallel harness each suite
destroyed the other's rows. The fixtures now live in a window no other
suite touches (30201-30600), cleanup is scoped to that window, and the
two tests serialize against each other over the genuinely global state
they must mutate (the allowlist table and the indexer cursor row).
@Miracle656
Miracle656 force-pushed the feature/511-rpc-count-reconciliation branch from 83aaaaf to d794d6b Compare August 30, 2026 14:05
@Miracle656

Copy link
Copy Markdown
Author

Rebased onto current dev. Two things beyond the mechanical rebase: (1) the metrics module lost its debug-fallback counter on dev (the decoder is exhaustive now), so this PR's stale references to it are gone and only the six reconcile series remain; (2) the reconcile tests' fixtures used a global table wipe and ledgers 850–900 — a neighbourhood dev's new commit_page test now also seeds — so under the parallel harness the suites destroyed each other's rows. The fixtures moved to an untouched window (30201–30600), cleanup is window-scoped, and the two tests serialize over the genuinely global state they mutate (allowlist + cursor row). Full suite green locally apart from two pre-existing failures that reproduce identically on the base commit in this environment.

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.

testnet: reconcile indexed event counts against the RPC source

1 participant