Skip to main content

zebra_chain/primitives/
zcash_history.rs

1//! Contains code that interfaces with the zcash_history crate from
2//! librustzcash.
3
4// TODO: remove after this module gets to be used
5#![allow(missing_docs)]
6
7mod tests;
8
9use std::{collections::BTreeMap, io, sync::Arc};
10
11use serde_big_array::BigArray;
12pub use zcash_history::{V1, V2, V3};
13
14use crate::{
15    block::{Block, ChainHistoryMmrRootHash},
16    orchard,
17    parameters::{Network, NetworkUpgrade},
18    sapling,
19};
20
21/// The note commitment tree roots of a block, used to construct its chain-history leaf.
22///
23/// The roots are grouped in a named struct rather than passed as adjacent positional arguments so
24/// the Orchard and Ironwood roots — which reuse the same [`orchard::tree::Root`] type — cannot be
25/// silently swapped, which would corrupt the ZIP-221 chain-history commitment.
26#[derive(Clone, Copy)]
27pub struct BlockCommitmentTreeRoots<'a> {
28    /// The root of the block's Sapling note commitment tree.
29    pub sapling: &'a sapling::tree::Root,
30    /// The root of the block's Orchard note commitment tree.
31    pub orchard: &'a orchard::tree::Root,
32    /// The root of the block's Ironwood note commitment tree.
33    pub ironwood: &'a orchard::tree::Root,
34}
35
36/// A trait to represent a version of `Tree`.
37pub trait Version: zcash_history::Version {
38    /// Convert a Block into the NodeData for this version.
39    ///
40    /// The Ironwood root in `roots` is ignored by all versions before V3 (NU6.3).
41    fn block_to_history_node(
42        block: Arc<Block>,
43        network: &Network,
44        roots: BlockCommitmentTreeRoots,
45    ) -> Self::NodeData;
46}
47
48/// A MMR Tree using zcash_history::Tree.
49///
50/// Currently it should not be used as a long-term data structure because it
51/// may grow without limits.
52pub struct Tree<V: zcash_history::Version> {
53    network: Network,
54    network_upgrade: NetworkUpgrade,
55    inner: zcash_history::Tree<V>,
56}
57
58/// An encoded tree node data.
59pub struct NodeData {
60    inner: [u8; zcash_history::MAX_NODE_DATA_SIZE],
61}
62
63impl From<&zcash_history::NodeData> for NodeData {
64    /// Convert from librustzcash.
65    fn from(inner_node: &zcash_history::NodeData) -> Self {
66        let mut node = NodeData {
67            inner: [0; zcash_history::MAX_NODE_DATA_SIZE],
68        };
69        inner_node
70            .write(&mut &mut node.inner[..])
71            .expect("buffer has the proper size");
72        node
73    }
74}
75
76/// An encoded entry in the tree.
77///
78/// Contains the node data and information about its position in the tree.
79#[derive(Clone, Debug, Serialize, Deserialize)]
80pub struct Entry {
81    #[serde(with = "BigArray")]
82    inner: [u8; zcash_history::MAX_ENTRY_SIZE],
83}
84
85impl Entry {
86    /// Reconstructs an [`Entry`] from raw serialized bytes written by an earlier database
87    /// format, zero-padding (or truncating) to the current [`zcash_history::MAX_ENTRY_SIZE`].
88    ///
89    /// The on-disk width of an entry is `zcash_history::MAX_ENTRY_SIZE`, which grew when NU6.3
90    /// (Ironwood) added fields to `zcash_history::NodeData`. Earlier formats stored the same node
91    /// data in a narrower array with fewer trailing zero bytes, so copying the stored prefix into
92    /// a zeroed current-width array preserves the entry. This is only used by the finalized
93    /// state's backward-compatible history-tree deserialization; new entries are written at the
94    /// current width.
95    pub fn from_raw_bytes_padded(bytes: &[u8]) -> Self {
96        let mut inner = [0; zcash_history::MAX_ENTRY_SIZE];
97        let len = bytes.len().min(inner.len());
98        inner[..len].copy_from_slice(&bytes[..len]);
99        Entry { inner }
100    }
101
102    /// Create a leaf Entry for the given block, its network, and the roots of its
103    /// note commitment trees.
104    fn new_leaf<V: Version>(
105        block: Arc<Block>,
106        network: &Network,
107        roots: BlockCommitmentTreeRoots,
108    ) -> Self {
109        let node_data = V::block_to_history_node(block, network, roots);
110        let inner_entry = zcash_history::Entry::<V>::new_leaf(node_data);
111        let mut entry = Entry {
112            inner: [0; zcash_history::MAX_ENTRY_SIZE],
113        };
114        inner_entry
115            .write(&mut &mut entry.inner[..])
116            .expect("buffer has the proper size");
117        entry
118    }
119}
120
121impl<V: Version> Tree<V> {
122    /// Create a MMR tree with the given length from the given cache of nodes.
123    ///
124    /// The `peaks` are the peaks of the MMR tree to build and their position in the
125    /// array representation of the tree.
126    /// The `extra` are extra nodes that enable removing nodes from the tree, and their position.
127    ///
128    /// Note that the length is usually larger than the length of `peaks` and `extra`, since
129    /// you don't need to pass every node, just the peaks of the tree (plus extra).
130    ///
131    /// # Panics
132    ///
133    /// Will panic if `peaks` is empty.
134    #[allow(clippy::unwrap_in_result)]
135    pub fn new_from_cache(
136        network: &Network,
137        network_upgrade: NetworkUpgrade,
138        length: u32,
139        peaks: &BTreeMap<u32, Entry>,
140        extra: &BTreeMap<u32, Entry>,
141    ) -> Result<Self, io::Error> {
142        let branch_id = network_upgrade
143            .branch_id()
144            .expect("unexpected pre-Overwinter MMR history tree");
145        let mut peaks_vec = Vec::new();
146        for (idx, entry) in peaks {
147            let inner_entry = zcash_history::Entry::from_bytes(branch_id.into(), entry.inner)?;
148            peaks_vec.push((*idx, inner_entry));
149        }
150        let mut extra_vec = Vec::new();
151        for (idx, entry) in extra {
152            let inner_entry = zcash_history::Entry::from_bytes(branch_id.into(), entry.inner)?;
153            extra_vec.push((*idx, inner_entry));
154        }
155        let inner = zcash_history::Tree::new(length, peaks_vec, extra_vec);
156        Ok(Tree {
157            network: network.clone(),
158            network_upgrade,
159            inner,
160        })
161    }
162
163    /// Create a single-node MMR tree for the given block.
164    ///
165    /// The Ironwood root in `roots` is ignored for V1/V2 trees.
166    #[allow(clippy::unwrap_in_result)]
167    pub fn new_from_block(
168        network: &Network,
169        block: Arc<Block>,
170        roots: BlockCommitmentTreeRoots,
171    ) -> Result<(Self, Entry), io::Error> {
172        let height = block
173            .coinbase_height()
174            .expect("block must have coinbase height during contextual verification");
175        let network_upgrade = NetworkUpgrade::current(network, height);
176        let entry0 = Entry::new_leaf::<V>(block, network, roots);
177        let mut peaks = BTreeMap::new();
178        peaks.insert(0u32, entry0);
179        Ok((
180            Tree::new_from_cache(network, network_upgrade, 1, &peaks, &BTreeMap::new())?,
181            peaks
182                .remove(&0u32)
183                .expect("must work since it was just added"),
184        ))
185    }
186
187    /// Append a new block to the tree, as a new leaf.
188    ///
189    /// The Ironwood root in `roots` is ignored for V1/V2 trees.
190    ///
191    /// Returns a vector of nodes added to the tree (leaf + internal nodes).
192    ///
193    /// # Panics
194    ///
195    /// Panics if the network upgrade of the given block is different from
196    /// the network upgrade of the other blocks in the tree.
197    #[allow(clippy::unwrap_in_result)]
198    pub fn append_leaf(
199        &mut self,
200        block: Arc<Block>,
201        roots: BlockCommitmentTreeRoots,
202    ) -> Result<Vec<Entry>, zcash_history::Error> {
203        let height = block
204            .coinbase_height()
205            .expect("block must have coinbase height during contextual verification");
206        let network_upgrade = NetworkUpgrade::current(&self.network, height);
207
208        assert!(
209            network_upgrade == self.network_upgrade,
210            "added block from network upgrade {:?} but history tree is restricted to {:?}",
211            network_upgrade,
212            self.network_upgrade
213        );
214
215        let node_data = V::block_to_history_node(block, &self.network, roots);
216        let appended = self.inner.append_leaf(node_data)?;
217
218        let mut new_nodes = Vec::new();
219        for entry_link in appended {
220            let mut entry = Entry {
221                inner: [0; zcash_history::MAX_ENTRY_SIZE],
222            };
223            self.inner
224                .resolve_link(entry_link)
225                .expect("entry was just generated so it must be valid")
226                .node()
227                .write(&mut &mut entry.inner[..])
228                .expect("buffer was created with enough capacity");
229            new_nodes.push(entry);
230        }
231        Ok(new_nodes)
232    }
233    /// Return the root hash of the tree, i.e. `hashChainHistoryRoot`.
234    pub fn hash(&self) -> ChainHistoryMmrRootHash {
235        // Both append_leaf() and truncate_leaf() leave a root node, so it should
236        // always exist.
237        V::hash(self.inner.root_node().expect("must have root node").data()).into()
238    }
239}
240
241impl<V: zcash_history::Version> std::fmt::Debug for Tree<V> {
242    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243        f.debug_struct("Tree")
244            .field("network", &self.network)
245            .field("network_upgrade", &self.network_upgrade)
246            .finish()
247    }
248}
249
250impl Version for zcash_history::V1 {
251    /// Convert a Block into a V1::NodeData used in the MMR tree.
252    ///
253    /// Only the Sapling root in `roots` is used; the Orchard and Ironwood roots are ignored.
254    fn block_to_history_node(
255        block: Arc<Block>,
256        network: &Network,
257        roots: BlockCommitmentTreeRoots,
258    ) -> Self::NodeData {
259        let sapling_root = roots.sapling;
260        let height = block
261            .coinbase_height()
262            .expect("block must have coinbase height during contextual verification");
263        let network_upgrade = NetworkUpgrade::current(network, height);
264        let branch_id = network_upgrade
265            .branch_id()
266            .expect("must have branch ID for chain history network upgrades");
267        let block_hash = block.hash().0;
268        let time: u32 = block
269            .header
270            .time
271            .timestamp()
272            .try_into()
273            .expect("deserialized and generated timestamps are u32 values");
274        let target = block.header.difficulty_threshold.0;
275        let sapling_root: [u8; 32] = sapling_root.into();
276        let work = block
277            .header
278            .difficulty_threshold
279            .to_work()
280            .expect("work must be valid during contextual verification");
281        // There is no direct `std::primitive::u128` to `bigint::U256` conversion
282        let work = primitive_types::U256::from_big_endian(&work.as_u128().to_be_bytes());
283
284        let sapling_tx_count = block.sapling_transactions_count();
285
286        match network_upgrade {
287            NetworkUpgrade::Genesis
288            | NetworkUpgrade::BeforeOverwinter
289            | NetworkUpgrade::Overwinter
290            | NetworkUpgrade::Sapling
291            | NetworkUpgrade::Blossom => {
292                panic!("HistoryTree does not exist for pre-Heartwood upgrades")
293            }
294            // Nu5 is included because this function is called by the V2 implementation
295            // since the V1::NodeData is included inside the V2::NodeData.
296            NetworkUpgrade::Heartwood
297            | NetworkUpgrade::Canopy
298            | NetworkUpgrade::Nu5
299            | NetworkUpgrade::Nu6
300            | NetworkUpgrade::Nu6_1
301            | NetworkUpgrade::Nu6_2
302            | NetworkUpgrade::Nu6_3
303            | NetworkUpgrade::Nu7 => {}
304            #[cfg(zcash_unstable = "zfuture")]
305            NetworkUpgrade::ZFuture => {}
306        };
307
308        zcash_history::NodeData {
309            consensus_branch_id: branch_id.into(),
310            subtree_commitment: block_hash,
311            start_time: time,
312            end_time: time,
313            start_target: target,
314            end_target: target,
315            start_sapling_root: sapling_root,
316            end_sapling_root: sapling_root,
317            subtree_total_work: work,
318            start_height: height.0 as u64,
319            end_height: height.0 as u64,
320            sapling_tx: sapling_tx_count,
321        }
322    }
323}
324
325impl Version for V2 {
326    /// Convert a Block into a V2::NodeData used in the MMR tree.
327    ///
328    /// The Sapling and Orchard roots in `roots` are used; the Ironwood root is ignored.
329    fn block_to_history_node(
330        block: Arc<Block>,
331        network: &Network,
332        roots: BlockCommitmentTreeRoots,
333    ) -> Self::NodeData {
334        let orchard_tx_count = block.orchard_transactions_count();
335        let node_data_v1 = V1::block_to_history_node(block, network, roots);
336        let orchard_root: [u8; 32] = roots.orchard.into();
337        Self::NodeData {
338            v1: node_data_v1,
339            start_orchard_root: orchard_root,
340            end_orchard_root: orchard_root,
341            orchard_tx: orchard_tx_count,
342        }
343    }
344}
345
346impl Version for V3 {
347    /// Convert a Block into a V3::NodeData used in the MMR tree (NU6.3 onward).
348    ///
349    /// Extends the V2 node data with the Ironwood note commitment tree root and the count of
350    /// transactions containing an Ironwood bundle.
351    fn block_to_history_node(
352        block: Arc<Block>,
353        network: &Network,
354        roots: BlockCommitmentTreeRoots,
355    ) -> Self::NodeData {
356        let ironwood_tx_count = block.ironwood_transactions_count();
357        let node_data_v2 = V2::block_to_history_node(block, network, roots);
358        let ironwood_root: [u8; 32] = roots.ironwood.into();
359        Self::NodeData {
360            v2: node_data_v2,
361            start_ironwood_root: ironwood_root,
362            end_ironwood_root: ironwood_root,
363            ironwood_tx: ironwood_tx_count,
364        }
365    }
366}