mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
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]>
157 lines
5.2 KiB
Go
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
|
|
}
|