> ## Documentation Index
> Fetch the complete documentation index at: https://seilabs-docs-bridge-release-v6-6-0.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Giga SS Store Migration Guide

> Migrate a Sei RPC node to Giga SS Store: split EVM state into a dedicated state-store backend so non-EVM modules stop paying EVM write amplification.

Giga SS Store is the next step in Sei's storage evolution on top of [SeiDB](/node/node-operators#architecture).
It splits the hot EVM state into its own dedicated state-store (SS) database
so the node can scale toward the **\~150k TPS** target throughput, and so
non-EVM modules stop paying write amplification for EVM state.

After migration the SS layer is repartitioned into two cooperating stores:

| Layer                                    | Cosmos backend                       | EVM backend                                      |
| ---------------------------------------- | ------------------------------------ | ------------------------------------------------ |
| **SC** (State Commit, app hash)          | `memiavl`                            | FlatKV                                           |
| **SS** (State Store, historical queries) | single MVCC DB (PebbleDB or RocksDB) | dedicated EVM SS MVCC DB(s) under `data/evm_ss/` |

Only the **SS** layer changes for this migration. SC layer config is untouched
and `memiavl` remains the authoritative source for the app hash, so this is
invisible to the network.

<Note>
  The SC-layer routing field `sc-write-mode` is emitted in the generated
  `app.toml` template under the `[state-store]` section. It defaults to
  `memiavl_only`, so leaving it at the default keeps the SC layer untouched for
  this migration.

  * `sc-write-mode` — write routing mode for EVM data in the SC layer. Valid
    values: `memiavl_only`, `migrate_evm`, `evm_migrated`, `migrate_all_but_bank`,
    `all_migrated_but_bank`, `migrate_bank`, `flatkv_only`, `test_only_dual_write`.
    An invalid value fails at config parse time with a clear error.
  * `sc-keys-to-migrate-per-block` — the number of keys migrated from `memiavl` to
    `flatKV` per block while in a migration mode. Defaults to `1024` and must be
    greater than `0`; ignored outside of a migration mode.

  The legacy `sc-read-mode` and `sc-enable-lattice-hash` fields have been
  **removed**. Read routing and lattice-hash participation are now derived
  automatically from the write mode and the on-disk migration state — an
  `app.toml` that still references either field is no longer valid.
</Note>

<Note>
  The `sc-write-mode` / `sc-read-mode` fields above configure EVM routing in the
  legacy composite commit store and are **distinct** from the internal
  `memiavl → flatKV` **migration state machine** used when converting the SC
  layer's state DB from `memiavl` to `flatKV`. That migration is driven by its
  own `WriteMode` enum inside the state-migration package, not by
  `sc-write-mode`. Node operators encounter these modes as the migration
  progresses through a linear sequence of on-disk **migration versions**:

  | WriteMode                                      | Migration version | Behavior                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
  | ---------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | `MemiavlOnly` (`memiavl_only`)                 | 0                 | Pre-migration baseline: every module routes to `memiavl`; `flatKV` is not in the data path (bootstrap passes `nil` for `flatKV`).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
  | `MigrateEVM` (`migrate_evm`)                   | 0 → 1             | Migrates the `evm/` module from `memiavl` to `flatKV` in batches. Reads for un-migrated keys are served from `memiavl` first and fall back to `flatKV` for brand-new keys created after the migration started; brand-new writes are routed to `flatKV` so the migration boundary can reach completion instead of chasing an ever-growing key tail. Iteration is forwarded to the `memiavl` iterator while the migration is `NotStarted`/`InProgress` (with the caveat that already-migrated keys are no longer visible there, so results may be incomplete mid-flight) and is refused once the migration is `Complete`. |
  | `EVMMigrated` (`evm_migrated`)                 | 1                 | Steady state: `evm/` lives in `flatKV`, every other module in `memiavl`; no migration manager in the path.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
  | `MigrateAllButBank` (`migrate_all_but_bank`)   | 1 → 2             | Migrates every module except `bank/` (and the already-migrated `evm/`) from `memiavl` to `flatKV`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
  | `AllMigratedButBank` (`all_migrated_but_bank`) | 2                 | Steady state: everything except `bank/` lives in `flatKV`; `bank/` remains in `memiavl`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
  | `MigrateBank` (`migrate_bank`)                 | 2 → 3             | Migrates the final `bank/` module from `memiavl` to `flatKV`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
  | `FlatKVOnly` (`flatkv_only`)                   | 3                 | Terminal state: every module routes to `flatKV`; `memiavl` is not in the data path (bootstrap passes `nil` for `memiavl`). A node can be booted **directly** into this steady state by setting `sc-write-mode = "flatkv_only"` in `app.toml` — `memiavl` is never allocated. Snapshot export and state-sync restore work correctly in this mode: the State Store is fully repopulated and the restored AppHash matches the snapshot source (the FlatKV exporter now emits its own module header, and empty-value writes are preserved rather than dropped on WAL replay).                                               |
  | `TestOnlyDualWrite` (`test_only_dual_write`)   | —                 | Test-only mode that dual-writes `evm/` traffic to both `memiavl` and `flatKV` while routing all other modules to `memiavl`; reads, proofs, and iteration are served exclusively by `memiavl`.                                                                                                                                                                                                                                                                                                                                                                                                                           |

  <Warning>
    `TestOnlyDualWrite` exists only to preserve parity with legacy
    composite-store dual-write tests and **must never be deployed to production
    machines**.
  </Warning>

  The appropriate router for a given `WriteMode` is constructed by the
  state-migration package's `BuildRouter` entrypoint, which returns the
  steady-state or in-flight migration router for that mode. Router construction
  requires a non-`nil` `memiavl` handle for every mode except `FlatKVOnly`, and a
  non-`nil` `flatKV` handle for every mode except `MemiavlOnly`.
