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]>
This commit is contained in:
2026-07-02 16:50:11 -07:00
co-authored by Hanzo Dev
parent c475e3377d
commit 0bd9927a26
11 changed files with 418 additions and 167 deletions
+163
View File
@@ -0,0 +1,163 @@
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package core
import (
"encoding/json"
"fmt"
"math/big"
"testing"
"github.com/holiman/uint256"
"github.com/luxfi/evm/core/state"
"github.com/luxfi/evm/precompile/registry"
"github.com/luxfi/geth/core/rawdb"
"github.com/luxfi/geth/core/tracing"
gethtypes "github.com/luxfi/geth/core/types"
"github.com/stretchr/testify/require"
// Register the full precompile set (incl. the AlwaysOn DEX settlement module at
// 0x9999), exactly as the production node does.
_ "github.com/luxfi/evm/precompile/registry"
)
// This file is the ship-gate regression for the DEX settlement precompile (0x9999)
// genesis-injection fix. Before the fix, 0x9999's EXTCODESIZE marker was baked into
// EVERY chain's genesis state unconditionally, which mutated the genesis state root and
// changed the genesis hash of every pre-2025 chain — breaking RLP re-import of the real
// archived Lux mainnet, Lux testnet and Zoo mainnet (all re-genesised Nov 2024, before
// the DEX settlement activation, Dec 2025). The fix gates the injection on the
// precompile's protocol ActivationTime: it lands in genesis state ONLY IF
// ActivationTime <= the genesis timestamp, so a chain born before activation reproduces
// its ORIGINAL genesis byte-for-byte, and 0x9999 instead activates later, at the block
// crossing the timestamp.
//
// The three real genesis hashes below are the authoritative block-0 hashes of the RLP
// exports in ~/work/lux/state/rlp/{lux-mainnet,lux-testnet,zoo-mainnet}. They are the
// literal import targets; if a computed hash drifts from them, the archived chains
// cannot be re-imported. addr9999 is defined in precompile_alwayson_test.go.
// canonicalGenesisJSON is the 2-account genesis shared by the Nov-2024 re-genesis of
// Lux mainnet, Lux testnet and Zoo mainnet: the historical warp precompile marker at
// 0x02..05 (code 0x01) + the treasury at 0x9011 funded with 2e30 wei — stateRoot
// 0x2d1cedac…, NO 0x9999. The three chains differ ONLY in chainId and genesis timestamp
// (all other header fields — gasLimit 0xb71b00, baseFee 25e9, zero difficulty/nonce/
// coinbase/mixhash, empty extraData, 16-field pre-withdrawals header — are identical).
// quasarTimestamp is set far in the future so Cancun is inactive at genesis and the
// header stays in the original pre-withdrawals (16-field) format.
const canonicalGenesisJSON = `{
"alloc": {
"0200000000000000000000000000000000000005": {"balance":"0x0","code":"0x01","nonce":"0x1"},
"9011E888251AB053B7bD1cdB598Db4f9DEd94714": {"balance":"0x193e5939a08ce9dbd480000000"}
},
"baseFeePerGas": "0x5d21dba00",
"coinbase": "0x0000000000000000000000000000000000000000",
"config": {
"berlinBlock":0,"byzantiumBlock":0,"chainId":%d,"constantinopleBlock":0,
"durangoTimestamp":0,"eip150Block":0,"eip155Block":0,"eip158Block":0,
"quasarTimestamp":253399622400,"evmTimestamp":0,
"fortunaTimestamp":253399622400,"graniteTimestamp":253399622400,
"homesteadBlock":0,"istanbulBlock":0,"londonBlock":0,"muirGlacierBlock":0,
"petersburgBlock":0,"terminalTotalDifficulty":0,
"feeConfig":{"baseFeeChangeDenominator":36,"blockGasCostStep":200000,"gasLimit":12000000,
"maxBlockGasCost":1000000,"minBaseFee":25000000000,"minBlockGasCost":0,
"targetBlockRate":2,"targetGas":15000000}
},
"difficulty": "0x0",
"extraData": "0x",
"gasLimit": "0xb71b00",
"gasUsed": "0x0",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"nonce": "0x0",
"number": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp": "%s"
}`
func canonicalGenesis(t *testing.T, chainID uint64, timestampHex string) *Genesis {
t.Helper()
var g Genesis
require.NoError(t, json.Unmarshal([]byte(fmt.Sprintf(canonicalGenesisJSON, chainID, timestampHex)), &g))
return &g
}
// TestGenesisReproduce_PreDexActivationChains is the hard ship gate: the fixed build
// reproduces the ORIGINAL block-0 genesis hash of each real archived chain, byte-
// identical, so its RLP re-imports. All three were re-genesised in Nov 2024, before the
// DEX settlement activation (Dec 2025), so NONE carries 0x9999 in genesis state.
func TestGenesisReproduce_PreDexActivationChains(t *testing.T) {
cases := []struct {
name string
chainID uint64
timestampHex string // block-0 header timestamp, from the RLP export
wantHash string // authoritative block-0 hash, the RLP import target
}{
{"lux-mainnet-96369", 96369, "0x672485c2", "0x3f4fa2a0b0ce089f52bf0ae9199c75ffdd76ecafc987794050cb0d286f1ec61e"},
{"lux-testnet-96368", 96368, "0x67259912", "0x1c5fe37764b8bc146dc88bc1c2e0259cd8369b07a06439bcfa1782b5d4fb0995"},
{"zoo-mainnet-200200", 200200, "0x6727e9c3", "0x7c548af47de27560779ccc67dda32a540944accc71dac3343da3b9cd18f14933"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
g := canonicalGenesis(t, tc.chainID, tc.timestampHex)
// Genesis timestamp is Nov 2024, strictly before the DEX activation.
require.Less(t, g.Timestamp, registry.DexSettleActivationTime,
"precondition: this chain's genesis must predate DEX settlement activation")
blk := g.ToBlock()
require.Equal(t, tc.wantHash, blk.Hash().Hex(),
"%s genesis hash MUST reproduce byte-identically or the archived RLP cannot import", tc.name)
// 0x9999 must NOT be in the genesis state (this is what preserves the hash).
sdb := genesisState(t, g)
require.Empty(t, sdb.GetCode(addr9999),
"0x9999 must be absent from a pre-activation chain's genesis state")
require.Zero(t, sdb.GetNonce(addr9999),
"0x9999 must carry no activation nonce at a pre-activation genesis")
})
}
}
// TestGenesisReproduce_FreshChainWantsDexAtGenesis pins condition 4: a genuinely fresh
// chain whose genesis timestamp is at/after the DEX activation DOES get 0x9999 injected
// into its genesis state (EXTCODESIZE marker present from block 0), and its genesis hash
// therefore differs from the same chain built with a pre-activation timestamp.
func TestGenesisReproduce_FreshChainWantsDexAtGenesis(t *testing.T) {
freshTS := registry.DexSettleActivationTime + 3600 // one hour after activation
g := canonicalGenesis(t, 424242, fmt.Sprintf("0x%x", freshTS))
require.GreaterOrEqual(t, g.Timestamp, registry.DexSettleActivationTime)
sdb := genesisState(t, g)
require.Equal(t, []byte{0x01}, sdb.GetCode(addr9999),
"a chain genesised at/after DEX activation MUST carry the 0x9999 marker at genesis")
require.Equal(t, uint64(1), sdb.GetNonce(addr9999),
"0x9999 must carry the activation nonce at a post-activation genesis")
// Its hash must differ from the identical chain built one second before activation
// (which omits 0x9999) — proving the injection is genuinely timestamp-gated, not inert.
pre := canonicalGenesis(t, 424242, fmt.Sprintf("0x%x", registry.DexSettleActivationTime-1))
require.NotEqual(t, pre.ToBlock().Hash(), g.ToBlock().Hash(),
"genesis hash must change across the activation boundary")
}
// genesisState rebuilds the committed genesis account trie so the test can inspect
// getCode(0x9999). It mirrors (*Genesis).toBlock exactly (ApplyPrecompileActivations at
// parent==nil, then alloc) and its root MUST equal ToBlock's, which it asserts.
func genesisState(t *testing.T, g *Genesis) *state.StateDB {
t.Helper()
db := rawdb.NewMemoryDatabase()
statedb, err := state.New(gethtypes.EmptyRootHash, state.NewDatabase(db), nil)
require.NoError(t, err)
require.NoError(t, ApplyPrecompileActivations(g.Config, nil, NewBlockContext(big.NewInt(int64(g.Number)), g.Timestamp), statedb))
for addr, acct := range g.Alloc {
statedb.SetBalance(addr, uint256.MustFromBig(acct.Balance), tracing.BalanceIncreaseGenesisBalance)
_ = statedb.SetCode(addr, acct.Code, tracing.CodeChangeGenesis)
statedb.SetNonce(addr, acct.Nonce, tracing.NonceChangeGenesis)
for k, v := range acct.Storage {
statedb.SetState(addr, k, v)
}
}
require.Equal(t, g.ToBlock().Root(), statedb.IntermediateRoot(false),
"rebuilt genesis state root must equal ToBlock's — otherwise this inspection is not authoritative")
return statedb
}
+7 -3
View File
@@ -79,9 +79,13 @@ func TestSkipPostMergeFieldsGenesisHash(t *testing.T) {
"stateRoot": "0x2d1cedac263020c5c56ef962f6abe0da1f5217bdc6468f8c9258a0ea23699e80",
"timestamp": "0x672485c2"
}`
// 0x9999 is genesis-active (first-run, always-on, no dated fork), so the
// genesis carries the precompile-activation marker → this hash.
const wantHash = "0x5609692a3a6d440a6a3c187cb886fc8afc16904fc34c06d6591f19ee07782166"
// The genesis timestamp (0x672485c2, Nov 2024) predates the DEX settlement (0x9999)
// activation (registry.DexSettleActivationTime, Dec 2025), so 0x9999 is NOT in genesis
// state and this is the TRUE canonical Lux mainnet genesis hash — byte-identical to the
// lux-mainnet-96369.rlp block-0 import target. (Before the genesis-injection fix this
// test asserted 0x5609692a…, the value the unconditional 0x9999 marker mutated it to;
// that was the bug, cross-checked here against the sibling test's `canonical` constant.)
const wantHash = "0x3f4fa2a0b0ce089f52bf0ae9199c75ffdd76ecafc987794050cb0d286f1ec61e"
var g Genesis
if err := json.Unmarshal([]byte(luxMainnetGenesis), &g); err != nil {
+4 -3
View File
@@ -63,9 +63,10 @@ func setupGenesisBlock(db ethdb.Database, triedb *triedb.Database, genesis *Gene
}
func TestGenesisBlockForTesting(t *testing.T) {
// Hash carries the genesis-active 0x9999 precompile marker (first-run,
// always-on, no dated fork).
genesisBlockForTestingHash := common.HexToHash("0x4e9727637befadbd73fb84f5360f3d026802c1a261025736692d2472c0c20379")
// Genesis timestamp is 0, which predates the DEX settlement (0x9999) activation
// (registry.DexSettleActivationTime, Dec 2025), so 0x9999 is NOT in genesis state and
// the hash is the plain single-account genesis — see genesis_dexsettle_reproduce_test.go.
genesisBlockForTestingHash := common.HexToHash("0x517cc43afd8c4516d00dae3c767b336d4ad9a9aeffbf0a4b205ef0bdc6343f35")
block := GenesisBlockForTesting(rawdb.NewMemoryDatabase(), common.Address{1}, big.NewInt(1))
if block.Hash() != genesisBlockForTestingHash {
t.Errorf("wrong testing genesis hash, got %v, want %v", block.Hash(), genesisBlockForTestingHash)
+118 -107
View File
@@ -12,47 +12,51 @@ import (
"github.com/luxfi/evm/params"
"github.com/luxfi/evm/params/extras"
"github.com/luxfi/evm/precompile/modules"
// registry side-effect: bridges the external DEX settlement module (0x9999)
// into the EVM's internal registry so it is visible as AlwaysOn here.
_ "github.com/luxfi/evm/precompile/registry"
"github.com/luxfi/evm/precompile/registry"
"github.com/luxfi/geth/common"
"github.com/luxfi/geth/core/rawdb"
"github.com/stretchr/testify/require"
)
// precompile_alwayson_test.go pins the activation of the DEX settlement money path
// 0x9999. It is a FIRST-RUN, no-legacy system precompile: ACTIVE FROM GENESIS on every
// Lux chain, with NO dated fork, NO activation timestamp, and NO per-net config (no
// dexSettleConfig genesis precompile, no precompileUpgrades entry). There is no
// pre-activation history to protect, so there is no "before" case and no plain-account
// branch. The activation has TWO coupled effects, both pinned here:
// precompile_alwayson_test.go pins the activation contract of the DEX settlement money
// path 0x9999. It is a SYSTEM precompile activated by the Lux PROTOCOL on every chain
// with NO per-net config (no dexSettleConfig genesis precompile, no precompileUpgrades
// entry) — but NOT from genesis. It turns on at the protocol constant
// registry.DexSettleActivationTime (2025-12-25), exactly like a dated fork, so a chain
// whose genesis predates it reproduces its ORIGINAL genesis (see
// genesis_dexsettle_reproduce_test.go). The activation couples two effects, both pinned
// here and both keyed on the SAME timestamp so they can never disagree:
//
// - DISPATCH: 0x9999 is in the enabled precompile set at EVERY timestamp (genesis
// included), so the EVM runs it from block 0. It is injected unconditionally from
// modules.AlwaysOnModules() — never gated on a timestamp.
// - MARKER: the precompile-activation marker (nonce=1 + non-empty code) is written into
// 0x9999 exactly ONCE, at genesis (the parent==nil transition in
// ApplyPrecompileActivations), so it lands in the committed genesis state root and
// EXTCODESIZE>0 / eth_getCode!=0x / Solidity's contract-existence guard pass from
// block 0. Every later block (parent!=nil) leaves it untouched.
//
// If dispatch regresses to timestamp-gated, a genesis-era call to 0x9999 would miss the
// settlement precompile. If the marker regresses to a forward (post-genesis) install, a
// typed Solidity call at genesis would fail the compiler's contract-existence guard
// (EXTCODESIZE==0).
// - DISPATCH: 0x9999 enters the enabled precompile set at timestamps >=
// DexSettleActivationTime, and is ABSENT before it. This is the set
// PrecompileOverride consults; the decision is a pure function of the passed-in
// timestamp (no process-global read), so it is race-safe and identical on every
// replay of a given block.
// - MARKER: the EXTCODESIZE marker (nonce=1 + non-empty code) is written into 0x9999
// exactly ONCE, on the block transition that crosses DexSettleActivationTime (which
// is genesis iff the genesis timestamp already reaches it). So EXTCODESIZE>0 /
// eth_getCode!=0x / Solidity's contract-existence guard pass from the activation
// block forward, and NOT before.
var settleAddr9999 = common.HexToAddress("0x0000000000000000000000000000000000009999")
// A representative spread of block timestamps: genesis (0), small, a realistic
// wall-clock value, and the max. 0x9999 must be enabled at ALL of them.
var dispatchTimestamps = []uint64{0, 1, 1_700_000_000, ^uint64(0)}
// addr9999 is the shared handle used by the genesis-reproduction regression in the
// sibling file genesis_dexsettle_reproduce_test.go.
var addr9999 = settleAddr9999
// activation is the protocol timestamp at which 0x9999 turns on (marker + dispatch).
var activation = registry.DexSettleActivationTime
// TestAlwaysOn_9999_Registered asserts the DEX settlement precompile bridges into the
// EVM registry as AlwaysOn and is enumerated by AlwaysOnModules().
// EVM registry as AlwaysOn, is enumerated by AlwaysOnModules(), and carries the protocol
// ActivationTime (Dec 2025) — the value the whole activation contract keys on.
func TestAlwaysOn_9999_Registered(t *testing.T) {
m, ok := modules.GetPrecompileModuleByAddress(settleAddr9999)
require.True(t, ok, "0x9999 must be registered in the EVM precompile registry")
require.True(t, m.AlwaysOn, "0x9999 (%s) must be AlwaysOn — it is the system money-path precompile", m.ConfigKey)
require.Equal(t, activation, m.ActivationTime,
"0x9999 must carry the DEX settlement protocol ActivationTime, not activate at genesis")
require.NotZero(t, activation, "the DEX settlement activation must be a real (post-genesis) timestamp")
found := false
for _, am := range modules.AlwaysOnModules() {
@@ -63,75 +67,79 @@ func TestAlwaysOn_9999_Registered(t *testing.T) {
require.True(t, found, "0x9999 must be enumerated by modules.AlwaysOnModules()")
}
// TestDexSettle_DispatchEnabledFromGenesis asserts 0x9999 is in the active precompile set
// at EVERY timestamp — genesis (ts=0) included — on a chain whose config carries NO
// precompile activation whatsoever (no genesis precompiles, no upgrades). This is the
// dispatch set PrecompileOverride consults. There is no "before" case: AlwaysOn means
// present from block 0, unconditionally.
func TestDexSettle_DispatchEnabledFromGenesis(t *testing.T) {
// A bare chain config — nothing activates 0x9999 via config; the AlwaysOn mechanism does.
// TestDexSettle_DispatchGatedByActivation asserts 0x9999 is ABSENT from the enabled
// precompile set before its activation timestamp and PRESENT at/after it, on a chain
// whose config carries NO precompile activation whatsoever (the AlwaysOn mechanism, not
// a genesis config, drives this).
func TestDexSettle_DispatchGatedByActivation(t *testing.T) {
cfg := params.WithExtra(&params.ChainConfig{}, &extras.ChainConfig{})
for _, ts := range dispatchTimestamps {
absentAt := []uint64{0, 1, activation - 1}
for _, ts := range absentAt {
rules := params.GetExtrasRules(params.Rules{}, cfg, ts)
_, ok := rules.Precompiles[settleAddr9999]
require.Truef(t, ok, "0x9999 must be in the enabled set at ts=%d — AlwaysOn from genesis", ts)
require.Falsef(t, ok, "0x9999 must be ABSENT at ts=%d (< activation %d)", ts, activation)
}
presentAt := []uint64{activation, activation + 1, ^uint64(0)}
for _, ts := range presentAt {
rules := params.GetExtrasRules(params.Rules{}, cfg, ts)
_, ok := rules.Precompiles[settleAddr9999]
require.Truef(t, ok, "0x9999 must be PRESENT at ts=%d (>= activation %d)", ts, activation)
}
}
// TestDexSettle_MarkerInstalledAtGenesis asserts the marker (nonce=1 + non-empty code) IS
// written at genesis (parent==nil) — the exact call core/genesis.go makes via
// ApplyPrecompileActivations(g.Config, nil, ...) before committing the genesis root. So
// EXTCODESIZE>0 / eth_getCode!=0x / Solidity contract-existence guard pass from block 0.
func TestDexSettle_MarkerInstalledAtGenesis(t *testing.T) {
db := rawdb.NewMemoryDatabase()
statedb, err := state.New(common.Hash{}, state.NewDatabase(db), nil)
require.NoError(t, err)
// TestDexSettle_MarkerAtActivationCrossing asserts the EXTCODESIZE marker is installed
// exactly on the transition that crosses the activation timestamp, and nowhere else.
func TestDexSettle_MarkerAtActivationCrossing(t *testing.T) {
newState := func() *state.StateDB {
db := rawdb.NewMemoryDatabase()
sdb, err := state.New(common.Hash{}, state.NewDatabase(db), nil)
require.NoError(t, err)
return sdb
}
cfg := &params.ChainConfig{}
// Genesis: parent==nil, mirroring core/genesis.go.
blockCtx := NewBlockContext(big.NewInt(0), 0)
require.NoError(t, ApplyPrecompileActivations(cfg, nil, blockCtx, statedb))
hasMarker := func(sdb *state.StateDB) bool {
return sdb.GetNonce(settleAddr9999) == 1 && len(sdb.GetCode(settleAddr9999)) > 0
}
require.Equal(t, uint64(1), statedb.GetNonce(settleAddr9999),
"0x9999 must carry the activation nonce from genesis (parent==nil)")
require.NotEmpty(t, statedb.GetCode(settleAddr9999),
"0x9999 must carry non-empty marker code from genesis (EXTCODESIZE>0)")
require.Greater(t, statedb.GetCodeSize(settleAddr9999), 0,
"EXTCODESIZE(0x9999) must be > 0 from genesis — Solidity contract-existence guard")
}
// Pre-activation genesis (parent==nil, ts < activation): NO marker — preserves the
// original genesis hash of every pre-2025 chain.
sdb := newState()
require.NoError(t, ApplyPrecompileActivations(cfg, nil, NewBlockContext(big.NewInt(0), activation-1), sdb))
require.False(t, hasMarker(sdb), "no marker at a pre-activation genesis")
// TestDexSettle_MarkerOnlyAtGenesis asserts the marker is installed EXACTLY ONCE, at
// genesis: a non-genesis block (parent!=nil) must NOT write it. In production the marker
// is already in the committed genesis state; every later block leaves 0x9999 untouched.
// (If this regressed to writing every block, 0x9999 would be needlessly re-dirtied in the
// journal on every block.)
func TestDexSettle_MarkerOnlyAtGenesis(t *testing.T) {
db := rawdb.NewMemoryDatabase()
statedb, err := state.New(common.Hash{}, state.NewDatabase(db), nil)
require.NoError(t, err)
// Post-activation genesis (parent==nil, ts >= activation): marker present (a fresh
// chain that wants 0x9999 at genesis).
sdb = newState()
require.NoError(t, ApplyPrecompileActivations(cfg, nil, NewBlockContext(big.NewInt(0), activation), sdb))
require.True(t, hasMarker(sdb), "marker at a post-activation genesis")
cfg := &params.ChainConfig{}
// A non-genesis block: parent != nil ⇒ no marker write on this fresh state.
parent := uint64(0)
blockCtx := NewBlockContext(big.NewInt(1), 1)
require.NoError(t, ApplyPrecompileActivations(cfg, &parent, blockCtx, statedb))
// Pre-activation block during replay (parent < block < activation): still no marker.
sdb = newState()
parent := activation - 100
require.NoError(t, ApplyPrecompileActivations(cfg, &parent, NewBlockContext(big.NewInt(10), activation-1), sdb))
require.False(t, hasMarker(sdb), "no marker before the crossing block")
require.Zero(t, statedb.GetNonce(settleAddr9999),
"0x9999 marker must be written only at genesis (parent==nil), never on a later block")
require.Empty(t, statedb.GetCode(settleAddr9999),
"0x9999 marker code must be written only at genesis, never on a later block")
// The crossing block (parent < activation <= block): marker installed here.
sdb = newState()
parent = activation - 1
require.NoError(t, ApplyPrecompileActivations(cfg, &parent, NewBlockContext(big.NewInt(11), activation), sdb))
require.True(t, hasMarker(sdb), "marker installed on the activation-crossing block")
// A later block wholly after activation (parent >= activation): this transition does
// NOT re-write the marker (in production it is already in committed state).
sdb = newState()
parent = activation
require.NoError(t, ApplyPrecompileActivations(cfg, &parent, NewBlockContext(big.NewInt(12), activation+100), sdb))
require.False(t, hasMarker(sdb), "the marker is written only on the crossing transition, not re-written after")
}
// TestDexSettle_GenesisBuilders_Skip9999 is the AUTHORITATIVE INFO1 regression: with the
// registry bridge imported (so 0x9999 is a live AlwaysOn module in the EVM registry), the
// genesis-config builders must NOT emit a timestamp-0 genesis config for 0x9999. Writing
// one would create a SECOND, conflicting activation path (the module's Configurator would
// run for a system precompile that has no activating config) alongside the AlwaysOn
// mechanism. One and only one activation path. (The extras-package guard test runs without
// the registry import, so 0x9999 is invisible there; THIS test sees it.)
// registry bridge imported (so 0x9999 is a live AlwaysOn module), the genesis-config
// builders must NOT emit a genesis config for 0x9999. Its activation is the AlwaysOn
// protocol mechanism (timestamp-gated dispatch + marker), never a genesis config — one
// and only one activation path.
func TestDexSettle_GenesisBuilders_Skip9999(t *testing.T) {
// Precondition: 0x9999 really is registered as AlwaysOn in this package's module view.
m, ok := modules.GetPrecompileModuleByAddress(settleAddr9999)
require.True(t, ok)
require.True(t, m.AlwaysOn)
@@ -139,8 +147,7 @@ func TestDexSettle_GenesisBuilders_Skip9999(t *testing.T) {
fromFunc := extras.AllGenesisPrecompiles()
_, in := fromFunc[m.ConfigKey]
require.Falsef(t, in,
"AllGenesisPrecompiles must SKIP the AlwaysOn 0x9999 module (%s) — it is activated by the "+
"AlwaysOn mechanism (unconditional dispatch + genesis marker), not a genesis config", m.ConfigKey)
"AllGenesisPrecompiles must SKIP the AlwaysOn 0x9999 module (%s)", m.ConfigKey)
var cfg extras.ChainConfig
cfg.SetAllGenesisPrecompiles()
@@ -149,46 +156,50 @@ func TestDexSettle_GenesisBuilders_Skip9999(t *testing.T) {
"SetAllGenesisPrecompiles must SKIP the AlwaysOn 0x9999 module (%s)", m.ConfigKey)
}
// TestDexSettle_PrecompileOverride_PresentUnconditional asserts the dispatch gate
// TestDexSettle_PrecompileOverride_GatedByActivation asserts the dispatch gate
// (LuxPrecompileOverrider.PrecompileOverride) returns the wrapped 0x9999 settlement
// contract at EVERY timestamp — genesis included — and is immune to the process-global
// lastRulesContext by construction (0x9999 is AlwaysOn, so its presence cannot depend on
// any timestamp). We POISON the global with an arbitrary timestamp first; the override
// must still answer PRESENT from its own field at every timestamp.
func TestDexSettle_PrecompileOverride_PresentUnconditional(t *testing.T) {
// contract at/after activation and ABSENT before it, and is immune to the process-global
// lastRulesContext by construction (the decision is bound to the overrider's OWN
// timestamp field). We POISON the global first; the override still answers from its field.
func TestDexSettle_PrecompileOverride_GatedByActivation(t *testing.T) {
cfg := params.WithExtra(&params.ChainConfig{}, &extras.ChainConfig{})
// Poison the global with a post-activation timestamp — a pre-activation override must
// still answer ABSENT from its own field.
params.SetRulesContext(&params.Rules{}, cfg, activation+1_000_000)
// Poison the global — a correct AlwaysOn override ignores it.
params.SetRulesContext(&params.Rules{}, cfg, 1_700_000_000)
for _, ts := range dispatchTimestamps {
for _, ts := range []uint64{0, 1, activation - 1} {
o := &LuxPrecompileOverrider{chainConfig: cfg, timestamp: ts}
_, ok := o.PrecompileOverride(settleAddr9999)
require.Falsef(t, ok, "0x9999 must NOT dispatch at ts=%d (< activation)", ts)
}
// Poison with a pre-activation timestamp — a post-activation override must still
// answer PRESENT from its own field.
params.SetRulesContext(&params.Rules{}, cfg, 0)
for _, ts := range []uint64{activation, activation + 1, ^uint64(0)} {
o := &LuxPrecompileOverrider{chainConfig: cfg, timestamp: ts}
c, ok := o.PrecompileOverride(settleAddr9999)
require.Truef(t, ok, "0x9999 must dispatch at ts=%d — AlwaysOn from genesis", ts)
require.Truef(t, ok, "0x9999 must dispatch at ts=%d (>= activation)", ts)
require.NotNilf(t, c, "0x9999 override must return the wrapped settlement contract at ts=%d", ts)
}
}
// TestDexSettle_PrecompileOverride_NoGlobalRace is the -race regression for the AlwaysOn
// model. It reproduces the relaunch scenario: a verify goroutine replaying a block
// concurrently with a post-genesis eth_call/estimateGas/worker goroutine that rewrites the
// last-writer-wins global timestamp. Because 0x9999 is AlwaysOn, BOTH overriders MUST
// deterministically see it PRESENT on every iteration — the timestamp gate that once made
// this racy is gone, so presence cannot depend on who wrote the global last.
// TestDexSettle_PrecompileOverride_NoGlobalRace is the -race regression for the
// activation model. It reproduces the relaunch scenario: a verify goroutine replaying a
// post-activation block concurrently with an eth_call/estimateGas/worker goroutine that
// rewrites the last-writer-wins global timestamp. Both overriders are pinned to their OWN
// post-activation timestamps, so both MUST deterministically see 0x9999 PRESENT on every
// iteration — presence cannot depend on who wrote the global last.
//
// Run with: go test -race -run NoGlobalRace ./core/
func TestDexSettle_PrecompileOverride_NoGlobalRace(t *testing.T) {
cfg := params.WithExtra(&params.ChainConfig{}, &extras.ChainConfig{})
a := &LuxPrecompileOverrider{chainConfig: cfg, timestamp: 0} // genesis replay
b := &LuxPrecompileOverrider{chainConfig: cfg, timestamp: 1_700_000_000} // later block / eth_call
a := &LuxPrecompileOverrider{chainConfig: cfg, timestamp: activation} // activation-block replay
b := &LuxPrecompileOverrider{chainConfig: cfg, timestamp: activation + 1 << 30} // later block / eth_call
const iters = 2000
done := make(chan struct{})
// Adversary: continuously clobber the global with alternating timestamps, mimicking
// concurrent eth_call (wall-clock) and any block-replay rules construction. This is
// precisely what SetRulesContext does on every Rules()/RulesAt() across goroutines.
go func() {
r := &params.Rules{}
for {
@@ -196,8 +207,8 @@ func TestDexSettle_PrecompileOverride_NoGlobalRace(t *testing.T) {
case <-done:
return
default:
params.SetRulesContext(r, cfg, 1_700_000_000)
params.SetRulesContext(r, cfg, 0)
params.SetRulesContext(r, cfg, activation+1)
params.SetRulesContext(r, cfg, 0) // pre-activation clobber
}
}
}()
@@ -210,8 +221,8 @@ func TestDexSettle_PrecompileOverride_NoGlobalRace(t *testing.T) {
defer wg.Done()
for i := 0; i < iters; i++ {
if _, ok := o.PrecompileOverride(settleAddr9999); !ok {
t.Errorf("0x9999 override saw ABSENT at iter %d — AlwaysOn must be present on "+
"every replay regardless of the concurrent global timestamp", i)
t.Errorf("0x9999 override saw ABSENT at iter %d — a post-activation override must be "+
"present on every replay regardless of the concurrent global timestamp", i)
return
}
}
+43 -22
View File
@@ -29,28 +29,17 @@ import (
func ApplyPrecompileActivations(c *params.ChainConfig, parentTimestamp *uint64, blockContext contract.ConfigurationBlockContext, statedb *state.StateDB) error {
blockTimestamp := blockContext.Timestamp()
// System precompiles (the DEX settlement money path 0x9999) are AlwaysOn: a
// FIRST-RUN, no-legacy feature with no dated fork and no pre-activation history to
// protect. Their EXTCODESIZE marker (nonce=1 + a non-empty code byte) is installed
// ONCE, at genesis, so EXTCODESIZE>0, eth_getCode!=0x, and Solidity's
// contract-existence guard pass for a typed call from block 0.
//
// Genesis is the parent==nil transition: core/genesis.go calls this with a nil
// parentTimestamp while building the genesis block, BEFORE the genesis state root is
// committed (statedb.IntermediateRoot), so the marker lands in the committed genesis
// root deterministically on every node. Every later block has a non-nil parent, so
// the marker is written exactly once and never re-touched. The module's Configurator
// is NOT invoked (a system precompile has no activating config; all params resolve at
// runtime from the consensus context); only the EXTCODESIZE marker is installed.
if parentTimestamp == nil {
for _, module := range modules.AlwaysOnModules() {
// Standard precompile-activation marker (identical to the config-driven path
// below): a non-empty nonce + code so the account is non-empty (not GC'd on
// finalize) and callable from Solidity.
statedb.SetNonce(module.Address, 1, tracing.NonceChangeUnspecified)
_ = statedb.SetCode(module.Address, []byte{0x1}, tracing.CodeChangeUnspecified)
}
}
// AlwaysOn system precompiles (the DEX settlement money path 0x9999) are activated by
// the Lux protocol on every chain with NO per-network config, at their protocol
// ActivationTime (a fork-like timestamp constant, set by the registry bridge), NOT
// unconditionally at genesis. Their EXTCODESIZE marker (nonce=1 + a non-empty code
// byte) is installed exactly ONCE, on the block transition that crosses ActivationTime
// — which is genesis IFF ActivationTime <= the genesis timestamp. A chain whose genesis
// predates ActivationTime does NOT get the marker in its genesis state (its original
// pre-precompile genesis hash is preserved byte-identically); the marker is instead
// installed later, during history replay, at the first block whose timestamp reaches
// ActivationTime. This mirrors the config-driven activation crossing logic below.
applyAlwaysOnActivations(parentTimestamp, blockTimestamp, statedb)
// Note: [modules.RegisteredModules] returns precompiles sorted by module addresses.
// This ensures:
@@ -98,6 +87,38 @@ func ApplyPrecompileActivations(c *params.ChainConfig, parentTimestamp *uint64,
return nil
}
// applyAlwaysOnActivations installs the EXTCODESIZE marker (nonce=1 + a non-empty code
// byte) for each AlwaysOn system precompile whose protocol ActivationTime is crossed by
// this block transition, so EXTCODESIZE>0 / eth_getCode!=0x / Solidity's contract-
// existence guard pass from the activation block forward.
//
// "Crossed by this transition" is defined exactly like the config-driven precompile
// activation below (GetActivatingPrecompileConfigs):
// - genesis (parentTimestamp == nil): crossed iff ActivationTime <= blockTimestamp,
// so the marker lands in the committed genesis state root ONLY for a chain genesised
// at/after ActivationTime. A pre-activation chain's genesis root is untouched.
// - later block (parentTimestamp != nil): crossed iff
// *parentTimestamp < ActivationTime <= blockTimestamp — the single transition where
// the precompile turns on during history replay.
// The marker is therefore written exactly once, deterministically, on every node. An
// AlwaysOn module's Configurator is NOT invoked (a system precompile has no activating
// config; all params resolve at runtime from the consensus context) — only the marker.
func applyAlwaysOnActivations(parentTimestamp *uint64, blockTimestamp uint64, statedb *state.StateDB) {
for _, module := range modules.AlwaysOnModules() {
var crossed bool
if parentTimestamp == nil {
crossed = module.ActivationTime <= blockTimestamp
} else {
crossed = *parentTimestamp < module.ActivationTime && module.ActivationTime <= blockTimestamp
}
if !crossed {
continue
}
statedb.SetNonce(module.Address, 1, tracing.NonceChangeUnspecified)
_ = statedb.SetCode(module.Address, []byte{0x1}, tracing.CodeChangeUnspecified)
}
}
// applyStateUpgrades checks if any of the state upgrades specified by the chain config are activated by the block
// transition from [parentTimestamp] to the timestamp set in [header]. If this is the case, it calls [Configure]
// to apply the necessary state transitions for the upgrade.
+13 -7
View File
@@ -455,14 +455,20 @@ func GetExtrasRules(ethRules Rules, c *ChainConfig, timestamp uint64) *extras.Ru
}
// System precompiles (the DEX settlement money path 0x9999) are AlwaysOn: a
// FIRST-RUN, no-legacy feature, active from genesis on every Lux chain with no dated
// fork and 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 are injected into the enabled
// dispatch set UNCONDITIONALLY, at every timestamp including genesis. This is the set
// PrecompileOverride consults; the matching EXTCODESIZE marker is installed once at
// genesis (parent==nil) in ApplyPrecompileActivations.
// 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()
}
+4 -4
View File
@@ -32,8 +32,8 @@ func TestAllGenesisPrecompilesDeterminism(t *testing.T) {
// Verify all registered NON-AlwaysOn modules are present, and that NO AlwaysOn
// module is present. AlwaysOn precompiles (e.g. the 0x9999 DEX settlement money
// path) are activated by the AlwaysOn mechanism (unconditional dispatch + a one-time
// genesis marker), NOT by a timestamp-0 genesis config; emitting one would create a
// path) are activated by the AlwaysOn mechanism (protocol-timestamp-gated dispatch +
// activation-crossing marker), NOT by a timestamp-0 genesis config; emitting one would create a
// second, conflicting activation path.
for _, module := range modules.RegisteredModules() {
_, ok := result1[module.ConfigKey]
@@ -51,8 +51,8 @@ func TestAllGenesisPrecompilesDeterminism(t *testing.T) {
// builders (AllGenesisPrecompiles and ChainConfig.SetAllGenesisPrecompiles) must never
// emit a timestamp-0 genesis config for an AlwaysOn module. Writing one would create a
// SECOND, conflicting activation path for an AlwaysOn system precompile (0x9999) — its
// Configurator would run alongside the AlwaysOn mechanism (unconditional dispatch +
// genesis marker). The invariant is enforced positively (skip any module with AlwaysOn)
// Configurator would run alongside the AlwaysOn mechanism (protocol-timestamp-gated
// dispatch + activation-crossing marker). The invariant is enforced positively (skip any module with AlwaysOn)
// so it holds even if an AlwaysOn module is later registered into this package's view.
func TestGenesisPrecompileBuilders_SkipAlwaysOn(t *testing.T) {
fromFunc := AllGenesisPrecompiles()
+13 -9
View File
@@ -15,16 +15,20 @@ import (
var errCannotBeNil = fmt.Errorf("timestamp cannot be nil")
// The DEX settlement money path 0x9999 is a FIRST-RUN, no-legacy system precompile: it
// is ACTIVE FROM GENESIS on every Lux chain, with no dated fork and no per-net config.
// There is no pre-activation history to protect, so there is no activation timestamp,
// no dispatch gate, and no "behaves as a plain account before activation" branch.
// 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 is unconditional (params/config_extra.go GetExtrasRules injects every
// modules.AlwaysOnModules() entry into the enabled set at every timestamp), and the
// EXTCODESIZE marker (SetNonce=1 + SetCode{0x1}) is installed once at genesis
// (core/state_processor_ext.go ApplyPrecompileActivations, on the parent==nil
// transition) so it lands in the committed genesis state root. See modules.AlwaysOn.
// 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 {
+19 -8
View File
@@ -20,15 +20,26 @@ type Module struct {
Contract contract.StatefulPrecompiledContract
// Configurator is used to configure the stateful precompile when the config is enabled.
contract.Configurator
// AlwaysOn marks a precompile that is ACTIVE on every chain from genesis with NO
// config entry (neither a genesis-inlined precompile nor a precompileUpgrades
// timestamp). It is the one-way activation for system precompiles that take no
// per-network parameters and resolve everything from the runtime (consensus
// context / atomic state). The DEX settlement precompile (0x9999) is the canonical
// case. An always-on module's Configurator is never invoked (no activating config);
// the host gives it the EXTCODESIZE marker at genesis and dispatches Run on every
// block. Bridged from github.com/luxfi/precompile/modules.Module.AlwaysOn.
// AlwaysOn marks a precompile that is a SYSTEM precompile activated by the Lux
// protocol on every chain with NO per-network config entry (neither a genesis-inlined
// precompile nor a precompileUpgrades timestamp). It takes no per-network parameters
// and resolves everything from the runtime (consensus context / atomic state). The DEX
// settlement precompile (0x9999) is the canonical case. An always-on module's
// Configurator is never invoked (no activating config); the host installs its
// EXTCODESIZE marker and dispatches Run. Bridged from
// github.com/luxfi/precompile/modules.Module.AlwaysOn.
AlwaysOn bool
// ActivationTime is the protocol timestamp at which an AlwaysOn system precompile
// becomes present (EXTCODESIZE marker installed + Run dispatched) on every Lux chain.
// It is NOT per-network config — it is a protocol constant, the analog of a fork
// timestamp, applied uniformly. The zero value means "present from genesis" (the
// historical always-on behavior). A non-zero value means the precompile is ABSENT
// before that timestamp and activates at the block transition crossing it — exactly
// like a dated fork — so a chain whose genesis predates ActivationTime does NOT carry
// the precompile in its genesis state (preserving that chain's original genesis hash),
// and the precompile activates later during history replay at the crossing block.
// Ignored unless AlwaysOn is set. Set by the registry bridge, not the JSON config.
ActivationTime uint64
}
type moduleArray []Module
+7 -4
View File
@@ -121,10 +121,13 @@ func RegisteredModules() []Module {
return registeredModules
}
// AlwaysOnModules returns the registered modules marked AlwaysOn — precompiles active
// on every chain from genesis with NO config entry. The host activates these
// unconditionally (EXTCODESIZE marker at genesis + Run dispatch on every block),
// independent of genesisPrecompiles / precompileUpgrades. Deterministic address order.
// AlwaysOnModules returns the registered modules marked AlwaysOn — system precompiles
// activated by the Lux protocol on every chain with NO config entry (independent of
// genesisPrecompiles / precompileUpgrades). The host activates each at its
// Module.ActivationTime — installing the EXTCODESIZE marker on the block transition that
// crosses it (genesis iff ActivationTime <= the genesis timestamp) and dispatching Run
// only at/after it — so a chain born before ActivationTime keeps its original genesis.
// Deterministic address order.
func AlwaysOnModules() []Module {
out := make([]Module, 0, len(registeredModules))
for _, m := range registeredModules {
+27
View File
@@ -37,6 +37,21 @@ import (
"github.com/luxfi/vm/chains/atomic"
)
// DexSettleActivationTime is the Lux-protocol timestamp at which the AlwaysOn DEX
// settlement system precompile (0x9999) becomes present — EXTCODESIZE marker installed
// and Run dispatched — on every Lux chain. 2025-12-25T23:20:00Z.
//
// It is a protocol constant (the analog of a fork timestamp), NOT per-network JSON
// config, so it applies uniformly and needs no genesisPrecompiles / precompileUpgrades
// entry. Every Lux chain in existence was (re-)genesised BEFORE this timestamp (Lux
// mainnet/testnet and Zoo mainnet block-0 are all Nov 2024), so none of them carry
// 0x9999 in their ORIGINAL genesis state — this constant is what keeps their genesis
// hashes byte-identical after the fix, while 0x9999 still activates later, at the block
// that crosses this timestamp during history replay (verified: ZERO 0x9999 usage on
// mainnet before it, so activation introduces no state divergence). A chain genesised
// at/after this timestamp gets 0x9999 in its genesis state.
const DexSettleActivationTime uint64 = 1766704800
func init() {
bridgeExternalModules()
}
@@ -57,6 +72,18 @@ func bridgeExternalModules() {
Configurator: &configuratorBridge{ext: extMod.Configurator, key: extMod.ConfigKey},
AlwaysOn: extMod.AlwaysOn,
}
// Lux activates its AlwaysOn system precompiles (the DEX settlement family,
// 0x9999) at the protocol timestamp DexSettleActivationTime, not at genesis.
// This is the Lux-network activation SCHEDULE for the precompile (the external
// module provides only the CODE); it is set here rather than in the external
// module so pre-activation chains reproduce their original genesis hash while
// 0x9999 still turns on, network-wide, at the crossing block. The external
// AlwaysOn module carries no activation time, so a genesis-active AlwaysOn
// module cannot occur today; if one is ever added it must declare ActivationTime
// explicitly here.
if extMod.AlwaysOn {
intMod.ActivationTime = DexSettleActivationTime
}
if err := modules.RegisterBridgedModule(intMod); err != nil {
log.Warn("Failed to bridge external precompile module",