> **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.

# Verification

Status: Experimental.

Amp extracts a chain's data from a [provider](/providers), like an RPC endpoint. Verification checks that what Amp
stored is what the chain recorded: it recomputes the chain's own fingerprints and signatures from the stored rows, and
compares them with the ones the chain published. A provider that returned corrupted or incomplete data, or a block
damaged on its way to storage, fails the check.

You can verify data as Amp extracts it, so a bad block is never stored, or check data already extracted with
`ampctl verify`. Verification works for [EVM RPC](/providers/evm-rpc) and [Solana](/providers/solana)
[raw datasets](/datasets/raw-datasets).

## How it works

Blockchains make their data checkable with two tools:

* **Hashes.** A hash is a short fingerprint computed from data: the same data always gives the same hash, and changing,
  adding, removing, or reordering anything gives a different one. Each block's header, the summary at the top of the
  block, holds hashes of the block's contents.
* **Signatures.** A signature proves that the holder of a private key approved a message. Anyone can check it with the
  matching public key, and it stops matching if the message changes.

Ethereum commits to a block's whole list of transactions with a single hash, its **transactions root**. It's the root of
a **Merkle tree**: a tree of hashes in which each hash covers the ones below it, so the one at the top covers every
transaction and their order. It does the same for the transactions' results, in the **receipts root**. Verification
rebuilds these trees from the stored rows and compares their roots with the ones in the stored header.

## What verification proves

### EVM chains

For each block, verification reads the `blocks`, `transactions`, and `logs` tables (see
[EVM RPC tables](/datasets/tables/evm-rpc)) and checks:

| Check             | How                                                                                                | What it proves                                                        |
| ----------------- | -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| Block hash        | Recomputes the block's hash from its header fields.                                                | Every header field is stored as the chain recorded it.                |
| Transactions root | Rebuilds the Merkle tree of the block's transactions.                                              | Every transaction is there, in order, and unchanged.                  |
| Receipts root     | Rebuilds each transaction's receipt from the stored transactions and logs, then their Merkle tree. | The results are right: each transaction's status, gas used, and logs. |

It handles every Ethereum transaction type: legacy, EIP-2930, EIP-1559, EIP-4844, and EIP-7702.

### Solana

For each slot, verification reads the `block_headers`, `block_rewards`, `transactions`, `messages`, and `instructions`
tables (see [Solana tables](/datasets/tables/solana)) and checks:

| Check                  | How                                                                                        | What it proves                                                                  |
| ---------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- |
| Transaction signatures | Rebuilds each transaction's signed message, and checks every signature against its signer. | Each transaction's message, accounts, and instructions are unchanged.           |
| Block hashes           | Decodes the stored block hash and previous block hash.                                     | They're well-formed Solana hashes.                                              |
| Hash chain             | Checks that each block names the previous stored block as its parent, with its hash.       | No block is missing between the stored slots. Only `ampctl verify` checks this. |
| Rows                   | Checks that the rows of a slot agree: slots, headers, messages, and transaction indexes.   | The stored rows are consistent with each other.                                 |
| Metadata               | Parses selected metadata fields, like addresses.                                           | They're valid JSON, addresses, and shapes, but not that their values are right. |

Solana skips slots that produce no block. A skipped slot isn't a gap: the next block names the last slot that produced a
block as its parent.

:::warning
Amp v0.0.42 can't verify version 1 transactions, which Solana has carried since epoch 1036, in September 2026. Both
extraction and `ampctl verify` fail on a slot that holds one, with
`unsupported transaction version 'v1' for slot 456192001, tx index 1`. Verify older slots only.
:::

### Side by side

| Guarantee                                        | EVM | Solana                    |
| ------------------------------------------------ | --- | ------------------------- |
| Header fields are correct                        | Yes | No                        |
| No transaction is missing                        | Yes | No                        |
| Transactions are in the right order              | Yes | No                        |
| Transactions are unchanged                       | Yes | Yes                       |
| Transaction results are correct                  | Yes | No                        |
| Stored rows are consistent with each other       | Yes | Yes                       |
| No block is missing between stored blocks        | No  | Yes, with `ampctl verify` |
| Blocks belong to the chain the network agreed on | No  | No                        |

### What verification doesn't prove

