1use super::*;
17use console::program::{FinalizeType, Future, Register};
18use snarkvm_synthesizer_program::{Await, FinalizeRegistersState, Operand, RegistersTrait};
19use snarkvm_utilities::try_vm_runtime;
20
21use std::collections::HashSet;
22
23impl<N: Network> Process<N> {
24 #[inline]
28 pub fn finalize_deployment<P: FinalizeStorage<N>>(
29 &self,
30 state: FinalizeGlobalState,
31 store: &FinalizeStore<N, P>,
32 deployment: &Deployment<N>,
33 fee: &Fee<N>,
34 ) -> Result<(Stack<N>, Vec<FinalizeOperation<N>>)> {
35 let timer = timer!("Process::finalize_deployment");
36
37 let version = deployment.version()?;
39
40 match version {
42 DeploymentVersion::V1 | DeploymentVersion::V2 => {
43 let mut stack = Stack::new(self, deployment.program())?;
45 lap!(timer, "Compute the stack");
46
47 stack.set_program_owner(deployment.program_owner());
49
50 for (name, (verifying_key, _)) in deployment.verifying_keys() {
52 stack.insert_verifying_key(name, verifying_key.clone())?;
53 }
54 lap!(timer, "Insert the verifying keys");
55
56 let mappings = match deployment.edition().is_zero() {
58 true => deployment.program().mappings().values().collect::<Vec<_>>(),
59 false => {
60 let existing_stack = self.get_stack(deployment.program_id())?;
62 let existing_mappings = existing_stack.program().mappings();
64 let mut new_mappings = Vec::new();
66 for mapping in deployment.program().mappings().values() {
67 if !existing_mappings.contains_key(mapping.name()) {
68 new_mappings.push(mapping);
69 }
70 }
71 new_mappings
72 }
73 };
74 lap!(timer, "Retrieve the mappings to initialize");
75
76 atomic_batch_scope!(store, {
78 let mut finalize_operations = Vec::with_capacity(deployment.program().mappings().len());
80
81 let fee_stack = self.get_stack(fee.program_id())?;
85 finalize_operations.extend(finalize_fee_transition(state, store, &fee_stack, fee)?);
87 lap!(timer, "Finalize transition for '{}/{}'", fee.program_id(), fee.function_name());
88
89 let program_id = deployment.program_id();
93 for mapping in mappings {
95 finalize_operations.push(store.initialize_mapping(*program_id, *mapping.name())?);
97 }
98 lap!(timer, "Initialize the program mappings");
99
100 if deployment.program().contains_constructor() {
103 let operations = finalize_constructor(state, store, &stack, N::TransitionID::default())?;
104 finalize_operations.extend(operations);
105 lap!(timer, "Execute the constructor");
106 }
107
108 finish!(timer, "Finished finalizing the deployment");
109 Ok((stack, finalize_operations))
111 })
112 }
113 DeploymentVersion::V3 => {
114 ensure!(
116 deployment.program_id() != &ProgramID::credits(),
117 "The 'credits.aleo' program cannot be deployed with DeploymentVersion::V3"
118 );
119
120 let existing_stack = self.get_stack(deployment.program_id())?;
122
123 let mut stack = Stack::new_raw(self, deployment.program(), *existing_stack.program_edition())?;
127 stack.initialize_and_check(self)?;
128 lap!(timer, "Compute the stack");
129
130 stack.set_program_owner(*existing_stack.program_owner());
132
133 for (name, (verifying_key, _)) in deployment.verifying_keys() {
135 stack.insert_verifying_key(name, verifying_key.clone())?;
136 }
137 lap!(timer, "Insert the verifying keys");
138
139 atomic_batch_scope!(store, {
141 let mut finalize_operations = Vec::new();
142
143 let fee_stack = self.get_stack(fee.program_id())?;
145 finalize_operations.extend(finalize_fee_transition(state, store, &fee_stack, fee)?);
147 lap!(timer, "Finalize transition for '{}/{}'", fee.program_id(), fee.function_name());
148
149 finish!(timer, "Finished finalizing the V3 deployment");
150 Ok((stack, finalize_operations))
151 })
152 }
153 }
154 }
155
156 #[inline]
160 pub fn finalize_execution<P: FinalizeStorage<N>>(
161 &self,
162 state: FinalizeGlobalState,
163 store: &FinalizeStore<N, P>,
164 execution: &Execution<N>,
165 fee: Option<&Fee<N>>,
166 ) -> Result<Vec<FinalizeOperation<N>>> {
167 let timer = timer!("Program::finalize_execution");
168
169 ensure!(!execution.is_empty(), "There are no transitions in the execution");
171
172 let transition = execution.peek()?;
175 let stack = self.get_stack(transition.program_id())?;
177 let minimum_number_of_calls = stack.get_minimum_number_of_calls(transition.function_name())?;
179 if stack.contains_dynamic_call(transition.function_name())? {
183 ensure!(
184 minimum_number_of_calls <= execution.len(),
185 "The number of transitions in the execution is incorrect. Expected at least {minimum_number_of_calls}, but found {}",
186 execution.len()
187 );
188 } else {
189 ensure!(
190 minimum_number_of_calls == execution.len(),
191 "The number of transitions in the execution is incorrect. Expected {minimum_number_of_calls}, but found {}",
192 execution.len()
193 );
194 }
195 lap!(timer, "Verify the number of transitions");
196
197 let dynamic_future_to_future: HashMap<(Field<N>, Field<N>, Field<N>, Field<N>), &Future<N>> = execution
202 .transitions()
203 .filter_map(|transition| {
204 transition.outputs().last().and_then(|output| output.future()).and_then(|future| {
205 let dynamic_future = DynamicFuture::from_future(future).ok()?;
206 let key = (
207 *dynamic_future.program_name(),
208 *dynamic_future.program_network(),
209 *dynamic_future.function_name(),
210 *dynamic_future.checksum(),
211 );
212 Some((key, future))
213 })
214 })
215 .collect();
216
217 let consensus_version = N::CONSENSUS_VERSION(state.block_height())?;
219 let call_graph = match (ConsensusVersion::V1..=ConsensusVersion::V2).contains(&consensus_version) {
220 true => self.construct_call_graph(execution.transitions())?,
221 false => HashMap::new(),
223 };
224
225 atomic_batch_scope!(store, {
226 let mut finalize_operations =
230 finalize_transition(state, store, &stack, transition, call_graph, dynamic_future_to_future)?;
231
232 if let Some(fee) = fee {
234 let fee_stack = self.get_stack(fee.program_id())?;
236 finalize_operations.extend(finalize_fee_transition(state, store, &fee_stack, fee)?);
238 lap!(timer, "Finalize transition for '{}/{}'", fee.program_id(), fee.function_name());
239 }
240
241 finish!(timer);
242 Ok(finalize_operations)
244 })
245 }
246
247 #[inline]
251 pub fn finalize_fee<P: FinalizeStorage<N>>(
252 &self,
253 state: FinalizeGlobalState,
254 store: &FinalizeStore<N, P>,
255 fee: &Fee<N>,
256 ) -> Result<Vec<FinalizeOperation<N>>> {
257 let timer = timer!("Program::finalize_fee");
258
259 atomic_batch_scope!(store, {
260 let stack = self.get_stack(fee.program_id())?;
262 let result = finalize_fee_transition(state, store, &stack, fee);
264 finish!(timer, "Finalize transition for '{}/{}'", fee.program_id(), fee.function_name());
265 result
267 })
268 }
269}
270
271fn finalize_fee_transition<N: Network, P: FinalizeStorage<N>>(
273 state: FinalizeGlobalState,
274 store: &FinalizeStore<N, P>,
275 stack: &Arc<Stack<N>>,
276 fee: &Fee<N>,
277) -> Result<Vec<FinalizeOperation<N>>> {
278 let consensus_version = N::CONSENSUS_VERSION(state.block_height())?;
280 let call_graph = match (ConsensusVersion::V1..=ConsensusVersion::V2).contains(&consensus_version) {
281 true => HashMap::from([(*fee.transition_id(), Vec::new())]),
282 false => HashMap::new(),
284 };
285
286 match finalize_transition(state, store, stack, fee, call_graph, Default::default()) {
288 Ok(finalize_operations) => Ok(finalize_operations),
290 Err(error) => bail!("'finalize' failed on '{}/{}' - {error}", fee.program_id(), fee.function_name()),
292 }
293}
294
295fn finalize_constructor<N: Network, P: FinalizeStorage<N>>(
297 state: FinalizeGlobalState,
298 store: &FinalizeStore<N, P>,
299 stack: &Stack<N>,
300 transition_id: N::TransitionID,
301) -> Result<Vec<FinalizeOperation<N>>> {
302 let program_id = stack.program_id();
304 dev_println!("Finalizing constructor for {}...", stack.program_id());
305
306 let mut finalize_operations = Vec::new();
308
309 let nonce = 0;
312
313 let Some(constructor) = stack.program().constructor() else {
315 return Ok(finalize_operations);
316 };
317
318 let constructor_types = stack.get_constructor_types()?.clone();
320
321 let mut registers = FinalizeRegisters::new(state, transition_id, *program_id.name(), constructor_types, nonce);
323
324 let scope_name = Identifier::<N>::from_str("constructor")?;
326
327 let mut counter = 0;
329
330 while counter < constructor.commands().len() {
332 let command = &constructor.commands()[counter];
334 match &command {
336 Command::Await(_) => {
337 bail!("Cannot `await` a Future in a constructor")
338 }
339 _ => finalize_command_except_await(
340 store,
341 stack,
342 &mut registers,
343 constructor.positions(),
344 command,
345 &mut counter,
346 &mut finalize_operations,
347 &scope_name,
348 )?,
349 };
350 }
351
352 Ok(finalize_operations)
354}
355
356fn finalize_transition<N: Network, P: FinalizeStorage<N>>(
358 state: FinalizeGlobalState,
359 store: &FinalizeStore<N, P>,
360 stack: &Arc<Stack<N>>,
361 transition: &Transition<N>,
362 call_graph: HashMap<N::TransitionID, Vec<N::TransitionID>>,
363 dynamic_future_to_future: HashMap<(Field<N>, Field<N>, Field<N>, Field<N>), &Future<N>>,
364) -> Result<Vec<FinalizeOperation<N>>> {
365 let program_id = transition.program_id();
367 let function_name = transition.function_name();
369
370 dev_println!("Finalizing transition for {}/{function_name}...", transition.program_id());
371 debug_assert_eq!(stack.program_id(), transition.program_id());
372
373 let future = match transition.outputs().last().and_then(|output| output.future()) {
375 Some(future) => future,
376 _ => return Ok(Vec::new()),
377 };
378
379 ensure!(
381 future.program_id() == program_id && future.function_name() == function_name,
382 "The program ID and function name of the future do not match the transition"
383 );
384
385 let mut finalize_operations = Vec::new();
387
388 let mut states = Vec::new();
390
391 let mut nonce = 0;
394 let is_dynamic = false;
396
397 states.push(initialize_finalize_state(state, future, stack, *transition.id(), nonce, is_dynamic)?);
399
400 'outer: while let Some(FinalizeState { mut counter, mut registers, stack, mut call_counter, mut awaited }) =
402 states.pop()
403 {
404 let Some(finalize) = stack.get_function_ref(registers.function_name())?.finalize_logic() else {
406 bail!(
407 "The function '{}/{}' does not have an associated finalize scope",
408 stack.program_id(),
409 registers.function_name()
410 )
411 };
412 let scope_name = *registers.function_name();
414 while counter < finalize.commands().len() {
416 let command = &finalize.commands()[counter];
418 match &command {
420 Command::Await(await_) => {
421 if let Register::Access(_, _) = await_.register() {
423 bail!("The 'await' register must be a locator")
424 };
425 ensure!(
427 !awaited.contains(await_.register()),
428 "The future register '{}' has already been awaited",
429 await_.register()
430 );
431
432 let consensus_version = N::CONSENSUS_VERSION(state.block_height())?;
436 let transition_id = if (ConsensusVersion::V1..=ConsensusVersion::V2).contains(&consensus_version) {
437 let transition_id = registers.transition_id();
439 match call_graph.get(transition_id) {
441 Some(transitions) => match transitions.get(call_counter) {
442 Some(transition_id) => *transition_id,
443 None => bail!("Child transition ID not found."),
444 },
445 None => bail!("Transition ID '{transition_id}' not found in call graph"),
446 }
447 } else {
448 *transition.id()
449 };
450
451 nonce += 1;
453
454 let callee_state = match try_vm_runtime!(|| setup_await(
456 state,
457 await_,
458 &stack,
459 ®isters,
460 transition_id,
461 nonce,
462 &dynamic_future_to_future,
463 )) {
464 Ok(Ok(callee_state)) => callee_state,
465 Ok(Err(error)) => bail!("'finalize' failed to evaluate command ({command}): {error}"),
467 Err(_) => bail!("'finalize' failed to evaluate command ({command})"),
469 };
470
471 call_counter += 1;
473 counter += 1;
475 awaited.insert(await_.register().clone());
477
478 let caller_state = FinalizeState { counter, registers, stack, call_counter, awaited };
480
481 states.push(caller_state);
483 states.push(callee_state);
485
486 continue 'outer;
487 }
488 _ => finalize_command_except_await(
489 store,
490 stack.deref(),
491 &mut registers,
492 finalize.positions(),
493 command,
494 &mut counter,
495 &mut finalize_operations,
496 &scope_name,
497 )?,
498 };
499 }
500 let mut unawaited = Vec::new();
502 for input in finalize.inputs() {
503 if matches!(input.finalize_type(), FinalizeType::Future(_) | FinalizeType::DynamicFuture)
504 && !awaited.contains(input.register())
505 {
506 unawaited.push(input.register().clone());
507 }
508 }
509 ensure!(
510 unawaited.is_empty(),
511 "The following future registers have not been awaited: {}",
512 unawaited.iter().map(|r| r.to_string()).collect::<Vec<_>>().join(", ")
513 );
514 }
515
516 Ok(finalize_operations)
518}
519
520struct FinalizeState<N: Network> {
522 counter: usize,
524 registers: FinalizeRegisters<N>,
526 stack: Arc<Stack<N>>,
528 call_counter: usize,
530 awaited: HashSet<Register<N>>,
532}
533
534fn initialize_finalize_state<N: Network>(
536 state: FinalizeGlobalState,
537 future: &Future<N>,
538 stack: &Arc<Stack<N>>,
539 transition_id: N::TransitionID,
540 nonce: u64,
541 is_dynamic: bool,
542) -> Result<FinalizeState<N>> {
543 let stack = match (stack.program_id() == future.program_id(), is_dynamic) {
545 (true, _) => stack.clone(),
546 (false, true) => stack.get_stack_global(future.program_id())?,
547 (false, false) => stack.get_external_stack(future.program_id())?,
548 };
549 let Some(finalize) = stack.get_function_ref(future.function_name())?.finalize_logic() else {
551 bail!(
552 "The function '{}/{}' does not have an associated finalize scope",
553 future.program_id(),
554 future.function_name()
555 )
556 };
557 let mut registers = FinalizeRegisters::new(
559 state,
560 transition_id,
561 *future.function_name(),
562 stack.get_finalize_types(future.function_name())?.clone(),
563 nonce,
564 );
565
566 finalize.inputs().iter().map(|i| i.register()).zip_eq(future.arguments().iter()).try_for_each(
569 |(register, input)| {
570 registers.store(stack.deref(), register, Value::from(input))
572 },
573 )?;
574
575 Ok(FinalizeState { counter: 0, registers, stack, call_counter: 0, awaited: Default::default() })
576}
577
578#[inline]
580fn finalize_command_except_await<N: Network>(
581 store: &FinalizeStore<N, impl FinalizeStorage<N>>,
582 stack: &impl StackTrait<N>,
583 registers: &mut FinalizeRegisters<N>,
584 positions: &HashMap<Identifier<N>, usize>,
585 command: &Command<N>,
586 counter: &mut usize,
587 finalize_operations: &mut Vec<FinalizeOperation<N>>,
588 scope_name: &Identifier<N>,
589) -> Result<()> {
590 match &command {
592 Command::BranchEq(branch_eq) => {
593 let result = try_vm_runtime!(|| branch_to(*counter, branch_eq, positions, stack, registers));
594 match result {
595 Ok(Ok(new_counter)) => {
596 *counter = new_counter;
597 }
598 Ok(Err(error)) => bail!("'{scope_name}' failed to evaluate command ({command}): {error}"),
600 Err(_) => bail!("'{scope_name}' failed to evaluate command ({command})"),
602 }
603 }
604 Command::BranchNeq(branch_neq) => {
605 let result = try_vm_runtime!(|| branch_to(*counter, branch_neq, positions, stack, registers));
606 match result {
607 Ok(Ok(new_counter)) => {
608 *counter = new_counter;
609 }
610 Ok(Err(error)) => bail!("'{scope_name}' failed to evaluate command ({command}): {error}"),
612 Err(_) => bail!("'{scope_name}' failed to evaluate command ({command})"),
614 }
615 }
616 Command::Await(_) => {
617 bail!("Cannot use `finalize_command_except_await` with an 'await' command")
618 }
619 _ => {
620 let result = try_vm_runtime!(|| command.finalize(stack, store, registers));
621 match result {
622 Ok(Ok(Some(finalize_operation))) => finalize_operations.push(finalize_operation),
624 Ok(Ok(None)) => {}
626 Ok(Err(error)) => bail!("'{scope_name}' failed to evaluate command ({command}): {error}"),
628 Err(_) => bail!("'{scope_name}' failed to evaluate command ({command})"),
630 }
631 *counter += 1;
632 }
633 };
634 Ok(())
635}
636
637#[inline]
639fn setup_await<N: Network>(
640 state: FinalizeGlobalState,
641 await_: &Await<N>,
642 stack: &Arc<Stack<N>>,
643 registers: &FinalizeRegisters<N>,
644 transition_id: N::TransitionID,
645 nonce: u64,
646 dynamic_future_to_future: &HashMap<(Field<N>, Field<N>, Field<N>, Field<N>), &Future<N>>,
647) -> Result<FinalizeState<N>> {
648 let (future, is_dynamic) = match registers.load(stack.deref(), &Operand::Register(await_.register().clone()))? {
650 Value::Future(future) => (future, false),
651 Value::DynamicFuture(dynamic_future) => {
652 let key = (
654 *dynamic_future.program_name(),
655 *dynamic_future.program_network(),
656 *dynamic_future.function_name(),
657 *dynamic_future.checksum(),
658 );
659 match dynamic_future_to_future.get(&key) {
661 Some(future) => ((*future).clone(), true),
662 None => bail!("Dynamic future '{key:?}' not found in dynamic-future-to-future map"),
663 }
664 }
665 _ => bail!("The input to 'await' is not a future or dynamic future"),
666 };
667 initialize_finalize_state(state, &future, stack, transition_id, nonce, is_dynamic)
669}
670
671fn branch_to<N: Network, const VARIANT: u8>(
673 counter: usize,
674 branch: &Branch<N, VARIANT>,
675 positions: &HashMap<Identifier<N>, usize>,
676 stack: &impl StackTrait<N>,
677 registers: &impl RegistersTrait<N>,
678) -> Result<usize> {
679 let first = registers.load(stack, branch.first())?;
681 let second = registers.load(stack, branch.second())?;
682
683 let get_position_index = |position: &Identifier<N>| match positions.get(position) {
685 Some(index) if *index > counter => Ok(*index),
686 Some(_) => bail!("Cannot branch to an earlier position '{position}' in the program"),
687 None => bail!("The position '{position}' does not exist."),
688 };
689
690 match VARIANT {
692 0 if first == second => get_position_index(branch.position()),
694 0 if first != second => Ok(counter + 1),
695 1 if first == second => Ok(counter + 1),
697 1 if first != second => get_position_index(branch.position()),
698 _ => bail!("Invalid 'branch' variant: {VARIANT}"),
699 }
700}
701
702#[cfg(test)]
703mod tests {
704 use super::*;
705 use crate::tests::test_execute::{sample_fee, sample_finalize_state};
706 use console::prelude::TestRng;
707 use snarkvm_ledger_store::{
708 BlockStore,
709 helpers::memory::{BlockMemory, FinalizeMemory},
710 };
711
712 use aleo_std::StorageMode;
713
714 type CurrentNetwork = console::network::MainnetV0;
715 type CurrentAleo = circuit::network::AleoV0;
716
717 #[test]
718 fn test_finalize_deployment() {
719 let rng = &mut TestRng::default();
720
721 let program = Program::<CurrentNetwork>::from_str(
723 r"
724program testing.aleo;
725
726struct message:
727 amount as u128;
728
729mapping account:
730 key as address.public;
731 value as u64.public;
732
733record token:
734 owner as address.private;
735 amount as u64.private;
736
737function initialize:
738 input r0 as address.private;
739 input r1 as u64.private;
740 cast r0 r1 into r2 as token.record;
741 output r2 as token.record;
742
743function compute:
744 input r0 as message.private;
745 input r1 as message.public;
746 input r2 as message.private;
747 input r3 as token.record;
748 add r0.amount r1.amount into r4;
749 cast r3.owner r3.amount into r5 as token.record;
750 output r4 as u128.public;
751 output r5 as token.record;",
752 )
753 .unwrap();
754
755 let mut process = Process::load().unwrap();
757 let deployment = process.deploy::<CurrentAleo, _>(&program, rng).unwrap();
759
760 let block_store = BlockStore::<CurrentNetwork, BlockMemory<_>>::open(StorageMode::new_test(None)).unwrap();
762 let finalize_store = FinalizeStore::<_, FinalizeMemory<_>>::open(StorageMode::new_test(None)).unwrap();
764
765 assert!(!process.contains_program(program.id()));
767
768 let fee = sample_fee::<_, CurrentAleo, _, _>(&process, &block_store, &finalize_store, rng);
770 let (stack, _) =
772 process.finalize_deployment(sample_finalize_state(1), &finalize_store, &deployment, &fee).unwrap();
773 process.add_stack(stack);
775
776 assert!(process.contains_program(program.id()));
778 }
779}