mirror of
https://github.com/hinto-janai/cuprate.git
synced 2024-12-22 11:39:30 +00:00
This commit is contained in:
parent
6f85531966
commit
e6c96a69fc
5 changed files with 260 additions and 171 deletions
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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<RpcBlockData>,
|
||||
seed_hash: [u8; 32],
|
||||
timestamp: u64,
|
||||
randomx_vm: Option<RandomXVM>,
|
||||
}
|
||||
|
||||
#[expect(clippy::needless_pass_by_value)]
|
||||
pub fn spawn_verify_pool(
|
||||
thread_count: NonZeroUsize,
|
||||
update: NonZeroU64,
|
||||
|
@ -28,22 +36,42 @@ 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Verifier {
|
||||
fn loop_listen_verify(mut self) {
|
||||
loop {
|
||||
let Ok(data) = rx.recv() else {
|
||||
println!("Exiting verify thread {i}/{thread_count}");
|
||||
let Ok(data) = self.rx.recv() else {
|
||||
println!("Exiting verify thread {}/{}", self.id, self.thread_count);
|
||||
return;
|
||||
};
|
||||
|
||||
// Panic info.
|
||||
self.verify(data);
|
||||
}
|
||||
}
|
||||
|
||||
fn verify(&mut self, data: RpcBlockData) {
|
||||
//----------------------------------------------- Create panic info.
|
||||
let p = format!("data: {data:#?}");
|
||||
|
||||
//----------------------------------------------- Extract data.
|
||||
let RpcBlockData {
|
||||
get_block_response,
|
||||
block,
|
||||
|
@ -52,7 +80,187 @@ pub fn spawn_verify_pool(
|
|||
txs,
|
||||
} = data;
|
||||
let GetBlockResponse { blob, block_header } = get_block_response;
|
||||
let BlockHeader {
|
||||
|
||||
//----------------------------------------------- Calculate some data.
|
||||
let calculated_block_reward = block
|
||||
.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();
|
||||
|
||||
//----------------------------------------------- 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);
|
||||
|
||||
self.verify_block_fields(
|
||||
calculated_block_weight,
|
||||
calculated_block_reward,
|
||||
&block,
|
||||
&p,
|
||||
block_header,
|
||||
);
|
||||
|
||||
let algo = self.verify_pow(
|
||||
block_header.height,
|
||||
seed_hash,
|
||||
block_header.pow_hash,
|
||||
&calculated_pow_data,
|
||||
&p,
|
||||
);
|
||||
|
||||
//----------------------------------------------- Print progress.
|
||||
self.print_progress(algo, seed_height, miner_tx_weight, block_header);
|
||||
}
|
||||
|
||||
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}");
|
||||
|
||||
assert!(
|
||||
!block.miner_transaction.prefix().outputs.is_empty(),
|
||||
"miner_tx has no outputs\n{p}"
|
||||
);
|
||||
|
||||
assert_ne!(calculated_block_reward, 0, "block reward is 0\n{p}");
|
||||
}
|
||||
|
||||
#[expect(clippy::significant_drop_tightening)]
|
||||
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 mut tx_set = TX_SET.lock().unwrap();
|
||||
|
||||
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_transaction_properties(txs: Vec<RpcTxData>, 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:#?}");
|
||||
}
|
||||
}
|
||||
|
||||
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}");
|
||||
|
||||
if timestamp != 0 {
|
||||
assert!(timestamp > self.timestamp, "{p}");
|
||||
self.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 = randomx_vm
|
||||
.calculate_hash(calculated_pow_data)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
("randomx", pow_hash)
|
||||
};
|
||||
|
||||
assert_eq!(calculated_pow_hash, pow_hash, "{p}",);
|
||||
|
||||
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,
|
||||
|
@ -65,117 +273,20 @@ pub fn spawn_verify_pool(
|
|||
prev_hash,
|
||||
reward,
|
||||
timestamp,
|
||||
} = block_header;
|
||||
|
||||
// Test block properties.
|
||||
assert_eq!(blob, block.serialize(), "{p:#?}");
|
||||
|
||||
assert!(
|
||||
!block.miner_transaction.prefix().outputs.is_empty(),
|
||||
"miner_tx has no outputs\n{p:#?}"
|
||||
);
|
||||
|
||||
let block_reward = block
|
||||
.miner_transaction
|
||||
.prefix()
|
||||
.outputs
|
||||
.iter()
|
||||
.map(|o| o.amount.unwrap())
|
||||
.sum::<u64>();
|
||||
assert_ne!(block_reward, 0, "block reward is 0\n{p:#?}");
|
||||
|
||||
let total_block_weight = txs
|
||||
.iter()
|
||||
.map(|RpcTxData { tx, .. }| tx.weight())
|
||||
.sum::<usize>();
|
||||
|
||||
// Test all transactions are unique.
|
||||
{
|
||||
static TX_SET: LazyLock<Mutex<HashSet<[u8; 32]>>> =
|
||||
LazyLock::new(|| Mutex::new(HashSet::new()));
|
||||
|
||||
let mut tx_set = TX_SET.lock().unwrap();
|
||||
|
||||
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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 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:#?}");
|
||||
}
|
||||
|
||||
// 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:#?}");
|
||||
|
||||
//
|
||||
let pow_data = block.serialize_pow_hash();
|
||||
|
||||
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 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)
|
||||
});
|
||||
|
||||
let pow_hash = randomx_vm
|
||||
.calculate_hash(&pow_data)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
("randomx", pow_hash)
|
||||
};
|
||||
|
||||
assert_eq!(calculated_pow_hash, pow_hash, "{p:#?}",);
|
||||
|
||||
}: 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 % update.get() != 0 {
|
||||
continue;
|
||||
if count % self.update.get() != 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let pow_hash = hex::encode(pow_hash);
|
||||
let seed_hash = hex::encode(seed_hash);
|
||||
let top_height = self.top_height;
|
||||
|
||||
let percent = (count as f64 / top_height as f64) * 100.0;
|
||||
|
||||
let elapsed = now.elapsed().as_secs_f64();
|
||||
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;
|
||||
|
@ -183,10 +294,11 @@ pub fn spawn_verify_pool(
|
|||
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);
|
||||
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)
|
||||
seed_hash | {seed_hash}
|
||||
|
@ -207,6 +319,4 @@ minor_version | {minor_version}
|
|||
num_txes | {num_txes}\n",
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue