diff --git a/algorithms/src/snark/varuna/data_structures/proof.rs b/algorithms/src/snark/varuna/data_structures/proof.rs index 4afa89fd5d..5541e5d505 100644 --- a/algorithms/src/snark/varuna/data_structures/proof.rs +++ b/algorithms/src/snark/varuna/data_structures/proof.rs @@ -15,20 +15,23 @@ use crate::{ SNARKError, - polycommit::sonic_pc, - snark::varuna::{CircuitId, ahp}, + polycommit::{kzg10::KZGCommitment, sonic_pc}, + snark::varuna::{CircuitId, VarunaVersion, ahp}, }; use ahp::prover::{FourthMessage, ThirdMessage}; use snarkvm_curves::PairingEngine; -use snarkvm_fields::PrimeField; +use snarkvm_fields::{One, PrimeField}; use snarkvm_utilities::{FromBytes, ToBytes, into_io_error, serialize::*}; +use anyhow::{Result, anyhow}; use std::{ collections::BTreeMap, io::{self, Read, Write}, }; +use std::mem::size_of; + #[derive(Clone, Debug, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)] pub struct Commitments { pub witness_commitments: Vec>, @@ -378,6 +381,82 @@ impl FromBytes for Proof { } } +/// Computes the size in bytes of a Varuna proof as produced by +/// `Proof::serialize_compressed` without needing to receive the proof itself. +/// +/// *Arguments*: +/// - `batch_sizes`: the batch sizes of the circuits and instances being +/// proved. +/// - `varuna_version`: the version of Varuna being used +/// - `hiding`: indicates whether the proof system is run in ZK mode +/// +/// *Returns*: +/// - `Ok(size)` for `VarunaVersion::V2`, where `size` is the size of the proof +/// in bytes. +/// - `Err` for `VarunaVersion::V1`. +pub fn proof_size( + batch_sizes: &[usize], + varuna_version: VarunaVersion, + hiding: bool, +) -> Result { + let n_circuits: usize = batch_sizes.len(); + let n_instances: usize = batch_sizes.iter().sum(); + + match varuna_version { + VarunaVersion::V1 => Err(anyhow!("Proof-size calculation not implemented for Varuna version V1")), + VarunaVersion::V2 => { + // All fields are serialised in Compressed mode The breakdown is as + // follows: + // - batch sizes: one `usize` (which is serialised as a `u64`) for each batch + // size, plus one `u64` for the number of batches. This contains the size + // information for the vectors in all other fields, which are therefore + // serialised without their length prefix. + // - commitments: + // + witness_commitments: n_instances commitments + // + mask_poly: 1 byte to encode the enum tag (a bool) plus one commitment if + // the variant is Some (if and only if the proof system is run in ZK mode) + // + h_0, g_1, g_2, h_2: four commitments + // + g_a, g_b, g_c: 3 * n_circuits commitments + // - evaluations: + // + g_1_eval: one field element + // + g_a_evals, g_b_evals, g_c_evals: 3 * n_circuits field elements + // - third_msg: + // + 3 * n_instances field elements + // - fourth_msg: + // + 3 * n_circuits field elements + // - pc_proof: + // + one usize for the size of the vector (which is always 3) + // + three of [1 commitment + 1 bool (for the Option tag) + {1 field element + // if the variant is Some (if and only if the proof system is run in ZK + // mode)}] + + let n_bool = 1; + let n_u64 = 1; + let n_field_elements = 1 + 6 * n_circuits + 3 * n_instances; + let n_commitments = 4 + n_instances + (if hiding { 1 } else { 0 }) + 3 * n_circuits; + + // The next three sizes are const functions + let size_bool = size_of::(); + let size_u64 = size_of::(); + + // The next two can be hard-coded if performance becomes critical + // and they are considered fully stable. They are 32 and 48 bytes at + // the time of writing, respectively (commitments are affine points + // written in compressed form). + let size_field_element = E::Fr::one().compressed_size(); + let size_commitment = KZGCommitment::::empty().compressed_size(); + + let size_pc_proof = size_u64 + 3 * (size_commitment + 1) + if hiding { size_field_element } else { 0 }; + + Ok(n_bool * size_bool + + (n_u64 + batch_sizes.len()) * size_u64 + + n_field_elements * size_field_element + + n_commitments * size_commitment + + size_pc_proof) + } + } +} + #[cfg(test)] mod test { #![allow(non_camel_case_types)] diff --git a/algorithms/src/snark/varuna/tests.rs b/algorithms/src/snark/varuna/tests.rs index 7f349d4d02..270850abe9 100644 --- a/algorithms/src/snark/varuna/tests.rs +++ b/algorithms/src/snark/varuna/tests.rs @@ -24,6 +24,7 @@ mod varuna { VarunaSNARK, VarunaVersion, mode::SNARKMode, + proof::proof_size, test_circuit::TestCircuit, }, traits::{AlgebraicSponge, SNARK}, @@ -33,6 +34,7 @@ mod varuna { use snarkvm_curves::bls12_377::{Bls12_377, Fq, Fr}; use snarkvm_utilities::{ + CanonicalSerialize, ToBytes, rand::{TestRng, Uniform}, }; @@ -131,6 +133,15 @@ mod varuna { $snark_inst::prove_batch(universal_prover, &fs_parameters, varuna_version, &pks_to_constraints, rng).unwrap(); println!("Called prover"); + if varuna_version == VarunaVersion::V2 { + let batch_sizes = proof.batch_sizes(); + let mut proof_bytes = vec![]; + proof.serialize_compressed(&mut proof_bytes).unwrap(); + let actual_size = proof_size::(&batch_sizes, VarunaVersion::V2, $snark_mode::ZK).unwrap(); + assert_eq!(proof_bytes.len(), actual_size); + println!("Compressed size is as expected ({actual_size} B)"); + } + assert!( $snark_inst::verify_batch(universal_verifier, &fs_parameters, varuna_version, &vks_to_inputs, &proof).unwrap(), "Batch verification failed with {instance_batch_size} instances and {circuit_batch_size} circuits for circuits: {constraints:?}" diff --git a/ledger/block/src/transaction/execution/mod.rs b/ledger/block/src/transaction/execution/mod.rs index 2337e35c4c..8649b8f022 100644 --- a/ledger/block/src/transaction/execution/mod.rs +++ b/ledger/block/src/transaction/execution/mod.rs @@ -134,7 +134,7 @@ impl Execution { } /// Returns an iterator over the underlying transitions. - pub fn transitions(&self) -> impl '_ + ExactSizeIterator + DoubleEndedIterator> { + pub fn transitions(&self) -> impl '_ + ExactSizeIterator + DoubleEndedIterator> + Clone { self.transitions.values() } diff --git a/synthesizer/process/src/cost.rs b/synthesizer/process/src/cost.rs index 47a4788021..50aa4e8ad5 100644 --- a/synthesizer/process/src/cost.rs +++ b/synthesizer/process/src/cost.rs @@ -13,14 +13,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{FinalizeTypes, Process, Stack, StackRef, StackTrait}; +use std::collections::HashMap; + +use crate::{Authorization, FinalizeTypes, Process, Stack, StackRef, StackTrait}; use console::{ prelude::*, program::{FinalizeType, Identifier, LiteralType, PlaintextType}, }; +use snarkvm_algorithms::snark::varuna::VarunaVersion; use snarkvm_ledger_block::{Deployment, Execution, Transaction}; use snarkvm_synthesizer_program::{CastType, Command, Instruction, Operand}; +use snarkvm_synthesizer_snark::proof_size; pub type MinimumCost = u64; pub type StorageCost = u64; @@ -49,16 +53,89 @@ pub fn execution_cost( process: &Process, execution: &Execution, consensus_version: ConsensusVersion, +) -> Result<(MinimumCost, ExecuteCostDetails)> { + let execution_size = execution.size_in_bytes()?; + + execution_cost_given_size(process, execution, execution_size, consensus_version) +} + +// Returns the execution cost in microcredits for a given execution whose size is provided as an argument. +fn execution_cost_given_size( + process: &Process, + execution: &Execution, + execution_size: u64, + consensus_version: ConsensusVersion, ) -> Result<(MinimumCost, ExecuteCostDetails)> { if consensus_version >= ConsensusVersion::V10 { - execution_cost_v3(process, execution) + execution_cost_v3(process, execution, execution_size) } else if consensus_version >= ConsensusVersion::V2 { - execution_cost_v2(process, execution) + execution_cost_v2(process, execution, execution_size) } else { - execution_cost_v1(process, execution) + execution_cost_v1(process, execution, execution_size) } } +/// Returns the execution cost in microcredits for a given `Authorization. +pub fn execution_cost_for_authorization( + process: &Process, + authorization: &Authorization, + consensus_version: ConsensusVersion, +) -> Result<(MinimumCost, ExecuteCostDetails)> { + ensure!( + consensus_version >= ConsensusVersion::V4, + "Execution-cost computation for authorization relies on proof-size estimation, which is only implemented for Varuna version >= V2 (consensus version >= V4)" + ); + + // Reconstruct an Execution from the Authorization. Note that the StateRoot + // does not affect the fee (it has constant size). + let reconstructed_execution = + Execution::from(authorization.transitions().values().cloned(), N::StateRoot::default(), None)?; + + // Compute the size of the proof that will result from proving the + // Authorization. The first step is to compute the Varuna batch sizes. The + // Varuna circuits that must be proved as part of an Execution are: + // - the circuits of each Transition + // - one inclusion circuit for input records to *all* of those Transitions + + // TODO: Dynamic dispatch, once implemented, will cause a third type of + // circuit to appear which needs to be accounted for here. + + let mut circuit_frequencies = HashMap::new(); + + // In order to compute the frequencies of function circuits, we mimic the + // operation of Process::verify_execution: + for transition in authorization.transitions().values() { + let entry = + circuit_frequencies.entry((*transition.program_id(), *transition.function_name())).or_insert(0usize); + *entry += 1; + } + + let mut batch_sizes: Vec = circuit_frequencies.values().cloned().collect(); + + // We now add the single batch of inclusion circuits for input records, if + // any: + let n_input_records = Authorization::number_of_input_records(authorization.transitions().values()); + if n_input_records > 0 { + batch_sizes.push(n_input_records); + } + + // Varuna is always run in hiding (i. e. ZK) mode when proving Executions. + let hiding_mode = true; + + // If future versions of Varuna are introduced, the correct one should be + // deduced here from the consensus version. Currently only the latest Varuna + // version V2 is supported. + let varuna_version = VarunaVersion::V2; + + let expected_proof_size = u64::try_from(proof_size::(&batch_sizes, varuna_version, hiding_mode)?)?; + let unproved_execution_size = reconstructed_execution.size_in_bytes()?; + let execution_size = unproved_execution_size.checked_add(expected_proof_size).ok_or(anyhow!( + "The execution size computation overflowed for an authorization when the proof was taken into account" + ))?; + + execution_cost_given_size(process, &reconstructed_execution, execution_size, consensus_version) +} + /// Returns the compute cost for a deployment in microcredits. /// This is used to limit the amount of single-threaded compute in the block generation hot /// path. This does NOT represent the full costs which a user has to pay. @@ -224,12 +301,13 @@ pub fn deployment_cost_v1( } /// Returns the *minimum* cost in microcredits to publish the given execution using the ARC_0005_COMPUTE_DISCOUNT. -pub fn execution_cost_v3( +fn execution_cost_v3( process: &Process, execution: &Execution, + execution_size: u64, ) -> Result<(MinimumCost, ExecuteCostDetails)> { // Compute the storage cost in microcredits. - let storage_cost = execution_storage_cost::(execution.size_in_bytes()?); + let storage_cost = execution_storage_cost::(execution_size); // Get the root transition. let transition = execution.peek()?; @@ -247,12 +325,13 @@ pub fn execution_cost_v3( } /// Returns the *minimum* cost in microcredits to publish the given execution. -pub fn execution_cost_v2( +fn execution_cost_v2( process: &Process, execution: &Execution, + execution_size: u64, ) -> Result<(MinimumCost, ExecuteCostDetails)> { // Compute the storage cost in microcredits. - let storage_cost = execution_storage_cost::(execution.size_in_bytes()?); + let storage_cost = execution_storage_cost::(execution_size); // Get the root transition. let transition = execution.peek()?; @@ -270,12 +349,13 @@ pub fn execution_cost_v2( } /// Returns the *minimum* cost in microcredits to publish the given execution. -pub fn execution_cost_v1( +fn execution_cost_v1( process: &Process, execution: &Execution, + execution_size: u64, ) -> Result<(MinimumCost, ExecuteCostDetails)> { // Compute the storage cost in microcredits. - let storage_cost = execution_storage_cost::(execution.size_in_bytes()?); + let storage_cost = execution_storage_cost::(execution_size); // Get the root transition. let transition = execution.peek()?; @@ -948,10 +1028,12 @@ function over_five_thousand: // Get execution and cost data. let execution_under_5000 = get_execution(&mut process, &program, &under_5000, ["2group"].into_iter()); let execution_size_under_5000 = execution_under_5000.size_in_bytes().unwrap(); - let (_, (storage_cost_under_5000, _)) = execution_cost_v3(&process, &execution_under_5000).unwrap(); + let (_, (storage_cost_under_5000, _)) = + execution_cost_v3(&process, &execution_under_5000, execution_size_under_5000).unwrap(); let execution_over_5000 = get_execution(&mut process, &program, &over_5000, ["2group"].into_iter()); let execution_size_over_5000 = execution_over_5000.size_in_bytes().unwrap(); - let (_, (storage_cost_over_5000, _)) = execution_cost_v3(&process, &execution_over_5000).unwrap(); + let (_, (storage_cost_over_5000, _)) = + execution_cost_v3(&process, &execution_over_5000, execution_size_over_5000).unwrap(); // Ensure the sizes are below and above the threshold respectively. assert!(execution_size_under_5000 < threshold); diff --git a/synthesizer/process/src/stack/authorization/mod.rs b/synthesizer/process/src/stack/authorization/mod.rs index 0d66a7fdbb..0d001df432 100644 --- a/synthesizer/process/src/stack/authorization/mod.rs +++ b/synthesizer/process/src/stack/authorization/mod.rs @@ -18,7 +18,7 @@ mod serialize; mod string; use console::{network::prelude::*, program::Request, types::Field}; -use snarkvm_ledger_block::{Transaction, Transition}; +use snarkvm_ledger_block::{Input, Transaction, Transition}; use snarkvm_synthesizer_program::StackTrait; use indexmap::IndexMap; @@ -285,6 +285,19 @@ impl PartialEq for Authorization { impl Eq for Authorization {} +impl Authorization { + /// Returns the total number of inputs to the passed `Transition`s that are of + /// type `Input::Record`. + // This method is used to ensure consistency between `prepare_verifier_inputs` + // and the batch-size calculation used in `execution_cost_for_authorization`. + #[inline] + pub fn number_of_input_records<'a>(transitions: impl ExactSizeIterator>) -> usize { + transitions + .map(|transition| transition.inputs().iter().filter(|input| matches!(input, Input::Record(_, _))).count()) + .sum() + } +} + /// Ensures the given request and transition correspond to one another. fn ensure_request_and_transition_matches( index: usize, @@ -336,6 +349,20 @@ fn ensure_request_and_transition_matches( Ok(()) } +#[cfg(feature = "test")] +impl Authorization { + /// Initialize an `Authorization` instance with the given requests and + /// transitions without performing any consistency checks between them. + pub fn from_unchecked((requests, transitions): (Vec>, Vec>)) -> Self { + Authorization { + requests: Arc::new(RwLock::new(VecDeque::from(requests))), + transitions: Arc::new(RwLock::new(IndexMap::from_iter( + transitions.into_iter().map(|transition| (*transition.id(), transition)), + ))), + } + } +} + #[cfg(test)] pub(crate) mod test_helpers { use super::*; diff --git a/synthesizer/process/src/tests/test_execute.rs b/synthesizer/process/src/tests/test_execute.rs index 567308ac61..5832ecfb7a 100644 --- a/synthesizer/process/src/tests/test_execute.rs +++ b/synthesizer/process/src/tests/test_execute.rs @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{CallStack, InclusionVersion, Process, Trace}; +use crate::{CallStack, InclusionVersion, Process, Trace, execution_cost, execution_cost_for_authorization}; use circuit::{Aleo, network::AleoV0}; use console::{ account::{Address, PrivateKey, ViewKey}, @@ -89,7 +89,7 @@ pub fn sample_fee, B: BlockStorage, P: Final // Prepare the assignments. trace.prepare(&Query::from(block_store)).unwrap(); // Compute the proof and construct the fee. - trace.prove_fee::(VarunaVersion::V1, rng).unwrap() + trace.prove_fee::(VarunaVersion::V2, rng).unwrap() } #[test] @@ -548,6 +548,9 @@ fn test_process_execute_transfer_public_to_private() { // Check again to make sure we didn't modify the authorization after calling `evaluate`. assert_eq!(authorization.len(), 1); + let expected_execution_cost = + execution_cost_for_authorization(&process, &authorization, ConsensusVersion::V10).unwrap(); + // Execute the request. let (response, mut trace) = process.execute::(authorization, rng).unwrap(); let candidate = response.outputs(); @@ -563,9 +566,12 @@ fn test_process_execute_transfer_public_to_private() { // Prepare the trace. trace.prepare(&Query::from(block_store)).unwrap(); // Prove the execution. - let execution = trace.prove_execution::("credits.aleo", VarunaVersion::V1, rng).unwrap(); + let execution = trace.prove_execution::("credits.aleo", VarunaVersion::V2, rng).unwrap(); // Verify the execution. - process.verify_execution(ConsensusVersion::V8, VarunaVersion::V1, InclusionVersion::V0, &execution).unwrap(); + process.verify_execution(ConsensusVersion::V10, VarunaVersion::V2, InclusionVersion::V1, &execution).unwrap(); + + // Check the execution cost + assert_eq!(execution_cost(&process, &execution, ConsensusVersion::V10).unwrap(), expected_execution_cost); // Ensure there is only one transition. assert_eq!(1, execution.transitions().len()); @@ -1310,7 +1316,7 @@ finalize compute: // Add the program to the process. let deployment = process.deploy::(&program, rng).unwrap(); // Check that the deployment verifies. - process.verify_deployment::(ConsensusVersion::V8, &deployment, rng).unwrap(); + process.verify_deployment::(ConsensusVersion::V10, &deployment, rng).unwrap(); // Compute the fee. let fee = sample_fee::<_, CurrentAleo, _, _>(&process, &block_store, &finalize_store, rng); // Finalize the deployment. @@ -1334,6 +1340,9 @@ finalize compute: .unwrap(); assert_eq!(authorization.len(), 1); + let expected_execution_cost = + execution_cost_for_authorization(&process, &authorization, ConsensusVersion::V10).unwrap(); + // Compute the output value. let response = process.evaluate::(authorization.replicate()).unwrap(); let candidate = response.outputs(); @@ -1350,9 +1359,12 @@ finalize compute: // Prepare the trace. trace.prepare(&Query::from(block_store)).unwrap(); // Prove the execution. - let execution = trace.prove_execution::("testing", VarunaVersion::V1, rng).unwrap(); + let execution = trace.prove_execution::("testing", VarunaVersion::V2, rng).unwrap(); // Verify the execution. - process.verify_execution(ConsensusVersion::V8, VarunaVersion::V1, InclusionVersion::V0, &execution).unwrap(); + process.verify_execution(ConsensusVersion::V10, VarunaVersion::V2, InclusionVersion::V1, &execution).unwrap(); + + // Check the execution cost + assert_eq!(execution_cost(&process, &execution, ConsensusVersion::V10).unwrap(), expected_execution_cost); // Now, finalize the execution. process.finalize_execution(sample_finalize_state(1), &finalize_store, &execution, None).unwrap(); @@ -1422,7 +1434,7 @@ finalize compute: // Add the program to the process. let deployment = process.deploy::(&program, rng).unwrap(); // Check that the deployment verifies. - process.verify_deployment::(ConsensusVersion::V8, &deployment, rng).unwrap(); + process.verify_deployment::(ConsensusVersion::V10, &deployment, rng).unwrap(); // Compute the fee. let fee = sample_fee::<_, CurrentAleo, _, _>(&process, &block_store, &finalize_store, rng); // Finalize the deployment. @@ -1446,6 +1458,9 @@ finalize compute: .unwrap(); assert_eq!(authorization.len(), 1); + let expected_execution_cost = + execution_cost_for_authorization(&process, &authorization, ConsensusVersion::V10).unwrap(); + // Compute the output value. let response = process.evaluate::(authorization.replicate()).unwrap(); let candidate = response.outputs(); @@ -1462,10 +1477,13 @@ finalize compute: // Prepare the trace. trace.prepare(&Query::from(block_store)).unwrap(); // Prove the execution. - let execution = trace.prove_execution::("testing", VarunaVersion::V1, rng).unwrap(); + let execution = trace.prove_execution::("testing", VarunaVersion::V2, rng).unwrap(); // Verify the execution. - process.verify_execution(ConsensusVersion::V8, VarunaVersion::V1, InclusionVersion::V0, &execution).unwrap(); + process.verify_execution(ConsensusVersion::V10, VarunaVersion::V2, InclusionVersion::V1, &execution).unwrap(); + + // Check the execution cost + assert_eq!(execution_cost(&process, &execution, ConsensusVersion::V10).unwrap(), expected_execution_cost); // Now, finalize the execution. process.finalize_execution(sample_finalize_state(1), &finalize_store, &execution, None).unwrap(); @@ -1549,7 +1567,7 @@ finalize mint_public: // Add the program to the process. let deployment = process.deploy::(&program, rng).unwrap(); // Check that the deployment verifies. - process.verify_deployment::(ConsensusVersion::V8, &deployment, rng).unwrap(); + process.verify_deployment::(ConsensusVersion::V10, &deployment, rng).unwrap(); // Compute the fee. let fee = sample_fee::<_, CurrentAleo, _, _>(&process, &block_store, &finalize_store, rng); // Finalize the deployment. @@ -1585,6 +1603,9 @@ finalize mint_public: // Check again to make sure we didn't modify the authorization after calling `evaluate`. assert_eq!(authorization.len(), 1); + let expected_execution_cost = + execution_cost_for_authorization(&process, &authorization, ConsensusVersion::V10).unwrap(); + // Execute the request. let (response, mut trace) = process.execute::(authorization, rng).unwrap(); let candidate = response.outputs(); @@ -1593,10 +1614,13 @@ finalize mint_public: // Prepare the trace. trace.prepare(&Query::from(block_store)).unwrap(); // Prove the execution. - let execution = trace.prove_execution::("token", VarunaVersion::V1, rng).unwrap(); + let execution = trace.prove_execution::("token", VarunaVersion::V2, rng).unwrap(); // Verify the execution. - process.verify_execution(ConsensusVersion::V8, VarunaVersion::V1, InclusionVersion::V0, &execution).unwrap(); + process.verify_execution(ConsensusVersion::V10, VarunaVersion::V2, InclusionVersion::V1, &execution).unwrap(); + + // Check the execution cost + assert_eq!(execution_cost(&process, &execution, ConsensusVersion::V10).unwrap(), expected_execution_cost); // Now, finalize the execution. process.finalize_execution(sample_finalize_state(1), &finalize_store, &execution, None).unwrap(); @@ -1678,7 +1702,7 @@ finalize mint_public: // Add the program to the process. let deployment = process.deploy::(&program0, rng).unwrap(); // Check that the deployment verifies. - process.verify_deployment::(ConsensusVersion::V8, &deployment, rng).unwrap(); + process.verify_deployment::(ConsensusVersion::V10, &deployment, rng).unwrap(); // Compute the fee. let fee = sample_fee::<_, CurrentAleo, _, _>(&process, &block_store, &finalize_store, rng); // Finalize the deployment. @@ -1718,7 +1742,7 @@ finalize init: // Add the program to the process. let deployment = process.deploy::(&program1, rng).unwrap(); // Check that the deployment verifies. - process.verify_deployment::(ConsensusVersion::V8, &deployment, rng).unwrap(); + process.verify_deployment::(ConsensusVersion::V10, &deployment, rng).unwrap(); // Compute the fee. let fee = sample_fee::<_, CurrentAleo, _, _>(&process, &block_store, &finalize_store, rng); // Finalize the deployment. @@ -1745,6 +1769,9 @@ finalize init: .unwrap(); assert_eq!(authorization.len(), 2); + let expected_execution_cost = + execution_cost_for_authorization(&process, &authorization, ConsensusVersion::V10).unwrap(); + // Compute the output value. let response = process.evaluate::(authorization.replicate()).unwrap(); let candidate = response.outputs(); @@ -1761,10 +1788,13 @@ finalize init: // Prepare the trace. trace.prepare(&Query::from(block_store)).unwrap(); // Prove the execution. - let execution = trace.prove_execution::("public_wallet", VarunaVersion::V1, rng).unwrap(); + let execution = trace.prove_execution::("public_wallet", VarunaVersion::V2, rng).unwrap(); // Verify the execution. - process.verify_execution(ConsensusVersion::V8, VarunaVersion::V1, InclusionVersion::V0, &execution).unwrap(); + process.verify_execution(ConsensusVersion::V10, VarunaVersion::V2, InclusionVersion::V1, &execution).unwrap(); + + // Check the execution cost + assert_eq!(execution_cost(&process, &execution, ConsensusVersion::V10).unwrap(), expected_execution_cost); // Now, finalize the execution. process.finalize_execution(sample_finalize_state(1), &finalize_store, &execution, None).unwrap(); @@ -1836,7 +1866,7 @@ finalize compute: // Add the program to the process. let deployment = process.deploy::(&program, rng).unwrap(); // Check that the deployment verifies. - process.verify_deployment::(ConsensusVersion::V8, &deployment, rng).unwrap(); + process.verify_deployment::(ConsensusVersion::V10, &deployment, rng).unwrap(); // Compute the fee. let fee = sample_fee::<_, CurrentAleo, _, _>(&process, &block_store, &finalize_store, rng); // Finalize the deployment. @@ -1868,6 +1898,9 @@ finalize compute: // Check again to make sure we didn't modify the authorization after calling `evaluate`. assert_eq!(authorization.len(), 1); + let expected_execution_cost = + execution_cost_for_authorization(&process, &authorization, ConsensusVersion::V10).unwrap(); + // Execute the request. let (response, mut trace) = process.execute::(authorization, rng).unwrap(); let candidate = response.outputs(); @@ -1876,10 +1909,13 @@ finalize compute: // Prepare the trace. trace.prepare(&Query::from(block_store)).unwrap(); // Prove the execution. - let execution = trace.prove_execution::("testing", VarunaVersion::V1, rng).unwrap(); + let execution = trace.prove_execution::("testing", VarunaVersion::V2, rng).unwrap(); // Verify the execution. - process.verify_execution(ConsensusVersion::V8, VarunaVersion::V1, InclusionVersion::V0, &execution).unwrap(); + process.verify_execution(ConsensusVersion::V10, VarunaVersion::V2, InclusionVersion::V1, &execution).unwrap(); + + // Check the execution cost + assert_eq!(execution_cost(&process, &execution, ConsensusVersion::V10).unwrap(), expected_execution_cost); // Now, finalize the execution. process.finalize_execution(sample_finalize_state(1), &finalize_store, &execution, None).unwrap(); @@ -1980,6 +2016,9 @@ function a: // Check again to make sure we didn't modify the authorization after calling `evaluate`. assert_eq!(authorization.len(), 3); + let expected_execution_cost = + execution_cost_for_authorization(&process, &authorization, ConsensusVersion::V10).unwrap(); + // Execute the request. let (response, mut trace) = process.execute::(authorization, rng).unwrap(); let candidate = response.outputs(); @@ -2006,10 +2045,13 @@ function a: // Prepare the trace. trace.prepare(&Query::from(block_store)).unwrap(); // Prove the execution. - let execution = trace.prove_execution::("two", VarunaVersion::V1, rng).unwrap(); + let execution = trace.prove_execution::("two", VarunaVersion::V2, rng).unwrap(); // Verify the execution. - process.verify_execution(ConsensusVersion::V8, VarunaVersion::V1, InclusionVersion::V0, &execution).unwrap(); + process.verify_execution(ConsensusVersion::V10, VarunaVersion::V2, InclusionVersion::V1, &execution).unwrap(); + + // Check the execution cost + assert_eq!(execution_cost(&process, &execution, ConsensusVersion::V10).unwrap(), expected_execution_cost); } #[test] @@ -2151,6 +2193,9 @@ fn test_complex_execution_order() { assert_eq!(authorization.len(), 10); println!("\nAuthorize\n{:#?}\n\n", authorization.to_vec_deque()); + let expected_execution_cost = + execution_cost_for_authorization(&process, &authorization, ConsensusVersion::V10).unwrap(); + let output = Value::::from_str("17u8").unwrap(); // Compute the output value. @@ -2193,10 +2238,13 @@ fn test_complex_execution_order() { // Prepare the trace. trace.prepare(&Query::from(block_store)).unwrap(); // Prove the execution. - let execution = trace.prove_execution::("four", VarunaVersion::V1, rng).unwrap(); + let execution = trace.prove_execution::("four", VarunaVersion::V2, rng).unwrap(); // Verify the execution. - process.verify_execution(ConsensusVersion::V8, VarunaVersion::V1, InclusionVersion::V0, &execution).unwrap(); + process.verify_execution(ConsensusVersion::V10, VarunaVersion::V2, InclusionVersion::V1, &execution).unwrap(); + + // Check the execution cost + assert_eq!(execution_cost(&process, &execution, ConsensusVersion::V10).unwrap(), expected_execution_cost); } #[test] @@ -2265,7 +2313,7 @@ finalize compute: // Add the program to the process. let deployment = process.deploy::(&program, rng).unwrap(); // Check that the deployment verifies. - process.verify_deployment::(ConsensusVersion::V8, &deployment, rng).unwrap(); + process.verify_deployment::(ConsensusVersion::V10, &deployment, rng).unwrap(); // Compute the fee. let fee = sample_fee::<_, CurrentAleo, _, _>(&process, &block_store, &finalize_store, rng); // Finalize the deployment. @@ -2296,6 +2344,9 @@ finalize compute: // Check again to make sure we didn't modify the authorization after calling `evaluate`. assert_eq!(authorization.len(), 1); + let expected_execution_cost = + execution_cost_for_authorization(&process, &authorization, ConsensusVersion::V10).unwrap(); + // Execute the request. let (response, mut trace) = process.execute::(authorization, rng).unwrap(); let candidate = response.outputs(); @@ -2304,10 +2355,13 @@ finalize compute: // Prepare the trace. trace.prepare(&Query::from(block_store)).unwrap(); // Prove the execution. - let execution = trace.prove_execution::("testing", VarunaVersion::V1, rng).unwrap(); + let execution = trace.prove_execution::("testing", VarunaVersion::V2, rng).unwrap(); // Verify the execution. - process.verify_execution(ConsensusVersion::V8, VarunaVersion::V1, InclusionVersion::V0, &execution).unwrap(); + process.verify_execution(ConsensusVersion::V10, VarunaVersion::V2, InclusionVersion::V1, &execution).unwrap(); + + // Check the execution cost + assert_eq!(execution_cost(&process, &execution, ConsensusVersion::V10).unwrap(), expected_execution_cost); // Now, finalize the execution. process.finalize_execution(sample_finalize_state(1), &finalize_store, &execution, None).unwrap(); @@ -2400,6 +2454,9 @@ function compute: // Check again to make sure we didn't modify the authorization after calling `evaluate`. assert_eq!(authorization.len(), 1); + let expected_execution_cost = + execution_cost_for_authorization(&process, &authorization, ConsensusVersion::V10).unwrap(); + // Execute the request. let (response, mut trace) = process.execute::(authorization, rng).unwrap(); let candidate = response.outputs(); @@ -2413,10 +2470,13 @@ function compute: // Prepare the trace. trace.prepare(&Query::from(block_store)).unwrap(); // Prove the execution. - let execution = trace.prove_execution::("testing", VarunaVersion::V1, rng).unwrap(); + let execution = trace.prove_execution::("testing", VarunaVersion::V2, rng).unwrap(); // Verify the execution. - process.verify_execution(ConsensusVersion::V8, VarunaVersion::V1, InclusionVersion::V0, &execution).unwrap(); + process.verify_execution(ConsensusVersion::V10, VarunaVersion::V2, InclusionVersion::V1, &execution).unwrap(); + + // Check the execution cost + assert_eq!(execution_cost(&process, &execution, ConsensusVersion::V10).unwrap(), expected_execution_cost); } #[test] @@ -2440,9 +2500,9 @@ fn test_process_deploy_credits_program() { let deployment = empty_process.deploy::(&program, rng).unwrap(); // Ensure the deployment is valid on the empty process. - empty_process.verify_deployment::(ConsensusVersion::V8, &deployment, rng).unwrap(); + empty_process.verify_deployment::(ConsensusVersion::V10, &deployment, rng).unwrap(); // Ensure the deployment is not valid on the standard process. - assert!(process.verify_deployment::(ConsensusVersion::V8, &deployment, rng).is_err()); + assert!(process.verify_deployment::(ConsensusVersion::V10, &deployment, rng).is_err()); // Create a new `credits.aleo` program. let program = Program::from_str( @@ -2464,9 +2524,9 @@ function compute: let deployment = empty_process.deploy::(&program, rng).unwrap(); // Ensure the deployment is valid on the empty process. - empty_process.verify_deployment::(ConsensusVersion::V8, &deployment, rng).unwrap(); + empty_process.verify_deployment::(ConsensusVersion::V10, &deployment, rng).unwrap(); // Ensure the deployment is not valid on the standard process. - assert!(process.verify_deployment::(ConsensusVersion::V8, &deployment, rng).is_err()); + assert!(process.verify_deployment::(ConsensusVersion::V10, &deployment, rng).is_err()); } #[test] @@ -2495,7 +2555,7 @@ function {function_name}: // Add the program to the process. let deployment = process.deploy::(&program, rng).unwrap(); // Check that the deployment verifies. - process.verify_deployment::(ConsensusVersion::V8, &deployment, rng).unwrap(); + process.verify_deployment::(ConsensusVersion::V10, &deployment, rng).unwrap(); // Compute the fee. let fee = sample_fee::<_, CurrentAleo, _, _>(&process, &block_store, &finalize_store, rng); // Finalize the deployment. @@ -2521,7 +2581,7 @@ function {function_name}: // Prepare the trace. trace.prepare(&Query::from(block_store.clone())).unwrap(); // Prove the execution. - trace.prove_execution::("testing", VarunaVersion::V1, rng).unwrap() + trace.prove_execution::("testing", VarunaVersion::V2, rng).unwrap() }; assert_eq!(execution_1.len(), 1); @@ -2540,7 +2600,7 @@ function {function_name}: // Prepare the trace. trace.prepare(&Query::from(block_store)).unwrap(); // Prove the execution. - trace.prove_execution::("testing", VarunaVersion::V1, rng).unwrap() + trace.prove_execution::("testing", VarunaVersion::V2, rng).unwrap() }; assert_eq!(execution_2.len(), 1); diff --git a/synthesizer/process/src/trace/mod.rs b/synthesizer/process/src/trace/mod.rs index 4c6fac7722..24501164c7 100644 --- a/synthesizer/process/src/trace/mod.rs +++ b/synthesizer/process/src/trace/mod.rs @@ -31,6 +31,8 @@ use snarkvm_synthesizer_snark::{Proof, ProvingKey, VerifyingKey}; use std::{collections::HashMap, sync::OnceLock}; +use crate::Authorization; + #[derive(Clone, Debug, Default)] pub struct Trace { /// The list of transitions. @@ -374,12 +376,21 @@ impl Trace { inclusion_version: InclusionVersion, mut verifier_inputs: Vec<(VerifyingKey, Vec>)>, global_state_root: N::StateRoot, - transitions: impl ExactSizeIterator>, + transitions: impl ExactSizeIterator> + Clone, proof: &Proof, ) -> Result<()> { // Construct the batch of inclusion verifier inputs. let batch_inclusion_inputs = - Inclusion::prepare_verifier_inputs(global_state_root, inclusion_version, transitions)?; + Inclusion::prepare_verifier_inputs(global_state_root, inclusion_version, transitions.clone())?; + + let expected_n_inclusions = Authorization::number_of_input_records(transitions); + ensure!( + batch_inclusion_inputs.len() == expected_n_inclusions, + "Unexpected number of inclusion inputs: {} instead of {}", + batch_inclusion_inputs.len(), + expected_n_inclusions + ); + // Insert the batch of inclusion verifier inputs to the verifier inputs. if !batch_inclusion_inputs.is_empty() { // Retrieve the inclusion verifying key depending on the inclusion version. diff --git a/synthesizer/snark/src/lib.rs b/synthesizer/snark/src/lib.rs index ad43689dcc..9f2a277d7f 100644 --- a/synthesizer/snark/src/lib.rs +++ b/synthesizer/snark/src/lib.rs @@ -32,7 +32,7 @@ mod certificate; pub use certificate::Certificate; mod proof; -pub use proof::Proof; +pub use proof::{Proof, proof_size}; mod proving_key; pub use proving_key::ProvingKey; diff --git a/synthesizer/snark/src/proof/mod.rs b/synthesizer/snark/src/proof/mod.rs index 508a8bfdf5..b6f70c4f23 100644 --- a/synthesizer/snark/src/proof/mod.rs +++ b/synthesizer/snark/src/proof/mod.rs @@ -13,6 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use snarkvm_algorithms::snark::varuna::{VarunaVersion, proof_size as varuna_proof_size}; + use super::*; mod bytes; @@ -39,3 +41,25 @@ impl Deref for Proof { &self.proof } } + +/// Computes the size in bytes of the proof as produced by +/// `Proof::write_le` without needing to receive the proof itself. +/// +/// *Arguments*: +/// - `batch_sizes`: the batch sizes of the circuits and instances being +/// proved. +/// - `varuna_version`: the version of Varuna being used +/// - `hiding`: indicates whether the proof system is run in ZK mode +/// +/// *Returns*: +/// - `Ok(size)` for `VarunaVersion::V2`, where `size` is the size of the proof +/// in bytes. +/// - `Err` for `VarunaVersion::V1`. +pub fn proof_size( + batch_sizes: &[usize], + varuna_version: VarunaVersion, + hiding_mode: bool, +) -> Result { + // The extra 1 byte comes from the serialised version number + varuna_proof_size::(batch_sizes, varuna_version, hiding_mode).map(|size| 1 + size) +} diff --git a/synthesizer/src/vm/execute.rs b/synthesizer/src/vm/execute.rs index 667348e532..6ecbb76c11 100644 --- a/synthesizer/src/vm/execute.rs +++ b/synthesizer/src/vm/execute.rs @@ -285,7 +285,7 @@ mod tests { types::Field, }; use snarkvm_ledger_block::Transition; - use snarkvm_synthesizer_process::{ConsensusFeeVersion, cost_per_command, execution_cost_v1, execution_cost_v2}; + use snarkvm_synthesizer_process::{ConsensusFeeVersion, cost_per_command}; use snarkvm_synthesizer_program::StackTrait; use indexmap::IndexMap; @@ -423,8 +423,8 @@ mod tests { let query = Query::VM(vm.block_store().clone()); let (execution, _) = vm.execute_authorization_raw(authorization, &query, rng).unwrap(); - let (cost, _) = execution_cost_v2(&vm.process().read(), &execution).unwrap(); - let (old_cost, _) = execution_cost_v1(&vm.process().read(), &execution).unwrap(); + let (cost, _) = execution_cost(&vm.process().read(), &execution, ConsensusVersion::V2).unwrap(); + let (old_cost, _) = execution_cost(&vm.process().read(), &execution, ConsensusVersion::V1).unwrap(); assert_eq!(34_060, cost); assert_eq!(51_060, old_cost); @@ -560,7 +560,7 @@ finalize test: let query = Query::VM(vm.block_store().clone()); let (execution, _) = vm.execute_authorization_raw(authorization, &query, rng).unwrap(); - let (cost, _) = execution_cost_v1(&vm.process().read(), &execution).unwrap(); + let (cost, _) = execution_cost(&vm.process().read(), &execution, ConsensusVersion::V1).unwrap(); println!("Cost: {cost}"); } @@ -948,7 +948,7 @@ finalize test: assert_eq!(execution.transitions().len(), ::MAX_INPUTS + 1); // Get the finalize cost of the execution. - let (_, (_, finalize_cost)) = execution_cost_v2(&vm.process().read(), &execution).unwrap(); + let (_, (_, finalize_cost)) = execution_cost(&vm.process().read(), &execution, ConsensusVersion::V2).unwrap(); // Compute the expected cost as the sum of the cost in microcredits of each command in each finalize block of each transition in the execution. let mut expected_cost = 0; @@ -1086,7 +1086,7 @@ finalize test: assert_eq!(execution.transitions().len(), Transaction::::MAX_TRANSITIONS - 1); // Get the finalize cost of the execution. - let (_, (_, finalize_cost)) = execution_cost_v2(&vm.process().read(), &execution).unwrap(); + let (_, (_, finalize_cost)) = execution_cost(&vm.process().read(), &execution, ConsensusVersion::V2).unwrap(); // Compute the expected cost as the sum of the cost in microcredits of each command in each finalize block of each transition in the execution. let mut expected_cost = 0; diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/mint_and_split.out b/synthesizer/tests/expectations/vm/execute_and_finalize/mint_and_split.out index aab6280cf0..a759bd1d65 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/mint_and_split.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/mint_and_split.out @@ -9,10 +9,11 @@ outputs: add_next_block: succeeded. - execute: Commitment '1266307482263846358970326041806201638141701138269282465033372005968041137990field' does not exist - execute: Input record for 'mint_and_split.aleo' must belong to the signer -additional: -- child_outputs: - credits.aleo/fee_public: +- verified: true + execute: + mint_and_split.aleo/split: outputs: - - '{"type":"future","id":"3572806592760112018519816250530207176073737548656800904593854927222723276908field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo19e6k5ferx3k8a9k79xtj4uuaztt2jl4eza7k43pygsu977yazypqqwdmw6,\n 1479u64\n ]\n}"}' -- {} -- {} + - '{"type":"record","id":"1699699739729170697265293269960858783252948270460198505329736189985368874527field","checksum":"5094546139349890416493032421390261246272276476596348590549876461855714748426field","value":"record1qvqsptua8f8daxpfrpte04gsm9g9xnd8j0dtl5eh8535vhcv2mtef9ctqyxx66trwfhkxun9v35hguerqqpqzqqknlyy8amkrheskym52rs9prf82xytk90s86wd0l27qupf39fyqtfz2jn7dm59xddt7wgm3vf7pvuhvx6yxn6kfnwqq6a6lg6puegqqd2cxh8","sender_ciphertext":"8320175049502704724056884998196528853724472019327482611754212406252051074685field"}' + - '{"type":"record","id":"172925729887562152149415289505492834678003067935898794702724748466949372684field","checksum":"1603740492188563572946000461530713958992296086854591140121132385743964448321field","value":"record1qvqsq6qge800ds78synw4lugedt3z0vaf5wcca78qrrva9g5g9p655g9qyxx66trwfhkxun9v35hguerqqpqzqz5l7mvjfa763n9t2xrh5ae503gunal63ex2jj4mcx4mwkwrhmnq66c48n9dywl2ed6224qwzwm5e2n07d25749csx9m90dngxu6q3q6l9w8mm","sender_ciphertext":"6029181475896378477744653708372210086548977530010352512129978860336467153920field"}' + speculate: the execution was accepted + add_next_block: succeeded. diff --git a/synthesizer/tests/test_vm_execute_and_finalize.rs b/synthesizer/tests/test_vm_execute_and_finalize.rs index 5d19836a51..915c8091c1 100644 --- a/synthesizer/tests/test_vm_execute_and_finalize.rs +++ b/synthesizer/tests/test_vm_execute_and_finalize.rs @@ -33,7 +33,8 @@ use snarkvm_ledger_block::{ Transition, }; use snarkvm_ledger_store::{ConsensusStorage, ConsensusStore}; -use snarkvm_synthesizer::{VM, program::FinalizeOperation}; +use snarkvm_synthesizer::{Authorization, VM, program::FinalizeOperation}; +use snarkvm_synthesizer_process::{execution_cost, execution_cost_for_authorization}; use snarkvm_synthesizer_program::FinalizeGlobalState; use anyhow::Result; @@ -237,6 +238,26 @@ fn run_test(test: &ProgramTest) -> serde_yaml::Mapping { } }; + // Test cost computation for Authorization + if transaction.is_execute() { + let consensus_version = + CurrentNetwork::CONSENSUS_VERSION(vm.block_store().current_block_height()).unwrap(); + + if consensus_version >= ConsensusVersion::V4 { + let execution = transaction.execution().unwrap(); + + let actual_cost = execution_cost(&vm.process().read(), execution, consensus_version).unwrap(); + + let authorization = + Authorization::from_unchecked((vec![], execution.transitions().cloned().collect())); + let expected_cost = + execution_cost_for_authorization(&vm.process().read(), &authorization, consensus_version) + .unwrap(); + + assert_eq!(actual_cost, expected_cost); + } + } + // Attempt to verify the transaction. let verified = vm.check_transaction(&transaction, None, rng).is_ok(); // Store the verification result. diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/mint_and_split.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/mint_and_split.aleo index 496131827c..205c650bc0 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/mint_and_split.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/mint_and_split.aleo @@ -15,6 +15,10 @@ cases: function: split inputs: [ "{ owner: aleo1j2hfs6yru47h2nvsjdefwtw6nwaj0y4zcl02juyy29txm7nt6y9qln7uhp.private, microcredits: 100u64.private, _nonce: 0group.public }", 50u64] private_key: APrivateKey1zkpJhviKDvvm7yu7SZuhSudVR7zjCRG2HznuAHwuGYc1xqN + - program: mint_and_split.aleo + function: split + inputs: [ "{ owner: aleo1j2hfs6yru47h2nvsjdefwtw6nwaj0y4zcl02juyy29txm7nt6y9qln7uhp.private, microcredits: 100u64.private, _nonce: 4745749250363947745807119124444606639539806979883854068889616498365610280135group.public, _version: 1u8.public }", 50u64] + private_key: APrivateKey1zkpFbGDx4znwxo1zrxfUscfGn1Vy3My3ia5gRHx3XwaLtCR */ program mint_and_split.aleo;