Commit Graph
275 Commits
Author SHA1 Message Date
zeekayandHanzo Dev 8c06c66a86 registry: link the precompile suite by its own registry
This file named 41 suite packages one by one so their init() would run, because
luxd rejects an upgrade.json activation for a configKey it has never seen with
"unknown precompile config". That made it a second record of what the suite
contains, and it had drifted: aivmbridge, swap and v3 registered in the suite and
were absent here, so no Lux chain could activate them however its genesis was
written.

Importing the suite's registry instead cannot drift — that package derives its set
from the packages that actually register rather than from a list. One record, kept
in the repository that owns the thing recorded.

The list was also pinned to suite v0.19.8 while the suite had moved to v0.19.21,
so even the import alone left 14 of 56 modules linked. With the bump the surface
a luxd built from this module dispatches is 56, which is exactly what the suite
holds — measurable rather than asserted: cmd/surface prints it, and it is the
list an upgrade.json is allowed to name.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-10 17:59:23 -07:00
zeekayandHanzo Dev 9f54b4984a Depend on the renamed utility package
github.com/luxfi/utils is now github.com/luxfi/util. Import paths and the
module requirement follow the rename, and the luxUtils alias becomes luxUtil.
The old module stays as an indirect requirement that published dependencies
still pull in.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-06 04:33:21 -07:00
zeekayandHanzo Dev b50fd4ba8f Take the geth address conversion locally now that BlackholeAddr is an array
constants.BlackholeAddr is a plain [20]byte, so constants no longer imports
geth. The fourteen sites here all want a geth common.Address, and each file
already imports geth/common, so each takes the free conversion itself.

Only one site was a compile error: the .Bytes() method call in
rewardmanager.DisableFeeRewards. Everywhere else [20]byte is assignable to
common.Address, so the change was invisible to the compiler, and the two
require.Equal assertions in rewardmanager would have compiled and then failed
at run time, because reflect.DeepEqual compares types before bytes. Those two
are wrapped for that reason, not for style.

The two failures in core, TestHeadPin_Across4096Boundary_NoLeak and the
invalid-chainID-0 panic in TestCreateThenDeletePostByzantium, reproduce
identically with these lines reverted and are unrelated.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-06 00:28:23 -07:00
zeekayandHanzo Dev 6337b219aa style: gofmt the tracked EVM tree
Co-authored-by: Hanzo Dev <[email protected]>
2026-08-21 11:51:32 -07:00
zeekayandHanzo Dev cf0561e110 evm: adopt a certified wrapper without replaying its accepted block
Co-authored-by: Hanzo Dev <[email protected]>
2026-08-19 00:31:35 -07:00
zeekayandHanzo Dev e8187ae5a5 Dial /v1, the only prefix luxd serves
luxd pins baseURL="/v1" (node/server/http/server.go:33) and has never
served /ext, so both exported constructors — plugin/evm/client.NewClient
and warp.NewClient — built a path no node answers. They are the seam
every out-of-tree caller goes through: cli/pkg/utils/staking.go,
sdk/validatormanager/uptime.go and netrunner/api all hand them a bare
node URI, and staking.go's own SplitRPCURI already parsed /v1/bc/<id>/rpc.
Those callers were already on /v1; this package was the lagging outlier,
so fixing it here fixes all of them without touching one.

Also corrects the simulator's default websocket endpoint, the hardhat
default, and the docs — including api.testnet.lux.network, which does not
serve RPC at all. The testnet host is api.lux-test.network (chainId
0x17870). Prefix-only sweeps miss that class of error.

Tests pin the constructed path against an httptest server, with both an
alias and a blockchain ID, so a future prefix change fails loudly instead
of silently 404ing in production.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-27 19:07:09 -07:00
zeekayandHanzo Dev c56386b415 stakeweight: make P-chain stake a usable on-chain vote weight
P-chain stake is not natively readable from the C-chain EVM, and the obvious
fix — a precompile that reads the validator set in Run() — is consensus-fatal.
The BlockContext handed to Run() (precompile/contract/interfaces.go:63) carries
Number, Timestamp and GetPredicateResults, and nothing else; a validator-set
read from there would have to use the node's own current P-chain height
(node vms/platformvm/validators/manager.go:214 getCurrentHeight ->
state.GetLastAccepted), so two honest nodes executing the same block a second
apart would compute different weights and fork the chain.

The one place a P-chain height IS agreed is predicate verification:
Block.VerifyWithContext passes ProposerVMBlockCtx.PChainHeight to
VerifyPredicate (plugin/evm/block.go:283), the pass/fail bitset is written into
the header (miner/worker.go:514) and re-checked on replay
(plugin/evm/block.go:366). A ballot verified there is a consensus-committed
fact, and historical sets stay reconstructible from P-chain weight diffs.

So the ballot CLAIMS a weight and the predicate refuses any overstatement:
  weight <= weightAt(ballot.pChainHeight)  - no flash-stake
  weight <= weightAt(block.pChainHeight)   - no exited or shrunken validator
  totalWeight == totalAt(snapshot)         - the denominator is exact
Under-claiming is always allowed, so a voter never has to predict which block
its transaction lands in.

The unit of weight is the validator, and it already includes delegations: the
P-chain keeps one weight per nodeID and folds each delegator into it
(node vms/platformvm/state/state_validators.go:570,582 — AddStaker(own stake)
then AddWeight(each delegator) under the same key). One map entry per nodeID is
what makes double-counting a delegation impossible. Delegators do not get a
separate ballot; their override is exit, which needs no code.

Authorisation reuses the BLS key the P-chain already binds to the nodeID, so
there is no registry to capture, no admin to hold it and no key ceremony. The
signature covers (domain, chainID, nodeID, voter, authEpoch) only — a standing
authorisation, reusable for every later proposal, rotated by signing a strictly
greater epoch.

Uptime is deliberately not a gate: it is absent from GetValidatorOutput and is
a per-node subjective measurement (all five mainnet validators report
uptime 0.0000 through the public API), so no deterministic rule can read it.

Proven against a read-only fork of mainnet 96369 — the real five-validator set,
real registered BLS keys, real 5e17/2.5e18 weights — including that no ballot
is accepted for any of them without the operator's key.