* **That the chain is the real one.** Verification proves that blocks are consistent with their own headers, not that
  those headers belong to the chain the network agreed on. A misconfigured or malicious provider could serve a
  consistent chain that isn't the canonical one, and it would pass.
* **That transactions ran correctly.** Amp doesn't re-run transactions, so state changes and gas calculations aren't
  checked, and neither are consensus rules, like timestamps and gas limits.
* **That Solana slots are complete.** Solana blocks carry no transaction root, so a missing transaction or a wrong order
  within a slot isn't detected.
* **Solana execution results.** Balances, fees, logs, rewards, return data, errors, and compute units aren't checked,
  beyond the shape of a few fields.
* **That Solana block hashes are right.** They're decoded and linked to their parents, but never recomputed: that
  needs data the tables don't hold.

## Verify data as Amp extracts it

Turn verification on when you deploy a raw dataset, with the encoding that matches its chain:

:::code-group
```bash [ampctl]
ampctl dataset deploy acme/eth_mainnet@1.0.0 --verify ethereum
```

```bash [Admin API]
curl -X POST http://localhost:1610/materialized-datasets/acme/eth_mainnet/versions/1.0.0 \
  -H 'Content-Type: application/json' \
  -d '{"verify": "ethereum"}'
```
:::

| Encoding   | For                                                                                                                             |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `ethereum` | EVM RPC datasets. The dataset needs its `blocks`, `transactions`, and `logs` tables.                                            |
| `solana`   | Solana datasets. The dataset needs its `block_headers`, `block_rewards`, `transactions`, `messages`, and `instructions` tables. |

Amp then verifies each block, or Solana slot, before writing it to storage. Extraction verifies each Solana slot on its
own, so it doesn't check the hash chain, which needs the parent slot: run `ampctl verify` for that.

