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 }