Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1290,6 +1290,7 @@ workflows:
or pipeline.git.branch == "testnet"
or pipeline.git.branch == "mainnet"
or pipeline.git.branch == "copilot/adjust-transaction-cache-key"
or pipeline.git.branch == "fix/scalar-outputs"
Comment thread
vicsn marked this conversation as resolved.
jobs:
- check-unused-dependencies # This can be cleaned up before releases
- check-cargo-semver-checks # This can be cleaned up before releases
Expand Down
6 changes: 6 additions & 0 deletions circuit/program/src/data/future/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,10 @@ impl<A: Aleo> Future<A> {
pub fn inputs(&self) -> &[Argument<A>] {
&self.arguments
}

/// Returns the arguments.
#[inline]
pub fn arguments(&self) -> &[Argument<A>] {
&self.arguments
}
}
4 changes: 3 additions & 1 deletion synthesizer/process/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ impl<N: Network> Process<N> {
// Retrieve the stack.
let stack = self.get_stack(request.program_id())?;
// Execute the circuit.
let response = stack.execute_function::<A, R>(call_stack, caller, root_tvk, rng)?;
let Some(response) = stack.execute_function::<A, R>(call_stack, caller, root_tvk, rng)? else {
return Err(anyhow!("Response should be present in `Execute` mode.").into());
};
lap!(timer, "Execute the function");

// Extract the trace.
Expand Down
21 changes: 15 additions & 6 deletions synthesizer/process/src/stack/call/dynamic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,11 @@ impl<N: Network> CallTrait<N> for CallDynamic<N> {
authorization.push(callee_request.clone())?;

// Execute the callee's request.
let callee_response =
target.substack().execute_function::<A, R>(call_stack, console_caller, root_tvk, rng)?;
let Some(callee_response) =
target.substack().execute_function::<A, R>(call_stack, console_caller, root_tvk, rng)?
else {
return Err(anyhow!("Response should be present in `Authorize` mode.").into());
};

// Convert the callee's outputs to the caller's context.
let caller_response_outputs = callee_response.to_dynamic_outputs()?;
Expand Down Expand Up @@ -467,8 +470,11 @@ impl<N: Network> CallTrait<N> for CallDynamic<N> {
call_stack.push(callee_request.clone())?;

// Evaluate the callee's request.
let callee_response =
target.substack().execute_function::<A, _>(call_stack, console_caller, root_tvk, rng)?;
let Some(callee_response) =
target.substack().execute_function::<A, _>(call_stack, console_caller, root_tvk, rng)?
else {
return Err(anyhow!("Response should be present in `PackageRun` mode.").into());
};

// Convert the callee's outputs to the caller's context.
let caller_response_outputs = callee_response.to_dynamic_outputs()?;
Expand Down Expand Up @@ -511,12 +517,15 @@ impl<N: Network> CallTrait<N> for CallDynamic<N> {
rng,
)?;
// Execute the request.
let callee_response = target.substack().execute_function::<A, R>(
let Some(callee_response) = target.substack().execute_function::<A, R>(
registers.call_stack(),
console_caller,
root_tvk,
rng,
)?;
)?
else {
return Err(anyhow!("Response should be present in `Execute` mode.").into());
};

// Ensure the values are equal.
if console_callee_response.outputs() != callee_response.outputs() {
Expand Down
130 changes: 68 additions & 62 deletions synthesizer/process/src/stack/call/standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,11 +328,18 @@ impl<N: Network> CallTrait<N> for Call<N> {
// Push the request onto the call stack.
call_stack.push(request.clone())?;

// Execute the request.
// Synthesize the circuit. Note the response will be empty.
let response = substack.execute_function::<A, R>(call_stack, console_caller, root_tvk, rng)?;

if response.is_some() {
return Err(anyhow!(
"execute_function should return an empty Response in Synthesize mode."
)
.into());
}

// Return the request and response.
(request, response)
(request, None)
}
// In Synthesize mode (with an existing proving key) or CheckDeployment mode, we generate dummy outputs to avoid building a full sub-circuit.
CallStack::Synthesize(_, private_key, ..) | CallStack::CheckDeployment(_, private_key, ..) => {
Expand All @@ -350,63 +357,8 @@ impl<N: Network> CallTrait<N> for Call<N> {
rng,
)?;

// Compute the address.
let address = Address::try_from(private_key)?;

// For each output, if it's a record, compute the randomizer and nonce.
let outputs = function
.outputs()
.iter()
.map(|output| match output.value_type() {
ValueType::Record(record_name) => {
let index = match output.operand() {
Operand::Register(Register::Locator(index)) => Field::from_u64(*index),
_ => {
return Err(anyhow!(
"Expected a `Register::Locator` operand for a record output."
));
}
};
// Sample the record.
Ok(Value::Record(substack.sample_record_using_tvk(
&address,
record_name,
*request.tvk(),
index,
rng,
)?))
}
// For non-record outputs, call sample_value.
_ => substack.sample_value(&address, &output.value_type().into(), rng),
})
.collect::<Result<Vec<_>>>()?;

// Construct the dummy response from these outputs.
let output_registers = function
.outputs()
.iter()
.map(|output| match output.operand() {
Operand::Register(register) => Some(register.clone()),
_ => None,
})
.collect::<Vec<_>>();

// Execute the request.
let response = crate::Response::new(
request.signer(),
request.network_id(),
substack.program().id(),
function.name(),
request.inputs().len(),
request.tvk(),
request.tcm(),
outputs,
&function.output_types(),
&output_registers,
)?;

// Return the request and response.
(request, response)
(request, None)
}
// In PackageRun mode, we sign and execute the request once.
CallStack::PackageRun(_, private_key, ..) => {
Expand Down Expand Up @@ -465,8 +417,12 @@ impl<N: Network> CallTrait<N> for Call<N> {
rng,
)?;
// Execute the request.
let response =
substack.execute_function::<A, R>(registers.call_stack(), console_caller, root_tvk, rng)?;
let Some(response) =
substack.execute_function::<A, R>(registers.call_stack(), console_caller, root_tvk, rng)?
else {
return Err(anyhow!("Response should be present in `Execute` mode.").into());
};

// Ensure the values are equal.
if console_response.outputs() != response.outputs() {
dev_eprintln!("\n{:#?} != {:#?}\n", console_response.outputs(), response.outputs());
Expand All @@ -477,7 +433,7 @@ impl<N: Network> CallTrait<N> for Call<N> {
.into());
}
// Return the request and response.
(request, response)
(request, Some(response))
}
}
};
Expand Down Expand Up @@ -547,6 +503,56 @@ impl<N: Network> CallTrait<N> for Call<N> {
})
.collect::<Vec<_>>();

let outputs = match registers.call_stack_ref() {
// In Synthesize and CheckDeployment modes, `response` is None at this point. Only its outputs
// are used in the remainder of this function, and only their types (and not specific values)
// are relevant, so we sample them instead.
CallStack::Synthesize(_, private_key, ..) | CallStack::CheckDeployment(_, private_key, ..) => {
if response.is_some() {
return Err(anyhow!("Response should be empty in Synthesize and CheckDeployment modes.").into());
}

let address = Address::try_from(private_key)?;

function
.outputs()
.iter()
.map(|output| match output.value_type() {
ValueType::Record(record_name) => {
let index = match output.operand() {
Operand::Register(Register::Locator(index)) => Field::from_u64(*index),
_ => {
return Err(anyhow!(
"Expected a `Register::Locator` operand for a record output."
));
}
};
// Sample the record.
Ok(Value::Record(substack.sample_record_using_tvk(
&address,
record_name,
*request.tvk(),
index,
rng,
)?))
}
// For non-record outputs, call sample_value.
_ => substack.sample_value(&address, &output.value_type().into(), rng),
})
.collect::<Result<Vec<_>>>()?
}
_ => {
if let Some(response) = response {
response.outputs().to_vec()
} else {
return Err(anyhow!(
"Response should be populated in all modes except Synthesize and CheckDeployment."
)
.into());
}
}
};

// Inject the outputs as `Mode::Private` (with the 'tcm' and output IDs as `Mode::Public`).
let outputs = circuit::Response::process_outputs_from_callback(
&network_id,
Expand All @@ -555,7 +561,7 @@ impl<N: Network> CallTrait<N> for Call<N> {
num_inputs,
&tvk,
&tcm,
response.outputs().to_vec(),
outputs,
&function.output_types(),
&output_registers,
None,
Expand Down
48 changes: 34 additions & 14 deletions synthesizer/process/src/stack/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ impl<N: Network> Stack<N> {
outputs
}

/// Executes a program function on the given inputs.
/// Executes a program function on the given inputs. The output includes a `Response` object if
/// not in `CheckDeployment` or `Synthesize` mode.
///
/// Note: To execute a transition, do **not** call this method. Instead, call `Process::execute`.
///
Expand All @@ -196,7 +197,7 @@ impl<N: Network> Stack<N> {
console_caller: Option<ProgramID<N>>,
console_root_tvk: Option<Field<N>>,
rng: &mut R,
) -> Result<Response<N>, StackExecError> {
) -> Result<Option<Response<N>>, StackExecError> {
let timer = timer!("Stack::execute_function");

// Ensure the global constants for the Aleo environment are initialized.
Expand Down Expand Up @@ -541,17 +542,26 @@ impl<N: Network> Stack<N> {
Self::log_circuit::<A>("Complete", &call_stack_type);

// Eject the response.
let response = response.eject_value();
let console_response =
if matches!(registers.call_stack_ref(), CallStack::Synthesize(..) | CallStack::CheckDeployment(..)) {
// When synthesizing proving/verifying keys or checking the latter,
// the values in the Response object are not relevant, and neither is its console counterpart.
None
} else {
let console_response = response.eject_value();

if console_response.outputs().len() != output_types.len() {
return Err(anyhow!("Number of outputs does not match number of output types").into());
}

if response.outputs().len() != output_types.len() {
return Err(anyhow!("Number of outputs does not match number of output types").into());
}
// Ensure the outputs matches the expected value types.
console_response.outputs().iter().zip_eq(&output_types).try_for_each(|(output, output_type)| {
// Ensure the output matches its expected type.
self.matches_value_type(output, output_type)
})?;

// Ensure the outputs matches the expected value types.
response.outputs().iter().zip_eq(&output_types).try_for_each(|(output, output_type)| {
// Ensure the output matches its expected type.
self.matches_value_type(output, output_type)
})?;
Some(console_response)
};

// If the circuit is in `Execute` or `PackageRun` mode, then ensure the circuit is satisfied.
if matches!(registers.call_stack_ref(), CallStack::Execute(..) | CallStack::PackageRun(..)) {
Expand Down Expand Up @@ -582,7 +592,12 @@ impl<N: Network> Stack<N> {
// If the circuit is in `Authorize` mode, then save the transition.
if let CallStack::Authorize(_, _, authorization) = registers.call_stack_ref() {
// Construct the transition.
let transition = Transition::from(&console_request, &response, &output_types, &output_registers)?;
let transition = Transition::from(
&console_request,
console_response.as_ref().unwrap(),

@mohammadfawaz mohammadfawaz Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe justify why this unwrap is safe (an expect maybe)

&output_types,
&output_registers,
)?;

// Add the transition to the authorization.
authorization.insert_transition(transition)?;
Expand All @@ -608,7 +623,12 @@ impl<N: Network> Stack<N> {
registers.ensure_console_and_circuit_registers_match()?;

// Construct the transition.
let transition = Transition::from(&console_request, &response, &output_types, &output_registers)?;
let transition = Transition::from(
&console_request,
console_response.as_ref().unwrap(),

@mohammadfawaz mohammadfawaz Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. expect instead of unwrap?

&output_types,
&output_registers,
)?;

// Retrieve the proving key.
let proving_key = self.get_proving_key(function.name())?;
Expand Down Expand Up @@ -655,7 +675,7 @@ impl<N: Network> Stack<N> {
finish!(timer);

// Return the response.
Ok(response)
Ok(console_response)
}
}

Expand Down
2 changes: 1 addition & 1 deletion synthesizer/process/src/stack/registers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ mod registers_trait;
use crate::{CallStack, RegisterTypes};
use console::{
network::prelude::*,
program::{Entry, Literal, Plaintext, Register, Request, Value},
program::{Entry, Literal, Plaintext, PlaintextType, Register, RegisterType, Request, Value},

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are used in the child module registers_circuit.rs, which imports super::*, which is why clippy was happy. I've now moved the two imports to the child module anyway.

types::{Address, Field},
};
use snarkvm_synthesizer_program::{Operand, RegistersCircuit, RegistersSigner, RegistersTrait, StackTrait};
Expand Down
Loading