snarkvm_synthesizer_process/stack/
execute.rs

1// Copyright (c) 2019-2025 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use super::*;
17
18impl<N: Network> Stack<N> {
19    /// Executes a program closure on the given inputs.
20    ///
21    /// # Errors
22    /// This method will halt if the given inputs are not the same length as the input statements.
23    pub fn execute_closure<A: circuit::Aleo<Network = N>>(
24        &self,
25        closure: &Closure<N>,
26        inputs: &[circuit::Value<A>],
27        call_stack: CallStack<N>,
28        signer: circuit::Address<A>,
29        caller: circuit::Address<A>,
30        tvk: circuit::Field<A>,
31    ) -> Result<Vec<circuit::Value<A>>> {
32        let timer = timer!("Stack::execute_closure");
33
34        // Ensure the call stack is not `Evaluate`.
35        ensure!(!matches!(call_stack, CallStack::Evaluate(..)), "Illegal operation: cannot evaluate in execute mode");
36
37        // Ensure the number of inputs matches the number of input statements.
38        if closure.inputs().len() != inputs.len() {
39            bail!("Expected {} inputs, found {}", closure.inputs().len(), inputs.len())
40        }
41        lap!(timer, "Check the number of inputs");
42
43        // Retrieve the number of public variables in the circuit.
44        let num_public = A::num_public();
45
46        // Initialize the registers.
47        let mut registers = Registers::new(call_stack, self.get_register_types(closure.name())?.clone());
48        // Set the transition signer, as a circuit.
49        registers.set_signer_circuit(signer);
50        // Set the transition caller, as a circuit.
51        registers.set_caller_circuit(caller);
52        // Set the transition view key, as a circuit.
53        registers.set_tvk_circuit(tvk);
54        lap!(timer, "Initialize the registers");
55
56        // Store the inputs.
57        closure.inputs().iter().map(|i| i.register()).zip_eq(inputs).try_for_each(|(register, input)| {
58            // If the circuit is in execute mode, then store the console input.
59            if let CallStack::Execute(..) = registers.call_stack_ref() {
60                use circuit::Eject;
61                // Assign the console input to the register.
62                registers.store(self, register, input.eject_value())?;
63            }
64            // Assign the circuit input to the register.
65            registers.store_circuit(self, register, input.clone())
66        })?;
67        lap!(timer, "Store the inputs");
68
69        // Execute the instructions.
70        for instruction in closure.instructions() {
71            // If the circuit is in execute mode, then evaluate the instructions.
72            if let CallStack::Execute(..) = registers.call_stack_ref() {
73                // If the evaluation fails, bail and return the error.
74                if let Err(error) = instruction.evaluate(self, &mut registers) {
75                    bail!("Failed to evaluate instruction ({instruction}): {error}");
76                }
77            }
78            // Execute the instruction.
79            instruction.execute(self, &mut registers)?;
80        }
81        lap!(timer, "Execute the instructions");
82
83        // Ensure the number of public variables remains the same.
84        ensure!(A::num_public() == num_public, "Illegal closure operation: instructions injected public variables");
85
86        use circuit::Inject;
87
88        // Load the outputs.
89        let outputs = closure
90            .outputs()
91            .iter()
92            .map(|output| {
93                match output.operand() {
94                    // If the operand is a literal, use the literal directly.
95                    Operand::Literal(literal) => Ok(circuit::Value::Plaintext(circuit::Plaintext::from(
96                        circuit::Literal::new(circuit::Mode::Constant, literal.clone()),
97                    ))),
98                    // If the operand is a register, retrieve the stack value from the register.
99                    Operand::Register(register) => registers.load_circuit(self, &Operand::Register(register.clone())),
100                    // If the operand is the program ID, convert the program ID into an address.
101                    Operand::ProgramID(program_id) => {
102                        Ok(circuit::Value::Plaintext(circuit::Plaintext::from(circuit::Literal::Address(
103                            circuit::Address::new(circuit::Mode::Constant, program_id.to_address()?),
104                        ))))
105                    }
106                    // If the operand is the signer, retrieve the signer from the registers.
107                    Operand::Signer => Ok(circuit::Value::Plaintext(circuit::Plaintext::from(
108                        circuit::Literal::Address(registers.signer_circuit()?),
109                    ))),
110                    // If the operand is the caller, retrieve the caller from the registers.
111                    Operand::Caller => Ok(circuit::Value::Plaintext(circuit::Plaintext::from(
112                        circuit::Literal::Address(registers.caller_circuit()?),
113                    ))),
114                    // If the operand is the block height, throw an error.
115                    Operand::BlockHeight => {
116                        bail!("Illegal operation: cannot retrieve the block height in a closure scope")
117                    }
118                    // If the operand is the block timestamp, throw an error.
119                    Operand::BlockTimestamp => {
120                        bail!("Illegal operation: cannot retrieve the block timestamp in a closure scope")
121                    }
122                    // If the operand is the network id, throw an error.
123                    Operand::NetworkID => {
124                        bail!("Illegal operation: cannot retrieve the network id in a closure scope")
125                    }
126                    // If the operand is the checksum, throw an error.
127                    Operand::Checksum(_) => bail!("Illegal operation: cannot retrieve the checksum in a closure scope"),
128                    // If the operand is the edition, throw an error.
129                    Operand::Edition(_) => bail!("Illegal operation: cannot retrieve the edition in a closure scope"),
130                    // If the operand is the program owner, throw an error.
131                    Operand::ProgramOwner(_) => {
132                        bail!("Illegal operation: cannot retrieve the program owner in a closure scope")
133                    }
134                }
135            })
136            .collect();
137        lap!(timer, "Load the outputs");
138
139        finish!(timer);
140        outputs
141    }
142
143    /// Executes a program function on the given inputs.
144    ///
145    /// Note: To execute a transition, do **not** call this method. Instead, call `Process::execute`.
146    ///
147    /// # Errors
148    /// This method will halt if the given inputs are not the same length as the input statements.
149    pub fn execute_function<A: circuit::Aleo<Network = N>, R: CryptoRng + Rng>(
150        &self,
151        mut call_stack: CallStack<N>,
152        console_caller: Option<ProgramID<N>>,
153        console_root_tvk: Option<Field<N>>,
154        rng: &mut R,
155    ) -> Result<Response<N>> {
156        let timer = timer!("Stack::execute_function");
157
158        // Ensure the global constants for the Aleo environment are initialized.
159        A::initialize_global_constants();
160        // Ensure the circuit environment is clean.
161        A::reset();
162
163        // If in 'CheckDeployment' mode, set the constraint limit and variable limit.
164        // We do not have to reset it after function calls because `CheckDeployment` mode does not execute those.
165        if let CallStack::CheckDeployment(_, _, _, constraint_limit, variable_limit) = &call_stack {
166            A::set_constraint_limit(*constraint_limit);
167            A::set_variable_limit(*variable_limit);
168        }
169
170        // Retrieve the next request.
171        let console_request = call_stack.pop()?;
172
173        // Ensure the network ID matches.
174        ensure!(
175            **console_request.network_id() == N::ID,
176            "Network ID mismatch. Expected {}, but found {}",
177            N::ID,
178            console_request.network_id()
179        );
180
181        // We can only have a root_tvk if this request was called by another request
182        ensure!(console_caller.is_some() == console_root_tvk.is_some());
183        // Determine if this is the top-level caller.
184        let console_is_root = console_caller.is_none();
185
186        // Determine the parent.
187        //  - If this execution is the top-level caller, then the parent is the program ID.
188        //  - If this execution is a child caller, then the parent is the caller.
189        let console_parent = match console_caller {
190            // If this execution is the top-level caller, then the parent is the program ID.
191            None => console_request.program_id().to_address()?,
192            // If this execution is a child caller, then the parent is the caller.
193            Some(console_caller) => console_caller.to_address()?,
194        };
195
196        // Retrieve the function from the program.
197        let function = self.get_function(console_request.function_name())?;
198        // Retrieve the number of inputs.
199        let num_inputs = function.inputs().len();
200        // Ensure the number of inputs matches the number of input statements.
201        if num_inputs != console_request.inputs().len() {
202            bail!("Expected {num_inputs} inputs, found {}", console_request.inputs().len())
203        }
204        // Retrieve the input types.
205        let input_types = function.input_types();
206        // Retrieve the output types.
207        let output_types = function.output_types();
208        lap!(timer, "Retrieve the input and output types");
209
210        // Ensure the inputs match their expected types.
211        console_request.inputs().iter().zip_eq(&input_types).try_for_each(|(input, input_type)| {
212            // Ensure the input matches the input type in the function.
213            self.matches_value_type(input, input_type)
214        })?;
215        lap!(timer, "Verify the input types");
216
217        // Retrieve the program checksum, if the program has a constructor.
218        let program_checksum = match self.program().contains_constructor() {
219            true => Some(self.program_checksum_as_field()?),
220            false => None,
221        };
222
223        // Ensure the request is well-formed.
224        ensure!(
225            console_request.verify(&input_types, console_is_root, program_checksum),
226            "[Execute] Request is invalid"
227        );
228        lap!(timer, "Verify the console request");
229
230        // Initialize the registers.
231        let mut registers = Registers::new(call_stack, self.get_register_types(function.name())?.clone());
232
233        // Set the root tvk, from a parent request or the current request.
234        let console_root_tvk = console_root_tvk.unwrap_or(*console_request.tvk());
235        // Inject the `root_tvk` as `Mode::Private`.
236        let root_tvk = circuit::Field::<A>::new(circuit::Mode::Private, console_root_tvk);
237        // Set the root tvk.
238        registers.set_root_tvk(console_root_tvk);
239        // Set the root tvk, as a circuit.
240        registers.set_root_tvk_circuit(root_tvk.clone());
241
242        // If a program checksum was passed in, Inject it as `Mode::Public`.
243        let program_checksum = program_checksum.map(|c| circuit::Field::<A>::new(circuit::Mode::Public, c));
244
245        use circuit::{Eject, Inject};
246
247        // Inject the transition public key `tpk` as `Mode::Public`.
248        let tpk = circuit::Group::<A>::new(circuit::Mode::Public, console_request.to_tpk());
249        // Inject the request as `Mode::Private`.
250        let request = circuit::Request::new(circuit::Mode::Private, console_request.clone());
251
252        // Inject `is_root` as `Mode::Public`.
253        let is_root = circuit::Boolean::new(circuit::Mode::Public, console_is_root);
254        // Inject the parent as `Mode::Public`.
255        let parent = circuit::Address::new(circuit::Mode::Public, console_parent);
256        // Determine the caller.
257        let caller = Ternary::ternary(&is_root, request.signer(), &parent);
258
259        // Ensure the request has a valid signature, inputs, and transition view key.
260        A::assert(request.verify(&input_types, &tpk, Some(root_tvk), is_root, program_checksum));
261        lap!(timer, "Verify the circuit request");
262
263        // Set the transition signer.
264        registers.set_signer(*console_request.signer());
265        // Set the transition signer, as a circuit.
266        registers.set_signer_circuit(request.signer().clone());
267
268        // Set the transition caller.
269        registers.set_caller(caller.eject_value());
270        // Set the transition caller, as a circuit.
271        registers.set_caller_circuit(caller);
272
273        // Set the transition view key.
274        registers.set_tvk(*console_request.tvk());
275        // Set the transition view key, as a circuit.
276        registers.set_tvk_circuit(request.tvk().clone());
277
278        lap!(timer, "Initialize the registers");
279
280        Self::log_circuit::<A>("Request");
281
282        // Retrieve the number of constraints for verifying the request in the circuit.
283        let num_request_constraints = A::num_constraints();
284
285        // Retrieve the number of public variables in the circuit.
286        let num_public = A::num_public();
287
288        // Store the inputs.
289        function.inputs().iter().map(|i| i.register()).zip_eq(request.inputs()).try_for_each(|(register, input)| {
290            // If the circuit is in execute mode, then store the console input.
291            if let CallStack::Execute(..) = registers.call_stack_ref() {
292                // Assign the console input to the register.
293                registers.store(self, register, input.eject_value())?;
294            }
295            // Assign the circuit input to the register.
296            registers.store_circuit(self, register, input.clone())
297        })?;
298        lap!(timer, "Store the inputs");
299
300        // Initialize a tracker to determine if there are any function calls.
301        let mut contains_function_call = false;
302
303        // Execute the instructions.
304        for instruction in function.instructions() {
305            // If the circuit is in execute mode, then evaluate the instructions.
306            if let CallStack::Execute(..) = registers.call_stack_ref() {
307                // Evaluate the instruction.
308                let result = match instruction {
309                    // If the instruction is a `call` instruction, we need to handle it separately.
310                    Instruction::Call(call) => CallTrait::evaluate(call, self, &mut registers, rng),
311                    // Otherwise, evaluate the instruction normally.
312                    _ => instruction.evaluate(self, &mut registers),
313                };
314                // If the evaluation fails, bail and return the error.
315                if let Err(error) = result {
316                    bail!("Failed to evaluate instruction ({instruction}): {error}");
317                }
318            }
319
320            // Execute the instruction.
321            let result = match instruction {
322                // If the instruction is a `call` instruction, we need to handle it separately.
323                Instruction::Call(call) => CallTrait::execute(call, self, &mut registers, rng),
324                // Otherwise, execute the instruction normally.
325                _ => instruction.execute(self, &mut registers),
326            };
327            // If the execution fails, bail and return the error.
328            if let Err(error) = result {
329                bail!("Failed to execute instruction ({instruction}): {error}");
330            }
331
332            // If the instruction was a function call, then set the tracker to `true`.
333            if let Instruction::Call(call) = instruction {
334                // Check if the call is a function call.
335                if call.is_function_call(self)? {
336                    contains_function_call = true;
337                }
338            }
339        }
340        lap!(timer, "Execute the instructions");
341
342        // Load the outputs.
343        let output_operands = &function.outputs().iter().map(|output| output.operand()).collect::<Vec<_>>();
344        let outputs = output_operands
345            .iter()
346            .map(|operand| {
347                match operand {
348                    // If the operand is a literal, use the literal directly.
349                    Operand::Literal(literal) => Ok(circuit::Value::Plaintext(circuit::Plaintext::from(
350                        circuit::Literal::new(circuit::Mode::Constant, literal.clone()),
351                    ))),
352                    // If the operand is a register, retrieve the stack value from the register.
353                    Operand::Register(register) => registers.load_circuit(self, &Operand::Register(register.clone())),
354                    // If the operand is the program ID, convert the program ID into an address.
355                    Operand::ProgramID(program_id) => {
356                        Ok(circuit::Value::Plaintext(circuit::Plaintext::from(circuit::Literal::Address(
357                            circuit::Address::new(circuit::Mode::Constant, program_id.to_address()?),
358                        ))))
359                    }
360                    // If the operand is the signer, retrieve the signer from the registers.
361                    Operand::Signer => Ok(circuit::Value::Plaintext(circuit::Plaintext::from(
362                        circuit::Literal::Address(registers.signer_circuit()?),
363                    ))),
364                    // If the operand is the caller, retrieve the caller from the registers.
365                    Operand::Caller => Ok(circuit::Value::Plaintext(circuit::Plaintext::from(
366                        circuit::Literal::Address(registers.caller_circuit()?),
367                    ))),
368                    // If the operand is the block height, throw an error.
369                    Operand::BlockHeight => {
370                        bail!("Illegal operation: cannot retrieve the block height in a function scope")
371                    }
372                    // If the operand is the block timestamp, throw an error.
373                    Operand::BlockTimestamp => {
374                        bail!("Illegal operation: cannot retrieve the block timestamp in a function scope")
375                    }
376                    // If the operand is the network id, throw an error.
377                    Operand::NetworkID => {
378                        bail!("Illegal operation: cannot retrieve the network id in a function scope")
379                    }
380                    // If the operand is the checksum, throw an error.
381                    Operand::Checksum(_) => {
382                        bail!("Illegal operation: cannot retrieve the checksum in a function scope")
383                    }
384                    // If the operand is the edition, throw an error.
385                    Operand::Edition(_) => {
386                        bail!("Illegal operation: cannot retrieve the edition in a function scope")
387                    }
388                    // If the operand is the program owner, throw an error.
389                    Operand::ProgramOwner(_) => {
390                        bail!("Illegal operation: cannot retrieve the program owner in a function scope")
391                    }
392                }
393            })
394            .collect::<Result<Vec<_>>>()?;
395        lap!(timer, "Load the outputs");
396
397        // Map the output operands into registers.
398        let output_registers = output_operands
399            .iter()
400            .map(|operand| match operand {
401                Operand::Register(register) => Some(register.clone()),
402                _ => None,
403            })
404            .collect::<Vec<_>>();
405
406        Self::log_circuit::<A>(format!("Function '{}()'", function.name()));
407
408        // Retrieve the number of constraints for executing the function in the circuit.
409        let num_function_constraints = A::num_constraints().saturating_sub(num_request_constraints);
410
411        // If the function does not contain function calls, ensure no new public variables were injected.
412        if !contains_function_call {
413            // Ensure the number of public variables remains the same.
414            ensure!(A::num_public() == num_public, "Instructions in function injected public variables");
415        }
416
417        // Construct the response.
418        let response = circuit::Response::from_outputs(
419            request.signer(),
420            request.network_id(),
421            request.program_id(),
422            request.function_name(),
423            num_inputs,
424            request.tvk(),
425            request.tcm(),
426            outputs,
427            &output_types,
428            &output_registers,
429        );
430        lap!(timer, "Construct the response");
431
432        Self::log_circuit::<A>("Response");
433
434        // Retrieve the number of constraints for verifying the response in the circuit.
435        let num_response_constraints =
436            A::num_constraints().saturating_sub(num_request_constraints).saturating_sub(num_function_constraints);
437
438        Self::log_circuit::<A>("Complete");
439
440        // Eject the response.
441        let response = response.eject_value();
442
443        // Ensure the outputs matches the expected value types.
444        response.outputs().iter().zip_eq(&output_types).try_for_each(|(output, output_type)| {
445            // Ensure the output matches its expected type.
446            self.matches_value_type(output, output_type)
447        })?;
448
449        // If the circuit is in `Execute` or `PackageRun` mode, then ensure the circuit is satisfied.
450        if matches!(registers.call_stack_ref(), CallStack::Execute(..) | CallStack::PackageRun(..)) {
451            // If the circuit is empty or not satisfied, then throw an error.
452            ensure!(
453                A::num_constraints() > 0 && A::is_satisfied(),
454                "'{}/{}' is not satisfied on the given inputs ({} constraints).",
455                self.program.id(),
456                function.name(),
457                A::num_constraints()
458            );
459        }
460
461        // Eject the circuit assignment and reset the circuit.
462        let assignment = A::eject_assignment_and_reset();
463
464        // If the circuit is in `Synthesize` or `Execute` mode, synthesize the circuit key, if it does not exist.
465        if matches!(registers.call_stack_ref(), CallStack::Synthesize(..) | CallStack::Execute(..)) {
466            // If the proving key does not exist, then synthesize it.
467            if !self.contains_proving_key(function.name()) {
468                // Add the circuit key to the mapping.
469                self.synthesize_from_assignment(function.name(), &assignment)?;
470                lap!(timer, "Synthesize the {} circuit key", function.name());
471            }
472        }
473        // If the circuit is in `Authorize` mode, then save the transition.
474        if let CallStack::Authorize(_, _, authorization) = registers.call_stack_ref() {
475            // Construct the transition.
476            let transition = Transition::from(&console_request, &response, &output_types, &output_registers)?;
477            // Add the transition to the authorization.
478            authorization.insert_transition(transition)?;
479            lap!(timer, "Save the transition");
480        }
481        // If the circuit is in `CheckDeployment` mode, then save the assignment.
482        else if let CallStack::CheckDeployment(_, _, assignments, _, _) = registers.call_stack_ref() {
483            // Construct the call metrics.
484            let metrics = CallMetrics {
485                program_id: *self.program_id(),
486                function_name: *function.name(),
487                num_instructions: function.instructions().len(),
488                num_request_constraints,
489                num_function_constraints,
490                num_response_constraints,
491            };
492            // Add the assignment to the assignments.
493            assignments.write().push((assignment, metrics));
494            lap!(timer, "Save the circuit assignment");
495        }
496        // If the circuit is in `Execute` mode, then execute the circuit into a transition.
497        else if let CallStack::Execute(_, trace) = registers.call_stack_ref() {
498            registers.ensure_console_and_circuit_registers_match()?;
499
500            // Construct the transition.
501            let transition = Transition::from(&console_request, &response, &output_types, &output_registers)?;
502
503            // Retrieve the proving key.
504            let proving_key = self.get_proving_key(function.name())?;
505            // Construct the call metrics.
506            let metrics = CallMetrics {
507                program_id: *self.program_id(),
508                function_name: *function.name(),
509                num_instructions: function.instructions().len(),
510                num_request_constraints,
511                num_function_constraints,
512                num_response_constraints,
513            };
514
515            // Add the transition to the trace.
516            trace.write().insert_transition(
517                console_request.input_ids(),
518                &transition,
519                (proving_key, assignment),
520                metrics,
521            )?;
522        }
523        // If the circuit is in `PackageRun` mode, then save the assignment.
524        else if let CallStack::PackageRun(_, _, assignments) = registers.call_stack_ref() {
525            // Construct the call metrics.
526            let metrics = CallMetrics {
527                program_id: *self.program_id(),
528                function_name: *function.name(),
529                num_instructions: function.instructions().len(),
530                num_request_constraints,
531                num_function_constraints,
532                num_response_constraints,
533            };
534            // Add the assignment to the assignments.
535            assignments.write().push((assignment, metrics));
536            lap!(timer, "Save the circuit assignment");
537        }
538
539        finish!(timer);
540
541        // Return the response.
542        Ok(response)
543    }
544}
545
546impl<N: Network> Stack<N> {
547    /// Prints the current state of the circuit.
548    #[allow(unused_variables)]
549    pub(crate) fn log_circuit<A: circuit::Aleo<Network = N>>(scope: impl std::fmt::Display) {
550        #[cfg(debug_assertions)]
551        {
552            use snarkvm_utilities::dev_println;
553
554            use colored::Colorize as _;
555
556            // Determine if the circuit is satisfied.
557            let is_satisfied = if A::is_satisfied() { "✅" } else { "❌" };
558            // Determine the count.
559            let (num_constant, num_public, num_private, num_constraints, num_nonzeros) = A::count();
560
561            let scope = scope.to_string().bold();
562
563            // Print the log.
564            dev_println!(
565                "{is_satisfied} {scope:width$} (Constant: {num_constant}, Public: {num_public}, Private: {num_private}, Constraints: {num_constraints}, NonZeros: {num_nonzeros:?})",
566                width = 20
567            );
568        }
569    }
570}