Commit Graph
379 Commits
Author SHA1 Message Date
zeekayandHanzo Dev 2815437287 A library whose CGpuTx is not this build's speaks no ABI the binding reads
Two version 7 libraries lay a tx out differently: one built before CGpuTx
carried the fee cap and tip has a 112-byte tx and no gpu_abi_tx_size. init
compares the library's gpu_abi_tx_size with its own sizeof(CGpuTx) and, on
a difference, declines every block as it declines a library of another
ABI; a library without the symbol does not link. The tip needs no width
check of its own: it is at most the fee cap, which has one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-23 14:33:46 -07:00
zeekayandHanzo Dev 1d08088efd The binding sends a tx's fee cap and tip, as ABI 7's CGpuTx carries them
go_bridge.h's CGpuTx has max_fee_per_gas and max_priority_fee_per_gas in
place of gas_price, 120 bytes on LP64. cevm.Transaction has GasFeeCap and
GasTipCap for them, both the gas price of a legacy or access-list tx, and
buildTxs sets both; the size check reads 120. cevm charges min(cap, base
fee + tip), as the EVM does, where it charged the cap whole, and declines
a tip above the cap.

The executor shapes tx.GasFeeCap() and tx.GasTipCap(), each held to 64
bits. The device test sends a transfer with its tip above its cap, which
the device declines.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-23 13:45:07 -07:00
zeekayandHanzo Dev ace7fa1e3f A CUDA lane's device is a GPU node the process sees, not only the driver
/dev/nvidiactl says the NVIDIA driver is loaded. A container can be given it
without a GPU, and the lane then declines on a host the tests took for one
with the device. hasDevice now also needs a /dev/nvidiaN node. A failed run
on a device host names what a decline there usually is, since the ABI
carries no reason.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-23 11:52:25 -07:00
zeekayandHanzo Dev 0f1f84e881 A GPU lane's test runs where the host has its device, and fails if it declines there
The tests took a GPU lane that declined the transfers for a lane without a
device and skipped it. A device that is there declines too, when its
shader is missing or its path regresses, and passed for an absent one.
hasDevice now says whether the host has the lane's device: the GPU every
Apple silicon Mac has for Metal, and an NVIDIA driver's control device for
CUDA. A lane without it is skipped; on a lane with it, the transfers must
run, as the evmgpu device test already requires.

The docs say a library of another ABI is never sent a block, rather than
never called: it still answers the backend queries.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-23 11:50:13 -07:00
zeekayandHanzo Dev 10f6937c1f The executor sends the header's difficulty as PREVRANDAO
It sent the MixDigest, which is zero on every Lux header. luxfi/evm's
NewEVMBlockContext answers 0x44 with the difficulty: from Shanghai on as
Random, and before it as DIFFICULTY, which is the same number. The block
context now carries the difficulty as a 32-byte word, and
TestPrevrandaoIsWhatTheGoEVMAnswers holds it to what NewEVMBlockContext
gives for the same header, before Shanghai and after.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-23 11:28:07 -07:00
zeekayandHanzo Dev 47028b9f41 A lane that runs no block is said once at start, at info
The start-up report warned about every CPU lane on every start: cevm's Go
entry passes no host, so a CPU lane declines the health battery, and a
build with no library linked runs nothing at all. Those lanes are what they
are, not something that went wrong, so they are named together in one info
line; a GPU lane that does not run the battery is still a warning of its
own. A loaded library of another ABI is one warning, and no lane is probed.

The report tests move out of backend_cgo_test.go, which the native build
failed: it expected ErrNotLinked from a build that links the library.
cevmreport_test.go runs in every build and holds the start-up report to
warn only about a GPU lane.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-23 11:28:01 -07:00
zeekayandHanzo Dev acd68484d7 The cevm binding reads ABI 7, and declines a library of another
ABIVersion is 7, the go_bridge.h ABI in which ok says whether the result is
the block's. The binding does not compile against a header that names
another, and init reads the loaded library's gpu_abi_version() instead of
panicking on it: against a library of another ABI, ExecuteBlock calls
nothing and declines every block, and a result of another ABI is declined
too, so the caller runs them on its own EVM.

