verify: split into methods
Some checks are pending
Deny / audit (push) Waiting to run

This commit is contained in:
hinto.janai 2024-12-16 00:38:54 -05:00
parent 6f85531966
commit e6c96a69fc
No known key found for this signature in database
GPG key ID: D47CE05FA175A499
5 changed files with 260 additions and 171 deletions

View file

@ -40,7 +40,7 @@ async fn main() {
let count = constants::TESTED_BLOCK_COUNT.load(Ordering::Acquire); let count = constants::TESTED_BLOCK_COUNT.load(Ordering::Acquire);
if top_height == count { if top_height == count {
println!("finished, took {}s", now.elapsed().as_secs()); println!("Finished, took {}s", now.elapsed().as_secs());
std::process::exit(0); std::process::exit(0);
} }

View file

@ -9,27 +9,12 @@ pub fn randomx_vm_default(seed_hash: &[u8; 32]) -> RandomXVM {
} }
/// Returns a [`RandomXVM`] with most optimization flags. /// Returns a [`RandomXVM`] with most optimization flags.
#[expect(dead_code)]
pub fn randomx_vm_optimized(seed_hash: &[u8; 32]) -> RandomXVM { pub fn randomx_vm_optimized(seed_hash: &[u8; 32]) -> RandomXVM {
// TODO: conditional FLAG_LARGE_PAGES, FLAG_JIT // TODO: conditional FLAG_LARGE_PAGES, FLAG_JIT
let mut vm_flag = RandomXFlag::FLAG_FULL_MEM; let vm_flag = RandomXFlag::get_recommended_flags() | RandomXFlag::FLAG_FULL_MEM;
let mut cache_flag = RandomXFlag::empty(); let cache_flag = RandomXFlag::get_recommended_flags();
#[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 hash = hex::encode(seed_hash); let hash = hex::encode(seed_hash);

View file

@ -180,13 +180,7 @@ impl RpcClient {
.try_into() .try_into()
.unwrap(); .unwrap();
let seed_hash = this let seed_hash = this.get_block(seed_height).await.block_header.hash;
.get_block(seed_height)
.await
.block_header
.hash
.try_into()
.unwrap();
(seed_height, seed_hash) (seed_height, seed_hash)
}; };

View file

@ -47,7 +47,7 @@ pub struct GetBlockResponse {
pub block_header: BlockHeader, pub block_header: BlockHeader,
} }
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Copy, Clone, Deserialize)]
pub(crate) struct BlockHeader { pub(crate) struct BlockHeader {
#[serde(deserialize_with = "deserialize")] #[serde(deserialize_with = "deserialize")]
pub hash: [u8; 32], pub hash: [u8; 32],

View file

@ -6,6 +6,8 @@ use std::{
}; };
use crossbeam::channel::Receiver; use crossbeam::channel::Receiver;
use monero_serai::block::Block;
use randomx_rs::RandomXVM;
use crate::{ use crate::{
constants::{RANDOMX_START_HEIGHT, TESTED_BLOCK_COUNT, TESTED_TX_COUNT}, constants::{RANDOMX_START_HEIGHT, TESTED_BLOCK_COUNT, TESTED_TX_COUNT},
@ -13,13 +15,19 @@ use crate::{
types::{BlockHeader, GetBlockResponse, RpcBlockData, RpcTxData}, types::{BlockHeader, GetBlockResponse, RpcBlockData, RpcTxData},
}; };
#[expect( struct Verifier {
clippy::needless_pass_by_value, id: usize,
clippy::cast_precision_loss, now: Instant,
clippy::cast_possible_truncation, thread_count: NonZeroUsize,
clippy::cast_sign_loss, update: NonZeroU64,
clippy::significant_drop_tightening top_height: u64,
)] rx: Receiver<RpcBlockData>,
seed_hash: [u8; 32],
timestamp: u64,
randomx_vm: Option<RandomXVM>,
}
#[expect(clippy::needless_pass_by_value)]
pub fn spawn_verify_pool( pub fn spawn_verify_pool(
thread_count: NonZeroUsize, thread_count: NonZeroUsize,
update: NonZeroU64, update: NonZeroU64,
@ -28,167 +36,271 @@ pub fn spawn_verify_pool(
) { ) {
let now = Instant::now(); let now = Instant::now();
for i in 0..thread_count.get() { for id in 0..thread_count.get() {
let rx = rx.clone(); let rx = rx.clone();
std::thread::spawn(move || { std::thread::spawn(move || {
let mut current_seed_hash = [0; 32]; Verifier {
let mut randomx_vm = None; id,
now,
thread_count,
update,
top_height,
rx,
seed_hash: [0; 32],
timestamp: 0,
randomx_vm: None,
}
.loop_listen_verify();
});
}
}
loop { impl Verifier {
let Ok(data) = rx.recv() else { fn loop_listen_verify(mut self) {
println!("Exiting verify thread {i}/{thread_count}"); loop {
return; let Ok(data) = self.rx.recv() else {
}; println!("Exiting verify thread {}/{}", self.id, self.thread_count);
return;
};
// Panic info. self.verify(data);
let p = format!("data: {data:#?}"); }
}
let RpcBlockData { fn verify(&mut self, data: RpcBlockData) {
get_block_response, //----------------------------------------------- Create panic info.
block, let p = format!("data: {data:#?}");
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;
// Test block properties. //----------------------------------------------- Extract data.
assert_eq!(blob, block.serialize(), "{p:#?}"); let RpcBlockData {
get_block_response,
block,
seed_height,
seed_hash,
txs,
} = data;
let GetBlockResponse { blob, block_header } = get_block_response;
assert!( //----------------------------------------------- Calculate some data.
!block.miner_transaction.prefix().outputs.is_empty(), let calculated_block_reward = block
"miner_tx has no outputs\n{p:#?}" .miner_transaction
); .prefix()
.outputs
.iter()
.map(|o| o.amount.unwrap())
.sum::<u64>();
let calculated_block_weight = txs
.iter()
.map(|RpcTxData { tx, .. }| tx.weight())
.sum::<usize>();
let calculated_pow_data = block.serialize_pow_hash();
let miner_tx_weight = block.miner_transaction.weight();
let block_reward = block //----------------------------------------------- Verify.
.miner_transaction Self::verify_block_properties(&blob, &block, calculated_block_reward, &p);
.prefix() Self::verify_all_transactions_are_unique(&txs, &p);
.outputs Self::verify_transaction_properties(txs, &p);
.iter()
.map(|o| o.amount.unwrap())
.sum::<u64>();
assert_ne!(block_reward, 0, "block reward is 0\n{p:#?}");
let total_block_weight = txs self.verify_block_fields(
.iter() calculated_block_weight,
.map(|RpcTxData { tx, .. }| tx.weight()) calculated_block_reward,
.sum::<usize>(); &block,
&p,
block_header,
);
// Test all transactions are unique. let algo = self.verify_pow(
{ block_header.height,
static TX_SET: LazyLock<Mutex<HashSet<[u8; 32]>>> = seed_hash,
LazyLock::new(|| Mutex::new(HashSet::new())); 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) { fn verify_block_properties(
assert!( block_blob: &[u8],
tx_set.insert(*tx_hash), block: &Block,
"duplicated tx_hash: {}, {p:#?}", calculated_block_reward: u64,
hex::encode(tx_hash), p: &str,
); ) {
} // Test block properties.
} assert_eq!(block_blob, block.serialize(), "{p}");
// Test transaction properties. assert!(
for RpcTxData { !block.miner_transaction.prefix().outputs.is_empty(),
tx, "miner_tx has no outputs\n{p}"
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:#?}");
}
// Test block fields are correct. assert_ne!(calculated_block_reward, 0, "block reward is 0\n{p}");
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:#?}");
// #[expect(clippy::significant_drop_tightening)]
let pow_data = block.serialize_pow_hash(); fn verify_all_transactions_are_unique(txs: &[RpcTxData], p: &str) {
static TX_SET: LazyLock<Mutex<HashSet<[u8; 32]>>> =
LazyLock::new(|| Mutex::new(HashSet::new()));
let (algo, calculated_pow_hash) = if height < RANDOMX_START_HEIGHT { let mut tx_set = TX_SET.lock().unwrap();
CryptoNightHash::hash(&pow_data, height)
} else {
if current_seed_hash != seed_hash {
randomx_vm = None;
}
let randomx_vm = randomx_vm.get_or_insert_with(|| { for tx_hash in txs.iter().map(|RpcTxData { tx_hash, .. }| tx_hash) {
current_seed_hash = seed_hash; assert!(
// crate::randomx::randomx_vm_optimized(&seed_hash) tx_set.insert(*tx_hash),
crate::randomx::randomx_vm_default(&seed_hash) "duplicated tx_hash: {}, {p}",
}); hex::encode(tx_hash),
);
}
}
let pow_hash = randomx_vm fn verify_transaction_properties(txs: Vec<RpcTxData>, p: &str) {
.calculate_hash(&pow_data) // Test transaction properties.
.unwrap() for RpcTxData {
.try_into() tx,
.unwrap(); 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; fn verify_pow(
let total_tx_count = TESTED_TX_COUNT.fetch_add(num_txes, Ordering::Release) + 1; &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 { let randomx_vm = self.randomx_vm.get_or_insert_with(|| {
continue; 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 pow_hash = randomx_vm
let seed_hash = hex::encode(seed_hash); .calculate_hash(calculated_pow_data)
let percent = (count as f64 / top_height as f64) * 100.0; .unwrap()
.try_into()
.unwrap();
let elapsed = now.elapsed().as_secs_f64(); ("randomx", pow_hash)
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 block_hash = hex::encode(hash); assert_eq!(calculated_pow_hash, pow_hash, "{p}",);
let miner_tx_hash = hex::encode(miner_tx_hash);
let prev_hash = hex::encode(prev_hash);
let miner_tx_weight = block.miner_transaction.weight();
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} seed_hash | {seed_hash}
pow_hash | {pow_hash} pow_hash | {pow_hash}
block_hash | {block_hash} block_hash | {block_hash}
@ -206,7 +318,5 @@ major_version | {major_version}
minor_version | {minor_version} minor_version | {minor_version}
num_txes | {num_txes}\n", num_txes | {num_txes}\n",
); );
}
});
} }
} }