Rust's is_multiple_of is an unstable feature (issue #128101). Replace with standard modulo operator for compatibility with stable Rust.
783 lines
23 KiB
Rust
783 lines
23 KiB
Rust
//! kHeavyHash Proof of Work algorithm.
|
||
//!
|
||
//! The kHeavyHash algorithm is designed to be ASIC-resistant by combining:
|
||
//! - SHA3-256 for cryptographic hashing
|
||
//! - Matrix multiplication in GF(2^8) for memory hardness
|
||
//! - Blake3 for fast nonce mixing
|
||
//!
|
||
//! Algorithm steps:
|
||
//! 1. pre_hash = SHA3-256(header_without_nonce)
|
||
//! 2. nonce_hash = Blake3(pre_hash || nonce)
|
||
//! 3. matrix_out = HeavyMatrix × pre_hash (in GF(2^8))
|
||
//! 4. mixed = matrix_out XOR nonce_hash
|
||
//! 5. pow_hash = SHA3-256(mixed)
|
||
|
||
use sha3::{Digest, Sha3_256};
|
||
use synor_types::Hash256;
|
||
|
||
use crate::matrix::OptimizedMatrix;
|
||
use crate::Target;
|
||
|
||
/// The kHeavyHash algorithm implementation.
|
||
pub struct KHeavyHash {
|
||
/// The optimized matrix for fast multiplication.
|
||
matrix: OptimizedMatrix,
|
||
}
|
||
|
||
impl KHeavyHash {
|
||
/// Creates a new kHeavyHash instance.
|
||
pub fn new() -> Self {
|
||
KHeavyHash {
|
||
matrix: OptimizedMatrix::new(),
|
||
}
|
||
}
|
||
|
||
/// Creates with a custom matrix seed.
|
||
pub fn with_seed(seed: &[u8; 32]) -> Self {
|
||
KHeavyHash {
|
||
matrix: OptimizedMatrix::from_seed(seed),
|
||
}
|
||
}
|
||
|
||
/// Computes the full kHeavyHash PoW.
|
||
///
|
||
/// # Arguments
|
||
/// * `header` - Block header bytes (without nonce)
|
||
/// * `nonce` - The nonce to try
|
||
///
|
||
/// # Returns
|
||
/// The PoW hash that must be compared against target.
|
||
pub fn hash(&self, header: &[u8], nonce: u64) -> PowHash {
|
||
// Step 1: Pre-hash the header
|
||
let pre_hash = self.pre_hash(header);
|
||
|
||
// Step 2-5: Compute final PoW hash with nonce
|
||
self.finalize(&pre_hash, nonce)
|
||
}
|
||
|
||
/// Pre-hashes the block header (can be cached).
|
||
///
|
||
/// This is SHA3-256 of the header without the nonce.
|
||
pub fn pre_hash(&self, header: &[u8]) -> Hash256 {
|
||
let mut hasher = Sha3_256::new();
|
||
hasher.update(header);
|
||
let result: [u8; 32] = hasher.finalize().into();
|
||
Hash256::from_bytes(result)
|
||
}
|
||
|
||
/// Finalizes the PoW hash given a pre-hash and nonce.
|
||
///
|
||
/// This allows mining to cache the pre-hash and only vary the nonce.
|
||
pub fn finalize(&self, pre_hash: &Hash256, nonce: u64) -> PowHash {
|
||
// Step 2: Hash nonce with pre_hash using Blake3
|
||
let nonce_bytes = nonce.to_le_bytes();
|
||
let mut nonce_input = [0u8; 40]; // 32 + 8
|
||
nonce_input[..32].copy_from_slice(pre_hash.as_bytes());
|
||
nonce_input[32..].copy_from_slice(&nonce_bytes);
|
||
let nonce_hash: [u8; 32] = blake3::hash(&nonce_input).into();
|
||
|
||
// Step 3: Matrix multiplication
|
||
let matrix_out = self.matrix.multiply(pre_hash.as_bytes());
|
||
|
||
// Step 4: XOR matrix output with nonce hash
|
||
let mut mixed = [0u8; 32];
|
||
for i in 0..32 {
|
||
mixed[i] = matrix_out[i] ^ nonce_hash[i];
|
||
}
|
||
|
||
// Step 5: Final SHA3-256
|
||
let mut hasher = Sha3_256::new();
|
||
hasher.update(mixed);
|
||
let final_hash: [u8; 32] = hasher.finalize().into();
|
||
|
||
PowHash {
|
||
hash: Hash256::from_bytes(final_hash),
|
||
nonce,
|
||
pre_hash: *pre_hash,
|
||
}
|
||
}
|
||
|
||
/// Verifies a PoW solution.
|
||
pub fn verify(&self, header: &[u8], nonce: u64, target: &Target) -> bool {
|
||
let pow = self.hash(header, nonce);
|
||
target.is_met_by(&pow.hash)
|
||
}
|
||
|
||
/// Mines a block by trying nonces until target is met.
|
||
///
|
||
/// # Arguments
|
||
/// * `header` - Block header bytes
|
||
/// * `target` - Difficulty target
|
||
/// * `start_nonce` - Starting nonce
|
||
/// * `max_tries` - Maximum nonces to try (0 = unlimited)
|
||
///
|
||
/// # Returns
|
||
/// The solving nonce and PoW hash, or None if not found.
|
||
pub fn mine(
|
||
&self,
|
||
header: &[u8],
|
||
target: &Target,
|
||
start_nonce: u64,
|
||
max_tries: u64,
|
||
) -> Option<PowHash> {
|
||
let pre_hash = self.pre_hash(header);
|
||
let limit = if max_tries == 0 {
|
||
u64::MAX
|
||
} else {
|
||
start_nonce.saturating_add(max_tries)
|
||
};
|
||
|
||
for nonce in start_nonce..limit {
|
||
let pow = self.finalize(&pre_hash, nonce);
|
||
if target.is_met_by(&pow.hash) {
|
||
return Some(pow);
|
||
}
|
||
}
|
||
|
||
None
|
||
}
|
||
|
||
/// Mines with a callback for progress reporting.
|
||
pub fn mine_with_callback<F>(
|
||
&self,
|
||
header: &[u8],
|
||
target: &Target,
|
||
start_nonce: u64,
|
||
max_tries: u64,
|
||
mut callback: F,
|
||
) -> Option<PowHash>
|
||
where
|
||
F: FnMut(u64, u64) -> bool, // (nonces_tried, current_nonce) -> continue?
|
||
{
|
||
let pre_hash = self.pre_hash(header);
|
||
let limit = if max_tries == 0 {
|
||
u64::MAX
|
||
} else {
|
||
start_nonce.saturating_add(max_tries)
|
||
};
|
||
|
||
let mut tried = 0u64;
|
||
for nonce in start_nonce..limit {
|
||
let pow = self.finalize(&pre_hash, nonce);
|
||
tried += 1;
|
||
|
||
if target.is_met_by(&pow.hash) {
|
||
return Some(pow);
|
||
}
|
||
|
||
// Report progress every 10000 hashes
|
||
if tried % 10000 == 0 && !callback(tried, nonce) {
|
||
return None; // Cancelled
|
||
}
|
||
}
|
||
|
||
None
|
||
}
|
||
|
||
/// Returns the matrix used by this hasher.
|
||
pub fn matrix(&self) -> &OptimizedMatrix {
|
||
&self.matrix
|
||
}
|
||
}
|
||
|
||
impl Default for KHeavyHash {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
impl Clone for KHeavyHash {
|
||
fn clone(&self) -> Self {
|
||
// Matrix is large, but we need to clone for multi-threading
|
||
KHeavyHash {
|
||
matrix: OptimizedMatrix::new(), // Recreate from default seed
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Result of a PoW computation.
|
||
#[derive(Clone, Debug)]
|
||
pub struct PowHash {
|
||
/// The final PoW hash.
|
||
pub hash: Hash256,
|
||
/// The nonce that produced this hash.
|
||
pub nonce: u64,
|
||
/// The pre-hash (for verification).
|
||
pub pre_hash: Hash256,
|
||
}
|
||
|
||
impl PowHash {
|
||
/// Checks if this PoW meets a target.
|
||
pub fn meets_target(&self, target: &Target) -> bool {
|
||
target.is_met_by(&self.hash)
|
||
}
|
||
|
||
/// Returns the hash as bytes.
|
||
pub fn as_bytes(&self) -> &[u8; 32] {
|
||
self.hash.as_bytes()
|
||
}
|
||
}
|
||
|
||
/// Parallel mining using multiple threads.
|
||
pub struct ParallelMiner {
|
||
/// kHeavyHash instances per thread.
|
||
hashers: Vec<KHeavyHash>,
|
||
/// Number of threads.
|
||
num_threads: usize,
|
||
}
|
||
|
||
impl ParallelMiner {
|
||
/// Creates a parallel miner with the specified number of threads.
|
||
pub fn new(num_threads: usize) -> Self {
|
||
let threads = if num_threads == 0 {
|
||
num_cpus()
|
||
} else {
|
||
num_threads
|
||
};
|
||
|
||
let hashers = (0..threads).map(|_| KHeavyHash::new()).collect();
|
||
|
||
ParallelMiner {
|
||
hashers,
|
||
num_threads: threads,
|
||
}
|
||
}
|
||
|
||
/// Returns the number of threads.
|
||
pub fn num_threads(&self) -> usize {
|
||
self.num_threads
|
||
}
|
||
|
||
/// Mines using all threads.
|
||
///
|
||
/// Each thread gets a different nonce range to search.
|
||
pub fn mine(&self, header: &[u8], target: &Target, start_nonce: u64) -> Option<PowHash> {
|
||
use std::sync::atomic::{AtomicBool, Ordering};
|
||
use std::sync::Arc;
|
||
|
||
let found = Arc::new(AtomicBool::new(false));
|
||
let result = Arc::new(parking_lot::Mutex::new(None));
|
||
|
||
let nonce_step = u64::MAX / self.num_threads as u64;
|
||
|
||
std::thread::scope(|s| {
|
||
for (i, hasher) in self.hashers.iter().enumerate() {
|
||
let thread_start = start_nonce.wrapping_add(i as u64 * nonce_step);
|
||
let found = Arc::clone(&found);
|
||
let result = Arc::clone(&result);
|
||
let header = header.to_vec();
|
||
let target = *target;
|
||
|
||
s.spawn(move || {
|
||
let pre_hash = hasher.pre_hash(&header);
|
||
let mut nonce = thread_start;
|
||
|
||
loop {
|
||
// Check if another thread found it
|
||
if found.load(Ordering::Relaxed) {
|
||
return;
|
||
}
|
||
|
||
let pow = hasher.finalize(&pre_hash, nonce);
|
||
|
||
if target.is_met_by(&pow.hash) {
|
||
found.store(true, Ordering::Relaxed);
|
||
*result.lock() = Some(pow);
|
||
return;
|
||
}
|
||
|
||
nonce = nonce.wrapping_add(1);
|
||
|
||
// Don't wrap into another thread's range
|
||
if nonce == thread_start.wrapping_add(nonce_step) {
|
||
return;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
Arc::try_unwrap(result).ok()?.into_inner()
|
||
}
|
||
}
|
||
|
||
/// Returns the number of CPU cores.
|
||
fn num_cpus() -> usize {
|
||
std::thread::available_parallelism()
|
||
.map(|n| n.get())
|
||
.unwrap_or(1)
|
||
}
|
||
|
||
/// Benchmark helper for hashrate measurement.
|
||
pub struct HashrateBenchmark {
|
||
hasher: KHeavyHash,
|
||
}
|
||
|
||
impl HashrateBenchmark {
|
||
/// Creates a new benchmark.
|
||
pub fn new() -> Self {
|
||
HashrateBenchmark {
|
||
hasher: KHeavyHash::new(),
|
||
}
|
||
}
|
||
|
||
/// Runs a benchmark for the specified duration.
|
||
///
|
||
/// Returns hashes per second.
|
||
pub fn run(&self, duration_ms: u64) -> f64 {
|
||
let header = [0x42u8; 80]; // Dummy header
|
||
let pre_hash = self.hasher.pre_hash(&header);
|
||
|
||
let start = std::time::Instant::now();
|
||
let mut count = 0u64;
|
||
let mut nonce = 0u64;
|
||
|
||
while start.elapsed().as_millis() < duration_ms as u128 {
|
||
let _ = self.hasher.finalize(&pre_hash, nonce);
|
||
nonce = nonce.wrapping_add(1);
|
||
count += 1;
|
||
}
|
||
|
||
let elapsed = start.elapsed().as_secs_f64();
|
||
count as f64 / elapsed
|
||
}
|
||
}
|
||
|
||
impl Default for HashrateBenchmark {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_kheavyhash_deterministic() {
|
||
let hasher = KHeavyHash::new();
|
||
let header = b"test block header data";
|
||
let nonce = 12345u64;
|
||
let hash1 = hasher.hash(header, nonce);
|
||
let hash2 = hasher.hash(header, nonce);
|
||
assert_eq!(hash1.hash, hash2.hash);
|
||
assert_eq!(hash1.nonce, hash2.nonce);
|
||
}
|
||
|
||
#[test]
|
||
fn test_kheavyhash_deterministic_different_instances() {
|
||
let hasher1 = KHeavyHash::new();
|
||
let hasher2 = KHeavyHash::new();
|
||
let header = b"test block header data";
|
||
let nonce = 12345u64;
|
||
let hash1 = hasher1.hash(header, nonce);
|
||
let hash2 = hasher2.hash(header, nonce);
|
||
assert_eq!(hash1.hash, hash2.hash);
|
||
}
|
||
|
||
#[test]
|
||
fn test_kheavyhash_different_nonces() {
|
||
let hasher = KHeavyHash::new();
|
||
let header = b"test block header data";
|
||
let hash1 = hasher.hash(header, 1);
|
||
let hash2 = hasher.hash(header, 2);
|
||
assert_ne!(hash1.hash, hash2.hash);
|
||
}
|
||
|
||
#[test]
|
||
fn test_kheavyhash_different_nonces_sequential() {
|
||
let hasher = KHeavyHash::new();
|
||
let header = b"sequential nonce test";
|
||
let mut hashes = Vec::new();
|
||
for nonce in 0..10 {
|
||
let hash = hasher.hash(header, nonce);
|
||
hashes.push(hash.hash);
|
||
}
|
||
for i in 0..hashes.len() {
|
||
for j in (i + 1)..hashes.len() {
|
||
assert_ne!(hashes[i], hashes[j]);
|
||
}
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_kheavyhash_different_headers_same_nonce() {
|
||
let hasher = KHeavyHash::new();
|
||
let nonce = 12345u64;
|
||
let hash1 = hasher.hash(b"header one", nonce);
|
||
let hash2 = hasher.hash(b"header two", nonce);
|
||
assert_ne!(hash1.hash, hash2.hash);
|
||
}
|
||
|
||
#[test]
|
||
fn test_kheavyhash_different_headers_produce_different_hashes() {
|
||
let hasher = KHeavyHash::new();
|
||
let nonce = 0;
|
||
let headers = [
|
||
b"header A".as_slice(),
|
||
b"header B".as_slice(),
|
||
b"header C".as_slice(),
|
||
b"longer header".as_slice(),
|
||
];
|
||
let mut hashes = Vec::new();
|
||
for header in &headers {
|
||
let hash = hasher.hash(header, nonce);
|
||
hashes.push(hash.hash);
|
||
}
|
||
for i in 0..hashes.len() {
|
||
for j in (i + 1)..hashes.len() {
|
||
assert_ne!(hashes[i], hashes[j]);
|
||
}
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_kheavyhash_empty_header() {
|
||
let hasher = KHeavyHash::new();
|
||
let hash = hasher.hash(b"", 0);
|
||
assert!(hash.hash.as_bytes().len() == 32);
|
||
}
|
||
|
||
#[test]
|
||
fn test_kheavyhash_large_header() {
|
||
let hasher = KHeavyHash::new();
|
||
let large_header = vec![0x42u8; 1024];
|
||
let hash = hasher.hash(&large_header, 0);
|
||
assert!(hash.hash.as_bytes().len() == 32);
|
||
}
|
||
|
||
#[test]
|
||
fn test_pre_hash_caching() {
|
||
let hasher = KHeavyHash::new();
|
||
let header = b"test block header data";
|
||
let pre_hash = hasher.pre_hash(header);
|
||
let pow1 = hasher.finalize(&pre_hash, 1);
|
||
let pow2 = hasher.finalize(&pre_hash, 2);
|
||
assert_eq!(pow1.pre_hash, pow2.pre_hash);
|
||
assert_ne!(pow1.hash, pow2.hash);
|
||
}
|
||
|
||
#[test]
|
||
fn test_pre_hash_deterministic() {
|
||
let hasher = KHeavyHash::new();
|
||
let header = b"deterministic pre-hash test";
|
||
let pre_hash1 = hasher.pre_hash(header);
|
||
let pre_hash2 = hasher.pre_hash(header);
|
||
assert_eq!(pre_hash1, pre_hash2);
|
||
}
|
||
|
||
#[test]
|
||
fn test_pre_hash_different_headers() {
|
||
let hasher = KHeavyHash::new();
|
||
let pre_hash1 = hasher.pre_hash(b"header one");
|
||
let pre_hash2 = hasher.pre_hash(b"header two");
|
||
assert_ne!(pre_hash1, pre_hash2);
|
||
}
|
||
|
||
#[test]
|
||
fn test_finalize_same_pre_hash_different_nonces() {
|
||
let hasher = KHeavyHash::new();
|
||
let pre_hash = hasher.pre_hash(b"test header");
|
||
let pow1 = hasher.finalize(&pre_hash, 100);
|
||
let pow2 = hasher.finalize(&pre_hash, 200);
|
||
assert_eq!(pow1.pre_hash, pow2.pre_hash);
|
||
assert_ne!(pow1.hash, pow2.hash);
|
||
assert_eq!(pow1.nonce, 100);
|
||
assert_eq!(pow2.nonce, 200);
|
||
}
|
||
|
||
#[test]
|
||
fn test_finalize_consistency_with_hash() {
|
||
let hasher = KHeavyHash::new();
|
||
let header = b"consistency test";
|
||
let nonce = 42u64;
|
||
let direct = hasher.hash(header, nonce);
|
||
let pre_hash = hasher.pre_hash(header);
|
||
let indirect = hasher.finalize(&pre_hash, nonce);
|
||
assert_eq!(direct.hash, indirect.hash);
|
||
assert_eq!(direct.nonce, indirect.nonce);
|
||
assert_eq!(direct.pre_hash, indirect.pre_hash);
|
||
}
|
||
|
||
#[test]
|
||
fn test_verify_valid_solution() {
|
||
let hasher = KHeavyHash::new();
|
||
let header = b"verify test";
|
||
let target = Target::max();
|
||
let pow = hasher.mine(header, &target, 0, 10000).unwrap();
|
||
assert!(hasher.verify(header, pow.nonce, &target));
|
||
}
|
||
|
||
#[test]
|
||
fn test_verify_returns_correct_bool() {
|
||
let hasher = KHeavyHash::new();
|
||
let header = b"verify bool test";
|
||
let target = Target::max();
|
||
let pow = hasher.mine(header, &target, 0, 10000).unwrap();
|
||
let result = hasher.verify(header, pow.nonce, &target);
|
||
assert!(result);
|
||
}
|
||
|
||
#[test]
|
||
fn test_with_seed_creates_different_hasher() {
|
||
let seed1 = [0x01u8; 32];
|
||
let seed2 = [0x02u8; 32];
|
||
let hasher1 = KHeavyHash::with_seed(&seed1);
|
||
let hasher2 = KHeavyHash::with_seed(&seed2);
|
||
let header = b"seed test";
|
||
let nonce = 0u64;
|
||
let hash1 = hasher1.hash(header, nonce);
|
||
let hash2 = hasher2.hash(header, nonce);
|
||
assert_ne!(hash1.hash, hash2.hash);
|
||
}
|
||
|
||
#[test]
|
||
fn test_with_seed_deterministic() {
|
||
let seed = [0x42u8; 32];
|
||
let hasher1 = KHeavyHash::with_seed(&seed);
|
||
let hasher2 = KHeavyHash::with_seed(&seed);
|
||
let header = b"seed deterministic test";
|
||
let nonce = 0u64;
|
||
let hash1 = hasher1.hash(header, nonce);
|
||
let hash2 = hasher2.hash(header, nonce);
|
||
assert_eq!(hash1.hash, hash2.hash);
|
||
}
|
||
|
||
#[test]
|
||
fn test_kheavyhash_default_trait() {
|
||
let hasher1 = KHeavyHash::default();
|
||
let hasher2 = KHeavyHash::new();
|
||
let header = b"default trait test";
|
||
let hash1 = hasher1.hash(header, 0);
|
||
let hash2 = hasher2.hash(header, 0);
|
||
assert_eq!(hash1.hash, hash2.hash);
|
||
}
|
||
|
||
#[test]
|
||
fn test_kheavyhash_clone() {
|
||
let hasher1 = KHeavyHash::new();
|
||
let hasher2 = hasher1.clone();
|
||
let header = b"clone test";
|
||
let hash1 = hasher1.hash(header, 0);
|
||
let hash2 = hasher2.hash(header, 0);
|
||
assert_eq!(hash1.hash, hash2.hash);
|
||
}
|
||
|
||
#[test]
|
||
fn test_matrix_accessor() {
|
||
let hasher = KHeavyHash::new();
|
||
let matrix = hasher.matrix();
|
||
let m1 = matrix.multiply(&[0x42u8; 32]);
|
||
let m2 = matrix.multiply(&[0x42u8; 32]);
|
||
assert_eq!(m1, m2);
|
||
}
|
||
|
||
#[test]
|
||
fn test_mine_easy_target() {
|
||
let hasher = KHeavyHash::new();
|
||
let header = b"mine me";
|
||
let target = Target::max();
|
||
let result = hasher.mine(header, &target, 0, 10000);
|
||
assert!(result.is_some());
|
||
let pow = result.unwrap();
|
||
assert!(target.is_met_by(&pow.hash));
|
||
}
|
||
|
||
#[test]
|
||
fn test_mine_returns_valid_nonce() {
|
||
let hasher = KHeavyHash::new();
|
||
let header = b"mining nonce test";
|
||
let target = Target::max();
|
||
let result = hasher.mine(header, &target, 0, 10000).unwrap();
|
||
let verification = hasher.hash(header, result.nonce);
|
||
assert!(target.is_met_by(&verification.hash));
|
||
}
|
||
|
||
#[test]
|
||
fn test_mine_with_start_nonce() {
|
||
let hasher = KHeavyHash::new();
|
||
let header = b"start nonce test";
|
||
let target = Target::max();
|
||
let start_nonce = 5000;
|
||
let result = hasher.mine(header, &target, start_nonce, 10000);
|
||
assert!(result.is_some());
|
||
let pow = result.unwrap();
|
||
assert!(pow.nonce >= start_nonce);
|
||
}
|
||
|
||
#[test]
|
||
fn test_mine_impossible_target() {
|
||
let hasher = KHeavyHash::new();
|
||
let header = b"impossible test";
|
||
let target = Target::from_bytes([0u8; 32]);
|
||
let result = hasher.mine(header, &target, 0, 100);
|
||
assert!(result.is_none());
|
||
}
|
||
|
||
#[test]
|
||
fn test_mine_with_callback_finds_solution() {
|
||
let hasher = KHeavyHash::new();
|
||
let header = b"callback test";
|
||
let target = Target::max();
|
||
let result = hasher.mine_with_callback(header, &target, 0, 50000, |_, _| true);
|
||
assert!(result.is_some());
|
||
}
|
||
|
||
#[test]
|
||
fn test_mine_with_callback_can_cancel() {
|
||
let hasher = KHeavyHash::new();
|
||
let header = b"cancel test";
|
||
let target = Target::from_bytes([0u8; 32]);
|
||
let mut called = false;
|
||
let result = hasher.mine_with_callback(header, &target, 0, 100000, |_, _| {
|
||
if called {
|
||
false
|
||
} else {
|
||
called = true;
|
||
true
|
||
}
|
||
});
|
||
assert!(result.is_none());
|
||
}
|
||
|
||
#[test]
|
||
fn test_pow_hash_meets_target() {
|
||
let hasher = KHeavyHash::new();
|
||
let header = b"pow hash test";
|
||
let target = Target::max();
|
||
let pow = hasher.mine(header, &target, 0, 10000).unwrap();
|
||
assert!(pow.meets_target(&target));
|
||
}
|
||
|
||
#[test]
|
||
fn test_pow_hash_as_bytes() {
|
||
let hasher = KHeavyHash::new();
|
||
let pow = hasher.hash(b"test", 0);
|
||
let bytes = pow.as_bytes();
|
||
assert_eq!(bytes.len(), 32);
|
||
assert_eq!(bytes, pow.hash.as_bytes());
|
||
}
|
||
|
||
#[test]
|
||
fn test_pow_hash_clone() {
|
||
let hasher = KHeavyHash::new();
|
||
let pow = hasher.hash(b"clone test", 42);
|
||
let cloned = pow.clone();
|
||
assert_eq!(cloned.hash, pow.hash);
|
||
assert_eq!(cloned.nonce, pow.nonce);
|
||
assert_eq!(cloned.pre_hash, pow.pre_hash);
|
||
}
|
||
|
||
#[test]
|
||
fn test_pow_hash_debug() {
|
||
let hasher = KHeavyHash::new();
|
||
let pow = hasher.hash(b"debug test", 0);
|
||
let debug_str = format!("{:?}", pow);
|
||
assert!(debug_str.contains("PowHash"));
|
||
}
|
||
|
||
#[test]
|
||
fn test_parallel_miner_creation_with_thread_count() {
|
||
let miner = ParallelMiner::new(4);
|
||
assert_eq!(miner.num_threads(), 4);
|
||
}
|
||
|
||
#[test]
|
||
fn test_parallel_miner_creation_single_thread() {
|
||
let miner = ParallelMiner::new(1);
|
||
assert_eq!(miner.num_threads(), 1);
|
||
}
|
||
|
||
#[test]
|
||
fn test_parallel_miner_auto_thread_detection() {
|
||
let miner = ParallelMiner::new(0);
|
||
assert!(miner.num_threads() >= 1);
|
||
}
|
||
|
||
#[test]
|
||
fn test_parallel_miner_mine_finds_solution() {
|
||
let miner = ParallelMiner::new(2);
|
||
let header = b"parallel mining test";
|
||
let target = Target::max();
|
||
let result = miner.mine(header, &target, 0);
|
||
assert!(result.is_some());
|
||
}
|
||
|
||
#[test]
|
||
fn test_parallel_miner_solution_meets_target() {
|
||
let miner = ParallelMiner::new(2);
|
||
let header = b"parallel target test";
|
||
let target = Target::max();
|
||
let result = miner.mine(header, &target, 0).unwrap();
|
||
assert!(target.is_met_by(&result.hash));
|
||
}
|
||
|
||
#[test]
|
||
fn test_parallel_miner_solution_is_valid() {
|
||
let miner = ParallelMiner::new(2);
|
||
let header = b"parallel valid test";
|
||
let target = Target::max();
|
||
let result = miner.mine(header, &target, 0).unwrap();
|
||
let hasher = KHeavyHash::new();
|
||
let verification = hasher.hash(header, result.nonce);
|
||
assert_eq!(verification.hash, result.hash);
|
||
}
|
||
|
||
#[test]
|
||
fn test_parallel_miner_different_start_nonce() {
|
||
let miner = ParallelMiner::new(2);
|
||
let header = b"start nonce parallel test";
|
||
let target = Target::max();
|
||
let result = miner.mine(header, &target, 1000);
|
||
assert!(result.is_some());
|
||
}
|
||
|
||
#[test]
|
||
fn test_hashrate_benchmark_returns_positive() {
|
||
let bench = HashrateBenchmark::new();
|
||
let hashrate = bench.run(100);
|
||
assert!(hashrate > 0.0);
|
||
}
|
||
|
||
#[test]
|
||
fn test_hashrate_benchmark_is_finite() {
|
||
let bench = HashrateBenchmark::new();
|
||
let hashrate = bench.run(100);
|
||
assert!(hashrate.is_finite());
|
||
}
|
||
|
||
#[test]
|
||
fn test_hashrate_benchmark_consistency() {
|
||
let bench = HashrateBenchmark::new();
|
||
let h1 = bench.run(100);
|
||
let h2 = bench.run(100);
|
||
assert!(h1 > 0.0);
|
||
assert!(h2 > 0.0);
|
||
let ratio = h1 / h2;
|
||
assert!(ratio > 0.1 && ratio < 10.0);
|
||
}
|
||
|
||
#[test]
|
||
fn test_hashrate_benchmark_default_trait() {
|
||
let bench1 = HashrateBenchmark::default();
|
||
let bench2 = HashrateBenchmark::new();
|
||
let h1 = bench1.run(50);
|
||
let h2 = bench2.run(50);
|
||
assert!(h1 > 0.0);
|
||
assert!(h2 > 0.0);
|
||
}
|
||
|
||
#[test]
|
||
fn test_hashrate_benchmark_short_duration() {
|
||
let bench = HashrateBenchmark::new();
|
||
let hashrate = bench.run(10);
|
||
assert!(hashrate > 0.0);
|
||
}
|
||
|
||
#[test]
|
||
fn test_hashrate_benchmark_longer_duration() {
|
||
let bench = HashrateBenchmark::new();
|
||
let hashrate = bench.run(200);
|
||
assert!(hashrate > 0.0);
|
||
}
|
||
}
|