1use 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#[allow(unused_imports)]
41use zebra_chain::subtree::NoteCommitmentSubtree;
42
43impl ZebraDb {
44 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 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 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 #[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 #[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 #[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 #[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 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 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 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 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 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 #[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 #[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 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 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 #[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 *height > tip_height {
223 return None;
224 }
225
226 let sapling_trees = self.db.cf_handle("sapling_note_commitment_tree").unwrap();
227
228 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 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 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 #[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 #[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 #[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 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 #[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 *height > tip_height {
349 return None;
350 }
351
352 let orchard_trees = self.db.cf_handle("orchard_note_commitment_tree").unwrap();
353
354 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 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 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 #[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 #[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 #[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 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 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 #[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 #[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 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 #[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 if height.is_min() || prev_sprout_tree != note_commitment_trees.sprout {
575 self.update_sprout_tree(zebra_db, ¬e_commitment_trees.sprout)
576 }
577
578 if height.is_min() || prev_sapling_tree != note_commitment_trees.sapling {
580 self.create_sapling_tree(zebra_db, height, ¬e_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 if height.is_min() || prev_orchard_tree != note_commitment_trees.orchard {
589 self.create_orchard_tree(zebra_db, height, ¬e_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 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 self.zs_insert(&sprout_anchors, tree.root(), tree);
618 self.zs_insert(&sprout_tree_cf, (), tree);
619 }
620
621 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 self.zs_delete_range(&sprout_tree_cf, from, to);
634 }
635
636 #[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 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 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 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 #[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 self.zs_delete_range(&sapling_tree_cf, from, to);
696 }
697
698 #[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 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 self.zs_delete_range(&sapling_subtree_cf, from, to);
720 }
721
722 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 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 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 #[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 self.zs_delete_range(&orchard_tree_cf, from, to);
775 }
776
777 #[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 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 self.zs_delete_range(&orchard_subtree_cf, from, to);
799 }
800}