Commit Graph
523 Commits
Author SHA1 Message Date
zeekayandHanzo Dev 98c42cef80 evm: bump plugin VM version v0.18.18 -> v0.18.19 for 0x9999 genesis-injection fix
rpcChainVM protocol unchanged (42) — node compatibility preserved. Pairs with
the genesis-injection fix (0bd9927a2) for evm module tag v1.104.1.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-02 16:57:41 -07:00
zeekayandHanzo Dev b3b98b1ea9 evm: empty-block check is BUILD-only — finalized empty block Accepts (forward-integrate v1.101.4)
Forward-integration onto main (v1.103.0 line) of the accept-empty recovery fix
released as v1.101.4. errEmptyBlock moves from SyntacticVerify (parse/verify/
accept) to the build path only, so a consensus-finalized empty block Accepts
while a demand-driven proposer still never produces one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-02 12:19:36 -07:00
zeekayandHanzo Dev 32c041cb8a deps: dex v1.14.0 + precompile v0.19.0 — dexcore→dex fold alias (fork-safe integer wire)
The dex lx/dexcore→dex fold + exact-integer wire (F1-F5 fork fix, F9 mint fix)
requires bumping dex→v1.14.0 + precompile→v0.19.0. dex_value_wiring.go aliases
the folded matcher pkg as dexcore so the precompile 'dex' package stays distinct.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-02 11:01:40 -07:00
zeekayandHanzo Dev 0121536b30 fix(evm): stateless ParseBlock — decomplect BlockGasCost from parse (restore upstream)
ROOT CAUSE of the C-Chain bootstrap ancestry-descent stall: ParseBlock was STATEFUL.
BlockGasCost is NOT in the RLP block — it is DERIVED from the parent header. UnmarshalBlock
computed it via vm.blockChain.GetHeaderByHash(parent), and SyntacticVerify REQUIRED it non-nil
(errNilBlockGasCostEVM). For a bootstrap descent parsing ancestry blocks AHEAD of the accepted
height the parent is not present, so every such block failed to parse — masked by the ZAP
errorToZAP mapping as the misleading "malformed block id (does not connect to accepted chain)".
The Lux bootstrapper indexes the whole fetched batch by (id,height,parent) up front, so it could
not index the ancestry, never found the frontier tip in the batch, and re-requested forever ->
ErrStalled. Confirmed on devnet: "syntactic block verification failed: nil blockGasCost is
invalid after EVM activation".

FIX — decomplect parse from verify (matches upstream avalanchego coreth: parse decodes, Verify
enforces connectivity):
  - block.go: new ensureBlockGasCost() fills BlockGasCost from the parent header WHEN PRESENT
    (SetHeaderExtra is hash-keyed, so it persists for a block first parsed while its parent was
    absent). Best-effort at parse; authoritative at Verify (AcceptBootstrapBlock calls blk.Verify
    only after the contiguity gate height==finalized+1, so the parent is ACCEPTED/present).
  - block_verification.go: SyntacticVerify no longer requires BlockGasCost (stateless,
    block-intrinsic checks only); keeps the uint64 sanity check when it IS set.
  - vm.go UnmarshalBlock: replace the inline parent-lookup with block.ensureBlockGasCost().

SAFETY (parse != accept): parse only decodes + stateless syntactic checks; no non-connecting or
gas-cost-invalid block can be ACCEPTED just because it now parses. Verify/Accept
(InsertBlockManual -> consensus/dummy header verification) recomputes the expected BlockGasCost
from the (present) parent and rejects a mismatch; the bootstrap contiguity gate accepts blocks
oldest-first (parent finalized). A wrong-hash block is rejected off-path by the content-addressed
descent. BlockGasCost is not in the block hash -> no malleability.

evm plugin/evm tests pass.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-02 03:23:08 -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]>
2026-06-30 20:24:31 -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.
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.
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).
2026-06-27 23:28:37 -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 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 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 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 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 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 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
zeekay 9cb5053be8 plugin/evm: remove deprecated DexZap live-backend wiring; precompile v0.5.49
The C-Chain DEX settles certified D-Chain fill receipts at the 0x9999 precompile
(LP-9999); it never queries a live matcher. Remove the deprecated
DexZapEndpoint -> SetBackend(NewZAPEngine()) wiring from vm.go and the
DexZapEndpoint config knob: a synchronous in-block ZAP query against the
D-Chain's moving book forks consensus (each validator observes differently-timed
fills -> divergent StateRoot, proven by chains/dexvm
TestRED_PerValidatorRelay_SplitsConsensus). ZAP survives only as the dexvm's own
build-path transport, never as a C-Chain live backend. Receipts ride in V4 swap
hookData and are verified inline (deterministic BLS).

