38 Commits
Author SHA1 Message Date
zeekayandHanzo Dev dd3ff1c9b5 dexvm: delete the D-Chain proxy, keep the asset registry
The proxy built for the canonical D-Chain slot (VMID "dexvm",
mDVT5EWMumBp3LCqvKwuyZQeY1VXr1jvjGNAt8nL4UFiXvqXr) that luxfi/dex cmd/dexd
already fills, and its wire spoke clob_* while the venue registers dex_*.
Installing it produced a node that loaded a VM, passed health, and answered
nothing — a failure that reads as a working chain. Nothing imported it but its
own plugin main, and the release matrix published it as an image on every tag.

dexvm/registry stays: it is the DEX asset registry, imported by the EVM value
wiring and dex-assets-validate, and it holds the Gate-A primitives its own
gate_test.go covers. The deleted enforce.go was a config adapter over it.

internal/slot pins the invariant by AST, so a comment may discuss the slot but
a declaration may not claim it: no package here names the D VMID, none declares
a clob_* method, and the release matrix ships no image for the slot. Runs
unfiltered on every push — a path filter would miss a NEW package claiming an
already-filled slot, which is the case worth catching.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-07 16:32:00 -07:00
zeekayandHanzo Dev 3e9fd77e98 rpc: /ext/ -> /v1/ on our own hosts
luxd serves /v1/*; /ext/* 404s. Third-party chains keep their own paths.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-03 21:15:23 -07:00
zeekay 8d0335a46b chore(chains): remove dexvm/plugin — gRPC transport, ZAP-native only
//go:build grpc importing node/vms/rpcchainvm. Unbuildable (Makefile takes the
cmd/plugin branch), unreferenced, and a second transport where ZAP is the one.
All 11 sibling VMs have cmd/plugin and only cmd/plugin.
2026-07-25 12:26:39 -07:00
zeekayandHanzo Dev 9ec41cf150 Native-ZAP struct-is-wire for the four straggler VMs (aivm/keyvm/quantumvm/dexvm)
Completes the "one and only one way" wire migration: every VM in this repo now
serializes blocks and txs through luxfi/zap struct-is-wire at fixed field
offsets — no encoding/json, no hand-rolled big-endian, no cursor codec.

- aivm: Block + AIVertex + nested CIntent native-encoded. Fixes a latent
  data-loss bug — AIVertex had unexported fields, so encoding/json silently
  marshalled it as {}; the native encoder round-trips every field.
- keyvm: Transaction (signing preimage = SigningBytes(), excludes Auth/Sig;
  full wire = signing || sig object, so the signed bytes stay a genuine prefix
  and authenticate() re-derives a byte-identical preimage) + Block.
- quantumvm: Block + BaseTransaction. Block wire is the quantum-signature
  preimage (deterministic fixed offsets, ordered tx list).
- dexvm: block envelope (txs were already ZAP). Preserves UnixNano timestamps,
  deterministic tx order, and content-addressed carried fills.

Hardening from the adversarial crypto review (must-holds all verified: no
keyvm signature malleability/replay, no aivm CIntent id-forgery/escrow tamper,
no dexvm ordering/fill tamper, all parsers fail closed):

- keyvm (M1): make the wire strictly canonical. zap follows the root offset
  and ignores unreferenced padding inside a message's declared size, so a twin
  buffer could decode to identical fields with a different hash (id-malleability).
  ParseTransaction now binds the id to the re-serialized (canonical) form and
  rejects any non-canonical input at the door — exactly one byte-string
  authenticates per logical tx. Regression test: the padded twin is rejected.
- quantumvm (M2): derive block + tx ids as sha256(Bytes()), matching the other
  three VMs. The prior ids.ToID(parent||height) over 40 bytes (and ids.ToID over
  the >=32-byte tx wire) silently yielded ids.Empty for every block/tx,
  collapsing the block store and tx pool to a single slot. The block id is no
  longer stored in the wire, so the invariant id == sha256(Bytes()) holds
  unconditionally. Regression tests: ids are non-Empty and collision-distinct.

Not in scope (tracked separately, fail-closed today): aivm RewardPerOperator is
escrowed but not bound by ComputeIntentID — a pre-existing design gap that needs
a coordinated A-side + C-side intent-id preimage bump before any non-fail-closed
CCommitVerifier ships.

Builds + tests green under the canonical CGO=0 -mod=readonly recipe; go.mod/go.sum
are the go mod tidy canonical set (indirect deps resolved forward, all within
major). Cryptographer verdict: SAFE TO PUBLISH.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-16 13:44:24 -07:00
zeekayandHanzo Dev 7786bf1a56 dexvm: remove dead blockInterval field; lock ns-resolution sub-second cadence + monotonic clamp
dexvm (D-Chain / DEX) is ALREADY 1ms-capable: nanosecond wire timestamps (UnixNano round-trip on
both linear Block and DAG DexVertex paths), Config.BlockInterval default already 1ms ('1ms blocks
for HFT'), deriveBlockHash on UnixNano, no whole-second truncation anywhere. The one artifact was a
dead ChainVM.blockInterval field (100ms, never read repo-wide) that falsely implied a floor — removed
(zero readers → semantically inert). Config.BlockInterval is now the single source of truth. Added two
//go:build redteam tests locking the previously-untested monotonicity clamp at ns resolution across
BOTH proposer entrypoints (BuildVertex + BuildBlock): a 1ms clock advance survives byte-exactly, the
sub-second component is preserved, a backward clock is clamped non-decreasing. Fairness invariants
unchanged (height is the strict total-order sequencer; block time non-decreasing; distinct sub-second
times → distinct block hashes; proposer-carried time, no wall-clock ordering race). Settlement/matching
untouched. Combined with the sub-second proposervm (node v1.36.19 UnixMilli window), a co-located
fiber/InfiniBand DEX runs 1ms blocks natively.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-16 11:01:36 -07:00
zeekayandHanzo Dev 17af562c54 deps: realign to node v1.36.9 / utxo v0.5.7 / zap v1.2.5 (surviving tags)
Bumps the plugin subgraph off wiped intermediate tags (was node v1.36.4 → utxo
v0.5.1) onto the current surviving set. dexvm/txs/wire.go: setBase helpers take
zap.ObjectBuilder by value (v1.2.4+ StartObject returns a value type). Full
chains build clean; migrated-VM + mpcvm/bridgevm suites green.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-14 15:55:09 -07:00
zeekayandHanzo Dev 3769a8ab27 dexvm: migrate tx wire json→native ZAP (~20-30x faster, D-Chain codec-free)
All 5 concrete tx types (ImportTx/ExportTx/RelayOrderTx/PlaceOrderTx/
CancelOrderTx) + nested AtomicInput/AtomicOutput now encode as
[typeByte][ZAP object] at fixed offsets instead of [typeByte][json body].
Repeated fields use the bridgevm blob-list idiom (u32 lens-list + concat blob
via SetList/SetBytes) — chains-module zap v1.2.0 has AddObjectPtr but not the
List.ObjectPtr reader. Deterministic by construction; TxID = Checksum256(wire);
canonical trailing-byte reject. Bench (ImportTx 2in/2out): marshal 18255→912ns
(~20x), parse 14923→498ns (~30x), far fewer allocs. Round-trip + determinism +
signature-through-wire tests green; dexvm redteam/conservation/custody suites
green (parse txs through this codec e2e).

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-14 15:35:09 -07:00
zeekayandHanzo Dev 1676121436 fix(dexvm): exact-integer ZAP wire — match dex v1.14.0, kill cross-arch fork
The dex venue (v1.14.0) froze its ZAP wire to EXACT INTEGERS: order/fill
price is uint64 fixed-point ×1e8 (the PriceInt grid), size is uint64 atomic
base units — no IEEE-754 float64. This eliminated every value-bearing
float→int conversion from the settlement path, whose out-of-range result is
implementation-defined per the Go spec and differs by CPU arch (arm64
saturates, amd64 wraps) — the cross-arch consensus fork (F1-F5).

The stateless atomic ZAP proxy still encoded/decoded the OLD float wire, so
it could no longer interoperate with the new venue. This propagates the
frozen frame byte-identically:

- relay.go: Fill{Price,Size} float64 -> uint64; DecodeFills reads big-endian
  uint64s and rejects zero price/size. New PriceScale const == zapwire.PriceScale.
- carried_fills.go: block-carried fills encode/decode as exact uint64s.
- atomic.go: drop the float→int rounding machinery (quantToCredit/quantToCharge/
  nearestIntWithin/settlementRoundEpsilon/maxSettlementUnit/float64FromBits/
  isFinitePositive). Add quoteUnits (floor(size×price/PriceScale) in big.Int,
  mirroring dex v4BalanceDelta / the matcher's quoteUnitsFor) and settleUint64
  (refuses an aggregate that exceeds uint64 rather than wrapping).
- vm.go settleFromFills: aggregate base=Σsize, quote=Σ per-fill floor as exact
  integers; slippage limit compared as fixed-point uint64; no float. Refund/
  proceeds are the venue's own integers — escrow-truncation mint is structurally
  impossible; spent>locked still refused.
- vm.go encodeCLOBPlace: price = tx.Price×PriceScale, size passthrough (no float).
- feegate.go: swapNativeFeeToLUX takes a fixed-point Fill.Price and floors via
  quoteUnits.
- txs/tx.go: RelayOrderTx.PriceLimit is fixed-point ×PriceScale (was float64-bits).

Dep: github.com/luxfi/dex v1.5.20 (indirect) -> v1.14.0 (direct, test imports
the pure-Go zapwire leaf as the parity oracle).

Tests: all conservation/atomicity/redteam tests translated to the integer
semantics WITHOUT weakening any invariant (the no-mint / overflow-refusal /
mixed-side / slippage / determinism proofs are preserved, re-anchored to the
venue's exact per-fill floor). New frame_parity_test.go asserts the proxy's
Place/Submit/Fill frames are BYTE-IDENTICAL to dex v1.14.0 zapwire.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-02 10:40:05 -07:00
zeekayandHanzo Dev 3893cdfe3b test(dexvm): prove settlement conservation is independent of trust
Add redteam-tagged Byzantine settlement tests proving the chains/dexvm
conservation bound holds even against a genuinely-signing byzantine venue
(co-located with the proposer, so every fabricated fill carries a VALID
Ed25519 attestation). The attestation gate proves a fill came from the
venue key; these prove that even a validly-signed fill cannot mint.

dexvm/byzantine_settlement_test.go (//go:build redteam):
  - OverNotionalAttestedFillCannotMint: a validly-attested BUY spending
    1200 quote against 1000 locked is refused by settleFromFills
    ("spent > locked"); 0 minted, escrow intact (keystone)
  - WithinCollateralAttestedFillSettles: positive control — same venue,
    a within-collateral fill settles (400 base + 200 refund). The
    discriminator is spent<=locked, not the attestation
  - MixedSideAttestedFillRefused: a validly-attested [BUY,SELL] stream is
    refused by the single-side guard; no over-credit
  - DeterministicRegardlessOfFillOrder: settle is a pure function of the
    fill SET — two orderings settle to byte-identical export legs (RED #9)

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-01 20:53:15 -07:00
zeekay 71225a5a3d feat(dexvm): DEX fees settle to canonical LUX (network-independent) — L2/L3 ready
D-Chain fees denominate in the ONE canonical L0 asset constants.UTXO_ASSET_ID (the
brand-neutral LUX UTXO literal), not the local network's UTXO asset. So any L2/L3
building on LUX with its own native currency still settles DEX fees to LUX. The fee
policy is network-independent (newFeePolicy() takes no networkID); the MinTxFeeFloor
gate is enforced in LUX on every chain. UTXO_ASSET_ID is the correct reference (D-Chain
is UTXO; an EVM-context DEX would use the EVM-native LUX). networkID retained as chain
identity only. Tests: fee asset is canonical LUX not local native; floor holds on an L2;
native->LUX deterministic; zero-fee rejected; min-fee accepted. dexvm green.
2026-06-26 15:30:05 -07:00
zeekay e19c11951a fix(dexvm): restore white-label universe deny-scan (refuse Liquidity/Liquid/partner)
The dexcore->dex refactor gutted the chain-label scan in AssertNoForbiddenAssetRefs
(it discarded chainLabel via '_ = chainLabel'), so the DEX startup gate no longer
refused a source chain labeled a 'Liquidity (white-label) universe' even though the
token was real on-chain — a regression of the never-mix-Liquidity boundary.

Restore the negative gate: IsForbiddenUniverseLabel scans the chain's human label for
forbidden off-network brands (liquid -> catches Liquid EVM/DEX/Liquidity; partner),
case-insensitive, and AssertNoForbiddenAssetRefs refuses by name. Positive ChainVerifier
proves existence; this polices brand. Closes the 3 regressed gate tests.
2026-06-26 11:55:15 -07:00
zeekay e44493230e dexvm: dexcore → dex rename (dex/pkg/dexcore → dex/pkg/dex) 2026-06-25 23:15:44 -07:00
zeekay 349e27b10c consensus parity: quantumvm joins the linear cert path; dexvm doc fix
quantumvm/vm.go: remove GetEngine() so the chain manager routes Q-Chain through
the linear consensuschain cert path (the same deterministic 2/3-stake BFT
certificate finality as C/D/P), not the undriven DAG engine. quantumvm already
implements the full chain.ChainVM/BlockBuilder surface; dropping GetEngine is the
whole change. Adds the _ chain.ChainVM + _ consensuschain.BlockBuilder asserts.

dexvm/factory.go: correct the stale 'Quasar consensus' comment — dexvm finalizes
via the linear cert path (Photon->Wave->Focus + 2/3-stake cert), same as C-Chain.
2026-06-25 16:07:07 -07:00
zeekay 2b42aa7f12 decomplect: drop redundant brand denylist from dexvm/registry
forbiddenChainTokens + IsForbiddenChainRef named the off-network white-label
universe by string to reject it. That negative gate is redundant: the
positive gate (ChainVerifier) already admits only real on-chain Lux assets,
so an off-network universe can never pass regardless. Removed the brand list
and the func; kept the genuinely-distinct guards (mock/synthetic liquidity,
ASCII-ticker-as-id). AssertNoForbiddenAssetRefs keeps its signature for the
gate's call shape. No brand strings remain; builds clean.
2026-06-24 11:10:32 -07:00
zeekay c5de091147 dexvm: Gate-A real-assets-only startup enforcement + LABELED rename
VM.Initialize now runs enforceRealAssetsOnly before marking initialized:
fail-closed over the consensus-supplied network identity (never operator-
spoofable). Refuses synthetic flags on a value net, value activation with no
legal consensus mode, a wrong-net/Liquidity manifest, value with no real
assets, bad allowed-kinds. Config gains the real-assets fields (manifest
path + SHA-256 pin, synthetic flags, consensus mode, caps/halt). Renames
HONEST_VALIDATOR_LAUNCH -> HONEST_VALIDATOR_LABELED across the registry
(value qualified by what it IS — labeled CFT parity — not when it runs).
Adds cmd/dex-assets-validate (CI proves every manifest entry real against
the target net RPC) + the validate-asset-manifests workflow. dexvm tests +
registry gate tests green.
2026-06-23 01:03:49 -07:00
zeekay e590e4a91c registry(dexvm): golden KAT — AssetID byte-parity with dexcore (MED-1)
Mirror the canonical cross-home golden KAT: registry.DeriveAssetID must reproduce
the EXACT 32-byte AssetID bytes that luxfi/dex pkg/dexcore.DeriveAssetID asserts
(AssetIDGoldenVectors). Fixed (networkID=2, source chain all-0x11, {ERC20, EVM_NATIVE,
UTXO}) → expected id vectors, byte-identical to the dexcore side. This locks the
resolve↔register equivalence the 0x9999 value path depends on: a registered AssetID
(here) and a swap-derived AssetID (dexcore, via the precompile resolver) name the SAME
asset by the SAME id. A drift in either home's preimage discipline now fails CI in BOTH
suites — a real consensus-identity divergence caught in test, never in prod.

Verified: CGO_ENABLED=0 go test ./dexvm/registry -run GoldenKAT green; the same three
vectors pass in dex/pkg/dexcore.
2026-06-22 22:51:00 -07:00
zeekay 5ff9aca513 feat(dex/registry): embed per-network asset manifests + localnet native builder
Make the per-network asset manifests (mainnet/testnet/devnet) available to a node
binary with no filesystem dependency, so the EVM plugin can build the 0x9999
value-path AssetResolver from the CI-approved, content-hashed asset set compiled
into the binary. Selection is by EVM chainID (96369/96368/96370). Localnet (1337),
whose C-Chain id is environment-specific, gets a runtime-rooted native-only manifest
(LocalnetNativeManifest) synthesised from the node's live cChainID — never a constant.

- embedded.go: //go:embed the three manifests; EmbeddedManifestFor(evmChainID) +
  LocalnetNativeManifest(networkID, cChainID).
- manifest.go: split decodeManifestBytes out of loadManifestBytes so the embed path
  and the disk path share one decoder (shape-validate + content-hash + unknown-field
  rejection identical for both).
2026-06-22 20:56:23 -07:00
zeekay 33634aad32 revert(dexvm): drop C<->D keeper RPC seam (dex.submitTx + dex.getSettlement)
The keeper/venue model is abandoned. The D-Chain (dexvm) trades via its own
consensus-driven state transition (matcher-at-Verify), not via an off-chain
keeper polling dex.getSettlement to drive 0x9999. Revert the three v1.3.16
commits' additions to the v1.3.15 working state:

  - vm.go:           remove GetSettlement + the proceeds-coordinate record
  - chainvm.go:      remove the SubmitTx RPC seam additions
  - api/service.go:  remove dex.submitTx + dex.getSettlement handlers
  - state/state.go:  remove PutSettlement/GetSettlement settlement index
  - api/submit_test.go, state/settlement_test.go: remove keeper-RPC tests

Deps are unchanged from v1.3.15 (go.mod/go.sum identical). bridgevm + dexvm
build clean and all tests pass.
2026-06-22 03:11:57 -07:00
zeekay 99ccfa4766 dexvm: record proceeds coordinate + dex.getSettlement RPC (keeper Phase-B input)
settleFromFills now records the proceeds D->C object's (outputID, amount) under the
collateral ref (the C->D intentID) via state.PutSettlement, written into the versiondb
so it commits atomically with the block. outputID = deriveUTXOID(settlementExportTx.ID(),
0) — the SAME key executeExport puts into shared memory — and the amount is the realized
proceeds; both are pure functions of consensus-agreed inputs (blockHash+txIndex seed the
export identity), so every validator writes the identical record and the StateRoot stays
consensus-safe. The record is purely informational (moves no value; never consulted by
the deterministic settle/consume path).

dex.getSettlement(collateralRef) reads it back (VM.GetSettlement -> state.GetSettlement)
so the off-chain keeper, after submitting the ImportTx + settling RelayOrderTx and the
proposer settles, learns the D->C object's outputID + amount and builds the C-side Phase-B
ImportSettlement (DS01 outputID|amount|intentID) that consumes the object and emits DEXFill.
This closes the keeper's export-discovery gap.

Tests: 3 state round-trip/isolation/escrow-coexistence + 3 RPC pending/found/bad-ref, all
pass (CGO_ENABLED=0; local cgo accel toolchain is broken — CI builds linux/amd64).
2026-06-22 02:23:46 -07:00
zeekay 0d8a06af7d dexvm: expose SubmitTx RPC (the C<->D keeper mempool seam)
The dexvm is a stateless atomic ZAP proxy whose ImportTx/RelayOrderTx/ExportTx
are deterministic block inputs, drawn from the proposer's pending pool. That pool
(ChainVM.SubmitTx -> pendingTxs + engine notify) was unreachable from outside the
process: CreateHandlers exposed only Ping/Status/Relay, so an off-chain keeper had
no way to inject the ImportTx that consumes the C->D atomic object the C-side
0x9999 SubmitSwapIntent writes, nor the settling RelayOrderTx. The full two-phase
seam therefore could not land a DEXFill.

This adds a dex.submitTx JSON-RPC method (api.Service.SubmitTx) that forwards
wire-encoded tx bytes to an api.TxSubmitter and returns the canonical
ids.Checksum256 tx id. The submitter is wired ONLY on the node path
(ChainVM.CreateHandlers builds the service with the ChainVM as submitter); the
inner VM's read-only CreateHandlers (standalone/test) reports ErrSubmitUnavailable.
Bootstrap-gated; admission/fee gate stays in ChainVM.SubmitTx. 6 unit tests cover
unavailable/not-bootstrapped/bad-hex/empty/success(verbatim+TxID)/submitter-error.
2026-06-22 01:23:00 -07:00
zeekay 4e0ddde49b dexvm: pin C<->D atomic seam wire (69-byte, spent witness) against precompile golden
The swap-seam HIGH fix (c242e01) extended encodeExportedOutput to 69 bytes
(rail|owner|asset|amount|spent), where the trailing spent witness carries the matched
INPUT amount on a D->C swap proceeds leg so the C-side ImportSettlement can enforce the
taker's recorded slippage limit. That widening MUST be byte-identical with the precompile's
exportedOutputSize9999 or D would export an object C cannot decode (every swap settlement
reverts ErrNativeSettleMalformed, no taker is ever credited).

native_seam_parity_test pins the lockstep on the D side: a hardcoded golden vector
(reproduced byte-for-byte by precompile/dex/native_seam_parity_test) + a round-trip that
proves the spent witness survives + a wrong-width rejection. If either repo's layout drifts,
one of the twin parity tests fails BEFORE the bytes reach consensus. Closes the dangling
'native_seam_parity_test pins it' reference in atomic.go.
2026-06-21 22:41:07 -07:00
zeekay 0777aeed15 dexvm: plumb fill-attestation pubkey through genesis + carry matched-spent on proceeds (swap-seam)
HIGH (inert attestation gate): FillAttestationPubKey was read by settleCarried but
NEVER written — parseConfig copied only 10 enumerated fields and parseGenesis carried
only DexZapEndpoint+TrustedChains. So in prod the pubkey was nil, verifyFillAttestation
returned nil, trustCarried was always true, and the fabricated-fills guard could never
engage: a malicious/MITM proposer could settle fabricated fills and drain a cross-taker's
seam reserve via the proceeds leg. Fix: add FillAttestationPubKey (hex) to the Genesis
struct + parseGenesis, assigned into vm.Config. It is GENESIS-pinned only — deliberately
absent from parseConfig — because it is consensus-affecting (gates trustCarried ->
escrow-consume + export legs -> computeStateRoot); a per-node value would fork the
StateRoot. A wrong-width key is a FATAL genesis error (fail-closed). FillAttestationSeed
stays json:"-" (KMS-only, never in a genesis/config blob).

MEDIUM (consensus comment): config.go now states the stronger invariant — the pubkey is
STRICTER than DexZapEndpoint (which is node-local transport, not recomputed in the
deterministic settle path) and MUST be a single genesis-pinned constant.

MEDIUM (taker-authenticated MEV floor): the proceeds export object now carries the matched
input (AtomicOutput.Spent; shared-memory object 61->69 bytes, both repos in lockstep) so
the C-side ImportSettlement can enforce the taker's OWN recorded slippage limit
(proceeds >= spent*worstRate) independently of the keeper's relay-supplied priceLimit. A
keeper that zeroes the relay limit to sandwich the taker still produces a proceeds object
C will refuse; understating spent loosens the floor but inflates refund, which the
per-taker principal cap + seam-reserve conservation independently bound.

Tests (redteam, CGO_ENABLED=0 pure Go): redteam_genesis_attestation_test drives a REAL
genesis blob through Initialize->parseGenesis (NOT a direct Config write) and proves the
pubkey reaches Config, a fabricated/unattested block becomes a full refund by virtue of
genesis alone, a wrong-width genesis key fails Initialize closed, and the runtime config
blob cannot set the consensus-affecting pubkey. native_seam canary pins the joint 69-byte
object wire (incl. spent) across both repos. Full dexvm redteam + unit suites green.
2026-06-21 22:41:07 -07:00
zeekay 8980d99883 dexvm: close HIGH-1 proceeds-unclaimable + MEV floor + fill attestation (swap-seam)
HIGH-1 (THE happy-path break, swapsWork=NO): settleFromFills exported the taker's
PROCEEDS leg under assetFromRef(ref,leg)=SHA256(ref||leg), a synthetic routing handle.
The C-side ImportSettlement requires recAsset==assetID(currency_out) (the injective id
that also keys seamReserve[assetOut]); a SHA256 handle never matched, so the proceeds
credit ALWAYS reverted (ErrNativeSettleAsset) and the taker never received their swap
output. FIX: settleFromFills exports the proceeds leg under the REAL output-asset id
carried with the settling relay (RelayOrderTx.AssetOut, signature-bound, keeper-asserted
and C-bound => a wrong value is taker-liveness-only, never theft/mint). Removed the dead
assetFromRef SHA256 helper. Kept the prior pass's owner-bind + one-time-consume +
per-intent binding intact.

MEDIUM (bounded sandwich/MEV): settleFromFills now enforces a worst-acceptable CLOB
price carried from the relay (RelayOrderTx.PriceLimit/LimitIsUpper, derived from the V4
SqrtPriceLimitX96): a fill worse than the limit is refused (BUY ceiling, SELL floor;
0=no limit), leaving the escrow reclaimable.

MEDIUM (single-proposer fabricated fills): populate+verify the reserved carried-fills
attestation. The venue signs blake3(domain||blockHash||entries) with Ed25519; every
validator verifies it before settling (verifyFillAttestation), gated behind
Config.FillAttestationPubKey (default-on once set; fail-secure full-refund when a block's
fills are unattested/forged). Config note: the pubkey MUST be identical across validators
(consensus-affecting, same class as DexZapEndpoint).

Tests (CGO_ENABLED=0, default + redteam): TestRED_SwapProceeds_* (unit + e2e happy-path
credit under the real output asset), TestRED_SwapSlippageLimit_*, TestRED_FillAttestation_*
(verify/forge/fabricated-refund/off-by-default). Full dexvm suite green.

Extends fix/swap-rail-escrow-owner-bind (27f4c35). Within chains v1.3.15.
2026-06-21 22:41:07 -07:00
zeekayandGitHub 6e46d242f6 Thinking Chains: chains (#6)
* dexvm: bind swap-settle authority+payout to authenticated escrow owner (CRITICAL escrow-theft fix)

The swap rail delegated per-taker refund-liveness + relay authority to the
unauthenticated RelayOrderTx sender with NO C-side owner binding, letting any
fee-paying tx name a victim's collateral ref and settle the locked collateral to
the attacker out of the shared seam reserve (cross-depositor theft).

Fix (load-bearing, derives authority from the already-authenticated cross-chain
object owner — the platformvm import/export model):
- state/state.go: escrow value is now owner(20)|asset(32)|amount(8); the owner
  leads. PutEscrow/GetEscrow carry it.
- atomic.go executeImport: persist the AUTHENTICATED importedOwner (read back from
  the consumed C->D shared-memory object) into the escrow.
- vm.go settleFromFills: a settle is authorized ONLY by the escrow's recorded
  owner (errSettleUnauthorized otherwise), and BOTH proceeds+refund legs export to
  that owner, never to the relay tx sender.
- txs/tx.go: RelayOrderTx.Verify now does present-gated secp256k1 authentication of
  From (Sign/SigningBytes/verifyFrom), wiring the dangling ErrInvalidSignature.
  From bears NO authority (authority is the escrow owner), so an unsigned relay is
  admissible and cannot escalate; a present-but-invalid signature is rejected.

Regression: redteam_escrow_owner_test.go proves the attacker is credited 0 even
with a valid own-key signature (defense in depth: escrow-owner bind holds
independent of the signature). Fixed a pre-existing off-by-one in
TestRED_FractionalFill (export asset offset). All dexvm tests pass (default +
-tags redteam, CGO_ENABLED=0 pure-Go).

Gated on red re-verify; NOT promoted to testnet/mainnet.

* aivm: A-Chain quorum-settlement engine (provider stake/slash, beacon selection, commit-reveal, settlement, receipt export, import committed C intent). LP-5300. NOTE: branched on swap-rail base; rebase to main on merge.

---------

Co-authored-by: zeekay <[email protected]>
2026-06-21 20:53:39 -07:00
zeekay a1169a810e dexvm: rail-tag the C<->D atomic object (D side of Red H1 fix)
The D side of the precompile/dex H1 rail-binding. The shared-memory object wire
goes 60->61 bytes: rail(1)|owner(20)|asset(32)|amount(8), byte-identical to the
precompile's exportedOutputSize9999. The rail tag is the lane the value travels
(RailSwap=0 default / RailLP=1) and round-trips through the atomic core unchanged.

- txs.AtomicOutput gains Rail; ImportTx/ExportTx.Verify enforce one-rail-per-leg
  (structural half, mirroring the asset bind).
- encode/decodeExportedOutput carry the rail byte (decode now returns rail too).
- executeImport binds the credited outputs' rail to the consumed UTXO's RECORDED
  rail (authoritative half; errImportMixedRails), so an import cannot re-lane
  value — a swap object onto the LP lane or vice-versa.
- executeWithdraw (the LP collect/withdraw export) stamps RailLP; the fill/refund
  settlement exports (settleFromFills) stay RailSwap (zero default).

New: TestNativeRail_ImportRejectsRailMismatch (both directions) +
TestNativeRail_ImportAcceptsMatchingRail (positive control). Wire-parity canary
TestNativeSeam_WireMatchesPrecompile asserts byte-identity on BOTH rails. LP-seam
round-trip + conservation/redteam offset helpers updated to the 61-byte wire.

CGO_ENABLED=0 GOWORK=off go test ./dexvm/... green.
2026-06-21 02:59:34 -07:00
zeekay fa7d32c86c dexvm: LP D-committed seam round-trip tests (commit import / collect export)
Prove the D side of the LP rail (precompile/dex 0x9999 modifyLiquidity /
collectPosition) consumes/produces the EXACT atomic wire WITHOUT any new D-side
primitive: a position-commit object (DL01) is byte-identical to any value object,
so the C->D commit leg == executeDeposit (atomic import -> CLOB ledger credit; the
position's collateral + fee accrual lives in the CLOB, funded by the consumed
object) and the D->C collect leg == executeWithdraw (debit realized balance ->
atomic export). Three tests: commit-funds-position (+ replay reject),
collect-exports-principal+fees, full round-trip conserves.
2026-06-21 02:15:54 -07:00
zeekay c8e45e6161 dexvm: native C<->D atomic seam round-trip tests
Prove the dexvm import/export wire is byte-identical to the precompile's atomic
object (TestNativeSeam_WireMatchesPrecompile), that a C->D object the precompile
PUT imports cleanly on D, that a D->C export the dexvm writes decodes as the
precompile's settlement object, and the full C->D->C round trip. Locks the
cross-chain seam compatibility the 0x9999 money path depends on.
2026-06-21 00:19:25 -07:00
zeekay 69d6251fa4 dexvm: bind import credit to consumed-UTXO asset/owner + custody hardening
executeImport reads the consumed UTXO owner|asset|amount from shared memory and binds the credit to it (rejects declared!=actual; native-aliasing closed); two-pass validate-then-consume so a rejected import mutates nothing. Withdraw-rail consume-once on fillRef.
2026-06-19 11:34:41 -07:00
zeekay 8f733194b3 chains/dexvm: atomic custody rail (custody.go) — deposit=import+credit, withdraw=debit+export
executeDeposit = executeImport (atomic C/X debit, consume-once) + clob_deposit relay (credit
D-Chain balance; refuses credited!=imported). executeWithdraw = clob_withdraw (debit, realized
clamped) + executeExport (credit exactly realized; refuses realized>want). Reuses the
conservation-tested ImportTx/ExportTx rail. custody_conservation_test: real atomic.SharedMemory,
deposit→trade→withdraw conserves C in==out, UTXO consumed once. NOTE: not yet wired into the
carried-fills BuildBlock plan (network-upgrade-gated follow-up, like #9).
2026-06-17 21:10:33 -07:00
zeekay 562bee534a chains/dexvm: #9 consensus-fork fix — proposer-relays-once + carry-fills-in-block
Decomplect obtain-fills (proposer, BuildBlock, once network-wide) from settle-from-carried-
fills (deterministic, Accept). No validator relays at Verify/Accept -> block output is a pure
fn of (height, carried ts, tx bytes, carried fills) -> byte-identical StateRoot. NEW
carried_fills.go wire section (block + vertex) with a RESERVED fill-signature field for the
future trustless path (no 2nd wire bump). #7 computeStateRoot completed (commits state+atomic).
TestRED_PerValidatorRelay_SplitsConsensus PASS (0 cross-validator relay); redteam 21/0 (37/0);
conservation/determinism green; race-clean. NETWORK-UPGRADE-GATED wire-format change.
2026-06-17 17:12:55 -07:00
zeekay 4e21d9910a chains/dexvm: rip out matcher+state -> stateless atomic ZAP proxy
orderbook/perpetuals/lending/mev dirs + backend.go deleted; vm.go 2230->830 lines.
NEW atomic.go (shared-memory import/export at Block.Accept) + relay.go (rpc.ZAPDial,
frame byte-identical to zapwire, no luxfi/dex dep). State holds only nonce:/receipt:/
consumed: -- no order/pool/position keys. txs reduced to import/export/relay. NEW
conservation_test.go::TestEndToEndAtomicValueConservation. Verified -tags dchain build+tests
green -race; grep matchAllOrders|tryMatch|crossOrders empty.
2026-06-17 13:31:58 -07:00
zeekay b8a2a5c4dc chains: scrub private-repo leakage from GPU bridges
OSS public chains module must not reference the private GPU plugin
implementation by repo name or filesystem path. Two leak types fixed:

1. Hard compile-time dependency in quantumvm:
   `#cgo CFLAGS: -I${SRCDIR}/../../../lux-private/gpu-kernels/include`
   Vendor the public ABI v14 header (backend_plugin.h + crypto.h, 1475
   LOC total) into chains/internal/luxgpu/include/lux/gpu/ and point
   CFLAGS at the in-tree path. Plugin DSO is still dlopen'd at runtime
   — only the public C ABI surface is vendored.

2. Soft leakage — `LUX_PRIVATE_GPU_KERNELS_DIR` env vocabulary, dev-tree
   probe paths to ~/work/lux-private/gpu-kernels/build/..., prose
   mentions of "lux-private/gpu-kernels" in package comments.
   All replaced with `LUX_GPU_PLUGIN_DIR` (universal env) and generic
   "GPU plugin" prose.

Bridges affected: aivm, bridgevm, dexvm, quantumvm, thresholdvm.
All 139 tests PASS post-scrub under both build tags. No functional
behavior change — only path / env / prose.
2026-06-05 15:46:00 -07:00
zeekay c1ef83cf34 dexvm: wire GPU plugin substrate via dlopen+dlsym
Adds four Go files to chains/dexvm so the DEX VM can call into the
lux-private/gpu-kernels plugin DEX launchers (AMM xyk + CLOB matcher +
BookArena lifecycle) without a build-time pkg-config dependency:

* backend.go            cross-build types: GPUBackend enum, LuxAmmReservePair
                        (16 bytes, byte-equal to include/lux/gpu/dex.h),
                        CLOB ABI constants, ErrGPUNotAvailable.
* dexvm_gpu.go (cgo)    dlopen probe at init() in order
                        cuda -> hip -> metal -> vulkan -> webgpu, resolving
                        lux_<backend>_{amm_xyk_batch,dex_clob_match,
                        dex_clob_arena_create,dex_clob_arena_destroy}.
                        Exposes AutoBackend / AMMSwap / CLOBMatch /
                        ArenaCreate / ArenaDestroy. unsafe.Sizeof guards
                        the AMM struct layout against silent ABI drift.
* dexvm_gpu_nocgo.go    symmetric stubs returning ErrGPUNotAvailable so
                        the package surface is identical under both tags.
* dexvm_gpu_test.go     round-trip test: dlopens the metal plugin (or any
                        other found under LUX_GPU_PLUGIN_DIR), runs a
                        single-pair AMM swap, asserts byte-equal output
                        to the CPU oracle (constant-product 1000 -> 1998
                        at rx=1M, ry=2M).

Existing dexvm vm.go / orderbook logic is untouched - consumers opt in
explicitly when ready. Both 'go build ./dexvm/...' and
'go build -tags cgo ./dexvm/...' are green.

Test output with LUX_GPU_PLUGIN_DIR=$HOME/work/lux-private/gpu-kernels/build-metal/backends/metal:
  --- PASS: TestGPUAMMSwapRoundTrip (0.08s) [backend=metal]
  --- PASS: TestGPUAMMSwapEmpty     (0.00s)
2026-06-05 14:54:56 -07:00
zeekay 9926e395b2 dexvm: gate user tx on FeePolicy (MinTxFee floor)
SubmitTx is the canonical user-mempool entry on D-Chain — every public
submission funnels through here. Attach a FlatPolicy at MinTxFeeFloor
(1 mLUX) in Initialize and gate SubmitTx through ValidateFee so
zero-fee user txs are refused before they touch pendingTxs. Internal
consensus-engine -> VM paths bypass the gate.
2026-05-21 16:47:55 -07:00
zeekay 38a694bde4 Consolidate VMs into single chains module; cevm Metal anchor + V4 dispatch
VM consolidation (one module, one place only):
- Removed per-VM go.mod/go.sum/VERSION from {aivm,bridgevm,dexvm,evm,
  graphvm,identityvm,keyvm,oraclevm,quantumvm,relayvm,thresholdvm,zkvm}
- All VMs now compile under github.com/luxfi/chains root module
- aivm/, dexvm/, identityvm/, keyvm/, oraclevm/ factory.go + vm.go updated
  for AccelHost wiring

cevm Go-side (evm/cevm/):
- cevm.go, cevm_cgo.go, cevm_nocgo.go: ExecuteBlockV4(backend, threads,
  txs, ctx, state) with StateAccount snapshot
- cevm_secp256k1.go: BatchRecoverSenders Metal LDFLAGS
- cevm_secp256k1_metal_anchor_darwin.{c,go}: anchor to keep linker from
  dropping libsecp256k1_metal.a
- cevm_secp256k1_nocgo.go: stub for non-cgo builds
- plugin.go, fetch-luxcpp.sh, luxcpp-SHA256SUMS, LUXCPP_VERSION:
  pinned luxcpp consumption
- evm/cevm/parallel/parallel.go: BlockExecutor adapter calls
  cevm.ExecuteBlockV4 with state snapshot built from txs+statedb

Quantum/ZK witness:
- quantumvm/quasar_witness.go, zkvm/zwitness.go

go.mod: removed dead `replace github.com/ethereum/go-ethereum => luxfi/geth`
directive (caused dual-path conflict after math semver bump; no remaining
ethereum/go-ethereum imports in source). go mod tidy clean.

Tests: TestBatchRecoverSenders PASS through Metal anchor (cevm package).
2026-05-05 10:17:49 -07:00
zeekay ccca40e63b fix: dexvm VMID type + quantumvm RPC name
- dexvm.VMID: [32]byte → ids.ID (consistent with other 10 VMs)
- quantumvm RPC service: "qvm" → "quantumvm" (matches package + dir)

Removes last vestiges of the old short-name convention.
2026-04-13 05:49:35 -07:00
zeekay 8045450815 feat: move all VM implementations into chains/
VMs now live in ONE place: chains/<name>/ owns the full implementation.
Previously split across node/vms/<name>/ (impl) + chains/<name>/main.go
(plugin wrapper). Now consolidated.

New layout:
  chains/<name>vm/*.go              — package <name>vm (VM code)
  chains/<name>vm/cmd/plugin/main.go — package main (plugin binary)
  chains/<name>vm/go.mod             — module github.com/luxfi/chains/<name>vm

Moved 11 VMs: aivm, bridgevm, dexvm, graphvm, identityvm, keyvm,
oraclevm, quantumvm, relayvm, thresholdvm, zkvm.

External callers now import github.com/luxfi/chains/<name> directly.
2026-04-13 05:27:00 -07:00
zeekay ea6e37dc24 feat: VM plugin monorepo — 14 independent binaries
Each VM builds as a standalone binary that the Lux node loads via
--plugin-dir. Node no longer bakes optional VMs in-process.

VMs: evm, dexvm, aivm, bridgevm, graphvm, identityvm, keyvm,
oraclevm, quantumvm, relayvm, servicenodevm, teleportvm,
thresholdvm, zkvm.

All 13 node-dependent VMs compile and report version correctly.
EVM requires CGo (luxcpp) — builds in CI only.
2026-04-12 13:05:45 -07:00