diff --git a/algorithms/src/snark/varuna/tests.rs b/algorithms/src/snark/varuna/tests.rs index 70eeb548e7..675ae2b197 100644 --- a/algorithms/src/snark/varuna/tests.rs +++ b/algorithms/src/snark/varuna/tests.rs @@ -778,8 +778,7 @@ mod varuna_test_vectors { create_test_vector("domain", "C", &format!("{:?}", variable_domain_elements), circuit); } - let fifth_oracles = - AHPForR1CS::<_, MM>::prover_fifth_round(verifier_fourth_msg.clone(), prover_state, rng).unwrap(); + let fifth_oracles = AHPForR1CS::<_, MM>::prover_fifth_round(verifier_fourth_msg, prover_state, rng).unwrap(); // Get coefficients of final oracle polynomial from round 5. let h_2 = format!("{:?}", fifth_oracles.h_2.coeffs().map(|(_, coeff)| coeff).collect::>()); diff --git a/synthesizer/process/src/finalize.rs b/synthesizer/process/src/finalize.rs index 40edfd5071..974ed1e158 100644 --- a/synthesizer/process/src/finalize.rs +++ b/synthesizer/process/src/finalize.rs @@ -356,7 +356,7 @@ fn initialize_finalize_state<'a, N: Network>( let (finalize, stack) = match stack.program_id() == future.program_id() { true => (stack.get_function_ref(future.function_name())?.finalize_logic(), stack), false => { - let stack = stack.get_external_stack(future.program_id())?; + let stack = stack.get_external_stack(future.program_id())?.as_ref(); (stack.get_function_ref(future.function_name())?.finalize_logic(), stack) } }; diff --git a/synthesizer/process/src/lib.rs b/synthesizer/process/src/lib.rs index 594781f0e7..392574ee2b 100644 --- a/synthesizer/process/src/lib.rs +++ b/synthesizer/process/src/lib.rs @@ -75,7 +75,7 @@ pub struct Process { /// The universal SRS. universal_srs: Arc>, /// The mapping of program IDs to stacks. - stacks: IndexMap, Stack>, + stacks: IndexMap, Arc>>, } impl Process { @@ -129,7 +129,7 @@ impl Process { #[inline] pub fn add_stack(&mut self, stack: Stack) { // Add the stack to the process. - self.stacks.insert(*stack.program_id(), stack); + self.stacks.insert(*stack.program_id(), Arc::new(stack)); } } @@ -202,7 +202,7 @@ impl Process { /// Returns the stack for the given program ID. #[inline] - pub fn get_stack(&self, program_id: impl TryInto>) -> Result<&Stack> { + pub fn get_stack(&self, program_id: impl TryInto>) -> Result<&Arc>> { // Prepare the program ID. let program_id = program_id.try_into().map_err(|_| anyhow!("Invalid program ID"))?; // Retrieve the stack. @@ -216,7 +216,7 @@ impl Process { /// Returns the program for the given program ID. #[inline] pub fn get_program(&self, program_id: impl TryInto>) -> Result<&Program> { - self.get_stack(program_id).map(Stack::program) + Ok(self.get_stack(program_id)?.program()) } /// Returns the proving key for the given program ID and function name. diff --git a/synthesizer/process/src/stack/call/mod.rs b/synthesizer/process/src/stack/call/mod.rs index ff31a73461..b5c3c8442b 100644 --- a/synthesizer/process/src/stack/call/mod.rs +++ b/synthesizer/process/src/stack/call/mod.rs @@ -13,6 +13,7 @@ // limitations under the License. use crate::{CallStack, Registers, RegistersCall, StackEvaluate, StackExecute}; +use aleo_std::prelude::{finish, lap, timer}; use console::{network::prelude::*, program::Request}; use synthesizer_program::{ Call, @@ -57,14 +58,17 @@ impl CallTrait for Call { stack: &(impl StackEvaluate + StackMatches + StackProgram), registers: &mut Registers, ) -> Result<()> { + let timer = timer!("Call::evaluate"); + // Load the operands values. - let inputs: Vec<_> = self.operands().iter().map(|operand| registers.load(stack, operand)).try_collect()?; + let inputs: Vec<_> = + self.operands().iter().map(|operand| registers.load(stack.deref(), operand)).try_collect()?; // Retrieve the substack and resource. let (substack, resource) = match self.operator() { // Retrieve the call stack and resource from the locator. CallOperator::Locator(locator) => { - (stack.get_external_stack(locator.program_id())?.clone(), locator.resource()) + (stack.get_external_stack(locator.program_id())?.as_ref(), locator.resource()) } CallOperator::Resource(resource) => { // TODO (howardwu): Revisit this decision to forbid calling internal functions. A record cannot be spent again. @@ -74,9 +78,10 @@ impl CallTrait for Call { bail!("Cannot call '{resource}'. Use a closure ('closure {resource}:') instead.") } - (stack.clone(), resource) + (stack, resource) } }; + lap!(timer, "Retrieved the substack and resource"); // If the operator is a closure, retrieve the closure and compute the output. let outputs = if let Ok(closure) = substack.program().get_closure(resource) { @@ -111,12 +116,14 @@ impl CallTrait for Call { else { bail!("Call operator '{}' is invalid or unsupported.", self.operator()) }; + lap!(timer, "Computed outputs"); // Assign the outputs to the destination registers. for (output, register) in outputs.into_iter().zip_eq(&self.destinations()) { // Assign the output to the register. registers.store(stack, register, output)?; } + finish!(timer); Ok(()) } @@ -134,6 +141,8 @@ impl CallTrait for Call { ), rng: &mut R, ) -> Result<()> { + let timer = timer!("Call::execute"); + // Load the operands values. let inputs: Vec<_> = self.operands().iter().map(|operand| registers.load_circuit(stack, operand)).try_collect()?; @@ -152,7 +161,7 @@ impl CallTrait for Call { if is_credits_program && (is_fee_private || is_fee_public) { bail!("Cannot perform an external call to 'credits.aleo/fee_private' or 'credits.aleo/fee_public'.") } else { - (stack.get_external_stack(locator.program_id())?.clone(), locator.resource()) + (stack.get_external_stack(locator.program_id())?.as_ref(), locator.resource()) } } CallOperator::Resource(resource) => { @@ -163,12 +172,14 @@ impl CallTrait for Call { bail!("Cannot call '{resource}'. Use a closure ('closure {resource}:') instead.") } - (stack.clone(), resource) + (stack, resource) } }; + lap!(timer, "Retrieve the substack and resource"); // If the operator is a closure, retrieve the closure and compute the output. let outputs = if let Ok(closure) = substack.program().get_closure(resource) { + lap!(timer, "Execute the closure"); // Execute the closure, and load the outputs. substack.execute_closure( &closure, @@ -181,6 +192,7 @@ impl CallTrait for Call { } // If the operator is a function, retrieve the function and compute the output. else if let Ok(function) = substack.program().get_function(resource) { + lap!(timer, "Execute the function"); // Retrieve the number of inputs. let num_inputs = function.inputs().len(); // Ensure the number of inputs matches the number of input statements. @@ -245,8 +257,8 @@ impl CallTrait for Call { // Push the request onto the call stack. call_stack.push(request.clone())?; - // Execute the request. - let response = substack.execute_function::(call_stack, console_caller, rng)?; + // Evaluate the request. + let response = substack.evaluate_function::(call_stack, console_caller)?; // Return the request and response. (request, response) } @@ -281,6 +293,8 @@ impl CallTrait for Call { } } }; + lap!(timer, "Computed the request and response"); + // Inject the existing circuit. A::inject_r1cs(r1cs); @@ -330,6 +344,7 @@ impl CallTrait for Call { None, ); A::assert(check_input_ids); + lap!(timer, "Checked the input ids"); // Inject the outputs as `Mode::Private` (with the 'tcm' and output IDs as `Mode::Public`). let outputs = circuit::Response::process_outputs_from_callback( @@ -342,6 +357,7 @@ impl CallTrait for Call { response.outputs().to_vec(), &function.output_types(), ); + lap!(timer, "Checked the outputs"); // Return the circuit outputs. outputs } @@ -355,6 +371,9 @@ impl CallTrait for Call { // Assign the output to the register. registers.store_circuit(stack, register, output)?; } + lap!(timer, "Assigned the outputs to registers"); + + finish!(timer); Ok(()) } diff --git a/synthesizer/process/src/stack/evaluate.rs b/synthesizer/process/src/stack/evaluate.rs index 4110da8a2c..4de683e245 100644 --- a/synthesizer/process/src/stack/evaluate.rs +++ b/synthesizer/process/src/stack/evaluate.rs @@ -106,6 +106,10 @@ impl StackEvaluate for Stack { // Retrieve the next request, based on the call stack mode. let (request, call_stack) = match &call_stack { CallStack::Evaluate(authorization) => (authorization.next()?, call_stack), + CallStack::CheckDeployment(requests, _, _) | CallStack::PackageRun(requests, _, _) => { + let last_request = requests.last().ok_or(anyhow!("CallStack does not contain request"))?.clone(); + (last_request, call_stack) + } // If the evaluation is performed in the `Execute` mode, create a new `Evaluate` mode. // This is done to ensure that evaluation during execution is performed consistently. CallStack::Execute(authorization, _) => { @@ -116,7 +120,7 @@ impl StackEvaluate for Stack { let call_stack = CallStack::Evaluate(authorization); (request, call_stack) } - _ => bail!("Illegal operation: call stack must be `Evaluate` or `Execute` in `evaluate_function`."), + _ => bail!("Illegal operation: call stack must not be `Synthesize` or `Authorize` in `evaluate_function`."), }; lap!(timer, "Retrieve the next request"); @@ -218,8 +222,6 @@ impl StackEvaluate for Stack { .collect::>>()?; lap!(timer, "Load the outputs"); - finish!(timer); - // Map the output operands to registers. let output_registers = output_operands .iter() @@ -228,9 +230,10 @@ impl StackEvaluate for Stack { _ => None, }) .collect::>(); + lap!(timer, "Loaded the output registers"); // Compute the response. - Response::new( + let response = Response::new( request.network_id(), self.program.id(), function.name(), @@ -240,6 +243,9 @@ impl StackEvaluate for Stack { outputs, &function.output_types(), &output_registers, - ) + ); + finish!(timer); + + response } } diff --git a/synthesizer/process/src/stack/helpers/initialize.rs b/synthesizer/process/src/stack/helpers/initialize.rs index 416bcc6a9a..46b6bd2465 100644 --- a/synthesizer/process/src/stack/helpers/initialize.rs +++ b/synthesizer/process/src/stack/helpers/initialize.rs @@ -58,7 +58,7 @@ impl Stack { impl Stack { /// Inserts the given external stack to the stack. #[inline] - fn insert_external_stack(&mut self, external_stack: Stack) -> Result<()> { + fn insert_external_stack(&mut self, external_stack: Arc>) -> Result<()> { // Retrieve the program ID. let program_id = *external_stack.program_id(); // Ensure the external stack is not already added. diff --git a/synthesizer/process/src/stack/mod.rs b/synthesizer/process/src/stack/mod.rs index 4fe6d18531..2b95b46fb7 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -169,7 +169,7 @@ pub struct Stack { /// The program (record types, structs, functions). program: Program, /// The mapping of external stacks as `(program ID, stack)`. - external_stacks: IndexMap, Stack>, + external_stacks: IndexMap, Arc>>, /// The mapping of closure and function names to their register types. register_types: IndexMap, RegisterTypes>, /// The mapping of finalize names to their register types. @@ -235,7 +235,7 @@ impl StackProgram for Stack { /// Returns the external stack for the given program ID. #[inline] - fn get_external_stack(&self, program_id: &ProgramID) -> Result<&Stack> { + fn get_external_stack(&self, program_id: &ProgramID) -> Result<&Arc>> { // Retrieve the external stack. self.external_stacks.get(program_id).ok_or_else(|| anyhow!("External program '{program_id}' does not exist.")) } diff --git a/synthesizer/program/src/traits/stack_and_registers.rs b/synthesizer/program/src/traits/stack_and_registers.rs index 4a88281c4a..50418e894f 100644 --- a/synthesizer/program/src/traits/stack_and_registers.rs +++ b/synthesizer/program/src/traits/stack_and_registers.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::sync::Arc; + use crate::{FinalizeGlobalState, Function, Operand, Program}; use console::{ network::Network, @@ -65,7 +67,7 @@ pub trait StackProgram { fn contains_external_record(&self, locator: &Locator) -> bool; /// Returns the external stack for the given program ID. - fn get_external_stack(&self, program_id: &ProgramID) -> Result<&Self>; + fn get_external_stack(&self, program_id: &ProgramID) -> Result<&Arc>; /// Returns the external program for the given program ID. fn get_external_program(&self, program_id: &ProgramID) -> Result<&Program>; diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index 237ed5f3cd..a130668240 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -975,4 +975,103 @@ function multitransfer: .unwrap(); vm.add_next_block(&sample_next_block(&vm, &caller_private_key, &[execution], rng).unwrap()).unwrap(); } + + #[test] + #[ignore] + fn test_deployment_memory_overload() { + const NUM_DEPLOYMENTS: usize = 32; + + let rng = &mut TestRng::default(); + + // Initialize a private key. + let private_key = sample_genesis_private_key(rng); + + // Initialize a view key. + let view_key = ViewKey::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 program_layer_0.aleo; + +mapping m: + key as u8.public; + value as u32.public; + +function do: + input r0 as u32.public; + async do r0 into r1; + output r1 as program_layer_0.aleo/do.future; + +finalize do: + input r0 as u32.public; + set r0 into m[0u8];", + ) + .unwrap(); + + let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); + + // For each layer, deploy a program that calls the program from the previous layer. + for i in 1..NUM_DEPLOYMENTS { + let mut program_string = String::new(); + // Add the import statements. + for j in 0..i { + program_string.push_str(&format!("import program_layer_{}.aleo;\n", j)); + } + // Add the program body. + program_string.push_str(&format!( + "program program_layer_{i}.aleo; + +mapping m: + key as u8.public; + value as u32.public; + +function do: + input r0 as u32.public; + call program_layer_{prev}.aleo/do r0 into r1; + async do r0 r1 into r2; + output r2 as program_layer_{i}.aleo/do.future; + +finalize do: + input r0 as u32.public; + input r1 as program_layer_{prev}.aleo/do.future; + await r1; + set r0 into m[0u8];", + prev = i - 1 + )); + // Construct the program. + let program = Program::from_str(&program_string).unwrap(); + + // Deploy the program. + let deployment = vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + + vm.add_next_block(&sample_next_block(&vm, &private_key, &[deployment], rng).unwrap()).unwrap(); + } + + // Fetch the unspent records. + let records = genesis.transitions().cloned().flat_map(Transition::into_records).collect::>(); + trace!("Unspent Records:\n{:#?}", records); + + // Select a record to spend. + let record = Some(records.values().next().unwrap().decrypt(&view_key).unwrap()); + + // Prepare the inputs. + let inputs = [Value::::from_str("1u32").unwrap()].into_iter(); + + // Execute. + let transaction = + vm.execute(&private_key, ("program_layer_30.aleo", "do"), inputs, record, 0, None, rng).unwrap(); + + // Verify. + vm.check_transaction(&transaction, None, rng).unwrap(); + } }