1use 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
51const DIFFICULTY_WINDOW: usize = 720;
55const _DIFFICULTY_LAG: usize = 15;
60const BUF_SIZE: usize = 735;
63const _DIFFICULTY_CUT: usize = 60;
69const RETAINED: usize = 600;
72const CUT_BEGIN: usize = 60;
74const CUT_END: usize = 660;
76const BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW: usize = 60;
78const BLOCK_FUTURE_TIME_LIMIT: Timestamp = Timestamp::from_u64(60 * 60 * 2);
80pub const RANDOMX_KEY_CHANGING_HEIGHT: u32 = 2048;
82pub const RANDOMX_KEY_CHANGE_DELAY: u32 = 64;
84
85#[derive(Clone)]
88pub struct PoWModule {
89 pub genesis: Timestamp,
91 pub target: u32,
93 pub fixed_difficulty: Option<BigUint>,
95 pub timestamps: RingBuffer<Timestamp, BUF_SIZE>,
97 pub difficulties: RingBuffer<BigUint, BUF_SIZE>,
99 pub cumulative_difficulty: BigUint,
104 pub darkfi_rx_keys: (HeaderHash, Option<HeaderHash>),
106 pub darkfi_rx_factory: RandomXFactory,
108 pub monero_rx_factory: RandomXFactory,
110}
111
112impl PoWModule {
113 pub fn new(
117 blockchain: Blockchain,
118 target: u32,
119 fixed_difficulty: Option<BigUint>,
120 height: Option<u32>,
121 ) -> Result<Self> {
122 let genesis = blockchain.genesis_block()?.header.timestamp;
124
125 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 let Some(diff) = &fixed_difficulty {
143 assert!(diff > &BigUint::zero());
144 }
145
146 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 pub fn next_difficulty(&self) -> Result<BigUint> {
174 let mut timestamps: Vec<Timestamp> =
177 self.timestamps.iter().take(DIFFICULTY_WINDOW).cloned().collect();
178
179 let length = timestamps.len();
181 if length < 2 {
182 return Ok(BigUint::one())
183 }
184
185 if let Some(diff) = &self.fixed_difficulty {
187 return Ok(diff.clone())
188 }
189
190 timestamps.sort_unstable();
192
193 let (cut_begin, cut_end) = self.cutoff(length)?;
195
196 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 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 let next_difficulty =
212 (total_work * self.target + time_span.inner() - BigUint::one()) / time_span.inner();
213
214 Ok(next_difficulty)
215 }
216
217 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 if
233 cut_begin + 2 > cut_end || cut_end > length {
235 return Err(Error::PoWCuttofCalculationError)
236 }
237
238 Ok((cut_begin, cut_end))
239 }
240
241 pub fn next_mine_target(&self) -> Result<BigUint> {
243 Ok(&*MAX_32_BYTES / &self.next_difficulty()?)
244 }
245
246 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 pub fn verify_difficulty(&self, difficulty: &BigUint) -> Result<bool> {
255 Ok(difficulty == &self.next_difficulty()?)
256 }
257
258 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 pub fn verify_timestamp_by_median(&self, timestamp: Timestamp) -> bool {
271 if timestamp <= self.genesis {
273 return false
274 }
275
276 if self.timestamps.len() < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW {
278 return true
279 }
280
281 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 pub fn verify_current_block(&mut self, header: &Header) -> Result<()> {
295 if !self.verify_current_timestamp(header.timestamp)? {
297 return Err(Error::PoWInvalidTimestamp)
298 }
299
300 self.verify_block_hash(header)
302 }
303
304 pub fn verify_block_target(&mut self, header: &Header, target: &BigUint) -> Result<BigUint> {
306 let verifier_setup = Instant::now();
307
308 let (out_hash, verification_time) = match &header.pow_data {
310 DarkFi => {
311 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 if out_hash > *target {
355 return Err(Error::PoWInvalidOutHash)
356 }
357
358 Ok(out_hash)
359 }
360
361 pub fn verify_block_hash(&mut self, header: &Header) -> Result<()> {
363 let target = self.next_mine_target()?;
365
366 let _ = self.verify_block_target(header, &target)?;
368 Ok(())
369 }
370
371 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 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 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 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 pub fn mine_block(
418 &self,
419 header: &mut Header,
420 threads: usize,
421 stop_signal: &Receiver<()>,
422 ) -> Result<()> {
423 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 let flags = get_mining_flags(false, false, false);
439 let vms = generate_mining_vms(flags, randomx_key, threads, stop_signal)?;
440
441 let target = self.next_mine_target()?;
443
444 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
459pub 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
477fn init_dataset(
480 flags: RandomXFlags,
481 input: &HeaderHash,
482 stop_signal: &Receiver<()>,
483) -> Result<RandomXDataset> {
484 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 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 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 for handle in handles {
513 let _ = handle.join();
514 }
515
516 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
524pub 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 let (cache, dataset) = if flags.contains(RandomXFlags::FULLMEM) {
536 let dataset = init_dataset(flags, input, stop_signal)?;
538 (None, Some(dataset))
539 } else {
540 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 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 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 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
576pub 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 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 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 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 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 for handle in handles {
666 let _ = handle.join();
667 }
668
669 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 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 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 module.verify_current_block(&next_block.header)?;
764
765 Ok(())
766 }
767}