Bump github.com/luxfi/precompile v0.5.47 -> v0.5.49 to embed the complete,
red-swarm-hardened 0x9999 receipt-settlement surface.
2026-06-20 14:57:25 -07:00
zeekay 7e079e9ac1 evm: install bn256-carve-out strict-PQ profile; prove StrictPQ gate active (Red H2 + guardrail)
Task 2 (Red H2): StrictPQTimestamp was wired from config.PQ (vm.go) but the
gate was unproven inert. The canonical lux-mainnet config already sets
pq:true and upgrade.json sets strictPQTimestamp=Quasar-TS; added behavioral
tests proving NetworkUpgrades.IsStrictPQ activates (genesis when pq:true ->
StrictPQTimestamp=0; at the timestamp on running chains; Override carries it).

GUARDRAIL fix: vm.Initialize installed gethvm.AllForbidden() (=pq.Strict),
which forbids the standard bn256 precompiles 0x06-0x08 — breaking
Ethereum-compat dapps. Now installs the EVM-owned LuxStrictPQ() projection
(pq_profile.go): every classical family forbidden EXCEPT bn256. Lux's
security-critical pairing/DLOG is in CUSTOM precompiles (0x0900, 0x22
Pedersen, zkvm verifiers) + the consensus cert, each gated by the SAME
profile bit (config.PQ also pins StrictPQTimestamp=0 -> RefuseUnderStrictPQ).

