From 0f4cf7ad4c87aa2105e45180fcb4ec6903ca2784 Mon Sep 17 00:00:00 2001 From: FiveMovesAhead Date: Fri, 2 May 2025 05:28:32 +0100 Subject: [PATCH] Split verification logic into separate crate. --- Cargo.toml | 2 +- tig-runtime/src/main.rs | 273 ++++++++------------------------------- tig-verifier/Cargo.toml | 22 ++++ tig-verifier/src/main.rs | 220 +++++++++++++++++++++++++++++++ tig-worker/src/main.rs | 3 +- 5 files changed, 295 insertions(+), 225 deletions(-) create mode 100644 tig-verifier/Cargo.toml create mode 100644 tig-verifier/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index b9708dff..e3bbaaea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ "tig-protocol", "tig-runtime", "tig-structs", - "tig-utils", + "tig-utils", "tig-verifier", "tig-worker", ] exclude = [] diff --git a/tig-runtime/src/main.rs b/tig-runtime/src/main.rs index 75aff2c2..3a1aa8cd 100644 --- a/tig-runtime/src/main.rs +++ b/tig-runtime/src/main.rs @@ -1,116 +1,75 @@ use anyhow::{anyhow, Result}; use clap::{arg, ArgAction, Command}; use libloading::Library; -use std::{fs, io::Read, panic, path::PathBuf}; +use std::{fs, panic, path::PathBuf}; use tig_challenges::*; use tig_structs::core::{BenchmarkSettings, OutputData, Solution}; use tig_utils::{compress_obj, dejsonify, jsonify}; #[cfg(feature = "cuda")] use { cudarc::{ - driver::{CudaModule, CudaStream, LaunchConfig, PushKernelArg}, + driver::{CudaContext, CudaModule, CudaStream, LaunchConfig, PushKernelArg}, nvrtc::Ptx, - runtime::sys::cudaDeviceProp, + runtime::{result::device::get_device_prop, sys::cudaDeviceProp}, }, std::sync::Arc, }; fn cli() -> Command { Command::new("tig-runtime") - .about("Computes or verifies solutions") + .about("Executes an algorithm on a single challenge instance") .arg_required_else_help(true) - .subcommand( - Command::new("compute_solution") - .about("Computes a solution") - .arg( - arg!( "Settings json string or path to json file") - .value_parser(clap::value_parser!(String)), - ) - .arg( - arg!( "A string used in seed generation") - .value_parser(clap::value_parser!(String)), - ) - .arg(arg!( "Nonce value").value_parser(clap::value_parser!(u64))) - .arg( - arg!( "Path to a shared object (*.so) file") - .value_parser(clap::value_parser!(PathBuf)), - ) - .arg( - arg!(--ptx [PTX] "Path to a CUDA ptx file") - .value_parser(clap::value_parser!(PathBuf)), - ) - .arg( - arg!(--fuel [FUEL] "Optional maximum fuel parameter") - .default_value("2000000000") - .value_parser(clap::value_parser!(u64)), - ) - .arg( - arg!(--output [OUTPUT_FILE] "If set, the output data will be saved to this file path (default json") - .value_parser(clap::value_parser!(PathBuf)), - ) - .arg( - arg!(--compress [COMPRESS] "If output file is set, the output data will be compressed as zlib") - .action(ArgAction::SetTrue) - ) - .arg( - arg!(--gpu [GPU] "Which GPU device to use") - .default_value("0") - .value_parser(clap::value_parser!(usize)), - ), + .arg( + arg!( "Settings json string or path to json file") + .value_parser(clap::value_parser!(String)), ) - .subcommand( - Command::new("verify_solution") - .about("Verifies a solution") - .arg( - arg!( "Settings json string or path to json file") - .value_parser(clap::value_parser!(String)), - ) - .arg( - arg!( "A string used in seed generation") - .value_parser(clap::value_parser!(String)), - ) - .arg(arg!( "Nonce value").value_parser(clap::value_parser!(u64))) - .arg( - arg!( "Solution json string, path to json file, or '-' for stdin") - .value_parser(clap::value_parser!(String)), - ) - .arg( - arg!(--ptx [PTX] "Path to a CUDA ptx file") - .value_parser(clap::value_parser!(PathBuf)), - ) - .arg( - arg!(--gpu [GPU] "Which GPU device to use") - .default_value("0") - .value_parser(clap::value_parser!(usize)), - ), + .arg( + arg!( "A string used in seed generation") + .value_parser(clap::value_parser!(String)), + ) + .arg(arg!( "Nonce value").value_parser(clap::value_parser!(u64))) + .arg( + arg!( "Path to a shared object (*.so) file") + .value_parser(clap::value_parser!(PathBuf)), + ) + .arg( + arg!(--ptx [PTX] "Path to a CUDA ptx file") + .value_parser(clap::value_parser!(PathBuf)), + ) + .arg( + arg!(--fuel [FUEL] "Optional maximum fuel parameter") + .default_value("2000000000") + .value_parser(clap::value_parser!(u64)), + ) + .arg( + arg!(--output [OUTPUT_FILE] "If set, the output data will be saved to this file path (default json") + .value_parser(clap::value_parser!(PathBuf)), + ) + .arg( + arg!(--compress [COMPRESS] "If output file is set, the output data will be compressed as zlib") + .action(ArgAction::SetTrue) + ) + .arg( + arg!(--gpu [GPU] "Which GPU device to use") + .default_value("0") + .value_parser(clap::value_parser!(usize)), ) } fn main() { let matches = cli().get_matches(); - if let Err(e) = match matches.subcommand() { - Some(("compute_solution", sub_m)) => compute_solution( - sub_m.get_one::("SETTINGS").unwrap().clone(), - sub_m.get_one::("RAND_HASH").unwrap().clone(), - *sub_m.get_one::("NONCE").unwrap(), - sub_m.get_one::("BINARY").unwrap().clone(), - sub_m.get_one::("ptx").cloned(), - *sub_m.get_one::("fuel").unwrap(), - sub_m.get_one::("output").cloned(), - sub_m.get_one::("compress").unwrap().clone(), - sub_m.get_one::("gpu").unwrap().clone(), - ), - Some(("verify_solution", sub_m)) => verify_solution( - sub_m.get_one::("SETTINGS").unwrap().clone(), - sub_m.get_one::("RAND_HASH").unwrap().clone(), - *sub_m.get_one::("NONCE").unwrap(), - sub_m.get_one::("SOLUTION").unwrap().clone(), - sub_m.get_one::("ptx").cloned(), - sub_m.get_one::("gpu").unwrap().clone(), - ), - _ => Err(anyhow!("Invalid subcommand")), - } { + if let Err(e) = compute_solution( + matches.get_one::("SETTINGS").unwrap().clone(), + matches.get_one::("RAND_HASH").unwrap().clone(), + *matches.get_one::("NONCE").unwrap(), + matches.get_one::("BINARY").unwrap().clone(), + matches.get_one::("ptx").cloned(), + *matches.get_one::("fuel").unwrap(), + matches.get_one::("output").cloned(), + matches.get_one::("compress").unwrap().clone(), + matches.get_one::("gpu").unwrap().clone(), + ) { eprintln!("Error: {}", e); std::process::exit(1); } @@ -200,12 +159,12 @@ pub fn compute_solution( let max_fuel_hex = format!("0x{:016x}", max_fuel); let modified_ptx = ptx_content.replace("0xdeadbeefdeadbeef", &max_fuel_hex); - let ptx = cudarc::nvrtc::Ptx::from_src(modified_ptx); - let ctx = cudarc::driver::CudaContext::new(gpu_device).unwrap(); + let ptx = Ptx::from_src(modified_ptx); + let ctx = CudaContext::new(gpu_device).unwrap(); ctx.set_blocking_synchronize()?; let module = ctx.load_module(ptx).unwrap(); let stream = ctx.default_stream(); - let prop = cudarc::runtime::result::device::get_device_prop(gpu_device as i32).unwrap(); + let prop = get_device_prop(gpu_device as i32).unwrap(); let challenge = $c::Challenge::generate_instance( seed, @@ -308,111 +267,6 @@ pub fn compute_solution( Ok(()) } -pub fn verify_solution( - settings: String, - rand_hash: String, - nonce: u64, - solution_path: String, - ptx_path: Option, - gpu_device: usize, -) -> Result<()> { - let settings = load_settings(&settings); - let solution = load_solution(&solution_path); - let seed = settings.calc_seed(&rand_hash, nonce); - - let mut err_msg = Option::::None; - - macro_rules! dispatch_challenges { - ( $( ($c:ident, $cpu_or_gpu:tt) ),+ $(,)? ) => {{ - match settings.challenge_id.as_str() { - $( - stringify!($c) => { - dispatch_challenges!(@expand $c, $cpu_or_gpu); - } - )+ - _ => panic!("Unsupported challenge"), - } - }}; - - (@expand $c:ident, cpu) => {{ - let challenge = $c::Challenge::generate_instance( - seed, - &settings.difficulty.into(), - ).unwrap(); - - match $c::Solution::try_from(solution) { - Ok(solution) => { - match challenge.verify_solution(&solution) { - Ok(_) => println!("Solution is valid"), - Err(e) => err_msg = Some(format!("Invalid solution: {}", e)), - } - }, - Err(_) => err_msg = Some(format!( - "Invalid solution. Cannot convert to {}::Solution", - stringify!($c) - )), - } - }}; - - (@expand $c:ident, gpu) => {{ - #[cfg(not(feature = "cuda"))] - panic!("tig-runtime was not compiled with '--features cuda'"); - - #[cfg(feature = "cuda")] - { - if ptx_path.is_none() { - panic!("PTX file is required for GPU challenges."); - } - - let ptx_path = ptx_path.unwrap(); - let ptx_content = std::fs::read_to_string(&ptx_path) - .map_err(|e| anyhow!("Failed to read PTX file: {}", e))?; - - let ptx = cudarc::nvrtc::Ptx::from_src(ptx_content); - let ctx = cudarc::driver::CudaContext::new(gpu_device).unwrap(); - ctx.set_blocking_synchronize()?; - let module = ctx.load_module(ptx).unwrap(); - let stream = ctx.default_stream(); - let prop = cudarc::runtime::result::device::get_device_prop(gpu_device as i32).unwrap(); - - let challenge = $c::Challenge::generate_instance( - seed, - &settings.difficulty.into(), - module.clone(), - stream.clone(), - &prop, - ).unwrap(); - - match $c::Solution::try_from(solution) { - Ok(solution) => { - match challenge.verify_solution(&solution, module.clone(), stream.clone(), &prop) { - Ok(_) => { - stream.synchronize()?; - ctx.synchronize()?; - println!("Solution is valid"); - }, - Err(e) => err_msg = Some(format!("Invalid solution: {}", e)), - } - }, - Err(_) => err_msg = Some(format!( - "Invalid solution. Cannot convert to {}::Solution", - stringify!($c) - )), - } - } - }}; - } - - dispatch_challenges!((c001, cpu), (c002, cpu), (c003, cpu), (c004, gpu)); - - if let Some(err_msg) = err_msg { - eprintln!("Verification error: {}", err_msg); - std::process::exit(1); - } - - Ok(()) -} - fn load_settings(settings: &str) -> BenchmarkSettings { let settings = if settings.ends_with(".json") { fs::read_to_string(settings).unwrap_or_else(|_| { @@ -437,28 +291,3 @@ pub fn load_module(path: &PathBuf) -> Result { Err(_) => Err(anyhow!("Failed to load module")), } } - -fn load_solution(solution: &str) -> Solution { - let solution = if solution == "-" { - let mut buffer = String::new(); - std::io::stdin() - .read_to_string(&mut buffer) - .unwrap_or_else(|_| { - eprintln!("Failed to read solution from stdin"); - std::process::exit(1); - }); - buffer - } else if solution.ends_with(".json") { - fs::read_to_string(&solution).unwrap_or_else(|_| { - eprintln!("Failed to read solution file: {}", solution); - std::process::exit(1); - }) - } else { - solution.to_string() - }; - - dejsonify::(&solution).unwrap_or_else(|_| { - eprintln!("Failed to parse solution"); - std::process::exit(1); - }) -} diff --git a/tig-verifier/Cargo.toml b/tig-verifier/Cargo.toml new file mode 100644 index 00000000..18b7489f --- /dev/null +++ b/tig-verifier/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "tig-verifier" +version = "0.1.0" +authors.workspace = true +repository.workspace = true +edition.workspace = true +readme.workspace = true + +[dependencies] +anyhow = "1.0.81" +clap = { version = "4.5.4" } +cudarc = { version = "0.16.2", features = [ + "cuda-version-from-build-system", +], optional = true } +serde = { version = "1.0.196", features = ["derive"] } +serde_json = { version = "1.0.113" } +tig-challenges = { path = "../tig-challenges" } +tig-structs = { path = "../tig-structs" } +tig-utils = { path = "../tig-utils" } + +[features] +cuda = ["cudarc", "tig-challenges/cuda"] diff --git a/tig-verifier/src/main.rs b/tig-verifier/src/main.rs new file mode 100644 index 00000000..81ff1c03 --- /dev/null +++ b/tig-verifier/src/main.rs @@ -0,0 +1,220 @@ +use anyhow::{anyhow, Result}; +use clap::{arg, Command}; +use std::{fs, io::Read, panic, path::PathBuf}; +use tig_challenges::*; +use tig_structs::core::{BenchmarkSettings, Solution}; +use tig_utils::dejsonify; + +#[cfg(feature = "cuda")] +use cudarc::{driver::CudaContext, nvrtc::Ptx, runtime::result::device::get_device_prop}; + +fn cli() -> Command { + Command::new("tig-verifier") + .about("Verifies a solution or merkle proof") + .arg_required_else_help(true) + .subcommand( + Command::new("verify_solution") + .about("Verifies a solution") + .arg( + arg!( "Settings json string or path to json file") + .value_parser(clap::value_parser!(String)), + ) + .arg( + arg!( "A string used in seed generation") + .value_parser(clap::value_parser!(String)), + ) + .arg(arg!( "Nonce value").value_parser(clap::value_parser!(u64))) + .arg( + arg!( "Solution json string, path to json file, or '-' for stdin") + .value_parser(clap::value_parser!(String)), + ) + .arg( + arg!(--ptx [PTX] "Path to a CUDA ptx file") + .value_parser(clap::value_parser!(PathBuf)), + ) + .arg( + arg!(--gpu [GPU] "Which GPU device to use") + .default_value("0") + .value_parser(clap::value_parser!(usize)), + ), + ) + .subcommand( + Command::new("verify_merkle_proof") + .about("Verifies a merkle proof") + .arg(arg!( "Merkle root").value_parser(clap::value_parser!(String))) + .arg( + arg!( "Merkle proof json string, path to json file, or '-' for stdin") + .value_parser(clap::value_parser!(String)), + ), + ) +} + +fn main() { + let matches = cli().get_matches(); + + if let Err(e) = match matches.subcommand() { + Some(("verify_solution", sub_m)) => verify_solution( + sub_m.get_one::("SETTINGS").unwrap().clone(), + sub_m.get_one::("RAND_HASH").unwrap().clone(), + *sub_m.get_one::("NONCE").unwrap(), + sub_m.get_one::("SOLUTION").unwrap().clone(), + sub_m.get_one::("ptx").cloned(), + sub_m.get_one::("gpu").unwrap().clone(), + ), + Some(("verify_merkle_proof", sub_m)) => verify_merkle_proof( + sub_m.get_one::("ROOT").unwrap().clone(), + sub_m.get_one::("PROOF").unwrap().clone(), + ), + _ => Err(anyhow!("Invalid subcommand")), + } { + eprintln!("Error: {}", e); + std::process::exit(1); + } +} + +pub fn verify_solution( + settings: String, + rand_hash: String, + nonce: u64, + solution_path: String, + ptx_path: Option, + gpu_device: usize, +) -> Result<()> { + let settings = load_settings(&settings); + let solution = load_solution(&solution_path); + let seed = settings.calc_seed(&rand_hash, nonce); + + let mut err_msg = Option::::None; + + macro_rules! dispatch_challenges { + ( $( ($c:ident, $cpu_or_gpu:tt) ),+ $(,)? ) => {{ + match settings.challenge_id.as_str() { + $( + stringify!($c) => { + dispatch_challenges!(@expand $c, $cpu_or_gpu); + } + )+ + _ => panic!("Unsupported challenge"), + } + }}; + + (@expand $c:ident, cpu) => {{ + let challenge = $c::Challenge::generate_instance( + seed, + &settings.difficulty.into(), + ).unwrap(); + + match $c::Solution::try_from(solution) { + Ok(solution) => { + match challenge.verify_solution(&solution) { + Ok(_) => println!("Solution is valid"), + Err(e) => err_msg = Some(format!("Invalid solution: {}", e)), + } + }, + Err(_) => err_msg = Some(format!( + "Invalid solution. Cannot convert to {}::Solution", + stringify!($c) + )), + } + }}; + + (@expand $c:ident, gpu) => {{ + #[cfg(not(feature = "cuda"))] + panic!("tig-runtime was not compiled with '--features cuda'"); + + #[cfg(feature = "cuda")] + { + if ptx_path.is_none() { + panic!("PTX file is required for GPU challenges."); + } + + let ptx = Ptx::from_file(ptx_path.unwrap()); + let ctx = CudaContext::new(gpu_device).unwrap(); + ctx.set_blocking_synchronize()?; + let module = ctx.load_module(ptx).unwrap(); + let stream = ctx.default_stream(); + let prop = get_device_prop(gpu_device as i32).unwrap(); + + let challenge = $c::Challenge::generate_instance( + seed, + &settings.difficulty.into(), + module.clone(), + stream.clone(), + &prop, + ).unwrap(); + + match $c::Solution::try_from(solution) { + Ok(solution) => { + match challenge.verify_solution(&solution, module.clone(), stream.clone(), &prop) { + Ok(_) => { + stream.synchronize()?; + ctx.synchronize()?; + println!("Solution is valid"); + }, + Err(e) => err_msg = Some(format!("Invalid solution: {}", e)), + } + }, + Err(_) => err_msg = Some(format!( + "Invalid solution. Cannot convert to {}::Solution", + stringify!($c) + )), + } + } + }}; + } + + dispatch_challenges!((c001, cpu), (c002, cpu), (c003, cpu), (c004, gpu)); + + if let Some(err_msg) = err_msg { + eprintln!("Verification error: {}", err_msg); + std::process::exit(1); + } + + Ok(()) +} + +pub fn verify_merkle_proof(_merkle_root: String, _merkle_proof: String) -> Result<()> { + // TODO + Err(anyhow!("Merkle proof verification is not implemented yet")) +} + +fn load_settings(settings: &str) -> BenchmarkSettings { + let settings = if settings.ends_with(".json") { + fs::read_to_string(settings).unwrap_or_else(|_| { + eprintln!("Failed to read settings file: {}", settings); + std::process::exit(1); + }) + } else { + settings.to_string() + }; + + dejsonify::(&settings).unwrap_or_else(|_| { + eprintln!("Failed to parse settings"); + std::process::exit(1); + }) +} + +fn load_solution(solution: &str) -> Solution { + let solution = if solution == "-" { + let mut buffer = String::new(); + std::io::stdin() + .read_to_string(&mut buffer) + .unwrap_or_else(|_| { + eprintln!("Failed to read solution from stdin"); + std::process::exit(1); + }); + buffer + } else if solution.ends_with(".json") { + fs::read_to_string(&solution).unwrap_or_else(|_| { + eprintln!("Failed to read solution file: {}", solution); + std::process::exit(1); + }) + } else { + solution.to_string() + }; + + dejsonify::(&solution).unwrap_or_else(|_| { + eprintln!("Failed to parse solution"); + std::process::exit(1); + }) +} diff --git a/tig-worker/src/main.rs b/tig-worker/src/main.rs index 2a14602d..c6c8d974 100644 --- a/tig-worker/src/main.rs +++ b/tig-worker/src/main.rs @@ -148,8 +148,7 @@ fn compute_batch( tokio::spawn(async move { let temp_file = NamedTempFile::new()?; let mut cmd = std::process::Command::new(runtime_path); - cmd.arg("compute_solution") - .arg(settings) + cmd.arg(settings) .arg(rand_hash) .arg(nonce.to_string()) .arg(binary_path)