</Note>

## Offline FlatKV EVM import (MigrateEVM)

The `seidb` tool ships an offline import path that moves the `evm/` module's
SC-layer data out of `memiavl` and into FlatKV without running the in-process
migration state machine. This is the operational entrypoint for the
`MigrateEVM` (V0 → V1) transition on a stopped node.

<Warning>
  This is an **offline** operation. The node must be fully stopped before
  running the import — the tool opens both the `memiavl` and FlatKV directories
  directly and will conflict with a running `seid`.
</Warning>

### Reading the latest memiavl version

Before importing, read the latest committed `memiavl` version from a stopped
node's data directory. In a multi-validator cluster, run this on every node
and pick the minimum so the import height is uniform across the cluster:

```bash copy theme={"dark"}
seidb memiavl-latest-version --data-dir <data_dir>
```

`--data-dir` may point at either the `data/` directory or the Sei home
directory; if the basename is `data`, its parent is treated as home. You can
also pass `--home <sei_home>` instead.

### Running the import

```bash copy theme={"dark"}
seidb import-flatkv-from-memiavl \
  --modules=evm \
  --data-dir <data_dir> \
  --height <h> \
  [--force]
```

| Flag         | Meaning                                                                                                              |
| ------------ | -------------------------------------------------------------------------------------------------------------------- |
| `--home`     | Sei home directory. Defaults to `$HOME/.sei`.                                                                        |
| `--data-dir` | Sei data directory or home directory. If the basename is `data`, its parent is used as home.                         |
| `--modules`  | Comma-separated module names to import. Initial production scope is **evm-only**; any other module name is rejected. |
| `--height`   | The `memiavl` version to import. `0` means latest.                                                                   |
| `--force`    | Overwrite existing committed FlatKV data.                                                                            |

The import resets FlatKV before loading the selected `memiavl` rows and
**refuses to overwrite committed FlatKV data unless `--force` is supplied**. If
an external error interrupts the import (context cancellation, exporter or
translator failure), the import is aborted rather than finalized — FlatKV is
left at its pre-import committed version, so the operation can be retried
without `--force`.

<Warning>
  The import **must run at the memiavl latest version**. Importing FlatKV at a
  height `H` lower than the latest `memiavl` version is rejected: on the next
  `GIGA_STORAGE` startup the composite commit store's `reconcileVersions` step
  would silently roll `memiavl` back to `H`, truncating every cosmos block in
  `(H, latest]`. If you genuinely need a non-latest height, roll `memiavl` back
  to that height yourself first (for example with `seid rollback`), then re-run
  the import. The CLI deliberately does **not** roll `memiavl` back on your
  behalf. A height ahead of the latest `memiavl` version is likewise rejected.
</Warning>

### Configuration constraints across the import boundary

