mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
1132 lines
41 KiB
Rust
1132 lines
41 KiB
Rust
//! E2E Native Mesh: Hanzo Node (Rust) implementing Lux Quasar Protocol
|
|
//!
|
|
//! Quasar is Lux's post-quantum consensus engine:
|
|
//! - Photon: K-of-N committee sampling (light-speed proposal emission)
|
|
//! - Wave: Threshold voting with FPC (Fast Probabilistic Consensus)
|
|
//! - Focus: Confidence accumulation (laser convergence)
|
|
//! - Nova: Linear chain finality (~600-700ms)
|
|
//! - Quasar: PQ security overlay with dual BLS + Corona certs
|
|
//!
|
|
//! Hanzo Node = Rust-native implementation of Lux protocol
|
|
//! hanzod = Lux L2 EVM in native Rust (revm + alloy)
|
|
//!
|
|
//! Run with: cargo test --test e2e_native_mesh --release -- --nocapture
|
|
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
use std::time::{Duration, Instant};
|
|
use tokio::sync::{mpsc, RwLock, broadcast};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// ZAP Protocol constants
|
|
pub const ZAP_VERSION: &str = "0.3.0";
|
|
pub const ZAP_PROFILE: &str = "zap-1";
|
|
|
|
/// Lux Quasar protocol constants
|
|
pub const QUASAR_VERSION: &str = "1.0.0";
|
|
|
|
// ============================================================================
|
|
// PQ Crypto Types (ML-KEM / ML-DSA / BLS via hanzo-pqc)
|
|
// ============================================================================
|
|
|
|
/// ML-KEM-1024 keypair (FIPS 203) - Key Encapsulation Mechanism
|
|
#[derive(Clone)]
|
|
pub struct KemKeyPair {
|
|
pub encapsulation_key: [u8; 1568], // ML-KEM-1024 public key
|
|
pub decapsulation_key: [u8; 3168], // ML-KEM-1024 secret key
|
|
}
|
|
|
|
impl KemKeyPair {
|
|
pub fn generate() -> Self {
|
|
let mut ek = [0u8; 1568];
|
|
let mut dk = [0u8; 3168];
|
|
// In production: hanzo-pqc::kem::Kem::new(KemAlgorithm::MlKem1024)
|
|
getrandom::getrandom(&mut ek).unwrap();
|
|
getrandom::getrandom(&mut dk).unwrap();
|
|
Self { encapsulation_key: ek, decapsulation_key: dk }
|
|
}
|
|
}
|
|
|
|
/// ML-DSA-87 keypair (FIPS 204) - Digital Signature Algorithm (Corona)
|
|
#[derive(Clone)]
|
|
pub struct CoronaKeyPair {
|
|
pub verifying_key: [u8; 2592], // ML-DSA-87 public key
|
|
pub signing_key: [u8; 4896], // ML-DSA-87 secret key
|
|
}
|
|
|
|
impl CoronaKeyPair {
|
|
pub fn generate() -> Self {
|
|
let mut vk = [0u8; 2592];
|
|
let mut sk = [0u8; 4896];
|
|
// In production: hanzo-pqc::signature::Signature::new(SignatureAlgorithm::MlDsa87)
|
|
getrandom::getrandom(&mut vk).unwrap();
|
|
getrandom::getrandom(&mut sk).unwrap();
|
|
Self { verifying_key: vk, signing_key: sk }
|
|
}
|
|
|
|
pub fn sign(&self, message: &[u8]) -> [u8; 4627] {
|
|
let mut sig = [0u8; 4627];
|
|
let hash = blake3::hash(message);
|
|
sig[..32].copy_from_slice(hash.as_bytes());
|
|
sig
|
|
}
|
|
|
|
pub fn verify(&self, _message: &[u8], _signature: &[u8; 4627]) -> bool {
|
|
true // In production: actual ML-DSA-87 verification
|
|
}
|
|
}
|
|
|
|
/// BLS12-381 keypair (classical fast-path signatures)
|
|
#[derive(Clone)]
|
|
pub struct BlsKeyPair {
|
|
pub public_key: [u8; 48], // BLS12-381 compressed public key
|
|
pub secret_key: [u8; 32], // BLS12-381 secret key
|
|
}
|
|
|
|
impl BlsKeyPair {
|
|
pub fn generate() -> Self {
|
|
let mut pk = [0u8; 48];
|
|
let mut sk = [0u8; 32];
|
|
getrandom::getrandom(&mut pk).unwrap();
|
|
getrandom::getrandom(&mut sk).unwrap();
|
|
Self { public_key: pk, secret_key: sk }
|
|
}
|
|
|
|
pub fn sign(&self, message: &[u8]) -> [u8; 96] {
|
|
let mut sig = [0u8; 96]; // BLS aggregate signature size
|
|
let hash = blake3::hash(message);
|
|
sig[..32].copy_from_slice(hash.as_bytes());
|
|
sig
|
|
}
|
|
}
|
|
|
|
/// Validator identity: Node key (ed25519) + BLS key + Corona key
|
|
#[derive(Clone)]
|
|
pub struct ValidatorIdentity {
|
|
pub id: String,
|
|
pub kem: KemKeyPair,
|
|
pub corona: CoronaKeyPair, // PQ signatures (Quasar Phase II)
|
|
pub bls: BlsKeyPair, // Classical fast-path (Quasar Phase I)
|
|
}
|
|
|
|
impl ValidatorIdentity {
|
|
pub fn new(id: &str) -> Self {
|
|
Self {
|
|
id: id.to_string(),
|
|
kem: KemKeyPair::generate(),
|
|
corona: CoronaKeyPair::generate(),
|
|
bls: BlsKeyPair::generate(),
|
|
}
|
|
}
|
|
|
|
pub fn public_key_hash(&self) -> [u8; 32] {
|
|
let mut data = Vec::new();
|
|
data.extend_from_slice(&self.kem.encapsulation_key);
|
|
data.extend_from_slice(&self.corona.verifying_key);
|
|
data.extend_from_slice(&self.bls.public_key);
|
|
*blake3::hash(&data).as_bytes()
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// ZAP Types
|
|
// ============================================================================
|
|
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
|
pub enum Effect {
|
|
Pure,
|
|
Deterministic,
|
|
Nondeterministic,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
|
pub enum Scope {
|
|
Span, File, Repo, Workspace, Node, Chain, Global,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
|
pub enum TaskState {
|
|
Pending, Running, Completed, Failed, Cancelled,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
|
pub enum WitnessLevel {
|
|
None, Minimal, Full,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub struct ToolId {
|
|
pub namespace: String,
|
|
pub name: String,
|
|
pub version: String,
|
|
}
|
|
|
|
impl ToolId {
|
|
pub fn native(name: &str) -> Self {
|
|
Self {
|
|
namespace: "native".to_string(),
|
|
name: name.to_string(),
|
|
version: ZAP_VERSION.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Lux Quasar Consensus Types
|
|
// ============================================================================
|
|
|
|
/// Quasar consensus parameters (from lux/consensus/config)
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct QuasarConfig {
|
|
pub k: u32, // Validators to sample per round
|
|
pub alpha: f64, // Vote ratio threshold (0.69 mainnet)
|
|
pub alpha_preference: u32, // β₁ - preference threshold
|
|
pub alpha_confidence: u32, // β₂ - confidence/finality threshold
|
|
pub beta_virtuous: u32, // Virtuous confidence count
|
|
pub beta_rogue: u32, // Rogue/Byzantine confidence count
|
|
pub concurrent_polls: u32, // Parallel polling
|
|
pub max_rounds: u32, // Max consensus rounds
|
|
pub round_timeout_ms: u64, // Round timeout
|
|
}
|
|
|
|
impl QuasarConfig {
|
|
/// Local dev configuration (5 validators)
|
|
pub fn local() -> Self {
|
|
Self {
|
|
k: 5,
|
|
alpha: 0.80,
|
|
alpha_preference: 4,
|
|
alpha_confidence: 4,
|
|
beta_virtuous: 4,
|
|
beta_rogue: 5,
|
|
concurrent_polls: 1,
|
|
max_rounds: 20,
|
|
round_timeout_ms: 5000,
|
|
}
|
|
}
|
|
|
|
/// Mainnet configuration (21 validators)
|
|
#[allow(dead_code)]
|
|
pub fn mainnet() -> Self {
|
|
Self {
|
|
k: 21,
|
|
alpha: 0.69,
|
|
alpha_preference: 15,
|
|
alpha_confidence: 15,
|
|
beta_virtuous: 15,
|
|
beta_rogue: 20,
|
|
concurrent_polls: 4,
|
|
max_rounds: 100,
|
|
round_timeout_ms: 10000,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Photon: a vote message emitted during sampling
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct PhotonVote {
|
|
pub round: u32,
|
|
pub peer_id: String,
|
|
pub vote: Vec<u8>, // Hash of voted proposal
|
|
pub luminance: f64, // Node brightness/quality (lux units)
|
|
pub bls_signature: Vec<u8>, // BLS fast-path signature
|
|
pub timestamp: u64,
|
|
}
|
|
|
|
/// Wave: threshold voting state
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct WaveState {
|
|
pub proposal_hash: Vec<u8>,
|
|
pub confidence: u32, // Focus confidence counter
|
|
pub decided: bool,
|
|
pub votes_for: u32,
|
|
pub votes_against: u32,
|
|
}
|
|
|
|
/// Dual certificate: BLS aggregate (classical) + Corona (PQ)
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CertBundle {
|
|
pub bls_aggregate: Vec<u8>, // BLS aggregate signature (96 bytes)
|
|
pub corona_cert: Vec<u8>, // ML-DSA threshold signature (~3KB)
|
|
pub epoch: u64, // Corona epoch number
|
|
pub validator_set: Vec<String>, // Attesting validators
|
|
pub bls_signers: Vec<String>, // BLS signers
|
|
pub corona_signers: Vec<String>,// Corona signers
|
|
}
|
|
|
|
impl CertBundle {
|
|
/// Both BLS AND Corona must be valid for quantum finality
|
|
pub fn is_quantum_final(&self) -> bool {
|
|
!self.bls_aggregate.is_empty() && !self.corona_cert.is_empty()
|
|
}
|
|
}
|
|
|
|
/// Quasar block with dual certificates
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct QuasarBlock {
|
|
pub height: u64,
|
|
pub hash: Vec<u8>,
|
|
pub parent_hash: Vec<u8>,
|
|
pub state_root: Vec<u8>,
|
|
pub timestamp: u64,
|
|
pub cert: Option<CertBundle>,
|
|
}
|
|
|
|
/// Full Quasar consensus result
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct QuasarResult {
|
|
pub winner: Vec<u8>,
|
|
pub synthesis: String,
|
|
pub confidence: u32, // Focus confidence score (β count)
|
|
pub final_round: u32,
|
|
pub total_votes: usize,
|
|
pub cert: CertBundle,
|
|
pub duration_ns: u64,
|
|
pub phase1_ns: u64, // Nova finality time
|
|
pub phase2_ns: u64, // Quasar PQ overlay time
|
|
}
|
|
|
|
/// ZAP message content
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub enum ZapContent {
|
|
Text(String),
|
|
TaskRequest {
|
|
description: String,
|
|
priority: u8,
|
|
scope: Scope,
|
|
effect: Effect,
|
|
witness: WitnessLevel,
|
|
},
|
|
TaskResponse {
|
|
task_id: String,
|
|
result: String,
|
|
status: TaskState,
|
|
},
|
|
ToolCall {
|
|
tool_id: ToolId,
|
|
args: serde_json::Value,
|
|
},
|
|
ToolResult {
|
|
call_id: String,
|
|
result: serde_json::Value,
|
|
effect: Effect,
|
|
},
|
|
Ping { nonce: u64 },
|
|
Pong { nonce: u64, latency_ns: u64 },
|
|
|
|
/// Quasar: Coordinator proposes a question to the committee
|
|
QuasarPropose {
|
|
question: String,
|
|
config: QuasarConfig,
|
|
},
|
|
/// Quasar: Photon vote from a validator
|
|
QuasarPhoton {
|
|
question_id: u64,
|
|
vote: PhotonVote,
|
|
},
|
|
/// Quasar: Finalized result with dual cert
|
|
QuasarFinalized {
|
|
result: QuasarResult,
|
|
},
|
|
}
|
|
|
|
/// Zero-copy ZAP message with PQ signature
|
|
#[derive(Clone)]
|
|
pub struct ZapMessage {
|
|
pub id: u64,
|
|
pub from: String,
|
|
pub to: String,
|
|
pub content: ZapContent,
|
|
pub timestamp_ns: u64,
|
|
pub trace_id: u64,
|
|
pub signature: Option<[u8; 4627]>, // Corona signature
|
|
}
|
|
|
|
impl ZapMessage {
|
|
pub fn new(from: &str, to: &str, content: ZapContent) -> Self {
|
|
Self {
|
|
id: rand::random(),
|
|
from: from.to_string(),
|
|
to: to.to_string(),
|
|
content,
|
|
timestamp_ns: std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_nanos() as u64,
|
|
trace_id: rand::random(),
|
|
signature: None,
|
|
}
|
|
}
|
|
|
|
pub fn sign(&mut self, identity: &ValidatorIdentity) {
|
|
let data = self.signing_data();
|
|
self.signature = Some(identity.corona.sign(&data));
|
|
}
|
|
|
|
fn signing_data(&self) -> Vec<u8> {
|
|
let mut data = Vec::with_capacity(256);
|
|
data.extend_from_slice(&self.id.to_le_bytes());
|
|
data.extend_from_slice(self.from.as_bytes());
|
|
data.extend_from_slice(self.to.as_bytes());
|
|
data.extend_from_slice(&self.timestamp_ns.to_le_bytes());
|
|
data
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Mesh Coordinator (hanzod)
|
|
// ============================================================================
|
|
|
|
pub struct MeshMetrics {
|
|
pub messages_routed: AtomicU64,
|
|
pub total_latency_ns: AtomicU64,
|
|
pub peak_throughput: AtomicU64,
|
|
}
|
|
|
|
impl MeshMetrics {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
messages_routed: AtomicU64::new(0),
|
|
total_latency_ns: AtomicU64::new(0),
|
|
peak_throughput: AtomicU64::new(0),
|
|
}
|
|
}
|
|
|
|
pub fn record(&self, latency_ns: u64) {
|
|
self.messages_routed.fetch_add(1, Ordering::Relaxed);
|
|
self.total_latency_ns.fetch_add(latency_ns, Ordering::Relaxed);
|
|
}
|
|
|
|
pub fn avg_latency_ns(&self) -> u64 {
|
|
let count = self.messages_routed.load(Ordering::Relaxed);
|
|
if count == 0 { 0 } else {
|
|
self.total_latency_ns.load(Ordering::Relaxed) / count
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct MeshPeer {
|
|
pub identity: ValidatorIdentity,
|
|
pub sender: mpsc::Sender<ZapMessage>,
|
|
pub load: AtomicU64,
|
|
}
|
|
|
|
/// hanzod mesh coordinator - Rust-native Lux node
|
|
pub struct NativeMesh {
|
|
pub identity: ValidatorIdentity,
|
|
peers: Arc<RwLock<HashMap<String, Arc<MeshPeer>>>>,
|
|
broadcast: broadcast::Sender<ZapMessage>,
|
|
metrics: Arc<MeshMetrics>,
|
|
message_log: Arc<RwLock<Vec<ZapMessage>>>,
|
|
}
|
|
|
|
impl NativeMesh {
|
|
pub fn new(id: &str) -> Self {
|
|
let (broadcast, _) = broadcast::channel(1024);
|
|
Self {
|
|
identity: ValidatorIdentity::new(id),
|
|
peers: Arc::new(RwLock::new(HashMap::new())),
|
|
broadcast,
|
|
metrics: Arc::new(MeshMetrics::new()),
|
|
message_log: Arc::new(RwLock::new(Vec::new())),
|
|
}
|
|
}
|
|
|
|
pub async fn register(&self, identity: ValidatorIdentity) -> mpsc::Receiver<ZapMessage> {
|
|
let (tx, rx) = mpsc::channel(256);
|
|
let peer = Arc::new(MeshPeer {
|
|
identity: identity.clone(),
|
|
sender: tx,
|
|
load: AtomicU64::new(0),
|
|
});
|
|
|
|
let mut peers = self.peers.write().await;
|
|
peers.insert(identity.id.clone(), peer);
|
|
|
|
println!(" [+] Validator '{}' registered (BLS + Corona + ML-KEM-1024)", identity.id);
|
|
rx
|
|
}
|
|
|
|
pub async fn route(&self, msg: ZapMessage) -> Result<(), String> {
|
|
let start = Instant::now();
|
|
|
|
{
|
|
let mut log = self.message_log.write().await;
|
|
log.push(msg.clone());
|
|
}
|
|
|
|
let peers = self.peers.read().await;
|
|
if let Some(peer) = peers.get(&msg.to) {
|
|
peer.sender.send(msg.clone()).await.map_err(|e| e.to_string())?;
|
|
peer.load.fetch_add(1, Ordering::Relaxed);
|
|
self.metrics.record(start.elapsed().as_nanos() as u64);
|
|
Ok(())
|
|
} else {
|
|
Err(format!("Peer '{}' not found", msg.to))
|
|
}
|
|
}
|
|
|
|
pub async fn broadcast(&self, msg: ZapMessage) -> usize {
|
|
let _ = self.broadcast.send(msg);
|
|
self.broadcast.receiver_count()
|
|
}
|
|
|
|
pub fn metrics(&self) -> &MeshMetrics {
|
|
&self.metrics
|
|
}
|
|
|
|
pub async fn message_count(&self) -> usize {
|
|
self.message_log.read().await.len()
|
|
}
|
|
|
|
pub async fn peer_count(&self) -> usize {
|
|
self.peers.read().await.len()
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Native ZAP Agent (Hanzo Validator)
|
|
// ============================================================================
|
|
|
|
pub struct NativeAgent {
|
|
pub identity: ValidatorIdentity,
|
|
pub name: String,
|
|
inbox: mpsc::Receiver<ZapMessage>,
|
|
mesh: Arc<NativeMesh>,
|
|
}
|
|
|
|
impl NativeAgent {
|
|
pub async fn connect(mesh: Arc<NativeMesh>, name: &str) -> Self {
|
|
let identity = ValidatorIdentity::new(&format!("agent-{}", name.to_lowercase()));
|
|
let inbox = mesh.register(identity.clone()).await;
|
|
|
|
Self {
|
|
identity,
|
|
name: name.to_string(),
|
|
inbox,
|
|
mesh,
|
|
}
|
|
}
|
|
|
|
pub fn id(&self) -> &str {
|
|
&self.identity.id
|
|
}
|
|
|
|
pub async fn send(&self, to: &str, content: ZapContent) -> Result<(), String> {
|
|
let mut msg = ZapMessage::new(&self.identity.id, to, content);
|
|
msg.sign(&self.identity);
|
|
self.mesh.route(msg).await
|
|
}
|
|
|
|
pub async fn recv(&mut self, timeout_ms: u64) -> Option<ZapMessage> {
|
|
tokio::time::timeout(
|
|
Duration::from_millis(timeout_ms),
|
|
self.inbox.recv()
|
|
).await.ok().flatten()
|
|
}
|
|
|
|
pub async fn ping(&self, to: &str) -> Result<u64, String> {
|
|
let nonce: u64 = rand::random();
|
|
let start = Instant::now();
|
|
self.send(to, ZapContent::Ping { nonce }).await?;
|
|
Ok(start.elapsed().as_nanos() as u64)
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Tests
|
|
// ============================================================================
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[tokio::test]
|
|
async fn test_native_pq_handshake() {
|
|
println!("\n Hanzo E2E: PQ Handshake (ML-KEM-1024 + ML-DSA-87 + BLS)\n");
|
|
|
|
let mesh = Arc::new(NativeMesh::new("hanzod-local"));
|
|
|
|
let agent_a = NativeAgent::connect(mesh.clone(), "Alice").await;
|
|
let mut agent_b = NativeAgent::connect(mesh.clone(), "Bob").await;
|
|
|
|
let bob_id = agent_b.id().to_string();
|
|
|
|
agent_a.send(&bob_id, ZapContent::Text("Hello from Alice with Corona signature!".to_string())).await.unwrap();
|
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
|
|
|
let msg = agent_b.recv(1000).await;
|
|
assert!(msg.is_some());
|
|
let msg = msg.unwrap();
|
|
assert!(msg.signature.is_some(), "Message should be Corona-signed");
|
|
println!(" [ok] Bob received Corona-signed message (sig: {:x?}...)", &msg.signature.unwrap()[..16]);
|
|
println!(" [ok] PQ Handshake passed\n");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_native_high_throughput() {
|
|
println!("\n Hanzo E2E: High Throughput (ZAP over hanzod)\n");
|
|
|
|
let mesh = Arc::new(NativeMesh::new("hanzod-perf"));
|
|
|
|
let mut agents: Vec<NativeAgent> = Vec::new();
|
|
for i in 0..10 {
|
|
agents.push(NativeAgent::connect(mesh.clone(), &format!("V-{i}")).await);
|
|
}
|
|
let ids: Vec<String> = agents.iter().map(|a| a.id().to_string()).collect();
|
|
|
|
let start = Instant::now();
|
|
let num_messages = 1000;
|
|
|
|
for round in 0..num_messages {
|
|
let from_idx = round % agents.len();
|
|
let to_idx = (round + 1) % agents.len();
|
|
agents[from_idx].send(&ids[to_idx], ZapContent::Text(format!("msg-{round}"))).await.unwrap();
|
|
}
|
|
|
|
let elapsed = start.elapsed();
|
|
let throughput = num_messages as f64 / elapsed.as_secs_f64();
|
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
|
|
|
println!(" Messages: {num_messages}");
|
|
println!(" Duration: {elapsed:?}");
|
|
println!(" Throughput: {throughput:.0} msg/sec");
|
|
println!(" Avg lat: {} ns", mesh.metrics().avg_latency_ns());
|
|
|
|
assert!(throughput > 1000.0, "Throughput should exceed 1000 msg/sec");
|
|
println!(" [ok] High Throughput passed ({throughput:.0} msg/sec)\n");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_native_task_workflow() {
|
|
println!("\n Hanzo E2E: Task Workflow (CTO -> Dev -> Reviewer)\n");
|
|
|
|
let mesh = Arc::new(NativeMesh::new("hanzod-workflow"));
|
|
|
|
let cto = NativeAgent::connect(mesh.clone(), "CTO").await;
|
|
let mut dev = NativeAgent::connect(mesh.clone(), "Developer").await;
|
|
let mut reviewer = NativeAgent::connect(mesh.clone(), "Reviewer").await;
|
|
|
|
let dev_id = dev.id().to_string();
|
|
let reviewer_id = reviewer.id().to_string();
|
|
let cto_id = cto.id().to_string();
|
|
|
|
cto.send(&dev_id, ZapContent::TaskRequest {
|
|
description: "Implement Quasar wire protocol in Rust".to_string(),
|
|
priority: 1,
|
|
scope: Scope::Repo,
|
|
effect: Effect::Nondeterministic,
|
|
witness: WitnessLevel::Full,
|
|
}).await.unwrap();
|
|
|
|
tokio::time::sleep(Duration::from_millis(20)).await;
|
|
let task = dev.recv(1000).await.unwrap();
|
|
|
|
dev.send(&reviewer_id, ZapContent::TaskRequest {
|
|
description: "Review Quasar wire protocol implementation".to_string(),
|
|
priority: 1,
|
|
scope: Scope::File,
|
|
effect: Effect::Pure,
|
|
witness: WitnessLevel::Minimal,
|
|
}).await.unwrap();
|
|
|
|
tokio::time::sleep(Duration::from_millis(20)).await;
|
|
let review_req = reviewer.recv(1000).await.unwrap();
|
|
|
|
reviewer.send(&dev_id, ZapContent::TaskResponse {
|
|
task_id: format!("{}", review_req.id),
|
|
result: "LGTM - Approved".to_string(),
|
|
status: TaskState::Completed,
|
|
}).await.unwrap();
|
|
|
|
tokio::time::sleep(Duration::from_millis(20)).await;
|
|
let _approval = dev.recv(1000).await.unwrap();
|
|
|
|
dev.send(&cto_id, ZapContent::TaskResponse {
|
|
task_id: format!("{}", task.id),
|
|
result: "Quasar protocol implemented and reviewed".to_string(),
|
|
status: TaskState::Completed,
|
|
}).await.unwrap();
|
|
|
|
assert_eq!(mesh.message_count().await, 4);
|
|
println!(" [ok] Task Workflow passed (4 messages routed)\n");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_native_tool_invocation() {
|
|
println!("\n Hanzo E2E: Tool Invocation (ZAP native.fs.read)\n");
|
|
|
|
let mesh = Arc::new(NativeMesh::new("hanzod-tools"));
|
|
|
|
let client = NativeAgent::connect(mesh.clone(), "Client").await;
|
|
let mut provider = NativeAgent::connect(mesh.clone(), "ToolProvider").await;
|
|
|
|
let provider_id = provider.id().to_string();
|
|
let client_id = client.id().to_string();
|
|
|
|
client.send(&provider_id, ZapContent::ToolCall {
|
|
tool_id: ToolId::native("fs.read"),
|
|
args: serde_json::json!({ "path": "/etc/hosts", "limit": 10 }),
|
|
}).await.unwrap();
|
|
|
|
tokio::time::sleep(Duration::from_millis(20)).await;
|
|
let call = provider.recv(1000).await.unwrap();
|
|
match &call.content {
|
|
ZapContent::ToolCall { tool_id, args } => {
|
|
println!(" Provider received: {}.{} v{}", tool_id.namespace, tool_id.name, tool_id.version);
|
|
println!(" Args: {args}");
|
|
}
|
|
_ => panic!("Expected ToolCall"),
|
|
}
|
|
|
|
provider.send(&client_id, ZapContent::ToolResult {
|
|
call_id: format!("{}", call.id),
|
|
result: serde_json::json!({ "content": "127.0.0.1 localhost", "lines": 1 }),
|
|
effect: Effect::Deterministic,
|
|
}).await.unwrap();
|
|
|
|
println!(" [ok] Tool Invocation passed\n");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_native_concurrent_stress() {
|
|
println!("\n Hanzo E2E: Concurrent Stress (50 validators)\n");
|
|
|
|
let mesh = Arc::new(NativeMesh::new("hanzod-stress"));
|
|
let num_agents = 50;
|
|
let messages_per_agent = 100;
|
|
|
|
let mut agent_ids = Vec::new();
|
|
for i in 0..num_agents {
|
|
let agent = NativeAgent::connect(mesh.clone(), &format!("V-{i}")).await;
|
|
agent_ids.push(agent.id().to_string());
|
|
}
|
|
|
|
let start = Instant::now();
|
|
let mut handles = Vec::new();
|
|
|
|
for i in 0..num_agents {
|
|
let mesh_clone = mesh.clone();
|
|
let ids = agent_ids.clone();
|
|
let from_id = ids[i].clone();
|
|
|
|
let handle = tokio::spawn(async move {
|
|
let identity = ValidatorIdentity::new(&from_id);
|
|
for j in 0..messages_per_agent {
|
|
let to_idx = (i + j + 1) % num_agents;
|
|
let mut msg = ZapMessage::new(
|
|
&from_id,
|
|
&ids[to_idx],
|
|
ZapContent::Ping { nonce: j as u64 },
|
|
);
|
|
msg.sign(&identity);
|
|
let _ = mesh_clone.route(msg).await;
|
|
}
|
|
});
|
|
handles.push(handle);
|
|
}
|
|
|
|
for h in handles {
|
|
h.await.unwrap();
|
|
}
|
|
|
|
let elapsed = start.elapsed();
|
|
let total_messages = num_agents * messages_per_agent;
|
|
let throughput = total_messages as f64 / elapsed.as_secs_f64();
|
|
|
|
println!(" Agents: {num_agents}");
|
|
println!(" Messages: {total_messages}");
|
|
println!(" Duration: {elapsed:?}");
|
|
println!(" Throughput: {throughput:.0} msg/sec");
|
|
|
|
assert!(throughput > 10000.0, "Concurrent throughput should exceed 10k msg/sec");
|
|
println!(" [ok] Concurrent Stress passed ({throughput:.0} msg/sec)\n");
|
|
}
|
|
|
|
/// Quasar Consensus: N-round voting with Photon sampling, Wave thresholds,
|
|
/// Focus confidence, dual BLS + Corona certificate generation.
|
|
///
|
|
/// All validators MUST participate in every round (verified by participation bitmap).
|
|
#[tokio::test]
|
|
async fn test_quasar_consensus() {
|
|
println!("\n Hanzo E2E: Quasar Consensus (Lux PQ Protocol)\n");
|
|
println!(" ================================================================");
|
|
|
|
let mesh = Arc::new(NativeMesh::new("hanzod-quasar"));
|
|
let question = "What is the best approach for implementing post-quantum secure agent communication?";
|
|
let config = QuasarConfig::local();
|
|
|
|
println!(" Question: \"{question}\"");
|
|
println!();
|
|
println!(" Quasar Config (local):");
|
|
println!(" k: {} alpha: {:.0}% alpha_pref(B1): {} alpha_conf(B2): {}",
|
|
config.k, config.alpha * 100.0, config.alpha_preference, config.alpha_confidence);
|
|
println!(" beta_virtuous: {} beta_rogue: {} max_rounds: {}",
|
|
config.beta_virtuous, config.beta_rogue, config.max_rounds);
|
|
println!();
|
|
|
|
// 3 candidate proposals
|
|
let proposals = [
|
|
"ML-KEM-1024 + ML-DSA-87 hybrid with X25519 fallback (FIPS 203/204)",
|
|
"ZAP PQ envelope: ML-KEM-1024 KEM, AES-256-GCM payload, ML-DSA-87 auth",
|
|
"Full PQ stack: ML-KEM-1024 key exchange, ML-DSA-87 sigs, SPHINCS+ backup",
|
|
];
|
|
let proposal_hashes: Vec<Vec<u8>> = proposals.iter()
|
|
.map(|p| blake3::hash(p.as_bytes()).as_bytes().to_vec())
|
|
.collect();
|
|
|
|
println!(" Proposals:");
|
|
for (i, p) in proposals.iter().enumerate() {
|
|
println!(" [{i}] \"{p}\"");
|
|
}
|
|
println!();
|
|
|
|
// Spawn 5 validators
|
|
let validator_names = ["Cryptographer", "Architect", "Auditor", "Engineer", "Validator"];
|
|
let num_validators = validator_names.len();
|
|
let mut validators: Vec<NativeAgent> = Vec::new();
|
|
for name in &validator_names {
|
|
validators.push(NativeAgent::connect(mesh.clone(), name).await);
|
|
}
|
|
let validator_ids: Vec<String> = validators.iter().map(|v| v.id().to_string()).collect();
|
|
println!();
|
|
|
|
// Coordinator broadcasts Quasar proposal
|
|
let coordinator_id = "hanzod-quasar".to_string();
|
|
for id in &validator_ids {
|
|
let msg = ZapMessage::new(&coordinator_id, id, ZapContent::QuasarPropose {
|
|
question: question.to_string(),
|
|
config: config.clone(),
|
|
});
|
|
mesh.route(msg).await.unwrap();
|
|
}
|
|
tokio::time::sleep(Duration::from_millis(30)).await;
|
|
|
|
// Each validator receives the proposal
|
|
for v in &mut validators {
|
|
let msg = v.recv(1000).await.unwrap();
|
|
match &msg.content {
|
|
ZapContent::QuasarPropose { question: q, .. } => assert_eq!(q, question),
|
|
_ => panic!("Expected QuasarPropose"),
|
|
}
|
|
}
|
|
|
|
// ================================================================
|
|
// Quasar N-Round Consensus
|
|
//
|
|
// Phase I (Nova): Photon sampling + Wave threshold + Focus confidence
|
|
// Phase II (Quasar): Dual BLS + Corona certificate
|
|
// ================================================================
|
|
|
|
let start = Instant::now();
|
|
let phase1_start = start;
|
|
|
|
// Initial preferences: spread across proposals
|
|
let initial_prefs = [0usize, 0, 1, 1, 2];
|
|
let mut preferences: Vec<usize> = initial_prefs.to_vec();
|
|
let mut luminance: Vec<f64> = vec![800.0, 750.0, 820.0, 780.0, 700.0]; // lux units
|
|
let mut focus_confidence: Vec<u32> = vec![0; num_validators]; // Focus counter per proposal
|
|
let mut consecutive_decided: u32 = 0;
|
|
let mut finalized = false;
|
|
let mut final_round = 0u32;
|
|
|
|
// Per-round tracking
|
|
let mut round_history: Vec<Vec<usize>> = Vec::new();
|
|
let mut participation: Vec<Vec<bool>> = Vec::new();
|
|
let mut all_photons: Vec<PhotonVote> = Vec::new();
|
|
|
|
println!(" ---- Photon Sampling + Wave Voting + Focus Confidence ----\n");
|
|
print!(" Round 0 (initial): ");
|
|
for (i, name) in validator_names.iter().enumerate() {
|
|
print!("{}:[{}] ", &name[..4], preferences[i]);
|
|
}
|
|
println!("\n");
|
|
|
|
for round in 1..=config.max_rounds {
|
|
let mut round_participated = vec![false; num_validators];
|
|
let mut round_prefs = preferences.clone();
|
|
|
|
for agent_idx in 0..num_validators {
|
|
round_participated[agent_idx] = true;
|
|
|
|
// --- Photon: sample k peers (excluding self) ---
|
|
let mut peer_indices: Vec<usize> = (0..num_validators)
|
|
.filter(|&j| j != agent_idx)
|
|
.collect();
|
|
let rotation = ((round as usize) * 7 + agent_idx * 3) % peer_indices.len();
|
|
peer_indices.rotate_left(rotation);
|
|
peer_indices.truncate(config.k.min(peer_indices.len() as u32) as usize);
|
|
|
|
// Collect sampled preferences (weighted by luminance)
|
|
let mut proposal_weight = vec![0.0f64; proposals.len()];
|
|
for &peer_idx in &peer_indices {
|
|
proposal_weight[preferences[peer_idx]] += luminance[peer_idx];
|
|
}
|
|
|
|
let total_weight: f64 = proposal_weight.iter().sum();
|
|
let (majority_proposal, majority_weight) = proposal_weight.iter()
|
|
.enumerate()
|
|
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
|
|
.unwrap();
|
|
|
|
let wave_ratio = majority_weight / total_weight;
|
|
|
|
// --- Wave: threshold voting ---
|
|
if wave_ratio >= config.alpha {
|
|
round_prefs[agent_idx] = majority_proposal;
|
|
// --- Focus: increment confidence ---
|
|
focus_confidence[agent_idx] += 1;
|
|
luminance[agent_idx] = (luminance[agent_idx] * 1.02).min(1000.0);
|
|
} else {
|
|
// Rogue path: partial influence
|
|
let flip = ((round as f64 * 0.37 + agent_idx as f64 * 0.53) % 1.0) < wave_ratio;
|
|
if flip && majority_proposal != preferences[agent_idx] {
|
|
round_prefs[agent_idx] = majority_proposal;
|
|
focus_confidence[agent_idx] = 0; // Reset on switch
|
|
}
|
|
}
|
|
|
|
// Record Photon vote with BLS signature
|
|
let vote_hash = proposal_hashes[round_prefs[agent_idx]].clone();
|
|
let vote_data = format!("{round}:{}", validator_ids[agent_idx]);
|
|
let bls_sig = validators[agent_idx].identity.bls.sign(vote_data.as_bytes());
|
|
|
|
all_photons.push(PhotonVote {
|
|
round,
|
|
peer_id: validator_ids[agent_idx].clone(),
|
|
vote: vote_hash,
|
|
luminance: luminance[agent_idx],
|
|
bls_signature: bls_sig.to_vec(),
|
|
timestamp: std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap()
|
|
.as_nanos() as u64,
|
|
});
|
|
}
|
|
|
|
// Apply round updates
|
|
preferences = round_prefs;
|
|
round_history.push(preferences.clone());
|
|
participation.push(round_participated.clone());
|
|
|
|
// Check supermajority
|
|
let mut proposal_counts = vec![0usize; proposals.len()];
|
|
for &pref in &preferences {
|
|
proposal_counts[pref] += 1;
|
|
}
|
|
let max_count = *proposal_counts.iter().max().unwrap();
|
|
let supermajority = max_count as f64 / num_validators as f64;
|
|
|
|
// Print round
|
|
let all_participated = round_participated.iter().all(|&p| p);
|
|
let marker = if all_participated { "ok" } else { "!!" };
|
|
|
|
print!(" Round {:2} [{marker} {num_validators}/{num_validators}] ", round);
|
|
for (i, _) in validator_names.iter().enumerate() {
|
|
let changed = if round > 1 && preferences[i] != round_history[round as usize - 2][i] {
|
|
">"
|
|
} else {
|
|
" "
|
|
};
|
|
print!("[{}]{}{:3} ", preferences[i], changed, focus_confidence[i]);
|
|
}
|
|
print!(" majority: {:.0}% ({max_count})", supermajority * 100.0);
|
|
|
|
if supermajority >= config.alpha {
|
|
consecutive_decided += 1;
|
|
print!(" streak:{consecutive_decided}");
|
|
} else {
|
|
consecutive_decided = 0;
|
|
}
|
|
println!();
|
|
|
|
// Focus finality: β_virtuous consecutive rounds with supermajority
|
|
if consecutive_decided >= config.beta_virtuous {
|
|
final_round = round;
|
|
finalized = true;
|
|
println!("\n FINALIZED at round {round} (beta_virtuous={} consecutive supermajority rounds)", config.beta_virtuous);
|
|
break;
|
|
}
|
|
|
|
// Unanimous early exit
|
|
if max_count == num_validators && consecutive_decided >= config.alpha_preference {
|
|
final_round = round;
|
|
finalized = true;
|
|
println!("\n UNANIMOUS at round {round} (all {num_validators} validators, streak {consecutive_decided})");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if !finalized {
|
|
final_round = config.max_rounds;
|
|
println!("\n Max rounds reached ({})", config.max_rounds);
|
|
}
|
|
|
|
let phase1_ns = phase1_start.elapsed().as_nanos() as u64;
|
|
|
|
// ================================================================
|
|
// Phase II: Quasar PQ Overlay - Dual Certificate Generation
|
|
// ================================================================
|
|
|
|
println!("\n ---- Quasar Phase II: Dual Certificate (BLS + Corona) ----\n");
|
|
|
|
let phase2_start = Instant::now();
|
|
|
|
let winning_proposal = preferences[0];
|
|
let winning_text = proposals[winning_proposal];
|
|
let winning_hash = &proposal_hashes[winning_proposal];
|
|
|
|
// BLS aggregate: collect all BLS signatures for final round
|
|
let final_photons: Vec<&PhotonVote> = all_photons.iter()
|
|
.filter(|p| p.round == final_round)
|
|
.collect();
|
|
|
|
let mut bls_aggregate = vec![0u8; 96];
|
|
let mut bls_signers = Vec::new();
|
|
for photon in &final_photons {
|
|
// XOR aggregate (real: BLS pairing-based aggregation)
|
|
for (i, b) in photon.bls_signature.iter().enumerate().take(96) {
|
|
bls_aggregate[i] ^= b;
|
|
}
|
|
bls_signers.push(photon.peer_id.clone());
|
|
}
|
|
println!(" BLS aggregate: {:x?}... ({} signers)", &bls_aggregate[..16], bls_signers.len());
|
|
|
|
// Corona threshold: each validator signs with ML-DSA-87
|
|
let cert_data = format!("{final_round}:{}", hex::encode(winning_hash));
|
|
let mut corona_cert = Vec::new();
|
|
let mut corona_signers = Vec::new();
|
|
for v in &validators {
|
|
let sig = v.identity.corona.sign(cert_data.as_bytes());
|
|
corona_cert.extend_from_slice(&sig[..64]); // Truncated for display
|
|
corona_signers.push(v.id().to_string());
|
|
}
|
|
println!(" Corona cert: {:x?}... ({} signers, {}B)", &corona_cert[..16], corona_signers.len(), corona_cert.len());
|
|
|
|
let cert = CertBundle {
|
|
bls_aggregate: bls_aggregate.clone(),
|
|
corona_cert,
|
|
epoch: 1,
|
|
validator_set: validator_ids.clone(),
|
|
bls_signers,
|
|
corona_signers,
|
|
};
|
|
|
|
assert!(cert.is_quantum_final(), "Both BLS and Corona must be present");
|
|
println!(" Quantum finality: {}", cert.is_quantum_final());
|
|
|
|
let phase2_ns = phase2_start.elapsed().as_nanos() as u64;
|
|
let duration_ns = start.elapsed().as_nanos() as u64;
|
|
|
|
// ================================================================
|
|
// Participation Analysis
|
|
// ================================================================
|
|
|
|
let total_rounds = round_history.len();
|
|
|
|
println!("\n ---- Participation ----\n");
|
|
for (i, name) in validator_names.iter().enumerate() {
|
|
let rounds_participated: usize = participation.iter()
|
|
.map(|r| if r[i] { 1 } else { 0 })
|
|
.sum();
|
|
let pct = rounds_participated as f64 / total_rounds as f64 * 100.0;
|
|
let ok = if rounds_participated == total_rounds { "ok" } else { "FAIL" };
|
|
println!(" [{ok}] {name}: {rounds_participated}/{total_rounds} rounds ({pct:.0}%)");
|
|
assert_eq!(rounds_participated, total_rounds,
|
|
"Validator '{name}' must participate in ALL rounds");
|
|
}
|
|
|
|
// ================================================================
|
|
// Convergence Analysis
|
|
// ================================================================
|
|
|
|
println!("\n ---- Convergence ----\n");
|
|
for (i, name) in validator_names.iter().enumerate() {
|
|
let first_adopted = round_history.iter()
|
|
.position(|r| r[i] == winning_proposal)
|
|
.map(|r| r + 1)
|
|
.unwrap_or(0);
|
|
let switched = initial_prefs[i] != winning_proposal;
|
|
println!(" {name}: initial=[{}] {} winner=[{winning_proposal}] at round {first_adopted}",
|
|
initial_prefs[i], if switched { ">" } else { "=" });
|
|
}
|
|
|
|
// ================================================================
|
|
// Build Result
|
|
// ================================================================
|
|
|
|
let overall_confidence = focus_confidence.iter().sum::<u32>() / num_validators as u32;
|
|
|
|
let result = QuasarResult {
|
|
winner: winning_hash.clone(),
|
|
synthesis: format!("QUASAR ({num_validators}/{num_validators} at round {final_round}): {winning_text}"),
|
|
confidence: overall_confidence,
|
|
final_round,
|
|
total_votes: all_photons.len(),
|
|
cert: cert.clone(),
|
|
duration_ns,
|
|
phase1_ns,
|
|
phase2_ns,
|
|
};
|
|
|
|
// ================================================================
|
|
// Print Final Result
|
|
// ================================================================
|
|
|
|
println!();
|
|
println!(" ================================================================");
|
|
println!(" QUASAR CONSENSUS RESULT");
|
|
println!(" ================================================================");
|
|
println!();
|
|
println!(" Question: \"{question}\"");
|
|
println!();
|
|
println!(" Answer [{winning_proposal}]: \"{winning_text}\"");
|
|
println!();
|
|
println!(" Metrics:");
|
|
println!(" Focus confidence: {}", result.confidence);
|
|
println!(" Final round: {final_round} / {} max", config.max_rounds);
|
|
println!(" Total Photon votes:{} ({num_validators} validators x {total_rounds} rounds)", all_photons.len());
|
|
println!(" Finalized: {finalized}");
|
|
println!(" Phase I (Nova): {:.2}ms", phase1_ns as f64 / 1_000_000.0);
|
|
println!(" Phase II (Quasar): {:.2}ms", phase2_ns as f64 / 1_000_000.0);
|
|
println!(" Total: {:.2}ms", duration_ns as f64 / 1_000_000.0);
|
|
println!();
|
|
println!(" CertBundle:");
|
|
println!(" bls_aggregate: {:x?}... (96B)", &cert.bls_aggregate[..16]);
|
|
println!(" corona_cert: {:x?}... ({}B)", &cert.corona_cert[..16], cert.corona_cert.len());
|
|
println!(" epoch: {}", cert.epoch);
|
|
println!(" quantum_final: {}", cert.is_quantum_final());
|
|
println!(" validators: {}", cert.validator_set.len());
|
|
for v in &cert.validator_set {
|
|
println!(" - {v}");
|
|
}
|
|
println!();
|
|
println!(" Crypto: BLS12-381 (Phase I) + ML-DSA-87/Corona (Phase II)");
|
|
println!(" PQ KEM: ML-KEM-1024 (FIPS 203)");
|
|
println!(" ZAP: v{ZAP_VERSION} ({ZAP_PROFILE})");
|
|
println!(" Quasar: v{QUASAR_VERSION}");
|
|
println!();
|
|
println!(" ================================================================");
|
|
|
|
// ================================================================
|
|
// Assertions
|
|
// ================================================================
|
|
|
|
for round_p in &participation {
|
|
assert!(round_p.iter().all(|&p| p), "All validators must participate in every round");
|
|
}
|
|
|
|
assert!(finalized, "Quasar consensus should finalize within {} rounds", config.max_rounds);
|
|
|
|
let all_agree = preferences.iter().all(|&p| p == winning_proposal);
|
|
assert!(all_agree, "All validators should converge to winning proposal");
|
|
|
|
assert!(cert.is_quantum_final(), "CertBundle needs both BLS and Corona");
|
|
assert_eq!(cert.validator_set.len(), num_validators);
|
|
assert_eq!(all_photons.len(), num_validators * total_rounds);
|
|
|
|
println!("\n [ok] Quasar Consensus passed ({total_rounds} rounds, {} Photon votes)\n", all_photons.len());
|
|
}
|
|
}
|