Commit Graph
1803 Commits
Author SHA1 Message Date
zeekay eeca28efb5 Merge branch 'fix/accept-materialize-root-v1.99.52'
# Conflicts:
#	go.mod
#	go.sum
2026-07-01 06:29:46 -07:00
zeekayandHanzo Dev f510f35b41 core: materialize accepted block state on the accept path (equivocation-commit-gap backstop)
Under equivocation on a fresh chain, the consensus engine can finalize (via the
α-of-K cert / catch-up path) a block that this node verified only as a transient
non-canonical sibling — whose state trie was then dereferenced when that sibling
was rejected — WITHOUT re-verifying it on the finalize path. The node's head then
points at a block whose state root is absent from the trie database, so the next
BuildBlock fails 'failed to create new current environment: missing trie node'
and the chain wedges. This is the fresh re-genesised testnet/devnet block-2 halt
(node v1.32.4 / evm v1.99.52); mainnet is unaffected (its genesis lacks the fresh
precompile suite and its mature operation does not equivocate at the tip).

Fix: in BlockChain.Accept, if the accepted block's state root is not present, re-
execute it on the parent (== last accepted, whose state is present by construction)
before it becomes the head. reprocessBlock's ValidateState re-checks the computed
root against the header, so a non-deterministic transition surfaces loudly rather
than silently. On the common (non-equivocation) path this is a cheap HasState no-op.
The WARN log fires ONLY on the commit-gap, so it doubles as confirming instrumentation.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-01 06:23:49 -07:00
zeekayandHanzo Dev a55e3fc1bf fix(evm): bounded-wake backstop so an idle demand-driven chain mines the first tx in seconds
Pre-existing idle-builder wake race (not caused by QCv2): on a demand-driven
C-Chain with no empty blocks, a tx arriving while no builder is parked in
waitForNeedToBuild broadcasts pendingSignal to no waiter and is lost; the tx is
then only caught on the NEXT needToBuild() poll, which — depending on the
proposervm slot loop and engine re-call cadence — could leave the first tx after
idle pending for a long/indefinite time (the mainnet treasury-sweep-stuck and
v1.32.1 devnet DEXFill-didn't-complete symptoms).

Fix: startPendingTxPoll — a node-lifetime ticker (builderPollInterval = 500ms)
that periodically broadcasts pendingSignal so any parked builder re-evaluates
needToBuild() against the live mempool. It is decomplected from the tx-submit
subscription (awaitSubmittedTxs remains the primary, immediate wake) so the two
wake sources are independent. It NEVER forces an empty block — a wake only
produces a build when needToBuild() is true — so demand-driven chains stay
empty-block-free, and it changes wake TIMING only, never block CONTENTS, so
validators still build byte-identical blocks (deterministic). needToBuild now
routes through an injectable pendingTxs predicate so the wake path is testable
without a live txpool.

Regression: TestBuilderWakesOnPendingTxAfterIdle reproduces the node bridge's
exact waitForEvent call on an idle builder, delivers a pending tx with NO submit
broadcast, and asserts it returns PendingTxs within a bounded deadline. It FAILS
on the pre-fix code (times out — the lost wakeup) and passes after.
TestBuilderPollBackstopStopsOnShutdown asserts the ticker exits cleanly. Builder
tests green under -race.

Co-authored-by: Hanzo Dev <[email protected]>
v1.99.52
2026-06-30 21:28:45 -07:00
zeekayandHanzo Dev edfef5c9cd fix(evm): bounded-wake backstop so an idle demand-driven chain mines the first tx in seconds
Pre-existing idle-builder wake race (not caused by QCv2): on a demand-driven
C-Chain with no empty blocks, a tx arriving while no builder is parked in
waitForNeedToBuild broadcasts pendingSignal to no waiter and is lost; the tx is
then only caught on the NEXT needToBuild() poll, which — depending on the
proposervm slot loop and engine re-call cadence — could leave the first tx after
idle pending for a long/indefinite time (the mainnet treasury-sweep-stuck and
v1.32.1 devnet DEXFill-didn't-complete symptoms).

Fix: startPendingTxPoll — a node-lifetime ticker (builderPollInterval = 500ms)
that periodically broadcasts pendingSignal so any parked builder re-evaluates
needToBuild() against the live mempool. It is decomplected from the tx-submit
subscription (awaitSubmittedTxs remains the primary, immediate wake) so the two
wake sources are independent. It NEVER forces an empty block — a wake only
produces a build when needToBuild() is true — so demand-driven chains stay
empty-block-free, and it changes wake TIMING only, never block CONTENTS, so
validators still build byte-identical blocks (deterministic). needToBuild now
routes through an injectable pendingTxs predicate so the wake path is testable
without a live txpool.

Regression: TestBuilderWakesOnPendingTxAfterIdle reproduces the node bridge's
exact waitForEvent call on an idle builder, delivers a pending tx with NO submit
broadcast, and asserts it returns PendingTxs within a bounded deadline. It FAILS
on the pre-fix code (times out — the lost wakeup) and passes after.
TestBuilderPollBackstopStopsOnShutdown asserts the ticker exits cleanly. Builder
tests green under -race.

Co-authored-by: Hanzo Dev <[email protected]>
v1.101.0
2026-06-30 20:24:31 -07:00
zeekayandHanzo Dev b1ae152443 fix(deps): repair vanished pins / go.sum drift ( gosum-refresh) + build green
Co-authored-by: Hanzo Dev <[email protected]>
v1.100.0
2026-06-30 13:55:51 -07:00
zeekayandHanzo Dev 4ce8cda073 bump: latest dealerless PQ (corona v0.10.3, pulsar v1.8.0)
Co-authored-by: Hanzo Dev <[email protected]>
2026-06-30 13:26:33 -07:00
zeekay 72c61b82d3 docs(brand): add hero banner 2026-06-28 20:35:22 -07:00
zeekay 9f54f2df6e chore(brand): dynamic hero banner 2026-06-28 20:35:21 -07:00
zeekay 832f920c65 bump: dealerless pulsar/corona/dkg/mlwe (no trusted dealer, no-reconstruct)
Move to the dealerless threshold-signature stack: corona v0.10.2 (Module-LWE
Ringtail, dealerless Pedersen-VSS) + pulsar v1.7.1 (dealerless Mithril RSS keygen
+ no-reconstruct signing, trusted dealer ripped) + dkg v0.3.5 + mlwe v0.2.1.
2026-06-28 19:41:22 -07:00
zeekay d096677067 evm: extract nextBuildTime pure helper + regression test for build pacing
Decomplect the build-pacing decision out of waitForEvent into a pure
nextBuildTime(earliestBuild, lastBuildTime) function (no clock read), so the
logic the network-halt fix depends on is deterministically testable. The
wall-clock comparison stays in waitForEvent.

TestNextBuildTime covers all 5 cases (first-build paces to target rate; past
earliestBuild builds immediately; target rate dominates retry floor; retry
floor prevents hot-loop; boundary tie-break). TestNextBuildTime_NeverEarlier
ThanTargetRate locks the invariant the fix relies on: the next build is NEVER
scheduled before parent.Time+TargetBlockRate, so a too-early high-fee block can
never re-introduce the small-tip stall.
v1.99.51
2026-06-28 00:52:09 -07:00
zeekay 5c129faa36 evm: pace block building to the target block rate
Second fresh-net block-production bug, exposed once WaitForEvent was unblocked.
The hand-rolled node notification bridge calls WaitForEvent in a tight loop and
builds a block the instant any tx is pending — with no pacing (ava's proposervm,
which Lux does not wrap, normally provides it). Two consequences:

1. Block gas cost: cost = parentCost + step·(TargetBlockRate − elapsed). A block
   built immediately after its parent (elapsed≈0) requires step·TargetBlockRate
   (e.g. 200000·2 = 400000) of tips to pass verifyBlockFee. A large deploy
   covers it; a single small transfer (~100000) cannot → BuildBlock fails →
   the tx never mines.
2. Equivocation: with all 5 validators building instantly off slightly different
   mempool snapshots, they produce different blocks at the same height; two can
   each gather an α-of-K cert → conflicting finality certs → Logger.Crit → the
   node exits(1) and crash-loops.

Fix: pace waitForEvent to parent.Time + TargetBlockRate (the point at which the
block gas cost decays back to the parent's level, 0 from genesis), mirroring the
timing ava's coreth builder applies. Small-tip txs now mine, and the ~rate-long
window lets the mempool gossip-converge so validators build the IDENTICAL block
(same ID, one cert) instead of racing to finalize conflicting blocks. Retry
floor 1ms→100ms so a failed BuildBlock cannot hot-loop.
v1.99.50
2026-06-28 00:13:45 -07:00
zeekay 8f1ecd61c2 evm: WaitForEvent must not block forever when called before builder init
Fresh-start block-production stall root cause. The node-side notification
bridge (node/chains/manager.go) calls VM.WaitForEvent in a loop with a
non-cancellable context.Background(), starting BEFORE SetState(NormalOp)
initializes vm.builder. The first WaitForEvent therefore parks in the
builder==nil select { ctx.Done | stateSyncDone | shutdownChan } and, since the
context never cancels and state-sync is off, blocks FOREVER -- the serial
bridge loop never re-calls WaitForEvent to observe the now-ready builder, so no
PendingTxs ever reaches the engine and no block is ever built.

ava avoids this with common.NotificationForwarder + cn.OnChange=CheckForEvent,
which cancels the outstanding WaitForEvent and re-subscribes on VM state change.
Lux replaced that with the hand-rolled bridge and lost the recovery path.

Fix (self-contained, VM-side): add builderReady chan, closed once by
onNormalOperationsStarted after vm.builder is set, and add a select case so a
WaitForEvent that parked early re-fetches the ready builder and proceeds.
Makes WaitForEvent correct for any caller, cancellable or not.

go.sum: re-record luxfi/age,pq checksums after upstream re-tag (verify ON).
v1.99.49
2026-06-27 23:28:37 -07:00
zeekay b2afccc5e2 deps: precompile v0.15.0 -> v0.16.0 (enable-everything surface)
Picks up the enable-everything custom-precompile change (RefuseUnderStrictPQ
removed from all verify-only precompiles; fflonk fail-closed; accel byte-
identity; dex/stableswap/graph determinism). Pairs with this module's standard-
precompile enable so the full builder surface is live in one plugin build.
v1.99.48
2026-06-27 21:28:22 -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 d3ec032775 evm: drop cmd/dexlive one-off — replaced by net-agnostic luxfi/trader 2026-06-27 19:27:13 -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 v1.99.47 2026-06-27 18:48:21 -07:00
zeekay 143c389e3f chore(deps): bump warp v1.22.0 -> v1.23.0, precompile v0.12.0 -> v0.13.0
warp v1.23.0 = typed finality-evidence model + Pulse->Corona relabel.
No evm consumer references the renamed symbols; warp/, warp/validators/, and
precompile/contracts/warp/ tests all pass. Pure dependency bump.
v1.99.46
2026-06-27 14:45:17 -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.
v1.99.45
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.
v1.99.44
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.
v1.99.43
2026-06-26 22:25:27 -07:00
zeekay 8e79dae137 merge: warp v1.20.0 ZAP migration (SignedCore/WarpEnvelope) into main v1.99.42 2026-06-26 21:27:49 -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 dd70b64d16 deps(evm): bump chains v1.3.19 -> v1.3.22 (restores dropped forbidden.go)
chains v1.3.19's dexvm/registry references AssertNoForbiddenAssetRefs,
looksLikeASCIITickerID, toHex and fromHex, but the file defining them
(forbidden.go) was dropped in that re-tagged release, so plugin/evm failed to
build (undefined symbols). The helpers are present again from v1.3.21; bump to
the latest v1.3 patch, v1.3.22.

MVS pulls the patch-level deps chains v1.3.22 requires: consensus v1.25.29,
crypto v1.19.26, dex v1.5.20 (zap v0.8.11 indirect). All x.x.x+ patch bumps, no
minor/major. Full repo builds; core/params/snapshot suites green and unchanged.
v1.99.41
2026-06-26 20:03:21 -07:00
zeekay 5fa8db6037 test(evm): fix snapshot-recovery prepare for non-empty (genesis-active) genesis
GenerateChain forcibly uses a hash-scheme trie db to read parent state, but the
snapshot suite runs the chain under both hash and path schemes. With an empty
genesis (pre genesis-active) parent.Root() is the empty root -- readable in any
scheme -- so block generation worked. With the genesis-active 0x9999 marker the
genesis state is non-empty, and a path-scheme chain commits its genesis nodes in
path format, invisible to GenerateChain's hash-scheme reader. state.New then
errored and GenerateChain returned 0 blocks (the error was discarded), so prepare
built an empty chain and restart recovered to genesis (head 0) instead of C4.

Generate blocks on a dedicated hash-scheme genDb via GenerateChainWithGenesis
(the pattern used by the rest of core's tests); the scheme-agnostic block
structures are re-executed into the chain's own db by InsertChain. The gapped and
wipe-crash variants source their extra blocks from genDb too.

The recovery engine is unchanged and verified correct for a non-empty genesis
under both hash and path schemes: on restart driven by lastAcceptedHash=C4 the
head, header and snapshot disk layer all land at C4 and block[4] is present.
2026-06-26 20:03:13 -07:00
zeekay 1c691fb210 test(evm): regen genesis hashes for genesis-active 0x9999 + fix MaxCodeSize drift
genesis-active 0x9999 puts the precompile marker in every genesis, so the genesis
hash changes (correct for a first-run, always-on network): TestSkipPostMergeFields
GenesisHash 0x3f4f->0x5609, TestGenesisBlockForTesting 0x517c->0x4e97. Also fix
pre-existing TestUpstreamParamsValues drift: Lux raises MaxCodeSize to 64KB (65536)
and MaxInitCodeSize to 2x (131072) vs Ethereum's 24576 — the test asserted stale
Ethereum values. params + core-genesis now GREEN.

REMAINING (not merged): the snapshot-recovery tests (TestRestartWithNewSnapshot ...)
fail — a geth snapshot-engine limitation with a non-empty genesis (block[N] of the
snapshot disk layer goes missing on restart). genesis-active EXPOSES it (marker makes
genesis non-empty) but does not cause it; it's a foundational geth-engine fix.
2026-06-26 18:34:33 -07:00
zeekay aed4b24f74 fix(evm): reconcile re-tagged go.sum (zap/dex/chains/...) + drop stale ChainConfig.DurangoTimestamp in test
The re-tag automation staled multiple luxfi module hashes ([email protected], [email protected],
[email protected], ...) so evm would not build/test (SECURITY ERROR). Re-recorded go.sum
fresh (GOPRIVATE bypasses sumdb for luxfi; public deps re-verified). Also removed a
stale DurangoTimestamp from a geth ChainConfig literal in config_test.go (the field
moved to extras.NetworkUpgrades). Unblocks the build. NOTE: params/extras (the
genesis-active StrictPQ work) passes; full evm green still needs the genesis-hash
regen (0x9999 genesis-active) + pre-existing failures (TestUpstreamParamsValues
param drift, snapshot geth-limitation) addressed — separate foundational pass.
2026-06-26 18:23:50 -07:00
zeekay cdbc464bfc feat(evm): genesis-active gateless 0x9999 + StrictPQ — first-run, one way, no dated fork
First-run network, no legacy, no pre-activation history to protect, so the
DexSettleActivationTime dated-fork gate is pure complexity. Delete the concept of
an activation time: 0x9999 dispatches unconditionally and its EXTCODESIZE marker
installs at genesis (parentTimestamp==nil); StrictPQ pinned active-from-genesis for
the Lux plugin (whole ecosystem is PQ-native — removed the per-VM config conditional).

Deleted: DexSettleActivationTime const + pointer/accessor/predicates, the cross-repo
mirror, the drift-guard test, and the 'behaves-as-plain-account-before-activation'
replay-safety branches/prose. One activation path; less code. Genesis determinism via
the single core/genesis.go toBlock() derivation. Feature tests green (0x9999 registered/
dispatched/marked-at-genesis; StrictPQ genesis+nil-inert; plugin PQ).
2026-06-26 14:40:00 -07:00
zeekay b02e2879a6 deps: bump chains v1.3.19 + consensus v1.25.21 + precompile v0.5.59 (no stragglers) v1.99.40 2026-06-23 09:29:06 -07:00
zeekay 12a9fbcc9a test(evm): regression guard — chain identity bound before NewBlockChain reprocess
Proves the unclean-restart fix: a non-nil *runtime.Runtime is readable on the
chain the instant construction returns (so it was present for any in-construction
reprocess), and nil stays nil for chains with no identity-gated precompiles.
v1.99.39
2026-06-23 08:54:50 -07:00
zeekay 7341b4a885 fix(evm): bind chain identity before NewBlockChain reprocess (no new type)
The 0x9999 DEX value path is identity-gated: the asset resolver refuses to
admit a market unless the running chain's identity matches. core.NewBlockChain
re-executes accepted blocks during construction (loadLastState recovery /
populateMissingTries backfill), but the identity was only bound onto the chain
AFTER eth.New returned (vm.go SetConsensusContext) — so an unclean restart after
a 0x9999 swap re-executed that block with networkID 0 / C-Chain Empty, reverted
the committed swap, failed ValidateState, and the node could not boot.

Fix is purely timing: hand NewBlockChain the *runtime.Runtime the VM already
holds (eth.Settings.ChainRuntime -> core.NewBlockChain) and bind the consensus
context BEFORE reprocess. No new identity type — networkID/chainID already live
on runtime.Runtime; the context is just the last-mile transport to geth's
ConsensusContext API. stdlib runtime aliased stdruntime in blockchain.go.
2026-06-23 08:53:26 -07:00
zeekay f849f49cdd deps: pin precompile v0.5.58 + runtime v1.1.3 (closes release-gate-b)
precompile v0.5.58 carries the 0x9999 value-path soundness floor (Block-STM
access-set, decentralized halt, in-state-vault/EVM-Call resolution; contains
the gate-b commit 23f49b6) and runtime v1.1.3 the GovernanceController seam
the value path reads. Builds standalone without go.work; gate-b closed
(evm no longer links the pre-fix precompile v0.5.57).
v1.99.38
2026-06-23 01:08:42 -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 b0f0787bef feat(evm): wire the 0x9999 value-path asset resolver + native D-client at boot
This is the SOLE production install of the always-on 0x9999 DEX value-path trust
root — the seam Red flagged as fail-closed-by-absence (InstallAssetResolver /
InstallDChainClient had zero production callers, so every swap reverted
ErrNoAssetResolver). vm.Initialize now constructs and installs, from the node's REAL
chain context (never a constant):

- a registry-backed dexcore.AssetResolver bound to (networkID, cChainID): it derives
  the canonical AssetID with the node's own ids and resolves it in a registry built
  from the per-network manifest (EmbeddedManifestFor by EVM chainID, or the runtime-
  rooted localnet native manifest). The manifest is identity-bound via
  NewRuntimeVerifier (wrong-net/wrong-chain refused) and admitted under the locked-down
  DefaultDexAssetPolicy synthetic-config deny-scan.
- the local in-process NativeDChainClient for the book/ledger custody seam (NOT a
  remote keeper/venue/ZAP engine).

Wired for ALL networks (mainnet 96369 / testnet 96368 / devnet 96370 / localnet 1337)
so the DEX is live out-of-the-box. FAIL-CLOSED, NEVER FATAL: a construction failure is
logged and the resolver is left UNINSTALLED (swaps revert), never opened; the node
still boots for all non-DEX usage. The install binds the SAME (networkID, cChainID)
the swap path's AtomicState cross-checks, so a mismatch is fail-closed at swap time.

- core/precompile_overrider.go: forward GetCodeSize (EXTCODESIZE) on the concrete
  stateDBAdapter so the value path's live-code verifier reads authoritative state.
2026-06-22 20:56:52 -07:00
zeekay 8b3f3cf9bc test(parallel): Block-STM determinism slice on SettleBatch — anti-fork (Phase 1)
The determinism half of the unified executor: parallel-eligible settlement
reproduces the canonical sequential state root. Reuses the REAL kernels
(detectConflicts via ConflictEdges, SettleBatch, geth IntermediateRoot) — no
second engine, no mock conflict detector.

  Test9999_DisjointFillsParallelRootEqualsSequential
    (== TestBlockSTM_DifferentMarketsParallel): fills on different markets ->
    detectConflicts finds 0 edges -> SettleBatch root == sequential root. In-test
    fail-without: collapsing the two onto one market yields an edge (proves the
    detector keys on the market, not vacuously empty).

  Test9999_ConflictingFillsReExecToSequentialRoot
    (== TestHotMarketBatchDeterministicRoot): two fills sharing an account on one
    HOT market -> detectConflicts returns edge (0,1) -> SettleBatch serializes in
    ascending-INDEX order -> root == C-then-D AND != the swapped D-then-C. Uses an
    order-SENSITIVE hot-market slot (keccak(prev||taker)) so the root genuinely
    depends on apply order — a balance-only fill is commutative and would make the
    determinism claim vacuous; the in-test guard rejects a commutative Apply.

Fail-without PROVEN by temporary revert: reversing SettleBatch's apply order
makes the root diverge to the swapped order and the conflicting-fills test FAILS
(restored after). Owner-named tests satisfied by the same bodies. Full
core/parallel suite green; no existing tests weakened.
2026-06-22 19:39:07 -07:00
zeekay 54450ae372 deps: require precompile v0.5.57 (ERC-20 settle CALL-only DELEGATECALL guard)
Consumes luxfi/precompile v0.5.57 — the RED-CLEARED money-path fix adding the
CALL-only guard (ErrSettleWrongContext) on 0x9999/0x9996 Run. Pairs with this
repo's callableEnvAllowlist gate so the env Call surface reaches only the DEX
settlement family. go.sum regenerated via 'go mod tidy' against the tagged
module (GOWORK=off — the CI condition).
v1.99.37
2026-06-22 14:37:01 -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
zooqueen 46a7de4200 test(parallel): live GPU ecrecover dispatch proof (Metal vtable, not CPU fallthrough)
Asserts DefaultGPU().Available()==true and GPU-recovered sender == CPU sender,
proving the block-insert ecrecover path dispatches to the real Metal backend
(libluxgpu_backend_metal.dylib) rather than the CPU fallthrough.
2026-06-22 02:36:21 -07:00
zeekay 582f78fe53 deps: consume precompile v0.5.56 (0x9999 swap-seam r2) v1.99.36 2026-06-21 22:47:49 -07:00
zeekay 4d8629eb6f deps: bump vm v1.2.5 + api v1.0.15 (UTXOAssetID rename — fixes LuxAssetID build break, latest patch) 2026-06-21 22:44:55 -07:00
zeekay 935da100ec deps: converge threshold v1.9.9 + crypto v1.19.21 + database v1.20.3 + geth v1.17.12 + warp v1.19.5 + metric v1.5.9 + proto v1.3.5 (latest patch within v1.x.x) 2026-06-21 22:38:15 -07:00
zeekay be0beda21b params/extras: relaunch-safety test + regenerate canonical upgrade fixtures
Round-2 follow-up to the luxfi/genesis upgrade.json reschedule fix (17
live-at-genesis precompiles pinned to blockTimestamp 0; warp two-step
strict-PQ toggle moved after the zero-timestamp block; dead dexConfig
0x9010 dropped).

- relaunch_safety_test.go (new): drives the node's REAL fork-application
  primitive extras.IsForkTransition (parent=nil reduces to fork <=
  genesisTime, exactly what GetActivatingPrecompileConfigs evaluates when
  a relaunched node builds its first rule set) against the embedded
  canonical fixtures. Asserts the invariant: every precompile that
  activates at/under the C-Chain genesis time (mainnet 1730446786 /
  testnet 1730517266) is in the live-at-block-0 set AND pinned to exactly
  0; every other entry is strictly future; and all 17 live precompiles are
  present (no relaunch divergence). No entry sits in the (0, genesisTime]
  retroactive-reschedule window.

- mainnet_upgrade_v46.json / testnet_upgrade_v47.json: regenerated
  byte-for-byte from the corrected canonical genesis configs (46 / 44
  entries; the 25 timestamp diffs were the 1766708400 -> 0 normalization
  plus the warp reorder).

- precompile_upgrade_rollout_test.go: WarpRequiresPrimaryNetworkSigners
  updated for the two-step toggle — warp is declared in the genesis
  chainConfig, and the schedule carries disable@strictPQ-1 then
  re-enable@strictPQ; the PQ signer-policy assertion targets the re-enable
  entry, and same-key strict-increase is enforced (mirrors
  verifyPrecompileUpgrades).

- upgrade_unmarshal_test.go / testnet_upgrade_unmarshal_test.go: fixture
  doc comments rewritten to the live-at-0 / strict-PQ-toggle / BLS /
  Quasar-Edition tier layout; RegressionProof entry-count assertion 45 ->
  46 to match the embedded fixture.

Verified: go vet clean; CGO_ENABLED=0 go test ./params/extras/...
./params/extras_e2e_test/... — RelaunchSafe (mainnet+testnet),
IsForwardCompatibleWithLiveActivations, WarpRequiresPrimaryNetworkSigners,
PrecompileTimestampsAreMonotonic, RegressionProof all PASS.
2026-06-21 22:22:38 -07:00
zeekay 003067d76b fix(extras): remove dead dexConfig from vendored upgrade fixtures + gates
node v1.30.27/28 dropped the 0x9010 DEX precompile; its config key
dexConfig is unregistered under the pinned luxfi/precompile v0.5.55, so
the vendored canonical fixtures carrying it made
TestMainnetUpgradeJSON_UnmarshalsAgainstRegistry FAIL with 'unknown
precompile config: dexConfig' (the C-Chain brick-on-boot the gate exists
to catch). Remove dexConfig from both fixtures (sync source: luxfi/genesis
configs/{mainnet,testnet}/upgrade.json, cleaned in lockstep), update the
canonical entry-count assertion 46->45, and drop dexConfig from the
lux-mainnet live-activations const so the forward-compat check stays
consistent. Verified CGO_ENABLED=0 go test ./params/extras_e2e_test/ -> ok.
2026-06-21 19:05:22 -07:00
zeekay 1e0b056fa7 params: remove leftover debug fmt prints
SetEthUpgrades wrote a [SET-ETH-UPGRADES] line to stderr and GetFeeConfig
wrote two DEBUG lines to stdout on every call — leftover debugging, not
intentional logging. Removed all three; dropped the now-unused fmt and os
imports from config_extra.go.

NOTE: coordinated cascade — another agent is bumping this repo's version for
an unrelated change. This is a pure code edit (print removal) on its own
branch; fold into the cascade.
2026-06-21 19:05:03 -07:00
zeekay 5d775c144f evm: require precompile v0.5.55 (native 0x9999 DEXFill/Initialize emission) (v1.99.35)
Pins the precompile tag that exports dex.DexSettleActivationTime so the
dexsettle_guard DRY test compiles and enforces extras==dex mirror equality.
go test -tags dexsettle_guard ./core/ PASSES against the pinned tag.
v1.99.35
2026-06-21 14:27:59 -07:00
zeekay 245d15d92f Merge fix/native-dex-indexing: dexsettle_guard DRY guard for cross-layer activation constant (v1.99.35) 2026-06-21 14:26:02 -07:00
zeekay 42eaf0d220 extras: correct DexSettleActivationTime annotation to 23:20:00Z + do-not-round guard
1766704800 is 2025-12-25T23:20:00Z, not midnight (1766620800). Value is the
canonical protocol constant, already live gating 0x9999 dispatch on devnet;
changing it would fork chains that crossed the boundary. Annotate the true
instant; warn against rounding. Comment-only — zero consensus change.
2026-06-21 14:26:02 -07:00
zeekay 25daffe90e evm/core: release-gate the cross-repo DexSettleActivationTime DRY guard behind a build tag
The guard TestDexSettleActivationTime_LayerMirrorsMatch references
dex.DexSettleActivationTime, which is defined in luxfi/precompile and post-dates
evm/go.mod's pinned precompile tag (v0.5.54). Under GOWORK=off (CI), evm/core
failed to build (undefined: dex.DexSettleActivationTime); it only compiled via an
untracked local go.work. The guard was therefore both inert in CI and a latent
CI break.

Move the single cross-repo guard into precompile_alwayson_dexsettle_guard_test.go
behind //go:build dexsettle_guard. Default CI excludes it, so evm/core builds and
tests against the pinned precompile tag with no go.work. The release step bumps
evm/go.mod to a precompile tag that exports the symbol and runs
`go test -tags dexsettle_guard ./core/`, enforcing the DRY invariant atomically
with the bump. precompile cannot host the guard (it does not import luxfi/evm; the
graph is strictly evm->precompile, so referencing extras.DexSettleActivationTime
from precompile would create a cycle). All other 0x9999 activation pins in
precompile_alwayson_test.go use only evm-local symbols and run on every CI build.
2026-06-21 13:51:10 -07:00
zeekay a309d4900a evm/core: guard dex.DexSettleActivationTime == extras.DexSettleActivationTime
The 0x9999 activation timestamp lives in TWO layers that cannot share a symbol: the
canonical extras.DexSettleActivationTime (this evm layer, used by the dispatch gate +
marker transition) and a layer-local mirror dex.DexSettleActivationTime (luxfi/
precompile, used to gate the precompile's new consensus-visible logs — precompile is
below evm in the import graph so it cannot import extras). If they drift, a settlement
could dispatch (extras gate) where the precompile withholds its log (stale mirror) ->
settlement-without-log window -> consensus split on replay. This test imports BOTH and
fails CI the instant either constant moves without the other.

NOTE: compiling this test against the new dex export requires evm go.mod to pin a
luxfi/precompile release that exports DexSettleActivationTime (>= v0.5.55, to be cut
from the precompile branch). Verified locally PASS against the local precompile via a
temporary replace (reverted); the go.mod bump is part of the coordinated release.
2026-06-21 13:16:17 -07:00