The native tests hold the library to that contract. A block of funded plain
transfers runs on a GPU lane to TxOK at 21000 gas each, or is declined there
when the host has no device, and a CPU lane declines it; a batch with code
or calldata is declined on every lane. The opcode, bytecode and gas-parity
tests ran code through the entry, which runs none, and are gone; the
block-context and sustained-Metal tests run transfers. A test that needs a
device skips on a GPU lane that declines. The tests that pin the build with
no library linked moved to cevm_nolib_test.go, outside the native build,
where they failed.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-23 11:27:54 -07:00
zeekayandHanzo Dev 596e18dfe1 The executor asks cevm nothing on a CPU lane, and Health tells an estimate apart
cevm's Go entry runs no block on a CPU lane: it passes no host, and a CPU
lane runs no plain transfer without one. A library that answers there
anyway answers a gas estimate, the tx's limit, which a transfer limited to
21000 passes off as a result; the executor now declines the block on a CPU
lane without asking. The Health probe's limit is 30000, so a lane that
answers the limit instead of running the transfer is not reported healthy.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-23 09:52:43 -07:00
zeekayandHanzo Dev 10522343a0 The executor receipts only cevm's one answer, and Health asks for it
cevm answers ok=1 only when every tx is a valid plain transfer that
succeeded on its 21000 intrinsic gas (gpu_dispatch.cpp ran_through), so
assemble accepts that and declines anything else: CallNotSupported, a
revert, a gas estimate at the limit. The same check holds a library that
answers ok=1 on other terms — main's still does, at the same ABI number — to
the one answer the executor can receipt. A block with a creation, calldata
or a recipient with code is declined before the snapshot and the cgo call,
which could only end in a decline.

BLOBBASEFEE is the fee the excess blob gas prices under the chain's blob
schedule, not the excess. Declines count as FallbackBackendUnavailable, the
linked backend refusing, rather than as a missing device.

Health ran five bytecode probes, all of which the entry declines, so every
lane logged unhealthy at start. It runs one funded plain transfer now: a GPU
lane is healthy when it answers TxOK at 21000, and a CPU lane says it
declined, which is all this entry does on a CPU.

The package doc no longer shows how to register the executor: it writes no
state, so a node that registered it would finalize a block on its parent's
state.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-23 09:47:47 -07:00
zeekayandHanzo Dev 6223789846 The cevm executor runs what cevm declines on the Go EVM
cevm's Go entry is gpu_execute_block (go_bridge.h, ABI 6); the
gpu_execute_block_v4 and gpu_free_result_v2 this package called are gone
from the library, so the native build did not link. The entry answers ok=0
for any block it does not run — any batch with code, and any refusal on
balance, nonce, price, block gas limit, revision or hashes — and that block
is the caller's. ExecuteBlock returns ErrDeclined for it, with no result,
and the executor declines the block to the Go EVM: (nil, nil), counted. A
decline is never an error, so CEVM_STRICT and ErrGPUEVMRequired are gone;
the state processor fell through on that error anyway.

Nothing wider than the 64-bit wire reaches cevm. A gas price or fee cap
above 2^64-1 crossed as 0; it, a value, a base fee or a chain id that does
not fit now declines the block, as does a tx the wire drops part of: an
access list, a blob or set-code tx, a tip above its fee cap.

Snapshot rows carry what the header asks for: keccak256 of nothing and the
empty trie root for an account that does not exist — the StateDB answers
zero, which cevm reads as saying nothing and declines — the real storage
root otherwise, and a row for the coinbase. cevm_cgo.go checks the four C
structs' sizes at compile time, so a header that grows a field cannot have
it cross as zero unnoticed.

The call into cevm is a field on the Executor, so the tests show a declined
block going to the Go EVM and a value of 2^64 never reaching cevm without
the library.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-23 09:22:10 -07:00
zeekayandHanzo Dev c1e4934ab7 The plugin says what it linked; it does not choose an EVM
luxfi/evm dropped in-process backend switching (2f9a3158): choosing an EVM
is choosing which plugin binary the host runs, and the switch it replaced
never ran a block — its executor returned nil, nil and its tagged build did
not compile. The callers stayed here, so this package no longer built
against evm at all: parallel.SetBackend, AutoEVM, GoEVM, ActiveBackend and
AvailableBackends are gone.

What is left is worth saying out loud, so one file reports it: the C++
library's ABI, the backends it offers, and the health battery each answered.
The cevm package carries its own build tags, so the cgo/nocgo split in the
caller went with them.

Two tests went too. They asserted that selection left a runnable lane
active and that it was idempotent, and there is no selection to be
idempotent about; that a build reports what it cannot execute is still
covered. The package went from listing no tests at all — it did not compile
— to running ten.

