/ ledger / puzzle / src / solutions / mod.rs
mod.rs
  1  // Copyright (c) 2019-2025 Alpha-Delta Network Inc.
  2  // This file is part of the deltavm library.
  3  
  4  // Licensed under the Apache License, Version 2.0 (the "License");
  5  // you may not use this file except in compliance with the License.
  6  // You may obtain a copy of the License at:
  7  
  8  // http://www.apache.org/licenses/LICENSE-2.0
  9  
 10  // Unless required by applicable law or agreed to in writing, software
 11  // distributed under the License is distributed on an "AS IS" BASIS,
 12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  // See the License for the specific language governing permissions and
 14  // limitations under the License.
 15  
 16  mod bytes;
 17  mod serialize;
 18  mod string;
 19  
 20  use crate::{Solution, SolutionID};
 21  use console::{network::prelude::*, prelude::DeserializeExt, types::Field};
 22  use indexmap::IndexMap;
 23  
 24  /// The individual solutions.
 25  #[derive(Clone, Eq, PartialEq)]
 26  pub struct PuzzleSolutions<N: Network> {
 27      /// The solutions for the puzzle.
 28      solutions: IndexMap<SolutionID<N>, Solution<N>>,
 29  }
 30  
 31  impl<N: Network> PuzzleSolutions<N> {
 32      /// Initializes a new instance of the solutions.
 33      pub fn new(solutions: Vec<Solution<N>>) -> Result<Self> {
 34          // Ensure the solutions are not empty.
 35          ensure!(!solutions.is_empty(), "There are no solutions to verify for the puzzle");
 36          // Ensure the number of solutions does not exceed `MAX_SOLUTIONS`.
 37          if solutions.len() > N::MAX_SOLUTIONS {
 38              bail!("Exceeded the maximum number of solutions ({} > {})", solutions.len(), N::MAX_SOLUTIONS);
 39          }
 40          // Ensure the solution IDs are unique.
 41          if has_duplicates(solutions.iter().map(Solution::id)) {
 42              bail!("The solutions contain duplicate solution IDs");
 43          }
 44          // Return the solutions.
 45          Ok(Self { solutions: solutions.into_iter().map(|solution| (solution.id(), solution)).collect() })
 46      }
 47  
 48      /// Returns the solution IDs.
 49      pub fn solution_ids(&self) -> impl '_ + Iterator<Item = &SolutionID<N>> {
 50          self.solutions.keys()
 51      }
 52  
 53      /// Returns the number of solutions.
 54      pub fn len(&self) -> usize {
 55          self.solutions.len()
 56      }
 57  
 58      /// Returns `true` if there are no solutions.
 59      pub fn is_empty(&self) -> bool {
 60          self.solutions.is_empty()
 61      }
 62  
 63      /// Returns the solution for the given solution ID.
 64      pub fn get_solution(&self, solution_id: &SolutionID<N>) -> Option<&Solution<N>> {
 65          self.solutions.get(solution_id)
 66      }
 67  
 68      /// Returns the accumulator challenge point.
 69      pub fn to_accumulator_point(&self) -> Result<Field<N>> {
 70          // Encode the solution IDs as field elements.
 71          let mut preimage = self.solution_ids().map(|id| Field::from_u64(**id)).collect::<Vec<_>>();
 72          // Pad the preimage to the required length.
 73          preimage.resize(N::MAX_SOLUTIONS, Field::zero());
 74          // Hash the preimage to obtain the accumulator point.
 75          N::hash_psd8(&preimage)
 76      }
 77  }
 78  
 79  impl<N: Network> Deref for PuzzleSolutions<N> {
 80      type Target = IndexMap<SolutionID<N>, Solution<N>>;
 81  
 82      fn deref(&self) -> &Self::Target {
 83          &self.solutions
 84      }
 85  }
 86  
 87  #[cfg(test)]
 88  mod tests {
 89      use super::*;
 90      use crate::PartialSolution;
 91      use console::account::{Address, PrivateKey};
 92  
 93      use std::collections::HashSet;
 94  
 95      type CurrentNetwork = console::network::MainnetV0;
 96  
 97      /// Returns the solutions for the given number of solutions.
 98      pub(crate) fn sample_solutions_with_count(
 99          num_solutions: usize,
100          rng: &mut TestRng,
101      ) -> PuzzleSolutions<CurrentNetwork> {
102          // Sample a new solutions.
103          let mut solutions = vec![];
104          for _ in 0..num_solutions {
105              let private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
106              let address = Address::try_from(private_key).unwrap();
107  
108              let partial_solution = PartialSolution::new(rng.r#gen(), address, u64::rand(rng)).unwrap();
109              let solution = Solution::new(partial_solution, u64::rand(rng));
110              solutions.push(solution);
111          }
112          PuzzleSolutions::new(solutions).unwrap()
113      }
114  
115      #[test]
116      fn test_new_is_not_empty() {
117          // Ensure the solutions are not empty.
118          assert!(PuzzleSolutions::<CurrentNetwork>::new(vec![]).is_err());
119      }
120  
121      #[test]
122      fn test_len() {
123          let mut rng = TestRng::default();
124  
125          for num_solutions in 1..<CurrentNetwork as Network>::MAX_SOLUTIONS {
126              // Sample random solutions.
127              let solutions = sample_solutions_with_count(num_solutions, &mut rng);
128              // Ensure the number of solutions is correct.
129              assert_eq!(num_solutions, solutions.len());
130          }
131      }
132  
133      #[test]
134      fn test_is_empty() {
135          let mut rng = TestRng::default();
136  
137          for num_solutions in 1..<CurrentNetwork as Network>::MAX_SOLUTIONS {
138              // Sample random solutions.
139              let solutions = sample_solutions_with_count(num_solutions, &mut rng);
140              // Ensure the solutions are not empty.
141              assert!(!solutions.is_empty());
142          }
143      }
144  
145      #[test]
146      fn test_solution_ids() {
147          let mut rng = TestRng::default();
148  
149          for num_solutions in 1..<CurrentNetwork as Network>::MAX_SOLUTIONS {
150              // Sample random solutions.
151              let solutions = sample_solutions_with_count(num_solutions, &mut rng);
152              // Ensure the solution IDs are unique.
153              assert_eq!(num_solutions, solutions.solution_ids().collect::<HashSet<_>>().len());
154          }
155      }
156  
157      #[test]
158      fn test_get_solution() {
159          let mut rng = TestRng::default();
160  
161          for num_solutions in 1..<CurrentNetwork as Network>::MAX_SOLUTIONS {
162              // Sample random solutions.
163              let solutions = sample_solutions_with_count(num_solutions, &mut rng);
164              // Ensure the solutions are not empty.
165              for solution_id in solutions.solution_ids() {
166                  assert_eq!(solutions.get_solution(solution_id).unwrap().id(), *solution_id);
167              }
168          }
169      }
170  
171      #[test]
172      fn test_to_accumulator_point() {
173          let mut rng = TestRng::default();
174  
175          for num_solutions in 1..<CurrentNetwork as Network>::MAX_SOLUTIONS {
176              // Sample random solutions.
177              let solutions = crate::solutions::tests::sample_solutions_with_count(num_solutions, &mut rng);
178              // Compute the candidate accumulator point.
179              let candidate = solutions.to_accumulator_point().unwrap();
180              // Compute the expected accumulator point.
181              let mut preimage = vec![Field::zero(); <CurrentNetwork as Network>::MAX_SOLUTIONS];
182              for (i, id) in solutions.keys().enumerate() {
183                  preimage[i] = Field::from_u64(**id);
184              }
185              let expected = <CurrentNetwork as Network>::hash_psd8(&preimage).unwrap();
186              assert_eq!(expected, candidate);
187          }
188      }
189  }