pow.rs 28 KB

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