> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://docs.ampdb.io/api/mcp` to find what you need.

# Reorgs

A chain sometimes replaces blocks it already published with different ones. This is a reorganization, or reorg. Amp
detects reorgs, replaces the data it extracted from the abandoned blocks, and tells streaming clients which data to
replace on their side.

## What a reorg is

A chain's nodes agree on one sequence of blocks, the canonical chain. Sometimes, two nodes produce competing blocks at
the same height, and the network settles on one later. The blocks that lose are abandoned (orphaned), along with the
transactions and logs in them, and the chain continues from the winners.

```text
┌─────┬─────┬─────┐
│ 100 │ 101 │ 102 │
└─────┴─────┴─────┘
      ┌─────┬─────┬─────┐
      │ 101'│ 102'│ 103'│
      └─────┴─────┴─────┘

canonical chain: 100, 101', 102', 103'
orphaned blocks: 101, 102
reorg depth: 2
```

Here, blocks 101 and 102 were replaced by 101' and 102'. The reorg's depth is the number of blocks replaced: 2. Data
from blocks 101 and 102 is no longer true, and anything computed from it must be redone.

Reorgs mostly affect a chain's most recent blocks. Once a block is finalized, which on Ethereum takes about 15 minutes,
it won't be replaced.

## How Amp handles them

Every block names its parent, the block before it, by its hash, a fingerprint of its contents. So a block that doesn't
name the previous block's hash as its parent reveals a reorg.

Amp records, for each [Parquet](/get-started/glossary#parquet) file it writes, the range of blocks it holds, with the
hash of its last block and of the block before its first. When newly extracted blocks don't link to the ones Amp
already has, it knows the chain reorganized. It extracts the new blocks, and from then on, queries read only the files
that form an unbroken chain of hashes. The files from abandoned blocks are no longer used.

Block numbers don't need to be consecutive: some chains, like Solana, skip numbers. Amp relies on hashes, not numbers,
to link blocks together.

## What streaming clients see

A [batch query](/querying/batch-queries) reads a consistent snapshot of the data, so it never sees a reorg. A
[streaming query](/querying/streaming-queries) can: it may have sent rows from blocks that the chain then replaced.

With every batch of rows, a streaming query sends the block range those rows came from, as JSON metadata. For a batch
of the [Quickstart](/get-started/quickstart)'s blocks:

```json
{
  "ranges": [
    {
      "network": "mainnet",
      "numbers": { "start": 24000000, "end": 24000099 },
      "hash": "0x6ed4b3511195052eea47925fe5035f5e3fc528440eed62649ba82058a1b70e40",
      "prev_hash": "0xe8f20171e085c248ac4d60b65ddeb9c1bd679149cc1be9925ad58799dc380b25"
    }
  ],
  "ranges_complete": false
}
```

* `ranges`: one range per network the rows came from.
* `numbers`: the first and last block of the range, both included.
* `hash`: the hash of the range's last block.
* `prev_hash`: the hash of the block before the range's first block.
* `ranges_complete`: `true` on the last batch for these ranges. The stream has then delivered everything for them, and
  you can safely save your position.

To detect a reorg, compare each range with the previous one for the same network:

* If it's the same range, the rows come from the same blocks: there's nothing to check.
* If the new range starts after the previous one ended, its `prev_hash` must equal the previous `hash`: the chain
  continues, even if block numbers were skipped.
* If the new range starts at or before the previous one's end, and its `prev_hash` differs from the previous `hash`,
  the chain reorganized. Throw away what you received for the blocks from the new range's start onwards: the stream now
  sends their replacements.

If the new range starts in the middle of a range you already received, the batches for that whole range are invalid.
Throw them away, and resume the stream from the last position you saved before it.

### Resuming a stream

To resume a stream, for example after your application restarts, send the last block you fully processed, for each
network, in the stream request's `amp-resume` header:

```json
{
  "mainnet": {
    "number": 24000099,
    "hash": "0x6ed4b3511195052eea47925fe5035f5e3fc528440eed62649ba82058a1b70e40"
  }
}
```

Take it from a batch with `ranges_complete: true`, never from one without: you could miss rows. Amp checks the hash, so
if the chain reorganized while you were away, the stream's first ranges show it. [Arrow
Flight](/querying/arrow-flight) documents the headers and metadata.

### Let the tools handle it

You rarely need to implement this yourself:

* The [TypeScript SDK](/sdks/typescript)'s [transactional streams](/sdks/typescript/transactional-streams) and
  [change data capture streams](/sdks/typescript/cdc-streams) track ranges, detect reorgs, and resume for you.
* The [Python SDK](/sdks/python)'s [loaders](/sdks/python/loaders) track ranges as they copy a stream into a
  database, and delete the rows of reorged blocks.
* [`ampsync`](/clis/ampsync) keeps a PostgreSQL copy of a dataset, reorgs included.

## Avoiding reorgs

A raw dataset can extract only finalized blocks, which can't be reorganized: generate its manifest with
`--finalized-blocks-only`. Its data then lags the chain's latest block by the time blocks take to finalize. Each chain
expresses finality its own way: see [Raw Datasets](/datasets/raw-datasets).
