Files
evmgpu/core/precompile_overrider.go
zeekayandHanzo Dev b3585e9b1d Each Rules carries the context its Lux rules follow from
GetRulesExtra derived a Rules' Lux rules from a process-wide last context:
the chain config and timestamp of whichever Rules any goroutine evaluated
most recently. Rules are evaluated concurrently, by block processing, the
miner, the tx pool and every eth_call, so a Rules could read another
evaluation's upgrades: its IsDurango, its predicaters, its enabled
precompiles, and the intrinsic gas that depends on them. The precompile
overrider read the same global when the EVM asked it for a precompile, so
which Lux precompiles a call reached could follow another goroutine's
block.

The context now travels with the Rules value. core's rules hook already put
the chain config and timestamp in Rules.Payload for its precompile
overrider; the overrider answers them, and GetRulesExtra reads them from
the Rules it is given. The overrider decides its own enabled set from them
too. RulesAt attaches them where no hook did. The global and
SetRulesContext are gone. Every call site evaluates its Rules for the block
it reads them for, so a caller that no other goroutine races sees the same
rules as before. The blob pool's state checks passed a zero Rules and read
the head's context the check before them had left; they now pass the
head's Rules.

TestRulesExtraIsTheRulesOwn evaluates two rule sets and reads the first;
TestPrecompileOverrideIsTheRulesOwn asks each one's overrider for the tx
allow list after the other was evaluated, in both orders;
TestRulesExtraUnderConcurrentEvaluation reads four rule sets on four
goroutines at once, across two times and two chains, including the
intrinsic gas of a creation and the precompile a call reaches.
TestActivePrecompiles, skipped for this bug, runs again.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-23 12:15:06 -07:00