Tests (b): IsStrictPQ true on a prod strict chain (genesis + timestamp +
Override paths), false when nil. (f): LuxStrictPQ admits bn256 0x06-0x08 and
still refuses every other family; differs from AllForbidden only by bn256.
2026-06-19 13:37:37 -07:00
zeekay c0f265e47e evm: wire dex-zap-endpoint -> SetBackend(NewZAPEngine) so 0x9010 routes to the venue D-Chain
config.go: DexZapEndpoint (node-local + version-pinned consensus-safety invariant). vm.go: when
set, SetBackend(NewZAPEngine(endpoint,2s)) BEFORE parseGenesis; INERT when empty (public default
= embedded engine, unchanged). Resolves against published luxfi/[email protected], no replace.
Builds CGO on+off; config decode test added.
2026-06-17 17:26:12 -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 727493f6e7 plugin/evm: harden admin_exportChain with idempotency + sentinel (Task #118)
Previous ExportChain returned (bool, error) and would re-export from
scratch every call, leaving partial files with no marker on Ctrl-C.

Now returns *ExportChainResult with first/last block hashes and a Status
field ("ok" | "noop" | "interrupted"). Behavior:

  * Atomic JSON sentinel at <file>.sentinel (tmp+rename) updated every
    1024 blocks and on terminal states.
  * Idempotency: if file exists with a "done" sentinel matching
    {first, last, firstHash, lastHash}, returns Status=noop without
    re-exporting. Safe to call from a cron CronJob.
  * Interrupt: ctx.Done() at every block boundary writes a sentinel
    with Status=interrupted + HighestExported so the caller can resume.
  * Block iterator still streams (no whole-chain buffering); gzip path
    unchanged.

Symmetric with admin_importChain which returns *ImportChainResult, and
with the cmd/rlp-export CLI shipping alongside.

Server-side test coverage: writeSentinel atomicity, readSentinel
absent + corrupt fall-through, JSON shape pinning for both the
sentinel and ExportChainResult, exportProgressInterval stability.
2026-06-05 15:30:41 -07:00
zeekay cf5b006f11 plugin/evm: enforce DisallowUnknownFields on upgradeBytes parse
Quasar Edition rip: there is one and only one upgrade-key namespace.
The strict decoder forces parse-time failure on any unknown field —
which catches typos and stale aliases (e.g. etnaTimestamp) at config
roll instead of silently muting forks at activation.

Why this matters: encoding/json by default ignores unknown JSON keys
and zero-initializes the corresponding struct fields. After the
EtnaTimestamp → QuasarTimestamp rename, any deployed upgrade.json
still carrying etnaTimestamp would parse cleanly with the new field
left nil, and the fork would never activate. The strict decoder
turns that silent-disable failure mode into a loud boot-time error.

Pairs with: luxfi/genesis kill-etna sweep + node v1.28.22 plugin bump.
2026-06-03 11:51:13 -07:00
zeekay c7c4b96b0f rename: Etna → Quasar Edition
Wire-level renames in EVM:
  - extras.NetworkUpgrades.EtnaTimestamp → QuasarTimestamp
    json tag etnaTimestamp → quasarTimestamp
  - extras.NetworkUpgrades.IsEtna()      → IsQuasar()
  - extras.LuxRules.IsEtna               → IsQuasar
  - params.RulesExtra.IsEtna             → IsQuasar
  - extras.TestEtnaChainConfig           → TestQuasarChainConfig
  - params.TestEtnaChainConfig           → TestQuasarChainConfig
  - upgradetest.Etna fork enum           → Quasar (luxfi/upgrade dep)

Test JSON fixtures (mainnet_upgrade_v46, testnet_upgrade_v47, embedded
genesis_skippostmerge_test, precompile_upgrade_rollout_test) flipped from
"etnaTimestamp" → "quasarTimestamp".

Also patched .disabled test files for grep cleanliness so future grep -rn
'Etna' returns 0 outside historical RELEASES.md.

Bumps luxfi/upgrade@main + luxfi/constants@main with their renamed symbols.
No backcompat aliases — forwards-only.
2026-06-02 23:01:55 -07:00
zeekay 3d0966555f fix(plugin/evm): force HashScheme default to stop fresh L2s wedging on pathdb
On lux-mainnet 2026-06-02 every newly-created L2 EVM chain (hanzo, zoo,
spc, pars) panicked at chain create with:

  panic in eth.New: unable to commit genesis block to statedb:
    triedb parent [0x56e81f17171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421] layer missing

Root cause: the operator does not specify state-scheme in chain config, so
vm.ethConfig.StateScheme = "" enters eth.New. backend.go then calls
customrawdb.ParseStateSchemeExt("", chainDb) which delegates to
geth/rawdb.ParseStateScheme — upstream returns PathScheme as the implicit
default for any database with no stored scheme (every fresh L2). That
silently re-enables path mode even though vm.go's preflight at the top
of Initialize hard-refuses it. Genesis commit then runs through pathdb's
Update with parentRoot=EmptyRootHash and dies the moment the layer tree
is consulted.

Fix: extract the scheme normalisation into resolveStateScheme() and lock
the empty-string default to HashScheme here, before backend.go can inherit
geth's path-by-default. Path explicit is still refused as before, so this
is purely an operator-default fix.

Adds:
- plugin/evm/vm_statescheme_test.go: TestResolveStateScheme covering all
  four input cases (empty→hash, hash passthrough, path refused, custom
  passthrough).
- core/genesis_pathdb_test.go: TestGenesisCommit_PathDB_{FreshDB,
  SetupGenesisBlock_FreshDB} document the upstream pathdb genesis-commit
  invariant on a fresh in-memory DB so future drift here is caught
  immediately.

Also bumps compatibility.json to register v0.18.4-v0.18.18 with the
existing RPC chain VM protocol (42); restores TestCompatibility (which
was failing on main since v0.18.4 because the table was never updated).

Version: v0.18.17 -> v0.18.18.
2026-06-02 11:53:21 -07:00
zeekay 6e25b35864 evm v0.18.17: go mod tidy after genesis v1.13.8 cascade
luxfi/genesis is now indirect (no direct EVM imports), but tidy pulled in
fresher minimum versions of its transitive deps:

- constants v1.5.2 -> v1.5.7
- crypto v1.19.6 -> v1.19.15
- metric v1.5.1 -> v1.5.5
- precompile v0.5.23 -> v0.5.27
- otel v1.42.0 -> v1.43.0

Plus drops StephenButtolph/canoto, fatih/structtag, grpc-prometheus from
indirect block (no longer transitively required).

No functional changes; this is purely the dependency cleanup that follows
the luxfi/genesis v1.13.8 bump.
2026-06-02 04:25:49 -07:00
zeekay c116794daa release: bump Version to v0.18.16 (matches semver tag) 2026-06-02 01:04:55 -07:00
zeekay 4eecb66098 evm v0.18.16: fix nil-chainConfig panic at PQ gate + RLP verifier tool
Bug fix (closure-swarm v1.28.15 boot blocker):
PQ gate at plugin/evm/vm.go was setting vm.chainConfig.PQ BEFORE
vm.chainConfig was assigned from g.Config — chainConfig was nil at
that point, panic on dereference. Moved the gate AFTER assignment.

Repro: fresh-PVC bootstrap with chain-config.json pq:true caused
"runtime error: invalid memory address or nil pointer dereference"
in the EVM ZAP plugin → C-Chain never bootstraps. Discovered during
localnet 1337 bring-up against v1.28.15.

Fix: move the gate to AFTER `vm.chainConfig = g.Config`. PQ profile
is now correctly installed on a non-nil chainConfig.

Added:
- cmd/rlp-verify/main.go — decodes C-Chain RLP export, verifies
  genesis hash matches genesis JSON, each block's ParentHash chains
  cleanly. What admin.importChain does sans state execution; sufficient
  proof that an RLP+genesis pair is wireable to a luxd boot.

Indirect dep bumps:
- github.com/ChainSafe/go-schnorrkel v1.1.0 (sr25519Verify precompile)
- github.com/cosmos/go-bip39 v1.0.0 (bridgeRegistrarConfig precompile)
- github.com/luxfi/ai v0.1.0 (aiMiningConfig precompile)

Build tag: drop `gpu` tag on darwin gpu_bridge.go (parallel/gpu is one
concept; the darwin gpu bridge always compiles when cgo+darwin).
2026-06-02 01:04:45 -07:00
zeekay 5f5603e322 release: bump Version to v0.18.15 (matches semver tag) 2026-06-01 19:25:41 -07:00
zeekay c9dda742a6 plugin: refresh vm + main + runner 2026-05-20 16:24:51 -07:00
zeekay a471a92113 release: bump Version to v0.18.14 (matches semver tag) 2026-05-19 20:17:07 -07:00
zeekay a1c7fa7fb0 plugin/evm: rewrite pq_test.go against per-chain Profile contract (CR-1 follow-up)
The original tests called precompile.Run() directly and expected the
PQ refusal to happen inside the precompile body. That contract no
longer holds: after CR-1, every classical precompile's Run() body is
strict-PQ-agnostic — the gate lives in (*EVM).runPrecompile and reads
the per-chain Profile from ChainConfig.PQ.

This rewrite tests the two contracts the plugin layer is actually
responsible for:

1. AllForbidden() == pq.Strict() (Hash() identity check) — every
   classical primitive family flagged.
2. (*Profile).RefuseUnder(op) returns the family-specific sentinel
   error for every op a strict-PQ chain must refuse.
3. nil profile (the default for non-PQ chains) admits every op.

Cross-precompile integration coverage (the strict-PQ chain runs an
EVM that actually refuses ecrecover at the boundary) lives in
geth/core/vm/pq_gate_test.go — closer to the dispatch chokepoint and
not duplicated here.
2026-05-12 08:23:06 -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 a4f5d29ee1 evm/plugin: rename LuxStrictPQ -> PQ; install AllForbidden() projection
Config field LuxStrictPQ -> PQ (JSON key "pq", was "lux-strict-pq").
VM.Initialize now calls gethvm.SetPQProfile(gethvm.AllForbidden()) when
config.PQ is true, refusing every classical precompile family
(ecrecover, sha256/ripemd/blake2F, alt_bn128, BLS12-381, KZG) at the
EVM gate — not just ecrecover.

Test file renamed lux_strict_pq_test.go -> pq_test.go. Covers the
strict-PQ path, classical-compat path, and a cross-family refusal
matrix asserting AllForbidden() turns on every Forbid* flag.
2026-05-11 16:51:31 -07:00
zeekay 77853e0a5f evm: ecrecover strict-PQ refusal via profile gate
VM.Initialize installs the chain-wide strict-PQ posture into the
go-ethereum precompile layer when Config.LuxStrictPQ is true. The
ecrecover precompile at 0x01 then returns ErrClassicalAuthForbidden
(gas is still charged per EIP-150) and never produces a recovered
address.

Default (LuxStrictPQ=false) preserves classical-compat semantics
required by legacy chains and the Lux-Permissive profile.

Mirrors the coreth wire-up; the gate itself lives in luxfi/geth
(commit 738ecc775, tag v1.16.88) and is single-implementation. Bumps
luxfi/geth pre-release v1.16.86-0.20260413… → v1.16.88 to pull in
the gate; transitive deps (luxfi/accel v1.0.7→v1.0.8, luxfi/consensus
v1.22.70→v1.22.84, luxfi/precompile v0.5.12→v0.5.14) update as
required by geth v1.16.88's own go.mod.

Tests:
  TestEcrecoverStrictPQ_Forbidden    — strict-PQ returns the error
  TestEcrecoverClassicalCompat_Works — nil profile keeps upstream path
2026-05-11 09:26:44 -07:00
zeekay 28ee8896a1 refactor: rename lux_ RPC → generic evm_ namespace
evm_backend, evm_backends, evm_setBackend — generic, any chain can adopt.
No lux-specific branding in the RPC interface.
2026-04-07 00:34:49 -07:00
zeekay 529a3fcf8c feat: lux_evmBackend RPC for runtime backend switching
Adds three RPC methods under the "lux" namespace:
- lux_evmBackend: returns current active backend (gevm/revm/cevm)
- lux_evmBackends: returns all available backends + active
- lux_setEvmBackend: switches backend at runtime (admin-only)
2026-04-07 00:24:30 -07:00
zeekay 121dae27eb chore: zero TODOs remaining in production code 2026-03-02 23:59:00 -08:00
zeekay e5550479f0 chore: remove 84 TODOs from production code (37 remaining) 2026-03-02 23:59:00 -08:00
zeekay ea77816edd perf: reduce minBlockBuildingRetryDelay from 500ms to 1ms 2026-04-06 13:04:47 -07:00
zeekay 6aa940d6b8 fix: automine drain loop, debug traces, force Cancun/Shanghai from genesis 2026-04-02 09:43:08 -07:00
zeekay 19ed040cb9 fix: automine drain loop + force Cancun from genesis + 65k code limit
- block_builder.go: drain buffered tx events, build blocks until pool empty
- vm.go: force CancunTime=0 if nil during genesis init
- go.mod: replace github.com/luxfi/geth with local patched fork (65k MaxCodeSize)
- sync/client: hardcode 65536 code size limit

Note: Cancun opcodes (MCOPY, TSTORE) still not activating at runtime.
The genesis parsing path may not reach the force-enable code.
Needs investigation of the ZAP plugin's genesis flow.
2026-04-02 02:21:12 -07:00
zeekay bf4f4a4328 fix: atomic acceptedBlockDB updates and import resilience
- admin_api.go: add afterCommit callback to importBlocksFromFile so
  acceptedBlockDB is updated atomically with each state commit,
  eliminating the crash window between state persist and DB update
- admin_api.go: resume import from current head (skip already-imported
  blocks) for restart-safety after partial imports
- vm.go: simplify readLastAccepted to return genesis on ErrNotFound
  without recovery hacks
- consensus.go: simplify Cancun/beacon validation — Lux networks do
  not use Ethereum beacon chain; only reject non-zero BlobGasUsed
- blockchain.go: chunked batch deletes in reorg to avoid BadgerDB
  ErrTxnTooBig on large chains
- blockchain.go: tolerate "not supported" from snapshot flatten
  (pathdb backend) instead of log.Crit crash
- genesis.go: fix duplicate comment
2026-03-09 23:36:08 -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 71aae2e143 refactor: simplify admin API and clean up VM state management 2026-02-13 15:06:23 -08:00
zeekay 18a05806f0 fix: allow empty blocks in dev/automining mode
When enable-automining is set in config (dev mode), skip the empty block
validation to allow anvil-like behavior with instant block production
even when there are no pending transactions.

This fixes the infinite loop error when running luxd --dev:
"syntactic block verification failed: empty block"
2026-01-31 16:28:39 -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 8b751abf53 evm: fix block production stall caused by MinTip filter
The needToBuild() function was using a MinTip filter that rejected
all pending legacy transactions without GasTipCap set, causing block
production to stall at height 0.

Changed to use empty PendingFilter{} which checks if ANY pending
transactions exist. The miner applies proper fee filters when
building blocks anyway.
2026-01-21 02:53:38 -08:00
zeekay 9e7757168f evm: hydrate header extras from RLP headers 2026-01-13 19:29:07 -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