Staff / expert · Blockchain infrastructure
The indexer reconnects after a reorg
A WebSocket subscription reconnects after missing several blocks, and a previously credited log now belongs to an orphaned block. How does the indexer recover?
Try explaining this aloud before opening the answer. State your assumptions and trace a boundary case.
package main
import "fmt"
type Block struct {
Height uint64
Hash, Parent string
}
func appendBlock(chain []Block, b Block) ([]Block, error) {
if len(chain) > 0 {
tip := chain[len(chain)-1]
if b.Height != tip.Height+1 || b.Parent != tip.Hash {
return chain, fmt.Errorf("reconcile canonical ancestor before append")
}
}
return append(chain, b), nil
}
func main() {
chain := []Block{{Height: 10, Hash: "A10", Parent: "A9"}}
chain, _ = appendBlock(chain, Block{Height: 11, Hash: "A11", Parent: "A10"})
_, err := appendBlock(chain, Block{Height: 11, Hash: "B11", Parent: "A10"})
fmt.Println(err) // replacement is not silently appended
}Reveal a strong answer
A WebSocket subscription is a live notification channel, not a durable ledger cursor. A process can disconnect, a provider can drop a slow subscriber and a chain can reorganize recently observed blocks. Store a checkpoint that includes chain identity and block hash, backfill a bounded range after reconnect and make the projection’s rollback or rebuild behavior explicit.
Use FilterLogs for bounded backfill and SubscribeFilterLogs for live delivery where supported. Record chain ID, block hash/number, transaction hash and log index as required by the projection. Treat removed logs as a signal to reconcile; reconnect with overlap and deduplicate. Bound queues and scan ranges, and checkpoint only after the corresponding durable effect is safe to replay. The second block at height 11 does not extend the current canonical tip, so the model demands reconciliation. It is not a full fork-choice algorithm. A real indexer locates the common ancestor, reverses or rebuilds affected projection state and checkpoints atomically before continuing along the new canonical branch.
A receipt or one observed block is not universal finality. Finality guarantees and acceptable confirmation policy depend on the network, consensus state and business consequence. Do not permanently credit an irreversible external effect solely from an unfinalized subscription event. Test a missed block, duplicate event, removed log, competing hash at one height, crash during projection update and provider history limits.
Common trap
Remembering only the last event number or transaction hash loses canonical-chain context and can miss removed events.
Try it
Design the canonical checkpoint, bounded backfill overlap, event identity, reversible projection and finality policy.
Worked solution
A subscription is not a durable cursor. Resume from a persisted chain/block-hash checkpoint, backfill with overlap, deduplicate and detect the canonical ancestor when hashes diverge. Roll back or rebuild affected projection state and checkpoint with its durable effect. Finality and acceptable confirmation policy depend on the network and business risk; an unfinalized observed log must not automatically trigger an irreversible external credit.
Change the constraint
What if the RPC provider has pruned the history required for reconciliation?
How did you do?
Self-assessment, not a compiler pass or interview score. Explain the reasoning unaided before marking understood.
Not marked. Progress stays on this browser, separate from course completion.
Primary references
Checked 2026-10-28. Use documentation for your pinned runtime, driver and tool version.