mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
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:
@@ -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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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(¶ms.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 := ¶ms.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 := ¶ms.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(¶ms.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(¶ms.Rules{}, cfg, activation+1_000_000)
|
||||
|
||||
// Poison the global — a correct AlwaysOn override ignores it.
|
||||
params.SetRulesContext(¶ms.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(¶ms.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(¶ms.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 := ¶ms.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
@@ -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
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user