diff --git a/tests/compat/src/main.rs b/tests/compat/src/main.rs index 21767a0..c6923e5 100644 --- a/tests/compat/src/main.rs +++ b/tests/compat/src/main.rs @@ -40,7 +40,7 @@ async fn main() { let count = constants::TESTED_BLOCK_COUNT.load(Ordering::Acquire); if top_height == count { - println!("finished, took {}s", now.elapsed().as_secs()); + println!("Finished, took {}s", now.elapsed().as_secs()); std::process::exit(0); } diff --git a/tests/compat/src/randomx.rs b/tests/compat/src/randomx.rs index 7fcb92f..6e3d8b4 100644 --- a/tests/compat/src/randomx.rs +++ b/tests/compat/src/randomx.rs @@ -9,27 +9,12 @@ pub fn randomx_vm_default(seed_hash: &[u8; 32]) -> RandomXVM { } /// Returns a [`RandomXVM`] with most optimization flags. +#[expect(dead_code)] pub fn randomx_vm_optimized(seed_hash: &[u8; 32]) -> RandomXVM { // TODO: conditional FLAG_LARGE_PAGES, FLAG_JIT - let mut vm_flag = RandomXFlag::FLAG_FULL_MEM; - let mut cache_flag = RandomXFlag::empty(); - - #[cfg(target_arch = "x86_64")] - for flag in [&mut vm_flag, &mut cache_flag] { - if is_x86_feature_detected!("aes") { - *flag |= RandomXFlag::FLAG_HARD_AES; - } - - match ( - is_x86_feature_detected!("ssse3"), - is_x86_feature_detected!("avx2"), - ) { - (true, _) => *flag |= RandomXFlag::FLAG_ARGON2_SSSE3, - (_, true) => *flag |= RandomXFlag::FLAG_ARGON2_AVX2, - (_, _) => *flag |= RandomXFlag::FLAG_ARGON2, - } - } + let vm_flag = RandomXFlag::get_recommended_flags() | RandomXFlag::FLAG_FULL_MEM; + let cache_flag = RandomXFlag::get_recommended_flags(); let hash = hex::encode(seed_hash); diff --git a/tests/compat/src/rpc.rs b/tests/compat/src/rpc.rs index 189e4d8..3315317 100644 --- a/tests/compat/src/rpc.rs +++ b/tests/compat/src/rpc.rs @@ -180,13 +180,7 @@ impl RpcClient { .try_into() .unwrap(); - let seed_hash = this - .get_block(seed_height) - .await - .block_header - .hash - .try_into() - .unwrap(); + let seed_hash = this.get_block(seed_height).await.block_header.hash; (seed_height, seed_hash) }; diff --git a/tests/compat/src/types.rs b/tests/compat/src/types.rs index 5277b50..1c9aac2 100644 --- a/tests/compat/src/types.rs +++ b/tests/compat/src/types.rs @@ -47,7 +47,7 @@ pub struct GetBlockResponse { pub block_header: BlockHeader, } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Copy, Clone, Deserialize)] pub(crate) struct BlockHeader { #[serde(deserialize_with = "deserialize")] pub hash: [u8; 32], diff --git a/tests/compat/src/verify.rs b/tests/compat/src/verify.rs index b482ebf..309fb66 100644 --- a/tests/compat/src/verify.rs +++ b/tests/compat/src/verify.rs @@ -6,6 +6,8 @@ use std::{ }; use crossbeam::channel::Receiver; +use monero_serai::block::Block; +use randomx_rs::RandomXVM; use crate::{ constants::{RANDOMX_START_HEIGHT, TESTED_BLOCK_COUNT, TESTED_TX_COUNT}, @@ -13,13 +15,19 @@ use crate::{ types::{BlockHeader, GetBlockResponse, RpcBlockData, RpcTxData}, }; -#[expect( - clippy::needless_pass_by_value, - clippy::cast_precision_loss, - clippy::cast_possible_truncation, - clippy::cast_sign_loss, - clippy::significant_drop_tightening -)] +struct Verifier { + id: usize, + now: Instant, + thread_count: NonZeroUsize, + update: NonZeroU64, + top_height: u64, + rx: Receiver, + seed_hash: [u8; 32], + timestamp: u64, + randomx_vm: Option, +} + +#[expect(clippy::needless_pass_by_value)] pub fn spawn_verify_pool( thread_count: NonZeroUsize, update: NonZeroU64, @@ -28,167 +36,271 @@ pub fn spawn_verify_pool( ) { let now = Instant::now(); - for i in 0..thread_count.get() { + for id in 0..thread_count.get() { let rx = rx.clone(); - std::thread::spawn(move || { - let mut current_seed_hash = [0; 32]; - let mut randomx_vm = None; + Verifier { + id, + now, + thread_count, + update, + top_height, + rx, + seed_hash: [0; 32], + timestamp: 0, + randomx_vm: None, + } + .loop_listen_verify(); + }); + } +} - loop { - let Ok(data) = rx.recv() else { - println!("Exiting verify thread {i}/{thread_count}"); - return; - }; +impl Verifier { + fn loop_listen_verify(mut self) { + loop { + let Ok(data) = self.rx.recv() else { + println!("Exiting verify thread {}/{}", self.id, self.thread_count); + return; + }; - // Panic info. - let p = format!("data: {data:#?}"); + self.verify(data); + } + } - let RpcBlockData { - get_block_response, - block, - seed_height, - seed_hash, - txs, - } = data; - let GetBlockResponse { blob, block_header } = get_block_response; - let BlockHeader { - block_weight, - hash, - pow_hash, - height, - major_version, - minor_version, - miner_tx_hash, - nonce, - num_txes, - prev_hash, - reward, - timestamp, - } = block_header; + fn verify(&mut self, data: RpcBlockData) { + //----------------------------------------------- Create panic info. + let p = format!("data: {data:#?}"); - // Test block properties. - assert_eq!(blob, block.serialize(), "{p:#?}"); + //----------------------------------------------- Extract data. + let RpcBlockData { + get_block_response, + block, + seed_height, + seed_hash, + txs, + } = data; + let GetBlockResponse { blob, block_header } = get_block_response; - assert!( - !block.miner_transaction.prefix().outputs.is_empty(), - "miner_tx has no outputs\n{p:#?}" - ); + //----------------------------------------------- Calculate some data. + let calculated_block_reward = block + .miner_transaction + .prefix() + .outputs + .iter() + .map(|o| o.amount.unwrap()) + .sum::(); + let calculated_block_weight = txs + .iter() + .map(|RpcTxData { tx, .. }| tx.weight()) + .sum::(); + let calculated_pow_data = block.serialize_pow_hash(); + let miner_tx_weight = block.miner_transaction.weight(); - let block_reward = block - .miner_transaction - .prefix() - .outputs - .iter() - .map(|o| o.amount.unwrap()) - .sum::(); - assert_ne!(block_reward, 0, "block reward is 0\n{p:#?}"); + //----------------------------------------------- Verify. + Self::verify_block_properties(&blob, &block, calculated_block_reward, &p); + Self::verify_all_transactions_are_unique(&txs, &p); + Self::verify_transaction_properties(txs, &p); - let total_block_weight = txs - .iter() - .map(|RpcTxData { tx, .. }| tx.weight()) - .sum::(); + self.verify_block_fields( + calculated_block_weight, + calculated_block_reward, + &block, + &p, + block_header, + ); - // Test all transactions are unique. - { - static TX_SET: LazyLock>> = - LazyLock::new(|| Mutex::new(HashSet::new())); + let algo = self.verify_pow( + block_header.height, + seed_hash, + block_header.pow_hash, + &calculated_pow_data, + &p, + ); - let mut tx_set = TX_SET.lock().unwrap(); + //----------------------------------------------- Print progress. + self.print_progress(algo, seed_height, miner_tx_weight, block_header); + } - for tx_hash in txs.iter().map(|RpcTxData { tx_hash, .. }| tx_hash) { - assert!( - tx_set.insert(*tx_hash), - "duplicated tx_hash: {}, {p:#?}", - hex::encode(tx_hash), - ); - } - } + fn verify_block_properties( + block_blob: &[u8], + block: &Block, + calculated_block_reward: u64, + p: &str, + ) { + // Test block properties. + assert_eq!(block_blob, block.serialize(), "{p}"); - // Test transaction properties. - for RpcTxData { - tx, - tx_blob, - tx_hash, - } in txs - { - assert_eq!(tx_hash, tx.hash(), "{p:#?}, tx: {tx:#?}"); - assert_ne!(tx.weight(), 0, "{p:#?}, tx: {tx:#?}"); - assert!(!tx.prefix().inputs.is_empty(), "{p:#?}, tx: {tx:#?}"); - assert_eq!(tx_blob, tx.serialize(), "{p:#?}, tx: {tx:#?}"); - assert!(matches!(tx.version(), 1 | 2), "{p:#?}, tx: {tx:#?}"); - } + assert!( + !block.miner_transaction.prefix().outputs.is_empty(), + "miner_tx has no outputs\n{p}" + ); - // Test block fields are correct. - assert_eq!(block_weight, total_block_weight, "{p:#?}"); - assert_ne!(block.miner_transaction.weight(), 0, "{p:#?}"); - assert_eq!(hash, block.hash(), "{p:#?}"); - assert_eq!( - height, - u64::try_from(block.number().unwrap()).unwrap(), - "{p:#?}" - ); - assert_eq!(major_version, block.header.hardfork_version, "{p:#?}"); - assert_eq!(minor_version, block.header.hardfork_signal, "{p:#?}"); - assert_eq!(miner_tx_hash, block.miner_transaction.hash(), "{p:#?}"); - assert_eq!(nonce, block.header.nonce, "{p:#?}"); - assert_eq!(num_txes, block.transactions.len(), "{p:#?}"); - assert_eq!(prev_hash, block.header.previous, "{p:#?}"); - assert_eq!(reward, block_reward, "{p:#?}"); - assert_eq!(timestamp, block.header.timestamp, "{p:#?}"); + assert_ne!(calculated_block_reward, 0, "block reward is 0\n{p}"); + } - // - let pow_data = block.serialize_pow_hash(); + #[expect(clippy::significant_drop_tightening)] + fn verify_all_transactions_are_unique(txs: &[RpcTxData], p: &str) { + static TX_SET: LazyLock>> = + LazyLock::new(|| Mutex::new(HashSet::new())); - let (algo, calculated_pow_hash) = if height < RANDOMX_START_HEIGHT { - CryptoNightHash::hash(&pow_data, height) - } else { - if current_seed_hash != seed_hash { - randomx_vm = None; - } + let mut tx_set = TX_SET.lock().unwrap(); - let randomx_vm = randomx_vm.get_or_insert_with(|| { - current_seed_hash = seed_hash; - // crate::randomx::randomx_vm_optimized(&seed_hash) - crate::randomx::randomx_vm_default(&seed_hash) - }); + for tx_hash in txs.iter().map(|RpcTxData { tx_hash, .. }| tx_hash) { + assert!( + tx_set.insert(*tx_hash), + "duplicated tx_hash: {}, {p}", + hex::encode(tx_hash), + ); + } + } - let pow_hash = randomx_vm - .calculate_hash(&pow_data) - .unwrap() - .try_into() - .unwrap(); + fn verify_transaction_properties(txs: Vec, p: &str) { + // Test transaction properties. + for RpcTxData { + tx, + tx_blob, + tx_hash, + } in txs + { + assert_eq!(tx_hash, tx.hash(), "{p}, tx: {tx:#?}"); + assert_ne!(tx.weight(), 0, "{p}, tx: {tx:#?}"); + assert!(!tx.prefix().inputs.is_empty(), "{p}, tx: {tx:#?}"); + assert_eq!(tx_blob, tx.serialize(), "{p}, tx: {tx:#?}"); + assert!(matches!(tx.version(), 1 | 2), "{p}, tx: {tx:#?}"); + } + } - ("randomx", pow_hash) - }; + fn verify_block_fields( + &mut self, + calculated_block_weight: usize, + calculated_block_reward: u64, + block: &Block, + p: &str, + BlockHeader { + block_weight, + hash, + pow_hash: _, + height, + major_version, + minor_version, + miner_tx_hash, + nonce, + num_txes, + prev_hash, + reward, + timestamp, + }: BlockHeader, + ) { + // Test block fields are correct. + assert_eq!(block_weight, calculated_block_weight, "{p}"); + assert_ne!(block.miner_transaction.weight(), 0, "{p}"); + assert_eq!(hash, block.hash(), "{p}"); + assert_eq!( + height, + u64::try_from(block.number().unwrap()).unwrap(), + "{p}" + ); + assert_eq!(major_version, block.header.hardfork_version, "{p}"); + assert_eq!(minor_version, block.header.hardfork_signal, "{p}"); + assert_eq!(miner_tx_hash, block.miner_transaction.hash(), "{p}"); + assert_eq!(nonce, block.header.nonce, "{p}"); + assert_eq!(num_txes, block.transactions.len(), "{p}"); + assert_eq!(prev_hash, block.header.previous, "{p}"); + assert_eq!(reward, calculated_block_reward, "{p}"); + assert_eq!(timestamp, block.header.timestamp, "{p}"); - assert_eq!(calculated_pow_hash, pow_hash, "{p:#?}",); + if timestamp != 0 { + assert!(timestamp > self.timestamp, "{p}"); + self.timestamp = timestamp; + } + } - let count = TESTED_BLOCK_COUNT.fetch_add(1, Ordering::Release) + 1; - let total_tx_count = TESTED_TX_COUNT.fetch_add(num_txes, Ordering::Release) + 1; + fn verify_pow( + &mut self, + height: u64, + seed_hash: [u8; 32], + pow_hash: [u8; 32], + calculated_pow_data: &[u8], + p: &str, + ) -> &'static str { + let (algo, calculated_pow_hash) = if height < RANDOMX_START_HEIGHT { + CryptoNightHash::hash(calculated_pow_data, height) + } else { + if self.seed_hash != seed_hash { + self.randomx_vm = None; + } - if count % update.get() != 0 { - continue; - } + let randomx_vm = self.randomx_vm.get_or_insert_with(|| { + self.seed_hash = seed_hash; + // crate::randomx::randomx_vm_optimized(&seed_hash) + crate::randomx::randomx_vm_default(&seed_hash) + }); - let pow_hash = hex::encode(pow_hash); - let seed_hash = hex::encode(seed_hash); - let percent = (count as f64 / top_height as f64) * 100.0; + let pow_hash = randomx_vm + .calculate_hash(calculated_pow_data) + .unwrap() + .try_into() + .unwrap(); - let elapsed = now.elapsed().as_secs_f64(); - let secs_per_hash = elapsed / count as f64; - let bps = count as f64 / elapsed; - let remaining_secs = (top_height as f64 - count as f64) * secs_per_hash; - let h = (remaining_secs / 60.0 / 60.0) as u64; - let m = (remaining_secs / 60.0 % 60.0) as u64; - let s = (remaining_secs % 60.0) as u64; + ("randomx", pow_hash) + }; - let block_hash = hex::encode(hash); - let miner_tx_hash = hex::encode(miner_tx_hash); - let prev_hash = hex::encode(prev_hash); - let miner_tx_weight = block.miner_transaction.weight(); + assert_eq!(calculated_pow_hash, pow_hash, "{p}",); - println!("progress | {count}/{top_height} ({percent:.2}%, {algo}, {bps:.2} blocks/sec, {h}h {m}m {s}s left) + algo + } + + #[expect( + clippy::cast_precision_loss, + clippy::cast_possible_truncation, + clippy::cast_sign_loss + )] + fn print_progress( + &self, + algo: &'static str, + seed_height: u64, + miner_tx_weight: usize, + BlockHeader { + block_weight, + hash, + pow_hash, + height, + major_version, + minor_version, + miner_tx_hash, + nonce, + num_txes, + prev_hash, + reward, + timestamp, + }: BlockHeader, + ) { + let count = TESTED_BLOCK_COUNT.fetch_add(1, Ordering::Release) + 1; + let total_tx_count = TESTED_TX_COUNT.fetch_add(num_txes, Ordering::Release) + 1; + + if count % self.update.get() != 0 { + return; + } + + let top_height = self.top_height; + + let percent = (count as f64 / top_height as f64) * 100.0; + + let elapsed = self.now.elapsed().as_secs_f64(); + let secs_per_hash = elapsed / count as f64; + let bps = count as f64 / elapsed; + let remaining_secs = (top_height as f64 - count as f64) * secs_per_hash; + let h = (remaining_secs / 60.0 / 60.0) as u64; + let m = (remaining_secs / 60.0 % 60.0) as u64; + let s = (remaining_secs % 60.0) as u64; + + let pow_hash = hex::encode(pow_hash); + let seed_hash = hex::encode(self.seed_hash); + let block_hash = hex::encode(hash); + let miner_tx_hash = hex::encode(miner_tx_hash); + let prev_hash = hex::encode(prev_hash); + + println!("progress | {count}/{top_height} ({percent:.2}%, {algo}, {bps:.2} blocks/sec, {h}h {m}m {s}s left) seed_hash | {seed_hash} pow_hash | {pow_hash} block_hash | {block_hash} @@ -206,7 +318,5 @@ major_version | {major_version} minor_version | {minor_version} num_txes | {num_txes}\n", ); - } - }); } }