248 lines
7.5 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package core
import (
"context"
"math/big"
"github.com/holiman/uint256"
"github.com/luxfi/evmgpu/commontype"
"github.com/luxfi/evmgpu/params"
"github.com/luxfi/evmgpu/precompile/contract"
"github.com/luxfi/evmgpu/precompile/modules"
"github.com/luxfi/evmgpu/precompile/precompileconfig"
"github.com/luxfi/geth/common"
"github.com/luxfi/geth/core/tracing"
"github.com/luxfi/geth/core/types"
"github.com/luxfi/geth/core/vm"
gethparams "github.com/luxfi/geth/params"
)
func init() {
// Register the rules hook to populate Rules.Payload with our PrecompileOverrider
gethparams.SetRulesHook(precompileHook)
}
// precompileHook populates Rules.Payload with the LuxPrecompileOverrider,
// which carries the chain config and timestamp these Rules were evaluated at:
// params.GetRulesExtra reads them from there.
func precompileHook(c *gethparams.ChainConfig, rules *gethparams.Rules, num *big.Int, isMerge bool, timestamp uint64) {
rules.Payload = &LuxPrecompileOverrider{
chainConfig: c,
timestamp: timestamp,
}
}
// LuxPrecompileOverrider implements vm.PrecompileOverrider to provide
// custom Lux precompiles (warp, nativeminter, etc.) to the EVM.
type LuxPrecompileOverrider struct {
chainConfig *gethparams.ChainConfig
timestamp uint64
}
// RulesContext is the chain config and timestamp the Rules carrying this
// overrider were evaluated at, which params.GetRulesExtra derives their Lux
// rules from.
func (o *LuxPrecompileOverrider) RulesContext() (*gethparams.ChainConfig, uint64) {
return o.chainConfig, o.timestamp
}
// PrecompileOverride returns the precompile at the given address if it's
// an active Lux custom precompile.
//
// The enabled set is computed from this overrider's own chain config and
// timestamp, set in precompileHook when the EVM's Rules were evaluated. The EVM
// asks lazily, when a CALL targets the address, and other goroutines evaluate
// Rules for other chains and times in between; deciding from o.timestamp makes
// every replay of a given block see the same enabled set. params.ChainConfig is
// a type alias of geth's ChainConfig, so o.chainConfig is passed directly.
func (o *LuxPrecompileOverrider) PrecompileOverride(addr common.Address) (vm.PrecompiledContract, bool) {
extrasRules := params.GetExtrasRules(gethparams.Rules{}, o.chainConfig, o.timestamp)
if cfg, ok := extrasRules.Precompiles[addr]; !ok || cfg.IsDisabled() {
return nil, false
}
// Find the module for this address
for _, module := range modules.RegisteredModules() {
if module.Address == addr && module.Contract != nil {
// Wrap the contract in our adapter
return &precompileAdapter{
address: addr,
contract: module.Contract,
}, true
}
}
return nil, false
}
// precompileAdapter wraps a contract.StatefulPrecompiledContract to implement
// geth's vm.StatefulPrecompiledContract interface.
type precompileAdapter struct {
address common.Address
contract contract.StatefulPrecompiledContract
}
// Name returns the precompile name
func (p *precompileAdapter) Name() string {
// Get the config key for this address
for _, module := range modules.RegisteredModules() {
if module.Address == p.address {
return module.ConfigKey
}
}
return "unknown"
}
// RequiredGas returns the gas required for this precompile
func (p *precompileAdapter) RequiredGas(input []byte) uint64 {
// For stateful precompiles, gas is calculated in RunStateful
return 0
}
// Run implements PrecompiledContract.Run (stateless - not used for stateful precompiles)
func (p *precompileAdapter) Run(input []byte) ([]byte, error) {
// Stateful precompiles use RunStateful instead
return nil, vm.ErrExecutionReverted
}
// RunStateful implements StatefulPrecompiledContract.RunStateful
func (p *precompileAdapter) RunStateful(env vm.PrecompileEnvironment, input []byte, suppliedGas uint64) ([]byte, uint64, error) {
// Create an AccessibleState adapter
accessibleState := &accessibleStateAdapter{env: env}
// Get caller and self addresses from environment
addresses := env.Addresses()
// Call the inner precompile
ret, remainingGas, err := p.contract.Run(
accessibleState,
addresses.Caller,
addresses.Self,
input,
suppliedGas,
env.ReadOnly(),
)
return ret, remainingGas, err
}
// accessibleStateAdapter adapts vm.PrecompileEnvironment to contract.AccessibleState
type accessibleStateAdapter struct {
env vm.PrecompileEnvironment
}
func (a *accessibleStateAdapter) GetStateDB() contract.StateDB {
return &stateDBAdapter{stateDB: a.env.StateDB()}
}
func (a *accessibleStateAdapter) GetBlockContext() contract.BlockContext {
return &blockContextAdapter{env: a.env}
}
func (a *accessibleStateAdapter) GetConsensusContext() context.Context {
return a.env.ConsensusContext()
}
func (a *accessibleStateAdapter) GetChainConfig() precompileconfig.ChainConfig {
return &chainConfigAdapter{config: a.env.ChainConfig()}
}
// stateDBAdapter adapts vm.StateDB to contract.StateDB
type stateDBAdapter struct {
stateDB vm.StateDB
}
func (s *stateDBAdapter) GetState(addr common.Address, hash common.Hash) common.Hash {
return s.stateDB.GetState(addr, hash)
}
func (s *stateDBAdapter) SetState(addr common.Address, key, value common.Hash) {
s.stateDB.SetState(addr, key, value)
}
func (s *stateDBAdapter) SetNonce(addr common.Address, nonce uint64) {
s.stateDB.SetNonce(addr, nonce, tracing.NonceChangeUnspecified)
}
func (s *stateDBAdapter) GetNonce(addr common.Address) uint64 {
return s.stateDB.GetNonce(addr)
}
func (s *stateDBAdapter) GetBalance(addr common.Address) *uint256.Int {
return s.stateDB.GetBalance(addr)
}
func (s *stateDBAdapter) AddBalance(addr common.Address, amount *uint256.Int) {
s.stateDB.AddBalance(addr, amount, tracing.BalanceChangeUnspecified)
}
func (s *stateDBAdapter) CreateAccount(addr common.Address) {
s.stateDB.CreateAccount(addr)
}
func (s *stateDBAdapter) Exist(addr common.Address) bool {
return s.stateDB.Exist(addr)
}
func (s *stateDBAdapter) AddLog(log *types.Log) {
s.stateDB.AddLog(log)
}
func (s *stateDBAdapter) GetPredicateStorageSlots(addr common.Address, index int) ([]byte, bool) {
// This requires accessing the predicate storage which is not exposed in the standard StateDB
// For now, return empty - this is only used for advanced predicates
return nil, false
}
func (s *stateDBAdapter) GetTxHash() common.Hash {
return s.stateDB.TxHash()
}
func (s *stateDBAdapter) Snapshot() int {
return s.stateDB.Snapshot()
}
func (s *stateDBAdapter) RevertToSnapshot(id int) {
s.stateDB.RevertToSnapshot(id)
}
// blockContextAdapter adapts vm.PrecompileEnvironment to contract.BlockContext
type blockContextAdapter struct {
env vm.PrecompileEnvironment
}
func (b *blockContextAdapter) Number() *big.Int {
return b.env.BlockNumber()
}
func (b *blockContextAdapter) Timestamp() uint64 {
return b.env.BlockTime()
}
func (b *blockContextAdapter) GetPredicateResults(txHash common.Hash, precompileAddress common.Address) []byte {
return nil // Not needed for standard precompiles
}
// chainConfigAdapter adapts geth's ChainConfig to precompileconfig.ChainConfig
type chainConfigAdapter struct {
config *gethparams.ChainConfig
}
func (c *chainConfigAdapter) GetFeeConfig() commontype.FeeConfig {
extra := params.GetExtra(c.config)
return extra.FeeConfig
}
func (c *chainConfigAdapter) AllowedFeeRecipients() bool {
extra := params.GetExtra(c.config)
return extra.AllowFeeRecipients
}
func (c *chainConfigAdapter) IsDurango(time uint64) bool {
extra := params.GetExtra(c.config)
return extra.IsDurango(time)
}