Files
evmgpu/core/genesis_test.go
zeekayandHanzo Dev 7c06a2194f core: configure genesis precompiles instead of skipping them
ApplyPrecompileActivations returned early whenever parentTimestamp was nil,
which is exactly the genesis transition. Any precompile declared in
GenesisPrecompiles was therefore never configured, its account never got the
nonce and code marker, and none of its storage reached the genesis state root.

luxfi/evm has no such guard, so the two EVMs computed different genesis state
for the same chain config. That is a consensus divergence, not a local test
concern: a chain whose config declares genesis precompiles would genesis to a
different root here than on the Go line.

The guard was also unnecessary. IsForkTransition already treats a nil parent
as "activate iff the configured timestamp has been reached", which is what
decides genesis activation in luxfi/evm too. Genesis stays deterministic.

Also refresh the testing-genesis hash: evmgpu computes
0x517cc43a..., which is the constant luxfi/evm asserts for the identical
GenesisBlockForTesting body. The runtimes already agreed; only the fixture had
gone stale.

go test ./core/ now passes.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-28 23:30:21 -07:00

407 lines
14 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//
// This file is a derived work, based on the go-ethereum library whose original
// notices appear below.
//
// It is distributed under a license compatible with the licensing terms of the
// original code from which it is derived.
//
// Much love to the original authors for their work.
// **********
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package core
import (
_ "embed"
"fmt"
"math/big"
"reflect"
"testing"
"github.com/luxfi/evmgpu/consensus/dummy"
"github.com/luxfi/evmgpu/core/state"
"github.com/luxfi/evmgpu/params"
"github.com/luxfi/evmgpu/params/extras"
"github.com/luxfi/evmgpu/plugin/evm/customrawdb"
"github.com/luxfi/evmgpu/precompile/allowlist"
"github.com/luxfi/evmgpu/precompile/contracts/deployerallowlist"
"github.com/luxfi/geth/common"
"github.com/luxfi/geth/core/rawdb"
"github.com/luxfi/geth/core/types"
"github.com/luxfi/geth/core/vm"
"github.com/luxfi/geth/ethdb"
"github.com/luxfi/geth/trie"
"github.com/luxfi/geth/triedb"
gethpathdb "github.com/luxfi/geth/triedb/pathdb"
// "github.com/luxfi/evmgpu/triedb/firewood"
"github.com/davecgh/go-spew/spew"
"github.com/luxfi/evmgpu/triedb/pathdb"
"github.com/luxfi/evmgpu/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func setupGenesisBlock(db ethdb.Database, triedb *triedb.Database, genesis *Genesis, lastAcceptedHash common.Hash) (*params.ChainConfig, common.Hash, error) {
return SetupGenesisBlock(db, triedb, genesis, lastAcceptedHash, false)
}
func TestGenesisBlockForTesting(t *testing.T) {
// This is the hash luxfi/evm computes for the same genesis, from the same
// GenesisBlockForTesting body. The two agree; the older constant predates
// the BlobGasUsed/ExcessBlobGas roundtrip fix that keeps zero values zero
// rather than nil on Cancun blocks.
genesisBlockForTestingHash := common.HexToHash("0x517cc43afd8c4516d00dae3c767b336d4ad9a9aeffbf0a4b205ef0bdc6343f35")
block := GenesisBlockForTesting(rawdb.NewMemoryDatabase(), common.Address{1}, big.NewInt(1))
if block.Hash() != genesisBlockForTestingHash {
t.Errorf("wrong testing genesis hash, got %v, want %v", block.Hash(), genesisBlockForTestingHash)
}
}
func TestSetupGenesis(t *testing.T) {
for _, scheme := range []string{rawdb.HashScheme, rawdb.PathScheme, customrawdb.FirewoodScheme} {
t.Run(scheme, func(t *testing.T) {
// Firewood disabled - skip firewood test (not fully implemented)
if scheme == customrawdb.FirewoodScheme {
t.Skip("Firewood backend not fully implemented")
}
testSetupGenesis(t, scheme)
})
}
}
func testSetupGenesis(t *testing.T, scheme string) {
preChainConfig := params.Copy(params.TestPreEVMChainConfig)
params.GetExtra(preChainConfig).EVMTimestamp = utils.NewUint64(100)
var (
customg = Genesis{
Config: preChainConfig,
Alloc: types.GenesisAlloc{
{1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
},
GasLimit: params.GetExtra(preChainConfig).FeeConfig.GasLimit.Uint64(),
}
oldcustomg = customg
)
// Compute the actual genesis hash from the genesis spec
customghash := customg.ToBlock().Hash()
rollbackpreChainConfig := params.Copy(preChainConfig)
params.GetExtra(rollbackpreChainConfig).EVMTimestamp = utils.NewUint64(90)
oldcustomg.Config = rollbackpreChainConfig
oldcustomghash := oldcustomg.ToBlock().Hash()
tests := []struct {
name string
fn func(ethdb.Database) (*params.ChainConfig, common.Hash, error)
wantConfig *params.ChainConfig
wantHash common.Hash
wantErr error
}{
{
name: "genesis without ChainConfig",
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) {
return setupGenesisBlock(db, triedb.NewDatabase(db, newDbConfig(t, scheme)), new(Genesis), common.Hash{})
},
wantErr: errGenesisNoConfig,
wantConfig: nil,
},
{
name: "no block in DB, genesis == nil",
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) {
return setupGenesisBlock(db, triedb.NewDatabase(db, newDbConfig(t, scheme)), nil, common.Hash{})
},
wantErr: ErrNoGenesis,
wantConfig: nil,
},
{
name: "custom block in DB, genesis == nil returns stored config",
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) {
tdb := triedb.NewDatabase(db, newDbConfig(t, scheme))
block, err := customg.Commit(db, tdb)
if err != nil {
t.Fatal(err)
}
// When genesis is nil but a block exists, setup returns the stored config
return setupGenesisBlock(db, tdb, nil, block.Hash())
},
wantErr: nil, // No error - returns stored config
wantConfig: customg.Config,
wantHash: customghash,
},
{
name: "compatible config in DB",
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) {
tdb := triedb.NewDatabase(db, newDbConfig(t, scheme))
block, err := oldcustomg.Commit(db, tdb)
if err != nil {
t.Fatal(err)
}
// Use the actual block hash from commit, not a hardcoded hash
return setupGenesisBlock(db, tdb, &customg, block.Hash())
},
wantHash: oldcustomghash,
wantConfig: customg.Config,
},
{
name: "config upgrade preserves stored genesis",
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) {
// Commit the 'old' genesis block with EVM transition at 90.
// Advance to block #4, past the EVM transition block of customg.
tdb := triedb.NewDatabase(db, newDbConfig(t, rawdb.HashScheme))
genesis, err := oldcustomg.Commit(db, tdb)
if err != nil {
t.Fatal(err)
}
_ = tdb.Close()
cacheConfig := DefaultCacheConfigWithScheme(scheme)
cacheConfig.ChainDataDir = t.TempDir()
bc, err := NewBlockChain(db, cacheConfig, &oldcustomg, dummy.NewFullFaker(), vm.Config{}, genesis.Hash(), false)
if err != nil {
t.Fatal(err)
}
defer bc.Stop()
_, blocks, _, err := GenerateChainWithGenesis(&oldcustomg, dummy.NewFullFaker(), 4, 25, nil)
if err != nil {
t.Fatal(err)
}
bc.InsertChain(blocks)
for _, block := range blocks {
if err := bc.Accept(block); err != nil {
t.Fatal(err)
}
}
// In the current implementation, config upgrades are allowed as long as
// the lastAccepted block is before the incompatible fork.
// The new config is written and returned.
return setupGenesisBlock(db, bc.TrieDB(), &customg, bc.lastAccepted.Hash())
},
wantHash: oldcustomghash, // Hash remains the original genesis hash
wantConfig: customg.Config,
wantErr: nil, // No error - upgrade is allowed
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%s %s", test.name, scheme), func(t *testing.T) {
db := rawdb.NewMemoryDatabase()
config, hash, err := test.fn(db)
// Check the return values.
if !reflect.DeepEqual(err, test.wantErr) {
spew := spew.ConfigState{DisablePointerAddresses: true, DisableCapacities: true}
t.Errorf("returned error %#v, want %#v", spew.NewFormatter(err), spew.NewFormatter(test.wantErr))
}
if !reflect.DeepEqual(config, test.wantConfig) {
t.Errorf("returned %v\nwant %v", config, test.wantConfig)
}
if hash != test.wantHash {
t.Errorf("returned hash %s, want %s", hash.Hex(), test.wantHash.Hex())
} else if err == nil {
// Check database content.
stored := rawdb.ReadBlock(db, test.wantHash, 0)
if stored.Hash() != test.wantHash {
t.Errorf("block in DB has hash %s, want %s", stored.Hash(), test.wantHash)
}
}
})
}
}
func TestStatefulPrecompilesConfigure(t *testing.T) {
type test struct {
getConfig func() *params.ChainConfig // Return the config that enables the stateful precompile at the genesis for the test
assertState func(t *testing.T, sdb *state.StateDB) // Check that the stateful precompiles were configured correctly
}
addr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC")
// Test suite to ensure that stateful precompiles are configured correctly in the genesis.
for name, test := range map[string]test{
"allow list enabled in genesis": {
getConfig: func() *params.ChainConfig {
config := params.Copy(params.TestChainConfig)
params.GetExtra(config).GenesisPrecompiles = extras.Precompiles{
deployerallowlist.ConfigKey: deployerallowlist.NewConfig(utils.NewUint64(0), []common.Address{addr}, nil, nil),
}
return config
},
assertState: func(t *testing.T, sdb *state.StateDB) {
assert.Equal(t, allowlist.AdminRole, deployerallowlist.GetContractDeployerAllowListStatus(sdb, addr), "unexpected allow list status for modified address")
assert.Equal(t, uint64(1), sdb.GetNonce(deployerallowlist.ContractAddress))
},
},
} {
t.Run(name, func(t *testing.T) {
config := test.getConfig()
genesis := &Genesis{
Config: config,
Alloc: types.GenesisAlloc{
{1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
},
GasLimit: params.GetExtra(config).FeeConfig.GasLimit.Uint64(),
}
db := rawdb.NewMemoryDatabase()
genesisBlock := genesis.ToBlock()
genesisRoot := genesisBlock.Root()
_, _, err := setupGenesisBlock(db, triedb.NewDatabase(db, triedb.HashDefaults), genesis, genesisBlock.Hash())
if err != nil {
t.Fatal(err)
}
statedb, err := state.New(genesisRoot, state.NewDatabase(db), nil)
if err != nil {
t.Fatal(err)
}
if test.assertState != nil {
test.assertState(t, statedb)
}
})
}
}
// regression test for precompile activation after header block
func TestPrecompileActivationAfterHeaderBlock(t *testing.T) {
db := rawdb.NewMemoryDatabase()
customg := Genesis{
Config: params.TestChainConfig,
Alloc: types.GenesisAlloc{
{1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
},
GasLimit: params.GetExtra(params.TestChainConfig).FeeConfig.GasLimit.Uint64(),
}
bc, _ := NewBlockChain(db, DefaultCacheConfig, &customg, dummy.NewFullFaker(), vm.Config{}, common.Hash{}, false)
defer bc.Stop()
// Advance header to block #4, past the ContractDeployerAllowListConfig.
_, blocks, _, _ := GenerateChainWithGenesis(&customg, dummy.NewFullFaker(), 4, 25, nil)
require := require.New(t)
_, err := bc.InsertChain(blocks)
require.NoError(err)
// accept up to block #2
for _, block := range blocks[:2] {
require.NoError(bc.Accept(block))
}
block := bc.CurrentBlock()
require.Equal(blocks[1].Hash(), bc.lastAccepted.Hash())
// header must be bigger than last accepted
require.Greater(block.Time, bc.lastAccepted.Time())
activatedGenesisConfig := params.Copy(customg.Config)
contractDeployerConfig := deployerallowlist.NewConfig(utils.NewUint64(51), nil, nil, nil)
params.GetExtra(activatedGenesisConfig).PrecompileUpgrades = []extras.PrecompileUpgrade{
{
Config: contractDeployerConfig,
},
}
customg.Config = activatedGenesisConfig
// assert block is after the activation block
require.Greater(block.Time, *contractDeployerConfig.Timestamp())
// assert last accepted block is before the activation block
require.Less(bc.lastAccepted.Time(), *contractDeployerConfig.Timestamp())
// This should not return any error since the last accepted block is before the activation block.
config, _, err := setupGenesisBlock(db, triedb.NewDatabase(db, nil), &customg, bc.lastAccepted.Hash())
require.NoError(err)
if !reflect.DeepEqual(config, customg.Config) {
t.Errorf("returned %v\nwant %v", config, customg.Config)
}
}
func TestGenesisWriteUpgradesRegression(t *testing.T) {
require := require.New(t)
config := params.Copy(params.TestChainConfig)
genesis := &Genesis{
Config: config,
Alloc: types.GenesisAlloc{
{1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
},
GasLimit: params.GetExtra(config).FeeConfig.GasLimit.Uint64(),
}
db := rawdb.NewMemoryDatabase()
trieDB := triedb.NewDatabase(db, triedb.HashDefaults)
genesisBlock := genesis.MustCommit(db, trieDB)
_, _, err := SetupGenesisBlock(db, trieDB, genesis, genesisBlock.Hash(), false)
require.NoError(err)
params.GetExtra(genesis.Config).PrecompileUpgrades = []extras.PrecompileUpgrade{
{
Config: deployerallowlist.NewConfig(utils.NewUint64(51), nil, nil, nil),
},
}
_, _, err = SetupGenesisBlock(db, trieDB, genesis, genesisBlock.Hash(), false)
require.NoError(err)
timestamp := uint64(100)
lastAcceptedBlock := types.NewBlock(&types.Header{
ParentHash: common.Hash{1, 2, 3},
Number: big.NewInt(100),
GasLimit: 8_000_000,
Extra: nil,
Time: timestamp,
}, nil, nil, trie.NewStackTrie(nil))
rawdb.WriteBlock(db, lastAcceptedBlock)
// Attempt restart after the chain has advanced past the activation of the precompile upgrade.
// This tests a regression where the UpgradeConfig would not be written to disk correctly.
_, _, err = SetupGenesisBlock(db, trieDB, genesis, lastAcceptedBlock.Hash(), false)
require.NoError(err)
}
func newDbConfig(t *testing.T, scheme string) *triedb.Config {
switch scheme {
case rawdb.HashScheme:
return triedb.HashDefaults
case rawdb.PathScheme:
return &triedb.Config{PathDB: &gethpathdb.Config{
ReadOnly: pathdb.Defaults.ReadOnly,
}}
case customrawdb.FirewoodScheme:
// Firewood disabled - use HashScheme instead
return triedb.HashDefaults
// fwCfg := firewood.Defaults
// // Create a unique temporary directory for each test
// fwCfg.FilePath = filepath.Join(t.TempDir(), "firewood_state") // matches blockchain.go
// return &triedb.Config{DBOverride: fwCfg.BackendConstructor}
default:
t.Fatalf("unknown scheme %s", scheme)
}
return nil
}
// NOTE: TestVerkleGenesisCommit removed - Verkle trie support requires full
// implementation of hashAlloc() for Verkle roots in genesis.go. The current
// Lux EVM genesis.toBlock() uses a statedb approach which isn't compatible
// with Verkle's genesis hash computation. Re-add this test when Verkle is
// properly implemented using the hashAlloc approach from upstream geth.