Address 0x0200...0006 is the next free slot after warp; eth_getCode on 96369
returns 0x for it and 0x01 for warp, so predicates are already live there.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-27 13:36:21 -07:00
zeekayandHanzo Dev 9c6d66ef6b eth,warp: gate EXPORT surfaces on the ⅔-stake Quasar tier, not the Nova accept tip (v1.36)
The two-tier consensus (v1.36) advances VM.Accept at a BARE MAJORITY (Nova), which is reorgable
and can fork under f=1 equivocation. The Ethereum `finalized`/`safe` tags and warp cross-chain
exports were still reading the accept tip — the "semantic collapse" the split exists to prevent.
Re-point every EXPORT surface onto the export-final (Quasar, ⅔-by-stake) frontier consensus
already exposes.

#1 CRITICAL — eth `finalized`/`safe` tags:
- eth.Ethereum gains a DURABLE, monotone lastQuasarHeight (SetLastQuasarHeight/LastQuasarHeight),
  pushed by consensus strictly AFTER accept. The plugin VM's SetLastQuasarFinalized persists it to
  acceptedBlockDB and reloads it on boot (readLastQuasarHeight), so the export surface never
  regresses on restart.
- api_backend HeaderByNumber/BlockByNumber resolve Finalized(-3)/Safe(-4) to GetBlockByNumber(
  LastQuasarHeight) BEFORE the IsAccepted() catch-all — NEVER a block above the Quasar height.
  `latest`/`pending` stay on the accept tip (correct — not finality tags).
- Test: TestFinalizedSafeResolveToQuasarNeverAboveIt proves finalized == the Quasar height,
  monotone, and never above the accepted tip.

#2 HIGH — warp cross-chain export double-spend:
- warp.verifyBlockMessage now requires the block be EXPORT-final (height ≤ BlockClient.
  LastQuasarHeight), so a validator signs a block hash only at Quasar finality, never on a
  reorgable Nova accept (two conflicting Nova forks could otherwise each aggregate a valid export).
- WarpQuorumNumeratorMinimum floored 33 → 67 (⅔): any warp message needs ≥⅔ stake to verify, so at
  most one of two conflicting exports can validate (two ⅔ sets intersect on >⅓ honest stake) —
  restoring the ⅔-accept safety the pre-split accept tier gave for free.

The consensus engine pushes the frontier via NetworkConfig.QuasarObserver; the node bridges it into
the VM (separate node commit). Nova-only chains (no export surface) are unaffected.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-10 04:58:54 -07:00
zeekayandHanzo Dev d260656efc 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
zeekay 5460f8370e feat(evm): enable standard precompiles — remove vestigial strict-PQ refusal layer
Enable-everything builder surface (public permissionless launch). The geth-layer
strict-PQ profile (LuxStrictPQ) refused ecrecover / p256Verify / sha256 /
ripemd160 / blake2f / bls12381 / kzg under vm.config.PQ — but ecrecover
(secp256k1) is THE Ethereum signature path every dapp depends on, and the rest
are verify-only / hash, key-safe. Refusing them breaks the builder surface.

The chain now installs NO forbidding pq.Profile: chainConfig.PQ stays nil,
which pq.RefuseUnder(nil) treats as permissive. The generic geth mechanism
(chainConfig.PQ + runPrecompile.RefuseUnder + legacypool nil-check) is kept for
any chain that wants it; Lux installs nothing.

This completes the decomplect started in luxfi/precompile: consensus PQ and the
EVM precompile profile were complected through config.PQ / StrictPQTimestamp.
After precompile dropped contract.RefuseUnderStrictPQ from every verify-only
precompile, the whole strict-PQ projection here was dead. Removed:
  - plugin/evm/pq_profile.go (LuxStrictPQ)
  - plugin/evm/vm.go: the `if vm.config.PQ { chainConfig.PQ = LuxStrictPQ() }`
    install + the unconditional StrictPQTimestamp=0 genesis pin
  - plugin/evm/config: the config.PQ bool field (no code writer; was JSON-opt-in)
  - params/extras/network_upgrades.go: StrictPQTimestamp field + apply + IsStrictPQ
  - precompile/registry/bridge.go + core/precompile_overrider.go: the dead
    IsStrictPQ adapters (only existed to feed the deleted StrictPQReporter)
  - tests: pq_test.go, strict_pq_activation_test.go, and the two StrictPQ
    assertions (+ orphaned helper) in precompile_upgrade_rollout_test.go

Lux's PQ security is enforced at consensus / identity (quasar/p3q), not by
refusing an EVM verifier a dapp asked for. The live lux-mainnet upgrade.json
never actually set strictPQTimestamp, so this matches production.

Build: full module green. Tests: params/extras, plugin/evm/config,
precompile/registry pass.
2026-06-27 21:14:05 -07:00
zeekay 06091588fc evm: precompile v0.15.0 (active-only DEX money path) + retire pqcrypto bundle (standalone mldsa/mlkem/slhdsa); no dormant AtomicStager, no backward compat 2026-06-27 18:48:21 -07:00
zeekay 2bc16e3494 refactor(warp): Core -> Message — track warp v1.22.0
Rename across the evm warp backend and the warp precompile: warp.Core ->
warp.Message, NewCore -> NewMessage, ParseCore -> ParseMessage, the .Core field
-> .Message, and local core/msgCore/testCore vars -> msg/testMsg. deps: warp
v1.21.0 -> v1.22.0, precompile v0.11.0 -> v0.12.0.

geth's core/types and core/vm imports are untouched — the rename is scoped to
the warp message type, never the geth core package. ./warp/... and
./precompile/contracts/warp/... green, including TestWarpWrongNetworkID.
2026-06-27 00:48:35 -07:00
zeekay 4d4e961f81 fix(warp): VerifyPredicate enforces Core.NetworkID == local (red LOW)
Red's re-review of the ZAP hard fork found the bespoke EVM verify path dropped
the NetworkID equality check that canonical warp.VerifyEnvelope enforces — a
defense-in-depth gap vs cross-network replay (mitigated by D folding NetworkID
and globally-unique SourceChainIDs, hence LOW, but the two verify paths must
enforce identical checks — one and one way).

