Files
evmgpu/core/state_processor_ext.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

130 lines
6.5 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package core
import (
"encoding/json"
"fmt"
"math/big"
"github.com/luxfi/evmgpu/core/extstate"
"github.com/luxfi/evmgpu/core/state"
"github.com/luxfi/evmgpu/params"
"github.com/luxfi/evmgpu/precompile/contract"
"github.com/luxfi/evmgpu/precompile/modules"
"github.com/luxfi/evmgpu/stateupgrade"
"github.com/luxfi/geth/core/tracing"
log "github.com/luxfi/log"
)
// ApplyPrecompileActivations checks if any of the precompiles specified by the chain config are enabled or disabled by the block
// transition from `parentTimestamp` to the timestamp set in `blockContext`. If this is the case, it calls [modules.Module]'s Configure
// to apply the necessary state transitions for the upgrade.
//
// Genesis is the transition with no parent, i.e. parentTimestamp == nil. It is not a
// special case here: IsForkTransition already reads a nil parent as "activate iff the
// configured timestamp has been reached", so a precompile declared in GenesisPrecompiles
// at timestamp 0 is configured into the genesis state, exactly as luxfi/evm does it.
// Genesis remains deterministic because it is still a pure function of the chain config,
// the alloc and the header. Short-circuiting on a nil parent instead would silently drop
// every genesis precompile from the state root and fork this EVM off the Go line.
//
// In block processing and building, [ApplyUpgrades] is called instead which also applies state upgrades.
func ApplyPrecompileActivations(c *params.ChainConfig, parentTimestamp *uint64, blockContext contract.ConfigurationBlockContext, statedb *state.StateDB) error {
blockTimestamp := blockContext.Timestamp()
// Note: [modules.RegisteredModules] returns precompiles sorted by module addresses.
// This ensures:
// - the order we call [modules.Module]'s Configure for each precompile is consistent
// - even if precompiles read/write state other than their own they will observe
// an identical global state in a deterministic order when they are configured.
extra := params.GetExtra(c)
for _, module := range modules.RegisteredModules() {
for _, activatingConfig := range extra.GetActivatingPrecompileConfigs(module.Address, parentTimestamp, blockTimestamp, extra.PrecompileUpgrades) {
// If this transition activates the upgrade, configure the stateful precompile.
// (or deconfigure it if it is being disabled.)
if activatingConfig.IsDisabled() {
log.Info("Disabling precompile", "name", module.ConfigKey)
statedb.SelfDestruct(module.Address)
// Calling [state.StateDB]'s Finalise here effectively commits the SelfDestruct call and wipes the contract state.
// This enables re-configuration of the same contract state in the same block.
// Without an immediate Finalise call after the SelfDestruct, a reconfigured precompiled state can be wiped out
// since SelfDestruct will be committed after the reconfiguration.
statedb.Finalise(true)
continue
}
var printIntf interface{}
marshalled, err := json.Marshal(activatingConfig)
if err == nil {
printIntf = string(marshalled)
} else {
printIntf = activatingConfig
}
log.Info("Activating new precompile", "name", module.ConfigKey, "config", printIntf)
// Set the nonce of the precompile's address (as is done when a contract is created) to ensure
// that it is marked as non-empty and will not be cleaned up when the statedb is finalized.
statedb.SetNonce(module.Address, 1, tracing.NonceChangeUnspecified)
// Set the code of the precompile's address to a non-zero length byte slice to ensure that the precompile
// can be called from within Solidity contracts. Solidity adds a check before invoking a contract to ensure
// that it does not attempt to invoke a non-existent contract.
_ = statedb.SetCode(module.Address, []byte{0x1}, tracing.CodeChangeUnspecified)
extstatedb := extstate.New(statedb)
adapter := extstate.NewPrecompileAdapter(extstatedb)
if err := module.Configure(params.GetExtra(c), activatingConfig, adapter, blockContext); err != nil {
return fmt.Errorf("could not configure precompile, name: %s, reason: %w", module.ConfigKey, err)
}
}
}
return nil
}
// applyStateUpgrades checks if any of the state upgrades specified by the chain config are activated by the block
// transition from [parentTimestamp] to the timestamp set in [header]. If this is the case, it calls [Configure]
// to apply the necessary state transitions for the upgrade.
func applyStateUpgrades(c *params.ChainConfig, parentTimestamp *uint64, blockContext contract.ConfigurationBlockContext, statedb *state.StateDB) error {
// Apply state upgrades
configExtra := params.GetExtra(c)
for _, upgrade := range configExtra.GetActivatingStateUpgrades(parentTimestamp, blockContext.Timestamp(), configExtra.StateUpgrades) {
log.Info("Applying state upgrade", "blockNumber", blockContext.Number(), "upgrade", upgrade)
adapter := state.NewStateUpgradeAdapter(statedb)
if err := stateupgrade.Configure(&upgrade, c, adapter, blockContext); err != nil {
return fmt.Errorf("could not configure state upgrade: %w", err)
}
}
return nil
}
// ApplyUpgrades checks if any of the precompile or state upgrades specified by the chain config are activated by the block
// transition from [parentTimestamp] to the timestamp set in [header]. If this is the case, it calls [Configure]
// to apply the necessary state transitions for the upgrade.
// This function is called:
// - in block processing to update the state when processing a block.
// - in the miner to apply the state upgrades when producing a block.
func ApplyUpgrades(c *params.ChainConfig, parentTimestamp *uint64, blockContext contract.ConfigurationBlockContext, statedb *state.StateDB) error {
if err := ApplyPrecompileActivations(c, parentTimestamp, blockContext, statedb); err != nil {
return err
}
return applyStateUpgrades(c, parentTimestamp, blockContext, statedb)
}
// BlockContext implements [contract.ConfigurationBlockContext].
type BlockContext struct {
number *big.Int
timestamp uint64
}
// NewBlockContext creates a [BlockContext] using the block number
// and block timestamp provided. This function is usually necessary to convert
// a `*types.Block` to be passed as a [contract.ConfigurationBlockContext]
// to [ApplyUpgrades].
func NewBlockContext(number *big.Int, timestamp uint64) *BlockContext {
return &BlockContext{
number: number,
timestamp: timestamp,
}
}
func (bc *BlockContext) Number() *big.Int { return bc.number }
func (bc *BlockContext) Timestamp() uint64 { return bc.timestamp }