hashiverse_lib/tools/pow_generator/
native_parallel_pow_generator.rs1#![cfg(not(target_arch = "wasm32"))]
11
12use crate::tools::pow_generator::pow_generator;
13use crate::tools::pow_generator::pow_generator::{JobTracker, PowGenerator};
14use crate::tools::types::{Hash, Pow, Salt};
15use std::sync::{Arc, Mutex};
16use tokio_util::sync::CancellationToken;
17
18pub struct NativeParallelPowGenerator {
19 tracker: Arc<Mutex<JobTracker>>,
20 cancellation_token: Option<CancellationToken>,
21}
22
23impl NativeParallelPowGenerator {
24 pub fn new() -> Self {
25 Self { tracker: Arc::new(Mutex::new(JobTracker::default())), cancellation_token: None }
26 }
27
28 pub fn with_cancellation_token(mut self, cancellation_token: CancellationToken) -> Self {
31 self.cancellation_token = Some(cancellation_token);
32 self
33 }
34}
35
36impl Default for NativeParallelPowGenerator {
37 fn default() -> Self { Self::new() }
38}
39
40#[async_trait::async_trait]
41impl PowGenerator for NativeParallelPowGenerator {
42 fn pool_size(&self) -> usize {
43 std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1)
44 }
45
46 async fn run_chunk(&self, _slot: usize, chunk_iterations: usize, pow_min: Pow, data_hash: Hash) -> anyhow::Result<(Salt, Pow, Hash)> {
47 let result = tokio::task::spawn_blocking(move || -> anyhow::Result<(Salt, Pow, Hash)> {
48 pow_generator::run_pool_chunk(chunk_iterations, pow_min, data_hash)
49 })
50 .await??;
51
52 Ok(result)
53 }
54
55 fn tracker(&self) -> &Arc<Mutex<JobTracker>> {
56 &self.tracker
57 }
58
59 fn cancellation_token(&self) -> Option<&CancellationToken> {
60 self.cancellation_token.as_ref()
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use crate::tools::pow::pow_compute_data_hash;
67 use crate::tools::pow_generator::native_parallel_pow_generator::NativeParallelPowGenerator;
68 use crate::tools::pow_generator::pow_generator::PowGenerator;
69 use crate::tools::tools;
70 use crate::tools::types::Pow;
71 use std::sync::Arc;
72 use std::time::Duration;
73 use tokio_util::sync::CancellationToken;
74
75 #[tokio::test]
76 async fn native_generates_valid_pow() -> anyhow::Result<()> {
77 const POW_MIN: Pow = Pow(12);
78 let mut data = [0u8; 64];
79 tools::random_fill_bytes(&mut data);
80 let data_hash = pow_compute_data_hash(&[&data]);
81 let generator = NativeParallelPowGenerator::new();
82 let (_, pow, _) = generator.generate("test", POW_MIN, data_hash).await?;
83 assert!(pow >= POW_MIN);
84 Ok(())
85 }
86
87 const UNREACHABLE_POW: Pow = Pow(255);
89
90 #[tokio::test]
91 async fn pre_cancelled_generator_fails_immediately() {
92 let cancellation_token = CancellationToken::new();
93 cancellation_token.cancel();
94 let generator = NativeParallelPowGenerator::new().with_cancellation_token(cancellation_token);
95 let data_hash = pow_compute_data_hash(&[b"pre-cancelled"]);
96
97 let result = tokio::time::timeout(Duration::from_secs(2), generator.generate("cancel", UNREACHABLE_POW, data_hash)).await.expect("generate did not return promptly");
98 assert!(result.is_err_and(|error| error.to_string().contains("cancelled")));
99 assert!(generator.active_jobs().is_empty());
100 }
101
102 #[tokio::test(flavor = "multi_thread")]
103 async fn cancel_mid_search_stops_generate() {
104 let cancellation_token = CancellationToken::new();
105 let generator = Arc::new(NativeParallelPowGenerator::new().with_cancellation_token(cancellation_token.clone()));
106 let data_hash = pow_compute_data_hash(&[b"cancel-mid-search"]);
107
108 let search = {
109 let generator = generator.clone();
110 tokio::spawn(async move { generator.generate("cancel", UNREACHABLE_POW, data_hash).await })
111 };
112 tokio::time::sleep(Duration::from_millis(100)).await;
113 assert!(!generator.active_jobs().is_empty(), "search should be running");
114 cancellation_token.cancel();
115
116 let result = tokio::time::timeout(Duration::from_secs(2), search).await.expect("generate did not stop within 2s of cancellation").unwrap();
117 assert!(result.is_err_and(|error| error.to_string().contains("cancelled")));
118 assert!(generator.active_jobs().is_empty(), "cancelled job must be removed from the tracker");
119 }
120
121 #[tokio::test(flavor = "multi_thread")]
122 async fn cancel_mid_search_stops_generate_best_effort() {
123 let cancellation_token = CancellationToken::new();
124 let generator = Arc::new(NativeParallelPowGenerator::new().with_cancellation_token(cancellation_token.clone()));
125 let data_hash = pow_compute_data_hash(&[b"cancel-best-effort"]);
126
127 let search = {
128 let generator = generator.clone();
129 tokio::spawn(async move { generator.generate_best_effort("cancel", usize::MAX, UNREACHABLE_POW, data_hash).await })
130 };
131 tokio::time::sleep(Duration::from_millis(100)).await;
132 cancellation_token.cancel();
133
134 let result = tokio::time::timeout(Duration::from_secs(2), search).await.expect("generate_best_effort did not stop within 2s of cancellation").unwrap();
135 assert!(result.is_err_and(|error| error.to_string().contains("cancelled")));
136 }
137
138 #[tokio::test]
139 async fn uncancelled_token_behaves_like_no_token() -> anyhow::Result<()> {
140 const POW_MIN: Pow = Pow(12);
141 let generator = NativeParallelPowGenerator::new().with_cancellation_token(CancellationToken::new());
142 let data_hash = pow_compute_data_hash(&[b"uncancelled"]);
143 let (_, pow, _) = generator.generate("test", POW_MIN, data_hash).await?;
144 assert!(pow >= POW_MIN);
145 let (_, best_effort_pow, _) = generator.generate_best_effort("test", 4096, POW_MIN, data_hash).await?;
146 assert!(best_effort_pow > Pow(0));
147 Ok(())
148 }
149}