use std::collections::BTreeMap;
use incrementalmerkletree::{
Position, Retention,
frontier::{CommitmentTree, Frontier},
};
use orchard::tree::MerkleHashOrchard;
use sapling_crypto::Node;
use shardtree::LocatedPrunableTree;
use zcash_primitives::{block::BlockHash, merkle_tree::read_commitment_tree};
use zcash_protocol::consensus::BlockHeight;
use zingo_netutils::lightwallet_protocol::TreeState;
#[cfg(not(feature = "darkside_test"))]
use {
crate::error::ServerError, shardtree::store::ShardStore, subtle::CtOption,
zingo_netutils::lightwallet_protocol::SubtreeRoot,
};
pub(crate) const SHARD_HEIGHT: u8 = 16;
#[derive(Debug)]
pub(crate) struct WitnessData {
pub(crate) sapling_initial_position: Position,
pub(crate) orchard_initial_position: Position,
pub(crate) sapling_leaves_and_retentions: Vec<(sapling_crypto::Node, Retention<BlockHeight>)>,
pub(crate) orchard_leaves_and_retentions: Vec<(MerkleHashOrchard, Retention<BlockHeight>)>,
}
impl WitnessData {
pub(crate) fn new(
sapling_initial_position: Position,
orchard_initial_position: Position,
) -> Self {
WitnessData {
sapling_initial_position,
orchard_initial_position,
sapling_leaves_and_retentions: Vec::new(),
orchard_leaves_and_retentions: Vec::new(),
}
}
}
#[derive(Debug)]
pub struct LocatedTreeData<H> {
pub(crate) subtree: LocatedPrunableTree<H>,
pub(crate) checkpoints: BTreeMap<BlockHeight, Position>,
}
pub(crate) fn build_located_trees<H>(
initial_position: Position,
leaves_and_retentions: Vec<(H, Retention<BlockHeight>)>,
located_tree_size: usize,
) -> Vec<LocatedTreeData<H>>
where
H: Copy + PartialEq + incrementalmerkletree::Hashable + Sync + Send,
{
let (sender, receiver) = crossbeam_channel::unbounded();
rayon::scope_fifo(|scope| {
for (i, chunk) in leaves_and_retentions.chunks(located_tree_size).enumerate() {
let sender = sender.clone();
scope.spawn_fifo(move |_scope| {
let start_position = initial_position + ((i * located_tree_size) as u64);
let tree = LocatedPrunableTree::from_iter(
start_position..(start_position + chunk.len() as u64),
incrementalmerkletree::Level::from(SHARD_HEIGHT),
chunk.iter().copied(),
);
let _ignore_error = sender.send(tree);
});
}
});
drop(sender);
let mut located_tree_data = Vec::new();
for tree in receiver.iter().flatten() {
located_tree_data.push(LocatedTreeData {
subtree: tree.subtree,
checkpoints: tree.checkpoints,
});
}
located_tree_data
}
#[cfg(not(feature = "darkside_test"))]
pub(crate) fn add_subtree_roots<S, const DEPTH: u8, const SHARD_HEIGHT: u8>(
subtree_root_start_index: usize,
subtree_roots: Vec<SubtreeRoot>,
shard_tree: &mut shardtree::ShardTree<S, DEPTH, SHARD_HEIGHT>,
) -> Result<(), ServerError>
where
S: ShardStore<
H: incrementalmerkletree::Hashable + Clone + PartialEq + FromBytes,
CheckpointId: Clone + Ord + std::fmt::Debug,
Error = std::convert::Infallible,
>,
{
for (index, tree_root) in subtree_roots.into_iter().enumerate() {
let node = <S::H as FromBytes>::from_bytes(
tree_root
.root_hash
.try_into()
.map_err(|_| ServerError::InvalidSubtreeRoot)?,
)
.into_option()
.ok_or(ServerError::InvalidSubtreeRoot)?;
let shard = LocatedPrunableTree::with_root_value(
incrementalmerkletree::Address::from_parts(
incrementalmerkletree::Level::new(SHARD_HEIGHT),
(subtree_root_start_index + index) as u64,
),
(node, shardtree::RetentionFlags::EPHEMERAL),
);
shard_tree.store_mut().put_shard(shard).expect("infallible");
}
Ok(())
}
#[cfg(not(feature = "darkside_test"))]
pub(crate) trait FromBytes
where
Self: Sized,
{
fn from_bytes(array: [u8; 32]) -> CtOption<Self>;
}
#[cfg(not(feature = "darkside_test"))]
impl FromBytes for orchard::tree::MerkleHashOrchard {
fn from_bytes(array: [u8; 32]) -> CtOption<Self> {
Self::from_bytes(&array)
}
}
#[cfg(not(feature = "darkside_test"))]
impl FromBytes for sapling_crypto::Node {
fn from_bytes(array: [u8; 32]) -> CtOption<Self> {
Self::from_bytes(array)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Frontiers {
block_height: BlockHeight,
block_hash: BlockHash,
final_sapling_tree:
Frontier<sapling_crypto::Node, { sapling_crypto::NOTE_COMMITMENT_TREE_DEPTH }>,
final_orchard_tree:
Frontier<orchard::tree::MerkleHashOrchard, { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }>,
}
#[allow(dead_code)]
impl Frontiers {
pub(crate) fn empty(block_height: BlockHeight, block_hash: BlockHash) -> Self {
Self {
block_height,
block_hash,
final_sapling_tree: Frontier::empty(),
final_orchard_tree: Frontier::empty(),
}
}
pub(crate) fn new(
block_height: BlockHeight,
block_hash: BlockHash,
final_sapling_tree: Frontier<
sapling_crypto::Node,
{ sapling_crypto::NOTE_COMMITMENT_TREE_DEPTH },
>,
final_orchard_tree: Frontier<
orchard::tree::MerkleHashOrchard,
{ orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 },
>,
) -> Self {
Self {
block_height,
block_hash,
final_sapling_tree,
final_orchard_tree,
}
}
pub(crate) fn block_height(&self) -> BlockHeight {
self.block_height
}
pub(crate) fn block_hash(&self) -> BlockHash {
self.block_hash
}
pub(crate) fn final_sapling_tree(
&self,
) -> &Frontier<sapling_crypto::Node, { sapling_crypto::NOTE_COMMITMENT_TREE_DEPTH }> {
&self.final_sapling_tree
}
pub(crate) fn final_orchard_tree(
&self,
) -> &Frontier<orchard::tree::MerkleHashOrchard, { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }>
{
&self.final_orchard_tree
}
}
impl TryFrom<TreeState> for Frontiers {
type Error = std::io::Error;
fn try_from(value: TreeState) -> Result<Self, Self::Error> {
let mut hash_bytes = hex::decode(&value.hash).map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Block hash is not valid hex: {e:?}"),
)
})?;
hash_bytes.reverse();
Ok(Frontiers::new(
value.height.try_into().map_err(|_| {
std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid block height")
})?,
BlockHash::try_from_slice(&hash_bytes).ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid block hash length.",
)
})?,
get_sapling_tree(&value)?.to_frontier(),
get_orchard_tree(&value)?.to_frontier(),
))
}
}
pub(crate) fn get_sapling_tree(
tree_state: &TreeState,
) -> std::io::Result<CommitmentTree<Node, { sapling_crypto::NOTE_COMMITMENT_TREE_DEPTH }>> {
if tree_state.sapling_tree.is_empty() {
Ok(CommitmentTree::empty())
} else {
let sapling_tree_bytes = hex::decode(&tree_state.sapling_tree).map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Hex decoding of Sapling tree bytes failed: {e:?}"),
)
})?;
read_commitment_tree::<Node, _, { sapling_crypto::NOTE_COMMITMENT_TREE_DEPTH }>(
&sapling_tree_bytes[..],
)
}
}
pub(crate) fn get_orchard_tree(
tree_state: &TreeState,
) -> std::io::Result<CommitmentTree<MerkleHashOrchard, { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }>>
{
if tree_state.orchard_tree.is_empty() {
Ok(CommitmentTree::empty())
} else {
let orchard_tree_bytes = hex::decode(&tree_state.orchard_tree).map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Hex decoding of Orchard tree bytes failed: {e:?}"),
)
})?;
read_commitment_tree::<MerkleHashOrchard, _, { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }>(
&orchard_tree_bytes[..],
)
}
}