use console::{
network::{prelude::*, BHPMerkleTree},
program::{InputID, OutputID, Request, Response},
types::{Address, Field},
};
use indexmap::IndexMap;
const TRANSACTION_DEPTH: u8 = 4;
const TRANSITION_DEPTH: u8 = 4;
pub struct Trace<N: Network> {
transaction: BHPMerkleTree<N, TRANSACTION_DEPTH>,
roots: IndexMap<u8, Field<N>>,
leaves: IndexMap<u8, Vec<Option<Field<N>>>>,
caller: Address<N>,
transition_index: u8,
input_index: u8,
output_index: u8,
is_finalized: bool,
}
impl<N: Network> Trace<N> {
pub fn new(request: &Request<N>, response: &Response<N>) -> Result<Self> {
let mut trace = Self {
transaction: N::merkle_tree_bhp::<TRANSACTION_DEPTH>(&[])?,
roots: IndexMap::new(),
leaves: IndexMap::new(),
caller: *request.caller(),
transition_index: 0,
input_index: 0,
output_index: 0,
is_finalized: false,
};
request.input_ids().iter().try_for_each(|input_id| {
match input_id {
InputID::Constant(input_hash) => trace.add_input(*input_hash),
InputID::Public(input_hash) => trace.add_input(*input_hash),
InputID::Private(input_hash) => trace.add_input(*input_hash),
InputID::Record(_, serial_number) => trace.add_input(*serial_number),
InputID::ExternalRecord(input_commitment) => trace.add_input(*input_commitment),
}
})?;
response.output_ids().iter().try_for_each(|output_id| {
match output_id {
OutputID::Constant(output_hash) => trace.add_output(*output_hash),
OutputID::Public(output_hash) => trace.add_output(*output_hash),
OutputID::Private(output_hash) => trace.add_output(*output_hash),
OutputID::Record(commitment, _, _) => trace.add_output(*commitment),
OutputID::ExternalRecord(output_commitment) => trace.add_output(*output_commitment),
}
})?;
Ok(trace)
}
pub const fn roots(&self) -> &IndexMap<u8, Field<N>> {
&self.roots
}
pub const fn leaves(&self) -> &IndexMap<u8, Vec<Option<Field<N>>>> {
&self.leaves
}
pub const fn caller(&self) -> &Address<N> {
&self.caller
}
pub fn add_input(&mut self, input: Field<N>) -> Result<()> {
ensure!(!self.is_finalized, "Trace is finalized, cannot add input");
ensure!((self.input_index as usize) < N::MAX_INPUTS, "Trace reached the maximum inputs for a program call");
ensure!(!input.is_zero(), "Input to the trace must be nonzero");
self.leaves.entry(self.transition_index).or_default().push(Some(input));
self.input_index += 1;
self.ensure_num_leaves()
}
pub fn add_output(&mut self, output: Field<N>) -> Result<()> {
ensure!(!self.is_finalized, "Trace is finalized, cannot add output");
ensure!((self.output_index as usize) < N::MAX_OUTPUTS, "Trace reached the maximum outputs for a program call");
ensure!(!output.is_zero(), "Output to the trace must be nonzero");
if self.output_index == 0 {
self.leaves
.entry(self.transition_index)
.or_default()
.extend(vec![None; N::MAX_INPUTS - self.input_index as usize]);
self.input_index = N::MAX_INPUTS as u8;
}
self.leaves.entry(self.transition_index).or_default().push(Some(output));
self.output_index += 1;
self.ensure_num_leaves()
}
pub fn next_transition(&mut self, caller: Address<N>) -> Result<()> {
ensure!(!self.is_finalized, "Trace is finalized, cannot call next transition");
ensure!(self.roots.len() == self.transition_index as usize, "Trace has incorrect number of transition roots");
ensure!((self.transition_index as usize) < N::MAX_TRANSITIONS, "Trace reached the maximum transitions");
ensure!(self.input_index > 0 || self.output_index > 0, "Trace cannot transition without inputs or outputs");
self.leaves
.entry(self.transition_index)
.or_default()
.extend(vec![None; N::MAX_INPUTS - self.input_index as usize]);
self.leaves
.entry(self.transition_index)
.or_default()
.extend(vec![None; N::MAX_OUTPUTS - self.output_index as usize]);
let transition = N::merkle_tree_bhp::<TRANSITION_DEPTH>(
&self
.leaves
.get(&self.transition_index)
.unwrap_or(&vec![])
.iter()
.map(|leaf| leaf.unwrap_or(Field::<N>::zero()).to_bits_le())
.collect::<Vec<_>>(),
)?;
self.transaction.append(&[transition.root().to_bits_le()])?;
self.roots.insert(self.transition_index, *transition.root());
self.caller = caller;
self.transition_index += 1;
self.input_index = 0;
self.output_index = 0;
self.ensure_num_leaves()
}
pub fn finalize(&mut self) -> Result<()> {
ensure!(!self.is_finalized, "Trace is already finalized");
ensure!(self.roots.len() == self.transition_index as usize, "Trace has incorrect number of transition roots");
ensure!((self.transition_index as usize) < N::MAX_TRANSITIONS, "Trace reached the maximum transitions");
if self.input_index > 0 || self.output_index > 0 {
self.leaves
.entry(self.transition_index)
.or_default()
.extend(vec![None; N::MAX_INPUTS - self.input_index as usize]);
self.leaves.entry(self.transition_index).or_default().extend(vec![
None;
N::MAX_OUTPUTS
- self.output_index as usize
]);
let transition = N::merkle_tree_bhp::<TRANSITION_DEPTH>(
&self
.leaves
.get(&self.transition_index)
.unwrap_or(&vec![])
.iter()
.map(|leaf| leaf.unwrap_or(Field::<N>::zero()).to_bits_le())
.collect::<Vec<_>>(),
)?;
self.transaction.append(&[transition.root().to_bits_le()])?;
self.roots.insert(self.transition_index, *transition.root());
self.transition_index += 1;
self.input_index = 0;
self.output_index = 0;
}
self.ensure_num_leaves()?;
self.ensure_transaction_root()
}
pub fn ensure_num_leaves(&self) -> Result<()> {
let num_leaves = self.leaves.values().fold(0, |acc, v| acc + v.len());
let expected_num_leaves = self.transition_index as usize * (N::MAX_INPUTS + N::MAX_OUTPUTS) as usize
+ (self.input_index + self.output_index) as usize;
ensure!(
num_leaves == expected_num_leaves,
"Trace has an incorrect number of leaves: expected {expected_num_leaves}, found {num_leaves}"
);
Ok(())
}
pub fn ensure_transaction_root(&self) -> Result<()> {
self.leaves
.values()
.map(|leaves| {
let leaf_nodes = leaves.iter().map(|leaf| leaf.unwrap_or(Field::<N>::zero()).to_bits_le());
Ok::<_, Error>(*N::merkle_tree_bhp::<TRANSITION_DEPTH>(&leaf_nodes.collect::<Vec<_>>())?.root())
})
.zip_eq(self.roots.values())
.try_for_each(|(root, expected_root)| {
let root = root?;
match root == *expected_root {
true => Ok(()),
false => bail!("Trace has an incorrect transition root: expected {expected_root}, found {root}"),
}
})?;
let root = *N::merkle_tree_bhp::<TRANSACTION_DEPTH>(
&self.roots.values().map(|subroot| subroot.to_bits_le()).collect::<Vec<_>>(),
)?
.root();
let expected_root = self.transaction.root();
match root == *expected_root {
true => Ok(()),
false => bail!("Trace has an incorrect transaction root: expected {expected_root}, found {root}"),
}
}
}