Co-authored-by: Hanzo Dev <[email protected]>
v1.7.45
2026-09-17 20:56:19 -07:00
zeekayandHanzo Dev f4d3133bb7 Retract v1.7.7
It does not name one body of code: the proxy and the checksum database hold
what it was published with and always will, GitHub serves something else,
and go.sum files in this org have held a third. Builds through the proxy and
builds that go direct disagree, and the direct ones only look fine because
GOPRIVATE skips the checksum database — a checksum SECURITY ERROR waiting
for the first consumer who builds without it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-17 20:56:19 -07:00
zeekayandHanzo Dev 5532c64501 Build on Go 1.27
Build and vet clean on 1.27.1.

Co-authored-by: Hanzo Dev <[email protected]>
v1.7.44
2026-09-17 14:22:13 -07:00
zeekayandHanzo Dev 2a95c62551 Workflows ask for the runner label that exists
One label per runner, spelled <os>-<arch>. The aliases these jobs named
were removed, and a job naming a label no runner carries waits instead of
failing.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-16 01:54:37 -07:00
zeekayandHanzo Dev c1c29c1e81 go 1.26.8
One toolchain across the estate. The tree carried 1.26.0, .1, .3, .4 and .5
at once, so which compiler built a binary depended on which repo it came
from.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 23:59:02 -07:00
zeekayandHanzo Dev 654f9c4a87 chain letters are lowercase
The primary-network chains answer to either case; one spelling beats two.
/v1/chain/c/rpc, /v1/chain/p, /v1/chain/x across the estate.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 15:21:35 -07:00
zeekayandHanzo Dev f13260c45d ci: a runner label names the platform, not the org
hanzo-build-linux-amd64, lux-build-linux-amd64, zoo-build-linux-amd64 and
every other org-prefixed spelling all meant the same machine. Which org a
job belongs to does not change which host can build it, so the org bought
one pool name per org per arch and each could go stale on its own -- the
evo host alone answered to fourteen names for one label.

One vocabulary now: linux-amd64, linux-arm64, macos-arm64, win-amd64. The
generic labels every plain workflow uses (ubuntu-latest, self-hosted) are
untouched, so naive CI keeps working.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 12:08:06 -07:00
zeekay a0e796b1fe bridgevm: the network declares its bridge limits, not the operator
The caps decide which transfers a block may carry — admissible() refuses a
transfer over the per-transfer cap and admit() refuses one over the daily cap,
both inside block verification. Reading them from the operator's chain config
meant two validators could hold different numbers and disagree about whether a
transfer was admissible, and the first one they disagreed about is a transfer
the builder keeps proposing and everyone else keeps refusing. A cap each
operator sets for themselves is not a cap.

Limits (minConfirmations, the two spend caps, the signer bond, the set size)
move to Genesis, where the network declares them once and every validator reads
the same numbers. BridgeConfig keeps only ExternalChains — endpoints and KMS
paths, how THIS node reaches the chains it was told to bridge, which cannot
change which transfers are valid. The dead BridgeFee field goes.

MaxSigners keeps its default: it is a capacity, not a risk decision.
v1.7.43
2026-09-07 00:06:37 -07:00
zeekay 6a9888847c B/M: a security crossing is its own act
M threshold-signs a security crossing the way it signs a fungible
transfer, and B carries the attestation to the destination register.
Same leaderless ceremony, same authorisation, same group key travelling
with the result so B verifies without asking M anything. What differs is
the message, and therefore the digest.

What B does here is narrower than what it does for a fungible asset. The
fungible path releases by minting: a confirmed lock authorises N units
to an account and nothing else has to be true. Whether a SECURITY may
exist on the destination at all is the destination register's question —
is this holder verified, do their claims satisfy the token, does a
holding period follow the paper across — and the token asks it in the
block that lands the position. B never decides a security may land. It
decides the attestation is genuine and says what it claims to say.

SecurityRequest is separate from BridgeRequest rather than a flag on it.
The two are released by different calls onto the destination, and a mint
against a register nobody asked is exactly the failure this path exists
to prevent; one type that could be either leaves that difference to a
branch somebody has to remember to write.

Two fields the destination cannot look up for itself travel with the
crossing — the holder's ONCHAINID on the source register, and when they
first received there. Without the second, an arriving position anchors
its clock to now, and a holding period a crossing resets is one a
crossing washes off.

