lib.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 #![forbid(unsafe_code)] 17 #![allow(clippy::too_many_arguments)] 18 #![warn(clippy::cast_possible_truncation)] 19 20 extern crate deltavm_console as console; 21 22 mod partial_solution; 23 pub use partial_solution::*; 24 25 mod solution; 26 pub use solution::*; 27 28 mod solution_id; 29 pub use solution_id::*; 30 31 mod solutions; 32 pub use solutions::*; 33 34 use 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 54 use alphastd::prelude::*; 55 use core::num::NonZeroUsize; 56 use indexmap::IndexMap; 57 #[cfg(feature = "locktick")] 58 use locktick::parking_lot::RwLock; 59 use lru::LruCache; 60 #[cfg(not(feature = "locktick"))] 61 use parking_lot::RwLock; 62 use rand::SeedableRng; 63 use rand_chacha::ChaChaRng; 64 use std::sync::Arc; 65 66 #[cfg(not(feature = "serial"))] 67 use rayon::prelude::*; 68 69 /// The arity of the Merkle tree. 70 const ARITY: u8 = 8; 71 /// The size of the cache. 72 const CACHE_SIZE: usize = 1 << 10; 73 74 /// The Merkle tree for the puzzle. 75 type MerkleTree = KaryMerkleTree<Sha3_256, Sha3_256, 9, { ARITY }>; 76 77 /// The puzzle trait. 78 pub trait PuzzleTrait<N: Network>: Send + Sync { 79 /// Initializes a new instance of the puzzle. 80 fn new() -> Self 81 where 82 Self: Sized; 83 84 /// Returns the leaves for the puzzle, given the epoch hash and seeded RNG. 85 fn to_leaves(&self, epoch_hash: N::BlockHash, rng: &mut ChaChaRng) -> Result<Vec<Vec<bool>>>; 86 87 /// Returns the batches of leaves for the puzzle, given the epoch hash and seeded RNGs. 88 fn to_all_leaves(&self, epoch_hash: N::BlockHash, rngs: Vec<ChaChaRng>) -> Result<Vec<Vec<Vec<bool>>>>; 89 } 90 91 #[derive(Clone)] 92 pub struct Puzzle<N: Network> { 93 /// The core puzzle. 94 inner: Arc<dyn PuzzleTrait<N>>, 95 /// The LRU cache of solution IDs to proof targets. 96 proof_target_cache: Arc<RwLock<LruCache<SolutionID<N>, u64>>>, 97 } 98 99 impl<N: Network> Puzzle<N> { 100 /// Initializes a new puzzle instance. 101 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 /// Returns the Merkle leaves for the puzzle, given the solution. 109 pub fn get_leaves(&self, solution: &PartialSolution<N>) -> Result<Vec<Vec<bool>>> { 110 // Initialize a seeded random number generator. 111 let mut rng = ChaChaRng::seed_from_u64(*solution.id()); 112 // Output the leaves. 113 self.inner.to_leaves(solution.epoch_hash(), &mut rng) 114 } 115 116 /// Returns each of the Merkle leaves for the puzzle, given the solutions. 117 pub fn get_all_leaves(&self, solutions: &PuzzleSolutions<N>) -> Result<Vec<Vec<Vec<bool>>>> { 118 // Ensure all of the solutions are for the same epoch. 119 ensure!( 120 cfg_values!(solutions).all(|solution| solution.epoch_hash() == solutions[0].epoch_hash()), 121 "The solutions are for different epochs" 122 ); 123 // Construct the RNGs. 124 let rngs = cfg_keys!(solutions).map(|solution_id| ChaChaRng::seed_from_u64(**solution_id)).collect::<Vec<_>>(); 125 // Output the leaves. 126 self.inner.to_all_leaves(solutions[0].epoch_hash(), rngs) 127 } 128 129 /// Returns the proof target given the solution. 130 pub fn get_proof_target(&self, solution: &Solution<N>) -> Result<u64> { 131 // Calculate the proof target. 132 let proof_target = self.get_proof_target_unchecked(solution)?; 133 // Ensure the proof target matches the expected proof target. 134 ensure!(solution.target() == proof_target, "The proof target does not match the expected proof target"); 135 // Return the proof target. 136 Ok(proof_target) 137 } 138 139 /// Returns the proof target given the solution. 140 /// 141 /// Note: This method does **not** check the proof target against the expected proof target. 142 pub fn get_proof_target_unchecked(&self, solution: &Solution<N>) -> Result<u64> { 143 // Calculate the proof target. 144 self.get_proof_target_from_partial_solution(solution.partial_solution()) 145 } 146 147 /// Returns the proof target given the partial solution. 148 pub fn get_proof_target_from_partial_solution(&self, partial_solution: &PartialSolution<N>) -> Result<u64> { 149 // If the proof target is in the cache, then return it. 150 if let Some(proof_target) = self.proof_target_cache.write().get(&partial_solution.id()) { 151 return Ok(*proof_target); 152 } 153 154 // Construct the leaves of the Merkle tree. 155 let leaves = self.get_leaves(partial_solution)?; 156 // Get the proof target. 157 let proof_target = Self::leaves_to_proof_target(&leaves)?; 158 159 // Insert the proof target into the cache. 160 self.proof_target_cache.write().put(partial_solution.id(), proof_target); 161 // Return the proof target. 162 Ok(proof_target) 163 } 164 165 /// Returns the proof targets given the solutions. 166 pub fn get_proof_targets(&self, solutions: &PuzzleSolutions<N>) -> Result<Vec<u64>> { 167 // Initialize the list of proof targets. 168 let mut targets = vec![0u64; solutions.len()]; 169 170 // Initialize a list of solutions that need to be computed for the proof target. 171 let mut to_compute = Vec::new(); 172 // Iterate over the solutions. 173 for (i, (id, solution)) in solutions.iter().enumerate() { 174 // Check if the proof target is in the cache. 175 match self.proof_target_cache.write().get(id) { 176 // If the proof target is in the cache, then store it. 177 Some(proof_target) => { 178 // Ensure that the proof target matches the expected proof target. 179 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 // Otherwise, add it to the list of solutions that need to be computed. 186 None => to_compute.push((i, id, *solution)), 187 } 188 } 189 190 if !to_compute.is_empty() { 191 // Construct the solutions object for those that need to be computed. 192 let solutions_subset = PuzzleSolutions::new(to_compute.iter().map(|(_, _, solution)| *solution).collect())?; 193 // Construct the leaves of the Merkle tree. 194 let leaves = self.get_all_leaves(&solutions_subset)?; 195 // Construct the Merkle roots and truncate them to a u64. 196 let targets_subset = cfg_iter!(leaves) 197 .zip(cfg_iter!(solutions_subset)) 198 .map(|(leaves, (solution_id, solution))| { 199 // Get the proof target. 200 let proof_target = Self::leaves_to_proof_target(leaves)?; 201 // Ensure that the proof target matches the expected proof target. 202 ensure!( 203 solution.target() == proof_target, 204 "The proof target does not match the computed proof target" 205 ); 206 // Insert the proof target into the cache. 207 self.proof_target_cache.write().put(*solution_id, proof_target); 208 // Return the proof target. 209 Ok((solution_id, proof_target)) 210 }) 211 .collect::<Result<IndexMap<_, _>>>()?; 212 213 // Recombine the proof targets. 214 for (i, id, _) in &to_compute { 215 targets[*i] = targets_subset[id]; 216 } 217 } 218 219 // Return the proof targets. 220 Ok(targets) 221 } 222 223 /// Returns the combined proof target of the solutions. 224 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 /// Returns a solution to the puzzle. 231 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 // Construct the partial solution. 239 let partial_solution = PartialSolution::new(epoch_hash, address, counter)?; 240 // Compute the proof target. 241 let proof_target = self.get_proof_target_from_partial_solution(&partial_solution)?; 242 // Check that the minimum proof target is met. 243 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 // Construct the solution. 250 Ok(Solution::new(partial_solution, proof_target)) 251 } 252 253 /// Returns `Ok(())` if the solution is valid. 254 pub fn check_solution( 255 &self, 256 solution: &Solution<N>, 257 expected_epoch_hash: N::BlockHash, 258 expected_proof_target: u64, 259 ) -> Result<()> { 260 // Ensure the epoch hash matches. 261 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 // Ensure the solution is greater than or equal to the expected proof target. 268 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 /// ATTENTION: This function will update the target if the solution target is different from the calculated one. 276 /// Returns `Ok(())` if the solution is valid. 277 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 // Ensure the epoch hash matches. 284 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 // Calculate the proof target of the solution. 291 let proof_target = self.get_proof_target_unchecked(solution)?; 292 293 // Set the target with the newly calculated proof target value. 294 solution.target = proof_target; 295 296 // Ensure the solution is greater than or equal to the expected proof target. 297 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 /// Returns `Ok(())` if the solutions are valid. 304 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 the solutions are not empty. 313 ensure!(!solutions.is_empty(), "The solutions are empty"); 314 // Ensure the number of solutions does not exceed `MAX_SOLUTIONS`. 315 if solutions.len() > N::MAX_SOLUTIONS { 316 bail!("Exceed the maximum number of solutions ({} > {})", solutions.len(), N::MAX_SOLUTIONS) 317 } 318 // Ensure the solution IDs are unique. 319 if has_duplicates(solutions.solution_ids()) { 320 bail!("The solutions contain duplicate solution IDs"); 321 } 322 lap!(timer, "Perform initial checks"); 323 324 // Ensure the epoch hash matches. 325 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 // Ensure the solutions meet the proof target requirement. 334 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 /// A helper function that takes leaves of a Merkle tree and returns the proof target. 348 fn leaves_to_proof_target(leaves: &[Vec<bool>]) -> Result<u64> { 349 // Construct the Merkle tree. 350 let merkle_tree = MerkleTree::new(&Sha3_256::default(), &Sha3_256::default(), leaves)?; 351 // Retrieve the Merkle tree root. 352 let root = merkle_tree.root(); 353 // Truncate to a u64. 354 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)] 362 mod 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 /// Initializes a new instance of the puzzle. 384 fn new() -> Self { 385 Self(PhantomData) 386 } 387 388 /// Returns the leaves for the puzzle, given the epoch hash and seeded RNG. 389 fn to_leaves(&self, epoch_hash: N::BlockHash, rng: &mut ChaChaRng) -> Result<Vec<Vec<bool>>> { 390 // Sample a random number of leaves. 391 let num_leaves = self.num_leaves(epoch_hash)?; 392 // Sample random field elements for each of the leaves, and convert them to bits. 393 let leaves = (0..num_leaves).map(|_| Field::<N>::rand(rng).to_bits_le()).collect::<Vec<_>>(); 394 // Return the leaves. 395 Ok(leaves) 396 } 397 398 /// Returns the batches of leaves for the puzzle, given the epoch hash and seeded RNGs. 399 fn to_all_leaves(&self, epoch_hash: N::BlockHash, rngs: Vec<ChaChaRng>) -> Result<Vec<Vec<Vec<bool>>>> { 400 // Sample a random number of leaves. 401 let num_leaves = self.num_leaves(epoch_hash)?; 402 // Initialize the list of leaves. 403 let mut leaves = Vec::with_capacity(rngs.len()); 404 // Construct the epoch inputs. 405 for mut rng in rngs { 406 // Sample random field elements for each of the leaves, and convert them to bits. 407 leaves.push((0..num_leaves).map(|_| Field::<N>::rand(&mut rng).to_bits_le()).collect::<Vec<_>>()); 408 } 409 // Return the leaves. 410 Ok(leaves) 411 } 412 } 413 414 impl<N: Network> SimplePuzzle<N> { 415 /// Returns the number of leaves given the epoch hash. 416 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 // Prepare the seed. 421 let seed = u64::from_bytes_le(&epoch_hash.to_bytes_le()?[0..8])?; 422 // Seed a random number generator from the epoch hash. 423 let mut epoch_rng = ChaChaRng::seed_from_u64(seed); 424 // Sample a random number of leaves. 425 Ok(epoch_rng.gen_range(MIN_NUMBER_OF_LEAVES..MAX_NUMBER_OF_LEAVES)) 426 } 427 } 428 429 /// Samples a new puzzle. 430 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 // Initialize a new puzzle. 439 let puzzle = sample_puzzle(); 440 441 // Initialize an epoch hash. 442 let epoch_hash = rng.r#gen(); 443 444 for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS { 445 // Initialize the solutions. 446 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 // Ensure the solutions are valid. 452 assert!(puzzle.check_solutions(&solutions, epoch_hash, 0u64).is_ok()); 453 454 // Ensure the solutions are invalid. 455 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 // Initialize a new puzzle. 465 let puzzle = sample_puzzle(); 466 467 // Initialize an epoch hash. 468 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 that the operation will pass if the minimum target is low enough. 479 assert!(puzzle.prove(epoch_hash, address, counter, Some(proof_target)).is_ok()); 480 481 // Assert that the operation will fail if the minimum target is too high. 482 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 // Ensure the solution is valid. 487 assert!(puzzle.check_solutions(&solutions, epoch_hash, proof_target).is_ok()); 488 489 // Ensure the solution is invalid. 490 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 // Initialize a new puzzle. 499 let puzzle = sample_puzzle(); 500 501 // Initialize an epoch hash. 502 let epoch_hash = rng.r#gen(); 503 504 // Generate inputs. 505 let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap(); 506 let address = Address::try_from(private_key).unwrap(); 507 508 // Generate a solution. 509 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 // Initialize a new puzzle. 521 let puzzle = sample_puzzle(); 522 523 // Initialize an epoch hash. 524 let epoch_hash = rng.r#gen(); 525 526 // Generate inputs. 527 let private_key = PrivateKey::<CurrentNetwork>::new(&mut rng).unwrap(); 528 let address = Address::try_from(private_key).unwrap(); 529 530 // Generate a solution. 531 let solution = puzzle.prove(epoch_hash, address, rng.r#gen(), None).unwrap(); 532 533 // Generate a solution with an incorrect target. 534 let incorrect_solution = Solution::new(*solution.partial_solution(), solution.target().saturating_add(1)); 535 536 // Ensure the incorrect solution is invalid. 537 assert!(puzzle.check_solution(&incorrect_solution, epoch_hash, 0u64).is_err()); 538 539 // Ensure the invalid solution is invalid on a fresh puzzle instance. 540 let new_puzzle = sample_puzzle(); 541 assert!(new_puzzle.check_solution(&incorrect_solution, epoch_hash, 0u64).is_err()); 542 543 // Ensure the incorrect solutions are invalid. 544 let incorrect_solutions = PuzzleSolutions::new(vec![incorrect_solution]).unwrap(); 545 assert!(puzzle.check_solutions(&incorrect_solutions, epoch_hash, 0u64).is_err()); 546 547 // Ensure the incorrect solutions are invalid on a fresh puzzle instance. 548 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 // Initialize a new puzzle. 557 let puzzle = sample_puzzle(); 558 559 // Initialize an epoch hash. 560 let epoch_hash = rng.r#gen(); 561 562 for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS { 563 // Initialize the incorrect solutions. 564 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 // Ensure the incorrect solutions are invalid. 573 assert!(puzzle.check_solutions(&incorrect_solutions, epoch_hash, 0u64).is_err()); 574 575 // Ensure the incorrect solutions are invalid on a fresh puzzle instance. 576 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 // Initialize a new puzzle. 586 let puzzle = sample_puzzle(); 587 588 // Initialize an epoch hash. 589 let epoch_hash = rng.r#gen(); 590 // Initialize an address. 591 let address = rng.r#gen(); 592 // Initialize a counter. 593 let counter = rng.r#gen(); 594 595 for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS { 596 // Initialize the solutions. 597 let solutions = 598 (0..batch_size).map(|_| puzzle.prove(epoch_hash, address, counter, None).unwrap()).collect::<Vec<_>>(); 599 // Ensure the solutions are invalid, if the batch size is greater than 1. 600 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 // Initialize an epoch hash. 619 let epoch_hash = rng.r#gen(); 620 621 for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS { 622 // Initialize a new puzzle. 623 let puzzle = sample_puzzle(); 624 // Initialize the solutions. 625 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 // Reinitialize the puzzle to *clear the cache*. 631 let puzzle = sample_puzzle(); 632 633 // Compute the proof targets. 634 let proof_targets = puzzle.get_proof_targets(&solutions).unwrap(); 635 636 // Ensure the proof targets are correct. 637 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 // Initialize an epoch hash. 648 let epoch_hash = rng.r#gen(); 649 650 for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS { 651 // Initialize a new puzzle. 652 let puzzle = sample_puzzle(); 653 // Initialize the solutions. 654 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 // Reinitialize the puzzle to *clear the cache*. 660 let puzzle = sample_puzzle(); 661 662 // Partially fill the cache. 663 for solution in solutions.values() { 664 // Flip a coin. 665 if rng.r#gen::<bool>() { 666 // This operation will fill the cache. 667 puzzle.get_proof_target(solution).unwrap(); 668 } 669 } 670 671 // Compute the proof targets. 672 let proof_targets = puzzle.get_proof_targets(&solutions).unwrap(); 673 674 // Ensure the proof targets are correct. 675 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 /// Use `cargo test profiler --features timer` to run this test. 682 #[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 // Initialize a new puzzle. 695 let puzzle = sample_puzzle(); 696 697 // Initialize an epoch hash. 698 let epoch_hash = rng.r#gen(); 699 700 for batch_size in [1, 2, <CurrentNetwork as Network>::MAX_SOLUTIONS] { 701 // Generate the solutions. 702 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 // Construct the solutions. 709 let solutions = PuzzleSolutions::new(solutions).unwrap(); 710 // Verify the solutions. 711 puzzle.check_solutions(&solutions, epoch_hash, 0u64).unwrap(); 712 } 713 714 bail!("\n\nRemember to #[ignore] this test!\n\n") 715 } 716 }