uhash-prover 0.4.1

UniversalHash solver backends (CPU/GPU) without chain transport
Documentation
//! Multi-threaded CPU solver.

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};

/// Multi-threaded CPU solver. Spawns OS threads with interleaved nonce
/// assignment to avoid contention.
pub struct ParallelCpuSolver {
    threads: usize,
}

impl ParallelCpuSolver {
    /// Create a new parallel solver with the given thread count.
    /// If `threads` is 0, it will be resolved in `recommended_lanes`.
    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)
    }
}