snarkvm_synthesizer_process/trace/
mod.rs1mod call_metrics;
17pub use call_metrics::*;
18
19mod inclusion;
20pub use inclusion::*;
21
22use algorithms::snark::varuna::VarunaVersion;
23use circuit::Assignment;
24use console::{
25 network::prelude::*,
26 program::{InputID, Locator},
27};
28use ledger_block::{Execution, Fee, Transition};
29use ledger_query::QueryTrait;
30use synthesizer_snark::{Proof, ProvingKey, VerifyingKey};
31
32use once_cell::sync::OnceCell;
33use std::collections::HashMap;
34
35#[derive(Clone, Debug, Default)]
36pub struct Trace<N: Network> {
37 transitions: Vec<Transition<N>>,
39 transition_tasks: HashMap<Locator<N>, (ProvingKey<N>, Vec<Assignment<N::Field>>)>,
41 inclusion_tasks: Inclusion<N>,
43 call_metrics: Vec<CallMetrics<N>>,
45
46 inclusion_assignments: OnceCell<Vec<InclusionAssignment<N>>>,
48 global_state_root: OnceCell<N::StateRoot>,
50}
51
52impl<N: Network> Trace<N> {
53 pub fn new() -> Self {
55 Self {
56 transitions: Vec::new(),
57 transition_tasks: HashMap::new(),
58 inclusion_tasks: Inclusion::new(),
59 inclusion_assignments: OnceCell::new(),
60 global_state_root: OnceCell::new(),
61 call_metrics: Vec::new(),
62 }
63 }
64
65 pub fn transitions(&self) -> &[Transition<N>] {
67 &self.transitions
68 }
69
70 pub fn call_metrics(&self) -> &[CallMetrics<N>] {
72 &self.call_metrics
73 }
74}
75
76impl<N: Network> Trace<N> {
77 pub fn insert_transition(
79 &mut self,
80 input_ids: &[InputID<N>],
81 transition: &Transition<N>,
82 (proving_key, assignment): (ProvingKey<N>, Assignment<N::Field>),
83 metrics: CallMetrics<N>,
84 ) -> Result<()> {
85 ensure!(self.inclusion_assignments.get().is_none());
87 ensure!(self.global_state_root.get().is_none());
88
89 self.inclusion_tasks.insert_transition(input_ids, transition)?;
91
92 let locator = Locator::new(*transition.program_id(), *transition.function_name());
94 self.transition_tasks.entry(locator).or_insert((proving_key, vec![])).1.push(assignment);
96 self.transitions.push(transition.clone());
98 self.call_metrics.push(metrics);
100
101 Ok(())
102 }
103}
104
105impl<N: Network> Trace<N> {
106 pub fn is_fee(&self) -> bool {
108 self.is_fee_private() || self.is_fee_public()
109 }
110
111 pub fn is_fee_private(&self) -> bool {
113 self.transitions.len() == 1 && self.transitions[0].is_fee_private()
115 }
116
117 pub fn is_fee_public(&self) -> bool {
119 self.transitions.len() == 1 && self.transitions[0].is_fee_public()
121 }
122}
123
124impl<N: Network> Trace<N> {
125 pub fn prepare(&mut self, query: impl QueryTrait<N>) -> Result<()> {
127 let (inclusion_assignments, global_state_root) = self.inclusion_tasks.prepare(&self.transitions, query)?;
129 self.inclusion_assignments
131 .set(inclusion_assignments)
132 .map_err(|_| anyhow!("Failed to set inclusion assignments"))?;
133 self.global_state_root.set(global_state_root).map_err(|_| anyhow!("Failed to set global state root"))?;
134 Ok(())
135 }
136
137 #[cfg(feature = "async")]
139 pub async fn prepare_async(&mut self, query: impl QueryTrait<N>) -> Result<()> {
140 let (inclusion_assignments, global_state_root) =
142 self.inclusion_tasks.prepare_async(&self.transitions, query).await?;
143 self.inclusion_assignments
145 .set(inclusion_assignments)
146 .map_err(|_| anyhow!("Failed to set inclusion assignments"))?;
147 self.global_state_root.set(global_state_root).map_err(|_| anyhow!("Failed to set global state root"))?;
148 Ok(())
149 }
150
151 pub fn prove_execution<A: circuit::Aleo<Network = N>, R: Rng + CryptoRng>(
153 &self,
154 locator: &str,
155 varuna_version: VarunaVersion,
156 rng: &mut R,
157 ) -> Result<Execution<N>> {
158 ensure!(!self.is_fee(), "The trace cannot call 'prove_execution' for a fee type");
160 ensure!(
162 self.transitions.iter().all(|transition| !(transition.is_fee_private() || transition.is_fee_public())),
163 "The trace cannot prove execution for a fee, call 'prove_fee' instead"
164 );
165 let inclusion_assignments =
167 self.inclusion_assignments.get().ok_or_else(|| anyhow!("Inclusion assignments have not been set"))?;
168 let global_state_root =
170 self.global_state_root.get().ok_or_else(|| anyhow!("Global state root has not been set"))?;
171 let proving_tasks = self.transition_tasks.values().cloned().collect();
173 let (global_state_root, proof) = Self::prove_batch::<A, R>(
175 locator,
176 varuna_version,
177 proving_tasks,
178 inclusion_assignments,
179 *global_state_root,
180 rng,
181 )?;
182 Execution::from(self.transitions.iter().cloned(), global_state_root, Some(proof))
184 }
185
186 pub fn prove_fee<A: circuit::Aleo<Network = N>, R: Rng + CryptoRng>(
188 &self,
189 varuna_version: VarunaVersion,
190 rng: &mut R,
191 ) -> Result<Fee<N>> {
192 let is_fee_public = self.is_fee_public();
194 let is_fee_private = self.is_fee_private();
195 ensure!(is_fee_public || is_fee_private, "The trace cannot call 'prove_fee' for an execution type");
196 let inclusion_assignments =
198 self.inclusion_assignments.get().ok_or_else(|| anyhow!("Inclusion assignments have not been set"))?;
199 match is_fee_public {
201 true => ensure!(inclusion_assignments.is_empty(), "Expected 0 inclusion assignments for proving the fee"),
202 false => ensure!(inclusion_assignments.len() == 1, "Expected 1 inclusion assignment for proving the fee"),
203 }
204 let global_state_root =
206 self.global_state_root.get().ok_or_else(|| anyhow!("Global state root has not been set"))?;
207 let fee_transition = &self.transitions[0];
209 let proving_tasks = self.transition_tasks.values().cloned().collect();
211 let (global_state_root, proof) = Self::prove_batch::<A, R>(
213 "credits.aleo/fee (private or public)",
214 varuna_version,
215 proving_tasks,
216 inclusion_assignments,
217 *global_state_root,
218 rng,
219 )?;
220 Ok(Fee::from_unchecked(fee_transition.clone(), global_state_root, Some(proof)))
222 }
223
224 pub fn verify_execution_proof(
227 locator: &str,
228 varuna_version: VarunaVersion,
229 verifier_inputs: Vec<(VerifyingKey<N>, Vec<Vec<N::Field>>)>,
230 execution: &Execution<N>,
231 ) -> Result<()> {
232 let global_state_root = execution.global_state_root();
234 if global_state_root == N::StateRoot::default() {
236 bail!("Inclusion expected the global state root in the execution to *not* be zero")
237 }
238 let Some(proof) = execution.proof() else { bail!("Expected the execution to contain a proof") };
240 match Self::verify_batch(
242 locator,
243 varuna_version,
244 verifier_inputs,
245 global_state_root,
246 execution.transitions(),
247 proof,
248 ) {
249 Ok(()) => Ok(()),
250 Err(e) => bail!("Execution is invalid - {e}"),
251 }
252 }
253
254 pub fn verify_fee_proof(
257 varuna_version: VarunaVersion,
258 verifier_inputs: (VerifyingKey<N>, Vec<Vec<N::Field>>),
259 fee: &Fee<N>,
260 ) -> Result<()> {
261 let global_state_root = fee.global_state_root();
263 if global_state_root == N::StateRoot::default() {
265 bail!("Inclusion expected the global state root in the fee to *not* be zero")
266 }
267 let Some(proof) = fee.proof() else { bail!("Expected the fee to contain a proof") };
269 match Self::verify_batch(
271 "credits.aleo/fee (private or public)",
272 varuna_version,
273 vec![verifier_inputs],
274 global_state_root,
275 [fee.transition()].into_iter(),
276 proof,
277 ) {
278 Ok(()) => Ok(()),
279 Err(e) => bail!("Fee is invalid - {e}"),
280 }
281 }
282}
283
284impl<N: Network> Trace<N> {
285 fn prove_batch<A: circuit::Aleo<Network = N>, R: Rng + CryptoRng>(
287 locator: &str,
288 varuna_version: VarunaVersion,
289 mut proving_tasks: Vec<(ProvingKey<N>, Vec<Assignment<N::Field>>)>,
290 inclusion_assignments: &[InclusionAssignment<N>],
291 global_state_root: N::StateRoot,
292 rng: &mut R,
293 ) -> Result<(N::StateRoot, Proof<N>)> {
294 if global_state_root == N::StateRoot::default() {
298 bail!("Inclusion expected the global state root in the execution to *not* be zero")
299 }
300
301 let mut batch_inclusions = Vec::with_capacity(inclusion_assignments.len());
303
304 for assignment in inclusion_assignments.iter() {
305 if global_state_root != assignment.state_path.global_state_root() {
307 bail!("Inclusion expected the global state root to be the same across iterations")
308 }
309 batch_inclusions.push(assignment.to_circuit_assignment::<A>()?);
311 }
312
313 if !batch_inclusions.is_empty() {
314 let proving_key = ProvingKey::<N>::new(N::inclusion_proving_key().clone());
316 proving_tasks.push((proving_key, batch_inclusions));
318 }
319
320 let proof = ProvingKey::prove_batch(locator, varuna_version, &proving_tasks, rng)?;
322 Ok((global_state_root, proof))
324 }
325
326 fn verify_batch<'a>(
329 locator: &str,
330 varuna_version: VarunaVersion,
331 mut verifier_inputs: Vec<(VerifyingKey<N>, Vec<Vec<N::Field>>)>,
332 global_state_root: N::StateRoot,
333 transitions: impl ExactSizeIterator<Item = &'a Transition<N>>,
334 proof: &Proof<N>,
335 ) -> Result<()> {
336 let batch_inclusion_inputs = Inclusion::prepare_verifier_inputs(global_state_root, transitions)?;
338 if !batch_inclusion_inputs.is_empty() {
340 let verifying_key = N::inclusion_verifying_key().clone();
342 let num_variables = verifying_key.circuit_info.num_public_and_private_variables as u64;
346 verifier_inputs.push((VerifyingKey::<N>::new(verifying_key, num_variables), batch_inclusion_inputs));
348 }
349 VerifyingKey::verify_batch(locator, varuna_version, verifier_inputs, proof)
351 .map_err(|e| anyhow!("Failed to verify proof - {e}"))
352 }
353}