/ ledger / puzzle / src / lib.rs
lib.rs
  1  // Copyright (c) 2025-2026 ACDC Network
  2  // This file is part of the alphavm library.
  3  //
  4  // Alpha Chain | Delta Chain Protocol
  5  // International Monetary Graphite.
  6  //
  7  // Derived from Aleo (https://aleo.org) and ProvableHQ (https://provable.com).
  8  // They built world-class ZK infrastructure. We installed the EASY button.
  9  // Their cryptography: elegant. Our modifications: bureaucracy-compatible.
 10  // Original brilliance: theirs. Robert's Rules: ours. Bugs: definitely ours.
 11  //
 12  // Original Aleo/ProvableHQ code subject to Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0
 13  // All modifications and new work: CC0 1.0 Universal Public Domain Dedication.
 14  // No rights reserved. No permission required. No warranty. No refunds.
 15  //
 16  // https://creativecommons.org/publicdomain/zero/1.0/
 17  // SPDX-License-Identifier: CC0-1.0
 18  
 19  #![forbid(unsafe_code)]
 20  #![allow(clippy::too_many_arguments)]
 21  #![warn(clippy::cast_possible_truncation)]
 22  
 23  extern crate alphavm_console as console;
 24  
 25  mod partial_solution;
 26  pub use partial_solution::*;
 27  
 28  mod solution;
 29  pub use solution::*;
 30  
 31  mod solution_id;
 32  pub use solution_id::*;
 33  
 34  mod solutions;
 35  pub use solutions::*;
 36  
 37  use console::{
 38      account::Address,
 39      algorithms::Sha3_256,
 40      collections::kary_merkle_tree::KaryMerkleTree,
 41      prelude::{
 42          anyhow,
 43          bail,
 44          cfg_into_iter,
 45          cfg_iter,
 46          cfg_keys,
 47          cfg_values,
 48          ensure,
 49          has_duplicates,
 50          FromBits,
 51          Network,
 52          Result,
 53      },
 54      types::U64,
 55  };
 56  
 57  use acdc_std::prelude::*;
 58  use core::num::NonZeroUsize;
 59  use indexmap::IndexMap;
 60  #[cfg(feature = "locktick")]
 61  use locktick::parking_lot::RwLock;
 62  use lru::LruCache;
 63  #[cfg(not(feature = "locktick"))]
 64  use parking_lot::RwLock;
 65  use rand::SeedableRng;
 66  use rand_chacha::ChaChaRng;
 67  use std::sync::Arc;
 68  
 69  #[cfg(not(feature = "serial"))]
 70  use rayon::prelude::*;
 71  
 72  /// The arity of the Merkle tree.
 73  const ARITY: u8 = 8;
 74  /// The size of the cache.
 75  const CACHE_SIZE: usize = 1 << 10;
 76  
 77  /// The Merkle tree for the puzzle.
 78  type MerkleTree = KaryMerkleTree<Sha3_256, Sha3_256, 9, { ARITY }>;
 79  
 80  /// The puzzle trait.
 81  pub trait PuzzleTrait<N: Network>: Send + Sync {
 82      /// Initializes a new instance of the puzzle.
 83      fn new() -> Self
 84      where
 85          Self: Sized;
 86  
 87      /// Returns the leaves for the puzzle, given the epoch hash and seeded RNG.
 88      fn to_leaves(&self, epoch_hash: N::BlockHash, rng: &mut ChaChaRng) -> Result<Vec<Vec<bool>>>;
 89  
 90      /// Returns the batches of leaves for the puzzle, given the epoch hash and seeded RNGs.
 91      fn to_all_leaves(&self, epoch_hash: N::BlockHash, rngs: Vec<ChaChaRng>) -> Result<Vec<Vec<Vec<bool>>>>;
 92  }
 93  
 94  #[derive(Clone)]
 95  pub struct Puzzle<N: Network> {
 96      /// The core puzzle.
 97      inner: Arc<dyn PuzzleTrait<N>>,
 98      /// The LRU cache of solution IDs to proof targets.
 99      proof_target_cache: Arc<RwLock<LruCache<SolutionID<N>, u64>>>,
100  }
101  
102  impl<N: Network> Puzzle<N> {
103      /// Initializes a new puzzle instance.
104      pub fn new<P: PuzzleTrait<N> + 'static>() -> Self {
105          Self {
106              inner: Arc::new(P::new()),
107              proof_target_cache: Arc::new(RwLock::new(LruCache::new(NonZeroUsize::new(CACHE_SIZE).unwrap()))),
108          }
109      }
110  
111      /// Returns the Merkle leaves for the puzzle, given the solution.
112      pub fn get_leaves(&self, solution: &PartialSolution<N>) -> Result<Vec<Vec<bool>>> {
113          // Initialize a seeded random number generator.
114          let mut rng = ChaChaRng::seed_from_u64(*solution.id());
115          // Output the leaves.
116          self.inner.to_leaves(solution.epoch_hash(), &mut rng)
117      }
118  
119      /// Returns each of the Merkle leaves for the puzzle, given the solutions.
120      pub fn get_all_leaves(&self, solutions: &PuzzleSolutions<N>) -> Result<Vec<Vec<Vec<bool>>>> {
121          // Ensure all of the solutions are for the same epoch.
122          ensure!(
123              cfg_values!(solutions).all(|solution| solution.epoch_hash() == solutions[0].epoch_hash()),
124              "The solutions are for different epochs"
125          );
126          // Construct the RNGs.
127          let rngs = cfg_keys!(solutions).map(|solution_id| ChaChaRng::seed_from_u64(**solution_id)).collect::<Vec<_>>();
128          // Output the leaves.
129          self.inner.to_all_leaves(solutions[0].epoch_hash(), rngs)
130      }
131  
132      /// Returns the proof target given the solution.
133      pub fn get_proof_target(&self, solution: &Solution<N>) -> Result<u64> {
134          // Calculate the proof target.
135          let proof_target = self.get_proof_target_unchecked(solution)?;
136          // Ensure the proof target matches the expected proof target.
137          ensure!(solution.target() == proof_target, "The proof target does not match the expected proof target");
138          // Return the proof target.
139          Ok(proof_target)
140      }
141  
142      /// Returns the proof target given the solution.
143      ///
144      /// Note: This method does **not** check the proof target against the expected proof target.
145      pub fn get_proof_target_unchecked(&self, solution: &Solution<N>) -> Result<u64> {
146          // Calculate the proof target.
147          self.get_proof_target_from_partial_solution(solution.partial_solution())
148      }
149  
150      /// Returns the proof target given the partial solution.
151      pub fn get_proof_target_from_partial_solution(&self, partial_solution: &PartialSolution<N>) -> Result<u64> {
152          // If the proof target is in the cache, then return it.
153          if let Some(proof_target) = self.proof_target_cache.write().get(&partial_solution.id()) {
154              return Ok(*proof_target);
155          }
156  
157          // Construct the leaves of the Merkle tree.
158          let leaves = self.get_leaves(partial_solution)?;
159          // Get the proof target.
160          let proof_target = Self::leaves_to_proof_target(&leaves)?;
161  
162          // Insert the proof target into the cache.
163          self.proof_target_cache.write().put(partial_solution.id(), proof_target);
164          // Return the proof target.
165          Ok(proof_target)
166      }
167  
168      /// Returns the proof targets given the solutions.
169      pub fn get_proof_targets(&self, solutions: &PuzzleSolutions<N>) -> Result<Vec<u64>> {
170          // Initialize the list of proof targets.
171          let mut targets = vec![0u64; solutions.len()];
172  
173          // Initialize a list of solutions that need to be computed for the proof target.
174          let mut to_compute = Vec::new();
175          // Iterate over the solutions.
176          for (i, (id, solution)) in solutions.iter().enumerate() {
177              // Check if the proof target is in the cache.
178              match self.proof_target_cache.write().get(id) {
179                  // If the proof target is in the cache, then store it.
180                  Some(proof_target) => {
181                      // Ensure that the proof target matches the expected proof target.
182                      ensure!(
183                          solution.target() == *proof_target,
184                          "The proof target does not match the cached proof target"
185                      );
186                      targets[i] = *proof_target
187                  }
188                  // Otherwise, add it to the list of solutions that need to be computed.
189                  None => to_compute.push((i, id, *solution)),
190              }
191          }
192  
193          if !to_compute.is_empty() {
194              // Construct the solutions object for those that need to be computed.
195              let solutions_subset = PuzzleSolutions::new(to_compute.iter().map(|(_, _, solution)| *solution).collect())?;
196              // Construct the leaves of the Merkle tree.
197              let leaves = self.get_all_leaves(&solutions_subset)?;
198              // Construct the Merkle roots and truncate them to a u64.
199              let targets_subset = cfg_iter!(leaves)
200                  .zip(cfg_iter!(solutions_subset))
201                  .map(|(leaves, (solution_id, solution))| {
202                      // Get the proof target.
203                      let proof_target = Self::leaves_to_proof_target(leaves)?;
204                      // Ensure that the proof target matches the expected proof target.
205                      ensure!(
206                          solution.target() == proof_target,
207                          "The proof target does not match the computed proof target"
208                      );
209                      // Insert the proof target into the cache.
210                      self.proof_target_cache.write().put(*solution_id, proof_target);
211                      // Return the proof target.
212                      Ok((solution_id, proof_target))
213                  })
214                  .collect::<Result<IndexMap<_, _>>>()?;
215  
216              // Recombine the proof targets.
217              for (i, id, _) in &to_compute {
218                  targets[*i] = targets_subset[id];
219              }
220          }
221  
222          // Return the proof targets.
223          Ok(targets)
224      }
225  
226      /// Returns the combined proof target of the solutions.
227      pub fn get_combined_proof_target(&self, solutions: &PuzzleSolutions<N>) -> Result<u128> {
228          self.get_proof_targets(solutions)?.into_iter().try_fold(0u128, |combined, proof_target| {
229              combined.checked_add(proof_target as u128).ok_or_else(|| anyhow!("Combined proof target overflowed"))
230          })
231      }
232  
233      /// Returns a solution to the puzzle.
234      pub fn prove(
235          &self,
236          epoch_hash: N::BlockHash,
237          address: Address<N>,
238          counter: u64,
239          minimum_proof_target: Option<u64>,
240      ) -> Result<Solution<N>> {
241          // Construct the partial solution.
242          let partial_solution = PartialSolution::new(epoch_hash, address, counter)?;
243          // Compute the proof target.
244          let proof_target = self.get_proof_target_from_partial_solution(&partial_solution)?;
245          // Check that the minimum proof target is met.
246          if let Some(minimum_proof_target) = minimum_proof_target {
247              if proof_target < minimum_proof_target {
248                  bail!("Solution was below the minimum proof target ({proof_target} < {minimum_proof_target})")
249              }
250          }
251  
252          // Construct the solution.
253          Ok(Solution::new(partial_solution, proof_target))
254      }
255  
256      /// Returns `Ok(())` if the solution is valid.
257      pub fn check_solution(
258          &self,
259          solution: &Solution<N>,
260          expected_epoch_hash: N::BlockHash,
261          expected_proof_target: u64,
262      ) -> Result<()> {
263          // Ensure the epoch hash matches.
264          if solution.epoch_hash() != expected_epoch_hash {
265              bail!(
266                  "Solution does not match the expected epoch hash (found '{}', expected '{expected_epoch_hash}')",
267                  solution.epoch_hash()
268              )
269          }
270          // Ensure the solution is greater than or equal to the expected proof target.
271          let proof_target = self.get_proof_target(solution)?;
272          if proof_target < expected_proof_target {
273              bail!("Solution does not meet the proof target requirement ({proof_target} < {expected_proof_target})")
274          }
275          Ok(())
276      }
277  
278      /// ATTENTION: This function will update the target if the solution target is different from the calculated one.
279      /// Returns `Ok(())` if the solution is valid.
280      pub fn check_solution_mut(
281          &self,
282          solution: &mut Solution<N>,
283          expected_epoch_hash: N::BlockHash,
284          expected_proof_target: u64,
285      ) -> Result<()> {
286          // Ensure the epoch hash matches.
287          if solution.epoch_hash() != expected_epoch_hash {
288              bail!(
289                  "Solution does not match the expected epoch hash (found '{}', expected '{expected_epoch_hash}')",
290                  solution.epoch_hash()
291              )
292          }
293          // Calculate the proof target of the solution.
294          let proof_target = self.get_proof_target_unchecked(solution)?;
295  
296          // Set the target with the newly calculated proof target value.
297          solution.target = proof_target;
298  
299          // Ensure the solution is greater than or equal to the expected proof target.
300          if proof_target < expected_proof_target {
301              bail!("Solution does not meet the proof target requirement ({proof_target} < {expected_proof_target})")
302          }
303          Ok(())
304      }
305  
306      /// Returns `Ok(())` if the solutions are valid.
307      pub fn check_solutions(
308          &self,
309          solutions: &PuzzleSolutions<N>,
310          expected_epoch_hash: N::BlockHash,
311          expected_proof_target: u64,
312      ) -> Result<()> {
313          let timer = timer!("Puzzle::verify");
314  
315          // Ensure the solutions are not empty.
316          ensure!(!solutions.is_empty(), "The solutions are empty");
317          // Ensure the number of solutions does not exceed `MAX_SOLUTIONS`.
318          if solutions.len() > N::MAX_SOLUTIONS {
319              bail!("Exceed the maximum number of solutions ({} > {})", solutions.len(), N::MAX_SOLUTIONS)
320          }
321          // Ensure the solution IDs are unique.
322          if has_duplicates(solutions.solution_ids()) {
323              bail!("The solutions contain duplicate solution IDs");
324          }
325          lap!(timer, "Perform initial checks");
326  
327          // Ensure the epoch hash matches.
328          cfg_iter!(solutions).try_for_each(|(solution_id, solution)| {
329              if solution.epoch_hash() != expected_epoch_hash {
330                  bail!("Solution '{solution_id}' did not match the expected epoch hash (found '{}', expected '{expected_epoch_hash}')", solution.epoch_hash())
331              }
332              Ok(())
333          })?;
334          lap!(timer, "Verify each epoch hash matches");
335  
336          // Ensure the solutions meet the proof target requirement.
337          cfg_into_iter!(self.get_proof_targets(solutions)?).enumerate().try_for_each(|(i, proof_target)| {
338              if proof_target < expected_proof_target {
339                  bail!(
340                      "Solution '{:?}' did not meet the proof target requirement ({proof_target} < {expected_proof_target})",
341                      solutions.get_index(i).map(|(id, _)| id)
342                  )
343              }
344              Ok(())
345          })?;
346          finish!(timer, "Verify each solution");
347          Ok(())
348      }
349  
350      /// A helper function that takes leaves of a Merkle tree and returns the proof target.
351      fn leaves_to_proof_target(leaves: &[Vec<bool>]) -> Result<u64> {
352          // Construct the Merkle tree.
353          let merkle_tree = MerkleTree::new(&Sha3_256::default(), &Sha3_256::default(), leaves)?;
354          // Retrieve the Merkle tree root.
355          let root = merkle_tree.root();
356          // Truncate to a u64.
357          match *U64::<N>::from_bits_be(&root[0..64])? {
358              0 => Ok(u64::MAX),
359              value => Ok(u64::MAX / value),
360          }
361      }
362  }
363  
364  #[cfg(test)]
365  mod tests {
366      use super::*;
367      use console::{
368          account::{Address, PrivateKey},
369          network::Network,
370          prelude::{FromBytes, TestRng, ToBits as TBits, ToBytes, Uniform},
371          types::Field,
372      };
373  
374      use anyhow::Result;
375      use core::marker::PhantomData;
376      use rand::{CryptoRng, Rng, RngCore, SeedableRng};
377      use rand_chacha::ChaChaRng;
378  
379      type CurrentNetwork = console::network::MainnetV0;
380  
381      const ITERATIONS: u64 = 100;
382  
383      pub struct SimplePuzzle<N: Network>(PhantomData<N>);
384  
385      impl<N: Network> PuzzleTrait<N> for SimplePuzzle<N> {
386          /// Initializes a new instance of the puzzle.
387          fn new() -> Self {
388              Self(PhantomData)
389          }
390  
391          /// Returns the leaves for the puzzle, given the epoch hash and seeded RNG.
392          fn to_leaves(&self, epoch_hash: N::BlockHash, rng: &mut ChaChaRng) -> Result<Vec<Vec<bool>>> {
393              // Sample a random number of leaves.
394              let num_leaves = self.num_leaves(epoch_hash)?;
395              // Sample random field elements for each of the leaves, and convert them to bits.
396              let leaves = (0..num_leaves).map(|_| Field::<N>::rand(rng).to_bits_le()).collect::<Vec<_>>();
397              // Return the leaves.
398              Ok(leaves)
399          }
400  
401          /// Returns the batches of leaves for the puzzle, given the epoch hash and seeded RNGs.
402          fn to_all_leaves(&self, epoch_hash: N::BlockHash, rngs: Vec<ChaChaRng>) -> Result<Vec<Vec<Vec<bool>>>> {
403              // Sample a random number of leaves.
404              let num_leaves = self.num_leaves(epoch_hash)?;
405              // Initialize the list of leaves.
406              let mut leaves = Vec::with_capacity(rngs.len());
407              // Construct the epoch inputs.
408              for mut rng in rngs {
409                  // Sample random field elements for each of the leaves, and convert them to bits.
410                  leaves.push((0..num_leaves).map(|_| Field::<N>::rand(&mut rng).to_bits_le()).collect::<Vec<_>>());
411              }
412              // Return the leaves.
413              Ok(leaves)
414          }
415      }
416  
417      impl<N: Network> SimplePuzzle<N> {
418          /// Returns the number of leaves given the epoch hash.
419          pub fn num_leaves(&self, epoch_hash: N::BlockHash) -> Result<usize> {
420              const MIN_NUMBER_OF_LEAVES: usize = 100;
421              const MAX_NUMBER_OF_LEAVES: usize = 200;
422  
423              // Prepare the seed.
424              let seed = u64::from_bytes_le(&epoch_hash.to_bytes_le()?[0..8])?;
425              // Seed a random number generator from the epoch hash.
426              let mut epoch_rng = ChaChaRng::seed_from_u64(seed);
427              // Sample a random number of leaves.
428              Ok(epoch_rng.gen_range(MIN_NUMBER_OF_LEAVES..MAX_NUMBER_OF_LEAVES))
429          }
430      }
431  
432      /// Samples a new puzzle.
433      fn sample_puzzle() -> Puzzle<CurrentNetwork> {
434          Puzzle::<CurrentNetwork>::new::<SimplePuzzle<CurrentNetwork>>()
435      }
436  
437      #[test]
438      fn test_puzzle() {
439          let mut rng = TestRng::default();
440  
441          // Initialize a new puzzle.
442          let puzzle = sample_puzzle();
443  
444          // Initialize an epoch hash.
445          let epoch_hash = rng.r#gen();
446  
447          for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS {
448              // Initialize the solutions.
449              let solutions = (0..batch_size)
450                  .map(|_| puzzle.prove(epoch_hash, rng.r#gen(), rng.r#gen(), None).unwrap())
451                  .collect::<Vec<_>>();
452              let solutions = PuzzleSolutions::new(solutions).unwrap();
453  
454              // Ensure the solutions are valid.
455              assert!(puzzle.check_solutions(&solutions, epoch_hash, 0u64).is_ok());
456  
457              // Ensure the solutions are invalid.
458              let bad_epoch_hash = rng.r#gen();
459              assert!(puzzle.check_solutions(&solutions, bad_epoch_hash, 0u64).is_err());
460          }
461      }
462  
463      #[test]
464      fn test_prove_with_minimum_proof_target() {
465          let mut rng = TestRng::default();
466  
467          // Initialize a new puzzle.
468          let puzzle = sample_puzzle();
469  
470          // Initialize an epoch hash.
471          let epoch_hash = rng.r#gen();
472  
473          for _ in 0..ITERATIONS {
474              let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
475              let address = Address::try_from(private_key).unwrap();
476              let counter = u64::rand(&mut rng);
477  
478              let solution = puzzle.prove(epoch_hash, address, counter, None).unwrap();
479              let proof_target = puzzle.get_proof_target(&solution).unwrap();
480  
481              // Assert that the operation will pass if the minimum target is low enough.
482              assert!(puzzle.prove(epoch_hash, address, counter, Some(proof_target)).is_ok());
483  
484              // Assert that the operation will fail if the minimum target is too high.
485              assert!(puzzle.prove(epoch_hash, address, counter, Some(proof_target.saturating_add(1))).is_err());
486  
487              let solutions = PuzzleSolutions::new(vec![solution]).unwrap();
488  
489              // Ensure the solution is valid.
490              assert!(puzzle.check_solutions(&solutions, epoch_hash, proof_target).is_ok());
491  
492              // Ensure the solution is invalid.
493              assert!(puzzle.check_solutions(&solutions, epoch_hash, proof_target.saturating_add(1)).is_err());
494          }
495      }
496  
497      #[test]
498      fn test_prove_with_no_minimum_proof_target() {
499          let mut rng = rand::thread_rng();
500  
501          // Initialize a new puzzle.
502          let puzzle = sample_puzzle();
503  
504          // Initialize an epoch hash.
505          let epoch_hash = rng.r#gen();
506  
507          // Generate inputs.
508          let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
509          let address = Address::try_from(private_key).unwrap();
510  
511          // Generate a solution.
512          let solution = puzzle.prove(epoch_hash, address, rng.r#gen(), None).unwrap();
513          assert!(puzzle.check_solution(&solution, epoch_hash, 0u64).is_ok());
514  
515          let solutions = PuzzleSolutions::new(vec![solution]).unwrap();
516          assert!(puzzle.check_solutions(&solutions, epoch_hash, 0u64).is_ok());
517      }
518  
519      #[test]
520      fn test_check_solution_with_incorrect_target_fails() {
521          let mut rng = rand::thread_rng();
522  
523          // Initialize a new puzzle.
524          let puzzle = sample_puzzle();
525  
526          // Initialize an epoch hash.
527          let epoch_hash = rng.r#gen();
528  
529          // Generate inputs.
530          let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap();
531          let address = Address::try_from(private_key).unwrap();
532  
533          // Generate a solution.
534          let solution = puzzle.prove(epoch_hash, address, rng.r#gen(), None).unwrap();
535  
536          // Generate a solution with an incorrect target.
537          let incorrect_solution = Solution::new(*solution.partial_solution(), solution.target().saturating_add(1));
538  
539          // Ensure the incorrect solution is invalid.
540          assert!(puzzle.check_solution(&incorrect_solution, epoch_hash, 0u64).is_err());
541  
542          // Ensure the invalid solution is invalid on a fresh puzzle instance.
543          let new_puzzle = sample_puzzle();
544          assert!(new_puzzle.check_solution(&incorrect_solution, epoch_hash, 0u64).is_err());
545  
546          // Ensure the incorrect solutions are invalid.
547          let incorrect_solutions = PuzzleSolutions::new(vec![incorrect_solution]).unwrap();
548          assert!(puzzle.check_solutions(&incorrect_solutions, epoch_hash, 0u64).is_err());
549  
550          // Ensure the incorrect solutions are invalid on a fresh puzzle instance.
551          let new_puzzle = sample_puzzle();
552          assert!(new_puzzle.check_solutions(&incorrect_solutions, epoch_hash, 0u64).is_err());
553      }
554  
555      #[test]
556      fn test_check_solutions_with_incorrect_target_fails() {
557          let mut rng = TestRng::default();
558  
559          // Initialize a new puzzle.
560          let puzzle = sample_puzzle();
561  
562          // Initialize an epoch hash.
563          let epoch_hash = rng.r#gen();
564  
565          for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS {
566              // Initialize the incorrect solutions.
567              let incorrect_solutions = (0..batch_size)
568                  .map(|_| {
569                      let solution = puzzle.prove(epoch_hash, rng.r#gen(), rng.r#gen(), None).unwrap();
570                      Solution::new(*solution.partial_solution(), solution.target().saturating_add(1))
571                  })
572                  .collect::<Vec<_>>();
573              let incorrect_solutions = PuzzleSolutions::new(incorrect_solutions).unwrap();
574  
575              // Ensure the incorrect solutions are invalid.
576              assert!(puzzle.check_solutions(&incorrect_solutions, epoch_hash, 0u64).is_err());
577  
578              // Ensure the incorrect solutions are invalid on a fresh puzzle instance.
579              let new_puzzle = sample_puzzle();
580              assert!(new_puzzle.check_solutions(&incorrect_solutions, epoch_hash, 0u64).is_err());
581          }
582      }
583  
584      #[test]
585      fn test_check_solutions_with_duplicate_nonces() {
586          let mut rng = TestRng::default();
587  
588          // Initialize a new puzzle.
589          let puzzle = sample_puzzle();
590  
591          // Initialize an epoch hash.
592          let epoch_hash = rng.r#gen();
593          // Initialize an address.
594          let address = rng.r#gen();
595          // Initialize a counter.
596          let counter = rng.r#gen();
597  
598          for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS {
599              // Initialize the solutions.
600              let solutions =
601                  (0..batch_size).map(|_| puzzle.prove(epoch_hash, address, counter, None).unwrap()).collect::<Vec<_>>();
602              // Ensure the solutions are invalid, if the batch size is greater than 1.
603              let solutions = match batch_size {
604                  1 => PuzzleSolutions::new(solutions).unwrap(),
605                  _ => {
606                      assert!(PuzzleSolutions::new(solutions).is_err());
607                      continue;
608                  }
609              };
610              match batch_size {
611                  1 => assert!(puzzle.check_solutions(&solutions, epoch_hash, 0u64).is_ok()),
612                  _ => unreachable!("There are duplicates that should not reach this point in the test"),
613              }
614          }
615      }
616  
617      #[test]
618      fn test_get_proof_targets_without_cache() {
619          let mut rng = TestRng::default();
620  
621          // Initialize an epoch hash.
622          let epoch_hash = rng.r#gen();
623  
624          for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS {
625              // Initialize a new puzzle.
626              let puzzle = sample_puzzle();
627              // Initialize the solutions.
628              let solutions = (0..batch_size)
629                  .map(|_| puzzle.prove(epoch_hash, rng.r#gen(), rng.r#gen(), None).unwrap())
630                  .collect::<Vec<_>>();
631              let solutions = PuzzleSolutions::new(solutions).unwrap();
632  
633              // Reinitialize the puzzle to *clear the cache*.
634              let puzzle = sample_puzzle();
635  
636              // Compute the proof targets.
637              let proof_targets = puzzle.get_proof_targets(&solutions).unwrap();
638  
639              // Ensure the proof targets are correct.
640              for ((_, solution), proof_target) in solutions.iter().zip(proof_targets) {
641                  assert_eq!(puzzle.get_proof_target(solution).unwrap(), proof_target);
642              }
643          }
644      }
645  
646      #[test]
647      fn test_get_proof_targets_with_partial_cache() {
648          let mut rng = TestRng::default();
649  
650          // Initialize an epoch hash.
651          let epoch_hash = rng.r#gen();
652  
653          for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS {
654              // Initialize a new puzzle.
655              let puzzle = sample_puzzle();
656              // Initialize the solutions.
657              let solutions = (0..batch_size)
658                  .map(|_| puzzle.prove(epoch_hash, rng.r#gen(), rng.r#gen(), None).unwrap())
659                  .collect::<Vec<_>>();
660              let solutions = PuzzleSolutions::new(solutions).unwrap();
661  
662              // Reinitialize the puzzle to *clear the cache*.
663              let puzzle = sample_puzzle();
664  
665              // Partially fill the cache.
666              for solution in solutions.values() {
667                  // Flip a coin.
668                  if rng.r#gen::<bool>() {
669                      // This operation will fill the cache.
670                      puzzle.get_proof_target(solution).unwrap();
671                  }
672              }
673  
674              // Compute the proof targets.
675              let proof_targets = puzzle.get_proof_targets(&solutions).unwrap();
676  
677              // Ensure the proof targets are correct.
678              for ((_, solution), proof_target) in solutions.iter().zip(proof_targets) {
679                  assert_eq!(puzzle.get_proof_target(solution).unwrap(), proof_target);
680              }
681          }
682      }
683  
684      /// Use `cargo test profiler --features timer` to run this test.
685      #[ignore]
686      #[test]
687      fn test_profiler() -> Result<()> {
688          fn sample_address_and_counter(rng: &mut (impl CryptoRng + RngCore)) -> (Address<CurrentNetwork>, u64) {
689              let private_key = PrivateKey::new(rng).unwrap();
690              let address = Address::try_from(private_key).unwrap();
691              let counter = rng.next_u64();
692              (address, counter)
693          }
694  
695          let mut rng = rand::thread_rng();
696  
697          // Initialize a new puzzle.
698          let puzzle = sample_puzzle();
699  
700          // Initialize an epoch hash.
701          let epoch_hash = rng.r#gen();
702  
703          for batch_size in [1, 2, <CurrentNetwork as Network>::MAX_SOLUTIONS] {
704              // Generate the solutions.
705              let solutions = (0..batch_size)
706                  .map(|_| {
707                      let (address, counter) = sample_address_and_counter(&mut rng);
708                      puzzle.prove(epoch_hash, address, counter, None).unwrap()
709                  })
710                  .collect::<Vec<_>>();
711              // Construct the solutions.
712              let solutions = PuzzleSolutions::new(solutions).unwrap();
713              // Verify the solutions.
714              puzzle.check_solutions(&solutions, epoch_hash, 0u64).unwrap();
715          }
716  
717          bail!("\n\nRemember to #[ignore] this test!\n\n")
718      }
719  }