mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
- Rename plugin/evm/upgrade/subnetevm package to feewindow - Rename SubnetEVMTimestamp to EVMTimestamp (JSON: evmTimestamp) - Rename IsSubnetEVM method to IsEVM - Rename SubnetEVMDefaultChainConfig to EVMDefaultChainConfig - Rename TestSubnetEVMChainConfig to TestEVMChainConfig - Rename TestPreSubnetEVMChainConfig to TestPreEVMChainConfig - Rename ethclient/subnetevmclient to ethclient/evmclient - Update VM factory ID from "subnetevm" to "evm" - Remove all backward compatibility aliases - Update all documentation and comments Breaking change: This removes all SubnetEVM naming - only "EVM" is supported. No backward compatibility maintained as per user requirement.
411 lines
15 KiB
Go
411 lines
15 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package extras
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/big"
|
|
"time"
|
|
|
|
"github.com/luxfi/evm/commontype"
|
|
"github.com/luxfi/evm/utils"
|
|
"github.com/luxfi/geth/common"
|
|
ethparams "github.com/luxfi/geth/params"
|
|
"github.com/luxfi/upgrade"
|
|
)
|
|
|
|
var (
|
|
// TODO: UnscheduledActivationTime seems to be removed from upgrade package
|
|
UnscheduledActivationTime = time.Unix(1<<63-1, 0) // Max time value
|
|
InitiallyActiveTime = time.Unix(0, 0) // Unix epoch
|
|
|
|
DefaultFeeConfig = commontype.FeeConfig{
|
|
GasLimit: big.NewInt(8_000_000),
|
|
TargetBlockRate: 2, // in seconds
|
|
|
|
MinBaseFee: big.NewInt(25_000_000_000),
|
|
TargetGas: big.NewInt(15_000_000),
|
|
BaseFeeChangeDenominator: big.NewInt(36),
|
|
|
|
MinBlockGasCost: big.NewInt(0),
|
|
MaxBlockGasCost: big.NewInt(1_000_000),
|
|
BlockGasCostStep: big.NewInt(200_000),
|
|
}
|
|
|
|
// TestFeeConfig uses minimal fees for backward compatibility with tests
|
|
TestFeeConfig = commontype.FeeConfig{
|
|
GasLimit: big.NewInt(8_000_000),
|
|
TargetBlockRate: 2,
|
|
|
|
MinBaseFee: big.NewInt(1), // 1 wei for test compatibility
|
|
TargetGas: big.NewInt(15_000_000),
|
|
BaseFeeChangeDenominator: big.NewInt(36),
|
|
|
|
MinBlockGasCost: big.NewInt(0),
|
|
MaxBlockGasCost: big.NewInt(1_000_000),
|
|
BlockGasCostStep: big.NewInt(200_000),
|
|
}
|
|
|
|
EVMDefaultChainConfig = &ChainConfig{
|
|
FeeConfig: DefaultFeeConfig,
|
|
NetworkUpgrades: GetDefaultNetworkUpgrades(), // TODO: upgrade.GetConfig seems to be removed
|
|
GenesisPrecompiles: Precompiles{},
|
|
}
|
|
|
|
TestChainConfig = &ChainConfig{
|
|
FeeConfig: TestFeeConfig, // Use TestFeeConfig with MinBaseFee=1 for test compatibility
|
|
NetworkUpgrades: GetDefaultNetworkUpgrades(),
|
|
GenesisPrecompiles: Precompiles{},
|
|
}
|
|
|
|
TestPreEVMChainConfig = copyAndSet(TestChainConfig, func(c *ChainConfig) {
|
|
c.NetworkUpgrades = NetworkUpgrades{
|
|
EVMTimestamp: utils.TimeToNewUint64(UnscheduledActivationTime),
|
|
DurangoTimestamp: utils.TimeToNewUint64(UnscheduledActivationTime),
|
|
EtnaTimestamp: utils.TimeToNewUint64(UnscheduledActivationTime),
|
|
FortunaTimestamp: utils.TimeToNewUint64(UnscheduledActivationTime),
|
|
}
|
|
})
|
|
|
|
TestEVMChainConfig = copyAndSet(TestChainConfig, func(c *ChainConfig) {
|
|
c.NetworkUpgrades = NetworkUpgrades{
|
|
EVMTimestamp: utils.NewUint64(0),
|
|
DurangoTimestamp: utils.TimeToNewUint64(UnscheduledActivationTime),
|
|
EtnaTimestamp: utils.TimeToNewUint64(UnscheduledActivationTime),
|
|
FortunaTimestamp: utils.TimeToNewUint64(UnscheduledActivationTime),
|
|
}
|
|
})
|
|
|
|
TestDurangoChainConfig = copyAndSet(TestChainConfig, func(c *ChainConfig) {
|
|
c.NetworkUpgrades = NetworkUpgrades{
|
|
EVMTimestamp: utils.NewUint64(0),
|
|
DurangoTimestamp: utils.TimeToNewUint64(InitiallyActiveTime),
|
|
EtnaTimestamp: utils.TimeToNewUint64(UnscheduledActivationTime),
|
|
FortunaTimestamp: utils.TimeToNewUint64(UnscheduledActivationTime),
|
|
}
|
|
})
|
|
|
|
TestEtnaChainConfig = copyAndSet(TestChainConfig, func(c *ChainConfig) {
|
|
c.NetworkUpgrades = NetworkUpgrades{
|
|
EVMTimestamp: utils.NewUint64(0),
|
|
DurangoTimestamp: utils.TimeToNewUint64(InitiallyActiveTime),
|
|
EtnaTimestamp: utils.TimeToNewUint64(InitiallyActiveTime),
|
|
FortunaTimestamp: utils.TimeToNewUint64(UnscheduledActivationTime),
|
|
}
|
|
})
|
|
|
|
TestFortunaChainConfig = copyAndSet(TestChainConfig, func(c *ChainConfig) {
|
|
c.NetworkUpgrades = NetworkUpgrades{
|
|
EVMTimestamp: utils.NewUint64(0),
|
|
DurangoTimestamp: utils.TimeToNewUint64(InitiallyActiveTime),
|
|
EtnaTimestamp: utils.TimeToNewUint64(InitiallyActiveTime),
|
|
FortunaTimestamp: utils.TimeToNewUint64(InitiallyActiveTime),
|
|
}
|
|
})
|
|
|
|
TestGraniteChainConfig = copyAndSet(TestChainConfig, func(c *ChainConfig) {
|
|
c.NetworkUpgrades = NetworkUpgrades{
|
|
EVMTimestamp: utils.NewUint64(0),
|
|
DurangoTimestamp: utils.TimeToNewUint64(InitiallyActiveTime),
|
|
EtnaTimestamp: utils.TimeToNewUint64(InitiallyActiveTime),
|
|
FortunaTimestamp: utils.TimeToNewUint64(InitiallyActiveTime),
|
|
GraniteTimestamp: utils.TimeToNewUint64(InitiallyActiveTime),
|
|
}
|
|
})
|
|
)
|
|
|
|
func copyAndSet(c *ChainConfig, set func(*ChainConfig)) *ChainConfig {
|
|
newConfig := *c
|
|
set(&newConfig)
|
|
return &newConfig
|
|
}
|
|
|
|
// UpgradeConfig includes the following configs that may be specified in upgradeBytes:
|
|
// - Timestamps that enable lux network upgrades,
|
|
// - Enabling or disabling precompiles as network upgrades.
|
|
type UpgradeConfig struct {
|
|
// Config for timestamps that enable network upgrades.
|
|
NetworkUpgradeOverrides *NetworkUpgrades `json:"networkUpgradeOverrides,omitempty"`
|
|
|
|
// Config for modifying state as a network upgrade.
|
|
StateUpgrades []StateUpgrade `json:"stateUpgrades,omitempty"`
|
|
|
|
// Config for enabling and disabling precompiles as network upgrades.
|
|
PrecompileUpgrades []PrecompileUpgrade `json:"precompileUpgrades,omitempty"`
|
|
}
|
|
|
|
// LuxContext provides Lux specific context directly into the EVM.
|
|
type LuxContext struct {
|
|
ConsensusCtx context.Context
|
|
}
|
|
|
|
type ChainConfig struct {
|
|
NetworkUpgrades // Config for timestamps that enable network upgrades.
|
|
|
|
LuxContext `json:"-"` // Lux specific context set during VM initialization. Not serialized.
|
|
|
|
FeeConfig commontype.FeeConfig `json:"feeConfig"` // Set the configuration for the dynamic fee algorithm
|
|
AllowFeeRecipients bool `json:"allowFeeRecipients,omitempty"` // Allows fees to be collected by block builders.
|
|
GenesisPrecompiles Precompiles `json:"-"` // Config for enabling precompiles from genesis. JSON encode/decode will be handled by the custom marshaler/unmarshaler.
|
|
UpgradeConfig `json:"-"` // Config specified in upgradeBytes (lux network upgrades or enable/disabling precompiles). Not serialized.
|
|
}
|
|
|
|
func (c *ChainConfig) CheckConfigCompatible(newConfig *ethparams.ChainConfig, headNumber *big.Int, headTimestamp uint64) *ethparams.ConfigCompatError {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
// Note: Cannot type assert concrete type *ethparams.ChainConfig to *ChainConfig
|
|
// For now, we skip the extra compatibility checks and return nil
|
|
// TODO: Implement proper compatibility checking between ethparams.ChainConfig and this ChainConfig
|
|
return nil
|
|
}
|
|
|
|
func (c *ChainConfig) checkConfigCompatible(newcfg *ChainConfig, headNumber *big.Int, headTimestamp uint64) *ethparams.ConfigCompatError {
|
|
if err := c.checkNetworkUpgradesCompatible(&newcfg.NetworkUpgrades, headTimestamp); err != nil {
|
|
return err
|
|
}
|
|
// Check that the precompiles on the new config are compatible with the existing precompile config.
|
|
if err := c.checkPrecompilesCompatible(newcfg.PrecompileUpgrades, headTimestamp); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check that the state upgrades on the new config are compatible with the existing state upgrade config.
|
|
if err := c.checkStateUpgradesCompatible(newcfg.StateUpgrades, headTimestamp); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *ChainConfig) Description() string {
|
|
if c == nil {
|
|
return ""
|
|
}
|
|
var banner string
|
|
|
|
banner += "Lux Upgrades (timestamp based):\n"
|
|
banner += c.NetworkUpgrades.Description()
|
|
banner += "\n"
|
|
|
|
upgradeConfigBytes, err := json.Marshal(c.UpgradeConfig)
|
|
if err != nil {
|
|
upgradeConfigBytes = []byte("cannot marshal UpgradeConfig")
|
|
}
|
|
banner += fmt.Sprintf("Upgrade Config: %s", string(upgradeConfigBytes))
|
|
banner += "\n"
|
|
|
|
feeBytes, err := json.Marshal(c.FeeConfig)
|
|
if err != nil {
|
|
feeBytes = []byte("cannot marshal FeeConfig")
|
|
}
|
|
banner += fmt.Sprintf("Fee Config: %s\n", string(feeBytes))
|
|
|
|
banner += fmt.Sprintf("Allow Fee Recipients: %v\n", c.AllowFeeRecipients)
|
|
|
|
return banner
|
|
}
|
|
|
|
// isForkTimestampIncompatible returns true if a fork scheduled at timestamp s1
|
|
// cannot be rescheduled to timestamp s2 because head is already past the fork.
|
|
func isForkTimestampIncompatible(s1, s2 *uint64, head uint64) bool {
|
|
return (isTimestampForked(s1, head) || isTimestampForked(s2, head)) && !configTimestampEqual(s1, s2)
|
|
}
|
|
|
|
// isTimestampForked returns whether a fork scheduled at timestamp s is active
|
|
// at the given head timestamp.
|
|
func isTimestampForked(s *uint64, head uint64) bool {
|
|
if s == nil {
|
|
return false
|
|
}
|
|
return *s <= head
|
|
}
|
|
|
|
func configTimestampEqual(x, y *uint64) bool {
|
|
if x == nil {
|
|
return y == nil
|
|
}
|
|
if y == nil {
|
|
return x == nil
|
|
}
|
|
return *x == *y
|
|
}
|
|
|
|
// UnmarshalJSON parses the JSON-encoded data and stores the result in the
|
|
// object pointed to by c.
|
|
// This is a custom unmarshaler to handle the Precompiles field.
|
|
// Precompiles was presented as an inline object in the JSON.
|
|
// This custom unmarshaler ensures backwards compatibility with the old format.
|
|
func (c *ChainConfig) UnmarshalJSON(data []byte) error {
|
|
// Alias ChainConfigExtra to avoid recursion
|
|
type _ChainConfigExtra ChainConfig
|
|
tmp := _ChainConfigExtra{}
|
|
if err := json.Unmarshal(data, &tmp); err != nil {
|
|
return err
|
|
}
|
|
|
|
// At this point we have populated all fields except PrecompileUpgrade
|
|
*c = ChainConfig(tmp)
|
|
|
|
// Unmarshal inlined PrecompileUpgrade
|
|
return json.Unmarshal(data, &c.GenesisPrecompiles)
|
|
}
|
|
|
|
// MarshalJSON returns the JSON encoding of c.
|
|
// This is a custom marshaler to handle the Precompiles field.
|
|
func (c *ChainConfig) MarshalJSON() ([]byte, error) {
|
|
// Alias ChainConfigExtra to avoid recursion
|
|
type _ChainConfigExtra ChainConfig
|
|
tmp, err := json.Marshal(_ChainConfigExtra(*c))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// To include PrecompileUpgrades, we unmarshal the json representing c
|
|
// then directly add the corresponding keys to the json.
|
|
raw := make(map[string]json.RawMessage)
|
|
if err := json.Unmarshal(tmp, &raw); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for key, value := range c.GenesisPrecompiles {
|
|
conf, err := json.Marshal(value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
raw[key] = conf
|
|
}
|
|
|
|
return json.Marshal(raw)
|
|
}
|
|
|
|
type fork struct {
|
|
name string
|
|
block *big.Int // some go-ethereum forks use block numbers
|
|
timestamp *uint64 // Lux forks use timestamps
|
|
optional bool // if true, the fork may be nil and next fork is still allowed
|
|
}
|
|
|
|
func (c *ChainConfig) CheckConfigForkOrder() error {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
// Note: In Lux, upgrades must take place via block timestamps instead
|
|
// of block numbers since blocks are produced asynchronously. Therefore, we do
|
|
// not check block timestamp forks in the same way as block number forks since
|
|
// it would not be a meaningful comparison. Instead, we only check that the
|
|
// Lux upgrades are enabled in order.
|
|
// Note: we do not add the precompile configs here because they are optional
|
|
// and independent, i.e. the order in which they are enabled does not impact
|
|
// the correctness of the chain config.
|
|
return checkForks(c.forkOrder(), false)
|
|
}
|
|
|
|
// checkForks checks that forks are enabled in order and returns an error if not.
|
|
// `blockFork` is true if the fork is a block number fork, false if it is a timestamp fork
|
|
func checkForks(forks []fork, blockFork bool) error {
|
|
lastFork := fork{}
|
|
for _, cur := range forks {
|
|
if lastFork.name != "" {
|
|
switch {
|
|
// Non-optional forks must all be present in the chain config up to the last defined fork
|
|
case lastFork.block == nil && lastFork.timestamp == nil && (cur.block != nil || cur.timestamp != nil):
|
|
if cur.block != nil {
|
|
return fmt.Errorf("unsupported fork ordering: %v not enabled, but %v enabled at block %v",
|
|
lastFork.name, cur.name, cur.block)
|
|
} else {
|
|
return fmt.Errorf("unsupported fork ordering: %v not enabled, but %v enabled at timestamp %v",
|
|
lastFork.name, cur.name, cur.timestamp)
|
|
}
|
|
|
|
// Fork (whether defined by block or timestamp) must follow the fork definition sequence
|
|
case (lastFork.block != nil && cur.block != nil) || (lastFork.timestamp != nil && cur.timestamp != nil):
|
|
if lastFork.block != nil && lastFork.block.Cmp(cur.block) > 0 {
|
|
return fmt.Errorf("unsupported fork ordering: %v enabled at block %v, but %v enabled at block %v",
|
|
lastFork.name, lastFork.block, cur.name, cur.block)
|
|
} else if lastFork.timestamp != nil && *lastFork.timestamp > *cur.timestamp {
|
|
return fmt.Errorf("unsupported fork ordering: %v enabled at timestamp %v, but %v enabled at timestamp %v",
|
|
lastFork.name, lastFork.timestamp, cur.name, cur.timestamp)
|
|
}
|
|
|
|
// Timestamp based forks can follow block based ones, but not the other way around
|
|
if lastFork.timestamp != nil && cur.block != nil {
|
|
return fmt.Errorf("unsupported fork ordering: %v used timestamp ordering, but %v reverted to block ordering",
|
|
lastFork.name, cur.name)
|
|
}
|
|
}
|
|
}
|
|
// If it was optional and not set, then ignore it
|
|
if !cur.optional || (cur.block != nil || cur.timestamp != nil) {
|
|
lastFork = cur
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Verify verifies chain config.
|
|
func (c *ChainConfig) Verify() error {
|
|
if err := c.FeeConfig.Verify(); err != nil {
|
|
return fmt.Errorf("invalid fee config: %w", err)
|
|
}
|
|
|
|
// Verify the precompile upgrades are internally consistent given the existing chainConfig.
|
|
if err := c.verifyPrecompileUpgrades(); err != nil {
|
|
return fmt.Errorf("invalid precompile upgrades: %w", err)
|
|
}
|
|
// Verify the state upgrades are internally consistent given the existing chainConfig.
|
|
if err := c.verifyStateUpgrades(); err != nil {
|
|
return fmt.Errorf("invalid state upgrades: %w", err)
|
|
}
|
|
|
|
// Verify the network upgrades are internally consistent given the existing chainConfig.
|
|
// Use default config for validation
|
|
agoUpgrades := upgrade.Config{}
|
|
if err := c.verifyNetworkUpgrades(agoUpgrades); err != nil {
|
|
return fmt.Errorf("invalid network upgrades: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsPrecompileEnabled returns whether precompile with `address` is enabled at `timestamp`.
|
|
func (c *ChainConfig) IsPrecompileEnabled(address common.Address, timestamp uint64) bool {
|
|
config := c.GetActivePrecompileConfig(address, timestamp)
|
|
return config != nil && !config.IsDisabled()
|
|
}
|
|
|
|
// GetFeeConfig returns the original FeeConfig contained in the genesis ChainConfig.
|
|
// Implements precompile.ChainConfig interface.
|
|
func (c *ChainConfig) GetFeeConfig() commontype.FeeConfig {
|
|
if c.FeeConfig.GasLimit == nil {
|
|
fmt.Printf("DEBUG GetFeeConfig: GasLimit is nil, c=%p\n", c)
|
|
} else {
|
|
fmt.Printf("DEBUG GetFeeConfig: GasLimit=%v, c=%p\n", c.FeeConfig.GasLimit, c)
|
|
}
|
|
return c.FeeConfig
|
|
}
|
|
|
|
// AllowedFeeRecipients returns the original AllowedFeeRecipients parameter contained in the genesis ChainConfig.
|
|
// Implements precompile.ChainConfig interface.
|
|
func (c *ChainConfig) AllowedFeeRecipients() bool {
|
|
return c.AllowFeeRecipients
|
|
}
|
|
|
|
// IsForkTransition returns true if `fork` activates during the transition from
|
|
// `parent` to `current`.
|
|
// Taking `parent` as a pointer allows for us to pass nil when checking forks
|
|
// that activate during genesis.
|
|
// Note: `parent` and `current` can be either both timestamp values, or both
|
|
// block number values, since this function works for both block number and
|
|
// timestamp activated forks.
|
|
func IsForkTransition(fork *uint64, parent *uint64, current uint64) bool {
|
|
var parentForked bool
|
|
if parent != nil {
|
|
parentForked = isTimestampForked(fork, *parent)
|
|
}
|
|
currentForked := isTimestampForked(fork, current)
|
|
return !parentForked && currentForked
|
|
}
|