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:
- Apply the block's output to durable state.
- Save the cursor in the same transaction when possible.
- 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.
| Message | Meaning | Required action |
|---|---|---|
BlockScopedData | Output for one block, with its cursor. | Apply the output and save the cursor after commit. |
BlockUndoSignal | Reorg correction with the last valid block and cursor. | Revert local state to the last valid block and save its cursor. |
ModulesProgress | Progress 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
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.