VerifyPredicate now rejects Core.NetworkID != GetNetworkID(ctx). The predicate
test runtime's NetworkID is aligned to constants.UnitTestID (it defaulted to 1
while cores are built with UnitTestID, a latent harness mismatch the missing
check had masked); new TestWarpWrongNetworkID proves the rejection.
2026-06-26 23:00:50 -07:00
zeekay a6b22c9824 refactor(warp,precompile)!: decomplect ZAP types — warp v1.21.0, precompile v0.11.0
Track the upstream rename across the evm warp backend
(warp/{service,backend,verifier_backend}) and the warp precompile
(precompile/contracts/warp/{config,contract,contract_warp_handler},
precompileconfig): SignedCore->Core, WarpEnvelope->Envelope.

Identifiers only — the digest D, ZAP c14n bytes and BLS Beam / Corona Pulse /
ML-DSA signing domains are unchanged. ./warp/... and
./precompile/contracts/warp/... green, including the 10k-validator predicate
sign->aggregate->verify.
2026-06-26 22:25:27 -07:00
zeekay f9280978e3 feat(warp): migrate evm onto warp v1.20.0 SignedCore/WarpEnvelope ZAP (drop deleted UnsignedMessage RLP API)
Hard-fork the last evm warp consumers onto the canonical ZAP message format
(one and one way only). warp v1.20.0 deleted UnsignedMessage; the signed
subject is now warp.SignedCore (D = keccak256(coreDST ‖ zap_c14n)) and the
signed wrapper is warp.WarpEnvelope. The BLS Beam signs warp.BeamSigningBytes(D).

deps: warp v1.19.5 -> v1.20.0, precompile v0.5.59 -> v0.10.0.

- warp/{backend,verifier_backend,service}: UnsignedMessage -> SignedCore;
  NewMessage -> NewWarpEnvelope; sign/verify over BeamSigningBytes(core.ID()).
- precompile/contracts/warp: ParseMessage -> ParseWarpEnvelope; .UnsignedMessage
  -> .Core; .Signature.(*BitSetSignature) -> .Beam (concrete). VerifyPredicate
  verifies the Beam over D with exported primitives (Bits + bls +
  BeamSigningBytes) because the precompile quorum denominator must include
  keyless validators, which warp.VerifyEnvelope/GetCanonicalValidatorSet reject.
- precompileconfig.WarpMessageWriter.AddMessage(*SignedCore) (mirrors precompile v0.10.0).
- plugin/evm/vm.go: warpVerifierAdapter over *SignedCore; collapse the duplicate
  external-warp import alias (warptypes -> luxwarp).
- remove redundant warp/{aggregator,bls_adapter,signer_adapter}.go: provably-dead
  duplicates of warp.SignatureAggregator / warp.NewSigner (zero production refs).
- tests migrated to ZAP; fixed a pre-existing offchain message-key test bug;
  revived the predicate suite (validator state injected via Runtime, the
  production path, replacing the decoupled WithValidatorState key).

Verified: go build ./... green; go test ./warp/... ./precompile/contracts/warp/...
green (incl. 10k-validator TestWarpPredicate). grep UnsignedMessage = 0.
2026-06-26 21:27:25 -07:00
zeekay 8272580165 evm: thread real consensus identity + GetCodeSize into 0x9999 value path
The 0x9999 DEX precompile must see the true (networkID, cChainID) and prove
an ERC-20 has live code, on BOTH paths:

- block processing: ApplyTransactionWithResults now threads bc.ConsensusContext()
  onto blockContext.ConsensusContext when absent (nil-guarded; the miner's
  NewEVMBlockContext already sets it). The stale "StatefulPrecompileHook not
  exposed" note is removed — vm.NewEVM preserves ConsensusContext onto evm.Context.
- eth_call/estimateGas: ChainContext.ConsensusContext now returns the backend
  blockchain's consensus context (which embeds the chain Runtime via
  runtime.WithContext), not the RPC request context — the latter made every
  identity-reading precompile see networkID 0 / C-Chain Empty (fail-closed) on
  eth_call. EthAPIBackend.ConsensusContext exposes it.
- stateDBBridge.GetCodeSize forwards EXTCODESIZE to the internal StateDB so the
  OnChainAssetVerifier's C1 live-code proof works; returns -1 (not 0) when
  unavailable so the value path fails CLOSED rather than admit a fabricated zero.

Adds cmd/dexlive (real end-to-end 0x9999 value-path harness over a running
localnet C-Chain: deploy 2 ERC-20s, open market, seed maker, taker swap, read
DEXFill + balance deltas; synthetic/no-code negative control must revert) and
the consensus-identity unit tests for both paths.
2026-06-23 01:06:53 -07:00
zeekay b80a7d0ffd evm: wire the per-network DEX governance controller into the 0x9999 value path
HIGH-3 host side. The always-on 0x9999 settlement precompile resolves its halt +
pot-seeding authority from contract.AtomicState.GovernanceController(); this is the
production wiring that supplies it — a per-network governance CONTRACT, never a
hardcoded mnemonic-derivable EOA.

  - dex_value_wiring.go (installDEXValuePath): bind rt.GovernanceController =
    dexGovernanceFor(networkID) onto the chain runtime at boot (the ONE production
    site), alongside the asset resolver + native D client, and log it. dexGovernanceFor
    is the genesis-set per-network governance-contract seam: it returns ids.ShortEmpty
    for every network TODAY (no Governor/Timelock is deployed at a known address on any
    Lux network yet), so every network runs FAIL-CLOSED (no halt authority — provably no
    single key can DoS the DEX) until its real governance CONTRACT address is wired in,
    per net. The directive is explicit: each entry MUST be a governance contract, NEVER a
    dev-mnemonic EOA, and NEVER the retired 0x9011…94714. The boot log makes an unset
    authority loud ("<unset: halt/seed FAIL-CLOSED — deploy + wire a governance contract>").
  - core/precompile_overrider.go + precompile/registry/bridge.go: accessibleStateAdapter
    and accessibleStateBridge implement GovernanceController() (ids.ShortID→common.Address,
    exact 20-byte conversion); the internal twin interface gains the method. Returns the
    zero address (fail-closed) when the runtime/atomic capability is absent.
  - precompile/contract/atomic.go (evm-internal twin): AtomicState gains GovernanceController().