The import moves only the SC-layer EVM data into FlatKV. When restarting the
node after the import, two settings **must stay off** across the import
boundary or the node will panic on startup:

* **`evm-ss-split = false`.** SS history for EVM remains in the existing
  combined cosmos SS database; the import does not populate a separate EVM SS
  directory. Flipping `evm-ss-split` to `true` triggers the rootmulti startup
  panic *"EVM SS directory ... does not exist but Cosmos SS already has
  history"*. Moving the SS layer to split mode is a separate state-sync
  workflow (see [Step 2](#step-2%3A-state-sync-into-the-new-layout)) and is out
  of scope for the offline SC import.
* **`sc-enable-lattice-hash = false`.** Before the import the chain ran without
  FlatKV, so tendermint persisted app hashes computed from `memiavl` alone for
  every block up to the import height. Enabling the lattice hash now would fold
  the FlatKV LtHash into the app hash, and the replay check at startup would
  fail with *"state.AppHash does not match AppHash after replay"*. Note that
  `dual_write` does not require the lattice hash — only `split_write` does.

A production rollout coordinates these transitions via a chain upgrade at an
agreed height rather than flipping them mid-life.

<Info>
  For the full operational failure-mode catalog and recovery/tooling roadmap for
  MigrateEVM, see
  [`sei-db/state_db/sc/migration/OPERATIONS.md`](https://github.com/sei-protocol/sei-chain/blob/main/sei-db/state_db/sc/migration/OPERATIONS.md)
  in `sei-chain`.
</Info>

<Info>This guide tracks the canonical procedure in [`docs/migration/giga_store_migration.md`](https://github.com/sei-protocol/sei-chain/blob/main/docs/migration/giga_store_migration.md) inside `sei-chain`. Open an issue there if anything here drifts.</Info>

## Prerequisites

<Warning>This migration is supported on **RPC nodes only**. Validator nodes and archive nodes are not supported by this flow yet — do not run it against either.</Warning>

* A `seid` build with the `evm-ss-split` flag wired in (Sei v6.5 or later). Older
  releases used per-key `evm-ss-write-mode` / `evm-ss-read-mode` toggles; if your
  `app.toml` still has those keys, upgrade `seid` before continuing.
* `sc-enable = true` and `ss-enable = true` in `app.toml`. Both must stay enabled.
* A trusted RPC endpoint to state-sync from (chain ID and trust-height source).
* Disk headroom for two SS databases. The EVM split does not duplicate data, but
  during migration both the old and the new layouts may briefly coexist on disk.

The migration **requires a full state sync**. There is no in-place migration
path and no live "dual-write then split" workflow — the state sync wipes the
local data directory and imports a fresh snapshot into the new layout.

## Benefits

* EVM reads are served exclusively from a dedicated EVM SS database.
* Non-EVM modules no longer pay write amplification for EVM state.
* A backend change (PebbleDB ↔ RocksDB) can be combined with the same state
  sync, since `ss-backend` drives both the Cosmos SS MVCC DB and every EVM SS
  sub-DB.

## What's different about EVM SS

EVM SS is **point-query only by design** (`Get` / `Has`). Iteration is
explicitly disabled on the EVM backend for performance: the hot EVM read path
is tuned for direct key lookups, and cross-bucket scans would defeat the
per-type sub-DB layout. Any EVM read that needs iteration must stay on the
Cosmos SS side.

## Migration Steps

### Step 1: Update `app.toml`

Apply the following settings in `~/.sei/config/app.toml`:

```toml copy theme={"dark"}
[state-commit]
# State commit is untouched by this migration.
sc-enable = true

[state-store]
ss-enable = true

# DBBackend for the Cosmos SS MVCC DB and for every EVM SS sub-DB.
# Supported: pebbledb, rocksdb. Default pebbledb.
ss-backend = "pebbledb"

# Route EVM state to the dedicated EVM SS backend.
# When false (default), EVM state lives in the Cosmos SS backend alongside
# everything else. When true, EVM data is routed exclusively to the EVM SS
# backend; non-EVM data stays in Cosmos SS. No fallback between backends.
evm-ss-split = true
```

If you want to switch SS backend in the same step:

* **PebbleDB → RocksDB**: set `ss-backend = "rocksdb"`, build `seid` with
  `-tags rocksdbBackend`, and install RocksDB per the
  [RocksDB Backend Guide](/node/rocksdb-backend). `ss-backend` drives both the
  Cosmos SS MVCC DB and every EVM SS sub-DB, so a single setting flips both.
* No data migration tool is needed across backends — the state sync populates
  the new layout.

### Step 2: State sync into the new layout

Giga SS Store is fully compatible with the existing state-snapshot format. On
import, the composite state store routes each snapshot node based on the
importing node's `evm-ss-split`:

* With `evm-ss-split = true`, EVM snapshot nodes go only into EVM SS and
  non-EVM nodes go only into Cosmos SS.
* The import path normalizes legacy `evm_flatkv` snapshot nodes to `evm`, so
  snapshots produced by either the old or new FlatKV module are accepted.

Both stores end up fully populated at the snapshot height, so the node can
start serving reads immediately.

The full state-sync flow is documented in the
[Statesync guide](/node/statesync). The minimal shape for this migration:

```bash copy theme={"dark"}
export TRUST_HEIGHT_DELTA=10000
export MONIKER="<moniker>"
export CHAIN_ID="<chain_id>"
export PRIMARY_ENDPOINT="<rpc_endpoint>"
export SEID_HOME="$HOME/.sei"

# 1. Stop seid
sudo systemctl stop seid

# 2. Back up files you need to preserve and wipe local state
cp $SEID_HOME/data/priv_validator_state.json /tmp/priv_validator_state.json
cp $SEID_HOME/config/priv_validator_key.json   /tmp/priv_validator_key.json
cp $SEID_HOME/config/genesis.json              /tmp/genesis.json
rm -rf $SEID_HOME/data/*
rm -rf $SEID_HOME/wasm
rm -rf $SEID_HOME/config/priv_validator_key.json
rm -rf $SEID_HOME/config/genesis.json
rm -rf $SEID_HOME/config/config.toml

# 3. Re-init, re-apply config.toml and app.toml (set Step 1 values again)
seid init --chain-id "$CHAIN_ID" "$MONIKER"

# 4. Resolve trust height/hash and persistent peers against PRIMARY_ENDPOINT,
#    then update config.toml. See /node/statesync for the full snippet.

# 5. Restore the backed-up files
cp /tmp/priv_validator_state.json $SEID_HOME/data/priv_validator_state.json
cp /tmp/priv_validator_key.json   $SEID_HOME/config/priv_validator_key.json
cp /tmp/genesis.json              $SEID_HOME/config/genesis.json

# 6. Start seid
sudo systemctl restart seid
```

<Warning>Make sure `priv_validator_key.json` is in safe storage before deleting it from the config directory. Loss of this key is unrecoverable for a validator and is not relevant to RPC-only nodes — but if you're following this from the wrong checklist you'll find out the hard way.</Warning>

### Step 3: Verify the new layout

Once the state sync completes and the node starts producing blocks, confirm
Giga SS Store is active in two places.

**Startup logs.** All three lines should appear:

```text theme={"dark"}
"SeiDB SS is enabled"                       # with the configured `backend`
"SeiDB EVM StateStore optimization is enabled"  # with the `separateDBs` label
"EVM state store enabled"                   # with `dir` and `separateDBs` labels
```

**EVM RPC.** `debug_traceBlockByNumber` is the cleanest end-to-end check —
it forces the node to read EVM state out of the new EVM SS backend:

```bash copy theme={"dark"}
curl -s -X POST http://127.0.0.1:8545 \
  -H 'Content-Type: application/json' \
  --data '{"jsonrpc":"2.0","method":"debug_traceBlockByNumber","params":["latest",{}],"id":1}'
```

The response should contain a `"result"` field rather than an RPC error.

## Safety checks

`seid` runs three DB-state checks at startup and refuses to launch if the EVM
SS and Cosmos SS DBs are inconsistent. They specifically catch the footgun of
flipping `evm-ss-split` from `false` to `true` without state syncing.

1. **EVM SS directory missing or empty** (before the EVM SS is opened). When
   `evm-ss-split = true`, the composite state store refuses to proceed if
   Cosmos SS already has committed history but the EVM SS directory
   (`data/evm_ss/` by default) does not exist or is empty. Failing before
   the sub-DBs are opened means a rejected config does not leave a confusing
   empty `data/evm_ss/` behind.
2. **EVM SS DB empty post-open, pre-recovery.** Belt-and-suspenders for (1)
   when the directory exists but its DBs are empty. The WAL only covers the
   last `KeepRecent` blocks, so replay cannot rebuild a fresh EVM SS from
   scratch.
3. **Mismatched earliest versions, post-recovery.** If the two DBs were
   populated from different snapshots (or pruned independently), historical
   reads would be inconsistent. A non-zero earliest-version divergence
   aborts startup.

If any check fires, the correct fix is either (a) complete the state sync
described above, or (b) set `evm-ss-split = false` and restart. If
`data/evm_ss/` is stale from a failed attempt, remove it before state syncing.

## Receipt backend default

When Giga Storage is enabled (`GIGA_STORAGE=true`), the receipt backend now
defaults to `pebble`. Previously the receipt backend was left unchanged and
had to be set explicitly through the `RECEIPT_BACKEND` environment variable.

This default is applied implicitly: enabling Giga Storage sets
`RECEIPT_BACKEND=pebble` unless you have already provided an explicit value.
To use a different receipt backend, set `RECEIPT_BACKEND` explicitly before
starting the node — an explicit value always takes precedence over the
pebble default.

```bash copy theme={"dark"}
# Giga Storage on, receipt backend defaults to pebble
export GIGA_STORAGE=true

# Override the pebble default with an explicit backend
export GIGA_STORAGE=true
export RECEIPT_BACKEND=<backend>
```

## Rollback

To roll back:

1. Set `evm-ss-split = false` in `app.toml`.
2. Restart the node. The EVM SS DB under `data/evm_ss/` is no longer opened
   but stays on disk until manually removed.

To fully reclaim the disk used by EVM SS, stop the node and delete
`data/evm_ss/` after reverting the setting.

<Warning>Cleanly rolling back to `evm-ss-split = false` requires another state sync. Under `evm-ss-split = true`, EVM writes go only to the EVM SS DB, so Cosmos SS will not have those writes. Restarting with `evm-ss-split = false` stops opening the EVM SS DB, but EVM-state queries will miss anything written after the Giga state sync until you re-state-sync without the split.</Warning>

## FAQ

### Where do the data files live after migrating?

New nodes use an organized subdirectory layout under `data/`. Existing nodes
with data in the legacy flat layout keep using their legacy paths automatically
(legacy takes precedence when present).

* Cosmos SS data lives under `data/state_store/cosmos/{backend}` on new nodes
  (e.g. `data/state_store/cosmos/pebbledb/` for the default `pebbledb` backend).
  Existing nodes with data under `data/{backend}` (e.g. `data/pebbledb/`)
  continue using that legacy path.
* EVM SS data lives under `data/state_store/evm/{backend}` on new nodes
  (e.g. `data/state_store/evm/pebbledb/`). Existing nodes with data under
  `data/evm_ss/` continue using that legacy path. The `evm-db-directory`
  config, when unset, defaults to `data/state_store/evm/{backend}`.
* SC data (`memiavl` + FlatKV) is untouched by this migration; on new nodes it
  lives under `data/state_commit/memiavl` and `data/state_commit/flatkv`, with
  legacy `data/committer.db` and `data/flatkv` retained when present.

### Does Giga SS Store change the app hash or consensus?

No. The SC layer is unchanged, so `memiavl` remains the authoritative source
for the app hash. Giga SS Store is a per-node SS change that is invisible to
the network.

### Can I migrate a validator node with this guide?

Not yet. This migration guide is for RPC nodes only.

### Can I migrate an archive node with this guide?

Not yet. Archive-node migration is out of scope for this guide.

### Can I toggle back to `evm-ss-split = false` after enabling it?

Yes, but cleanly rolling back requires another state sync — see the
[Rollback](#rollback) section above.

### Why can't I just flip `evm-ss-split = true` on a running node?

Because `evm-ss-split = true` requires the EVM SS DB to already contain the
full history that Cosmos SS has. A live flip would leave the EVM SS DB empty
while the composite store refuses to fall back to Cosmos SS, which would
translate into missing EVM state at query time. The safety checks above
block this scenario at startup.

### Does Giga SS Store support historical proofs?

No, same as SeiDB. SS stores raw KVs and does not reconstruct IAVL-style
proofs.
