#![allow(missing_docs)]
mod tests;
use std::{collections::BTreeMap, io, sync::Arc};
use serde_big_array::BigArray;
pub use zcash_history::{V1, V2};
use crate::{
block::{Block, ChainHistoryMmrRootHash},
orchard,
parameters::{Network, NetworkUpgrade},
sapling,
};
pub trait Version: zcash_history::Version {
fn block_to_history_node(
block: Arc<Block>,
network: &Network,
sapling_root: &sapling::tree::Root,
orchard_root: &orchard::tree::Root,
) -> Self::NodeData;
}
pub struct Tree<V: zcash_history::Version> {
network: Network,
network_upgrade: NetworkUpgrade,
inner: zcash_history::Tree<V>,
}
pub struct NodeData {
inner: [u8; zcash_history::MAX_NODE_DATA_SIZE],
}
impl From<&zcash_history::NodeData> for NodeData {
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
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Entry {
#[serde(with = "BigArray")]
inner: [u8; zcash_history::MAX_ENTRY_SIZE],
}
impl Entry {
fn new_leaf<V: Version>(
block: Arc<Block>,
network: &Network,
sapling_root: &sapling::tree::Root,
orchard_root: &orchard::tree::Root,
) -> Self {
let node_data = V::block_to_history_node(block, network, sapling_root, orchard_root);
let inner_entry = zcash_history::Entry::<V>::new_leaf(node_data);
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<V: Version> Tree<V> {
#[allow(clippy::unwrap_in_result)]
pub fn new_from_cache(
network: &Network,
network_upgrade: NetworkUpgrade,
length: u32,
peaks: &BTreeMap<u32, Entry>,
extra: &BTreeMap<u32, Entry>,
) -> Result<Self, io::Error> {
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.clone(),
network_upgrade,
inner,
})
}
#[allow(clippy::unwrap_in_result)]
pub fn new_from_block(
network: &Network,
block: Arc<Block>,
sapling_root: &sapling::tree::Root,
orchard_root: &orchard::tree::Root,
) -> Result<(Self, Entry), io::Error> {
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::<V>(block, network, sapling_root, orchard_root);
let mut peaks = BTreeMap::new();
peaks.insert(0u32, entry0);
Ok((
Tree::new_from_cache(network, network_upgrade, 1, &peaks, &BTreeMap::new())?,
peaks
.remove(&0u32)
.expect("must work since it was just added"),
))
}
#[allow(clippy::unwrap_in_result)]
pub fn append_leaf(
&mut self,
block: Arc<Block>,
sapling_root: &sapling::tree::Root,
orchard_root: &orchard::tree::Root,
) -> Result<Vec<Entry>, 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);
assert!(
network_upgrade == self.network_upgrade,
"added block from network upgrade {:?} but history tree is restricted to {:?}",
network_upgrade,
self.network_upgrade
);
let node_data = V::block_to_history_node(block, &self.network, sapling_root, orchard_root);
let appended = self.inner.append_leaf(node_data)?;
let mut new_nodes = Vec::new();
for entry_link in appended {
let mut entry = Entry {
inner: [0; zcash_history::MAX_ENTRY_SIZE],
};
self.inner
.resolve_link(entry_link)
.expect("entry was just generated so it must be valid")
.node()
.write(&mut &mut entry.inner[..])
.expect("buffer was created with enough capacity");
new_nodes.push(entry);
}
Ok(new_nodes)
}
pub fn hash(&self) -> ChainHistoryMmrRootHash {
V::hash(self.inner.root_node().expect("must have root node").data()).into()
}
}
impl<V: zcash_history::Version> std::fmt::Debug for Tree<V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Tree")
.field("network", &self.network)
.field("network_upgrade", &self.network_upgrade)
.finish()
}
}
impl Version for zcash_history::V1 {
fn block_to_history_node(
block: Arc<Block>,
network: &Network,
sapling_root: &sapling::tree::Root,
_orchard_root: &orchard::tree::Root,
) -> Self::NodeData {
let height = block
.coinbase_height()
.expect("block must have coinbase height during contextual verification");
let network_upgrade = NetworkUpgrade::current(network, height);
let branch_id = network_upgrade
.branch_id()
.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");
let work = primitive_types::U256::from_big_endian(&work.as_u128().to_be_bytes());
let sapling_tx_count = block.sapling_transactions_count();
match network_upgrade {
NetworkUpgrade::Genesis
| NetworkUpgrade::BeforeOverwinter
| NetworkUpgrade::Overwinter
| NetworkUpgrade::Sapling
| NetworkUpgrade::Blossom => {
panic!("HistoryTree does not exist for pre-Heartwood upgrades")
}
NetworkUpgrade::Heartwood
| NetworkUpgrade::Canopy
| NetworkUpgrade::Nu5
| NetworkUpgrade::Nu6 => 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,
},
}
}
}
impl Version for V2 {
fn block_to_history_node(
block: Arc<Block>,
network: &Network,
sapling_root: &sapling::tree::Root,
orchard_root: &orchard::tree::Root,
) -> Self::NodeData {
let orchard_tx_count = block.orchard_transactions_count();
let node_data_v1 = V1::block_to_history_node(block, network, sapling_root, orchard_root);
let orchard_root: [u8; 32] = orchard_root.into();
Self::NodeData {
v1: node_data_v1,
start_orchard_root: orchard_root,
end_orchard_root: orchard_root,
orchard_tx: orchard_tx_count,
}
}
}