use super::*;
use console::program::{FinalizeType, Future, Register};
use synthesizer_program::{Await, FinalizeRegistersState, Operand};
use utilities::try_vm_runtime;
use std::collections::HashSet;
impl<N: Network> Process<N> {
#[inline]
pub fn finalize_deployment<P: FinalizeStorage<N>>(
&self,
state: FinalizeGlobalState,
store: &FinalizeStore<N, P>,
deployment: &Deployment<N>,
fee: &Fee<N>,
) -> Result<(Stack<N>, Vec<FinalizeOperation<N>>)> {
let timer = timer!("Process::finalize_deployment");
let stack = Stack::new(self, deployment.program())?;
lap!(timer, "Compute the stack");
for (function_name, (verifying_key, _)) in deployment.verifying_keys() {
stack.insert_verifying_key(function_name, verifying_key.clone())?;
}
lap!(timer, "Insert the verifying keys");
atomic_batch_scope!(store, {
let mut finalize_operations = Vec::with_capacity(deployment.program().mappings().len());
let fee_stack = self.get_stack(fee.program_id())?;
finalize_operations.extend(finalize_fee_transition(state, store, fee_stack, fee)?);
lap!(timer, "Finalize transition for '{}/{}'", fee.program_id(), fee.function_name());
let program_id = deployment.program_id();
for mapping in deployment.program().mappings().values() {
finalize_operations.push(store.initialize_mapping(*program_id, *mapping.name())?);
}
finish!(timer, "Initialize the program mappings");
Ok((stack, finalize_operations))
})
}
#[inline]
pub fn finalize_execution<P: FinalizeStorage<N>>(
&self,
state: FinalizeGlobalState,
store: &FinalizeStore<N, P>,
execution: &Execution<N>,
fee: Option<&Fee<N>>,
) -> Result<Vec<FinalizeOperation<N>>> {
let timer = timer!("Program::finalize_execution");
ensure!(!execution.is_empty(), "There are no transitions in the execution");
let transition = execution.peek()?;
let stack = self.get_stack(transition.program_id())?;
let number_of_calls = stack.get_number_of_calls(transition.function_name())?;
ensure!(
number_of_calls == execution.len(),
"The number of transitions in the execution is incorrect. Expected {number_of_calls}, but found {}",
execution.len()
);
lap!(timer, "Verify the number of transitions");
let call_graph = self.construct_call_graph(execution)?;
atomic_batch_scope!(store, {
let mut finalize_operations = finalize_transition(state, store, stack, transition, call_graph)?;
if let Some(fee) = fee {
let fee_stack = self.get_stack(fee.program_id())?;
finalize_operations.extend(finalize_fee_transition(state, store, fee_stack, fee)?);
lap!(timer, "Finalize transition for '{}/{}'", fee.program_id(), fee.function_name());
}
finish!(timer);
Ok(finalize_operations)
})
}
#[inline]
pub fn finalize_fee<P: FinalizeStorage<N>>(
&self,
state: FinalizeGlobalState,
store: &FinalizeStore<N, P>,
fee: &Fee<N>,
) -> Result<Vec<FinalizeOperation<N>>> {
let timer = timer!("Program::finalize_fee");
atomic_batch_scope!(store, {
let stack = self.get_stack(fee.program_id())?;
let result = finalize_fee_transition(state, store, stack, fee);
finish!(timer, "Finalize transition for '{}/{}'", fee.program_id(), fee.function_name());
result
})
}
}
fn finalize_fee_transition<N: Network, P: FinalizeStorage<N>>(
state: FinalizeGlobalState,
store: &FinalizeStore<N, P>,
stack: &Stack<N>,
fee: &Fee<N>,
) -> Result<Vec<FinalizeOperation<N>>> {
let mut call_graph = HashMap::new();
call_graph.insert(*fee.transition_id(), Vec::new());
match finalize_transition(state, store, stack, fee, call_graph) {
Ok(finalize_operations) => Ok(finalize_operations),
Err(error) => bail!("'finalize' failed on '{}/{}' - {error}", fee.program_id(), fee.function_name()),
}
}
fn finalize_transition<N: Network, P: FinalizeStorage<N>>(
state: FinalizeGlobalState,
store: &FinalizeStore<N, P>,
stack: &Stack<N>,
transition: &Transition<N>,
call_graph: HashMap<N::TransitionID, Vec<N::TransitionID>>,
) -> Result<Vec<FinalizeOperation<N>>> {
let program_id = transition.program_id();
let function_name = transition.function_name();
#[cfg(debug_assertions)]
println!("Finalizing transition for {}/{function_name}...", transition.program_id());
debug_assert_eq!(stack.program_id(), transition.program_id());
let future = match transition.outputs().last().and_then(|output| output.future()) {
Some(future) => future,
_ => return Ok(Vec::new()),
};
ensure!(
future.program_id() == program_id && future.function_name() == function_name,
"The program ID and function name of the future do not match the transition"
);
let mut finalize_operations = Vec::new();
let mut states = Vec::new();
states.push(initialize_finalize_state(state, future, stack, *transition.id())?);
'outer: while let Some(FinalizeState {
mut counter,
finalize,
mut registers,
stack,
mut call_counter,
mut awaited,
}) = states.pop()
{
while counter < finalize.commands().len() {
let command = &finalize.commands()[counter];
match &command {
Command::BranchEq(branch_eq) => {
let result = try_vm_runtime!(|| branch_to(counter, branch_eq, finalize, stack, ®isters));
match result {
Ok(Ok(new_counter)) => {
counter = new_counter;
}
Ok(Err(error)) => bail!("'finalize' failed to evaluate command ({command}): {error}"),
Err(_) => bail!("'finalize' failed to evaluate command ({command})"),
}
}
Command::BranchNeq(branch_neq) => {
let result = try_vm_runtime!(|| branch_to(counter, branch_neq, finalize, stack, ®isters));
match result {
Ok(Ok(new_counter)) => {
counter = new_counter;
}
Ok(Err(error)) => bail!("'finalize' failed to evaluate command ({command}): {error}"),
Err(_) => bail!("'finalize' failed to evaluate command ({command})"),
}
}
Command::Await(await_) => {
if let Register::Access(_, _) = await_.register() {
bail!("The 'await' register must be a locator")
};
ensure!(
!awaited.contains(await_.register()),
"The future register '{}' has already been awaited",
await_.register()
);
let transition_id = registers.transition_id();
let child_transition_id = match call_graph.get(transition_id) {
Some(transitions) => match transitions.get(call_counter) {
Some(transition_id) => *transition_id,
None => bail!("Child transition ID not found."),
},
None => bail!("Transition ID '{transition_id}' not found in call graph"),
};
let callee_state =
match try_vm_runtime!(|| setup_await(state, await_, stack, ®isters, child_transition_id)) {
Ok(Ok(callee_state)) => callee_state,
Ok(Err(error)) => bail!("'finalize' failed to evaluate command ({command}): {error}"),
Err(_) => bail!("'finalize' failed to evaluate command ({command})"),
};
call_counter += 1;
counter += 1;
awaited.insert(await_.register().clone());
let caller_state = FinalizeState { counter, finalize, registers, stack, call_counter, awaited };
states.push(caller_state);
states.push(callee_state);
continue 'outer;
}
_ => {
let result = try_vm_runtime!(|| command.finalize(stack, store, &mut registers));
match result {
Ok(Ok(Some(finalize_operation))) => finalize_operations.push(finalize_operation),
Ok(Ok(None)) => {}
Ok(Err(error)) => bail!("'finalize' failed to evaluate command ({command}): {error}"),
Err(_) => bail!("'finalize' failed to evaluate command ({command})"),
}
counter += 1;
}
};
}
let mut unawaited = Vec::new();
for input in finalize.inputs() {
if matches!(input.finalize_type(), FinalizeType::Future(_)) && !awaited.contains(input.register()) {
unawaited.push(input.register().clone());
}
}
ensure!(
unawaited.is_empty(),
"The following future registers have not been awaited: {}",
unawaited.iter().map(|r| r.to_string()).collect::<Vec<_>>().join(", ")
);
}
Ok(finalize_operations)
}
struct FinalizeState<'a, N: Network> {
counter: usize,
finalize: &'a Finalize<N>,
registers: FinalizeRegisters<N>,
stack: &'a Stack<N>,
call_counter: usize,
awaited: HashSet<Register<N>>,
}
fn initialize_finalize_state<'a, N: Network>(
state: FinalizeGlobalState,
future: &Future<N>,
stack: &'a Stack<N>,
transition_id: N::TransitionID,
) -> Result<FinalizeState<'a, N>> {
let (finalize, stack) = match stack.program_id() == future.program_id() {
true => (stack.get_function_ref(future.function_name())?.finalize_logic(), stack),
false => {
let stack = stack.get_external_stack(future.program_id())?.as_ref();
(stack.get_function_ref(future.function_name())?.finalize_logic(), stack)
}
};
let finalize = match finalize {
Some(finalize) => finalize,
None => bail!(
"The function '{}/{}' does not have an associated finalize block",
future.program_id(),
future.function_name()
),
};
let mut registers = FinalizeRegisters::new(
state,
transition_id,
*future.function_name(),
stack.get_finalize_types(future.function_name())?.clone(),
);
finalize.inputs().iter().map(|i| i.register()).zip_eq(future.arguments().iter()).try_for_each(
|(register, input)| {
registers.store(stack, register, Value::from(input))
},
)?;
Ok(FinalizeState { counter: 0, finalize, registers, stack, call_counter: 0, awaited: Default::default() })
}
#[inline]
fn setup_await<'a, N: Network>(
state: FinalizeGlobalState,
await_: &Await<N>,
stack: &'a Stack<N>,
registers: &FinalizeRegisters<N>,
transition_id: N::TransitionID,
) -> Result<FinalizeState<'a, N>> {
let future = match registers.load(stack, &Operand::Register(await_.register().clone()))? {
Value::Future(future) => future,
_ => bail!("The input to 'await' is not a future"),
};
initialize_finalize_state(state, &future, stack, transition_id)
}
#[inline]
fn branch_to<N: Network, const VARIANT: u8>(
counter: usize,
branch: &Branch<N, VARIANT>,
finalize: &Finalize<N>,
stack: &Stack<N>,
registers: &FinalizeRegisters<N>,
) -> Result<usize> {
let first = registers.load(stack, branch.first())?;
let second = registers.load(stack, branch.second())?;
let get_position_index = |position: &Identifier<N>| match finalize.positions().get(position) {
Some(index) if *index > counter => Ok(*index),
Some(_) => bail!("Cannot branch to an earlier position '{position}' in the program"),
None => bail!("The position '{position}' does not exist."),
};
match VARIANT {
0 if first == second => get_position_index(branch.position()),
0 if first != second => Ok(counter + 1),
1 if first == second => Ok(counter + 1),
1 if first != second => get_position_index(branch.position()),
_ => bail!("Invalid 'branch' variant: {VARIANT}"),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::test_execute::{sample_fee, sample_finalize_state};
use console::prelude::TestRng;
use ledger_store::{
BlockStore,
helpers::memory::{BlockMemory, FinalizeMemory},
};
type CurrentNetwork = console::network::MainnetV0;
type CurrentAleo = circuit::network::AleoV0;
#[test]
fn test_finalize_deployment() {
let rng = &mut TestRng::default();
let program = Program::<CurrentNetwork>::from_str(
r"
program testing.aleo;
struct message:
amount as u128;
mapping account:
key as address.public;
value as u64.public;
record token:
owner as address.private;
amount as u64.private;
function initialize:
input r0 as address.private;
input r1 as u64.private;
cast r0 r1 into r2 as token.record;
output r2 as token.record;
function compute:
input r0 as message.private;
input r1 as message.public;
input r2 as message.private;
input r3 as token.record;
add r0.amount r1.amount into r4;
cast r3.owner r3.amount into r5 as token.record;
output r4 as u128.public;
output r5 as token.record;",
)
.unwrap();
let mut process = Process::load().unwrap();
let deployment = process.deploy::<CurrentAleo, _>(&program, rng).unwrap();
let block_store = BlockStore::<CurrentNetwork, BlockMemory<_>>::open(None).unwrap();
let finalize_store = FinalizeStore::<_, FinalizeMemory<_>>::open(None).unwrap();
assert!(!process.contains_program(program.id()));
let fee = sample_fee::<_, CurrentAleo, _, _>(&process, &block_store, &finalize_store, rng);
let (stack, _) =
process.finalize_deployment(sample_finalize_state(1), &finalize_store, &deployment, &fee).unwrap();
process.add_stack(stack);
assert!(process.contains_program(program.id()));
}
}