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    /// Verify provided block hash is less than provided mine target.
305    pub fn verify_block_target(&mut self, header: &Header, target: &BigUint) -> Result<BigUint> {
306        let verifier_setup = Instant::now();
307
308        // Grab verifier output hash based on block PoW data
309        let (out_hash, verification_time) = match &header.pow_data {
310            DarkFi => {
311                // Check which VM key should be used.
312                // We only use the next key when the next block is the
313                // height changing one.
314                let randomx_key = if header.height > RANDOMX_KEY_CHANGING_HEIGHT &&
315                    header.height % RANDOMX_KEY_CHANGING_HEIGHT == RANDOMX_KEY_CHANGE_DELAY
316                {
317                    &self
318                        .darkfi_rx_keys
319                        .1
320                        .ok_or_else(|| Error::ParseFailed("darkfi_rx_keys.1 unwrap() error"))?
321                } else {
322                    &self.darkfi_rx_keys.0
323                };
324
325                let vm = self.darkfi_rx_factory.create(&randomx_key.inner()[..])?;
326
327                debug!(
328                    target: "validator::pow::verify_block_target",
329                    "[VERIFIER] DarkFi PoW setup time: {:?}",
330                    verifier_setup.elapsed(),
331                );
332
333                let verification_time = Instant::now();
334                let out_hash = vm.calculate_hash(&header.to_block_hashing_blob())?;
335                (BigUint::from_bytes_le(&out_hash), verification_time)
336            }
337            Monero(powdata) => {
338                let vm = self.monero_rx_factory.create(powdata.randomx_key())?;
339
340                debug!(
341                    target: "validator::pow::verify_block_target",
342                    "[VERIFIER] Monero PoW setup time: {:?}",
343                    verifier_setup.elapsed(),
344                );
345
346                let verification_time = Instant::now();
347                let out_hash = vm.calculate_hash(&powdata.to_block_hashing_blob())?;
348                (BigUint::from_bytes_le(&out_hash), verification_time)
349            }
350        };
351        debug!(target: "validator::pow::verify_block_target", "[VERIFIER] Verification time: {:?}", verification_time.elapsed());
352
353        // Verify hash is less than the provided mine target
354        if out_hash > *target {
355            return Err(Error::PoWInvalidOutHash)
356        }
357
358        Ok(out_hash)
359    }
360
361    /// Verify provided block corresponds to next mine target.
362    pub fn verify_block_hash(&mut self, header: &Header) -> Result<()> {
363        // Grab the next mine target
364        let target = self.next_mine_target()?;
365
366        // Verify hash is less than the expected mine target
367        let _ = self.verify_block_target(header, &target)?;
368        Ok(())
369    }
370
371    /// Append provided header timestamp and difficulty to the ring
372    /// buffers, and check if we need to rotate and/or create the next
373    /// key RandomX VM in the native PoW factory.
374    pub fn append(&mut self, header: &Header, difficulty: &BigUint) -> Result<()> {
375        self.timestamps.push(header.timestamp);
376        self.cumulative_difficulty += difficulty;
377        self.difficulties.push(self.cumulative_difficulty.clone());
378
379        if header.height < RANDOMX_KEY_CHANGING_HEIGHT {
380            return Ok(())
381        }
382
383        // Check if need to set the new key
384        if header.height.is_multiple_of(RANDOMX_KEY_CHANGING_HEIGHT) {
385            let next_key = header.hash();
386            let _ = self.darkfi_rx_factory.create(&next_key.inner()[..])?;
387            self.darkfi_rx_keys.1 = Some(next_key);
388            return Ok(())
389        }
390
391        // Check if need to rotate keys
392        if header.height % RANDOMX_KEY_CHANGING_HEIGHT == RANDOMX_KEY_CHANGE_DELAY {
393            self.darkfi_rx_keys.0 = self
394                .darkfi_rx_keys
395                .1
396                .ok_or_else(|| Error::ParseFailed("darkfi_rx_keys.1 unwrap() error"))?;
397            self.darkfi_rx_keys.1 = None;
398        }
399
400        Ok(())
401    }
402
403    /// Append provided block difficulty to the ring buffers and insert
404    /// it to provided overlay.
405    pub fn append_difficulty(
406        &mut self,
407        overlay: &BlockchainOverlayPtr,
408        header: &Header,
409        difficulty: BlockDifficulty,
410    ) -> Result<()> {
411        self.append(header, &difficulty.difficulty)?;
412        overlay.lock().unwrap().blocks.insert_difficulty(&[difficulty])
413    }
414
415    /// Mine provided block, based on next mine target.
416    /// Note: this is used in tests not in actual mining.
417    pub fn mine_block(
418        &self,
419        header: &mut Header,
420        threads: usize,
421        stop_signal: &Receiver<()>,
422    ) -> Result<()> {
423        // Grab the RandomX key to use.
424        // We only use the next key when the next block is the
425        // height changing one.
426        let randomx_key = if header.height > RANDOMX_KEY_CHANGING_HEIGHT &&
427            header.height % RANDOMX_KEY_CHANGING_HEIGHT == RANDOMX_KEY_CHANGE_DELAY
428        {
429            &self
430                .darkfi_rx_keys
431                .1
432                .ok_or_else(|| Error::ParseFailed("darkfi_rx_keys.1 unwrap() error"))?
433        } else {
434            &self.darkfi_rx_keys.0
435        };
436
437        // Generate the RandomX VMs for the key
438        let flags = get_mining_flags(false, false, false);
439        let vms = generate_mining_vms(flags, randomx_key, threads, stop_signal)?;
440
441        // Grab the next mine target
442        let target = self.next_mine_target()?;
443
444        // Mine the block
445        mine_block(&vms, &target, header, stop_signal)
446    }
447}
448
449impl std::fmt::Display for PoWModule {
450    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
451        write!(f, "PoWModule:")?;
452        write!(f, "\ttarget: {}", self.target)?;
453        write!(f, "\ttimestamps: {:?}", self.timestamps)?;
454        write!(f, "\tdifficulties: {:?}", self.difficulties)?;
455        write!(f, "\tcumulative_difficulty: {}", self.cumulative_difficulty)
456    }
457}
458
459/// Auxiliary function to define `RandomXFlags` used in mining.
460///
461/// Note: RandomX recommended flags will include `SSSE3` and `AVX2`
462/// extensions if CPU supports them.
463pub fn get_mining_flags(fast_mode: bool, large_pages: bool, secure: bool) -> RandomXFlags {
464    let mut flags = RandomXFlags::get_recommended_flags();
465    if fast_mode {
466        flags |= RandomXFlags::FULLMEM;
467    }
468    if large_pages {
469        flags |= RandomXFlags::LARGEPAGES;
470    }
471    if secure && flags.contains(RandomXFlags::JIT) {
472        flags |= RandomXFlags::SECURE;
473    }
474    flags
475}
476
477/// Auxiliary function to initialize a `RandomXDataset` using all
478/// available threads.
479fn init_dataset(
480    flags: RandomXFlags,
481    input: &HeaderHash,
482    stop_signal: &Receiver<()>,
483) -> Result<RandomXDataset> {
484    // Allocate cache and dataset
485    let cache = RandomXCache::new(flags, &input.inner()[..])?;
486    let dataset_item_count = RandomXDataset::count()?;
487    let dataset = RandomXDataset::new(flags, cache, dataset_item_count)?;
488
489    // Multithreaded dataset init using all available threads
490    let threads = thread::available_parallelism().map(|n| n.get()).unwrap_or(1);
491    debug!(target: "validator::pow::init_dataset", "[MINER] Initializing RandomX dataset using {threads} threads...");
492    let mut handles = Vec::with_capacity(threads);
493    let threads_u32 = threads as u32;
494    let per_thread = dataset_item_count / threads_u32;
495    let remainder = dataset_item_count % threads_u32;
496    for t in 0..threads_u32 {
497        // Check if stop signal is received
498        if stop_signal.is_full() {
499            debug!(target: "validator::pow::init_dataset", "[MINER] Stop signal received, threads creation loop exiting");
500            break
501        }
502
503        let dataset = dataset.clone();
504        let start_item = t * per_thread;
505        let count = per_thread + if t == threads_u32 - 1 { remainder } else { 0 };
506        handles.push(thread::spawn(move || {
507            dataset.init(start_item, count);
508        }));
509    }
510
511    // Wait for threads to finish setup
512    for handle in handles {
513        let _ = handle.join();
514    }
515
516    // Check if stop signal is received
517    if stop_signal.is_full() {
518        debug!(target: "validator::pow::init_dataset", "[MINER] Stop signal received, exiting");
519        return Err(Error::MinerTaskStopped);
520    }
521    Ok(dataset)
522}
523
524/// Auxiliary function to generate mining VMs for provided RandomX key.
525pub fn generate_mining_vms(
526    flags: RandomXFlags,
527    input: &HeaderHash,
528    threads: usize,
529    stop_signal: &Receiver<()>,
530) -> Result<Vec<Arc<RandomXVM>>> {
531    debug!(target: "validator::pow::generate_mining_vms", "[MINER] Initializing RandomX cache and dataset...");
532    debug!(target: "validator::pow::generate_mining_vms", "[MINER] PoW input: {input}");
533    let setup_start = Instant::now();
534    // Check if fast mode is enabled
535    let (cache, dataset) = if flags.contains(RandomXFlags::FULLMEM) {
536        // Initialize dataset
537        let dataset = init_dataset(flags, input, stop_signal)?;
538        (None, Some(dataset))
539    } else {
540        // Initialize cache for light mode
541        let cache = RandomXCache::new(flags, &input.inner()[..])?;
542        (Some(cache), None)
543    };
544    debug!(target: "validator::pow::generate_mining_vms", "[MINER] Initialized RandomX cache and dataset: {:?}", setup_start.elapsed());
545
546    // Single thread mining VM
547    if threads == 1 {
548        debug!(target: "validator::pow::generate_mining_vms", "[MINER] Initializing RandomX VM...");
549        let vm_start = Instant::now();
550        let vm = Arc::new(RandomXVM::new(flags, cache, dataset)?);
551        debug!(target: "validator::pow::generate_mining_vms", "[MINER] Initialized RandomX VM in {:?}", vm_start.elapsed());
552        debug!(target: "validator::pow::generate_mining_vms", "[MINER] Setup time: {:?}", setup_start.elapsed());
553        return Ok(vec![vm])
554    }
555
556    // Multi thread mining VMs
557    debug!(target: "validator::pow::generate_mining_vms", "[MINER] Initializing {threads} RandomX VMs...");
558    let mut vms = Vec::with_capacity(threads);
559    let threads_u32 = threads as u32;
560    for t in 0..threads_u32 {
561        // Check if stop signal is received
562        if stop_signal.is_full() {
563            debug!(target: "validator::pow::generate_mining_vms", "[MINER] Stop signal received, exiting");
564            return Err(Error::MinerTaskStopped);
565        }
566
567        debug!(target: "validator::pow::generate_mining_vms", "[MINER] Initializing RandomX VM #{t}...");
568        let vm_start = Instant::now();
569        vms.push(Arc::new(RandomXVM::new(flags, cache.clone(), dataset.clone())?));
570        debug!(target: "validator::pow::generate_mining_vms", "[MINER] Initialized RandomX VM #{t} in {:?}", vm_start.elapsed());
571    }
572    debug!(target: "validator::pow::generate_mining_vms", "[MINER] Setup time: {:?}", setup_start.elapsed());
573    Ok(vms)
574}
575
576/// Mine provided header, based on provided PoW module next mine
577/// target, using provided RandomX VMs setup.
578pub fn mine_block(
579    vms: &[Arc<RandomXVM>],
580    target: &BigUint,
581    header: &mut Header,
582    stop_signal: &Receiver<()>,
583) -> Result<()> {
584    debug!(target: "validator::pow::mine_block", "[MINER] Mine target: 0x{target:064x}");
585
586    // Check VMs were provided
587    if vms.is_empty() {
588        error!(target: "validator::pow::mine_block", "[MINER] No VMs were provided!");
589        return Err(Error::MinerTaskStopped)
590    }
591
592    debug!(target: "validator::pow::randomx_vms_mine", "[MINER] Initializing mining threads...");
593    let mut handles = Vec::with_capacity(vms.len());
594    let atomic_nonce = Arc::new(AtomicU32::new(0));
595    let found_header = Arc::new(AtomicBool::new(false));
596    let found_nonce = Arc::new(AtomicU32::new(0));
597    let threads = vms.len() as u32;
598    let mining_start = Instant::now();
599    for t in 0..threads {
600        // Check if stop signal is received
601        if stop_signal.is_full() {
602            debug!(target: "validator::pow::randomx_vms_mine", "[MINER] Stop signal received, threads creation loop exiting");
603            break
604        }
605
606        if found_header.load(Ordering::SeqCst) {
607            debug!(target: "validator::pow::randomx_vms_mine", "[MINER] Block header found, threads creation loop exiting");
608            break
609        }
610
611        let vm = vms[t as usize].clone();
612        let target = target.clone();
613        let mut thread_header = header.clone();
614        let atomic_nonce = atomic_nonce.clone();
615        let found_header = found_header.clone();
616        let found_nonce = found_nonce.clone();
617        let stop_signal = stop_signal.clone();
618
619        handles.push(thread::spawn(move || {
620            let mut last_nonce = atomic_nonce.fetch_add(1, Ordering::SeqCst);
621            thread_header.nonce = last_nonce;
622            if let Err(e) = vm.calculate_hash_first(thread_header.hash().inner()) {
623                error!(target: "validator::pow::randomx_vms_mine", "[MINER] Calculating hash in thread #{t} failed: {e}");
624                return
625            };
626            loop {
627                // Check if stop signal was received
628                if stop_signal.is_full() {
629                    debug!(target: "validator::pow::randomx_vms_mine", "[MINER] Stop signal received, thread #{t} exiting");
630                    break
631                }
632
633                if found_header.load(Ordering::SeqCst) {
634                    debug!(target: "validator::pow::randomx_vms_mine", "[MINER] Block header found, thread #{t} exiting");
635                    break;
636                }
637
638                thread_header.nonce = atomic_nonce.fetch_add(1, Ordering::SeqCst);
639                let out_hash = match vm.calculate_hash_next(thread_header.hash().inner()) {
640                    Ok(hash) => hash,
641                    Err(e) => {
642                        error!(target: "validator::pow::randomx_vms_mine", "[MINER] Calculating hash in thread #{t} failed: {e}");
643                        break
644                    }
645                };
646                let out_hash = BigUint::from_bytes_le(&out_hash);
647                if out_hash <= target {
648                    found_header.store(true, Ordering::SeqCst);
649                    // Since out hash refers to previous run nonce
650                    thread_header.nonce = last_nonce;
651                    found_nonce.store(thread_header.nonce, Ordering::SeqCst);
652                    debug!(target: "validator::pow::randomx_vms_mine", "[MINER] Thread #{t} found block header using nonce {}",
653                        thread_header.nonce
654                    );
655                    debug!(target: "validator::pow::randomx_vms_mine", "[MINER] Block header hash {}", thread_header.hash());
656                    debug!(target: "validator::pow::randomx_vms_mine", "[MINER] RandomX output: 0x{out_hash:064x}");
657                    break;
658                }
659                last_nonce = thread_header.nonce;
660            }
661        }));
662    }
663
664    // Wait for threads to finish mining
665    for handle in handles {
666        let _ = handle.join();
667    }
668
669    // Check if stop signal is received
670    if stop_signal.is_full() {
671        debug!(target: "validator::pow::randomx_vms_mine", "[MINER] Stop signal received, exiting");
672        return Err(Error::MinerTaskStopped);
673    }
674
675    debug!(target: "validator::pow::randomx_vms_mine", "[MINER] Completed mining in {:?}", mining_start.elapsed());
676    header.nonce = found_nonce.load(Ordering::SeqCst);
677    debug!(target: "validator::pow::randomx_vms_mine", "[MINER] Mined header: {header:?}");
678    Ok(())
679}
680
681#[cfg(test)]
682mod tests {
683    use std::{
684        io::{BufRead, Cursor},
685        process::Command,
686    };
687
688    use darkfi_sdk::num_traits::Num;
689    use num_bigint::BigUint;
690    use sled_overlay::sled;
691
692    use crate::{
693        blockchain::{header_store::Header, BlockInfo, Blockchain},
694        Result,
695    };
696
697    use super::PoWModule;
698
699    const DEFAULT_TEST_THREADS: usize = 2;
700    const DEFAULT_TEST_DIFFICULTY_TARGET: u32 = 120;
701
702    #[test]
703    fn test_wide_difficulty() -> Result<()> {
704        let sled_db = sled::Config::new().temporary(true).open()?;
705        let blockchain = Blockchain::new(&sled_db)?;
706        let genesis_block = BlockInfo::default();
707        blockchain.add_block(&genesis_block)?;
708
709        let mut module = PoWModule::new(blockchain, DEFAULT_TEST_DIFFICULTY_TARGET, None, None)?;
710
711        let output = Command::new("./script/monero_gen_wide_data.py").output().unwrap();
712        let reader = Cursor::new(output.stdout);
713
714        let mut previous = genesis_block.header;
715        for (n, line) in reader.lines().enumerate() {
716            let line = line.unwrap();
717            let parts: Vec<String> = line.split(' ').map(|x| x.to_string()).collect();
718            assert!(parts.len() == 2);
719
720            let header = Header::new(
721                previous.hash(),
722                previous.height + 1,
723                0,
724                parts[0].parse::<u64>().unwrap().into(),
725            );
726            let difficulty = BigUint::from_str_radix(&parts[1], 10).unwrap();
727
728            let res = module.next_difficulty()?;
729
730            if res != difficulty {
731                eprintln!("Wrong wide difficulty for block {n}");
732                eprintln!("Expected: {difficulty}");
733                eprintln!("Found: {res}");
734                assert!(res == difficulty);
735            }
736
737            module.append(&header, &difficulty)?;
738            previous = header;
739        }
740
741        Ok(())
742    }
743
744    #[test]
745    fn test_miner_correctness() -> Result<()> {
746        // Default setup
747        let sled_db = sled::Config::new().temporary(true).open()?;
748        let blockchain = Blockchain::new(&sled_db)?;
749        let mut genesis_block = BlockInfo::default();
750        genesis_block.header.timestamp = 0.into();
751        blockchain.add_block(&genesis_block)?;
752
753        let mut module = PoWModule::new(blockchain, DEFAULT_TEST_DIFFICULTY_TARGET, None, None)?;
754
755        let (_, recvr) = smol::channel::bounded(1);
756
757        // Mine next block
758        let mut next_block = BlockInfo::default();
759        next_block.header.previous = genesis_block.hash();
760        module.mine_block(&mut next_block.header, DEFAULT_TEST_THREADS, &recvr)?;
761
762        // Verify it
763        module.verify_current_block(&next_block.header)?;
764
765        Ok(())
766    }
767}