Files
evm/params/extras/network_upgrades.go
zeekayandHanzo Dev d260656efc evm: gate DEX 0x9999 genesis injection on protocol ActivationTime (preserve pre-2025 genesis)
The AlwaysOn DEX settlement precompile (0x9999) had its EXTCODESIZE marker
baked into EVERY chain's genesis state unconditionally (parent==nil in
ApplyPrecompileActivations), mutating the genesis state root. This changed the
genesis hash of every chain genesised before the DEX activation, breaking RLP
re-import of the real archived Lux mainnet (0x3f4fa2a0), Lux testnet
(0x1c5fe377) and Zoo mainnet (0x7c548af4) — all re-genesised Nov 2024, before
the Dec-2025 activation.

Fix: an AlwaysOn system precompile carries a protocol ActivationTime (Dec 2025
for 0x9999, set by the registry bridge — a fork-like constant, not per-net
config). Its genesis marker is installed ONLY IF ActivationTime <= the genesis
timestamp; otherwise it activates later, on the block transition that crosses
ActivationTime during history replay. Dispatch (GetExtrasRules) is gated on the
same timestamp so a codeless address never dispatches. A chain born before
activation reproduces its ORIGINAL genesis byte-for-byte; a fresh chain born
at/after activation still gets 0x9999 at genesis.

Verified against the real RLP block-0 of all three chains (byte-identical
reproduction) plus a fresh post-activation chain. Corrects two pre-existing
tests that had hardcoded the buggy with-0x9999 hashes as "canonical".

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-02 16:50:11 -07:00

