1#![forbid(unsafe_code)]
17#![allow(clippy::too_many_arguments)]
18#![warn(clippy::cast_possible_truncation)]
19
20extern crate snarkvm_console as console;
21
22mod partial_solution;
23pub use partial_solution::*;
24
25mod solution;
26pub use solution::*;
27
28mod solution_id;
29pub use solution_id::*;
30
31mod solutions;
32pub use solutions::*;
33
34use console::{
35 account::Address,
36 algorithms::Sha3_256,
37 collections::kary_merkle_tree::KaryMerkleTree,
38 prelude::{
39 FromBits,
40 Network,
41 Result,
42 anyhow,
43 bail,
44 cfg_into_iter,
45 cfg_iter,
46 cfg_keys,
47 cfg_values,
48 ensure,
49 has_duplicates,
50 },
51 types::U64,
52};
53
54use aleo_std::prelude::*;
55use core::num::NonZeroUsize;
56use indexmap::IndexMap;
57#[cfg(feature = "locktick")]
58use locktick::parking_lot::RwLock;
59use lru::LruCache;
60#[cfg(not(feature = "locktick"))]
61use parking_lot::RwLock;
62use rand::SeedableRng;
63use rand_chacha::ChaChaRng;
64use std::sync::Arc;
65
66#[cfg(not(feature = "serial"))]
67use rayon::prelude::*;
68
69const ARITY: u8 = 8;
71const CACHE_SIZE: usize = 1 << 10;
73
74type MerkleTree = KaryMerkleTree<Sha3_256, Sha3_256, 9, { ARITY }>;
76
77pub trait PuzzleTrait<N: Network>: Send + Sync {
79 fn new() -> Self
81 where
82 Self: Sized;
83
84 fn to_leaves(&self, epoch_hash: N::BlockHash, rng: &mut ChaChaRng) -> Result<Vec<Vec<bool>>>;
86
87 fn to_all_leaves(&self, epoch_hash: N::BlockHash, rngs: Vec<ChaChaRng>) -> Result<Vec<Vec<Vec<bool>>>>;
89}
90
91#[derive(Clone)]
92pub struct Puzzle<N: Network> {
93 inner: Arc<dyn PuzzleTrait<N>>,
95 proof_target_cache: Arc<RwLock<LruCache<SolutionID<N>, u64>>>,
97}
98
99impl<N: Network> Puzzle<N> {
100 pub fn new<P: PuzzleTrait<N> + 'static>() -> Self {
102 Self {
103 inner: Arc::new(P::new()),
104 proof_target_cache: Arc::new(RwLock::new(LruCache::new(NonZeroUsize::new(CACHE_SIZE).unwrap()))),
105 }
106 }
107
108 pub fn get_leaves(&self, solution: &PartialSolution<N>) -> Result<Vec<Vec<bool>>> {
110 let mut rng = ChaChaRng::seed_from_u64(*solution.id());
112 self.inner.to_leaves(solution.epoch_hash(), &mut rng)
114 }
115
116 pub fn get_all_leaves(&self, solutions: &PuzzleSolutions<N>) -> Result<Vec<Vec<Vec<bool>>>> {
118 ensure!(
120 cfg_values!(solutions).all(|solution| solution.epoch_hash() == solutions[0].epoch_hash()),
121 "The solutions are for different epochs"
122 );
123 let rngs = cfg_keys!(solutions).map(|solution_id| ChaChaRng::seed_from_u64(**solution_id)).collect::<Vec<_>>();
125 self.inner.to_all_leaves(solutions[0].epoch_hash(), rngs)
127 }
128
129 pub fn get_proof_target(&self, solution: &Solution<N>) -> Result<u64> {
131 let proof_target = self.get_proof_target_unchecked(solution)?;
133 ensure!(solution.target() == proof_target, "The proof target does not match the expected proof target");
135 Ok(proof_target)
137 }
138
139 pub fn get_proof_target_unchecked(&self, solution: &Solution<N>) -> Result<u64> {
143 self.get_proof_target_from_partial_solution(solution.partial_solution())
145 }
146
147 pub fn get_proof_target_from_partial_solution(&self, partial_solution: &PartialSolution<N>) -> Result<u64> {
149 if let Some(proof_target) = self.proof_target_cache.write().get(&partial_solution.id()) {
151 return Ok(*proof_target);
152 }
153
154 let leaves = self.get_leaves(partial_solution)?;
156 let proof_target = Self::leaves_to_proof_target(&leaves)?;
158
159 self.proof_target_cache.write().put(partial_solution.id(), proof_target);
161 Ok(proof_target)
163 }
164
165 pub fn get_proof_targets(&self, solutions: &PuzzleSolutions<N>) -> Result<Vec<u64>> {
167 let mut targets = vec![0u64; solutions.len()];
169
170 let mut to_compute = Vec::new();
172 for (i, (id, solution)) in solutions.iter().enumerate() {
174 match self.proof_target_cache.write().get(id) {
176 Some(proof_target) => {
178 ensure!(
180 solution.target() == *proof_target,
181 "The proof target does not match the cached proof target"
182 );
183 targets[i] = *proof_target
184 }
185 None => to_compute.push((i, id, *solution)),
187 }
188 }
189
190 if !to_compute.is_empty() {
191 let solutions_subset = PuzzleSolutions::new(to_compute.iter().map(|(_, _, solution)| *solution).collect())?;
193 let leaves = self.get_all_leaves(&solutions_subset)?;
195 let targets_subset = cfg_iter!(leaves)
197 .zip(cfg_iter!(solutions_subset))
198 .map(|(leaves, (solution_id, solution))| {
199 let proof_target = Self::leaves_to_proof_target(leaves)?;
201 ensure!(
203 solution.target() == proof_target,
204 "The proof target does not match the computed proof target"
205 );
206 self.proof_target_cache.write().put(*solution_id, proof_target);
208 Ok((solution_id, proof_target))
210 })
211 .collect::<Result<IndexMap<_, _>>>()?;
212
213 for (i, id, _) in &to_compute {
215 targets[*i] = targets_subset[id];
216 }
217 }
218
219 Ok(targets)
221 }
222
223 pub fn get_combined_proof_target(&self, solutions: &PuzzleSolutions<N>) -> Result<u128> {
225 self.get_proof_targets(solutions)?.into_iter().try_fold(0u128, |combined, proof_target| {
226 combined.checked_add(proof_target as u128).ok_or_else(|| anyhow!("Combined proof target overflowed"))
227 })
228 }
229
230 pub fn prove(
232 &self,
233 epoch_hash: N::BlockHash,
234 address: Address<N>,
235 counter: u64,
236 minimum_proof_target: Option<u64>,
237 ) -> Result<Solution<N>> {
238 let partial_solution = PartialSolution::new(epoch_hash, address, counter)?;
240 let proof_target = self.get_proof_target_from_partial_solution(&partial_solution)?;
242 if let Some(minimum_proof_target) = minimum_proof_target {
244 if proof_target < minimum_proof_target {
245 bail!("Solution was below the minimum proof target ({proof_target} < {minimum_proof_target})")
246 }
247 }
248
249 Ok(Solution::new(partial_solution, proof_target))
251 }
252
253 pub fn check_solution(
255 &self,
256 solution: &Solution<N>,
257 expected_epoch_hash: N::BlockHash,
258 expected_proof_target: u64,
259 ) -> Result<()> {
260 if solution.epoch_hash() != expected_epoch_hash {
262 bail!(
263 "Solution does not match the expected epoch hash (found '{}', expected '{expected_epoch_hash}')",
264 solution.epoch_hash()
265 )
266 }
267 let proof_target = self.get_proof_target(solution)?;
269 if proof_target < expected_proof_target {
270 bail!("Solution does not meet the proof target requirement ({proof_target} < {expected_proof_target})")
271 }
272 Ok(())
273 }
274
275 pub fn check_solution_mut(
278 &self,
279 solution: &mut Solution<N>,
280 expected_epoch_hash: N::BlockHash,
281 expected_proof_target: u64,
282 ) -> Result<()> {
283 if solution.epoch_hash() != expected_epoch_hash {
285 bail!(
286 "Solution does not match the expected epoch hash (found '{}', expected '{expected_epoch_hash}')",
287 solution.epoch_hash()
288 )
289 }
290 let proof_target = self.get_proof_target_unchecked(solution)?;
292
293 solution.target = proof_target;
295
296 if proof_target < expected_proof_target {
298 bail!("Solution does not meet the proof target requirement ({proof_target} < {expected_proof_target})")
299 }
300 Ok(())
301 }
302
303 pub fn check_solutions(
305 &self,
306 solutions: &PuzzleSolutions<N>,
307 expected_epoch_hash: N::BlockHash,
308 expected_proof_target: u64,
309 ) -> Result<()> {
310 let timer = timer!("Puzzle::verify");
311
312 ensure!(!solutions.is_empty(), "The solutions are empty");
314 if solutions.len() > N::MAX_SOLUTIONS {
316 bail!("Exceed the maximum number of solutions ({} > {})", solutions.len(), N::MAX_SOLUTIONS)
317 }
318 if has_duplicates(solutions.solution_ids()) {
320 bail!("The solutions contain duplicate solution IDs");
321 }
322 lap!(timer, "Perform initial checks");
323
324 cfg_iter!(solutions).try_for_each(|(solution_id, solution)| {
326 if solution.epoch_hash() != expected_epoch_hash {
327 bail!("Solution '{solution_id}' did not match the expected epoch hash (found '{}', expected '{expected_epoch_hash}')", solution.epoch_hash())
328 }
329 Ok(())
330 })?;
331 lap!(timer, "Verify each epoch hash matches");
332
333 cfg_into_iter!(self.get_proof_targets(solutions)?).enumerate().try_for_each(|(i, proof_target)| {
335 if proof_target < expected_proof_target {
336 bail!(
337 "Solution '{:?}' did not meet the proof target requirement ({proof_target} < {expected_proof_target})",
338 solutions.get_index(i).map(|(id, _)| id)
339 )
340 }
341 Ok(())
342 })?;
343 finish!(timer, "Verify each solution");
344 Ok(())
345 }
346
347 fn leaves_to_proof_target(leaves: &[Vec<bool>]) -> Result<u64> {
349 let merkle_tree = MerkleTree::new(&Sha3_256::default(), &Sha3_256::default(), leaves)?;
351 let root = merkle_tree.root();
353 match *U64::<N>::from_bits_be(&root[0..64])? {
355 0 => Ok(u64::MAX),
356 value => Ok(u64::MAX / value),
357 }
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 use super::*;
364 use console::{
365 account::{Address, PrivateKey},
366 network::Network,
367 prelude::{FromBytes, TestRng, ToBits as TBits, ToBytes, Uniform},
368 types::Field,
369 };
370
371 use anyhow::Result;
372 use core::marker::PhantomData;
373 use rand::{CryptoRng, Rng, RngCore, SeedableRng};
374 use rand_chacha::ChaChaRng;
375
376 type CurrentNetwork = console::network::MainnetV0;
377
378 const ITERATIONS: u64 = 100;
379
380 pub struct SimplePuzzle<N: Network>(PhantomData<N>);
381
382 impl<N: Network> PuzzleTrait<N> for SimplePuzzle<N> {
383 fn new() -> Self {
385 Self(PhantomData)
386 }
387
388 fn to_leaves(&self, epoch_hash: N::BlockHash, rng: &mut ChaChaRng) -> Result<Vec<Vec<bool>>> {
390 let num_leaves = self.num_leaves(epoch_hash)?;
392 let leaves = (0..num_leaves).map(|_| Field::<N>::rand(rng).to_bits_le()).collect::<Vec<_>>();
394 Ok(leaves)
396 }
397
398 fn to_all_leaves(&self, epoch_hash: N::BlockHash, rngs: Vec<ChaChaRng>) -> Result<Vec<Vec<Vec<bool>>>> {
400 let num_leaves = self.num_leaves(epoch_hash)?;
402 let mut leaves = Vec::with_capacity(rngs.len());
404 for mut rng in rngs {
406 leaves.push((0..num_leaves).map(|_| Field::<N>::rand(&mut rng).to_bits_le()).collect::<Vec<_>>());
408 }
409 Ok(leaves)
411 }
412 }
413
414 impl<N: Network> SimplePuzzle<N> {
415 pub fn num_leaves(&self, epoch_hash: N::BlockHash) -> Result<usize> {
417 const MIN_NUMBER_OF_LEAVES: usize = 100;
418 const MAX_NUMBER_OF_LEAVES: usize = 200;
419
420 let seed = u64::from_bytes_le(&epoch_hash.to_bytes_le()?[0..8])?;
422 let mut epoch_rng = ChaChaRng::seed_from_u64(seed);
424 Ok(epoch_rng.gen_range(MIN_NUMBER_OF_LEAVES..MAX_NUMBER_OF_LEAVES))
426 }
427 }
428
429 fn sample_puzzle() -> Puzzle<CurrentNetwork> {
431 Puzzle::<CurrentNetwork>::new::<SimplePuzzle<CurrentNetwork>>()
432 }
433
434 #[test]
435 fn test_puzzle() {
436 let mut rng = TestRng::default();
437
438 let puzzle = sample_puzzle();
440
441 let epoch_hash = rng.r#gen();
443
444 for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS {
445 let solutions = (0..batch_size)
447 .map(|_| puzzle.prove(epoch_hash, rng.r#gen(), rng.r#gen(), None).unwrap())
448 .collect::<Vec<_>>();
449 let solutions = PuzzleSolutions::new(solutions).unwrap();
450
451 assert!(puzzle.check_solutions(&solutions, epoch_hash, 0u64).is_ok());
453
454 let bad_epoch_hash = rng.r#gen();
456 assert!(puzzle.check_solutions(&solutions, bad_epoch_hash, 0u64).is_err());
457 }
458 }
459
460 #[test]
461 fn test_prove_with_minimum_proof_target() {
462 let mut rng = TestRng::default();
463
464 let puzzle = sample_puzzle();
466
467 let epoch_hash = rng.r#gen();
469
470 for _ in 0..ITERATIONS {
471 let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
472 let address = Address::try_from(private_key).unwrap();
473 let counter = u64::rand(&mut rng);
474
475 let solution = puzzle.prove(epoch_hash, address, counter, None).unwrap();
476 let proof_target = puzzle.get_proof_target(&solution).unwrap();
477
478 assert!(puzzle.prove(epoch_hash, address, counter, Some(proof_target)).is_ok());
480
481 assert!(puzzle.prove(epoch_hash, address, counter, Some(proof_target.saturating_add(1))).is_err());
483
484 let solutions = PuzzleSolutions::new(vec![solution]).unwrap();
485
486 assert!(puzzle.check_solutions(&solutions, epoch_hash, proof_target).is_ok());
488
489 assert!(puzzle.check_solutions(&solutions, epoch_hash, proof_target.saturating_add(1)).is_err());
491 }
492 }
493
494 #[test]
495 fn test_prove_with_no_minimum_proof_target() {
496 let mut rng = rand::thread_rng();
497
498 let puzzle = sample_puzzle();
500
501 let epoch_hash = rng.r#gen();
503
504 let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
506 let address = Address::try_from(private_key).unwrap();
507
508 let solution = puzzle.prove(epoch_hash, address, rng.r#gen(), None).unwrap();
510 assert!(puzzle.check_solution(&solution, epoch_hash, 0u64).is_ok());
511
512 let solutions = PuzzleSolutions::new(vec![solution]).unwrap();
513 assert!(puzzle.check_solutions(&solutions, epoch_hash, 0u64).is_ok());
514 }
515
516 #[test]
517 fn test_check_solution_with_incorrect_target_fails() {
518 let mut rng = rand::thread_rng();
519
520 let puzzle = sample_puzzle();
522
523 let epoch_hash = rng.r#gen();
525
526 let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
528 let address = Address::try_from(private_key).unwrap();
529
530 let solution = puzzle.prove(epoch_hash, address, rng.r#gen(), None).unwrap();
532
533 let incorrect_solution = Solution::new(*solution.partial_solution(), solution.target().saturating_add(1));
535
536 assert!(puzzle.check_solution(&incorrect_solution, epoch_hash, 0u64).is_err());
538
539 let new_puzzle = sample_puzzle();
541 assert!(new_puzzle.check_solution(&incorrect_solution, epoch_hash, 0u64).is_err());
542
543 let incorrect_solutions = PuzzleSolutions::new(vec![incorrect_solution]).unwrap();
545 assert!(puzzle.check_solutions(&incorrect_solutions, epoch_hash, 0u64).is_err());
546
547 let new_puzzle = sample_puzzle();
549 assert!(new_puzzle.check_solutions(&incorrect_solutions, epoch_hash, 0u64).is_err());
550 }
551
552 #[test]
553 fn test_check_solutions_with_incorrect_target_fails() {
554 let mut rng = TestRng::default();
555
556 let puzzle = sample_puzzle();
558
559 let epoch_hash = rng.r#gen();
561
562 for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS {
563 let incorrect_solutions = (0..batch_size)
565 .map(|_| {
566 let solution = puzzle.prove(epoch_hash, rng.r#gen(), rng.r#gen(), None).unwrap();
567 Solution::new(*solution.partial_solution(), solution.target().saturating_add(1))
568 })
569 .collect::<Vec<_>>();
570 let incorrect_solutions = PuzzleSolutions::new(incorrect_solutions).unwrap();
571
572 assert!(puzzle.check_solutions(&incorrect_solutions, epoch_hash, 0u64).is_err());
574
575 let new_puzzle = sample_puzzle();
577 assert!(new_puzzle.check_solutions(&incorrect_solutions, epoch_hash, 0u64).is_err());
578 }
579 }
580
581 #[test]
582 fn test_check_solutions_with_duplicate_nonces() {
583 let mut rng = TestRng::default();
584
585 let puzzle = sample_puzzle();
587
588 let epoch_hash = rng.r#gen();
590 let address = rng.r#gen();
592 let counter = rng.r#gen();
594
595 for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS {
596 let solutions =
598 (0..batch_size).map(|_| puzzle.prove(epoch_hash, address, counter, None).unwrap()).collect::<Vec<_>>();
599 let solutions = match batch_size {
601 1 => PuzzleSolutions::new(solutions).unwrap(),
602 _ => {
603 assert!(PuzzleSolutions::new(solutions).is_err());
604 continue;
605 }
606 };
607 match batch_size {
608 1 => assert!(puzzle.check_solutions(&solutions, epoch_hash, 0u64).is_ok()),
609 _ => unreachable!("There are duplicates that should not reach this point in the test"),
610 }
611 }
612 }
613
614 #[test]
615 fn test_get_proof_targets_without_cache() {
616 let mut rng = TestRng::default();
617
618 let epoch_hash = rng.r#gen();
620
621 for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS {
622 let puzzle = sample_puzzle();
624 let solutions = (0..batch_size)
626 .map(|_| puzzle.prove(epoch_hash, rng.r#gen(), rng.r#gen(), None).unwrap())
627 .collect::<Vec<_>>();
628 let solutions = PuzzleSolutions::new(solutions).unwrap();
629
630 let puzzle = sample_puzzle();
632
633 let proof_targets = puzzle.get_proof_targets(&solutions).unwrap();
635
636 for ((_, solution), proof_target) in solutions.iter().zip(proof_targets) {
638 assert_eq!(puzzle.get_proof_target(solution).unwrap(), proof_target);
639 }
640 }
641 }
642
643 #[test]
644 fn test_get_proof_targets_with_partial_cache() {
645 let mut rng = TestRng::default();
646
647 let epoch_hash = rng.r#gen();
649
650 for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS {
651 let puzzle = sample_puzzle();
653 let solutions = (0..batch_size)
655 .map(|_| puzzle.prove(epoch_hash, rng.r#gen(), rng.r#gen(), None).unwrap())
656 .collect::<Vec<_>>();
657 let solutions = PuzzleSolutions::new(solutions).unwrap();
658
659 let puzzle = sample_puzzle();
661
662 for solution in solutions.values() {
664 if rng.r#gen::<bool>() {
666 puzzle.get_proof_target(solution).unwrap();
668 }
669 }
670
671 let proof_targets = puzzle.get_proof_targets(&solutions).unwrap();
673
674 for ((_, solution), proof_target) in solutions.iter().zip(proof_targets) {
676 assert_eq!(puzzle.get_proof_target(solution).unwrap(), proof_target);
677 }
678 }
679 }
680
681 #[ignore]
683 #[test]
684 fn test_profiler() -> Result<()> {
685 fn sample_address_and_counter(rng: &mut (impl CryptoRng + RngCore)) -> (Address<CurrentNetwork>, u64) {
686 let private_key = PrivateKey::new(rng).unwrap();
687 let address = Address::try_from(private_key).unwrap();
688 let counter = rng.next_u64();
689 (address, counter)
690 }
691
692 let mut rng = rand::thread_rng();
693
694 let puzzle = sample_puzzle();
696
697 let epoch_hash = rng.r#gen();
699
700 for batch_size in [1, 2, <CurrentNetwork as Network>::MAX_SOLUTIONS] {
701 let solutions = (0..batch_size)
703 .map(|_| {
704 let (address, counter) = sample_address_and_counter(&mut rng);
705 puzzle.prove(epoch_hash, address, counter, None).unwrap()
706 })
707 .collect::<Vec<_>>();
708 let solutions = PuzzleSolutions::new(solutions).unwrap();
710 puzzle.check_solutions(&solutions, epoch_hash, 0u64).unwrap();
712 }
713
714 bail!("\n\nRemember to #[ignore] this test!\n\n")
715 }
716}