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 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_crypto::Node>> {
275 let sapling_subtrees = self
276 .db
277 .cf_handle("sapling_note_commitment_subtree")
278 .unwrap();
279
280 let subtree_data: NoteCommitmentSubtreeData<sapling_crypto::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_crypto::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_crypto::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_crypto::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(
471 &mut self,
472 zebra_db: &ZebraDb,
473 finalized: &FinalizedBlock,
474 ) {
475 #[cfg(feature = "indexer")]
476 let FinalizedBlock { block, height, .. } = finalized;
477
478 #[cfg(feature = "indexer")]
480 for (tx_index, transaction) in block.transactions.iter().enumerate() {
481 let tx_loc = TransactionLocation::from_usize(*height, tx_index);
482 self.prepare_nullifier_batch(zebra_db, transaction, tx_loc);
483 }
484
485 #[cfg(not(feature = "indexer"))]
486 for transaction in &finalized.block.transactions {
487 self.prepare_nullifier_batch(zebra_db, transaction);
488 }
489 }
490
491 #[allow(clippy::unwrap_in_result)]
498 pub fn prepare_nullifier_batch(
499 &mut self,
500 zebra_db: &ZebraDb,
501 transaction: &Transaction,
502 #[cfg(feature = "indexer")] transaction_location: TransactionLocation,
503 ) {
504 let db = &zebra_db.db;
505 let sprout_nullifiers = db.cf_handle("sprout_nullifiers").unwrap();
506 let sapling_nullifiers = db.cf_handle("sapling_nullifiers").unwrap();
507 let orchard_nullifiers = db.cf_handle("orchard_nullifiers").unwrap();
508
509 #[cfg(feature = "indexer")]
510 let insert_value = transaction_location;
511 #[cfg(not(feature = "indexer"))]
512 let insert_value = ();
513
514 for sprout_nullifier in transaction.sprout_nullifiers() {
516 self.zs_insert(&sprout_nullifiers, sprout_nullifier, insert_value);
517 }
518 for sapling_nullifier in transaction.sapling_nullifiers() {
519 self.zs_insert(&sapling_nullifiers, sapling_nullifier, insert_value);
520 }
521 for orchard_nullifier in transaction.orchard_nullifiers() {
522 self.zs_insert(&orchard_nullifiers, orchard_nullifier, insert_value);
523 }
524 }
525
526 #[allow(clippy::unwrap_in_result)]
532 pub fn prepare_trees_batch(
533 &mut self,
534 zebra_db: &ZebraDb,
535 finalized: &FinalizedBlock,
536 prev_note_commitment_trees: Option<NoteCommitmentTrees>,
537 ) {
538 let FinalizedBlock {
539 height,
540 treestate:
541 Treestate {
542 note_commitment_trees,
543 history_tree,
544 },
545 ..
546 } = finalized;
547
548 let prev_sprout_tree = prev_note_commitment_trees.as_ref().map_or_else(
549 || zebra_db.sprout_tree_for_tip(),
550 |prev_trees| prev_trees.sprout.clone(),
551 );
552 let prev_sapling_tree = prev_note_commitment_trees.as_ref().map_or_else(
553 || zebra_db.sapling_tree_for_tip(),
554 |prev_trees| prev_trees.sapling.clone(),
555 );
556 let prev_orchard_tree = prev_note_commitment_trees.as_ref().map_or_else(
557 || zebra_db.orchard_tree_for_tip(),
558 |prev_trees| prev_trees.orchard.clone(),
559 );
560
561 if height.is_min() || prev_sprout_tree != note_commitment_trees.sprout {
563 self.update_sprout_tree(zebra_db, ¬e_commitment_trees.sprout)
564 }
565
566 if height.is_min() || prev_sapling_tree != note_commitment_trees.sapling {
568 self.create_sapling_tree(zebra_db, height, ¬e_commitment_trees.sapling);
569
570 if let Some(subtree) = note_commitment_trees.sapling_subtree {
571 self.insert_sapling_subtree(zebra_db, &subtree);
572 }
573 }
574
575 if height.is_min() || prev_orchard_tree != note_commitment_trees.orchard {
577 self.create_orchard_tree(zebra_db, height, ¬e_commitment_trees.orchard);
578
579 if let Some(subtree) = note_commitment_trees.orchard_subtree {
580 self.insert_orchard_subtree(zebra_db, &subtree);
581 }
582 }
583
584 self.update_history_tree(zebra_db, history_tree);
585 }
586
587 pub fn update_sprout_tree(
591 &mut self,
592 zebra_db: &ZebraDb,
593 tree: &sprout::tree::NoteCommitmentTree,
594 ) {
595 let sprout_anchors = zebra_db.db.cf_handle("sprout_anchors").unwrap();
596 let sprout_tree_cf = zebra_db
597 .db
598 .cf_handle("sprout_note_commitment_tree")
599 .unwrap();
600
601 self.zs_insert(&sprout_anchors, tree.root(), tree);
604 self.zs_insert(&sprout_tree_cf, (), tree);
605 }
606
607 pub fn delete_range_sprout_tree(&mut self, zebra_db: &ZebraDb, from: &Height, to: &Height) {
613 let sprout_tree_cf = zebra_db
614 .db
615 .cf_handle("sprout_note_commitment_tree")
616 .unwrap();
617
618 self.zs_delete_range(&sprout_tree_cf, from, to);
620 }
621
622 #[allow(dead_code)]
624 pub fn delete_sprout_anchor(&mut self, zebra_db: &ZebraDb, anchor: &sprout::tree::Root) {
625 let sprout_anchors = zebra_db.db.cf_handle("sprout_anchors").unwrap();
626 self.zs_delete(&sprout_anchors, anchor);
627 }
628
629 pub fn create_sapling_tree(
634 &mut self,
635 zebra_db: &ZebraDb,
636 height: &Height,
637 tree: &sapling::tree::NoteCommitmentTree,
638 ) {
639 let sapling_anchors = zebra_db.db.cf_handle("sapling_anchors").unwrap();
640 let sapling_tree_cf = zebra_db
641 .db
642 .cf_handle("sapling_note_commitment_tree")
643 .unwrap();
644
645 self.zs_insert(&sapling_anchors, tree.root(), ());
646 self.zs_insert(&sapling_tree_cf, height, tree);
647 }
648
649 pub fn insert_sapling_subtree(
651 &mut self,
652 zebra_db: &ZebraDb,
653 subtree: &NoteCommitmentSubtree<sapling_crypto::Node>,
654 ) {
655 let sapling_subtree_cf = zebra_db
656 .db
657 .cf_handle("sapling_note_commitment_subtree")
658 .unwrap();
659 self.zs_insert(&sapling_subtree_cf, subtree.index, subtree.into_data());
660 }
661
662 pub fn delete_sapling_tree(&mut self, zebra_db: &ZebraDb, height: &Height) {
664 let sapling_tree_cf = zebra_db
665 .db
666 .cf_handle("sapling_note_commitment_tree")
667 .unwrap();
668 self.zs_delete(&sapling_tree_cf, height);
669 }
670
671 #[allow(dead_code)]
674 pub fn delete_range_sapling_tree(&mut self, zebra_db: &ZebraDb, from: &Height, to: &Height) {
675 let sapling_tree_cf = zebra_db
676 .db
677 .cf_handle("sapling_note_commitment_tree")
678 .unwrap();
679
680 self.zs_delete_range(&sapling_tree_cf, from, to);
682 }
683
684 #[allow(dead_code)]
686 pub fn delete_sapling_anchor(&mut self, zebra_db: &ZebraDb, anchor: &sapling::tree::Root) {
687 let sapling_anchors = zebra_db.db.cf_handle("sapling_anchors").unwrap();
688 self.zs_delete(&sapling_anchors, anchor);
689 }
690
691 pub fn delete_range_sapling_subtree(
694 &mut self,
695 zebra_db: &ZebraDb,
696 from: NoteCommitmentSubtreeIndex,
697 to: NoteCommitmentSubtreeIndex,
698 ) {
699 let sapling_subtree_cf = zebra_db
700 .db
701 .cf_handle("sapling_note_commitment_subtree")
702 .unwrap();
703
704 self.zs_delete_range(&sapling_subtree_cf, from, to);
706 }
707
708 pub fn create_orchard_tree(
713 &mut self,
714 zebra_db: &ZebraDb,
715 height: &Height,
716 tree: &orchard::tree::NoteCommitmentTree,
717 ) {
718 let orchard_anchors = zebra_db.db.cf_handle("orchard_anchors").unwrap();
719 let orchard_tree_cf = zebra_db
720 .db
721 .cf_handle("orchard_note_commitment_tree")
722 .unwrap();
723
724 self.zs_insert(&orchard_anchors, tree.root(), ());
725 self.zs_insert(&orchard_tree_cf, height, tree);
726 }
727
728 pub fn insert_orchard_subtree(
730 &mut self,
731 zebra_db: &ZebraDb,
732 subtree: &NoteCommitmentSubtree<orchard::tree::Node>,
733 ) {
734 let orchard_subtree_cf = zebra_db
735 .db
736 .cf_handle("orchard_note_commitment_subtree")
737 .unwrap();
738 self.zs_insert(&orchard_subtree_cf, subtree.index, subtree.into_data());
739 }
740
741 pub fn delete_orchard_tree(&mut self, zebra_db: &ZebraDb, height: &Height) {
743 let orchard_tree_cf = zebra_db
744 .db
745 .cf_handle("orchard_note_commitment_tree")
746 .unwrap();
747 self.zs_delete(&orchard_tree_cf, height);
748 }
749
750 #[allow(dead_code)]
753 pub fn delete_range_orchard_tree(&mut self, zebra_db: &ZebraDb, from: &Height, to: &Height) {
754 let orchard_tree_cf = zebra_db
755 .db
756 .cf_handle("orchard_note_commitment_tree")
757 .unwrap();
758
759 self.zs_delete_range(&orchard_tree_cf, from, to);
761 }
762
763 #[allow(dead_code)]
765 pub fn delete_orchard_anchor(&mut self, zebra_db: &ZebraDb, anchor: &orchard::tree::Root) {
766 let orchard_anchors = zebra_db.db.cf_handle("orchard_anchors").unwrap();
767 self.zs_delete(&orchard_anchors, anchor);
768 }
769
770 pub fn delete_range_orchard_subtree(
773 &mut self,
774 zebra_db: &ZebraDb,
775 from: NoteCommitmentSubtreeIndex,
776 to: NoteCommitmentSubtreeIndex,
777 ) {
778 let orchard_subtree_cf = zebra_db
779 .db
780 .cf_handle("orchard_note_commitment_subtree")
781 .unwrap();
782
783 self.zs_delete_range(&orchard_subtree_cf, from, to);
785 }
786}