Summary
gravity_node db migrate-changesets refuses to run on any stock datadir: the pruned-database preflight requires the earliest AccountChangeSets entry to be at block 1, but genesis initialization writes the genesis-alloc reverts at block 0 on every chain. The block-0 genesis entries are misclassified as evidence of pruning and the command exits 1 in under a second:
Error: changeset history is pruned; migrating a pruned database is not yet supported
As it stands, no existing (non-pruned, fully archival) datadir can be migrated to the static-file changeset layout.
Root cause
Two pieces, both verified against source and live data:
-
Preflight check — crates/cli/commands/src/db/migrate_changesets.rs:76-85 (introduced in 85521fc276ec3ae4db518a582be6fbb542521055 feat(cli): add reth db migrate-changesets command; unchanged by the crash-window fix 162bc04673201d18e66bc21ef832e3814bd6d639):
// Pruned nodes whose earliest changeset is above block 1 are not supported: ...
eyre::ensure!(
provider
.tx_ref()
.cursor_read::<tables::AccountChangeSets>()?
.first()?
.is_none_or(|(block, _)| block == 1),
"changeset history is pruned; migrating a pruned database is not yet supported"
);
The comment says "earliest changeset is above block 1", but the code rejects anything != 1 — so block 0 is caught by accident.
-
Genesis writes changesets at block 0 — insert_genesis_state (crates/storage/db-common/src/init.rs:263-281) inserts a revert entry for every alloc account keyed by the genesis block number:
reverts_init.insert(*address, (Some(None), ...));
...
let all_reverts_init: RevertsInit = HashMap::from_iter([(block, reverts_init)]); // block = 0
So every stock chain has AccountChangeSets/StorageChangeSets entries at block 0 by construction, and the preflight can never pass.
Reproduction (deterministic, <1 s, any stock datadir)
Stop a node, then:
gravity_node db --datadir <node>/data/reth \
--chain <base>/genesis.json \
--datadir.static-files <node>/data/reth \
migrate-changesets
→ exit 1, changeset history is pruned; migrating a pruned database is not yet supported.
Evidence
Hit on the first-ever live execution of the command in our storage-v2 e2e (storage_v2_upgrade case in gravity-sdk), against a datadir produced by the full acceptance path: v1.7.5 chain with real history → rolling upgrade to gravity-reth-merge-v2.3.0 → Alpha forward activation → migrate. Probed on two independent nodes:
db get mdbx AccountChangeSets 0 <alloc-address> → entry present (info: null, i.e. the genesis-alloc revert shape);
- blocks 1 / 2 / 5 / 100 entries all present — the history is contiguous from genesis;
- no prune segments configured anywhere (
reth.toml clean);
- pre-migration table counts on the probed node:
AccountChangeSets 15334 / StorageChangeSets 27896.
By construction from init.rs this should equally affect a fresh v2.3.0 datadir (not separately exercised).
Likely why crate-level tests didn't catch it: test fixtures that seed changesets from block 1 without going through init_genesis never produce the block-0 entries.
Suggested fix direction
- Preflight: accept earliest block ∈ {0, 1} (or explicitly detect "gap above genesis" instead of
!= 1).
- Decide how block 0 is represented in the static-file segments: the SF-fresh init path already anchors an empty block 0 (
init.rs:182 comment "no changesets, so an empty block 0 anchors the append chain"), whereas legacy datadirs carry real revert entries at block 0 — the migration writer needs a defined answer (fold genesis reverts into the block-0 segment entry vs. treat block 0 as the empty anchor and start data at 1). That's a product decision; either way the preflight message should stop calling healthy databases pruned.
Environment
- Branch
gravity-reth-merge-v2.3.0, HEAD 8d4fa2a915e65bc19f6b022172f371d1367ee278 (built via gravity-sdk local [patch] integration, quick-release profile)
- RocksDB backend, Debian 12 host
Related minor tooling observation (can split into its own issue)
While diagnosing: db list <dupsort-table> --json/--raw panics decoding composite RocksDB keys (db_tool/mod.rs:67 area), and bare db list requires a TTY. db list <table> --count is unaffected.
Summary
gravity_node db migrate-changesetsrefuses to run on any stock datadir: the pruned-database preflight requires the earliestAccountChangeSetsentry to be at block 1, but genesis initialization writes the genesis-alloc reverts at block 0 on every chain. The block-0 genesis entries are misclassified as evidence of pruning and the command exits 1 in under a second:As it stands, no existing (non-pruned, fully archival) datadir can be migrated to the static-file changeset layout.
Root cause
Two pieces, both verified against source and live data:
Preflight check —
crates/cli/commands/src/db/migrate_changesets.rs:76-85(introduced in85521fc276ec3ae4db518a582be6fbb542521055feat(cli): add reth db migrate-changesets command; unchanged by the crash-window fix162bc04673201d18e66bc21ef832e3814bd6d639):The comment says "earliest changeset is above block 1", but the code rejects anything
!= 1— so block 0 is caught by accident.Genesis writes changesets at block 0 —
insert_genesis_state(crates/storage/db-common/src/init.rs:263-281) inserts a revert entry for every alloc account keyed by the genesis block number:So every stock chain has
AccountChangeSets/StorageChangeSetsentries at block 0 by construction, and the preflight can never pass.Reproduction (deterministic, <1 s, any stock datadir)
Stop a node, then:
→ exit 1,
changeset history is pruned; migrating a pruned database is not yet supported.Evidence
Hit on the first-ever live execution of the command in our storage-v2 e2e (
storage_v2_upgradecase in gravity-sdk), against a datadir produced by the full acceptance path: v1.7.5 chain with real history → rolling upgrade togravity-reth-merge-v2.3.0→ Alpha forward activation → migrate. Probed on two independent nodes:db get mdbx AccountChangeSets 0 <alloc-address>→ entry present (info: null, i.e. the genesis-alloc revert shape);reth.tomlclean);AccountChangeSets15334 /StorageChangeSets27896.By construction from
init.rsthis should equally affect a fresh v2.3.0 datadir (not separately exercised).Likely why crate-level tests didn't catch it: test fixtures that seed changesets from block 1 without going through
init_genesisnever produce the block-0 entries.Suggested fix direction
!= 1).init.rs:182comment "no changesets, so an empty block 0 anchors the append chain"), whereas legacy datadirs carry real revert entries at block 0 — the migration writer needs a defined answer (fold genesis reverts into the block-0 segment entry vs. treat block 0 as the empty anchor and start data at 1). That's a product decision; either way the preflight message should stop calling healthy databases pruned.Environment
gravity-reth-merge-v2.3.0, HEAD8d4fa2a915e65bc19f6b022172f371d1367ee278(built via gravity-sdk local[patch]integration, quick-release profile)Related minor tooling observation (can split into its own issue)
While diagnosing:
db list <dupsort-table> --json/--rawpanics decoding composite RocksDB keys (db_tool/mod.rs:67area), and baredb listrequires a TTY.db list <table> --countis unaffected.