Builds clean (CGO off) across plugin/evm, core, precompile/registry; the dexsettle_guard
parity test passes. Normal trading is unaffected (halt switches are emergency controls,
off by default); only the ability to halt is gated — which previously only a mnemonic-EOA
holder had (a vuln), so no legitimate capability is lost and a censorship/DoS vector is removed.
2026-06-22 22:50:19 -07:00
zeekay 06c4d8916c precompile env: gate the Call surface to the DEX settlement family (0x9999/0x9996)
GetPrecompileEnv() is on the SHARED external contract.AccessibleState interface,
so the live Call surface (whose sub-call msg.sender is the asserting precompile's
OWN address) is reachable by every external precompile that type-asserts the
optional callableEnv capability. Bound that blast radius with defense-in-depth:

- precompile/registry/bridge.go: callableEnvAllowlist keyed on the geth env's
  self-address (env.Addresses().Self), populated from the canonical
  dex.LXSettleAddress (0x9999) + dex.DEXPositionManagerAddress (0x9996) constants
  (one source of truth). Every other external precompile (PQ/ZK verifiers at
  0x0122xx, future ones) gets a nil callable env and falls back to fail-secure
  refusal. No live behaviour change today (none assert callableEnv yet) — it
  prevents a FUTURE precompile silently acquiring sub-call power. Sits on top of
  each settlement Run's CALL-only guard.
- core/precompile_overrider.go: GetPrecompileEnv comment corrected — the accessor
  is off the EVM-INTERNAL interface but IS on the shared external one; reach is
  bounded by the bridge gate + the settle CALL-only guard.
- precompile/registry/bridge_precompileenv_test.go: recordingEnv carries a self;
  TestBridge_GetPrecompileEnv_NonSettlementSelf_Gated (0x012204 verifier -> nil,
  no Call) + _PositionManagerSelf_Allowed (0x9996 -> non-nil); WiredForwardsCall
  now models the 0x9999 self.
2026-06-22 14:37:01 -07:00
zeekay 1e41171291 precompile env: wire 0x9999 ERC-20 Call surface to the DEX precompile
ROOT CAUSE: the external DEX 0x9999 settlement precompile's ERC-20 leg (transferFrom/
transfer/balanceOf on the token contract) needs the EVM Call surface, but the adapter
chain that delivers its AccessibleState narrowed Call away:

  geth evmPrecompileEnv{Call}
   -> evm/core accessibleStateAdapter{env}        (env captured, not exposed)
   -> evm/registry accessibleStateBridge          (GetPrecompileEnv() => nil)
   -> luxfi/precompile contract.PrecompileEnvironment (no Call)

so poolStateAdapter.callable() got nil and every ERC-20 deposit/withdraw refused with
ErrERC20VaultUnavailable (fail-secure — never minted). Native LUX settlement was
unaffected because it uses GetBalance/SubBalance, already plumbed through the bridges.

FIX (minimal, off the shared many-implementer interfaces):
- evm/core accessibleStateAdapter.GetPrecompileEnv() exposes the geth env it already
  holds (concrete accessor, NOT a widening of the internal contract.AccessibleState).
- evm/registry accessibleStateBridge.GetPrecompileEnv() type-asserts that accessor and
  returns precompileEnvBridge, which adds the callableEnv-shaped Call the precompile
  type-asserts; nil (fail-secure) when no geth env is present.
- Call forwards with NO caller proxying: the token's msg.sender is the precompile self
  (0x9999), matching the depositor's approve(0x9999) allowance. Proxying would check
  the wrong allowance and break the pull.

geth is UNTOUCHED: vm.PrecompileEnvironment already declares Call; the DEX never runs
through geth's own accessibleStateAdapter (that path serves only the PQ precompiles).

Regression guards: core pins *accessibleStateAdapter satisfies the envProvider accessor
and returns its env; registry pins the bridge returns a non-nil callable env that
forwards Call (value passed, ZERO options) when wired, and untyped-nil otherwise
(no-env + typed-nil traps).
2026-06-22 14:37:01 -07:00
zeekay 8e04c4e23f evm: fix 0x9999 dispatch consensus-divergence on replay path + harden value/genesis edges (v1.99.34)
Red review of the 0x9999 dated-fork activation (v1.99.33) found ONE HIGH that
blocked the production deploy, plus a money-path footgun and two guards. The
money path (V4 swap ABI, EXTCODESIZE marker, two-phase atomic settle) is
byte-for-byte unchanged — only the dispatch-path timestamp source, the SubBalance
fail-mode, the genesis-builder guard, and stale comments change. Bumps
precompile v0.5.53 -> v0.5.54 (Red L1 comment scrub).

H1 (HIGH, blocked deploy) — consensus-divergence on the relaunch/replay path:
  core.LuxPrecompileOverrider.PrecompileOverride read the PROCESS-GLOBAL
  params.lastRulesContext via GetRulesExtra(Rules{}). That global is rewritten
  last-writer-wins by SetRulesContext on every Rules()/RulesAt() — including
  concurrent eth_call/estimateGas/miner goroutines. The geth EVM invokes
  PrecompileOverride LAZILY during opcode execution, a different instant than when
  precompileHook set the global. On a live, RPC-serving post-fork node replaying a
  PRE-fork RLP block (admin.importChain), a concurrent post-fork eth_call could
  overwrite the global timestamp between the verify goroutine's NewEVM(pre-fork
  block) and its tx dispatch -> the pre-fork block sees 0x9999 ENABLED and
  dispatches SettleContract.Run during plain-account execution -> STATE DIVERGENCE.
  Fix: the dispatch gate now decides from the overrider's OWN per-EVM fields
  (o.chainConfig + o.timestamp) via the pure params.GetExtrasRules — which performs
  the SAME dated-fork (extras.IsDexSettleActive) computation GetRulesExtra did,
  minus the racy global read. Audited every GetRulesExtra reader: only the overrider
  was on the precompile-dispatch path; no other dispatch/consensus-replay call site
  consults the 0x9999 gate. Every replay of a given block now yields the same
  enabled set on every validator, independent of concurrent activity.

