mirror of
https://github.com/luxfi/evmgpu.git
synced 2026-10-30 16:57:11 +00:00
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]>
465 lines
14 KiB
Go
465 lines
14 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package params
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"math/big"
|
|
"sync"
|
|
|
|
"github.com/luxfi/evmgpu/params/extras"
|
|
"github.com/luxfi/evmgpu/precompile/modules"
|
|
"github.com/luxfi/evmgpu/precompile/precompileconfig"
|
|
"github.com/luxfi/evmgpu/utils"
|
|
"github.com/luxfi/geth/common"
|
|
ethparams "github.com/luxfi/geth/params"
|
|
"github.com/luxfi/upgrade"
|
|
)
|
|
|
|
const (
|
|
maxJSONLen = 64 * 1024 * 1024 // 64MB
|
|
|
|
// TODO: Value to pass to geth's Rules by default where the appropriate
|
|
// context is not available in the lux code. (similar to context.TODO())
|
|
IsMergeTODO = true
|
|
)
|
|
|
|
var (
|
|
DefaultChainID = big.NewInt(43214)
|
|
DefaultFeeConfig = extras.DefaultFeeConfig
|
|
|
|
// Use the constants from extras package
|
|
initiallyActive = uint64(extras.InitiallyActiveTime.Unix())
|
|
unscheduledActivation = uint64(extras.UnscheduledActivationTime.Unix())
|
|
|
|
// Simple map-based replacement for libevm payloads system
|
|
chainConfigExtras = make(map[*ChainConfig]*extras.ChainConfig)
|
|
chainConfigMutex sync.RWMutex
|
|
)
|
|
|
|
// rulesContext is what a Rules value carries in its Payload: the chain config
|
|
// and timestamp it was evaluated at, which is what its Lux rules follow from.
|
|
// core's rules hook gives every Rules that ChainConfig.Rules makes a Payload
|
|
// that answers it, so each Rules names its own and GetRulesExtra reads nothing
|
|
// that another evaluation can change.
|
|
type rulesContext interface {
|
|
RulesContext() (*ChainConfig, uint64)
|
|
}
|
|
|
|
// evaluatedAt is the rulesContext RulesAt attaches to a Rules that carries none:
|
|
// one made where core's rules hook is not linked.
|
|
type evaluatedAt struct {
|
|
config *ChainConfig
|
|
timestamp uint64
|
|
}
|
|
|
|
func (e evaluatedAt) RulesContext() (*ChainConfig, uint64) { return e.config, e.timestamp }
|
|
|
|
// getPrecompileAddress returns the address for a precompile config
|
|
func getPrecompileAddress(config precompileconfig.Config) common.Address {
|
|
// Get all registered modules
|
|
for _, module := range modules.RegisteredModules() {
|
|
// Match by config key
|
|
if module.ConfigKey == config.Key() {
|
|
return module.Address
|
|
}
|
|
}
|
|
return common.Address{}
|
|
}
|
|
|
|
// RulesExtra represents extra EVM rules - part of libevm integration
|
|
type RulesExtra struct {
|
|
IsEVM bool
|
|
IsDurango bool
|
|
IsQuasar bool
|
|
IsFortuna bool
|
|
IsGranite bool
|
|
|
|
// Fields for predicate support
|
|
PredicatersExist bool
|
|
Predicaters map[common.Address]precompileconfig.Predicater
|
|
|
|
// LuxRules for header verification
|
|
LuxRules extras.LuxRules
|
|
|
|
// Active precompiles for this rule set
|
|
activePrecompiles map[common.Address]bool
|
|
}
|
|
|
|
// IsPrecompileEnabled checks if a precompile is enabled
|
|
func (r RulesExtra) IsPrecompileEnabled(addr common.Address) bool {
|
|
if r.activePrecompiles == nil {
|
|
return false
|
|
}
|
|
return r.activePrecompiles[addr]
|
|
}
|
|
|
|
// SetEthUpgrades enables Ethereum network upgrades using the same time as
|
|
// the Lux network upgrade that enables them.
|
|
//
|
|
// TODO: Prior to Cancun, Lux upgrades are referenced inline in the
|
|
// code in place of their Ethereum counterparts. The original Ethereum names
|
|
// should be restored for maintainability.
|
|
func SetEthUpgrades(c *ChainConfig) error {
|
|
if c.HomesteadBlock == nil {
|
|
c.HomesteadBlock = big.NewInt(0)
|
|
}
|
|
if c.EIP150Block == nil {
|
|
c.EIP150Block = big.NewInt(0)
|
|
}
|
|
if c.EIP155Block == nil {
|
|
c.EIP155Block = big.NewInt(0)
|
|
}
|
|
if c.EIP158Block == nil {
|
|
c.EIP158Block = big.NewInt(0)
|
|
}
|
|
if c.ByzantiumBlock == nil {
|
|
c.ByzantiumBlock = big.NewInt(0)
|
|
}
|
|
if c.ConstantinopleBlock == nil {
|
|
c.ConstantinopleBlock = big.NewInt(0)
|
|
}
|
|
if c.PetersburgBlock == nil {
|
|
c.PetersburgBlock = big.NewInt(0)
|
|
}
|
|
if c.IstanbulBlock == nil {
|
|
c.IstanbulBlock = big.NewInt(0)
|
|
}
|
|
if c.MuirGlacierBlock == nil {
|
|
c.MuirGlacierBlock = big.NewInt(0)
|
|
}
|
|
if c.BerlinBlock == nil {
|
|
c.BerlinBlock = big.NewInt(0)
|
|
}
|
|
if c.LondonBlock == nil {
|
|
c.LondonBlock = big.NewInt(0)
|
|
}
|
|
|
|
extra := GetExtra(c)
|
|
// We only mark Eth upgrades as enabled if we have marked them as scheduled.
|
|
if durango := extra.DurangoTimestamp; durango != nil && *durango < unscheduledActivation {
|
|
c.ShanghaiTime = utils.NewUint64(*durango)
|
|
}
|
|
|
|
if quasar := extra.QuasarTimestamp; quasar != nil && *quasar < unscheduledActivation {
|
|
c.CancunTime = utils.NewUint64(*quasar)
|
|
// Cancun requires BlobScheduleConfig for CalcExcessBlobGas in block building.
|
|
// Without this, latestBlobConfig() returns nil causing a nil pointer panic.
|
|
if c.BlobScheduleConfig == nil {
|
|
c.BlobScheduleConfig = ðparams.BlobScheduleConfig{
|
|
Cancun: ethparams.DefaultCancunBlobConfig,
|
|
}
|
|
} else if c.BlobScheduleConfig.Cancun == nil {
|
|
c.BlobScheduleConfig.Cancun = ethparams.DefaultCancunBlobConfig
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func GetExtra(c *ChainConfig) *extras.ChainConfig {
|
|
chainConfigMutex.RLock()
|
|
ex, ok := chainConfigExtras[c]
|
|
chainConfigMutex.RUnlock()
|
|
|
|
if !ok || ex == nil {
|
|
chainConfigMutex.Lock()
|
|
// Double-check after acquiring write lock
|
|
ex, ok = chainConfigExtras[c]
|
|
if !ok || ex == nil {
|
|
ex = &extras.ChainConfig{}
|
|
chainConfigExtras[c] = ex
|
|
}
|
|
chainConfigMutex.Unlock()
|
|
}
|
|
return ex
|
|
}
|
|
|
|
// Copy creates a deep copy of the ChainConfig and its extras.
|
|
// Returns a pointer to ensure the extras mapping remains valid.
|
|
func Copy(c *ChainConfig) *ChainConfig {
|
|
cpy := *c
|
|
extraCpy := *GetExtra(c)
|
|
result := &cpy
|
|
WithExtra(result, &extraCpy)
|
|
return result
|
|
}
|
|
|
|
// WithExtra sets the extra payload on `c` and returns the modified argument.
|
|
func WithExtra(c *ChainConfig, extra *extras.ChainConfig) *ChainConfig {
|
|
chainConfigMutex.Lock()
|
|
chainConfigExtras[c] = extra
|
|
chainConfigMutex.Unlock()
|
|
return c
|
|
}
|
|
|
|
// SetExtra updates the ChainConfig extra payload
|
|
func SetExtra(c *ChainConfig, extra *extras.ChainConfig) {
|
|
chainConfigMutex.Lock()
|
|
chainConfigExtras[c] = extra
|
|
chainConfigMutex.Unlock()
|
|
}
|
|
|
|
type ChainConfigWithUpgradesJSON struct {
|
|
ChainConfig
|
|
UpgradeConfig extras.UpgradeConfig `json:"upgrades,omitempty"`
|
|
}
|
|
|
|
// MarshalJSON implements json.Marshaler. This is a workaround for the fact that
|
|
// the embedded ChainConfig struct has a MarshalJSON method, which prevents
|
|
// the default JSON marshalling from working for UpgradeConfig.
|
|
// TODO: consider removing this method by allowing external tag for the embedded
|
|
// ChainConfig struct.
|
|
func (cu *ChainConfigWithUpgradesJSON) MarshalJSON() ([]byte, error) {
|
|
// First get the extras
|
|
extra := GetExtra(&cu.ChainConfig)
|
|
// fmt.Printf("DEBUG ChainConfigWithUpgradesJSON.MarshalJSON: extra=%+v\n", extra)
|
|
// fmt.Printf("DEBUG extra.FeeConfig: %+v\n", extra.FeeConfig)
|
|
|
|
// Marshal the base ChainConfig
|
|
baseJSON, err := json.Marshal(&cu.ChainConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Marshal the extras
|
|
extraJSON, err := extra.MarshalJSON()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Debug
|
|
// fmt.Printf("DEBUG: Base JSON: %s\n", string(baseJSON))
|
|
// fmt.Printf("DEBUG: Extra JSON: %s\n", string(extraJSON))
|
|
|
|
// Parse both as maps to merge
|
|
var baseMap map[string]json.RawMessage
|
|
if err := json.Unmarshal(baseJSON, &baseMap); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var extraMap map[string]json.RawMessage
|
|
if err := json.Unmarshal(extraJSON, &extraMap); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Merge extras into base - overwrite any conflicting keys with extras values
|
|
for k, v := range extraMap {
|
|
baseMap[k] = v
|
|
}
|
|
|
|
// Now marshal the merged config
|
|
chainConfigJSON, err := json.Marshal(baseMap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(chainConfigJSON) > maxJSONLen {
|
|
return nil, errors.New("value too large")
|
|
}
|
|
|
|
type upgrades struct {
|
|
UpgradeConfig extras.UpgradeConfig `json:"upgrades"`
|
|
}
|
|
|
|
upgradeJSON, err := json.Marshal(upgrades{cu.UpgradeConfig})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(upgradeJSON) > maxJSONLen {
|
|
return nil, errors.New("value too large")
|
|
}
|
|
|
|
// merge the two JSON objects
|
|
mergedJSON := make([]byte, 0, len(chainConfigJSON)+len(upgradeJSON)+1)
|
|
mergedJSON = append(mergedJSON, chainConfigJSON[:len(chainConfigJSON)-1]...)
|
|
mergedJSON = append(mergedJSON, ',')
|
|
mergedJSON = append(mergedJSON, upgradeJSON[1:]...)
|
|
return mergedJSON, nil
|
|
}
|
|
|
|
func (cu *ChainConfigWithUpgradesJSON) UnmarshalJSON(input []byte) error {
|
|
var cc ChainConfig
|
|
if err := json.Unmarshal(input, &cc); err != nil {
|
|
return err
|
|
}
|
|
|
|
type upgrades struct {
|
|
UpgradeConfig extras.UpgradeConfig `json:"upgrades"`
|
|
}
|
|
|
|
var u upgrades
|
|
if err := json.Unmarshal(input, &u); err != nil {
|
|
return err
|
|
}
|
|
cu.ChainConfig = cc
|
|
cu.UpgradeConfig = u.UpgradeConfig
|
|
return nil
|
|
}
|
|
|
|
// ToWithUpgradesJSON converts the ChainConfig to ChainConfigWithUpgradesJSON with upgrades explicitly displayed.
|
|
// ChainConfig does not include upgrades in its JSON output.
|
|
// This is a workaround for showing upgrades in the JSON output.
|
|
func ToWithUpgradesJSON(c *ChainConfig) *ChainConfigWithUpgradesJSON {
|
|
return &ChainConfigWithUpgradesJSON{
|
|
ChainConfig: *c,
|
|
UpgradeConfig: GetExtra(c).UpgradeConfig,
|
|
}
|
|
}
|
|
|
|
func SetNetworkUpgradeDefaults(c *ChainConfig) {
|
|
// TODO: NetworkUpgrades field not available in current context.Context
|
|
// GetExtra(c).NetworkUpgrades.SetDefaults(GetExtra(c).ConsensusCtx.NetworkUpgrades)
|
|
// For now, set empty defaults with empty upgrade config
|
|
emptyUpgradeConfig := upgrade.Config{}
|
|
GetExtra(c).SetDefaults(emptyUpgradeConfig)
|
|
}
|
|
|
|
// GetRulesExtra returns the RulesExtra for the given Rules: the Lux rules of
|
|
// the chain config and timestamp those Rules were evaluated at, which they
|
|
// carry in their Payload. Rules that carry none get the default RulesExtra.
|
|
func GetRulesExtra(rules Rules) RulesExtra {
|
|
var (
|
|
chainConfig *ChainConfig
|
|
timestamp uint64
|
|
)
|
|
if rc, ok := rules.Payload.(rulesContext); ok {
|
|
chainConfig, timestamp = rc.RulesContext()
|
|
}
|
|
|
|
if chainConfig == nil {
|
|
// No context found, return default RulesExtra
|
|
rulesExtra := RulesExtra{
|
|
IsEVM: true,
|
|
IsDurango: true,
|
|
IsQuasar: true,
|
|
IsFortuna: true,
|
|
IsGranite: true,
|
|
PredicatersExist: false,
|
|
Predicaters: make(map[common.Address]precompileconfig.Predicater),
|
|
}
|
|
rulesExtra.LuxRules = extras.LuxRules{
|
|
IsEVM: true,
|
|
IsDurango: true,
|
|
IsQuasar: true,
|
|
IsFortuna: true,
|
|
IsGranite: true,
|
|
}
|
|
rulesExtra.activePrecompiles = make(map[common.Address]bool)
|
|
return rulesExtra
|
|
}
|
|
|
|
// Get the extras rules for the chain config and timestamp
|
|
extrasRules := GetExtrasRules(rules, chainConfig, timestamp)
|
|
|
|
// Build RulesExtra from extras.Rules
|
|
rulesExtra := RulesExtra{
|
|
IsEVM: extrasRules.IsEVM,
|
|
IsDurango: extrasRules.IsDurango,
|
|
IsQuasar: extrasRules.IsQuasar,
|
|
IsFortuna: extrasRules.IsFortuna,
|
|
IsGranite: extrasRules.IsGranite,
|
|
PredicatersExist: len(extrasRules.Predicaters) > 0,
|
|
Predicaters: extrasRules.Predicaters,
|
|
LuxRules: extrasRules.LuxRules,
|
|
activePrecompiles: make(map[common.Address]bool),
|
|
}
|
|
|
|
// Populate active precompiles
|
|
for addr, config := range extrasRules.Precompiles {
|
|
if !config.IsDisabled() {
|
|
rulesExtra.activePrecompiles[addr] = true
|
|
}
|
|
}
|
|
|
|
return rulesExtra
|
|
}
|
|
|
|
// GetExtrasRules returns the extras.Rules for the given params.Rules and timestamp
|
|
func GetExtrasRules(ethRules Rules, c *ChainConfig, timestamp uint64) *extras.Rules {
|
|
if c == nil {
|
|
return &extras.Rules{
|
|
LuxRules: extras.LuxRules{},
|
|
Precompiles: make(map[common.Address]precompileconfig.Config),
|
|
Predicaters: make(map[common.Address]precompileconfig.Predicater),
|
|
AccepterPrecompiles: make(map[common.Address]precompileconfig.Accepter),
|
|
}
|
|
}
|
|
|
|
extra := GetExtra(c)
|
|
luxRules := extra.GetLuxRules(timestamp)
|
|
|
|
// Build extras.Rules
|
|
rules := &extras.Rules{
|
|
LuxRules: luxRules,
|
|
Precompiles: make(map[common.Address]precompileconfig.Config),
|
|
Predicaters: make(map[common.Address]precompileconfig.Predicater),
|
|
AccepterPrecompiles: make(map[common.Address]precompileconfig.Accepter),
|
|
}
|
|
|
|
// Track which precompiles have been disabled by upgrades
|
|
disabledByUpgrade := make(map[string]bool)
|
|
|
|
// Add active precompiles based on upgrades
|
|
for _, upgrade := range extra.PrecompileUpgrades {
|
|
if upgrade.Timestamp() != nil && *upgrade.Timestamp() <= timestamp {
|
|
// Get address from the registry based on the config
|
|
address := getPrecompileAddress(upgrade.Config)
|
|
if address == (common.Address{}) {
|
|
continue // Skip if no address found
|
|
}
|
|
|
|
if upgrade.IsDisabled() {
|
|
delete(rules.Precompiles, address)
|
|
delete(rules.Predicaters, address)
|
|
delete(rules.AccepterPrecompiles, address)
|
|
// Track that this precompile was disabled
|
|
disabledByUpgrade[upgrade.Config.Key()] = true
|
|
} else {
|
|
rules.Precompiles[address] = upgrade.Config
|
|
if predicater, ok := upgrade.Config.(precompileconfig.Predicater); ok {
|
|
rules.Predicaters[address] = predicater
|
|
}
|
|
if accepter, ok := upgrade.Config.(precompileconfig.Accepter); ok {
|
|
rules.AccepterPrecompiles[address] = accepter
|
|
}
|
|
// If re-enabled, remove from disabled tracking
|
|
disabledByUpgrade[upgrade.Config.Key()] = false
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add genesis precompiles if they are active at this timestamp
|
|
// A genesis precompile is active if its Timestamp() <= timestamp and it's not disabled
|
|
for key, config := range extra.GenesisPrecompiles {
|
|
if config.IsDisabled() {
|
|
continue
|
|
}
|
|
// Check if the precompile was disabled by an upgrade
|
|
if disabledByUpgrade[key] {
|
|
continue // Skip - an upgrade disabled this genesis precompile
|
|
}
|
|
// Check if the precompile is active at this timestamp
|
|
configTs := config.Timestamp()
|
|
if configTs == nil || *configTs > timestamp {
|
|
continue // Not yet active
|
|
}
|
|
// Get address from the key
|
|
module, ok := modules.GetPrecompileModule(key)
|
|
if !ok {
|
|
continue // Skip unknown precompiles
|
|
}
|
|
address := module.Address
|
|
|
|
rules.Precompiles[address] = config
|
|
if predicater, ok := config.(precompileconfig.Predicater); ok {
|
|
rules.Predicaters[address] = predicater
|
|
}
|
|
if accepter, ok := config.(precompileconfig.Accepter); ok {
|
|
rules.AccepterPrecompiles[address] = accepter
|
|
}
|
|
}
|
|
|
|
return rules
|
|
}
|