A short identity is refused rather than read as an absence: treating it
as absent lets a truncated field pass for "this holder had none". An
absent one travels as zero, which is honest.

Quantity is a uint256. A billion shares at eighteen decimals is an
ordinary size for a register and does not fit the uint64 the fungible
path carries; a truncated quantity is a position that arrives smaller
than it left, so it is refused rather than truncated.

Signer ids convert to strings at the boundary so a verifier on the far
side needs nothing of M's type graph. The crossing type itself is
aliased from internal/bridgeattest rather than copied — attest_bridge.go
keeps its own copy of BridgeTransfer, and two definitions kept in step
by hand is a way for a digest to drift across a boundary whose whole
value is that it cannot.
v1.7.42
2026-09-06 12:30:50 -07:00
zeekay e081ccef5f bridgeattest: the attested message for a security crossing
A fungible transfer says move N units of an asset to an account. A
security crossing cannot be said that way: the destination has to run
the register before the position exists there — whether the recipient is
verified, what act this is (an arrival is not a mint), and whose clock
the position brings with it.

None of that fits BridgeTransfer, whose layout is frozen and whose
Amount is a uint64 — too small for an eighteen-decimal position above
about 18.4 tokens. So SecurityCrossing is a sibling message with its own
domain tag rather than a widening of that one. The frozen digest stays
frozen, and a fungible attestation can never be replayed as a security
one because the tag they commit to differs in the first bytes of the
preimage. That property is asserted, not assumed.

Two fields carry what the destination cannot ask the source for:

  Identity  the holder's ONCHAINID on the SOURCE register. The register's
            clocks belong to the person, not the key, and the
            destination has no way to look it up.
  Acquired  when the holder first received on the source. Without it an
            arriving position anchors to now, and a holding period a
            crossing resets is a holding period a crossing washes off.

Both are zero where the source had none — an honest absence the
destination weighs as it likes, rather than a default that reads as an
identity of zero.

Quantity is 32 bytes big-endian for the full uint256.

VerifyAgainst takes the expected group key as an argument, following the
fix already recorded on the fungible verifier: reading the key off the
struct being verified answers "was this signed by whoever signed it",
which an attacker's own key satisfies. Asserted here too.

Every field is bound — each of the seven, mutated one at a time, stops
the signature verifying.
2026-09-06 12:30:49 -07:00
zeekayandHanzo Dev 77fa2901e2 Take the fee types from their permanent home, and let go of the node
luxfi/node is being retired. Six of the seven packages chains once reached
into it for already come from their new homes; the fee types were the last,
and they were held here only by the oraclevm and relayvm feegate tests.
Those two assert on the policy value that luxfi/oracle and luxfi/relay hand
back, so they could not move before those modules did. oracle v1.1.3 and
relay v1.1.2 build against luxfi/vm/vms/types/fee, so the tests now compare
against the same type the VM returns rather than a look-alike from the node.

With that, no source or requirement in chains names luxfi/node.

Co-authored-by: Hanzo Dev <[email protected]>
v1.7.41
2026-09-06 03:48:46 -07:00
zeekay 2f6257af69 build the plugin images on both native pools
Dockerfile.plugin compiles nothing: it copies build/<vm>, the binary the
step above it produces with `go build` on the runner, so the architecture
of the image is the architecture of the machine that ran that build. Asking
one amd64 runner for two platforms would stamp an arm64 manifest onto an
amd64 ELF — an image that pulls on an arm64 host and dies with `exec format
error`, which is worse than the amd64-only manifest it replaces, because
the manifest promises it will run.

One job per architecture compiles natively (evm's CGO included, no
cross-toolchain, no emulation) and pushes <image>:sha-<sha7>-<arch>. A
merge job joins the pair under the release names and refuses to be green
unless every one of them carries both platforms.
2026-09-05 19:08:03 -07:00
zeekayandHanzo Dev 0d4846533e build the VMs without the daemon in their closure
The plugin-SDK surface the VMs programmed against lived in the daemon:
the admission fee policy, the VM factory alias, version, and artifacts.
Twelve VMs now take it from chains/fee, chains/artifacts, luxfi/vm and
luxfi/version instead, so a plugin no longer drags the daemon it runs
under. oraclevm and relayvm still do, through luxfi/oracle and
luxfi/relay; they clear when those two are cut.

