Skip to content

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

← Back to Fork Changelog