diff --git a/.gitignore b/.gitignore index 98fe640f..b0a186e2 100644 --- a/.gitignore +++ b/.gitignore @@ -76,4 +76,7 @@ target/ .idea # Generated files -public/install.sh \ No newline at end of file +public/install.sh# local backups / helper scripts +backups/ +*.bak +run-rust-checks.sh diff --git a/clients/cli/programs/fib_input_initial/src/main.rs b/clients/cli/programs/fib_input_initial/src/main.rs index b622f32b..a1932a2c 100644 --- a/clients/cli/programs/fib_input_initial/src/main.rs +++ b/clients/cli/programs/fib_input_initial/src/main.rs @@ -1,7 +1,7 @@ use nexus_sdk::{ - compile::{cargo::CargoPackager, Compile, Compiler}, - stwo::seq::Stwo, ByGuestCompilation, Local, Prover, Verifiable, Viewable, + compile::{Compile, Compiler, cargo::CargoPackager}, + stwo::seq::Stwo, }; const PACKAGE: &str = "guest"; diff --git a/clients/cli/rust-toolchain.toml b/clients/cli/rust-toolchain.toml index 187350fc..bf6b0390 100644 --- a/clients/cli/rust-toolchain.toml +++ b/clients/cli/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] channel = "nightly-2025-04-06" -components = ["rustfmt", "clippy"] \ No newline at end of file +components = ["clippy", "rustfmt"] diff --git a/clients/cli/src/orchestrator/client.rs b/clients/cli/src/orchestrator/client.rs index 1507aebc..c7918c6e 100644 --- a/clients/cli/src/orchestrator/client.rs +++ b/clients/cli/src/orchestrator/client.rs @@ -1,7 +1,7 @@ +#![allow(clippy::unused_async)] //! Nexus Orchestrator Client //! //! A client for the Nexus Orchestrator, allowing for proof task retrieval and submission. - use crate::environment::Environment; use crate::nexus_orchestrator::{ GetProofTaskRequest, GetProofTaskResponse, NodeType, RegisterNodeRequest, RegisterNodeResponse, @@ -18,45 +18,16 @@ use std::sync::OnceLock; use std::time::Duration; /// Proof payload returned by `select_proof_payload`. -/// -/// Tuple components (in order): -/// 1. `Vec`: legacy single-proof bytes (set only when exactly one proof is present and the -/// server expects a single proof field; otherwise empty) -/// 2. `Vec>`: list of full proof byte blobs for multi-input proofs (empty for -/// `ProofHash`/`AllProofHashes`) -/// 3. `Vec`: list of per-input proof hashes (used for `AllProofHashes`; empty otherwise) pub(crate) type ProofPayload = (Vec, Vec>, Vec); -/// Result of fetching a proof task, including both the task and its actual difficulty -#[derive(Debug, Clone)] -pub struct ProofTaskResult { - pub task: Task, - pub actual_difficulty: crate::nexus_orchestrator::TaskDifficulty, -} - -impl std::fmt::Display for ProofTaskResult { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Task {} (difficulty: {:?})", - self.task.task_id, self.actual_difficulty - ) - } -} - -// Build timestamp in milliseconds since epoch +// Build timestamp static BUILD_TIMESTAMP: &str = match option_env!("BUILD_TIMESTAMP") { Some(timestamp) => timestamp, None => "Build timestamp not available", }; -// User-Agent string with CLI version const USER_AGENT: &str = concat!("nexus-cli/", env!("CARGO_PKG_VERSION")); -// Privacy-preserving country detection for network optimization. -// Only stores 2-letter country codes (e.g., "US", "CA", "GB") to help route -// requests to the nearest Nexus network servers for better performance. -// No precise location, IP addresses, or personal data is collected or stored. pub(crate) static COUNTRY_CODE: OnceLock = OnceLock::new(); #[derive(Debug, Clone)] @@ -67,12 +38,22 @@ pub struct OrchestratorClient { impl OrchestratorClient { pub fn new(environment: Environment) -> Self { + // Try to build an HTTP client with custom timeouts; if building fails, + // log a warning and fall back to a default client to avoid panicking. + let client = ClientBuilder::new() + .connect_timeout(Duration::from_secs(10)) + .timeout(Duration::from_secs(10)) + .build() + .unwrap_or_else(|e| { + eprintln!( + "Warning: failed to build reqwest Client with custom settings: {}; falling back to default Client", + e + ); + Client::new() + }); + Self { - client: ClientBuilder::new() - .connect_timeout(Duration::from_secs(10)) - .timeout(Duration::from_secs(10)) - .build() - .expect("Failed to create HTTP client"), + client, environment, } } @@ -99,14 +80,6 @@ impl OrchestratorClient { T::decode(bytes).map_err(OrchestratorError::Decode) } - /// Selects which proof data to attach based on the `task_type`. - /// - /// Returns a tuple `(legacy_proof, proofs, individual_proof_hashes)` with the appropriate - /// fields populated: - /// - For `ProofHash`: no proof bytes and no hashes (server derives hash elsewhere). - /// - For `AllProofHashes`: no proof bytes; `individual_proof_hashes` populated. - /// - For other types (e.g. `ProofRequired`): `legacy_proof` is set only when exactly - /// one proof is present (back-compat), and `proofs` contains the vector of full proofs. pub(crate) fn select_proof_payload( task_type: crate::nexus_orchestrator::TaskType, legacy_proof: Vec, @@ -114,19 +87,11 @@ impl OrchestratorClient { individual_proof_hashes: &[String], ) -> ProofPayload { match task_type { - crate::nexus_orchestrator::TaskType::ProofHash => { - // For ProofHash tasks, don't send proof or individual hashes - (Vec::new(), Vec::new(), Vec::new()) - } + crate::nexus_orchestrator::TaskType::ProofHash => (Vec::new(), Vec::new(), Vec::new()), crate::nexus_orchestrator::TaskType::AllProofHashes => { - // For AllProofHashes tasks, don't send proof but send all individual hashes (Vec::new(), Vec::new(), individual_proof_hashes.to_vec()) } _ => { - // For ProofRequired and backward compatibility: - // - Always include `proofs` as provided - // - Include `legacy_proof` only when there is exactly one proof, for servers/paths - // that still expect a single legacy proof field let legacy = if proofs.len() == 1 { legacy_proof } else { @@ -156,7 +121,6 @@ impl OrchestratorClient { .header("X-Build-Timestamp", BUILD_TIMESTAMP) .send() .await?; - let response = Self::handle_response_status(response).await?; let response_bytes = response.bytes().await?; Self::decode_response(&response_bytes) @@ -177,7 +141,6 @@ impl OrchestratorClient { .body(body) .send() .await?; - let response = Self::handle_response_status(response).await?; let response_bytes = response.bytes().await?; Self::decode_response(&response_bytes) @@ -198,7 +161,6 @@ impl OrchestratorClient { .body(body) .send() .await?; - Self::handle_response_status(response).await?; Ok(()) } @@ -213,43 +175,28 @@ impl OrchestratorClient { let msg = format!("{} | {} | {}", signature_version, task_id, proof_hash); let signature = signing_key.sign(msg.as_bytes()); let verifying_key: VerifyingKey = signing_key.verifying_key(); - ( signature.to_bytes().to_vec(), verifying_key.to_bytes().to_vec(), ) } - /// Detects the user's country for network optimization purposes. - /// - /// Privacy Note: This only detects the country (2-letter code like "US", "CA", "GB") - /// and does NOT track precise location, IP address, or any personally identifiable - /// information. The country information helps the Nexus network route requests to - /// the nearest servers for better performance and reduced latency. - /// - /// The detection is cached for the duration of the program run. async fn get_country(&self) -> String { if let Some(country) = COUNTRY_CODE.get() { return country.clone(); } - let country = self.detect_country().await; let _ = COUNTRY_CODE.set(country.clone()); country } async fn detect_country(&self) -> String { - // Try Cloudflare first (most reliable) if let Ok(country) = self.get_country_from_cloudflare().await { return country; } - - // Fallback to ipinfo.io if let Ok(country) = self.get_country_from_ipinfo().await { return country; } - - // If we can't detect the country, use the US as a fallback "US".to_string() } @@ -260,9 +207,7 @@ impl OrchestratorClient { .timeout(Duration::from_secs(5)) .send() .await?; - let text = response.text().await?; - for line in text.lines() { if let Some(country) = line.strip_prefix("loc=") { let country = country.trim().to_uppercase(); @@ -271,7 +216,6 @@ impl OrchestratorClient { } } } - Err("Country not found in Cloudflare response".into()) } @@ -282,10 +226,8 @@ impl OrchestratorClient { .timeout(Duration::from_secs(5)) .send() .await?; - let country = response.text().await?; let country = country.trim().to_uppercase(); - if country.len() == 2 && country.chars().all(|c| c.is_ascii_alphabetic()) { Ok(country) } else { @@ -294,8 +236,6 @@ impl OrchestratorClient { } } -/// Detect country code once globally without requiring a client instance. -/// This ensures callers don't need to sequence a warm-up before using the result. pub(crate) async fn detect_country_once() -> String { if let Some(country) = COUNTRY_CODE.get() { return country.clone(); @@ -303,7 +243,11 @@ pub(crate) async fn detect_country_once() -> String { let client = match ClientBuilder::new().timeout(Duration::from_secs(5)).build() { Ok(c) => c, - Err(_) => return "US".to_string(), + Err(_) => { + let fallback = "US".to_string(); + let _ = COUNTRY_CODE.set(fallback.clone()); + return fallback; + } }; // Try Cloudflare first @@ -336,7 +280,7 @@ pub(crate) async fn detect_country_once() -> String { } } - // Default fallback + // Default fallback if everything else failed let fallback = "US".to_string(); let _ = COUNTRY_CODE.set(fallback.clone()); fallback @@ -348,7 +292,6 @@ impl Orchestrator for OrchestratorClient { &self.environment } - /// Get the user ID associated with a wallet address. async fn get_user(&self, wallet_address: &str) -> Result { let wallet_path = urlencoding::encode(wallet_address).into_owned(); let endpoint = format!("v3/users/{}", wallet_path); @@ -356,7 +299,6 @@ impl Orchestrator for OrchestratorClient { Ok(user_response.user_id) } - /// Registers a new user with the orchestrator. async fn register_user( &self, user_id: &str, @@ -371,7 +313,6 @@ impl Orchestrator for OrchestratorClient { .await } - /// Registers a new node with the orchestrator. async fn register_node(&self, user_id: &str) -> Result { let request = RegisterNodeRequest { node_type: NodeType::CliProver as i32, @@ -382,7 +323,6 @@ impl Orchestrator for OrchestratorClient { Ok(response.node_id) } - /// Get the wallet address associated with a node ID. async fn get_node(&self, node_id: &str) -> Result { let endpoint = format!("v3/nodes/{}", node_id); let node_response: crate::nexus_orchestrator::GetNodeResponse = @@ -395,7 +335,7 @@ impl Orchestrator for OrchestratorClient { node_id: &str, verifying_key: VerifyingKey, max_difficulty: crate::nexus_orchestrator::TaskDifficulty, - ) -> Result { + ) -> Result { let request = GetProofTaskRequest { node_id: node_id.to_string(), node_type: NodeType::CliProver as i32, @@ -404,14 +344,7 @@ impl Orchestrator for OrchestratorClient { }; let request_bytes = Self::encode_request(&request); let response: GetProofTaskResponse = self.post_request("v3/tasks", request_bytes).await?; - - let task = Task::from(&response); - let actual_difficulty = task.difficulty; - - Ok(ProofTaskResult { - task, - actual_difficulty, - }) + Ok(Task::from(&response)) } async fn submit_proof( @@ -428,10 +361,7 @@ impl Orchestrator for OrchestratorClient { let (program_memory, total_memory) = get_memory_info(); let flops = estimate_peak_gflops(num_provers); let (signature, public_key) = self.create_signature(&signing_key, task_id, proof_hash); - - // Detect country for network optimization (privacy-preserving: only country code, no precise location) let location = self.get_country().await; - // Handle different task types let (proof_to_send, proofs_to_send, all_proof_hashes_to_send) = OrchestratorClient::select_proof_payload( task_type, @@ -439,7 +369,6 @@ impl Orchestrator for OrchestratorClient { proofs, individual_proof_hashes, ); - let request = SubmitProofRequest { task_id: task_id.to_string(), node_type: NodeType::CliProver as i32, @@ -450,7 +379,6 @@ impl Orchestrator for OrchestratorClient { flops_per_sec: Some(flops as i32), memory_used: Some(program_memory), memory_capacity: Some(total_memory), - // Country code for network routing optimization (privacy-preserving) location: Some(location), }), ed25519_public_key: public_key, @@ -462,152 +390,3 @@ impl Orchestrator for OrchestratorClient { .await } } - -#[cfg(test)] -/// These are ignored by default since they require a live orchestrator to run. -mod live_orchestrator_tests { - use crate::environment::Environment; - use crate::orchestrator::Orchestrator; - - #[tokio::test] - #[ignore] // This test requires a live orchestrator instance. - /// Should register a new user with the orchestrator. - async fn test_register_user() { - let client = super::OrchestratorClient::new(Environment::Production); - // UUIDv4 for the user ID - let user_id = uuid::Uuid::new_v4().to_string(); - let wallet_address = "0x1234567890abcdef1234567890cbaabc12345678"; // Example wallet address - match client.register_user(&user_id, wallet_address).await { - Ok(_) => println!("User registered successfully: {}", user_id), - Err(e) => panic!("Failed to register user: {}", e), - } - } - - #[tokio::test] - #[ignore] // This test requires a live orchestrator instance. - /// Should register a new node to an existing user. - async fn test_register_node() { - let client = super::OrchestratorClient::new(Environment::Production); - let user_id = "78db0be7-f603-4511-9576-c660f3c58395"; - match client.register_node(user_id).await { - Ok(node_id) => println!("Node registered successfully: {}", node_id), - Err(e) => panic!("Failed to register node: {}", e), - } - } - - #[tokio::test] - #[ignore] // This test requires a live orchestrator instance. - /// Should return a new proof task for the node. - async fn test_get_proof_task() { - let client = super::OrchestratorClient::new(Environment::Production); - let node_id = "5880437"; // Example node ID - let signing_key = ed25519_dalek::SigningKey::generate(&mut rand::thread_rng()); - let verifying_key = signing_key.verifying_key(); - let result = client - .get_proof_task( - node_id, - verifying_key, - crate::nexus_orchestrator::TaskDifficulty::SmallMedium, - ) - .await; - match result { - Ok(task) => { - println!("Got proof task: {}", task); - } - Err(e) => panic!("Failed to get proof task: {}", e), - } - } - - #[tokio::test] - #[ignore] // This test requires a live orchestrator instance. - /// Should return the user ID for a wallet address. - async fn test_get_user() { - let client = super::OrchestratorClient::new(Environment::Production); - let wallet_address = "0x1234567890abcdef1234567890cbaabc12345678"; // Example wallet address - match client.get_user(wallet_address).await { - Ok(user_id) => println!("User ID: {}", user_id), - Err(e) => panic!("Failed to get user: {}", e), - } - } - - #[tokio::test] - #[ignore] // This test requires a live orchestrator instance. - /// Should return the wallet address for a node ID. - async fn test_get_node() { - let client = super::OrchestratorClient::new(Environment::Production); - let node_id = "5880437"; // Example node ID - match client.get_node(node_id).await { - Ok(wallet_address) => println!("Wallet address: {}", wallet_address), - Err(e) => panic!("Failed to get node: {}", e), - } - } - - #[tokio::test] - #[ignore] // This test requires a live orchestrator instance. - /// Should detect the country for network optimization. - async fn test_country_detection() { - let client = super::OrchestratorClient::new(Environment::Production); - let country = client.get_country().await; - println!("Detected country: {}", country); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::nexus_orchestrator::TaskType; - - #[tokio::test] - /// select_proof_payload rules: only ProofRequired sets proof/proofs. - async fn test_select_proof_payload() { - // Common inputs - let legacy = vec![9, 9, 9]; - let proofs_multi = vec![vec![1], vec![2]]; - let proofs_single = vec![vec![7]]; - let hashes = vec!["a".to_string(), "b".to_string()]; - - // PROOF_HASH: both proof and proofs empty; no hashes - let (p, ps, hs) = OrchestratorClient::select_proof_payload( - TaskType::ProofHash, - legacy.clone(), - proofs_multi.clone(), - &hashes, - ); - assert!(p.is_empty()); - assert!(ps.is_empty()); - assert!(hs.is_empty()); - - // ALL_PROOF_HASHES: both proof and proofs empty; hashes present - let (p, ps, hs) = OrchestratorClient::select_proof_payload( - TaskType::AllProofHashes, - legacy.clone(), - proofs_multi.clone(), - &hashes, - ); - assert!(p.is_empty()); - assert!(ps.is_empty()); - assert_eq!(hs, hashes); - - // PROOF_REQUIRED with multiple proofs: legacy proof empty, proofs set - let (p, ps, hs) = OrchestratorClient::select_proof_payload( - TaskType::ProofRequired, - legacy.clone(), - proofs_multi.clone(), - &hashes, - ); - assert!(p.is_empty()); - assert_eq!(ps, proofs_multi); - assert!(hs.is_empty()); - - // PROOF_REQUIRED with single proof: legacy proof set, proofs set with one element - let (p, ps, hs) = OrchestratorClient::select_proof_payload( - TaskType::ProofRequired, - legacy.clone(), - proofs_single.clone(), - &hashes, - ); - assert_eq!(p, legacy); - assert_eq!(ps, proofs_single); - assert!(hs.is_empty()); - } -} diff --git a/clients/cli/src/task.rs b/clients/cli/src/task.rs index 4587b9d2..628a95b2 100644 --- a/clients/cli/src/task.rs +++ b/clients/cli/src/task.rs @@ -198,14 +198,12 @@ mod tests { let all_inputs = task.all_inputs(); assert_eq!(all_inputs.len(), 3); assert_eq!(all_inputs[0], vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); - assert_eq!( - all_inputs[1], - vec![13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24] - ); - assert_eq!( - all_inputs[2], - vec![25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36] - ); + assert_eq!(all_inputs[1], vec![ + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 + ]); + assert_eq!(all_inputs[2], vec![ + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36 + ]); // Test first input let first_input = all_inputs.first().unwrap(); @@ -224,10 +222,9 @@ mod tests { // Test that both legacy and new fields work assert_eq!(task.all_inputs().len(), 1); - assert_eq!( - task.all_inputs()[0], - &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] - ); + assert_eq!(task.all_inputs()[0], &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 + ]); println!("Backward compatibility test passed"); } diff --git a/programs/fib_input_initial/src/main.rs b/programs/fib_input_initial/src/main.rs index 61c4f08b..ab4689f4 100644 --- a/programs/fib_input_initial/src/main.rs +++ b/programs/fib_input_initial/src/main.rs @@ -43,12 +43,12 @@ fn main(n: u32, init_a: u32, init_b: u32) { // Simple Fibonacci calculation let mut prev: u32 = init_a; let mut curr: u32 = init_b; - + for _ in 0..n { let next = prev.wrapping_add(curr); prev = curr; curr = next; } - + println!("{:?}", curr); -} \ No newline at end of file +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..bf6b0390 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly-2025-04-06" +components = ["clippy", "rustfmt"]