Skip to content

feat(indexer): N-block reorg detection + rewind#47

Merged
github-actions[bot] merged 1 commit into
mainfrom
chore/indexer-tier3-arch
May 10, 2026
Merged

feat(indexer): N-block reorg detection + rewind#47
github-actions[bot] merged 1 commit into
mainfrom
chore/indexer-tier3-arch

Conversation

@satyakwok
Copy link
Copy Markdown
Member

Summary

Tier 3 of the indexer audit — first arch-level fix lands. Builds on top of #45 (Tier 1) + #46 (Tier 2).

Pre-Tier-3 `sync.ts` only checked the immediate parentHash of the new block — that catches a 1-block reorg but silently loses data on any reorg that lands a different block at a height already indexed. BFT chains rarely reorg deep but the indexer should be correct against the worst case (validator re-org during binary swap or chain.db rsync recovery).

What lands

New module `apps/indexer/src/reorg.ts` exports `checkAndRewindReorg`:

  1. Read `last_synced_height` from `_meta`.
  2. `SELECT` block hashes from `blocks` for `[synced - DEPTH, synced]`.
  3. Refetch the canonical hash for each height via chain RPC. With viem batch transport (Tier 1) these collapse to one HTTP request.
  4. Walk forward, find first divergence.
  5. On divergence: `DELETE FROM blocks` (FK cascade clears `transactions` + `logs`) + `DELETE FROM token_transfers` + rewind `last_synced_height` to `divergedAt - 1`, all in one SQL transaction. Bumps the `reorg_count` observability counter in `_meta`.

Wired into `apps/indexer/src/index.ts` via `setInterval`:

Env Default Purpose
`INDEXER_REORG_INTERVAL_MS` 60000 Cadence between checks
`INDEXER_REORG_CHECK_DEPTH` 16 How many blocks back to verify

Cleared on graceful shutdown alongside the `stats_daily_mv` refresh timer.

Deferred Tier 3 items (separate follow-up PRs)

The remaining Tier 3 work is each a multi-day refactor and lands in its own PR:

Test plan

  • `pnpm turbo build` passes
  • On a staging DB, manually mutate one `blocks.hash` to a wrong value and verify the next check rewinds correctly + bumps `reorg_count`
  • Verify `token_transfers` rows are cleaned up alongside cascaded `transactions` / `logs`
  • On a quiescent indexer (no new tips), `reorg_count` stays 0 across multiple intervals

Pre-Tier-3 sync only checked the immediate parentHash of the new block —
that catches a 1-block reorg but silently loses data on any reorg that
lands a different block at a height already indexed. BFT chains rarely
reorg deep but the indexer should be correct against the worst case
(validator re-org during binary swap + chain.db rsync recovery).

New module apps/indexer/src/reorg.ts exports checkAndRewindReorg:

  1. Read last_synced_height from _meta.
  2. SELECT block hashes from blocks for [synced - DEPTH, synced].
  3. Refetch the canonical hash for each height via chain RPC. With
     viem batch transport (Tier 1) these collapse to one HTTP request.
  4. Walk forward, find first divergence.
  5. On divergence: DELETE FROM blocks (FK cascade clears txs + logs)
     + DELETE FROM token_transfers + rewind last_synced_height to
     divergedAt - 1, all in one SQL transaction. Bumps the reorg_count
     observability counter in _meta.

Wired into apps/indexer/src/index.ts via setInterval — default 60 s
cadence (env INDEXER_REORG_INTERVAL_MS), default depth 16 blocks
(env INDEXER_REORG_CHECK_DEPTH). Cleared on graceful shutdown alongside
the stats_daily_mv refresh timer.

The remaining Tier 3 items from the audit — declarative event handlers,
GraphQL surface, table partitioning — are each multi-day refactors and
land in their own follow-up PRs.
@github-actions github-actions Bot enabled auto-merge (squash) May 10, 2026 20:18
@github-actions github-actions Bot merged commit 2a82414 into main May 10, 2026
5 checks 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.

1 participant