The experiment
Branch A head: 3; branch B head: 3. Same height: true. Different hashes: true. Index unchanged after node-only revert: true.
01 / Anchor — Deploy TaskBoard at block 1; snapshot the node before any task event.
02 / Branch A — Complete tasks #42 and #99. Persist both indexers at A’s head, including row identities and block checkpoints.
03 / Replace history — Restore only the node. Complete #7 and #9 on branch B; keep A’s persisted index untouched. Both heads are block 3.
04 / Reconcile — Locate the common ancestor at block 1. Remove 2 A rows and replay 2 B rows.
05 / Challenge the conclusion — Run replay, stale-disk restart, shallow-history and interrupted-RPC controls. Exercise a separate completion-count projector on a subsequent block.
Projection comparison
| Adapter | Outcome | State agrees | Orphan / missing / duplicate rows |
|---|
| Height-only cursor | diverged | false | 2 / 2 / 0 |
| Rollback + replay | matched | true | 0 / 0 / 0 |
| Clean rebuild | matched | true | 0 / 0 / 0 |
Controls
Normal indexing: PASS (expected pass; expectation met)
Before the branch switch, the projection agrees with contract storage and canonical event identities.
Branch replacement exercised: PASS (expected pass; expectation met)
Node restore returned true; A/B reach the same height with distinct block hashes. The on-disk index stayed byte-for-byte unchanged when only the node reverted.
Expose the flawed projection: PASS (expected pass; expectation met)
Height-only indexing retains two orphaned rows and misses two canonical rows, despite the same completed-task total.
Recover state and exact rows: PASS (expected pass; expectation met)
Recovered task state and row identities match both pinned contract truth and a clean rebuild.
Replay is idempotent: PASS (expected pass; expectation met)
A second sync at the same canonical head preserves the complete persisted snapshot and adds zero rows.
Restart from the stale disk index: PASS (expected pass; expectation met)
A fresh adapter instance loads A from JSON on disk and recovers to the same B state and rows; no in-memory recovery state is carried over.
Insufficient rollback depth: INCONCLUSIVE (expected inconclusive; expectation met)
No common ancestor within 1 block(s); full reconstruction required. The old index must remain unchanged; no claim of recovery.
Interrupted RPC read: INCONCLUSIVE (expected inconclusive; expectation met)
Injected RPC failure while reading canonical logs. The candidate snapshot is discarded; the persisted index stays unchanged.
Second projector example: PASS (expected pass; expectation met)
The exported Projector interface runs an independent transition counter: after reopening #7, the result is 1 and agrees with contract storage.
Canonical rows
Block 2 · task #7 → complete
0x1d81b1e60bcc244e3f76baa473fbf43a3035a30c2774f50d3363fced42682830:0x8a1b7d6ac79ac3e17964d4388e10a2d220823dbec7c78527716a657eec5a7f68:0
Block 3 · task #9 → complete
0x15da9b43db2853b3f010650c8ece038474fc3f92967fe62cad078f01f84a0ccf:0x262330430bc829d772af7dd23f93ec5584d9a0008262a7ccbd3db3db45e3bc29:0
Pinned truth
TaskBoard storage via eth_call with EIP-1898 blockHash + requireCanonical; eth_getLogs pinned to each canonical blockHash. Target hash checked before and after.
Head 0x15da9b43db2853b3f010650c8ece038474fc3f92967fe62cad078f01f84a0ccf
{
"7": true,
"9": true,
"42": false,
"99": false
}Second adapter
A separate delta-based projector consumes the canonical stream and then a new #7 → open transition at block 4. The contract counter is read at that exact block hash. Result 1; contract 1.
Limits
- This is controlled branch replacement using snapshot/revert on one local EVM. It does not test consensus, network propagation, public-chain finality or a live reorg.
- The reference indexers cover TaskBoard event streams and JSON-file persistence. The adapter interface is not a universal repair layer for arbitrary databases.
- The browser displays a saved execution. It cannot execute the Node/EDR runner; run the documented local command and import its JSON to inspect your own result.
- Negative controls deliberately produce inconclusive results. A shallow rollback window or failed RPC read cannot establish recovery.
- Contract truth covers the four known task IDs and completedCount. Row comparison additionally checks missing, orphaned and duplicate event identities.
Reproduce
npm ci
npm run lab
The runner creates a new local EVM and overwrites the latest JSON, standalone HTML and local index snapshots. No wallet or funds are required.
Engine source digest: 16f7135f19cf96ed4a3a9330f93d1e950fa16c93f287ea372047baf7c817e098
Generated by Reorg Lab · Jhonatan del rio mejia, with Codex assistance · MIT