darkfi/validator/
pow.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2026 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use std::{
20    sync::{
21        atomic::{AtomicBool, AtomicU32, Ordering},
22        Arc,
23    },
24    thread,
25    time::Instant,
26};
27
28use darkfi_sdk::num_traits::{One, Zero};
29use num_bigint::BigUint;
30use randomx::{RandomXCache, RandomXDataset, RandomXFlags, RandomXVM};
31use smol::channel::Receiver;
32use tracing::{debug, error};
33
34use crate::{
35    blockchain::{
36        block_store::BlockDifficulty,
37        header_store::{
38            Header, HeaderHash,
39            PowData::{DarkFi, Monero},
40        },
41        Blockchain, BlockchainOverlayPtr,
42    },
43    util::{ringbuffer::RingBuffer, time::Timestamp},
44    validator::{
45        utils::{median, MAX_32_BYTES},
46        RandomXFactory,
47    },
48    Error, Result,
49};
50
51// Note: We have combined some constants for better performance.
52/// Amount of max items(blocks) to use for next difficulty calculation.
53/// Must be >= 2 and == BUF_SIZE - DIFFICULTY_LAG.
54const DIFFICULTY_WINDOW: usize = 720;
55/// Amount of latest blocks to exlude from the calculation.
56/// Our ring buffer has length: DIFFICULTY_WINDOW + DIFFICULTY_LAG,
57/// but we only use DIFFICULTY_WINDOW items in calculations.
58/// Must be == BUF_SIZE - DIFFICULTY_WINDOW.
59const _DIFFICULTY_LAG: usize = 15;
60/// Ring buffer length.
61/// Must be == DIFFICULTY_WINDOW + DIFFICULTY_LAG
62const BUF_SIZE: usize = 735;
63/// Used to calculate how many items to retain for next difficulty
64/// calculation. We are keeping the middle items, meaning cutting
65/// both from frond and back of the ring buffer, ending up with max
66/// DIFFICULTY_WINDOW - 2*DIFFICULTY_CUT items.
67/// (2*DIFFICULTY_CUT <= DIFFICULTY_WINDOW-2) must be true.
68const _DIFFICULTY_CUT: usize = 60;
69/// Max items to use for next difficulty calculation.
70/// Must be DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT
71const RETAINED: usize = 600;
72/// Already known cutoff start index for this config
73const CUT_BEGIN: usize = 60;
74/// Already known cutoff end index for this config
75const CUT_END: usize = 660;
76/// How many most recent blocks to use to verify new blocks' timestamp
77const BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW: usize = 60;
78/// Time limit in the future of what blocks can be
79const BLOCK_FUTURE_TIME_LIMIT: Timestamp = Timestamp::from_u64(60 * 60 * 2);
80/// RandomX VM key changing height
81pub const RANDOMX_KEY_CHANGING_HEIGHT: u32 = 2048;
82/// RandomX VM key change delay
83pub const RANDOMX_KEY_CHANGE_DELAY: u32 = 64;
84
85/// This struct represents the information required by the PoW
86/// algorithm.
87#[derive(Clone)]
88pub struct PoWModule {
89    /// Genesis block timestamp
90    pub genesis: Timestamp,
91    /// Target block time, in seconds
92    pub target: u32,
93    /// Optional fixed difficulty
94    pub fixed_difficulty: Option<BigUint>,
95    /// Latest block timestamps ringbuffer
96    pub timestamps: RingBuffer<Timestamp, BUF_SIZE>,
97    /// Latest block cumulative difficulties ringbuffer
98    pub difficulties: RingBuffer<BigUint, BUF_SIZE>,
99    /// Total blocks cumulative difficulty
100    /// Note: we keep this as a struct field for faster
101    /// access(optimization), since its always same as
102    /// difficulties buffer last.
103    pub cumulative_difficulty: BigUint,
104    /// Native PoW RandomX VMs current and next keys pair
105    pub darkfi_rx_keys: (HeaderHash, Option<HeaderHash>),
106    /// RandomXFactory for native PoW (Arc from parent)
107    pub darkfi_rx_factory: RandomXFactory,
108    /// RandomXFactory for Monero PoW (Arc from parent)
109    pub monero_rx_factory: RandomXFactory,
110}
111
112impl PoWModule {
113    /// Initialize a new `PowModule` for provided target over provided
114    /// `Blockchain`. Optionally, a fixed difficulty can be set and/or
115    /// initialize before some height.
116    pub fn new(
117        blockchain: Blockchain,
118        target: u32,
119        fixed_difficulty: Option<BigUint>,
120        height: Option<u32>,
121    ) -> Result<Self> {
122        // Retrieve genesis block timestamp
123        let genesis = blockchain.genesis_block()?.header.timestamp;
124
125        // Retrieving last BUF_SIZE difficulties from blockchain to
126        // build the buffers.
127        let mut timestamps = RingBuffer::<Timestamp, BUF_SIZE>::new();
128        let mut difficulties = RingBuffer::<BigUint, BUF_SIZE>::new();
129        let mut cumulative_difficulty = BigUint::zero();
130        let last_n = match height {
131            Some(h) => blockchain.blocks.get_difficulties_before(h, BUF_SIZE)?,
132            None => blockchain.blocks.get_last_n_difficulties(BUF_SIZE)?,
133        };
134        for difficulty in last_n {
135            timestamps.push(difficulty.timestamp);
136            difficulties.push(difficulty.cumulative_difficulty.clone());
137            cumulative_difficulty = difficulty.cumulative_difficulty;
138        }
139
140        // If a fixed difficulty has been set, assert its greater than
141        // zero.
142        if let Some(diff) = &fixed_difficulty {
143            assert!(diff > &BigUint::zero());
144        }
145
146        // Retrieve current and next native PoW RandomX VM keys pair,
147        // and generate the RandomX factories.
148        let darkfi_rx_keys = blockchain.get_randomx_vm_keys(
149            &RANDOMX_KEY_CHANGING_HEIGHT,
150            &RANDOMX_KEY_CHANGE_DELAY,
151            height,
152        )?;
153        let darkfi_rx_factory = RandomXFactory::default();
154        let monero_rx_factory = RandomXFactory::default();
155
156        Ok(Self {
157            genesis,
158            target,
159            fixed_difficulty,
160            timestamps,
161            difficulties,
162            cumulative_difficulty,
163            darkfi_rx_keys,
164            darkfi_rx_factory,
165            monero_rx_factory,
166        })
167    }
168
169    /// Compute the next mining difficulty, based on current ring
170    /// buffers. If ring buffers contain 2 or less items, difficulty 1
171    /// is returned. If a fixed difficulty has been set, this function
172    /// will always return that after first 2 difficulties.
173    pub fn next_difficulty(&self) -> Result<BigUint> {
174        // Retrieve first DIFFICULTY_WINDOW timestamps from the ring
175        // buffer.
176        let mut timestamps: Vec<Timestamp> =
177            self.timestamps.iter().take(DIFFICULTY_WINDOW).cloned().collect();
178
179        // Check we have enough timestamps
180        let length = timestamps.len();
181        if length < 2 {
182            return Ok(BigUint::one())
183        }
184
185        // If a fixed difficulty has been set, return that
186        if let Some(diff) = &self.fixed_difficulty {
187            return Ok(diff.clone())
188        }
189
190        // Sort the timestamps vector
191        timestamps.sort_unstable();
192
193        // Grab cutoff indexes
194        let (cut_begin, cut_end) = self.cutoff(length)?;
195
196        // Calculate total time span
197        let cut_end = cut_end - 1;
198
199        let mut time_span = timestamps[cut_end].checked_sub(timestamps[cut_begin])?;
200        if time_span.inner() == 0 {
201            time_span = 1.into();
202        }
203
204        // Calculate total work done during this time span
205        let total_work = &self.difficulties[cut_end] - &self.difficulties[cut_begin];
206        if total_work <= BigUint::zero() {
207            return Err(Error::PoWTotalWorkIsZero)
208        }
209
210        // Compute next difficulty
211        let next_difficulty =
212            (total_work * self.target + time_span.inner() - BigUint::one()) / time_span.inner();
213
214        Ok(next_difficulty)
215    }
216
217    /// Calculate cutoff indexes.
218    /// If buffers have been filled, we return the
219    /// already known indexes, for performance.
220    fn cutoff(&self, length: usize) -> Result<(usize, usize)> {
221        if length >= DIFFICULTY_WINDOW {
222            return Ok((CUT_BEGIN, CUT_END))
223        }
224
225        let (cut_begin, cut_end) = if length <= RETAINED {
226            (0, length)
227        } else {
228            let cut_begin = (length - RETAINED).div_ceil(2);
229            (cut_begin, cut_begin + RETAINED)
230        };
231        // Sanity check
232        if
233        /* cut_begin < 0 || */
234        cut_begin + 2 > cut_end || cut_end > length {
235            return Err(Error::PoWCuttofCalculationError)
236        }
237
238        Ok((cut_begin, cut_end))
239    }
240
241    /// Compute the next mine target.
242    pub fn next_mine_target(&self) -> Result<BigUint> {
243        Ok(&*MAX_32_BYTES / &self.next_difficulty()?)
244    }
245
246    /// Compute the next mine target and difficulty.
247    pub fn next_mine_target_and_difficulty(&self) -> Result<(BigUint, BigUint)> {
248        let difficulty = self.next_difficulty()?;
249        let mine_target = &*MAX_32_BYTES / &difficulty;
250        Ok((mine_target, difficulty))
251    }
252
253    /// Verify provided difficulty corresponds to the next one.
254    pub fn verify_difficulty(&self, difficulty: &BigUint) -> Result<bool> {
255        Ok(difficulty == &self.next_difficulty()?)
256    }
257
258    /// Verify provided block timestamp is not far in the future and
259    /// check its valid acorrding to current timestamps median.
260    pub fn verify_current_timestamp(&self, timestamp: Timestamp) -> Result<bool> {
261        if timestamp > Timestamp::current_time().checked_add(BLOCK_FUTURE_TIME_LIMIT)? {
262            return Ok(false)
263        }
264
265        Ok(self.verify_timestamp_by_median(timestamp))
266    }
267
268    /// Verify provided block timestamp is valid and matches certain
269    /// criteria.
270    pub fn verify_timestamp_by_median(&self, timestamp: Timestamp) -> bool {
271        // Check timestamp is after genesis one
272        if timestamp <= self.genesis {
273            return false
274        }
275
276        // If not enough blocks, no proper median yet, return true
277        if self.timestamps.len() < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW {
278            return true
279        }
280
281        // Make sure the timestamp is higher or equal to the median
282        let timestamps = self
283            .timestamps
284            .iter()
285            .rev()
286            .take(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW)
287            .map(|x| x.inner())
288            .collect();
289
290        timestamp >= median(timestamps).into()
291    }
292
293    /// Verify provided block timestamp and hash.
294    pub fn verify_current_block(&mut self, header: &Header) -> Result<()> {
295        // First we verify the block's timestamp
296        if !self.verify_current_timestamp(header.timestamp)? {
297            return Err(Error::PoWInvalidTimestamp)
298        }
299
300        // Then we verify the block's hash
301        self.verify_block_hash(header)
302    }
303
304    /// Compute provided block header hash based on its PoW data.
305    pub fn calculate_hash(&mut self, header: &Header) -> Result<BigUint> {
306        // Grab corresponding VM based on block PoW data
307        let vm_setup = Instant::now();
308        let (pow_type, vm, blob) = match &header.pow_data {
309            DarkFi => {
310                // Check which VM key should be used.
311                // We only use the next key when the next block is the
312                // height changing one.
313                let randomx_key = if header.height > RANDOMX_KEY_CHANGING_HEIGHT &&
314                    header.height % RANDOMX_KEY_CHANGING_HEIGHT == RANDOMX_KEY_CHANGE_DELAY
315                {
316                    &self
317                        .darkfi_rx_keys
318                        .1
319                        .ok_or_else(|| Error::ParseFailed("darkfi_rx_keys.1 unwrap() error"))?
320                } else {
321                    &self.darkfi_rx_keys.0
322                };
323
324                (
325                    "DarkFi",
326                    self.darkfi_rx_factory.create(&randomx_key.inner()[..])?,
327                    header.to_block_hashing_blob(),
328                )
329            }
330            Monero(powdata) => (
331                "Monero",
332                self.monero_rx_factory.create(powdata.randomx_key())?,
333                powdata.to_block_hashing_blob(),
334            ),
335        };
336        debug!(target: "validator::pow::calculate_hash", "[VERIFIER] {pow_type} PoW setup time: {:?}", vm_setup.elapsed());
337
338        // Compute the hash and convert it to `BigUint`
339        let compute_time = Instant::now();
340        let out_hash = vm.calculate_hash(&blob)?;
341        let out_hash = BigUint::from_bytes_le(&out_hash);
342        debug!(target: "validator::pow::calculate_hash", "[VERIFIER] Hash compute time: {:?}", compute_time.elapsed());
343
344        Ok(out_hash)
345    }
346
347    /// Verify provided block hash is less than provided mine target.
348    pub fn verify_block_target(&mut self, header: &Header, target: &BigUint) -> Result<BigUint> {
349        // Grab verifier output hash based on block PoW data
350        let out_hash = self.calculate_hash(header)?;
351
352        // Verify hash is less than the provided mine target
353        if out_hash > *target {
354            return Err(Error::PoWInvalidOutHash)
355        }
356
357        Ok(out_hash)
358    }
359
360    /// Verify provided block corresponds to next mine target.
361    pub fn verify_block_hash(&mut self, header: &Header) -> Result<()> {
362        // Grab the next mine target
363        let target = self.next_mine_target()?;
364
365        // Verify hash is less than the expected mine target
366        let _ = self.verify_block_target(header, &target)?;
367        Ok(())
368    }
369
370    /// Append provided header timestamp and difficulty to the ring
371    /// buffers, and check if we need to rotate and/or create the next
372    /// key RandomX VM in the native PoW factory.
373    pub fn append(&mut self, header: &Header, difficulty: &BigUint) -> Result<()> {
374        self.timestamps.push(header.timestamp);
375        self.cumulative_difficulty += difficulty;
376        self.difficulties.push(self.cumulative_difficulty.clone());
377
378        if header.height < RANDOMX_KEY_CHANGING_HEIGHT {
379            return Ok(())
380        }
381
382        // Check if need to set the new key
383        if header.height.is_multiple_of(RANDOMX_KEY_CHANGING_HEIGHT) {
384            let next_key = header.hash();
385            let _ = self.darkfi_rx_factory.create(&next_key.inner()[..])?;
386            self.darkfi_rx_keys.1 = Some(next_key);
387            return Ok(())
388        }
389
390        // Check if need to rotate keys
391        if header.height % RANDOMX_KEY_CHANGING_HEIGHT == RANDOMX_KEY_CHANGE_DELAY {
392            self.darkfi_rx_keys.0 = self
393                .darkfi_rx_keys
394                .1
395                .ok_or_else(|| Error::ParseFailed("darkfi_rx_keys.1 unwrap() error"))?;
396            self.darkfi_rx_keys.1 = None;
397        }
398
399        Ok(())
400    }
401
402    /// Append provided block difficulty to the ring buffers and insert
403    /// it to provided overlay.
404    pub fn append_difficulty(
405        &mut self,
406        overlay: &BlockchainOverlayPtr,
407        header: &Header,
408        difficulty: BlockDifficulty,
409    ) -> Result<()> {
410        self.append(header, &difficulty.difficulty)?;
411        overlay.lock().unwrap().blocks.insert_difficulty(&[difficulty])
412    }
413
414    /// Mine provided block, based on next mine target.
415    /// Note: this is used in tests not in actual mining.
416    pub fn mine_block(
417        &self,
418        header: &mut Header,
419        threads: usize,
420        stop_signal: &Receiver<()>,
421    ) -> Result<()> {
422        // Grab the RandomX key to use.
423        // We only use the next key when the next block is the
424        // height changing one.
425        let randomx_key = if header.height > RANDOMX_KEY_CHANGING_HEIGHT &&
426            header.height % RANDOMX_KEY_CHANGING_HEIGHT == RANDOMX_KEY_CHANGE_DELAY
427        {
428            &self
429                .darkfi_rx_keys
430                .1
431                .ok_or_else(|| Error::ParseFailed("darkfi_rx_keys.1 unwrap() error"))?
432        } else {
433            &self.darkfi_rx_keys.0
434        };
435
436        // Generate the RandomX VMs for the key
437        let flags = get_mining_flags(false, false, false);
438        let vms = generate_mining_vms(flags, randomx_key, threads, stop_signal)?;
439
440        // Grab the next mine target
441        let target = self.next_mine_target()?;
442
443        // Mine the block
444        mine_block(&vms, &target, header, stop_signal)
445    }
446}
447
448impl std::fmt::Display for PoWModule {
449    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
450        write!(f, "PoWModule:")?;
451        write!(f, "\ttarget: {}", self.target)?;
452        write!(f, "\ttimestamps: {:?}", self.timestamps)?;
453        write!(f, "\tdifficulties: {:?}", self.difficulties)?;
454        write!(f, "\tcumulative_difficulty: {}", self.cumulative_difficulty)
455    }
456}
457
458/// Auxiliary function to define `RandomXFlags` used in mining.
459///
460/// Note: RandomX recommended flags will include `SSSE3` and `AVX2`
461/// extensions if CPU supports them.
462pub fn get_mining_flags(fast_mode: bool, large_pages: bool, secure: bool) -> RandomXFlags {
463    let mut flags = RandomXFlags::get_recommended_flags();
464    if fast_mode {
465        flags |= RandomXFlags::FULLMEM;
466    }
467    if large_pages {
468        flags |= RandomXFlags::LARGEPAGES;
469    }
470    if secure && flags.contains(RandomXFlags::JIT) {
471        flags |= RandomXFlags::SECURE;
472    }
473    flags
474}
475
476/// Auxiliary function to initialize a `RandomXDataset` using all
477/// available threads.
478fn init_dataset(
479    flags: RandomXFlags,
480    input: &HeaderHash,
481    stop_signal: &Receiver<()>,
482) -> Result<RandomXDataset> {
483    // Allocate cache and dataset
484    let cache = RandomXCache::new(flags, &input.inner()[..])?;
485    let dataset_item_count = RandomXDataset::count()?;
486    let dataset = RandomXDataset::new(flags, cache, dataset_item_count)?;
487
488    // Multithreaded dataset init using all available threads
489    let threads = thread::available_parallelism().map(|n| n.get()).unwrap_or(1);
490    debug!(target: "validator::pow::init_dataset", "[MINER] Initializing RandomX dataset using {threads} threads...");
491    let mut handles = Vec::with_capacity(threads);
492    let threads_u32 = threads as u32;
493    let per_thread = dataset_item_count / threads_u32;
494    let remainder = dataset_item_count % threads_u32;
495    for t in 0..threads_u32 {
496        // Check if stop signal is received
497        if stop_signal.is_full() {
498            debug!(target: "validator::pow::init_dataset", "[MINER] Stop signal received, threads creation loop exiting");
499            break
500        }
501
502        let dataset = dataset.clone();
503        let start_item = t * per_thread;
504        let count = per_thread + if t == threads_u32 - 1 { remainder } else { 0 };
505        handles.push(thread::spawn(move || {
506            dataset.init(start_item, count);
507        }));
508    }
509
510    // Wait for threads to finish setup
511    for handle in handles {
512        let _ = handle.join();
513    }
514
515    // Check if stop signal is received
516    if stop_signal.is_full() {
517        debug!(target: "validator::pow::init_dataset", "[MINER] Stop signal received, exiting");
518        return Err(Error::MinerTaskStopped);
519    }
520    Ok(dataset)
521}
522
523/// Auxiliary function to generate mining VMs for provided RandomX key.
524pub fn generate_mining_vms(
525    flags: RandomXFlags,
526    input: &HeaderHash,
527    threads: usize,
528    stop_signal: &Receiver<()>,
529) -> Result<Vec<Arc<RandomXVM>>> {
530    debug!(target: "validator::pow::generate_mining_vms", "[MINER] Initializing RandomX cache and dataset...");
531    debug!(target: "validator::pow::generate_mining_vms", "[MINER] PoW input: {input}");
532    let setup_start = Instant::now();
533    // Check if fast mode is enabled
534    let (cache, dataset) = if flags.contains(RandomXFlags::FULLMEM) {
535        // Initialize dataset
536        let dataset = init_dataset(flags, input, stop_signal)?;
537        (None, Some(dataset))
538    } else {
539        // Initialize cache for light mode
540        let cache = RandomXCache::new(flags, &input.inner()[..])?;
541        (Some(cache), None)
542    };
543    debug!(target: "validator::pow::generate_mining_vms", "[MINER] Initialized RandomX cache and dataset: {:?}", setup_start.elapsed());
544
545    // Single thread mining VM
546    if threads == 1 {
547        debug!(target: "validator::pow::generate_mining_vms", "[MINER] Initializing RandomX VM...");
548        let vm_start = Instant::now();
549        let vm = Arc::new(RandomXVM::new(flags, cache, dataset)?);
550        debug!(target: "validator::pow::generate_mining_vms", "[MINER] Initialized RandomX VM in {:?}", vm_start.elapsed());
551        debug!(target: "validator::pow::generate_mining_vms", "[MINER] Setup time: {:?}", setup_start.elapsed());
552        return Ok(vec![vm])
553    }
554
555    // Multi thread mining VMs
556    debug!(target: "validator::pow::generate_mining_vms", "[MINER] Initializing {threads} RandomX VMs...");
557    let mut vms = Vec::with_capacity(threads);
558    let threads_u32 = threads as u32;
559    for t in 0..threads_u32 {
560        // Check if stop signal is received
561        if stop_signal.is_full() {
562            debug!(target: "validator::pow::generate_mining_vms", "[MINER] Stop signal received, exiting");
563            return Err(Error::MinerTaskStopped);
564        }
565
566        debug!(target: "validator::pow::generate_mining_vms", "[MINER] Initializing RandomX VM #{t}...");
567        let vm_start = Instant::now();
568        vms.push(Arc::new(RandomXVM::new(flags, cache.clone(), dataset.clone())?));
569        debug!(target: "validator::pow::generate_mining_vms", "[MINER] Initialized RandomX VM #{t} in {:?}", vm_start.elapsed());
570    }
571    debug!(target: "validator::pow::generate_mining_vms", "[MINER] Setup time: {:?}", setup_start.elapsed());
572    Ok(vms)
573}
574
575/// Mine provided header, based on provided PoW module next mine
576/// target, using provided RandomX VMs setup.
577pub fn mine_block(
578    vms: &[Arc<RandomXVM>],
579    target: &BigUint,
580    header: &mut Header,
581    stop_signal: &Receiver<()>,
582) -> Result<()> {
583    debug!(target: "validator::pow::mine_block", "[MINER] Mine target: 0x{target:064x}");
584
585    // Check VMs were provided
586    if vms.is_empty() {
587        error!(target: "validator::pow::mine_block", "[MINER] No VMs were provided!");
588        return Err(Error::MinerTaskStopped)
589    }
590
591    debug!(target: "validator::pow::randomx_vms_mine", "[MINER] Initializing mining threads...");
592    let mut handles = Vec::with_capacity(vms.len());
593    let atomic_nonce = Arc::new(AtomicU32::new(0));
594    let found_header = Arc::new(AtomicBool::new(false));
595    let found_nonce = Arc::new(AtomicU32::new(0));
596    let threads = vms.len() as u32;
597    let mining_start = Instant::now();
598    for t in 0..threads {
599        // Check if stop signal is received
600        if stop_signal.is_full() {
601            debug!(target: "validator::pow::randomx_vms_mine", "[MINER] Stop signal received, threads creation loop exiting");
602            break
603        }
604
605        if found_header.load(Ordering::SeqCst) {
606            debug!(target: "validator::pow::randomx_vms_mine", "[MINER] Block header found, threads creation loop exiting");
607            break
608        }
609
610        let vm = vms[t as usize].clone();
611        let target = target.clone();
612        let mut thread_header = header.clone();
613        let atomic_nonce = atomic_nonce.clone();
614        let found_header = found_header.clone();
615        let found_nonce = found_nonce.clone();
616        let stop_signal = stop_signal.clone();
617
618        handles.push(thread::spawn(move || {
619            let mut last_nonce = atomic_nonce.fetch_add(1, Ordering::SeqCst);
620            thread_header.nonce = last_nonce;
621            if let Err(e) = vm.calculate_hash_first(thread_header.hash().inner()) {
622                error!(target: "validator::pow::randomx_vms_mine", "[MINER] Calculating hash in thread #{t} failed: {e}");
623                return
624            };
625            loop {
626                // Check if stop signal was received
627                if stop_signal.is_full() {
628                    debug!(target: "validator::pow::randomx_vms_mine", "[MINER] Stop signal received, thread #{t} exiting");
629                    break
630                }
631
632                if found_header.load(Ordering::SeqCst) {
633                    debug!(target: "validator::pow::randomx_vms_mine", "[MINER] Block header found, thread #{t} exiting");
634                    break;
635                }
636
637                thread_header.nonce = atomic_nonce.fetch_add(1, Ordering::SeqCst);
638                let out_hash = match vm.calculate_hash_next(thread_header.hash().inner()) {
639                    Ok(hash) => hash,
640                    Err(e) => {
641                        error!(target: "validator::pow::randomx_vms_mine", "[MINER] Calculating hash in thread #{t} failed: {e}");
642                        break
643                    }
644                };
645                let out_hash = BigUint::from_bytes_le(&out_hash);
646                if out_hash <= target {
647                    found_header.store(true, Ordering::SeqCst);
648                    // Since out hash refers to previous run nonce
649                    thread_header.nonce = last_nonce;
650                    found_nonce.store(thread_header.nonce, Ordering::SeqCst);
651                    debug!(target: "validator::pow::randomx_vms_mine", "[MINER] Thread #{t} found block header using nonce {}",
652                        thread_header.nonce
653                    );
654                    debug!(target: "validator::pow::randomx_vms_mine", "[MINER] Block header hash {}", thread_header.hash());
655                    debug!(target: "validator::pow::randomx_vms_mine", "[MINER] RandomX output: 0x{out_hash:064x}");
656                    break;
657                }
658                last_nonce = thread_header.nonce;
659            }
660        }));
661    }
662
663    // Wait for threads to finish mining
664    for handle in handles {
665        let _ = handle.join();
666    }
667
668    // Check if stop signal is received
669    if stop_signal.is_full() {
670        debug!(target: "validator::pow::randomx_vms_mine", "[MINER] Stop signal received, exiting");
671        return Err(Error::MinerTaskStopped);
672    }
673
674    debug!(target: "validator::pow::randomx_vms_mine", "[MINER] Completed mining in {:?}", mining_start.elapsed());
675    header.nonce = found_nonce.load(Ordering::SeqCst);
676    debug!(target: "validator::pow::randomx_vms_mine", "[MINER] Mined header: {header:?}");
677    Ok(())
678}
679
680#[cfg(test)]
681mod tests {
682    use std::{
683        io::{BufRead, Cursor},
684        process::Command,
685    };
686
687    use darkfi_sdk::num_traits::Num;
688    use num_bigint::BigUint;
689    use sled_overlay::sled;
690
691    use crate::{
692        blockchain::{header_store::Header, BlockInfo, Blockchain},
693        Result,
694    };
695
696    use super::PoWModule;
697
698    const DEFAULT_TEST_THREADS: usize = 2;
699    const DEFAULT_TEST_DIFFICULTY_TARGET: u32 = 120;
700
701    #[test]
702    fn test_wide_difficulty() -> Result<()> {
703        let sled_db = sled::Config::new().temporary(true).open()?;
704        let blockchain = Blockchain::new(&sled_db)?;
705        let genesis_block = BlockInfo::default();
706        blockchain.add_block(&genesis_block)?;
707
708        let mut module = PoWModule::new(blockchain, DEFAULT_TEST_DIFFICULTY_TARGET, None, None)?;
709
710        let output = Command::new("./script/monero_gen_wide_data.py").output().unwrap();
711        let reader = Cursor::new(output.stdout);
712
713        let mut previous = genesis_block.header;
714        for (n, line) in reader.lines().enumerate() {
715            let line = line.unwrap();
716            let parts: Vec<String> = line.split(' ').map(|x| x.to_string()).collect();
717            assert!(parts.len() == 2);
718
719            let header = Header::new(
720                previous.hash(),
721                previous.height + 1,
722                0,
723                parts[0].parse::<u64>().unwrap().into(),
724            );
725            let difficulty = BigUint::from_str_radix(&parts[1], 10).unwrap();
726
727            let res = module.next_difficulty()?;
728
729            if res != difficulty {
730                eprintln!("Wrong wide difficulty for block {n}");
731                eprintln!("Expected: {difficulty}");
732                eprintln!("Found: {res}");
733                assert!(res == difficulty);
734            }
735
736            module.append(&header, &difficulty)?;
737            previous = header;
738        }
739
740        Ok(())
741    }
742
743    #[test]
744    fn test_miner_correctness() -> Result<()> {
745        // Default setup
746        let sled_db = sled::Config::new().temporary(true).open()?;
747        let blockchain = Blockchain::new(&sled_db)?;
748        let mut genesis_block = BlockInfo::default();
749        genesis_block.header.timestamp = 0.into();
750        blockchain.add_block(&genesis_block)?;
751
752        let mut module = PoWModule::new(blockchain, DEFAULT_TEST_DIFFICULTY_TARGET, None, None)?;
753
754        let (_, recvr) = smol::channel::bounded(1);
755
756        // Mine next block
757        let mut next_block = BlockInfo::default();
758        next_block.header.previous = genesis_block.hash();
759        module.mine_block(&mut next_block.header, DEFAULT_TEST_THREADS, &recvr)?;
760
761        // Verify it
762        module.verify_current_block(&next_block.header)?;
763
764        Ok(())
765    }
766}