Files
genesis/LLM.md
T
Hanzo AI 4383ad4583 genesis: unify mnemonic env to LUX_MNEMONIC + kill vesting docstring rot
One env, one way. The genesis mnemonic is now read from exactly one
env var: LUX_MNEMONIC. The prior two-env fallback chain
(MNEMONIC > LIGHT_MNEMONIC) is gone. Per-env isolation is by a
DIFFERENT mnemonic per env (loaded from KMS), not by env-var name.

pkg/genesis/keys.go:
- New MnemonicEnvVar = "LUX_MNEMONIC" constant.
- getMnemonicEnv() reads only LUX_MNEMONIC; no fallback.
- LightMnemonic constant kept (it's the well-known dev seed VALUE;
  pass it as the value of LUX_MNEMONIC for local nets).
- IsProductionNetwork docstring updated to spell out the canonical 4:
  mainnet(1) / testnet(2) / devnet(3) refuse public mnemonics;
  local(1337)+ allows LightMnemonic.
- Docstring rot purged: the prior "50M free + 50M vesting at 1%/year
  over 100y" claim was a lie — buildConfigFromKeyInfos never wired the
  100-period schedule (the code admits the schedule "overflows zapdb
  batch limit"). Replaced with the truth: 1000 × 50M LUX on X-Chain
  AND P-Chain, immediate spend; C-Chain is treasury-only (2T LUX).
- Dead code deleted: StakingStartTime, UnlockInterval consts,
  buildUnlockSchedule() function.

pkg/genesis/allocations.go:
- VestingConfig struct removed.
- DefaultVesting() removed.
- WithVesting() builder method removed.
- Vesting branches in PChain() / PChainMap() collapsed to immediate-only.
- MainnetAllocations no longer calls WithVesting(DefaultVesting()).

pkg/genesis/validator_keys.go:
- VestingPeriods const removed.
- GeneratePChainAllocationsWithVesting() removed.

pkg/genesis/networks.go (new):
- Canonical primary-network-id table: MainnetID / TestnetID / DevnetID
  / LocalID = 1 / 2 / 3 / 1337. Matching C-Chain (EVM) chain IDs:
  96369 / 96368 / 96370 / 31337.
- IsCanonicalPrimaryNetwork helper.

cmd/checkkeys/main.go:
- Reads LUX_MNEMONIC via genesis.MnemonicEnvVar (no fallback).

cmd/derive100/main.go:
- Usage hint updated: mnemonic-env: LUX_MNEMONIC.

cmd/genesis/main.go:
- Flag help, env-var docs, examples, and error messages all reference
  LUX_MNEMONIC.

pkg/genesis/light_mnemonic_guard_test.go:
- All t.Setenv calls use LUX_MNEMONIC.
- Test labels reference LightMnemonic (the constant) not the dead env name.

configs/getgenesis_test.go:
- Comment updated: LightMnemonic instead of LIGHT_MNEMONIC.

CHANGELOG.md + LLM.md:
- Documented the unification + the dead-code removal.

The validator stake-lock (3-entry 5y/10y/20y schedule attached to the
validator's UTXO inside buildConfigFromKeyInfos at keys.go:1077-1083)
is unchanged — that locked-stake bucket is the only UnlockSchedule in
the canonical genesis path and the ProtocolVM needs it.

External-caller sweep: grepped ~/work/lux, ~/work/hanzo, ~/work/zoo,
~/work/luxfi for genesis.VestingConfig / genesis.DefaultVesting /
genesis.GeneratePChainAllocationsWithVesting / genesis.StakingStartTime
/ genesis.UnlockInterval — zero hits. Safe to delete. (luxfi/keys at
~/work/lux/keys has its own MainnetAllocations copy — separate repo,
not affected.)

Verification:
- go build ./... → clean
- go vet ./... → clean
- go test ./... -count=1 → ok configs, ok pkg/genesis
- grep '[^_X]MNEMONIC\|LIGHT_MNEMONIC' under pkg/genesis cmd configs →
  zero matches (only CHANGELOG history + Python script local vars).
- grep VestingConfig|buildUnlockSchedule|GeneratePChainAllocationsWithVesting
  under source → zero matches.
2026-06-02 22:21:27 -07:00

27 KiB

Genesis

Canonical genesis configurations for all Lux blockchain networks. This repo is the single source of truth for chain IDs, genesis hashes, state roots, and required precompiles. Other repos (~/work/lux/{node,evm,state}) reference these values; do not duplicate them.

Latest Tag: v1.9.6

Post-E2E-PQ State (current)

Genesis now pins a ChainSecurityProfile for every chain it generates, and resolves it at load time. The profile hash is committed into the chain's bootstrap so a profile-flip is a wrong-network event, not a silent classification change.

Recent significant commits

SHA Tag Impact
85a8fc8 v1.9.6 Pin ChainSecurityProfile into Config + Resolve at load (closes F102 genesis layer)
1d73c8d v1.9.6 Swap mldsa65 keygen from cloudflare/circl to luxfi/crypto/pq/mldsa
e698307 v1.9.5 HD branch hardening + LightMnemonic production refusal (F12/F30/F31/F35)
fe4781c v1.9.5 LUX_DISABLE_CCHAIN=1 omits C-Chain from primary genesis
2b16f45 v1.9.5 Operator-overridable C-Chain genesis via LUX_CCHAIN_GENESIS_FILE

Zoo testnet/devnet 39-char alloc address bug (May 2026)

configs/zoo-{testnet,devnet}/genesis.json shipped six alloc addresses with only 39 hex chars instead of 40 — the leading 0 nibble was stripped at genesis-generation time (commit 2b46a8b, "regenerate all network configs"). The six bad addresses on both files were:

Truncated (39ch, shipped) Correct (40ch, present in C-chain alloc)
2aCB0ee16fd2B1bF465F85D7Ee112fA80C055FC 02aCB0ee16fd2B1bF465F85D7Ee112fA80C055FC
3B8Bd0A20b05AADC9E457A4c967f45eEeC163c4 03B8Bd0A20b05AADC9E457A4c967f45eEeC163c4
54D2e1dA5A1B5b9Da158acbA42Aed1d45F5CF02 054D2e1dA5A1B5b9Da158acbA42Aed1d45F5CF02
5CAE5842A08165d4d736dA9E49438574283aaE9 05CAE5842A08165d4d736dA9E49438574283aaE9
698495959f5570420BAA955dae5eA2864933ACf 0698495959f5570420BAA955dae5eA2864933ACf
7283AA2c30523E9190A17b9598ff65599BAAe17 07283AA2c30523E9190A17b9598ff65599BAAe17

Symptom: the EVM refused to bootstrap the chain — parsing genesis: hex string of odd length. The original CreateChainTx (ZAKw... on testnet, 2nLY... on devnet) carries the broken genesis bytes immutably, so we had to re-issue under a new chain name and rewrite the gateway. The 39→40-char fix applied in genesis is now the SAME content the EVM expects.

Operators bootstrapping a fresh zoo chain should pin the post-fix checksums in this file (see ### Verify Checksum). Mainnet zoo (zoo-mainnet/genesis.json) was not affected — it has only the treasury account plus the 0x0200…0005 precompile.

Chain bootstrap: chain-name collision (P-chain)

platform.IsChainNameTaken is case-insensitive and global across all chains. A failed CreateChainTx (e.g. the broken zoo above) still permanently squats its BlockchainName on the P-chain ledger — you cannot reuse the name even after fixing the genesis. Work-around used on zoo testnet/devnet: issue under a throwaway name (ZOOTEST123 / ZOODEVTEST123), then rewrite the krakend gateway endpoint so /ext/bc/zoo/rpc/ext/bc/<new-bcID>/rpc. The route /ext/bc/<name>/rpc is registered by luxfi/node/chains/manager.go only on successful VM init — broken chains never claim the route, so gateway-side rewrite is sufficient.

Active versions

  • Repo: v1.9.6 (next: v1.9.7).
  • Pinned by: luxfi/node v1.26.10.
  • Pulls: luxfi/consensus v1.23.4+ (for ChainSecurityProfile definition + ComputeHash), luxfi/crypto v1.18.4+ (mldsa65 keygen).

Cross-repo dependencies

  • luxfi/consensus/configChainSecurityProfile, Resolve, ComputeHash.
  • luxfi/crypto/pq/mldsa/mldsa65 → validator key generation.
  • luxfi/node/genesis/builder → consumes this repo's JSON config and converts types (string → ids.NodeID, uint64 → time.Duration).

Where to look for X

  • ChainSecurityProfile pin: pkg/genesis/security_profile.go
  • Profile hash verification: pkg/genesis/security_profile.go:Resolve
  • HD branch hardening: pkg/wallet/hd/*.go
  • Per-network configs: configs/{mainnet,testnet,devnet,local}/

Open follow-ups

  • Mainnet/testnet/devnet genesis JSONs still reference the legacy-compat profile while operators migrate validator keys. The strict-PQ profile is encoded but not yet the default genesis pin.

Canonical Requirements

Every Lux chain genesis MUST satisfy these, or block import / consensus will fail:

Requirement Mainnet C-Chain How to verify
chainId 96369 (NOT 1) jq -r .chainId configs/mainnet/cchain.json
Genesis hash 0x3f4fa2a0b0ce089f52bf0ae9199c75ffdd76ecafc987794050cb0d286f1ec61e geth init --datadir /tmp/v configs/mainnet/cchain.json 2>&1 | grep "genesis hash"
State root 0x2d1cedac263020c5c56ef962f6abe0da1f5217bdc6468f8c9258a0ea23699e80 jq -r .stateRoot configs/mainnet/cchain.json
Warp precompile in alloc 0200000000000000000000000000000000000005 nonce:0x1 code:0x01 jq '.alloc."0200000000000000000000000000000000000005"' configs/mainnet/cchain.json
warpConfig.blockTimestamp 1730446786 (== genesis ts 0x672485c2) must equal genesis timestamp
Treasury 0x9011E888251AB053B7bD1cdB598Db4f9DEd94714 = 2T LUX jq '.alloc."0x9011…"' configs/mainnet/cchain.json
File determinism sorted, sha256 matches Canonical Genesis Checksums table below shasum -a 256 configs/mainnet/cchain.json

If genesis hash drifts → check cChainGenesis in genesis.json is the literal canonical cchain.json content. Common drift: chainId=1 + missing warp = wrong-network template.

Networks

C-Chain (Primary Network)

Network Network ID Chain ID Genesis Hash Treasury
Mainnet 1 96369 0x3f4fa2a0b0ce089f52bf0ae9199c75ffdd76ecafc987794050cb0d286f1ec61e 2T LUX
Testnet 2 96368 0xfc909f7e992d9cb91485f114f6d333f3823a12f2c72bbf51ed2c8eea749b2d2e 2T LUX
Devnet 3 96370 0x836f6053473e4331bb347afc45b641f12075c63a302f4e56e64239a3ba4acd4b 2T LUX
Local 1337 1337 - 2T LUX

Historical note: testnet was 0x1c5fe37764b8bc146dc88bc1c2e0259cd8369b07a06439bcfa1782b5d4fb0995 and devnet was 0x53fe8be293555d20de41847f96081f4e8beca1ee2c206999ffbf7c70e497cf43 before commits 2bf1ca3 (Shanghai/Cancun/Prague activated at genesis) and d3cbd78 (gasLimit bumped to 1B). Both networks were "block 0 fresh" with no committed historical state, so the hash update is intentional; rollouts after 2026-05-21 use the new hashes. Mainnet hash is unchanged (its cChainGenesis is locked by the 1.08M-block historical chain).

Primary-Network Letter Chains (all baked into genesis)

Every primary network ships all 10 letter-chain shards, so every chain spawns at startup — no post-launch CreateBlockchainTx required. The shard schema is {x,c,d,q,a,b,t,z,g,k}chain.json (P-Chain is implicit; it is the primary network and carries the validator set + chain registry).

Letter aliases (LP-134 user-facing naming): F-Chain ≡ T-Chain (threshold FHE encrypted compute), M-Chain ≡ K-Chain (KMS + MPC topology). The canonical letter on disk and in VM IDs is T / K; F / M are aliases registered on the same VM IDs (builder.TChainAliases, builder.KChainAliases).

Letter VM Mainnet Testnet Devnet Localnet
X XVM (UTXO)
C EVM 96369 96368 96370 31337
D DexVM 96469 96468 96470 31447
Q QuantumVM 96569 96568 96570 31557
A AIVM 96669 96668 96670 31667
B BridgeVM 96769 96768 96770 31777
T (=F) ThresholdVM 96869 96868 96870 31887
Z ZKVM 96969 96968 96970 31997
G GraphVM 97069 97068 97070 32107
K (=M) KeyVM 97169 97168 97170 32217

Pattern: each letter's mainnet ID is 96369 + 100*Δ_from_C; testnet = mainnet-1, devnet = mainnet+1 (mirrors the C-Chain triple). Localnet uses a separate 31337 + 110*Δ series. All 36 EVM chain IDs are pairwise distinct so a misrouted transaction cannot be replayed against the wrong chain.

The contract that every primary network ships all 10 shards is locked by configs.TestGetGenesis_AllPrimaryChainsBakedIn — removing a chain on mainnet/testnet/devnet/localnet is now a load-bearing test edit, not a silent operator switch.

Application Chains

Chain Mainnet ID Testnet ID Devnet ID Treasury
Zoo 200200 200201 200202 2T ZOO
SPC 36911 36910 36912 1B SPC
Hanzo 36963 36962* 36964 1B AI
Pars 494949 494950 494951 2T PARS

*Note: Hanzo testnet was deployed with chain ID 36964 (devnet ID) due to a deployment error. The intended ID is 36962.

Deployed Genesis Hashes (All Chains)

Chain Mainnet Testnet Devnet
C-Chain 0x3f4fa2a...1ec61e 0x53fe8be...97cf43 0x53fe8be...97cf43
Zoo 0x7c548af...14933 0x0652fb2...d1979 0x0652fb2...d1979
SPC 0x4dc9fd5...cfac8 0x210fcc1...0b692 0x0652fb2...d1979
Hanzo 0x6cf786a...48e67 0x6cf786a...48e67 0x6cf786a...48e67
Pars 0xa32921a...b2f34 0xa32921a...b2f34 0xa32921a...b2f34

Genesis Treasury

All networks use the same production treasury address:

Address: 0x9011E888251AB053B7bD1cdB598Db4f9DEd94714
Balance: 2,000,000,000,000,000,000,000,000,000,000 wei (2T LUX)
Hex:     0x193e5939a08ce9dbd480000000

Directory Structure

configs/
├── mainnet/
│   ├── cchain.json         # C-Chain genesis (chain ID 96369)
│   ├── pchain.json         # P-Chain genesis (ProtocolVM)
│   ├── network.json        # Network ID, message, startTime
│   └── bootstrappers.json  # Bootstrap nodes
├── testnet/
│   ├── cchain.json         # C-Chain genesis (chain ID 96368)
│   └── genesis.json
├── devnet/
│   └── cchain.json
├── local/
│   ├── cchain.json
│   └── genesis.json
├── zoo-{mainnet,testnet,devnet}/
│   └── genesis.json        # Zoo (200200/200201/200202)
├── spc-{mainnet,testnet,devnet}/
│   └── genesis.json        # SPC (36911/36910/36912)
├── hanzo-{mainnet,testnet,devnet}/
│   └── genesis.json        # Hanzo (36963/36962/36964)
└── pars-{mainnet,testnet,devnet}/
    └── genesis.json        # Pars (494949/494950/494951)

C-Chain Configuration

Mainnet (96369)

{
  "chainId": 96369,
  "gasLimit": 12000000,
  "targetBlockRate": 2,
  "minBaseFee": 25000000000,
  "stateRoot": "0x2d1cedac263020c5c56ef962f6abe0da1f5217bdc6468f8c9258a0ea23699e80",
  "baseFeePerGas": "0x5d21dba00"
}

Network Parameters

Parameter Mainnet Testnet Devnet Local
Block Rate 2s 2s 1s 1s
Gas Limit 12M 12M 20M 15M
Min Base Fee 25 gwei 25 gwei 1 gwei 1 gwei
Chain ID 96369 96368 96370 1337

Warp Precompile (Required for Genesis Hash)

Both mainnet and testnet C-Chain genesis MUST include the warp precompile:

{
  "alloc": {
    "0200000000000000000000000000000000000005": {
      "balance": "0x0",
      "nonce": "0x1",
      "code": "0x01"
    },
    "0x9011E888251AB053B7bD1cdB598Db4f9DEd94714": {
      "balance": "0x193e5939a08ce9dbd480000000"
    }
  }
}

This produces the canonical state root: 0x2d1cedac263020c5c56ef962f6abe0da1f5217bdc6468f8c9258a0ea23699e80

Upgrade Timestamps

C-Chain protocol upgrades activated Dec 25, 2024 @ 4:20pm UTC (timestamp 1735143600):

{
  "evmTimestamp": 0,
  "durangoTimestamp": 0,
  "etnaTimestamp": 1735143600,
  "fortunaTimestamp": 1735143600,
  "graniteTimestamp": 1735143600
}

Genesis Package (pkg/genesis/)

Core Types

// Config - Top-level genesis configuration
type Config struct {
    NetworkID                  uint32
    Allocations                []Allocation
    StartTime                  uint64
    InitialStakeDuration       uint64
    InitialStakeDurationOffset uint64
    InitialStakedFunds         []ids.ShortID
    InitialStakers             []Staker
    CChainGenesis              string
    Message                    string
}

// Staker - Initial validator
type Staker struct {
    NodeID        ids.NodeID
    RewardAddress ids.ShortID
    DelegationFee uint32
    Signer        *ProofOfPossession
    Weight        uint64
    StartTime     uint64
    EndTime       uint64
}

// Allocation - Genesis allocation
type Allocation struct {
    EVMAddr        ids.ShortID    `json:"evmAddr"`
    UTXOAddr       ids.ShortID    `json:"utxoAddr"`
    InitialAmount  uint64         `json:"initialAmount"`
    UnlockSchedule []LockedAmount `json:"unlockSchedule"`
}

Address naming — EVMAddr + UTXOAddr. Always.

  • EVMAddr (Go) ↔ evmAddr (JSON) — address on EVM-modeled chains (C and other EVM letter chains).
  • UTXOAddr (Go) ↔ utxoAddr (JSON) — address on UTXO-modeled chains (P, X; share the same 20-byte ShortID; bech32 prefix differentiates chain).

Never use ETHAddr / LUXAddr / ethAddr / luxAddr in new code. The fields name the chain model (EVM, UTXO), not a specific implementation we happen to be wire-compatible with.

The one exception is ParseETHAddress(string) ids.ShortID — that's the input description (parses an Ethereum-format hex string like 0x...). The function output binds to EVMAddr.

Bech32 Address Format

Addresses use chain prefix + bech32 encoding:

Format: {chain}-{hrp}1{data}{checksum}
Example: P-lux1ck0t9h5u7jvvzhx29n99guqjsfkpzt67wgx7wg

Chain Prefixes:
- P-lux1...  (P-Chain — ProtocolVM)
- X-lux1...  (X-Chain/Exchange)
- C-0x...    (C-Chain/EVM hex)

Important: The bech32 checksum is computed using only the HRP ("lux"), not the chain prefix ("P-lux").

CLI Tools

genesis (cmd/genesis/)

Generate genesis configurations:

# Generate from mnemonic
LUX_MNEMONIC="test test..." ./genesis -network-id 96369 -validators 5 -output genesis.json

# From existing keys
./genesis -network mainnet -keys-dir ~/.lux/keys -output genesis.json

extract-genesis (cmd/extract-genesis/)

Extract genesis from existing chain data:

./extract-genesis -chaindata /path/to/chaindata -output cchain.json

State Repository Integration

Historical blockchain state is maintained separately in luxfi/state.

Available State Data

Network Blocks RLP Size Location
Lux Mainnet 1,082,780 1.2 GB rlp/lux-mainnet/lux-mainnet-96369.rlp
Lux Testnet 218 695 KB rlp/lux-testnet/lux-testnet-96368.rlp
Zoo Mainnet 799 1.3 MB rlp/zoo-mainnet/zoo-mainnet-200200.rlp
Zoo Testnet 84 156 KB rlp/zoo-testnet/zoo-testnet-200201.rlp
SPC Mainnet 11 7.8 KB rlp/spc-mainnet/spc-mainnet-36911.rlp

Import Workflow

# Clone repos
git clone https://github.com/luxfi/genesis
git clone https://github.com/luxfi/state

# Initialize with genesis
geth init --datadir /tmp/lux genesis/configs/mainnet/cchain.json

# Import blocks
geth import --datadir /tmp/lux state/rlp/lux-mainnet/lux-mainnet-96369.rlp

LuxEVM Header Formats

State repo RLP uses LuxEVM header encoding:

Fields Format Usage
16 Genesis block No optional fields
17-19 Post-genesis ExtDataHash, ExtDataGasUsed, BlockGasCost

Coreth field order (positions 15+):

15: ExtDataHash    (common.Hash - value type, required)
16: BaseFee        (*big.Int)
17: ExtDataGasUsed (*big.Int)
18: BlockGasCost   (*big.Int)

Mainnet Validators

Node NodeID P-Chain Address
1 NodeID-FrtEjhat6RUqjEWCJgYZKqBaxY2Woyy5G P-lux1ck0t9h5u7jvvzhx29n99guqjsfkpzt67wgx7wg
2 NodeID-9hq49qGVZN7M7tXxdpF3AqptQGdmPCFnQ P-lux1dclruwcn9ug8u0jjk3ukh676jr3lsy4er9m3l5
3 NodeID-8osEnSC4LQFdG1LMit12CBsE6BfKGHNAw P-lux1qdv9zns0gpfesw0h28jqp2up6h77du2damqf88
4 NodeID-MrdgTgPuddyo7anomKr4akMcoKzVKcgbG P-lux1jjs4nx7ul4d6pnsjtpv2khzu8p4yctegvass46
5 NodeID-Mgd5yHs4pe6qRjkBcW5Y7oqHA51j4afcC P-lux1970ngvf6s6rsndrkvjzr6lfvf2tdl30529c435

Token Decimals

// P-Chain/X-Chain: 6 decimals (microLUX as base unit)
const (
    MicroLux uint64 = 1            // 0.000001 LUX
    MilliLux uint64 = 1_000        // 0.001 LUX
    Lux      uint64 = 1_000_000    // 1 LUX
)

// C-Chain: 18 decimals (wei, standard EVM)
// 1 LUX = 10^18 wei on C-Chain

Verification

Genesis Hash Check

# Verify mainnet C-Chain genesis
geth init --datadir /tmp/verify configs/mainnet/cchain.json 2>&1 | grep "genesis hash"
# Expected: 0x3f4fa2a0b0ce089f52bf0ae9199c75ffdd76ecafc987794050cb0d286f1ec61e

# Verify state root
cat configs/mainnet/cchain.json | jq -r '.stateRoot'
# Expected: 0x2d1cedac263020c5c56ef962f6abe0da1f5217bdc6468f8c9258a0ea23699e80

Network ID Verification

cat configs/mainnet/network.json | jq '.networkID'
# Expected: 96369

cat configs/testnet/network.json | jq '.networkID'  
# Expected: 96368

Fortuna Timestamp Fix (2025-12-25)

Problem

Block import failed with gas limit mismatch when importing Zoo chain blocks:

invalid gas limit: have 12000000, want 10000000

Root Cause

Zoo genesis files had fortunaTimestamp: 0 which activates Fortuna from block 0. Fortuna uses dynamic gas limits via state.MaxCapacity() returning 10M, but original blocks had 12M gas limit (Cortina rules).

Solution

Set far-future timestamps for Fortuna, Etna, and Granite upgrades:

{
  "etnaTimestamp": 253399622400,
  "fortunaTimestamp": 253399622400,
  "graniteTimestamp": 253399622400
}

Files Updated

  • configs/zoo-mainnet/genesis.json
  • configs/zoo-testnet/genesis.json

Verified Block Counts (2025-12-28)

Network Chain ID Blocks Genesis Hash
Lux Mainnet 96369 1,082,780 0x3f4fa2a0b0ce089f52bf0ae9199c75ffdd76ecafc987794050cb0d286f1ec61e
Lux Testnet 96368 218 0x1c5fe37764b8bc146dc88bc1c2e0259cd8369b07a06439bcfa1782b5d4fb0995
Zoo Mainnet 200200 799 0x7c548af47de27560779ccc67dda32a540944accc71dac3343da3b9cd18f14933
Zoo Testnet 200201 84 0x0652fb2fde1460544a5893e5eba5095ff566861cbc87fcb1c73be2b81d6d1979
SPC Mainnet 36911 11 0x4dc9fd5cf4ee64609f140ba0aa50f320cadf0ae8b59a29415979bc05b17cfac8

Canonical Genesis Checksums (SHA256)

IMPORTANT: All genesis files are sorted alphabetically by key for determinism. These checksums are the canonical reference - if they don't match, the file has been modified.

File SHA256
mainnet/cchain.json 968e9f3cd19a9b88ca157db3894622272ded02f0f184b5386493ff55897001be
testnet/cchain.json ba211e1bbbd7807c2224d240628facb2dfb6c2d6cde2e274eb881a57609ed038
zoo-mainnet/genesis.json 14c01e8bb5ac13144a3730c695847d53506647efbaada3f3240a16e7b10684f7
zoo-testnet/genesis.json b8eea8c6b3276ebadce7e8ef9296bc14b56990ae04cd200a4663f5af21193534
zoo-devnet/genesis.json 4890a40fe7891722c90efb728f11b14aeef9795c2b897eb0140e1e0ae3b6254c
spc-mainnet/genesis.json 9c01115559556fc245d4993b193889370bd99edd22a7fa1e9e3d7ddafbb63535
spc-testnet/genesis.json af12bb81dd9abce26b7a311f00fa2ac7f5adfb996e698b9f473252199f747b2c
spc-devnet/genesis.json 317d54ac7846b0f174964552b4949d858072f6ffbc37f6c10092e01d595ce46f
hanzo-mainnet/genesis.json adae3c441b34db937dbcd961450bff5bcabd388a5479fe9ba7380ef61f083637
hanzo-testnet/genesis.json a2d00685bf7f414815dc3579552e19045183428c520f4f5b23a6726cf4002c32
hanzo-devnet/genesis.json 1c489d29c08857696909f029a1b2cda554e059e86c630ae64f35a5243f4bd25c
pars-mainnet/genesis.json ca35306a73f4cbfeccc8018b71596b122f698a23f3c038993a2ad34ca315e539
pars-testnet/genesis.json 9d7ff05187bb2570d2a1af8ccfa41a99dfaf8b56558decf127209ac6d5faec85
pars-devnet/genesis.json 3fc55f6be7b46dd6955ab2ec95186a4aa7da13991650909cb5f9ecbaa51b06c7

Verify Checksum

cd ~/work/lux/genesis/configs
shasum -a 256 zoo-mainnet/genesis.json
# Should output: 241fd0209253e7a5a04c55c7a6f4f0ba864a83dd9840c4969d5a1b5b5f6c3366

Re-sort if Modified

# If file was modified, re-sort it:
jq -S '.' genesis.json > genesis.sorted.json && mv genesis.sorted.json genesis.json

Genesis Hash Fix (2025-12-28)

Problem

Mainnet genesis hash was computing as 0xc3069db32ef986d8e287458e44066eb04205648e602dc2fede561c424a6516de instead of the expected 0x3f4fa2a0b0ce089f52bf0ae9199c75ffdd76ecafc987794050cb0d286f1ec61e from historical RLP blocks.

Root Cause

The warpConfig.blockTimestamp was set to 0, which caused ApplyPrecompileActivations() in coreth to add the warp precompile to genesis state BEFORE the alloc was applied. When warp wasn't in alloc, this changed the state trie and produced a different genesis hash.

Key insight from coreth/core/genesis.go:271:

// ApplyPrecompileActivations runs FIRST (line 271)
ApplyPrecompileActivations(g.Config, nil, blockContext, statedb)

// Then alloc is applied (lines 276-283)
for addr, account := range g.Alloc {
    // This overwrites precompile if in alloc, leaves it if not
}

Solution

The original mainnet genesis was created with warp precompile IN the alloc section:

{
  "alloc": {
    "0200000000000000000000000000000000000005": {
      "balance": "0x0",
      "nonce": "0x1", 
      "code": "0x01"
    }
  },
  "warpConfig": {
    "blockTimestamp": 1730446786,
    "quorumNumerator": 67,
    "requirePrimaryNetworkSigners": false
  }
}

The warpConfig.blockTimestamp equals the genesis timestamp (0x672485c2 = 1730446786), meaning warp is activated at genesis. Having warp in both alloc AND warpConfig ensures consistent state.

Files Restored

  • configs/mainnet/cchain.json - Restored from git with warp in alloc
  • configs/mainnet/genesis.json - Updated cChainGenesis to match

Verification

# Start mainnet
netrunner start --networks mainnet

# Verify genesis hash
curl -X POST -H 'Content-Type: application/json' \
  --data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x0",false],"id":1}' \
  http://127.0.0.1:9630/ext/bc/C/rpc | jq '.result.hash'
# Expected: "0x3f4fa2a0b0ce089f52bf0ae9199c75ffdd76ecafc987794050cb0d286f1ec61e"

# Verify warp precompile in state
curl -X POST -H 'Content-Type: application/json' \
  --data '{"jsonrpc":"2.0","method":"eth_getCode","params":["0x0200000000000000000000000000000000000005","0x0"],"id":1}' \
  http://127.0.0.1:9630/ext/bc/C/rpc | jq '.result'
# Expected: "0x01"

Known Issues

Documentation Discrepancies

  1. Network IDs: Some older docs reference Network ID 1/2 instead of 96369/96368
  2. File paths: Docs may reference chain.rlp.gz but actual files are {network}-{chainid}.rlp
  3. Block counts: Documentation may be outdated - verify against actual RLP files

State Repo Sync

The state repo should be periodically updated with:

  • Latest block exports
  • Updated metadata.json files
  • Corrected documentation

RLP Import via luxd --import-chain-data

End-to-end auto-import of historical chain data on luxd startup. Replaces the prior geth-sidecar workflow.

Flow

  1. luxd --import-chain-data=<path.rlp> (existing flag, was unwired).
  2. node/config/config.go config bridge injects import-chain-data into nodeConfig.ChainConfigs["C"].Config JSON map. Merges with any existing C-Chain config.
  3. EVM plugin reads vm.config.ImportChainData in Initialize() after initializeChain() returns. If non-empty, calls importBlocksFromFile (same code path as admin_importChain RPC).
  4. RLP stream decoded in batches of 2,500 blocks. State trie committed every CommitInterval (4,096) blocks; acceptedBlockDB updated atomically with each commit so restart-safe.
  5. Final state commit + last-accepted update at end-of-stream.

Required cChainGenesis (NON-NEGOTIABLE)

genesis.json cChainGenesis MUST be the canonical mainnet config:

  • chainId: 96369 (NOT 1 — chainId 1 in cChainGenesis means genesis.json was overwritten with a wrong-network template; restore from cchain.json)
  • Warp precompile in alloc: address 0200000000000000000000000000000000000005, nonce: "0x1", code: "0x01"
  • warpConfig.blockTimestamp: 1730446786 (== genesis timestamp 0x672485c2)

If any of these drift, the EVM computes a non-canonical genesis hash (commonly 0xcd5083cb…) and RLP block 1's parentHash won't match → import fails with batch 0: block 1 parent mismatch - expected genesis 0xcd…, got 0x3f4fa2a0….

Restore from canonical:

python3 -c "import json
canon = json.load(open('configs/mainnet/cchain.json'))
g = json.load(open('configs/mainnet/genesis.json'))
g['cChainGenesis'] = json.dumps(canon, separators=(',', ':'), sort_keys=True)
json.dump(g, open('configs/mainnet/genesis.json', 'w'), indent=2, sort_keys=True)"

Wiring sites (when re-implementing)

Layer File What
Flag node/config/keys.go ImportChainDataKey Already exists
Flag node/config/flags.go:370 Already exists
Bridge node/config/config.go:2012-2028 NEW: inject into chainConfigs["C"] after getChainConfigs
Plugin config evm/plugin/evm/config/config.go Config.ImportChainData NEW: matches JSON key import-chain-data
Plugin init evm/plugin/evm/vm.go Initialize() post-initializeChain NEW: calls importBlocksFromFile
Import logic evm/plugin/evm/admin_api.go:220 importBlocksFromFile Pre-existing — used by admin_importChain

macOS gotcha (kills luxd silently)

After cp of the luxd binary to a new path, macOS gatekeeper can SIGKILL on launch (exit 137, no output). The provenance attribute (com.apple.provenance) plus an invalidated ad-hoc signature is the cause. Fix:

codesign --force --sign - ~/work/lux/node/build/luxd
codesign --force --sign - ~/work/lux/node/build/plugins/*

Plugin filename / VM ID

The Lux EVM plugin has one canonical VM ID for both C-Chain and other EVM chains:

mgj786NP7uDwBCcq6YwThhaN8FLyybkCa4zBWTQbNgmK6k9A6

This is CB58(ids.ID{'e','v','m'}) per luxfi/constants.EVMID. luxd's chain manager, the lux-operator, and luxfi/cli all reference this one ID. Install the plugin binary at ~/.lux/plugins/mgj786NP7uDwBCcq6YwThhaN8FLyybkCa4zBWTQbNgmK6k9A6 — no other filename, no aliases.

Verified output (mainnet)

read last accepted hash=0x3f4fa2a0b0ce089f52bf0ae9199c75ffdd76ecafc987794050cb0d286f1ec61e height=0
Loaded most recent local header  hash=0x3f4fa2a0…
Initializing snapshots  headHash=0x3f4fa2a0…  headRoot=0x2d1cedac263020c5c56ef962f6abe0da1f5217bdc6468f8c9258a0ea23699e80
Auto-importing chain data at startup  path=…/lux-mainnet-96369.rlp
ImportChain: inserting batch  batch=0 blocks=2500 firstNum=1
ImportChain: batch done  batch=0 imported=2500 height=2500
…

Throughput: ~550 blocks/sec early, ~150 blocks/sec by ~100k blocks (state-trie growth). 1.08M blocks → ~60-90 min wall time on M-series.