origin/main had drifted to a docs-only commit that did not include the
bridgevm BLS threshold-scheme registration (v1.3.11, 7761e80) — without it the
v1.30.16 bridge regression recurs. Merge keeps both.
bridgevm runs as a standalone rpcchainvm plugin; mpc.go called
threshold.GetScheme(SchemeBLS) but never imported crypto/threshold/bls,
whose init() registers it. The full node binary pulled it in via
platformvm, but the v1.30.16 plugin build didn't -> B-Chain failed VM
init ('threshold: scheme not registered'). Blank-import to self-register.
The Z-Chain is DEFINITIVELY strict-PQ (shielded settlement on the canonical
Lux strict-PQ profile), but the switches were inert: the default ZConfig
omitted StrictPQ (->false) and RegisterZKPrecompiles had ZERO callers.
Task 1 — drive BOTH switches from the single ZConfig.StrictPQ field:
- Default ZConfig now pins StrictPQ=true + ProofSystem='stark' (the only
system accepted under strict-PQ). A genesis with no explicit ZConfig
yields a strict-PQ chain; a permissive deployment MUST set StrictPQ=false
explicitly.
- VM.Initialize now calls precompiles.RegisterZKPrecompiles(reg, config.
StrictPQ) — same bit. On strict-PQ the classical Groth16 (0x80) / PLONK
(0x81) verifiers are NOT registered (fail-closed by absence); only
STARK/FRI (0x82) + the fail-closed stubs exist. Added VM.ZKPrecompiles()
/ VM.StrictPQ() accessors.
LOW (Red) — TestStrictPQ_RealBN254VKLoadErrors drove a test-local mirror
(reapplyStrictPQVKGuard), not the production loadVerifyingKeys. Added a
config path (ZConfig.VerifyingKeys) to supply real keys in-memory; the test
now drives the REAL NewProofVerifier->loadVerifyingKeys and asserts the
strict-PQ real-bn254-VK guard fires there. Deleted the mirror.
Tests (a)(c): default Z-Chain is strict-PQ + registers strictPQ=true;
classical 0x80/0x81 absent, 0x82 present; a groth16 shielded proof is
REFUSED before reaching bn254; explicit StrictPQ=false flips both switches
in lockstep (profile-driven, not hardcoded); real-VK guard via the
production loader; non-strict chain still accepts a real bn254 VK.
H1 — shielded-tx ProofVerifier was classical-only (groth16/plonk) and
fail-closed only via all-zero dummy VKs; real bn254 VKs would re-enable a
CRQC-forgeable shield/unshield mint/steal path. Add ZConfig.StrictPQ and a
single gate refuseClassicalUnderStrictPQ(proofType) called at the top of
VerifyTransactionProof AND per-tx in the GPU batch path (which verifies
groth16 inline, bypassing VerifyTransactionProof). Under strict-PQ only
"stark" is accepted — wired to precompile/starkfri (cSHAKE256/Goldilocks
FRI, fail-closed until the prover binds), public inputs bound to the tx's
nullifiers+commitments. loadVerifyingKeys now ERRORS on a non-dummy bn254
VK under strict-PQ (explicit fail-closed, not implicit dummy-keys). The
remaining prover work (p3q_prove C ABI + shielded/ML-DSA AIR) is tracked
as a code comment; shielded value is effectively disabled on strict-PQ
chains until it lands — the correct no-classical-fallback posture.
H2 — Groth16/PLONK precompiles (0x80/0x81) were registered unconditionally
and Run([]byte) has no AccessibleState to call RefuseUnderStrictPQ.
RegisterZKPrecompiles(registry, strictPQ) now omits Groth16/PLONK on
strict-PQ chains (registering STARK 0x82 + fail-closed Halo2/Nova stubs
only) so 0x80/0x81 fail closed by absence; non-strict chains keep them as
an optional building block (kept, not deleted). CrossChainZKVerifier gains
StrictPQ that refuses routing Groth16/PLONK while still routing STARK.
H4 — verifyPLONK computed a self-cancelling pairing, discarded the result,
ignored public inputs, and returned valid for ANY >=544-byte blob (total
bypass). Now fails closed (errPLONKVerifierIncomplete) after structural
parsing — NEVER universal-accepts.
Groth16/bn254/PLONK remain present-but-gated; STARK/FRI (P3Q) is the only
accepted proof path on strict-PQ chains.
Tests: strict-PQ refuses groth16/plonk/bulletproofs (direct + GPU-batch);
real-VK-load errors; STARK fails-closed unbound and accepts bound with
nullifier+commitment-bound public inputs; non-strict path unchanged;
strict registry omits classical + keeps STARK; cross-chain strict refuses
classical; PLONK never universal-accepts. H-02 regression updated to the
new wired-STARK fail-closed behavior.
Make the Z-Chain MLDSA-rollup verification path post-quantum. The
rollup aggregates per-validator ML-DSA-65 sigs over the round digest;
the proof was classical Groth16/bn254 (pairing-based, quantum-breakable)
and is now a strict-PQ STARK/FRI proof (P3Q: Plonky3 fork, cSHAKE256
Merkle over Goldilocks, FRI; no KZG, no pairings, no trusted setup).
VERIFIER (the clean half, done):
- precompiles/zk_verifiers.go: STARKVerifier stub -> delegates to
precompile/starkfri.Verify. Parses [proof_len][proof][pub_len][pub];
proof must carry the 'P3Q1' magic. FAILS CLOSED (errVerifierUnavailable,
never resultValid) when no FRI binding is registered (CGO_ENABLED=0 /
no -tags starkfri_p3q) — no forgery oracle. The classical
Groth16Verifier/PLONKVerifier are KEPT intact at their own addresses.
- precompiles/crosschain.go: route VerifierTypeSTARK to STARKVerifierAddr
(was errNotImplemented).
- tests: STARK now proven to delegate to starkfri (accept->0x01,
reject->0x00, bad-magic rejected before callback, unbound fails closed)
and to route cross-chain; Groth16/PLONK tests unchanged.
PROVER (the honest remaining step, FLAGGED — NOT faked):
- zwitness.go: MLDSAGroth16 -> MLDSAStark rename; doc now states the
proof is STARK/FRI. Body stays ErrZWitnessNotImplemented: ~/work/lux/p3q
exposes a Rust prover but NO p3q_prove C ABI, and has NO MLDSA-rollup
AIR. Emitting fake proof bytes on the finality path would be a forgery
oracle, so the prover binding is left as the outstanding step.
go build ./zkvm/... + go test ./zkvm/precompiles/ green (CGO_ENABLED=0,
GOWORK=off). Real cgo-tagged starkfri binding (-tags starkfri_p3q)
compiles + KAT passes against the in-tree p3q staticlib.
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.
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.
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.
Replaces non-canonical scale-set / org-prefixed labels with the
existing labels every arcd host registers with. Matches evo for
amd64 and spark for arm64. No new labels added.
Picks up the 2026-06-06 relicensing:
- luxfi/vm v1.2.0 → Lux Ecosystem License v1.2 (was Lux Research v1.0).
License-only retag of the extracted runtime; patent reservation
preserved for runtime optimization surfaces, royalty-free for
Descending Chains.
- luxfi/sampler v1.1.0 → BSD-3-Clause (was Lux Research v1.0).
License restored to match the luxfi/node provenance (originally
extracted from node/utils/sampler).
- luxfi/staking v1.5.0 → BSD-3-Clause (was Lux Research v1.0).
License restored to match the luxfi/node provenance.
No code changes in this commit — go.mod/go.sum only.
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.
Adds four Go files to chains/quantumvm so the Q-Chain VM can call into
the lux-private/gpu-kernels plugin's batched ML-DSA verify, ML-DSA sign,
and SLH-DSA verify slots without a build-time pkg-config dependency:
* backend.go cross-build types: Backend enum, GPUBackend
interface, MLDSAMode + MLDSA65* size constants,
SLHDSAVariant enum + per-variant pk/sig widths
(FIPS 204 / FIPS 205), ErrGPUNotAvailable,
AutoBackend(). Build-tag-free so vm.go can
reference the surface regardless of CGO_ENABLED.
* quantumvm_gpu.go cgo dlopen probe at init() in order
(cgo) cuda -> hip -> metal -> vulkan -> webgpu,
resolving lux_gpu_backend_init and verifying
the ABI v14 cookies (abi_version + vtbl_size)
plus the load-bearing op_mldsa_verify_batch
slot before opening a LuxBackendContext. Three
vtbl-slot wrappers dispatch through C
trampolines into op_mldsa_verify_batch,
op_mldsa_sign_batch, and the SHAKE-128f /
SHAKE-192f variants of op_slhdsa_verify_batch.
* quantumvm_gpu_nocgo.go symmetric noGPUBackend stub returning
ErrGPUNotAvailable on every method so the
package surface is identical under !cgo.
* quantumvm_gpu_test.go round-trip test: dlopens the configured
plugin (LUX_GPU_PLUGIN_DIR override or default
loader path), generates an ML-DSA-65 keypair +
signs a 64-byte digest via circl, then submits
the triple through MLDSAVerifyBatch and asserts
results[0] == true. Cleanly SKIPs when no
plugin loaded or when the backend returns
NOT_SUPPORTED for op_mldsa_verify_batch (e.g.
plugins built without LUX_GPU_LINK_CRYPTO).
The probe is header-only: cgo CFLAGS points at
${SRCDIR}/../../../lux-private/gpu-kernels/include for the v14 vtbl
layout, but the .dylib / .so is dlopen'd at runtime - no pkg-config,
no static link, no luxcpp/install dependency. Existing quantumvm
vm.go, quantum/signer.go, and quantumvm/quasar.go are untouched;
consumers opt in explicitly when ready. Both 'go build ./quantumvm/...'
and 'CGO_ENABLED=1 go build ./quantumvm/...' are green.
Test output with LUX_GPU_PLUGIN_DIR pointing at a freshly-built
libluxgpu_backend_vulkan.dylib:
--- PASS: TestAutoBackend_Probes (0.00s) [backend=vulkan]
--- PASS: TestMLDSAVerifyBatch_RoundTrip (5.56s) [backend=vulkan,
op_mldsa_verify_batch honest verify -> true]
--- PASS: TestMLDSAVerifyBatch_LengthMismatch (0.00s)
--- PASS: TestMLDSAVerifyBatch_NonMLDSA65Skips (0.00s)
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)
Mirrors the evm/cevm pattern (cevm_cgo.go / cevm_nocgo.go +
backend_cgo.go). Three new files in bridgevm/:
backend.go — Backend enum, layout structs (byte-equal to
ops/bridgevm/cuda/bridgevm_kernels_common.cuh),
ErrGPUNotAvailable, GPUBackend interface,
unsafe.Sizeof asserts in init().
bridgevm_gpu.go cgo — direct dlopen(3) + dlsym(3) for the per-backend
plugin libluxgpu_backend_<bk>.{so,dylib}.
Probes cuda → hip → metal → vulkan → webgpu in
order; first plugin exposing all five
lux_<bk>_bridgevm_* launchers wins.
bridgevm_gpu_nocgo.go — stub returning ErrGPUNotAvailable on every method.
Direct dlopen (NOT pkg-config) so the public chains module builds without
the private gpu-kernels repo present. Search path: \$LUX_GPU_PLUGIN_DIR,
\$LUXCPP_PREFIX/lib/lux-gpu, \$LUXCPP_PREFIX/lib, the dev tree under
~/work/lux-private/gpu-kernels/build/backends/<bk>, CWD, and finally the
system loader's default.
Five launcher signatures dispatched via cgo trampolines (one per shape) —
matches the C ABI in include/lux/gpu/bridgevm.h. Layout structs include
explicit '_pad*' fields to keep Go sizeof() byte-equal to the C++
alignas(16) layout; the init() sizeof asserts catch any drift at process
start with a per-struct panic message.
vm.go / bridge_signing.go / mpc.go are NOT touched — this is a thin
substrate; the round applier opts in later when it's ready to route
through ActiveGPUBackend().
bridgevm_gpu_test.go — four tests under //go:build cgo:
TestActiveBackend — AutoBackend() returns a known tag.
TestStubReturnsErrGPUNotAvailable
— BackendNone path returns the sentinel.
TestLayoutSizesMatchHeader — runtime-equivalent of the C++ static_asserts.
TestSignerApplyZeroFixture — dlopen + dlsym + zero-fixture round trip
through lux_<best>_bridgevm_signer_apply.
Builds verified under both CGO_ENABLED=0 (nocgo path active) and
CGO_ENABLED=1 -tags cgo (cgo path active). All four cgo-tagged tests
pass on the local dev box (Vulkan plugin auto-detected from the
gpu-kernels build tree).
Mirrors chains/evm/cevm/cevm_cgo.go + cevm_nocgo.go but uses pure
dlopen/dlsym instead of pkg-config so the chains module compiles
without the lux-private/gpu-kernels build tree present.
New files
=========
* aivm_gpu.go (cgo) — GPUBackend with six host-launcher
wrappers (AttestationApply,
ProvenanceApply, AnchorApply,
EpochTransition, InferenceStep,
ProofVerify). Layout structs match
ops/aivm/cuda/aivm_kernels_common.cuh
byte-for-byte; an init() panics on drift.
* aivm_gpu_nocgo.go — stub GPUBackend returning
ErrGPUNotAvailable so vm.go's gpu paths
degrade cleanly.
* backend.go (cgo) — dlopen probe at process start. Walks
libluxgpu_backend_cuda.so → hip.so →
metal.dylib → vulkan.{so,dylib} →
webgpu.{so,dylib}. Honors
LUX_GPU_PLUGIN_DIR /
LUX_PRIVATE_GPU_KERNELS_DIR /
LUXCPP_PREFIX env. Exposes the public
parallel.SetBackend(AutoAIVM) analogue
(Mode / SetBackend / ActiveMode /
EffectiveBackendKind).
* aivm_gpu_test.go (cgo) — layout-drift guard + dlopen round-trip:
opens the best-available plugin and
calls lux_<kind>_aivm_attestation_apply
with a zero-input fixture, asserts rc=0
and applied_out=0.
vm.go is NOT modified. Two internal hooks (gpuAvailable() and
gpuTransitionApply(...)) are exported under both build tags so
Build/Verify/Accept can opt into the GPU transition when the wiring
on the consumer side is ready.
Test output (M1 Max)
====================
* CGO_ENABLED=0 go build ./aivm/... → clean
* CGO_ENABLED=1 go build -tags cgo ./aivm/... → clean
* CGO_ENABLED=0 go test ./aivm/ → ok
* CGO_ENABLED=1 LUX_PRIVATE_GPU_KERNELS_DIR=...
go test -tags cgo ./aivm/ → ok
TestLayoutSizes PASS
TestRoundTripAttestation PASS
aivm: using plugin metal at
…/build/metal-only/backends/metal/libluxgpu_backend_metal.dylib
TestRoundTripProofVerifyZeroOps PASS
TestNoPluginFallback PASS
TestModeAPI PASS
Was using canonical workflow defaults (hanzoai-scoped labels) which
do not dispatch for luxfi org. Switched to round-robin pool labels:
runner-amd64: luxfi-amd64 (evo)
runner-arm64: luxfi-arm64 (spark + dbc, round-robin)
runner-deploy: lux-deploy-linux-amd64 (DO ARC pool)
The luxfi/bridge daemon's bchain client (internal/bchain.Client) calls
bridge_getInfo / bridge_getSupportedChains / bridge_getChainConfig /
bridge_getSignature against the B-Chain RPC for permissionless discovery
+ MPC signature retrieval. Until now BridgeVM only implemented the
settlement subset (estimateFee, submitRequest, getStatus, cancelRequest,
health, getMPCPublicKey) — daemons fell back to assumed local values
for the discovery surface, a centralization vector.
This commit:
- Adds GetBridgeInfo, GetSupportedChains, GetChainConfig, GetSignature
to bridgevm.Service.
- Wires all four into the JSON-RPC dispatcher (bridge_* and bridge.*
aliases).
- GetSignature reads from the swap-store record (populated by the MPC
signing pipeline once the threshold quorum aggregates shares).
Distinguishes "no such swap" from "still signing" via separate errors
so callers can poll without dispatching on a generic error code.
- GetChainConfig matches the ChainID case-insensitively and surfaces
the requested id in the error message when the chain is not in
SupportedChains.
- Adds 6 table-driven tests covering happy paths, not-found, empty
params, and the not-yet-available signature poll.
After this lands the luxfi/bridge daemon stops needing any cached
fallback for these four endpoints — the B-Chain VM is authoritative
end-to-end. Daemon code itself needs no change; its bchain.Client
already calls these method names.
Tests: go test ./bridgevm/... — 36/36 pass (30 pre-existing + 6 new).
The luxfi/bridge daemon's bchain client (internal/bchain.Client) calls
bridge_getInfo / bridge_getSupportedChains / bridge_getChainConfig /
bridge_getSignature against the B-Chain RPC for permissionless discovery
+ MPC signature retrieval. Until now BridgeVM only implemented the
settlement subset (estimateFee, submitRequest, getStatus, cancelRequest,
health, getMPCPublicKey) — daemons fell back to assumed local values
for the discovery surface, a centralization vector.
This commit:
- Adds GetBridgeInfo, GetSupportedChains, GetChainConfig, GetSignature
to bridgevm.Service.
- Wires all four into the JSON-RPC dispatcher (bridge_* and bridge.*
aliases).
- GetSignature reads from the swap-store record (populated by the MPC
signing pipeline once the threshold quorum aggregates shares).
Distinguishes "no such swap" from "still signing" via separate errors
so callers can poll without dispatching on a generic error code.
- GetChainConfig matches the ChainID case-insensitively and surfaces
the requested id in the error message when the chain is not in
SupportedChains.
- Adds 6 table-driven tests covering happy paths, not-found, empty
params, and the not-yet-available signature poll.
After this lands the luxfi/bridge daemon stops needing any cached
fallback for these four endpoints — the B-Chain VM is authoritative
end-to-end. Daemon code itself needs no change; its bchain.Client
already calls these method names.
Tests: go test ./bridgevm/... — 36/36 pass (30 pre-existing + 6 new).
The bridge is permissionless and non-custodial. Authoritative quote
math and swap state must live where the validator quorum runs
consensus — in this VM — not in any single daemon's local cache.
This change moves both into bridgevm and exposes them via JSON-RPC
so any client can reach the same settlement decisions.
New RPC methods:
bridge_estimateFee — authoritative quote computation
bridge_submitRequest — create + quote-snapshot
bridge_getStatus — read swap by id
bridge_cancelRequest — idempotent cancel
bridge_health — liveness probe
bridge_getMPCPublicKey — threshold group public key
Architecture:
- QuoteEngine (quote.go) StaticPriceFeed → quorum oracle later
- SwapStore (swap_store.go) in-memory genesis impl; block-persistence next
- Service methods (rpc.go) JSON-RPC dispatch (snake + dot aliases)
Attestation aggregation already lived in bridge_signing.go via
BridgeSigner + BridgeMessageValidator — no changes needed there.
LP-333 signer-set methods unchanged.
The cross-chain GPU-resident block builder is proprietary. The package
has been relocated to lux-private/multichain. Public chains repo keeps
only the per-chain VM implementations (evm, dexvm, fhevm, etc.) — those
stay OSS and are what external operators interact with.
What's still public here:
evm/cevm GPU EVM block dispatch (V4 ABI)
dexvm Lux DEX VM (CLOB + AMM)
bridgevm, thresholdvm,
zkvm, quantumvm, ... other per-chain VMs
What moved private:
multichain Cross-chain atomic block builder
(the in-memory GPU multi-chain coordinator
that runs C+D+F transitions in one block)
Public consumers that need cross-chain orchestration can still use the
per-chain Execute APIs directly. The atomic multi-chain coordinator is
the closed-source piece.
New package github.com/luxfi/chains/multichain that runs C-Chain (EVM),
D-Chain (DEX), F-Chain (FHE) transitions atomically in one block. A tx
that calls a cross-chain precompile no longer bounces CPU→GPU→CPU→GPU
for each chain boundary — the Coordinator orchestrates the per-chain
kernels via a fixed-point loop until every declared CrossRef resolves,
then seals all three sub-blocks together.
API:
CrossChainBatch { CChainTxs, DChainOps, FChainOps, CrossRefs }
MultiChainBlock { CChainBlock, DChainBlock, FChainBlock, Receipts, StateRoots }
ChainKernel.Execute(in) → (out, err)
ChainKernel.Seal() → (subBlock, stateRoot, err)
Coordinator.ExecuteBlock(batch) → (*MultiChainBlock, err)
Atomicity: any kernel error or unresolved CrossRef rejects the entire
block — no partial commit. CrossRefs in the input batch are ASSERTIONS
(post-conditions) verified at seal time; they are NOT pre-dispatched
queues. The kernel is the single source of truth for cross-chain calls.
Phase 1 (now): host coordinates sequential per-chain dispatch with
goroutine parallelism between independent chain kernels each round.
Phase 2 (luxcpp shared-MvMemory, future): the same BlockBuilder API is
satisfied by a single GPU kernel that runs all three chains over shared
device memory. Callers don't change — only the underlying dispatch.
Tests: 4 subtests cover standalone chains, cross-chain dispatch with
cref resolution, unresolved-cref rejection, and missing-kernel detection.
All pass.
Collapses the Go-side cgo entry points to a single canonical path:
ExecuteBlock(backend, threads, txs, ctx, state)
└─> C.gpu_execute_block_v4(...)
ExecuteBlockV1(backend, txs) -> ExecuteBlock + downcast
ExecuteBlockV2(backend, threads, txs) -> ExecuteBlock(nil, nil)
ExecuteBlockV3(backend, threads, txs, ctx) -> ExecuteBlock(ctx, nil)
ExecuteBlockV4 = ExecuteBlock (rename)
No more parallel C cgo calls. The luxcpp-side gpu_execute_block / _v2 / _v3
entries are now dead and can be removed in the next luxcpp release.
Health probe: the `callBridge` exception now applies on every backend
instead of GPU-only. The old V2 path masked kernel status with TxOK; the
canonical path surfaces the real CallNotSupported. Until the V5 kernel
implements CALL/CREATE on device, the probe accepts any clean return as
"dispatcher reached the bridge" — truth, not a mask.
Per "kill this version proliferation and have one and one way only":
- ExecuteBlock(backend, threads, txs, ctx, state) is the single canonical
Go-side entry. Takes everything, returns everything.
- ExecuteBlockV1/V2/V3/V4 are marked Deprecated and retained as test-file
compatibility shims pointing at the same underlying C ABI. New code MUST
use ExecuteBlock.
- chains/evm/cevm/parallel/parallel.go switched to ExecuteBlock. parallel
test suite still green.
The C-side ABI sprawl (gpu_execute_block / _v2 / _v3 / _v4) stays for now —
deleting them is a luxcpp/cevm refactor that ripples through internal
dispatch. The Go surface that consumers see is single-entry today; the C
cleanup is the next bounded follow-up in luxcpp.
CALL/CREATE on device — the "100% GPU EVM" gap — remains kernel-side work
in luxcpp/cevm/lib/evm/gpu/kernel/. The host-side scaffold (call_frame.hpp,
code_resolver.cpp, evmc_bridge.cpp, the GpuHostBridge type) is real but
the kernel CALL handler still emits TxCallNotSupported. That's the next
multi-week kernel push, not a Go-side change.
Per "no LUX_ in front of shit for no reason" — the strict-mode knob is
cevm-specific, not Lux-specific.
LUX_CEVM_STRICT → CEVM_STRICT
Default unchanged (strict=on). Docs updated.
Removes the silent (nil, nil) "fall through to Go EVM" return at the three
points where cevm V4 cannot complete a block on device:
- tx value exceeds uint64
- kernel returns TxCallNotSupported (CALL/CREATE family)
- block has logs (V4 ABI has no per-tx logs)
Replaces each with declineBlock(reason, block, tx_index):
* Default (LUX_CEVM_STRICT=1): returns ErrGPUEVMRequired so the caller
cannot silently shadow-execute on the Go EVM. The lie that hid the
missing GPU CALL/CREATE is removed.
* Legacy (LUX_CEVM_STRICT=0): returns (nil, nil) for V4→V5 transition
rollback only.
Every fallback records backend.RecordFallback(FallbackUnsupported, "cevm:<reason>")
so production observability surfaces the rate at which blocks decline.
V5 kernel spec lands at evm/cevm/V5_ABI.md: gpu_execute_block_v5 with
code-table, storage_root threading, per-tx logs. Strict mode becomes
unconditional once V5 ships and the env var retires.
The user's verbatim demand:
> "fix ALL of a-chain so it works in MULTI-CHAIN for any Lux public L1 L2
> we must work without any trusted daler or other"
WHAT CHANGED
DefaultConfig (PUBLIC-BFT-SAFE DEFAULTS):
- VerificationMode: ModeOptimistic (default) — providers post results
with bond; N-block challenge period; uncontested = finalised;
challenged = fraud-proof flow with slashing. NO trusted dealer,
NO TEE assumption, NO single point of trust.
- RequireTEEAttestation: false (was true) — TEE is OPTIONAL
acceleration, NOT a registration gate. Providers without TEE can
register and operate under the optimistic-verification flow.
- HostChainID: "primary" — AIVM is chain-agnostic. Override per
L1/L2 instance (Hanzo, Zoo, Pars, Liquidity, etc.).
- ChallengeWindowBlocks: 100 (~5-10 min finality)
- RedundancyFactor: 3 (for ModeMultiPartyRedundant — M-of-N consensus)
- MinProviderBond: 1000 LUX equivalent (slashed on fraud proof or
majority divergence)
DefaultPermissionedConfig (OPT-IN, NOT public mainnet):
- VerificationMode: ModeTEEAttested
- RequireTEEAttestation: true
- For regulated/KYC'd subnets where TEE-vendor trust is acceptable
by policy
THREE VERIFICATION MODES
1. ModeOptimistic (default, public-BFT): post + challenge + fraud proof.
2. ModeMultiPartyRedundant: M-of-N validators run same task, consensus
on output hash. Public-BFT-safe. M× compute cost.
3. ModeTEEAttested: opt-in, permissioned subnets only. TEE is folded
into the trust score as ACCELERATION (skip challenge for high
trust score); NEVER changes the trust root.
RegisterProvider:
- Public-chain path: no TEE required. Provider bond + challenge flow
enforce correctness.
- Permissioned path (RequireTEEAttestation=true): TEE attestation
required, must meet MinTrustScore. Error message points operators
at the public-chain alternative.
MULTI-CHAIN DEPLOYABILITY
- HostChainID tells AIVM which L1/L2 it's running on.
- "primary" = anchor to Q-Chain via MerkleAnchorFreq blocks.
- White-label L1/L2 = anchor to host chain's own finality (Pulsar
for PQ-finality, Corona for Corona-finality, etc.).
- AIVM code is identical across all chains; only the HostChainID
config field differs.
TESTS
- TestDefaultConfig pins public-BFT-safe defaults: TEE NOT required,
mode is Optimistic, HostChainID is "primary".
- TestDefaultPermissionedConfig pins the opt-in permissioned path.
- TestVerificationMode_PublicBFTSafe asserts default mode is always
one of (Optimistic, MultiPartyRedundant), NEVER TEE-attested as
the trust root.
go.mod refresh as side-effect of adding luxfi/node@latest for
vms/types/fee.
graphvm: G-Chain is read-only (graphql.go explicitly rejects `mutation`
operations). Wires NoUserTxPolicy{} so any attempted user-tx is refused
at the boundary. Field + Initialize gate added; fee.Validate ensures
the policy choice is sound at boot.
oraclevm + relayvm: these chains are thin re-export shims of the
canonical impls in luxfi/oracle/vm and luxfi/relay/vm (which already
have feegate.go). Added in-file pointers documenting where the gate
lives, so future readers don't add a duplicate stub here.
Closes the FeePolicy migration: every VM in luxfi/chains/ either has
its own feegate.go (8 VMs), points to the canonical impl (oraclevm +
relayvm), or is exempt via native gas (evm). The matrix at
node/CLAUDE.md FeePolicy section is now complete.
Replace the hand-rolled ${SRCDIR}/../../../../luxcpp/... CFLAGS+LDFLAGS
blocks in cevm_cgo.go and cevm_secp256k1.go with the canonical .pc
bundles. External Go consumers building from \$GOMODCACHE no longer
need a luxcpp checkout next to lux/; they only need the install prefix
on \$PKG_CONFIG_PATH.
cevm_cgo.go consumes lux-cevm (libevm + libevm-gpu + transitive
GPU-host static archives).
cevm_secp256k1.go consumes lux-crypto-secp256k1 (libsecp256k1_cpu +
libkeccak_cpu). libsecp256k1_metal stays on the build-tree path with
the existing darwin-only anchor file until a separate .pc bundle for
the Metal driver lands.
M-Chain is committee-driven — keygen and signing requests come from
validators via consensus, not a user mempool. Attach the
fee.NoUserTxPolicy sentinel in Initialize so the boot-time
fee.Validate explicitly approves the no-user-tx posture and any
caller that reaches the gate gets ErrChainAcceptsNoUserTxs.