From 209864d88767932f9f3191a5523239d3b5542721 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Thu, 22 Feb 2024 11:05:51 -0800 Subject: [PATCH 1/6] Fix calculation of finalize cost --- synthesizer/process/src/cost.rs | 39 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/synthesizer/process/src/cost.rs b/synthesizer/process/src/cost.rs index d30bc504f5..13b006b6e0 100644 --- a/synthesizer/process/src/cost.rs +++ b/synthesizer/process/src/cost.rs @@ -60,21 +60,11 @@ pub fn execution_cost(process: &Process, execution: &Execution // Compute the storage cost in microcredits. let storage_cost = execution.size_in_bytes()?; - // Compute the finalize cost in microcredits. - let mut finalize_cost = 0u64; - // Iterate over the transitions to accumulate the finalize cost. - for transition in execution.transitions() { - // Retrieve the program ID and function name. - let (program_id, function_name) = (transition.program_id(), transition.function_name()); - // Retrieve the finalize cost. - let cost = cost_in_microcredits(process.get_stack(program_id)?, function_name)?; - // Accumulate the finalize cost. - if cost > 0 { - finalize_cost = finalize_cost - .checked_add(cost) - .ok_or(anyhow!("The finalize cost computation overflowed on '{program_id}/{function_name}'"))?; - } - } + // Get the root transition. + let transition = execution.peek()?; + + // Get the finalize cost for the root transition. + let finalize_cost = process.get_stack(transition.program_id())?.get_finalize_cost(transition.function_name())?; // Compute the total cost in microcredits. let total_cost = storage_cost @@ -370,10 +360,19 @@ pub fn cost_in_microcredits(stack: &Stack, function_name: &Identi Command::Position(_) => Ok(100), }; + // Get the cost of finalizing all futures. + let mut future_cost = 0u64; + for input in finalize.inputs() { + if let FinalizeType::Future(future) = input.finalize_type() { + // Get the external stack for the future. + let stack = stack.get_external_stack(future.program_id())?; + // Accumulate the finalize cost of the future. + future_cost += cost_in_microcredits(stack, future.resource())?; + } + } + // Aggregate the cost of all commands in the program. - finalize - .commands() - .iter() - .map(cost) - .try_fold(0u64, |acc, res| res.and_then(|x| acc.checked_add(x).ok_or(anyhow!("Finalize cost overflowed")))) + finalize.commands().iter().map(cost).try_fold(future_cost, |acc, res| { + res.and_then(|x| acc.checked_add(x).ok_or(anyhow!("Finalize cost overflowed"))) + }) } From a734470e58da8ed690e871137455eb014b43eec7 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Thu, 22 Feb 2024 11:06:30 -0800 Subject: [PATCH 2/6] Cache finalize cost in Stack --- synthesizer/process/src/stack/helpers/initialize.rs | 5 +++++ synthesizer/process/src/stack/mod.rs | 13 ++++++++++++- .../program/src/traits/stack_and_registers.rs | 3 +++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/synthesizer/process/src/stack/helpers/initialize.rs b/synthesizer/process/src/stack/helpers/initialize.rs index 98e008ba3b..5b3f6219e9 100644 --- a/synthesizer/process/src/stack/helpers/initialize.rs +++ b/synthesizer/process/src/stack/helpers/initialize.rs @@ -28,6 +28,7 @@ impl Stack { proving_keys: Default::default(), verifying_keys: Default::default(), number_of_calls: Default::default(), + finalize_costs: Default::default(), program_depth: 0, }; @@ -82,6 +83,10 @@ impl Stack { ); // Add the number of calls to the stack. stack.number_of_calls.insert(*function.name(), num_calls); + + // Add the finalize cost to the stack. + let finalize_cost = cost_in_microcredits(&stack, function.name())?; + stack.finalize_costs.insert(*function.name(), finalize_cost); } // Return the stack. diff --git a/synthesizer/process/src/stack/mod.rs b/synthesizer/process/src/stack/mod.rs index 6e10f4946c..aa0f7af67b 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -36,7 +36,7 @@ mod evaluate; mod execute; mod helpers; -use crate::{traits::*, CallMetrics, Process, Trace}; +use crate::{cost_in_microcredits, traits::*, CallMetrics, Process, Trace}; use console::{ account::{Address, PrivateKey}, network::prelude::*, @@ -187,6 +187,8 @@ pub struct Stack { verifying_keys: Arc, VerifyingKey>>>, /// The mapping of function names to the number of calls. number_of_calls: IndexMap, usize>, + /// The mapping of function names to finalize cost. + finalize_costs: IndexMap, u64>, /// The program depth. program_depth: usize, } @@ -274,6 +276,15 @@ impl StackProgram for Stack { external_program.get_record(locator.resource()) } + /// Returns the expected finalize cost for the given function name. + #[inline] + fn get_finalize_cost(&self, function_name: &Identifier) -> Result { + self.finalize_costs + .get(function_name) + .copied() + .ok_or_else(|| anyhow!("Function '{function_name}' does not exist")) + } + /// Returns the function with the given function name. #[inline] fn get_function(&self, function_name: &Identifier) -> Result> { diff --git a/synthesizer/program/src/traits/stack_and_registers.rs b/synthesizer/program/src/traits/stack_and_registers.rs index 1b79996aeb..214ea1eee6 100644 --- a/synthesizer/program/src/traits/stack_and_registers.rs +++ b/synthesizer/program/src/traits/stack_and_registers.rs @@ -80,6 +80,9 @@ pub trait StackProgram { /// Returns `true` if the stack contains the external record. fn get_external_record(&self, locator: &Locator) -> Result<&RecordType>; + /// Returns the expected finalize cost for the given function name. + fn get_finalize_cost(&self, function_name: &Identifier) -> Result; + /// Returns the function with the given function name. fn get_function(&self, function_name: &Identifier) -> Result>; From dfcf20873a4db71fd8dbf7259ed0493e1020fb86 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Thu, 22 Feb 2024 12:53:19 -0800 Subject: [PATCH 3/6] Remove redundant check --- synthesizer/process/src/cost.rs | 2 +- synthesizer/process/src/stack/helpers/initialize.rs | 9 ++++++++- synthesizer/process/src/verify_deployment.rs | 10 ---------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/synthesizer/process/src/cost.rs b/synthesizer/process/src/cost.rs index 13b006b6e0..e3dc471068 100644 --- a/synthesizer/process/src/cost.rs +++ b/synthesizer/process/src/cost.rs @@ -367,7 +367,7 @@ pub fn cost_in_microcredits(stack: &Stack, function_name: &Identi // Get the external stack for the future. let stack = stack.get_external_stack(future.program_id())?; // Accumulate the finalize cost of the future. - future_cost += cost_in_microcredits(stack, future.resource())?; + future_cost += stack.get_finalize_cost(future.resource())?; } } diff --git a/synthesizer/process/src/stack/helpers/initialize.rs b/synthesizer/process/src/stack/helpers/initialize.rs index 5b3f6219e9..ea1c35f370 100644 --- a/synthesizer/process/src/stack/helpers/initialize.rs +++ b/synthesizer/process/src/stack/helpers/initialize.rs @@ -84,8 +84,15 @@ impl Stack { // Add the number of calls to the stack. stack.number_of_calls.insert(*function.name(), num_calls); - // Add the finalize cost to the stack. + // Get the finalize cost. let finalize_cost = cost_in_microcredits(&stack, function.name())?; + // Check that the finalize cost does not exceed the maximum. + ensure!( + finalize_cost <= N::TRANSACTION_SPEND_LIMIT, + "Finalize block '{}' has a cost '{finalize_cost}' which exceeds the transaction spend limit '{}'", + function.name(), + N::TRANSACTION_SPEND_LIMIT + ); stack.finalize_costs.insert(*function.name(), finalize_cost); } diff --git a/synthesizer/process/src/verify_deployment.rs b/synthesizer/process/src/verify_deployment.rs index 843bc5340b..f499fdebcc 100644 --- a/synthesizer/process/src/verify_deployment.rs +++ b/synthesizer/process/src/verify_deployment.rs @@ -33,16 +33,6 @@ impl Process { let stack = Stack::new(self, deployment.program())?; lap!(timer, "Compute the stack"); - // Ensure that each finalize block does not exceed the `TRANSACTION_SPEND_LIMIT`. - for (function_name, _) in deployment.program().functions() { - let finalize_cost = cost_in_microcredits(&stack, function_name)?; - ensure!( - finalize_cost <= N::TRANSACTION_SPEND_LIMIT, - "Finalize block '{function_name}' has a cost '{finalize_cost}' which exceeds the transaction spend limit '{}'", - N::TRANSACTION_SPEND_LIMIT - ); - } - // Ensure the verifying keys are well-formed and the certificates are valid. let verification = stack.verify_deployment::(deployment, rng); lap!(timer, "Verify the deployment"); From a8bccbca788e62a8b0151fead61baf3e8ffeb42a Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Thu, 22 Feb 2024 18:44:05 -0800 Subject: [PATCH 4/6] Fix tests --- ledger/src/tests.rs | 20 +++++++------------- synthesizer/process/src/cost.rs | 4 +++- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/ledger/src/tests.rs b/ledger/src/tests.rs index 8446e47e48..e995da9438 100644 --- a/ledger/src/tests.rs +++ b/ledger/src/tests.rs @@ -1525,15 +1525,9 @@ fn test_deployment_exceeding_max_transaction_spend() { )) .unwrap(); - // Initialize a stack for the program. - let stack = Stack::::new(&ledger.vm().process().read(), &program).unwrap(); - - // Check the finalize cost. - let finalize_cost = cost_in_microcredits(&stack, &Identifier::from_str("foo").unwrap()).unwrap(); - - // If the finalize cost exceeds the maximum transaction spend, assign the program to the exceeding program and break. - // Otherwise, assign the program to the allowed program and continue. - if finalize_cost > ::TRANSACTION_SPEND_LIMIT { + // Attempt to initialize a `Stack` for the program. + // If this fails, then by `Stack::initialize` the finalize cost exceeds the `TRANSACTION_SPEND_LIMIT`. + if Stack::::new(&ledger.vm().process().read(), &program).is_err() { exceeding_program = Some(program); break; } else { @@ -1567,9 +1561,9 @@ fn test_deployment_exceeding_max_transaction_spend() { // Check that the program exists in the VM. assert!(ledger.vm().contains_program(allowed_program.id())); - // Deploy the exceeding program. - let deployment = ledger.vm().deploy(&private_key, &exceeding_program, None, 0, None, rng).unwrap(); + // Attempt to deploy the exceeding program. + let result = ledger.vm().deploy(&private_key, &exceeding_program, None, 0, None, rng); - // Verify the deployment transaction. - assert!(ledger.vm().check_transaction(&deployment, None, rng).is_err()); + // Check that the deployment failed. + assert!(result.is_err()); } diff --git a/synthesizer/process/src/cost.rs b/synthesizer/process/src/cost.rs index e3dc471068..ff9776db29 100644 --- a/synthesizer/process/src/cost.rs +++ b/synthesizer/process/src/cost.rs @@ -367,7 +367,9 @@ pub fn cost_in_microcredits(stack: &Stack, function_name: &Identi // Get the external stack for the future. let stack = stack.get_external_stack(future.program_id())?; // Accumulate the finalize cost of the future. - future_cost += stack.get_finalize_cost(future.resource())?; + future_cost = future_cost + .checked_add(stack.get_finalize_cost(future.resource())?) + .ok_or(anyhow!("Finalize cost overflowed"))?; } } From 985452231660eae2964ddd9f7ebfd21179067cbb Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Thu, 22 Feb 2024 18:44:43 -0800 Subject: [PATCH 5/6] Cleanup --- ledger/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger/src/tests.rs b/ledger/src/tests.rs index e995da9438..3ede876f82 100644 --- a/ledger/src/tests.rs +++ b/ledger/src/tests.rs @@ -28,7 +28,7 @@ use indexmap::IndexMap; use ledger_block::{ConfirmedTransaction, Rejected, Transaction}; use ledger_committee::{Committee, MIN_VALIDATOR_STAKE}; use ledger_store::{helpers::memory::ConsensusMemory, ConsensusStore}; -use synthesizer::{prelude::cost_in_microcredits, program::Program, vm::VM, Stack}; +use synthesizer::{program::Program, vm::VM, Stack}; #[test] fn test_load() { From 2375daa51d4938b2b242b779f652ef594522dd24 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Sat, 2 Mar 2024 17:43:44 -0800 Subject: [PATCH 6/6] Fmt --- synthesizer/process/src/tests/test_execute.rs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/synthesizer/process/src/tests/test_execute.rs b/synthesizer/process/src/tests/test_execute.rs index 8ba8de479b..676e6d7a65 100644 --- a/synthesizer/process/src/tests/test_execute.rs +++ b/synthesizer/process/src/tests/test_execute.rs @@ -16,6 +16,7 @@ use crate::{ traits::{StackEvaluate, StackExecute}, CallStack, Process, + Stack, Trace, }; use circuit::{network::AleoV0, Aleo}; @@ -2611,3 +2612,32 @@ fn test_max_imports() { )); assert!(result.is_err()); } + +#[test] +fn test_program_exceeding_transaction_spend_limit() { + // Construct a finalize body whose finalize cost is excessively large. + let finalize_body = (0..::MAX_COMMANDS) + .map(|i| format!("hash.bhp256 0field into r{i} as field;")) + .collect::>() + .join("\n"); + // Construct the program. + let program = Program::from_str(&format!( + r"program test_max_spend_limit.aleo; + function foo: + async foo into r0; + output r0 as test_max_spend_limit.aleo/foo.future; + finalize foo:{finalize_body}", + )) + .unwrap(); + + // Initialize a `Process`. + let mut process = Process::::load().unwrap(); + + // Attempt to add the program to the process, which should fail. + let result = process.add_program(&program); + assert!(result.is_err()); + + // Attempt to initialize a `Stack` directly with the program, which should fail. + let result = Stack::initialize(&process, &program); + assert!(result.is_err()); +}