1#![forbid(unsafe_code)]
17#![allow(clippy::too_many_arguments)]
18#![warn(clippy::cast_possible_truncation)]
19
20mod partial_solution;
21pub use partial_solution::*;
22
23mod solution;
24pub use solution::*;
25
26mod solution_id;
27pub use solution_id::*;
28
29mod solutions;
30pub use solutions::*;
31
32use console::{
33 account::Address,
34 algorithms::Sha3_256,
35 collections::kary_merkle_tree::KaryMerkleTree,
36 prelude::{
37 FromBits,
38 Network,
39 Result,
40 anyhow,
41 bail,
42 cfg_into_iter,
43 cfg_iter,
44 cfg_keys,
45 cfg_values,
46 ensure,
47 has_duplicates,
48 },
49 types::U64,
50};
51
52use aleo_std::prelude::*;
53use core::num::NonZeroUsize;
54use indexmap::IndexMap;
55use lru::LruCache;
56use parking_lot::RwLock;
57use rand::SeedableRng;
58use rand_chacha::ChaChaRng;
59use std::sync::Arc;
60
61#[cfg(not(feature = "serial"))]
62use rayon::prelude::*;
63
64const ARITY: u8 = 8;
66const CACHE_SIZE: usize = 1 << 10;
68
69type MerkleTree = KaryMerkleTree<Sha3_256, Sha3_256, 9, { ARITY }>;
71
72pub trait PuzzleTrait<N: Network>: Send + Sync {
74 fn new() -> Self
76 where
77 Self: Sized;
78
79 fn to_leaves(&self, epoch_hash: N::BlockHash, rng: &mut ChaChaRng) -> Result<Vec<Vec<bool>>>;
81
82 fn to_all_leaves(&self, epoch_hash: N::BlockHash, rngs: Vec<ChaChaRng>) -> Result<Vec<Vec<Vec<bool>>>>;
84}
85
86#[derive(Clone)]
87pub struct Puzzle<N: Network> {
88 inner: Arc<dyn PuzzleTrait<N>>,
90 proof_target_cache: Arc<RwLock<LruCache<SolutionID<N>, u64>>>,
92}
93
94impl<N: Network> Puzzle<N> {
95 pub fn new<P: PuzzleTrait<N> + 'static>() -> Self {
97 Self {
98 inner: Arc::new(P::new()),
99 proof_target_cache: Arc::new(RwLock::new(LruCache::new(NonZeroUsize::new(CACHE_SIZE).unwrap()))),
100 }
101 }
102
103 pub fn get_leaves(&self, solution: &PartialSolution<N>) -> Result<Vec<Vec<bool>>> {
105 let mut rng = ChaChaRng::seed_from_u64(*solution.id());
107 self.inner.to_leaves(solution.epoch_hash(), &mut rng)
109 }
110
111 pub fn get_all_leaves(&self, solutions: &PuzzleSolutions<N>) -> Result<Vec<Vec<Vec<bool>>>> {
113 ensure!(
115 cfg_values!(solutions).all(|solution| solution.epoch_hash() == solutions[0].epoch_hash()),
116 "The solutions are for different epochs"
117 );
118 let rngs = cfg_keys!(solutions).map(|solution_id| ChaChaRng::seed_from_u64(**solution_id)).collect::<Vec<_>>();
120 self.inner.to_all_leaves(solutions[0].epoch_hash(), rngs)
122 }
123
124 pub fn get_proof_target(&self, solution: &Solution<N>) -> Result<u64> {
126 let proof_target = self.get_proof_target_unchecked(solution)?;
128 ensure!(solution.target() == proof_target, "The proof target does not match the expected proof target");
130 Ok(proof_target)
132 }
133
134 pub fn get_proof_target_unchecked(&self, solution: &Solution<N>) -> Result<u64> {
138 self.get_proof_target_from_partial_solution(solution.partial_solution())
140 }
141
142 pub fn get_proof_target_from_partial_solution(&self, partial_solution: &PartialSolution<N>) -> Result<u64> {
144 if let Some(proof_target) = self.proof_target_cache.write().get(&partial_solution.id()) {
146 return Ok(*proof_target);
147 }
148
149 let leaves = self.get_leaves(partial_solution)?;
151 let proof_target = Self::leaves_to_proof_target(&leaves)?;
153
154 self.proof_target_cache.write().put(partial_solution.id(), proof_target);
156 Ok(proof_target)
158 }
159
160 pub fn get_proof_targets(&self, solutions: &PuzzleSolutions<N>) -> Result<Vec<u64>> {
162 let mut targets = vec![0u64; solutions.len()];
164
165 let mut to_compute = Vec::new();
167 for (i, (id, solution)) in solutions.iter().enumerate() {
169 match self.proof_target_cache.write().get(id) {
171 Some(proof_target) => {
173 ensure!(
175 solution.target() == *proof_target,
176 "The proof target does not match the cached proof target"
177 );
178 targets[i] = *proof_target
179 }
180 None => to_compute.push((i, id, *solution)),
182 }
183 }
184
185 if !to_compute.is_empty() {
186 let solutions_subset = PuzzleSolutions::new(to_compute.iter().map(|(_, _, solution)| *solution).collect())?;
188 let leaves = self.get_all_leaves(&solutions_subset)?;
190 let targets_subset = cfg_iter!(leaves)
192 .zip(cfg_iter!(solutions_subset))
193 .map(|(leaves, (solution_id, solution))| {
194 let proof_target = Self::leaves_to_proof_target(leaves)?;
196 ensure!(
198 solution.target() == proof_target,
199 "The proof target does not match the computed proof target"
200 );
201 self.proof_target_cache.write().put(*solution_id, proof_target);
203 Ok((solution_id, proof_target))
205 })
206 .collect::<Result<IndexMap<_, _>>>()?;
207
208 for (i, id, _) in &to_compute {
210 targets[*i] = targets_subset[id];
211 }
212 }
213
214 Ok(targets)
216 }
217
218 pub fn get_combined_proof_target(&self, solutions: &PuzzleSolutions<N>) -> Result<u128> {
220 self.get_proof_targets(solutions)?.into_iter().try_fold(0u128, |combined, proof_target| {
221 combined.checked_add(proof_target as u128).ok_or_else(|| anyhow!("Combined proof target overflowed"))
222 })
223 }
224
225 pub fn prove(
227 &self,
228 epoch_hash: N::BlockHash,
229 address: Address<N>,
230 counter: u64,
231 minimum_proof_target: Option<u64>,
232 ) -> Result<Solution<N>> {
233 let partial_solution = PartialSolution::new(epoch_hash, address, counter)?;
235 let proof_target = self.get_proof_target_from_partial_solution(&partial_solution)?;
237 if let Some(minimum_proof_target) = minimum_proof_target {
239 if proof_target < minimum_proof_target {
240 bail!("Solution was below the minimum proof target ({proof_target} < {minimum_proof_target})")
241 }
242 }
243
244 Ok(Solution::new(partial_solution, proof_target))
246 }
247
248 pub fn check_solution(
250 &self,
251 solution: &Solution<N>,
252 expected_epoch_hash: N::BlockHash,
253 expected_proof_target: u64,
254 ) -> Result<()> {
255 if solution.epoch_hash() != expected_epoch_hash {
257 bail!(
258 "Solution does not match the expected epoch hash (found '{}', expected '{expected_epoch_hash}')",
259 solution.epoch_hash()
260 )
261 }
262 let proof_target = self.get_proof_target(solution)?;
264 if proof_target < expected_proof_target {
265 bail!("Solution does not meet the proof target requirement ({proof_target} < {expected_proof_target})")
266 }
267 Ok(())
268 }
269
270 pub fn check_solution_mut(
273 &self,
274 solution: &mut Solution<N>,
275 expected_epoch_hash: N::BlockHash,
276 expected_proof_target: u64,
277 ) -> Result<()> {
278 if solution.epoch_hash() != expected_epoch_hash {
280 bail!(
281 "Solution does not match the expected epoch hash (found '{}', expected '{expected_epoch_hash}')",
282 solution.epoch_hash()
283 )
284 }
285 let proof_target = self.get_proof_target_unchecked(solution)?;
287
288 solution.target = proof_target;
290
291 if proof_target < expected_proof_target {
293 bail!("Solution does not meet the proof target requirement ({proof_target} < {expected_proof_target})")
294 }
295 Ok(())
296 }
297
298 pub fn check_solutions(
300 &self,
301 solutions: &PuzzleSolutions<N>,
302 expected_epoch_hash: N::BlockHash,
303 expected_proof_target: u64,
304 ) -> Result<()> {
305 let timer = timer!("Puzzle::verify");
306
307 ensure!(!solutions.is_empty(), "The solutions are empty");
309 if solutions.len() > N::MAX_SOLUTIONS {
311 bail!("Exceed the maximum number of solutions ({} > {})", solutions.len(), N::MAX_SOLUTIONS)
312 }
313 if has_duplicates(solutions.solution_ids()) {
315 bail!("The solutions contain duplicate solution IDs");
316 }
317 lap!(timer, "Perform initial checks");
318
319 cfg_iter!(solutions).try_for_each(|(solution_id, solution)| {
321 if solution.epoch_hash() != expected_epoch_hash {
322 bail!("Solution '{solution_id}' did not match the expected epoch hash (found '{}', expected '{expected_epoch_hash}')", solution.epoch_hash())
323 }
324 Ok(())
325 })?;
326 lap!(timer, "Verify each epoch hash matches");
327
328 cfg_into_iter!(self.get_proof_targets(solutions)?).enumerate().try_for_each(|(i, proof_target)| {
330 if proof_target < expected_proof_target {
331 bail!(
332 "Solution '{:?}' did not meet the proof target requirement ({proof_target} < {expected_proof_target})",
333 solutions.get_index(i).map(|(id, _)| id)
334 )
335 }
336 Ok(())
337 })?;
338 finish!(timer, "Verify each solution");
339 Ok(())
340 }
341
342 fn leaves_to_proof_target(leaves: &[Vec<bool>]) -> Result<u64> {
344 let merkle_tree = MerkleTree::new(&Sha3_256::default(), &Sha3_256::default(), leaves)?;
346 let root = merkle_tree.root();
348 match *U64::<N>::from_bits_be(&root[0..64])? {
350 0 => Ok(u64::MAX),
351 value => Ok(u64::MAX / value),
352 }
353 }
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359 use console::{
360 account::{Address, PrivateKey},
361 network::Network,
362 prelude::{FromBytes, TestRng, ToBits as TBits, ToBytes, Uniform},
363 types::Field,
364 };
365
366 use anyhow::Result;
367 use core::marker::PhantomData;
368 use rand::{CryptoRng, Rng, RngCore, SeedableRng};
369 use rand_chacha::ChaChaRng;
370
371 type CurrentNetwork = console::network::MainnetV0;
372
373 const ITERATIONS: u64 = 100;
374
375 pub struct SimplePuzzle<N: Network>(PhantomData<N>);
376
377 impl<N: Network> PuzzleTrait<N> for SimplePuzzle<N> {
378 fn new() -> Self {
380 Self(PhantomData)
381 }
382
383 fn to_leaves(&self, epoch_hash: N::BlockHash, rng: &mut ChaChaRng) -> Result<Vec<Vec<bool>>> {
385 let num_leaves = self.num_leaves(epoch_hash)?;
387 let leaves = (0..num_leaves).map(|_| Field::<N>::rand(rng).to_bits_le()).collect::<Vec<_>>();
389 Ok(leaves)
391 }
392
393 fn to_all_leaves(&self, epoch_hash: N::BlockHash, rngs: Vec<ChaChaRng>) -> Result<Vec<Vec<Vec<bool>>>> {
395 let num_leaves = self.num_leaves(epoch_hash)?;
397 let mut leaves = Vec::with_capacity(rngs.len());
399 for mut rng in rngs {
401 leaves.push((0..num_leaves).map(|_| Field::<N>::rand(&mut rng).to_bits_le()).collect::<Vec<_>>());
403 }
404 Ok(leaves)
406 }
407 }
408
409 impl<N: Network> SimplePuzzle<N> {
410 pub fn num_leaves(&self, epoch_hash: N::BlockHash) -> Result<usize> {
412 const MIN_NUMBER_OF_LEAVES: usize = 100;
413 const MAX_NUMBER_OF_LEAVES: usize = 200;
414
415 let seed = u64::from_bytes_le(&epoch_hash.to_bytes_le()?[0..8])?;
417 let mut epoch_rng = ChaChaRng::seed_from_u64(seed);
419 Ok(epoch_rng.gen_range(MIN_NUMBER_OF_LEAVES..MAX_NUMBER_OF_LEAVES))
421 }
422 }
423
424 fn sample_puzzle() -> Puzzle<CurrentNetwork> {
426 Puzzle::<CurrentNetwork>::new::<SimplePuzzle<CurrentNetwork>>()
427 }
428
429 #[test]
430 fn test_puzzle() {
431 let mut rng = TestRng::default();
432
433 let puzzle = sample_puzzle();
435
436 let epoch_hash = rng.gen();
438
439 for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS {
440 let solutions = (0..batch_size)
442 .map(|_| puzzle.prove(epoch_hash, rng.gen(), rng.gen(), None).unwrap())
443 .collect::<Vec<_>>();
444 let solutions = PuzzleSolutions::new(solutions).unwrap();
445
446 assert!(puzzle.check_solutions(&solutions, epoch_hash, 0u64).is_ok());
448
449 let bad_epoch_hash = rng.gen();
451 assert!(puzzle.check_solutions(&solutions, bad_epoch_hash, 0u64).is_err());
452 }
453 }
454
455 #[test]
456 fn test_prove_with_minimum_proof_target() {
457 let mut rng = TestRng::default();
458
459 let puzzle = sample_puzzle();
461
462 let epoch_hash = rng.gen();
464
465 for _ in 0..ITERATIONS {
466 let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
467 let address = Address::try_from(private_key).unwrap();
468 let counter = u64::rand(&mut rng);
469
470 let solution = puzzle.prove(epoch_hash, address, counter, None).unwrap();
471 let proof_target = puzzle.get_proof_target(&solution).unwrap();
472
473 assert!(puzzle.prove(epoch_hash, address, counter, Some(proof_target)).is_ok());
475
476 assert!(puzzle.prove(epoch_hash, address, counter, Some(proof_target.saturating_add(1))).is_err());
478
479 let solutions = PuzzleSolutions::new(vec![solution]).unwrap();
480
481 assert!(puzzle.check_solutions(&solutions, epoch_hash, proof_target).is_ok());
483
484 assert!(puzzle.check_solutions(&solutions, epoch_hash, proof_target.saturating_add(1)).is_err());
486 }
487 }
488
489 #[test]
490 fn test_prove_with_no_minimum_proof_target() {
491 let mut rng = rand::thread_rng();
492
493 let puzzle = sample_puzzle();
495
496 let epoch_hash = rng.gen();
498
499 let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
501 let address = Address::try_from(private_key).unwrap();
502
503 let solution = puzzle.prove(epoch_hash, address, rng.gen(), None).unwrap();
505 assert!(puzzle.check_solution(&solution, epoch_hash, 0u64).is_ok());
506
507 let solutions = PuzzleSolutions::new(vec![solution]).unwrap();
508 assert!(puzzle.check_solutions(&solutions, epoch_hash, 0u64).is_ok());
509 }
510
511 #[test]
512 fn test_check_solution_with_incorrect_target_fails() {
513 let mut rng = rand::thread_rng();
514
515 let puzzle = sample_puzzle();
517
518 let epoch_hash = rng.gen();
520
521 let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
523 let address = Address::try_from(private_key).unwrap();
524
525 let solution = puzzle.prove(epoch_hash, address, rng.gen(), None).unwrap();
527
528 let incorrect_solution = Solution::new(*solution.partial_solution(), solution.target().saturating_add(1));
530
531 assert!(puzzle.check_solution(&incorrect_solution, epoch_hash, 0u64).is_err());
533
534 let new_puzzle = sample_puzzle();
536 assert!(new_puzzle.check_solution(&incorrect_solution, epoch_hash, 0u64).is_err());
537
538 let incorrect_solutions = PuzzleSolutions::new(vec![incorrect_solution]).unwrap();
540 assert!(puzzle.check_solutions(&incorrect_solutions, epoch_hash, 0u64).is_err());
541
542 let new_puzzle = sample_puzzle();
544 assert!(new_puzzle.check_solutions(&incorrect_solutions, epoch_hash, 0u64).is_err());
545 }
546
547 #[test]
548 fn test_check_solutions_with_incorrect_target_fails() {
549 let mut rng = TestRng::default();
550
551 let puzzle = sample_puzzle();
553
554 let epoch_hash = rng.gen();
556
557 for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS {
558 let incorrect_solutions = (0..batch_size)
560 .map(|_| {
561 let solution = puzzle.prove(epoch_hash, rng.gen(), rng.gen(), None).unwrap();
562 Solution::new(*solution.partial_solution(), solution.target().saturating_add(1))
563 })
564 .collect::<Vec<_>>();
565 let incorrect_solutions = PuzzleSolutions::new(incorrect_solutions).unwrap();
566
567 assert!(puzzle.check_solutions(&incorrect_solutions, epoch_hash, 0u64).is_err());
569
570 let new_puzzle = sample_puzzle();
572 assert!(new_puzzle.check_solutions(&incorrect_solutions, epoch_hash, 0u64).is_err());
573 }
574 }
575
576 #[test]
577 fn test_check_solutions_with_duplicate_nonces() {
578 let mut rng = TestRng::default();
579
580 let puzzle = sample_puzzle();
582
583 let epoch_hash = rng.gen();
585 let address = rng.gen();
587 let counter = rng.gen();
589
590 for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS {
591 let solutions =
593 (0..batch_size).map(|_| puzzle.prove(epoch_hash, address, counter, None).unwrap()).collect::<Vec<_>>();
594 let solutions = match batch_size {
596 1 => PuzzleSolutions::new(solutions).unwrap(),
597 _ => {
598 assert!(PuzzleSolutions::new(solutions).is_err());
599 continue;
600 }
601 };
602 match batch_size {
603 1 => assert!(puzzle.check_solutions(&solutions, epoch_hash, 0u64).is_ok()),
604 _ => unreachable!("There are duplicates that should not reach this point in the test"),
605 }
606 }
607 }
608
609 #[test]
610 fn test_get_proof_targets_without_cache() {
611 let mut rng = TestRng::default();
612
613 let epoch_hash = rng.gen();
615
616 for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS {
617 let puzzle = sample_puzzle();
619 let solutions = (0..batch_size)
621 .map(|_| puzzle.prove(epoch_hash, rng.gen(), rng.gen(), None).unwrap())
622 .collect::<Vec<_>>();
623 let solutions = PuzzleSolutions::new(solutions).unwrap();
624
625 let puzzle = sample_puzzle();
627
628 let proof_targets = puzzle.get_proof_targets(&solutions).unwrap();
630
631 for ((_, solution), proof_target) in solutions.iter().zip(proof_targets) {
633 assert_eq!(puzzle.get_proof_target(solution).unwrap(), proof_target);
634 }
635 }
636 }
637
638 #[test]
639 fn test_get_proof_targets_with_partial_cache() {
640 let mut rng = TestRng::default();
641
642 let epoch_hash = rng.gen();
644
645 for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS {
646 let puzzle = sample_puzzle();
648 let solutions = (0..batch_size)
650 .map(|_| puzzle.prove(epoch_hash, rng.gen(), rng.gen(), None).unwrap())
651 .collect::<Vec<_>>();
652 let solutions = PuzzleSolutions::new(solutions).unwrap();
653
654 let puzzle = sample_puzzle();
656
657 for solution in solutions.values() {
659 if rng.gen::<bool>() {
661 puzzle.get_proof_target(solution).unwrap();
663 }
664 }
665
666 let proof_targets = puzzle.get_proof_targets(&solutions).unwrap();
668
669 for ((_, solution), proof_target) in solutions.iter().zip(proof_targets) {
671 assert_eq!(puzzle.get_proof_target(solution).unwrap(), proof_target);
672 }
673 }
674 }
675
676 #[ignore]
678 #[test]
679 fn test_profiler() -> Result<()> {
680 fn sample_address_and_counter(rng: &mut (impl CryptoRng + RngCore)) -> (Address<CurrentNetwork>, u64) {
681 let private_key = PrivateKey::new(rng).unwrap();
682 let address = Address::try_from(private_key).unwrap();
683 let counter = rng.next_u64();
684 (address, counter)
685 }
686
687 let mut rng = rand::thread_rng();
688
689 let puzzle = sample_puzzle();
691
692 let epoch_hash = rng.gen();
694
695 for batch_size in [1, 2, <CurrentNetwork as Network>::MAX_SOLUTIONS] {
696 let solutions = (0..batch_size)
698 .map(|_| {
699 let (address, counter) = sample_address_and_counter(&mut rng);
700 puzzle.prove(epoch_hash, address, counter, None).unwrap()
701 })
702 .collect::<Vec<_>>();
703 let solutions = PuzzleSolutions::new(solutions).unwrap();
705 puzzle.check_solutions(&solutions, epoch_hash, 0u64).unwrap();
707 }
708
709 bail!("\n\nRemember to #[ignore] this test!\n\n")
710 }
711}