use super::*;
use console::program::{Future, Register};
use synthesizer_program::{Await, FinalizeRegistersState, Operand};
use utilities::handle_halting;
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())?);
while let Some(FinalizeState {
mut counter,
finalize,
mut registers,
stack,
mut call_counter,
mut recent_call_locator,
}) = states.pop()
{
while counter < finalize.commands().len() {
let command = &finalize.commands()[counter];
match &command {
Command::BranchEq(branch_eq) => {
let result = handle_halting!(panic::AssertUnwindSafe(|| {
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 = handle_halting!(panic::AssertUnwindSafe(|| {
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_) => {
let locator = *match await_.register() {
Register::Locator(locator) => locator,
Register::Access(..) => bail!("The 'await' register must be a locator"),
};
if let Some(recent_call_locator) = recent_call_locator {
ensure!(
locator > recent_call_locator,
"Await register's locator '{locator}' must be greater than the last seen call locator '{recent_call_locator}'",
)
}
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 handle_halting!(panic::AssertUnwindSafe(|| {
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})"),
};
recent_call_locator = Some(locator);
call_counter += 1;
counter += 1;
let caller_state =
FinalizeState { counter, finalize, registers, stack, call_counter, recent_call_locator };
states.push(caller_state);
states.push(callee_state);
break;
}
_ => {
let result =
handle_halting!(panic::AssertUnwindSafe(|| { 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;
}
};
}
}
Ok(finalize_operations)
}
struct FinalizeState<'a, N: Network> {
counter: usize,
finalize: &'a Finalize<N>,
registers: FinalizeRegisters<N>,
stack: &'a Stack<N>,
call_counter: usize,
recent_call_locator: Option<u64>,
}
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, recent_call_locator: None })
}
#[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::{
helpers::memory::{BlockMemory, FinalizeMemory},
BlockStore,
};
type CurrentNetwork = console::network::Testnet3;
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()));
}
}