From 2a5cb9d9eed8f18e8e27bd92b7a4cf056e372d71 Mon Sep 17 00:00:00 2001 From: Diego Prats Date: Mon, 9 Dec 2024 10:39:37 -0800 Subject: [PATCH 1/4] move print statement of prover id --- clients/cli/src/prover.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/clients/cli/src/prover.rs b/clients/cli/src/prover.rs index e6af116..6ab0b73 100644 --- a/clients/cli/src/prover.rs +++ b/clients/cli/src/prover.rs @@ -116,6 +116,11 @@ async fn main() -> Result<(), Box> { // get or generate the prover id let prover_id = prover_id_manager::get_or_generate_prover_id(); + println!( + "\t✔ Your current prover identifier is {}", + prover_id.bright_cyan() + ); + println!( "\n===== {}...\n", "Connecting to Nexus Network".bold().underline() @@ -132,11 +137,6 @@ async fn main() -> Result<(), Box> { // Connect to the Orchestrator with exponential backoff let mut client = connect_to_orchestrator_with_infinite_retry(&ws_addr_string, &prover_id).await; - println!( - "\t✔ Your current prover identifier is {}", - prover_id.bright_cyan() - ); - println!( "\n{}", "Success! Connection complete!\n".green().bold().underline() From f790d26aae0b3dd3e937e1927778cf1e39ca8afb Mon Sep 17 00:00:00 2001 From: Diego Prats Date: Mon, 9 Dec 2024 10:39:53 -0800 Subject: [PATCH 2/4] make prover id logic writing/reading more robust --- clients/cli/src/prover_id_manager.rs | 149 +++++++++++++++++---------- 1 file changed, 96 insertions(+), 53 deletions(-) diff --git a/clients/cli/src/prover_id_manager.rs b/clients/cli/src/prover_id_manager.rs index 32a71da..7884451 100644 --- a/clients/cli/src/prover_id_manager.rs +++ b/clients/cli/src/prover_id_manager.rs @@ -5,8 +5,7 @@ use std::{fs, path::Path}; /// Gets an existing prover ID from the filesystem or generates a new one pub fn get_or_generate_prover_id() -> String { - // If the prover_id file is found, use the contents, otherwise generate a new random id - // and store it. e.g., "happy-cloud-42" + // Generate default ID that we'll use if we can't read/write a saved one let default_prover_id: String = format!( "{}-{}-{}", random_word::gen(Lang::En), @@ -14,68 +13,112 @@ pub fn get_or_generate_prover_id() -> String { rand::thread_rng().next_u32() % 100, ); - // setting the prover-id we will use (either from the file or generated) - let prover_id: String = match home::home_dir() { - Some(path) if !path.as_os_str().is_empty() => { - let nexus_dir = Path::new(&path).join(".nexus"); - - // Try to read the prover-id file - match fs::read(nexus_dir.join("prover-id")) { - // 1. If file exists and can be read: - Ok(buf) => match String::from_utf8(buf) { - Ok(id) => id.trim().to_string(), // Trim whitespace - Err(e) => { - eprintln!("Failed to read prover-id file. Using default: {}", e); - default_prover_id // Fall back to generated ID, if file has invalid UTF-8 + let home_path = match home::home_dir() { + Some(path) if !path.as_os_str().is_empty() => path, + _ => { + println!( + "Could not determine home directory, using temporary prover-id: {}", + default_prover_id + ); + return default_prover_id; + } + }; + + let nexus_dir = home_path.join(".nexus"); + let prover_id_path = nexus_dir.join("prover-id"); + + // First check if .nexus directory exists + if !nexus_dir.exists() { + println!("Attempting to create .nexus directory"); + if let Err(e) = fs::create_dir(&nexus_dir) { + eprintln!( + "{}: {}", + "Warning: Failed to create .nexus directory" + .to_string() + .yellow(), + e + ); + return default_prover_id; + } + println!("Successfully created .nexus directory"); + + // Save the new prover ID + if let Err(e) = fs::write(&prover_id_path, &default_prover_id) { + println!("Failed to save prover-id to file: {}", e); + return default_prover_id; + } + println!( + "Successfully saved new prover-id to file: {}", + default_prover_id + ); + return default_prover_id; + } + + // .nexus exists, try to read prover-id file + match fs::read(&prover_id_path) { + Ok(buf) => match String::from_utf8(buf) { + Ok(id) => { + println!("Successfully read existing prover-id from file: {}", id); + id.trim().to_string() + } + Err(e) => { + println!( + "Found file but content is not valid UTF-8, using default. Error: {}", + e + ); + default_prover_id + } + }, + Err(e) => { + eprintln!( + "{}: {}", + "Warning: Could not read prover-id file" + .to_string() + .yellow(), + e + ); + + match e.kind() { + std::io::ErrorKind::NotFound => { + // File doesn't exist in existing .nexus directory, create it + if let Err(e) = fs::write(&prover_id_path, &default_prover_id) { + println!("Failed to save prover-id to file: {}", e); + } else { + println!( + "Successfully saved new prover-id to file: {}", + default_prover_id + ); } - }, - // 2. If file doesn't exist or can't be read: - Err(e) => { + } + std::io::ErrorKind::PermissionDenied => { eprintln!( "{}: {}", - "Warning: Could not read prover-id file" + "Error: Permission denied when accessing prover-id file" + .to_string() + .yellow(), + e + ); + } + std::io::ErrorKind::InvalidData => { + eprintln!( + "{}: {}", + "Error: Prover-id file is corrupted".to_string().yellow(), + e + ); + } + _ => { + eprintln!( + "{}: {}", + "Error: Unexpected IO error when reading prover-id file" .to_string() .yellow(), e ); - - // if the error is because the file doesn't exist - // Try to save the generated prover-id to the file - if e.kind() == std::io::ErrorKind::NotFound { - // Try to create the .nexus directory - match fs::create_dir(nexus_dir.clone()) { - Ok(_) => { - // Only try to write file if directory was created successfully - if let Err(e) = - fs::write(nexus_dir.join("prover-id"), &default_prover_id) - { - eprintln!("Warning: Could not save prover-id: {}", e); - } - } - Err(e) => { - eprintln!( - "{}: {}", - "Warning: Failed to create .nexus directory" - .to_string() - .yellow(), - e - ); - } - } - } - - // Use the previously generated prover-id - default_prover_id } } - } - _ => { - println!("Unable to determine home directory. Using temporary prover-id."); default_prover_id } - }; - - prover_id + } } #[cfg(test)] From cd3515b5f42059ba3cc5f7b92d3d3380ab1a52c5 Mon Sep 17 00:00:00 2001 From: Diego Prats Date: Mon, 9 Dec 2024 10:51:56 -0800 Subject: [PATCH 3/4] update tests --- clients/cli/src/prover.rs | 2 +- clients/cli/src/prover_id_manager.rs | 302 +++++++++++++++++++-------- 2 files changed, 215 insertions(+), 89 deletions(-) diff --git a/clients/cli/src/prover.rs b/clients/cli/src/prover.rs index 6ab0b73..b4e5b02 100644 --- a/clients/cli/src/prover.rs +++ b/clients/cli/src/prover.rs @@ -117,7 +117,7 @@ async fn main() -> Result<(), Box> { let prover_id = prover_id_manager::get_or_generate_prover_id(); println!( - "\t✔ Your current prover identifier is {}", + "\n\t✔ Your current prover identifier is {}", prover_id.bright_cyan() ); diff --git a/clients/cli/src/prover_id_manager.rs b/clients/cli/src/prover_id_manager.rs index 7884451..d82b8d2 100644 --- a/clients/cli/src/prover_id_manager.rs +++ b/clients/cli/src/prover_id_manager.rs @@ -1,122 +1,138 @@ use colored::Colorize; use rand::RngCore; use random_word::Lang; -use std::{fs, path::Path}; +use std::{fs, path::Path, path::PathBuf}; /// Gets an existing prover ID from the filesystem or generates a new one +/// This is the main entry point for getting a prover ID pub fn get_or_generate_prover_id() -> String { - // Generate default ID that we'll use if we can't read/write a saved one - let default_prover_id: String = format!( - "{}-{}-{}", - random_word::gen(Lang::En), - random_word::gen(Lang::En), - rand::thread_rng().next_u32() % 100, - ); + let default_prover_id = generate_default_id(); - let home_path = match home::home_dir() { - Some(path) if !path.as_os_str().is_empty() => path, - _ => { - println!( - "Could not determine home directory, using temporary prover-id: {}", - default_prover_id - ); - return default_prover_id; - } + let home_path = match get_home_directory() { + Ok(path) => path, + Err(_) => return default_prover_id, }; let nexus_dir = home_path.join(".nexus"); let prover_id_path = nexus_dir.join("prover-id"); - // First check if .nexus directory exists if !nexus_dir.exists() { - println!("Attempting to create .nexus directory"); - if let Err(e) = fs::create_dir(&nexus_dir) { + return handle_first_time_setup(&nexus_dir, &prover_id_path, &default_prover_id); + } + + match read_existing_prover_id(&prover_id_path) { + Ok(id) => { + println!("Successfully read existing prover-id from file: {}", id); + id + } + Err(e) => { eprintln!( "{}: {}", - "Warning: Failed to create .nexus directory" + "Warning: Could not read prover-id file" .to_string() .yellow(), e ); - return default_prover_id; + handle_read_error(e, &prover_id_path, &default_prover_id); + default_prover_id } - println!("Successfully created .nexus directory"); + } +} + +fn generate_default_id() -> String { + format!( + "{}-{}-{}", + random_word::gen(Lang::En), + random_word::gen(Lang::En), + rand::thread_rng().next_u32() % 100, + ) +} - // Save the new prover ID - if let Err(e) = fs::write(&prover_id_path, &default_prover_id) { - println!("Failed to save prover-id to file: {}", e); - return default_prover_id; +fn get_home_directory() -> Result { + match home::home_dir() { + Some(path) if !path.as_os_str().is_empty() => Ok(path), + _ => { + println!("Could not determine home directory"); + Err("No home directory found") } - println!( - "Successfully saved new prover-id to file: {}", - default_prover_id + } +} + +fn handle_first_time_setup( + nexus_dir: &Path, + prover_id_path: &Path, + default_prover_id: &str, +) -> String { + println!("Attempting to create .nexus directory"); + if let Err(e) = fs::create_dir(nexus_dir) { + eprintln!( + "{}: {}", + "Warning: Failed to create .nexus directory" + .to_string() + .yellow(), + e ); - return default_prover_id; + return default_prover_id.to_string(); } - // .nexus exists, try to read prover-id file - match fs::read(&prover_id_path) { - Ok(buf) => match String::from_utf8(buf) { - Ok(id) => { - println!("Successfully read existing prover-id from file: {}", id); - id.trim().to_string() - } - Err(e) => { - println!( - "Found file but content is not valid UTF-8, using default. Error: {}", - e - ); - default_prover_id - } - }, - Err(e) => { + save_prover_id(prover_id_path, default_prover_id); + default_prover_id.to_string() +} + +fn read_existing_prover_id(prover_id_path: &Path) -> Result { + let buf = fs::read(prover_id_path)?; + let id = String::from_utf8(buf) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))? + .trim() + .to_string(); + + if id.is_empty() { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Prover ID file is empty", + )); + } + + Ok(id) +} + +fn save_prover_id(path: &Path, id: &str) { + if let Err(e) = fs::write(path, id) { + println!("Failed to save prover-id to file: {}", e); + } else { + println!("Successfully saved new prover-id to file: {}", id); + } +} + +fn handle_read_error(e: std::io::Error, path: &Path, default_id: &str) { + match e.kind() { + std::io::ErrorKind::NotFound => { + save_prover_id(path, default_id); + } + std::io::ErrorKind::PermissionDenied => { eprintln!( "{}: {}", - "Warning: Could not read prover-id file" + "Error: Permission denied when accessing prover-id file" + .to_string() + .yellow(), + e + ); + } + std::io::ErrorKind::InvalidData => { + eprintln!( + "{}: {}", + "Error: Prover-id file is corrupted".to_string().yellow(), + e + ); + } + _ => { + eprintln!( + "{}: {}", + "Error: Unexpected IO error when reading prover-id file" .to_string() .yellow(), e ); - - match e.kind() { - std::io::ErrorKind::NotFound => { - // File doesn't exist in existing .nexus directory, create it - if let Err(e) = fs::write(&prover_id_path, &default_prover_id) { - println!("Failed to save prover-id to file: {}", e); - } else { - println!( - "Successfully saved new prover-id to file: {}", - default_prover_id - ); - } - } - std::io::ErrorKind::PermissionDenied => { - eprintln!( - "{}: {}", - "Error: Permission denied when accessing prover-id file" - .to_string() - .yellow(), - e - ); - } - std::io::ErrorKind::InvalidData => { - eprintln!( - "{}: {}", - "Error: Prover-id file is corrupted".to_string().yellow(), - e - ); - } - _ => { - eprintln!( - "{}: {}", - "Error: Unexpected IO error when reading prover-id file" - .to_string() - .yellow(), - e - ); - } - } - default_prover_id } } } @@ -250,4 +266,114 @@ mod tests { } std::thread::sleep(std::time::Duration::from_millis(10)); } + + /// Tests handling of corrupted prover-id file + #[test] + #[serial] + fn test_corrupted_prover_id_file() { + let temp_dir = TempDir::new().unwrap(); + env::set_var("HOME", temp_dir.path()); + + // Create .nexus directory and corrupted prover-id file + let nexus_dir = temp_dir.path().join(".nexus"); + fs::create_dir(&nexus_dir).unwrap(); + + let id_path = nexus_dir.join("prover-id"); + fs::write(&id_path, vec![0xFF, 0xFE, 0xFF]).unwrap(); // Invalid UTF-8 + + let id = get_or_generate_prover_id(); + assert!(id.contains('-'), "Should generate new valid ID"); + } + + /// Tests handling of permission denied scenarios + #[test] + #[serial] + #[cfg(unix)] // This test only works on Unix-like systems + fn test_permission_denied() { + use std::os::unix::fs::PermissionsExt; + + let temp_dir = TempDir::new().unwrap(); + env::set_var("HOME", temp_dir.path()); + + // Create .nexus directory with read-only permissions + let nexus_dir = temp_dir.path().join(".nexus"); + fs::create_dir(&nexus_dir).unwrap(); + + let metadata = fs::metadata(&nexus_dir).unwrap(); + let mut perms = metadata.permissions(); + perms.set_mode(0o444); // read-only + fs::set_permissions(&nexus_dir, perms).unwrap(); + + let id = get_or_generate_prover_id(); + assert!( + id.contains('-'), + "Should generate new ID when permissions denied" + ); + } + + /// Tests that IDs are properly formatted + #[test] + #[serial] + fn test_id_format() { + let id = get_or_generate_prover_id(); + let parts: Vec<&str> = id.split('-').collect(); + + assert_eq!(parts.len(), 3, "ID should have three parts"); + assert!( + parts[0].chars().all(|c| c.is_ascii_alphabetic()), + "First word should be alphabetic" + ); + assert!( + parts[1].chars().all(|c| c.is_ascii_alphabetic()), + "Second word should be alphabetic" + ); + assert!( + parts[2].parse::().is_ok(), + "Last part should be a number" + ); + assert!( + parts[2].parse::().unwrap() < 100, + "Number should be less than 100" + ); + } + + /// Tests behavior with empty prover-id file + #[test] + #[serial] + fn test_empty_prover_id_file() { + let temp_dir = TempDir::new().unwrap(); + env::set_var("HOME", temp_dir.path()); + + let nexus_dir = temp_dir.path().join(".nexus"); + fs::create_dir(&nexus_dir).unwrap(); + + let id_path = nexus_dir.join("prover-id"); + fs::write(&id_path, "").unwrap(); + + let id = get_or_generate_prover_id(); + assert!(id.contains('-'), "Should generate new ID for empty file"); + } + + /// Tests behavior when home directory is not available + #[test] + #[serial] + fn test_no_home_directory() { + env::remove_var("HOME"); + + let id = get_or_generate_prover_id(); + assert!( + id.contains('-'), + "Should generate temporary ID without home dir" + ); + } + + /// Tests that generated IDs are unique + #[test] + fn test_id_uniqueness() { + let mut ids = std::collections::HashSet::new(); + for _ in 0..100 { + let id = generate_default_id(); + assert!(ids.insert(id), "Generated IDs should be unique"); + } + } } From 16b1a6504993a9e25568983a921ea078b608b111 Mon Sep 17 00:00:00 2001 From: Diego Prats Date: Mon, 9 Dec 2024 11:03:01 -0800 Subject: [PATCH 4/4] src/prover_id_manager.rs --- clients/cli/src/prover_id_manager.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clients/cli/src/prover_id_manager.rs b/clients/cli/src/prover_id_manager.rs index d82b8d2..894c4fa 100644 --- a/clients/cli/src/prover_id_manager.rs +++ b/clients/cli/src/prover_id_manager.rs @@ -16,15 +16,19 @@ pub fn get_or_generate_prover_id() -> String { let nexus_dir = home_path.join(".nexus"); let prover_id_path = nexus_dir.join("prover-id"); + // 1. If the .nexus directory doesn't exist, we need to create it if !nexus_dir.exists() { return handle_first_time_setup(&nexus_dir, &prover_id_path, &default_prover_id); } + // 2. If the .nexus directory exists, we need to read the prover-id file match read_existing_prover_id(&prover_id_path) { + // 2.1 Happy path - we successfully read the prover-id file Ok(id) => { println!("Successfully read existing prover-id from file: {}", id); id } + // 2.2 We couldn't read the prover-id file, so we may need to create a new one Err(e) => { eprintln!( "{}: {}",