mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +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 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. 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; TestRulesExtraUnderConcurrentEvaluation reads four rule sets on four goroutines at once, across two times and two chains, including the intrinsic gas of a creation. TestActivePrecompiles, skipped for this bug, runs again. Co-authored-by: Hanzo Dev <[email protected]>
483 lines
15 KiB
Go
483 lines
15 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/evm/params/extras"
|
|
"github.com/luxfi/evm/precompile/modules"
|
|
"github.com/luxfi/evm/precompile/precompileconfig"
|
|
"github.com/luxfi/evm/utils"
|
|
"github.com/luxfi/geth/common"
|
|
ethparams "github.com/luxfi/geth/params"
|
|
"github.com/luxfi/upgrade"
|
|
)
|
|
|
|
const (
|
|
maxJSONLen = 64 * 1024 * 1024 // 64MB
|
|
|
|
// IsMergeTODO is the default merge flag passed to geth's Rules when the
|
|
// calling context does not have an explicit merge status. Lux treats
|
|
// all chains as post-merge. (Analogous 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.
|
|
//
|
|
// Prior to Cancun, Lux upgrades are referenced inline in place of their
|
|
// Ethereum counterparts. The original Ethereum fork names should be restored
|
|
// for long-term 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.
|
|
// This custom MarshalJSON exists because the embedded ChainConfig has its own
|
|
// MarshalJSON which shadows UpgradeConfig. An external JSON tag on the embedded
|
|
// struct would remove the need for this override.
|
|
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) {
|
|
// NetworkUpgrades are not available via the consensus context; use empty defaults.
|
|
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
|
|
}
|
|
}
|
|
|
|
// System precompiles (the DEX settlement money path 0x9999) are AlwaysOn: a
|
|
// system precompile activated by the Lux protocol on every chain with no per-net
|
|
// config. They take no per-net parameters and resolve everything at runtime (consensus
|
|
// context / atomic state), so there is nothing to seed into genesisPrecompiles or
|
|
// precompileUpgrades. They enter the enabled dispatch set at their protocol
|
|
// ActivationTime (a fork-like timestamp constant), consistent with the EXTCODESIZE
|
|
// marker installed at the same crossing transition by ApplyPrecompileActivations: a
|
|
// precompile that is not yet present in state (EXTCODESIZE==0) must not dispatch, and
|
|
// one that is present must. This is the set PrecompileOverride consults; the decision
|
|
// is a pure function of the passed-in timestamp (no global read), so it is race-safe
|
|
// and identical on every replay of a given block.
|
|
for _, module := range modules.AlwaysOnModules() {
|
|
if module.ActivationTime > timestamp {
|
|
continue // not yet activated at this timestamp
|
|
}
|
|
rules.Precompiles[module.Address] = module.Configurator.MakeGenesisConfig()
|
|
}
|
|
|
|
return rules
|
|
}
|