mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
core/genesis: honor SkipPostMergeFields flag from JSON
Lux mainnet C-Chain canonical genesis hash is
0x3f4fa2a0b0ce089f52bf0ae9199c75ffdd76ecafc987794050cb0d286f1ec61e,
matching the 1,082,781-block historic RLP export at
lux-mainnet-96369.rlp. The genesis JSON activates Cancun at
genesis time (cancunTime=0) for MCOPY etc., but the genesis BLOCK
itself predates Cancun and must use the 16-field pre-Shanghai
header format to preserve hash continuity with the historic chain.
luxfi/geth's core.Genesis exposes SkipPostMergeFields for exactly
this case. luxfi/evm shipped the JSON unmarshaling but ignored the
flag in toBlock(), so any post-merge field that fork-activates at
genesis time (ParentBeaconRoot, ExcessBlobGas, BlobGasUsed,
WithdrawalsHash) gets baked into the header and shifts the
computed genesis hash, e.g. to 0x1ade42ec…. luxd then panics
during EnsureGenesisState because the resulting state root has no
parent in pathdb's layertree ("triedb parent
[0x56e81f17…] layer missing"), which blocks mainnet C-Chain
bootstrap and admin_importChain.
Mirror the upstream geth pattern: gate the entire Shanghai+
post-merge field assignment block with `if !g.SkipPostMergeFields`,
and round-trip the flag through MarshalJSON/UnmarshalJSON in
gen_genesis.go.
Verification (TestSkipPostMergeFieldsGenesisHash pins both):
- `skipPostMergeFields: true` → 0x3f4fa2a0… (canonical, RLP block 0)
- flag absent / false → different hash (gate is active)
This commit is contained in:
+42
-36
@@ -19,24 +19,25 @@ var _ = (*genesisSpecMarshaling)(nil)
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (g Genesis) MarshalJSON() ([]byte, error) {
|
||||
type Genesis struct {
|
||||
Config *params.ChainConfig `json:"config"`
|
||||
Nonce math.HexOrDecimal64 `json:"nonce"`
|
||||
Timestamp math.HexOrDecimal64 `json:"timestamp"`
|
||||
ExtraData hexutil.Bytes `json:"extraData"`
|
||||
GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"`
|
||||
Mixhash common.Hash `json:"mixHash"`
|
||||
Coinbase common.Address `json:"coinbase"`
|
||||
Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"`
|
||||
AirdropHash common.Hash `json:"airdropHash"`
|
||||
AirdropAmount *math.HexOrDecimal256 `json:"airdropAmount"`
|
||||
AirdropData []byte `json:"-"`
|
||||
Number math.HexOrDecimal64 `json:"number"`
|
||||
GasUsed math.HexOrDecimal64 `json:"gasUsed"`
|
||||
ParentHash common.Hash `json:"parentHash"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
|
||||
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"`
|
||||
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"`
|
||||
Config *params.ChainConfig `json:"config"`
|
||||
Nonce math.HexOrDecimal64 `json:"nonce"`
|
||||
Timestamp math.HexOrDecimal64 `json:"timestamp"`
|
||||
ExtraData hexutil.Bytes `json:"extraData"`
|
||||
GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"`
|
||||
Mixhash common.Hash `json:"mixHash"`
|
||||
Coinbase common.Address `json:"coinbase"`
|
||||
Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"`
|
||||
AirdropHash common.Hash `json:"airdropHash"`
|
||||
AirdropAmount *math.HexOrDecimal256 `json:"airdropAmount"`
|
||||
AirdropData []byte `json:"-"`
|
||||
Number math.HexOrDecimal64 `json:"number"`
|
||||
GasUsed math.HexOrDecimal64 `json:"gasUsed"`
|
||||
ParentHash common.Hash `json:"parentHash"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
|
||||
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"`
|
||||
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"`
|
||||
SkipPostMergeFields bool `json:"skipPostMergeFields,omitempty"`
|
||||
}
|
||||
var enc Genesis
|
||||
enc.Config = g.Config
|
||||
@@ -62,30 +63,32 @@ func (g Genesis) MarshalJSON() ([]byte, error) {
|
||||
enc.BaseFee = (*math.HexOrDecimal256)(g.BaseFee)
|
||||
enc.ExcessBlobGas = (*math.HexOrDecimal64)(g.ExcessBlobGas)
|
||||
enc.BlobGasUsed = (*math.HexOrDecimal64)(g.BlobGasUsed)
|
||||
enc.SkipPostMergeFields = g.SkipPostMergeFields
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (g *Genesis) UnmarshalJSON(input []byte) error {
|
||||
type Genesis struct {
|
||||
Config *params.ChainConfig `json:"config"`
|
||||
Nonce *math.HexOrDecimal64 `json:"nonce"`
|
||||
Timestamp *math.HexOrDecimal64 `json:"timestamp"`
|
||||
ExtraData *hexutil.Bytes `json:"extraData"`
|
||||
GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"`
|
||||
Mixhash *common.Hash `json:"mixHash"`
|
||||
Coinbase *common.Address `json:"coinbase"`
|
||||
Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"`
|
||||
AirdropHash *common.Hash `json:"airdropHash"`
|
||||
AirdropAmount *math.HexOrDecimal256 `json:"airdropAmount"`
|
||||
AirdropData []byte `json:"-"`
|
||||
Number *math.HexOrDecimal64 `json:"number"`
|
||||
GasUsed *math.HexOrDecimal64 `json:"gasUsed"`
|
||||
ParentHash *common.Hash `json:"parentHash"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
|
||||
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"`
|
||||
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"`
|
||||
Config *params.ChainConfig `json:"config"`
|
||||
Nonce *math.HexOrDecimal64 `json:"nonce"`
|
||||
Timestamp *math.HexOrDecimal64 `json:"timestamp"`
|
||||
ExtraData *hexutil.Bytes `json:"extraData"`
|
||||
GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"`
|
||||
Mixhash *common.Hash `json:"mixHash"`
|
||||
Coinbase *common.Address `json:"coinbase"`
|
||||
Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"`
|
||||
AirdropHash *common.Hash `json:"airdropHash"`
|
||||
AirdropAmount *math.HexOrDecimal256 `json:"airdropAmount"`
|
||||
AirdropData []byte `json:"-"`
|
||||
Number *math.HexOrDecimal64 `json:"number"`
|
||||
GasUsed *math.HexOrDecimal64 `json:"gasUsed"`
|
||||
ParentHash *common.Hash `json:"parentHash"`
|
||||
BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"`
|
||||
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"`
|
||||
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"`
|
||||
SkipPostMergeFields *bool `json:"skipPostMergeFields,omitempty"`
|
||||
}
|
||||
var dec Genesis
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
@@ -151,5 +154,8 @@ func (g *Genesis) UnmarshalJSON(input []byte) error {
|
||||
if dec.BlobGasUsed != nil {
|
||||
g.BlobGasUsed = (*uint64)(dec.BlobGasUsed)
|
||||
}
|
||||
if dec.SkipPostMergeFields != nil {
|
||||
g.SkipPostMergeFields = *dec.SkipPostMergeFields
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+39
-16
@@ -94,6 +94,19 @@ type Genesis struct {
|
||||
BaseFee *big.Int `json:"baseFeePerGas"` // EIP-1559
|
||||
ExcessBlobGas *uint64 `json:"excessBlobGas"` // EIP-4844
|
||||
BlobGasUsed *uint64 `json:"blobGasUsed"` // EIP-4844
|
||||
|
||||
// SkipPostMergeFields disables adding Shanghai/Cancun/Prague header
|
||||
// fields (WithdrawalsHash, ParentBeaconRoot, ExcessBlobGas,
|
||||
// BlobGasUsed, RequestsHash) to the genesis block, regardless of
|
||||
// whether the corresponding forks are active at genesis time.
|
||||
//
|
||||
// Lux mainnet's canonical C-Chain genesis hash (0x3f4fa2a0…) was
|
||||
// produced with the 16-field pre-Shanghai header format. The chain
|
||||
// activates Cancun at genesis time (cancunTime=0) for EVM bytecode
|
||||
// (MCOPY etc.) but the genesis BLOCK itself must stay in the legacy
|
||||
// header shape to preserve hash continuity with historic RLP
|
||||
// exports. Mirrors the SkipPostMergeFields flag in luxfi/geth core.
|
||||
SkipPostMergeFields bool `json:"skipPostMergeFields,omitempty"`
|
||||
}
|
||||
|
||||
// field type overrides for gencodec
|
||||
@@ -444,23 +457,33 @@ func (g *Genesis) toBlock(db ethdb.Database, triedb *triedb.Database) *types.Blo
|
||||
// at genesis timestamp, but the genesis BLOCK itself must remain
|
||||
// in pre-withdrawals format to preserve hash continuity. Only
|
||||
// post-genesis blocks (Number > 0) get a WithdrawalsHash here.
|
||||
if num.Sign() != 0 && conf.IsShanghai(num, g.Timestamp) {
|
||||
head.WithdrawalsHash = &types.EmptyWithdrawalsHash
|
||||
withdrawals = make([]*types.Withdrawal, 0)
|
||||
}
|
||||
if conf.IsCancun(num, g.Timestamp) {
|
||||
// EIP-4788: The parentBeaconBlockRoot of the genesis block is always
|
||||
// the zero hash. This is because the genesis block does not have a parent
|
||||
// by definition.
|
||||
head.ParentBeaconRoot = new(common.Hash)
|
||||
// EIP-4844 fields
|
||||
head.ExcessBlobGas = g.ExcessBlobGas
|
||||
head.BlobGasUsed = g.BlobGasUsed
|
||||
if head.ExcessBlobGas == nil {
|
||||
head.ExcessBlobGas = new(uint64)
|
||||
//
|
||||
// SkipPostMergeFields is the explicit operator opt-out for ALL
|
||||
// post-merge header fields (mirrors luxfi/geth core.Genesis). When
|
||||
// set, neither WithdrawalsHash, ParentBeaconRoot, ExcessBlobGas
|
||||
// nor BlobGasUsed are populated, regardless of fork activation
|
||||
// time. This is the canonical path for replaying historic RLP
|
||||
// exports whose block-0 hash was produced before any post-merge
|
||||
// fork existed (e.g. lux-mainnet-96369.rlp → 0x3f4fa2a0…).
|
||||
if !g.SkipPostMergeFields {
|
||||
if num.Sign() != 0 && conf.IsShanghai(num, g.Timestamp) {
|
||||
head.WithdrawalsHash = &types.EmptyWithdrawalsHash
|
||||
withdrawals = make([]*types.Withdrawal, 0)
|
||||
}
|
||||
if head.BlobGasUsed == nil {
|
||||
head.BlobGasUsed = new(uint64)
|
||||
if conf.IsCancun(num, g.Timestamp) {
|
||||
// EIP-4788: The parentBeaconBlockRoot of the genesis block is always
|
||||
// the zero hash. This is because the genesis block does not have a parent
|
||||
// by definition.
|
||||
head.ParentBeaconRoot = new(common.Hash)
|
||||
// EIP-4844 fields
|
||||
head.ExcessBlobGas = g.ExcessBlobGas
|
||||
head.BlobGasUsed = g.BlobGasUsed
|
||||
if head.ExcessBlobGas == nil {
|
||||
head.ExcessBlobGas = new(uint64)
|
||||
}
|
||||
if head.BlobGasUsed == nil {
|
||||
head.BlobGasUsed = new(uint64)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/geth/common"
|
||||
)
|
||||
|
||||
// TestSkipPostMergeFieldsGenesisHash pins the canonical Lux mainnet
|
||||
// C-Chain genesis hash 0x3f4fa2a0b0ce089f52bf0ae9199c75ffdd76ecafc987794050cb0d286f1ec61e.
|
||||
//
|
||||
// The genesis JSON sets cancunTime=0, so without SkipPostMergeFields
|
||||
// the genesis block would carry ParentBeaconRoot/ExcessBlobGas/BlobGasUsed
|
||||
// header fields that did not exist when the historic chain was launched.
|
||||
// Honoring the JSON flag preserves hash continuity with the historic
|
||||
// 1.08M-block RLP export at lux-mainnet-96369.rlp (block 0 hash above).
|
||||
func TestSkipPostMergeFieldsGenesisHash(t *testing.T) {
|
||||
const luxMainnetGenesis = `{
|
||||
"alloc": {
|
||||
"0200000000000000000000000000000000000005": {
|
||||
"balance": "0x0",
|
||||
"code": "0x01",
|
||||
"nonce": "0x1"
|
||||
},
|
||||
"0x9011E888251AB053B7bD1cdB598Db4f9DEd94714": {
|
||||
"balance": "0x193e5939a08ce9dbd480000000"
|
||||
}
|
||||
},
|
||||
"baseFeePerGas": "0x5d21dba00",
|
||||
"coinbase": "0x0000000000000000000000000000000000000000",
|
||||
"config": {
|
||||
"arrowGlacierBlock": 0,
|
||||
"berlinBlock": 0,
|
||||
"byzantiumBlock": 0,
|
||||
"cancunTime": 0,
|
||||
"chainId": 96369,
|
||||
"constantinopleBlock": 0,
|
||||
"eip150Block": 0,
|
||||
"eip155Block": 0,
|
||||
"eip158Block": 0,
|
||||
"evmTimestamp": 0,
|
||||
"feeConfig": {
|
||||
"baseFeeChangeDenominator": 36,
|
||||
"blockGasCostStep": 200000,
|
||||
"gasLimit": 12000000,
|
||||
"maxBlockGasCost": 1000000,
|
||||
"minBaseFee": 25000000000,
|
||||
"minBlockGasCost": 0,
|
||||
"targetBlockRate": 2,
|
||||
"targetGas": 500000000
|
||||
},
|
||||
"grayGlacierBlock": 0,
|
||||
"homesteadBlock": 0,
|
||||
"istanbulBlock": 0,
|
||||
"londonBlock": 0,
|
||||
"mergeNetsplitBlock": 0,
|
||||
"muirGlacierBlock": 0,
|
||||
"petersburgBlock": 0,
|
||||
"shanghaiTime": 0,
|
||||
"terminalTotalDifficulty": 0,
|
||||
"durangoTimestamp": 0,
|
||||
"etnaTimestamp": 0,
|
||||
"fortunaTimestamp": 0,
|
||||
"graniteTimestamp": 0
|
||||
},
|
||||
"difficulty": "0x0",
|
||||
"extraData": "0x",
|
||||
"gasLimit": "0xb71b00",
|
||||
"gasUsed": "0x0",
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"nonce": "0x0",
|
||||
"number": "0x0",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"skipPostMergeFields": true,
|
||||
"stateRoot": "0x2d1cedac263020c5c56ef962f6abe0da1f5217bdc6468f8c9258a0ea23699e80",
|
||||
"timestamp": "0x672485c2"
|
||||
}`
|
||||
const wantHash = "0x3f4fa2a0b0ce089f52bf0ae9199c75ffdd76ecafc987794050cb0d286f1ec61e"
|
||||
|
||||
var g Genesis
|
||||
if err := json.Unmarshal([]byte(luxMainnetGenesis), &g); err != nil {
|
||||
t.Fatalf("unmarshal genesis: %v", err)
|
||||
}
|
||||
if !g.SkipPostMergeFields {
|
||||
t.Fatalf("SkipPostMergeFields not honored from JSON")
|
||||
}
|
||||
got := g.ToBlock().Hash()
|
||||
if got != common.HexToHash(wantHash) {
|
||||
t.Fatalf("genesis hash mismatch:\n got %s\n want %s", got.Hex(), wantHash)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPostMergeFieldsActiveWhenFlagFalse ensures that without
|
||||
// SkipPostMergeFields the genesis hash flips to a different value
|
||||
// (i.e. the flag is the gate, not a no-op). Uses the same chain spec
|
||||
// minus the flag.
|
||||
func TestPostMergeFieldsActiveWhenFlagFalse(t *testing.T) {
|
||||
const baseGenesis = `{
|
||||
"alloc": {
|
||||
"0200000000000000000000000000000000000005": {
|
||||
"balance": "0x0",
|
||||
"code": "0x01",
|
||||
"nonce": "0x1"
|
||||
},
|
||||
"0x9011E888251AB053B7bD1cdB598Db4f9DEd94714": {
|
||||
"balance": "0x193e5939a08ce9dbd480000000"
|
||||
}
|
||||
},
|
||||
"baseFeePerGas": "0x5d21dba00",
|
||||
"coinbase": "0x0000000000000000000000000000000000000000",
|
||||
"config": {
|
||||
"arrowGlacierBlock": 0,
|
||||
"berlinBlock": 0,
|
||||
"byzantiumBlock": 0,
|
||||
"cancunTime": 0,
|
||||
"chainId": 96369,
|
||||
"constantinopleBlock": 0,
|
||||
"eip150Block": 0,
|
||||
"eip155Block": 0,
|
||||
"eip158Block": 0,
|
||||
"evmTimestamp": 0,
|
||||
"feeConfig": {
|
||||
"baseFeeChangeDenominator": 36,
|
||||
"blockGasCostStep": 200000,
|
||||
"gasLimit": 12000000,
|
||||
"maxBlockGasCost": 1000000,
|
||||
"minBaseFee": 25000000000,
|
||||
"minBlockGasCost": 0,
|
||||
"targetBlockRate": 2,
|
||||
"targetGas": 500000000
|
||||
},
|
||||
"grayGlacierBlock": 0,
|
||||
"homesteadBlock": 0,
|
||||
"istanbulBlock": 0,
|
||||
"londonBlock": 0,
|
||||
"mergeNetsplitBlock": 0,
|
||||
"muirGlacierBlock": 0,
|
||||
"petersburgBlock": 0,
|
||||
"shanghaiTime": 0,
|
||||
"terminalTotalDifficulty": 0,
|
||||
"durangoTimestamp": 0,
|
||||
"etnaTimestamp": 0,
|
||||
"fortunaTimestamp": 0,
|
||||
"graniteTimestamp": 0
|
||||
},
|
||||
"difficulty": "0x0",
|
||||
"extraData": "0x",
|
||||
"gasLimit": "0xb71b00",
|
||||
"gasUsed": "0x0",
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"nonce": "0x0",
|
||||
"number": "0x0",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"stateRoot": "0x2d1cedac263020c5c56ef962f6abe0da1f5217bdc6468f8c9258a0ea23699e80",
|
||||
"timestamp": "0x672485c2"
|
||||
}`
|
||||
var g Genesis
|
||||
if err := json.Unmarshal([]byte(baseGenesis), &g); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
if g.SkipPostMergeFields {
|
||||
t.Fatalf("SkipPostMergeFields should default false")
|
||||
}
|
||||
got := g.ToBlock().Hash()
|
||||
canonical := common.HexToHash("0x3f4fa2a0b0ce089f52bf0ae9199c75ffdd76ecafc987794050cb0d286f1ec61e")
|
||||
if got == canonical {
|
||||
t.Fatalf("expected different hash when post-merge fields are active, got canonical %s", canonical.Hex())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user