/ synthesizer / process / src / lib.rs
lib.rs
  1  // Copyright (c) 2019-2025 Alpha-Delta Network Inc.
  2  // This file is part of the deltavm 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  
 16  #![forbid(unsafe_code)]
 17  #![allow(clippy::too_many_arguments)]
 18  // #![warn(clippy::cast_possible_truncation)]
 19  // TODO (howardwu): Update the return type on `execute` after stabilizing the interface.
 20  #![allow(clippy::type_complexity)]
 21  
 22  extern crate deltavm_circuit as circuit;
 23  extern crate deltavm_console as console;
 24  
 25  mod cost;
 26  pub use cost::*;
 27  
 28  mod stack;
 29  pub use stack::*;
 30  
 31  mod trace;
 32  pub use trace::*;
 33  
 34  mod authorize;
 35  mod deploy;
 36  mod evaluate;
 37  mod execute;
 38  mod finalize;
 39  mod verify_deployment;
 40  mod verify_execution;
 41  mod verify_fee;
 42  
 43  #[cfg(test)]
 44  mod tests;
 45  
 46  use deltavm_algorithms::snark::varuna::VarunaVersion;
 47  use deltavm_ledger_block::{Deployment, Execution, Fee, Input, Output, Transaction, Transition};
 48  use deltavm_ledger_store::{FinalizeStorage, FinalizeStore, atomic_batch_scope};
 49  use deltavm_synthesizer_program::{
 50      Branch,
 51      Command,
 52      FinalizeGlobalState,
 53      FinalizeOperation,
 54      Instruction,
 55      Program,
 56      StackTrait,
 57  };
 58  use deltavm_synthesizer_snark::{ProvingKey, UniversalSRS, VerifyingKey};
 59  use deltavm_utilities::{defer, dev_println};
 60  use console::{
 61      account::PrivateKey,
 62      network::prelude::*,
 63      program::{
 64          Identifier,
 65          Literal,
 66          Locator,
 67          Plaintext,
 68          ProgramID,
 69          Record,
 70          Request,
 71          Response,
 72          Value,
 73          compute_function_id,
 74      },
 75      types::{Field, U16, U64},
 76  };
 77  
 78  use alphastd::prelude::{finish, lap, timer};
 79  use indexmap::IndexMap;
 80  #[cfg(feature = "locktick")]
 81  use locktick::parking_lot::RwLock;
 82  #[cfg(not(feature = "locktick"))]
 83  use parking_lot::RwLock;
 84  use std::{collections::HashMap, sync::Arc};
 85  
 86  #[derive(Clone)]
 87  pub struct Process<N: Network> {
 88      /// The universal SRS.
 89      universal_srs: UniversalSRS<N>,
 90      /// The mapping of program IDs to stacks.
 91      stacks: Arc<RwLock<IndexMap<ProgramID<N>, Arc<Stack<N>>>>>,
 92      /// The mapping of program IDs to old stacks.
 93      old_stacks: Arc<RwLock<IndexMap<ProgramID<N>, Option<Arc<Stack<N>>>>>>,
 94  }
 95  
 96  impl<N: Network> Process<N> {
 97      /// Initializes a new process.
 98      #[inline]
 99      pub fn setup<A: circuit::Alpha<Network = N>, R: Rng + CryptoRng>(rng: &mut R) -> Result<Self> {
100          let timer = timer!("Process:setup");
101  
102          // Initialize the process.
103          let mut process =
104              Self { universal_srs: UniversalSRS::load()?, stacks: Default::default(), old_stacks: Default::default() };
105          lap!(timer, "Initialize process");
106  
107          // Initialize the 'credits.delta' program.
108          let program = Program::credits()?;
109          lap!(timer, "Load credits program");
110  
111          // Compute the 'credits.delta' program stack.
112          let stack = Stack::new(&process, &program)?;
113          lap!(timer, "Initialize stack");
114  
115          // Synthesize the 'credits.delta' circuit keys.
116          for function_name in program.functions().keys() {
117              stack.synthesize_key::<A, _>(function_name, rng)?;
118              lap!(timer, "Synthesize circuit keys for {function_name}");
119          }
120          lap!(timer, "Synthesize credits program keys");
121  
122          // Add the 'credits.delta' stack to the process.
123          process.add_stack(stack);
124  
125          finish!(timer);
126          // Return the process.
127          Ok(process)
128      }
129  
130      /// Adds a new stack to the process.
131      /// If the program already exists, then the existing stack is replaced and the original stack is returned.
132      /// Note. This method assumes that the provided stack is valid.
133      #[inline]
134      pub fn add_stack(&mut self, stack: Stack<N>) -> Option<Arc<Stack<N>>> {
135          // Get the program ID.
136          let program_id = *stack.program_id();
137          // Arc the stack first to limit the scope of the write lock.
138          let stack = Arc::new(stack);
139          // Insert the stack into the process, replacing the existing stack if it exists.
140          self.stacks.write().insert(program_id, stack)
141      }
142  
143      /// Stages a stack to be added to the process.
144      /// The new stack is active, while the old stack is retained in `old_stacks`.
145      /// The `commit_stacks` method must be called to finalize the addition of the new stack.
146      /// The `revert_stacks` method can be called to revert the staged stacks.
147      #[inline]
148      pub fn stage_stack(&self, stack: Stack<N>) {
149          // Get the program ID.
150          let program_id = *stack.program_id();
151          // Arc the stack first to limit the scope of the write lock.
152          let stack = Arc::new(stack);
153          // If no entry in `old_stacks` exists for `program_id`, store the old stack.
154          // Note: If `old_stack` is `None`, it means that we are adding a new program to the process.
155          let old_stack = self.stacks.write().insert(program_id, stack);
156          let mut old_stacks = self.old_stacks.write();
157          if !old_stacks.contains_key(&program_id) {
158              old_stacks.insert(program_id, old_stack);
159          }
160      }
161  
162      /// Commits the staged stacks to the process.
163      /// This finalizes the addition of the new stacks and clears the old stacks.
164      #[inline]
165      pub fn commit_stacks(&self) {
166          // Clear the old stacks.
167          self.old_stacks.write().clear();
168      }
169  
170      /// Reverts the staged stacks, restoring the previous state of the process.
171      /// This will remove the new stacks and restore the old stacks.
172      #[inline]
173      pub fn revert_stacks(&self) {
174          // Restore the old stacks.
175          for (program_id, stack) in self.old_stacks.write().drain(..) {
176              // If the stack is `None`, remove the program from the process.
177              // Otherwise, insert the old stack back into the process.
178              if let Some(stack) = stack {
179                  self.stacks.write().insert(program_id, stack);
180              } else {
181                  self.stacks.write().shift_remove(&program_id);
182              }
183          }
184      }
185  }
186  
187  impl<N: Network> Process<N> {
188      /// Initializes a new process.
189      #[inline]
190      pub fn load() -> Result<Self> {
191          let timer = timer!("Process::load");
192  
193          // Initialize the process.
194          let mut process =
195              Self { universal_srs: UniversalSRS::load()?, stacks: Default::default(), old_stacks: Default::default() };
196          lap!(timer, "Initialize process");
197  
198          // Initialize the 'credits.delta' program.
199          let program = Program::credits()?;
200          lap!(timer, "Load credits program");
201  
202          // Compute the 'credits.delta' program stack.
203          let stack = Stack::new(&process, &program)?;
204          lap!(timer, "Initialize stack");
205  
206          // Synthesize the 'credits.delta' verifying keys.
207          for function_name in program.functions().keys() {
208              // Load the verifying key.
209              let verifying_key = N::get_credits_verifying_key(function_name.to_string())?;
210              // Retrieve the number of public and private variables.
211              // Note: This number does *NOT* include the number of constants. This is safe because
212              // this program is never deployed, as it is a first-class citizen of the protocol.
213              let num_variables = verifying_key.circuit_info.num_public_and_private_variables as u64;
214              // Insert the verifying key.
215              stack.insert_verifying_key(function_name, VerifyingKey::new(verifying_key.clone(), num_variables))?;
216              lap!(timer, "Load verifying key for {function_name}");
217          }
218          lap!(timer, "Load circuit keys");
219  
220          // Add the stack to the process.
221          process.add_stack(stack);
222  
223          finish!(timer, "Process::load");
224          // Return the process.
225          Ok(process)
226      }
227  
228      /// Initializes a new process with the V0 credits.delta verifiying keys.
229      #[inline]
230      pub fn load_v0() -> Result<Self> {
231          let timer = timer!("Process::load_v0");
232  
233          // Initialize the process.
234          let mut process =
235              Self { universal_srs: UniversalSRS::load()?, stacks: Default::default(), old_stacks: Default::default() };
236          lap!(timer, "Initialize process");
237  
238          // Initialize the 'credits.delta' program.
239          let program = Program::credits()?;
240          lap!(timer, "Load credits program");
241  
242          // Compute the 'credits.delta' program stack.
243          let stack = Stack::new(&process, &program)?;
244          lap!(timer, "Initialize stack");
245  
246          // Synthesize the 'credits.delta' verifying keys.
247          for function_name in program.functions().keys() {
248              // Load the verifying key.
249              let verifying_key = N::get_credits_v0_verifying_key(function_name.to_string())?;
250              // Retrieve the number of public and private variables.
251              // Note: This number does *NOT* include the number of constants. This is safe because
252              // this program is never deployed, as it is a first-class citizen of the protocol.
253              let num_variables = verifying_key.circuit_info.num_public_and_private_variables as u64;
254              // Insert the verifying key.
255              stack.insert_verifying_key(function_name, VerifyingKey::new(verifying_key.clone(), num_variables))?;
256              lap!(timer, "Load verifying key for {function_name}");
257          }
258          lap!(timer, "Load circuit keys");
259  
260          // Add the stack to the process.
261          process.add_stack(stack);
262  
263          finish!(timer, "Process::load_v0");
264          // Return the process.
265          Ok(process)
266      }
267  
268      /// Initializes a new process without downloading the 'credits.delta' circuit keys (for web contexts).
269      #[inline]
270      #[cfg(feature = "wasm")]
271      pub fn load_web() -> Result<Self> {
272          // Initialize the process.
273          let mut process =
274              Self { universal_srs: UniversalSRS::load()?, stacks: Default::default(), old_stacks: Default::default() };
275  
276          // Initialize the 'credits.delta' program.
277          let program = Program::credits()?;
278  
279          // Compute the 'credits.delta' program stack.
280          let stack = Stack::new(&process, &program)?;
281  
282          // Add the stack to the process.
283          process.add_stack(stack);
284  
285          // Return the process.
286          Ok(process)
287      }
288  
289      /// Adds a new program to the process, verifying that it is a valid addition.
290      /// If the program exists, then the existing stack is replaced and discarded.
291      /// Note. This method should **NOT** be used by the on-chain VM to add new program, use `finalize_deployment` or `load_deployment` instead instead.
292      #[inline]
293      pub fn add_program(&mut self, program: &Program<N>) -> Result<()> {
294          // Initialize the 'credits.delta' program ID.
295          let credits_program_id = ProgramID::<N>::from_str("credits.delta")?;
296          // If the program is not 'credits.delta', compute the program stack, and add it to the process.
297          if program.id() != &credits_program_id {
298              self.add_stack(Stack::new(self, program)?);
299          }
300          Ok(())
301      }
302  
303      /// Adds a new program with the given edition to the process, verifying that it is a valid addition.
304      /// If the program exists, then the existing stack is replaced and discarded.
305      /// Note. This method should **NOT** be used by the on-chain VM to add new program, use `finalize_deployment` or `load_deployment` instead instead.
306      #[inline]
307      pub fn add_program_with_edition(&mut self, program: &Program<N>, edition: u16) -> Result<()> {
308          // Initialize the 'credits.delta' program ID.
309          let credits_program_id = ProgramID::<N>::from_str("credits.delta")?;
310          // If the program is not 'credits.delta', compute the program stack, and add it to the process.
311          if program.id() != &credits_program_id {
312              let stack = Stack::new_raw(self, program, edition)?;
313              stack.initialize_and_check(self)?;
314              self.add_stack(stack);
315          }
316          Ok(())
317      }
318  
319      /// Adds a set of programs and editions, in topological order, to the process, deferring validation of the programs until all programs are added.
320      /// If a program exists, then the existing stack is replaced and discarded.
321      /// Either all programs are added or none are.
322      /// Note. This method should **NOT** be used by the on-chain VM to add new program, use `finalize_deployment` or `load_deployment` instead instead.
323      #[inline]
324      pub fn add_programs_with_editions(&mut self, programs: &[(Program<N>, u16)]) -> Result<()> {
325          // Initialize the 'credits.delta' program ID.
326          let credits_program_id = ProgramID::<N>::from_str("credits.delta")?;
327          // Defer cleanup of the uncommitted stacks.
328          defer! {
329              self.revert_stacks()
330          }
331          // Initialize raw stacks for each of the programs, skipping `credits.delta`.
332          for (program, edition) in programs {
333              if program.id() != &credits_program_id {
334                  self.stage_stack(Stack::new_raw(self, program, *edition)?)
335              }
336          }
337          // For each stack, check and initialize it before adding it to the process.
338          for (program, _) in programs {
339              // Retrieve the stack.
340              let stack = self.get_stack(program.id())?;
341              // Initialize and check the stack for well-formedness.
342              stack.initialize_and_check(self)?;
343          }
344          // Commit the staged stacks.
345          self.commit_stacks();
346          Ok(())
347      }
348  
349      /// Returns the universal SRS.
350      #[inline]
351      pub const fn universal_srs(&self) -> &UniversalSRS<N> {
352          &self.universal_srs
353      }
354  
355      /// Returns `true` if the process contains the program with the given ID.
356      #[inline]
357      pub fn contains_program(&self, program_id: &ProgramID<N>) -> bool {
358          self.stacks.read().contains_key(program_id)
359      }
360  
361      /// Returns the program IDs of all programs in the process.
362      #[inline]
363      pub fn program_ids(&self) -> Vec<ProgramID<N>> {
364          self.stacks.read().keys().copied().collect()
365      }
366  
367      /// Returns the stack for the given program ID.
368      #[inline]
369      pub fn get_stack(&self, program_id: impl TryInto<ProgramID<N>>) -> Result<Arc<Stack<N>>> {
370          // Prepare the program ID.
371          let program_id = program_id.try_into().map_err(|_| anyhow!("Invalid program ID"))?;
372          // Retrieve the stack.
373          let stack = self
374              .stacks
375              .read()
376              .get(&program_id)
377              .ok_or_else(|| anyhow!("Program '{program_id}' does not exist"))?
378              .clone();
379          // Ensure the program ID matches.
380          ensure!(stack.program_id() == &program_id, "Expected program '{}', found '{program_id}'", stack.program_id());
381          // Return the stack.
382          Ok(stack)
383      }
384  
385      /// Returns the proving key for the given program ID and function name.
386      #[inline]
387      pub fn get_proving_key(
388          &self,
389          program_id: impl TryInto<ProgramID<N>>,
390          function_name: impl TryInto<Identifier<N>>,
391      ) -> Result<ProvingKey<N>> {
392          // Prepare the function name.
393          let function_name = function_name.try_into().map_err(|_| anyhow!("Invalid function name"))?;
394          // Return the proving key.
395          self.get_stack(program_id)?.get_proving_key(&function_name)
396      }
397  
398      /// Returns the verifying key for the given program ID and function name.
399      #[inline]
400      pub fn get_verifying_key(
401          &self,
402          program_id: impl TryInto<ProgramID<N>>,
403          function_name: impl TryInto<Identifier<N>>,
404      ) -> Result<VerifyingKey<N>> {
405          // Prepare the function name.
406          let function_name = function_name.try_into().map_err(|_| anyhow!("Invalid function name"))?;
407          // Return the verifying key.
408          self.get_stack(program_id)?.get_verifying_key(&function_name)
409      }
410  
411      /// Inserts the given proving key, for the given program ID and function name.
412      #[inline]
413      pub fn insert_proving_key(
414          &self,
415          program_id: &ProgramID<N>,
416          function_name: &Identifier<N>,
417          proving_key: ProvingKey<N>,
418      ) -> Result<()> {
419          self.get_stack(program_id)?.insert_proving_key(function_name, proving_key)
420      }
421  
422      /// Removes the given proving key, for the given program ID and function name.
423      #[inline]
424      pub fn remove_proving_key(&self, program_id: &ProgramID<N>, function_name: &Identifier<N>) -> Result<()> {
425          self.get_stack(program_id)?.remove_proving_key(function_name);
426          Ok(())
427      }
428  
429      /// Inserts the given verifying key, for the given program ID and function name.
430      #[inline]
431      pub fn insert_verifying_key(
432          &self,
433          program_id: &ProgramID<N>,
434          function_name: &Identifier<N>,
435          verifying_key: VerifyingKey<N>,
436      ) -> Result<()> {
437          self.get_stack(program_id)?.insert_verifying_key(function_name, verifying_key)
438      }
439  
440      /// Removes the given verifying key, for the given program ID and function name.
441      #[inline]
442      pub fn remove_verifying_key(&self, program_id: &ProgramID<N>, function_name: &Identifier<N>) -> Result<()> {
443          self.get_stack(program_id)?.remove_verifying_key(function_name);
444          Ok(())
445      }
446  
447      /// Synthesizes the proving and verifying key for the given program ID and function name.
448      #[inline]
449      pub fn synthesize_key<A: circuit::Alpha<Network = N>, R: Rng + CryptoRng>(
450          &self,
451          program_id: &ProgramID<N>,
452          function_name: &Identifier<N>,
453          rng: &mut R,
454      ) -> Result<()> {
455          // Synthesize the proving and verifying key.
456          self.get_stack(program_id)?.synthesize_key::<A, R>(function_name, rng)
457      }
458  }
459  
460  #[cfg(test)]
461  pub mod test_helpers {
462      use super::*;
463      use deltavm_ledger_block::Transition;
464      use deltavm_ledger_query::Query;
465      use deltavm_ledger_store::{BlockStore, helpers::memory::BlockMemory};
466      use deltavm_synthesizer_program::Program;
467      use console::{account::PrivateKey, network::MainnetV0, program::Identifier};
468  
469      use alphastd::StorageMode;
470      use std::sync::OnceLock;
471  
472      type CurrentNetwork = MainnetV0;
473      type CurrentAlpha = circuit::network::AlphaV0;
474  
475      /// Returns an execution for the given program and function name.
476      pub fn get_execution(
477          process: &mut Process<CurrentNetwork>,
478          program: &Program<CurrentNetwork>,
479          function_name: &Identifier<CurrentNetwork>,
480          inputs: impl ExactSizeIterator<Item = impl TryInto<Value<CurrentNetwork>>>,
481      ) -> Execution<CurrentNetwork> {
482          // Initialize a new rng.
483          let rng = &mut TestRng::default();
484  
485          // Initialize a private key.
486          let private_key = PrivateKey::new(rng).unwrap();
487  
488          // Add the program to the process if doesn't yet exist.
489          if !process.contains_program(program.id()) {
490              process.add_program(program).unwrap();
491          }
492  
493          // Compute the authorization.
494          let authorization =
495              process.authorize::<CurrentAlpha, _>(&private_key, program.id(), function_name, inputs, rng).unwrap();
496  
497          // Execute the program.
498          let (_, mut trace) = process.execute::<CurrentAlpha, _>(authorization, rng).unwrap();
499  
500          // Initialize a new block store.
501          let block_store = BlockStore::<CurrentNetwork, BlockMemory<_>>::open(StorageMode::new_test(None)).unwrap();
502  
503          // Prepare the assignments from the block store.
504          trace.prepare(&deltavm_ledger_query::Query::from(block_store)).unwrap();
505  
506          // Get the locator.
507          let locator = format!("{:?}:{function_name:?}", program.id());
508  
509          // Return the execution object.
510          trace.prove_execution::<CurrentAlpha, _>(&locator, VarunaVersion::V1, rng).unwrap()
511      }
512  
513      pub fn sample_key() -> (Identifier<CurrentNetwork>, ProvingKey<CurrentNetwork>, VerifyingKey<CurrentNetwork>) {
514          static INSTANCE: OnceLock<(
515              Identifier<CurrentNetwork>,
516              ProvingKey<CurrentNetwork>,
517              VerifyingKey<CurrentNetwork>,
518          )> = OnceLock::new();
519          INSTANCE
520              .get_or_init(|| {
521                  // Initialize a new program.
522                  let (string, program) = Program::<CurrentNetwork>::parse(
523                      r"
524  program testing.delta;
525  
526  function compute:
527      input r0 as u32.private;
528      input r1 as u32.public;
529      add r0 r1 into r2;
530      output r2 as u32.public;",
531                  )
532                  .unwrap();
533                  assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
534  
535                  // Declare the function name.
536                  let function_name = Identifier::from_str("compute").unwrap();
537  
538                  // Initialize the RNG.
539                  let rng = &mut TestRng::default();
540  
541                  // Construct the process.
542                  let process = sample_process(&program);
543  
544                  // Synthesize a proving and verifying key.
545                  process.synthesize_key::<CurrentAlpha, _>(program.id(), &function_name, rng).unwrap();
546  
547                  // Get the proving and verifying key.
548                  let proving_key = process.get_proving_key(program.id(), function_name).unwrap();
549                  let verifying_key = process.get_verifying_key(program.id(), function_name).unwrap();
550  
551                  (function_name, proving_key, verifying_key)
552              })
553              .clone()
554      }
555  
556      pub(crate) fn sample_execution() -> Execution<CurrentNetwork> {
557          static INSTANCE: OnceLock<Execution<CurrentNetwork>> = OnceLock::new();
558          INSTANCE
559              .get_or_init(|| {
560                  // Initialize a new program.
561                  let (string, program) = Program::<CurrentNetwork>::parse(
562                      r"
563  program testing.delta;
564  
565  function compute:
566      input r0 as u32.private;
567      input r1 as u32.public;
568      add r0 r1 into r2;
569      output r2 as u32.public;",
570                  )
571                  .unwrap();
572                  assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
573  
574                  // Declare the function name.
575                  let function_name = Identifier::from_str("compute").unwrap();
576  
577                  // Initialize the RNG.
578                  let rng = &mut TestRng::default();
579                  // Initialize a new caller account.
580                  let caller_private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
581  
582                  // Initialize a new block store.
583                  let block_store =
584                      BlockStore::<CurrentNetwork, BlockMemory<_>>::open(StorageMode::new_test(None)).unwrap();
585  
586                  // Construct the process.
587                  let process = sample_process(&program);
588                  // Authorize the function call.
589                  let authorization = process
590                      .authorize::<CurrentAlpha, _>(
591                          &caller_private_key,
592                          program.id(),
593                          function_name,
594                          ["5u32", "10u32"].into_iter(),
595                          rng,
596                      )
597                      .unwrap();
598                  assert_eq!(authorization.len(), 1);
599                  // Execute the request.
600                  let (_response, mut trace) = process.execute::<CurrentAlpha, _>(authorization, rng).unwrap();
601                  assert_eq!(trace.transitions().len(), 1);
602  
603                  // Prepare the trace.
604                  trace.prepare(&Query::from(block_store)).unwrap();
605                  // Compute the execution.
606                  trace.prove_execution::<CurrentAlpha, _>("testing", VarunaVersion::V1, rng).unwrap()
607              })
608              .clone()
609      }
610  
611      pub fn sample_transition() -> Transition<CurrentNetwork> {
612          // Retrieve the execution.
613          let mut execution = sample_execution();
614          // Ensure the execution is not empty.
615          assert!(!execution.is_empty());
616          // Return the transition.
617          execution.pop().unwrap()
618      }
619  
620      /// Initializes a new process with the given program.
621      pub(crate) fn sample_process(program: &Program<CurrentNetwork>) -> Process<CurrentNetwork> {
622          // Construct a new process.
623          let mut process = Process::load().unwrap();
624          // Add the program to the process.
625          process.add_program(program).unwrap();
626          // Return the process.
627          process
628      }
629  }