feat(indexer): reconcile indexed event counts against the RPC source - #550
feat(indexer): reconcile indexed event counts against the RPC source#550Miracle656 wants to merge 2 commits into
Conversation
|
@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! 🚀 |
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).
83aaaaf to
d794d6b
Compare
|
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 |
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): everyRECONCILE_INTERVAL_MS(default 10 min) it re-fetches the most recentRECONCILE_LEDGER_SPANsettled ledgers (default 400 — deliberately the same window as the nightly testnet-correctness suite, i.e. a meaningful testnet window) sittingRECONCILE_TIP_MARGINbehind 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 countssoroban_eventsplusparse_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-runremains the on-demand path for arbitrary historical ranges.RpcClient::get_latest_ledgerloses 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_fromboundaries never read as missing; parse-error rows count as accounted for. Runs over a 400-ledger window continuously; choice documented. Full workspace suite green;promtoolclean; env vars documented (CI-enforced).