1use std::{
20 sync::{
21 atomic::{AtomicBool, AtomicU64, Ordering},
22 Arc,
23 },
24 thread,
25 time::Instant,
26};
27
28use darkfi_sdk::num_traits::{One, Zero};
29use log::debug;
30use num_bigint::BigUint;
31use randomx::{RandomXCache, RandomXDataset, RandomXFlags, RandomXVM};
32use smol::channel::Receiver;
33
34use crate::{
35 blockchain::{
36 block_store::{BlockDifficulty, BlockInfo},
37 Blockchain, BlockchainOverlayPtr,
38 },
39 util::{ringbuffer::RingBuffer, time::Timestamp},
40 validator::utils::median,
41 Error, Result,
42};
43
44const DIFFICULTY_WINDOW: usize = 720;
48const _DIFFICULTY_LAG: usize = 15;
53const BUF_SIZE: usize = 735;
56const _DIFFICULTY_CUT: usize = 60;
62const RETAINED: usize = 600;
65const CUT_BEGIN: usize = 60;
67const CUT_END: usize = 660;
69const BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW: usize = 60;
71const BLOCK_FUTURE_TIME_LIMIT: Timestamp = Timestamp::from_u64(60 * 60 * 2);
73
74#[derive(Clone)]
76pub struct PoWModule {
77 pub genesis: Timestamp,
79 pub target: u32,
81 pub fixed_difficulty: Option<BigUint>,
83 pub timestamps: RingBuffer<Timestamp, BUF_SIZE>,
85 pub difficulties: RingBuffer<BigUint, BUF_SIZE>,
87 pub cumulative_difficulty: BigUint,
92}
93
94impl PoWModule {
95 pub fn new(
98 blockchain: Blockchain,
99 target: u32,
100 fixed_difficulty: Option<BigUint>,
101 height: Option<u32>,
102 ) -> Result<Self> {
103 let genesis = blockchain.genesis_block()?.header.timestamp;
105
106 let mut timestamps = RingBuffer::<Timestamp, BUF_SIZE>::new();
108 let mut difficulties = RingBuffer::<BigUint, BUF_SIZE>::new();
109 let mut cumulative_difficulty = BigUint::zero();
110 let last_n = match height {
111 Some(h) => blockchain.blocks.get_difficulties_before(h, BUF_SIZE)?,
112 None => blockchain.blocks.get_last_n_difficulties(BUF_SIZE)?,
113 };
114 for difficulty in last_n {
115 timestamps.push(difficulty.timestamp);
116 difficulties.push(difficulty.cumulative_difficulty.clone());
117 cumulative_difficulty = difficulty.cumulative_difficulty;
118 }
119
120 if let Some(diff) = &fixed_difficulty {
122 assert!(diff > &BigUint::zero());
123 }
124
125 Ok(Self {
126 genesis,
127 target,
128 fixed_difficulty,
129 timestamps,
130 difficulties,
131 cumulative_difficulty,
132 })
133 }
134
135 pub fn next_difficulty(&self) -> Result<BigUint> {
140 let mut timestamps: Vec<Timestamp> =
142 self.timestamps.iter().take(DIFFICULTY_WINDOW).cloned().collect();
143
144 let length = timestamps.len();
146 if length < 2 {
147 return Ok(BigUint::one())
148 }
149
150 if let Some(diff) = &self.fixed_difficulty {
152 return Ok(diff.clone())
153 }
154
155 timestamps.sort_unstable();
157
158 let (cut_begin, cut_end) = self.cutoff(length)?;
160
161 let cut_end = cut_end - 1;
163
164 let mut time_span = timestamps[cut_end].checked_sub(timestamps[cut_begin])?;
165 if time_span.inner() == 0 {
166 time_span = 1.into();
167 }
168
169 let total_work = &self.difficulties[cut_end] - &self.difficulties[cut_begin];
171 if total_work <= BigUint::zero() {
172 return Err(Error::PoWTotalWorkIsZero)
173 }
174
175 let next_difficulty =
177 (total_work * self.target + time_span.inner() - BigUint::one()) / time_span.inner();
178
179 Ok(next_difficulty)
180 }
181
182 fn cutoff(&self, length: usize) -> Result<(usize, usize)> {
186 if length >= DIFFICULTY_WINDOW {
187 return Ok((CUT_BEGIN, CUT_END))
188 }
189
190 let (cut_begin, cut_end) = if length <= RETAINED {
191 (0, length)
192 } else {
193 let cut_begin = (length - RETAINED).div_ceil(2);
194 (cut_begin, cut_begin + RETAINED)
195 };
196 if
198 cut_begin + 2 > cut_end || cut_end > length {
200 return Err(Error::PoWCuttofCalculationError)
201 }
202
203 Ok((cut_begin, cut_end))
204 }
205
206 pub fn next_mine_target(&self) -> Result<BigUint> {
208 Ok(BigUint::from_bytes_be(&[0xFF; 32]) / &self.next_difficulty()?)
209 }
210
211 pub fn next_mine_target_and_difficulty(&self) -> Result<(BigUint, BigUint)> {
213 let difficulty = self.next_difficulty()?;
214 let mine_target = BigUint::from_bytes_be(&[0xFF; 32]) / &difficulty;
215 Ok((mine_target, difficulty))
216 }
217
218 pub fn verify_difficulty(&self, difficulty: &BigUint) -> Result<bool> {
220 Ok(difficulty == &self.next_difficulty()?)
221 }
222
223 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
230 Ok(self.verify_timestamp_by_median(timestamp))
231 }
232
233 pub fn verify_timestamp_by_median(&self, timestamp: Timestamp) -> bool {
235 if timestamp <= self.genesis {
237 return false
238 }
239
240 if self.timestamps.len() < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW {
242 return true
243 }
244
245 let timestamps = self
247 .timestamps
248 .iter()
249 .rev()
250 .take(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW)
251 .map(|x| x.inner())
252 .collect();
253
254 timestamp >= median(timestamps).into()
255 }
256
257 pub fn verify_current_block(&self, block: &BlockInfo) -> Result<()> {
259 if !self.verify_current_timestamp(block.header.timestamp)? {
261 return Err(Error::PoWInvalidTimestamp)
262 }
263
264 self.verify_block_hash(block)
266 }
267
268 pub fn verify_block_hash(&self, block: &BlockInfo) -> Result<()> {
271 let verifier_setup = Instant::now();
272
273 let target = self.next_mine_target()?;
275
276 let flags = RandomXFlags::default();
278 let cache = RandomXCache::new(flags, block.header.previous.inner()).unwrap();
279 let vm = RandomXVM::new(flags, &cache).unwrap();
280 debug!(target: "validator::pow::verify_block", "[VERIFIER] Setup time: {:?}", verifier_setup.elapsed());
281
282 let verification_time = Instant::now();
284 let out_hash = vm.hash(block.header.hash().inner());
285 let out_hash = BigUint::from_bytes_be(&out_hash);
286
287 if out_hash > target {
289 return Err(Error::PoWInvalidOutHash)
290 }
291 debug!(target: "validator::pow::verify_block", "[VERIFIER] Verification time: {:?}", verification_time.elapsed());
292
293 Ok(())
294 }
295
296 pub fn append(&mut self, timestamp: Timestamp, difficulty: &BigUint) {
298 self.timestamps.push(timestamp);
299 self.cumulative_difficulty += difficulty;
300 self.difficulties.push(self.cumulative_difficulty.clone());
301 }
302
303 pub fn append_difficulty(
306 &mut self,
307 overlay: &BlockchainOverlayPtr,
308 difficulty: BlockDifficulty,
309 ) -> Result<()> {
310 self.append(difficulty.timestamp, &difficulty.difficulty);
311 overlay.lock().unwrap().blocks.insert_difficulty(&[difficulty])
312 }
313
314 pub fn mine_block(
316 &self,
317 miner_block: &mut BlockInfo,
318 threads: usize,
319 stop_signal: &Receiver<()>,
320 ) -> Result<()> {
321 let target = self.next_mine_target()?;
323
324 mine_block(&target, miner_block, threads, stop_signal)
325 }
326}
327
328impl std::fmt::Display for PoWModule {
329 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
330 write!(f, "PoWModule:")?;
331 write!(f, "\ttarget: {}", self.target)?;
332 write!(f, "\ttimestamps: {:?}", self.timestamps)?;
333 write!(f, "\tdifficulties: {:?}", self.difficulties)?;
334 write!(f, "\tcumulative_difficulty: {}", self.cumulative_difficulty)
335 }
336}
337
338pub fn mine_block(
340 target: &BigUint,
341 miner_block: &mut BlockInfo,
342 threads: usize,
343 stop_signal: &Receiver<()>,
344) -> Result<()> {
345 let miner_setup = Instant::now();
346
347 debug!(target: "validator::pow::mine_block", "[MINER] Mine target: 0x{target:064x}");
348 let input = miner_block.header.previous;
350 debug!(target: "validator::pow::mine_block", "[MINER] PoW input: {input}");
351 let flags = RandomXFlags::default() | RandomXFlags::FULLMEM;
352 debug!(target: "validator::pow::mine_block", "[MINER] Initializing RandomX dataset...");
353 let dataset = Arc::new(RandomXDataset::new(flags, input.inner(), threads).unwrap());
354 debug!(target: "validator::pow::mine_block", "[MINER] Setup time: {:?}", miner_setup.elapsed());
355
356 let mining_time = Instant::now();
358 let mut handles = vec![];
359 let found_block = Arc::new(AtomicBool::new(false));
360 let found_nonce = Arc::new(AtomicU64::new(0));
361 let threads = threads as u64;
362 for t in 0..threads {
363 let target = target.clone();
364 let mut block = miner_block.clone();
365 let found_block = Arc::clone(&found_block);
366 let found_nonce = Arc::clone(&found_nonce);
367 let dataset = Arc::clone(&dataset);
368 let stop_signal = stop_signal.clone();
369
370 handles.push(thread::spawn(move || {
371 debug!(target: "validator::pow::mine_block", "[MINER] Initializing RandomX VM #{t}...");
372 let mut miner_nonce = t;
373 let vm = RandomXVM::new_fast(flags, &dataset).unwrap();
374 loop {
375 if stop_signal.is_full() {
377 debug!(target: "validator::pow::mine_block", "[MINER] Stop signal received, thread #{t} exiting");
378 break
379 }
380
381 block.header.nonce = miner_nonce;
382 if found_block.load(Ordering::SeqCst) {
383 debug!(target: "validator::pow::mine_block", "[MINER] Block found, thread #{t} exiting");
384 break
385 }
386
387 let out_hash = vm.hash(block.hash().inner());
388 let out_hash = BigUint::from_bytes_be(&out_hash);
389 if out_hash <= target {
390 found_block.store(true, Ordering::SeqCst);
391 found_nonce.store(miner_nonce, Ordering::SeqCst);
392 debug!(target: "validator::pow::mine_block", "[MINER] Thread #{t} found block using nonce {miner_nonce}");
393 debug!(target: "validator::pow::mine_block", "[MINER] Block hash {}", block.hash());
394 debug!(target: "validator::pow::mine_block", "[MINER] RandomX output: 0x{out_hash:064x}");
395 break
396 }
397
398 miner_nonce += threads;
401 }
402 }));
403 }
404
405 for handle in handles {
406 let _ = handle.join();
407 }
408 if stop_signal.is_full() {
410 return Err(Error::MinerTaskStopped)
411 }
412
413 debug!(target: "validator::pow::mine_block", "[MINER] Mining time: {:?}", mining_time.elapsed());
414
415 miner_block.header.nonce = found_nonce.load(Ordering::SeqCst);
417
418 Ok(())
419}
420
421#[cfg(test)]
422mod tests {
423 use std::{
424 io::{BufRead, Cursor},
425 process::Command,
426 };
427
428 use darkfi_sdk::num_traits::Num;
429 use num_bigint::BigUint;
430 use sled_overlay::sled;
431
432 use crate::{
433 blockchain::{BlockInfo, Blockchain},
434 Result,
435 };
436
437 use super::PoWModule;
438
439 const DEFAULT_TEST_THREADS: usize = 2;
440 const DEFAULT_TEST_DIFFICULTY_TARGET: u32 = 120;
441
442 #[test]
443 fn test_wide_difficulty() -> Result<()> {
444 let sled_db = sled::Config::new().temporary(true).open()?;
445 let blockchain = Blockchain::new(&sled_db)?;
446 let genesis_block = BlockInfo::default();
447 blockchain.add_block(&genesis_block)?;
448 let mut module = PoWModule::new(blockchain, DEFAULT_TEST_DIFFICULTY_TARGET, None, None)?;
449
450 let output = Command::new("./script/research/pow/gen_wide_data.py").output().unwrap();
451 let reader = Cursor::new(output.stdout);
452
453 for (n, line) in reader.lines().enumerate() {
454 let line = line.unwrap();
455 let parts: Vec<String> = line.split(' ').map(|x| x.to_string()).collect();
456 assert!(parts.len() == 2);
457
458 let timestamp = parts[0].parse::<u64>().unwrap().into();
459 let difficulty = BigUint::from_str_radix(&parts[1], 10).unwrap();
460
461 let res = module.next_difficulty()?;
462
463 if res != difficulty {
464 eprintln!("Wrong wide difficulty for block {n}");
465 eprintln!("Expected: {difficulty}");
466 eprintln!("Found: {res}");
467 assert!(res == difficulty);
468 }
469
470 module.append(timestamp, &difficulty);
471 }
472
473 Ok(())
474 }
475
476 #[test]
477 fn test_miner_correctness() -> Result<()> {
478 let sled_db = sled::Config::new().temporary(true).open()?;
480 let blockchain = Blockchain::new(&sled_db)?;
481 let mut genesis_block = BlockInfo::default();
482 genesis_block.header.timestamp = 0.into();
483 blockchain.add_block(&genesis_block)?;
484 let module = PoWModule::new(blockchain, DEFAULT_TEST_DIFFICULTY_TARGET, None, None)?;
485 let (_, recvr) = smol::channel::bounded(1);
486
487 let mut next_block = BlockInfo::default();
489 next_block.header.previous = genesis_block.hash();
490 module.mine_block(&mut next_block, DEFAULT_TEST_THREADS, &recvr)?;
491
492 module.verify_current_block(&next_block)?;
494
495 Ok(())
496 }
497}