L2 (money-path footgun) — registry stateDBBridge.SubBalance fail-open silent mint:
  when the subBalancer type-assertion failed it logged + returned a zero 'previous
  balance' WITHOUT subtracting, while the caller treats the debit as successful ->
  native value minted. Now FAILS CLOSED: it panics, which the EVM recovers into a
  reverted call, so the enclosing value move rolls back rather than committing
  half-done. Structurally unreachable in prod (the concrete adapter implements
  SubBalance) but no longer mints if a future StateDB omits it.

INFO1 (latent footgun) — genesis-config builders bypassed the dated fork:
  SetAllGenesisPrecompiles/AllGenesisPrecompiles iterated ALL RegisteredModules()
  incl the AlwaysOn 0x9999 and wrote timestamp-0 genesis configs, which would enable
  0x9999 from block 0 on a fresh chain — bypassing the dated fork and re-introducing
  the genesis-marker mutation. Both builders now skip AlwaysOn modules.

INFO2 (tests):
  - core: -race regression TestDexSettle_PrecompileOverride_NoGlobalRace reproduces
    the exact relaunch race (pre-fork verify vs concurrent post-fork eth_call
    clobbering the global) and asserts the pre-fork side ALWAYS sees 0x9999 ABSENT,
    post-fork ALWAYS PRESENT. PerEVMTimestamp pins the gate reads o.timestamp not the
    global. Replay e2e asserts a pre-fork value transfer to 0x9999 credits a PLAIN
    account WITHOUT invoking the precompile. All proven to FAIL under the old
    global-read code and PASS under the fix; -race clean.
  - extras + core: TestGenesisPrecompileBuilders_SkipAlwaysOn / _Skip9999 pin INFO1.
  - subbalance test comment refreshed to 0x9999 + fail-closed semantics.
2026-06-21 12:42:42 -07:00
zeekay d6a6584ad1 dex(0x9999): timestamp-gated dated-fork activation + marker install (v1.99.33)
Replace the v1.99.32 "always-on, dispatch-only, no marker" activation of the
0x9999 DEX settlement money path with the CTO's exact model: a SINGLE canonical
dated fork that both enables dispatch AND installs the EXTCODESIZE marker —
forward, at the fork boundary, never in historical genesis.

The fork: extras.DexSettleActivationTime = 1766704800 (Dec 25 2025 00:00:00 UTC),
defined ONCE in params/extras/network_upgrades.go alongside the Durango/Quasar/
Fortuna/Granite built-in upgrades. NOT per-net config (no per-net upgrade.json
precompileUpgrades entry, no genesisPrecompiles entry) — one dated fork, identical
on every Lux network, DRY. This is the proper canonical fork mechanism for a
system precompile that takes no per-net parameters (0x9999 resolves protocolFee-
Controller=DefaultDAOTreasury and dChainID at runtime from the consensus context).

Two coupled effects, both gated on that one timestamp:

  1. DISPATCH (params/config_extra.go GetExtrasRules): the AlwaysOnModules() inject
     into the enabled precompile set is now wrapped in extras.IsDexSettleActive(ts)
     — present at/after Dec 25, ABSENT before. This is REQUIRED for canonical
     history: replaying a pre-Dec-25 block (the ~/work/lux/state RLP snapshot, e.g.
     lux-mainnet 96369 = 1,082,780 blocks via admin.importChain) must see 0x9999 as
     a PLAIN ACCOUNT so a historical value transfer to that address behaves as it
     did originally, not as a precompile dispatch. The prior unconditional inject
     would have diverged replay from canonical state.

  2. MARKER (core/state_processor_ext.go ApplyPrecompileActivations): on the block
     transition that CROSSES the fork (params.IsDexSettleForkTransition →
     extras.IsForkTransition), write the STANDARD precompile-activation marker
     (SetNonce=1 + SetCode{0x1}) into 0x9999 — identical to the config-driven path.
     This is the forward state upgrade: EXTCODESIZE(0x9999)>0, eth_getCode!=0x, and
     Solidity's contract-existence guard passes for a typed IPoolManager(0x9999)
     .swap(...). The prior change DROPPED this marker to avoid mutating genesis;
     installing it forward at the fork preserves the genesis hash (pre-activation
     blocks get NO marker) while making typed calls work post-activation. Reverts
     the v1.99.32 "writes NO genesis state for always-on modules" skip.

IsForkTransition fires exactly once (!parentForked && currentForked). For a fresh
net whose genesis timestamp is already >= the fork (parent==nil), it fires at
genesis — so fresh/local/new nets carry the marker from block 0 via the SAME
mechanism, no separate genesisPrecompiles entry (CTO step 4). For an existing net
(genesis ts < fork) it does NOT fire at genesis — genesis hash immutable — and
fires forward at the Dec 25 block.

KEPT byte-for-byte: runtime DChainID via BCLookup.Lookup("D"), DefaultDAOTreasury
protocolFeeController, networkID/cChainID from runtime context, AlwaysOn module
tagging + AlwaysOnModules(), all settlement/money-path logic.

0x9010: with precompile v0.5.53 the 0x9010 module is no longer registered, so the
EVM never dispatches it and eth_getCode(0x9010)=0x. registry comment updated to
LP-9999. No EVM-side 0x9010 dispatch reference existed.

deps: precompile v0.5.52 -> v0.5.53.

