add tests/pow

This commit is contained in:
hinto.janai 2024-12-12 14:56:32 -05:00
parent bd2624572a
commit 1d1cba4f63
No known key found for this signature in database
GPG key ID: D47CE05FA175A499
6 changed files with 328 additions and 1 deletions

18
Cargo.lock generated
View file

@ -3331,6 +3331,24 @@ dependencies = [
"tokio",
]
[[package]]
name = "tests-pow"
version = "0.1.0"
dependencies = [
"cuprate-consensus-rules",
"cuprate-cryptonight",
"function_name",
"hex",
"monero-serai",
"randomx-rs",
"rayon",
"reqwest",
"serde",
"serde_json",
"thread_local",
"tokio",
]
[[package]]
name = "thiserror"
version = "1.0.66"

View file

@ -54,6 +54,7 @@ members = [
"types",
# Tests
"tests/pow",
"tests/monero-serai",
]

View file

@ -56,7 +56,7 @@ async fn main() {
count = c;
println!(
"blocks processed ... {c} ({:.2}%)",
"blocks processed ... {c}/{top_height} ({:.2}%)",
(c as f64 / top_height as f64) * 100.0
);

22
tests/pow/Cargo.toml Normal file
View file

@ -0,0 +1,22 @@
[package]
name = "tests-pow"
version = "0.1.0"
edition = "2021"
[dependencies]
cuprate-cryptonight = { workspace = true }
cuprate-consensus-rules = { workspace = true }
function_name = { workspace = true }
thread_local = { workspace = true }
monero-serai = { workspace = true }
hex = { workspace = true, features = ["serde", "std"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["std"] }
tokio = { workspace = true, features = ["full"] }
reqwest = { version = "0.12", features = ["json"] }
rayon = { workspace = true }
randomx-rs = { workspace = true }
[lints]
workspace = true

32
tests/pow/src/main.rs Normal file
View file

@ -0,0 +1,32 @@
use std::{
sync::atomic::{AtomicUsize, Ordering},
time::{Duration, Instant},
};
mod rpc;
pub static TESTED_BLOCK_COUNT: AtomicUsize = AtomicUsize::new(0);
#[tokio::main]
async fn main() {
let now = Instant::now();
let rpc_node_url = if let Ok(url) = std::env::var("RPC_NODE_URL") {
url
} else {
"http://127.0.0.1:18081/json_rpc".to_string()
};
println!("rpc_node_url: {rpc_node_url}");
let rpc_client = rpc::RpcClient::new(rpc_node_url).await;
tokio::join!(
rpc_client.cryptonight_v0(),
rpc_client.cryptonight_v1(),
rpc_client.cryptonight_v2(),
rpc_client.cryptonight_r(),
rpc_client.randomx(),
);
println!("finished all PoW, took: {}s", now.elapsed().as_secs());
}

254
tests/pow/src/rpc.rs Normal file
View file

@ -0,0 +1,254 @@
use std::{
collections::BTreeMap,
ops::Range,
sync::{atomic::Ordering, Mutex},
};
use function_name::named;
use hex::serde::deserialize;
use monero_serai::block::Block;
use randomx_rs::{RandomXCache, RandomXFlag, RandomXVM};
use reqwest::{
header::{HeaderMap, HeaderValue},
Client, ClientBuilder,
};
use serde::Deserialize;
use serde_json::{json, Value};
use thread_local::ThreadLocal;
use crate::TESTED_BLOCK_COUNT;
#[derive(Debug, Clone, Deserialize)]
struct JsonRpcResponse {
result: GetBlockResponse,
}
#[derive(Debug, Clone, Deserialize)]
struct GetBlockResponse {
#[serde(deserialize_with = "deserialize")]
pub blob: Vec<u8>,
pub block_header: BlockHeader,
}
#[derive(Debug, Clone, Deserialize)]
struct BlockHeader {
#[serde(deserialize_with = "deserialize")]
pub pow_hash: Vec<u8>,
#[serde(deserialize_with = "deserialize")]
pub hash: Vec<u8>,
}
#[derive(Debug, Clone)]
pub(crate) struct RpcClient {
client: Client,
rpc_node_url: String,
top_height: usize,
}
impl RpcClient {
pub(crate) async fn new(rpc_node_url: String) -> Self {
let headers = {
let mut h = HeaderMap::new();
h.insert("Content-Type", HeaderValue::from_static("application/json"));
h
};
let client = ClientBuilder::new()
.default_headers(headers)
.build()
.unwrap();
let request = json!({
"jsonrpc": "2.0",
"id": 0,
"method": "get_last_block_header",
"params": {}
});
let top_height = client
.get(&rpc_node_url)
.json(&request)
.send()
.await
.unwrap()
.json::<Value>()
.await
.unwrap()
.get("result")
.unwrap()
.get("block_header")
.unwrap()
.get("height")
.unwrap()
.as_u64()
.unwrap()
.try_into()
.unwrap();
println!("top_height: {top_height}");
assert!(top_height > 3301441, "node is behind");
Self {
client,
rpc_node_url,
top_height,
}
}
async fn get_block(&self, height: usize) -> GetBlockResponse {
let request = json!({
"jsonrpc": "2.0",
"id": 0,
"method": "get_block",
"params": {"height": height, "fill_pow_hash": true}
});
tokio::task::spawn(self.client.get(&self.rpc_node_url).json(&request).send())
.await
.unwrap()
.unwrap()
.json::<JsonRpcResponse>()
.await
.unwrap()
.result
}
async fn test<const RANDOMX: bool>(
&self,
range: Range<usize>,
hash: impl Fn(Vec<u8>, u64, u64, [u8; 32]) -> [u8; 32] + Send + Sync + 'static + Copy,
name: &'static str,
) {
let tasks = range.map(|height| {
let task = self.get_block(height);
(height, task)
});
for (height, task) in tasks {
let result = task.await;
let (seed_height, seed_hash) = if RANDOMX {
let seed_height = cuprate_consensus_rules::blocks::randomx_seed_height(height);
let seed_hash: [u8; 32] = self
.get_block(seed_height)
.await
.block_header
.hash
.try_into()
.unwrap();
(seed_height, seed_hash)
} else {
(0, [0; 32])
};
let top_height = self.top_height;
#[expect(clippy::cast_precision_loss)]
rayon::spawn(move || {
let GetBlockResponse { blob, block_header } = result;
let header = block_header;
let block = match Block::read(&mut blob.as_slice()) {
Ok(b) => b,
Err(e) => panic!("{e:?}\nblob: {blob:?}, header: {header:?}"),
};
let pow_hash = hash(
block.serialize_pow_hash(),
height.try_into().unwrap(),
seed_height.try_into().unwrap(),
seed_hash,
);
assert_eq!(
header.pow_hash, pow_hash,
"\nheight: {height}\nheader: {header:#?}\nblock: {block:#?}"
);
let count = TESTED_BLOCK_COUNT.fetch_add(1, Ordering::Release);
let hex_header = hex::encode(header.pow_hash);
let hex_hash = hex::encode(pow_hash);
let percent = (count as f64 / top_height as f64) * 100.0;
println!(
"progress | {count}/{top_height} ({percent:.2}%)
height | {height}
algo | {name}
header | {hex_header}
hash | {hex_hash}\n"
);
});
}
}
#[named]
pub(crate) async fn cryptonight_v0(&self) {
self.test::<false>(
0..1546000,
|b, _, _, _| cuprate_cryptonight::cryptonight_hash_v0(&b),
function_name!(),
)
.await;
}
#[named]
pub(crate) async fn cryptonight_v1(&self) {
self.test::<false>(
1546000..1685555,
|b, _, _, _| cuprate_cryptonight::cryptonight_hash_v1(&b).unwrap(),
function_name!(),
)
.await;
}
#[named]
pub(crate) async fn cryptonight_v2(&self) {
self.test::<false>(
1685555..1788000,
|b, _, _, _| cuprate_cryptonight::cryptonight_hash_v2(&b),
function_name!(),
)
.await;
}
#[named]
pub(crate) async fn cryptonight_r(&self) {
self.test::<false>(
1788000..1978433,
|b, h, _, _| cuprate_cryptonight::cryptonight_hash_r(&b, h),
function_name!(),
)
.await;
}
#[named]
pub(crate) async fn randomx(&self) {
#[expect(clippy::significant_drop_tightening)]
let function = move |bytes: Vec<u8>, _, seed_height, seed_hash: [u8; 32]| {
static RANDOMX_VM: ThreadLocal<Mutex<BTreeMap<u64, RandomXVM>>> = ThreadLocal::new();
let mut thread_local = RANDOMX_VM
.get_or(|| Mutex::new(BTreeMap::new()))
.lock()
.unwrap();
let randomx_vm = thread_local.entry(seed_height).or_insert_with(|| {
let flag = RandomXFlag::get_recommended_flags();
let cache = RandomXCache::new(flag, &seed_hash).unwrap();
RandomXVM::new(flag, Some(cache), None).unwrap()
});
randomx_vm
.calculate_hash(&bytes)
.unwrap()
.try_into()
.unwrap()
};
self.test::<true>(1978433..self.top_height, function, function_name!())
.await;
}
}