use std::{collections::BTreeMap, sync::Arc};
use zakura_chain::{
ironwood, orchard, sapling,
subtree::{NoteCommitmentSubtreeData, NoteCommitmentSubtreeIndex},
};
use crate::{
service::{finalized_state::ZakuraDb, non_finalized_state::Chain},
HashOrHeight,
};
#[allow(unused_imports)]
use zakura_chain::subtree::NoteCommitmentSubtree;
pub fn sapling_tree<C>(
chain: Option<C>,
db: &ZakuraDb,
hash_or_height: HashOrHeight,
) -> Option<Arc<sapling::tree::NoteCommitmentTree>>
where
C: AsRef<Chain>,
{
chain
.and_then(|chain| chain.as_ref().sapling_tree(hash_or_height))
.or_else(|| db.sapling_tree_by_hash_or_height(hash_or_height))
}
pub fn sapling_subtrees<C>(
chain: Option<C>,
db: &ZakuraDb,
range: impl std::ops::RangeBounds<NoteCommitmentSubtreeIndex> + Clone,
) -> BTreeMap<NoteCommitmentSubtreeIndex, NoteCommitmentSubtreeData<sapling_crypto::Node>>
where
C: AsRef<Chain>,
{
subtrees(
chain,
range,
|chain, range| chain.sapling_subtrees_in_range(range),
|range| db.sapling_subtree_list_by_index_range(range),
)
}
pub fn orchard_tree<C>(
chain: Option<C>,
db: &ZakuraDb,
hash_or_height: HashOrHeight,
) -> Option<Arc<orchard::tree::NoteCommitmentTree>>
where
C: AsRef<Chain>,
{
chain
.and_then(|chain| chain.as_ref().orchard_tree(hash_or_height))
.or_else(|| db.orchard_tree_by_hash_or_height(hash_or_height))
}
pub fn orchard_subtrees<C>(
chain: Option<C>,
db: &ZakuraDb,
range: impl std::ops::RangeBounds<NoteCommitmentSubtreeIndex> + Clone,
) -> BTreeMap<NoteCommitmentSubtreeIndex, NoteCommitmentSubtreeData<orchard::tree::Node>>
where
C: AsRef<Chain>,
{
subtrees(
chain,
range,
|chain, range| chain.orchard_subtrees_in_range(range),
|range| db.orchard_subtree_list_by_index_range(range),
)
}
pub fn ironwood_tree<C>(
chain: Option<C>,
db: &ZakuraDb,
hash_or_height: HashOrHeight,
) -> Option<Arc<ironwood::tree::NoteCommitmentTree>>
where
C: AsRef<Chain>,
{
chain
.and_then(|chain| chain.as_ref().ironwood_tree(hash_or_height))
.or_else(|| db.ironwood_tree_by_hash_or_height(hash_or_height))
}
pub fn ironwood_subtrees<C>(
chain: Option<C>,
db: &ZakuraDb,
range: impl std::ops::RangeBounds<NoteCommitmentSubtreeIndex> + Clone,
) -> BTreeMap<NoteCommitmentSubtreeIndex, NoteCommitmentSubtreeData<ironwood::tree::Node>>
where
C: AsRef<Chain>,
{
subtrees(
chain,
range,
|chain, range| chain.ironwood_subtrees_in_range(range),
|range| db.ironwood_subtree_list_by_index_range(range),
)
}
fn subtrees<C, Range, Node, ChainSubtreeFn, DbSubtreeFn>(
chain: Option<C>,
range: Range,
read_chain: ChainSubtreeFn,
read_disk: DbSubtreeFn,
) -> BTreeMap<NoteCommitmentSubtreeIndex, NoteCommitmentSubtreeData<Node>>
where
C: AsRef<Chain>,
Node: PartialEq,
Range: std::ops::RangeBounds<NoteCommitmentSubtreeIndex> + Clone,
ChainSubtreeFn: FnOnce(
&Chain,
Range,
)
-> BTreeMap<NoteCommitmentSubtreeIndex, NoteCommitmentSubtreeData<Node>>,
DbSubtreeFn:
FnOnce(Range) -> BTreeMap<NoteCommitmentSubtreeIndex, NoteCommitmentSubtreeData<Node>>,
{
use std::ops::Bound::*;
let Some(start_index) = (match range.start_bound().cloned() {
Included(start_index) => Some(start_index),
Excluded(start_index) => start_index.0.checked_add(1).map(Into::into),
Unbounded => Some(0.into()),
}) else {
return BTreeMap::new();
};
let results = match chain.map(|chain| read_chain(chain.as_ref(), range.clone())) {
Some(chain_results) if chain_results.contains_key(&start_index) => return chain_results,
Some(chain_results) => {
let mut db_results = read_disk(range);
for (chain_index, chain_subtree) in chain_results {
let Some(db_subtree) = db_results.get(&chain_index) else {
db_results.insert(chain_index, chain_subtree);
continue;
};
if &chain_subtree != db_subtree {
break;
}
}
db_results
}
None => read_disk(range),
};
if results.contains_key(&start_index) {
results
} else {
BTreeMap::new()
}
}
pub fn history_tree<C>(
chain: Option<C>,
db: &ZakuraDb,
hash_or_height: HashOrHeight,
) -> Option<Arc<zakura_chain::history_tree::HistoryTree>>
where
C: AsRef<Chain>,
{
chain
.and_then(|chain| chain.as_ref().history_tree(hash_or_height))
.or_else(|| {
let (tip_height, tip_hash) = db.tip()?;
match hash_or_height {
HashOrHeight::Height(height) if height == tip_height => Some(db.history_tree()),
HashOrHeight::Hash(hash) if hash == tip_hash => Some(db.history_tree()),
_ => None,
}
})
}