Merge: dex 0x9999 always-on settlement + runtime DChainID (v1.99.32)

This commit is contained in:
2026-06-21 09:21:12 -07:00
10 changed files with 180 additions and 3 deletions
+91
View File
@@ -0,0 +1,91 @@
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package core
import (
"math/big"
"testing"
"github.com/luxfi/evm/core/state"
"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/geth/common"
"github.com/luxfi/geth/core/rawdb"
"github.com/stretchr/testify/require"
)
// precompile_alwayson_test.go pins the always-on activation of the DEX settlement
// money path 0x9999. It is ACTIVE on every chain from genesis with NO config entry
// (no dexSettleConfig genesis precompile, no precompileUpgrades timestamp). Activation
// is DISPATCH-only by design: the precompile is in the enabled set so the EVM runs it
// for a tx-to-0x9999, a low-level CALL/STATICCALL, and the 0x9010 V4 forward. It
// deliberately gets NO genesis state write — the genesis root is consensus-critical
// and immutable for existing networks, so always-on must never change the genesis hash.
//
// If always-on regresses, 0x9999 would only be callable on chains carrying a
// dexSettleConfig entry — exactly the per-net config this design kills.
var settleAddr9999 = common.HexToAddress("0x0000000000000000000000000000000000009999")
// TestAlwaysOn_9999_Registered asserts the DEX settlement precompile bridges into the
// EVM registry as AlwaysOn and is enumerated by AlwaysOnModules().
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 always-on money path", m.ConfigKey)
found := false
for _, am := range modules.AlwaysOnModules() {
if am.Address == settleAddr9999 {
found = true
}
}
require.True(t, found, "0x9999 must be enumerated by modules.AlwaysOnModules()")
}
// TestAlwaysOn_9999_DispatchEnabledWithNoConfig asserts 0x9999 is in the active
// precompile set on a chain whose config carries NO precompile activation whatsoever
// (no genesis precompiles, no upgrades). This is the dispatch gate the EVM consults in
// PrecompileOverride: if it is enabled, the precompile runs. Always-on guarantees it is
// enabled on every chain at every timestamp with zero config.
func TestAlwaysOn_9999_DispatchEnabledWithNoConfig(t *testing.T) {
// A bare chain config — nothing activates 0x9999 via config; only always-on can.
cfg := params.WithExtra(&params.ChainConfig{}, &extras.ChainConfig{})
// GetExtrasRules is the pure function that builds the per-block enabled precompile
// set (PrecompileOverride consults its IsPrecompileEnabled). Always-on injects 0x9999
// here unconditionally. Assert it is present in the enabled set with NO config, at
// genesis and at a far-future timestamp (always-on is timestamp-independent).
rules0 := params.GetExtrasRules(params.Rules{}, cfg, 0)
_, ok0 := rules0.Precompiles[settleAddr9999]
require.True(t, ok0,
"0x9999 must be in the enabled precompile set at genesis with NO config — the always-on money path")
rulesLater := params.GetExtrasRules(params.Rules{}, cfg, 2_000_000_000)
_, okLater := rulesLater.Precompiles[settleAddr9999]
require.True(t, okLater, "0x9999 must stay in the enabled set at every timestamp")
}
// TestAlwaysOn_NoGenesisStateWrite asserts always-on does NOT mutate genesis state:
// ApplyPrecompileActivations at genesis with a bare config leaves 0x9999 with no code
// and no nonce. This guards the consensus-critical invariant that always-on never
// changes the genesis hash (which would fork existing networks).
func TestAlwaysOn_NoGenesisStateWrite(t *testing.T) {
db := rawdb.NewMemoryDatabase()
statedb, err := state.New(common.Hash{}, state.NewDatabase(db), nil)
require.NoError(t, err)
cfg := &params.ChainConfig{}
blockCtx := NewBlockContext(big.NewInt(0), 0)
require.NoError(t, ApplyPrecompileActivations(cfg, nil, blockCtx, statedb))
require.Zero(t, statedb.GetNonce(settleAddr9999),
"0x9999 must NOT get a genesis nonce — always-on writes no genesis state (genesis hash is immutable)")
require.Empty(t, statedb.GetCode(settleAddr9999),
"0x9999 must NOT get genesis code — always-on writes no genesis state (genesis hash is immutable)")
}
+24
View File
@@ -189,6 +189,30 @@ func (a *accessibleStateAdapter) CChainID() ids.ID {
return ids.Empty
}
// DChainID resolves the D-Chain (dexvm) blockchain id from the runtime chain topology
// — the consensus context's blockchain-alias lookup of "D". The node registers the
// dexvm chain under the "D"/"dex"/"dexvm" aliases at startup (initChainAliases), before
// any chain bootstraps, so the lookup is populated and identical on every validator and
// every re-execution. This is the always-on DEX settlement seam's D peer with ZERO
// per-net config. Returns ids.Empty when the runtime is absent or the network has no
// dexvm deployed (the "D" alias does not resolve) — the calling precompile then keeps
// the seam closed rather than guess a peer.
func (a *accessibleStateAdapter) DChainID() ids.ID {
rt := a.runtimeFromCtx()
if rt == nil {
return ids.Empty
}
bc := rt.GetBCLookup()
if bc == nil {
return ids.Empty
}
dID, err := bc.Lookup("D")
if err != nil {
return ids.Empty
}
return dID
}
func (a *accessibleStateAdapter) TxID() ids.ID {
// The EVM tx hash is the transaction identity the precompile binds its
// cross-chain object to. ids.ID and common.Hash are both 32 bytes.
+13
View File
@@ -28,6 +28,19 @@ import (
// In block processing and building, [ApplyUpgrades] is called instead which also applies state upgrades.
func ApplyPrecompileActivations(c *params.ChainConfig, parentTimestamp *uint64, blockContext contract.ConfigurationBlockContext, statedb *state.StateDB) error {
blockTimestamp := blockContext.Timestamp()
// Always-on precompiles (e.g. the DEX settlement money path 0x9999) are NOT
// activated here and intentionally get NO genesis state write. The genesis state
// root is consensus-critical and immutable for every existing network — writing an
// EXTCODESIZE marker (nonce/code) at genesis would change the genesis hash and
// fork live chains (a fresh-syncing node would compute a hash that disagrees with
// the committed genesis). Always-on activation is therefore DISPATCH-only: the
// precompile is in the enabled set (params.GetExtrasRules injects it) so the EVM
// runs it for a tx-to-0x9999, a low-level CALL/STATICCALL, and the 0x9010 V4
// forward — none of which need a code marker. The module's Configurator is never
// invoked (there is no activating config; all params resolve at runtime). See the
// dispatch injection in params/config_extra.go GetExtrasRules.
// Note: [modules.RegisteredModules] returns precompiles sorted by module addresses.
// This ensures:
// - the order we call [modules.Module]'s Configure for each precompile is consistent
+1 -1
View File
@@ -32,7 +32,7 @@ require (
github.com/luxfi/metric v1.5.8
github.com/luxfi/p2p v1.21.1
github.com/luxfi/pq v1.0.3
github.com/luxfi/precompile v0.5.51
github.com/luxfi/precompile v0.5.52
github.com/luxfi/runtime v1.1.1
github.com/luxfi/sys v0.1.0
github.com/luxfi/timer v1.0.2
+2 -2
View File
@@ -276,8 +276,8 @@ github.com/luxfi/p2p v1.21.1 h1:gmz1JMDhzHIL3dQlhwIDvR4OlFuhNVfnWUl/ipYhAIo=
github.com/luxfi/p2p v1.21.1/go.mod h1:SsNPR5fPGWWNem9plGWhSmRqyDoysJ3kPAN0zG0g3iw=
github.com/luxfi/pq v1.0.3 h1:pFlQm1+5FuKTDUh2y/23bXWkN4I2Rc5iuxJypwDFFMs=
github.com/luxfi/pq v1.0.3/go.mod h1:8bppZcRElfrVt0n3nYCZW3iX1TvhvzNbdjNdK1irgIE=
github.com/luxfi/precompile v0.5.51 h1:2Xv1GdOPZ1ZKta2S+zS278NxZxoFFmWUV9LWQmZqNy4=
github.com/luxfi/precompile v0.5.51/go.mod h1:aR523IXj+VQAIU1CYP/64dVwKK93DDssHj1tGFdzLTY=
github.com/luxfi/precompile v0.5.52 h1:1gw4iJEv5Tj8bBwNctlMympe3lIzhmFymgetKglqZGU=
github.com/luxfi/precompile v0.5.52/go.mod h1:aR523IXj+VQAIU1CYP/64dVwKK93DDssHj1tGFdzLTY=
github.com/luxfi/proto v1.3.4 h1:dNrdQ/414Pe9otIekVUmQ9QLqemaMFK43H4N3DIbuzQ=
github.com/luxfi/proto v1.3.4/go.mod h1:S80KrTzISCktptit/pWLzk6nHgagjJS86qbQL6gHutE=
github.com/luxfi/resource v0.0.1 h1:mTh+ICWSy548GTUSSyx7V/X5dV18oEwxZeQEYGJQhD4=
+11
View File
@@ -458,5 +458,16 @@ func GetExtrasRules(ethRules Rules, c *ChainConfig, timestamp uint64) *extras.Ru
}
}
// Always-on precompiles (e.g. the DEX settlement money path 0x9999) are ACTIVE on
// every chain at every timestamp with NO config entry — neither a genesis-inlined
// precompile nor a precompileUpgrades timestamp. They take no per-net parameters and
// resolve everything at runtime (consensus context / atomic state), so we add them
// to the enabled set unconditionally here. This is the ONE activation way for them;
// there is no per-net config to write, ever. (The EXTCODESIZE genesis marker is
// applied separately in ApplyPrecompileActivations.)
for _, module := range modules.AlwaysOnModules() {
rules.Precompiles[module.Address] = module.Configurator.MakeGenesisConfig()
}
return rules
}
+6
View File
@@ -29,6 +29,12 @@ type AtomicState interface {
ChainID() ids.ID
// CChainID is the C-Chain peer id (== ChainID on the C-Chain itself).
CChainID() ids.ID
// DChainID is the D-Chain (dexvm) blockchain id the C<->D atomic seam routes to,
// resolved by the host from the chain topology (the consensus context's
// blockchain-alias lookup of "D"). ids.Empty on a network with no dexvm deployed.
// This is the runtime-resolved D peer for the always-on DEX settlement precompile
// (0x9999) — zero per-net config; deterministic per network on every validator.
DChainID() ids.ID
// TxID is the executing transaction's id.
TxID() ids.ID
// CallIndex is this precompile invocation's per-tx ordinal.
+9
View File
@@ -20,6 +20,15 @@ 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 bool
}
type moduleArray []Module
+14
View File
@@ -121,6 +121,20 @@ 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.
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.
+9
View File
@@ -53,6 +53,7 @@ func bridgeExternalModules() {
Address: extMod.Address,
Contract: &contractBridge{ext: extMod.Contract},
Configurator: &configuratorBridge{ext: extMod.Configurator, key: extMod.ConfigKey},
AlwaysOn: extMod.AlwaysOn,
}
if err := modules.RegisterBridgedModule(intMod); err != nil {
@@ -141,6 +142,7 @@ type internalAtomic interface {
NetworkID() uint32
ChainID() ids.ID
CChainID() ids.ID
DChainID() ids.ID
TxID() ids.ID
CallIndex() uint32
}
@@ -180,6 +182,13 @@ func (a *accessibleStateBridge) CChainID() ids.ID {
return ids.Empty
}
func (a *accessibleStateBridge) DChainID() ids.ID {
if at := a.atomicOrNil(); at != nil {
return at.DChainID()
}
return ids.Empty
}
func (a *accessibleStateBridge) TxID() ids.ID {
if at := a.atomicOrNil(); at != nil {
return at.TxID()