go build/vet/test exit 0, 37 packages ok. go list -deps reports zero
daemon packages for all twelve, confirmed against the built binaries.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-04 01:34:11 -07:00
zeekay a93f6e3dec build the VMs without the daemon in their closure
A plugin is its own binary; the daemon that loads it has no business being
linked into it. Twelve VMs reached into github.com/luxfi/node for four things,
and each one now programs against a home that is actually its own:

- fee admission (Policy, FlatPolicy, NoUserTxPolicy, Validate) joins the
  settlement half already in chains/fee. One fee package, and the nodefee alias
  that marked the split is gone.
- the cross-chain artifact types move to chains/artifacts.
- the factory the daemon registers is luxfi/vm/manager.Factory, and the version
  types are luxfi/version. Both are ordinary modules the daemon reads too, so a
  VM imports them directly rather than through anything of ours.

The GPU switch is deleted, not moved. It was a global set during daemon startup
that a separate process could never observe, so every plugin read its default;
FHEOptions.Backend is the switch that was really there.

go list -deps names no luxfi/node for aivm, bridgevm, dexvm, evm, fhevm,
graphvm, identityvm, keyvm, mpcvm, quantumvm, schain or zkvm, and the built
plugin binaries carry no such string. oraclevm and relayvm still do: they are
re-export shims over luxfi/oracle and luxfi/relay, which build their fee
policies upstream. Their tests keep naming the daemon's fee package because
that is the type their value has.
2026-09-03 21:10:40 -07:00
zeekayandHanzo Dev 50c64a1068 Ask every endpoint what chain it is on, and which chain that id names
Ported onto the current tree from work stranded on a copy a hundred commits
behind it. The dial-time check asked endpoints[0] for its chain id and trusted
the rest; every endpoint answers for itself now, and one that disagrees with the
configuration or cannot answer refuses the client by name.

A chain id is not an identity: a fork inherits the id of the chain it left, so a
forked endpoint answers eth_chainId correctly and the id check cannot see it.
ExternalChainConfig gains Genesis, the hash of block 0, and when it is set every
endpoint must produce it — the same commitment platformvm's adopt record calls
Identity. A malformed hash is refused at startup rather than folded into one
that can never match. Left unset, the question is unanswered and startup says
so.

The confirmation read stays the least advanced answer of every endpoint, as it
already was here; the port re-applies it over the older file it arrived in.
The wrong-network test now asserts the claim in the refusal rather than the
words around it, since the refusal names the endpoint.

(cherry picked from commit 678800144052032009a58472b61873aa4b20a52c)

Co-authored-by: Hanzo Dev <[email protected]>
v1.7.40
2026-09-03 21:02:46 -07:00
zeekayandHanzo Dev 27672d9e4d An unobserved lock releases nothing
releaseOnce checked the source lock's depth only when it happened to have a
client for the source chain and the request happened to name a transaction:
`if src != nil && req.SourceTxID != ids.Empty`. Both of those arrive in the
event data a request is built from, so naming a chain this node has no client
for, or naming no transaction at all, skipped the check and went on to request
the custody threshold signature — the one that authorizes the mint at the
destination gateway. A caller could choose to be unchecked. Both cases now
refuse before anything reaches M.

What the check establishes is narrower than it reads, and now says so: it takes
the receipt's status and block number, never decodes the lock event, and
compares nothing to this transfer. It shows the named transaction succeeded and
is buried, not that it is this transfer's lock.

The depth itself came from endpoints[0]. Verifying every endpoint at dial time
and then trusting one of them afterwards buys nothing for the decision that
gates the release: one endpoint could present a lock 1 block deep as 101. It is
now the least advanced answer of every endpoint, so an endpoint can hold a
release back and none can manufacture one — and a client with no endpoints
refuses rather than returning the seed the loop starts from, which is the
maximum and would clear any minimum.

(cherry picked from commit 91230c4d48dd9c8ac01e24301163573d0da6f315)

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-03 21:02:45 -07:00
zeekayandHanzo Dev 9a568c2bbb feat(fhevm): add plugin entrypoint for binary plugin build
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-03 13:21:35 -07:00
zeekayandHanzo Dev 0d9fec309a chore: symlink AGENTS.md + GEMINI.md + CLAUDE.md -> LLM.md
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 13:42:24 -07:00
zeekayandHanzo Dev 17bdadba30 chain routes: /v1/bc -> /v1/chain
luxd serves chain routes under /v1/chain, and the node, cli and sdk releases
already ship it. The deployed gateways still answer the old prefix, so remote
reads break until they are redeployed; the estate moves together rather than
sitting half converted.

