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