diff --git a/Cargo.lock b/Cargo.lock index 88ecc3d5..28ba5191 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1294,7 +1294,7 @@ dependencies = [ [[package]] name = "initializer" -version = "0.5.1" +version = "0.5.2" dependencies = [ "base64 0.21.3", "clap 4.4.4", @@ -1535,6 +1535,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "num-complex" version = "0.4.4" @@ -1792,7 +1801,7 @@ dependencies = [ [[package]] name = "post-cbindings" -version = "0.5.1" +version = "0.5.2" dependencies = [ "cbindgen", "log", @@ -1803,7 +1812,7 @@ dependencies = [ [[package]] name = "post-rs" -version = "0.5.1" +version = "0.5.2" dependencies = [ "aes", "bitvec", @@ -1932,7 +1941,7 @@ dependencies = [ [[package]] name = "profiler" -version = "0.5.1" +version = "0.5.2" dependencies = [ "clap 4.4.4", "env_logger", @@ -2403,7 +2412,7 @@ dependencies = [ [[package]] name = "scrypt-ocl" -version = "0.5.1" +version = "0.5.2" dependencies = [ "log", "ocl", @@ -2490,7 +2499,7 @@ dependencies = [ [[package]] name = "service" -version = "0.5.1" +version = "0.5.2" dependencies = [ "async-stream", "clap 4.4.4", @@ -2503,6 +2512,7 @@ dependencies = [ "prost", "rcgen", "rstest 0.18.2", + "sysinfo", "tempfile", "tokio", "tokio-stream", @@ -2639,6 +2649,21 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sysinfo" +version = "0.29.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a18d114d420ada3a891e6bc8e96a2023402203296a47cdd65083377dad18ba5" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "winapi", +] + [[package]] name = "tap" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index b6933c9b..a7fd192c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [".", "ffi", "scrypt-ocl", "initializer", "profiler", "service"] [package] name = "post-rs" -version = "0.5.1" +version = "0.5.2" edition = "2021" [lib] diff --git a/benches/verifying.rs b/benches/verifying.rs index 9b690dc2..a1c36d8f 100644 --- a/benches/verifying.rs +++ b/benches/verifying.rs @@ -1,49 +1,59 @@ +use std::sync::atomic::AtomicBool; + use criterion::{criterion_group, criterion_main, Criterion}; use post::{ + config::{InitConfig, ProofConfig, ScryptParams}, + initialize::{CpuInitializer, Initialize}, metadata::ProofMetadata, pow::randomx::{PoW, RandomXFlag}, - prove::Proof, - verification::{Verifier, VerifyingParams}, + prove::generate_proof, + verification::Verifier, }; #[cfg(not(windows))] use pprof::criterion::{Output, PProfProfiler}; - -use scrypt_jane::scrypt::ScryptParams; +use tempfile::tempdir; fn verifying(c: &mut Criterion) { let challenge = b"hello world, challenge me!!!!!!!"; - let metadata = ProofMetadata { - node_id: [0u8; 32], - commitment_atx_id: [0u8; 32], - challenge: *challenge, - num_units: 1, - labels_per_unit: 1024 * 1024 * 1024, - }; - let num_labels = metadata.num_units as u64 * metadata.labels_per_unit; - - let verifier = Verifier::new(Box::new( - PoW::new(RandomXFlag::get_recommended_flags()).unwrap(), - )); + let datadir = tempdir().unwrap(); - let (k2, k3) = (37, 37); - let proof = Proof::new( - 0, - (0..k2 as u64).collect::>().as_slice(), - num_labels, - 0, - ); - let params = VerifyingParams { - difficulty: u64::MAX, - k2, - k3, + let cfg = ProofConfig { + k1: 199, + k2: 37, + k3: 37, pow_difficulty: [0xFF; 32], - scrypt: ScryptParams::new(12, 0, 0), }; + let init_cfg = InitConfig { + min_num_units: 1, + max_num_units: 1, + labels_per_unit: 200, + scrypt: ScryptParams::new(8192, 1, 1), + }; + + let metadata = CpuInitializer::new(init_cfg.scrypt) + .initialize( + datadir.path(), + &[0u8; 32], + &[0u8; 32], + init_cfg.labels_per_unit, + 1, + init_cfg.labels_per_unit, + None, + ) + .unwrap(); + + let pow_flags = RandomXFlag::get_recommended_flags(); + // Generate a proof + let stop = AtomicBool::new(false); + let proof = generate_proof(datadir.path(), challenge, cfg, 32, 1, pow_flags, stop).unwrap(); + let metadata = ProofMetadata::new(metadata, *challenge); + // Bench verifying the proof + let verifier = Verifier::new(Box::new(PoW::new(pow_flags).unwrap())); c.bench_function("verify", |b| { b.iter(|| { verifier - .verify(&proof, &metadata, params) + .verify(&proof, &metadata, &cfg, &init_cfg) .expect("proof should be valid"); }); }); diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 0021a0b1..66f3fd38 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "post-cbindings" -version = "0.5.1" +version = "0.5.2" edition = "2021" diff --git a/ffi/src/initialization.rs b/ffi/src/initialization.rs index 7d9c99ec..32bcd8e5 100644 --- a/ffi/src/initialization.rs +++ b/ffi/src/initialization.rs @@ -1,9 +1,9 @@ use std::{error::Error, ffi::c_char, fmt::Debug}; use post::{ + config::ScryptParams, initialize::{CpuInitializer, Initialize}, pos_verification::VerificationError, - ScryptParams, }; use scrypt_ocl::{ocl::DeviceType, OpenClInitializer, ProviderId}; @@ -186,11 +186,7 @@ fn _new_initializer( }; let instance: Box = match provider_id { - CPU_PROVIDER_ID => Box::new(CpuInitializer::new(ScryptParams::new( - n.ilog2() as u8 - 1, - 0, - 0, - ))), + CPU_PROVIDER_ID => Box::new(CpuInitializer::new(ScryptParams::new(n, 1, 1))), id => Box::new(OpenClInitializer::new( Some(ProviderId(id)), n, @@ -260,8 +256,8 @@ mod tests { }; use post::{ + config::ScryptParams, initialize::{CpuInitializer, Initialize, MockInitialize}, - ScryptParams, }; use tempfile::tempdir; @@ -299,7 +295,7 @@ mod tests { let mut expected = Vec::::with_capacity(indices.clone().count()); - CpuInitializer::new(ScryptParams::new(4, 0, 0)) + CpuInitializer::new(ScryptParams::new(32, 1, 1)) .initialize_to( &mut expected, &[0u8; 32], @@ -420,32 +416,25 @@ mod tests { fn initialize_and_verify() { // Initialize some data let datadir = tempdir().unwrap(); + let scrypt = ScryptParams::new(2, 1, 1); - let cfg = post::config::Config { - k1: 23, - k2: 32, - k3: 10, - pow_difficulty: [0xFF; 32], - scrypt: ScryptParams::new(0, 0, 0), - }; - - CpuInitializer::new(cfg.scrypt) + CpuInitializer::new(scrypt) .initialize(datadir.path(), &[0u8; 32], &[0u8; 32], 256, 31, 700, None) .unwrap(); // Verify the data let datapath = CString::new(datadir.path().to_str().unwrap()).unwrap(); - let result = verify_pos(datapath.as_ptr(), null(), null(), 100.0, cfg.scrypt); + let result = verify_pos(datapath.as_ptr(), null(), null(), 100.0, scrypt); assert_eq!(VerifyResult::Ok, result); // verify with wrong scrypt params - let wrong_scrypt = ScryptParams::new(2, 0, 0); + let wrong_scrypt = ScryptParams::new(4, 1, 1); let result = verify_pos(datapath.as_ptr(), null(), null(), 100.0, wrong_scrypt); assert_eq!(VerifyResult::Invalid, result); // verify with non-existent path let path = CString::new("non-existent-path").unwrap(); - let result = verify_pos(path.as_ptr(), null(), null(), 100.0, cfg.scrypt); + let result = verify_pos(path.as_ptr(), null(), null(), 100.0, scrypt); assert_eq!(VerifyResult::Failed, result); } } diff --git a/ffi/src/post_impl.rs b/ffi/src/post_impl.rs index 0dc4ca0b..7ed6e81e 100644 --- a/ffi/src/post_impl.rs +++ b/ffi/src/post_impl.rs @@ -8,13 +8,12 @@ use std::{ sync::atomic::AtomicBool, }; -pub use post::config::Config; -pub use post::metadata::ProofMetadata; -pub use post::ScryptParams; use post::{ + config::{InitConfig, ProofConfig}, + metadata::ProofMetadata, pow::randomx::{PoW, RandomXFlag}, prove, - verification::{Verifier, VerifyingParams}, + verification::Verifier, }; use crate::ArrayU8; @@ -73,7 +72,7 @@ pub unsafe extern "C" fn free_proof(proof: *mut Proof) { pub extern "C" fn generate_proof( datadir: *const c_char, challenge: *const c_uchar, - cfg: Config, + cfg: ProofConfig, nonces: usize, threads: usize, pow_flags: RandomXFlag, @@ -91,7 +90,7 @@ pub extern "C" fn generate_proof( fn _generate_proof( datadir: *const c_char, challenge: *const c_uchar, - cfg: Config, + cfg: ProofConfig, nonces: usize, threads: usize, pow_flags: RandomXFlag, @@ -169,7 +168,8 @@ pub unsafe extern "C" fn verify_proof( verifier: *const Verifier, proof: Proof, metadata: *const ProofMetadata, - cfg: Config, + cfg: ProofConfig, + init_cfg: InitConfig, ) -> VerifyResult { let verifier = match verifier.as_ref() { Some(verifier) => verifier, @@ -192,12 +192,7 @@ pub unsafe extern "C" fn verify_proof( None => return VerifyResult::InvalidArgument, }; - let params = match VerifyingParams::new(metadata, &cfg) { - Ok(params) => params, - Err(_) => return VerifyResult::InvalidArgument, - }; - - match verifier.verify(&proof, metadata, params) { + match verifier.verify(&proof, metadata, &cfg, &init_cfg) { Ok(_) => VerifyResult::Ok, Err(err) => { log::error!("Proof is invalid: {err}"); @@ -208,17 +203,19 @@ pub unsafe extern "C" fn verify_proof( #[cfg(test)] mod tests { - use post::{initialize::Initialize, metadata::ProofMetadata, pow::randomx::RandomXFlag}; + use post::{ + config::ScryptParams, initialize::Initialize, metadata::ProofMetadata, + pow::randomx::RandomXFlag, + }; #[test] fn datadir_must_be_utf8() { let datadir = std::ffi::CString::new([159, 146, 150]).unwrap(); - let cfg = super::Config { + let cfg = super::ProofConfig { k1: 10, k2: 20, k3: 20, pow_difficulty: [0xFF; 32], - scrypt: super::ScryptParams::new(1, 1, 1), }; let result = super::_generate_proof( datadir.as_ptr(), @@ -251,12 +248,17 @@ mod tests { pow: 0, }, std::ptr::null(), - super::Config { + super::ProofConfig { k1: 1, k2: 2, k3: 2, pow_difficulty: [0xFF; 32], - scrypt: super::ScryptParams::new(1, 0, 0), + }, + super::InitConfig { + min_num_units: 1, + max_num_units: 1, + labels_per_unit: 1, + scrypt: ScryptParams::new(2, 1, 1), }, ) }; @@ -266,10 +268,9 @@ mod tests { #[test] fn test_end_to_end() { // Initialize some data first - let labels_per_unit = 200; let datadir = tempfile::tempdir().unwrap(); - let cfg = post::config::Config { + let cfg = post::config::ProofConfig { k1: 10, k2: 10, k3: 10, @@ -278,17 +279,23 @@ mod tests { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, ], - scrypt: post::ScryptParams::new(0, 0, 0), }; - let meta = post::initialize::CpuInitializer::new(cfg.scrypt) + let init_cfg = post::config::InitConfig { + min_num_units: 1, + max_num_units: 2, + labels_per_unit: 200, + scrypt: ScryptParams::new(2, 1, 1), + }; + + let meta = post::initialize::CpuInitializer::new(init_cfg.scrypt) .initialize( datadir.path(), &[77; 32], &[0u8; 32], - labels_per_unit, + init_cfg.labels_per_unit, 2, - labels_per_unit, + 100, None, ) .unwrap(); @@ -314,17 +321,11 @@ mod tests { pow_flags, ); - let proof_metadata = ProofMetadata { - node_id: meta.node_id, - commitment_atx_id: meta.commitment_atx_id, - challenge: *challenge, - num_units: meta.num_units, - labels_per_unit: meta.labels_per_unit, + let proof_metadata = ProofMetadata::new(meta, *challenge); + let result = unsafe { + crate::post_impl::verify_proof(verifier, *cproof, &proof_metadata, cfg, init_cfg) }; - let result = - unsafe { crate::post_impl::verify_proof(verifier, *cproof, &proof_metadata as _, cfg) }; - assert_eq!(result, super::VerifyResult::Ok); // Modify the proof to have different k2pow @@ -336,7 +337,7 @@ mod tests { }; let result = unsafe { - crate::post_impl::verify_proof(verifier, invalid_proof, &proof_metadata as _, cfg) + crate::post_impl::verify_proof(verifier, invalid_proof, &proof_metadata, cfg, init_cfg) }; assert_eq!(result, super::VerifyResult::Invalid); diff --git a/initializer/Cargo.toml b/initializer/Cargo.toml index f78f395f..8d6015b2 100644 --- a/initializer/Cargo.toml +++ b/initializer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "initializer" -version = "0.5.1" +version = "0.5.2" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/initializer/src/main.rs b/initializer/src/main.rs index f9fe615f..7dc19423 100644 --- a/initializer/src/main.rs +++ b/initializer/src/main.rs @@ -9,8 +9,8 @@ use base64::{engine::general_purpose, Engine}; use clap::{Args, Parser, Subcommand, ValueEnum}; use eyre::Context; use post::{ + config::ScryptParams, initialize::{CpuInitializer, Initialize, LABEL_SIZE}, - ScryptParams, }; use rand::seq::IteratorRandom; use rayon::prelude::{ParallelBridge, ParallelIterator}; @@ -128,7 +128,7 @@ fn verify_data(args: VerifyData) -> eyre::Result<()> { let input_file_size = input_file.metadata()?.len(); let labels_in_file = input_file_size / 16; let labels_to_verify = (labels_in_file as f64 * (args.fraction / 100.0)) as usize; - let scrypt_params = ScryptParams::new(args.n.ilog2() as u8 - 1, 0, 0); + let scrypt_params = ScryptParams::new(args.n, 1, 1); let mut rng = rand::thread_rng(); (0..labels_in_file) @@ -171,11 +171,7 @@ fn initialize(args: InitializeArgs) -> eyre::Result<()> { eyre::ensure!(args.n.is_power_of_two(), "scrypt N must be a power of two"); let mut initializer: Box = match args.method { - InitializationMethod::Cpu => Box::new(CpuInitializer::new(ScryptParams::new( - args.n.ilog2() as u8 - 1, - 0, - 0, - ))), + InitializationMethod::Cpu => Box::new(CpuInitializer::new(ScryptParams::new(args.n, 1, 1))), InitializationMethod::Gpu => Box::new(OpenClInitializer::new( args.provider.map(ProviderId), args.n, diff --git a/profiler/Cargo.toml b/profiler/Cargo.toml index c1503917..cd837fff 100644 --- a/profiler/Cargo.toml +++ b/profiler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "profiler" -version = "0.5.1" +version = "0.5.2" edition = "2021" [dependencies] diff --git a/scrypt-ocl/Cargo.toml b/scrypt-ocl/Cargo.toml index 8acb9e74..f7951cc7 100644 --- a/scrypt-ocl/Cargo.toml +++ b/scrypt-ocl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "scrypt-ocl" -version = "0.5.1" +version = "0.5.2" edition = "2021" [dependencies] diff --git a/scrypt-ocl/src/lib.rs b/scrypt-ocl/src/lib.rs index a3e84442..ef3dc8e2 100644 --- a/scrypt-ocl/src/lib.rs +++ b/scrypt-ocl/src/lib.rs @@ -356,8 +356,8 @@ impl Initialize for OpenClInitializer { #[cfg(test)] mod tests { use post::{ + config::ScryptParams, initialize::{CpuInitializer, Initialize}, - ScryptParams, }; use rstest::rstest; @@ -386,7 +386,7 @@ mod tests { .unwrap(); let mut expected = Vec::with_capacity(1); - CpuInitializer::new(ScryptParams::new(12, 0, 0)) + CpuInitializer::new(ScryptParams::new(8192, 1, 1)) .initialize_to(&mut expected, &[0u8; 32], 0..1, None) .unwrap(); @@ -411,7 +411,7 @@ mod tests { let mut expected = Vec::::with_capacity(usize::try_from(indices.end - indices.start).unwrap()); - CpuInitializer::new(ScryptParams::new(n.ilog2() as u8 - 1, 0, 0)) + CpuInitializer::new(ScryptParams::new(n, 1, 1)) .initialize_to(&mut expected, &[0u8; 32], indices, None) .unwrap(); @@ -436,7 +436,7 @@ mod tests { let mut expected = Vec::::with_capacity(usize::try_from(indices.end - indices.start).unwrap()); - CpuInitializer::new(ScryptParams::new(n.ilog2() as u8 - 1, 0, 0)) + CpuInitializer::new(ScryptParams::new(n, 1, 1)) .initialize_to(&mut expected, &[0u8; 32], indices, None) .unwrap(); @@ -457,7 +457,7 @@ mod tests { let mut expected = Vec::::with_capacity(usize::try_from(indices.end - indices.start).unwrap()); - CpuInitializer::new(ScryptParams::new(12, 0, 0)) + CpuInitializer::new(ScryptParams::new(8192, 1, 1)) .initialize_to(&mut expected, commitment, indices, None) .unwrap(); @@ -485,7 +485,7 @@ mod tests { let nonce = opencl_nonce.expect("vrf nonce not found"); let mut label = Vec::::with_capacity(LABEL_SIZE); - let mut cpu_initializer = CpuInitializer::new(ScryptParams::new(n.ilog2() as u8 - 1, 0, 0)); + let mut cpu_initializer = CpuInitializer::new(ScryptParams::new(n, 1, 1)); cpu_initializer .initialize_to(&mut label, commitment, nonce.index..nonce.index + 1, None) .unwrap(); @@ -525,7 +525,7 @@ mod tests { let mut expected = Vec::::with_capacity(usize::try_from(indices.end - indices.start).unwrap()); - CpuInitializer::new(ScryptParams::new(N.ilog2() as u8 - 1, 0, 0)) + CpuInitializer::new(ScryptParams::new(N, 1, 1)) .initialize_to(&mut expected, &[0u8; 32], indices, None) .unwrap(); diff --git a/service/Cargo.toml b/service/Cargo.toml index bc144590..d53d490a 100644 --- a/service/Cargo.toml +++ b/service/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "service" -version = "0.5.1" +version = "0.5.2" edition = "2021" [lib] @@ -26,6 +26,7 @@ env_logger = "0.10.0" clap = { version = "4.4.4", features = ["derive"] } hex = "0.4.3" mockall = "0.11.4" +sysinfo = "0.29.10" [build-dependencies] tonic-build = "0.10.0" diff --git a/service/src/main.rs b/service/src/main.rs index 42607995..77de09a1 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -2,6 +2,7 @@ use std::{fs::read_to_string, net::SocketAddr, path::PathBuf, sync::Arc, time::D use clap::{Args, Parser, ValueEnum}; use eyre::Context; +use sysinfo::{Pid, ProcessExt, ProcessStatus, System, SystemExt}; use tokio::net::TcpListener; use tonic::transport::{Certificate, Identity}; @@ -31,6 +32,10 @@ struct Cli { #[command(flatten, next_help_heading = "TLS configuration")] tls: Option, + /// watch PID and exit if it dies + #[arg(long)] + watch_pid: Option, + /// address to listen on for operator service /// the operator service is disabled if not specified #[arg(long)] @@ -40,14 +45,23 @@ struct Cli { #[derive(Args, Debug)] /// POST configuration - network parameters struct PostConfig { + /// The minimal number of units that must be initialized. + #[arg(long, default_value_t = 4)] + pub min_num_units: u32, + /// The maximal number of units that can be initialized. + #[arg(long, default_value_t = u32::MAX)] + pub max_num_units: u32, + /// The number of labels per unit. + #[arg(long, default_value_t = 4294967296)] + pub labels_per_unit: u64, /// K1 specifies the difficulty for a label to be a candidate for a proof - #[arg(long, default_value = "26")] + #[arg(long, default_value_t = 26)] k1: u32, /// K2 is the number of labels below the required difficulty required for a proof - #[arg(long, default_value = "37")] + #[arg(long, default_value_t = 37)] k2: u32, /// K3 is the size of the subset of proof indices that is validated - #[arg(long, default_value = "37")] + #[arg(long, default_value_t = 37)] k3: u32, /// difficulty for the nonce proof of work (aka "k2pow") #[arg( @@ -61,22 +75,6 @@ struct PostConfig { scrypt: ScryptParams, } -impl From for post::config::Config { - fn from(val: PostConfig) -> Self { - post::config::Config { - k1: val.k1, - k2: val.k2, - k3: val.k3, - pow_difficulty: val.pow_difficulty, - scrypt: post::ScryptParams::new( - val.scrypt.n.ilog2() as u8 - 1, - val.scrypt.r.ilog2() as u8, - val.scrypt.p.ilog2() as u8, - ), - } - } -} - /// Scrypt parameters for initialization #[derive(Args, Debug)] struct ScryptParams { @@ -181,9 +179,25 @@ async fn main() -> eyre::Result<()> { log::info!("POST network parameters: {:?}", args.post_config); log::info!("POST proving settings: {:?}", args.post_settings); + let scrypt = post::config::ScryptParams::new( + args.post_config.scrypt.n, + args.post_config.scrypt.r, + args.post_config.scrypt.p, + ); let service = post_service::service::PostService::new( args.dir, - args.post_config.into(), + post::config::ProofConfig { + k1: args.post_config.k1, + k2: args.post_config.k2, + k3: args.post_config.k3, + pow_difficulty: args.post_config.pow_difficulty, + }, + post::config::InitConfig { + min_num_units: args.post_config.min_num_units, + max_num_units: args.post_config.max_num_units, + labels_per_unit: args.post_config.labels_per_unit, + scrypt, + }, args.post_settings.nonces, args.post_settings.threads, args.post_settings.randomx_mode.into(), @@ -219,5 +233,73 @@ async fn main() -> eyre::Result<()> { } let client = client::ServiceClient::new(args.address, args.reconnect_interval_s, tls, service)?; - client.run().await + let client_handle = tokio::spawn(client.run()); + + if let Some(pid) = args.watch_pid { + tokio::task::spawn_blocking(move || watch_pid(pid, Duration::from_secs(1))).await?; + Ok(()) + } else { + client_handle.await? + } +} + +// watch given PID and return when it dies +fn watch_pid(pid: Pid, interval: Duration) { + log::info!("watching PID {pid}"); + + let mut sys = System::new(); + while sys.refresh_process(pid) { + if let Some(p) = sys.process(pid) { + match p.status() { + ProcessStatus::Zombie | ProcessStatus::Dead => { + break; + } + _ => {} + } + } + std::thread::sleep(interval); + } + + log::info!("PID {pid} died"); +} + +#[cfg(test)] +mod tests { + use std::process::Command; + + use sysinfo::PidExt; + + #[tokio::test] + async fn watching_pid_zombie() { + let mut proc = Command::new("sleep").arg("99999").spawn().unwrap(); + let pid = proc.id(); + + let handle = tokio::task::spawn_blocking(move || { + super::watch_pid( + sysinfo::Pid::from_u32(pid), + std::time::Duration::from_millis(10), + ) + }); + // just kill leaves a zombie process + proc.kill().unwrap(); + handle.await.unwrap(); + } + + #[tokio::test] + async fn watching_pid_reaped() { + let mut proc = Command::new("sleep").arg("99999").spawn().unwrap(); + let pid = proc.id(); + + let handle = tokio::task::spawn_blocking(move || { + super::watch_pid( + sysinfo::Pid::from_u32(pid), + std::time::Duration::from_millis(10), + ) + }); + + // kill and wait + proc.kill().unwrap(); + proc.wait().unwrap(); + handle.await.unwrap(); + } } diff --git a/service/src/service.rs b/service/src/service.rs index 8aa6933d..3b274281 100644 --- a/service/src/service.rs +++ b/service/src/service.rs @@ -10,7 +10,7 @@ use post::{ metadata::{PostMetadata, ProofMetadata}, pow::randomx::{PoW, RandomXFlag}, prove::Proof, - verification::{Verifier, VerifyingParams}, + verification::Verifier, }; #[derive(Debug)] @@ -27,7 +27,8 @@ struct ProofGenProcess { pub struct PostService { datadir: PathBuf, - cfg: post::config::Config, + cfg: post::config::ProofConfig, + init_cfg: post::config::InitConfig, nonces: usize, threads: usize, pow_flags: RandomXFlag, @@ -40,7 +41,8 @@ pub struct PostService { impl PostService { pub fn new( datadir: PathBuf, - cfg: post::config::Config, + cfg: post::config::ProofConfig, + init_cfg: post::config::InitConfig, nonces: usize, threads: usize, pow_flags: RandomXFlag, @@ -49,6 +51,7 @@ impl PostService { proof_generation: Mutex::new(None), datadir, cfg, + init_cfg, nonces, threads, pow_flags, @@ -113,7 +116,7 @@ impl crate::client::PostService for PostService { fn verify_proof(&self, proof: &Proof, metadata: &ProofMetadata) -> eyre::Result<()> { self.verifier - .verify(proof, metadata, VerifyingParams::new(metadata, &self.cfg)?) + .verify(proof, metadata, &self.cfg, &self.init_cfg) .wrap_err("verifying proof") } diff --git a/service/tests/test_client.rs b/service/tests/test_client.rs index 29339c2f..eb8c659e 100644 --- a/service/tests/test_client.rs +++ b/service/tests/test_client.rs @@ -224,15 +224,20 @@ async fn test_broken_request_no_kind() { #[tokio::test] async fn test_get_metadata(#[case] vrf_difficulty: Option<[u8; 32]>) { let datadir = tempdir().unwrap(); - let cfg = post::config::Config { + let cfg = post::config::ProofConfig { k1: 23, k2: 32, k3: 10, pow_difficulty: [0xFF; 32], - scrypt: post::ScryptParams::new(0, 0, 0), + }; + let init_cfg = post::config::InitConfig { + min_num_units: 1, + max_num_units: 1000, + labels_per_unit: 256 * 16, + scrypt: post::config::ScryptParams::new(2, 1, 1), }; - let metadata = CpuInitializer::new(cfg.scrypt) + let metadata = CpuInitializer::new(init_cfg.scrypt) .initialize( datadir.path(), &[77; 32], @@ -249,6 +254,7 @@ async fn test_get_metadata(#[case] vrf_difficulty: Option<[u8; 32]>) { let service = post_service::service::PostService::new( datadir.path().into(), cfg, + init_cfg, 16, 1, post::pow::randomx::RandomXFlag::get_recommended_flags(), diff --git a/service/tests/test_operator.rs b/service/tests/test_operator.rs index 9d172a56..768af9bd 100644 --- a/service/tests/test_operator.rs +++ b/service/tests/test_operator.rs @@ -6,7 +6,6 @@ use tokio::{net::TcpListener, time::sleep}; use post::{ initialize::{CpuInitializer, Initialize}, pow::randomx::RandomXFlag, - ScryptParams, }; use post_service::{ client::spacemesh_v1::{service_response, GenProofStatus}, @@ -28,23 +27,43 @@ async fn test_gen_proof_in_progress() { // Initialize some data let datadir = tempfile::tempdir().unwrap(); - let cfg = post::config::Config { + let cfg = post::config::ProofConfig { k1: 8, k2: 4, k3: 4, pow_difficulty: [0xFF; 32], - scrypt: ScryptParams::new(0, 0, 0), + }; + let init_cfg = post::config::InitConfig { + min_num_units: 1, + max_num_units: 100, + labels_per_unit: 256, + scrypt: post::config::ScryptParams::new(2, 1, 1), }; - CpuInitializer::new(cfg.scrypt) - .initialize(datadir.path(), &[0xBE; 32], &[0xCE; 32], 256, 4, 256, None) + CpuInitializer::new(init_cfg.scrypt) + .initialize( + datadir.path(), + &[0xBE; 32], + &[0xCE; 32], + init_cfg.labels_per_unit, + 4, + 256, + None, + ) .unwrap(); let pow_flags = RandomXFlag::get_recommended_flags(); let service = Arc::new( - post_service::service::PostService::new(datadir.into_path(), cfg, 16, 1, pow_flags) - .unwrap(), + post_service::service::PostService::new( + datadir.into_path(), + cfg, + init_cfg, + 16, + 1, + pow_flags, + ) + .unwrap(), ); let mut test_server = TestServer::new().await; diff --git a/service/tests/test_service.rs b/service/tests/test_service.rs index c479a5bf..65dbe832 100644 --- a/service/tests/test_service.rs +++ b/service/tests/test_service.rs @@ -1,35 +1,39 @@ use std::{thread::sleep, time::Duration}; use post::{ + config::{InitConfig, ProofConfig, ScryptParams}, initialize::{CpuInitializer, Initialize}, metadata::ProofMetadata, pow::randomx::RandomXFlag, - ScryptParams, }; use post_service::{client::PostService, service::ProofGenState}; #[test] fn test_generate_and_verify() { // Initialize some data - let labels_per_unit = 256; let datadir = tempfile::tempdir().unwrap(); - let cfg = post::config::Config { + let cfg = ProofConfig { k1: 8, k2: 4, k3: 4, pow_difficulty: [0xFF; 32], - scrypt: ScryptParams::new(0, 0, 0), + }; + let init_cfg = InitConfig { + min_num_units: 1, + max_num_units: 1000, + labels_per_unit: 256, + scrypt: ScryptParams::new(2, 1, 1), }; - let metadata = CpuInitializer::new(cfg.scrypt) + let metadata = CpuInitializer::new(init_cfg.scrypt) .initialize( datadir.path(), &[0xBE; 32], &[0xCE; 32], - labels_per_unit, + init_cfg.labels_per_unit, 4, - labels_per_unit, + init_cfg.labels_per_unit, None, ) .unwrap(); @@ -37,9 +41,15 @@ fn test_generate_and_verify() { let pow_flags = RandomXFlag::get_recommended_flags(); // Generate a proof - let service = - post_service::service::PostService::new(datadir.into_path(), cfg, 16, 1, pow_flags) - .unwrap(); + let service = post_service::service::PostService::new( + datadir.into_path(), + cfg, + init_cfg, + 16, + 1, + pow_flags, + ) + .unwrap(); let (proof, metadata) = loop { if let ProofGenState::Finished { proof } = service.gen_proof(vec![0xCA; 32]).unwrap() { @@ -57,25 +67,29 @@ fn test_generate_and_verify() { #[test] fn reject_invalid_challenge() { // Initialize some data - let labels_per_unit = 256; let datadir = tempfile::tempdir().unwrap(); - let cfg = post::config::Config { + let cfg = ProofConfig { k1: 8, k2: 4, k3: 4, pow_difficulty: [0xFF; 32], - scrypt: ScryptParams::new(0, 0, 0), + }; + let init_cfg = InitConfig { + min_num_units: 1, + max_num_units: 1000, + labels_per_unit: 256, + scrypt: ScryptParams::new(2, 1, 1), }; - CpuInitializer::new(cfg.scrypt) + CpuInitializer::new(init_cfg.scrypt) .initialize( datadir.path(), &[0xBE; 32], &[0xCE; 32], - labels_per_unit, + init_cfg.labels_per_unit, 4, - labels_per_unit, + init_cfg.labels_per_unit, None, ) .unwrap(); @@ -84,6 +98,7 @@ fn reject_invalid_challenge() { let service = post_service::service::PostService::new( datadir.into_path(), cfg, + init_cfg, 16, 1, RandomXFlag::get_recommended_flags(), @@ -95,25 +110,29 @@ fn reject_invalid_challenge() { #[test] fn cannot_run_parallel_proof_gens() { // Initialize some data - let labels_per_unit = 256; let datadir = tempfile::tempdir().unwrap(); - let cfg = post::config::Config { + let cfg = ProofConfig { k1: 8, k2: 4, k3: 4, pow_difficulty: [0xFF; 32], - scrypt: ScryptParams::new(0, 0, 0), + }; + let init_cfg = InitConfig { + min_num_units: 1, + max_num_units: 1000, + labels_per_unit: 256, + scrypt: ScryptParams::new(2, 1, 1), }; - CpuInitializer::new(cfg.scrypt) + CpuInitializer::new(init_cfg.scrypt) .initialize( datadir.path(), &[0xBE; 32], &[0xCE; 32], - labels_per_unit, + init_cfg.labels_per_unit, 4, - labels_per_unit, + init_cfg.labels_per_unit, None, ) .unwrap(); @@ -122,6 +141,7 @@ fn cannot_run_parallel_proof_gens() { let service = post_service::service::PostService::new( datadir.into_path(), cfg, + init_cfg, 16, 1, RandomXFlag::get_recommended_flags(), diff --git a/src/config.rs b/src/config.rs index ecb840ab..65519eaf 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,19 @@ #[repr(C)] #[derive(Debug, Clone, Copy)] -pub struct Config { +pub struct InitConfig { + /// The minimal number of units that must be initialized. + pub min_num_units: u32, + /// The maximal number of units that can be initialized. + pub max_num_units: u32, + /// The number of labels per unit. + pub labels_per_unit: u64, + /// Scrypt paramters for initilizing labels + pub scrypt: ScryptParams, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct ProofConfig { /// K1 specifies the difficulty for a label to be a candidate for a proof. pub k1: u32, /// K2 is the number of labels below the required difficulty required for a proof. @@ -10,6 +23,32 @@ pub struct Config { /// Difficulty for the nonce proof of work. Lower values increase difficulty of finding /// `pow` for [Proof][crate::prove::Proof]. pub pow_difficulty: [u8; 32], - /// Scrypt paramters for initilizing labels - pub scrypt: scrypt_jane::scrypt::ScryptParams, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct ScryptParams { + pub n: usize, + pub r: usize, + pub p: usize, +} + +impl ScryptParams { + pub fn new(n: usize, r: usize, p: usize) -> Self { + assert!(n >= 2); + assert!(n.is_power_of_two()); + assert!(r.is_power_of_two()); + assert!(p.is_power_of_two()); + Self { n, r, p } + } +} + +impl From for scrypt_jane::scrypt::ScryptParams { + fn from(params: ScryptParams) -> Self { + Self::new( + params.n.ilog2() as u8 - 1, + params.r.ilog2() as u8, + params.p.ilog2() as u8, + ) + } } diff --git a/src/difficulty.rs b/src/difficulty.rs index dd7aca2e..aabc91da 100644 --- a/src/difficulty.rs +++ b/src/difficulty.rs @@ -1,4 +1,4 @@ -use eyre::Context; +use primitive_types::U256; /// Calculate proving difficulty. /// @@ -7,14 +7,30 @@ use eyre::Context; /// /// The difficulty is calculated as: /// difficulty = 2^64 * K1 / num_labels -pub(crate) fn proving_difficulty(k1: u32, num_labels: u64) -> eyre::Result { - eyre::ensure!(num_labels > 0, "number of label blocks must be > 0"); - eyre::ensure!( - num_labels > k1 as u64, - format!("number of labels ({num_labels}) must be bigger than k1 ({k1})") - ); +pub(crate) fn proving_difficulty(k1: u32, num_labels: u64) -> Result { + if num_labels == 0 { + return Err("number of label blocks must be > 0".to_string()); + } + if num_labels <= k1 as u64 { + return Err(format!( + "number of labels ({num_labels}) must be bigger than k1 ({k1})" + )); + } let difficulty = (1u128 << 64) * k1 as u128 / num_labels as u128; - u64::try_from(difficulty).wrap_err("difficulty doesn't fit in u64") + u64::try_from(difficulty).or(Err("difficulty doesn't fit in u64".to_string())) +} + +/// Scale PoW difficulty by the number of units. +/// +/// The more units of data, the more difficult the PoW should be (linearly). +/// Because the PoW looks for values < difficulty, we need to scale the difficulty down. +/// The difficulty threshold is calculated as: +/// difficulty = difficulty / num_units +pub(crate) fn scale_pow_difficulty(difficulty: &[u8; 32], num_units: u32) -> [u8; 32] { + let difficulty_scaled = U256::from_big_endian(difficulty) / num_units; + let mut difficulty = [0u8; 32]; + difficulty_scaled.to_big_endian(&mut difficulty); + difficulty } #[test] @@ -34,3 +50,31 @@ fn difficulty_calculation() { assert_eq!(proving_difficulty(1, 4).unwrap(), 1u64 << (64 - 2)); assert_eq!(proving_difficulty(1, 128).unwrap(), 1u64 << (64 - 7)); } + +/// Test that PoW threshold is scaled with num_units. +#[test] +fn scaling_pow_thresholds() { + { + // don't scale when num_units is 1 + let difficulty = scale_pow_difficulty(&[0xFF; 32], 1); + assert_eq!(difficulty, [0xFF; 32]); + } + { + // scale with num_units + let difficulty = scale_pow_difficulty(&[0xFF; 32], 2); + assert!(difficulty < [0xFF; 32]); + assert_eq!( + difficulty.as_slice(), + [&[0x7F], [0xFF; 31].as_slice()].concat() + ); + } + { + // scale with num_units + let difficulty = scale_pow_difficulty(&[0xFF; 32], 2_u32.pow(5)); + assert!(difficulty < [0xFF; 32]); + assert_eq!( + difficulty.as_slice(), + [&[0xFF >> 5], [0xFF; 31].as_slice()].concat() + ); + } +} diff --git a/src/initialize.rs b/src/initialize.rs index 409dd20d..884661f0 100644 --- a/src/initialize.rs +++ b/src/initialize.rs @@ -8,9 +8,9 @@ use std::{ use mockall::automock; use rayon::prelude::{IntoParallelIterator, ParallelIterator}; -use scrypt_jane::scrypt::{scrypt, ScryptParams}; +use scrypt_jane::scrypt::scrypt; -use crate::metadata::PostMetadata; +use crate::{config::ScryptParams, metadata::PostMetadata}; pub const LABEL_SIZE: usize = 16; pub const ENTIRE_LABEL_SIZE: usize = 32; @@ -115,7 +115,7 @@ impl Initialize for CpuInitializer { let mut scrypt_data = [0u8; 72]; scrypt_data[0..32].copy_from_slice(commitment); scrypt_data[32..40].copy_from_slice(&index.to_le_bytes()); - scrypt(&scrypt_data, &[], self.scrypt_params, &mut label); + scrypt(&scrypt_data, &[], self.scrypt_params.into(), &mut label); label }) .collect::>(); @@ -168,7 +168,7 @@ mod tests { let mut pos_file = tempfile::tempfile().unwrap(); let commitment = [0u8; 32]; - let scrypt_params = ScryptParams::new(1, 0, 0); + let scrypt_params = ScryptParams::new(4, 1, 1); CpuInitializer::new(scrypt_params) .initialize_to(&mut pos_file, &commitment, labels, None) .unwrap(); @@ -178,7 +178,7 @@ mod tests { #[test] fn test_initialize_fits_in_single_file() { - let scrypt_params = ScryptParams::new(1, 0, 0); + let scrypt_params = ScryptParams::new(4, 1, 1); let data_dir = tempfile::tempdir().unwrap(); let data_path = data_dir.path(); CpuInitializer::new(scrypt_params) @@ -201,7 +201,7 @@ mod tests { #[test] fn test_initialize_returns_metadata() { - let scrypt_params = ScryptParams::new(1, 0, 0); + let scrypt_params = ScryptParams::new(4, 1, 1); let data_dir = tempfile::tempdir().unwrap(); let node_id = rand::random::<[u8; 32]>(); let commitment_atx_id = rand::random::<[u8; 32]>(); @@ -228,7 +228,7 @@ mod tests { #[test] fn test_initialize_split_many_files() { - let scrypt_params = ScryptParams::new(1, 0, 0); + let scrypt_params = ScryptParams::new(4, 1, 1); let data_dir = tempfile::tempdir().unwrap(); let data_path = data_dir.path(); CpuInitializer::new(scrypt_params) @@ -262,7 +262,7 @@ mod tests { #[test] fn initialization_to_many_files_gives_same_result_as_single_file() { - let scrypt_params = ScryptParams::new(1, 0, 0); + let scrypt_params = ScryptParams::new(4, 1, 1); let data_dir = tempfile::tempdir().unwrap(); let data_path = data_dir.path(); diff --git a/src/lib.rs b/src/lib.rs index a0beab4c..7bb1db03 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,3 @@ pub mod prove; mod random_values_gen; pub mod reader; pub mod verification; - -// Reexport scrypt-jane params -pub use scrypt_jane::scrypt::ScryptParams; diff --git a/src/pos_verification.rs b/src/pos_verification.rs index 96bafd28..a6cb69e3 100644 --- a/src/pos_verification.rs +++ b/src/pos_verification.rs @@ -5,9 +5,9 @@ use std::{io::Read, io::Seek, path::Path}; use itertools::Itertools; use rand::seq::IteratorRandom; use rayon::prelude::{ParallelBridge, ParallelIterator}; -use scrypt_jane::scrypt::ScryptParams; use crate::{ + config::ScryptParams, initialize::{calc_commitment, CpuInitializer, Initialize}, metadata, }; diff --git a/src/prove.rs b/src/prove.rs index cf461582..d74b8eda 100644 --- a/src/prove.rs +++ b/src/prove.rs @@ -25,7 +25,7 @@ use rayon::prelude::{ParallelBridge, ParallelIterator}; use crate::{ cipher::AesCipher, compression::{compress_indices, required_bits}, - config::Config, + config::ProofConfig, difficulty::proving_difficulty, metadata::{self, PostMetadata}, pow, @@ -61,13 +61,13 @@ pub struct ProvingParams { } impl ProvingParams { - pub fn new(metadata: &PostMetadata, cfg: &Config) -> eyre::Result { + pub fn new(metadata: &PostMetadata, cfg: &ProofConfig) -> eyre::Result { let num_labels = metadata.num_units as u64 * metadata.labels_per_unit; let mut pow_difficulty = [0u8; 32]; let difficulty_scaled = U256::from_big_endian(&cfg.pow_difficulty) / metadata.num_units; difficulty_scaled.to_big_endian(&mut pow_difficulty); Ok(Self { - difficulty: proving_difficulty(cfg.k1, num_labels)?, + difficulty: proving_difficulty(cfg.k1, num_labels).map_err(|e| eyre::eyre!(e))?, pow_difficulty, }) } @@ -264,7 +264,7 @@ impl Prover for Prover8_56 { pub fn generate_proof( datadir: &Path, challenge: &[u8; 32], - cfg: Config, + cfg: ProofConfig, nonces: usize, threads: usize, pow_flags: RandomXFlag, @@ -357,7 +357,6 @@ mod tests { use crate::{compression::decompress_indexes, difficulty::proving_difficulty}; use mockall::predicate::{always, eq}; use rand::{thread_rng, RngCore}; - use scrypt_jane::scrypt::ScryptParams; use std::{collections::HashMap, iter::repeat}; #[test] @@ -383,12 +382,11 @@ mod tests { max_file_size: 1024, ..Default::default() }; - let cfg = Config { + let cfg = ProofConfig { k1: 279, k2: 300, k3: 65, pow_difficulty: [0xFF; 32], - scrypt: ScryptParams::new(1, 0, 0), }; let params = ProvingParams::new(&meta, &cfg).unwrap(); let mut pow_prover = pow::MockProver::new(); @@ -419,12 +417,11 @@ mod tests { max_file_size: 1024, ..Default::default() }; - let cfg = Config { + let cfg = ProofConfig { k1: 279, k2: 300, k3: 65, pow_difficulty: [0xFF; 32], - scrypt: ScryptParams::new(1, 0, 0), }; let mut pow_prover = pow::MockProver::new(); pow_prover @@ -438,12 +435,11 @@ mod tests { /// Test that PoW threshold is scaled with num_units. #[test] fn scaling_pows_thresholds() { - let cfg = Config { + let cfg = ProofConfig { k1: 32, k2: 32, k3: 10, pow_difficulty: [0x0F; 32], - scrypt: ScryptParams::new(2, 0, 0), }; let metadata = PostMetadata { num_units: 1, diff --git a/src/verification.rs b/src/verification.rs index 6ce6d694..fbc4bd34 100644 --- a/src/verification.rs +++ b/src/verification.rs @@ -39,14 +39,12 @@ use std::cmp::Ordering; use cipher::BlockEncrypt; use itertools::Itertools; -use primitive_types::U256; -use scrypt_jane::scrypt::ScryptParams; use crate::{ cipher::AesCipher, compression::{decompress_indexes, required_bits}, - config::Config, - difficulty::proving_difficulty, + config::{InitConfig, ProofConfig}, + difficulty::{proving_difficulty, scale_pow_difficulty}, initialize::{calc_commitment, generate_label}, metadata::ProofMetadata, pow::PowVerifier, @@ -56,35 +54,6 @@ use crate::{ const NONCES_PER_AES: u32 = Prover8_56::NONCES_PER_AES; -#[derive(Debug, Clone, Copy)] -pub struct VerifyingParams { - pub difficulty: u64, - pub k2: u32, - pub k3: u32, - pub pow_difficulty: [u8; 32], - pub scrypt: ScryptParams, -} - -impl VerifyingParams { - pub fn new(metadata: &ProofMetadata, cfg: &Config) -> eyre::Result { - let num_labels = metadata.num_units as u64 * metadata.labels_per_unit; - - // Scale PoW difficulty by number of units - eyre::ensure!(metadata.num_units > 0, "num_units must be > 0"); - let difficulty_scaled = U256::from_big_endian(&cfg.pow_difficulty) / metadata.num_units; - let mut pow_difficulty = [0u8; 32]; - difficulty_scaled.to_big_endian(&mut pow_difficulty); - - Ok(Self { - difficulty: proving_difficulty(cfg.k1, num_labels)?, - k2: cfg.k2, - k3: cfg.k3, - pow_difficulty, - scrypt: cfg.scrypt, - }) - } -} - pub struct Verifier { pow_verifier: Box, } @@ -111,6 +80,45 @@ pub enum Error { difficulty_lsb: u64, label: [u8; 16], }, + #[error(transparent)] + InvalidMetadata(#[from] MetadataValidationError), + #[error("invalid number of labels: (0)")] + InvalidNumLabels(String), +} + +#[derive(thiserror::Error, Debug)] +pub enum MetadataValidationError { + #[error("numunits too small: {got} < {min}")] + NumUnitsTooSmall { min: u32, got: u32 }, + #[error("numunits too large: {got} < {max}")] + NumUnitsTooLarge { max: u32, got: u32 }, + #[error("invalid labels_per_unit: {got} != {expected}")] + LabelsPerUnitInvalid { expected: u64, got: u64 }, +} + +pub fn verify_metadata( + metadata: &ProofMetadata, + init_cfg: &InitConfig, +) -> Result<(), MetadataValidationError> { + if metadata.num_units < init_cfg.min_num_units { + return Err(MetadataValidationError::NumUnitsTooSmall { + min: init_cfg.min_num_units, + got: metadata.num_units, + }); + } + if metadata.num_units > init_cfg.max_num_units { + return Err(MetadataValidationError::NumUnitsTooLarge { + max: init_cfg.max_num_units, + got: metadata.num_units, + }); + } + if metadata.labels_per_unit != init_cfg.labels_per_unit { + return Err(MetadataValidationError::LabelsPerUnitInvalid { + expected: init_cfg.labels_per_unit, + got: metadata.labels_per_unit, + }); + } + Ok(()) } impl Verifier { @@ -130,9 +138,13 @@ impl Verifier { &self, proof: &Proof, metadata: &ProofMetadata, - params: VerifyingParams, + cfg: &ProofConfig, + init_cfg: &InitConfig, ) -> Result<(), Error> { + verify_metadata(metadata, init_cfg)?; + let challenge = metadata.challenge; + let pow_difficulty = scale_pow_difficulty(&cfg.pow_difficulty, metadata.num_units); // Verify K2 PoW let nonce_group = proof.nonce / NONCES_PER_AES; @@ -142,14 +154,14 @@ impl Verifier { .try_into() .map_err(|_| Error::NonceGroupOutOfBounds(nonce_group))?, &challenge[..8].try_into().unwrap(), - ¶ms.pow_difficulty, + &pow_difficulty, &metadata.node_id, )?; // Verify the number of indices against K2 - let num_lables = metadata.num_units as u64 * metadata.labels_per_unit; - let bits_per_index = required_bits(num_lables); - let expected = expected_indices_bytes(bits_per_index, params.k2); + let num_labels = metadata.num_units as u64 * init_cfg.labels_per_unit; + let bits_per_index = required_bits(num_labels); + let expected = expected_indices_bytes(bits_per_index, cfg.k2); if proof.indices.len() != expected { return Err(Error::InvalidIndicesLen { expected, @@ -158,12 +170,14 @@ impl Verifier { } let indices_unpacked = decompress_indexes(&proof.indices, bits_per_index) - .take(params.k2 as usize) + .take(cfg.k2 as usize) .collect_vec(); let commitment = calc_commitment(&metadata.node_id, &metadata.commitment_atx_id); let cipher = AesCipher::new(&challenge, nonce_group, proof.pow); let lazy_cipher = AesCipher::new_lazy(&challenge, proof.nonce, nonce_group, proof.pow); - let (difficulty_msb, difficulty_lsb) = Prover8_56::split_difficulty(params.difficulty); + + let difficulty = proving_difficulty(cfg.k1, num_labels).map_err(Error::InvalidNumLabels)?; + let (difficulty_msb, difficulty_lsb) = Prover8_56::split_difficulty(difficulty); let output_index = (proof.nonce % NONCES_PER_AES) as usize; @@ -175,11 +189,11 @@ impl Verifier { &proof.pow.to_le_bytes(), ]; - let k3_indices = RandomValuesIterator::new(indices_unpacked, seed).take(params.k3 as usize); + let k3_indices = RandomValuesIterator::new(indices_unpacked, seed).take(cfg.k3 as usize); k3_indices.into_iter().try_for_each(|index| { let mut output = [0u8; 16]; - let label = generate_label(&commitment, params.scrypt, index); + let label = generate_label(&commitment, init_cfg.scrypt, index); cipher .aes .encrypt_block_b2b(&label.into(), (&mut output).into()); @@ -239,14 +253,15 @@ fn expected_indices_bytes(required_bits: usize, k2: u32) -> usize { mod tests { use std::borrow::Cow; - use scrypt_jane::scrypt::ScryptParams; - use crate::{ - config::Config, metadata::ProofMetadata, pow::MockPowVerifier, prove::Proof, + config::{InitConfig, ProofConfig, ScryptParams}, + metadata::ProofMetadata, + pow::MockPowVerifier, + prove::Proof, verification::Error, }; - use super::{expected_indices_bytes, next_multiple_of, Verifier, VerifyingParams}; + use super::{expected_indices_bytes, next_multiple_of, Verifier}; #[test] fn test_next_mutliple_of() { @@ -263,12 +278,17 @@ mod tests { #[test] fn reject_invalid_pow() { - let params = VerifyingParams { - difficulty: u64::MAX, + let cfg = ProofConfig { + k1: 3, k2: 3, k3: 3, pow_difficulty: [0xFF; 32], - scrypt: ScryptParams::new(1, 0, 0), + }; + let init_cfg = InitConfig { + min_num_units: 1, + max_num_units: 10, + labels_per_unit: 2048, + scrypt: ScryptParams::new(2, 1, 1), }; let fake_metadata = ProofMetadata { @@ -290,19 +310,25 @@ mod tests { pow: 0, }, &fake_metadata, - params, + &cfg, + &init_cfg, ); assert!(matches!(result, Err(Error::InvalidPoW(_)))); } #[test] fn reject_invalid_proof() { - let params = VerifyingParams { - difficulty: u64::MAX, + let pcfg = ProofConfig { + k1: 10, k2: 10, k3: 10, pow_difficulty: [0xFF; 32], - scrypt: ScryptParams::new(1, 0, 0), + }; + let icfg = InitConfig { + min_num_units: 1, + max_num_units: 10, + labels_per_unit: 2048, + scrypt: ScryptParams::new(4, 1, 1), }; let fake_metadata = ProofMetadata { @@ -323,7 +349,7 @@ mod tests { indices: Cow::from(vec![]), pow: 0, }; - let result = verifier.verify(&empty_proof, &fake_metadata, params); + let result = verifier.verify(&empty_proof, &fake_metadata, &pcfg, &icfg); assert!(matches!( result, Err(Error::InvalidIndicesLen { @@ -338,7 +364,7 @@ mod tests { indices: Cow::from(vec![]), pow: 0, }; - let res = verifier.verify(&nonce_out_of_bounds_proof, &fake_metadata, params); + let res = verifier.verify(&nonce_out_of_bounds_proof, &fake_metadata, &pcfg, &icfg); assert!(matches!(res, Err(Error::NonceGroupOutOfBounds(256)))); } { @@ -347,7 +373,8 @@ mod tests { indices: Cow::from(vec![1, 2, 3]), pow: 0, }; - let result = verifier.verify(&proof_with_not_enough_indices, &fake_metadata, params); + let result = + verifier.verify(&proof_with_not_enough_indices, &fake_metadata, &pcfg, &icfg); assert!(matches!( result, Err(Error::InvalidIndicesLen { @@ -358,50 +385,42 @@ mod tests { } } - /// Test that PoW threshold is scaled with num_units. #[test] - fn scaling_pow_thresholds() { - let cfg = Config { - k1: 0, - k2: 0, - k3: 0, - pow_difficulty: [0xFF; 32], - scrypt: ScryptParams::new(2, 0, 0), - }; - let metadata = ProofMetadata { - node_id: [0u8; 32], - commitment_atx_id: [0u8; 32], - challenge: [0u8; 32], + fn verify_metadata() { + let valid_meta = ProofMetadata { + node_id: [0; 32], + commitment_atx_id: [0; 32], + challenge: [0; 32], num_units: 1, labels_per_unit: 100, }; + let init_cfg = InitConfig { + min_num_units: 1, + max_num_units: 10, + labels_per_unit: 100, + scrypt: ScryptParams::new(2, 1, 1), + }; + assert!(super::verify_metadata(&valid_meta, &init_cfg).is_ok()); { - // reject zero num_units - let params = VerifyingParams::new( - &ProofMetadata { - num_units: 0, - ..metadata - }, - &cfg, - ); - assert!(params.is_err()); + let num_units_small = ProofMetadata { + num_units: 0, + ..valid_meta + }; + assert!(super::verify_metadata(&num_units_small, &init_cfg).is_err()); } { - // don't scale when num_units is 1 - let params = VerifyingParams::new(&metadata, &cfg).unwrap(); - assert_eq!(params.pow_difficulty, cfg.pow_difficulty); + let num_units_large = ProofMetadata { + num_units: 99, + ..valid_meta + }; + assert!(super::verify_metadata(&num_units_large, &init_cfg).is_err()); } { - // scale with num_units - let params = VerifyingParams::new( - &ProofMetadata { - num_units: 10, - ..metadata - }, - &cfg, - ) - .unwrap(); - assert!(params.pow_difficulty < cfg.pow_difficulty); + let invalid_labels_per_unit = ProofMetadata { + labels_per_unit: 99, + ..valid_meta + }; + assert!(super::verify_metadata(&invalid_labels_per_unit, &init_cfg).is_err()); } } } diff --git a/tests/generate_and_verify.rs b/tests/generate_and_verify.rs index b22dcf02..425863e5 100644 --- a/tests/generate_and_verify.rs +++ b/tests/generate_and_verify.rs @@ -1,38 +1,42 @@ use std::sync::atomic::AtomicBool; use post::{ + config::{InitConfig, ScryptParams}, initialize::{CpuInitializer, Initialize}, metadata::ProofMetadata, pow::randomx::{PoW, RandomXFlag}, prove::generate_proof, - verification::{Verifier, VerifyingParams}, + verification::Verifier, }; -use scrypt_jane::scrypt::ScryptParams; use tempfile::tempdir; #[test] fn test_generate_and_verify() { // Initialize some data let challenge = b"hello world, challenge me!!!!!!!"; - let labels_per_unit = 256 * 16; let datadir = tempdir().unwrap(); - let cfg = post::config::Config { + let cfg = post::config::ProofConfig { k1: 23, k2: 32, k3: 10, pow_difficulty: [0xFF; 32], - scrypt: ScryptParams::new(0, 0, 0), + }; + let init_cfg = InitConfig { + min_num_units: 1, + max_num_units: 1000, + labels_per_unit: 256 * 16, + scrypt: ScryptParams::new(2, 1, 1), }; - let metadata = CpuInitializer::new(cfg.scrypt) + let metadata = CpuInitializer::new(init_cfg.scrypt) .initialize( datadir.path(), &[77; 32], &[0u8; 32], - labels_per_unit, + init_cfg.labels_per_unit, 31, - labels_per_unit, + 1000, None, ) .unwrap(); @@ -46,22 +50,14 @@ fn test_generate_and_verify() { let metadata = ProofMetadata::new(metadata, *challenge); let verifier = Verifier::new(Box::new(PoW::new(pow_flags).unwrap())); verifier - .verify( - &proof, - &metadata, - VerifyingParams::new(&metadata, &cfg).unwrap(), - ) + .verify(&proof, &metadata, &cfg, &init_cfg) .expect("proof should be valid"); // Check that the proof is invalid if we modify one index let mut invalid_proof = proof; invalid_proof.pow -= 1; verifier - .verify( - &invalid_proof, - &metadata, - VerifyingParams::new(&metadata, &cfg).unwrap(), - ) + .verify(&invalid_proof, &metadata, &cfg, &init_cfg) .expect_err("proof should be invalid"); } @@ -71,25 +67,29 @@ fn test_generate_and_verify() { fn test_generate_and_verify_difficulty_msb_not_zero() { // Initialize some data let challenge = b"hello world, challenge me!!!!!!!"; - let labels_per_unit = 200; let datadir = tempdir().unwrap(); - let cfg = post::config::Config { + let cfg = post::config::ProofConfig { k1: 20, k2: 30, k3: 30, pow_difficulty: [0xFF; 32], - scrypt: ScryptParams::new(0, 0, 0), + }; + let init_cfg = InitConfig { + min_num_units: 1, + max_num_units: 1000, + labels_per_unit: 200, + scrypt: ScryptParams::new(2, 1, 1), }; - let metadata = CpuInitializer::new(cfg.scrypt) + let metadata = CpuInitializer::new(init_cfg.scrypt) .initialize( datadir.path(), &[0u8; 32], &[0u8; 32], - labels_per_unit, + init_cfg.labels_per_unit, 2, - labels_per_unit, + init_cfg.labels_per_unit, None, ) .unwrap(); @@ -100,30 +100,16 @@ fn test_generate_and_verify_difficulty_msb_not_zero() { let proof = generate_proof(datadir.path(), challenge, cfg, 32, 1, pow_flags, stop).unwrap(); // Verify the proof - let metadata = ProofMetadata { - node_id: metadata.node_id, - commitment_atx_id: metadata.commitment_atx_id, - challenge: *challenge, - num_units: metadata.num_units, - labels_per_unit: metadata.labels_per_unit, - }; + let metadata = ProofMetadata::new(metadata, *challenge); let verifier = Verifier::new(Box::new(PoW::new(pow_flags).unwrap())); verifier - .verify( - &proof, - &metadata, - VerifyingParams::new(&metadata, &cfg).unwrap(), - ) + .verify(&proof, &metadata, &cfg, &init_cfg) .expect("proof should be valid"); // Check that the proof is invalid if we modify one index let mut invalid_proof = proof; invalid_proof.indices.to_mut()[0] += 1; verifier - .verify( - &invalid_proof, - &metadata, - VerifyingParams::new(&metadata, &cfg).unwrap(), - ) + .verify(&invalid_proof, &metadata, &cfg, &init_cfg) .expect_err("proof should be invalid"); } diff --git a/tests/initialize_and_verify.rs b/tests/initialize_and_verify.rs index 71b80fd8..3103543c 100644 --- a/tests/initialize_and_verify.rs +++ b/tests/initialize_and_verify.rs @@ -1,36 +1,30 @@ use std::io::Write; use post::{ + config::ScryptParams, initialize::{CpuInitializer, Initialize}, pos_verification::verify_files, }; -use scrypt_jane::scrypt::ScryptParams; + use tempfile::tempdir; #[test] fn test_generate_and_verify() { // Initialize some data let datadir = tempdir().unwrap(); + let scrypt = ScryptParams::new(2, 1, 1); - let cfg = post::config::Config { - k1: 23, - k2: 32, - k3: 10, - pow_difficulty: [0xFF; 32], - scrypt: ScryptParams::new(0, 0, 0), - }; - - CpuInitializer::new(cfg.scrypt) + CpuInitializer::new(scrypt) .initialize(datadir.path(), &[0u8; 32], &[0u8; 32], 256, 31, 700, None) .unwrap(); // Verify the data - verify_files(datadir.path(), 100.0, None, None, cfg.scrypt).unwrap(); - verify_files(datadir.path(), 1.0, None, None, cfg.scrypt).unwrap(); - verify_files(datadir.path(), 1.0, Some(0), Some(1), cfg.scrypt).unwrap(); + verify_files(datadir.path(), 100.0, None, None, scrypt).unwrap(); + verify_files(datadir.path(), 1.0, None, None, scrypt).unwrap(); + verify_files(datadir.path(), 1.0, Some(0), Some(1), scrypt).unwrap(); // Try verification with wrong scrypt params - let wrong_scrypt = ScryptParams::new(2, 0, 0); + let wrong_scrypt = ScryptParams::new(4, 1, 1); assert!(verify_files(datadir.path(), 100.0, None, None, wrong_scrypt).is_err()); assert!(verify_files(datadir.path(), 1.0, None, None, wrong_scrypt).is_err()); assert!(verify_files(datadir.path(), 100.0, Some(0), Some(0), wrong_scrypt).is_err()); @@ -43,12 +37,12 @@ fn test_generate_and_verify() { file.write_all(&[0u8; 16]).unwrap(); - assert!(verify_files(datadir.path(), 100.0, None, None, cfg.scrypt).is_err()); - assert!(verify_files(datadir.path(), 100.0, Some(1), Some(1), cfg.scrypt).is_err()); - assert!(verify_files(datadir.path(), 100.0, None, Some(1), cfg.scrypt).is_err()); - assert!(verify_files(datadir.path(), 100.0, Some(1), None, cfg.scrypt).is_err()); + assert!(verify_files(datadir.path(), 100.0, None, None, scrypt).is_err()); + assert!(verify_files(datadir.path(), 100.0, Some(1), Some(1), scrypt).is_err()); + assert!(verify_files(datadir.path(), 100.0, None, Some(1), scrypt).is_err()); + assert!(verify_files(datadir.path(), 100.0, Some(1), None, scrypt).is_err()); // skip corrupted files - pass - verify_files(datadir.path(), 100.0, None, Some(0), cfg.scrypt).unwrap(); - verify_files(datadir.path(), 100.0, Some(2), None, cfg.scrypt).unwrap(); + verify_files(datadir.path(), 100.0, None, Some(0), scrypt).unwrap(); + verify_files(datadir.path(), 100.0, Some(2), None, scrypt).unwrap(); }