Lines that record an observation - a probe, a status code, a past release -
keep the prefix they were written against, as do URLs on hosts that are not
ours. Avalanche endpoints move to /ext/bc, which is the path that host serves.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 08:19:10 -07:00
zeekay 4aa7898664 Include string.h once v1.7.39 2026-09-01 02:38:03 -07:00
zeekay b00b12408a Ask for the plugin directory once 2026-09-01 02:10:25 -07:00
zeekay c49dae1bb9 Close the library mpcvm just refused to trust 2026-09-01 02:03:02 -07:00
zeekay 56e81b4132 Retract v1.7.37, which supersedes a release it does not contain 2026-09-01 01:55:11 -07:00
zeekay b1b67ee46c Join the v1.7.37 line, whose one unique fix is already here
v1.7.37 branched from 964b6b0, which is BEFORE v1.7.36, so it carries a higher
number than a release it does not contain. Three of its four commits are
accounted for: the hero-card change is here by patch-id as 60b7b3e, the codec
fix as af7b1dc with internal/codec byte-identical, and the GPU plugin identity
work is cherry-picked in 114440f — the one thing on that line no other line
had.

The tree is not taken. Its go.mod retracts v1.7.36 for serving zkvm's routes
flat; this line serves them the same way, so taking that directive would
retract one release for what its successors also do. The route shape is left
for whoever owns it.
2026-09-01 01:53:40 -07:00
zeekay a7f17a965f Join the parallel line into this history
luxfi/chains was developed twice from the same starting point: two roots with
the same subject and date, no commit in common, 41 commits on one side and 288
on the other. This records that the two are one project again.

The tree is this line's. Eighteen of the parallel line's commits are already
here by patch-id and seventeen more by an exact-subject twin the re-creation
re-hashed. What remains unique to it is one comment-only commit, applied in
f849e8f.

Its tree is not taken. The files it holds and this line does not were deleted
here on purpose and with reasons: the D-Chain proxy that answered nothing
(e4d04be, which added an AST check against its return), the vestigial BLS
custody subsystem (b0cc6d8), thresholdvm after the rename to mpcvm (51a7797).
Merging its tree would undo four deliberate removals and trip the check the
first one installed.
2026-09-01 01:53:01 -07:00
zeekayandHanzo Dev d7199487d2 Build the stub plugins the loader is now allowed to trust
The three GPU suites compiled a library exporting launcher names and nothing
else, which is exactly what the identity check refuses, so eighteen tests
failed against a loader that had started doing its job. The stubs now export
lux_gpu_backend_init answering with this build's ABI version and vtable size,
compiled against the real header so an ABI bump moves them with it.

Three assertions pinned the behaviour that was removed — aivm expecting the
bare filename, mpcvm the bare leaves, bridgevm the working directory — and now
pin its absence.

TestALibraryThatOnlyExportsTheNames is new in each package: it plants the
unidentified build, watches the loader refuse it, then opens the identified
build from the same place so the refusal is the identity check rather than a
stub that never worked.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 01:52:39 -07:00
zeekay c0d4022c71 Cite the specs by the numbers they carry
LP-134 was renumbered LP-1340 (lp-1340-lux-chain-topology); 21 citations
across 16 files still named the old number, which resolves to nothing.

Corona rests on Module-LWE, not Ring-LWE — LP-4805 says so in three places
and b17e086 already corrected one file, leaving evm/main.go and
schain/SECURITY_review.md claiming the wrong hardness assumption. The
two-family argument in LP-4900 depends on which lattice problem this is.

P3Q at 0x012205 is kind-byte signature dispatch; STARK/FRI is starkfri at
0x012220 (LP-7050). Naming P3Q for a scheme at another slot describes a
precompile that is not the one being imported.

Carries the substance of 2f22fa2, the one commit the parallel line held that
this history did not.
2026-09-01 01:40:34 -07:00
zeekayandHanzo Dev 6aa5b77a44 Identify a GPU plugin before trusting it, and load only from a named place
A GPU plugin replaces a VM's state transitions wholesale. Three of the four
seams reached one by dlsym'ing launcher names out of a library nothing had
identified, and all four would fall back to a bare filename — which makes the
dynamic loader's default search path, and in bridgevm's case the working
directory of whoever started the node, the thing that decides which code
computes consensus state.

