use alloc::{
collections::{BTreeMap, BTreeSet},
sync::Arc,
vec::Vec,
};
use miden_utils_indexing::newtype_id;
use crate::{
Word,
advice::AdviceMap,
mast::{ExecutableMastForest, MastForest, MastNode, MastNodeExt, MastNodeId},
};
newtype_id!(MastForestId);
#[derive(Debug)]
pub struct SparseMastForest {
nodes: BTreeMap<MastNodeId, MastNode>,
digests: BTreeMap<MastNodeId, Word>,
num_nodes: usize,
roots: Vec<MastNodeId>,
advice_map: AdviceMap,
commitment_cache: Word,
}
impl SparseMastForest {
pub fn nodes(&self) -> &BTreeMap<MastNodeId, MastNode> {
&self.nodes
}
pub fn num_nodes(&self) -> usize {
self.num_nodes
}
pub fn procedure_roots(&self) -> &[MastNodeId] {
&self.roots
}
pub fn advice_map(&self) -> &AdviceMap {
&self.advice_map
}
pub fn commitment(&self) -> Word {
self.commitment_cache
}
}
impl ExecutableMastForest for SparseMastForest {
#[inline(always)]
fn get_node_by_id(&self, node_id: MastNodeId) -> Option<&MastNode> {
self.nodes.get(&node_id)
}
#[inline(always)]
fn get_digest_by_id(&self, node_id: MastNodeId) -> Option<Word> {
if let Some(node) = self.nodes.get(&node_id) {
return Some(node.digest());
}
self.digests.get(&node_id).copied()
}
#[inline(always)]
fn find_procedure_root(&self, digest: Word) -> Option<MastNodeId> {
self.roots.iter().find_map(|&root_id| {
let node = self.nodes.get(&root_id)?;
(node.digest() == digest).then_some(root_id)
})
}
#[inline(always)]
fn advice_map(&self) -> &AdviceMap {
&self.advice_map
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VisitKind {
FullVisit,
DigestOnly,
}
#[derive(Debug)]
pub struct SparseMastForestBuilder {
source: Arc<MastForest>,
num_nodes: usize,
full_visits: BTreeSet<MastNodeId>,
digest_only_visits: BTreeSet<MastNodeId>,
}
impl SparseMastForestBuilder {
pub fn new(source: Arc<MastForest>) -> Self {
let num_nodes = source.nodes().len();
Self {
source,
num_nodes,
full_visits: BTreeSet::new(),
digest_only_visits: BTreeSet::new(),
}
}
pub fn record_visit(&mut self, node_id: MastNodeId, kind: VisitKind) {
match kind {
VisitKind::FullVisit => {
self.full_visits.insert(node_id);
},
VisitKind::DigestOnly => {
self.digest_only_visits.insert(node_id);
},
}
}
pub fn source(&self) -> &Arc<MastForest> {
&self.source
}
pub fn finalize(self) -> SparseMastForest {
let SparseMastForestBuilder {
source,
num_nodes,
full_visits,
digest_only_visits,
} = self;
let mut nodes = BTreeMap::new();
for node_id in &full_visits {
let node = source
.get_node_by_id(*node_id)
.expect("recorded full-visit id must exist in source forest");
nodes.insert(*node_id, node.clone());
}
let mut digests = BTreeMap::new();
for node_id in digest_only_visits {
if full_visits.contains(&node_id) {
continue;
}
let node = source
.get_node_by_id(node_id)
.expect("recorded digest-only id must exist in source forest");
digests.insert(node_id, node.digest());
}
SparseMastForest {
nodes,
digests,
num_nodes,
roots: source.procedure_roots().to_vec(),
advice_map: source.advice_map().clone(),
commitment_cache: source.commitment(),
}
}
}