use super::*;
macro_rules! prepare_impl {
($self:ident, $transitions:ident, $query:ident, $current_state_root:ident, $current_block_height:ident, $get_state_paths_for_commitments:ident $(, $await:ident)?) => {{
Transaction::<N>::check_execution_size($transitions.len())?;
let mut transaction_tree = N::merkle_tree_bhp::<TRANSACTION_DEPTH>(&[])?;
let mut assignments = vec![];
let current_block_height = {
$query.$current_block_height()
$(.$await)?
}?;
let consensus_version = N::CONSENSUS_VERSION(current_block_height)?;
let commitments: Vec<_> = $transitions
.iter()
.flat_map(|t| $self.input_tasks.get(t.id()).into_iter().flatten())
.filter_map(|task| task.local.is_none().then_some(task.commitment))
.collect();
let mut state_paths: VecDeque<_> = $query.$get_state_paths_for_commitments(&commitments)
$(.$await)??.into();
let global_state_root = match state_paths.front() {
Some(path) => path.global_state_root(),
None => {
$query.$current_state_root()
$(.$await)?
}?
};
if *global_state_root == Field::zero() {
bail!("Inclusion expected the global state root in the execution to *not* be zero")
}
for (transition_index, transition) in $transitions.iter().enumerate() {
let transaction_leaf = TransactionLeaf::new_execution(transition_index as u16, **transition.id());
match $self.input_tasks.get(transition.id()) {
Some(tasks) => {
for task in tasks {
let local_state_root = (*transaction_tree.root()).into();
let state_path = match &task.local {
Some((transaction_leaf, transition_root, tcm, transition_path, transition_leaf)) => {
let transaction_path =
transaction_tree.prove(transaction_leaf.index() as usize, &transaction_leaf.to_bits_le())?;
StatePath::new_local(
global_state_root,
local_state_root,
transaction_path,
*transaction_leaf,
*transition_root,
*tcm,
transition_path.clone(),
*transition_leaf,
)?
}
None => {
state_paths.pop_front()
.ok_or(anyhow!("Missing state path for commitment {}", task.commitment))?
}
};
if global_state_root != state_path.global_state_root() {
bail!("Inclusion expected the global state root to be the same across iterations")
}
let assignment = if (ConsensusVersion::V1..=ConsensusVersion::V7).contains(&consensus_version) {
let assignment = InclusionV0Assignment::new(
state_path,
task.commitment,
task.gamma,
task.serial_number,
local_state_root,
task.local.is_none(), );
InclusionAssignmentWrapper::V0(assignment)
} else {
let (is_record_block_height_reached, upgrade_block_height) = match transition.is_credits() {
true => (!transition.is_upgrade(), N::INCLUSION_UPGRADE_HEIGHT()?.saturating_add(1)),
false => (true, 0),
};
let assignment = InclusionAssignment::new(
state_path,
task.commitment,
task.gamma,
task.serial_number,
is_record_block_height_reached,
upgrade_block_height,
local_state_root,
task.local.is_none(), );
InclusionAssignmentWrapper::V1(assignment)
};
assignments.push(assignment);
}
}
None => bail!("Missing input tasks for transition {} in inclusion", transition.id()),
}
transaction_tree.append(&[transaction_leaf.to_bits_le()])?;
}
Ok((assignments, global_state_root))
}};
}
impl<N: Network> Inclusion<N> {
pub fn prepare(
&self,
transitions: &[Transition<N>],
query: &dyn QueryTrait<N>,
) -> Result<(Vec<InclusionAssignmentWrapper<N>>, N::StateRoot)> {
prepare_impl!(
self,
transitions,
query,
current_state_root,
current_block_height,
get_state_paths_for_commitments
)
}
#[cfg(feature = "async")]
pub async fn prepare_async(
&self,
transitions: &[Transition<N>],
query: &dyn QueryTrait<N>,
) -> Result<(Vec<InclusionAssignmentWrapper<N>>, N::StateRoot)> {
prepare_impl!(
self,
transitions,
query,
current_state_root_async,
current_block_height_async,
get_state_paths_for_commitments_async,
await
)
}
}