diff --git a/tig-runtime/src/main.rs b/tig-runtime/src/main.rs index 5ee30db..75aff2c 100644 --- a/tig-runtime/src/main.rs +++ b/tig-runtime/src/main.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Result}; use clap::{arg, ArgAction, Command}; use libloading::Library; -use std::{fs, panic, path::PathBuf}; +use std::{fs, io::Read, panic, path::PathBuf}; use tig_challenges::*; use tig_structs::core::{BenchmarkSettings, OutputData, Solution}; use tig_utils::{compress_obj, dejsonify, jsonify}; @@ -58,6 +58,32 @@ fn cli() -> Command { .value_parser(clap::value_parser!(usize)), ), ) + .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)), + ), + ) } fn main() { @@ -75,6 +101,14 @@ fn main() { 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")), } { eprintln!("Error: {}", e); @@ -161,7 +195,6 @@ pub fn compute_solution( )? }; - // Set the fuel limit in the PTX file let ptx_content = std::fs::read_to_string(&ptx_path) .map_err(|e| anyhow!("Failed to read PTX file: {}", e))?; let max_fuel_hex = format!("0x{:016x}", max_fuel); @@ -275,6 +308,111 @@ 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(|_| { @@ -300,42 +438,27 @@ pub fn load_module(path: &PathBuf) -> Result { } } -/* +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() + }; -#include -#include -#include -#include -#include - -#define FUELUSAGE_OK (0) -#define FUELUSAGE_EXCEEDED (1) -#define CHECK_FUEL_LIMIT asm("trap;") - -__device__ u_int64_t gbl_SIGNATURE = 0; // Run-time signature -__device__ u_int64_t gbl_FUELUSAGE = 0; // Fuel usage -__device__ u_int64_t gbl_ERRORSTAT = 0; // Error status -- set to non-zero if fuel runs out - -extern "C" __global__ void initialize_kernel() -{ - gbl_ERRORSTAT = FUELUSAGE_OK; - gbl_SIGNATURE = 0; - gbl_FUELUSAGE = 0; - - return; + dejsonify::(&solution).unwrap_or_else(|_| { + eprintln!("Failed to parse solution"); + std::process::exit(1); + }) } - -extern "C" __global__ void finalize_kernel( - u_int64_t *fuelusage_ptr, // RETURNED: (64-bit) Fuel usage - u_int64_t *signature_ptr, // RETURNED: (64-bit) Run-time signature - u_int64_t *errorstat_ptr // RETURNED: (64-bit) Error status -) -{ - fuelusage_ptr[0] = gbl_FUELUSAGE; // RETURNED: (64-bit) Fuel usage - signature_ptr[0] = gbl_SIGNATURE; // RETURNED: (64-bit) Run-time signature - errorstat_ptr[0] = gbl_ERRORSTAT; // RETURNED: (64-bit) Error status -- set to non-zero if fuel runs out - - return; -} - -*/