Migrating from core-geth¶
core-geth is etclabscore's Ethereum Classic client — a parallel go-ethereum fork with its own configuration surface and on-disk conventions. A datadir created by core-geth with its default settings can be adopted directly by this client — no resync required.
| Metric | Value |
|---|---|
Resync needed (default hashdb datadir) |
No |
Resync needed (--state.scheme=path datadir) |
Yes — see State scheme below |
| Dedicated files | 1 |
Freezer / ancient database¶
The on-disk freezer (ancient store) layout is identical to core-geth's: every chain
freezer table — headers, bodies, receipts, hashes — uses the same compression settings,
including the diffs (total-difficulty) table, which both clients store
uncompressed (noSnappy: true in core/rawdb/ancient_scheme.go). None of this
format is fork-specific, so a core-geth ancient store opens as-is, with no conversion step.
Chain config migration¶
core-geth persists the chain config under the same database key as go-ethereum
(ethereum-config-<genesisHash>), but serializes it under different JSON field names —
including the standard Ethereum fork fields (eip2FBlock vs. this client's
homesteadBlock, eip161FBlock vs. eip158Block, …) and the ETC-specific ones
(ecip1017FBlock vs. ecip1017Block, ecip1099FBlock vs. ecip1099Block, …).
Because encoding/json silently drops unrecognized fields, reading a core-geth config
with this client's struct leaves HomesteadBlock — and every ETC-specific field — nil.
Left alone, that partially-parsed config would fail ChainConfig.CheckCompatible against
the hardcoded genesis and force a rewind to the Homestead block (1,150,000 on Classic),
which in practice means a near-full resync.
core/coregeth_migration.go closes that gap with a one-shot, idempotent, on-disk
migration that runs on every startup from eth/backend.go, right after the chain
database is opened but before genesis/chain-config setup:
node starts → eth/backend.go New()
→ core.MigrateCoreGethChainConfig(chainDb) [core/coregeth_migration.go]
genesisHash = ReadCanonicalHash(db, 0)
→ empty datadir (no genesis yet) → no-op; SetupGenesisBlock writes a fresh config later
canonical = canonicalETCChainConfig(genesisHash) // Classic or Mordor only
→ not a known ETC genesis (ETH, private chain) → no-op; never touch a config we don't understand
stored = ReadChainConfig(db, genesisHash)
→ no stored config yet → no-op; nothing to migrate
→ stored.HomesteadBlock != nil → no-op; already this client's format
→ else: WriteChainConfig(db, genesisHash, canonical) // rewrite in place + log Warn
A nil HomesteadBlock is the unambiguous trigger: a config natively written by this
client for a known ETC network always has it set (1,150,000 on Classic, 0 on Mordor), so
its absence can only mean an unmigrated core-geth config. Once the rewrite happens, the
datadir is indistinguishable from one this client created itself and the migration is a
permanent no-op for it from then on. Because it mutates the persisted config rather than
translating it on every read, the migration — and its single call site in
eth/backend.go — can be deleted outright once core-geth support is no longer needed.
State scheme¶
core-geth defaults to the hash-based state scheme (hashdb), which this client reads
natively — the on-disk scheme is auto-detected and reused, with no conversion step.
--state.scheme=path datadirs are not compatible
A core-geth datadir created with --state.scheme=path (not core-geth's default) uses
core-geth's pathdb format, which predates and differs from this client's. That
datadir is not readable by this client and requires a full resync. Only default
(hashdb) core-geth datadirs are adopted as-is.
Genesis-hash disambiguation¶
Ethereum Classic and Ethereum mainnet share the same genesis hash, so opening either
datadir without specifying the network could otherwise be misread as the other. Run a
Classic node with --classic (or --mordor for the testnet).
As a safety net for embedded use — where the network flag might be missed — a stored
Classic or Mordor config on disk is honoured over the shared mainnet genesis hash:
Genesis.chainConfigOrDefault checks stored.IsClassic() before falling back to
hash-based network resolution (core/genesis.go), so a datadir that already carries a
Classic config keeps resolving to Classic even without the flag.
Dedicated Files¶
| File | Description | Changed |
|---|---|---|
core/coregeth_migration.go · diff |
One-shot on-disk migration — rewrites a core-geth chain config into this client's canonical format on first startup | +101 -0 |