Tests (core/precompile_alwayson_test.go, rewritten — DRY, replaces the prior
unconditional/no-marker assertions):
  - TestDexSettle_DispatchGate_TimestampGated: 0x9999 absent pre-fork, present
    at/after (replay safety + post-activation dispatch).
  - TestDexSettle_NoGenesisMarker_ExistingNet: genesis (ts<fork) writes no marker
    (genesis hash immutable).
  - TestDexSettle_MarkerInstalledAtForkTransition: marker (nonce=1 + code, EXTCODESIZE>0)
    installed at the boundary block.
  - TestDexSettle_ForkTransitionFiresOnce: fires once; fresh-net genesis>=fork at
    block 0; existing-net genesis<fork not at genesis.
  - TestAlwaysOn_9999_Registered: 0x9999 bridged AlwaysOn.
  core + params/extras green incl. the real-mainnet genesis-hash guards. (Two
  pre-existing unrelated failures on pristine main: params/config_test.go references
  a removed DurangoTimestamp field; params/extras mainnet upgrade.json ringConfig/
  warpConfig timestamp guards — both fail identically with these changes stashed.)
2026-06-21 12:09:46 -07:00
zeekay 570308a113 dex(0x9999): always-on settlement activation + runtime DChainID (v1.99.32)
Wire the EVM side of the always-on DEX settlement money path 0x9999. It is ACTIVE
on every chain from genesis with NO config entry — neither a genesis-inlined
precompile nor a precompileUpgrades timestamp. Zero per-net config, one way.

Activation is DISPATCH-only (genesis state is consensus-critical and immutable):
  - params.GetExtrasRules injects every modules.AlwaysOnModules() entry into the
    enabled precompile set unconditionally (every chain, every timestamp). This is
    what PrecompileOverride.IsPrecompileEnabled consults, so 0x9999 runs for a
    tx-to-0x9999, a low-level CALL/STATICCALL, and the 0x9010 V4 forward.
  - ApplyPrecompileActivations writes NO genesis state for always-on modules. An
    EXTCODESIZE marker at genesis would change the genesis hash and FORK existing
    networks (a fresh-syncing node would disagree with the committed genesis). The
    real lux-mainnet genesis hash is preserved (TestSkipPostMergeFieldsGenesisHash).
  - The always-on module's Configurator is never invoked (no activating config).

DChainID runtime resolution (THE crux): contract.AtomicState gains DChainID(); the
EVM adapter (accessibleStateAdapter) resolves it from the consensus context via
runtime.GetBCLookup().Lookup("D") — the dexvm "D"/"dex"/"dexvm" alias the node
registers in initChainAliases before any chain bootstraps. Identical on every
validator and re-execution; ids.Empty on a network with no dexvm (seam stays closed,
fail-secure). The registry bridge forwards DChainID across the internal/external
AtomicState surfaces. modules.Module gains AlwaysOn (+ AlwaysOnModules()), bridged
from github.com/luxfi/precompile.

deps: precompile v0.5.51 -> v0.5.52 (DChainID + AlwaysOn + always-on settle module).

Tests: core suite green incl. the real-mainnet genesis-hash guards. New
core/precompile_alwayson_test.go: 0x9999 registers AlwaysOn, is dispatch-enabled with
NO config at genesis + far-future ts, and writes NO genesis state (genesis-hash
immutability). (Two pre-existing unrelated failures: params/config_test.go references
a removed DurangoTimestamp field; mainnet upgrade.json aiMining/warp timestamp guards
— both fail identically on pristine main.)
2026-06-21 09:21:06 -07:00
zeekay 8f942b3943 plugin/evm: one-batch-atomic DEX native seam flush (FIX 2 chain-halt)
The host side of the 0x9999 native C<->D atomic seam, and the consensus-liveness
fix Red found.

THE BUG: Block.Accept applied the staged atomic ops to shared memory FIRST
(sm.Apply, no batch -> committed immediately) and advanced the node-local seq
marker LATER, in a separate versiondb.Commit(). atomic.SharedMemory.Apply is NOT
idempotent (duplicate Put errors). A crash BETWEEN them left shared memory
mutated but the marker unadvanced; on restart the same parent->current window
re-collected, sm.Apply re-applied the present Put, errored, and Accept returned
fatal -> the chain HALTED.

THE FIX (mirrors chains/dexvm commitAtomic): flushDexAtomic -> stageDexAtomic
stages the advanced dexAtomicSeqKey marker INTO the versiondb in-memory layer and
returns (reqs, sm). Block.Accept snapshots the versiondb (marker + lastAccepted)
via CommitBatch() and passes it to sm.Apply(reqs, batch) — its WriteAll commits
the shared-memory mutation AND the batch in ONE database write. The marker advance
and the SM mutation are now all-or-nothing: a crash applies-and-marks both or
neither, and a re-accept of an applied window sees an advanced marker -> empty
window -> no duplicate Put -> no halt. With no atomic ops the plain
versiondb.Commit() path is unchanged.

Also: core/precompile_overrider.go wires CallIndex() through the EVM accessible-
state adapter; precompile/registry/bridge.go + precompile/contract/atomic.go
forward the optional contract.AtomicState capability to luxfi/precompile.

Regression for the closed halt window lives in precompile
native_haltwindow_test.go (unit). The live-path test (real versiondb + crash
injection) is CI-only (CGO + luxfi/accel LUXCPP). Local symbol-resolution proof:
CGO_ENABLED=0 go build ./plugin/evm/ compiles clean.
2026-06-21 00:19:47 -07:00
zooqueenandHanzo Dev d125bb74f3 feat(registry): activate AI inference + model-registry precompiles (precompile v0.5.38)
Bump luxfi/precompile v0.5.37 -> v0.5.38 and register the two new AI-family precompiles:
- inference     (0x0300..0003): deterministic on-chain int8 transformer (byte-exact across
                                Go/Rust/Metal/CUDA/HIP; the trustless on-chain 'thinking' path)
- modelregistry (0x0300..0002): governance-adopted versioned model-commitment registry
v0.5.38 also carries the ai (0x0300) atomic cross-chain mining methods (verifyAndMintWork/Data).

Additive + DORMANT: registering != activating. No chain runs these until its genesis/upgrade
config enables the ConfigKey (aiInferenceConfig / modelRegistryConfig). Mainnet C-Chain + zoo
do not; the Zoo L3 Beluga genesis does. Full registry init() verified clean — all precompiles
register with no address collision.