Deploying a derived or Phaser dataset with `--verify` fails with `UNSUPPORTED_OPTION`. To turn verification on for a
dataset that's already deployed, [stop it](/managing-amp/datasets#stop-a-deployment), then deploy it again with
`--verify`. See
[Managing Datasets](/managing-amp/datasets) for the other deploy options, and
[Deploy a dataset version](/managing-amp/admin-api/materialized-datasets#deploy-a-dataset-version) for the endpoint.

### When a block fails

When a block fails verification during extraction, Amp doesn't write it, or the blocks it was extracting with it. The
extraction job stops with the error `Verification failed`, followed by the check that failed. The
[dataset's status](/managing-amp/dataset-status) shows it. Here, the provider returned block 24,000,042 with a wrong
`gas_used`:

:::terminal
```bash
ampctl dataset status acme/eth_mainnet@1.0.0
```

```
ID:              1
Status:          RUNNING
Kind:            Raw
Dataset:         acme/eth_mainnet@1.0.0
tables:
  blocks:
    files_count: 0
    total_size_bytes: 0 B
    networks:
      (none)
…
Error detail:
  error_code: MATERIALIZATION
  error_message: Materialization failed
  error_context:
    attempt_index: 1
    block_range_end: 24000099
    block_range_start: 24000000
    dataset: acme/eth_mainnet
    manifest_hash: b9e2ebd92c0752ba8a61a233f9cc5d3f4f8b601b1b789a882a5dd8ece9bca56b
    stack_trace:
      "Verification failed"
      "ethereum verification failed"
      "block hash mismatch: computed 0x05079fdc25f53c900c38eb033f96b8d8fc8bdb5d7bd44ec9c117490456921a0d, expected 0xd10b87c52a771e86cb29d655281913f74507f158eab999f337aade55e994c299"
```
:::

The job then [retries](/managing-amp/jobs/retries), like after any error it may recover from: by default, it retries
until you stop it, waiting longer after each failure.

A provider that keeps serving the same bad block keeps the job failing on it. Switch providers, either by fixing the
provider or by stopping the deployment and deploying it again with `--provider` to pin another one.

A mismatched encoding fails the same way: `--verify ethereum` on a dataset without a `blocks` table fails with
`verification enabled but 'blocks' table not found in dataset`.

## Verify data already extracted

`ampctl verify` checks a range of blocks that Amp already extracted, reading them through the query server. It also
checks the Solana hash chain, which extraction can't.

### Prerequisites

* A deployed EVM RPC or Solana raw dataset, extracted through the range you want to check. This page uses
  `acme/eth_mainnet@1.0.0`, from the [quickstart](/get-started/quickstart), which holds blocks 24,000,000 to
  24,000,099.
* Amp's [query server](/running-amp/query-server) reachable. In [solo mode](/running-amp/solo-mode), it's at
  `http://localhost:1602`.
* [`ampctl`](/clis/ampctl/verify) installed.
* To compare failed EVM blocks with what an RPC endpoint returns (optional), its URL in `RPC_URL`:

  ```bash
  export RPC_URL=…
  ```

### Run it

Give the dataset, the checks that match its chain, and the first and last block to check:

:::terminal
```bash
ampctl verify --dataset acme/eth_mainnet@1.0.0 --encoding ethereum --start-block 24000000 --end-block 24000099
```

```text
[00:00:01] [========================================] 100/100 blocks (100%)
Successfully verified blocks 24000000..=24000099
```
:::

`--encoding` chooses the checks: `ethereum` for EVM RPC datasets, and `solana` for Solana datasets. It defaults to
`ethereum`, so give `--encoding solana` for a Solana dataset. `--start-block` defaults to `0`: give the first block the
dataset holds, since EVM verification needs every block in the range (see [Run errors](/managing-amp/verification#run-errors)).

### Options

| Option                        | Type                      | Default                        | Description                                                                                                                                                  |
| ----------------------------- | ------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `--dataset <DATASET>`         | `namespace/name@revision` | `edgeandnode/ethereum_mainnet` | The dataset to verify. The revision is a version, `latest`, `dev`, or a manifest hash, like `--dataset acme/eth_mainnet@latest`. Without one, it's `latest`. |
| `--encoding <ENCODING>`       | `ethereum` or `solana`    | `ethereum`                     | The checks to run, which must match the dataset's chain, like `--encoding solana`.                                                                           |
| `--end-block <END_BLOCK>`     | integer                   | required                       | The last block, or Solana slot, to verify, included, like `--end-block 24000099`.                                                                            |
| `--start-block <START_BLOCK>` | integer                   | `0`                            | The first block to verify, like `--start-block 24000000`. Must not be after `--end-block`.                                                                   |
| `--parallelism <PARALLELISM>` | integer, at least 1       | `1`                            | How many workers read and verify blocks at once, in batches of 8,192 blocks, like `--parallelism 4`.                                                         |
| `--rpc-url <RPC_URL>`         | URL                       | none                           | An RPC endpoint to fetch each failed block from, to show how the stored block differs, like `--rpc-url $RPC_URL`. EVM datasets only.                         |
| `--amp-url <AMP_URL>`         | URL                       | `http://localhost:1602`        | The query server to read the dataset's rows from, over Arrow Flight.                                                                                         |
| `--amp-auth <AMP_AUTH>`       | string                    | none                           | A bearer token for the query server.                                                                                                                         |

### Failures

A failed block doesn't stop the run. `ampctl` prints each one above the progress bar as it finds it, checks the rest of
the range, then reports how many failed and exits with status 1. Here, three blocks were extracted from a provider
that changed one of each block's header fields, transactions, and receipts (the progress bar is left out):

:::terminal
```bash
ampctl verify --dataset acme/eth_mainnet@1.0.0 --encoding ethereum --start-block 24000000 --end-block 24000099
```

```text
  FAIL block 24000042: verify block 24000042: block hash mismatch: computed 0x05079fdc25f53c900c38eb033f96b8d8fc8bdb5d7bd44ec9c117490456921a0d, expected 0xd10b87c52a771e86cb29d655281913f74507f158eab999f337aade55e994c299
  FAIL block 24000060: verify block 24000060: transactions root mismatch: computed 0x0f46068690af906e20538fe2fd4e4e3bc8e12b4e62bc1e3a8e94c614be23c15c, expected 0xd546837d0a09f336423ef3bdcedca813fce619b776a74428a2866f93a35f0efb
  FAIL block 24000077: verify block 24000077: receipts root mismatch: computed 0x474f6dc5930261f60b91765ce2c74f690670974cafc83eb9fee8dd928fd09ef5, expected 0xd720ce1f8b7acc583c2bb7c7a0f3ca11101c3f192f8e384489b7e07f351819cc
Verified 97/100 blocks (3 failed)
✗ Verification failed for 3 block(s)
```
:::

The `FAIL` lines only show in a terminal. In a script, or with `TERM` unset or `dumb`, only the count and the exit
status say that blocks failed.

Each failure names the check that failed:

| Failure                                                              | Chain  | What's wrong                                                                                              |
| -------------------------------------------------------------------- | ------ | --------------------------------------------------------------------------------------------------------- |
| `block hash mismatch: computed …, expected …`                        | EVM    | A header field isn't what the chain recorded.                                                             |
| `transactions root mismatch: computed …, expected …`                 | EVM    | A transaction is missing, extra, out of order, or changed.                                                |
| `receipts root mismatch: computed …, expected …`                     | EVM    | A transaction's status, gas used, or logs are wrong, or a log is missing.                                 |
| `unsupported transaction type: …`                                    | EVM    | The block holds a transaction type verification can't rebuild, like an L2's deposits.                     |
| `signature verification failed for slot …, tx index …`               | Solana | A transaction's message doesn't match its signatures.                                                     |
| `block hash is invalid for slot …`                                   | Solana | The stored block hash isn't a valid Solana hash.                                                          |
| `slot … records parent slot …, but the preceding block is slot …`    | Solana | A block is missing between two stored slots.                                                              |
| `previous block hash mismatch for slot …`                            | Solana | A block's previous hash doesn't match its parent's hash: one is wrong, or they come from different forks. |
| `slot … records parent slot …, which the dataset holds no block for` | Solana | A block is missing just before the range, though the dataset holds earlier ones.                          |

Other Solana failures report rows that don't agree, like `missing message for slot …, tx index …`.

#### Compare with an RPC endpoint

For an EVM dataset, add `--rpc-url` to fetch each failed block from an RPC endpoint and compare it with the stored one:

```bash
ampctl verify --dataset acme/eth_mainnet@1.0.0 --start-block 24000040 --end-block 24000049 --rpc-url $RPC_URL
```

The failure then lists each field where the two differ, as `path: left=stored value, right=RPC value`, with
`<missing>` for a field one side lacks. If the RPC endpoint's block fails verification too, the failure says
`RPC verification also failed`, followed by why: the endpoint serves the same bad data, or the block holds something
verification can't check. With `--encoding solana`, `--rpc-url` fails with
`--rpc-url fallback is only supported with --encoding ethereum`.

#### Run errors

Some errors stop the run rather than failing a block. It then ends with `Verification worker(s) failed` and
`✗ verification worker task(s) failed`, after a line starting with `Worker error:` that says why, which shows only in
a terminal. The most common one is a block missing from the dataset: EVM verification needs every block in the range, so
`dataset missing block 100001` means the dataset hasn't extracted that far. Lower `--end-block`, or wait for extraction
to catch up.

When reading from the query server fails, or a batch takes more than 30 minutes, `ampctl` retries it, waiting up to 10
seconds between attempts and printing `Ethereum fetch failed (retry in …)` or `Solana fetch failed (retry in …)`. It
keeps retrying until the read succeeds, so stop it with Ctrl+C if the query server is down.

## Limitations

* Verification can't prove that blocks belong to the chain the network agreed on. Use providers you trust.
* L2 deposit transactions, on Arbitrum and OP Stack chains, aren't supported: blocks holding them fail with
  `unsupported transaction type`.
* Solana verification assumes the stored order of each transaction's top-level instructions is the original order.
  The query server doesn't always return them in that order, so `ampctl verify` fails a correct block with
  `signature verification failed for slot …, tx index …` about one run in ten. Run it again before you trust a Solana
  failure.
* Amp v0.0.42 can't verify Solana's version 1 transactions.
* Solana's hash chain only proves that stored slots link to each other, not that the hashes are the ones Solana
  computed: a source reporting a consistent but wrong chain passes. Extraction doesn't check it.
* The Solana hash chain proves nothing is missing between the first and last slot stored in the range. Slots missing
  after the last stored one, or a range that stores no slots, pass.
* `ampctl verify` can only compare failed blocks with an RPC endpoint for EVM datasets.
