mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
Merge: dex 0x9999 always-on settlement + runtime DChainID (v1.99.32)
This commit is contained in:
@@ -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(¶ms.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 := ¶ms.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)")
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user