Reorg behavior
State is a pure function of the chain. When the chain changes, the state changes with it, by rolling back and replaying.
- The four steps an indexer takes when a block it already applied is no longer on the chain.
- What
CONFIRMATIONS_FINALof 6 settles, and what it does not. - Which records a reorg can move: a birth height, a commit age, a ring, a founding flag.
- Why the artifact id survives a reorg but not a replacement that changes the txid.
- How to prove your rollback is exact instead of hoping, using regtest and a state root.
The rule
Four steps, with nothing in between them. The public protocol page puts it in one line: a reorg is a replay, not a repair.
- Detect that the block at some height no longer matches the chain your node reports.
- Roll state back to the last common ancestor, the fork point.
- Replay every block from the fork point forward, in order, applying the same rules as always.
- Serve the result.
There is no reconciliation logic, no patching, and no attempt to preserve anything that was built on blocks that no longer exist. Replay is the only correct answer, and it is also the simplest one.
Finality
CONFIRMATIONS_FINAL is 6. That is the depth at which the indexer treats a record as settled
and stops expecting it to change.
| Confirmations | How to treat it |
|---|---|
| 0 | It has not happened. Mempool state is not protocol state. |
| 1 to 5 | Provisional. Show it, label it as unconfirmed to that depth, and expect it might vanish. |
| 6 or more | Final for practical purposes. A deeper reorg is possible and would still be handled by replay. |
Six is a convention, not a law of physics. The protocol is correct under any reorg depth because it always replays. Six is where the product stops warning you.
What a reorg can change
Find the row that matches what was in the dropped block. All five resolve by replay, but they do not leave you in the same place, and the second row is the one that can turn a valid reveal into a too young one.
| If the reorg drops | Effect |
|---|---|
| Your reveal | The artifact stops existing. If the transaction is mined again later, the artifact returns with a
new birth_height. The artifact id is unchanged as long as the txid is unchanged. |
| Your commit | The commit output moves to a different height, so the 144 block age is measured from the new height. A reveal that was valid may become too young. |
| A spend of your carrier | The ring is removed and the artifact returns to its previous carrier with its old stretch intact. Depth resumes as if the spend never happened, because it did not. |
| Blocks around a window boundary | Founding status is recomputed from the new heights. An artifact that qualified may not, and one that did not may now qualify. |
| Nothing of yours | Heights below the fork point are untouched, so most artifacts see no change at all. |
The id is derived from the reveal txid and the carrier index, not from a height. A reorg that moves your reveal to a different block leaves the id alone. A replacement that changes the txid creates a different id, and that is not a reorg, it is a different transaction.
Requirements for an implementation
Five requirements. The third is the one most often skipped, and the one that corrupts state without saying anything.
- Store block hashes, not just heights. You cannot detect a reorg by height alone.
- Keep enough history to roll back. Deep enough to cover any reorg you are willing to survive without a full reindex. Deeper is cheaper than a rebuild.
- Make rollback exact. After rolling back to height H and replaying to height H, the state root must equal what it was the first time. If it does not, your rollback is lossy.
- Never serve a mixture. While rolling back and replaying, either serve the old consistent state or report not ready. Do not serve half replayed state.
- Reindex is always available. If rollback cannot reach the fork point, rebuild from the deployment start height. Slow is acceptable, wrong is not.
What clients should do
- Read
tip_heightandindexed_heightfromGET /patina/statusand show the height everything was read at. - Label anything under 6 confirmations as provisional.
- Do not cache artifact records past the point where a reorg could invalidate them. If you cache, cache with the height and revalidate.
- Never celebrate a mint at zero confirmations.
Testing it
Reorg handling is the part of an indexer most likely to be wrong, because it is rarely exercised in production. Test it deliberately on regtest:
- Mine a chain, create an artifact, note the state root.
- Invalidate the block containing the reveal and mine an alternative branch.
- Confirm the artifact disappears.
- Mine the reveal again on the new branch. Confirm the artifact returns with the new height.
- Roll the whole thing back and replay from scratch. Confirm the state root matches a fresh index of the final chain.
The golden vectors include reorg cases. See Golden vectors and state roots.