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