A library exporting the five bridgevm launcher names and nothing else,
compiled from twenty lines of C that return 0:

  before, LUX_GPU_PLUGIN_DIR=/tmp/rogue   AutoBackend = hip
  before, no env, cwd holds the .so       AutoBackend = hip
  after,  either                          AutoBackend = none

The plugin header says a loader must check abi_version and vtbl_size. aivm,
bridgevm and mpcvm now do so before resolving a single launcher: a name was
the only thing being matched, and a name is not a contract.

Bare filenames and the CWD entry are gone. What is left is LUX_GPU_PLUGIN_DIR
and LUXCPP_PREFIX — places an operator named. Unset means no plugin, which is
the configuration every box without a GPU is already in.

Cherry-picked from 634df5e, whose quantumvm hunk is dropped: that seam's file
was deleted in 409d0e4 and the chain no longer loads a plugin.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 01:36:45 -07:00
zeekay 1eb35500ef Merge the tip and Seed fixes into the current line
chain/store.go gains ErrNotOnTip, so Accept re-checks that the block still
extends the accepted tip under the same lock that commits it, and Seed records
the tip it wrote. Open already refuses to read any error as a fresh chain;
a Seed that left no tip was the remaining way to look fresh after writing.

Carries the zkvm, identityvm and graphvm work the fixes were built on.
2026-09-01 01:32:34 -07:00
zeekayandHanzo Dev a5edffeba2 Restore the chain prefix the running nodes answer
The rename to /v1/chain is real in luxd source, but the deployed fleet has not
taken it yet: probed just now, api.lux.network, api.lux-test.network,
api.zoo.network and api.hanzo.network each answer 200 on /v1/bc/C/rpc and 404 on
/v1/chain/C/rpc. Composing /v1/chain against those hosts reaches nothing, which
is the failure the rename set out to prevent.

The move belongs with the node redeploy, not ahead of it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-30 21:21:32 -07:00
zeekayandHanzo Dev 6c2b79d070 chain routes: /v1/bc -> /v1/chain
luxd serves chain routes under /v1/chain; the /v1/bc prefix it replaced is
gone, so anything composing it reaches nothing. Lines that record an
observation - a probe, a status code, a past release - keep the prefix they
were written against, as do URLs on hosts that are not ours.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-30 20:42:54 -07:00
zeekay cce073153d docs: chain routes are /v1/chain, health probes are /v1/health/ops
luxd serves chain APIs under one segment from constants.ChainAliasPrefix
(constants v1.6.4, node v1.36.181), and the health service moved onto typed
ops. Published addresses now match what a node answers on:

  /v1/bc/<alias>/...    -> /v1/chain/<alias>/...
  /ext/bc/<alias>/...   -> /v1/chain/<alias>/...
  /v1/health            -> /v1/health/ops/health     (full report)
  /v1/health/liveness   -> /v1/health/ops/liveness
  /v1/health/readiness  -> /v1/health/ops/readiness

Observations keep their original addresses: a dated note or a recorded status
code is a fact about when it was written, not an instruction to follow.
2026-08-30 17:31:37 -07:00
zeekay 51430e25c1 Name Zen in the hero card's font stack
The social card asked for Inter first. That is not a face this estate
licenses, ships or ever drew, so a renderer with Inter installed painted
the wrong face and one without it silently fell through to system-ui.
Zen leads now; the same tail follows it.
v1.7.38
2026-08-30 15:47:44 -07:00
zeekayandHanzo Dev b11515d169 chains: retract v1.7.36, whose zkvm routes answer 404
v1.7.36 serves the zkvm handlers one per flat route, so a caller of /rpc,
/privacy or /proof receives 404. Node's TestChainHandlerServesItsOwnPaths
names /rpc/getStatus and fails against it, and passes against v1.7.33,
against v1.7.35 and against this line.

The tag stays where it is. retract says it was published in error without
rewriting history.

Co-authored-by: Hanzo Dev <[email protected]>
v1.7.37
2026-08-30 15:16:24 -07:00
zeekayandHanzo Dev 0ac784c33a codec, quantumvm: a lower-case name on the wire reaches an exported Go method
quantumvm registered node's JSON-RPC codec — the one that raises the initial
of the function named after the dot. That codec is now internal/codec, so the
spelling quantumvm serves, quantumvm.getBlock for Service.GetBlock, is decided
in this module.

