The flags, keys, committee, mesh, chain on disk, catch-up by certificate and JSON-RPC are lux-node 0.2.1's run(). The chain answers at /v1/chain/hanzo. Claude-Session: https://claude.ai/code/session_011Ge9UnfkEK37Lapx5nfPQK
5.4 KiB
node
hanzod — the Hanzo network node. Rust. One binary.
What this repository is
A validator on this network, and the Hanzo chain it serves.
The host it runs on is a dependency, at a tag. That host is where identity, the post-quantum handshake, the validator mesh, the block engine, the interpreter and the JSON-RPC server live. Every node on the network — whatever language it is written in — runs the same host, because two nodes that disagree about what a block means are two networks.
What this repository holds is what is Hanzo's: the networks and their numbers, the genesis each starts from, the name this node answers under, the data directory, the ports, and the command line. Nothing here re-implements anything the host already states.
Layout
src/main.rs Hanzo's spec — name, client, three networks — and one call
genesis/*.json one chain genesis per network
Everything else — flags, keys, committee, mesh, the chain on disk, catch-up and
JSON-RPC — is lux_node::run. The chain answers at /v1/chain/hanzo and
/v1/chain/hanzo/rpc; the C-Chain name belongs to Lux alone.
Two numbers, not one
A validator joins a NETWORK. A transaction is signed against a CHAIN. They are different numbers and collapsing them is how a wallet and a validator come to disagree about what they are on — visible only after something has been signed for the wrong thing.
network id 1 mainnet 2 testnet 3 devnet
chain id 36963 36962 36964
The chain ids are LP-018's canonical map for this brand. The network ids are the
network whose validator set carries the chain: an operator running hanzod
holds a seat in that set and serves this chain on it.
The SDK's default ports are 9650 (RPC) and 9651 (mesh); a deployment passes
--rpc and --mesh. The data directory is ~/.hanzod.
The rule comes from the standard
The consensus package named in Cargo.toml is what decides anything. The proof
of possession a validator publishes, the admission rule a committee is built by,
and the thresholds a certificate is measured against are read from it, so what
this node calls a quorum is what every other implementation calls one. Nothing
here restates a threshold.
A certificate needs a supermajority of the SEATS, strictly more than two thirds: three of four, four of five, five of seven. And it needs at least four seats at all — below that the fault budget is zero and one compromised key forges it. The standard refuses it by name:
proposing: no certificate: Cert(MinCommittee { n: 2, need: 4 })
A validator is named by its key
Two keys, because they answer two questions. ML-DSA-65 is WHO: it names the
validator and is proved on every link. BLS12-381 is WHAT IT SAID: it signs votes
and is what a certificate aggregates. Both are made on first use and kept 0600.
A published line is the ML-DSA identity, the BLS key, and the proof of possession — 1952, 48 and 96 bytes. No name, because there is nothing on the line to lie about: the name is derived from the identity, and the proof binds the voting key to it.
Nothing is derived from a shared seed. A network brought up by handing every node one seed is one validator wearing several hats.
The name this node answers to
web3_clientVersion and every version surface name this node — hanzod/v… —
and the chain is served under hanzo. Execution is the SDK's, untouched.
What is refused
Refusal is the interesting half. Every one of these has a test or a run.
- A network name that is not one of the three. Never a default.
- A genesis whose chain is not this network's chain.
- A genesis that names a network, when that is not this network. A chain document carried inside another network's genesis belongs to that network.
- A field that is present and unreadable — a quoted chain id, a negative one. Never a zero.
- A committee file with one bad line, refused whole (
PopInvalid), so the node never runs against a set missing a member it believes is there. - A validator that is not in the committee it was handed.
--rpcand--meshon one address.- A
--chainthat is not exactly 32 bytes of hexadecimal.
Bringing a network up
A committee is the list of what its validators published, so it takes two steps and nothing can publish before the file exists:
for n in 0 1 2 3 4; do hanzod --data n$n --publish >> committee.txt; done
hanzod --network devnet --data n0 --committee committee.txt --peers <addresses>
--peers is positions, not names: the names are fixed by the committee file and
the handshake proves them, so an address belonging to someone else is refused
rather than believed. The mesh is not the quorum — peers reports who was
reached and the rule already says how many are enough.
Building
cargo build --release
cargo test --release
Both clean, no warnings.
Conventions
- Copyright is Hanzo AI, Inc.; new files carry
SPDX-License-Identifier: BSD-3-Clause-Eco. Upstream attribution lives inNOTICE. - The word for another network's brand does not appear in prose, help text, log
lines, or anything a person reads. The dependency lines in
Cargo.tomland the attribution inNOTICEname the packages this depends on, which is the dependency and not a brand surface. - No path dependencies and no vendored copies: the host resolves to a tag.
LLM.mdis the one document file.CLAUDE.md,AGENTS.mdandGEMINI.mdare gitignored symlinks to it.