//! Contains code that interfaces with the zcash_history crate from //! librustzcash. // TODO: remove after this module gets to be used #![allow(dead_code)] mod tests; use std::{collections::HashMap, convert::TryInto, io, sync::Arc}; use crate::{ block::{Block, ChainHistoryMmrRootHash}, parameters::{ConsensusBranchId, Network, NetworkUpgrade}, sapling, }; /// A MMR Tree using zcash_history::Tree. /// /// Currently it should not be used as a long-term data structure because it /// may grow without limits. pub struct Tree { network: Network, network_upgrade: NetworkUpgrade, inner: zcash_history::Tree, } /// An encoded tree node data. pub struct NodeData { inner: [u8; zcash_history::MAX_NODE_DATA_SIZE], } impl From<&zcash_history::NodeData> for NodeData { /// Convert from librustzcash. fn from(inner_node: &zcash_history::NodeData) -> Self { let mut node = NodeData { inner: [0; zcash_history::MAX_NODE_DATA_SIZE], }; inner_node .write(&mut &mut node.inner[..]) .expect("buffer has the proper size"); node } } /// An encoded entry in the tree. /// Contains the node data and information about its position in the tree. pub struct Entry { inner: [u8; zcash_history::MAX_ENTRY_SIZE], } impl From> for Entry { /// Convert from librustzcash. fn from(inner_entry: zcash_history::Entry) -> Self { let mut entry = Entry { inner: [0; zcash_history::MAX_ENTRY_SIZE], }; inner_entry .write(&mut &mut entry.inner[..]) .expect("buffer has the proper size"); entry } } impl Entry { /// Create a leaf Entry for the given block, its network, and the root of its /// Sapling note commitment tree. fn new_leaf(block: Arc, network: Network, sapling_root: &sapling::tree::Root) -> Self { let node_data = block_to_history_node(block, network, sapling_root); let inner_entry = zcash_history::Entry::::new_leaf(node_data); inner_entry.into() } /// Create a node (non-leaf) Entry from the encoded node data and the indices of /// its children (in the array representation of the MMR tree). fn new_node( branch_id: ConsensusBranchId, data: NodeData, left_idx: u32, right_idx: u32, ) -> Result { let node_data = zcash_history::NodeData::from_bytes(branch_id.into(), data.inner)?; let inner_entry = zcash_history::Entry::new( node_data, zcash_history::EntryLink::Stored(left_idx), zcash_history::EntryLink::Stored(right_idx), ); Ok(inner_entry.into()) } } impl Tree { /// Create a MMR tree with the given length from the given cache of nodes. /// /// The `peaks` are the peaks of the MMR tree to build and their position in the /// array representation of the tree. /// The `extra` are extra nodes that enable removing nodes from the tree, and their position. /// /// Note that the length is usually larger than the length of `peaks` and `extra`, since /// you don't need to pass every node, just the peaks of the tree (plus extra). /// /// # Panics /// /// Will panic if `peaks` is empty. fn new_from_cache( network: Network, network_upgrade: NetworkUpgrade, length: u32, peaks: &HashMap, extra: &HashMap, ) -> Result { let branch_id = network_upgrade .branch_id() .expect("unexpected pre-Overwinter MMR history tree"); let mut peaks_vec = Vec::new(); for (idx, entry) in peaks { let inner_entry = zcash_history::Entry::from_bytes(branch_id.into(), entry.inner)?; peaks_vec.push((*idx, inner_entry)); } let mut extra_vec = Vec::new(); for (idx, entry) in extra { let inner_entry = zcash_history::Entry::from_bytes(branch_id.into(), entry.inner)?; extra_vec.push((*idx, inner_entry)); } let inner = zcash_history::Tree::new(length, peaks_vec, extra_vec); Ok(Tree { network, network_upgrade, inner, }) } /// Create a single-node MMR tree for the given block. /// /// `sapling_root` is the root of the Sapling note commitment tree of the block. fn new_from_block( network: Network, block: Arc, sapling_root: &sapling::tree::Root, ) -> Result { let height = block .coinbase_height() .expect("block must have coinbase height during contextual verification"); let network_upgrade = NetworkUpgrade::current(network, height); let entry0 = Entry::new_leaf(block, network, sapling_root); let mut peaks = HashMap::new(); peaks.insert(0u32, &entry0); Tree::new_from_cache(network, network_upgrade, 1, &peaks, &HashMap::new()) } /// Append a new block to the tree, as a new leaf. /// /// `sapling_root` is the root of the Sapling note commitment tree of the block. /// /// Returns a vector of nodes added to the tree (leaf + internal nodes). /// /// # Panics /// /// Panics if the network upgrade of the given block is different from /// the network upgrade of the other blocks in the tree. fn append_leaf( &mut self, block: Arc, sapling_root: &sapling::tree::Root, ) -> Result, zcash_history::Error> { let height = block .coinbase_height() .expect("block must have coinbase height during contextual verification"); let network_upgrade = NetworkUpgrade::current(self.network, height); if self.network_upgrade != network_upgrade { panic!( "added block from network upgrade {:?} but MMR tree is restricted to {:?}", network_upgrade, self.network_upgrade ); } let node_data = block_to_history_node(block, self.network, sapling_root); let appended = self.inner.append_leaf(node_data)?; let mut new_nodes = Vec::new(); for entry in appended { let mut node = NodeData { inner: [0; zcash_history::MAX_NODE_DATA_SIZE], }; self.inner .resolve_link(entry) .expect("entry was just generated so it must be valid") .data() .write(&mut &mut node.inner[..]) .expect("buffer was created with enough capacity"); new_nodes.push(node); } Ok(new_nodes) } /// Append multiple blocks to the tree. fn append_leaf_iter( &mut self, vals: impl Iterator, sapling::tree::Root)>, ) -> Result, zcash_history::Error> { let mut new_nodes = Vec::new(); for (block, root) in vals { new_nodes.append(&mut self.append_leaf(block, &root)?); } Ok(new_nodes) } /// Remove the last leaf (block) from the tree. /// /// Returns the number of nodes removed from the tree after the operation. fn truncate_leaf(&mut self) -> Result { self.inner.truncate_leaf() } /// Return the root hash of the tree, i.e. `hashChainHistoryRoot`. fn hash(&self) -> ChainHistoryMmrRootHash { // Both append_leaf() and truncate_leaf() leave a root node, so it should // always exist. self.inner .root_node() .expect("must have root node") .data() .hash() .into() } } /// Convert a Block into a zcash_history::NodeData used in the MMR tree. /// /// `sapling_root` is the root of the Sapling note commitment tree of the block. fn block_to_history_node( block: Arc, network: Network, sapling_root: &sapling::tree::Root, ) -> zcash_history::NodeData { let height = block .coinbase_height() .expect("block must have coinbase height during contextual verification"); let branch_id = ConsensusBranchId::current(network, height) .expect("must have branch ID for chain history network upgrades"); let block_hash = block.hash().0; let time: u32 = block .header .time .timestamp() .try_into() .expect("deserialized and generated timestamps are u32 values"); let target = block.header.difficulty_threshold.0; let sapling_root: [u8; 32] = sapling_root.into(); let work = block .header .difficulty_threshold .to_work() .expect("work must be valid during contextual verification"); // There is no direct `std::primitive::u128` to `bigint::U256` conversion let work = bigint::U256::from_big_endian(&work.as_u128().to_be_bytes()); let sapling_tx_count = count_sapling_transactions(block); zcash_history::NodeData { consensus_branch_id: branch_id.into(), subtree_commitment: block_hash, start_time: time, end_time: time, start_target: target, end_target: target, start_sapling_root: sapling_root, end_sapling_root: sapling_root, subtree_total_work: work, start_height: height.0 as u64, end_height: height.0 as u64, sapling_tx: sapling_tx_count, } } /// Count how many Sapling transactions exist in a block, /// i.e. transactions "where either of vSpendsSapling or vOutputsSapling is non-empty" /// (https://zips.z.cash/zip-0221#tree-node-specification). fn count_sapling_transactions(block: Arc) -> u64 { block .transactions .iter() .filter(|tx| tx.has_sapling_shielded_data()) .count() .try_into() .expect("number of transactions must fit u64") }