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

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

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

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

157 lines
5.2 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package modules
import (
"fmt"
"sort"
"github.com/luxfi/constants"
"github.com/luxfi/evm/utils"
"github.com/luxfi/geth/common"
)
var (
// registeredModules is a list of Module to preserve order
// for deterministic iteration
registeredModules = make([]Module, 0)
// Reserved address ranges for stateful precompiles
//
// LP-ALIGNED ADDRESSING (LP-9015):
// BASE = 0x10000, address = BASE + (P << 12) | (C << 8) | II
// P = Family (LP range first digit), C = Chain slot, II = Item index
//
// Family Pages (P nibble aligns with LP-xxxx first digit):
// P=0: 0x10000-0x10FFF - Core/Network (AllowLists, Minting, Rewards)
// P=2: 0x12000-0x12FFF - LP-2xxx (Q-Chain, PQ Identity)
// P=3: 0x13000-0x13FFF - LP-3xxx (C-Chain, EVM/Crypto)
// P=4: 0x14000-0x14FFF - LP-4xxx (Z-Chain, Privacy/ZK)
// P=5: 0x15000-0x15FFF - LP-5xxx (T-Chain, Threshold/MPC)
// P=6: 0x16000-0x16FFF - LP-6xxx (B-Chain, Bridges)
// P=7: 0x17000-0x17FFF - LP-7xxx (A-Chain, AI)
// P=9: 0x19000-0x19FFF - LP-9xxx (DEX/Markets)
//
// Chain Slots (C nibble):
// 0=P-Chain, 1=X-Chain, 2=C-Chain, 3=Q-Chain, 4=A-Chain
// 5=B-Chain, 6=Z-Chain, 7=M-Chain, 8=Zoo, 9=Hanzo, A=SPC
reservedRanges = []utils.AddressRange{
// LP-aligned precompile range: 0x10000-0x1FFFF
{
Start: common.HexToAddress("0x0000000000000000000000000000000000010000"),
End: common.HexToAddress("0x000000000000000000000000000000000001ffff"),
},
// Legacy coreth-compatible precompile range: 0x0200000000000000000000000000000000000000 - 0x02000000000000000000000000000000000000ff
// Required for canonical mainnet (chain ID 96369) genesis hash compatibility:
// historical warp precompile lives at 0x0200000000000000000000000000000000000005.
{
Start: common.HexToAddress("0x0200000000000000000000000000000000000000"),
End: common.HexToAddress("0x02000000000000000000000000000000000000ff"),
},
}
)
// ReservedAddress returns true if [addr] is in a reserved range for custom precompiles
func ReservedAddress(addr common.Address) bool {
for _, reservedRange := range reservedRanges {
if reservedRange.Contains(addr) {
return true
}
}
return false
}
// RegisterModule registers a stateful precompile module
func RegisterModule(stm Module) error {
address := stm.Address
key := stm.ConfigKey
if address == constants.BlackholeAddr {
return fmt.Errorf("address %s overlaps with blackhole address", address)
}
if !ReservedAddress(address) {
return fmt.Errorf("address %s not in a reserved range", address)
}
return registerModuleInternal(stm, address, key)
}
// RegisterBridgedModule registers a module bridged from the external precompile
// registry. It skips the reserved address range check because external precompiles
// use their own address scheme (LP-aligned, burn addresses, etc.).
func RegisterBridgedModule(stm Module) error {
return registerModuleInternal(stm, stm.Address, stm.ConfigKey)
}
func registerModuleInternal(stm Module, address common.Address, key string) error {
for _, registeredModule := range registeredModules {
if registeredModule.ConfigKey == key {
return fmt.Errorf("name %s already used by a stateful precompile", key)
}
if registeredModule.Address == address {
return fmt.Errorf("address %s already used by a stateful precompile", address)
}
}
// sort by address to ensure deterministic iteration
registeredModules = insertSortedByAddress(registeredModules, stm)
return nil
}
func GetPrecompileModuleByAddress(address common.Address) (Module, bool) {
for _, stm := range registeredModules {
if stm.Address == address {
return stm, true
}
}
return Module{}, false
}
func GetPrecompileModule(key string) (Module, bool) {
for _, stm := range registeredModules {
if stm.ConfigKey == key {
return stm, true
}
}
return Module{}, false
}
func RegisteredModules() []Module {
return registeredModules
}
// 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 {
if m.AlwaysOn {
out = append(out, m)
}
}
return out
}
// AllGenesisPrecompiles returns a map of all registered precompile configs
// configured for genesis activation (timestamp = 0). This is used to populate
// the genesisPrecompiles field in ChainConfig for deterministic genesis hash.
func AllGenesisPrecompiles() map[string]interface{} {
result := make(map[string]interface{})
for _, module := range registeredModules {
result[module.ConfigKey] = module.Configurator.MakeGenesisConfig()
}
return result
}
func insertSortedByAddress(data []Module, stm Module) []Module {
data = append(data, stm)
sort.Sort(moduleArray(data))
return data
}