367 lines
14 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package extras
import (
"fmt"
"reflect"
"time"
"github.com/luxfi/evm/utils"
ethparams "github.com/luxfi/geth/params"
"github.com/luxfi/upgrade"
)
var errCannotBeNil = fmt.Errorf("timestamp cannot be nil")
// The DEX settlement money path 0x9999 is a system precompile activated by the Lux
// PROTOCOL on every chain with NO per-net config — but NOT from genesis. It turns on at
// the protocol constant registry.DexSettleActivationTime (Dec 2025), exactly like a
// dated fork. Every Lux chain in existence was (re-)genesised BEFORE that timestamp, so
// none carries 0x9999 in its ORIGINAL genesis state; treating it as genesis-active would
// mutate the genesis state root and break re-import of those chains' archived RLPs.
//
// Dispatch and the EXTCODESIZE marker are keyed on the SAME activation timestamp so they
// can never disagree: params/config_extra.go GetExtrasRules injects the module into the
// enabled set only at timestamps >= modules.Module.ActivationTime, and
// core/state_processor_ext.go ApplyPrecompileActivations installs the marker (SetNonce=1
// + SetCode{0x1}) exactly once, on the block transition that crosses ActivationTime
// (which is genesis iff the genesis timestamp already reaches it). Before activation the
// address behaves as a plain account. See modules.AlwaysOn / modules.Module.ActivationTime.
// newTimestampCompatError creates a ConfigCompatError for timestamp mismatches
func newTimestampCompatError(what string, storedtime, newtime *uint64) *ethparams.ConfigCompatError {
var rew *uint64
switch {
case storedtime == nil:
rew = newtime
case newtime == nil || *storedtime < *newtime:
rew = newtime
default:
rew = storedtime
}
err := &ethparams.ConfigCompatError{
What: what,
StoredTime: storedtime,
NewTime: newtime,
RewindToTime: 0,
}
if rew != nil && *rew > 0 {
err.RewindToTime = *rew - 1
}
return err
}
// NetworkUpgrades contains timestamps that enable network upgrades.
// Lux specific network upgrades are also included here.
// (nil = no fork, 0 = already activated)
type NetworkUpgrades struct {
// EVMTimestamp is a placeholder that activates Lux Upgrades prior to ApricotPhase6
EVMTimestamp *uint64 `json:"evmTimestamp,omitempty"`
// Durango activates the Shanghai Execution Spec Upgrade from Ethereum (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md#included-eips)
// and Lux Warp Messaging.
// Note: EIP-4895 is excluded since withdrawals are not relevant to the Lux C-Chain or Chains running the EVM.
DurangoTimestamp *uint64 `json:"durangoTimestamp,omitempty"`
// Placeholder for QuasarTimestamp
QuasarTimestamp *uint64 `json:"quasarTimestamp,omitempty"`
// Fortuna has no effect on EVM by itself, but is included for completeness.
FortunaTimestamp *uint64 `json:"fortunaTimestamp,omitempty"`
// Granite is a placeholder for the next upgrade.
GraniteTimestamp *uint64 `json:"graniteTimestamp,omitempty"`
}
func (n *NetworkUpgrades) Equal(other *NetworkUpgrades) bool {
return reflect.DeepEqual(n, other)
}
func (n *NetworkUpgrades) checkNetworkUpgradesCompatible(newcfg *NetworkUpgrades, time uint64) *ethparams.ConfigCompatError {
if isForkTimestampIncompatible(n.EVMTimestamp, newcfg.EVMTimestamp, time) {
return newTimestampCompatError("EVM fork block timestamp", n.EVMTimestamp, newcfg.EVMTimestamp)
}
if isForkTimestampIncompatible(n.DurangoTimestamp, newcfg.DurangoTimestamp, time) {
return newTimestampCompatError("Durango fork block timestamp", n.DurangoTimestamp, newcfg.DurangoTimestamp)
}
if isForkTimestampIncompatible(n.QuasarTimestamp, newcfg.QuasarTimestamp, time) {
return newTimestampCompatError("Quasar fork block timestamp", n.QuasarTimestamp, newcfg.QuasarTimestamp)
}
// Fortuna is optional, so allow nil values even after the fork time
// Only check incompatibility if both values are non-nil
if n.FortunaTimestamp != nil && newcfg.FortunaTimestamp != nil {
if isForkTimestampIncompatible(n.FortunaTimestamp, newcfg.FortunaTimestamp, time) {
return newTimestampCompatError("Fortuna fork block timestamp", n.FortunaTimestamp, newcfg.FortunaTimestamp)
}
}
if isForkTimestampIncompatible(n.GraniteTimestamp, newcfg.GraniteTimestamp, time) {
return newTimestampCompatError("Granite fork block timestamp", n.GraniteTimestamp, newcfg.GraniteTimestamp)
}
return nil
}
func (n *NetworkUpgrades) forkOrder() []fork {
return []fork{
{name: "evmTimestamp", timestamp: n.EVMTimestamp},
{name: "durangoTimestamp", timestamp: n.DurangoTimestamp},
{name: "quasarTimestamp", timestamp: n.QuasarTimestamp},
{name: "fortunaTimestamp", timestamp: n.FortunaTimestamp, optional: true},
{name: "graniteTimestamp", timestamp: n.GraniteTimestamp},
}
}
// SetDefaults sets the default values for the network upgrades.
// Only nil timestamps are overridden with defaults. An explicit value of 0
// means "active at genesis" and is preserved.
func (n *NetworkUpgrades) SetDefaults(agoUpgrades upgrade.Config) {
defaults := GetNetworkUpgrades(agoUpgrades)
if n.EVMTimestamp == nil {
n.EVMTimestamp = defaults.EVMTimestamp
}
if n.DurangoTimestamp == nil {
n.DurangoTimestamp = defaults.DurangoTimestamp
}
if n.QuasarTimestamp == nil {
n.QuasarTimestamp = defaults.QuasarTimestamp
}
if n.FortunaTimestamp == nil {
n.FortunaTimestamp = defaults.FortunaTimestamp
}
if n.GraniteTimestamp == nil {
n.GraniteTimestamp = defaults.GraniteTimestamp
}
}
// verifyNetworkUpgrades checks that the network upgrades are well formed.
func (n *NetworkUpgrades) verifyNetworkUpgrades(agoUpgrades upgrade.Config) error {
defaults := GetNetworkUpgrades(agoUpgrades)
// EVMTimestamp must not be nil
if n.EVMTimestamp == nil {
return fmt.Errorf("EVM fork block timestamp is invalid: %w", errCannotBeNil)
}
// EVMTimestamp must be 0 (activated at genesis)
if *n.EVMTimestamp != 0 {
return fmt.Errorf("EVM fork block timestamp is invalid: must be 0 (activated at genesis), got %d", *n.EVMTimestamp)
}
if err := verifyWithDefault(n.EVMTimestamp, defaults.EVMTimestamp); err != nil {
return fmt.Errorf("EVM fork block timestamp is invalid: %w", err)
}
// DurangoTimestamp must not be nil
if n.DurangoTimestamp == nil {
return fmt.Errorf("Durango fork block timestamp is invalid: %w", errCannotBeNil)
}
if defaults.DurangoTimestamp != nil {
// If the default activates at genesis, the config must also activate at genesis.
if *defaults.DurangoTimestamp == 0 && *n.DurangoTimestamp != 0 {
return fmt.Errorf("Durango fork block timestamp is invalid: cannot be changed from genesis activation (0) to %d", *n.DurangoTimestamp)
}
if err := verifyWithDefault(n.DurangoTimestamp, defaults.DurangoTimestamp); err != nil {
return fmt.Errorf("Durango fork block timestamp is invalid: %w", err)
}
}
// QuasarTimestamp — when the default is set (non-nil), the config must
// also set it and must not be earlier than the default.
if defaults.QuasarTimestamp != nil {
if n.QuasarTimestamp == nil {
return fmt.Errorf("Quasar fork block timestamp is invalid: %w", errCannotBeNil)
}
if err := verifyWithDefault(n.QuasarTimestamp, defaults.QuasarTimestamp); err != nil {
return fmt.Errorf("Quasar fork block timestamp is invalid: %w", err)
}
}
// FortunaTimestamp — same rule: if default is set, config must match.
if defaults.FortunaTimestamp != nil {
if n.FortunaTimestamp == nil {
return fmt.Errorf("Fortuna fork block timestamp is invalid: %w", errCannotBeNil)
}
if err := verifyWithDefault(n.FortunaTimestamp, defaults.FortunaTimestamp); err != nil {
return fmt.Errorf("Fortuna fork block timestamp is invalid: %w", err)
}
}
// GraniteTimestamp — same rule.
if defaults.GraniteTimestamp != nil {
if n.GraniteTimestamp == nil {
return fmt.Errorf("Granite fork block timestamp is invalid: %w", errCannotBeNil)
}
if err := verifyWithDefault(n.GraniteTimestamp, defaults.GraniteTimestamp); err != nil {
return fmt.Errorf("Granite fork block timestamp is invalid: %w", err)
}
}
// Check that forks are enabled in order
if err := checkForks(n.forkOrder(), false); err != nil {
return err
}
return nil
}
func (n *NetworkUpgrades) Override(o *NetworkUpgrades) {
if o.EVMTimestamp != nil {
n.EVMTimestamp = o.EVMTimestamp
}
if o.DurangoTimestamp != nil {
n.DurangoTimestamp = o.DurangoTimestamp
}
if o.QuasarTimestamp != nil {
n.QuasarTimestamp = o.QuasarTimestamp
}
if o.FortunaTimestamp != nil {
n.FortunaTimestamp = o.FortunaTimestamp
}
if o.GraniteTimestamp != nil {
n.GraniteTimestamp = o.GraniteTimestamp
}
}
// IsEVM returns whether [time] represents a block
// with a timestamp after the EVM upgrade time.
func (n NetworkUpgrades) IsEVM(time uint64) bool {
return isTimestampForked(n.EVMTimestamp, time)
}
// IsDurango returns whether [time] represents a block
// with a timestamp after the Durango upgrade time.
func (n NetworkUpgrades) IsDurango(time uint64) bool {
return isTimestampForked(n.DurangoTimestamp, time)
}
// IsQuasar returns whether [time] represents a block
// with a timestamp after the Quasar Edition upgrade time.
func (n NetworkUpgrades) IsQuasar(time uint64) bool {
return isTimestampForked(n.QuasarTimestamp, time)
}
// IsFortuna returns whether [time] represents a block
// with a timestamp after the Fortuna upgrade time.
func (n *NetworkUpgrades) IsFortuna(time uint64) bool {
return isTimestampForked(n.FortunaTimestamp, time)
}
// IsGranite returns whether [time] represents a block
// with a timestamp after the Granite upgrade time.
func (n *NetworkUpgrades) IsGranite(time uint64) bool {
return isTimestampForked(n.GraniteTimestamp, time)
}
func (n *NetworkUpgrades) Description() string {
var banner string
banner += fmt.Sprintf(" - EVM Timestamp: @%-10v (https://github.com/luxfi/node/releases/tag/v1.10.0)\n", ptrToString(n.EVMTimestamp))
banner += fmt.Sprintf(" - Durango Timestamp: @%-10v (https://github.com/luxfi/node/releases/tag/v1.11.0)\n", ptrToString(n.DurangoTimestamp))
banner += fmt.Sprintf(" - Quasar Timestamp: @%-10v (https://github.com/luxfi/node/releases/tag/v1.12.0)\n", ptrToString(n.QuasarTimestamp))
banner += fmt.Sprintf(" - Fortuna Timestamp: @%-10v (https://github.com/luxfi/node/releases/tag/v1.13.0)\n", ptrToString(n.FortunaTimestamp))
banner += fmt.Sprintf(" - Granite Timestamp: @%-10v (https://github.com/luxfi/node/releases/tag/v1.14.0)\n", ptrToString(n.GraniteTimestamp))
return banner
}
type LuxRules struct {
IsEVM bool
IsDurango bool
IsQuasar bool
IsFortuna bool
IsGranite bool
}
func (n *NetworkUpgrades) GetLuxRules(time uint64) LuxRules {
return LuxRules{
IsEVM: n.IsEVM(time),
IsDurango: n.IsDurango(time),
IsQuasar: n.IsQuasar(time),
IsFortuna: n.IsFortuna(time),
IsGranite: n.IsGranite(time),
}
}
// GetNetworkUpgrades returns the network upgrades for the specified luxd upgrades.
// Nil values are used to indicate optional upgrades.
// The function respects the upgrade times from the config - if an upgrade is scheduled
// at or after UnscheduledActivationTime, it is considered not scheduled.
// InitiallyActiveTime is treated as "activated at genesis (timestamp 0)".
func GetNetworkUpgrades(agoUpgrade upgrade.Config) NetworkUpgrades {
// EVM is always activated at genesis (0)
result := NetworkUpgrades{
EVMTimestamp: utils.NewUint64(0),
}
// Use upgrade.UnscheduledActivationTime as the threshold for "unscheduled"
// UnscheduledActivationTime is time.Date(9999, time.December, 1, 0, 0, 0, 0, time.UTC)
unscheduledTime := uint64(upgrade.UnscheduledActivationTime.Unix())
// InitiallyActiveTime means "always active from genesis" for EVM purposes
// We convert it to 0 (genesis timestamp) for EVM network upgrades
initiallyActiveTime := upgrade.InitiallyActiveTime
// Helper to convert upgrade time to EVM timestamp
// Returns nil if unscheduled or zero time, 0 if initially active, or the actual timestamp
toEVMTimestamp := func(t time.Time) *uint64 {
// Zero time (not set) is treated as unscheduled
if t.IsZero() {
return nil
}
// Check if scheduled at unscheduled time (far future)
if t.Unix() >= int64(unscheduledTime) {
return nil // Unscheduled
}
// InitiallyActiveTime means "already active from genesis"
if t.Equal(initiallyActiveTime) {
return utils.NewUint64(0) // Genesis activation
}
return utils.NewUint64(uint64(t.Unix()))
}
// Check DurangoTime
result.DurangoTimestamp = toEVMTimestamp(agoUpgrade.DurangoTime)
// Check QuasarTime
result.QuasarTimestamp = toEVMTimestamp(agoUpgrade.QuasarTime)
// Check FortunaTime
result.FortunaTimestamp = toEVMTimestamp(agoUpgrade.FortunaTime)
// Check GraniteTime
result.GraniteTimestamp = toEVMTimestamp(agoUpgrade.GraniteTime)
return result
}
// GetDefaultNetworkUpgrades returns default network upgrades.
// All upgrades are enabled at genesis (timestamp 0) so that every chain
// running the Lux EVM has the full opcode set (PUSH0, MCOPY, TSTORE,
// TLOAD, BLOBHASH, BLOBBASEFEE, etc.) available from block 0.
func GetDefaultNetworkUpgrades() NetworkUpgrades {
return NetworkUpgrades{
EVMTimestamp: utils.NewUint64(0),
DurangoTimestamp: utils.NewUint64(0),
QuasarTimestamp: utils.NewUint64(0),
FortunaTimestamp: utils.NewUint64(0),
GraniteTimestamp: utils.NewUint64(0),
}
}
// verifyWithDefault checks that the provided timestamp is greater than or equal to the default timestamp.
func verifyWithDefault(configTimestamp *uint64, defaultTimestamp *uint64) error {
if defaultTimestamp == nil {
return nil
}
if configTimestamp == nil {
return errCannotBeNil
}
if *configTimestamp < *defaultTimestamp {
return fmt.Errorf("provided timestamp (%d) must be greater than or equal to the default timestamp (%d)", *configTimestamp, *defaultTimestamp)
}
return nil
}
func ptrToString(val *uint64) string {
if val == nil {
return "nil"
}
return fmt.Sprintf("%d", *val)
}