Skip to main content

zebra_chain/
history_tree.rs

1//! History tree (Merkle mountain range) structure that contains information about
2//! the block history as specified in ZIP-221.
3
4mod tests;
5
6use std::{
7    collections::{BTreeMap, HashSet},
8    io,
9    ops::Deref,
10    sync::Arc,
11};
12
13use thiserror::Error;
14
15use crate::{
16    block::{Block, ChainHistoryMmrRootHash, Height},
17    fmt::SummaryDebug,
18    parameters::{Network, NetworkUpgrade},
19    primitives::zcash_history::{
20        BlockCommitmentTreeRoots, Entry, Tree, V1 as PreOrchard, V2 as OrchardOnward,
21        V3 as IronwoodOnward,
22    },
23};
24
25/// An error describing why a history tree operation failed.
26#[derive(Debug, Error)]
27#[non_exhaustive]
28#[allow(missing_docs)]
29pub enum HistoryTreeError {
30    #[error("zcash_history error: {inner:?}")]
31    #[non_exhaustive]
32    InnerError { inner: zcash_history::Error },
33
34    #[error("I/O error: {0}")]
35    IOError(#[from] io::Error),
36}
37
38impl PartialEq for HistoryTreeError {
39    fn eq(&self, other: &Self) -> bool {
40        // Workaround since subtypes do not implement Eq.
41        // This is only used for tests anyway.
42        format!("{self:?}") == format!("{other:?}")
43    }
44}
45
46impl Eq for HistoryTreeError {}
47
48/// The inner [Tree] in one of its supported versions.
49#[derive(Debug)]
50enum InnerHistoryTree {
51    /// A pre-Orchard tree.
52    PreOrchard(Tree<PreOrchard>),
53    /// An Orchard-onward tree (NU5 to pre-NU6.3).
54    OrchardOnward(Tree<OrchardOnward>),
55    /// An Ironwood-onward tree (NU6.3 onward), which also commits to the Ironwood pool.
56    IronwoodOnward(Tree<IronwoodOnward>),
57}
58
59/// History tree (Merkle mountain range) structure that contains information about
60/// the block history, as specified in [ZIP-221](https://zips.z.cash/zip-0221).
61#[derive(Debug)]
62pub struct NonEmptyHistoryTree {
63    network: Network,
64    network_upgrade: NetworkUpgrade,
65    /// Merkle mountain range tree from `zcash_history`.
66    /// This is a "runtime" structure used to add / remove nodes, and it's not
67    /// persistent.
68    inner: InnerHistoryTree,
69    /// The number of nodes in the tree.
70    size: u32,
71    /// The peaks of the tree, indexed by their position in the array representation
72    /// of the tree. This can be persisted to save the tree.
73    peaks: SummaryDebug<BTreeMap<u32, Entry>>,
74    /// The height of the most recent block added to the tree.
75    current_height: Height,
76}
77
78impl NonEmptyHistoryTree {
79    /// Recreate a [`HistoryTree`] from previously saved data.
80    ///
81    /// The parameters must come from the values of [`NonEmptyHistoryTree::size`],
82    /// [`NonEmptyHistoryTree::peaks`] and [`NonEmptyHistoryTree::current_height`] of a HistoryTree.
83    pub fn from_cache(
84        network: &Network,
85        size: u32,
86        peaks: BTreeMap<u32, Entry>,
87        current_height: Height,
88    ) -> Result<Self, HistoryTreeError> {
89        let network_upgrade = NetworkUpgrade::current(network, current_height);
90        let inner = match network_upgrade {
91            NetworkUpgrade::Genesis
92            | NetworkUpgrade::BeforeOverwinter
93            | NetworkUpgrade::Overwinter
94            | NetworkUpgrade::Sapling
95            | NetworkUpgrade::Blossom => {
96                panic!("HistoryTree does not exist for pre-Heartwood upgrades")
97            }
98            NetworkUpgrade::Heartwood | NetworkUpgrade::Canopy => {
99                let tree = Tree::<PreOrchard>::new_from_cache(
100                    network,
101                    network_upgrade,
102                    size,
103                    &peaks,
104                    &Default::default(),
105                )?;
106                InnerHistoryTree::PreOrchard(tree)
107            }
108            NetworkUpgrade::Nu5
109            | NetworkUpgrade::Nu6
110            | NetworkUpgrade::Nu6_1
111            | NetworkUpgrade::Nu6_2 => {
112                let tree = Tree::<OrchardOnward>::new_from_cache(
113                    network,
114                    network_upgrade,
115                    size,
116                    &peaks,
117                    &Default::default(),
118                )?;
119                InnerHistoryTree::OrchardOnward(tree)
120            }
121            NetworkUpgrade::Nu6_3 | NetworkUpgrade::Nu7 => {
122                let tree = Tree::<IronwoodOnward>::new_from_cache(
123                    network,
124                    network_upgrade,
125                    size,
126                    &peaks,
127                    &Default::default(),
128                )?;
129                InnerHistoryTree::IronwoodOnward(tree)
130            }
131
132            #[cfg(zcash_unstable = "zfuture")]
133            NetworkUpgrade::ZFuture => {
134                let tree = Tree::<IronwoodOnward>::new_from_cache(
135                    network,
136                    network_upgrade,
137                    size,
138                    &peaks,
139                    &Default::default(),
140                )?;
141                InnerHistoryTree::IronwoodOnward(tree)
142            }
143        };
144        Ok(Self {
145            network: network.clone(),
146            network_upgrade,
147            inner,
148            size,
149            peaks: peaks.into(),
150            current_height,
151        })
152    }
153
154    /// Create a new history tree with a single block.
155    ///
156    /// The Orchard root in `roots` is ignored for pre-Orchard blocks, and the Ironwood root for
157    /// pre-NU6.3 blocks; each inner tree version only reads the roots it commits to.
158    #[allow(clippy::unwrap_in_result)]
159    pub fn from_block(
160        network: &Network,
161        block: Arc<Block>,
162        roots: BlockCommitmentTreeRoots,
163    ) -> Result<Self, HistoryTreeError> {
164        let height = block
165            .coinbase_height()
166            .expect("block must have coinbase height during contextual verification");
167        let network_upgrade = NetworkUpgrade::current(network, height);
168        let (tree, entry) = match network_upgrade {
169            NetworkUpgrade::Genesis
170            | NetworkUpgrade::BeforeOverwinter
171            | NetworkUpgrade::Overwinter
172            | NetworkUpgrade::Sapling
173            | NetworkUpgrade::Blossom => {
174                panic!("HistoryTree does not exist for pre-Heartwood upgrades")
175            }
176            NetworkUpgrade::Heartwood | NetworkUpgrade::Canopy => {
177                let (tree, entry) = Tree::<PreOrchard>::new_from_block(network, block, roots)?;
178                (InnerHistoryTree::PreOrchard(tree), entry)
179            }
180            NetworkUpgrade::Nu5
181            | NetworkUpgrade::Nu6
182            | NetworkUpgrade::Nu6_1
183            | NetworkUpgrade::Nu6_2 => {
184                let (tree, entry) = Tree::<OrchardOnward>::new_from_block(network, block, roots)?;
185                (InnerHistoryTree::OrchardOnward(tree), entry)
186            }
187            NetworkUpgrade::Nu6_3 | NetworkUpgrade::Nu7 => {
188                let (tree, entry) = Tree::<IronwoodOnward>::new_from_block(network, block, roots)?;
189                (InnerHistoryTree::IronwoodOnward(tree), entry)
190            }
191
192            #[cfg(zcash_unstable = "zfuture")]
193            NetworkUpgrade::ZFuture => {
194                let (tree, entry) = Tree::<IronwoodOnward>::new_from_block(network, block, roots)?;
195                (InnerHistoryTree::IronwoodOnward(tree), entry)
196            }
197        };
198        let mut peaks = BTreeMap::new();
199        peaks.insert(0u32, entry);
200        Ok(NonEmptyHistoryTree {
201            network: network.clone(),
202            network_upgrade,
203            inner: tree,
204            size: 1,
205            peaks: peaks.into(),
206            current_height: height,
207        })
208    }
209
210    /// Add block data to the tree.
211    ///
212    /// The Orchard root in `roots` is ignored for pre-Orchard blocks, and the Ironwood root for
213    /// pre-NU6.3 blocks.
214    ///
215    /// # Panics
216    ///
217    /// If the block height is not one more than the previously pushed block.
218    #[allow(clippy::unwrap_in_result)]
219    pub fn push(
220        &mut self,
221        block: Arc<Block>,
222        roots: BlockCommitmentTreeRoots,
223    ) -> Result<(), HistoryTreeError> {
224        // Check if the block has the expected height.
225        // librustzcash assumes the heights are correct and corrupts the tree if they are wrong,
226        // resulting in a confusing error, which we prevent here.
227        let height = block
228            .coinbase_height()
229            .expect("block must have coinbase height during contextual verification");
230
231        assert!(
232            Some(height) == self.current_height + 1,
233            "added block with height {:?} but it must be {:?}+1",
234            height,
235            self.current_height
236        );
237
238        let network_upgrade = NetworkUpgrade::current(&self.network, height);
239        if network_upgrade != self.network_upgrade {
240            // This is the activation block of a network upgrade.
241            // Create a new tree.
242            let new_tree = Self::from_block(&self.network, block, roots)?;
243            // Replaces self with the new tree
244            *self = new_tree;
245            assert_eq!(self.network_upgrade, network_upgrade);
246            return Ok(());
247        }
248
249        let new_entries = match &mut self.inner {
250            InnerHistoryTree::PreOrchard(tree) => tree
251                .append_leaf(block, roots)
252                .map_err(|e| HistoryTreeError::InnerError { inner: e })?,
253            InnerHistoryTree::OrchardOnward(tree) => tree
254                .append_leaf(block, roots)
255                .map_err(|e| HistoryTreeError::InnerError { inner: e })?,
256            InnerHistoryTree::IronwoodOnward(tree) => tree
257                .append_leaf(block, roots)
258                .map_err(|e| HistoryTreeError::InnerError { inner: e })?,
259        };
260        for entry in new_entries {
261            // Not every entry is a peak; those will be trimmed later
262            self.peaks.insert(self.size, entry);
263            self.size += 1;
264        }
265        self.prune()?;
266        self.current_height = height;
267        Ok(())
268    }
269
270    /// Extend the history tree with the given blocks.
271    pub fn try_extend<'a, T: IntoIterator<Item = (Arc<Block>, BlockCommitmentTreeRoots<'a>)>>(
272        &mut self,
273        iter: T,
274    ) -> Result<(), HistoryTreeError> {
275        for (block, roots) in iter {
276            self.push(block, roots)?;
277        }
278        Ok(())
279    }
280
281    /// Prune tree, removing all non-peak entries.
282    fn prune(&mut self) -> Result<(), io::Error> {
283        // Go through all the peaks of the tree.
284        // This code is based on a librustzcash example:
285        // https://github.com/zcash/librustzcash/blob/02052526925fba9389f1428d6df254d4dec967e6/zcash_history/examples/long.rs
286        // The explanation of how it works is from zcashd:
287        // https://github.com/zcash/zcash/blob/0247c0c682d59184a717a6536edb0d18834be9a7/src/coins.cpp#L351
288
289        let mut peak_pos_set = HashSet::new();
290
291        // Assume the following example peak layout with 14 leaves, and 25 stored nodes in
292        // total (the "tree length"):
293        //
294        //             P
295        //            /\
296        //           /  \
297        //          / \  \
298        //        /    \  \  Altitude
299        //     _A_      \  \    3
300        //   _/   \_     B  \   2
301        //  / \   / \   / \  C  1
302        // /\ /\ /\ /\ /\ /\ /\ 0
303        //
304        // We start by determining the altitude of the highest peak (A).
305        let mut alt = (32 - (self.size + 1).leading_zeros() - 1) - 1;
306
307        // We determine the position of the highest peak (A) by pretending it is the right
308        // sibling in a tree, and its left-most leaf has position 0. Then the left sibling
309        // of (A) has position -1, and so we can "jump" to the peak's position by computing
310        // -1 + 2^(alt + 1) - 1.
311        let mut peak_pos = (1 << (alt + 1)) - 2;
312
313        // Now that we have the position and altitude of the highest peak (A), we collect
314        // the remaining peaks (B, C). We navigate the peaks as if they were nodes in this
315        // Merkle tree (with additional imaginary nodes 1 and 2, that have positions beyond
316        // the MMR's length):
317        //
318        //             / \
319        //            /   \
320        //           /     \
321        //         /         \
322        //       A ==========> 1
323        //      / \          //  \
324        //    _/   \_       B ==> 2
325        //   /\     /\     /\    //
326        //  /  \   /  \   /  \   C
327        // /\  /\ /\  /\ /\  /\ /\
328        //
329        loop {
330            // If peak_pos is out of bounds of the tree, we compute the position of its left
331            // child, and drop down one level in the tree.
332            if peak_pos >= self.size {
333                // left child, -2^alt
334                peak_pos -= 1 << alt;
335                alt -= 1;
336            }
337
338            // If the peak exists, we take it and then continue with its right sibling.
339            if peak_pos < self.size {
340                // There is a peak at index `peak_pos`
341                peak_pos_set.insert(peak_pos);
342
343                // right sibling
344                peak_pos = peak_pos + (1 << (alt + 1)) - 1;
345            }
346
347            if alt == 0 {
348                break;
349            }
350        }
351
352        // Remove all non-peak entries
353        self.peaks.retain(|k, _| peak_pos_set.contains(k));
354        // Rebuild tree
355        self.inner = self.rebuilt_inner()?;
356        Ok(())
357    }
358
359    /// Rebuilds the inner tree from the cached `network`, `network_upgrade`, `size`, and `peaks`.
360    ///
361    /// Shared by [`Self::prune`] and the [`Clone`] impl, which reconstruct the inner tree
362    /// identically and differ only in how they handle the (practically impossible) rebuild error.
363    fn rebuilt_inner(&self) -> Result<InnerHistoryTree, io::Error> {
364        Ok(match &self.inner {
365            InnerHistoryTree::PreOrchard(_) => {
366                InnerHistoryTree::PreOrchard(Tree::<PreOrchard>::new_from_cache(
367                    &self.network,
368                    self.network_upgrade,
369                    self.size,
370                    &self.peaks,
371                    &Default::default(),
372                )?)
373            }
374            InnerHistoryTree::OrchardOnward(_) => {
375                InnerHistoryTree::OrchardOnward(Tree::<OrchardOnward>::new_from_cache(
376                    &self.network,
377                    self.network_upgrade,
378                    self.size,
379                    &self.peaks,
380                    &Default::default(),
381                )?)
382            }
383            InnerHistoryTree::IronwoodOnward(_) => {
384                InnerHistoryTree::IronwoodOnward(Tree::<IronwoodOnward>::new_from_cache(
385                    &self.network,
386                    self.network_upgrade,
387                    self.size,
388                    &self.peaks,
389                    &Default::default(),
390                )?)
391            }
392        })
393    }
394
395    /// Return the hash of the tree root.
396    pub fn hash(&self) -> ChainHistoryMmrRootHash {
397        match &self.inner {
398            InnerHistoryTree::PreOrchard(tree) => tree.hash(),
399            InnerHistoryTree::OrchardOnward(tree) => tree.hash(),
400            InnerHistoryTree::IronwoodOnward(tree) => tree.hash(),
401        }
402    }
403
404    /// Return the peaks of the tree.
405    pub fn peaks(&self) -> &BTreeMap<u32, Entry> {
406        &self.peaks
407    }
408
409    /// Return the (total) number of nodes in the tree.
410    pub fn size(&self) -> u32 {
411        self.size
412    }
413
414    /// Return the height of the last added block.
415    pub fn current_height(&self) -> Height {
416        self.current_height
417    }
418
419    /// Return the network where this tree is used.
420    pub fn network(&self) -> &Network {
421        &self.network
422    }
423}
424
425impl Clone for NonEmptyHistoryTree {
426    fn clone(&self) -> Self {
427        let tree = self
428            .rebuilt_inner()
429            .expect("rebuilding an existing tree should always work");
430        NonEmptyHistoryTree {
431            network: self.network.clone(),
432            network_upgrade: self.network_upgrade,
433            inner: tree,
434            size: self.size,
435            peaks: self.peaks.clone(),
436            current_height: self.current_height,
437        }
438    }
439}
440
441/// A History Tree that keeps track of its own creation in the Heartwood
442/// activation block, being empty beforehand.
443#[derive(Debug, Default, Clone)]
444pub struct HistoryTree(Option<NonEmptyHistoryTree>);
445
446impl HistoryTree {
447    /// Create a HistoryTree from a block.
448    ///
449    /// If the block is pre-Heartwood, it returns an empty history tree.
450    #[allow(clippy::unwrap_in_result)]
451    pub fn from_block(
452        network: &Network,
453        block: Arc<Block>,
454        roots: BlockCommitmentTreeRoots,
455    ) -> Result<Self, HistoryTreeError> {
456        let Some(heartwood_height) = NetworkUpgrade::Heartwood.activation_height(network) else {
457            // Return early if there is no Heartwood activation height.
458            return Ok(HistoryTree(None));
459        };
460
461        match block
462            .coinbase_height()
463            .expect("must have height")
464            .cmp(&heartwood_height)
465        {
466            std::cmp::Ordering::Less => Ok(HistoryTree(None)),
467            _ => Ok(NonEmptyHistoryTree::from_block(network, block, roots)?.into()),
468        }
469    }
470
471    /// Push a block to a maybe-existing HistoryTree, handling network upgrades.
472    ///
473    /// The tree is updated in-place. It is created when pushing the Heartwood
474    /// activation block.
475    #[allow(clippy::unwrap_in_result)]
476    pub fn push(
477        &mut self,
478        network: &Network,
479        block: Arc<Block>,
480        roots: BlockCommitmentTreeRoots,
481    ) -> Result<(), HistoryTreeError> {
482        let Some(heartwood_height) = NetworkUpgrade::Heartwood.activation_height(network) else {
483            assert!(
484                self.0.is_none(),
485                "history tree must not exist pre-Heartwood"
486            );
487
488            return Ok(());
489        };
490
491        match block
492            .coinbase_height()
493            .expect("must have height")
494            .cmp(&heartwood_height)
495        {
496            std::cmp::Ordering::Less => {
497                assert!(
498                    self.0.is_none(),
499                    "history tree must not exist pre-Heartwood"
500                );
501            }
502            std::cmp::Ordering::Equal => {
503                let tree = Some(NonEmptyHistoryTree::from_block(network, block, roots)?);
504                // Replace the current object with the new tree
505                *self = HistoryTree(tree);
506            }
507            std::cmp::Ordering::Greater => {
508                self.0
509                    .as_mut()
510                    .expect("history tree must exist Heartwood-onward")
511                    .push(block.clone(), roots)?;
512            }
513        };
514        Ok(())
515    }
516
517    /// Return the hash of the tree root if the tree is not empty.
518    pub fn hash(&self) -> Option<ChainHistoryMmrRootHash> {
519        Some(self.0.as_ref()?.hash())
520    }
521}
522
523impl From<NonEmptyHistoryTree> for HistoryTree {
524    fn from(tree: NonEmptyHistoryTree) -> Self {
525        HistoryTree(Some(tree))
526    }
527}
528
529impl From<Option<NonEmptyHistoryTree>> for HistoryTree {
530    fn from(tree: Option<NonEmptyHistoryTree>) -> Self {
531        HistoryTree(tree)
532    }
533}
534
535impl Deref for HistoryTree {
536    type Target = Option<NonEmptyHistoryTree>;
537    fn deref(&self) -> &Self::Target {
538        &self.0
539    }
540}
541
542impl PartialEq for HistoryTree {
543    fn eq(&self, other: &Self) -> bool {
544        self.as_ref().map(|tree| tree.hash()) == other.as_ref().map(|other_tree| other_tree.hash())
545    }
546}
547
548impl Eq for HistoryTree {}