Co-authored-by: Hanzo Dev <[email protected]>
2026-06-10 08:15:23 -07:00
zeekay edce485ac1 evm: codec rip — message.Manager + validators state hand-rolled binary
Salvages an in-flight Wave 1C cascade on evm/ (the EVM fork separate
from coreth and evmgpu). 17 files touched. Mirrors the coreth
migration patterns:

- plugin/evm/message/codec.go: defines local Manager interface with
  hand-rolled wrappers.Packer; drops codec.Manager polymorphic registry.
- plugin/evm/message/request.go: typed Marshaler boundary.
- sync/{client,handlers}/*.go: codec.Manager → message.Manager swap.
- plugin/evm/customrawdb/*: explicit big-endian for state sync schema.
- plugin/evm/validators/state/{codec,state,state_test}.go: hand-rolled.
- plugin/evm/upgrade/feewindow/window.go + plugin/evm/vm.go +
  network/network.go + plugin/evm/network_handler.go: call-site fixups.
- precompile/registry/registry.go: codec-free registration path.

Builds clean: go build ./...
2026-06-05 15:31:59 -07:00
zeekay 3a570ec52b params/extras: rollout regression tests + register 16 missing precompiles
Two changes for the Quasar Edition rollout:

1. precompile/registry/registry.go side-effect imports the 16 modules
   that ship in the canonical upgrade.json but were not yet wired into
   the registry: attestation, babyjubjub, bls12381, bridge, compute,
   curve25519, ed25519, math, pasta, pedersen, poseidon, sr25519,
   stableswap, vrf, x25519, xwing. Without these imports each module's
   init() never runs, so modules.RegisteredModules omits them and
   UpgradeConfig.UnmarshalJSON rejects the activation with
   "unknown precompile config: <key>", bricking luxd boot.

2. params/extras/precompile_upgrade_rollout_test.go (NEW) is the
   forward-compat regression gate against the live lux-mainnet config.
   Five subtests:
   - IsForwardCompatibleWithLiveActivations: every precompile
     activation currently in the lux-mainnet StatefulSet inline
     upgrade.json (18 entries at blockTimestamp:0 including the
     dead* family) MUST be preserved at the same blockTimestamp in
     the canonical upgrade.json, else checkPrecompileCompatible
     refuses boot.
   - PrecompileTimestampsAreMonotonic: verifyPrecompileUpgrades
     rejects non-monotonic timestamps; we verify the canonical
     config respects this.
   - WarpRequiresPrimaryNetworkSigners: warpConfig MUST pin
     requirePrimaryNetworkSigners=true (red-review finding #8).
   - HasStrictPQActivation: networkUpgradeOverrides MUST set
     strictPQTimestamp=1766708400 (Quasar timestamp) so
     contract.RefuseUnderStrictPQ fires.
   - HasStrictPQTrue: ~/work/lux/state/chain-configs/lux-mainnet/
     config.json MUST set "pq": true so the EVM plugin boots into
     strict-PQ posture.

The test uses raw JSON parsing (not typed UpgradeConfig.Unmarshal) so
it does not depend on the registry side-effect imports being loaded
in the test binary — avoiding cgo linker conflicts between sr25519's
NaCl-compat randombytes and the HQC PQClean randombytes shim.

Red review findings #2, #7, #8.
2026-06-01 21:12:21 -07:00
zeekay cad4a25469 feat: register all 8 LP-4200 PQ precompiles (magnetar 0x012207 + hqc 0x012208 + p3q 0x012205 added to init imports); bump luxfi/precompile v0.5.17→v0.5.23; force luxfi/lens v0.1.3 to break transitive lens-rc1 cascade 2026-05-19 11:42:17 -07:00
zeekay 8b4fc26000 precompile/registry: swap corona (old) for LP-4200 corona+pulsar threshold precompiles
The published v0.5.17 precompile pkg moved the Ring-LWE threshold scheme to
slot 0x012206 under the name 'corona' (its canonical upstream name) and
added the Module-LWE threshold (Pulsar) at 0x012204. Corona is gone —
its package was renamed corona during the LP-4200 unification pass.

Also bumps luxfi/precompile to v0.5.17 to pull in the corona/pulsar/p3q
module registrations and the bridge ErrChainNameTooLong sentinel.
2026-05-13 15:51:29 -07:00
zeekay 47d4a4aa94 params/extras: StrictPQTimestamp + per-chain PQ posture wiring (CR-2)
Closes CR-2 from the cross-stack adversarial audit: PQ posture on
multi-chain hosts moves from a process-global to a per-chain
ChainConfig setting. A strict-PQ subnet and a permissive L1 in the
same node process now refuse / admit classical primitives correctly,
independent of which subnet last initialised.

Surface:
  - NetworkUpgrades.StrictPQTimestamp *uint64 (json:"strictPQTimestamp")
    nil = chain is classical-permissive (default for non-Lux integrators)
    0   = strict-PQ from genesis (set by plugin/evm when config.PQ=true)
  - NetworkUpgrades.IsStrictPQ(time uint64) bool — fork rule
  - chainConfigAdapter.IsStrictPQ — bridges params/extras into the
    contract.StrictPQReporter contract that stateful precompiles call.
  - extChainConfigBridge.IsStrictPQ — type-asserts via a local
    interface so non-Lux chains integrating Lux precompiles remain
    classical-permissive (no contract widening forced on them).
  - plugin/evm: when config.PQ=true, vm.chainConfig.PQ is installed
    (covers 0x01..0x0a standard precompiles via runPrecompile gate) AND
    extras.StrictPQTimestamp is set to genesis (covers Lux stateful
    precompiles — KZG, Groth16, PLONK, fflonk, Halo2, BN254-Pedersen,
    BabyJubJub, Pallas/Vesta — via contract.RefuseUnderStrictPQ).

Module bumps:
  - github.com/luxfi/geth v1.16.91
    (bundles MLDSATxType (0x42) + per-chain PQ profile gate)
  - github.com/luxfi/pq v1.0.1-0.20260512064747-3d4c9414e15e
    (RefuseUnder promoted to method on *Profile)

No semantic change for chains that do not opt into PQ:
StrictPQTimestamp is nil, IsStrictPQ returns false, every classical
primitive admits, gas accounting unchanged.
2026-05-11 23:54:35 -07:00
zeekay 953fd6cc17 evm: bump luxfi/geth v1.16.90; assorted fixes
Bumps luxfi/geth pin to v1.16.90 to pull in the PQ profile gate
(refuse(Op) at every classical precompile) and the restored
p3q + pulsar registrations. Transitively bumps luxfi/precompile to
v0.5.16.

Additional fixes:
- core/genesis.go: skip WithdrawalsHash on genesis block to preserve
  the canonical Lux mainnet genesis hash (chain ID 96369) under
  Durango fork activation.
- params/config_extra.go: surface SetEthUpgrades errors with
  os.Stderr context for boot diagnostics.
- precompile/contracts/warp/module.go + precompile/modules/registerer.go:
  housekeeping.

Removed stale .bak files.
2026-05-11 18:10:12 -07:00
zeekay 988e4f97ee feat: Lux EVM — GPU auto-detect, anchor precompile, nogpu build tag 2026-03-02 18:00:00 -08:00
zeekay 121dae27eb chore: zero TODOs remaining in production code 2026-03-02 23:59:00 -08:00
zeekay 3da82f7eb2 feat: add bridge precompile registry module
Register bridge precompile in the modules registerer and add
bridge.go registry implementation.
2026-03-12 16:13:54 -07:00
zeekay 736fa4e15c feat: register zk, blake3, and dead address precompiles
Add ZK proof verification (Groth16, PLONK, Halo2), Blake3 hash
function, and dead/burn address routing precompiles to the registry.
2026-03-11 13:32:45 -07:00
zeekay 0adfd3575f fix: apply gofmt formatting to all Go files
36 files had minor formatting issues detected by gofmt.
2026-02-13 17:07:27 -08:00
zeekay d5c9b2be62 feat: update to vm v1.0.23 with ZAP-based Serve function
Updates dependencies:
- github.com/luxfi/vm v1.0.22 → v1.0.23 (adds ZAP transport Serve)
- github.com/luxfi/log v1.3.0 → v1.4.1 (fixes module path declaration)
- github.com/luxfi/consensus v1.22.53 → v1.22.56
- github.com/luxfi/crypto v1.17.39 → v1.17.40
- github.com/luxfi/database v1.17.38 → v1.17.39
- github.com/luxfi/geth v1.16.69 → v1.16.73
- github.com/luxfi/p2p v1.18.7 → v1.18.8
- github.com/luxfi/warp v1.18.4 → v1.18.5

The vm package now provides a ZAP-based Serve function by default,
eliminating the need for the grpc build tag for VM plugins.

Also includes various improvements to validators, warp service,
and precompile interfaces.
2026-01-24 12:01:19 -08:00
zeekay d32e075809 refactor: update to vm v1.0.18, migrate log and metrics packages
- Update vm dependency to v1.0.18
- Remove internal log package in favor of luxfi/log
- Migrate prometheus to gatherer package
2026-01-11 21:38:34 -08:00
zeekay f105287739 update imports for vm/crypto and jsonrpc 2026-01-08 22:14:50 -08:00
zeekay 0852bfd75e Upate constants package 2026-01-08 20:39:54 -08:00
zeekay e440ade25e refactor: update imports to github.com/luxfi/constants 2026-01-08 19:36:51 -08:00
zeekay 4234da3b48 Update admin api, precompiles 2026-01-06 11:51:58 -08:00
zeekay e493a8aaa2 Update math imports to use math/big subpackage 2026-01-03 18:26:06 -08:00
zeekay 4b7da04687 Use luxfi/math/math instead of geth/common/math 2026-01-03 17:37:03 -08:00
Fuma Nama 569b09a1c7 chore(deps): update to latest luxfi packages, remove local replaces
- Remove local replace directives for node and geth
- Update to latest published versions:
  - geth v1.16.67
  - crypto v1.17.32
  - database v1.2.18
  - ids v1.2.6
  - p2p v1.18.3
2025-12-31 18:02:38 -08:00
zeekay df05c46ab3 Update precompile imports to singular module name
Changed import paths from github.com/luxfi/precompiles to
github.com/luxfi/precompile v0.2.0. Updated go.mod to require
the new singular module and exclude old plural versions.
2025-12-30 22:58:32 -08:00
zeekay 4692930eff feat(precompiles): enable FHE and rename precompileHook
- Enable FHE precompile from standalone precompiles package
- Rename luxRulesHook to precompileHook for clarity
- DEX precompiles to be imported from standalone precompiles package
- Clean up debug logging from vm.go

The FHE precompile uses pure Go TFHE implementation from
github.com/luxfi/fhe for on-chain homomorphic encryption.
2025-12-29 23:05:42 -08:00
zeekay 1c7d8d23ab chore: update module from luxfi/constants to luxfi/const v1.4.0
Update all imports to use renamed module path.
2025-12-29 14:25:24 -08:00
zeekay f5a46918fb refactor: consolidate crypto precompiles to standalone package
- Remove duplicate fhe, mldsa, pqcrypto, quasar from evm
- Update registry to import from github.com/luxfi/precompiles
- Remove local node replace directive
- Upgrade node to v1.22.69, precompiles to v0.1.8
- Keep EVM-specific precompiles: deployerallowlist, feemanager,
  nativeminter, rewardmanager, txallowlist, warp
2025-12-28 16:29:13 -08:00
zeekay 2df37edcf1 fix: upgrade geth to v1.16.62 and configure PQ crypto precompiles
- Upgrade geth to v1.16.62 (fixes circl import path in precompiles)
- Add proper config.go and module.go for mldsa precompile
- Add proper module.go for pqcrypto precompile
- Register both precompiles in registry.go
- Update LLM.md with PQ crypto documentation

All tests pass including previously failing accounts/abi/bind tests.
2025-12-24 23:56:48 -08:00
zeekay 170c1ba5f8 refactor: remove warp aliases, use warp.Signer directly
- Remove luxWarp alias, use warp package directly
- Remove AppSender indirection, use p2p.Sender directly
- Update warptypes alias where local warp package conflicts
- Update node to v1.22.43, warp to v1.16.39
2025-12-22 04:12:50 -08:00