Why
The existing replayability card states the invariant; this PoC tests whether a production library actually buys it. A fast query layer that leaves stale rows after a reorg is faster at returning the wrong answer.
An indexer owns the rollback and replay rules behind the database state an application treats as truth. And how much rollback it must support is exactly the finality lag of the chain: only the unfinalized window can be orphaned, so that window is where the undo machinery lives — an RPC cache never has to think about this; an indexer is defined by it.
How it works
Index one small contract with Ponder, force a local reorg, and assert that derived rows match a clean replay byte for byte. Measure initial sync, restart recovery, RPC calls, and schema-change rebuild time.
PoC
- Index transfers from one small contract with Ponder.
- Index the same range twice and verify idempotency.
- Replace the final local block with a competing hash.
- Verify stale derived rows disappear and the result matches a clean replay byte for byte.
- Record initial sync, restart recovery, RPC use, and rebuild time after one schema change.
| Measurement | Question |
|---|---|
| Replay equality | Can the database be discarded and rebuilt exactly? |
| Reorg rollback | Do orphaned events leave any derived state? |
| Schema rebuild | Can the product evolve without hand-patching history? |
Reorg bugs live almost exclusively in derived state. Deleting orphaned raw events is easy (DELETE WHERE block > N); an aggregate updated as balance += amount can only be rolled back by inverting it or re-deriving it, which is why the test asserts on derived rows and not on the event log.
Reference: Ponder official documentation.
Review clarification
The indexer is not a choice
An RPC node answers "what is X's balance now"; it cannot answer product questions — "all transfers for this user, sorted," "top holders," "volume per day." Every real application ends up copying chain data into a database, so the question is never whether to run an indexer, only who wrote it: you, or a library. And whoever maintains that database inherits the rollback and replay rules — that inheritance is the card's thesis.
Why each PoC step exists
| Step | What it catches |
|---|---|
| Index the same range twice | Hidden handler state — double-counting is the cheapest determinism smoke test |
| Replace the tip with a competing hash | The reorg itself — evm_snapshot → mine → evm_revert → mine makes one on demand, on a laptop |
| Check derived rows, not raw rows | Where reorg bugs actually live — DELETE WHERE block > N is easy; balance += amount can only be inverted or re-derived |
| Byte-for-byte vs. a clean replay | The gold standard: the DB is a pure function of the chain — no wall-clock timestamps, no order-dependence, no reorg residue |
| Schema rebuild | Whether the DB accidentally became a source of truth — a derived DB evolves by wipe-and-reindex, never by hand-patching history |
The finality boundary
How much rollback machinery an indexer must keep is exactly the finality lag of its chain: only the unfinalized window can be orphaned, so the undo log lives there and can be dropped once a block is final. An RPC cache never has to think about this; an indexer is defined by it.
What the card does not say
It does not say "Ponder is good." The reasoning chain — we cannot avoid an indexer, and the indexer must own reorgs — is the card's setup, but the last step, therefore Ponder, is earned by the test, not assumed. Ponder claims to own the problem (its pitch against The Graph's subgraphs and Subsquid is local-first TypeScript with reorg handling built in); the PoC exists to verify that claim before a database is bet on it. Pass, and the reorg machinery came for free. One stale row, and what remains is a fast query layer that returns wrong answers.