mirror of
https://github.com/luxfi/docs.git
synced 2026-09-24 00:25:32 +00:00
docs: describe the three implementations, and how to build and check each
Lux is one protocol realised in Go, Rust and C++, and the docs described only
the Go one — so a reader had no way to tell which implementation a statement
was about, or that two of them are measured against the third rather than
against the spec.
Three new pages, one concern each:
/implementations what exists in each language across the six planes
/build native build recipes for all three
/conformance the corpus, the pinned oracle, the harness and its gaps
Refreshed where the existing pages were wrong or ambiguous:
/zap the version word is 2, not 1; the header rules a decoder must
enforce, including the refusals, which are part of the protocol
/zap/family four layers answer to the name ZAP and two of them share no
bytes; naming them apart is the fix, not a version field
/gpu one C++ implementation with bindings, not three; CUDA is not
cleared for anything entering a state root
/nodes a node with no --http-port listens on 9630, not 9650
Every recipe and every port here was run or read out of the source, and the
one failing conformance check is recorded as failing rather than asserted to
be correct.
Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -7,6 +7,15 @@ description: Multi-layer architecture of Lux Network
|
||||
|
||||
Lux Network employs a multi-layer architecture designed for scalability, security, and flexibility.
|
||||
|
||||
<Callout>
|
||||
**One protocol, three implementations.** The design on this page is realised in
|
||||
Go, Rust and C++. `luxfi/node` (Go) is what runs on every live network and is
|
||||
the answer the other two are measured against. See
|
||||
[Implementations](/implementations) for what exists in each language,
|
||||
[Build from source](/build) for how to build them, and
|
||||
[Conformance](/conformance) for how they are held to one answer.
|
||||
</Callout>
|
||||
|
||||
## Network Layers
|
||||
|
||||
### P-Chain (Platform Chain)
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
---
|
||||
title: Build from source
|
||||
description: Native build recipes for the Go, Rust and C++ implementations
|
||||
---
|
||||
|
||||
Every recipe on this page is the one the repository itself uses. If you only
|
||||
want to *run* a node, take a release instead — [Run Nodes](/nodes) starts there.
|
||||
Build from source when you are changing an implementation or checking one
|
||||
against [the corpus](/conformance).
|
||||
|
||||
<Callout>
|
||||
Toolchain versions below are the ones these recipes are known to work on, not
|
||||
minimums discovered by bisection. Older ones may work; nobody has checked.
|
||||
</Callout>
|
||||
|
||||
| Stack | Toolchain | Build | Test |
|
||||
|---|---|---|---|
|
||||
| Go | Go 1.26 | `./scripts/build.sh` | `go test ./...` |
|
||||
| Rust | rustc 1.97, cargo 1.97 | `cargo build --release` | `cargo test` |
|
||||
| C++ | CMake 3.28, Ninja 1.11, GCC 13 or Clang 18 | `cmake --build build` | `ctest --test-dir build` |
|
||||
|
||||
## Go — `luxd`
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/node
|
||||
cd node
|
||||
./scripts/build.sh
|
||||
./build/luxd --version
|
||||
```
|
||||
|
||||
The build takes its Go experiment flags and its FIPS settings from
|
||||
`scripts/constants.sh`, and the `Makefile` sources the same file. That is
|
||||
deliberate: `make` and the scripts cannot compile different things, and a flag
|
||||
combination that is known to break gets refused in one place rather than
|
||||
producing a binary that fails at startup.
|
||||
|
||||
`make build` is the same build. Profiles trade size for symbols:
|
||||
|
||||
| Profile | Command | Result |
|
||||
|---|---|---|
|
||||
| minimal | `./scripts/build.sh` | ZAP transport, all VMs, stripped |
|
||||
| core | `./scripts/build.sh -p core` | P/X/C only, stripped, plugin-ready |
|
||||
| dev | `./scripts/build.sh -p dev` | all VMs with debug symbols |
|
||||
| tiny | `./scripts/build.sh -p tiny` | all VMs, compressed |
|
||||
| race | `./scripts/build.sh -r` | minimal with the race detector |
|
||||
|
||||
**cgo matters here.** `CGO_ENABLED=1` is the default and selects the assembly
|
||||
BLS implementation; `CGO_ENABLED=0` selects a pure-Go one. They are two
|
||||
different implementations of a consensus-critical primitive, so a suite run
|
||||
with one flag has not exercised the other. The race detector also needs cgo, so
|
||||
`CGO_ENABLED=0` silently disables `-race` as well.
|
||||
|
||||
### Consensus and the EVM on their own
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/consensus && cd consensus
|
||||
go build ./... && go test ./protocol/...
|
||||
```
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/evmgpu && cd evmgpu
|
||||
CGO_ENABLED=0 go build ./... # pure Go
|
||||
go build ./... # needs luxgpu on PKG_CONFIG_PATH
|
||||
```
|
||||
|
||||
`evmgpu` links the C++ GPU stack through pkg-config. Without `luxgpu.pc` on
|
||||
`PKG_CONFIG_PATH` the cgo build fails at configure time; build with
|
||||
`CGO_ENABLED=0` or [build `luxcpp/gpu` first](#the-gpu-stack).
|
||||
|
||||
### Verifying the node you built
|
||||
|
||||
A node serves everything under `/v1/`. There is no `/ext/` and no `/api/`. The
|
||||
default ports are **9630** for HTTP and **9631** for staking.
|
||||
|
||||
```bash
|
||||
./build/luxd &
|
||||
|
||||
curl -s -X POST -H 'content-type: application/json' \
|
||||
--data '{"jsonrpc":"2.0","id":1,"method":"info.getNodeVersion","params":{}}' \
|
||||
http://127.0.0.1:9630/v1/info
|
||||
|
||||
curl -s http://127.0.0.1:9630/v1/health
|
||||
|
||||
curl -s -X POST -H 'content-type: application/json' \
|
||||
--data '{"jsonrpc":"2.0","id":1,"method":"eth_chainId","params":[]}' \
|
||||
http://127.0.0.1:9630/v1/bc/C/rpc
|
||||
```
|
||||
|
||||
`info.getNodeVersion` returns the version **and the git commit** the binary was
|
||||
built from. That commit is what pins a corpus, so record it whenever you capture
|
||||
one.
|
||||
|
||||
To serve the same handler chain over the ZAP transport instead of HTTP/1.1, set
|
||||
a bind address — it is off unless you ask for it:
|
||||
|
||||
```bash
|
||||
ZAP_RPC_LISTEN=:9651 ./build/luxd
|
||||
```
|
||||
|
||||
## Rust — `hanzod` and the crates
|
||||
|
||||
```bash
|
||||
cargo build --release --bin hanzod
|
||||
./target/release/hanzod --version
|
||||
```
|
||||
|
||||
The consensus crate builds and tests standalone, and is the crate the harness
|
||||
puts questions to:
|
||||
|
||||
```bash
|
||||
cd consensus/pkg/rust # the lux-consensus crate lives inside luxfi/consensus
|
||||
cargo test
|
||||
```
|
||||
|
||||
<Callout type="warn">
|
||||
`hanzod`'s consensus crate currently depends on `lux-consensus` by a **relative
|
||||
path** into a sibling checkout of `luxfi/consensus`, not by a published version.
|
||||
A clean checkout of `hanzo/node` on its own will not resolve it. Until that is a
|
||||
version dependency, clone both repositories side by side.
|
||||
</Callout>
|
||||
|
||||
Building is not participating. `hanzod` starts and runs the Quasar engine; it
|
||||
has no block source and no vote transport, so it does not yet take part in a
|
||||
network. Treat a successful build as a successful build.
|
||||
|
||||
## C++ — the daemon and the finality gate
|
||||
|
||||
Both C++ repositories are out-of-source CMake builds and both find their
|
||||
dependencies through `LUXCPP_ROOT`.
|
||||
|
||||
### `consensus2` — the finality gate
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/consensus2 && cd consensus2
|
||||
cmake -S . -B build \
|
||||
-DLUXCPP_ROOT=/path/to/luxcpp \
|
||||
-DZAP_DIR=/path/to/luxcpp/zap-cpp-core/include \
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build -j"$(nproc)"
|
||||
ctest --test-dir build --output-on-failure
|
||||
```
|
||||
|
||||
Sanitizers instrument `consensus2`'s own code and leave the reused assembly
|
||||
crypto alone: `-DCONSENSUS2_SANITIZE=address,undefined` or `=thread`.
|
||||
|
||||
ThreadSanitizer aborts with `unexpected memory mapping` on kernels that hand out
|
||||
32 bits of mmap randomness — its shadow mapping does not fit. That is the
|
||||
loader, not the code. Run it with randomization off:
|
||||
|
||||
```bash
|
||||
setarch -R ctest --test-dir build
|
||||
```
|
||||
|
||||
### `node2` — the daemon
|
||||
|
||||
```bash
|
||||
git clone https://github.com/luxfi/node2 && cd node2
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build -j"$(nproc)"
|
||||
ctest --test-dir build --output-on-failure
|
||||
```
|
||||
|
||||
`node2` finds `consensus2` and `luxcpp` automatically when they sit beside it;
|
||||
`-DCONSENSUS2_DIR` and `-DLUXCPP_ROOT` override. Its suite includes the reused
|
||||
`consensus2` tests, so a green `node2` run covers both.
|
||||
|
||||
A real multi-process cluster over loopback TCP, rather than threads in one
|
||||
process:
|
||||
|
||||
```bash
|
||||
./scripts/cluster.sh build/node2d 19310 5 # five validators
|
||||
./scripts/cluster.sh build/node2d 19310 5 4 # ...with validator 4 held down
|
||||
```
|
||||
|
||||
### `cevm` — the C++ EVM
|
||||
|
||||
```bash
|
||||
cmake -S . -B build/release -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build/release
|
||||
ctest --test-dir build/release --output-on-failure
|
||||
```
|
||||
|
||||
### The GPU stack
|
||||
|
||||
`luxcpp/gpu` picks its backend at compile time or at runtime, in the order
|
||||
CUDA → Metal → WebGPU → CPU. `LUX_GPU_BACKEND` overrides the choice. The Go
|
||||
bindings in `luxfi/gpu` link it through pkg-config; see [GPU](/gpu).
|
||||
|
||||
## Cross-checking what you built
|
||||
|
||||
Building three implementations proves three compilers agree with themselves. It
|
||||
proves nothing about whether they agree with each other — that is what the
|
||||
[conformance harness](/conformance) is for, and it is the gate an
|
||||
implementation passes before it goes anywhere near a live network.
|
||||
@@ -0,0 +1,153 @@
|
||||
---
|
||||
title: Conformance
|
||||
description: The corpus that holds three implementations to one answer, and the harness that checks them
|
||||
---
|
||||
|
||||
Three implementations of one protocol will agree on the easy cases by accident
|
||||
and disagree on the hard ones silently. Conformance is the machinery that makes
|
||||
the disagreement loud and early instead of quiet and on-chain.
|
||||
|
||||
The corpus lives in [`luxfi/conformance`](https://github.com/luxfi/conformance).
|
||||
It holds canonical outputs captured from a live Go node. Every other
|
||||
implementation must reproduce them before it is allowed near a network.
|
||||
|
||||
## The contract
|
||||
|
||||
> For every vector, an implementation is conformant when the same input
|
||||
> produces output **equal** to the recorded result. Equality is exact: same hex,
|
||||
> same field values, same canonical JSON.
|
||||
|
||||
"Close" is a fork. There is no tolerance because there is nothing for a
|
||||
tolerance to mean — a block hash that is nearly right identifies a different
|
||||
block, and a quorum threshold that is off by one accepts a block the rest of the
|
||||
network refuses.
|
||||
|
||||
## What is recorded
|
||||
|
||||
Vectors are grouped by plane. Each plane is a place two implementations can
|
||||
disagree and produce two chains.
|
||||
|
||||
| Vector | Plane | What it pins |
|
||||
|---|---|---|
|
||||
| `evm_chainid`, `evm_genesis_block` | EVM | The chain id and genesis block as the node reports them |
|
||||
| `evm_header` | EVM | The exact bytes the genesis hash is taken over |
|
||||
| `evm_transaction` | EVM | Signing hash, wire bytes, recovered sender and list root per transaction type, plus the intrinsic gas floor and both contract-address rules |
|
||||
| `fpc_theta` | Consensus | θ per phase, and the α it yields at each committee size |
|
||||
| `luminance` | Consensus | The weight committees are sampled on, at every step of a trajectory |
|
||||
| `pchain_validators` | Consensus | The validator set that defines the committee |
|
||||
| `zap_handshake` | Wire | The first frame two peers exchange, and the frames Go refuses |
|
||||
| `zap_arena` | Wire | Every scalar, all four list kinds, the null shapes, nested objects through a signed pointer, and seven malformed frames with Go's answer to each |
|
||||
| `zap_frame` | Wire | The *other* ZAP — the [frame RPC](/zap/family), recorded so the shared name stops being an argument |
|
||||
| `network_id`, `node_version` | Provenance | What the corpus was captured against |
|
||||
|
||||
Two details in that table carry more weight than they look like they do.
|
||||
|
||||
**Refusals are recorded, not just acceptances.** `zap_handshake` and `zap_arena`
|
||||
both record frames Go's decoder *rejects*. An implementation must reject them
|
||||
too. Accepting a frame the network rejects is not leniency — it is a second
|
||||
protocol, and the peer that admits the message sees a history nobody else does.
|
||||
|
||||
**Floats are recorded as bits.** θ and luminance are stored as IEEE-754 bit
|
||||
patterns, because a float is not its decimal rendering and two implementations
|
||||
that print the same sixteen digits can still be two different numbers.
|
||||
|
||||
## The oracle is pinned
|
||||
|
||||
`node_version.json` records the exact Go build the corpus came from — version
|
||||
and git commit. Re-capturing against a different build is a deliberate act: bump
|
||||
the pin, re-run the capture, review the diff.
|
||||
|
||||
> A vector that changes without a version bump is a regression in the oracle,
|
||||
> not a new truth.
|
||||
|
||||
The current pin is `luxd/1.36.133` at commit `f81aad95`. A node built from
|
||||
`luxfi/node` HEAD today reports a higher version, so re-capturing without
|
||||
bumping would silently move the ground everything else is measured against.
|
||||
|
||||
## Reproducing the corpus
|
||||
|
||||
```bash
|
||||
scripts/capture.sh http://127.0.0.1:9630
|
||||
```
|
||||
|
||||
One command, because the corpus is asked for in two ways and both halves must
|
||||
come from the same build:
|
||||
|
||||
- Over `/v1/`, it asks a **running node** what state it is in.
|
||||
- Through `oracle/`, a Go program linked against `luxfi/consensus`,
|
||||
`luxfi/geth` and `luxfi/zap`, it asks the **packages** what their functions do.
|
||||
|
||||
Vectors are never hand-edited. The corpus is always something Go actually said.
|
||||
|
||||
The oracle checks its own work before writing. The genesis header vector is
|
||||
published only if keccak256 of the recorded bytes equals what `Header.Hash()`
|
||||
returns *and* equals the hash the node reported — three ways of getting the same
|
||||
number, or no vector.
|
||||
|
||||
## Running the harness
|
||||
|
||||
One harness exists today, in Rust. It carries **no vectors of its own** — a
|
||||
second copy of the truth is a second truth — and it puts the corpus's questions
|
||||
to the crates that would answer them on a live network: `lux-consensus` for
|
||||
consensus, `hanzo-zap` for the wire, `alloy` for the EVM.
|
||||
|
||||
```bash
|
||||
cd lux-rs/conformance
|
||||
LUX_VECTORS=/path/to/luxfi/conformance/vectors cargo test
|
||||
```
|
||||
|
||||
`LUX_VECTORS` defaults to a sibling checkout. **A missing corpus fails; it never
|
||||
skips.** A suite that passes when it checked nothing is worse than one that
|
||||
fails, because it will be believed.
|
||||
|
||||
### Current result
|
||||
|
||||
Twelve checks across four planes. Eleven pass:
|
||||
|
||||
| Plane | Checks | Result |
|
||||
|---|---|---|
|
||||
| `consensus_fpc` | θ and α match the oracle | pass |
|
||||
| `consensus_luminance` | the whole trajectory matches | pass |
|
||||
| `evm_header` | fields read back; hashes to the block hash | pass |
|
||||
| `evm_transaction` | signed bytes decode; signing hash, id, sender and list root match | pass |
|
||||
| `zap_handshake` | the frames Go refuses are refused | pass |
|
||||
| `zap_handshake` | the frame bytes match the oracle | **fail** |
|
||||
|
||||
The one failure is a single 16-bit word: Rust writes version `1` where Go writes
|
||||
`2`, on all six recorded node ids. It is a [known and deliberate
|
||||
divergence](/zap#the-version-word) — a reader in the fleet has not been widened
|
||||
yet, and readers are widened before emitters flip. It is left failing rather
|
||||
than asserted-as-correct, because a test that locks in a divergence will keep
|
||||
passing after the divergence is fixed.
|
||||
|
||||
### Reading a failure
|
||||
|
||||
A failure prints the id it disagreed on, what Go said, and what the
|
||||
implementation said. That is enough to fix it, and deliberately not enough to
|
||||
argue about — one of the two byte strings is on the network and the other is
|
||||
not.
|
||||
|
||||
## What is not covered
|
||||
|
||||
Stated plainly, because a gap that is not written down gets mistaken for a pass.
|
||||
|
||||
- **No C++ harness against this corpus.** `consensus2` has its own conformance
|
||||
test with its own captured vectors, and it passes; it is not pointed at this
|
||||
corpus. Two corpora agreeing today is luck, not design.
|
||||
- **No end-to-end consensus sequence.** The corpus covers the functions a
|
||||
decision is made of, not yet a sequence of accept, reject and reorg decisions.
|
||||
- **No Rust EVM building a header** from a genesis specification, and no
|
||||
execution to a state root. Those need a chain with transactions in it, and the
|
||||
C-Chain sits at block zero.
|
||||
- **No execution-plane vectors from the C++ EVM.** `cevm`'s opcode state roots
|
||||
are checked only against `cevm`'s own goldens, so a divergence in gas
|
||||
accounting is invisible to every other implementation.
|
||||
|
||||
## The rule
|
||||
|
||||
> A harness that is not green does not ship. A network is not migrated to an
|
||||
> implementation until its harness is green against the corpus captured from
|
||||
> that network's own Go node.
|
||||
|
||||
Green against a devnet corpus says nothing about mainnet: a corpus records what
|
||||
one node said, not what the protocol permits.
|
||||
@@ -171,6 +171,33 @@ without degrading finality or cert size.
|
||||
| M-Chain | MPC ceremonies (CGGMP21 / FROST) | Bridge custody for external wallets (per LP-134) |
|
||||
| F-Chain | FHE compute + TFHE bootstrap-key generation | Encrypted EVM, encrypted ML inference (per LP-134) |
|
||||
|
||||
## Implementations
|
||||
|
||||
Quasar exists in three languages. `luxfi/consensus` (Go) is the one that runs
|
||||
on every live network, and the other two conform to it rather than to the spec
|
||||
independently — a spec has room for two readings, a running chain does not.
|
||||
|
||||
| Language | Module | State |
|
||||
|---|---|---|
|
||||
| Go | `luxfi/consensus` | The network. `protocol/` covers Photon, Wave, Focus, Prism, Horizon, Flare, Ray, Field, Quasar and Pulsar. |
|
||||
| Rust | `lux-consensus` (in `luxfi/consensus/pkg/rust`) | The FPC threshold and luminance planes are byte-exact to Go, checked against the corpus. Used by `hanzod`. |
|
||||
| C++ | `luxfi/consensus2` | A leaderless finality gate with real BLS12-381 verification, embedded by `node2d`. Its canonical vote message is byte-identical to Go's. |
|
||||
|
||||
Two properties every implementation has to preserve, because getting either
|
||||
wrong looks like a rounding difference and is not one:
|
||||
|
||||
- **α is derived, never assumed.** FPC picks θ per phase and α is `ceil(θ·k)`.
|
||||
Two implementations that derive θ with hashes that merely resemble each other
|
||||
will disagree on how many votes accept a round — one finalizes what the other
|
||||
refuses.
|
||||
- **The stake floor and the signer floor are one definition.** The verifier
|
||||
re-derives both from the live validator set rather than reading a
|
||||
certificate's claim about itself, so a set of votes relabelled to a higher
|
||||
tier dies on the floor it did not meet.
|
||||
|
||||
See [Implementations](/implementations) for the full picture and
|
||||
[Conformance](/conformance) for how these are checked.
|
||||
|
||||
## Further reading
|
||||
|
||||
- [Post-Quantum Security](/post-quantum) — Pulsar (TALUS) and Corona (Ringtail) in depth
|
||||
|
||||
@@ -6,6 +6,29 @@ icon: Cpu
|
||||
|
||||
Go bindings for `luxcpp/gpu`, providing GPU-accelerated array operations for crypto, ZK, and ML workloads. Used by `github.com/luxfi/accel` for high-level blockchain acceleration.
|
||||
|
||||
## Where the GPU stack lives
|
||||
|
||||
Unlike the other planes, this one is **not** three implementations of one thing.
|
||||
There is one implementation — C++ — and everything else is a binding to it.
|
||||
|
||||
| Language | Role |
|
||||
|---|---|
|
||||
| C++ (`luxcpp/gpu`, `luxcpp/accel`) | The kernels and the launchers. The whole stack. |
|
||||
| Go (`luxfi/gpu`, `luxfi/accel`) | cgo bindings. Passes Go-heap pointers across the boundary. |
|
||||
| Rust | None. |
|
||||
|
||||
Two consequences follow, and both matter more here than for a pure library:
|
||||
|
||||
- **The cgo boundary is a consensus surface.** A launcher that reads a length a
|
||||
peer supplied, or that adopts a stale error from an unrelated caller sharing
|
||||
its thread, does not crash — it hashes something else. Two nodes on different
|
||||
hardware then compute different state roots from identical input, which is a
|
||||
fork rather than an outage.
|
||||
- **A launcher without a numeric oracle is untested.** Checking that a kernel
|
||||
file exists and starts with the right magic number is a file test. The test
|
||||
that counts compares the launcher's output against a CPU computation of the
|
||||
same thing.
|
||||
|
||||
## Backend Support
|
||||
|
||||
| Backend | Platform | Dependencies | Priority |
|
||||
@@ -17,6 +40,14 @@ Go bindings for `luxcpp/gpu`, providing GPU-accelerated array operations for cry
|
||||
|
||||
Backend is auto-detected at runtime in priority order. Override with `LUX_GPU_BACKEND` env var (`cuda`, `metal`, `webgpu`, `cpu`).
|
||||
|
||||
<Callout type="warn">
|
||||
**CUDA is not cleared for consensus work.** Its launchers do not yet observe the
|
||||
same host/device pointer contract as HIP, and the Go binding calls them through a
|
||||
signature they do not all declare. Use `LUX_GPU_BACKEND=cpu` for anything whose
|
||||
output enters a state root, until the CUDA launchers are ported and have CPU
|
||||
oracles of their own.
|
||||
</Callout>
|
||||
|
||||
## Build Modes
|
||||
|
||||
| Mode | Command | GPU Support |
|
||||
@@ -24,6 +55,10 @@ Backend is auto-detected at runtime in priority order. Override with `LUX_GPU_BA
|
||||
| **CPU only** | `CGO_ENABLED=0 go build ./...` | No (pure Go fallback) |
|
||||
| **GPU enabled** | `CGO_ENABLED=1 go build ./...` | Yes (requires luxcpp/gpu) |
|
||||
|
||||
The GPU build needs `luxgpu.pc` on `PKG_CONFIG_PATH`; without it the cgo build
|
||||
fails at configure time rather than falling back. See
|
||||
[Build from source](/build#the-gpu-stack).
|
||||
|
||||
## Data Types
|
||||
|
||||
| Type | Description |
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
---
|
||||
title: Implementations
|
||||
description: One protocol, three implementations — Go is the network, Rust and C++ conform to it
|
||||
---
|
||||
|
||||
Lux is one protocol with three implementations. They are not three products.
|
||||
They are three programs that must produce the same answer to the same question,
|
||||
and one of them is the answer.
|
||||
|
||||
**`luxfi/node` (Go) is the network.** It is what runs on mainnet, testnet and
|
||||
devnet. When Rust or C++ disagrees with it, Rust or C++ is wrong — not because
|
||||
Go is better written, but because a chain has exactly one history and Go is
|
||||
already keeping it. An implementation is correct only insofar as it reproduces,
|
||||
byte for byte, what the Go node produces.
|
||||
|
||||
<Callout>
|
||||
Nothing here is a roadmap. Each row below is a state you can check today by
|
||||
building the thing and running the [conformance harness](/conformance) against
|
||||
the corpus. A claim with no vector behind it is not a claim.
|
||||
</Callout>
|
||||
|
||||
## The three
|
||||
|
||||
| | Language | Repository | Binary | What it is |
|
||||
|---|---|---|---|---|
|
||||
| **luxd** | Go | `luxfi/node` | `luxd` | The network. Every live chain. The oracle every other row is measured against. |
|
||||
| **hanzod** | Rust | `hanzo/node` | `hanzod` | The Rust node. Runs the Quasar engine through the `lux-consensus` crate. |
|
||||
| **node2** | C++ | `luxfi/node2` | `node2d` | The C++ daemon. Embeds `luxfi/consensus2` as its finality gate. |
|
||||
|
||||
Supporting crates and libraries, one concern each:
|
||||
|
||||
| Plane | Go | Rust | C++ |
|
||||
|---|---|---|---|
|
||||
| Consensus | `luxfi/consensus` | `lux-consensus` (`luxfi/consensus/pkg/rust`) | `luxfi/consensus2` |
|
||||
| EVM | `luxfi/geth`, `luxfi/evm`, `luxfi/evmgpu` | `lux-evm` — `revm` as the machine, `alloy` for the encodings | `cevm` |
|
||||
| Wire — arena codec | `luxfi/zap` | `hanzo-zap` | — |
|
||||
| Wire — frame RPC | `luxfi/api/zap` | — | `luxcpp/zap-cpp-core` |
|
||||
| HTTP over ZAP | `zap-proto/http` | — | — |
|
||||
| Service framework | `zap-proto/zip` | — | — |
|
||||
| GPU | `luxfi/gpu` (bindings) | — | `luxcpp/gpu`, `luxcpp/accel` |
|
||||
|
||||
## The six planes
|
||||
|
||||
A "plane" is a place where two implementations can disagree and produce two
|
||||
chains. There are six, and each has its own page.
|
||||
|
||||
<Cards>
|
||||
<Card title="Node" href="/nodes">
|
||||
Bootstrapping, P2P, the RPC surface, the VM registry. Every path is under `/v1/`.
|
||||
</Card>
|
||||
<Card title="Consensus" href="/consensus">
|
||||
Quasar — Photon, Wave, Focus, and the compact-certificate envelope.
|
||||
</Card>
|
||||
<Card title="EVM" href="/chains/cchain">
|
||||
The C-Chain. Header preimage, transaction signing, state root, gas.
|
||||
</Card>
|
||||
<Card title="ZAP" href="/zap">
|
||||
The arena codec. Sixteen-byte header, zero-copy field access, pinned to Go's bytes.
|
||||
</Card>
|
||||
<Card title="zip and the ZAP family" href="/zap/family">
|
||||
Four layers answer to one name. `zip` declares an operation once and projects it onto every transport.
|
||||
</Card>
|
||||
<Card title="GPU" href="/gpu">
|
||||
Kernels and launchers. Only C++ has a full stack.
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
## Where each stack stands
|
||||
|
||||
Building and passing your own tests is the easy half. What the tables below
|
||||
record is the harder half — whether the thing agrees with Go. `—` means it does
|
||||
not exist in that language and is not meant to.
|
||||
|
||||
### Go — `luxfi/*`
|
||||
|
||||
| Plane | State |
|
||||
|---|---|
|
||||
| node | The oracle. `./scripts/build.sh` on `luxfi/node` produces `luxd`; the local devnet answers `/v1/info` and `/v1/health`. |
|
||||
| consensus | The oracle. `go test ./protocol/...` green across Photon, Wave, Focus, Prism, Horizon, Flare, Ray, Field, Quasar, Pulsar. |
|
||||
| EVM | The oracle, with a **split to settle**: `luxfi/geth`'s `Hash19` and `luxfi/evm`'s `HeaderSerializable` encode the same header to different byte lengths. One block, one hash — one of the two shapes has to go, and until it does no other language can conform to "the" preimage. |
|
||||
| ZAP | The oracle. `luxfi/zap` emits version word `2` and accepts `1` and `2`. |
|
||||
| zip | `zap-proto/zip`. Go only — it is the service framework, not a wire format. |
|
||||
| GPU | `luxfi/gpu` is a cgo binding over the C++ stack, not an independent implementation. |
|
||||
|
||||
### Rust — `hanzo/node`, `lux-rs/*`
|
||||
|
||||
| Plane | State |
|
||||
|---|---|
|
||||
| node | `hanzod` builds and runs. It is **not yet a network participant**: the Quasar engine is wired, the block source and vote transport are not. Do not read "runs" as "validates". |
|
||||
| consensus | `lux-consensus` builds, tests green, and the FPC threshold and luminance planes are byte-exact to Go. The aggregate-certificate path is being **removed**, not fixed: Go's `QuorumCert` carries no aggregate field, so there is nothing for an aggregate verifier to conform to. |
|
||||
| EVM | Genesis hash, the signing hash for every transaction type, sender recovery and the transaction-list root are byte-exact to Go. Header *sealing* is not: the header type in use cannot hold `blockGasCost` or the `extData` fields, and Lux sets `blockGasCost` on every produced header. |
|
||||
| ZAP | `hanzo-zap` reads and writes the arena codec and refuses the frames Go refuses. It emits version word `1` where Go emits `2` — see [the ordering rule](/zap#the-version-word). |
|
||||
| zip | — |
|
||||
| GPU | — |
|
||||
|
||||
### C++ — `luxfi/node2`, `luxfi/consensus2`, `cevm`, `luxcpp/*`
|
||||
|
||||
| Plane | State |
|
||||
|---|---|
|
||||
| node | `node2d` builds; 17 of 17 tests pass; a five-process cluster finalizes over real loopback TCP. |
|
||||
| consensus | `consensus2` builds; 11 of 11 tests pass, including its conformance test against the Go corpus. |
|
||||
| EVM | `cevm`'s trie and interpreter match `geth` across every opcode vector in its suite. The shipped state transition does not read the EVM's refund output, so a storage clear costs more gas than `geth` charges — the correct transition already exists in the repository and has to be promoted over the shipped one. |
|
||||
| ZAP | `luxcpp/zap-cpp-core` implements the **frame RPC**, and its constants match Go's exactly. It does not implement the arena codec, and is not supposed to. |
|
||||
| zip | — |
|
||||
| GPU | The only full GPU stack, so there is nothing to conform *to* — correctness is against a CPU computation of the same thing. Where a launcher has such an oracle, HIP matches it; many launchers do not have one yet, and CUDA is [not cleared](/gpu) for anything entering a state root. |
|
||||
|
||||
## The migration rule
|
||||
|
||||
`luxd` → `hanzod` is a real end-state, and it passes through the corpus rather
|
||||
than around it.
|
||||
|
||||
> An implementation does not replace another on a live network until its harness
|
||||
> is green against the corpus captured from **that network's own Go node**.
|
||||
|
||||
Two consequences worth stating plainly:
|
||||
|
||||
- **Green somewhere else does not count.** A harness green against a devnet
|
||||
corpus says nothing about mainnet, because the corpus records what one node
|
||||
said, not what the protocol permits.
|
||||
- **A harness that skips is a harness that failed.** The Rust harness fails on a
|
||||
missing corpus rather than passing on zero checks. A suite that passes when it
|
||||
checked nothing is worse than one that fails, because it will be believed.
|
||||
|
||||
## Duplicates to collapse
|
||||
|
||||
Two things doing one job is a defect. These are known and named so they are not
|
||||
rediscovered:
|
||||
|
||||
- **`cevm` has two homes.** Both remotes share a root commit and have since
|
||||
diverged. One of them stops receiving consensus-critical fixes the moment
|
||||
someone picks the wrong one.
|
||||
- **`hanzo-zap` has more than one copy.** They were identical and have drifted —
|
||||
one is now nearly twice the length of the other. The same wire question has to
|
||||
be answered twice, and can be answered differently.
|
||||
- **The GPU ABI header has three copies.** The drifted copy also defines the
|
||||
version constant its own compile-time check reads, so the check against
|
||||
exactly this cannot fire.
|
||||
- **`evmgpu` pins an older module set than `node`** — a different `geth` and a
|
||||
different `precompile` — which is a state-root divergence waiting for a
|
||||
precompile to be called.
|
||||
|
||||
## See also
|
||||
|
||||
- [Build from source](/build) — verified native recipes for all three.
|
||||
- [Conformance](/conformance) — the corpus, the pinned oracle, the harness.
|
||||
@@ -14,6 +14,17 @@ description: Post-quantum secure, multi-consensus blockchain infrastructure
|
||||
| [Nodes](/nodes) | Validators, full nodes, archive nodes |
|
||||
| [Staking](/staking) | Delegation and liquid staking |
|
||||
|
||||
## Implementations
|
||||
|
||||
One protocol in three languages. Go is what runs on every live network; Rust and
|
||||
C++ conform to it.
|
||||
|
||||
| Name | Description |
|
||||
|------|-------------|
|
||||
| [Implementations](/implementations) | Go, Rust and C++ across the six planes |
|
||||
| [Build from source](/build) | Native build recipes for all three |
|
||||
| [Conformance](/conformance) | The corpus, the pinned oracle, the harness |
|
||||
|
||||
## Chains
|
||||
|
||||
| Name | Description |
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
"consensus",
|
||||
"post-quantum",
|
||||
"tokenomics",
|
||||
"---Implementations---",
|
||||
"implementations",
|
||||
"build",
|
||||
"conformance",
|
||||
"---Nodes---",
|
||||
"nodes",
|
||||
"staking",
|
||||
|
||||
@@ -66,8 +66,15 @@ lux validator add \
|
||||
|
||||
## Monitoring
|
||||
|
||||
Access the node metrics and monitoring:
|
||||
A node with no `--http-port` set listens on **9630**, and stakes on **9631**.
|
||||
Every path is under `/v1/`.
|
||||
|
||||
- **Metrics endpoint**: `http://localhost:9650/v1/metrics`
|
||||
- **Health check**: `http://localhost:9650/v1/health`
|
||||
- **Info API**: `http://localhost:9650/v1/info`
|
||||
- **Metrics endpoint**: `http://localhost:9630/v1/metrics`
|
||||
- **Health check**: `http://localhost:9630/v1/health`
|
||||
- **Info API**: `http://localhost:9630/v1/info`
|
||||
|
||||
## Building one
|
||||
|
||||
The pages above run a node from a release. To build any of the three
|
||||
implementations from source — Go, Rust or C++ — see
|
||||
[Build from source](/build).
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
---
|
||||
title: The ZAP family
|
||||
description: Four layers answer to the name ZAP — the arena codec, the frame RPC, HTTP over ZAP, and zip
|
||||
---
|
||||
|
||||
Four different things in this stack answer to "ZAP". They are layers, not
|
||||
versions: none of them is an upgrade of another, and two of them cannot read a
|
||||
single byte the other writes. Saying "the ZAP format" identifies nothing.
|
||||
|
||||
This page names them apart. Everywhere else in these docs, the unqualified word
|
||||
**ZAP** means the arena codec — layer 1.
|
||||
|
||||
| Layer | Module | Header | What it does |
|
||||
|---|---|---|---|
|
||||
| 1 · **arena codec** | `luxfi/zap` | 16 bytes, little-endian, magic `ZAP\0` | Turns a struct into bytes you can read fields out of without parsing |
|
||||
| 2 · **frame RPC** | `luxfi/api/zap` · `luxcpp/zap-cpp-core` | 5 bytes, big-endian, + a 4-byte request id | Multiplexes request/response over one connection |
|
||||
| 3 · **HTTP over ZAP** | `zap-proto/http` | layer 2 | `net/http` semantics on the ZAP transport |
|
||||
| 4 · **zip** | `zap-proto/zip` | layer 3 | Declares an operation once and projects it onto every interface |
|
||||
|
||||
A fifth framing, unrelated to all of these, carries the post-quantum peer
|
||||
handshake: a bare 4-byte big-endian length prefix in `luxfi/node`'s
|
||||
`network/peer`, capped at 16 KiB. It has no type byte, no magic and no request
|
||||
id. It is not part of this family and only looks like layer 2 from a distance.
|
||||
|
||||
## Layer 1 — the arena codec
|
||||
|
||||
Zero-copy: a field read is a pointer offset, not a parse. Every multi-byte
|
||||
integer is little-endian and offsets are relative to the offset field itself.
|
||||
Full detail, including the header rules a decoder must enforce, is on the
|
||||
[ZAP page](/zap).
|
||||
|
||||
Implementations: Go (`luxfi/zap`), Rust (`hanzo-zap`), Python, TypeScript.
|
||||
**There is no C++ arena codec.** A C++ service speaks layer 2.
|
||||
|
||||
## Layer 2 — the frame RPC
|
||||
|
||||
A different protocol with the same name, and the one C++ implements.
|
||||
|
||||
```
|
||||
[ 4-byte BE length ][ 1-byte type ][ 4-byte BE request id ][ body ]
|
||||
```
|
||||
|
||||
The type byte carries two flags in its high bits, so a service id has to fit in
|
||||
the low six:
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Header size | 5 bytes |
|
||||
| Request-id size | 4 bytes |
|
||||
| Maximum message | 16 MiB |
|
||||
| Response flag | `0x80` |
|
||||
| Error flag | `0x40` |
|
||||
| Type mask | `0x3F` |
|
||||
|
||||
A service id at or above `0x40` sets a flag bit, and both ends will read it as
|
||||
a flag and dispatch to the wrong handler. Ids stay in `1..63`.
|
||||
|
||||
Payload scalars here are **big-endian** — the opposite of layer 1 — and bytes
|
||||
and strings carry a `u32` length prefix. This is the clearest reason the two
|
||||
layers cannot be treated as one protocol with a version field.
|
||||
|
||||
Implementations: Go (`luxfi/api/zap`), C++ (`luxcpp/zap-cpp-core`). The C++
|
||||
constants — header size, request-id size, message cap, both flags, the mask —
|
||||
match Go's exactly. **There is no Rust frame RPC.**
|
||||
|
||||
## Layer 3 — HTTP over ZAP
|
||||
|
||||
`zap-proto/http` gives `net/http` request/response semantics over the ZAP
|
||||
transport, so an existing handler and an existing client work unchanged while
|
||||
the wire underneath changes. Confidentiality comes from an X-Wing hybrid key
|
||||
exchange (X25519 + ML-KEM-768) at the transport layer rather than from a bearer
|
||||
token at the application layer.
|
||||
|
||||
`luxd` serves its entire `/v1/*` handler chain — the same CORS, host filter and
|
||||
router as the HTTP listener — over this layer, so a gateway can reach a node
|
||||
over native ZAP instead of HTTP/1.1. It is opt-in and additive: the listener
|
||||
starts only when `ZAP_RPC_LISTEN` names a bind address, and a failure to start
|
||||
it is a warning, never fatal. A node upgrade never silently opens a port.
|
||||
|
||||
```bash
|
||||
ZAP_RPC_LISTEN=:9651 ./build/luxd
|
||||
```
|
||||
|
||||
## Layer 4 — zip
|
||||
|
||||
`zap-proto/zip` is the ZAP-native Go service framework, built on Fiber v3 and
|
||||
fasthttp. It is **not a serialization format** — that is layer 1, and the
|
||||
confusion is the reason this page exists.
|
||||
|
||||
The idea is one declaration and no second copy of it. A route is declared once
|
||||
as a typed operation, with its input and output types as the contract, and zip
|
||||
projects that one declaration into:
|
||||
|
||||
- a REST route,
|
||||
- an OpenAPI description,
|
||||
- an MCP tool,
|
||||
- a CLI verb,
|
||||
- a by-name call plane,
|
||||
|
||||
with ZAP as the primary transport and HTTP as a secondary view of the same
|
||||
operations. Nothing in that list is written twice, so none of them can drift
|
||||
from the others.
|
||||
|
||||
`luxd` does not use zip today; it serves `/v1/` through its own router over
|
||||
layer 3. zip is where Lux services that are not the node itself declare their
|
||||
surface.
|
||||
|
||||
## Why the names have to be fixed
|
||||
|
||||
The corpus records both layer 1 and layer 2 side by side, not because an
|
||||
implementation needs both, but so the name stops being an argument in review. A
|
||||
sentence like "our ZAP decoder rejects that frame" is true of one layer and
|
||||
false of the other, and there is no way to tell which from the sentence.
|
||||
|
||||
One name per format. Until the modules are renamed, qualify it: *arena codec*
|
||||
or *frame RPC*, never bare "ZAP" in a place where it could be either.
|
||||
@@ -11,6 +11,13 @@ ZAP (**Z**ero-copy **A**pplication **P**rotocol) is a high-performance binary pr
|
||||
import "github.com/luxfi/zap"
|
||||
```
|
||||
|
||||
<Callout>
|
||||
This page is the **arena codec** — the 16-byte-header format `luxfi/zap`
|
||||
encodes. Three other layers also answer to the name ZAP, including one whose
|
||||
header is five big-endian bytes and shares nothing with this one. See
|
||||
[the ZAP family](/zap/family) before assuming which is meant.
|
||||
</Callout>
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | MCP JSON-RPC | ZAP | Improvement |
|
||||
@@ -20,21 +27,72 @@ import "github.com/luxfi/zap"
|
||||
| Allocations | 58/op | 2/op | 29x fewer |
|
||||
| Parse time | ~1,000 ns | 2.9 ns | 345x faster |
|
||||
|
||||
## Wire Format
|
||||
## Wire format
|
||||
|
||||
Every ZAP message has a 16-byte header followed by a variable-length data segment:
|
||||
Every message is a 16-byte header followed by a variable-length data segment.
|
||||
All multi-byte integers are little-endian, and an offset is relative to the
|
||||
position of the offset field itself. Data segments are 8-byte aligned.
|
||||
|
||||
```
|
||||
Header (16 bytes)
|
||||
Magic: "ZAP\x00" (4 bytes)
|
||||
Version: 1 (2 bytes)
|
||||
Flags: compression, etc. (2 bytes)
|
||||
Root Offset: offset to root object (4 bytes)
|
||||
Size: total message size (4 bytes)
|
||||
Data Segment (variable)
|
||||
Magic "ZAP\x00" bytes 0..4
|
||||
Version 2 (see below) bytes 4..6
|
||||
Flags compressed / encrypted / signed bytes 6..8
|
||||
Root offset offset to the root object bytes 8..12
|
||||
Size total message size bytes 12..16
|
||||
Data segment (variable)
|
||||
Structs, lists, text, bytes...
|
||||
```
|
||||
|
||||
### The version word
|
||||
|
||||
Two schemas are defined, and the number is a **schema** discriminator, not a
|
||||
release number:
|
||||
|
||||
| Value | Schema |
|
||||
|---|---|
|
||||
| `1` | Legacy platform-chain schema — `NetworkID` at byte 0, no transaction-kind discriminator |
|
||||
| `2` | Current schema — transaction kind at byte 0, every other field shifted by one |
|
||||
|
||||
Go's builder **emits `2`** and its reader **accepts `1` and `2`**. The overlap
|
||||
is not free: a version-1-shaped transaction with `NetworkID = 11` has `0x0B` at
|
||||
byte 0, which is also a valid transaction-kind value, so a version-1 header over
|
||||
version-2 bytes would pass a discriminator check and then misread the rest of
|
||||
the buffer. Wrapping code requires version 2.
|
||||
|
||||
A reader is widened before an emitter is flipped, never the other way round:
|
||||
every reader in the fleet must accept the new value before the first writer
|
||||
produces it. Rust (`hanzo-zap`) emits `1` today for exactly this reason — one
|
||||
reader has not been widened yet — and its handshake bytes therefore differ from
|
||||
Go's in one 16-bit word. That difference is [recorded in the corpus](/conformance)
|
||||
as a known, deliberate divergence rather than hidden behind a passing test.
|
||||
|
||||
### What a decoder must refuse
|
||||
|
||||
Refusal is part of the protocol. Accepting a frame the network rejects is not
|
||||
leniency, it is a second protocol — one peer admits a message the rest of the
|
||||
network will not, and the two see different histories.
|
||||
|
||||
Go's reader enforces three rules and a conformant decoder enforces all three:
|
||||
|
||||
1. The magic is exactly `ZAP\0`.
|
||||
2. The version is `1` or `2`.
|
||||
3. The declared size at bytes 12..16 is **at least** the 16-byte header and **at
|
||||
most** the number of bytes actually present. The message is then truncated to
|
||||
that declared size.
|
||||
|
||||
Rule 3 is the one that gets left out, because a decoder that checks only magic
|
||||
and version still reads every well-formed message correctly, and every test
|
||||
written from the happy path passes. What it also does is read past the end of a
|
||||
short frame and return values Go never saw. That is a **state** divergence, not
|
||||
a transport one: these are the bytes transactions and predicate results are
|
||||
decoded from, so the two nodes do not fail to talk — they agree on different
|
||||
data.
|
||||
|
||||
The corpus records Go's verdict for each malformed frame, so a decoder can be
|
||||
held to all three rules rather than to the two that are easy to remember. If
|
||||
you are writing one, that is the check to run first.
|
||||
|
||||
## Supported Types
|
||||
|
||||
| Type | Size | Description |
|
||||
@@ -68,10 +126,30 @@ node := zap.NewNode(zap.NodeConfig{
|
||||
|
||||
Zero-copy semantics are preserved under TLS. The TLS layer operates on the same buffer without additional heap allocations.
|
||||
|
||||
## Use Cases
|
||||
## Port
|
||||
|
||||
- **AI Agent Orchestration**: 20-tool orchestrator runs at 150K ops/sec vs 10K with JSON-RPC
|
||||
- **VM-to-VM Communication**: Cross-VM message routing on the Lux network
|
||||
- **GPU Cluster / FHE Kernels**: Zero-copy ciphertext transfer between compute nodes
|
||||
`9999` is ZAP the way `80` is HTTP and `443` is HTTPS. Every ZAP-hosting service
|
||||
binds it; the DNS name says which service answers.
|
||||
|
||||
## Use cases
|
||||
|
||||
- **AI agent orchestration**: 20-tool orchestrator runs at 150K ops/sec vs 10K with JSON-RPC
|
||||
- **VM-to-VM communication**: cross-VM message routing on the Lux network
|
||||
- **GPU cluster / FHE kernels**: zero-copy ciphertext transfer between compute nodes
|
||||
- **Consensus**: 5-node consensus reaching agreement in ~450 microseconds
|
||||
- **MCP Bridge**: Auto-discover and accelerate existing MCP servers with ZAP transport
|
||||
- **MCP bridge**: auto-discover and accelerate existing MCP servers with ZAP transport
|
||||
|
||||
## Implementations
|
||||
|
||||
| Language | Module | Notes |
|
||||
|---|---|---|
|
||||
| Go | `luxfi/zap` | The oracle. Emits version `2`, accepts `1` and `2`. |
|
||||
| Rust | `hanzo-zap` | Used by `hanzod` and by the conformance harness. Emits version `1` — see [the version word](#the-version-word). |
|
||||
| Python | `hanzo-zap` | Reader and writer. |
|
||||
| TypeScript | `@zap-proto/zap` | Reader and writer. |
|
||||
| C++ | — | None. A C++ service speaks the [frame RPC](/zap/family) instead. |
|
||||
|
||||
A decoder is conformant when it agrees with [the corpus](/conformance) — which
|
||||
records the bytes Go writes **and** the malformed frames Go refuses — not when
|
||||
it round-trips its own output. Round-tripping your own output only proves the
|
||||
encoder and decoder were written by the same person.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"pages": [
|
||||
"index",
|
||||
"family",
|
||||
"api",
|
||||
"---Package reference---",
|
||||
"reference"
|
||||
|
||||
Reference in New Issue
Block a user