Skip to main content

cast/cmd/
miner.rs

1use alloy_primitives::B256;
2use std::sync::{
3    Arc,
4    atomic::{AtomicBool, Ordering},
5};
6
7/// Mines a salt by iterating B256 values in parallel until `check` returns `Some`.
8///
9/// Each of the `n_threads` threads starts at `salt + thread_index` and steps by `n_threads`,
10/// ensuring non-overlapping coverage. Returns `None` only if all threads panicked.
11pub(crate) fn mine_salt<T, F>(salt: B256, n_threads: usize, check: F) -> Option<T>
12where
13    T: Send + 'static,
14    F: FnMut(B256) -> Option<T> + Clone + Send + 'static,
15{
16    let found = Arc::new(AtomicBool::new(false));
17    let mut handles = Vec::with_capacity(n_threads);
18
19    for i in 0..n_threads {
20        let increment = n_threads;
21        let found = Arc::clone(&found);
22        let mut check = check.clone();
23
24        handles.push(std::thread::spawn(move || {
25            #[repr(C)]
26            struct B256Aligned(B256, [usize; 0]);
27
28            let mut salt = B256Aligned(salt, []);
29            // SAFETY: `B256` is aligned to `usize`.
30            let salt_word = unsafe {
31                &mut *salt.0.as_mut_ptr().add(32 - usize::BITS as usize / 8).cast::<usize>()
32            };
33            // Important: offset by thread index to avoid duplicate work across threads.
34            *salt_word = salt_word.wrapping_add(i);
35
36            loop {
37                if found.load(Ordering::Relaxed) {
38                    break None;
39                }
40
41                if let Some(result) = check(salt.0) {
42                    found.store(true, Ordering::Relaxed);
43                    break Some(result);
44                }
45
46                *salt_word = salt_word.wrapping_add(increment);
47            }
48        }));
49    }
50
51    handles.into_iter().find_map(|h| h.join().ok().flatten())
52}