Skip to content

Commit

Permalink
Merge pull request #2176 from AleoHQ/fix/test-deploy-overload
Browse files Browse the repository at this point in the history
Reduce overhead of programs with deep nested imports
  • Loading branch information
howardwu authored Nov 24, 2023
2 parents 710b12d + 1a9c58e commit 802491c
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 23 deletions.
3 changes: 1 addition & 2 deletions algorithms/src/snark/varuna/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Vec<_>>());
Expand Down
2 changes: 1 addition & 1 deletion synthesizer/process/src/finalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
};
Expand Down
8 changes: 4 additions & 4 deletions synthesizer/process/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ pub struct Process<N: Network> {
/// The universal SRS.
universal_srs: Arc<UniversalSRS<N>>,
/// The mapping of program IDs to stacks.
stacks: IndexMap<ProgramID<N>, Stack<N>>,
stacks: IndexMap<ProgramID<N>, Arc<Stack<N>>>,
}

impl<N: Network> Process<N> {
Expand Down Expand Up @@ -129,7 +129,7 @@ impl<N: Network> Process<N> {
#[inline]
pub fn add_stack(&mut self, stack: Stack<N>) {
// Add the stack to the process.
self.stacks.insert(*stack.program_id(), stack);
self.stacks.insert(*stack.program_id(), Arc::new(stack));
}
}

Expand Down Expand Up @@ -202,7 +202,7 @@ impl<N: Network> Process<N> {

/// Returns the stack for the given program ID.
#[inline]
pub fn get_stack(&self, program_id: impl TryInto<ProgramID<N>>) -> Result<&Stack<N>> {
pub fn get_stack(&self, program_id: impl TryInto<ProgramID<N>>) -> Result<&Arc<Stack<N>>> {
// Prepare the program ID.
let program_id = program_id.try_into().map_err(|_| anyhow!("Invalid program ID"))?;
// Retrieve the stack.
Expand All @@ -216,7 +216,7 @@ impl<N: Network> Process<N> {
/// Returns the program for the given program ID.
#[inline]
pub fn get_program(&self, program_id: impl TryInto<ProgramID<N>>) -> Result<&Program<N>> {
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.
Expand Down
33 changes: 26 additions & 7 deletions synthesizer/process/src/stack/call/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -57,14 +58,17 @@ impl<N: Network> CallTrait<N> for Call<N> {
stack: &(impl StackEvaluate<N> + StackMatches<N> + StackProgram<N>),
registers: &mut Registers<N, A>,
) -> 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.
Expand All @@ -74,9 +78,10 @@ impl<N: Network> CallTrait<N> for Call<N> {
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) {
Expand Down Expand Up @@ -111,12 +116,14 @@ impl<N: Network> CallTrait<N> for Call<N> {
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(())
}
Expand All @@ -134,6 +141,8 @@ impl<N: Network> CallTrait<N> for Call<N> {
),
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()?;
Expand All @@ -152,7 +161,7 @@ impl<N: Network> CallTrait<N> for Call<N> {
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) => {
Expand All @@ -163,12 +172,14 @@ impl<N: Network> CallTrait<N> for Call<N> {
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,
Expand All @@ -181,6 +192,7 @@ impl<N: Network> CallTrait<N> for Call<N> {
}
// 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.
Expand Down Expand Up @@ -245,8 +257,8 @@ impl<N: Network> CallTrait<N> for Call<N> {
// Push the request onto the call stack.
call_stack.push(request.clone())?;

// Execute the request.
let response = substack.execute_function::<A, R>(call_stack, console_caller, rng)?;
// Evaluate the request.
let response = substack.evaluate_function::<A>(call_stack, console_caller)?;
// Return the request and response.
(request, response)
}
Expand Down Expand Up @@ -281,6 +293,8 @@ impl<N: Network> CallTrait<N> for Call<N> {
}
}
};
lap!(timer, "Computed the request and response");

// Inject the existing circuit.
A::inject_r1cs(r1cs);

Expand Down Expand Up @@ -330,6 +344,7 @@ impl<N: Network> CallTrait<N> for Call<N> {
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(
Expand All @@ -342,6 +357,7 @@ impl<N: Network> CallTrait<N> for Call<N> {
response.outputs().to_vec(),
&function.output_types(),
);
lap!(timer, "Checked the outputs");
// Return the circuit outputs.
outputs
}
Expand All @@ -355,6 +371,9 @@ impl<N: Network> CallTrait<N> for Call<N> {
// Assign the output to the register.
registers.store_circuit(stack, register, output)?;
}
lap!(timer, "Assigned the outputs to registers");

finish!(timer);

Ok(())
}
Expand Down
16 changes: 11 additions & 5 deletions synthesizer/process/src/stack/evaluate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ impl<N: Network> StackEvaluate<N> for Stack<N> {
// 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, _) => {
Expand All @@ -116,7 +120,7 @@ impl<N: Network> StackEvaluate<N> for Stack<N> {
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");

Expand Down Expand Up @@ -218,8 +222,6 @@ impl<N: Network> StackEvaluate<N> for Stack<N> {
.collect::<Result<Vec<_>>>()?;
lap!(timer, "Load the outputs");

finish!(timer);

// Map the output operands to registers.
let output_registers = output_operands
.iter()
Expand All @@ -228,9 +230,10 @@ impl<N: Network> StackEvaluate<N> for Stack<N> {
_ => None,
})
.collect::<Vec<_>>();
lap!(timer, "Loaded the output registers");

// Compute the response.
Response::new(
let response = Response::new(
request.network_id(),
self.program.id(),
function.name(),
Expand All @@ -240,6 +243,9 @@ impl<N: Network> StackEvaluate<N> for Stack<N> {
outputs,
&function.output_types(),
&output_registers,
)
);
finish!(timer);

response
}
}
2 changes: 1 addition & 1 deletion synthesizer/process/src/stack/helpers/initialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ impl<N: Network> Stack<N> {
impl<N: Network> Stack<N> {
/// Inserts the given external stack to the stack.
#[inline]
fn insert_external_stack(&mut self, external_stack: Stack<N>) -> Result<()> {
fn insert_external_stack(&mut self, external_stack: Arc<Stack<N>>) -> Result<()> {
// Retrieve the program ID.
let program_id = *external_stack.program_id();
// Ensure the external stack is not already added.
Expand Down
4 changes: 2 additions & 2 deletions synthesizer/process/src/stack/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ pub struct Stack<N: Network> {
/// The program (record types, structs, functions).
program: Program<N>,
/// The mapping of external stacks as `(program ID, stack)`.
external_stacks: IndexMap<ProgramID<N>, Stack<N>>,
external_stacks: IndexMap<ProgramID<N>, Arc<Stack<N>>>,
/// The mapping of closure and function names to their register types.
register_types: IndexMap<Identifier<N>, RegisterTypes<N>>,
/// The mapping of finalize names to their register types.
Expand Down Expand Up @@ -235,7 +235,7 @@ impl<N: Network> StackProgram<N> for Stack<N> {

/// Returns the external stack for the given program ID.
#[inline]
fn get_external_stack(&self, program_id: &ProgramID<N>) -> Result<&Stack<N>> {
fn get_external_stack(&self, program_id: &ProgramID<N>) -> Result<&Arc<Stack<N>>> {
// Retrieve the external stack.
self.external_stacks.get(program_id).ok_or_else(|| anyhow!("External program '{program_id}' does not exist."))
}
Expand Down
4 changes: 3 additions & 1 deletion synthesizer/program/src/traits/stack_and_registers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -65,7 +67,7 @@ pub trait StackProgram<N: Network> {
fn contains_external_record(&self, locator: &Locator<N>) -> bool;

/// Returns the external stack for the given program ID.
fn get_external_stack(&self, program_id: &ProgramID<N>) -> Result<&Self>;
fn get_external_stack(&self, program_id: &ProgramID<N>) -> Result<&Arc<Self>>;

/// Returns the external program for the given program ID.
fn get_external_program(&self, program_id: &ProgramID<N>) -> Result<&Program<N>>;
Expand Down
99 changes: 99 additions & 0 deletions synthesizer/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<IndexMap<_, _>>();
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::<CurrentNetwork>::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();
}
}

0 comments on commit 802491c

Please sign in to comment.