HIP-20: Blockchain Node Standard. Status Draft. Hanzo's own standard โ read this before implementing against it.
This proposal defines the standard for Hanzo Node, a Rust-based blockchain node optimized for decentralized AI compute coordination. Hanzo Node uses libp2p for peer-to-peer networking, gossipsub for message propagation, RocksDB for persistent state, Candle (HIP-0019) for on-node tensor operations, and Proof of AI (PoAI) for consensus. It settles periodically to the Lux Network L1 for finality via the luxfi bridge libraries.
All nodes participating in the Hanzo compute network MUST implement this specification. The protocol defines peer discovery, compute job lifecycle, GPU inventory advertisement, block structure, transaction types, RPC interface, and settlement mechanics.
Repository: github.com/hanzoai/node Language: Rust Networking: libp2p (rust-libp2p) Consensus: Proof of AI (PoAI), derived from ZIP-002
Hanzo operates a decentralized compute network that coordinates AI inference and training jobs across heterogeneous GPU providers. This network requires a purpose-built blockchain node -- not a general-purpose chain runtime -- because AI compute coordination has requirements that no existing node software satisfies together.
AI inference requests have latency budgets measured in hundreds of milliseconds. A user asking a question to a chat interface expects a first-token response in under 500ms. The coordination layer that assigns that request to a GPU provider, routes the prompt, and streams the response cannot add seconds of overhead. General-purpose L1 chains finalize blocks in 1-4 seconds at best. Hanzo Node targets 200ms block intervals for compute coordination, with the understanding that final economic settlement happens on Lux L1 at a slower cadence.
The node must maintain a real-time inventory of GPU resources across the network: which nodes have which GPUs, how much VRAM is free, what models are loaded, and what their current utilization is. This is not application-level state that can sit in a smart contract -- it changes on every block and drives consensus-critical scheduling decisions. The node must natively understand GPU capabilities.
Proof of AI consensus requires verifying that a node actually performed a specified inference computation and produced the correct output. This verification involves running a subset of tensor operations (via Candle) and comparing results. EVM opcodes cannot express matrix multiplication on 16-bit floating point tensors. The verification logic must be native to the node runtime.
If every validator must re-execute every AI inference to verify it, the hardware requirements become the same as the compute providers themselves -- defeating the purpose of a decentralized network. By separating fast coordination (Hanzo Node) from final settlement (Lux L1), we allow lightweight validators to participate in ordering and scheduling while only a subset of nodes perform full compute verification.
AI compute payments must be settled on a chain with established economic security, not a new chain bootstrapping its validator set. Lux Network provides this settlement layer. Hanzo Node checkpoints state roots and payment summaries to Lux L1 at regular intervals, inheriting its finality guarantees for economic transactions while maintaining independent fast finality for compute coordination.
Lux Node (Go, Quasar consensus) is designed for general-purpose L1 operation with VM plugins. Ethereum clients are designed for EVM execution. Cosmos SDK nodes are designed for application-specific chains with Tendermint consensus. None of them natively support GPU inventory management, AI inference verification, sub-200ms block production, or tensor operation verification. Building on any of these would require replacing so many components that forking provides no advantage over a purpose-built implementation.
This section explains the reasoning behind every major architectural choice. None of these decisions are arbitrary -- each follows from a specific constraint of the AI compute coordination problem.
The choice of Rust over Go, C++, or Java is driven by three hard requirements of consensus-critical code.
Memory safety without garbage collection. Consensus code must be deterministic. Two validators processing the same block must arrive at the same state. Garbage collection introduces non-deterministic pauses -- a GC cycle during block production can cause a validator to miss its slot, triggering unnecessary view changes and reducing network throughput. Rust eliminates this class of failure through compile-time ownership tracking. Memory is freed deterministically when values go out of scope.
Go (used by Lux Node and most Ethereum clients) has a concurrent garbage collector that is well-tuned for server workloads but still introduces tail latency spikes. For a node producing blocks every 200ms, a 50ms GC pause means missing 25% of a block interval. Rust has zero GC pauses because it has no GC.
Deterministic execution for validators. When two validators execute the same transaction, they must produce identical state transitions. C++ allows this but provides no protection against undefined behavior -- a buffer overflow or use-after-free in consensus code means validators silently diverge. Rust's type system prevents undefined behavior at compile time. If it compiles, the execution is deterministic (modulo floating point, which we handle with fixed-point arithmetic in consensus-critical paths).
Safe concurrency for parallel verification. Compute verification involves running tensor operations across multiple CPU cores simultaneously. In C++, parallelizing matrix multiplication requires careful manual synchronization to avoid data races. In Go, goroutines with shared state require mutex discipline that the compiler does not enforce. Rust's ownership model makes data races a compile-time error. If you can express the parallelism, it is correct by construction.
The tradeoff is development velocity. Rust's learning curve is steeper than Go's, and compile times are longer. We accept this because node software is infrastructure that changes infrequently once correct. The operational benefits -- zero GC pauses, no segfaults, no data races -- compound across every block produced by every validator for the lifetime of the network.
Hanzo Node and Lux Node serve different roles in the stack, analogous to how an application server and a database serve different roles in web infrastructure.
Lux provides economic settlement. When a compute provider completes a job and earns $AI tokens, that payment must be finalized on a chain with strong economic security. Lux L1 has an established validator set, significant stake, and proven consensus. It is the right layer for irreversible value transfer.
Hanzo provides compute coordination. Assigning an inference job to a specific GPU, routing the request, streaming the response, and verifying the result must happen in sub-second timeframes. This coordination does not need the same finality guarantees as a token transfer -- if a job assignment is reversed, the worst case is that the job is re-assigned and re-executed, not that funds are lost.
The two-layer design separates concerns:
Finality
Latency Throughput Security Model
-------- ---------- --------------
Lux L1 (Settlement) 2-4s 4500 TPS Economic (staked LUX)
Hanzo Node (Coord) 200ms 10000+ TPS Compute (PoAI)
Settlement occurs via periodic checkpoints. Every N Hanzo blocks (configurable, default 100 -- approximately every 20 seconds), the node posts a state root and payment summary to Lux L1 via the luxfi bridge. This gives Lux-level finality to economic outcomes while keeping coordination latency independent.
A Hanzo Node consists of four major subsystems, each responsible for a distinct concern.
+------------------------------------------------------------------+
| Hanzo Node |
+------------------------------------------------------------------+
| |
| +-------------------+ +-------------------+ |
| | Networking | | Consensus | |
| | (libp2p) |<-->| (PoAI) | |
| | | | | |
| | - Gossipsub | | - Block production | |
| | - Kademlia DHT | | - PoAI verification| |
| | - mDNS | | - Leader election | |
| | - Request/Response | | - Slashing | |
| +-------------------+ +-------------------+ |
| | | |
| v v |
| +-------------------+ +-------------------+ |
| | Execution | | Storage | |
| | (Candle) | | (RocksDB) | |
| | | | | |
| | - Inference verify | | - Block store | |
| | - Tensor ops | | - State trie | |
| | - Model loading | | - GPU inventory | |
| | - Result hashing | | - Transaction pool | |
| +-------------------+ +-------------------+ |
| |
+------------------------------------------------------------------+
| | |
v v v
JSON-RPC API Lux Settlement Metrics/Logs
(port 8545) (luxfi bridge) (Prometheus)
Handles all peer-to-peer communication. Implemented with rust-libp2p v0.54+.
use libp2p::{
gossipsub, identify, kad, mdns, noise, ping,
swarm::NetworkBehaviour, tcp, yamux, PeerId, Swarm,
};
#[derive(NetworkBehaviour)]
pub struct HanzoNodeBehaviour {
/// Gossipsub for block/tx/compute propagation
pub gossipsub: gossipsub::Behaviour,
/// Kademlia DHT for global peer discovery
pub kademlia: kad::Behaviour<kad::store::MemoryStore>,
/// mDNS for local network peer discovery
pub mdns: mdns::tokio::Behaviour,
/// Identify protocol for peer metadata exchange
pub identify: identify::Behaviour,
/// Ping for liveness detection
pub ping: ping::Behaviour,
/// Request/response for direct queries (state sync, block fetch)
pub request_response: request_response::cbor::Behaviour<NodeRequest, NodeResponse>,
}
Transport configuration uses Noise encryption and Yamux multiplexing:
let transport = tcp::tokio::Transport::new(tcp::Config::default())
.upgrade(upgrade::Version::V1Lazy)
.authenticate(noise::Config::new(&local_keypair)?)
.multiplex(yamux::Config::default())
.boxed();
All connections are encrypted with the Noise protocol (XX handshake pattern). Multiplexing uses Yamux, which supports up to 256 concurrent streams per connection.
Implements Proof of AI consensus. The consensus cycle operates on 200ms block intervals.
pub struct PoAIConsensus {
/// Current validator set with staking weights
validators: ValidatorSet,
/// Candle runtime for inference verification
verifier: InferenceVerifier,
/// Random beacon for subset selection
vrf: VRFSigner,
/// Current round state
round: RoundState,
}
impl PoAIConsensus {
/// Determine if this node should produce the next block.
/// Leader election is weighted by stake and compute capacity.
pub fn is_leader(&self, slot: u64) -> bool {
let seed = self.vrf.evaluate(slot);
let threshold = self.validators.leader_threshold(&self.local_id);
seed < threshold
}
/// Verify a block's PoAI proofs by re-executing a random subset.
pub async fn verify_block(&self, block: &Block) -> Result<bool, ConsensusError> {
let subset = self.select_verification_subset(block, 0.10); // 10%
for proof in subset {
let expected = self.verifier.run_inference(
&proof.model_id,
&proof.input,
).await?;
if !proof.output.approx_eq(&expected, TOLERANCE) {
return Ok(false);
}
}
Ok(true)
}
}
Provides tensor operations for PoAI verification. Uses Hanzo Candle (HIP-0019) for inference execution. This subsystem does not serve user-facing inference -- it only verifies that compute providers produced correct results.
pub struct InferenceVerifier {
/// Loaded model weights (verification-only, may be quantized)
models: HashMap<ModelId, candle_nn::VarMap>,
/// Candle device (CPU for verification; GPU optional)
device: candle_core::Device,
}
impl InferenceVerifier {
/// Re-execute inference and return output hash for comparison.
pub async fn run_inference(
&self,
model_id: &ModelId,
input: &InferenceInput,
) -> Result<InferenceOutput, VerifyError> {
let model = self.models.get(model_id)
.ok_or(VerifyError::ModelNotLoaded(model_id.clone()))?;
let tensor = candle_core::Tensor::from_slice(
&input.tokens,
&[1, input.tokens.len()],
&self.device,
)?;
let output = model.forward(&tensor)?;
Ok(InferenceOutput::from_tensor(output))
}
}
Verification uses CPU by default. Validators are not required to own GPUs -- they only need enough compute to re-execute 10% of inferences per block. A modern 32-core server can verify blocks in real time without a GPU.
Persistent state storage uses RocksDB with column families for logical separation.
pub struct NodeStorage {
db: rocksdb::DB,
}
// Column families
const CF_BLOCKS: &str = "blocks"; // Block headers and bodies
const CF_STATE: &str = "state"; // Current state trie
const CF_TRANSACTIONS: &str = "transactions"; // Transaction index
const CF_INVENTORY: &str = "inventory"; // GPU inventory snapshots
const CF_COMPUTE: &str = "compute"; // Active compute sessions
const CF_CHECKPOINTS: &str = "checkpoints"; // Lux settlement checkpoints
State is stored as a Merkle Patricia Trie, enabling efficient state proofs for Lux L1 checkpointing. The state root is included in every block header.
Hanzo Node uses a three-tier discovery mechanism to find and connect to peers.
New nodes connect to a hardcoded list of well-known bootstrap nodes operated by Hanzo and community partners. These nodes are stable, long-running, and have high uptime SLAs.
const BOOTSTRAP_NODES: &[&str] = &[
"/dns4/boot1.hanzo.ai/tcp/9000/p2p/12D3KooW...",
"/dns4/boot2.hanzo.ai/tcp/9000/p2p/12D3KooW...",
"/dns4/boot3.hanzo.ai/tcp/9000/p2p/12D3KooW...",
"/dns4/boot-us.hanzo.network/tcp/9000/p2p/12D3KooW...",
"/dns4/boot-eu.hanzo.network/tcp/9000/p2p/12D3KooW...",
"/dns4/boot-ap.hanzo.network/tcp/9000/p2p/12D3KooW...",
];
Bootstrap nodes serve only for initial peer discovery. Once a node has discovered peers through the DHT, it no longer depends on bootstrap nodes.
After connecting to bootstrap nodes, the node joins the Kademlia DHT to discover the full peer set. The DHT stores peer multiaddresses keyed by peer ID.
let mut kademlia_config = kad::Config::default();
kademlia_config.set_query_timeout(Duration::from_secs(30));
kademlia_config.set_record_ttl(Some(Duration::from_secs(3600)));
kademlia_config.set_replication_factor(
NonZeroUsize::new(20).unwrap()
);
let kademlia = kad::Behaviour::new(
local_peer_id,
kad::store::MemoryStore::new(local_peer_id),
);
Nodes perform a DHT walk every 5 minutes to discover new peers and refresh routing table entries.
For development and private deployments, mDNS discovers peers on the local network without any external infrastructure.
let mdns = mdns::tokio::Behaviour::new(
mdns::Config {
ttl: Duration::from_secs(300),
query_interval: Duration::from_secs(60),
enable_ipv6: false,
},
local_peer_id,
)?;
The gossipsub mesh forms the primary communication fabric.
Bootstrap Nodes (well-known, DNS-routable)
/ | \
v v v
[DHT Discovery via Kademlia across all peers]
| | |
v v v
Gossipsub Mesh (topic-based, self-healing)
+---------------------------------------------+
| |
| /hanzo/blocks/1.0.0 |
| Block headers + bodies |
| Propagation target: < 100ms |
| |
| /hanzo/compute/1.0.0 |
| Job submissions + assignments |
| Propagation target: < 50ms |
| |
| /hanzo/inference/1.0.0 |
| Inference requests + streaming results |
| Propagation target: < 50ms |
| |
| /hanzo/state/1.0.0 |
| State sync requests + responses |
| On-demand (new node joining) |
| |
| /hanzo/inventory/1.0.0 |
| GPU capability advertisements |
| Heartbeat interval: 30s |
| |
+---------------------------------------------+
|
v
Lux L1 (Settlement Layer)
Checkpoint every ~20s
All Hanzo Node protocols use the following identifiers for multistream-select negotiation.
| Protocol | Identifier | Transport | Description | |----------|-----------|-----------|-------------| | Block Gossip | /hanzo/blocks/1.0.0 | Gossipsub | Block propagation | | Compute Gossip | /hanzo/compute/1.0.0 | Gossipsub | Job coordination | | Inference Gossip | /hanzo/inference/1.0.0 | Gossipsub | Inference routing | | State Sync | /hanzo/state/1.0.0 | Gossipsub | State announcements | | Inventory | /hanzo/inventory/1.0.0 | Gossipsub | GPU advertisements | | Block Fetch | /hanzo/block-fetch/1.0.0 | Request/Response | Historical block retrieval | | State Fetch | /hanzo/state-fetch/1.0.0 | Request/Response | State trie queries | | Peer Info | /hanzo/peer-info/1.0.0 | Request/Response | Node capability queries |
Every block consists of a header and a body. The header is sufficient for consensus validation. The body contains the full transaction and compute result data.
/// Block header. Fixed-size (approximately 256 bytes serialized).
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct BlockHeader {
/// Block number (monotonically increasing)
pub height: u64,
/// Timestamp (Unix milliseconds)
pub timestamp: u64,
/// SHA-256 hash of the previous block header
pub prev_hash: [u8; 32],
/// Merkle root of all transactions in the body
pub tx_merkle_root: [u8; 32],
/// Merkle root of all compute results in the body
pub compute_merkle_root: [u8; 32],
/// State trie root after applying this block
pub state_root: [u8; 32],
/// Block producer's peer ID
pub producer: PeerId,
/// VRF proof for leader election
pub vrf_proof: VRFProof,
/// PoAI proof: references to verified inferences
pub ai_proof: AIProof,
/// Block producer's Ed25519 signature over the header
pub signature: [u8; 64],
}
/// Block body. Variable size.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct BlockBody {
/// Ordered list of transactions
pub transactions: Vec<Transaction>,
/// Compute results included in this block
pub compute_results: Vec<ComputeResult>,
/// GPU inventory updates (heartbeats received this block)
pub inventory_updates: Vec<InventoryUpdate>,
}
/// PoAI proof included in every block header.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct AIProof {
/// Number of inference computations performed by the producer
pub inference_count: u32,
/// Hashes of inference inputs used
pub input_hashes: Vec<[u8; 32]>,
/// Hashes of inference outputs produced
pub output_hashes: Vec<[u8; 32]>,
/// Model identifiers used
pub model_ids: Vec<ModelId>,
/// Total FLOPs expended (self-reported, verified by subset check)
pub total_flops: u64,
}
Hanzo Node supports six transaction types, each serving a distinct function in the compute coordination lifecycle.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum TransactionType {
/// Request AI compute (inference or training)
ComputeRequest(ComputeRequestData),
/// Report completed compute with results
ComputeResult(ComputeResultData),
/// Lock $AI tokens as validator/provider stake
Stake(StakeData),
/// Begin unstaking with cooldown period
Unstake(UnstakeData),
/// Transfer $AI tokens between accounts
Transfer(TransferData),
/// Update GPU inventory advertisement
InventoryUpdate(InventoryData),
}
Submitted by clients requesting AI inference or training.
pub struct ComputeRequestData {
/// Unique job identifier (client-generated UUID)
pub job_id: [u8; 16],
/// Type of compute: Inference, Training, Embedding, FineTune
pub job_type: JobType,
/// Model to use (e.g., "zen-72b", "zen-8b-instruct")
pub model_id: ModelId,
/// Serialized input (prompt tokens, training data reference, etc.)
pub input: Vec<u8>,
/// Maximum $AI tokens the client will pay
pub max_fee: u64,
/// Required GPU constraints (min VRAM, min TFLOPS)
pub gpu_requirements: GpuRequirements,
/// Maximum acceptable latency in milliseconds
pub latency_budget_ms: u32,
/// Client's public key for result encryption
pub client_pubkey: [u8; 32],
}
Submitted by compute providers upon job completion.
pub struct ComputeResultData {
/// References the original ComputeRequest job_id
pub job_id: [u8; 16],
/// Provider's peer ID
pub provider: PeerId,
/// Serialized output (generated tokens, training metrics, etc.)
pub output: Vec<u8>,
/// SHA-256 hash of the output for quick verification
pub output_hash: [u8; 32],
/// Execution time in milliseconds
pub execution_time_ms: u64,
/// GPU used (from inventory)
pub gpu_id: GpuId,
/// Actual $AI fee charged (must be <= max_fee from request)
pub fee: u64,
}
Every compute job progresses through a deterministic state machine tracked in the chain state.
ComputeRequest tx
|
v
+---------+
| Pending | (in mempool, awaiting assignment)
+---------+
|
Scheduler assigns to provider
|
v
+----------+
| Assigned | (provider notified via gossipsub)
+----------+
|
Provider begins execution
|
v
+-----------+
| Computing | (provider executing inference/training)
+-----------+
|
ComputeResult tx submitted
|
v
+-----------+
| Verifying | (PoAI subset verification in progress)
+-----------+
/ \
/ \
pass / \ fail
v v
+-----------+ +-----------+
| Complete | | Disputed |
+-----------+ +-----------+
| |
Payment released Slash + reassign
Timeout rules:
latency_budget_ms from the request. If the provider exceeds the budget, the job is reassigned.A job is scheduled as pieces, not as a whole. The state machine above runs per piece; a job that fits on one provider is the degenerate case of a single piece. How a job decomposes follows from its job_type.
Inference: one piece if the model fits in a single provider's VRAM. If it does not, one piece per pipeline stage โ a contiguous group of transformer layers.
Embedding: one piece per batch of texts. Pieces are embarrassingly parallel.
Training: data-parallel. Each piece processes a shard of the dataset on a separate provider, and the block producer that scheduled the job aggregates gradients.
Batch inference: one piece per request, or per group of requests sharing a model.
pub struct Piece {
/// References the originating ComputeRequest
pub job_id: [u8; 16],
/// Index of this piece within the job
pub index: usize,
/// Position in the compute job state machine
pub state: JobState,
/// Serialized input for this piece
pub input: Vec<u8>,
/// SHA-256 hash of the input, for verification
pub input_hash: [u8; 32],
/// Providers holding this piece (more than one when redundancy > 1)
pub assigned_providers: Vec<PeerId>,
/// Results received so far, keyed by provider
pub results: HashMap<PeerId, ComputeResultData>,
/// The result that reached PoAI consensus
pub verified_result: Option<Vec<u8>>,
/// Set when this piece is a pipeline stage
pub pipeline_stage: Option<PipelineStage>,
/// Number of providers the piece is computed on
pub redundancy: usize,
/// Derived from the request's latency_budget_ms
pub deadline: Option<u64>,
pub priority: u32,
pub retry_count: usize,
/// Default: 3
pub max_retries: usize,
}
A model too large for any single provider's VRAM is split across providers, one pipeline stage per piece.
+--------------+ +--------------+ +--------------+ +--------------+
| Node A | | Node B | | Node C | | Node D |
| Layers 0-15 |-->| Layers 16-31 |-->| Layers 32-47 |-->| Layers 48-63 |
| (16 GB VRAM) | | (16 GB VRAM) | | (16 GB VRAM) | | (16 GB VRAM) |
+--------------+ +--------------+ +--------------+ +--------------+
Stage 0 Stage 1 Stage 2 Stage 3
pub struct PipelineStage {
/// Position of this stage in the pipeline
pub stage_index: usize,
pub total_stages: usize,
/// Start and end layer indices this stage holds
pub layer_range: (usize, usize),
/// Provider running the previous stage
pub upstream_peer: Option<PeerId>,
/// Provider running the next stage
pub downstream_peer: Option<PeerId>,
/// Size of the inter-stage tensor transfer
pub activation_size_bytes: u64,
}
Pipeline parallelism trades VRAM for network, so the protocol pays that cost down: stages prefer providers in the same geographic region, activation tensors are transferred in FP16 or quantized, micro-batch size is tuned to amortize the hop, and a provider must advertise at least 1 Gbps to be eligible for a stage.
Compute providers advertise their GPU capabilities via the /hanzo/inventory/1.0.0 gossipsub topic. Advertisements are sent every 30 seconds and included in block bodies for state tracking.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct InventoryUpdate {
/// Provider's peer ID
pub provider: PeerId,
/// List of available GPUs
pub gpus: Vec<GpuCapability>,
/// Current overall utilization (0.0 to 1.0)
pub utilization: f32,
/// Models currently loaded in VRAM
pub loaded_models: Vec<ModelId>,
/// Network bandwidth (Mbps, measured)
pub bandwidth_mbps: u32,
/// Geographic region (ISO 3166-1 alpha-2)
pub region: String,
/// Timestamp and signature
pub timestamp: u64,
pub signature: [u8; 64],
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct GpuCapability {
/// Unique identifier for this GPU on this node
pub gpu_id: GpuId,
/// GPU model name (e.g., "NVIDIA H100 SXM5 80GB")
pub model: String,
/// Total VRAM in megabytes
pub vram_total_mb: u32,
/// Available (free) VRAM in megabytes
pub vram_free_mb: u32,
/// Peak TFLOPS (FP16)
pub tflops_fp16: f32,
/// Peak TFLOPS (INT8)
pub tflops_int8: f32,
/// CUDA compute capability (e.g., 9.0 for H100)
pub compute_capability: f32,
/// Backend: CUDA, ROCm, Metal
pub backend: GpuBackend,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum GpuBackend {
Cuda,
Rocm,
Metal,
}
The scheduler uses GPU inventory to make placement decisions. A ComputeRequest specifying min_vram: 40000 (40 GB) for a 70B parameter model will only be assigned to providers with GPUs reporting vram_free_mb >= 40000.
The scheduler runs on every block producer and assigns pending ComputeRequests to available providers. The algorithm optimizes for three objectives in priority order:
pub fn schedule_job(
job: &ComputeRequest,
inventory: &InventoryState,
) -> Option<PeerId> {
let mut candidates: Vec<(PeerId, f64)> = inventory
.providers()
.filter(|p| p.meets_requirements(&job.gpu_requirements))
.filter(|p| p.has_model_loaded(&job.model_id)
|| p.can_load_model(&job.model_id))
.map(|p| {
let latency_score = 1.0 / (1.0 + p.estimated_latency_ms as f64);
let utilization_score = 1.0 - p.utilization as f64;
let cost_score = 1.0 / (1.0 + p.fee_rate as f64);
let score = 0.4 * latency_score
+ 0.3 * utilization_score
+ 0.3 * cost_score;
(p.peer_id, score)
})
.collect();
candidates.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
candidates.first().map(|(peer_id, _)| *peer_id)
}
Providers that have the requested model already loaded in VRAM receive a significant bonus because they avoid the cold-start latency of model loading (which can take 10-60 seconds for large models).
Every provider carries a reputation score in chain state. It gates which work a provider may be assigned and which roles it may hold, and it is the filter applied before the scheduler scores anyone.
pub struct NodeReputation {
pub peer_id: PeerId,
/// 0.0 to 1.0
pub score: f64,
pub total_tasks: u64,
pub successful_tasks: u64,
pub failed_tasks: u64,
pub slashed_count: u32,
/// Rolling 30-day uptime
pub uptime_ratio: f64,
pub avg_latency_ms: u64,
pub joined_at: u64,
}
Score changes are applied when a piece leaves the state machine, or when a heartbeat is missed:
| Event | Score Change | |-------|-------------| | Verified computation (correct) | +0.01 (capped at 1.0) | | Failed verification (incorrect) | -0.10 | | Slashed (malicious) | -0.25 | | Task timeout (no result) | -0.05 | | Heartbeat missed | -0.02 | | Consistent uptime (30 days) | +0.05 bonus |
A node below the threshold for an action is not a low-ranked candidate for it, it is not a candidate at all:
| Action | Minimum Score | |--------|--------------| | Accept compute tasks | 0.3 | | Accept high-priority tasks | 0.7 | | Serve as validator | 0.8 | | Produce blocks | 0.9 |
New nodes start at 0.5 and must earn the rest through honest participation. Reputation is not transferable and is not purchasable with stake; stake bounds the loss from misbehavior, reputation records it.
Hanzo Node exposes a JSON-RPC 2.0 API on port 8545 (configurable). The API is divided into namespaces.
// Submit a compute request
{
"jsonrpc": "2.0",
"method": "hanzo_submitCompute",
"params": [{
"job_type": "inference",
"model_id": "zen-72b",
"input": "base64-encoded-prompt-tokens",
"max_fee": "1000000",
"gpu_requirements": {
"min_vram_mb": 40000,
"min_tflops_fp16": 100.0
},
"latency_budget_ms": 5000
}],
"id": 1
}
// Response
{
"jsonrpc": "2.0",
"result": {
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "pending",
"estimated_wait_ms": 200
},
"id": 1
}
// Query job status
{
"jsonrpc": "2.0",
"method": "hanzo_getJobStatus",
"params": ["550e8400-e29b-41d4-a716-446655440000"],
"id": 2
}
// Get network GPU inventory
{
"jsonrpc": "2.0",
"method": "hanzo_getInventory",
"params": [{"min_vram_mb": 24000, "backend": "cuda"}],
"id": 3
}
// Get compute statistics
{
"jsonrpc": "2.0",
"method": "hanzo_getStats",
"params": [],
"id": 4
}
// Get latest block
{"jsonrpc": "2.0", "method": "chain_getBlock", "params": ["latest"], "id": 1}
// Get block by height
{"jsonrpc": "2.0", "method": "chain_getBlock", "params": [12345], "id": 2}
// Get transaction by hash
{"jsonrpc": "2.0", "method": "chain_getTransaction", "params": ["0xabc..."], "id": 3}
// Get account balance ($AI tokens)
{"jsonrpc": "2.0", "method": "chain_getBalance", "params": ["0xdef..."], "id": 4}
// Get current validator set
{"jsonrpc": "2.0", "method": "chain_getValidators", "params": [], "id": 5}
// Get connected peer count
{"jsonrpc": "2.0", "method": "net_peerCount", "params": [], "id": 1}
// Get node's peer ID and multiaddresses
{"jsonrpc": "2.0", "method": "net_localInfo", "params": [], "id": 2}
// Get gossipsub mesh health per topic
{"jsonrpc": "2.0", "method": "net_meshHealth", "params": [], "id": 3}
Nodes are configured via a YAML file. All fields have sensible defaults.
# hanzo-node.yaml
# Node identity
identity:
# Path to Ed25519 keypair (generated on first run if absent)
keypair: /var/lib/hanzo-node/keypair.json
# Network configuration
network:
# Listen addresses
listen:
- /ip4/0.0.0.0/tcp/9000
- /ip6/::/tcp/9000
# Target peer count
target_peers: 50
# Maximum peer count
max_peers: 256
# Bootstrap nodes (default: Hanzo-operated)
bootstrap:
- /dns4/boot1.hanzo.ai/tcp/9000/p2p/12D3KooW...
- /dns4/boot2.hanzo.ai/tcp/9000/p2p/12D3KooW...
# Enable mDNS for local discovery
mdns: true
# Consensus configuration
consensus:
# Role: validator, provider, or light
role: validator
# Minimum stake to produce blocks (in $AI base units)
min_stake: 2000000000000000000000 # 2000 AI
# Block interval in milliseconds
block_interval_ms: 200
# PoAI verification subset ratio (0.0 to 1.0)
verification_ratio: 0.10
# Compute configuration (for providers)
compute:
# Enable compute provider mode
enabled: false
# GPU devices to expose
gpus: auto # auto-detect, or list specific device indices
# Models to preload into VRAM
preload_models:
- zen-8b-instruct
- zen-72b
# Maximum concurrent jobs
max_concurrent_jobs: 4
# Fee rate in $AI per TFLOP
fee_per_tflop: 100
# Storage configuration
storage:
# RocksDB data directory
data_dir: /var/lib/hanzo-node/data
# State pruning (keep last N blocks of full state)
state_pruning: 10000
# Block retention (keep last N blocks)
block_retention: 100000
# RPC configuration
rpc:
# Enable JSON-RPC API
enabled: true
# Listen address
listen: 127.0.0.1:8545
# Allowed origins (CORS)
cors: ["*"]
# Rate limit (requests per second per IP)
rate_limit: 100
# Settlement configuration (Lux L1 bridge)
settlement:
# Lux RPC endpoint
lux_rpc: https://api.lux.network/ext/bc/C/rpc
# Checkpoint interval (in Hanzo blocks)
checkpoint_interval: 100 # ~20 seconds
# Bridge contract address on Lux
bridge_contract: "0x..."
# Observability
metrics:
# Prometheus metrics endpoint
enabled: true
listen: 0.0.0.0:9090
Hanzo Node settles economic state to Lux L1 via periodic checkpoints. This provides Lux-level finality for $AI token transfers and compute payments.
Every checkpoint_interval blocks (default 100, approximately 20 seconds), the block producer constructs a checkpoint.
#[derive(Serialize, Deserialize)]
pub struct LuxCheckpoint {
/// Hanzo block height at checkpoint
pub hanzo_height: u64,
/// State root at this height
pub state_root: [u8; 32],
/// Merkle root of all payments since last checkpoint
pub payments_root: [u8; 32],
/// Total $AI transferred in this checkpoint period
pub total_volume: u64,
/// Number of compute jobs completed
pub jobs_completed: u32,
/// Validator signatures (2/3+ threshold)
pub signatures: Vec<ValidatorSignature>,
}
The checkpoint is submitted to a bridge contract on Lux C-Chain using the luxfi Rust SDK.
use luxfi_sdk::bridge::{BridgeClient, CheckpointSubmission};
pub async fn submit_checkpoint(
checkpoint: &LuxCheckpoint,
lux_rpc: &str,
bridge_contract: &str,
) -> Result<TxHash, BridgeError> {
let client = BridgeClient::new(lux_rpc, bridge_contract)?;
let submission = CheckpointSubmission {
hanzo_height: checkpoint.hanzo_height,
state_root: checkpoint.state_root,
payments_root: checkpoint.payments_root,
total_volume: checkpoint.total_volume,
signatures: &checkpoint.signatures,
};
client.submit_checkpoint(submission).await
}
The bridge contract on Lux verifies that 2/3+ of Hanzo validators signed the checkpoint before accepting it. Once accepted, the checkpoint is final on Lux L1 and cannot be reverted.
The LLM Gateway routes inference requests to the Hanzo Node network when decentralized compute is selected as a backend.
Client Request
|
v
LLM Gateway (port 4000)
|
|-- Provider: openai --> OpenAI API
|-- Provider: anthropic --> Anthropic API
|-- Provider: hanzo --> Hanzo Node RPC (hanzo_submitCompute)
| |
| v
| Gossipsub propagation
| |
| v
| Compute provider executes
| |
| v
| Result returned via gossipsub
|
v
Client Response (streaming)
Compute pricing is determined by the Hamiltonian Market Maker. Providers set base fee rates, but actual prices are adjusted by HMM based on real-time supply and demand across the GPU inventory.
Provider fee_per_tflop: 100 $AI
Network utilization: 85%
HMM price adjustment: 1.4x
Effective price: 140 $AI per TFLOP
When network utilization is low, HMM reduces effective prices to attract demand. When utilization is high, prices increase to incentivize new providers to join.
Candle is used in two contexts within Hanzo Node:
All economic settlement uses luxfi packages. The node never imports go-ethereum or ava-labs libraries.
# Cargo.toml dependencies for Lux integration
[dependencies]
luxfi-sdk = "1.4"
luxfi-bridge = "1.2"
luxfi-types = "1.1"
Staking 2000 $AI to become a validator provides economic Sybil resistance. Creating multiple validator identities requires proportionally more capital.
libp2p's Kademlia DHT is susceptible to eclipse attacks where an adversary surrounds a target node with malicious peers. Mitigations:
A dishonest compute provider could return incorrect inference results. PoAI subset verification detects this with high probability:
The verification ratio can be increased for high-value computations at the cost of higher validator CPU usage.
Rate limiting at multiple layers:
The Lux bridge contract requires 2/3+ validator signatures on each checkpoint. A compromised minority of validators cannot submit false checkpoints. The bridge contract on Lux L1 validates signature thresholds before accepting state updates.
{
"height": 0,
"timestamp": 1706140800000,
"prev_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"tx_merkle_root": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"compute_merkle_root": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"state_root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"ai_proof": {
"inference_count": 0,
"input_hashes": [],
"output_hashes": [],
"model_ids": [],
"total_flops": 0
}
}
The SHA-256 hash of the canonical JSON encoding of this genesis block header is the genesis hash for the network. The tx_merkle_root and compute_merkle_root are the SHA-256 hash of the empty byte string (no transactions or results in genesis). The state_root is the hash of an empty Merkle Patricia Trie.
{
"provider": "12D3KooWProviderPeerIdBase58Encoded",
"gpus": [
{
"gpu_id": "gpu-0",
"model": "NVIDIA H100 SXM5 80GB",
"vram_total_mb": 81920,
"vram_free_mb": 65536,
"tflops_fp16": 989.5,
"tflops_int8": 1979.0,
"compute_capability": 9.0,
"backend": "Cuda"
}
],
"utilization": 0.20,
"loaded_models": ["zen-72b"],
"bandwidth_mbps": 10000,
"region": "US"
}
Repository: github.com/hanzoai/node
Crate Structure:
hanzo-node -- Binary entry point and CLIhanzo-consensus -- PoAI consensus enginehanzo-network -- libp2p networking layerhanzo-storage -- RocksDB state managementhanzo-rpc -- JSON-RPC API serverhanzo-bridge -- Lux L1 settlement bridge (luxfi packages)hanzo-types -- Shared types (blocks, transactions, inventory)Build and Run:
# Build
cargo build --release
# Start a validator node
./target/release/hanzo-node \
--config hanzo-node.yaml \
--role validator
# Start a compute provider node
./target/release/hanzo-node \
--config hanzo-node.yaml \
--role provider \
--gpus auto
# Start a light client (no block production, RPC only)
./target/release/hanzo-node \
--config hanzo-node.yaml \
--role light
Default Ports:
Copyright and related rights waived via CC0.