Skip to content

Cursors and reorgs

Persist Substreams cursors and handle undo signals for reliable consumers.

Decide how your consumer keeps stored data complete and correct when streams disconnect and the chain reorganizes. Two rules cover both: save each cursor in the same transaction as the output it follows, and revert writes after the last valid block when an undo signal arrives.

Cursor model

Each BlockScopedData message carries one block's output and a cursor. The cursor is the resume token for the stream position after that block. Reconnecting with it continues at the next block.

Commit in this order:

  1. Apply the block's output to durable state.
  2. Save the cursor in the same transaction when possible.
  3. After a disconnect, resume from the latest committed cursor.

A cursor saved before its output commits can skip that block after a restart. A cursor kept only in memory makes the consumer replay work from an earlier block, so writes must also be idempotent.

Reorg and undo model

Near the chain head, a block can be replaced by a different block at the same height. Substreams then sends a BlockUndoSignal that names the last valid block and its cursor. Your consumer must revert everything it wrote after that block, then continue with the replacement blocks.

Blocks N+1 and N+2 were replaced. The undo signal names N as the last valid block, so the consumer reverts what it wrote after N, stores cursor cN, and applies the replacement blocks.
MessageMeaningRequired action
BlockScopedDataOutput for one block, with its cursor.Apply the output and save the cursor after commit.
BlockUndoSignalReorg correction with the last valid block and cursor.Revert local state to the last valid block and save its cursor.
ModulesProgressProgress for active jobs and stages.Use for monitoring and backfill visibility.

To revert, keep enough history to undo recent writes, for example by recording the block number on each row. If your workload cannot revert writes, stream final blocks only; output then trails the chain head.

The full sequence

Commit each block's output together with its cursor. On an undo signal, revert to the last valid block and keep its cursor, then resume from the stored cursor after a reconnect.

Practical consequences

  • Store cursors durably, not only in process memory.
  • Make writes idempotent by block number, block hash, ordinal, or a package-specific key.
  • Handle undo signals before claiming exactly-once materialization.
  • Reconnect with backoff, and resume from the latest committed cursor.

Next steps