The spelling is unchanged: an initial that is already upper-case is refused,
and an argument that will not read reports one message. identityvm, keyvm and
bridgevm register gorilla's plain codec and keep it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-30 14:54:14 -07:00
zeekayandHanzo Dev 8f0ec4b5fc codec, quantumvm: a lower-case name on the wire reaches an exported Go method
quantumvm registered node's JSON-RPC codec — the one that raises the initial
of the function named after the dot. That codec is now internal/codec, so the
spelling quantumvm serves, quantumvm.getBlock for Service.GetBlock, is decided
in this module.

The spelling is unchanged: an initial that is already upper-case is refused,
and an argument that will not read reports one message. identityvm, keyvm and
bridgevm register gorilla's plain codec and keep it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-30 14:45:34 -07:00
zeekay 3abc34922f gpu: identify a plugin before trusting it, and load only from a named place
A GPU plugin replaces a VM's state transitions wholesale. Three of the four
seams reached one by dlsym'ing launcher names out of a library nothing had
identified, and all four would fall back to a bare filename — which makes the
dynamic loader's default search path, and in bridgevm's case the working
directory of whoever started the node, the thing that decides which code
computes consensus state.

That is not theoretical. A library exporting the five bridgevm launcher names
and nothing else, compiled from twenty lines of C that return 0:

  before, LUX_GPU_PLUGIN_DIR=/tmp/rogue   AutoBackend = hip
  before, no env, cwd holds the .so       AutoBackend = hip
  after,  either                          AutoBackend = none

Two changes, neither of them new machinery.

The plugin header says a loader MUST check abi_version and vtbl_size, and
quantumvm already does, through lux_gpu_backend_init. aivm, bridgevm and mpcvm
now do the same before resolving a single launcher: a name is what was being
matched, and a name is not a contract.

Bare filenames and the CWD entry are gone from all four. What is left is
LUX_GPU_PLUGIN_DIR and LUXCPP_PREFIX — places an operator named. No new
environment variable: unset means no plugin, which is already the supported
configuration and the one every box without a GPU is in.

Verified with the real HIP plugin from lux-private/gpu-kernels: aivm, bridgevm,
mpcvm and quantumvm all build, vet and test green, aivm TestRoundTripAttestation
passes through the loaded plugin, and the rogue library is refused.
2026-08-29 19:04:26 -07:00
zeekay 85e238e525 Format what the tip change touched 2026-08-29 13:05:58 -07:00
zeekay e82c509a4e Decide the tip under the lock that commits
Every adopter asked whether its block still extended the tip, released
that reading, and only then asked the store for the lock. The tip moves
in between and the commit lands anyway, writing the height index and the
tip pointer for a block on an abandoned branch -- which every peer
bootstrapping from that index is then served as canonical. The lock is
not reentrant, so the check cannot come from the caller and be held
across the commit: it comes from the store or it is not held at all.

Parent joins Height on the Block interface because a place in the
sequence takes both, and height alone is the reading that lets an old
accepted block pass. Two concurrent accepts fall to the same move, not
just the Verify-to-Accept window: the tip a block is judged against is
now read inside the critical section that commits it, so the second is
refused rather than serialised behind a reading taken before the first
landed. The store also exempts nothing, where identityvm exempted height
zero -- and its Verify skips every parent check at height zero, so a peer
could hand a live chain a second genesis carrying records of its choosing
and send the tip and the height back to the start.

mpcvm keeps its root comparison, which answers a different question --
whether applying this block reaches the root the network agreed on -- and
stays true of a node whose state moved for any reason. That a moved tip
also showed up there is a consequence of that chain refusing empty
blocks, not what the comparison is for.

Seed records the tip in that same commit, because seeding is what makes
a chain no longer fresh. Recording none left Open, for which a missing
tip is a fresh chain, reporting a seeded node fresh on every boot and
asking for the allocation again: a duplicate its own state refuses, and
a node that cannot restart until it produces a block it cannot produce.
2026-08-28 20:57:03 -07:00
zeekay 9aed2192a6 Point the citations at the renumbered specs
Corona is Module-LWE, not Ring-LWE, and 0x012205 verifies the P3Q
threshold-signature family rather than a STARK -- the FRI verifier is a
separate precompile at 0x012220.
2026-08-28 20:24:10 -07:00