From d2f8d1e47882269359c304948f1882f20bb3a953 Mon Sep 17 00:00:00 2001 From: Victor Sint Nicolaas Date: Thu, 11 Apr 2024 13:07:52 +0200 Subject: [PATCH] Track num_variables for each function in a Deployment to prevent going over those limits Lower MAX_DEPLOYMENT_VARIABLES Initialize circuit gadget constants before synthesis Update test expectation --- circuit/network/src/lib.rs | 3 + circuit/network/src/v0.rs | 23 +++++ console/network/src/lib.rs | 2 +- .../block/src/transaction/deployment/bytes.rs | 16 ++-- .../block/src/transaction/deployment/mod.rs | 49 +++++++--- .../src/transaction/deployment/serialize.rs | 4 +- .../store/src/helpers/memory/transaction.rs | 9 ++ .../store/src/helpers/rocksdb/internal/id.rs | 2 + .../store/src/helpers/rocksdb/transaction.rs | 9 ++ ledger/store/src/transaction/deployment.rs | 30 ++++-- synthesizer/process/src/deploy.rs | 2 +- synthesizer/process/src/finalize.rs | 2 +- synthesizer/process/src/stack/deploy.rs | 16 ++-- synthesizer/process/src/stack/execute.rs | 13 +-- .../process/src/stack/helpers/initialize.rs | 1 + .../process/src/stack/helpers/synthesize.rs | 7 +- synthesizer/process/src/stack/mod.rs | 32 +++++++ .../process/src/trace/call_metrics/mod.rs | 1 - synthesizer/src/vm/mod.rs | 91 +++++++++++++++++-- .../vm/execute_and_finalize/test_rand.out | 2 +- 20 files changed, 255 insertions(+), 59 deletions(-) diff --git a/circuit/network/src/lib.rs b/circuit/network/src/lib.rs index 8a17f1c7fe8..cc650aad81b 100644 --- a/circuit/network/src/lib.rs +++ b/circuit/network/src/lib.rs @@ -26,6 +26,9 @@ 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; + /// Initializes all of the constants for the Aleo environment. + fn init_constants(); + /// Returns the encryption domain as a constant field element. fn encryption_domain() -> Field; diff --git a/circuit/network/src/v0.rs b/circuit/network/src/v0.rs index 49e61511220..43fc01865c8 100644 --- a/circuit/network/src/v0.rs +++ b/circuit/network/src/v0.rs @@ -101,6 +101,29 @@ thread_local! { pub struct AleoV0; impl Aleo for AleoV0 { + /// Initializes all of the constants for the Aleo environment. + fn init_constants() { + GENERATOR_G.with(|_| ()); + ENCRYPTION_DOMAIN.with(|_| ()); + GRAPH_KEY_DOMAIN.with(|_| ()); + SERIAL_NUMBER_DOMAIN.with(|_| ()); + BHP_256.with(|_| ()); + BHP_512.with(|_| ()); + BHP_768.with(|_| ()); + BHP_1024.with(|_| ()); + KECCAK_256.with(|_| ()); + KECCAK_384.with(|_| ()); + KECCAK_512.with(|_| ()); + PEDERSEN_64.with(|_| ()); + PEDERSEN_128.with(|_| ()); + POSEIDON_2.with(|_| ()); + POSEIDON_4.with(|_| ()); + POSEIDON_8.with(|_| ()); + SHA3_256.with(|_| ()); + SHA3_384.with(|_| ()); + SHA3_512.with(|_| ()); + } + /// Returns the encryption domain as a constant field element. fn encryption_domain() -> Field { ENCRYPTION_DOMAIN.with(|domain| domain.clone()) diff --git a/console/network/src/lib.rs b/console/network/src/lib.rs index eabf0a5c82d..faec9b71787 100644 --- a/console/network/src/lib.rs +++ b/console/network/src/lib.rs @@ -117,7 +117,7 @@ pub trait Network: /// The maximum number of constraints in a deployment. const MAX_DEPLOYMENT_CONSTRAINTS: u64 = 1 << 20; // 1,048,576 constraints /// The maximum number of variables in a deployment. - const MAX_DEPLOYMENT_VARIABLES: u64 = 1 << 22; // 4,194,304 variables + const MAX_DEPLOYMENT_VARIABLES: u64 = 1 << 20; // 1,048,576 variables /// The maximum number of microcredits that can be spent as a fee. const MAX_FEE: u64 = 1_000_000_000_000_000; /// The maximum number of microcredits that can be spent on a finalize block. diff --git a/ledger/block/src/transaction/deployment/bytes.rs b/ledger/block/src/transaction/deployment/bytes.rs index e8e97757a61..a4aed9266d2 100644 --- a/ledger/block/src/transaction/deployment/bytes.rs +++ b/ledger/block/src/transaction/deployment/bytes.rs @@ -31,8 +31,8 @@ impl FromBytes for Deployment { // Read the number of entries in the bundle. let num_entries = u16::read_le(&mut reader)?; - // Read the verifying keys. - let mut verifying_keys = Vec::with_capacity(num_entries as usize); + // Read the function specifications. + let mut function_specs = Vec::with_capacity(num_entries as usize); for _ in 0..num_entries { // Read the identifier. let identifier = Identifier::::read_le(&mut reader)?; @@ -40,12 +40,14 @@ impl FromBytes for Deployment { let verifying_key = VerifyingKey::::read_le(&mut reader)?; // Read the certificate. let certificate = Certificate::::read_le(&mut reader)?; + // Read the variable count. + let variable_count = u64::read_le(&mut reader)?; // Add the entry. - verifying_keys.push((identifier, (verifying_key, certificate))); + function_specs.push((identifier, (verifying_key, certificate, variable_count))); } // Return the deployment. - Self::new(edition, program, verifying_keys).map_err(|err| error(format!("{err}"))) + Self::new(edition, program, function_specs).map_err(|err| error(format!("{err}"))) } } @@ -59,15 +61,17 @@ impl ToBytes for Deployment { // Write the program. self.program.write_le(&mut writer)?; // Write the number of entries in the bundle. - (u16::try_from(self.verifying_keys.len()).map_err(|e| error(e.to_string()))?).write_le(&mut writer)?; + (u16::try_from(self.function_specs.len()).map_err(|e| error(e.to_string()))?).write_le(&mut writer)?; // Write each entry. - for (function_name, (verifying_key, certificate)) in &self.verifying_keys { + for (function_name, (verifying_key, certificate, variable_count)) in &self.function_specs { // Write the function name. function_name.write_le(&mut writer)?; // Write the verifying key. verifying_key.write_le(&mut writer)?; // Write the certificate. certificate.write_le(&mut writer)?; + // Write the variable count. + variable_count.write_le(&mut writer)?; } Ok(()) } diff --git a/ledger/block/src/transaction/deployment/mod.rs b/ledger/block/src/transaction/deployment/mod.rs index bf898b41bab..105f5b12eeb 100644 --- a/ledger/block/src/transaction/deployment/mod.rs +++ b/ledger/block/src/transaction/deployment/mod.rs @@ -27,25 +27,27 @@ use console::{ use synthesizer_program::Program; use synthesizer_snark::{Certificate, VerifyingKey}; +/// The verification key, certificate, and number of variables for a function. +type FunctionSpec = (VerifyingKey, Certificate, u64); + #[derive(Clone, PartialEq, Eq)] pub struct Deployment { /// The edition. edition: u16, /// The program. program: Program, - /// The mapping of function names to their verifying key and certificate. - verifying_keys: Vec<(Identifier, (VerifyingKey, Certificate))>, + /// The mapping of function names to their specification. + function_specs: Vec<(Identifier, FunctionSpec)>, } - impl Deployment { /// Initializes a new deployment. pub fn new( edition: u16, program: Program, - verifying_keys: Vec<(Identifier, (VerifyingKey, Certificate))>, + function_specs: Vec<(Identifier, FunctionSpec)>, ) -> Result { // Construct the deployment. - let deployment = Self { edition, program, verifying_keys }; + let deployment = Self { edition, program, function_specs }; // Ensure the deployment is ordered. deployment.check_is_ordered()?; // Return the deployment. @@ -70,17 +72,17 @@ impl Deployment { ); // Ensure the deployment contains verifying keys. ensure!( - !self.verifying_keys.is_empty(), + !self.function_specs.is_empty(), "No verifying keys present in the deployment for program '{program_id}'" ); - // Ensure the number of functions matches the number of verifying keys. - if self.program.functions().len() != self.verifying_keys.len() { + // Ensure the number of functions matches the number of function specifications. + if self.program.functions().len() != self.function_specs.len() { bail!("Deployment has an incorrect number of verifying keys, according to the program."); } - // Ensure the function and verifying keys correspond. - for ((function_name, function), (name, _)) in self.program.functions().iter().zip_eq(&self.verifying_keys) { + // Ensure the function and specs correspond. + for ((function_name, function), (name, _)) in self.program.functions().iter().zip_eq(&self.function_specs) { // Ensure the function name is correct. if function_name != function.name() { bail!("The function key is '{function_name}', but the function name is '{}'", function.name()) @@ -92,7 +94,7 @@ impl Deployment { } ensure!( - !has_duplicates(self.verifying_keys.iter().map(|(name, ..)| name)), + !has_duplicates(self.function_specs.iter().map(|(name, ..)| name)), "A duplicate function name was found" ); @@ -119,9 +121,9 @@ impl Deployment { self.program.id() } - /// Returns the verifying keys. - pub const fn verifying_keys(&self) -> &Vec<(Identifier, (VerifyingKey, Certificate))> { - &self.verifying_keys + /// Returns the function specifications. + pub const fn verifying_keys(&self) -> &Vec<(Identifier, FunctionSpec)> { + &self.function_specs } /// Returns the sum of the constraint counts for all functions in this deployment. @@ -129,7 +131,7 @@ impl Deployment { // Initialize the accumulator. let mut num_combined_constraints = 0u64; // Iterate over the functions. - for (_, (vk, _)) in &self.verifying_keys { + for (_, (vk, _, _)) in &self.function_specs { // Add the number of constraints. // Note: This method must be *checked* because the claimed constraint count // is from the user, not the synthesizer. @@ -141,6 +143,23 @@ impl Deployment { Ok(num_combined_constraints) } + /// Returns the sum of the variable counts for all functions in this deployment. + pub fn num_combined_variables(&self) -> Result { + // Initialize the accumulator. + let mut num_combined_variables = 0u64; + // Iterate over the functions. + for (_, (_, _, variable_count)) in &self.function_specs { + // Add the number of variables. + // Note: This method must be *checked* because the claimed variable count + // is from the user, not the synthesizer. + num_combined_variables = num_combined_variables + .checked_add(*variable_count) + .ok_or_else(|| anyhow!("Overflow when counting variables for '{}'", self.program_id()))?; + } + // Return the number of combined constraints. + Ok(num_combined_variables) + } + /// Returns the deployment ID. pub fn to_deployment_id(&self) -> Result> { Ok(*Transaction::deployment_tree(self, None)?.root()) diff --git a/ledger/block/src/transaction/deployment/serialize.rs b/ledger/block/src/transaction/deployment/serialize.rs index 88af802fc97..ee9ac51cd5c 100644 --- a/ledger/block/src/transaction/deployment/serialize.rs +++ b/ledger/block/src/transaction/deployment/serialize.rs @@ -22,7 +22,7 @@ impl Serialize for Deployment { let mut deployment = serializer.serialize_struct("Deployment", 3)?; deployment.serialize_field("edition", &self.edition)?; deployment.serialize_field("program", &self.program)?; - deployment.serialize_field("verifying_keys", &self.verifying_keys)?; + deployment.serialize_field("function_specs", &self.function_specs)?; deployment.end() } false => ToBytesSerializer::serialize_with_size_encoding(self, serializer), @@ -45,7 +45,7 @@ impl<'de, N: Network> Deserialize<'de> for Deployment { // Retrieve the program. DeserializeExt::take_from_value::(&mut deployment, "program")?, // Retrieve the verifying keys. - DeserializeExt::take_from_value::(&mut deployment, "verifying_keys")?, + DeserializeExt::take_from_value::(&mut deployment, "function_specs")?, ) .map_err(de::Error::custom)?; diff --git a/ledger/store/src/helpers/memory/transaction.rs b/ledger/store/src/helpers/memory/transaction.rs index 66b7b14867f..eb80a51af42 100644 --- a/ledger/store/src/helpers/memory/transaction.rs +++ b/ledger/store/src/helpers/memory/transaction.rs @@ -103,6 +103,8 @@ pub struct DeploymentMemory { verifying_key_map: MemoryMap<(ProgramID, Identifier, u16), VerifyingKey>, /// The certificate map. certificate_map: MemoryMap<(ProgramID, Identifier, u16), Certificate>, + /// The variable count map. + variable_count_map: MemoryMap<(ProgramID, Identifier, u16), u64>, /// The fee store. fee_store: FeeStore>, } @@ -116,6 +118,7 @@ impl DeploymentStorage for DeploymentMemory { type ProgramMap = MemoryMap<(ProgramID, u16), Program>; type VerifyingKeyMap = MemoryMap<(ProgramID, Identifier, u16), VerifyingKey>; type CertificateMap = MemoryMap<(ProgramID, Identifier, u16), Certificate>; + type VariableCountMap = MemoryMap<(ProgramID, Identifier, u16), u64>; type FeeStorage = FeeMemory; /// Initializes the deployment storage. @@ -128,6 +131,7 @@ impl DeploymentStorage for DeploymentMemory { program_map: MemoryMap::default(), verifying_key_map: MemoryMap::default(), certificate_map: MemoryMap::default(), + variable_count_map: MemoryMap::default(), fee_store, }) } @@ -167,6 +171,11 @@ impl DeploymentStorage for DeploymentMemory { &self.certificate_map } + /// Returns the variable count map. + fn variable_count_map(&self) -> &Self::VariableCountMap { + &self.variable_count_map + } + /// Returns the fee store. fn fee_store(&self) -> &FeeStore { &self.fee_store diff --git a/ledger/store/src/helpers/rocksdb/internal/id.rs b/ledger/store/src/helpers/rocksdb/internal/id.rs index 6093fdad748..4ada48b0c11 100644 --- a/ledger/store/src/helpers/rocksdb/internal/id.rs +++ b/ledger/store/src/helpers/rocksdb/internal/id.rs @@ -110,6 +110,7 @@ pub enum DeploymentMap { Program = DataID::DeploymentProgramMap as u16, VerifyingKey = DataID::DeploymentVerifyingKeyMap as u16, Certificate = DataID::DeploymentCertificateMap as u16, + VariableCount = DataID::DeploymentVariableCountMap as u16, } /// The RocksDB map prefix for execution-related entries. @@ -252,6 +253,7 @@ enum DataID { DeploymentProgramMap, DeploymentVerifyingKeyMap, DeploymentCertificateMap, + DeploymentVariableCountMap, // Execution ExecutionIDMap, ExecutionReverseIDMap, diff --git a/ledger/store/src/helpers/rocksdb/transaction.rs b/ledger/store/src/helpers/rocksdb/transaction.rs index 2cac1c9c2c0..ff1610d77ee 100644 --- a/ledger/store/src/helpers/rocksdb/transaction.rs +++ b/ledger/store/src/helpers/rocksdb/transaction.rs @@ -113,6 +113,8 @@ pub struct DeploymentDB { verifying_key_map: DataMap<(ProgramID, Identifier, u16), VerifyingKey>, /// The certificate map. certificate_map: DataMap<(ProgramID, Identifier, u16), Certificate>, + /// The variable count map. + variable_count_map: DataMap<(ProgramID, Identifier, u16), u64>, /// The fee store. fee_store: FeeStore>, } @@ -126,6 +128,7 @@ impl DeploymentStorage for DeploymentDB { type ProgramMap = DataMap<(ProgramID, u16), Program>; type VerifyingKeyMap = DataMap<(ProgramID, Identifier, u16), VerifyingKey>; type CertificateMap = DataMap<(ProgramID, Identifier, u16), Certificate>; + type VariableCountMap = DataMap<(ProgramID, Identifier, u16), u64>; type FeeStorage = FeeDB; /// Initializes the deployment storage. @@ -140,6 +143,7 @@ impl DeploymentStorage for DeploymentDB { program_map: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::Program))?, verifying_key_map: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::VerifyingKey))?, certificate_map: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::Certificate))?, + variable_count_map: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::VariableCount))?, fee_store, }) } @@ -179,6 +183,11 @@ impl DeploymentStorage for DeploymentDB { &self.certificate_map } + /// Returns the variable count map. + fn variable_count_map(&self) -> &Self::VariableCountMap { + &self.variable_count_map + } + /// Returns the fee store. fn fee_store(&self) -> &FeeStore { &self.fee_store diff --git a/ledger/store/src/transaction/deployment.rs b/ledger/store/src/transaction/deployment.rs index bdc7d95b46f..1b41181c414 100644 --- a/ledger/store/src/transaction/deployment.rs +++ b/ledger/store/src/transaction/deployment.rs @@ -49,6 +49,8 @@ pub trait DeploymentStorage: Clone + Send + Sync { type VerifyingKeyMap: for<'a> Map<'a, (ProgramID, Identifier, u16), VerifyingKey>; /// The mapping of `(program ID, function name, edition)` to `certificate`. type CertificateMap: for<'a> Map<'a, (ProgramID, Identifier, u16), Certificate>; + /// The mapping of `(program ID, function name, edition)` to `variable count`. + type VariableCountMap: for<'a> Map<'a, (ProgramID, Identifier, u16), u64>; /// The fee storage. type FeeStorage: FeeStorage; @@ -69,6 +71,8 @@ pub trait DeploymentStorage: Clone + Send + Sync { fn verifying_key_map(&self) -> &Self::VerifyingKeyMap; /// Returns the certificate map. fn certificate_map(&self) -> &Self::CertificateMap; + /// Returns the variable count map. + fn variable_count_map(&self) -> &Self::VariableCountMap; /// Returns the fee storage. fn fee_store(&self) -> &FeeStore; @@ -196,11 +200,13 @@ pub trait DeploymentStorage: Clone + Send + Sync { self.program_map().insert((program_id, edition), program.clone())?; // Store the verifying keys and certificates. - for (function_name, (verifying_key, certificate)) in deployment.verifying_keys() { + for (function_name, (verifying_key, certificate, variable_count)) in deployment.verifying_keys() { // Store the verifying key. self.verifying_key_map().insert((program_id, *function_name, edition), verifying_key.clone())?; // Store the certificate. self.certificate_map().insert((program_id, *function_name, edition), certificate.clone())?; + // Store the variable count. + self.variable_count_map().insert((program_id, *function_name, edition), *variable_count)?; } // Store the fee transition. @@ -400,27 +406,37 @@ pub trait DeploymentStorage: Clone + Send + Sync { None => bail!("Failed to get the deployed program '{program_id}' (edition {edition})"), }; - // Initialize a vector for the verifying keys and certificates. - let mut verifying_keys = Vec::with_capacity(program.functions().len()); + // Initialize a vector for the function_specs. + let mut function_specs = Vec::with_capacity(program.functions().len()); // Retrieve the verifying keys and certificates. for function_name in program.functions().keys() { // Retrieve the verifying key. let verifying_key = match self.verifying_key_map().get_confirmed(&(program_id, *function_name, edition))? { Some(verifying_key) => cow_to_cloned!(verifying_key), - None => bail!("Failed to get the verifying key for '{program_id}/{function_name}' (edition {edition})"), + None => { + bail!("Failed to get the verifying key for '{program_id}/{function_name}' (edition {edition})") + } }; // Retrieve the certificate. let certificate = match self.certificate_map().get_confirmed(&(program_id, *function_name, edition))? { Some(certificate) => cow_to_cloned!(certificate), None => bail!("Failed to get the certificate for '{program_id}/{function_name}' (edition {edition})"), }; - // Add the verifying key and certificate to the deployment. - verifying_keys.push((*function_name, (verifying_key, certificate))); + // Retrieve the variable count. + let variable_count = + match self.variable_count_map().get_confirmed(&(program_id, *function_name, edition))? { + Some(variable_count) => cow_to_cloned!(variable_count), + None => { + bail!("Failed to get the variable count for '{program_id}/{function_name}' (edition {edition})") + } + }; + // Add the verifying key, certificate and variable_count to the deployment. + function_specs.push((*function_name, (verifying_key, certificate, variable_count))); } // Return the deployment. - Ok(Some(Deployment::new(edition, program, verifying_keys)?)) + Ok(Some(Deployment::new(edition, program, function_specs)?)) } /// Returns the fee for the given `transaction ID`. diff --git a/synthesizer/process/src/deploy.rs b/synthesizer/process/src/deploy.rs index b876a37f0eb..5bd6a234870 100644 --- a/synthesizer/process/src/deploy.rs +++ b/synthesizer/process/src/deploy.rs @@ -48,7 +48,7 @@ impl Process { lap!(timer, "Compute the stack"); // Insert the verifying keys. - for (function_name, (verifying_key, _)) in deployment.verifying_keys() { + for (function_name, (verifying_key, _, _)) in deployment.verifying_keys() { stack.insert_verifying_key(function_name, verifying_key.clone())?; } lap!(timer, "Insert the verifying keys"); diff --git a/synthesizer/process/src/finalize.rs b/synthesizer/process/src/finalize.rs index 5b420ac064f..04d482929b8 100644 --- a/synthesizer/process/src/finalize.rs +++ b/synthesizer/process/src/finalize.rs @@ -38,7 +38,7 @@ impl Process { lap!(timer, "Compute the stack"); // Insert the verifying keys. - for (function_name, (verifying_key, _)) in deployment.verifying_keys() { + for (function_name, (verifying_key, _, _)) in deployment.verifying_keys() { stack.insert_verifying_key(function_name, verifying_key.clone())?; } lap!(timer, "Insert the verifying keys"); diff --git a/synthesizer/process/src/stack/deploy.rs b/synthesizer/process/src/stack/deploy.rs index 3836a799d71..fe36958976c 100644 --- a/synthesizer/process/src/stack/deploy.rs +++ b/synthesizer/process/src/stack/deploy.rs @@ -37,6 +37,8 @@ impl Stack { let proving_key = self.get_proving_key(function_name)?; // Retrieve the verifying key. let verifying_key = self.get_verifying_key(function_name)?; + // Retrieve the function variables. + let variable_count = self.get_variable_count(function_name)?; lap!(timer, "Retrieve the keys for {function_name}"); // Certify the circuit. @@ -44,7 +46,7 @@ impl Stack { lap!(timer, "Certify the circuit"); // Add the verifying key and certificate to the bundle. - verifying_keys.push((*function_name, (verifying_key, certificate))); + verifying_keys.push((*function_name, (verifying_key, certificate, variable_count))); } finish!(timer); @@ -76,6 +78,9 @@ impl Stack { // Check that the number of combined constraints does not exceed the deployment limit. ensure!(deployment.num_combined_constraints()? <= N::MAX_DEPLOYMENT_CONSTRAINTS); + // Check that the number of combined variables does not exceed the deployment limit. + ensure!(deployment.num_combined_variables()? <= N::MAX_DEPLOYMENT_VARIABLES); + // Construct the call stacks and assignments used to verify the certificates. let mut call_stacks = Vec::with_capacity(deployment.verifying_keys().len()); @@ -92,7 +97,7 @@ impl Stack { ); // Iterate through the program functions and construct the callstacks and corresponding assignments. - for (function, (_, (verifying_key, _))) in + for (function, (_, (verifying_key, _, variable_limit))) in deployment.program().functions().values().zip_eq(deployment.verifying_keys()) { // Initialize a burner private key. @@ -132,20 +137,19 @@ impl Stack { lap!(timer, "Compute the request for {}", function.name()); // Initialize the assignments. let assignments = Assignments::::default(); - // Initialize the variable limit. - let variable_limit = N::MAX_DEPLOYMENT_VARIABLES / N::MAX_FUNCTIONS as u64; // Initialize the constraint limit. Account for the constraint added after synthesis that makes the Varuna zerocheck hiding. let Some(constraint_limit) = verifying_key.circuit_info.num_constraints.checked_sub(1) else { // Since a deployment must always pay non-zero fee, it must always have at least one constraint. bail!("The constraint limit of 0 for function '{}' is invalid", function.name()); }; + // Initialize the call stack. let call_stack = CallStack::CheckDeployment( vec![request], burner_private_key, assignments.clone(), Some(constraint_limit as u64), - Some(variable_limit), + Some(*variable_limit), ); // Append the function name, callstack, and assignments. call_stacks.push((function.name(), call_stack, assignments)); @@ -154,7 +158,7 @@ impl Stack { // Verify the certificates. let rngs = (0..call_stacks.len()).map(|_| StdRng::from_seed(rng.gen())).collect::>(); cfg_into_iter!(call_stacks).zip_eq(deployment.verifying_keys()).zip_eq(rngs).try_for_each( - |(((function_name, call_stack, assignments), (_, (verifying_key, certificate))), mut rng)| { + |(((function_name, call_stack, assignments), (_, (verifying_key, certificate, _))), mut rng)| { // Synthesize the circuit. if let Err(err) = self.execute_function::(call_stack, caller, root_tvk, &mut rng) { bail!("Failed to synthesize the circuit for '{function_name}': {err}") diff --git a/synthesizer/process/src/stack/execute.rs b/synthesizer/process/src/stack/execute.rs index f3075728d3e..80338a16790 100644 --- a/synthesizer/process/src/stack/execute.rs +++ b/synthesizer/process/src/stack/execute.rs @@ -140,14 +140,16 @@ impl StackExecute for Stack { ) -> Result> { let timer = timer!("Stack::execute_function"); + // Ensure constant circuit gadgets are initialized. + A::init_constants(); // Ensure the circuit environment is clean. A::reset(); // If in 'CheckDeployment' mode, set the constraint limit. // We do not have to reset it after function calls because `CheckDeployment` mode does not execute those. - if let CallStack::CheckDeployment(_, _, _, constraint_limit, var_limit) = &call_stack { + if let CallStack::CheckDeployment(_, _, _, constraint_limit, variable_limit) = &call_stack { A::set_constraint_limit(*constraint_limit); - A::set_variable_limit(*var_limit); + A::set_variable_limit(*variable_limit); } // Retrieve the next request. @@ -403,8 +405,6 @@ impl StackExecute for Stack { self.matches_value_type(output, output_type) })?; - println!("A::num_constants(): {}", A::num_constants()); - // If the circuit is in `Execute` or `PackageRun` mode, then ensure the circuit is satisfied. if matches!(registers.call_stack(), CallStack::Execute(..) | CallStack::PackageRun(..)) { // If the circuit is empty or not satisfied, then throw an error. @@ -430,7 +430,7 @@ impl StackExecute for Stack { // If the proving key does not exist, then synthesize it. if !self.contains_proving_key(function.name()) { // Add the circuit key to the mapping. - self.synthesize_from_assignment(function.name(), &assignment)?; + self.synthesize_from_assignment(function.name(), &assignment, num_variables)?; lap!(timer, "Synthesize the {} circuit key", function.name()); } } @@ -448,7 +448,6 @@ impl StackExecute for Stack { let metrics = CallMetrics { program_id: *self.program_id(), function_name: *function.name(), - num_variables, num_instructions: function.instructions().len(), num_request_constraints, num_function_constraints, @@ -471,7 +470,6 @@ impl StackExecute for Stack { let metrics = CallMetrics { program_id: *self.program_id(), function_name: *function.name(), - num_variables, num_instructions: function.instructions().len(), num_request_constraints, num_function_constraints, @@ -492,7 +490,6 @@ impl StackExecute for Stack { let metrics = CallMetrics { program_id: *self.program_id(), function_name: *function.name(), - num_variables, num_instructions: function.instructions().len(), num_request_constraints, num_function_constraints, diff --git a/synthesizer/process/src/stack/helpers/initialize.rs b/synthesizer/process/src/stack/helpers/initialize.rs index ea1c35f370a..0340eb66ceb 100644 --- a/synthesizer/process/src/stack/helpers/initialize.rs +++ b/synthesizer/process/src/stack/helpers/initialize.rs @@ -27,6 +27,7 @@ impl Stack { universal_srs: process.universal_srs().clone(), proving_keys: Default::default(), verifying_keys: Default::default(), + variable_counts: Default::default(), number_of_calls: Default::default(), finalize_costs: Default::default(), program_depth: 0, diff --git a/synthesizer/process/src/stack/helpers/synthesize.rs b/synthesizer/process/src/stack/helpers/synthesize.rs index 72d7d7bc243..e5532a7374e 100644 --- a/synthesizer/process/src/stack/helpers/synthesize.rs +++ b/synthesizer/process/src/stack/helpers/synthesize.rs @@ -80,6 +80,8 @@ impl Stack { ensure!(self.contains_proving_key(function_name), "Function '{function_name}' is missing a proving key."); // Ensure the verifying key exists. ensure!(self.contains_verifying_key(function_name), "Function '{function_name}' is missing a verifying key."); + // Ensure the variable count exists. + ensure!(self.contains_variable_count(function_name), "Function '{function_name}' is missing variable count."); Ok(()) } @@ -89,6 +91,7 @@ impl Stack { &self, function_name: &Identifier, assignment: &circuit::Assignment, + variable_count: u64, ) -> Result<()> { // If the proving and verifying key already exist, skip the synthesis for this function. if self.contains_proving_key(function_name) && self.contains_verifying_key(function_name) { @@ -100,6 +103,8 @@ impl Stack { // Insert the proving key. self.insert_proving_key(function_name, proving_key)?; // Insert the verifying key. - self.insert_verifying_key(function_name, verifying_key) + self.insert_verifying_key(function_name, verifying_key)?; + // Insert the variable count. + self.insert_variable_count(function_name, variable_count) } } diff --git a/synthesizer/process/src/stack/mod.rs b/synthesizer/process/src/stack/mod.rs index 070b1391dda..9b391080fab 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -186,6 +186,8 @@ pub struct Stack { proving_keys: Arc, ProvingKey>>>, /// The mapping of function name to verifying key. verifying_keys: Arc, VerifyingKey>>>, + /// The mapping of function name to number of variables. + variable_counts: Arc, u64>>>, /// The mapping of function names to the number of calls. number_of_calls: IndexMap, usize>, /// The mapping of function names to finalize cost. @@ -377,6 +379,12 @@ impl Stack { self.verifying_keys.read().contains_key(function_name) } + /// Returns `true` if the function variables for the given function name exists. + #[inline] + pub fn contains_variable_count(&self, function_name: &Identifier) -> bool { + self.variable_counts.read().contains_key(function_name) + } + /// Returns the proving key for the given function name. #[inline] pub fn get_proving_key(&self, function_name: &Identifier) -> Result> { @@ -399,6 +407,16 @@ impl Stack { } } + /// Returns the variable count for the given function name. + #[inline] + pub fn get_variable_count(&self, function_name: &Identifier) -> Result { + // Return the number of function variables, if it exists. + match self.variable_counts.read().get(function_name) { + Some(variable_count) => Ok(*variable_count), + None => bail!("Variable count not found for: {}/{function_name}", self.program.id()), + } + } + /// Inserts the given proving key for the given function name. #[inline] pub fn insert_proving_key(&self, function_name: &Identifier, proving_key: ProvingKey) -> Result<()> { @@ -427,6 +445,20 @@ impl Stack { Ok(()) } + /// Inserts the given variable count for the given function name. + #[inline] + pub fn insert_variable_count(&self, function_name: &Identifier, variable_count: u64) -> Result<()> { + // Ensure the function name exists in the program. + ensure!( + self.program.contains_function(function_name), + "Function '{function_name}' does not exist in program '{}'.", + self.program.id() + ); + // Insert the number of function variables. + self.variable_counts.write().insert(*function_name, variable_count); + Ok(()) + } + /// Removes the proving key for the given function name. #[inline] pub fn remove_proving_key(&self, function_name: &Identifier) { diff --git a/synthesizer/process/src/trace/call_metrics/mod.rs b/synthesizer/process/src/trace/call_metrics/mod.rs index 4a682808112..d45fec6d943 100644 --- a/synthesizer/process/src/trace/call_metrics/mod.rs +++ b/synthesizer/process/src/trace/call_metrics/mod.rs @@ -22,7 +22,6 @@ pub struct CallMetrics { pub program_id: ProgramID, pub function_name: Identifier, pub num_instructions: usize, - pub num_variables: u64, pub num_request_constraints: u64, pub num_function_constraints: u64, pub num_response_constraints: u64, diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index c516ec8550f..87cd6eaf254 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -1031,7 +1031,7 @@ function a: // Note: `deployment_transaction_ids` is sorted lexicographically by transaction ID, so the order may change if we update internal methods. assert_eq!( deployment_transaction_ids, - vec![deployment_1.id(), deployment_4.id(), deployment_3.id(), deployment_2.id()], + vec![deployment_4.id(), deployment_3.id(), deployment_1.id(), deployment_2.id()], "Update me if serialization has changed" ); } @@ -1401,11 +1401,11 @@ function do: // Increase the number of constraints in the verifying keys. let mut vks_with_overreport = Vec::with_capacity(deployment.verifying_keys().len()); - for (id, (vk, cert)) in deployment.verifying_keys() { + for (id, (vk, cert, vars)) in deployment.verifying_keys() { let mut vk = vk.deref().clone(); vk.circuit_info.num_constraints += 1; let vk = VerifyingKey::new(Arc::new(vk)); - vks_with_overreport.push((*id, (vk, cert.clone()))); + vks_with_overreport.push((*id, (vk, cert.clone(), *vars))); } // Each additional constraint costs 25 microcredits, so we need to increase the fee by 25 microcredits. @@ -1465,11 +1465,11 @@ function do: // Decrease the number of constraints in the verifying keys. let mut vks_with_underreport = Vec::with_capacity(deployment.verifying_keys().len()); - for (id, (vk, cert)) in deployment.verifying_keys() { + for (id, (vk, cert, vars)) in deployment.verifying_keys() { let mut vk = vk.deref().clone(); vk.circuit_info.num_constraints -= 2; let vk = VerifyingKey::new(Arc::new(vk)); - vks_with_underreport.push((*id, (vk, cert.clone()))); + vks_with_underreport.push((*id, (vk, cert.clone(), *vars))); } // Create a new deployment transaction with the underreported verifying keys. @@ -1503,6 +1503,79 @@ function do: vm.add_next_block(&block).unwrap(); } + #[test] + fn test_deployment_variable_underreport() { + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + let address = Address::try_from(&private_key).unwrap(); + + // Initialize the genesis block. + let genesis = sample_genesis_block(rng); + + // Initialize the VM. + let vm = sample_vm(); + // Update the VM. + vm.add_next_block(&genesis).unwrap(); + + // Deploy the base program. + let program = Program::from_str( + r" +program synthesis_underreport.aleo; + +function do: + input r0 as u32.private; + add r0 r0 into r1; + output r1 as u32.public;", + ) + .unwrap(); + + // Create the deployment transaction. + let transaction = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + + // Destructure the deployment transaction. + let Transaction::Deploy(txid, program_owner, deployment, fee) = transaction else { + panic!("Expected a deployment transaction"); + }; + + // Decrease the number of reported variables in the verifying keys. + let mut vks_with_underreport = Vec::with_capacity(deployment.verifying_keys().len()); + for (id, (vk, cert, vars)) in deployment.verifying_keys() { + vks_with_underreport.push((*id, (vk.clone(), cert.clone(), vars - 2))); + } + + // Create a new deployment transaction with the underreported verifying keys. + let adjusted_deployment = + Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_underreport).unwrap(); + let adjusted_transaction = Transaction::Deploy(txid, program_owner, Box::new(adjusted_deployment), fee); + + // Verify the deployment transaction. It should error when synthesizing the first variable over the vk limit. + let result = vm.check_transaction(&adjusted_transaction, None, rng); + assert!(result.is_err()); + + // Create a standard transaction + // Prepare the inputs. + let inputs = [ + Value::::from_str(&address.to_string()).unwrap(), + Value::::from_str("1u64").unwrap(), + ] + .into_iter(); + + // Execute. + let transaction = + vm.execute(&private_key, ("credits.aleo", "transfer_public"), inputs, None, 0, None, rng).unwrap(); + + // Check that the deployment transaction will be aborted if injected into a block. + let block = sample_next_block(&vm, &private_key, &[transaction, adjusted_transaction.clone()], rng).unwrap(); + + // Check that the block aborts the deployment transaction. + assert_eq!(block.aborted_transaction_ids(), &vec![adjusted_transaction.id()]); + + // Update the VM. + vm.add_next_block(&block).unwrap(); + } + #[test] #[ignore] fn test_deployment_memory_overload() { @@ -1891,7 +1964,7 @@ finalize transfer_public: Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, _ => panic!("Expected a valid balance"), }; - assert_eq!(balance, 182_499_997_483_583, "Update me if the initial balance changes."); + assert_eq!(balance, 182_499_997_475_583, "Update me if the initial balance changes."); // Check the balance of the `credits_wrapper` program. let balance = match vm @@ -1943,7 +2016,7 @@ finalize transfer_public: Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, _ => panic!("Expected a valid balance"), }; - assert_eq!(balance, 182_499_997_431_058, "Update me if the initial balance changes."); + assert_eq!(balance, 182_499_997_423_058, "Update me if the initial balance changes."); // Check the balance of the `credits_wrapper` program. let balance = match vm @@ -2082,7 +2155,7 @@ finalize transfer_public_as_signer: Some(Value::Plaintext(Plaintext::Literal(Literal::U64(balance), _))) => *balance, _ => panic!("Expected a valid balance"), }; - assert_eq!(balance, 182_499_997_412_068, "Update me if the initial balance changes."); + assert_eq!(balance, 182_499_997_404_068, "Update me if the initial balance changes."); // Check the `credits_wrapper` program does not have any balance. let balance = vm @@ -2237,7 +2310,7 @@ finalize transfer_public_to_private: _ => panic!("Expected a valid balance"), }; - assert_eq!(balance, 182_499_996_924_681, "Update me if the initial balance changes."); + assert_eq!(balance, 182_499_996_916_681, "Update me if the initial balance changes."); // Check that the `credits_wrapper` program has a balance of 0. let balance = match vm 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 51fb5b5e601..d71649dc35d 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out @@ -26,7 +26,7 @@ outputs: 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 accepted + speculate: the execution was rejected add_next_block: succeeded. additional: - child_outputs: