zebra_state/service/finalized_state/zebra_db/
shielded.rs

1//! Provides high-level access to database shielded:
2//! - nullifiers
3//! - note commitment trees
4//! - anchors
5//!
6//! This module makes sure that:
7//! - all disk writes happen inside a RocksDB transaction, and
8//! - format-specific invariants are maintained.
9//!
10//! # Correctness
11//!
12//! [`crate::constants::state_database_format_version_in_code()`] must be incremented
13//! each time the database format (column, serialization, etc) changes.
14
15use std::{
16    collections::{BTreeMap, HashMap},
17    sync::Arc,
18};
19
20use zebra_chain::{
21    block::Height,
22    orchard,
23    parallel::tree::NoteCommitmentTrees,
24    sapling, sprout,
25    subtree::{NoteCommitmentSubtreeData, NoteCommitmentSubtreeIndex},
26    transaction::Transaction,
27};
28
29use crate::{
30    request::{FinalizedBlock, Treestate},
31    service::finalized_state::{
32        disk_db::{DiskWriteBatch, ReadDisk, WriteDisk},
33        disk_format::RawBytes,
34        zebra_db::ZebraDb,
35    },
36    BoxError, TransactionLocation,
37};
38
39// Doc-only items
40#[allow(unused_imports)]
41use zebra_chain::subtree::NoteCommitmentSubtree;
42
43impl ZebraDb {
44    // Read shielded methods
45
46    /// Returns `true` if the finalized state contains `sprout_nullifier`.
47    pub fn contains_sprout_nullifier(&self, sprout_nullifier: &sprout::Nullifier) -> bool {
48        let sprout_nullifiers = self.db.cf_handle("sprout_nullifiers").unwrap();
49        self.db.zs_contains(&sprout_nullifiers, &sprout_nullifier)
50    }
51
52    /// Returns `true` if the finalized state contains `sapling_nullifier`.
53    pub fn contains_sapling_nullifier(&self, sapling_nullifier: &sapling::Nullifier) -> bool {
54        let sapling_nullifiers = self.db.cf_handle("sapling_nullifiers").unwrap();
55        self.db.zs_contains(&sapling_nullifiers, &sapling_nullifier)
56    }
57
58    /// Returns `true` if the finalized state contains `orchard_nullifier`.
59    pub fn contains_orchard_nullifier(&self, orchard_nullifier: &orchard::Nullifier) -> bool {
60        let orchard_nullifiers = self.db.cf_handle("orchard_nullifiers").unwrap();
61        self.db.zs_contains(&orchard_nullifiers, &orchard_nullifier)
62    }
63
64    /// Returns the [`TransactionLocation`] of the transaction that revealed
65    /// the given [`sprout::Nullifier`], if it is revealed in the finalized state and its
66    /// spending transaction hash has been indexed.
67    #[allow(clippy::unwrap_in_result)]
68    pub fn sprout_revealing_tx_loc(
69        &self,
70        sprout_nullifier: &sprout::Nullifier,
71    ) -> Option<TransactionLocation> {
72        let sprout_nullifiers = self.db.cf_handle("sprout_nullifiers").unwrap();
73        self.db.zs_get(&sprout_nullifiers, &sprout_nullifier)?
74    }
75
76    /// Returns the [`TransactionLocation`] of the transaction that revealed
77    /// the given [`sapling::Nullifier`], if it is revealed in the finalized state and its
78    /// spending transaction hash has been indexed.
79    #[allow(clippy::unwrap_in_result)]
80    pub fn sapling_revealing_tx_loc(
81        &self,
82        sapling_nullifier: &sapling::Nullifier,
83    ) -> Option<TransactionLocation> {
84        let sapling_nullifiers = self.db.cf_handle("sapling_nullifiers").unwrap();
85        self.db.zs_get(&sapling_nullifiers, &sapling_nullifier)?
86    }
87
88    /// Returns the [`TransactionLocation`] of the transaction that revealed
89    /// the given [`orchard::Nullifier`], if it is revealed in the finalized state and its
90    /// spending transaction hash has been indexed.
91    #[allow(clippy::unwrap_in_result)]
92    pub fn orchard_revealing_tx_loc(
93        &self,
94        orchard_nullifier: &orchard::Nullifier,
95    ) -> Option<TransactionLocation> {
96        let orchard_nullifiers = self.db.cf_handle("orchard_nullifiers").unwrap();
97        self.db.zs_get(&orchard_nullifiers, &orchard_nullifier)?
98    }
99
100    /// Returns `true` if the finalized state contains `sprout_anchor`.
101    #[allow(dead_code)]
102    pub fn contains_sprout_anchor(&self, sprout_anchor: &sprout::tree::Root) -> bool {
103        let sprout_anchors = self.db.cf_handle("sprout_anchors").unwrap();
104        self.db.zs_contains(&sprout_anchors, &sprout_anchor)
105    }
106
107    /// Returns `true` if the finalized state contains `sapling_anchor`.
108    pub fn contains_sapling_anchor(&self, sapling_anchor: &sapling::tree::Root) -> bool {
109        let sapling_anchors = self.db.cf_handle("sapling_anchors").unwrap();
110        self.db.zs_contains(&sapling_anchors, &sapling_anchor)
111    }
112
113    /// Returns `true` if the finalized state contains `orchard_anchor`.
114    pub fn contains_orchard_anchor(&self, orchard_anchor: &orchard::tree::Root) -> bool {
115        let orchard_anchors = self.db.cf_handle("orchard_anchors").unwrap();
116        self.db.zs_contains(&orchard_anchors, &orchard_anchor)
117    }
118
119    // # Sprout trees
120
121    /// Returns the Sprout note commitment tree of the finalized tip
122    /// or the empty tree if the state is empty.
123    pub fn sprout_tree_for_tip(&self) -> Arc<sprout::tree::NoteCommitmentTree> {
124        if self.is_empty() {
125            return Arc::<sprout::tree::NoteCommitmentTree>::default();
126        }
127
128        let sprout_tree_cf = self.db.cf_handle("sprout_note_commitment_tree").unwrap();
129
130        // # Backwards Compatibility
131        //
132        // This code can read the column family format in 1.2.0 and earlier (tip height key),
133        // and after PR #7392 is merged (empty key). The height-based code can be removed when
134        // versions 1.2.0 and earlier are no longer supported.
135        //
136        // # Concurrency
137        //
138        // There is only one entry in this column family, which is atomically updated by a block
139        // write batch (database transaction). If we used a height as the column family tree,
140        // any updates between reading the tip height and reading the tree could cause panics.
141        //
142        // So we use the empty key `()`. Since the key has a constant value, we will always read
143        // the latest tree.
144        let mut sprout_tree: Option<Arc<sprout::tree::NoteCommitmentTree>> =
145            self.db.zs_get(&sprout_tree_cf, &());
146
147        if sprout_tree.is_none() {
148            // In Zebra 1.4.0 and later, we don't update the sprout tip tree unless it is changed.
149            // And we write with a `()` key, not a height key.
150            // So we need to look for the most recent update height if the `()` key has never been written.
151            sprout_tree = self
152                .db
153                .zs_last_key_value(&sprout_tree_cf)
154                .map(|(_key, tree_value): (Height, _)| tree_value);
155        }
156
157        sprout_tree.expect("Sprout note commitment tree must exist if there is a finalized tip")
158    }
159
160    /// Returns the Sprout note commitment tree matching the given anchor.
161    ///
162    /// This is used for interstitial tree building, which is unique to Sprout.
163    #[allow(clippy::unwrap_in_result)]
164    pub fn sprout_tree_by_anchor(
165        &self,
166        sprout_anchor: &sprout::tree::Root,
167    ) -> Option<Arc<sprout::tree::NoteCommitmentTree>> {
168        let sprout_anchors_handle = self.db.cf_handle("sprout_anchors").unwrap();
169
170        self.db
171            .zs_get(&sprout_anchors_handle, sprout_anchor)
172            .map(Arc::new)
173    }
174
175    /// Returns all the Sprout note commitment trees in the database.
176    ///
177    /// Calling this method can load a lot of data into RAM, and delay block commit transactions.
178    #[allow(dead_code)]
179    pub fn sprout_trees_full_map(
180        &self,
181    ) -> HashMap<sprout::tree::Root, Arc<sprout::tree::NoteCommitmentTree>> {
182        let sprout_anchors_handle = self.db.cf_handle("sprout_anchors").unwrap();
183
184        self.db
185            .zs_items_in_range_unordered(&sprout_anchors_handle, ..)
186    }
187
188    /// Returns all the Sprout note commitment tip trees.
189    /// We only store the sprout tree for the tip, so this method is mainly used in tests.
190    pub fn sprout_trees_full_tip(
191        &self,
192    ) -> impl Iterator<Item = (RawBytes, Arc<sprout::tree::NoteCommitmentTree>)> + '_ {
193        let sprout_trees = self.db.cf_handle("sprout_note_commitment_tree").unwrap();
194        self.db.zs_forward_range_iter(&sprout_trees, ..)
195    }
196
197    // # Sapling trees
198
199    /// Returns the Sapling note commitment tree of the finalized tip or the empty tree if the state
200    /// is empty.
201    pub fn sapling_tree_for_tip(&self) -> Arc<sapling::tree::NoteCommitmentTree> {
202        let height = match self.finalized_tip_height() {
203            Some(h) => h,
204            None => return Default::default(),
205        };
206
207        self.sapling_tree_by_height(&height)
208            .expect("Sapling note commitment tree must exist if there is a finalized tip")
209    }
210
211    /// Returns the Sapling note commitment tree matching the given block height, or `None` if the
212    /// height is above the finalized tip.
213    #[allow(clippy::unwrap_in_result)]
214    pub fn sapling_tree_by_height(
215        &self,
216        height: &Height,
217    ) -> Option<Arc<sapling::tree::NoteCommitmentTree>> {
218        let tip_height = self.finalized_tip_height()?;
219
220        // If we're above the tip, searching backwards would always return the tip tree.
221        // But the correct answer is "we don't know that tree yet".
222        if *height > tip_height {
223            return None;
224        }
225
226        let sapling_trees = self.db.cf_handle("sapling_note_commitment_tree").unwrap();
227
228        // If we know there must be a tree, search backwards for it.
229        let (_first_duplicate_height, tree) = self
230            .db
231            .zs_prev_key_value_back_from(&sapling_trees, height)
232            .expect(
233                "Sapling note commitment trees must exist for all heights below the finalized tip",
234            );
235
236        Some(Arc::new(tree))
237    }
238
239    /// Returns the Sapling note commitment trees in the supplied range, in increasing height order.
240    pub fn sapling_tree_by_height_range<R>(
241        &self,
242        range: R,
243    ) -> impl Iterator<Item = (Height, Arc<sapling::tree::NoteCommitmentTree>)> + '_
244    where
245        R: std::ops::RangeBounds<Height>,
246    {
247        let sapling_trees = self.db.cf_handle("sapling_note_commitment_tree").unwrap();
248        self.db.zs_forward_range_iter(&sapling_trees, range)
249    }
250
251    /// Returns the Sapling note commitment trees in the reversed range, in decreasing height order.
252    pub fn sapling_tree_by_reversed_height_range<R>(
253        &self,
254        range: R,
255    ) -> impl Iterator<Item = (Height, Arc<sapling::tree::NoteCommitmentTree>)> + '_
256    where
257        R: std::ops::RangeBounds<Height>,
258    {
259        let sapling_trees = self.db.cf_handle("sapling_note_commitment_tree").unwrap();
260        self.db.zs_reverse_range_iter(&sapling_trees, range)
261    }
262
263    /// Returns the Sapling note commitment subtree at this `index`.
264    ///
265    /// # Correctness
266    ///
267    /// This method should not be used to get subtrees for RPC responses,
268    /// because those subtree lists require that the start subtree is present in the list.
269    /// Instead, use `sapling_subtree_list_by_index_for_rpc()`.
270    #[allow(clippy::unwrap_in_result)]
271    pub(in super::super) fn sapling_subtree_by_index(
272        &self,
273        index: impl Into<NoteCommitmentSubtreeIndex> + Copy,
274    ) -> Option<NoteCommitmentSubtree<sapling::tree::Node>> {
275        let sapling_subtrees = self
276            .db
277            .cf_handle("sapling_note_commitment_subtree")
278            .unwrap();
279
280        let subtree_data: NoteCommitmentSubtreeData<sapling::tree::Node> =
281            self.db.zs_get(&sapling_subtrees, &index.into())?;
282
283        Some(subtree_data.with_index(index))
284    }
285
286    /// Returns a list of Sapling [`NoteCommitmentSubtree`]s in the provided range.
287    #[allow(clippy::unwrap_in_result)]
288    pub fn sapling_subtree_list_by_index_range(
289        &self,
290        range: impl std::ops::RangeBounds<NoteCommitmentSubtreeIndex>,
291    ) -> BTreeMap<NoteCommitmentSubtreeIndex, NoteCommitmentSubtreeData<sapling::tree::Node>> {
292        let sapling_subtrees = self
293            .db
294            .cf_handle("sapling_note_commitment_subtree")
295            .unwrap();
296
297        self.db
298            .zs_forward_range_iter(&sapling_subtrees, range)
299            .collect()
300    }
301
302    /// Get the sapling note commitment subtress for the finalized tip.
303    #[allow(clippy::unwrap_in_result)]
304    fn sapling_subtree_for_tip(&self) -> Option<NoteCommitmentSubtree<sapling::tree::Node>> {
305        let sapling_subtrees = self
306            .db
307            .cf_handle("sapling_note_commitment_subtree")
308            .unwrap();
309
310        let (index, subtree_data): (
311            NoteCommitmentSubtreeIndex,
312            NoteCommitmentSubtreeData<sapling::tree::Node>,
313        ) = self.db.zs_last_key_value(&sapling_subtrees)?;
314
315        let tip_height = self.finalized_tip_height()?;
316        if subtree_data.end_height != tip_height {
317            return None;
318        }
319
320        Some(subtree_data.with_index(index))
321    }
322
323    // Orchard trees
324
325    /// Returns the Orchard note commitment tree of the finalized tip or the empty tree if the state
326    /// is empty.
327    pub fn orchard_tree_for_tip(&self) -> Arc<orchard::tree::NoteCommitmentTree> {
328        let height = match self.finalized_tip_height() {
329            Some(h) => h,
330            None => return Default::default(),
331        };
332
333        self.orchard_tree_by_height(&height)
334            .expect("Orchard note commitment tree must exist if there is a finalized tip")
335    }
336
337    /// Returns the Orchard note commitment tree matching the given block height,
338    /// or `None` if the height is above the finalized tip.
339    #[allow(clippy::unwrap_in_result)]
340    pub fn orchard_tree_by_height(
341        &self,
342        height: &Height,
343    ) -> Option<Arc<orchard::tree::NoteCommitmentTree>> {
344        let tip_height = self.finalized_tip_height()?;
345
346        // If we're above the tip, searching backwards would always return the tip tree.
347        // But the correct answer is "we don't know that tree yet".
348        if *height > tip_height {
349            return None;
350        }
351
352        let orchard_trees = self.db.cf_handle("orchard_note_commitment_tree").unwrap();
353
354        // If we know there must be a tree, search backwards for it.
355        let (_first_duplicate_height, tree) = self
356            .db
357            .zs_prev_key_value_back_from(&orchard_trees, height)
358            .expect(
359                "Orchard note commitment trees must exist for all heights below the finalized tip",
360            );
361
362        Some(Arc::new(tree))
363    }
364
365    /// Returns the Orchard note commitment trees in the supplied range, in increasing height order.
366    pub fn orchard_tree_by_height_range<R>(
367        &self,
368        range: R,
369    ) -> impl Iterator<Item = (Height, Arc<orchard::tree::NoteCommitmentTree>)> + '_
370    where
371        R: std::ops::RangeBounds<Height>,
372    {
373        let orchard_trees = self.db.cf_handle("orchard_note_commitment_tree").unwrap();
374        self.db.zs_forward_range_iter(&orchard_trees, range)
375    }
376
377    /// Returns the Orchard note commitment trees in the reversed range, in decreasing height order.
378    pub fn orchard_tree_by_reversed_height_range<R>(
379        &self,
380        range: R,
381    ) -> impl Iterator<Item = (Height, Arc<orchard::tree::NoteCommitmentTree>)> + '_
382    where
383        R: std::ops::RangeBounds<Height>,
384    {
385        let orchard_trees = self.db.cf_handle("orchard_note_commitment_tree").unwrap();
386        self.db.zs_reverse_range_iter(&orchard_trees, range)
387    }
388
389    /// Returns the Orchard note commitment subtree at this `index`.
390    ///
391    /// # Correctness
392    ///
393    /// This method should not be used to get subtrees for RPC responses,
394    /// because those subtree lists require that the start subtree is present in the list.
395    /// Instead, use `orchard_subtree_list_by_index_for_rpc()`.
396    #[allow(clippy::unwrap_in_result)]
397    pub(in super::super) fn orchard_subtree_by_index(
398        &self,
399        index: impl Into<NoteCommitmentSubtreeIndex> + Copy,
400    ) -> Option<NoteCommitmentSubtree<orchard::tree::Node>> {
401        let orchard_subtrees = self
402            .db
403            .cf_handle("orchard_note_commitment_subtree")
404            .unwrap();
405
406        let subtree_data: NoteCommitmentSubtreeData<orchard::tree::Node> =
407            self.db.zs_get(&orchard_subtrees, &index.into())?;
408
409        Some(subtree_data.with_index(index))
410    }
411
412    /// Returns a list of Orchard [`NoteCommitmentSubtree`]s in the provided range.
413    #[allow(clippy::unwrap_in_result)]
414    pub fn orchard_subtree_list_by_index_range(
415        &self,
416        range: impl std::ops::RangeBounds<NoteCommitmentSubtreeIndex>,
417    ) -> BTreeMap<NoteCommitmentSubtreeIndex, NoteCommitmentSubtreeData<orchard::tree::Node>> {
418        let orchard_subtrees = self
419            .db
420            .cf_handle("orchard_note_commitment_subtree")
421            .unwrap();
422
423        self.db
424            .zs_forward_range_iter(&orchard_subtrees, range)
425            .collect()
426    }
427
428    /// Get the orchard note commitment subtress for the finalized tip.
429    #[allow(clippy::unwrap_in_result)]
430    fn orchard_subtree_for_tip(&self) -> Option<NoteCommitmentSubtree<orchard::tree::Node>> {
431        let orchard_subtrees = self
432            .db
433            .cf_handle("orchard_note_commitment_subtree")
434            .unwrap();
435
436        let (index, subtree_data): (
437            NoteCommitmentSubtreeIndex,
438            NoteCommitmentSubtreeData<orchard::tree::Node>,
439        ) = self.db.zs_last_key_value(&orchard_subtrees)?;
440
441        let tip_height = self.finalized_tip_height()?;
442        if subtree_data.end_height != tip_height {
443            return None;
444        }
445
446        Some(subtree_data.with_index(index))
447    }
448
449    /// Returns the shielded note commitment trees of the finalized tip
450    /// or the empty trees if the state is empty.
451    /// Additionally, returns the sapling and orchard subtrees for the finalized tip if
452    /// the current subtree is finalizing in the tip, None otherwise.
453    pub fn note_commitment_trees_for_tip(&self) -> NoteCommitmentTrees {
454        NoteCommitmentTrees {
455            sprout: self.sprout_tree_for_tip(),
456            sapling: self.sapling_tree_for_tip(),
457            sapling_subtree: self.sapling_subtree_for_tip(),
458            orchard: self.orchard_tree_for_tip(),
459            orchard_subtree: self.orchard_subtree_for_tip(),
460        }
461    }
462}
463
464impl DiskWriteBatch {
465    /// Prepare a database batch containing `finalized.block`'s shielded transaction indexes,
466    /// and return it (without actually writing anything).
467    ///
468    /// If this method returns an error, it will be propagated,
469    /// and the batch should not be written to the database.
470    ///
471    /// # Errors
472    ///
473    /// - Propagates any errors from updating note commitment trees
474    pub fn prepare_shielded_transaction_batch(
475        &mut self,
476        zebra_db: &ZebraDb,
477        finalized: &FinalizedBlock,
478    ) -> Result<(), BoxError> {
479        #[cfg(feature = "indexer")]
480        let FinalizedBlock { block, height, .. } = finalized;
481
482        // Index each transaction's shielded data
483        #[cfg(feature = "indexer")]
484        for (tx_index, transaction) in block.transactions.iter().enumerate() {
485            let tx_loc = TransactionLocation::from_usize(*height, tx_index);
486            self.prepare_nullifier_batch(zebra_db, transaction, tx_loc)?;
487        }
488
489        #[cfg(not(feature = "indexer"))]
490        for transaction in &finalized.block.transactions {
491            self.prepare_nullifier_batch(zebra_db, transaction)?;
492        }
493
494        Ok(())
495    }
496
497    /// Prepare a database batch containing `finalized.block`'s nullifiers,
498    /// and return it (without actually writing anything).
499    ///
500    /// # Errors
501    ///
502    /// - This method doesn't currently return any errors, but it might in future
503    #[allow(clippy::unwrap_in_result)]
504    pub fn prepare_nullifier_batch(
505        &mut self,
506        zebra_db: &ZebraDb,
507        transaction: &Transaction,
508        #[cfg(feature = "indexer")] transaction_location: TransactionLocation,
509    ) -> Result<(), BoxError> {
510        let db = &zebra_db.db;
511        let sprout_nullifiers = db.cf_handle("sprout_nullifiers").unwrap();
512        let sapling_nullifiers = db.cf_handle("sapling_nullifiers").unwrap();
513        let orchard_nullifiers = db.cf_handle("orchard_nullifiers").unwrap();
514
515        #[cfg(feature = "indexer")]
516        let insert_value = transaction_location;
517        #[cfg(not(feature = "indexer"))]
518        let insert_value = ();
519
520        // Mark sprout, sapling and orchard nullifiers as spent
521        for sprout_nullifier in transaction.sprout_nullifiers() {
522            self.zs_insert(&sprout_nullifiers, sprout_nullifier, insert_value);
523        }
524        for sapling_nullifier in transaction.sapling_nullifiers() {
525            self.zs_insert(&sapling_nullifiers, sapling_nullifier, insert_value);
526        }
527        for orchard_nullifier in transaction.orchard_nullifiers() {
528            self.zs_insert(&orchard_nullifiers, orchard_nullifier, insert_value);
529        }
530
531        Ok(())
532    }
533
534    /// Prepare a database batch containing the note commitment and history tree updates
535    /// from `finalized.block`, and return it (without actually writing anything).
536    ///
537    /// If this method returns an error, it will be propagated,
538    /// and the batch should not be written to the database.
539    ///
540    /// # Errors
541    ///
542    /// - Propagates any errors from updating the history tree
543    #[allow(clippy::unwrap_in_result)]
544    pub fn prepare_trees_batch(
545        &mut self,
546        zebra_db: &ZebraDb,
547        finalized: &FinalizedBlock,
548        prev_note_commitment_trees: Option<NoteCommitmentTrees>,
549    ) -> Result<(), BoxError> {
550        let FinalizedBlock {
551            height,
552            treestate:
553                Treestate {
554                    note_commitment_trees,
555                    history_tree,
556                },
557            ..
558        } = finalized;
559
560        let prev_sprout_tree = prev_note_commitment_trees.as_ref().map_or_else(
561            || zebra_db.sprout_tree_for_tip(),
562            |prev_trees| prev_trees.sprout.clone(),
563        );
564        let prev_sapling_tree = prev_note_commitment_trees.as_ref().map_or_else(
565            || zebra_db.sapling_tree_for_tip(),
566            |prev_trees| prev_trees.sapling.clone(),
567        );
568        let prev_orchard_tree = prev_note_commitment_trees.as_ref().map_or_else(
569            || zebra_db.orchard_tree_for_tip(),
570            |prev_trees| prev_trees.orchard.clone(),
571        );
572
573        // Update the Sprout tree and store its anchor only if it has changed
574        if height.is_min() || prev_sprout_tree != note_commitment_trees.sprout {
575            self.update_sprout_tree(zebra_db, &note_commitment_trees.sprout)
576        }
577
578        // Store the Sapling tree, anchor, and any new subtrees only if they have changed
579        if height.is_min() || prev_sapling_tree != note_commitment_trees.sapling {
580            self.create_sapling_tree(zebra_db, height, &note_commitment_trees.sapling);
581
582            if let Some(subtree) = note_commitment_trees.sapling_subtree {
583                self.insert_sapling_subtree(zebra_db, &subtree);
584            }
585        }
586
587        // Store the Orchard tree, anchor, and any new subtrees only if they have changed
588        if height.is_min() || prev_orchard_tree != note_commitment_trees.orchard {
589            self.create_orchard_tree(zebra_db, height, &note_commitment_trees.orchard);
590
591            if let Some(subtree) = note_commitment_trees.orchard_subtree {
592                self.insert_orchard_subtree(zebra_db, &subtree);
593            }
594        }
595
596        self.update_history_tree(zebra_db, history_tree);
597
598        Ok(())
599    }
600
601    // Sprout tree methods
602
603    /// Updates the Sprout note commitment tree for the tip, and the Sprout anchors.
604    pub fn update_sprout_tree(
605        &mut self,
606        zebra_db: &ZebraDb,
607        tree: &sprout::tree::NoteCommitmentTree,
608    ) {
609        let sprout_anchors = zebra_db.db.cf_handle("sprout_anchors").unwrap();
610        let sprout_tree_cf = zebra_db
611            .db
612            .cf_handle("sprout_note_commitment_tree")
613            .unwrap();
614
615        // Sprout lookups need all previous trees by their anchors.
616        // The root must be calculated first, so it is cached in the database.
617        self.zs_insert(&sprout_anchors, tree.root(), tree);
618        self.zs_insert(&sprout_tree_cf, (), tree);
619    }
620
621    /// Legacy method: Deletes the range of Sprout note commitment trees at the given [`Height`]s.
622    /// Doesn't delete anchors from the anchor index. Doesn't delete the upper bound.
623    ///
624    /// From state format 25.3.0 onwards, the Sprout trees are indexed by an empty key,
625    /// so this method does nothing.
626    pub fn delete_range_sprout_tree(&mut self, zebra_db: &ZebraDb, from: &Height, to: &Height) {
627        let sprout_tree_cf = zebra_db
628            .db
629            .cf_handle("sprout_note_commitment_tree")
630            .unwrap();
631
632        // TODO: convert zs_delete_range() to take std::ops::RangeBounds
633        self.zs_delete_range(&sprout_tree_cf, from, to);
634    }
635
636    /// Deletes the given Sprout note commitment tree `anchor`.
637    #[allow(dead_code)]
638    pub fn delete_sprout_anchor(&mut self, zebra_db: &ZebraDb, anchor: &sprout::tree::Root) {
639        let sprout_anchors = zebra_db.db.cf_handle("sprout_anchors").unwrap();
640        self.zs_delete(&sprout_anchors, anchor);
641    }
642
643    // Sapling tree methods
644
645    /// Inserts or overwrites the Sapling note commitment tree at the given [`Height`],
646    /// and the Sapling anchors.
647    pub fn create_sapling_tree(
648        &mut self,
649        zebra_db: &ZebraDb,
650        height: &Height,
651        tree: &sapling::tree::NoteCommitmentTree,
652    ) {
653        let sapling_anchors = zebra_db.db.cf_handle("sapling_anchors").unwrap();
654        let sapling_tree_cf = zebra_db
655            .db
656            .cf_handle("sapling_note_commitment_tree")
657            .unwrap();
658
659        self.zs_insert(&sapling_anchors, tree.root(), ());
660        self.zs_insert(&sapling_tree_cf, height, tree);
661    }
662
663    /// Inserts the Sapling note commitment subtree into the batch.
664    pub fn insert_sapling_subtree(
665        &mut self,
666        zebra_db: &ZebraDb,
667        subtree: &NoteCommitmentSubtree<sapling::tree::Node>,
668    ) {
669        let sapling_subtree_cf = zebra_db
670            .db
671            .cf_handle("sapling_note_commitment_subtree")
672            .unwrap();
673        self.zs_insert(&sapling_subtree_cf, subtree.index, subtree.into_data());
674    }
675
676    /// Deletes the Sapling note commitment tree at the given [`Height`].
677    pub fn delete_sapling_tree(&mut self, zebra_db: &ZebraDb, height: &Height) {
678        let sapling_tree_cf = zebra_db
679            .db
680            .cf_handle("sapling_note_commitment_tree")
681            .unwrap();
682        self.zs_delete(&sapling_tree_cf, height);
683    }
684
685    /// Deletes the range of Sapling note commitment trees at the given [`Height`]s.
686    /// Doesn't delete anchors from the anchor index. Doesn't delete the upper bound.
687    #[allow(dead_code)]
688    pub fn delete_range_sapling_tree(&mut self, zebra_db: &ZebraDb, from: &Height, to: &Height) {
689        let sapling_tree_cf = zebra_db
690            .db
691            .cf_handle("sapling_note_commitment_tree")
692            .unwrap();
693
694        // TODO: convert zs_delete_range() to take std::ops::RangeBounds
695        self.zs_delete_range(&sapling_tree_cf, from, to);
696    }
697
698    /// Deletes the given Sapling note commitment tree `anchor`.
699    #[allow(dead_code)]
700    pub fn delete_sapling_anchor(&mut self, zebra_db: &ZebraDb, anchor: &sapling::tree::Root) {
701        let sapling_anchors = zebra_db.db.cf_handle("sapling_anchors").unwrap();
702        self.zs_delete(&sapling_anchors, anchor);
703    }
704
705    /// Deletes the range of Sapling subtrees at the given [`NoteCommitmentSubtreeIndex`]es.
706    /// Doesn't delete the upper bound.
707    pub fn delete_range_sapling_subtree(
708        &mut self,
709        zebra_db: &ZebraDb,
710        from: NoteCommitmentSubtreeIndex,
711        to: NoteCommitmentSubtreeIndex,
712    ) {
713        let sapling_subtree_cf = zebra_db
714            .db
715            .cf_handle("sapling_note_commitment_subtree")
716            .unwrap();
717
718        // TODO: convert zs_delete_range() to take std::ops::RangeBounds
719        self.zs_delete_range(&sapling_subtree_cf, from, to);
720    }
721
722    // Orchard tree methods
723
724    /// Inserts or overwrites the Orchard note commitment tree at the given [`Height`],
725    /// and the Orchard anchors.
726    pub fn create_orchard_tree(
727        &mut self,
728        zebra_db: &ZebraDb,
729        height: &Height,
730        tree: &orchard::tree::NoteCommitmentTree,
731    ) {
732        let orchard_anchors = zebra_db.db.cf_handle("orchard_anchors").unwrap();
733        let orchard_tree_cf = zebra_db
734            .db
735            .cf_handle("orchard_note_commitment_tree")
736            .unwrap();
737
738        self.zs_insert(&orchard_anchors, tree.root(), ());
739        self.zs_insert(&orchard_tree_cf, height, tree);
740    }
741
742    /// Inserts the Orchard note commitment subtree into the batch.
743    pub fn insert_orchard_subtree(
744        &mut self,
745        zebra_db: &ZebraDb,
746        subtree: &NoteCommitmentSubtree<orchard::tree::Node>,
747    ) {
748        let orchard_subtree_cf = zebra_db
749            .db
750            .cf_handle("orchard_note_commitment_subtree")
751            .unwrap();
752        self.zs_insert(&orchard_subtree_cf, subtree.index, subtree.into_data());
753    }
754
755    /// Deletes the Orchard note commitment tree at the given [`Height`].
756    pub fn delete_orchard_tree(&mut self, zebra_db: &ZebraDb, height: &Height) {
757        let orchard_tree_cf = zebra_db
758            .db
759            .cf_handle("orchard_note_commitment_tree")
760            .unwrap();
761        self.zs_delete(&orchard_tree_cf, height);
762    }
763
764    /// Deletes the range of Orchard note commitment trees at the given [`Height`]s.
765    /// Doesn't delete anchors from the anchor index. Doesn't delete the upper bound.
766    #[allow(dead_code)]
767    pub fn delete_range_orchard_tree(&mut self, zebra_db: &ZebraDb, from: &Height, to: &Height) {
768        let orchard_tree_cf = zebra_db
769            .db
770            .cf_handle("orchard_note_commitment_tree")
771            .unwrap();
772
773        // TODO: convert zs_delete_range() to take std::ops::RangeBounds
774        self.zs_delete_range(&orchard_tree_cf, from, to);
775    }
776
777    /// Deletes the given Orchard note commitment tree `anchor`.
778    #[allow(dead_code)]
779    pub fn delete_orchard_anchor(&mut self, zebra_db: &ZebraDb, anchor: &orchard::tree::Root) {
780        let orchard_anchors = zebra_db.db.cf_handle("orchard_anchors").unwrap();
781        self.zs_delete(&orchard_anchors, anchor);
782    }
783
784    /// Deletes the range of Orchard subtrees at the given [`NoteCommitmentSubtreeIndex`]es.
785    /// Doesn't delete the upper bound.
786    pub fn delete_range_orchard_subtree(
787        &mut self,
788        zebra_db: &ZebraDb,
789        from: NoteCommitmentSubtreeIndex,
790        to: NoteCommitmentSubtreeIndex,
791    ) {
792        let orchard_subtree_cf = zebra_db
793            .db
794            .cf_handle("orchard_note_commitment_subtree")
795            .unwrap();
796
797        // TODO: convert zs_delete_range() to take std::ops::RangeBounds
798        self.zs_delete_range(&orchard_subtree_cf, from, to);
799    }
800}