use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use anyhow::Result;
use uhash_core::{meets_difficulty, UniversalHash};
use crate::solver::{ProofResult, Solver};
pub struct ParallelCpuSolver {
threads: usize,
}
impl ParallelCpuSolver {
pub fn new(threads: usize) -> Self {
Self { threads }
}
fn effective_threads(&self) -> usize {
if self.threads == 0 {
std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1)
} else {
self.threads
}
}
}
impl Solver for ParallelCpuSolver {
fn backend_name(&self) -> &'static str {
"cpu"
}
fn recommended_lanes(&mut self, requested: usize) -> usize {
if requested == 0 {
self.effective_threads() * 1024
} else {
requested
}
}
fn find_proof_batch(
&mut self,
header_without_nonce: &[u8],
start_nonce: u64,
lanes: usize,
difficulty: u32,
) -> Result<ProofResult> {
let threads = self.effective_threads();
let found = Arc::new(AtomicBool::new(false));
let winner = Arc::new(Mutex::new(None::<(u64, [u8; 32])>));
let challenge = Arc::new(header_without_nonce.to_vec());
let mut handles = Vec::with_capacity(threads);
for tid in 0..threads {
let found = Arc::clone(&found);
let winner = Arc::clone(&winner);
let challenge = Arc::clone(&challenge);
handles.push(std::thread::spawn(move || {
let mut hasher = UniversalHash::new();
let mut input = Vec::with_capacity(challenge.len() + 8);
let mut lane = tid;
while lane < lanes && !found.load(Ordering::Relaxed) {
let nonce = start_nonce.saturating_add(lane as u64);
input.clear();
input.extend_from_slice(&challenge);
input.extend_from_slice(&nonce.to_le_bytes());
let hash = hasher.hash(&input);
if meets_difficulty(&hash, difficulty) {
if !found.swap(true, Ordering::Relaxed) {
let mut guard = winner.lock().expect("winner mutex poisoned");
*guard = Some((nonce, hash));
}
break;
}
lane += threads;
}
}));
}
for h in handles {
h.join()
.map_err(|_| anyhow::anyhow!("cpu mining worker panicked"))?;
}
let result = *winner.lock().expect("winner mutex poisoned");
Ok(result)
}
fn benchmark_hashes(
&mut self,
header_without_nonce: &[u8],
start_nonce: u64,
lanes: usize,
) -> Result<usize> {
let threads = self.effective_threads();
let challenge = Arc::new(header_without_nonce.to_vec());
let mut handles = Vec::with_capacity(threads);
for tid in 0..threads {
let challenge = Arc::clone(&challenge);
handles.push(std::thread::spawn(move || {
let mut hasher = UniversalHash::new();
let mut input = Vec::with_capacity(challenge.len() + 8);
let mut lane = tid;
while lane < lanes {
let nonce = start_nonce.saturating_add(lane as u64);
input.clear();
input.extend_from_slice(&challenge);
input.extend_from_slice(&nonce.to_le_bytes());
let _ = hasher.hash(&input);
lane += threads;
}
}));
}
for h in handles {
h.join()
.map_err(|_| anyhow::anyhow!("cpu benchmark worker panicked"))?;
}
Ok(lanes)
}
}