From 48dd2db633ea81843df86296a9bcac10a10e47d9 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:56:00 -0700 Subject: [PATCH] Add puzzle --- .circleci/config.yml | 41 +- .github/workflows/benchmarks.yml | 2 +- Cargo.lock | 77 ++- Cargo.toml | 3 +- .../src/snark/varuna/data_structures/proof.rs | 4 + circuit/environment/src/environment.rs | 1 + circuit/network/src/lib.rs | 1 + console/Cargo.toml | 1 + console/network/Cargo.toml | 1 + console/network/environment/src/lib.rs | 1 + console/network/src/lib.rs | 22 +- ledger/Cargo.toml | 14 +- ledger/block/Cargo.toml | 14 +- ledger/block/src/helpers/target.rs | 39 +- ledger/block/src/lib.rs | 24 +- ledger/block/src/solutions/bytes.rs | 2 +- ledger/block/src/solutions/mod.rs | 28 +- ledger/block/src/solutions/serialize.rs | 14 +- ledger/block/src/verify.rs | 36 +- ledger/coinbase/benches/coinbase_puzzle.rs | 121 ---- ledger/coinbase/src/hash.rs | 79 --- .../src/helpers/coinbase_solution/mod.rs | 98 --- .../src/helpers/epoch_challenge/bytes.rs | 74 --- .../src/helpers/epoch_challenge/mod.rs | 84 --- ledger/coinbase/src/helpers/mod.rs | 84 --- .../src/helpers/partial_solution/mod.rs | 65 -- .../src/helpers/prover_solution/bytes.rs | 59 -- .../src/helpers/prover_solution/mod.rs | 102 ---- .../src/helpers/prover_solution/serialize.rs | 110 ---- .../src/helpers/prover_solution/string.rs | 63 -- .../src/helpers/puzzle_commitment/mod.rs | 68 --- ledger/coinbase/src/lib.rs | 249 -------- ledger/coinbase/src/tests.rs | 151 ----- ledger/committee/Cargo.toml | 4 - ledger/narwhal/batch-header/src/lib.rs | 2 +- ledger/narwhal/transmission-id/Cargo.toml | 6 +- ledger/narwhal/transmission-id/src/lib.rs | 24 +- ledger/narwhal/transmission-id/src/string.rs | 4 +- ledger/narwhal/transmission/Cargo.toml | 10 +- ledger/narwhal/transmission/src/lib.rs | 12 +- ledger/{coinbase => puzzle}/Cargo.toml | 55 +- ledger/{coinbase => puzzle}/LICENSE.md | 0 ledger/{coinbase => puzzle}/README.md | 4 +- ledger/puzzle/benches/puzzle.rs | 82 +++ ledger/puzzle/epoch/Cargo.toml | 67 ++ ledger/puzzle/epoch/LICENSE.md | 194 ++++++ ledger/puzzle/epoch/README.md | 5 + ledger/puzzle/epoch/src/lib.rs | 18 + ledger/puzzle/epoch/src/merkle/mod.rs | 134 ++++ ledger/puzzle/src/lib.rs | 578 ++++++++++++++++++ .../src/solution}/bytes.rs | 28 +- ledger/puzzle/src/solution/mod.rs | 63 ++ .../src/solution}/serialize.rs | 58 +- .../src/solution}/string.rs | 24 +- .../src/solution_id}/bytes.rs | 24 +- ledger/puzzle/src/solution_id/mod.rs | 56 ++ .../src/solution_id}/serialize.rs | 22 +- .../src/solution_id}/string.rs | 54 +- .../src/solutions}/bytes.rs | 18 +- ledger/puzzle/src/solutions/mod.rs | 186 ++++++ .../src/solutions}/serialize.rs | 24 +- .../src/solutions}/string.rs | 10 +- ledger/src/advance.rs | 51 +- ledger/src/check_next_block.rs | 8 +- ledger/src/contains.rs | 8 +- ledger/src/find.rs | 9 +- ledger/src/get.rs | 18 +- ledger/src/helpers/bft.rs | 6 +- ledger/src/iterators.rs | 2 +- ledger/src/lib.rs | 33 +- ledger/src/tests.rs | 249 +++++++- ledger/store/Cargo.toml | 14 +- ledger/store/src/block/mod.rs | 67 +- ledger/store/src/helpers/memory/block.rs | 24 +- ledger/store/src/helpers/rocksdb/block.rs | 24 +- .../store/src/helpers/rocksdb/internal/id.rs | 4 +- .../src/mainnet/resources/block.genesis | Bin 15485 -> 15485 bytes synthesizer/Cargo.toml | 10 + synthesizer/src/vm/finalize.rs | 13 +- synthesizer/src/vm/helpers/macros.rs | 15 + synthesizer/src/vm/mod.rs | 43 +- .../vm/execute_and_finalize/test_rand.out | 4 +- utilities/src/parallel.rs | 14 + 83 files changed, 2227 insertions(+), 1887 deletions(-) delete mode 100644 ledger/coinbase/benches/coinbase_puzzle.rs delete mode 100644 ledger/coinbase/src/hash.rs delete mode 100644 ledger/coinbase/src/helpers/coinbase_solution/mod.rs delete mode 100644 ledger/coinbase/src/helpers/epoch_challenge/bytes.rs delete mode 100644 ledger/coinbase/src/helpers/epoch_challenge/mod.rs delete mode 100644 ledger/coinbase/src/helpers/mod.rs delete mode 100644 ledger/coinbase/src/helpers/partial_solution/mod.rs delete mode 100644 ledger/coinbase/src/helpers/prover_solution/bytes.rs delete mode 100644 ledger/coinbase/src/helpers/prover_solution/mod.rs delete mode 100644 ledger/coinbase/src/helpers/prover_solution/serialize.rs delete mode 100644 ledger/coinbase/src/helpers/prover_solution/string.rs delete mode 100644 ledger/coinbase/src/helpers/puzzle_commitment/mod.rs delete mode 100644 ledger/coinbase/src/lib.rs delete mode 100644 ledger/coinbase/src/tests.rs rename ledger/{coinbase => puzzle}/Cargo.toml (68%) rename ledger/{coinbase => puzzle}/LICENSE.md (100%) rename ledger/{coinbase => puzzle}/README.md (51%) create mode 100644 ledger/puzzle/benches/puzzle.rs create mode 100644 ledger/puzzle/epoch/Cargo.toml create mode 100644 ledger/puzzle/epoch/LICENSE.md create mode 100644 ledger/puzzle/epoch/README.md create mode 100644 ledger/puzzle/epoch/src/lib.rs create mode 100644 ledger/puzzle/epoch/src/merkle/mod.rs create mode 100644 ledger/puzzle/src/lib.rs rename ledger/{coinbase/src/helpers/partial_solution => puzzle/src/solution}/bytes.rs (61%) create mode 100644 ledger/puzzle/src/solution/mod.rs rename ledger/{coinbase/src/helpers/partial_solution => puzzle/src/solution}/serialize.rs (57%) rename ledger/{coinbase/src/helpers/partial_solution => puzzle/src/solution}/string.rs (65%) rename ledger/{coinbase/src/helpers/puzzle_commitment => puzzle/src/solution_id}/bytes.rs (61%) create mode 100644 ledger/puzzle/src/solution_id/mod.rs rename ledger/{coinbase/src/helpers/puzzle_commitment => puzzle/src/solution_id}/serialize.rs (75%) rename ledger/{coinbase/src/helpers/puzzle_commitment => puzzle/src/solution_id}/string.rs (50%) rename ledger/{coinbase/src/helpers/coinbase_solution => puzzle/src/solutions}/bytes.rs (69%) create mode 100644 ledger/puzzle/src/solutions/mod.rs rename ledger/{coinbase/src/helpers/coinbase_solution => puzzle/src/solutions}/serialize.rs (79%) rename ledger/{coinbase/src/helpers/coinbase_solution => puzzle/src/solutions}/string.rs (82%) diff --git a/.circleci/config.yml b/.circleci/config.yml index e98a78be0e..6131c59f57 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -493,32 +493,33 @@ jobs: workspace_member: ledger cache_key: snarkvm-ledger-with-rocksdb-cache - ledger-authority: + ledger-with-valid-solutions: docker: - image: cimg/rust:1.72.1 resource_class: 2xlarge steps: - run_serial: - workspace_member: ledger/authority - cache_key: snarkvm-ledger-authority-cache + flags: valid_solutions --features=test + workspace_member: ledger + cache_key: snarkvm-ledger-with-valid-solutions-cache - ledger-block: + ledger-authority: docker: - image: cimg/rust:1.72.1 resource_class: 2xlarge steps: - run_serial: - workspace_member: ledger/block - cache_key: snarkvm-ledger-block-cache + workspace_member: ledger/authority + cache_key: snarkvm-ledger-authority-cache - ledger-coinbase: + ledger-block: docker: - image: cimg/rust:1.72.1 resource_class: 2xlarge steps: - run_serial: - workspace_member: ledger/coinbase - cache_key: snarkvm-ledger-coinbase-cache + workspace_member: ledger/block + cache_key: snarkvm-ledger-block-cache ledger-committee: docker: @@ -592,6 +593,24 @@ jobs: workspace_member: ledger/narwhal/transmission-id cache_key: snarkvm-ledger-narwhal-transmission-id-cache + ledger-puzzle: + docker: + - image: cimg/rust:1.72.1 + resource_class: 2xlarge + steps: + - run_serial: + workspace_member: ledger/puzzle + cache_key: snarkvm-ledger-puzzle-cache + + ledger-puzzle-epoch: + docker: + - image: cimg/rust:1.72.1 + resource_class: 2xlarge + steps: + - run_serial: + workspace_member: ledger/puzzle/epoch + cache_key: snarkvm-ledger-puzzle-epoch-cache + ledger-query: docker: - image: cimg/rust:1.72.1 @@ -869,9 +888,9 @@ workflows: - ledger # TODO (howardwu) - Implement `open_testing` on all storage, update to `CurrentConsensusStore::open_testing`, then re-enable. # - ledger-with-rocksdb + - ledger-with-valid-solutions - ledger-authority - ledger-block - - ledger-coinbase - ledger-committee - ledger-narwhal - ledger-narwhal-batch-certificate @@ -880,6 +899,8 @@ workflows: - ledger-narwhal-subdag - ledger-narwhal-transmission - ledger-narwhal-transmission-id + - ledger-puzzle + - ledger-puzzle-epoch - ledger-query - ledger-store - ledger-test-helpers diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index c2e1481726..3fa5727be5 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -84,7 +84,7 @@ jobs: - name: Benchmark ledger/coinbase run: | cd ledger/coinbase - cargo bench --bench coinbase_puzzle --features "setup" -- --output-format bencher | tee -a ../../output.txt + cargo bench --bench puzzle --features "setup" -- --output-format bencher | tee -a ../../output.txt cd ../.. # Clean benchmark output to remove unnecessary lines diff --git a/Cargo.lock b/Cargo.lock index d6ae219602..b69b5ddcaf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3073,9 +3073,9 @@ dependencies = [ "snarkvm-console", "snarkvm-ledger-authority", "snarkvm-ledger-block", - "snarkvm-ledger-coinbase", "snarkvm-ledger-committee", "snarkvm-ledger-narwhal", + "snarkvm-ledger-puzzle", "snarkvm-ledger-query", "snarkvm-ledger-store", "snarkvm-ledger-test-helpers", @@ -3109,11 +3109,11 @@ dependencies = [ "snarkvm-circuit", "snarkvm-console", "snarkvm-ledger-authority", - "snarkvm-ledger-coinbase", "snarkvm-ledger-committee", "snarkvm-ledger-narwhal-batch-header", "snarkvm-ledger-narwhal-subdag", "snarkvm-ledger-narwhal-transmission-id", + "snarkvm-ledger-puzzle", "snarkvm-ledger-query", "snarkvm-ledger-store", "snarkvm-synthesizer-process", @@ -3121,27 +3121,6 @@ dependencies = [ "snarkvm-synthesizer-snark", ] -[[package]] -name = "snarkvm-ledger-coinbase" -version = "0.16.19" -dependencies = [ - "aleo-std", - "anyhow", - "bincode", - "blake2", - "criterion", - "indexmap 2.1.0", - "rand", - "rayon", - "serde_json", - "snarkvm-algorithms", - "snarkvm-console", - "snarkvm-curves", - "snarkvm-fields", - "snarkvm-synthesizer-snark", - "snarkvm-utilities", -] - [[package]] name = "snarkvm-ledger-committee" version = "0.16.19" @@ -3239,8 +3218,8 @@ dependencies = [ "serde_json", "snarkvm-console", "snarkvm-ledger-block", - "snarkvm-ledger-coinbase", "snarkvm-ledger-narwhal-data", + "snarkvm-ledger-puzzle", ] [[package]] @@ -3250,7 +3229,42 @@ dependencies = [ "bincode", "serde_json", "snarkvm-console", - "snarkvm-ledger-coinbase", + "snarkvm-ledger-puzzle", +] + +[[package]] +name = "snarkvm-ledger-puzzle" +version = "0.16.19" +dependencies = [ + "aleo-std", + "anyhow", + "bincode", + "criterion", + "indexmap 2.1.0", + "lru", + "once_cell", + "parking_lot", + "rand", + "rand_chacha", + "rayon", + "serde_json", + "snarkvm-algorithms", + "snarkvm-console", + "snarkvm-ledger-puzzle-epoch", +] + +[[package]] +name = "snarkvm-ledger-puzzle-epoch" +version = "0.16.19" +dependencies = [ + "anyhow", + "colored", + "indexmap 2.1.0", + "rand", + "rand_chacha", + "rayon", + "snarkvm-console", + "snarkvm-ledger-puzzle", ] [[package]] @@ -3283,9 +3297,9 @@ dependencies = [ "snarkvm-console", "snarkvm-ledger-authority", "snarkvm-ledger-block", - "snarkvm-ledger-coinbase", "snarkvm-ledger-committee", "snarkvm-ledger-narwhal-batch-certificate", + "snarkvm-ledger-puzzle", "snarkvm-ledger-test-helpers", "snarkvm-synthesizer-program", "snarkvm-synthesizer-snark", @@ -3371,6 +3385,8 @@ dependencies = [ "snarkvm-ledger-block", "snarkvm-ledger-committee", "snarkvm-ledger-narwhal-batch-header", + "snarkvm-ledger-puzzle", + "snarkvm-ledger-puzzle-epoch", "snarkvm-ledger-query", "snarkvm-ledger-store", "snarkvm-ledger-test-helpers", @@ -4105,9 +4121,9 @@ checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" [[package]] name = "wasm-bindgen-test" -version = "0.3.40" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139bd73305d50e1c1c4333210c0db43d989395b64a237bd35c10ef3832a7f70c" +checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" dependencies = [ "console_error_panic_hook", "js-sys", @@ -4119,13 +4135,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.40" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70072aebfe5da66d2716002c729a14e4aec4da0e23cc2ea66323dac541c93928" +checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" dependencies = [ "proc-macro2", "quote 1.0.35", - "syn 2.0.48", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index adf37e8061..55faaa164e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,7 +64,6 @@ members = [ "ledger", "ledger/authority", "ledger/block", - "ledger/coinbase", "ledger/committee", "ledger/narwhal", "ledger/narwhal/batch-certificate", @@ -73,6 +72,8 @@ members = [ "ledger/narwhal/subdag", "ledger/narwhal/transmission", "ledger/narwhal/transmission-id", + "ledger/puzzle", + "ledger/puzzle/epoch", "ledger/query", "ledger/store", "ledger/test-helpers", diff --git a/algorithms/src/snark/varuna/data_structures/proof.rs b/algorithms/src/snark/varuna/data_structures/proof.rs index 7ebc495714..87625cf960 100644 --- a/algorithms/src/snark/varuna/data_structures/proof.rs +++ b/algorithms/src/snark/varuna/data_structures/proof.rs @@ -255,6 +255,10 @@ impl Proof { Ok(Self { batch_sizes, commitments, evaluations, third_msg, fourth_msg, pc_proof }) } + pub fn is_hiding(&self) -> bool { + self.pc_proof.is_hiding() + } + pub fn batch_sizes(&self) -> &[usize] { &self.batch_sizes } diff --git a/circuit/environment/src/environment.rs b/circuit/environment/src/environment.rs index d8fdc5a181..541da58919 100644 --- a/circuit/environment/src/environment.rs +++ b/circuit/environment/src/environment.rs @@ -18,6 +18,7 @@ use snarkvm_fields::traits::*; use core::{fmt, hash}; +/// Attention: Do not use `Send + Sync` on this trait, as it is not thread-safe. pub trait Environment: 'static + Copy + Clone + fmt::Debug + fmt::Display + Eq + PartialEq + hash::Hash { type Network: console::Network; diff --git a/circuit/network/src/lib.rs b/circuit/network/src/lib.rs index d79538cf64..8a17f1c7fe 100644 --- a/circuit/network/src/lib.rs +++ b/circuit/network/src/lib.rs @@ -21,6 +21,7 @@ pub use v0::*; use snarkvm_circuit_collections::merkle_tree::MerklePath; use snarkvm_circuit_types::{environment::Environment, Boolean, Field, Group, Scalar}; +/// Attention: Do not use `Send + Sync` on this trait, as it is not thread-safe. pub trait Aleo: Environment { /// The maximum number of field elements in data (must not exceed u16::MAX). const MAX_DATA_SIZE_IN_FIELDS: u32 = ::MAX_DATA_SIZE_IN_FIELDS; diff --git a/console/Cargo.toml b/console/Cargo.toml index f3f029f135..a860213836 100644 --- a/console/Cargo.toml +++ b/console/Cargo.toml @@ -48,6 +48,7 @@ default = [ wasm = [ "snarkvm-console-network/wasm" ] test = [ "snarkvm-console-account/test", + "snarkvm-console-network/test", "snarkvm-console-program/test" ] account = [ "network", "snarkvm-console-account" ] diff --git a/console/network/Cargo.toml b/console/network/Cargo.toml index 5ea82ad718..551bd13eee 100644 --- a/console/network/Cargo.toml +++ b/console/network/Cargo.toml @@ -12,6 +12,7 @@ wasm = [ "snarkvm-algorithms/polycommit_wasm", "snarkvm-parameters/wasm" ] +test = [] [dependencies.snarkvm-algorithms] path = "../../algorithms" diff --git a/console/network/environment/src/lib.rs b/console/network/environment/src/lib.rs index c7c54316ac..05f748ab77 100644 --- a/console/network/environment/src/lib.rs +++ b/console/network/environment/src/lib.rs @@ -66,6 +66,7 @@ pub mod prelude { cfg_into_iter, cfg_iter, cfg_iter_mut, + cfg_keys, cfg_reduce, cfg_values, error, diff --git a/console/network/src/lib.rs b/console/network/src/lib.rs index cdd08e7227..7442adeeeb 100644 --- a/console/network/src/lib.rs +++ b/console/network/src/lib.rs @@ -90,9 +90,23 @@ pub trait Network: /// The fixed timestamp of the genesis block. const GENESIS_TIMESTAMP: i64 = 1696118400; // 2023-10-01 00:00:00 UTC /// The genesis block coinbase target. - const GENESIS_COINBASE_TARGET: u64 = (1u64 << 32).saturating_sub(1); + #[cfg(not(feature = "test"))] + const GENESIS_COINBASE_TARGET: u64 = (1u64 << 10).saturating_sub(1); + /// The genesis block coinbase target. + /// This is deliberately set to a low value (32) for testing purposes only. + #[cfg(feature = "test")] + const GENESIS_COINBASE_TARGET: u64 = (1u64 << 5).saturating_sub(1); + /// The genesis block proof target. + #[cfg(not(feature = "test"))] + const GENESIS_PROOF_TARGET: u64 = 1u64 << 8; /// The genesis block proof target. - const GENESIS_PROOF_TARGET: u64 = 1u64 << 25; + /// This is deliberately set to a low value (8) for testing purposes only. + #[cfg(feature = "test")] + const GENESIS_PROOF_TARGET: u64 = 1u64 << 3; + /// The maximum number of solutions that can be included per block as a power of 2. + const MAX_SOLUTIONS_AS_POWER_OF_TWO: u8 = 2; // 4 solutions + /// The maximum number of solutions that can be included per block. + const MAX_SOLUTIONS: usize = 1 << Self::MAX_SOLUTIONS_AS_POWER_OF_TWO; // 4 solutions /// The starting supply of Aleo credits. const STARTING_SUPPLY: u64 = 1_500_000_000_000_000; // 1.5B credits @@ -113,10 +127,6 @@ pub trait Network: const ANCHOR_TIME: u16 = 25; /// The expected time per block in seconds. const BLOCK_TIME: u16 = 10; - /// The coinbase puzzle degree. - const COINBASE_PUZZLE_DEGREE: u32 = (1 << 13) - 1; // 8,191 - /// The maximum number of solutions that can be included per block. - const MAX_SOLUTIONS: usize = 1 << 8; // 256 solutions /// The number of blocks per epoch. const NUM_BLOCKS_PER_EPOCH: u32 = 3600 / Self::BLOCK_TIME as u32; // 360 blocks == ~1 hour diff --git a/ledger/Cargo.toml b/ledger/Cargo.toml index 6451d7b364..be34ffd2b4 100644 --- a/ledger/Cargo.toml +++ b/ledger/Cargo.toml @@ -39,14 +39,14 @@ serial = [ "console/serial", "ledger-authority/serial", "ledger-block/serial", - "ledger-coinbase/serial", "ledger-committee/serial", "ledger-narwhal/serial", + "ledger-puzzle/serial", "ledger-query/serial", "ledger-store/serial", "synthesizer/serial" ] -test = [ "ledger-block/test", "ledger-store/test" ] +test = [ "console/test", "ledger-block/test", "ledger-store/test" ] test-helpers = [ "ledger-test-helpers", "ledger-committee/test-helpers", @@ -69,11 +69,6 @@ package = "snarkvm-ledger-block" path = "./block" version = "=0.16.19" -[dependencies.ledger-coinbase] -package = "snarkvm-ledger-coinbase" -path = "./coinbase" -version = "=0.16.19" - [dependencies.ledger-committee] package = "snarkvm-ledger-committee" path = "./committee" @@ -84,6 +79,11 @@ package = "snarkvm-ledger-narwhal" path = "./narwhal" version = "=0.16.19" +[dependencies.ledger-puzzle] +package = "snarkvm-ledger-puzzle" +path = "puzzle" +version = "=0.16.19" + [dependencies.ledger-query] package = "snarkvm-ledger-query" path = "./query" diff --git a/ledger/block/Cargo.toml b/ledger/block/Cargo.toml index 7174c09fbc..de289f8bd7 100644 --- a/ledger/block/Cargo.toml +++ b/ledger/block/Cargo.toml @@ -21,7 +21,7 @@ default = [ "indexmap/rayon", "rayon" ] serial = [ "console/serial", "ledger-authority/serial", - "ledger-coinbase/serial", + "ledger-puzzle/serial", "ledger-committee/serial", "synthesizer-program/serial", "synthesizer-snark/serial" @@ -29,7 +29,7 @@ serial = [ wasm = [ "console/wasm", "ledger-authority/wasm", - "ledger-coinbase/wasm", + "ledger-puzzle/wasm", "ledger-committee/wasm", "synthesizer-program/wasm", "synthesizer-snark/wasm" @@ -46,11 +46,6 @@ package = "snarkvm-ledger-authority" path = "../authority" version = "=0.16.19" -[dependencies.ledger-coinbase] -package = "snarkvm-ledger-coinbase" -path = "../../ledger/coinbase" -version = "=0.16.19" - [dependencies.ledger-committee] package = "snarkvm-ledger-committee" path = "../../ledger/committee" @@ -71,6 +66,11 @@ package = "snarkvm-ledger-narwhal-transmission-id" path = "../narwhal/transmission-id" version = "=0.16.19" +[dependencies.ledger-puzzle] +package = "snarkvm-ledger-puzzle" +path = "../puzzle" +version = "=0.16.19" + [dependencies.synthesizer-program] package = "snarkvm-synthesizer-program" path = "../../synthesizer/program" diff --git a/ledger/block/src/helpers/target.rs b/ledger/block/src/helpers/target.rs index 40b2c9f461..57c7851377 100644 --- a/ledger/block/src/helpers/target.rs +++ b/ledger/block/src/helpers/target.rs @@ -18,7 +18,7 @@ use console::prelude::{ensure, Result}; pub const MAX_COINBASE_REWARD: u64 = 190_258_739; // Coinbase reward at block 1. /// Calculate the block reward, given the total supply, block time, coinbase reward, and transaction fees. -/// R_staking = floor((0.05 * S) / H_Y1) + CR / 2 + TX_F. +/// R_staking = floor((0.05 * S) / H_Y1) + CR / 3 + TX_F. /// S = Total supply. /// H_Y1 = Expected block height at year 1. /// CR = Coinbase reward. @@ -31,13 +31,13 @@ pub const fn block_reward(total_supply: u64, block_time: u16, coinbase_reward: u // Compute the block reward: (0.05 * S) / H_Y1. let block_reward = annual_reward / block_height_at_year_1 as u64; // Return the sum of the block reward, coinbase reward, and transaction fees. - block_reward + (coinbase_reward / 2) + transaction_fees + block_reward + (coinbase_reward / 3) + transaction_fees } /// Calculate the puzzle reward, given the coinbase reward. pub const fn puzzle_reward(coinbase_reward: u64) -> u64 { - // Return the coinbase reward divided by 2. - coinbase_reward / 2 + // Return the coinbase reward multiplied by 2 and divided by 3. + coinbase_reward.saturating_mul(2).saturating_div(3) } /// Calculates the coinbase reward for a given block. @@ -132,8 +132,11 @@ pub fn coinbase_target( } /// Calculate the minimum proof target for the given coinbase target. -pub fn proof_target(coinbase_target: u64, genesis_proof_target: u64) -> u64 { - coinbase_target.checked_shr(7).map(|target| target.saturating_add(1)).unwrap_or(genesis_proof_target) +pub fn proof_target(coinbase_target: u64, genesis_proof_target: u64, max_solutions_as_power_of_two: u8) -> u64 { + coinbase_target + .checked_shr(max_solutions_as_power_of_two as u32) + .map(|target| target.saturating_add(1)) + .unwrap_or(genesis_proof_target) } /// Retarget algorithm using fixed point arithmetic from https://www.reference.cash/protocol/forks/2020-11-15-asert. @@ -543,7 +546,11 @@ mod tests { fn test_new_targets(rng: &mut TestRng, minimum_coinbase_target: u64) { let previous_coinbase_target: u64 = rng.gen_range(minimum_coinbase_target..u64::MAX); - let previous_prover_target = proof_target(previous_coinbase_target, CurrentNetwork::GENESIS_PROOF_TARGET); + let previous_prover_target = proof_target( + previous_coinbase_target, + CurrentNetwork::GENESIS_PROOF_TARGET, + CurrentNetwork::MAX_SOLUTIONS_AS_POWER_OF_TWO, + ); let previous_timestamp = rng.gen(); @@ -558,7 +565,11 @@ mod tests { CurrentNetwork::GENESIS_COINBASE_TARGET, ) .unwrap(); - let new_prover_target = proof_target(new_coinbase_target, CurrentNetwork::GENESIS_PROOF_TARGET); + let new_prover_target = proof_target( + new_coinbase_target, + CurrentNetwork::GENESIS_PROOF_TARGET, + CurrentNetwork::MAX_SOLUTIONS_AS_POWER_OF_TWO, + ); assert_eq!(new_coinbase_target, previous_coinbase_target); assert_eq!(new_prover_target, previous_prover_target); @@ -573,7 +584,11 @@ mod tests { CurrentNetwork::GENESIS_COINBASE_TARGET, ) .unwrap(); - let new_prover_target = proof_target(new_coinbase_target, CurrentNetwork::GENESIS_PROOF_TARGET); + let new_prover_target = proof_target( + new_coinbase_target, + CurrentNetwork::GENESIS_PROOF_TARGET, + CurrentNetwork::MAX_SOLUTIONS_AS_POWER_OF_TWO, + ); assert!(new_coinbase_target < previous_coinbase_target); assert!(new_prover_target < previous_prover_target); @@ -588,7 +603,11 @@ mod tests { CurrentNetwork::GENESIS_COINBASE_TARGET, ) .unwrap(); - let new_prover_target = proof_target(new_coinbase_target, CurrentNetwork::GENESIS_PROOF_TARGET); + let new_prover_target = proof_target( + new_coinbase_target, + CurrentNetwork::GENESIS_PROOF_TARGET, + CurrentNetwork::MAX_SOLUTIONS_AS_POWER_OF_TWO, + ); assert!(new_coinbase_target > previous_coinbase_target); assert!(new_prover_target > previous_prover_target); diff --git a/ledger/block/src/lib.rs b/ledger/block/src/lib.rs index 596de24a6c..e8b86bb420 100644 --- a/ledger/block/src/lib.rs +++ b/ledger/block/src/lib.rs @@ -54,10 +54,10 @@ use console::{ types::{Field, Group, U64}, }; use ledger_authority::Authority; -use ledger_coinbase::{CoinbaseSolution, ProverSolution, PuzzleCommitment}; use ledger_committee::Committee; use ledger_narwhal_subdag::Subdag; use ledger_narwhal_transmission_id::TransmissionID; +use ledger_puzzle::{PuzzleSolutions, Solution, SolutionID}; #[derive(Clone, PartialEq, Eq)] pub struct Block { @@ -74,7 +74,7 @@ pub struct Block { /// The solutions in the block. solutions: Solutions, /// The aborted solution IDs in this block. - aborted_solution_ids: Vec>, + aborted_solution_ids: Vec>, /// The transactions in this block. transactions: Transactions, /// The aborted transaction IDs in this block. @@ -90,7 +90,7 @@ impl Block { header: Header, ratifications: Ratifications, solutions: Solutions, - aborted_solution_ids: Vec>, + aborted_solution_ids: Vec>, transactions: Transactions, aborted_transaction_ids: Vec, rng: &mut R, @@ -120,7 +120,7 @@ impl Block { subdag: Subdag, ratifications: Ratifications, solutions: Solutions, - aborted_solution_ids: Vec>, + aborted_solution_ids: Vec>, transactions: Transactions, aborted_transaction_ids: Vec, ) -> Result { @@ -147,7 +147,7 @@ impl Block { authority: Authority, ratifications: Ratifications, solutions: Solutions, - aborted_solution_ids: Vec>, + aborted_solution_ids: Vec>, transactions: Transactions, aborted_transaction_ids: Vec, ) -> Result { @@ -235,7 +235,7 @@ impl Block { authority: Authority, ratifications: Ratifications, solutions: Solutions, - aborted_solution_ids: Vec>, + aborted_solution_ids: Vec>, transactions: Transactions, aborted_transaction_ids: Vec, ) -> Result { @@ -281,7 +281,7 @@ impl Block { } /// Returns the aborted solution IDs in this block. - pub const fn aborted_solution_ids(&self) -> &Vec> { + pub const fn aborted_solution_ids(&self) -> &Vec> { &self.aborted_solution_ids } @@ -407,8 +407,8 @@ impl Block { impl Block { /// Returns the solution with the given solution ID, if it exists. - pub fn get_solution(&self, puzzle_commitment: &PuzzleCommitment) -> Option<&ProverSolution> { - self.solutions.as_ref().and_then(|solution| solution.get_solution(puzzle_commitment)) + pub fn get_solution(&self, solution_id: &SolutionID) -> Option<&Solution> { + self.solutions.as_ref().and_then(|solution| solution.get_solution(solution_id)) } /// Returns the transaction with the given transaction ID, if it exists. @@ -460,9 +460,9 @@ impl Block { } impl Block { - /// Returns the puzzle commitments in this block. - pub fn puzzle_commitments(&self) -> Option>> { - self.solutions.as_ref().map(|solution| solution.puzzle_commitments()) + /// Returns the solution IDs in this block. + pub fn solution_ids(&self) -> Option>> { + self.solutions.as_ref().map(|solution| solution.solution_ids()) } /// Returns an iterator over the transaction IDs, for all transactions in `self`. diff --git a/ledger/block/src/solutions/bytes.rs b/ledger/block/src/solutions/bytes.rs index aa92c63114..17dd27d088 100644 --- a/ledger/block/src/solutions/bytes.rs +++ b/ledger/block/src/solutions/bytes.rs @@ -34,7 +34,7 @@ impl FromBytes for Solutions { } 1 => { // Read the solutions. - let solutions: CoinbaseSolution = FromBytes::read_le(&mut reader)?; + let solutions: PuzzleSolutions = FromBytes::read_le(&mut reader)?; // Return the solutions. Self::new(solutions).map_err(error) } diff --git a/ledger/block/src/solutions/mod.rs b/ledger/block/src/solutions/mod.rs index 7cdbd1d8a8..9934e4e1b6 100644 --- a/ledger/block/src/solutions/mod.rs +++ b/ledger/block/src/solutions/mod.rs @@ -18,14 +18,14 @@ mod serialize; mod string; use console::{network::prelude::*, types::Field}; -use ledger_coinbase::{CoinbaseSolution, PuzzleCommitment}; use ledger_committee::Committee; use ledger_narwhal_batch_header::BatchHeader; +use ledger_puzzle::{PuzzleSolutions, SolutionID}; #[derive(Clone, Eq, PartialEq)] pub struct Solutions { - /// The prover solutions for the coinbase puzzle. - solutions: Option>, + /// The prover solutions for the puzzle. + solutions: Option>, } impl Solutions { @@ -35,9 +35,9 @@ impl Solutions { * Committee::::MAX_COMMITTEE_SIZE as usize; } -impl From>> for Solutions { +impl From>> for Solutions { /// Initializes a new instance of the solutions. - fn from(solutions: Option>) -> Self { + fn from(solutions: Option>) -> Self { // Return the solutions. Self { solutions } } @@ -45,7 +45,7 @@ impl From>> for Solutions { impl Solutions { /// Initializes a new instance of the solutions. - pub fn new(solutions: CoinbaseSolution) -> Result { + pub fn new(solutions: PuzzleSolutions) -> Result { // Return the solutions. Ok(Self { solutions: Some(solutions) }) } @@ -66,26 +66,16 @@ impl Solutions { impl Solutions { /// Returns an iterator over the solution IDs. - pub fn solution_ids<'a>(&'a self) -> Box> + 'a> { + pub fn solution_ids<'a>(&'a self) -> Box> + 'a> { match &self.solutions { Some(solutions) => Box::new(solutions.keys()), - None => Box::new(std::iter::empty::<&PuzzleCommitment>()), - } - } -} - -impl Solutions { - /// Returns the combined sum of the prover solutions. - pub fn to_combined_proof_target(&self) -> Result { - match &self.solutions { - Some(solutions) => solutions.to_combined_proof_target(), - None => Ok(0), + None => Box::new(std::iter::empty::<&SolutionID>()), } } } impl Deref for Solutions { - type Target = Option>; + type Target = Option>; /// Returns the solutions. fn deref(&self) -> &Self::Target { diff --git a/ledger/block/src/solutions/serialize.rs b/ledger/block/src/solutions/serialize.rs index 712d624e91..8418b3e49e 100644 --- a/ledger/block/src/solutions/serialize.rs +++ b/ledger/block/src/solutions/serialize.rs @@ -63,23 +63,19 @@ impl<'de, N: Network> Deserialize<'de> for Solutions { pub(super) mod tests { use super::*; use console::account::{Address, PrivateKey}; - use ledger_coinbase::{PartialSolution, ProverSolution, PuzzleCommitment, PuzzleProof}; + use ledger_puzzle::Solution; type CurrentNetwork = console::network::MainnetV0; pub(crate) fn sample_solutions(rng: &mut TestRng) -> Solutions { // Sample a new solutions. - let mut prover_solutions = vec![]; - for _ in 0..rng.gen_range(1..10) { + let mut solutions = vec![]; + for _ in 0..rng.gen_range(1..=CurrentNetwork::MAX_SOLUTIONS) { let private_key = PrivateKey::::new(rng).unwrap(); let address = Address::try_from(private_key).unwrap(); - - let commitment = PuzzleCommitment::from_g1_affine(rng.gen()); - let partial_solution = PartialSolution::new(address, u64::rand(rng), commitment); - let proof = PuzzleProof:: { w: rng.gen(), random_v: None }; - prover_solutions.push(ProverSolution::new(partial_solution, proof)); + solutions.push(Solution::new(rng.gen(), address, u64::rand(rng)).unwrap()); } - Solutions::new(CoinbaseSolution::new(prover_solutions).unwrap()).unwrap() + Solutions::new(PuzzleSolutions::new(solutions).unwrap()).unwrap() } #[test] diff --git a/ledger/block/src/verify.rs b/ledger/block/src/verify.rs index 74a3e86e2e..71f4b933b5 100644 --- a/ledger/block/src/verify.rs +++ b/ledger/block/src/verify.rs @@ -16,7 +16,7 @@ #![allow(clippy::type_complexity)] use super::*; -use ledger_coinbase::{CoinbasePuzzle, EpochChallenge}; +use ledger_puzzle::Puzzle; use synthesizer_program::FinalizeOperation; use std::collections::HashSet; @@ -32,11 +32,11 @@ impl Block { current_state_root: N::StateRoot, previous_committee_lookback: &Committee, current_committee_lookback: &Committee, - current_puzzle: &CoinbasePuzzle, - current_epoch_challenge: &EpochChallenge, + current_puzzle: &Puzzle, + current_epoch_hash: N::BlockHash, current_timestamp: i64, ratified_finalize_operations: Vec>, - ) -> Result<(Vec>, Vec)> { + ) -> Result<(Vec>, Vec)> { // Ensure the block hash is correct. self.verify_hash(previous_block.height(), previous_block.hash())?; @@ -64,7 +64,7 @@ impl Block { expected_last_coinbase_timestamp, expected_block_reward, expected_puzzle_reward, - ) = self.verify_solutions(previous_block, current_puzzle, current_epoch_challenge)?; + ) = self.verify_solutions(previous_block, current_puzzle, current_epoch_hash)?; // Ensure the block ratifications are correct. self.verify_ratifications(expected_block_reward, expected_puzzle_reward)?; @@ -151,7 +151,7 @@ impl Block { previous_height: u32, previous_committee_lookback: &Committee, current_committee_lookback: &Committee, - ) -> Result<(u64, u32, i64, Vec>, Vec)> { + ) -> Result<(u64, u32, i64, Vec>, Vec)> { // Note: Do not remove this. This ensures that all blocks after genesis are quorum blocks. #[cfg(not(any(test, feature = "test")))] ensure!(self.authority.is_quorum(), "The next block must be a quorum block"); @@ -304,8 +304,8 @@ impl Block { fn verify_solutions( &self, previous_block: &Block, - current_puzzle: &CoinbasePuzzle, - current_epoch_challenge: &EpochChallenge, + current_puzzle: &Puzzle, + current_epoch_hash: N::BlockHash, ) -> Result<(u128, u128, u64, u64, u64, i64, u64, u64)> { let height = self.height(); let timestamp = self.timestamp(); @@ -334,7 +334,7 @@ impl Block { if has_duplicates( self.solutions .as_ref() - .map(CoinbaseSolution::puzzle_commitments) + .map(PuzzleSolutions::solution_ids) .into_iter() .flatten() .chain(self.aborted_solution_ids()), @@ -343,13 +343,16 @@ impl Block { } // Compute the combined proof target. - let combined_proof_target = self.solutions.to_combined_proof_target()?; + let combined_proof_target = match self.solutions.deref() { + Some(solutions) => current_puzzle.get_combined_proof_target(solutions)?, + None => 0u128, + }; let (expected_cumulative_proof_target, is_coinbase_target_reached) = match self.solutions.deref() { Some(coinbase) => { // Ensure the puzzle proof is valid. if let Err(e) = - current_puzzle.check_solutions(coinbase, current_epoch_challenge, previous_block.proof_target()) + current_puzzle.check_solutions(coinbase, current_epoch_hash, previous_block.proof_target()) { bail!("Block {height} contains an invalid puzzle proof - {e}"); } @@ -397,7 +400,8 @@ impl Block { N::GENESIS_COINBASE_TARGET, )?; // Ensure the proof target is correct. - let expected_proof_target = proof_target(expected_coinbase_target, N::GENESIS_PROOF_TARGET); + let expected_proof_target = + proof_target(expected_coinbase_target, N::GENESIS_PROOF_TARGET, N::MAX_SOLUTIONS_AS_POWER_OF_TWO); // Determine the expected last coinbase target. let expected_last_coinbase_target = match is_coinbase_target_reached { @@ -565,11 +569,11 @@ impl Block { /// Returns the IDs of the transactions and solutions that should already exist in the ledger. pub(super) fn check_subdag_transmissions( subdag: &Subdag, - solutions: &Option>, - aborted_solution_ids: &[PuzzleCommitment], + solutions: &Option>, + aborted_solution_ids: &[SolutionID], transactions: &Transactions, aborted_transaction_ids: &[N::TransactionID], - ) -> Result<(Vec>, Vec)> { + ) -> Result<(Vec>, Vec)> { // Prepare an iterator over the solution IDs. let mut solutions = solutions.as_ref().map(|s| s.deref()).into_iter().flatten().peekable(); // Prepare an iterator over the unconfirmed transaction IDs. @@ -599,7 +603,7 @@ impl Block { TransmissionID::Solution(solution_id) => { match solutions.peek() { // Check the next solution matches the expected solution ID. - Some((_, solution)) if solution.commitment() == *solution_id => { + Some((_, solution)) if solution.id() == *solution_id => { // Increment the solution iterator. solutions.next(); } diff --git a/ledger/coinbase/benches/coinbase_puzzle.rs b/ledger/coinbase/benches/coinbase_puzzle.rs deleted file mode 100644 index d48757930a..0000000000 --- a/ledger/coinbase/benches/coinbase_puzzle.rs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (C) 2019-2023 Aleo Systems Inc. -// This file is part of the snarkVM library. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#![allow(clippy::single_element_loop)] - -#[macro_use] -extern crate criterion; - -use console::{ - account::*, - network::{MainnetV0, Network}, -}; -use snarkvm_ledger_coinbase::{CoinbasePuzzle, CoinbaseSolution, EpochChallenge, PuzzleConfig}; - -use criterion::Criterion; -use rand::{self, thread_rng, CryptoRng, RngCore}; - -type CoinbasePuzzleInst = CoinbasePuzzle; - -fn sample_inputs( - degree: u32, - rng: &mut (impl CryptoRng + RngCore), -) -> (EpochChallenge, Address, u64) { - let epoch_challenge = sample_epoch_challenge(degree, rng); - let (address, nonce) = sample_address_and_nonce(rng); - (epoch_challenge, address, nonce) -} - -fn sample_epoch_challenge(degree: u32, rng: &mut (impl CryptoRng + RngCore)) -> EpochChallenge { - EpochChallenge::new(rng.next_u32(), Default::default(), degree).unwrap() -} - -fn sample_address_and_nonce(rng: &mut (impl CryptoRng + RngCore)) -> (Address, u64) { - let private_key = PrivateKey::new(rng).unwrap(); - let address = Address::try_from(private_key).unwrap(); - let nonce = rng.next_u64(); - (address, nonce) -} - -#[cfg(feature = "setup")] -fn coinbase_puzzle_trim(c: &mut Criterion) { - let max_degree = 1 << 15; - let max_config = PuzzleConfig { degree: max_degree }; - let universal_srs = CoinbasePuzzle::::setup(max_config).unwrap(); - - for degree in [(1 << 13) - 1] { - let config = PuzzleConfig { degree }; - - c.bench_function(&format!("CoinbasePuzzle::Trim 2^{}", ((degree + 1) as f64).log2()), |b| { - b.iter(|| CoinbasePuzzleInst::trim(&universal_srs, config).unwrap()) - }); - } -} - -#[cfg(feature = "setup")] -fn coinbase_puzzle_prove(c: &mut Criterion) { - let rng = &mut thread_rng(); - - let max_degree = 1 << 15; - let max_config = PuzzleConfig { degree: max_degree }; - let universal_srs = CoinbasePuzzle::::setup(max_config).unwrap(); - - for degree in [(1 << 13) - 1] { - let config = PuzzleConfig { degree }; - let puzzle = CoinbasePuzzleInst::trim(&universal_srs, config).unwrap(); - - c.bench_function(&format!("CoinbasePuzzle::Prove 2^{}", ((degree + 1) as f64).log2()), |b| { - let (epoch_challenge, address, nonce) = sample_inputs(degree, rng); - b.iter(|| puzzle.prove(&epoch_challenge, address, nonce, None).unwrap()) - }); - } -} - -#[cfg(feature = "setup")] -fn coinbase_puzzle_verify(c: &mut Criterion) { - let rng = &mut thread_rng(); - - let max_degree = 1 << 15; - let max_config = PuzzleConfig { degree: max_degree }; - let universal_srs = CoinbasePuzzle::::setup(max_config).unwrap(); - - for degree in [(1 << 13) - 1] { - let config = PuzzleConfig { degree }; - let puzzle = CoinbasePuzzleInst::trim(&universal_srs, config).unwrap(); - let epoch_challenge = sample_epoch_challenge(degree, rng); - - for batch_size in [10, 100, ::MAX_SOLUTIONS] { - let solutions = (0..batch_size) - .map(|_| { - let (address, nonce) = sample_address_and_nonce(rng); - puzzle.prove(&epoch_challenge, address, nonce, None).unwrap() - }) - .collect::>(); - let solutions = CoinbaseSolution::new(solutions).unwrap(); - - c.bench_function( - &format!("CoinbasePuzzle::Verify {batch_size} of 2^{}", ((degree + 1) as f64).log2()), - |b| b.iter(|| puzzle.check_solutions(&solutions, &epoch_challenge, 0u64).unwrap()), - ); - } - } -} - -criterion_group! { - name = coinbase_puzzle; - config = Criterion::default().sample_size(10); - targets = coinbase_puzzle_trim, coinbase_puzzle_prove, coinbase_puzzle_verify, -} - -criterion_main!(coinbase_puzzle); diff --git a/ledger/coinbase/src/hash.rs b/ledger/coinbase/src/hash.rs deleted file mode 100644 index cf1339ca3a..0000000000 --- a/ledger/coinbase/src/hash.rs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (C) 2019-2023 Aleo Systems Inc. -// This file is part of the snarkVM library. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use console::prelude::{bail, cfg_into_iter, ensure, Result, Zero}; -use snarkvm_algorithms::{fft::DensePolynomial, polycommit::kzg10::KZGCommitment}; -use snarkvm_curves::PairingEngine; -use snarkvm_fields::PrimeField; -use snarkvm_utilities::CanonicalSerialize; - -use blake2::Digest; - -#[cfg(not(feature = "serial"))] -use rayon::prelude::*; - -pub fn hash_to_coefficients(input: &[u8], num_coefficients: u32) -> Vec { - // Hash the input. - let hash = blake2::Blake2s256::digest(input); - // Hash with a counter and return the coefficients. - cfg_into_iter!(0..num_coefficients) - .map(|counter| { - let mut input_with_counter = [0u8; 36]; - input_with_counter[..32].copy_from_slice(&hash); - input_with_counter[32..].copy_from_slice(&counter.to_le_bytes()); - F::from_bytes_le_mod_order(&blake2::Blake2b512::digest(input_with_counter)) - }) - .collect() -} - -pub fn hash_to_polynomial(input: &[u8], degree: u32) -> DensePolynomial { - // Hash the input into coefficients. - let coefficients = hash_to_coefficients(input, degree + 1); - // Construct the polynomial from the coefficients. - DensePolynomial::from_coefficients_vec(coefficients) -} - -pub fn hash_commitment(commitment: &KZGCommitment) -> Result { - // Convert the commitment into bytes. - let mut bytes = Vec::with_capacity(96); - commitment.serialize_uncompressed(&mut bytes)?; - ensure!(bytes.len() == 96, "Invalid commitment byte length for hashing"); - - // Return the hash of the commitment. - Ok(E::Fr::from_bytes_le_mod_order(&blake2::Blake2b512::digest(&bytes))) -} - -pub fn hash_commitments( - commitments: impl ExactSizeIterator>, -) -> Result> { - // Retrieve the number of commitments. - let num_commitments = match u32::try_from(commitments.len()) { - Ok(num_commitments) => num_commitments, - Err(_) => bail!("Cannot hash more than 2^32 commitments: found {}", commitments.len()), - }; - ensure!(!num_commitments.is_zero(), "No commitments provided for hashing"); - - // Convert the commitments into bytes. - let bytes = commitments - .flat_map(|commitment| { - let mut bytes = Vec::with_capacity(96); - commitment.serialize_uncompressed(&mut bytes).unwrap(); - bytes - }) - .collect::>(); - ensure!(bytes.len() == 96 * usize::try_from(num_commitments)?, "Invalid commitment byte length for hashing"); - - // Hash the commitment bytes into coefficients. - Ok(hash_to_coefficients(&bytes, num_commitments + 1)) -} diff --git a/ledger/coinbase/src/helpers/coinbase_solution/mod.rs b/ledger/coinbase/src/helpers/coinbase_solution/mod.rs deleted file mode 100644 index a735607a53..0000000000 --- a/ledger/coinbase/src/helpers/coinbase_solution/mod.rs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (C) 2019-2023 Aleo Systems Inc. -// This file is part of the snarkVM library. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -mod bytes; -mod serialize; -mod string; - -use super::*; - -use indexmap::IndexMap; - -/// The coinbase puzzle solution is composed of individual prover solutions. -#[derive(Clone, Eq, PartialEq)] -pub struct CoinbaseSolution { - /// The prover solutions for the coinbase puzzle. - solutions: IndexMap, ProverSolution>, -} - -impl CoinbaseSolution { - /// Initializes a new instance of the solutions. - pub fn new(solutions: Vec>) -> Result { - // Ensure the solutions are not empty. - ensure!(!solutions.is_empty(), "There are no solutions to verify for the coinbase puzzle"); - // Ensure the number of partial solutions does not exceed `MAX_PROVER_SOLUTIONS`. - if solutions.len() > N::MAX_SOLUTIONS { - bail!( - "The solutions exceed the allowed number of partial solutions. ({} > {})", - solutions.len(), - N::MAX_SOLUTIONS - ); - } - // Ensure the puzzle commitments are unique. - if has_duplicates(solutions.iter().map(|s| s.commitment())) { - bail!("The solutions contain duplicate puzzle commitments"); - } - // Return the solutions. - Ok(Self { solutions: solutions.into_iter().map(|solution| (solution.commitment(), solution)).collect() }) - } - - /// Returns the puzzle commitments. - pub fn puzzle_commitments(&self) -> impl '_ + Iterator> { - self.solutions.keys() - } - - /// Returns the number of solutions. - pub fn len(&self) -> usize { - self.solutions.len() - } - - /// Returns `true` if there are no solutions. - pub fn is_empty(&self) -> bool { - self.solutions.is_empty() - } - - /// Returns the prover solution for the puzzle commitment. - pub fn get_solution(&self, puzzle_commitment: &PuzzleCommitment) -> Option<&ProverSolution> { - self.solutions.get(puzzle_commitment) - } - - /// Returns the combined sum of the prover solutions. - pub fn to_combined_proof_target(&self) -> Result { - // Compute the combined proof target as a u128. - self.solutions.values().try_fold(0u128, |combined, solution| { - combined.checked_add(solution.to_target()? as u128).ok_or_else(|| anyhow!("Combined target overflowed")) - }) - } - - /// Returns the accumulator challenge point. - pub fn to_accumulator_point(&self) -> Result> { - let mut challenge_points = hash_commitments(self.solutions.keys().map(|pcm| **pcm))?; - ensure!(challenge_points.len() == self.solutions.len() + 1, "Invalid number of challenge points"); - - // Pop the last challenge point as the accumulator challenge point. - match challenge_points.pop() { - Some(point) => Ok(Field::new(point)), - None => bail!("Missing the accumulator challenge point"), - } - } -} - -impl Deref for CoinbaseSolution { - type Target = IndexMap, ProverSolution>; - - fn deref(&self) -> &Self::Target { - &self.solutions - } -} diff --git a/ledger/coinbase/src/helpers/epoch_challenge/bytes.rs b/ledger/coinbase/src/helpers/epoch_challenge/bytes.rs deleted file mode 100644 index 45398c0956..0000000000 --- a/ledger/coinbase/src/helpers/epoch_challenge/bytes.rs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (C) 2019-2023 Aleo Systems Inc. -// This file is part of the snarkVM library. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::*; - -impl FromBytes for EpochChallenge { - /// Reads the epoch challenge from a buffer. - fn read_le(mut reader: R) -> IoResult { - // Read the epoch number. - let epoch_number = FromBytes::read_le(&mut reader)?; - // Read the epoch block hash. - let epoch_block_hash = FromBytes::read_le(&mut reader)?; - // Read the epoch degree. - let degree = FromBytes::read_le(&mut reader)?; - // Return the epoch challenge. - Self::new(epoch_number, epoch_block_hash, degree).map_err(|e| error(e.to_string())) - } -} - -impl ToBytes for EpochChallenge { - /// Writes the epoch challenge to a buffer. - fn write_le(&self, mut writer: W) -> IoResult<()> { - // Write the epoch number. - self.epoch_number.write_le(&mut writer)?; - // Write the epoch block hash. - self.epoch_block_hash.write_le(&mut writer)?; - // Write the epoch degree. - self.degree().write_le(&mut writer) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use console::network::MainnetV0; - - use rand::RngCore; - - type CurrentNetwork = MainnetV0; - - const ITERATIONS: usize = 100; - - #[test] - fn test_bytes() { - let mut rng = TestRng::default(); - - for _ in 0..ITERATIONS { - // Sample a new epoch challenge. - let degree: u16 = rng.gen(); // Bound the maximal test degree to 2^16. - let expected = EpochChallenge::::new(rng.next_u32(), rng.gen(), degree as u32).unwrap(); - - // Check the byte representation. - let expected_bytes = expected.to_bytes_le().unwrap(); - let candidate = EpochChallenge::read_le(&expected_bytes[..]).unwrap(); - assert_eq!(expected.epoch_number(), candidate.epoch_number()); - assert_eq!(expected.epoch_block_hash(), candidate.epoch_block_hash()); - assert_eq!(expected.degree(), candidate.degree()); - assert_eq!(expected, candidate); - - assert!(EpochChallenge::::read_le(&expected_bytes[1..]).is_err()); - } - } -} diff --git a/ledger/coinbase/src/helpers/epoch_challenge/mod.rs b/ledger/coinbase/src/helpers/epoch_challenge/mod.rs deleted file mode 100644 index 9a9860cec8..0000000000 --- a/ledger/coinbase/src/helpers/epoch_challenge/mod.rs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (C) 2019-2023 Aleo Systems Inc. -// This file is part of the snarkVM library. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -mod bytes; - -use super::*; -use crate::hash_to_polynomial; -use snarkvm_algorithms::fft::Evaluations as EvaluationsOnDomain; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct EpochChallenge { - /// The epoch number. - epoch_number: u32, - /// The epoch block hash, defined as the block hash right before the epoch updated. - epoch_block_hash: N::BlockHash, - /// The epoch polynomial. - epoch_polynomial: DensePolynomial<::Fr>, - /// The evaluations of the epoch polynomial over the product domain. - epoch_polynomial_evaluations: EvaluationsOnDomain<::Fr>, -} - -impl EpochChallenge { - /// Initializes a new epoch challenge. - pub fn new(epoch_number: u32, epoch_block_hash: N::BlockHash, degree: u32) -> Result { - // Construct the 'input' as '( epoch_number || epoch_block_hash )' - let mut input = vec![]; - epoch_number.write_le(&mut input)?; - epoch_block_hash.write_le(&mut input)?; - - let product_domain = CoinbasePuzzle::::product_domain(degree)?; - - let epoch_polynomial = hash_to_polynomial::<::Fr>(&input, degree); - ensure!(u32::try_from(epoch_polynomial.degree()).is_ok(), "Degree is too large"); - - let epoch_polynomial_evaluations = epoch_polynomial.evaluate_over_domain_by_ref(product_domain); - // Returns the epoch challenge. - Ok(EpochChallenge { epoch_number, epoch_block_hash, epoch_polynomial, epoch_polynomial_evaluations }) - } - - /// Returns the epoch number for the solution. - pub const fn epoch_number(&self) -> u32 { - self.epoch_number - } - - /// Returns the epoch block hash for the solution. - pub const fn epoch_block_hash(&self) -> N::BlockHash { - self.epoch_block_hash - } - - /// Returns the epoch polynomial for the solution. - pub const fn epoch_polynomial(&self) -> &DensePolynomial<::Fr> { - &self.epoch_polynomial - } - - /// Returns the evaluations of the epoch polynomial over the product domain. - pub const fn epoch_polynomial_evaluations(&self) -> &EvaluationsOnDomain<::Fr> { - &self.epoch_polynomial_evaluations - } - - /// Returns the number of coefficients of the epoch polynomial. - pub fn degree(&self) -> u32 { - // Convert the degree into a u32. - // The `unwrap` is guaranteed to succeed as we check the degree is less - // than `u32::MAX` in `new`. - u32::try_from(self.epoch_polynomial.degree()).unwrap() - } - - /// Returns the number of coefficients of the epoch polynomial. - pub fn num_coefficients(&self) -> Result { - let degree = self.degree(); - degree.checked_add(1).ok_or_else(|| anyhow!("Epoch polynomial degree ({degree} + 1) overflows")) - } -} diff --git a/ledger/coinbase/src/helpers/mod.rs b/ledger/coinbase/src/helpers/mod.rs deleted file mode 100644 index 10926b3366..0000000000 --- a/ledger/coinbase/src/helpers/mod.rs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (C) 2019-2023 Aleo Systems Inc. -// This file is part of the snarkVM library. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -mod coinbase_solution; -pub use coinbase_solution::*; - -mod epoch_challenge; -pub use epoch_challenge::*; - -mod partial_solution; -pub use partial_solution::*; - -mod prover_solution; -pub use prover_solution::*; - -mod puzzle_commitment; -pub use puzzle_commitment::*; - -use crate::{hash_commitment, hash_commitments, CoinbasePuzzle}; -use console::{account::Address, prelude::*, types::Field}; -use snarkvm_algorithms::{ - fft::{domain::FFTPrecomputation, DensePolynomial, EvaluationDomain}, - polycommit::kzg10::{KZGCommitment, KZGProof, LagrangeBasis, VerifierKey, KZG10}, -}; -use snarkvm_curves::PairingEngine; -use snarkvm_utilities::{FromBytes, ToBytes}; - -use anyhow::Result; -use std::{ - borrow::Cow, - io::{Read, Result as IoResult, Write}, -}; - -/// The proof of opening the polynomial, for the solution. -pub type PuzzleProof = KZGProof<::PairingCurve>; - -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct PuzzleConfig { - /// The maximum degree of the polynomial. - pub degree: u32, -} - -pub type CoinbaseVerifyingKey = VerifierKey<::PairingCurve>; - -#[derive(Clone, Debug)] -pub struct CoinbaseProvingKey { - /// The key used to commit to polynomials in Lagrange basis. - pub lagrange_basis_at_beta_g: Vec<::G1Affine>, - /// Domain used to compute the product of the epoch polynomial and the prover polynomial. - pub product_domain: EvaluationDomain<::Fr>, - /// Precomputation to speed up FFTs. - pub fft_precomputation: FFTPrecomputation<::Fr>, - /// Elements of the product domain. - pub product_domain_elements: Vec<::Fr>, - /// The verifying key of the coinbase puzzle. - pub verifying_key: CoinbaseVerifyingKey, -} - -impl CoinbaseProvingKey { - /// Obtain elements of the SRS in the lagrange basis powers. - pub fn lagrange_basis(&self) -> LagrangeBasis { - LagrangeBasis { - lagrange_basis_at_beta_g: Cow::Borrowed(self.lagrange_basis_at_beta_g.as_slice()), - powers_of_beta_times_gamma_g: Cow::Owned(vec![]), - domain: self.product_domain, - } - } - - /// Returns the elements of the product domain. - pub fn product_domain_elements(&self) -> &[::Fr] { - &self.product_domain_elements - } -} diff --git a/ledger/coinbase/src/helpers/partial_solution/mod.rs b/ledger/coinbase/src/helpers/partial_solution/mod.rs deleted file mode 100644 index 4fc078e310..0000000000 --- a/ledger/coinbase/src/helpers/partial_solution/mod.rs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2019-2023 Aleo Systems Inc. -// This file is part of the snarkVM library. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -mod bytes; -mod serialize; -mod string; - -use super::*; - -/// The partial solution for the coinbase puzzle from a prover. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct PartialSolution { - /// The address of the prover. - address: Address, - /// The nonce for the solution. - nonce: u64, - /// The commitment for the solution. - commitment: PuzzleCommitment, -} - -impl PartialSolution { - /// Initializes a new instance of the partial solution. - pub fn new>>(address: Address, nonce: u64, commitment: C) -> Self { - Self { address, nonce, commitment: commitment.into() } - } - - /// Returns the address of the prover. - pub const fn address(&self) -> Address { - self.address - } - - /// Returns the nonce for the solution. - pub const fn nonce(&self) -> u64 { - self.nonce - } - - /// Returns the commitment for the solution. - pub const fn commitment(&self) -> PuzzleCommitment { - self.commitment - } - - /// Returns the prover polynomial. - pub fn to_prover_polynomial( - &self, - epoch_challenge: &EpochChallenge, - ) -> Result::Fr>> { - CoinbasePuzzle::prover_polynomial(epoch_challenge, self.address(), self.nonce()) - } - - /// Returns the target of the solution. - pub fn to_target(&self) -> Result { - self.commitment.to_target() - } -} diff --git a/ledger/coinbase/src/helpers/prover_solution/bytes.rs b/ledger/coinbase/src/helpers/prover_solution/bytes.rs deleted file mode 100644 index 20e846b5f5..0000000000 --- a/ledger/coinbase/src/helpers/prover_solution/bytes.rs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (C) 2019-2023 Aleo Systems Inc. -// This file is part of the snarkVM library. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::*; - -impl FromBytes for ProverSolution { - /// Reads the prover solution from the buffer. - fn read_le(mut reader: R) -> IoResult { - let partial_solution: PartialSolution = FromBytes::read_le(&mut reader)?; - let proof = KZGProof::read_le(&mut reader)?; - - Ok(Self::new(partial_solution, proof)) - } -} - -impl ToBytes for ProverSolution { - /// Writes the prover solution to the buffer. - fn write_le(&self, mut writer: W) -> IoResult<()> { - self.partial_solution.write_le(&mut writer)?; - self.proof.write_le(&mut writer) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use console::{account::PrivateKey, network::MainnetV0}; - - type CurrentNetwork = MainnetV0; - - #[test] - fn test_bytes() -> Result<()> { - let mut rng = TestRng::default(); - let private_key = PrivateKey::::new(&mut rng)?; - let address = Address::try_from(private_key)?; - - // Sample a new prover solution. - let partial_solution = PartialSolution::new(address, u64::rand(&mut rng), KZGCommitment(rng.gen())); - let expected = ProverSolution::new(partial_solution, KZGProof { w: rng.gen(), random_v: None }); - - // Check the byte representation. - let expected_bytes = expected.to_bytes_le()?; - assert_eq!(expected, ProverSolution::read_le(&expected_bytes[..])?); - assert!(ProverSolution::::read_le(&expected_bytes[1..]).is_err()); - - Ok(()) - } -} diff --git a/ledger/coinbase/src/helpers/prover_solution/mod.rs b/ledger/coinbase/src/helpers/prover_solution/mod.rs deleted file mode 100644 index 548ba3773f..0000000000 --- a/ledger/coinbase/src/helpers/prover_solution/mod.rs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (C) 2019-2023 Aleo Systems Inc. -// This file is part of the snarkVM library. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -mod bytes; -mod serialize; -mod string; - -use super::*; - -/// The prover solution for the coinbase puzzle from a prover. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct ProverSolution { - /// The core data of the prover solution. - partial_solution: PartialSolution, - /// The proof for the solution. - proof: PuzzleProof, -} - -impl ProverSolution { - /// Initializes a new instance of the prover solution. - pub const fn new(partial_solution: PartialSolution, proof: PuzzleProof) -> Self { - Self { partial_solution, proof } - } - - /// Returns `true` if the prover solution is valid. - pub fn verify( - &self, - verifying_key: &CoinbaseVerifyingKey, - epoch_challenge: &EpochChallenge, - proof_target: u64, - ) -> Result { - // Ensure the proof is non-hiding. - if self.proof.is_hiding() { - return Ok(false); - } - - // Ensure that the prover solution is greater than the proof target. - if self.to_target()? < proof_target { - bail!("Prover puzzle does not meet the proof target requirements.") - } - - // Compute the prover polynomial. - let prover_polynomial = self.partial_solution.to_prover_polynomial(epoch_challenge)?; - - // Compute the challenge point. - let challenge_point = hash_commitment(&self.commitment())?; - - // Evaluate the epoch and prover polynomials at the challenge point. - let epoch_evaluation = epoch_challenge.epoch_polynomial().evaluate(challenge_point); - let prover_evaluation = prover_polynomial.evaluate(challenge_point); - - // Compute the claimed value by multiplying the evaluations. - let claimed_value = epoch_evaluation * prover_evaluation; - - // Check the KZG proof. - Ok(KZG10::check(verifying_key, &self.commitment(), challenge_point, claimed_value, self.proof())?) - } - - /// Returns the address of the prover. - pub const fn address(&self) -> Address { - self.partial_solution.address() - } - - /// Returns the nonce for the solution. - pub const fn nonce(&self) -> u64 { - self.partial_solution.nonce() - } - - /// Returns the commitment for the solution. - pub const fn commitment(&self) -> PuzzleCommitment { - self.partial_solution.commitment() - } - - /// Returns the proof for the solution. - pub const fn proof(&self) -> &PuzzleProof { - &self.proof - } - - /// Returns the prover polynomial. - pub fn to_prover_polynomial( - &self, - epoch_challenge: &EpochChallenge, - ) -> Result::Fr>> { - self.partial_solution.to_prover_polynomial(epoch_challenge) - } - - /// Returns the target of the solution. - pub fn to_target(&self) -> Result { - self.partial_solution.to_target() - } -} diff --git a/ledger/coinbase/src/helpers/prover_solution/serialize.rs b/ledger/coinbase/src/helpers/prover_solution/serialize.rs deleted file mode 100644 index 0b737310b0..0000000000 --- a/ledger/coinbase/src/helpers/prover_solution/serialize.rs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (C) 2019-2023 Aleo Systems Inc. -// This file is part of the snarkVM library. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::*; - -use snarkvm_utilities::DeserializeExt; - -impl Serialize for ProverSolution { - /// Serializes the prover solution to a JSON-string or buffer. - fn serialize(&self, serializer: S) -> Result { - match serializer.is_human_readable() { - true => { - let mut prover_solution = - serializer.serialize_struct("ProverSolution", 2 + self.proof.random_v.is_some() as usize)?; - prover_solution.serialize_field("partial_solution", &self.partial_solution)?; - prover_solution.serialize_field("proof.w", &self.proof.w)?; - if let Some(random_v) = &self.proof.random_v { - prover_solution.serialize_field("proof.random_v", &random_v)?; - } - prover_solution.end() - } - false => ToBytesSerializer::serialize_with_size_encoding(self, serializer), - } - } -} - -impl<'de, N: Network> Deserialize<'de> for ProverSolution { - /// Deserializes the prover solution from a JSON-string or buffer. - fn deserialize>(deserializer: D) -> Result { - match deserializer.is_human_readable() { - true => { - let mut prover_solution = serde_json::Value::deserialize(deserializer)?; - Ok(Self::new( - DeserializeExt::take_from_value::(&mut prover_solution, "partial_solution")?, - KZGProof { - w: DeserializeExt::take_from_value::(&mut prover_solution, "proof.w")?, - random_v: serde_json::from_value( - prover_solution.get_mut("proof.random_v").unwrap_or(&mut serde_json::Value::Null).take(), - ) - .map_err(de::Error::custom)?, - }, - )) - } - false => FromBytesDeserializer::::deserialize_with_size_encoding(deserializer, "prover solution"), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use console::{account::PrivateKey, network::MainnetV0}; - - type CurrentNetwork = MainnetV0; - - #[test] - fn test_serde_json() -> Result<()> { - let mut rng = TestRng::default(); - let private_key = PrivateKey::::new(&mut rng)?; - let address = Address::try_from(private_key)?; - - // Sample a new prover puzzle solution. - let partial_solution = PartialSolution::new(address, u64::rand(&mut rng), KZGCommitment(rng.gen())); - let expected = ProverSolution::new(partial_solution, KZGProof { w: rng.gen(), random_v: None }); - - // Serialize - let expected_string = &expected.to_string(); - let candidate_string = serde_json::to_string(&expected)?; - assert_eq!(expected, serde_json::from_str(&candidate_string)?); - - // Deserialize - assert_eq!(expected, ProverSolution::from_str(expected_string)?); - assert_eq!(expected, serde_json::from_str(&candidate_string)?); - - Ok(()) - } - - #[test] - fn test_bincode() -> Result<()> { - let mut rng = TestRng::default(); - let private_key = PrivateKey::::new(&mut rng)?; - let address = Address::try_from(private_key)?; - - // Sample a new prover puzzle solution. - let partial_solution = PartialSolution::new(address, u64::rand(&mut rng), KZGCommitment(rng.gen())); - let expected = ProverSolution::new(partial_solution, KZGProof { w: rng.gen(), random_v: None }); - - // Serialize - let expected_bytes = expected.to_bytes_le()?; - let expected_bytes_with_size_encoding = bincode::serialize(&expected)?; - assert_eq!(&expected_bytes[..], &expected_bytes_with_size_encoding[8..]); - - // Deserialize - assert_eq!(expected, ProverSolution::read_le(&expected_bytes[..])?); - assert_eq!(expected, bincode::deserialize(&expected_bytes_with_size_encoding[..])?); - - Ok(()) - } -} diff --git a/ledger/coinbase/src/helpers/prover_solution/string.rs b/ledger/coinbase/src/helpers/prover_solution/string.rs deleted file mode 100644 index 1c96dcb4ed..0000000000 --- a/ledger/coinbase/src/helpers/prover_solution/string.rs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (C) 2019-2023 Aleo Systems Inc. -// This file is part of the snarkVM library. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::*; - -impl FromStr for ProverSolution { - type Err = Error; - - /// Initializes the prover solution from a JSON-string. - fn from_str(partial_prover_solution: &str) -> Result { - Ok(serde_json::from_str(partial_prover_solution)?) - } -} - -impl Debug for ProverSolution { - /// Prints the prover solution as a JSON-string. - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Display::fmt(self, f) - } -} - -impl Display for ProverSolution { - /// Displays the prover solution as a JSON-string. - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}", serde_json::to_string(self).map_err::(ser::Error::custom)?) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use console::{account::PrivateKey, network::MainnetV0}; - - type CurrentNetwork = MainnetV0; - - #[test] - fn test_string() -> Result<()> { - let mut rng = TestRng::default(); - let private_key = PrivateKey::::new(&mut rng)?; - let address = Address::try_from(private_key)?; - - // Sample a new prover puzzle solution. - let partial_solution = PartialSolution::new(address, u64::rand(&mut rng), KZGCommitment(rng.gen())); - let expected = ProverSolution::new(partial_solution, KZGProof { w: rng.gen(), random_v: None }); - - // Check the string representation. - let candidate = expected.to_string(); - assert_eq!(expected, ProverSolution::from_str(&candidate)?); - - Ok(()) - } -} diff --git a/ledger/coinbase/src/helpers/puzzle_commitment/mod.rs b/ledger/coinbase/src/helpers/puzzle_commitment/mod.rs deleted file mode 100644 index b4b14414ec..0000000000 --- a/ledger/coinbase/src/helpers/puzzle_commitment/mod.rs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (C) 2019-2023 Aleo Systems Inc. -// This file is part of the snarkVM library. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -mod bytes; -mod serialize; -mod string; - -pub use string::PUZZLE_COMMITMENT_PREFIX; - -use super::*; -use snarkvm_algorithms::crypto_hash::sha256d_to_u64; - -/// A coinbase puzzle commitment to a polynomial. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct PuzzleCommitment { - /// The commitment for the solution. - commitment: KZGCommitment<::PairingCurve>, -} - -impl PuzzleCommitment { - /// Initializes a new instance of the puzzle commitment. - pub const fn new(commitment: KZGCommitment<::PairingCurve>) -> Self { - Self { commitment } - } - - /// Initializes a new instance of the puzzle commitment. - pub fn from_g1_affine(commitment: <::PairingCurve as PairingEngine>::G1Affine) -> Self { - Self::from(KZGCommitment(commitment)) - } - - /// Returns the proof target. - pub fn to_target(&self) -> Result { - let hash_to_u64 = sha256d_to_u64(&self.commitment.to_bytes_le()?); - if hash_to_u64 == 0 { Ok(u64::MAX) } else { Ok(u64::MAX / hash_to_u64) } - } -} - -impl From::PairingCurve>> for PuzzleCommitment { - /// Initializes a new instance of the puzzle commitment. - fn from(commitment: KZGCommitment<::PairingCurve>) -> Self { - Self::new(commitment) - } -} - -impl Default for PuzzleCommitment { - fn default() -> Self { - Self::new(KZGCommitment::empty()) - } -} - -impl Deref for PuzzleCommitment { - type Target = KZGCommitment<::PairingCurve>; - - fn deref(&self) -> &Self::Target { - &self.commitment - } -} diff --git a/ledger/coinbase/src/lib.rs b/ledger/coinbase/src/lib.rs deleted file mode 100644 index c3016e150b..0000000000 --- a/ledger/coinbase/src/lib.rs +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright (C) 2019-2023 Aleo Systems Inc. -// This file is part of the snarkVM library. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#![forbid(unsafe_code)] -#![allow(clippy::too_many_arguments)] -#![warn(clippy::cast_possible_truncation)] - -mod helpers; -pub use helpers::*; - -mod hash; -use hash::*; - -#[cfg(test)] -mod tests; - -use console::{ - account::Address, - prelude::{anyhow, bail, cfg_iter, ensure, has_duplicates, Network, Result, ToBytes}, -}; -use snarkvm_algorithms::{ - fft::{DensePolynomial, EvaluationDomain}, - polycommit::kzg10::{UniversalParams as SRS, KZG10}, -}; -use snarkvm_curves::PairingEngine; -use snarkvm_fields::Zero; -use snarkvm_synthesizer_snark::UniversalSRS; - -use aleo_std::prelude::*; -use std::sync::Arc; - -#[cfg(not(feature = "serial"))] -use rayon::prelude::*; - -#[derive(Clone)] -pub enum CoinbasePuzzle { - /// The prover contains the coinbase puzzle proving key. - Prover(Arc>), - /// The verifier contains the coinbase puzzle verifying key. - Verifier(Arc>), -} - -impl CoinbasePuzzle { - /// Initializes a new `SRS` for the coinbase puzzle. - #[cfg(any(test, feature = "setup"))] - pub fn setup(config: PuzzleConfig) -> Result> { - // The SRS must support committing to the product of two degree `n` polynomials. - // Thus, the SRS must support committing to a polynomial of degree `2n - 1`. - let total_degree = (2 * config.degree - 1).try_into()?; - let srs = KZG10::load_srs(total_degree)?; - Ok(srs) - } - - /// Load the coinbase puzzle proving and verifying keys. - pub fn load() -> Result { - let max_degree = N::COINBASE_PUZZLE_DEGREE; - // Load the universal SRS. - let universal_srs = UniversalSRS::::load()?; - // Trim the universal SRS to the maximum degree. - Self::trim(&*universal_srs, PuzzleConfig { degree: max_degree }) - } - - pub fn trim(srs: &SRS, config: PuzzleConfig) -> Result { - // As above, we must support committing to the product of two degree `n` polynomials. - // Thus, the SRS must support committing to a polynomial of degree `2n - 1`. - // Since the upper bound to `srs.powers_of_beta_g` takes as input the number - // of coefficients. The degree of the product has `2n - 1` coefficients. - // - // Hence, we request the powers of beta for the interval [0, 2n]. - let product_domain = Self::product_domain(config.degree)?; - - let lagrange_basis_at_beta_g = srs.lagrange_basis(product_domain)?; - let fft_precomputation = product_domain.precompute_fft(); - let product_domain_elements = product_domain.elements().collect(); - - let vk = CoinbaseVerifyingKey:: { - g: srs.power_of_beta_g(0)?, - gamma_g: ::G1Affine::zero(), // We don't use gamma_g later on since we are not hiding. - h: srs.h, - beta_h: srs.beta_h(), - prepared_h: srs.prepared_h.clone(), - prepared_beta_h: srs.prepared_beta_h.clone(), - }; - - let pk = CoinbaseProvingKey { - product_domain, - product_domain_elements, - lagrange_basis_at_beta_g, - fft_precomputation, - verifying_key: vk, - }; - - Ok(Self::Prover(Arc::new(pk))) - } - - /// Returns a prover solution to the coinbase puzzle. - pub fn prove( - &self, - epoch_challenge: &EpochChallenge, - address: Address, - nonce: u64, - minimum_proof_target: Option, - ) -> Result> { - // Retrieve the coinbase proving key. - let pk = match self { - Self::Prover(coinbase_proving_key) => coinbase_proving_key, - Self::Verifier(_) => bail!("Cannot prove the coinbase puzzle with a verifier"), - }; - - let polynomial = Self::prover_polynomial(epoch_challenge, address, nonce)?; - - let product_evaluations = { - let polynomial_evaluations = pk.product_domain.in_order_fft_with_pc(&polynomial, &pk.fft_precomputation); - pk.product_domain.mul_polynomials_in_evaluation_domain( - polynomial_evaluations, - &epoch_challenge.epoch_polynomial_evaluations().evaluations, - )? - }; - let (commitment, _rand) = KZG10::commit_lagrange(&pk.lagrange_basis(), &product_evaluations, None, None)?; - - let partial_solution = PartialSolution::new(address, nonce, commitment); - - // Check that the minimum target is met. - if let Some(minimum_target) = minimum_proof_target { - let proof_target = partial_solution.to_target()?; - ensure!( - proof_target >= minimum_target, - "Prover solution was below the necessary proof target ({proof_target} < {minimum_target})" - ); - } - - let point = hash_commitment(&commitment)?; - let product_eval_at_point = polynomial.evaluate(point) * epoch_challenge.epoch_polynomial().evaluate(point); - - let proof = KZG10::open_lagrange( - &pk.lagrange_basis(), - pk.product_domain_elements(), - &product_evaluations, - point, - product_eval_at_point, - )?; - ensure!(!proof.is_hiding(), "The prover solution must contain a non-hiding proof"); - - debug_assert!(KZG10::check(&pk.verifying_key, &commitment, point, product_eval_at_point, &proof)?); - - Ok(ProverSolution::new(partial_solution, proof)) - } - - /// Returns `true` if the solutions are valid. - pub fn check_solutions( - &self, - solutions: &CoinbaseSolution, - epoch_challenge: &EpochChallenge, - proof_target: u64, - ) -> Result<()> { - let timer = timer!("CoinbasePuzzle::verify"); - - // Ensure the solutions are not empty. - ensure!(!solutions.is_empty(), "There are no solutions to verify for the coinbase puzzle"); - - // Ensure the number of partial solutions does not exceed `MAX_PROVER_SOLUTIONS`. - if solutions.len() > N::MAX_SOLUTIONS { - bail!( - "The solutions exceed the allowed number of partial solutions. ({} > {})", - solutions.len(), - N::MAX_SOLUTIONS - ); - } - - // Ensure the puzzle commitments are unique. - if has_duplicates(solutions.puzzle_commitments()) { - bail!("The solutions contain duplicate puzzle commitments"); - } - lap!(timer, "Perform initial checks"); - - // Verify each prover solution. - if !cfg_iter!(solutions).all(|(_, solution)| { - solution.verify(self.coinbase_verifying_key(), epoch_challenge, proof_target).unwrap_or(false) - }) { - bail!("The solutions contain an invalid prover solution"); - } - finish!(timer, "Verify each solution"); - - Ok(()) - } - - /// Returns the coinbase proving key. - pub fn coinbase_proving_key(&self) -> Result<&CoinbaseProvingKey> { - match self { - Self::Prover(coinbase_proving_key) => Ok(coinbase_proving_key), - Self::Verifier(_) => bail!("Cannot fetch the coinbase proving key with a verifier"), - } - } - - /// Returns the coinbase verifying key. - pub fn coinbase_verifying_key(&self) -> &CoinbaseVerifyingKey { - match self { - Self::Prover(coinbase_proving_key) => &coinbase_proving_key.verifying_key, - Self::Verifier(coinbase_verifying_key) => coinbase_verifying_key, - } - } -} - -impl CoinbasePuzzle { - /// Checks that the degree for the epoch and prover polynomial is within bounds, - /// and returns the evaluation domain for the product polynomial. - pub(crate) fn product_domain(degree: u32) -> Result> { - ensure!(degree != 0, "Degree cannot be zero"); - let num_coefficients = degree.checked_add(1).ok_or_else(|| anyhow!("Degree is too large"))?; - let product_num_coefficients = num_coefficients - .checked_mul(2) - .and_then(|t| t.checked_sub(1)) - .ok_or_else(|| anyhow!("Degree is too large"))?; - assert_eq!(product_num_coefficients, 2 * degree + 1); - let product_domain = - EvaluationDomain::new(product_num_coefficients.try_into()?).ok_or_else(|| anyhow!("Invalid degree"))?; - assert_eq!(product_domain.size(), (product_num_coefficients as usize).checked_next_power_of_two().unwrap()); - Ok(product_domain) - } - - /// Returns the prover polynomial for the coinbase puzzle. - fn prover_polynomial( - epoch_challenge: &EpochChallenge, - address: Address, - nonce: u64, - ) -> Result::Fr>> { - let input = { - let mut bytes = [0u8; 76]; - epoch_challenge.epoch_number().write_le(&mut bytes[..4])?; - epoch_challenge.epoch_block_hash().write_le(&mut bytes[4..36])?; - address.write_le(&mut bytes[36..68])?; - nonce.write_le(&mut bytes[68..])?; - - bytes - }; - Ok(hash_to_polynomial::<::Fr>(&input, epoch_challenge.degree())) - } -} diff --git a/ledger/coinbase/src/tests.rs b/ledger/coinbase/src/tests.rs deleted file mode 100644 index 54311253e4..0000000000 --- a/ledger/coinbase/src/tests.rs +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (C) 2019-2023 Aleo Systems Inc. -// This file is part of the snarkVM library. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::*; -use console::{account::*, network::MainnetV0}; -use snarkvm_utilities::Uniform; - -use rand::RngCore; - -const ITERATIONS: u64 = 100; - -#[test] -fn test_coinbase_puzzle() { - let mut rng = TestRng::default(); - - let max_degree = 1 << 15; - let max_config = PuzzleConfig { degree: max_degree }; - let srs = CoinbasePuzzle::::setup(max_config).unwrap(); - - for log_degree in 5..10 { - let degree = (1 << log_degree) - 1; - let config = PuzzleConfig { degree }; - let puzzle = CoinbasePuzzle::::trim(&srs, config).unwrap(); - let epoch_challenge = EpochChallenge::new(rng.next_u32(), Default::default(), degree).unwrap(); - - for batch_size in 1..10 { - let solutions = (0..batch_size) - .map(|_| { - let private_key = PrivateKey::::new(&mut rng).unwrap(); - let address = Address::try_from(private_key).unwrap(); - let nonce = u64::rand(&mut rng); - puzzle.prove(&epoch_challenge, address, nonce, None).unwrap() - }) - .collect::>(); - let full_solution = CoinbaseSolution::new(solutions).unwrap(); - assert!(puzzle.check_solutions(&full_solution, &epoch_challenge, 0u64).is_ok()); - - let bad_epoch_challenge = EpochChallenge::new(rng.next_u32(), Default::default(), degree).unwrap(); - assert!(puzzle.check_solutions(&full_solution, &bad_epoch_challenge, 0u64).is_err()); - } - } -} - -#[test] -fn test_prover_solution_minimum_target() { - let mut rng = TestRng::default(); - - let max_degree = 1 << 15; - let max_config = PuzzleConfig { degree: max_degree }; - let srs = CoinbasePuzzle::::setup(max_config).unwrap(); - - for log_degree in 5..10 { - let degree = (1 << log_degree) - 1; - let config = PuzzleConfig { degree }; - let puzzle = CoinbasePuzzle::::trim(&srs, config).unwrap(); - let epoch_challenge = EpochChallenge::new(rng.next_u32(), Default::default(), degree).unwrap(); - - for _ in 0..ITERATIONS { - let private_key = PrivateKey::::new(&mut rng).unwrap(); - let address = Address::try_from(private_key).unwrap(); - let nonce = u64::rand(&mut rng); - - let solution = puzzle.prove(&epoch_challenge, address, nonce, None).unwrap(); - let proof_target = solution.to_target().unwrap(); - - // Assert that the operation will pass if the minimum target is low enough. - assert!(puzzle.prove(&epoch_challenge, address, nonce, Some(proof_target.saturating_sub(1))).is_ok()); - - // Assert that the operation will fail if the minimum target is too high. - assert!(puzzle.prove(&epoch_challenge, address, nonce, Some(proof_target.saturating_add(1))).is_err()); - } - } -} - -#[test] -fn test_edge_case_for_degree() { - let mut rng = rand::thread_rng(); - - // Generate srs. - let max_degree = 1 << 15; - let max_config = PuzzleConfig { degree: max_degree }; - let srs = CoinbasePuzzle::::setup(max_config).unwrap(); - - // Generate PK and VK. - let degree = (1 << 13) - 1; - let puzzle = CoinbasePuzzle::::trim(&srs, PuzzleConfig { degree }).unwrap(); - - // Generate proof inputs - let private_key = PrivateKey::::new(&mut rng).unwrap(); - let address = Address::try_from(private_key).unwrap(); - let epoch_challenge = EpochChallenge::new(rng.gen(), Default::default(), degree).unwrap(); - - // Generate a prover solution. - let prover_solution = puzzle.prove(&epoch_challenge, address, rng.gen(), None).unwrap(); - let coinbase_solution = CoinbaseSolution::new(vec![prover_solution]).unwrap(); - assert!(puzzle.check_solutions(&coinbase_solution, &epoch_challenge, 0u64).is_ok()); -} - -/// Use `cargo test profiler --features timer` to run this test. -#[ignore] -#[test] -fn test_profiler() -> Result<()> { - fn sample_address_and_nonce(rng: &mut (impl CryptoRng + RngCore)) -> (Address, u64) { - let private_key = PrivateKey::new(rng).unwrap(); - let address = Address::try_from(private_key).unwrap(); - let nonce = rng.next_u64(); - (address, nonce) - } - - let mut rng = rand::thread_rng(); - - // Generate srs. - let max_degree = 1 << 15; - let max_config = PuzzleConfig { degree: max_degree }; - let universal_srs = CoinbasePuzzle::::setup(max_config).unwrap(); - - // Generate PK and VK. - let degree = (1 << 13) - 1; - let config = PuzzleConfig { degree }; - let puzzle = CoinbasePuzzle::trim(&universal_srs, config).unwrap(); - - // Generate proof inputs - let epoch_challenge = EpochChallenge::new(rng.next_u32(), Default::default(), degree).unwrap(); - - for batch_size in [10, 100, ::MAX_SOLUTIONS] { - // Generate the solutions. - let solutions = (0..batch_size) - .map(|_| { - let (address, nonce) = sample_address_and_nonce(&mut rng); - puzzle.prove(&epoch_challenge, address, nonce, None).unwrap() - }) - .collect::>(); - // Construct the solutions. - let solutions = CoinbaseSolution::new(solutions).unwrap(); - // Verify the solutions. - puzzle.check_solutions(&solutions, &epoch_challenge, 0u64).unwrap(); - } - - bail!("\n\nRemember to #[ignore] this test!\n\n") -} diff --git a/ledger/committee/Cargo.toml b/ledger/committee/Cargo.toml index 8c784ee899..00a8d55338 100644 --- a/ledger/committee/Cargo.toml +++ b/ledger/committee/Cargo.toml @@ -94,10 +94,6 @@ version = "0.4" [dev-dependencies.rayon] version = "1" -[dev-dependencies.ledger-narwhal-batch-header] -package = "snarkvm-ledger-narwhal-batch-header" -path = "../narwhal/batch-header" - [dev-dependencies.snarkvm-ledger-committee] path = "." features = [ "prop-tests" ] diff --git a/ledger/narwhal/batch-header/src/lib.rs b/ledger/narwhal/batch-header/src/lib.rs index 4412b2771b..9799254d29 100644 --- a/ledger/narwhal/batch-header/src/lib.rs +++ b/ledger/narwhal/batch-header/src/lib.rs @@ -59,7 +59,7 @@ impl BatchHeader { #[cfg(not(any(test, feature = "test-helpers")))] pub const MAX_CERTIFICATES: u16 = 10; /// The maximum number of certificates in a batch. - /// This is set to a deliberately high value (100) for testing purposes only. + /// This is deliberately set to a high value (100) for testing purposes only. #[cfg(any(test, feature = "test-helpers"))] pub const MAX_CERTIFICATES: u16 = 100; /// The maximum number of rounds to store before garbage collecting. diff --git a/ledger/narwhal/transmission-id/Cargo.toml b/ledger/narwhal/transmission-id/Cargo.toml index 9ab29797eb..abe68bf1ed 100644 --- a/ledger/narwhal/transmission-id/Cargo.toml +++ b/ledger/narwhal/transmission-id/Cargo.toml @@ -34,9 +34,9 @@ package = "snarkvm-console" path = "../../../console" version = "=0.16.19" -[dependencies.ledger-coinbase] -package = "snarkvm-ledger-coinbase" -path = "../../coinbase" +[dependencies.ledger-puzzle] +package = "snarkvm-ledger-puzzle" +path = "../../puzzle" version = "=0.16.19" [dev-dependencies.bincode] diff --git a/ledger/narwhal/transmission-id/src/lib.rs b/ledger/narwhal/transmission-id/src/lib.rs index 285b58ff53..7ba75ba2a1 100644 --- a/ledger/narwhal/transmission-id/src/lib.rs +++ b/ledger/narwhal/transmission-id/src/lib.rs @@ -20,22 +20,22 @@ mod serialize; mod string; use console::{network::TRANSACTION_PREFIX, prelude::*}; -use ledger_coinbase::{PuzzleCommitment, PUZZLE_COMMITMENT_PREFIX}; +use ledger_puzzle::{SolutionID, SOLUTION_ID_PREFIX}; #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub enum TransmissionID { /// A ratification. Ratification, - /// A prover solution. - Solution(PuzzleCommitment), + /// A solution. + Solution(SolutionID), /// A transaction. Transaction(N::TransactionID), } -impl From> for TransmissionID { - /// Converts the puzzle commitment into a transmission ID. - fn from(puzzle_commitment: PuzzleCommitment) -> Self { - Self::Solution(puzzle_commitment) +impl From> for TransmissionID { + /// Converts the solution ID into a transmission ID. + fn from(solution_id: SolutionID) -> Self { + Self::Solution(solution_id) } } @@ -47,10 +47,10 @@ impl From<&N::TransactionID> for TransmissionID { } impl TransmissionID { - /// Returns the puzzle commitment if the transmission is a solution. - pub fn solution(&self) -> Option> { + /// Returns the solution ID if the transmission is a solution. + pub fn solution(&self) -> Option> { match self { - Self::Solution(puzzle_commitment) => Some(*puzzle_commitment), + Self::Solution(solution_id) => Some(*solution_id), _ => None, } } @@ -79,9 +79,9 @@ pub mod test_helpers { pub fn sample_transmission_ids(rng: &mut TestRng) -> Vec> { // Initialize a sample vector. let mut sample = Vec::with_capacity(10); - // Append sample puzzle commitments. + // Append sample solution IDs. for _ in 0..5 { - sample.push(TransmissionID::Solution(PuzzleCommitment::from_g1_affine(rng.gen()))); + sample.push(TransmissionID::Solution(SolutionID::from(rng.gen::()))); } // Append sample transaction IDs. for _ in 0..5 { diff --git a/ledger/narwhal/transmission-id/src/string.rs b/ledger/narwhal/transmission-id/src/string.rs index e2e0b8ce86..817c878236 100644 --- a/ledger/narwhal/transmission-id/src/string.rs +++ b/ledger/narwhal/transmission-id/src/string.rs @@ -19,8 +19,8 @@ impl FromStr for TransmissionID { /// Initializes the transmission ID from a string. fn from_str(input: &str) -> Result { - if input.starts_with(PUZZLE_COMMITMENT_PREFIX) { - Ok(Self::Solution(PuzzleCommitment::from_str(input)?)) + if input.starts_with(SOLUTION_ID_PREFIX) { + Ok(Self::Solution(SolutionID::from_str(input)?)) } else if input.starts_with(TRANSACTION_PREFIX) { Ok(Self::Transaction( N::TransactionID::from_str(input).map_err(|_| anyhow!("Failed to parse transaction ID: {input}"))?, diff --git a/ledger/narwhal/transmission/Cargo.toml b/ledger/narwhal/transmission/Cargo.toml index c209403679..44b3bb5705 100644 --- a/ledger/narwhal/transmission/Cargo.toml +++ b/ledger/narwhal/transmission/Cargo.toml @@ -39,16 +39,16 @@ package = "snarkvm-ledger-block" path = "../../block" version = "=0.16.19" -[dependencies.ledger-coinbase] -package = "snarkvm-ledger-coinbase" -path = "../../coinbase" -version = "=0.16.19" - [dependencies.ledger-narwhal-data] package = "snarkvm-ledger-narwhal-data" path = "../data" version = "=0.16.19" +[dependencies.ledger-puzzle] +package = "snarkvm-ledger-puzzle" +path = "../../puzzle" +version = "=0.16.19" + [dependencies.bytes] version = "1" diff --git a/ledger/narwhal/transmission/src/lib.rs b/ledger/narwhal/transmission/src/lib.rs index a7e7950c21..44d2eb83ca 100644 --- a/ledger/narwhal/transmission/src/lib.rs +++ b/ledger/narwhal/transmission/src/lib.rs @@ -21,8 +21,8 @@ mod string; use console::prelude::*; use ledger_block::Transaction; -use ledger_coinbase::ProverSolution; use ledger_narwhal_data::Data; +use ledger_puzzle::Solution; #[derive(Clone, PartialEq, Eq)] pub enum Transmission { @@ -30,15 +30,15 @@ pub enum Transmission { Ratification, /// A prover solution. /// Attention: Observe that the solution is encapsulated in `Data`, and thus possibly unchecked. - Solution(Data>), + Solution(Data>), /// A transaction. /// Attention: Observe that the transaction is encapsulated in `Data`, and thus possibly unchecked. Transaction(Data>), } -impl From> for Transmission { +impl From> for Transmission { /// Converts the prover solution into a transmission. - fn from(solution: ProverSolution) -> Self { + fn from(solution: Solution) -> Self { Self::Solution(Data::Object(solution)) } } @@ -50,9 +50,9 @@ impl From> for Transmission { } } -impl From>> for Transmission { +impl From>> for Transmission { /// Converts the prover solution into a transmission. - fn from(solution: Data>) -> Self { + fn from(solution: Data>) -> Self { Self::Solution(solution) } } diff --git a/ledger/coinbase/Cargo.toml b/ledger/puzzle/Cargo.toml similarity index 68% rename from ledger/coinbase/Cargo.toml rename to ledger/puzzle/Cargo.toml index dbb5b6ce3f..453cf8d8ed 100644 --- a/ledger/coinbase/Cargo.toml +++ b/ledger/puzzle/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "snarkvm-ledger-coinbase" +name = "snarkvm-ledger-puzzle" version = "0.16.19" authors = [ "The Aleo Team " ] -description = "Coinbase puzzle for a decentralized virtual machine" +description = "Puzzle for a decentralized virtual machine" homepage = "https://aleo.org" repository = "https://github.com/AleoHQ/snarkVM" keywords = [ @@ -24,27 +24,23 @@ license = "Apache-2.0" edition = "2021" [[bench]] -name = "coinbase_puzzle" -path = "benches/coinbase_puzzle.rs" +name = "puzzle" +path = "benches/puzzle.rs" harness = false required-features = [ "setup" ] [features] -default = [ ] +default = [ "indexmap/rayon", "rayon" ] cuda = [ "snarkvm-algorithms/cuda" ] serial = [ "console/serial", "snarkvm-algorithms/serial", - "snarkvm-curves/serial", - "snarkvm-fields/serial", - "snarkvm-utilities/serial" ] setup = [ ] timer = [ "aleo-std/timer" ] wasm = [ "console/wasm", "snarkvm-algorithms/wasm", - "snarkvm-utilities/wasm" ] [dependencies.console] @@ -56,23 +52,6 @@ version = "=0.16.19" path = "../../algorithms" version = "=0.16.19" -[dependencies.snarkvm-curves] -path = "../../curves" -version = "=0.16.19" - -[dependencies.snarkvm-fields] -path = "../../fields" -version = "=0.16.19" - -[dependencies.snarkvm-synthesizer-snark] -path = "../../synthesizer/snark" -version = "=0.16.19" - -[dependencies.snarkvm-utilities] -path = "../../utilities" -version = "=0.16.19" -default-features = false - [dependencies.aleo-std] version = "0.1.24" default-features = false @@ -83,16 +62,28 @@ version = "1.0.73" [dependencies.bincode] version = "1" -[dependencies.blake2] -version = "0.10" -default-features = false - [dependencies.indexmap] version = "2.0" features = [ "serde", "rayon" ] +[dependencies.lru] +version = "0.12" + +[dependencies.once_cell] +version = "1.18" + +[dependencies.parking_lot] +version = "0.12" + +[dependencies.rand] +version = "0.8" + +[dependencies.rand_chacha] +version = "0.3.1" + [dependencies.rayon] version = "1" +optional = true [dependencies.serde_json] version = "1.0" @@ -108,3 +99,7 @@ version = "0.5.1" [dev-dependencies.rand] version = "0.8" + +[dev-dependencies.snarkvm-ledger-puzzle-epoch] +path = "epoch" +version = "=0.16.19" diff --git a/ledger/coinbase/LICENSE.md b/ledger/puzzle/LICENSE.md similarity index 100% rename from ledger/coinbase/LICENSE.md rename to ledger/puzzle/LICENSE.md diff --git a/ledger/coinbase/README.md b/ledger/puzzle/README.md similarity index 51% rename from ledger/coinbase/README.md rename to ledger/puzzle/README.md index 81948b6954..0350fbee9d 100644 --- a/ledger/coinbase/README.md +++ b/ledger/puzzle/README.md @@ -1,5 +1,5 @@ -# snarkvm-ledger-coinbase +# snarkvm-ledger-puzzle -[![Crates.io](https://img.shields.io/crates/v/snarkvm-ledger-coinbase.svg?color=neon)](https://crates.io/crates/snarkvm-ledger-coinbase) +[![Crates.io](https://img.shields.io/crates/v/snarkvm-ledger-puzzle.svg?color=neon)](https://crates.io/crates/snarkvm-ledger-puzzle) [![Authors](https://img.shields.io/badge/authors-Aleo-orange.svg)](https://aleo.org) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](./LICENSE.md) diff --git a/ledger/puzzle/benches/puzzle.rs b/ledger/puzzle/benches/puzzle.rs new file mode 100644 index 0000000000..2e30433bc3 --- /dev/null +++ b/ledger/puzzle/benches/puzzle.rs @@ -0,0 +1,82 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![allow(clippy::single_element_loop)] + +#[macro_use] +extern crate criterion; + +use console::{ + account::*, + network::{MainnetV0, Network}, +}; +use snarkvm_ledger_puzzle::{Puzzle, PuzzleSolutions}; +use snarkvm_ledger_puzzle_epoch::MerklePuzzle; + +use criterion::Criterion; +use rand::{self, thread_rng, CryptoRng, RngCore}; + +fn sample_address_and_counter(rng: &mut (impl CryptoRng + RngCore)) -> (Address, u64) { + let private_key = PrivateKey::new(rng).unwrap(); + let address = Address::try_from(private_key).unwrap(); + let counter = rng.next_u64(); + (address, counter) +} + +fn puzzle_prove(c: &mut Criterion) { + let rng = &mut thread_rng(); + + // Initialize a new puzzle. + let puzzle = Puzzle::::new::>(); + + // Initialize an epoch hash. + let epoch_hash = rng.gen(); + + c.bench_function("Puzzle::prove", |b| { + let (address, counter) = sample_address_and_counter(rng); + b.iter(|| puzzle.prove(epoch_hash, address, counter, None).unwrap()) + }); +} + +fn puzzle_verify(c: &mut Criterion) { + let rng = &mut thread_rng(); + + // Initialize a new puzzle. + let puzzle = Puzzle::::new::>(); + + // Initialize an epoch hash. + let epoch_hash = rng.gen(); + + for batch_size in [1, 2, ::MAX_SOLUTIONS] { + let solutions = (0..batch_size) + .map(|_| { + let (address, counter) = sample_address_and_counter(rng); + puzzle.prove(epoch_hash, address, counter, None).unwrap() + }) + .collect::>(); + let solutions = PuzzleSolutions::new(solutions).unwrap(); + + c.bench_function("Puzzle::check_solutions", |b| { + b.iter(|| puzzle.check_solutions(&solutions, epoch_hash, 0u64).unwrap()) + }); + } +} + +criterion_group! { + name = puzzle; + config = Criterion::default().sample_size(10); + targets = puzzle_prove, puzzle_verify, +} + +criterion_main!(puzzle); diff --git a/ledger/puzzle/epoch/Cargo.toml b/ledger/puzzle/epoch/Cargo.toml new file mode 100644 index 0000000000..f247a1052f --- /dev/null +++ b/ledger/puzzle/epoch/Cargo.toml @@ -0,0 +1,67 @@ +[package] +name = "snarkvm-ledger-puzzle-epoch" +version = "0.16.19" +authors = [ "The Aleo Team " ] +description = "Epoch puzzle for a decentralized virtual machine" +homepage = "https://aleo.org" +repository = "https://github.com/AleoHQ/snarkVM" +keywords = [ + "aleo", + "cryptography", + "blockchain", + "decentralized", + "zero-knowledge" +] +categories = [ + "compilers", + "cryptography", + "mathematics", + "wasm", + "web-programming" +] +include = [ "Cargo.toml", "src", "README.md", "LICENSE.md" ] +license = "Apache-2.0" +edition = "2021" + +[features] +default = [ "merkle", "rayon" ] +serial = [ + "console/serial", + "snarkvm-ledger-puzzle/serial", +] +merkle = [ ] +wasm = [ "console/wasm" ] + +[dependencies.console] +package = "snarkvm-console" +path = "../../../console" +version = "=0.16.19" + +[dependencies.snarkvm-ledger-puzzle] +path = "../." +version = "=0.16.19" + +[dependencies.anyhow] +version = "1.0.73" + +[dependencies.colored] +version = "2" + +[dependencies.indexmap] +version = "2.0" +features = [ "serde", "rayon" ] + +[dependencies.rand] +version = "0.8" + +[dependencies.rand_chacha] +version = "0.3.1" + +[dependencies.rayon] +version = "1" +optional = true + +[dev-dependencies.console] +package = "snarkvm-console" +path = "../../../console" +features = [ "test" ] diff --git a/ledger/puzzle/epoch/LICENSE.md b/ledger/puzzle/epoch/LICENSE.md new file mode 100644 index 0000000000..d0af96c393 --- /dev/null +++ b/ledger/puzzle/epoch/LICENSE.md @@ -0,0 +1,194 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/ledger/puzzle/epoch/README.md b/ledger/puzzle/epoch/README.md new file mode 100644 index 0000000000..680ac3ac3b --- /dev/null +++ b/ledger/puzzle/epoch/README.md @@ -0,0 +1,5 @@ +# snarkvm-ledger-puzzle-epoch + +[![Crates.io](https://img.shields.io/crates/v/snarkvm-ledger-puzzle-epoch.svg?color=neon)](https://crates.io/crates/snarkvm-ledger-puzzle-epoch) +[![Authors](https://img.shields.io/badge/authors-Aleo-orange.svg)](https://aleo.org) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](./LICENSE.md) diff --git a/ledger/puzzle/epoch/src/lib.rs b/ledger/puzzle/epoch/src/lib.rs new file mode 100644 index 0000000000..a6d3089b30 --- /dev/null +++ b/ledger/puzzle/epoch/src/lib.rs @@ -0,0 +1,18 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[cfg(feature = "merkle")] +pub mod merkle; +#[cfg(feature = "merkle")] +pub use merkle::*; diff --git a/ledger/puzzle/epoch/src/merkle/mod.rs b/ledger/puzzle/epoch/src/merkle/mod.rs new file mode 100644 index 0000000000..aa6fb4e272 --- /dev/null +++ b/ledger/puzzle/epoch/src/merkle/mod.rs @@ -0,0 +1,134 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use console::{ + network::Network, + prelude::{cfg_into_iter, FromBytes, ToBits as TBits, ToBytes, Uniform}, + types::Field, +}; +use snarkvm_ledger_puzzle::PuzzleTrait; + +use anyhow::Result; +use core::marker::PhantomData; +use rand::{Rng, SeedableRng}; +use rand_chacha::ChaChaRng; + +#[cfg(not(feature = "serial"))] +use rayon::prelude::*; + +const MIN_NUMBER_OF_LEAVES: usize = 100_000; +const MAX_NUMBER_OF_LEAVES: usize = 200_000; + +pub struct MerklePuzzle(PhantomData); + +impl PuzzleTrait for MerklePuzzle { + /// Initializes a new instance of the puzzle. + fn new() -> Self { + Self(PhantomData) + } + + /// Returns the leaves for the puzzle, given the epoch hash and seeded RNG. + fn to_leaves(&self, epoch_hash: N::BlockHash, rng: &mut ChaChaRng) -> Result>> { + // Sample a random number of leaves. + let num_leaves = self.num_leaves(epoch_hash)?; + // Sample random field elements for each of the leaves, and convert them to bits. + let leaves = (0..num_leaves).map(|_| Field::::rand(rng).to_bits_le()).collect::>(); + // Return the leaves. + Ok(leaves) + } + + /// Returns the batches of leaves for the puzzle, given the epoch hash and seeded RNGs. + fn to_all_leaves(&self, epoch_hash: N::BlockHash, rngs: Vec) -> Result>>> { + // Sample a random number of leaves. + let num_leaves = self.num_leaves(epoch_hash)?; + // Construct the epoch inputs. + let leaves = cfg_into_iter!(rngs) + .map(|mut rng| { + // Sample random field elements for each of the leaves, and convert them to bits. + (0..num_leaves).map(|_| Field::::rand(&mut rng).to_bits_le()).collect::>() + }) + .collect::>(); + // Return the leaves. + Ok(leaves) + } +} + +impl MerklePuzzle { + /// Returns the number of leaves given the epoch hash. + pub fn num_leaves(&self, epoch_hash: N::BlockHash) -> Result { + // Prepare the seed. + let seed = u64::from_bytes_le(&epoch_hash.to_bytes_le()?[0..8])?; + // Seed a random number generator from the epoch hash. + let mut epoch_rng = ChaChaRng::seed_from_u64(seed); + // Sample a random number of leaves. + Ok(epoch_rng.gen_range(MIN_NUMBER_OF_LEAVES..MAX_NUMBER_OF_LEAVES)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + type CurrentNetwork = console::network::MainnetV0; + + #[test] + fn test_num_leaves() { + // Initialize the epoch hash. + let epoch_hash = ::BlockHash::default(); + // Initialize the puzzle. + let puzzle = MerklePuzzle::::new(); + // Sample the number of leaves. + let num_leaves = puzzle.num_leaves(epoch_hash).unwrap(); + // Ensure the number of leaves is within the expected range. + assert!((MIN_NUMBER_OF_LEAVES..=MAX_NUMBER_OF_LEAVES).contains(&num_leaves)); + } + + #[test] + fn test_to_leaves() { + // Initialize the epoch hash. + let epoch_hash = ::BlockHash::default(); + // Initialize the puzzle. + let puzzle = MerklePuzzle::::new(); + // Sample the number of leaves. + let num_leaves = puzzle.num_leaves(epoch_hash).unwrap(); + // Sample the leaves. + let leaves = puzzle.to_leaves(epoch_hash, &mut ChaChaRng::seed_from_u64(0)).unwrap(); + // Ensure the number of leaves is within the expected range. + assert_eq!(leaves.len(), num_leaves); + } + + #[test] + fn test_to_all_leaves() { + // Initialize the epoch hash. + let epoch_hash = ::BlockHash::default(); + // Initialize the puzzle. + let puzzle = MerklePuzzle::::new(); + // Sample the number of leaves. + let num_leaves = puzzle.num_leaves(epoch_hash).unwrap(); + // Sample the leaves. + let leaves = + puzzle.to_all_leaves(epoch_hash, vec![ChaChaRng::seed_from_u64(0), ChaChaRng::seed_from_u64(1)]).unwrap(); + // Ensure the number of leaves is within the expected range. + assert_eq!(leaves.len(), 2); + assert_eq!(leaves[0].len(), num_leaves); + assert_eq!(leaves[1].len(), num_leaves); + + // Now, ensure it matches with the call to `to_leaves`. + let leaves_single = puzzle.to_leaves(epoch_hash, &mut ChaChaRng::seed_from_u64(0)).unwrap(); + assert_eq!(leaves_single, leaves[0]); + + let leaves_single = puzzle.to_leaves(epoch_hash, &mut ChaChaRng::seed_from_u64(1)).unwrap(); + assert_eq!(leaves_single, leaves[1]); + } +} diff --git a/ledger/puzzle/src/lib.rs b/ledger/puzzle/src/lib.rs new file mode 100644 index 0000000000..88f586b63f --- /dev/null +++ b/ledger/puzzle/src/lib.rs @@ -0,0 +1,578 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![forbid(unsafe_code)] +#![allow(clippy::too_many_arguments)] +#![warn(clippy::cast_possible_truncation)] + +mod solution; +pub use solution::*; + +mod solution_id; +pub use solution_id::*; + +mod solutions; +pub use solutions::*; + +use console::{ + account::Address, + algorithms::Sha3_256, + collections::kary_merkle_tree::KaryMerkleTree, + prelude::{ + anyhow, + bail, + cfg_into_iter, + cfg_iter, + cfg_keys, + cfg_values, + ensure, + has_duplicates, + FromBits, + Network, + Result, + }, + types::U64, +}; + +use aleo_std::prelude::*; +use core::num::NonZeroUsize; +use indexmap::IndexMap; +use lru::LruCache; +use parking_lot::RwLock; +use rand::SeedableRng; +use rand_chacha::ChaChaRng; +use std::sync::Arc; + +#[cfg(not(feature = "serial"))] +use rayon::prelude::*; + +/// The arity of the Merkle tree. +const ARITY: u8 = 8; + +/// The Merkle tree for the puzzle. +type MerkleTree = KaryMerkleTree; + +/// The puzzle trait. +pub trait PuzzleTrait: Send + Sync { + /// Initializes a new instance of the puzzle. + fn new() -> Self + where + Self: Sized; + + /// Returns the leaves for the puzzle, given the epoch hash and seeded RNG. + fn to_leaves(&self, epoch_hash: N::BlockHash, rng: &mut ChaChaRng) -> Result>>; + + /// Returns the batches of leaves for the puzzle, given the epoch hash and seeded RNGs. + fn to_all_leaves(&self, epoch_hash: N::BlockHash, rngs: Vec) -> Result>>>; +} + +#[derive(Clone)] +pub struct Puzzle { + /// The core puzzle. + inner: Arc>, + /// The LRU cache of solution IDs to proof targets. + proof_target_cache: Arc, u64>>>, +} + +impl Puzzle { + /// Initializes a new puzzle instance. + pub fn new + 'static>() -> Self { + Self { + inner: Arc::new(P::new()), + proof_target_cache: Arc::new(RwLock::new(LruCache::new(NonZeroUsize::new(1 << 10).unwrap()))), + } + } + + /// Returns the Merkle leaves for the puzzle, given the solution. + pub fn get_leaves(&self, solution: &Solution) -> Result>> { + // Initialize a seeded random number generator. + let mut rng = ChaChaRng::seed_from_u64(*solution.id()); + // Output the leaves. + self.inner.to_leaves(solution.epoch_hash(), &mut rng) + } + + /// Returns each of the Merkle leaves for the puzzle, given the solutions. + pub fn get_all_leaves(&self, solutions: &PuzzleSolutions) -> Result>>> { + // Ensure all of the solutions are for the same epoch. + ensure!( + cfg_values!(solutions).all(|solution| solution.epoch_hash() == solutions[0].epoch_hash()), + "The solutions are for different epochs" + ); + // Construct the RNGs. + let rngs = cfg_keys!(solutions).map(|solution_id| ChaChaRng::seed_from_u64(**solution_id)).collect::>(); + // Output the leaves. + self.inner.to_all_leaves(solutions[0].epoch_hash(), rngs) + } + + /// Returns the proof target given the solution. + pub fn get_proof_target(&self, solution: &Solution) -> Result { + // If the proof target is in the cache, then return it. + if let Some(proof_target) = self.proof_target_cache.write().get(&solution.id()) { + return Ok(*proof_target); + } + + // Construct the leaves of the Merkle tree. + let leaves = self.get_leaves(solution)?; + // Construct the Merkle tree. + let merkle_tree = MerkleTree::new(&Sha3_256::default(), &Sha3_256::default(), &leaves)?; + // Retrieve the Merkle tree root. + let root = merkle_tree.root(); + // Truncate to a u64. + let proof_target = match *U64::::from_bits_be(&root[0..64])? { + 0 => u64::MAX, + value => u64::MAX / value, + }; + + // Insert the proof target into the cache. + self.proof_target_cache.write().put(solution.id(), proof_target); + // Return the proof target. + Ok(proof_target) + } + + /// Returns the proof targets given the solutions. + pub fn get_proof_targets(&self, solutions: &PuzzleSolutions) -> Result> { + // Initialize the list of proof targets. + let mut targets = vec![0u64; solutions.len()]; + + // Initialize a list of solutions that need to be computed for the proof target. + let mut to_compute = Vec::new(); + // Iterate over the solutions. + for (i, (id, solution)) in solutions.iter().enumerate() { + // Check if the proof target is in the cache. + match self.proof_target_cache.write().get(id) { + // If the proof target is in the cache, then store it. + Some(proof_target) => targets[i] = *proof_target, + // Otherwise, add it to the list of solutions that need to be computed. + None => to_compute.push((i, id, *solution)), + } + } + + if !to_compute.is_empty() { + // Construct the solutions object for those that need to be computed. + let solutions_subset = PuzzleSolutions::new(to_compute.iter().map(|(_, _, solution)| *solution).collect())?; + // Construct the leaves of the Merkle tree. + let leaves = self.get_all_leaves(&solutions_subset)?; + // Construct the Merkle roots and truncate them to a u64. + let targets_subset = cfg_iter!(leaves) + .zip(cfg_keys!(solutions_subset)) + .map(|(leaves, solution_id)| { + // Construct the Merkle tree. + let merkle_tree = MerkleTree::new(&Sha3_256::default(), &Sha3_256::default(), leaves)?; + // Retrieve the Merkle tree root. + let root = merkle_tree.root(); + // Truncate to a u64. + let proof_target = match *U64::::from_bits_be(&root[0..64])? { + 0 => u64::MAX, + value => u64::MAX / value, + }; + // Insert the proof target into the cache. + self.proof_target_cache.write().put(*solution_id, proof_target); + // Return the proof target. + Ok((solution_id, proof_target)) + }) + .collect::>>()?; + + // Recombine the proof targets. + for (i, id, _) in to_compute.iter() { + targets[*i] = targets_subset[id]; + } + } + + // Return the proof targets. + Ok(targets) + } + + /// Returns the combined proof target of the solutions. + pub fn get_combined_proof_target(&self, solutions: &PuzzleSolutions) -> Result { + self.get_proof_targets(solutions)?.into_iter().try_fold(0u128, |combined, proof_target| { + combined.checked_add(proof_target as u128).ok_or_else(|| anyhow!("Combined proof target overflowed")) + }) + } + + /// Returns a solution to the puzzle. + pub fn prove( + &self, + epoch_hash: N::BlockHash, + address: Address, + counter: u64, + minimum_proof_target: Option, + ) -> Result> { + // Construct the solution. + let solution = Solution::new(epoch_hash, address, counter)?; + // Compute the proof target. + let proof_target = self.get_proof_target(&solution)?; + // Check that the minimum proof target is met. + if let Some(minimum_proof_target) = minimum_proof_target { + if proof_target < minimum_proof_target { + bail!("Solution was below the minimum proof target ({proof_target} < {minimum_proof_target})") + } + } + // Return the solution. + Ok(solution) + } + + /// Returns `Ok(())` if the solution is valid. + pub fn check_solution( + &self, + solution: &Solution, + expected_epoch_hash: N::BlockHash, + expected_proof_target: u64, + ) -> Result<()> { + // Ensure the epoch hash matches. + if solution.epoch_hash() != expected_epoch_hash { + bail!("Solution does not match the expected epoch hash") + } + // Ensure the solution is greater than or equal to the expected proof target. + if self.get_proof_target(solution)? < expected_proof_target { + bail!("Solution does not meet the proof target requirement") + } + Ok(()) + } + + /// Returns `Ok(())` if the solutions are valid. + pub fn check_solutions( + &self, + solutions: &PuzzleSolutions, + expected_epoch_hash: N::BlockHash, + expected_proof_target: u64, + ) -> Result<()> { + let timer = timer!("Puzzle::verify"); + + // Ensure the solutions are not empty. + ensure!(!solutions.is_empty(), "The solutions are empty"); + // Ensure the number of solutions does not exceed `MAX_SOLUTIONS`. + if solutions.len() > N::MAX_SOLUTIONS { + bail!("Exceed the maximum number of solutions ({} > {})", solutions.len(), N::MAX_SOLUTIONS) + } + // Ensure the solution IDs are unique. + if has_duplicates(solutions.solution_ids()) { + bail!("The solutions contain duplicate solution IDs"); + } + lap!(timer, "Perform initial checks"); + + // Ensure the epoch hash matches. + cfg_iter!(solutions).try_for_each(|(solution_id, solution)| { + if solution.epoch_hash() != expected_epoch_hash { + bail!("Solution '{solution_id}' did not match the expected epoch hash") + } + Ok(()) + })?; + lap!(timer, "Verify each epoch hash matches"); + + // Ensure the solutions meet the proof target requirement. + cfg_into_iter!(self.get_proof_targets(solutions)?).enumerate().try_for_each(|(i, proof_target)| { + if proof_target < expected_proof_target { + bail!( + "Solution '{:?}' did not meet the proof target requirement", + solutions.get_index(i).map(|(id, _)| id) + ) + } + Ok(()) + })?; + finish!(timer, "Verify each solution"); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use console::{ + account::{Address, PrivateKey}, + network::Network, + prelude::{FromBytes, TestRng, ToBits as TBits, ToBytes, Uniform}, + types::Field, + }; + + use anyhow::Result; + use core::marker::PhantomData; + use rand::{CryptoRng, Rng, RngCore, SeedableRng}; + use rand_chacha::ChaChaRng; + + type CurrentNetwork = console::network::MainnetV0; + + const ITERATIONS: u64 = 100; + + pub struct SimplePuzzle(PhantomData); + + impl PuzzleTrait for SimplePuzzle { + /// Initializes a new instance of the puzzle. + fn new() -> Self { + Self(PhantomData) + } + + /// Returns the leaves for the puzzle, given the epoch hash and seeded RNG. + fn to_leaves(&self, epoch_hash: N::BlockHash, rng: &mut ChaChaRng) -> Result>> { + // Sample a random number of leaves. + let num_leaves = self.num_leaves(epoch_hash)?; + // Sample random field elements for each of the leaves, and convert them to bits. + let leaves = (0..num_leaves).map(|_| Field::::rand(rng).to_bits_le()).collect::>(); + // Return the leaves. + Ok(leaves) + } + + /// Returns the batches of leaves for the puzzle, given the epoch hash and seeded RNGs. + fn to_all_leaves(&self, epoch_hash: N::BlockHash, rngs: Vec) -> Result>>> { + // Sample a random number of leaves. + let num_leaves = self.num_leaves(epoch_hash)?; + // Initialize the list of leaves. + let mut leaves = Vec::with_capacity(rngs.len()); + // Construct the epoch inputs. + for mut rng in rngs { + // Sample random field elements for each of the leaves, and convert them to bits. + leaves.push((0..num_leaves).map(|_| Field::::rand(&mut rng).to_bits_le()).collect::>()); + } + // Return the leaves. + Ok(leaves) + } + } + + impl SimplePuzzle { + /// Returns the number of leaves given the epoch hash. + pub fn num_leaves(&self, epoch_hash: N::BlockHash) -> Result { + const MIN_NUMBER_OF_LEAVES: usize = 100; + const MAX_NUMBER_OF_LEAVES: usize = 200; + + // Prepare the seed. + let seed = u64::from_bytes_le(&epoch_hash.to_bytes_le()?[0..8])?; + // Seed a random number generator from the epoch hash. + let mut epoch_rng = ChaChaRng::seed_from_u64(seed); + // Sample a random number of leaves. + Ok(epoch_rng.gen_range(MIN_NUMBER_OF_LEAVES..MAX_NUMBER_OF_LEAVES)) + } + } + + /// Samples a new puzzle. + fn sample_puzzle() -> Puzzle { + Puzzle::::new::>() + } + + #[test] + fn test_puzzle() { + let mut rng = TestRng::default(); + + // Initialize a new puzzle. + let puzzle = sample_puzzle(); + + // Initialize an epoch hash. + let epoch_hash = rng.gen(); + + for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS { + // Initialize the solutions. + let solutions = (0..batch_size) + .map(|_| puzzle.prove(epoch_hash, rng.gen(), rng.gen(), None).unwrap()) + .collect::>(); + let solutions = PuzzleSolutions::new(solutions).unwrap(); + + // Ensure the solutions are valid. + assert!(puzzle.check_solutions(&solutions, epoch_hash, 0u64).is_ok()); + + // Ensure the solutions are invalid. + let bad_epoch_hash = rng.gen(); + assert!(puzzle.check_solutions(&solutions, bad_epoch_hash, 0u64).is_err()); + } + } + + #[test] + fn test_prove_with_minimum_proof_target() { + let mut rng = TestRng::default(); + + // Initialize a new puzzle. + let puzzle = sample_puzzle(); + + // Initialize an epoch hash. + let epoch_hash = rng.gen(); + + for _ in 0..ITERATIONS { + let private_key = PrivateKey::::new(&mut rng).unwrap(); + let address = Address::try_from(private_key).unwrap(); + let counter = u64::rand(&mut rng); + + let solution = puzzle.prove(epoch_hash, address, counter, None).unwrap(); + let proof_target = puzzle.get_proof_target(&solution).unwrap(); + + // Assert that the operation will pass if the minimum target is low enough. + assert!(puzzle.prove(epoch_hash, address, counter, Some(proof_target)).is_ok()); + + // Assert that the operation will fail if the minimum target is too high. + assert!(puzzle.prove(epoch_hash, address, counter, Some(proof_target.saturating_add(1))).is_err()); + + let solutions = PuzzleSolutions::new(vec![solution]).unwrap(); + + // Ensure the solution is valid. + assert!(puzzle.check_solutions(&solutions, epoch_hash, proof_target).is_ok()); + + // Ensure the solution is invalid. + assert!(puzzle.check_solutions(&solutions, epoch_hash, proof_target.saturating_add(1)).is_err()); + } + } + + #[test] + fn test_prove_with_no_minimum_proof_target() { + let mut rng = rand::thread_rng(); + + // Initialize a new puzzle. + let puzzle = sample_puzzle(); + + // Initialize an epoch hash. + let epoch_hash = rng.gen(); + + // Generate inputs. + let private_key = PrivateKey::::new(&mut rng).unwrap(); + let address = Address::try_from(private_key).unwrap(); + + // Generate a solution. + let solution = puzzle.prove(epoch_hash, address, rng.gen(), None).unwrap(); + assert!(puzzle.check_solution(&solution, epoch_hash, 0u64).is_ok()); + + let solutions = PuzzleSolutions::new(vec![solution]).unwrap(); + assert!(puzzle.check_solutions(&solutions, epoch_hash, 0u64).is_ok()); + } + + #[test] + fn test_check_solutions_with_duplicate_nonces() { + let mut rng = TestRng::default(); + + // Initialize a new puzzle. + let puzzle = sample_puzzle(); + + // Initialize an epoch hash. + let epoch_hash = rng.gen(); + // Initialize an address. + let address = rng.gen(); + // Initialize a counter. + let counter = rng.gen(); + + for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS { + // Initialize the solutions. + let solutions = + (0..batch_size).map(|_| puzzle.prove(epoch_hash, address, counter, None).unwrap()).collect::>(); + // Ensure the solutions are invalid, if the batch size is greater than 1. + let solutions = match batch_size { + 1 => PuzzleSolutions::new(solutions).unwrap(), + _ => { + assert!(PuzzleSolutions::new(solutions).is_err()); + continue; + } + }; + match batch_size { + 1 => assert!(puzzle.check_solutions(&solutions, epoch_hash, 0u64).is_ok()), + _ => unreachable!("There are duplicates that should not reach this point in the test"), + } + } + } + + #[test] + fn test_get_proof_targets_without_cache() { + let mut rng = TestRng::default(); + + // Initialize an epoch hash. + let epoch_hash = rng.gen(); + + for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS { + // Initialize a new puzzle. + let puzzle = sample_puzzle(); + // Initialize the solutions. + let solutions = (0..batch_size) + .map(|_| puzzle.prove(epoch_hash, rng.gen(), rng.gen(), None).unwrap()) + .collect::>(); + let solutions = PuzzleSolutions::new(solutions).unwrap(); + + // Reinitialize the puzzle to *clear the cache*. + let puzzle = sample_puzzle(); + + // Compute the proof targets. + let proof_targets = puzzle.get_proof_targets(&solutions).unwrap(); + + // Ensure the proof targets are correct. + for ((_, solution), proof_target) in solutions.iter().zip(proof_targets) { + assert_eq!(puzzle.get_proof_target(solution).unwrap(), proof_target); + } + } + } + + #[test] + fn test_get_proof_targets_with_partial_cache() { + let mut rng = TestRng::default(); + + // Initialize an epoch hash. + let epoch_hash = rng.gen(); + + for batch_size in 1..=CurrentNetwork::MAX_SOLUTIONS { + // Initialize a new puzzle. + let puzzle = sample_puzzle(); + // Initialize the solutions. + let solutions = (0..batch_size) + .map(|_| puzzle.prove(epoch_hash, rng.gen(), rng.gen(), None).unwrap()) + .collect::>(); + let solutions = PuzzleSolutions::new(solutions).unwrap(); + + // Reinitialize the puzzle to *clear the cache*. + let puzzle = sample_puzzle(); + + // Partially fill the cache. + for solution in solutions.values() { + // Flip a coin. + if rng.gen::() { + // This operation will fill the cache. + puzzle.get_proof_target(solution).unwrap(); + } + } + + // Compute the proof targets. + let proof_targets = puzzle.get_proof_targets(&solutions).unwrap(); + + // Ensure the proof targets are correct. + for ((_, solution), proof_target) in solutions.iter().zip(proof_targets) { + assert_eq!(puzzle.get_proof_target(solution).unwrap(), proof_target); + } + } + } + + /// Use `cargo test profiler --features timer` to run this test. + #[ignore] + #[test] + fn test_profiler() -> Result<()> { + fn sample_address_and_counter(rng: &mut (impl CryptoRng + RngCore)) -> (Address, u64) { + let private_key = PrivateKey::new(rng).unwrap(); + let address = Address::try_from(private_key).unwrap(); + let counter = rng.next_u64(); + (address, counter) + } + + let mut rng = rand::thread_rng(); + + // Initialize a new puzzle. + let puzzle = sample_puzzle(); + + // Initialize an epoch hash. + let epoch_hash = rng.gen(); + + for batch_size in [1, 2, ::MAX_SOLUTIONS] { + // Generate the solutions. + let solutions = (0..batch_size) + .map(|_| { + let (address, counter) = sample_address_and_counter(&mut rng); + puzzle.prove(epoch_hash, address, counter, None).unwrap() + }) + .collect::>(); + // Construct the solutions. + let solutions = PuzzleSolutions::new(solutions).unwrap(); + // Verify the solutions. + puzzle.check_solutions(&solutions, epoch_hash, 0u64).unwrap(); + } + + bail!("\n\nRemember to #[ignore] this test!\n\n") + } +} diff --git a/ledger/coinbase/src/helpers/partial_solution/bytes.rs b/ledger/puzzle/src/solution/bytes.rs similarity index 61% rename from ledger/coinbase/src/helpers/partial_solution/bytes.rs rename to ledger/puzzle/src/solution/bytes.rs index 57ef315034..b8022389cd 100644 --- a/ledger/coinbase/src/helpers/partial_solution/bytes.rs +++ b/ledger/puzzle/src/solution/bytes.rs @@ -14,23 +14,23 @@ use super::*; -impl FromBytes for PartialSolution { - /// Reads the partial solution from the buffer. +impl FromBytes for Solution { + /// Reads the solution from the buffer. fn read_le(mut reader: R) -> IoResult { - let address: Address = FromBytes::read_le(&mut reader)?; - let nonce = u64::read_le(&mut reader)?; - let commitment = KZGCommitment::read_le(&mut reader)?; + let epoch_hash = N::BlockHash::read_le(&mut reader)?; + let address = Address::::read_le(&mut reader)?; + let counter = u64::read_le(&mut reader)?; - Ok(Self::new(address, nonce, commitment)) + Self::new(epoch_hash, address, counter).map_err(error) } } -impl ToBytes for PartialSolution { - /// Writes the partial solution to the buffer. +impl ToBytes for Solution { + /// Writes the solution to the buffer. fn write_le(&self, mut writer: W) -> IoResult<()> { + self.epoch_hash.write_le(&mut writer)?; self.address.write_le(&mut writer)?; - self.nonce.write_le(&mut writer)?; - self.commitment.write_le(&mut writer) + self.counter.write_le(&mut writer) } } @@ -47,13 +47,13 @@ mod tests { let private_key = PrivateKey::::new(&mut rng)?; let address = Address::try_from(private_key)?; - // Sample a new partial solution. - let expected = PartialSolution::new(address, u64::rand(&mut rng), KZGCommitment(rng.gen())); + // Sample a new solution. + let expected = Solution::new(rng.gen(), address, u64::rand(&mut rng)).unwrap(); // Check the byte representation. let expected_bytes = expected.to_bytes_le()?; - assert_eq!(expected, PartialSolution::read_le(&expected_bytes[..])?); - assert!(PartialSolution::::read_le(&expected_bytes[1..]).is_err()); + assert_eq!(expected, Solution::read_le(&expected_bytes[..])?); + assert!(Solution::::read_le(&expected_bytes[1..]).is_err()); Ok(()) } diff --git a/ledger/puzzle/src/solution/mod.rs b/ledger/puzzle/src/solution/mod.rs new file mode 100644 index 0000000000..e8a2ecce40 --- /dev/null +++ b/ledger/puzzle/src/solution/mod.rs @@ -0,0 +1,63 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod bytes; +mod serialize; +mod string; + +use crate::SolutionID; +use console::{account::Address, network::prelude::*, prelude::DeserializeExt}; + +/// The solution for the puzzle from a prover. +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct Solution { + /// The solution ID. + solution_id: SolutionID, + /// The epoch hash. + epoch_hash: N::BlockHash, + /// The address of the prover. + address: Address, + /// The counter for the solution. + counter: u64, +} + +impl Solution { + /// Initializes a new instance of the solution. + pub fn new(epoch_hash: N::BlockHash, address: Address, counter: u64) -> Result { + // Compute the solution ID. + let solution_id = SolutionID::new(epoch_hash, address, counter)?; + // Return the partial solution. + Ok(Self { solution_id, epoch_hash, address, counter }) + } + + /// Returns the solution ID. + pub const fn id(&self) -> SolutionID { + self.solution_id + } + + /// Returns the epoch hash of the solution. + pub const fn epoch_hash(&self) -> N::BlockHash { + self.epoch_hash + } + + /// Returns the address of the prover. + pub const fn address(&self) -> Address { + self.address + } + + /// Returns the counter for the solution. + pub const fn counter(&self) -> u64 { + self.counter + } +} diff --git a/ledger/coinbase/src/helpers/partial_solution/serialize.rs b/ledger/puzzle/src/solution/serialize.rs similarity index 57% rename from ledger/coinbase/src/helpers/partial_solution/serialize.rs rename to ledger/puzzle/src/solution/serialize.rs index c71cfdb89e..a96034e56e 100644 --- a/ledger/coinbase/src/helpers/partial_solution/serialize.rs +++ b/ledger/puzzle/src/solution/serialize.rs @@ -14,37 +14,47 @@ use super::*; -use snarkvm_utilities::DeserializeExt; - -impl Serialize for PartialSolution { - /// Serializes the partial solution to a JSON-string or buffer. +impl Serialize for Solution { + /// Serializes the solution to a JSON-string or buffer. fn serialize(&self, serializer: S) -> Result { match serializer.is_human_readable() { true => { - let mut partial_prover_solution = serializer.serialize_struct("PartialSolution", 3)?; - partial_prover_solution.serialize_field("address", &self.address)?; - partial_prover_solution.serialize_field("nonce", &self.nonce)?; - partial_prover_solution.serialize_field("commitment", &self.commitment)?; - partial_prover_solution.end() + let mut partial_solution = serializer.serialize_struct("Solution", 4)?; + partial_solution.serialize_field("solution_id", &self.solution_id)?; + partial_solution.serialize_field("epoch_hash", &self.epoch_hash)?; + partial_solution.serialize_field("address", &self.address)?; + partial_solution.serialize_field("counter", &self.counter)?; + partial_solution.end() } false => ToBytesSerializer::serialize_with_size_encoding(self, serializer), } } } -impl<'de, N: Network> Deserialize<'de> for PartialSolution { - /// Deserializes the partial solution from a JSON-string or buffer. +impl<'de, N: Network> Deserialize<'de> for Solution { + /// Deserializes the solution from a JSON-string or buffer. fn deserialize>(deserializer: D) -> Result { match deserializer.is_human_readable() { true => { - let mut partial_prover_solution = serde_json::Value::deserialize(deserializer)?; - Ok(Self::new( - DeserializeExt::take_from_value::(&mut partial_prover_solution, "address")?, - DeserializeExt::take_from_value::(&mut partial_prover_solution, "nonce")?, - >::take_from_value::(&mut partial_prover_solution, "commitment")?, - )) + let mut partial_solution = serde_json::Value::deserialize(deserializer)?; + let solution_id: SolutionID = + DeserializeExt::take_from_value::(&mut partial_solution, "solution_id")?; + + // Recover the partial solution. + let solution = Self::new( + DeserializeExt::take_from_value::(&mut partial_solution, "epoch_hash")?, + DeserializeExt::take_from_value::(&mut partial_solution, "address")?, + DeserializeExt::take_from_value::(&mut partial_solution, "counter")?, + ) + .map_err(de::Error::custom)?; + + // Ensure the solution ID matches. + match solution_id == solution.id() { + true => Ok(solution), + false => Err(error("Mismatching solution ID, possible data corruption")).map_err(de::Error::custom), + } } - false => FromBytesDeserializer::::deserialize_with_size_encoding(deserializer, "partial solution"), + false => FromBytesDeserializer::::deserialize_with_size_encoding(deserializer, "solution"), } } } @@ -62,8 +72,8 @@ mod tests { let private_key = PrivateKey::::new(&mut rng)?; let address = Address::try_from(private_key)?; - // Sample a new partial solution. - let expected = PartialSolution::new(address, u64::rand(&mut rng), KZGCommitment(rng.gen())); + // Sample a new solution. + let expected = Solution::new(rng.gen(), address, u64::rand(&mut rng)).unwrap(); // Serialize let expected_string = &expected.to_string(); @@ -71,7 +81,7 @@ mod tests { assert_eq!(expected, serde_json::from_str(&candidate_string)?); // Deserialize - assert_eq!(expected, PartialSolution::from_str(expected_string)?); + assert_eq!(expected, Solution::from_str(expected_string)?); assert_eq!(expected, serde_json::from_str(&candidate_string)?); Ok(()) @@ -83,8 +93,8 @@ mod tests { let private_key = PrivateKey::::new(&mut rng)?; let address = Address::try_from(private_key)?; - // Sample a new partial solution. - let expected = PartialSolution::new(address, u64::rand(&mut rng), KZGCommitment(rng.gen())); + // Sample a new solution. + let expected = Solution::new(rng.gen(), address, u64::rand(&mut rng)).unwrap(); // Serialize let expected_bytes = expected.to_bytes_le()?; @@ -92,7 +102,7 @@ mod tests { assert_eq!(&expected_bytes[..], &expected_bytes_with_size_encoding[8..]); // Deserialize - assert_eq!(expected, PartialSolution::read_le(&expected_bytes[..])?); + assert_eq!(expected, Solution::read_le(&expected_bytes[..])?); assert_eq!(expected, bincode::deserialize(&expected_bytes_with_size_encoding[..])?); Ok(()) diff --git a/ledger/coinbase/src/helpers/partial_solution/string.rs b/ledger/puzzle/src/solution/string.rs similarity index 65% rename from ledger/coinbase/src/helpers/partial_solution/string.rs rename to ledger/puzzle/src/solution/string.rs index d194cc2121..187fd4870b 100644 --- a/ledger/coinbase/src/helpers/partial_solution/string.rs +++ b/ledger/puzzle/src/solution/string.rs @@ -14,24 +14,24 @@ use super::*; -impl FromStr for PartialSolution { +impl FromStr for Solution { type Err = Error; - /// Initializes the partial solution from a JSON-string. - fn from_str(partial_prover_solution: &str) -> Result { - Ok(serde_json::from_str(partial_prover_solution)?) + /// Initializes the solution from a JSON-string. + fn from_str(solution: &str) -> Result { + Ok(serde_json::from_str(solution)?) } } -impl Debug for PartialSolution { - /// Prints the partial solution as a JSON-string. +impl Debug for Solution { + /// Prints the solution as a JSON-string. fn fmt(&self, f: &mut Formatter) -> fmt::Result { Display::fmt(self, f) } } -impl Display for PartialSolution { - /// Displays the partial solution as a JSON-string. +impl Display for Solution { + /// Displays the solution as a JSON-string. fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{}", serde_json::to_string(self).map_err::(ser::Error::custom)?) } @@ -50,12 +50,12 @@ mod tests { let private_key = PrivateKey::::new(&mut rng)?; let address = Address::try_from(private_key)?; - // Sample a new partial solution. - let expected = PartialSolution::new(address, u64::rand(&mut rng), KZGCommitment(rng.gen())); + // Sample a new solution. + let expected = Solution::new(rng.gen(), address, u64::rand(&mut rng)).unwrap(); // Check the string representation. - let candidate = format!("{expected}"); - assert_eq!(expected, PartialSolution::from_str(&candidate)?); + let candidate = expected.to_string(); + assert_eq!(expected, Solution::from_str(&candidate)?); Ok(()) } diff --git a/ledger/coinbase/src/helpers/puzzle_commitment/bytes.rs b/ledger/puzzle/src/solution_id/bytes.rs similarity index 61% rename from ledger/coinbase/src/helpers/puzzle_commitment/bytes.rs rename to ledger/puzzle/src/solution_id/bytes.rs index f76cca0553..34edbb8521 100644 --- a/ledger/coinbase/src/helpers/puzzle_commitment/bytes.rs +++ b/ledger/puzzle/src/solution_id/bytes.rs @@ -14,19 +14,19 @@ use super::*; -impl FromBytes for PuzzleCommitment { - /// Reads the puzzle commitment from the buffer. +impl FromBytes for SolutionID { + /// Reads the solution ID from the buffer. fn read_le(mut reader: R) -> IoResult { - let commitment = KZGCommitment::read_le(&mut reader)?; + let nonce = u64::read_le(&mut reader)?; - Ok(Self::new(commitment)) + Ok(Self::from(nonce)) } } -impl ToBytes for PuzzleCommitment { - /// Writes the puzzle commitment to the buffer. +impl ToBytes for SolutionID { + /// Writes the solution ID to the buffer. fn write_le(&self, mut writer: W) -> IoResult<()> { - self.commitment.write_le(&mut writer) + self.0.write_le(&mut writer) } } @@ -40,14 +40,14 @@ mod tests { #[test] fn test_bytes() -> Result<()> { let mut rng = TestRng::default(); - // Sample a new puzzle commitment. - let expected = PuzzleCommitment::::new(KZGCommitment(rng.gen())); + // Sample a new solution ID. + let expected = SolutionID::::from(rng.gen::()); // Check the byte representation. let expected_bytes = expected.to_bytes_le()?; - assert_eq!(expected_bytes.len(), 48); - assert_eq!(expected, PuzzleCommitment::read_le(&expected_bytes[..])?); - assert!(PuzzleCommitment::::read_le(&expected_bytes[1..]).is_err()); + assert_eq!(expected_bytes.len(), 8); + assert_eq!(expected, SolutionID::read_le(&expected_bytes[..])?); + assert!(SolutionID::::read_le(&expected_bytes[1..]).is_err()); Ok(()) } diff --git a/ledger/puzzle/src/solution_id/mod.rs b/ledger/puzzle/src/solution_id/mod.rs new file mode 100644 index 0000000000..433f4b992d --- /dev/null +++ b/ledger/puzzle/src/solution_id/mod.rs @@ -0,0 +1,56 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod bytes; +mod serialize; +mod string; + +pub use string::SOLUTION_ID_PREFIX; + +use console::{account::Address, network::prelude::*}; +use snarkvm_algorithms::crypto_hash::sha256d_to_u64; + +use core::marker::PhantomData; + +/// The solution ID. +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct SolutionID(u64, PhantomData); + +impl From for SolutionID { + /// Initializes a new instance of the solution ID. + fn from(nonce: u64) -> Self { + Self(nonce, PhantomData) + } +} + +impl SolutionID { + /// Initializes the solution ID from the given epoch hash, address, and counter. + pub fn new(epoch_hash: N::BlockHash, address: Address, counter: u64) -> Result { + // Construct the nonce as sha256d(epoch_hash_bytes_le[0..8] || address || counter). + let mut bytes_le = Vec::new(); + let lower_bytes = &epoch_hash.to_bytes_le()?[0..8]; + bytes_le.extend_from_slice(lower_bytes); + bytes_le.extend_from_slice(&address.to_bytes_le()?); + bytes_le.extend_from_slice(&counter.to_bytes_le()?); + Ok(Self::from(sha256d_to_u64(&bytes_le))) + } +} + +impl Deref for SolutionID { + type Target = u64; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/ledger/coinbase/src/helpers/puzzle_commitment/serialize.rs b/ledger/puzzle/src/solution_id/serialize.rs similarity index 75% rename from ledger/coinbase/src/helpers/puzzle_commitment/serialize.rs rename to ledger/puzzle/src/solution_id/serialize.rs index 61283b5551..b2f26abcf0 100644 --- a/ledger/coinbase/src/helpers/puzzle_commitment/serialize.rs +++ b/ledger/puzzle/src/solution_id/serialize.rs @@ -14,8 +14,8 @@ use super::*; -impl Serialize for PuzzleCommitment { - /// Serializes the puzzle commitment to a string or buffer. +impl Serialize for SolutionID { + /// Serializes the solution ID to a string or buffer. fn serialize(&self, serializer: S) -> Result { match serializer.is_human_readable() { true => serializer.collect_str(self), @@ -24,12 +24,12 @@ impl Serialize for PuzzleCommitment { } } -impl<'de, N: Network> Deserialize<'de> for PuzzleCommitment { - /// Deserializes the puzzle commitment from a string or buffer. +impl<'de, N: Network> Deserialize<'de> for SolutionID { + /// Deserializes the solution ID from a string or buffer. fn deserialize>(deserializer: D) -> Result { match deserializer.is_human_readable() { true => FromStr::from_str(&String::deserialize(deserializer)?).map_err(de::Error::custom), - false => FromBytesDeserializer::::deserialize_with_size_encoding(deserializer, "puzzle commitment"), + false => FromBytesDeserializer::::deserialize_with_size_encoding(deserializer, "solution ID"), } } } @@ -45,8 +45,8 @@ mod tests { fn test_serde_json() -> Result<()> { let mut rng = TestRng::default(); - // Sample a new puzzle commitment. - let expected = PuzzleCommitment::::new(KZGCommitment(rng.gen())); + // Sample a new solution ID. + let expected = SolutionID::::from(rng.gen::()); // Serialize let expected_string = &expected.to_string(); @@ -54,7 +54,7 @@ mod tests { assert_eq!(expected, serde_json::from_str(&candidate_string)?); // Deserialize - assert_eq!(expected, PuzzleCommitment::from_str(expected_string)?); + assert_eq!(expected, SolutionID::from_str(expected_string)?); assert_eq!(expected, serde_json::from_str(&candidate_string)?); Ok(()) @@ -64,8 +64,8 @@ mod tests { fn test_bincode() -> Result<()> { let mut rng = TestRng::default(); - // Sample a new puzzle commitment. - let expected = PuzzleCommitment::::new(KZGCommitment(rng.gen())); + // Sample a new solution ID. + let expected = SolutionID::::from(rng.gen::()); // Serialize let expected_bytes = expected.to_bytes_le()?; @@ -73,7 +73,7 @@ mod tests { assert_eq!(&expected_bytes[..], &expected_bytes_with_size_encoding[8..]); // Deserialize - assert_eq!(expected, PuzzleCommitment::read_le(&expected_bytes[..])?); + assert_eq!(expected, SolutionID::read_le(&expected_bytes[..])?); assert_eq!(expected, bincode::deserialize(&expected_bytes_with_size_encoding[..])?); Ok(()) diff --git a/ledger/coinbase/src/helpers/puzzle_commitment/string.rs b/ledger/puzzle/src/solution_id/string.rs similarity index 50% rename from ledger/coinbase/src/helpers/puzzle_commitment/string.rs rename to ledger/puzzle/src/solution_id/string.rs index 3560889e60..760b5dfc73 100644 --- a/ledger/coinbase/src/helpers/puzzle_commitment/string.rs +++ b/ledger/puzzle/src/solution_id/string.rs @@ -14,41 +14,41 @@ use super::*; -pub static PUZZLE_COMMITMENT_PREFIX: &str = "puzzle"; +pub static SOLUTION_ID_PREFIX: &str = "solution"; -impl FromStr for PuzzleCommitment { +impl FromStr for SolutionID { type Err = Error; - /// Reads in the puzzle commitment string. - fn from_str(puzzle_commitment: &str) -> Result { - // Decode the puzzle commitment string from bech32m. - let (hrp, data, variant) = bech32::decode(puzzle_commitment)?; - if hrp != PUZZLE_COMMITMENT_PREFIX { - bail!("Failed to decode puzzle commitment: '{hrp}' is an invalid prefix") + /// Reads in the solution ID string. + fn from_str(solution_id: &str) -> Result { + // Decode the solution ID string from bech32m. + let (hrp, data, variant) = bech32::decode(solution_id)?; + if hrp != SOLUTION_ID_PREFIX { + bail!("Failed to decode solution ID: '{hrp}' is an invalid prefix") } else if data.is_empty() { - bail!("Failed to decode puzzle commitment: data field is empty") + bail!("Failed to decode solution ID: data field is empty") } else if variant != bech32::Variant::Bech32m { - bail!("Found a puzzle commitment that is not bech32m encoded: {puzzle_commitment}"); + bail!("Found a solution ID that is not bech32m encoded: {solution_id}"); } - // Decode the puzzle commitment data from u5 to u8, and into the puzzle commitment. + // Decode the solution ID data from u5 to u8, and into the solution ID. Ok(Self::read_le(&Vec::from_base32(&data)?[..])?) } } -impl Debug for PuzzleCommitment { +impl Debug for SolutionID { fn fmt(&self, f: &mut Formatter) -> fmt::Result { Display::fmt(self, f) } } -impl Display for PuzzleCommitment { - /// Writes the puzzle commitment as a bech32m string. +impl Display for SolutionID { + /// Writes the solution ID as a bech32m string. fn fmt(&self, f: &mut Formatter) -> fmt::Result { - // Convert the puzzle commitment to bytes. + // Convert the solution ID to bytes. let bytes = self.to_bytes_le().map_err(|_| fmt::Error)?; // Encode the bytes into bech32m. - let string = bech32::encode(PUZZLE_COMMITMENT_PREFIX, bytes.to_base32(), bech32::Variant::Bech32m) - .map_err(|_| fmt::Error)?; + let string = + bech32::encode(SOLUTION_ID_PREFIX, bytes.to_base32(), bech32::Variant::Bech32m).map_err(|_| fmt::Error)?; // Output the string. Display::fmt(&string, f) } @@ -66,19 +66,19 @@ mod tests { #[test] fn test_string() -> Result<()> { // Ensure type and empty value fails. - assert!(PuzzleCommitment::::from_str(&format!("{PUZZLE_COMMITMENT_PREFIX}1")).is_err()); - assert!(PuzzleCommitment::::from_str("").is_err()); + assert!(SolutionID::::from_str(&format!("{SOLUTION_ID_PREFIX}1")).is_err()); + assert!(SolutionID::::from_str("").is_err()); let mut rng = TestRng::default(); for _ in 0..ITERATIONS { - // Sample a new puzzle commitment. - let expected = PuzzleCommitment::::new(KZGCommitment(rng.gen())); + // Sample a new solution ID. + let expected = SolutionID::::from(rng.gen::()); // Check the string representation. let candidate = format!("{expected}"); - assert_eq!(expected, PuzzleCommitment::from_str(&candidate)?); - assert_eq!(PUZZLE_COMMITMENT_PREFIX, candidate.to_string().split('1').next().unwrap()); + assert_eq!(expected, SolutionID::from_str(&candidate)?); + assert_eq!(SOLUTION_ID_PREFIX, candidate.to_string().split('1').next().unwrap()); } Ok(()) } @@ -88,14 +88,14 @@ mod tests { let mut rng = TestRng::default(); for _ in 0..ITERATIONS { - // Sample a new puzzle commitment. - let expected = PuzzleCommitment::::new(KZGCommitment(rng.gen())); + // Sample a new solution ID. + let expected = SolutionID::::from(rng.gen::()); let candidate = expected.to_string(); assert_eq!(format!("{expected}"), candidate); - assert_eq!(PUZZLE_COMMITMENT_PREFIX, candidate.split('1').next().unwrap()); + assert_eq!(SOLUTION_ID_PREFIX, candidate.split('1').next().unwrap()); - let candidate_recovered = PuzzleCommitment::::from_str(&candidate.to_string())?; + let candidate_recovered = SolutionID::::from_str(&candidate.to_string())?; assert_eq!(expected, candidate_recovered); } Ok(()) diff --git a/ledger/coinbase/src/helpers/coinbase_solution/bytes.rs b/ledger/puzzle/src/solutions/bytes.rs similarity index 69% rename from ledger/coinbase/src/helpers/coinbase_solution/bytes.rs rename to ledger/puzzle/src/solutions/bytes.rs index 66d541a3fb..4716c471ec 100644 --- a/ledger/coinbase/src/helpers/coinbase_solution/bytes.rs +++ b/ledger/puzzle/src/solutions/bytes.rs @@ -14,26 +14,26 @@ use super::*; -impl FromBytes for CoinbaseSolution { +impl FromBytes for PuzzleSolutions { /// Reads the solutions from the buffer. fn read_le(mut reader: R) -> IoResult { // Read the number of solutions. - let num_solutions: u16 = FromBytes::read_le(&mut reader)?; + let num_solutions: u8 = FromBytes::read_le(&mut reader)?; // Read the solutions. - let mut prover_solutions = Vec::with_capacity(num_solutions as usize); + let mut solutions = Vec::with_capacity(num_solutions as usize); for _ in 0..num_solutions { - prover_solutions.push(ProverSolution::read_le(&mut reader)?); + solutions.push(Solution::read_le(&mut reader)?); } // Return the solutions. - Self::new(prover_solutions).map_err(error) + Self::new(solutions).map_err(error) } } -impl ToBytes for CoinbaseSolution { +impl ToBytes for PuzzleSolutions { /// Writes the solutions to the buffer. fn write_le(&self, mut writer: W) -> IoResult<()> { // Write the number of solutions. - (u16::try_from(self.solutions.len()).map_err(|e| error(e.to_string()))?).write_le(&mut writer)?; + (u8::try_from(self.solutions.len()).map_err(error)?).write_le(&mut writer)?; // Write the solutions. for solution in self.solutions.values() { solution.write_le(&mut writer)?; @@ -51,11 +51,11 @@ mod tests { let mut rng = TestRng::default(); // Sample random solutions. - let expected = crate::helpers::coinbase_solution::serialize::tests::sample_solutions(&mut rng); + let expected = crate::solutions::serialize::tests::sample_solutions(&mut rng); // Check the byte representation. let expected_bytes = expected.to_bytes_le()?; - assert_eq!(expected, CoinbaseSolution::read_le(&expected_bytes[..])?); + assert_eq!(expected, PuzzleSolutions::read_le(&expected_bytes[..])?); Ok(()) } } diff --git a/ledger/puzzle/src/solutions/mod.rs b/ledger/puzzle/src/solutions/mod.rs new file mode 100644 index 0000000000..906a2f6066 --- /dev/null +++ b/ledger/puzzle/src/solutions/mod.rs @@ -0,0 +1,186 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod bytes; +mod serialize; +mod string; + +use crate::{Solution, SolutionID}; +use console::{network::prelude::*, prelude::DeserializeExt, types::Field}; +use indexmap::IndexMap; + +/// The individual solutions. +#[derive(Clone, Eq, PartialEq)] +pub struct PuzzleSolutions { + /// The solutions for the puzzle. + solutions: IndexMap, Solution>, +} + +impl PuzzleSolutions { + /// Initializes a new instance of the solutions. + pub fn new(solutions: Vec>) -> Result { + // Ensure the solutions are not empty. + ensure!(!solutions.is_empty(), "There are no solutions to verify for the puzzle"); + // Ensure the number of solutions does not exceed `MAX_SOLUTIONS`. + if solutions.len() > N::MAX_SOLUTIONS { + bail!("Exceeded the maximum number of solutions ({} > {})", solutions.len(), N::MAX_SOLUTIONS); + } + // Ensure the solution IDs are unique. + if has_duplicates(solutions.iter().map(Solution::id)) { + bail!("The solutions contain duplicate solution IDs"); + } + // Return the solutions. + Ok(Self { solutions: solutions.into_iter().map(|solution| (solution.id(), solution)).collect() }) + } + + /// Returns the solution IDs. + pub fn solution_ids(&self) -> impl '_ + Iterator> { + self.solutions.keys() + } + + /// Returns the number of solutions. + pub fn len(&self) -> usize { + self.solutions.len() + } + + /// Returns `true` if there are no solutions. + pub fn is_empty(&self) -> bool { + self.solutions.is_empty() + } + + /// Returns the solution for the given solution ID. + pub fn get_solution(&self, solution_id: &SolutionID) -> Option<&Solution> { + self.solutions.get(solution_id) + } + + /// Returns the accumulator challenge point. + pub fn to_accumulator_point(&self) -> Result> { + // Encode the solution IDs as field elements. + let mut preimage = self.solution_ids().map(|id| Field::from_u64(**id)).collect::>(); + // Pad the preimage to the required length. + preimage.resize(N::MAX_SOLUTIONS, Field::zero()); + // Hash the preimage to obtain the accumulator point. + N::hash_psd8(&preimage) + } +} + +impl Deref for PuzzleSolutions { + type Target = IndexMap, Solution>; + + fn deref(&self) -> &Self::Target { + &self.solutions + } +} + +#[cfg(test)] +mod tests { + use super::*; + use console::account::{Address, PrivateKey}; + + use std::collections::HashSet; + + type CurrentNetwork = console::network::MainnetV0; + + /// Returns the solutions for the given number of solutions. + pub(crate) fn sample_solutions_with_count( + num_solutions: usize, + rng: &mut TestRng, + ) -> PuzzleSolutions { + // Sample a new solutions. + let mut solutions = vec![]; + for _ in 0..num_solutions { + let private_key = PrivateKey::::new(rng).unwrap(); + let address = Address::try_from(private_key).unwrap(); + + let solution = Solution::new(rng.gen(), address, u64::rand(rng)).unwrap(); + solutions.push(solution); + } + PuzzleSolutions::new(solutions).unwrap() + } + + #[test] + fn test_new_is_not_empty() { + // Ensure the solutions are not empty. + assert!(PuzzleSolutions::::new(vec![]).is_err()); + } + + #[test] + fn test_len() { + let mut rng = TestRng::default(); + + for num_solutions in 1..::MAX_SOLUTIONS { + // Sample random solutions. + let solutions = sample_solutions_with_count(num_solutions, &mut rng); + // Ensure the number of solutions is correct. + assert_eq!(num_solutions, solutions.len()); + } + } + + #[test] + fn test_is_empty() { + let mut rng = TestRng::default(); + + for num_solutions in 1..::MAX_SOLUTIONS { + // Sample random solutions. + let solutions = sample_solutions_with_count(num_solutions, &mut rng); + // Ensure the solutions are not empty. + assert!(!solutions.is_empty()); + } + } + + #[test] + fn test_solution_ids() { + let mut rng = TestRng::default(); + + for num_solutions in 1..::MAX_SOLUTIONS { + // Sample random solutions. + let solutions = sample_solutions_with_count(num_solutions, &mut rng); + // Ensure the solution IDs are unique. + assert_eq!(num_solutions, solutions.solution_ids().collect::>().len()); + } + } + + #[test] + fn test_get_solution() { + let mut rng = TestRng::default(); + + for num_solutions in 1..::MAX_SOLUTIONS { + // Sample random solutions. + let solutions = sample_solutions_with_count(num_solutions, &mut rng); + // Ensure the solutions are not empty. + for solution_id in solutions.solution_ids() { + assert_eq!(solutions.get_solution(solution_id).unwrap().id(), *solution_id); + } + } + } + + #[test] + fn test_to_accumulator_point() { + let mut rng = TestRng::default(); + + for num_solutions in 1..::MAX_SOLUTIONS { + // Sample random solutions. + let solutions = crate::solutions::tests::sample_solutions_with_count(num_solutions, &mut rng); + // Compute the candidate accumulator point. + let candidate = solutions.to_accumulator_point().unwrap(); + // Compute the expected accumulator point. + let mut preimage = vec![Field::zero(); ::MAX_SOLUTIONS]; + for (i, id) in solutions.keys().enumerate() { + preimage[i] = Field::from_u64(**id); + } + let expected = ::hash_psd8(&preimage).unwrap(); + assert_eq!(expected, candidate); + } + } +} diff --git a/ledger/coinbase/src/helpers/coinbase_solution/serialize.rs b/ledger/puzzle/src/solutions/serialize.rs similarity index 79% rename from ledger/coinbase/src/helpers/coinbase_solution/serialize.rs rename to ledger/puzzle/src/solutions/serialize.rs index ab461e7023..b377c7ce6c 100644 --- a/ledger/coinbase/src/helpers/coinbase_solution/serialize.rs +++ b/ledger/puzzle/src/solutions/serialize.rs @@ -14,9 +14,7 @@ use super::*; -use snarkvm_utilities::DeserializeExt; - -impl Serialize for CoinbaseSolution { +impl Serialize for PuzzleSolutions { /// Serializes the solutions to a JSON-string or buffer. fn serialize(&self, serializer: S) -> Result { match serializer.is_human_readable() { @@ -30,7 +28,7 @@ impl Serialize for CoinbaseSolution { } } -impl<'de, N: Network> Deserialize<'de> for CoinbaseSolution { +impl<'de, N: Network> Deserialize<'de> for PuzzleSolutions { /// Deserializes the solutions from a JSON-string or buffer. fn deserialize>(deserializer: D) -> Result { match deserializer.is_human_readable() { @@ -46,21 +44,21 @@ impl<'de, N: Network> Deserialize<'de> for CoinbaseSolution { #[cfg(test)] pub(super) mod tests { use super::*; - use console::account::PrivateKey; + use console::account::{Address, PrivateKey}; type CurrentNetwork = console::network::MainnetV0; - pub(crate) fn sample_solutions(rng: &mut TestRng) -> CoinbaseSolution { + pub(crate) fn sample_solutions(rng: &mut TestRng) -> PuzzleSolutions { // Sample a new solutions. - let mut prover_solutions = vec![]; - for _ in 0..rng.gen_range(1..10) { + let mut solutions = vec![]; + for _ in 0..rng.gen_range(1..CurrentNetwork::MAX_SOLUTIONS) { let private_key = PrivateKey::::new(rng).unwrap(); let address = Address::try_from(private_key).unwrap(); - let partial_solution = PartialSolution::new(address, u64::rand(rng), KZGCommitment(rng.gen())); - prover_solutions.push(ProverSolution::new(partial_solution, KZGProof { w: rng.gen(), random_v: None })); + let solution = Solution::new(rng.gen(), address, u64::rand(rng)).unwrap(); + solutions.push(solution); } - CoinbaseSolution::new(prover_solutions).unwrap() + PuzzleSolutions::new(solutions).unwrap() } #[test] @@ -76,7 +74,7 @@ pub(super) mod tests { assert_eq!(expected, serde_json::from_str(&candidate_string)?); // Deserialize - assert_eq!(expected, CoinbaseSolution::from_str(expected_string)?); + assert_eq!(expected, PuzzleSolutions::from_str(expected_string)?); assert_eq!(expected, serde_json::from_str(&candidate_string)?); Ok(()) @@ -95,7 +93,7 @@ pub(super) mod tests { assert_eq!(&expected_bytes[..], &expected_bytes_with_size_encoding[8..]); // Deserialize - assert_eq!(expected, CoinbaseSolution::read_le(&expected_bytes[..])?); + assert_eq!(expected, PuzzleSolutions::read_le(&expected_bytes[..])?); assert_eq!(expected, bincode::deserialize(&expected_bytes_with_size_encoding[..])?); Ok(()) diff --git a/ledger/coinbase/src/helpers/coinbase_solution/string.rs b/ledger/puzzle/src/solutions/string.rs similarity index 82% rename from ledger/coinbase/src/helpers/coinbase_solution/string.rs rename to ledger/puzzle/src/solutions/string.rs index cf44a84a39..7de56f52a2 100644 --- a/ledger/coinbase/src/helpers/coinbase_solution/string.rs +++ b/ledger/puzzle/src/solutions/string.rs @@ -14,7 +14,7 @@ use super::*; -impl FromStr for CoinbaseSolution { +impl FromStr for PuzzleSolutions { type Err = Error; /// Initializes the solutions from a JSON-string. @@ -23,14 +23,14 @@ impl FromStr for CoinbaseSolution { } } -impl Debug for CoinbaseSolution { +impl Debug for PuzzleSolutions { /// Prints the solutions as a JSON-string. fn fmt(&self, f: &mut Formatter) -> fmt::Result { Display::fmt(self, f) } } -impl Display for CoinbaseSolution { +impl Display for PuzzleSolutions { /// Displays the solutions as a JSON-string. fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{}", serde_json::to_string(self).map_err::(ser::Error::custom)?) @@ -46,11 +46,11 @@ mod tests { let mut rng = TestRng::default(); // Sample random solutions. - let expected = crate::helpers::coinbase_solution::serialize::tests::sample_solutions(&mut rng); + let expected = crate::solutions::serialize::tests::sample_solutions(&mut rng); // Check the string representation. let candidate = format!("{expected}"); - assert_eq!(expected, CoinbaseSolution::from_str(&candidate)?); + assert_eq!(expected, PuzzleSolutions::from_str(&candidate)?); Ok(()) } diff --git a/ledger/src/advance.rs b/ledger/src/advance.rs index 8fdb2315b7..f94e9998e1 100644 --- a/ledger/src/advance.rs +++ b/ledger/src/advance.rs @@ -51,7 +51,7 @@ impl> Ledger { &self, private_key: &PrivateKey, candidate_ratifications: Vec>, - candidate_solutions: Vec>, + candidate_solutions: Vec>, candidate_transactions: Vec>, rng: &mut R, ) -> Result> { @@ -102,10 +102,10 @@ impl> Ledger { *self.current_committee.write() = Some(current_committee); } - // If the block is the start of a new epoch, or the epoch challenge has not been set, update the current epoch challenge. - if block.height() % N::NUM_BLOCKS_PER_EPOCH == 0 || self.current_epoch_challenge.read().is_none() { - // Update the current epoch challenge. - self.current_epoch_challenge.write().clone_from(&self.get_epoch_challenge(block.height()).ok()); + // If the block is the start of a new epoch, or the epoch hash has not been set, update the current epoch hash. + if block.height() % N::NUM_BLOCKS_PER_EPOCH == 0 || self.current_epoch_hash.read().is_none() { + // Update the current epoch hash. + self.current_epoch_hash.write().clone_from(&self.get_epoch_hash(block.height()).ok()); } Ok(()) @@ -119,8 +119,8 @@ pub fn split_candidate_solutions( verification_fn: F, ) -> (Vec, Vec) where - T: Sized + Send, - F: Fn(&T) -> bool + Send + Sync, + T: Sized, + F: Fn(&T) -> bool, { // Separate the candidate solutions into valid and aborted solutions. let mut valid_candidate_solutions = Vec::with_capacity(max_solutions); @@ -146,7 +146,8 @@ where }; // Verify the solutions in the chunk. - let verification_results: Vec<_> = cfg_into_iter!(candidates_chunk) + let verification_results: Vec<_> = candidates_chunk + .into_iter() .rev() .map(|solution| { let verified = verification_fn(&solution); @@ -175,32 +176,24 @@ impl> Ledger { previous_block: &Block, subdag: Option<&Subdag>, candidate_ratifications: Vec>, - candidate_solutions: Vec>, + candidate_solutions: Vec>, candidate_transactions: Vec>, rng: &mut R, - ) -> Result<( - Header, - Ratifications, - Solutions, - Vec>, - Transactions, - Vec, - )> { + ) -> Result<(Header, Ratifications, Solutions, Vec>, Transactions, Vec)> + { // Construct the solutions. let (solutions, aborted_solutions, solutions_root, combined_proof_target) = match candidate_solutions.is_empty() { true => (None, vec![], Field::::zero(), 0u128), false => { - // Retrieve the coinbase verifying key. - let coinbase_verifying_key = self.coinbase_puzzle.coinbase_verifying_key(); - // Retrieve the latest epoch challenge. - let latest_epoch_challenge = self.latest_epoch_challenge()?; + // Retrieve the latest epoch hash. + let latest_epoch_hash = self.latest_epoch_hash()?; + // Retrieve the latest proof target. + let latest_proof_target = self.latest_proof_target(); // Separate the candidate solutions into valid and aborted solutions. let (valid_candidate_solutions, aborted_candidate_solutions) = split_candidate_solutions(candidate_solutions, N::MAX_SOLUTIONS, |solution| { - solution - .verify(coinbase_verifying_key, &latest_epoch_challenge, self.latest_proof_target()) - .unwrap_or(false) + self.puzzle().check_solution(solution, latest_epoch_hash, latest_proof_target).is_ok() }); // Check if there are any valid solutions. @@ -208,11 +201,11 @@ impl> Ledger { true => (None, aborted_candidate_solutions, Field::::zero(), 0u128), false => { // Construct the solutions. - let solutions = CoinbaseSolution::new(valid_candidate_solutions)?; + let solutions = PuzzleSolutions::new(valid_candidate_solutions)?; // Compute the solutions root. let solutions_root = solutions.to_accumulator_point()?; // Compute the combined proof target. - let combined_proof_target = solutions.to_combined_proof_target()?; + let combined_proof_target = self.puzzle().get_combined_proof_target(&solutions)?; // Output the solutions, solutions root, and combined proof target. (Some(solutions), aborted_candidate_solutions, solutions_root, combined_proof_target) } @@ -223,8 +216,7 @@ impl> Ledger { let solutions = Solutions::from(solutions); // Construct the aborted solution IDs. - let aborted_solution_ids = - aborted_solutions.into_iter().map(|solution| solution.commitment()).collect::>(); + let aborted_solution_ids = aborted_solutions.iter().map(Solution::id).collect::>(); // Retrieve the latest state root. let latest_state_root = self.latest_state_root(); @@ -286,7 +278,8 @@ impl> Ledger { N::GENESIS_COINBASE_TARGET, )?; // Construct the next proof target. - let next_proof_target = proof_target(next_coinbase_target, N::GENESIS_PROOF_TARGET); + let next_proof_target = + proof_target(next_coinbase_target, N::GENESIS_PROOF_TARGET, N::MAX_SOLUTIONS_AS_POWER_OF_TWO); // Construct the next last coinbase target and next last coinbase timestamp. let (next_last_coinbase_target, next_last_coinbase_timestamp) = match is_coinbase_target_reached { diff --git a/ledger/src/check_next_block.rs b/ledger/src/check_next_block.rs index ffb6213299..77752b478f 100644 --- a/ledger/src/check_next_block.rs +++ b/ledger/src/check_next_block.rs @@ -31,7 +31,7 @@ impl> Ledger { // Ensure the solutions do not already exist. for solution_id in block.solutions().solution_ids() { - if self.contains_puzzle_commitment(solution_id)? { + if self.contains_solution_id(solution_id)? { bail!("Solution ID {solution_id} already exists in the ledger"); } } @@ -111,15 +111,15 @@ impl> Ledger { self.latest_state_root(), &previous_committee_lookback, &committee_lookback, - self.coinbase_puzzle(), - &self.latest_epoch_challenge()?, + self.puzzle(), + self.latest_epoch_hash()?, OffsetDateTime::now_utc().unix_timestamp(), ratified_finalize_operations, )?; // Ensure that each existing solution ID from the block exists in the ledger. for existing_solution_id in expected_existing_solution_ids { - if !self.contains_puzzle_commitment(&existing_solution_id)? { + if !self.contains_solution_id(&existing_solution_id)? { bail!("Solution ID '{existing_solution_id}' does not exist in the ledger"); } } diff --git a/ledger/src/contains.rs b/ledger/src/contains.rs index 7b5500f434..5f75b826bb 100644 --- a/ledger/src/contains.rs +++ b/ledger/src/contains.rs @@ -44,14 +44,14 @@ impl> Ledger { pub fn contains_transmission(&self, transmission_id: &TransmissionID) -> Result { match transmission_id { TransmissionID::Ratification => Ok(false), - TransmissionID::Solution(solution_id) => self.contains_puzzle_commitment(solution_id), + TransmissionID::Solution(solution_id) => self.contains_solution_id(solution_id), TransmissionID::Transaction(transaction_id) => self.contains_transaction_id(transaction_id), } } - /// Returns `true` if the given puzzle commitment exists. - pub fn contains_puzzle_commitment(&self, solution_id: &PuzzleCommitment) -> Result { - self.vm.block_store().contains_puzzle_commitment(solution_id) + /// Returns `true` if the given solution ID exists. + pub fn contains_solution_id(&self, solution_id: &SolutionID) -> Result { + self.vm.block_store().contains_solution_id(solution_id) } /* Transaction */ diff --git a/ledger/src/find.rs b/ledger/src/find.rs index 0e547d79d0..8b6cff547b 100644 --- a/ledger/src/find.rs +++ b/ledger/src/find.rs @@ -25,12 +25,9 @@ impl> Ledger { self.vm.block_store().find_block_hash(transaction_id) } - /// Returns the block height that contains the given `puzzle commitment`. - pub fn find_block_height_from_puzzle_commitment( - &self, - puzzle_commitment: &PuzzleCommitment, - ) -> Result> { - self.vm.block_store().find_block_height_from_puzzle_commitment(puzzle_commitment) + /// Returns the block height that contains the given `solution ID`. + pub fn find_block_height_from_solution_id(&self, solution_id: &SolutionID) -> Result> { + self.vm.block_store().find_block_height_from_solution_id(solution_id) } /// Returns the transaction ID that contains the given `program ID`. diff --git a/ledger/src/get.rs b/ledger/src/get.rs index 06263a4841..22259d2517 100644 --- a/ledger/src/get.rs +++ b/ledger/src/get.rs @@ -35,16 +35,16 @@ impl> Ledger { self.vm.block_store().get_state_path_for_commitment(commitment) } - /// Returns the epoch challenge for the given block height. - pub fn get_epoch_challenge(&self, block_height: u32) -> Result> { + /// Returns the epoch hash for the given block height. + pub fn get_epoch_hash(&self, block_height: u32) -> Result { // Compute the epoch number from the current block height. - let epoch_number = block_height / N::NUM_BLOCKS_PER_EPOCH; + let epoch_number = block_height.saturating_div(N::NUM_BLOCKS_PER_EPOCH); // Compute the epoch starting height (a multiple of `NUM_BLOCKS_PER_EPOCH`). - let epoch_starting_height = epoch_number * N::NUM_BLOCKS_PER_EPOCH; - // Retrieve the epoch block hash, defined as the 'previous block hash' from the epoch starting height. - let epoch_block_hash = self.get_previous_hash(epoch_starting_height)?; - // Construct the epoch challenge. - EpochChallenge::new(epoch_number, epoch_block_hash, N::COINBASE_PUZZLE_DEGREE) + let epoch_starting_height = epoch_number.saturating_mul(N::NUM_BLOCKS_PER_EPOCH); + // Retrieve the epoch hash, defined as the 'previous block hash' from the epoch starting height. + let epoch_hash = self.get_previous_hash(epoch_starting_height)?; + // Construct the epoch hash. + Ok(epoch_hash) } /// Returns the block for the given block height. @@ -215,7 +215,7 @@ impl> Ledger { } /// Returns the solution for the given solution ID. - pub fn get_solution(&self, solution_id: &PuzzleCommitment) -> Result> { + pub fn get_solution(&self, solution_id: &SolutionID) -> Result> { self.vm.block_store().get_solution(solution_id) } diff --git a/ledger/src/helpers/bft.rs b/ledger/src/helpers/bft.rs index 3b89c61509..3d786d6e9e 100644 --- a/ledger/src/helpers/bft.rs +++ b/ledger/src/helpers/bft.rs @@ -16,8 +16,8 @@ use console::network::Network; use ledger_block::{Ratify, Transaction}; -use ledger_coinbase::ProverSolution; use ledger_narwhal::{Transmission, TransmissionID}; +use ledger_puzzle::Solution; use anyhow::{bail, ensure, Result}; use std::collections::HashSet; @@ -28,7 +28,7 @@ use std::collections::HashSet; /// This method guarantees that the output is 1) order-preserving, and 2) unique. pub fn decouple_transmissions( transmissions: impl Iterator, Transmission)>, -) -> Result<(Vec>, Vec>, Vec>)> { +) -> Result<(Vec>, Vec>, Vec>)> { // Initialize a list for the ratifications. let ratifications = Vec::new(); // Initialize a list for the solutions. @@ -50,7 +50,7 @@ pub fn decouple_transmissions( // Deserialize the solution. let solution = solution.deserialize_blocking()?; // Ensure the transmission ID corresponds to the solution. - ensure!(commitment == solution.commitment(), "Mismatching transmission ID (solution)"); + ensure!(commitment == solution.id(), "Mismatching transmission ID (solution)"); // Insert the solution into the list. solutions.push(solution); } diff --git a/ledger/src/iterators.rs b/ledger/src/iterators.rs index 1c6880e56c..11e461cee7 100644 --- a/ledger/src/iterators.rs +++ b/ledger/src/iterators.rs @@ -21,7 +21,7 @@ impl> Ledger { } /// Returns an iterator over the solution IDs, for all blocks in `self`. - pub fn solution_ids(&self) -> impl '_ + Iterator>> { + pub fn solution_ids(&self) -> impl '_ + Iterator>> { self.vm.block_store().solution_ids() } diff --git a/ledger/src/lib.rs b/ledger/src/lib.rs index d7928b9581..957d45e783 100644 --- a/ledger/src/lib.rs +++ b/ledger/src/lib.rs @@ -20,9 +20,9 @@ extern crate tracing; pub use ledger_authority as authority; pub use ledger_block as block; -pub use ledger_coinbase as coinbase; pub use ledger_committee as committee; pub use ledger_narwhal as narwhal; +pub use ledger_puzzle as puzzle; pub use ledger_query as query; pub use ledger_store as store; @@ -52,9 +52,9 @@ use console::{ types::{Field, Group}, }; use ledger_authority::Authority; -use ledger_coinbase::{CoinbasePuzzle, CoinbaseSolution, EpochChallenge, ProverSolution, PuzzleCommitment}; use ledger_committee::Committee; use ledger_narwhal::{BatchCertificate, Subdag, Transmission, TransmissionID}; +use ledger_puzzle::{Puzzle, PuzzleSolutions, Solution, SolutionID}; use ledger_query::Query; use ledger_store::{ConsensusStorage, ConsensusStore}; use synthesizer::{ @@ -99,10 +99,8 @@ pub struct Ledger> { vm: VM, /// The genesis block. genesis_block: Block, - /// The coinbase puzzle. - coinbase_puzzle: CoinbasePuzzle, - /// The current epoch challenge. - current_epoch_challenge: Arc>>>, + /// The current epoch hash. + current_epoch_hash: Arc>>, /// The current committee. current_committee: Arc>>>, /// The current block. @@ -165,8 +163,7 @@ impl> Ledger { let mut ledger = Self { vm, genesis_block: genesis_block.clone(), - coinbase_puzzle: CoinbasePuzzle::::load()?, - current_epoch_challenge: Default::default(), + current_epoch_hash: Default::default(), current_committee: Arc::new(RwLock::new(current_committee)), current_block: Arc::new(RwLock::new(genesis_block.clone())), }; @@ -190,8 +187,8 @@ impl> Ledger { ledger.current_block = Arc::new(RwLock::new(block)); // Set the current committee (and ensures the latest committee exists). ledger.current_committee = Arc::new(RwLock::new(Some(ledger.latest_committee()?))); - // Set the current epoch challenge. - ledger.current_epoch_challenge = Arc::new(RwLock::new(Some(ledger.get_epoch_challenge(latest_height)?))); + // Set the current epoch hash. + ledger.current_epoch_hash = Arc::new(RwLock::new(Some(ledger.get_epoch_hash(latest_height)?))); finish!(timer, "Initialize ledger"); Ok(ledger) @@ -202,9 +199,9 @@ impl> Ledger { &self.vm } - /// Returns the coinbase puzzle. - pub const fn coinbase_puzzle(&self) -> &CoinbasePuzzle { - &self.coinbase_puzzle + /// Returns the puzzle. + pub const fn puzzle(&self) -> &Puzzle { + self.vm.puzzle() } /// Returns the latest committee. @@ -225,11 +222,11 @@ impl> Ledger { self.current_block.read().height() / N::NUM_BLOCKS_PER_EPOCH } - /// Returns the latest epoch challenge. - pub fn latest_epoch_challenge(&self) -> Result> { - match self.current_epoch_challenge.read().as_ref() { - Some(challenge) => Ok(challenge.clone()), - None => self.get_epoch_challenge(self.latest_height()), + /// Returns the latest epoch hash. + pub fn latest_epoch_hash(&self) -> Result { + match self.current_epoch_hash.read().as_ref() { + Some(epoch_hash) => Ok(*epoch_hash), + None => self.get_epoch_hash(self.latest_height()), } } diff --git a/ledger/src/tests.rs b/ledger/src/tests.rs index 6188b57c28..31e6e918b7 100644 --- a/ledger/src/tests.rs +++ b/ledger/src/tests.rs @@ -24,12 +24,13 @@ use console::{ network::prelude::*, program::{Entry, Identifier, Literal, Plaintext, ProgramID, Value}, }; -use indexmap::IndexMap; use ledger_block::{ConfirmedTransaction, Rejected, Transaction}; use ledger_committee::{Committee, MIN_VALIDATOR_STAKE}; use ledger_store::{helpers::memory::ConsensusMemory, ConsensusStore}; use synthesizer::{program::Program, vm::VM, Stack}; +use indexmap::IndexMap; + #[test] fn test_load() { let rng = &mut TestRng::default(); @@ -685,15 +686,15 @@ fn test_aborted_solution_ids() { // Initialize the test environment. let crate::test_helpers::TestEnv { ledger, private_key, address, .. } = crate::test_helpers::sample_test_env(rng); - // Retrieve the coinbase puzzle parameters. - let coinbase_puzzle = ledger.coinbase_puzzle(); - let epoch_challenge = ledger.latest_epoch_challenge().unwrap(); + // Retrieve the puzzle parameters. + let puzzle = ledger.puzzle(); + let latest_epoch_hash = ledger.latest_epoch_hash().unwrap(); let minimum_proof_target = ledger.latest_proof_target(); // Create a solution that is less than the minimum proof target. - let mut invalid_solution = coinbase_puzzle.prove(&epoch_challenge, address, rng.gen(), None).unwrap(); - while invalid_solution.to_target().unwrap() >= minimum_proof_target { - invalid_solution = coinbase_puzzle.prove(&epoch_challenge, address, rng.gen(), None).unwrap(); + let mut invalid_solution = puzzle.prove(latest_epoch_hash, address, rng.gen(), None).unwrap(); + while puzzle.get_proof_target(&invalid_solution).unwrap() >= minimum_proof_target { + invalid_solution = puzzle.prove(latest_epoch_hash, address, rng.gen(), None).unwrap(); } // Create a valid transaction for the block. @@ -722,7 +723,7 @@ fn test_aborted_solution_ids() { // Enforce that the block solution was aborted properly. assert!(block.solutions().is_empty()); - assert_eq!(block.aborted_solution_ids(), &vec![invalid_solution.commitment()]); + assert_eq!(block.aborted_solution_ids(), &vec![invalid_solution.id()]); } #[test] @@ -1672,3 +1673,235 @@ fn test_deployment_exceeding_max_transaction_spend() { // Check that the deployment failed. assert!(result.is_err()); } + +// These tests require the proof targets to be low enough to be able to generate **valid** solutions. +// This requires the 'test' feature to be enabled for the `console` dependency. +#[cfg(feature = "test")] +mod valid_solutions { + use super::*; + use rand::prelude::SliceRandom; + use std::collections::HashSet; + + #[test] + fn test_duplicate_solution_ids() { + // Print the cfg to ensure that the test is running in the correct environment. + let rng = &mut TestRng::default(); + + // Initialize the test environment. + let crate::test_helpers::TestEnv { ledger, private_key, address, .. } = + crate::test_helpers::sample_test_env(rng); + + // Retrieve the puzzle parameters. + let puzzle = ledger.puzzle(); + let latest_epoch_hash = ledger.latest_epoch_hash().unwrap(); + let minimum_proof_target = ledger.latest_proof_target(); + + // Create a solution that is greater than the minimum proof target. + let mut valid_solution = puzzle.prove(latest_epoch_hash, address, rng.gen(), None).unwrap(); + while puzzle.get_proof_target(&valid_solution).unwrap() < minimum_proof_target { + println!( + "Solution is invalid: {} < {}", + puzzle.get_proof_target(&valid_solution).unwrap(), + minimum_proof_target + ); + valid_solution = puzzle.prove(latest_epoch_hash, address, rng.gen(), None).unwrap(); + } + + // Create a valid transaction for the block. + let inputs = [Value::from_str(&format!("{address}")).unwrap(), Value::from_str("10u64").unwrap()]; + let transfer_transaction = ledger + .vm + .execute(&private_key, ("credits.aleo", "transfer_public"), inputs.iter(), None, 0, None, rng) + .unwrap(); + + // Check that block creation fails when duplicate solution IDs are provided. + let result = ledger.prepare_advance_to_next_beacon_block( + &private_key, + vec![], + vec![valid_solution, valid_solution], + vec![transfer_transaction.clone()], + rng, + ); + assert!(result.is_err()); + + // Create a block. + let block = ledger + .prepare_advance_to_next_beacon_block( + &private_key, + vec![], + vec![valid_solution], + vec![transfer_transaction], + rng, + ) + .unwrap(); + + // Check that the next block is valid. + ledger.check_next_block(&block, rng).unwrap(); + + // Add the deployment block to the ledger. + ledger.advance_to_next_block(&block).unwrap(); + + // Enforce that the block solution was accepted properly. + assert_eq!(block.solutions().len(), 1); + assert_eq!(block.aborted_solution_ids().len(), 0) + } + + #[test] + fn test_excess_invalid_solution_ids() { + // Note that the sum of `NUM_INVALID_SOLUTIONS` and `NUM_VALID_SOLUTIONS` should exceed the maximum number of solutions. + const NUM_INVALID_SOLUTIONS: usize = CurrentNetwork::MAX_SOLUTIONS; + const NUM_VALID_SOLUTIONS: usize = CurrentNetwork::MAX_SOLUTIONS; + + let rng = &mut TestRng::default(); + + // Initialize the test environment. + let crate::test_helpers::TestEnv { ledger, private_key, address, .. } = + crate::test_helpers::sample_test_env(rng); + + // Retrieve the puzzle parameters. + let puzzle = ledger.puzzle(); + let latest_epoch_hash = ledger.latest_epoch_hash().unwrap(); + let minimum_proof_target = ledger.latest_proof_target(); + + // Initialize storage for the valid and invalid solutions + let mut valid_solutions = Vec::with_capacity(NUM_VALID_SOLUTIONS); + let mut invalid_solutions = Vec::with_capacity(NUM_INVALID_SOLUTIONS); + + // Create solutions that are greater than the minimum proof target. + while valid_solutions.len() < NUM_VALID_SOLUTIONS { + let solution = puzzle.prove(latest_epoch_hash, address, rng.gen(), None).unwrap(); + if puzzle.get_proof_target(&solution).unwrap() < minimum_proof_target { + if invalid_solutions.len() < NUM_INVALID_SOLUTIONS { + invalid_solutions.push(solution); + } + } else { + valid_solutions.push(solution); + } + } + // Create the remaining solutions that are less than the minimum proof target. + while invalid_solutions.len() < NUM_INVALID_SOLUTIONS { + let solution = puzzle.prove(latest_epoch_hash, address, rng.gen(), None).unwrap(); + if puzzle.get_proof_target(&solution).unwrap() < minimum_proof_target { + invalid_solutions.push(solution); + } + } + + // Check the length of the valid and invalid solutions. + assert_eq!(valid_solutions.len(), NUM_VALID_SOLUTIONS); + assert_eq!(invalid_solutions.len(), NUM_INVALID_SOLUTIONS); + + // Concatenate and shuffle the solutions. + let mut candidate_solutions = valid_solutions.clone(); + candidate_solutions.extend(invalid_solutions.clone()); + candidate_solutions.shuffle(rng); + + // Create a valid transaction for the block. + let inputs = [Value::from_str(&format!("{address}")).unwrap(), Value::from_str("10u64").unwrap()]; + let transfer_transaction = ledger + .vm + .execute(&private_key, ("credits.aleo", "transfer_public"), inputs.iter(), None, 0, None, rng) + .unwrap(); + + // Create a block. + let block = ledger + .prepare_advance_to_next_beacon_block( + &private_key, + vec![], + candidate_solutions, + vec![transfer_transaction], + rng, + ) + .unwrap(); + + // Check that the next block is valid. + ledger.check_next_block(&block, rng).unwrap(); + + // Add the deployment block to the ledger. + ledger.advance_to_next_block(&block).unwrap(); + + // Check that the block's solutions are well-formed. + assert_eq!(block.aborted_solution_ids().len(), NUM_INVALID_SOLUTIONS); + assert_eq!(block.solutions().len(), NUM_VALID_SOLUTIONS); + + let block_solutions = block.solutions().solution_ids().cloned().collect::>(); + let valid_solutions = valid_solutions.iter().map(|s| s.id()).collect::>(); + assert_eq!(block_solutions, valid_solutions, "Valid solutions do not match"); + + let block_aborted_solution_ids = block.aborted_solution_ids().iter().cloned().collect::>(); + let invalid_solutions = invalid_solutions.iter().map(|s| s.id()).collect::>(); + assert_eq!(block_aborted_solution_ids, invalid_solutions, "Invalid solutions do not match"); + } + + #[test] + fn test_excess_valid_solution_ids() { + // Note that this should be greater than the maximum number of solutions. + const NUM_VALID_SOLUTIONS: usize = 2 * CurrentNetwork::MAX_SOLUTIONS; + + let rng = &mut TestRng::default(); + + // Initialize the test environment. + let crate::test_helpers::TestEnv { ledger, private_key, address, .. } = + crate::test_helpers::sample_test_env(rng); + + // Retrieve the puzzle parameters. + let puzzle = ledger.puzzle(); + let latest_epoch_hash = ledger.latest_epoch_hash().unwrap(); + let minimum_proof_target = ledger.latest_proof_target(); + + // Initialize storage for the valid solutions + let mut valid_solutions = Vec::with_capacity(NUM_VALID_SOLUTIONS); + + // Create solutions that are greater than the minimum proof target. + while valid_solutions.len() < NUM_VALID_SOLUTIONS { + let solution = puzzle.prove(latest_epoch_hash, address, rng.gen(), None).unwrap(); + if puzzle.get_proof_target(&solution).unwrap() >= minimum_proof_target { + valid_solutions.push(solution); + } + } + + // Check the length of the valid solutions. + assert_eq!(valid_solutions.len(), NUM_VALID_SOLUTIONS); + + // Shuffle the solutions. + let mut candidate_solutions = valid_solutions; + candidate_solutions.shuffle(rng); + + // Create a valid transaction for the block. + let inputs = [Value::from_str(&format!("{address}")).unwrap(), Value::from_str("10u64").unwrap()]; + let transfer_transaction = ledger + .vm + .execute(&private_key, ("credits.aleo", "transfer_public"), inputs.iter(), None, 0, None, rng) + .unwrap(); + + // Create a block. + let block = ledger + .prepare_advance_to_next_beacon_block( + &private_key, + vec![], + candidate_solutions.clone(), + vec![transfer_transaction], + rng, + ) + .unwrap(); + + // Check that the next block is valid. + ledger.check_next_block(&block, rng).unwrap(); + + // Add the deployment block to the ledger. + ledger.advance_to_next_block(&block).unwrap(); + + // Check that the block's solutions are well-formed. + assert_eq!(block.solutions().len(), CurrentNetwork::MAX_SOLUTIONS); + assert_eq!(block.aborted_solution_ids().len(), NUM_VALID_SOLUTIONS - CurrentNetwork::MAX_SOLUTIONS); + + let block_solutions = block.solutions().solution_ids().cloned().collect::>(); + let expected_accepted_solutions = + candidate_solutions.iter().take(CurrentNetwork::MAX_SOLUTIONS).map(|s| s.id()).collect::>(); + assert_eq!(block_solutions, expected_accepted_solutions, "Accepted solutions do not match"); + + let block_aborted_solution_ids = block.aborted_solution_ids().iter().cloned().collect::>(); + let expected_aborted_solutions = + candidate_solutions.iter().skip(CurrentNetwork::MAX_SOLUTIONS).map(|s| s.id()).collect::>(); + assert_eq!(block_aborted_solution_ids, expected_aborted_solutions, "Aborted solutions do not match"); + } +} diff --git a/ledger/store/Cargo.toml b/ledger/store/Cargo.toml index 02d3cb8ea7..2a8fc50486 100644 --- a/ledger/store/Cargo.toml +++ b/ledger/store/Cargo.toml @@ -22,7 +22,7 @@ rocks = [ "once_cell", "rocksdb", "tracing" ] serial = [ "console/serial", "ledger-block/serial", - "ledger-coinbase/serial", + "ledger-puzzle/serial", "ledger-committee/serial", "synthesizer-program/serial", "synthesizer-snark/serial" @@ -31,7 +31,7 @@ wasm = [ "console/wasm", "ledger-authority/wasm", "ledger-block/wasm", - "ledger-coinbase/wasm", + "ledger-puzzle/wasm", "ledger-committee/wasm", "ledger-narwhal-batch-certificate/wasm", "synthesizer-program/wasm", @@ -54,11 +54,6 @@ package = "snarkvm-ledger-block" path = "../block" version = "=0.16.19" -[dependencies.ledger-coinbase] -package = "snarkvm-ledger-coinbase" -path = "../coinbase" -version = "=0.16.19" - [dependencies.ledger-committee] package = "snarkvm-ledger-committee" path = "../committee" @@ -69,6 +64,11 @@ package = "snarkvm-ledger-narwhal-batch-certificate" path = "../narwhal/batch-certificate" version = "=0.16.19" +[dependencies.ledger-puzzle] +package = "snarkvm-ledger-puzzle" +path = "../puzzle" +version = "=0.16.19" + [dependencies.synthesizer-program] package = "snarkvm-synthesizer-program" path = "../../synthesizer/program" diff --git a/ledger/store/src/block/mod.rs b/ledger/store/src/block/mod.rs index 8fdf338799..717e33c952 100644 --- a/ledger/store/src/block/mod.rs +++ b/ledger/store/src/block/mod.rs @@ -41,8 +41,8 @@ use ledger_block::{ Transaction, Transactions, }; -use ledger_coinbase::{ProverSolution, PuzzleCommitment}; use ledger_narwhal_batch_certificate::BatchCertificate; +use ledger_puzzle::{Solution, SolutionID}; use synthesizer_program::{FinalizeOperation, Program}; use aleo_std_storage::StorageMode; @@ -123,12 +123,12 @@ pub trait BlockStorage: 'static + Clone + Send + Sync { type RatificationsMap: for<'a> Map<'a, N::BlockHash, Ratifications>; /// The mapping of `block hash` to `block solutions`. type SolutionsMap: for<'a> Map<'a, N::BlockHash, Solutions>; - /// The mapping of `puzzle commitment` to `block height`. - type PuzzleCommitmentsMap: for<'a> Map<'a, PuzzleCommitment, u32>; + /// The mapping of `solution ID` to `block height`. + type SolutionIDsMap: for<'a> Map<'a, SolutionID, u32>; /// The mapping of `block hash` to `[aborted solution ID]`. - type AbortedSolutionIDsMap: for<'a> Map<'a, N::BlockHash, Vec>>; + type AbortedSolutionIDsMap: for<'a> Map<'a, N::BlockHash, Vec>>; /// The mapping of aborted `solution ID` to `block height`. - type AbortedSolutionHeightsMap: for<'a> Map<'a, PuzzleCommitment, u32>; + type AbortedSolutionHeightsMap: for<'a> Map<'a, SolutionID, u32>; /// The mapping of `block hash` to `[transaction ID]`. type TransactionsMap: for<'a> Map<'a, N::BlockHash, Vec>; /// The mapping of `block hash` to `[aborted transaction ID]`. @@ -165,8 +165,8 @@ pub trait BlockStorage: 'static + Clone + Send + Sync { fn ratifications_map(&self) -> &Self::RatificationsMap; /// Returns the solutions map. fn solutions_map(&self) -> &Self::SolutionsMap; - /// Returns the puzzle commitments map. - fn puzzle_commitments_map(&self) -> &Self::PuzzleCommitmentsMap; + /// Returns the solution IDs map. + fn solution_ids_map(&self) -> &Self::SolutionIDsMap; /// Returns the aborted solution IDs map. fn aborted_solution_ids_map(&self) -> &Self::AbortedSolutionIDsMap; /// Returns the aborted solution heights map. @@ -205,7 +205,7 @@ pub trait BlockStorage: 'static + Clone + Send + Sync { self.certificate_map().start_atomic(); self.ratifications_map().start_atomic(); self.solutions_map().start_atomic(); - self.puzzle_commitments_map().start_atomic(); + self.solution_ids_map().start_atomic(); self.aborted_solution_ids_map().start_atomic(); self.aborted_solution_heights_map().start_atomic(); self.transactions_map().start_atomic(); @@ -227,7 +227,7 @@ pub trait BlockStorage: 'static + Clone + Send + Sync { || self.certificate_map().is_atomic_in_progress() || self.ratifications_map().is_atomic_in_progress() || self.solutions_map().is_atomic_in_progress() - || self.puzzle_commitments_map().is_atomic_in_progress() + || self.solution_ids_map().is_atomic_in_progress() || self.aborted_solution_ids_map().is_atomic_in_progress() || self.aborted_solution_heights_map().is_atomic_in_progress() || self.transactions_map().is_atomic_in_progress() @@ -249,7 +249,7 @@ pub trait BlockStorage: 'static + Clone + Send + Sync { self.certificate_map().atomic_checkpoint(); self.ratifications_map().atomic_checkpoint(); self.solutions_map().atomic_checkpoint(); - self.puzzle_commitments_map().atomic_checkpoint(); + self.solution_ids_map().atomic_checkpoint(); self.aborted_solution_ids_map().atomic_checkpoint(); self.aborted_solution_heights_map().atomic_checkpoint(); self.transactions_map().atomic_checkpoint(); @@ -271,7 +271,7 @@ pub trait BlockStorage: 'static + Clone + Send + Sync { self.certificate_map().clear_latest_checkpoint(); self.ratifications_map().clear_latest_checkpoint(); self.solutions_map().clear_latest_checkpoint(); - self.puzzle_commitments_map().clear_latest_checkpoint(); + self.solution_ids_map().clear_latest_checkpoint(); self.aborted_solution_ids_map().clear_latest_checkpoint(); self.aborted_solution_heights_map().clear_latest_checkpoint(); self.transactions_map().clear_latest_checkpoint(); @@ -293,7 +293,7 @@ pub trait BlockStorage: 'static + Clone + Send + Sync { self.certificate_map().atomic_rewind(); self.ratifications_map().atomic_rewind(); self.solutions_map().atomic_rewind(); - self.puzzle_commitments_map().atomic_rewind(); + self.solution_ids_map().atomic_rewind(); self.aborted_solution_ids_map().atomic_rewind(); self.aborted_solution_heights_map().atomic_rewind(); self.transactions_map().atomic_rewind(); @@ -315,7 +315,7 @@ pub trait BlockStorage: 'static + Clone + Send + Sync { self.certificate_map().abort_atomic(); self.ratifications_map().abort_atomic(); self.solutions_map().abort_atomic(); - self.puzzle_commitments_map().abort_atomic(); + self.solution_ids_map().abort_atomic(); self.aborted_solution_ids_map().abort_atomic(); self.aborted_solution_heights_map().abort_atomic(); self.transactions_map().abort_atomic(); @@ -337,7 +337,7 @@ pub trait BlockStorage: 'static + Clone + Send + Sync { self.certificate_map().finish_atomic()?; self.ratifications_map().finish_atomic()?; self.solutions_map().finish_atomic()?; - self.puzzle_commitments_map().finish_atomic()?; + self.solution_ids_map().finish_atomic()?; self.aborted_solution_ids_map().finish_atomic()?; self.aborted_solution_heights_map().finish_atomic()?; self.transactions_map().finish_atomic()?; @@ -417,7 +417,7 @@ pub trait BlockStorage: 'static + Clone + Send + Sync { // Store the block solution IDs. for solution_id in block.solutions().solution_ids() { - self.puzzle_commitments_map().insert(*solution_id, block.height())?; + self.solution_ids_map().insert(*solution_id, block.height())?; } // Store the aborted solution IDs. @@ -548,7 +548,7 @@ pub trait BlockStorage: 'static + Clone + Send + Sync { // Remove the block solution IDs. for solution_id in solutions.solution_ids() { - self.puzzle_commitments_map().remove(solution_id)?; + self.solution_ids_map().remove(solution_id)?; } // Remove the aborted solution IDs. @@ -627,9 +627,9 @@ pub trait BlockStorage: 'static + Clone + Send + Sync { } } - /// Returns the block height that contains the given `puzzle commitment`. - fn find_block_height_from_puzzle_commitment(&self, solution_id: &PuzzleCommitment) -> Result> { - match self.puzzle_commitments_map().get_confirmed(solution_id)? { + /// Returns the block height that contains the given `solution ID`. + fn find_block_height_from_solution_id(&self, solution_id: &SolutionID) -> Result> { + match self.solution_ids_map().get_confirmed(solution_id)? { Some(block_height) => Ok(Some(cow_to_copied!(block_height))), None => match self.aborted_solution_heights_map().get_confirmed(solution_id)? { Some(block_height) => Ok(Some(cow_to_copied!(block_height))), @@ -826,9 +826,9 @@ pub trait BlockStorage: 'static + Clone + Send + Sync { } /// Returns the prover solution for the given solution ID. - fn get_solution(&self, solution_id: &PuzzleCommitment) -> Result> { + fn get_solution(&self, solution_id: &SolutionID) -> Result> { // Retrieve the block height for the solution ID. - let Some(block_height) = self.find_block_height_from_puzzle_commitment(solution_id)? else { + let Some(block_height) = self.find_block_height_from_solution_id(solution_id)? else { bail!("The block height for solution ID '{solution_id}' is missing in block storage") }; // Retrieve the block hash. @@ -849,7 +849,7 @@ pub trait BlockStorage: 'static + Clone + Send + Sync { } /// Returns the block aborted solution IDs for the given `block hash`. - fn get_block_aborted_solution_ids(&self, block_hash: &N::BlockHash) -> Result>>> { + fn get_block_aborted_solution_ids(&self, block_hash: &N::BlockHash) -> Result>>> { match self.aborted_solution_ids_map().get_confirmed(block_hash)? { Some(aborted_solution_ids) => Ok(Some(cow_to_cloned!(aborted_solution_ids))), None => Ok(None), @@ -1187,12 +1187,9 @@ impl> BlockStore { self.storage.find_block_hash(transaction_id) } - /// Returns the block height that contains the given `puzzle commitment`. - pub fn find_block_height_from_puzzle_commitment( - &self, - puzzle_commitment: &PuzzleCommitment, - ) -> Result> { - self.storage.find_block_height_from_puzzle_commitment(puzzle_commitment) + /// Returns the block height that contains the given `solution ID`. + pub fn find_block_height_from_solution_id(&self, solution_id: &SolutionID) -> Result> { + self.storage.find_block_height_from_solution_id(solution_id) } } @@ -1248,7 +1245,7 @@ impl> BlockStore { } /// Returns the prover solution for the given solution ID. - pub fn get_solution(&self, solution_id: &PuzzleCommitment) -> Result> { + pub fn get_solution(&self, solution_id: &SolutionID) -> Result> { self.storage.get_solution(solution_id) } @@ -1336,13 +1333,13 @@ impl> BlockStore { } /// Returns `true` if the given solution ID exists. - pub fn contains_puzzle_commitment(&self, solution_id: &PuzzleCommitment) -> Result { - Ok(self.storage.puzzle_commitments_map().contains_key_confirmed(solution_id)? - || self.contains_aborted_puzzle_commitment(solution_id)?) + pub fn contains_solution_id(&self, solution_id: &SolutionID) -> Result { + Ok(self.storage.solution_ids_map().contains_key_confirmed(solution_id)? + || self.contains_aborted_solution_id(solution_id)?) } /// Returns `true` if the given aborted solution ID exists. - fn contains_aborted_puzzle_commitment(&self, solution_id: &PuzzleCommitment) -> Result { + fn contains_aborted_solution_id(&self, solution_id: &SolutionID) -> Result { self.storage.aborted_solution_heights_map().contains_key_confirmed(solution_id) } } @@ -1364,8 +1361,8 @@ impl> BlockStore { } /// Returns an iterator over the solution IDs, for all blocks in `self`. - pub fn solution_ids(&self) -> impl '_ + Iterator>> { - self.storage.puzzle_commitments_map().keys_confirmed() + pub fn solution_ids(&self) -> impl '_ + Iterator>> { + self.storage.solution_ids_map().keys_confirmed() } } diff --git a/ledger/store/src/helpers/memory/block.rs b/ledger/store/src/helpers/memory/block.rs index e9ab94931a..1562087b6c 100644 --- a/ledger/store/src/helpers/memory/block.rs +++ b/ledger/store/src/helpers/memory/block.rs @@ -22,7 +22,7 @@ use crate::{ use console::{prelude::*, types::Field}; use ledger_authority::Authority; use ledger_block::{Header, Ratifications, Rejected, Solutions}; -use ledger_coinbase::PuzzleCommitment; +use ledger_puzzle::SolutionID; use synthesizer_program::FinalizeOperation; use aleo_std_storage::StorageMode; @@ -49,12 +49,12 @@ pub struct BlockMemory { ratifications_map: MemoryMap>, /// The solutions map. solutions_map: MemoryMap>, - /// The puzzle commitments map. - puzzle_commitments_map: MemoryMap, u32>, + /// The solution IDs map. + solution_ids_map: MemoryMap, u32>, /// The aborted solution IDs map. - aborted_solution_ids_map: MemoryMap>>, + aborted_solution_ids_map: MemoryMap>>, /// The aborted solution heights map. - aborted_solution_heights_map: MemoryMap, u32>, + aborted_solution_heights_map: MemoryMap, u32>, /// The transactions map. transactions_map: MemoryMap>, /// The aborted transaction IDs map. @@ -81,9 +81,9 @@ impl BlockStorage for BlockMemory { type CertificateMap = MemoryMap, (u32, u64)>; type RatificationsMap = MemoryMap>; type SolutionsMap = MemoryMap>; - type PuzzleCommitmentsMap = MemoryMap, u32>; - type AbortedSolutionIDsMap = MemoryMap>>; - type AbortedSolutionHeightsMap = MemoryMap, u32>; + type SolutionIDsMap = MemoryMap, u32>; + type AbortedSolutionIDsMap = MemoryMap>>; + type AbortedSolutionHeightsMap = MemoryMap, u32>; type TransactionsMap = MemoryMap>; type AbortedTransactionIDsMap = MemoryMap>; type RejectedOrAbortedTransactionIDMap = MemoryMap; @@ -109,7 +109,7 @@ impl BlockStorage for BlockMemory { certificate_map: MemoryMap::default(), ratifications_map: MemoryMap::default(), solutions_map: MemoryMap::default(), - puzzle_commitments_map: MemoryMap::default(), + solution_ids_map: MemoryMap::default(), aborted_solution_ids_map: MemoryMap::default(), aborted_solution_heights_map: MemoryMap::default(), transactions_map: MemoryMap::default(), @@ -166,9 +166,9 @@ impl BlockStorage for BlockMemory { &self.solutions_map } - /// Returns the puzzle commitments map. - fn puzzle_commitments_map(&self) -> &Self::PuzzleCommitmentsMap { - &self.puzzle_commitments_map + /// Returns the solution IDs map. + fn solution_ids_map(&self) -> &Self::SolutionIDsMap { + &self.solution_ids_map } /// Returns the aborted solution IDs map. diff --git a/ledger/store/src/helpers/rocksdb/block.rs b/ledger/store/src/helpers/rocksdb/block.rs index dffa488d29..498e8eebf6 100644 --- a/ledger/store/src/helpers/rocksdb/block.rs +++ b/ledger/store/src/helpers/rocksdb/block.rs @@ -28,7 +28,7 @@ use crate::{ use console::{prelude::*, types::Field}; use ledger_authority::Authority; use ledger_block::{Header, Ratifications, Rejected, Solutions}; -use ledger_coinbase::PuzzleCommitment; +use ledger_puzzle::SolutionID; use synthesizer_program::FinalizeOperation; use aleo_std_storage::StorageMode; @@ -55,12 +55,12 @@ pub struct BlockDB { ratifications_map: DataMap>, /// The solutions map. solutions_map: DataMap>, - /// The puzzle commitments map. - puzzle_commitments_map: DataMap, u32>, + /// The solution IDs map. + solution_ids_map: DataMap, u32>, /// The aborted solution IDs map. - aborted_solution_ids_map: DataMap>>, + aborted_solution_ids_map: DataMap>>, /// The aborted solution heights map. - aborted_solution_heights_map: DataMap, u32>, + aborted_solution_heights_map: DataMap, u32>, /// The transactions map. transactions_map: DataMap>, /// The aborted transaction IDs map. @@ -87,9 +87,9 @@ impl BlockStorage for BlockDB { type CertificateMap = DataMap, (u32, u64)>; type RatificationsMap = DataMap>; type SolutionsMap = DataMap>; - type PuzzleCommitmentsMap = DataMap, u32>; - type AbortedSolutionIDsMap = DataMap>>; - type AbortedSolutionHeightsMap = DataMap, u32>; + type SolutionIDsMap = DataMap, u32>; + type AbortedSolutionIDsMap = DataMap>>; + type AbortedSolutionHeightsMap = DataMap, u32>; type TransactionsMap = DataMap>; type AbortedTransactionIDsMap = DataMap>; type RejectedOrAbortedTransactionIDMap = DataMap; @@ -115,7 +115,7 @@ impl BlockStorage for BlockDB { certificate_map: internal::RocksDB::open_map(N::ID, storage.clone(), MapID::Block(BlockMap::Certificate))?, ratifications_map: internal::RocksDB::open_map(N::ID, storage.clone(), MapID::Block(BlockMap::Ratifications))?, solutions_map: internal::RocksDB::open_map(N::ID, storage.clone(), MapID::Block(BlockMap::Solutions))?, - puzzle_commitments_map: internal::RocksDB::open_map(N::ID, storage.clone(), MapID::Block(BlockMap::PuzzleCommitments))?, + solution_ids_map: internal::RocksDB::open_map(N::ID, storage.clone(), MapID::Block(BlockMap::PuzzleCommitments))?, aborted_solution_ids_map: internal::RocksDB::open_map(N::ID, storage.clone(), MapID::Block(BlockMap::AbortedSolutionIDs))?, aborted_solution_heights_map: internal::RocksDB::open_map(N::ID, storage.clone(), MapID::Block(BlockMap::AbortedSolutionHeights))?, transactions_map: internal::RocksDB::open_map(N::ID, storage.clone(), MapID::Block(BlockMap::Transactions))?, @@ -172,9 +172,9 @@ impl BlockStorage for BlockDB { &self.solutions_map } - /// Returns the puzzle commitments map. - fn puzzle_commitments_map(&self) -> &Self::PuzzleCommitmentsMap { - &self.puzzle_commitments_map + /// Returns the solution IDs map. + fn solution_ids_map(&self) -> &Self::SolutionIDsMap { + &self.solution_ids_map } /// Returns the aborted solution IDs map. diff --git a/ledger/store/src/helpers/rocksdb/internal/id.rs b/ledger/store/src/helpers/rocksdb/internal/id.rs index 29066c6096..6093fdad74 100644 --- a/ledger/store/src/helpers/rocksdb/internal/id.rs +++ b/ledger/store/src/helpers/rocksdb/internal/id.rs @@ -76,7 +76,7 @@ pub enum BlockMap { Certificate = DataID::BlockCertificateMap as u16, Ratifications = DataID::BlockRatificationsMap as u16, Solutions = DataID::BlockSolutionsMap as u16, - PuzzleCommitments = DataID::BlockPuzzleCommitmentsMap as u16, + PuzzleCommitments = DataID::BlockSolutionIDsMap as u16, AbortedSolutionIDs = DataID::BlockAbortedSolutionIDsMap as u16, AbortedSolutionHeights = DataID::BlockAbortedSolutionHeightsMap as u16, Transactions = DataID::BlockTransactionsMap as u16, @@ -232,7 +232,7 @@ enum DataID { BlockCertificateMap, BlockRatificationsMap, BlockSolutionsMap, - BlockPuzzleCommitmentsMap, + BlockSolutionIDsMap, BlockAbortedSolutionIDsMap, BlockAbortedSolutionHeightsMap, BlockTransactionsMap, diff --git a/parameters/src/mainnet/resources/block.genesis b/parameters/src/mainnet/resources/block.genesis index a293dd0431b26fbc72dd988a206daf577ead95f7..9eb4226636fbc391bf7502448a405b4b8dfc9cb0 100644 GIT binary patch delta 11683 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 delta 11686 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 BjF12T diff --git a/synthesizer/Cargo.toml b/synthesizer/Cargo.toml index ff0ff6034f..382dcf22ac 100644 --- a/synthesizer/Cargo.toml +++ b/synthesizer/Cargo.toml @@ -90,6 +90,16 @@ package = "snarkvm-ledger-committee" path = "../ledger/committee" version = "=0.16.19" +[dependencies.ledger-puzzle] +package = "snarkvm-ledger-puzzle" +path = "../ledger/puzzle" +version = "=0.16.19" + +[dependencies.ledger-puzzle-epoch] +package = "snarkvm-ledger-puzzle-epoch" +path = "../ledger/puzzle/epoch" +version = "=0.16.19" + [dependencies.ledger-query] package = "snarkvm-ledger-query" path = "../ledger/query" diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index c2100d2051..e9fe5d90f5 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -192,7 +192,7 @@ impl> VM { #[cfg(not(any(test, feature = "test")))] pub const MAXIMUM_CONFIRMED_TRANSACTIONS: usize = Transactions::::MAX_TRANSACTIONS; /// The maximum number of confirmed transactions allowed in a block. - /// This is set to a deliberately low value (8) for testing purposes only. + /// This is deliberately set to a low value (8) for testing purposes only. #[cfg(any(test, feature = "test"))] pub const MAXIMUM_CONFIRMED_TRANSACTIONS: usize = 8; @@ -539,7 +539,7 @@ impl> VM { let post_ratifications = reward_ratifications.iter().chain(post_ratifications); // Process the post-ratifications. - match Self::atomic_post_ratify(store, state, post_ratifications, solutions) { + match Self::atomic_post_ratify(&self.puzzle, store, state, post_ratifications, solutions) { // Store the finalize operations from the post-ratify. Ok(operations) => ratified_finalize_operations.extend(operations), // Note: This will abort the entire atomic batch. @@ -783,7 +783,7 @@ impl> VM { /* Perform the ratifications after finalize. */ - match Self::atomic_post_ratify(store, state, post_ratifications, solutions) { + match Self::atomic_post_ratify(&self.puzzle, store, state, post_ratifications, solutions) { // Store the finalize operations from the post-ratify. Ok(operations) => ratified_finalize_operations.extend(operations), // Note: This will abort the entire atomic batch. @@ -1000,6 +1000,7 @@ impl> VM { /// Performs the post-ratifications after finalizing transactions. #[inline] fn atomic_post_ratify<'a>( + puzzle: &Puzzle, store: &FinalizeStore, state: FinalizeGlobalState, post_ratifications: impl Iterator>, @@ -1077,8 +1078,10 @@ impl> VM { continue; }; // Compute the proof targets, with the corresponding addresses. - let proof_targets = - solutions.values().map(|s| Ok((s.address(), s.to_target()?))).collect::>>()?; + let proof_targets = solutions + .values() + .map(|s| Ok((s.address(), puzzle.get_proof_target(s)?))) + .collect::>>()?; // Calculate the proving rewards. let proving_rewards = proving_rewards(proof_targets, *puzzle_reward); // Iterate over the proving rewards. diff --git a/synthesizer/src/vm/helpers/macros.rs b/synthesizer/src/vm/helpers/macros.rs index e0c67c08c4..4404d7ab93 100644 --- a/synthesizer/src/vm/helpers/macros.rs +++ b/synthesizer/src/vm/helpers/macros.rs @@ -52,6 +52,21 @@ macro_rules! cast_mut_ref { }}; } +/// A helper macro to dedup the `Network` trait and `Aleo` trait and process its given logic. +#[macro_export] +macro_rules! convert { + // Example: convert!(logic) + ($logic:ident) => {{ + match N::ID { + console::network::MainnetV0::ID => { + // Process the logic. + $logic!(console::network::MainnetV0, circuit::AleoV0) + } + _ => bail!("Unsupported VM configuration for network: {}", N::ID), + } + }}; +} + /// A helper macro to dedup the `Network` trait and `Aleo` trait and process its given logic. #[macro_export] macro_rules! process { diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index f8025b2e00..299f2057dc 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -21,7 +21,7 @@ mod execute; mod finalize; mod verify; -use crate::{cast_mut_ref, cast_ref, process}; +use crate::{cast_mut_ref, cast_ref, convert, process}; use console::{ account::{Address, PrivateKey}, network::prelude::*, @@ -43,6 +43,7 @@ use ledger_block::{ Transactions, }; use ledger_committee::Committee; +use ledger_puzzle::Puzzle; use ledger_query::Query; use ledger_store::{ atomic_finalize, @@ -72,6 +73,8 @@ use rayon::prelude::*; pub struct VM> { /// The process. process: Arc>>, + /// The puzzle. + puzzle: Puzzle, /// The VM store. store: ConsensusStore, /// The lock to guarantee atomicity over calls to speculate and finalize. @@ -179,6 +182,7 @@ impl> VM { // Return the new VM. Ok(Self { process: Arc::new(RwLock::new(process)), + puzzle: Self::new_puzzle()?, store, atomic_lock: Arc::new(Mutex::new(())), block_lock: Arc::new(Mutex::new(())), @@ -200,6 +204,12 @@ impl> VM { self.process.clone() } + /// Returns the puzzle. + #[inline] + pub const fn puzzle(&self) -> &Puzzle { + &self.puzzle + } + /// Returns the partially-verified transactions. #[inline] pub fn partially_verified_transactions(&self) -> Arc>> { @@ -233,6 +243,21 @@ impl> VM { } } +impl> VM { + /// Returns a new instance of the puzzle. + pub fn new_puzzle() -> Result> { + // Initialize a new instance of the puzzle. + macro_rules! logic { + ($network:path, $aleo:path) => {{ + let puzzle = Puzzle::new::>(); + Ok(cast_ref!(puzzle as Puzzle).clone()) + }}; + } + // Initialize the puzzle. + convert!(logic) + } +} + impl> VM { /// Returns a new genesis block for a beacon chain. pub fn genesis_beacon(&self, private_key: &PrivateKey, rng: &mut R) -> Result> { @@ -1425,4 +1450,20 @@ finalize do: // Verify. vm.check_transaction(&transaction, None, rng).unwrap(); } + + #[test] + fn test_vm_puzzle() { + // Attention: This test is used to ensure that the VM has performed downcasting correctly for + // the puzzle, and that the underlying traits in the puzzle are working correctly. Please + // *do not delete* this test as it is a critical safety check for the integrity of the + // instantiation of the puzzle in the VM. + + let rng = &mut TestRng::default(); + + // Initialize the VM. + let vm = sample_vm(); + + // Ensure this call succeeds. + vm.puzzle.prove(rng.gen(), rng.gen(), rng.gen(), None).unwrap(); + } } diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out b/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out index 6380117c04..51fb5b5e60 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out @@ -19,14 +19,14 @@ outputs: test_rand.aleo/rand_chacha_check: outputs: - '{"type":"future","id":"3094014759641313043901697261267946468626327163450942596641947962222295207390field","value":"{\n program_id: test_rand.aleo,\n function_name: rand_chacha_check,\n arguments: [\n 0field,\n false\n ]\n}"}' - speculate: the execution was rejected + speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: test_rand.aleo/rand_chacha_check: outputs: - '{"type":"future","id":"818878742790741579153893179075772445872751227433677932822653185952935999557field","value":"{\n program_id: test_rand.aleo,\n function_name: rand_chacha_check,\n arguments: [\n 1field,\n true\n ]\n}"}' - speculate: the execution was rejected + speculate: the execution was accepted add_next_block: succeeded. additional: - child_outputs: diff --git a/utilities/src/parallel.rs b/utilities/src/parallel.rs index 73bc141709..05d95978af 100644 --- a/utilities/src/parallel.rs +++ b/utilities/src/parallel.rs @@ -196,6 +196,20 @@ macro_rules! cfg_reduce_with { }}; } +/// Turns a collection into an iterator. +#[macro_export] +macro_rules! cfg_keys { + ($e: expr) => {{ + #[cfg(not(feature = "serial"))] + let result = $e.par_keys(); + + #[cfg(feature = "serial")] + let result = $e.keys(); + + result + }}; +} + /// Turns a collection into an iterator. #[macro_export] macro_rules! cfg_values {