1use std::sync::Arc;
4
5use crossbeam_channel::{Receiver, TryRecvError};
6use hex_literal::hex;
7use itertools::Itertools;
8use semver::Version;
9use tracing::instrument;
10
11use zebra_chain::{
12 block::Height,
13 orchard,
14 parallel::tree::NoteCommitmentTrees,
15 parameters::Network::*,
16 sapling,
17 subtree::{NoteCommitmentSubtree, NoteCommitmentSubtreeIndex},
18};
19
20use crate::service::finalized_state::{
21 disk_format::upgrade::{CancelFormatChange, DiskFormatUpgrade},
22 DiskWriteBatch, ZebraDb,
23};
24
25pub struct AddSubtrees;
27
28impl DiskFormatUpgrade for AddSubtrees {
29 fn version(&self) -> Version {
30 Version::new(25, 2, 2)
31 }
32
33 fn description(&self) -> &'static str {
34 "add subtrees upgrade"
35 }
36
37 fn prepare(
38 &self,
39 initial_tip_height: Height,
40 upgrade_db: &ZebraDb,
41 cancel_receiver: &Receiver<CancelFormatChange>,
42 older_disk_version: &Version,
43 ) -> Result<(), CancelFormatChange> {
44 let first_version_for_adding_subtrees = Version::new(25, 2, 0);
45 if older_disk_version >= &first_version_for_adding_subtrees {
46 reset(initial_tip_height, upgrade_db, cancel_receiver)?;
48 }
49
50 Ok(())
51 }
52
53 fn run(
60 &self,
61 initial_tip_height: Height,
62 upgrade_db: &ZebraDb,
63 cancel_receiver: &Receiver<CancelFormatChange>,
64 ) -> Result<(), CancelFormatChange> {
65 let subtrees = upgrade_db
84 .sapling_tree_by_reversed_height_range(..=initial_tip_height)
85 .tuple_windows()
87 .map(|((end_height, tree), (prev_end_height, prev_tree))| {
89 (prev_end_height, prev_tree, end_height, tree)
90 })
91 .filter(|(_prev_end_height, prev_tree, _end_height, tree)| {
93 tree.contains_new_subtree(prev_tree)
94 });
95
96 for (prev_end_height, prev_tree, end_height, tree) in subtrees {
97 if !matches!(cancel_receiver.try_recv(), Err(TryRecvError::Empty)) {
99 return Err(CancelFormatChange);
100 }
101
102 let subtree =
103 calculate_sapling_subtree(upgrade_db, prev_end_height, prev_tree, end_height, tree);
104 write_sapling_subtree(upgrade_db, subtree);
105 }
106
107 let subtrees = upgrade_db
109 .orchard_tree_by_reversed_height_range(..=initial_tip_height)
110 .tuple_windows()
112 .map(|((end_height, tree), (prev_end_height, prev_tree))| {
114 (prev_end_height, prev_tree, end_height, tree)
115 })
116 .filter(|(_prev_end_height, prev_tree, _end_height, tree)| {
118 tree.contains_new_subtree(prev_tree)
119 });
120
121 for (prev_end_height, prev_tree, end_height, tree) in subtrees {
122 if !matches!(cancel_receiver.try_recv(), Err(TryRecvError::Empty)) {
124 return Err(CancelFormatChange);
125 }
126
127 let subtree =
128 calculate_orchard_subtree(upgrade_db, prev_end_height, prev_tree, end_height, tree);
129 write_orchard_subtree(upgrade_db, subtree);
130 }
131
132 Ok(())
133 }
134
135 #[allow(clippy::unwrap_in_result)]
136 fn validate(
137 &self,
138 db: &ZebraDb,
139 cancel_receiver: &Receiver<CancelFormatChange>,
140 ) -> Result<Result<(), String>, CancelFormatChange> {
141 let quick_result = subtree_format_calculation_pre_checks(db);
143
144 let sapling_result = check_sapling_subtrees(db, cancel_receiver)?;
146 let orchard_result = check_orchard_subtrees(db, cancel_receiver)?;
147
148 if quick_result.is_err() || sapling_result.is_err() || orchard_result.is_err() {
149 let err = Err(format!(
150 "missing or invalid subtree(s): \
151 quick: {quick_result:?}, sapling: {sapling_result:?}, orchard: {orchard_result:?}"
152 ));
153 warn!(?err);
154 return Ok(err);
155 }
156
157 Ok(Ok(()))
158 }
159}
160
161#[allow(clippy::unwrap_in_result)]
165#[instrument(skip(upgrade_db, cancel_receiver))]
166pub fn reset(
167 _initial_tip_height: Height,
168 upgrade_db: &ZebraDb,
169 cancel_receiver: &Receiver<CancelFormatChange>,
170) -> Result<(), CancelFormatChange> {
171 if !matches!(cancel_receiver.try_recv(), Err(TryRecvError::Empty)) {
173 return Err(CancelFormatChange);
174 }
175
176 let mut batch = DiskWriteBatch::new();
181 batch.delete_range_sapling_subtree(upgrade_db, 0.into(), u16::MAX.into());
182 upgrade_db
183 .write_batch(batch)
184 .expect("deleting old sapling note commitment subtrees is a valid database operation");
185
186 if !matches!(cancel_receiver.try_recv(), Err(TryRecvError::Empty)) {
187 return Err(CancelFormatChange);
188 }
189
190 let mut batch = DiskWriteBatch::new();
191 batch.delete_range_orchard_subtree(upgrade_db, 0.into(), u16::MAX.into());
192 upgrade_db
193 .write_batch(batch)
194 .expect("deleting old orchard note commitment subtrees is a valid database operation");
195
196 Ok(())
197}
198
199pub fn subtree_format_calculation_pre_checks(db: &ZebraDb) -> Result<(), String> {
207 let sapling_result = quick_check_sapling_subtrees(db);
209 let orchard_result = quick_check_orchard_subtrees(db);
210
211 if sapling_result.is_err() || orchard_result.is_err() {
212 let err = Err(format!(
213 "missing or bad first subtree: sapling: {sapling_result:?}, orchard: {orchard_result:?}"
214 ));
215 warn!(?err);
216 return err;
217 }
218
219 Ok(())
220}
221
222fn first_sapling_mainnet_subtree() -> NoteCommitmentSubtree<sapling_crypto::Node> {
224 NoteCommitmentSubtree {
229 index: 0.into(),
230 root: sapling_crypto::Node::from_bytes(hex!(
231 "754bb593ea42d231a7ddf367640f09bbf59dc00f2c1d2003cc340e0c016b5b13"
232 ))
233 .expect("test vector is valid"),
234 end_height: Height(558822),
235 }
236}
237
238fn first_orchard_mainnet_subtree() -> NoteCommitmentSubtree<orchard::tree::Node> {
240 NoteCommitmentSubtree {
245 index: 0.into(),
246 root: hex!("d4e323b3ae0cabfb6be4087fec8c66d9a9bbfc354bf1d9588b6620448182063b")
247 .as_slice()
248 .try_into()
249 .expect("test vector is valid"),
250 end_height: Height(1707429),
251 }
252}
253
254fn quick_check_sapling_subtrees(db: &ZebraDb) -> Result<(), &'static str> {
261 if db.network() != Mainnet {
263 return Ok(());
264 }
265
266 let Some(NoteCommitmentSubtreeIndex(tip_subtree_index)) =
267 db.sapling_tree_for_tip().subtree_index()
268 else {
269 return Ok(());
270 };
271
272 if tip_subtree_index == 0 && !db.sapling_tree_for_tip().is_complete_subtree() {
273 return Ok(());
274 }
275
276 let first_complete_subtree = db
278 .sapling_tree_by_height_range(..)
279 .tuple_windows()
281 .map(|((prev_end_height, prev_tree), (end_height, tree))| {
282 (prev_end_height, prev_tree, end_height, tree)
283 })
284 .find(|(_prev_end_height, prev_tree, _end_height, tree)| {
285 tree.contains_new_subtree(prev_tree)
286 });
287
288 let Some((prev_end_height, prev_tree, end_height, tree)) = first_complete_subtree else {
289 let result = Err("iterator did not find complete subtree, but the tree has it");
290 error!(?result);
291 return result;
292 };
293
294 let expected_subtree = first_sapling_mainnet_subtree();
296
297 let db_subtree = calculate_sapling_subtree(db, prev_end_height, prev_tree, end_height, tree);
298
299 if db_subtree != expected_subtree {
300 let result = Err("first subtree did not match expected test vector");
301 error!(?result, ?db_subtree, ?expected_subtree);
302 return result;
303 }
304
305 Ok(())
306}
307
308fn quick_check_orchard_subtrees(db: &ZebraDb) -> Result<(), &'static str> {
315 if db.network() != Mainnet {
317 return Ok(());
318 }
319
320 let Some(NoteCommitmentSubtreeIndex(tip_subtree_index)) =
321 db.orchard_tree_for_tip().subtree_index()
322 else {
323 return Ok(());
324 };
325
326 if tip_subtree_index == 0 && !db.orchard_tree_for_tip().is_complete_subtree() {
327 return Ok(());
328 }
329
330 let first_complete_subtree = db
332 .orchard_tree_by_height_range(..)
333 .tuple_windows()
335 .map(|((prev_end_height, prev_tree), (end_height, tree))| {
336 (prev_end_height, prev_tree, end_height, tree)
337 })
338 .find(|(_prev_end_height, prev_tree, _end_height, tree)| {
339 tree.contains_new_subtree(prev_tree)
340 });
341
342 let Some((prev_end_height, prev_tree, end_height, tree)) = first_complete_subtree else {
343 let result = Err("iterator did not find complete subtree, but the tree has it");
344 error!(?result);
345 return result;
346 };
347
348 let expected_subtree = first_orchard_mainnet_subtree();
350
351 let db_subtree = calculate_orchard_subtree(db, prev_end_height, prev_tree, end_height, tree);
352
353 if db_subtree != expected_subtree {
354 let result = Err("first subtree did not match expected test vector");
355 error!(?result, ?db_subtree, ?expected_subtree);
356 return result;
357 }
358
359 Ok(())
360}
361
362fn check_sapling_subtrees(
366 db: &ZebraDb,
367 cancel_receiver: &Receiver<CancelFormatChange>,
368) -> Result<Result<(), &'static str>, CancelFormatChange> {
369 let Some(NoteCommitmentSubtreeIndex(mut first_incomplete_subtree_index)) =
370 db.sapling_tree_for_tip().subtree_index()
371 else {
372 return Ok(Ok(()));
373 };
374
375 if db.sapling_tree_for_tip().is_complete_subtree() {
377 first_incomplete_subtree_index += 1;
378 }
379
380 let mut result = Ok(());
381 for index in 0..first_incomplete_subtree_index {
382 if !matches!(cancel_receiver.try_recv(), Err(TryRecvError::Empty)) {
384 return Err(CancelFormatChange);
385 }
386
387 let Some(subtree) = db.sapling_subtree_by_index(index) else {
389 result = Err("missing subtree");
390 error!(?result, index);
391 continue;
392 };
393
394 let Some(tree) = db.sapling_tree_by_height(&subtree.end_height) else {
396 result = Err("missing note commitment tree at subtree completion height");
397 error!(?result, ?subtree.end_height);
398 continue;
399 };
400
401 if let Some((index, node)) = tree.completed_subtree_index_and_root() {
403 if subtree.index != index {
404 result = Err("completed subtree indexes should match");
405 error!(?result);
406 }
407
408 if subtree.root != node {
409 result = Err("completed subtree roots should match");
410 error!(?result);
411 }
412 }
413 else {
415 let Ok(prev_height) = subtree.end_height.previous() else {
416 result = Err("Note commitment subtrees should not end at the minimal height");
417 error!(?result, ?subtree.end_height);
418 continue;
419 };
420
421 let Some(prev_tree) = db.sapling_tree_by_height(&prev_height) else {
422 result = Err("missing note commitment tree below subtree completion height");
423 error!(?result, ?subtree.end_height);
424 continue;
425 };
426
427 let prev_subtree_index = prev_tree.subtree_index();
428 let subtree_index = tree.subtree_index();
429 if subtree_index <= prev_subtree_index {
430 result =
431 Err("note commitment tree at end height should have incremented subtree index");
432 error!(?result, ?subtree_index, ?prev_subtree_index,);
433 }
434 }
435 }
436
437 let mut subtree_count = 0;
438 for (index, height, tree) in db
439 .sapling_tree_by_height_range(..)
440 .filter_map(|(height, tree)| Some((tree.subtree_index()?, height, tree)))
442 .filter_map(|(subtree_index, height, tree)| {
444 if tree.is_complete_subtree() || subtree_index.0 > subtree_count {
445 let subtree_index = subtree_count;
446 subtree_count += 1;
447 Some((subtree_index, height, tree))
448 } else {
449 None
450 }
451 })
452 {
453 if !matches!(cancel_receiver.try_recv(), Err(TryRecvError::Empty)) {
455 return Err(CancelFormatChange);
456 }
457
458 let Some(subtree) = db.sapling_subtree_by_index(index) else {
460 result = Err("missing subtree");
461 error!(?result, index);
462 continue;
463 };
464
465 if subtree.end_height != height {
467 let is_complete = tree.is_complete_subtree();
468 result = Err("bad sapling subtree end height");
469 error!(?result, ?subtree.end_height, ?height, ?index, ?is_complete, );
470 }
471
472 if let Some((_index, node)) = tree.completed_subtree_index_and_root() {
474 if subtree.root != node {
475 result = Err("completed subtree roots should match");
476 error!(?result);
477 }
478 }
479 }
480
481 if result.is_err() {
482 error!(
483 ?result,
484 ?subtree_count,
485 first_incomplete_subtree_index,
486 "missing or bad sapling subtrees"
487 );
488 }
489
490 Ok(result)
491}
492
493fn check_orchard_subtrees(
497 db: &ZebraDb,
498 cancel_receiver: &Receiver<CancelFormatChange>,
499) -> Result<Result<(), &'static str>, CancelFormatChange> {
500 let Some(NoteCommitmentSubtreeIndex(mut first_incomplete_subtree_index)) =
501 db.orchard_tree_for_tip().subtree_index()
502 else {
503 return Ok(Ok(()));
504 };
505
506 if db.orchard_tree_for_tip().is_complete_subtree() {
508 first_incomplete_subtree_index += 1;
509 }
510
511 let mut result = Ok(());
512 for index in 0..first_incomplete_subtree_index {
513 if !matches!(cancel_receiver.try_recv(), Err(TryRecvError::Empty)) {
515 return Err(CancelFormatChange);
516 }
517
518 let Some(subtree) = db.orchard_subtree_by_index(index) else {
520 result = Err("missing subtree");
521 error!(?result, index);
522 continue;
523 };
524
525 let Some(tree) = db.orchard_tree_by_height(&subtree.end_height) else {
527 result = Err("missing note commitment tree at subtree completion height");
528 error!(?result, ?subtree.end_height);
529 continue;
530 };
531
532 if let Some((index, node)) = tree.completed_subtree_index_and_root() {
534 if subtree.index != index {
535 result = Err("completed subtree indexes should match");
536 error!(?result);
537 }
538
539 if subtree.root != node {
540 result = Err("completed subtree roots should match");
541 error!(?result);
542 }
543 }
544 else {
546 let Ok(prev_height) = subtree.end_height.previous() else {
547 result = Err("Note commitment subtrees should not end at the minimal height");
548 error!(?result, ?subtree.end_height);
549 continue;
550 };
551
552 let Some(prev_tree) = db.orchard_tree_by_height(&prev_height) else {
553 result = Err("missing note commitment tree below subtree completion height");
554 error!(?result, ?subtree.end_height);
555 continue;
556 };
557
558 let prev_subtree_index = prev_tree.subtree_index();
559 let subtree_index = tree.subtree_index();
560 if subtree_index <= prev_subtree_index {
561 result =
562 Err("note commitment tree at end height should have incremented subtree index");
563 error!(?result, ?subtree_index, ?prev_subtree_index,);
564 }
565 }
566 }
567
568 let mut subtree_count = 0;
569 for (index, height, tree) in db
570 .orchard_tree_by_height_range(..)
571 .filter_map(|(height, tree)| Some((tree.subtree_index()?, height, tree)))
573 .filter_map(|(subtree_index, height, tree)| {
575 if tree.is_complete_subtree() || subtree_index.0 > subtree_count {
576 let subtree_index = subtree_count;
577 subtree_count += 1;
578 Some((subtree_index, height, tree))
579 } else {
580 None
581 }
582 })
583 {
584 if !matches!(cancel_receiver.try_recv(), Err(TryRecvError::Empty)) {
586 return Err(CancelFormatChange);
587 }
588
589 let Some(subtree) = db.orchard_subtree_by_index(index) else {
591 result = Err("missing subtree");
592 error!(?result, index);
593 continue;
594 };
595
596 if subtree.end_height != height {
598 let is_complete = tree.is_complete_subtree();
599 result = Err("bad orchard subtree end height");
600 error!(?result, ?subtree.end_height, ?height, ?index, ?is_complete, );
601 }
602
603 if let Some((_index, node)) = tree.completed_subtree_index_and_root() {
605 if subtree.root != node {
606 result = Err("completed subtree roots should match");
607 error!(?result);
608 }
609 }
610 }
611
612 if result.is_err() {
613 error!(
614 ?result,
615 ?subtree_count,
616 first_incomplete_subtree_index,
617 "missing or bad orchard subtrees"
618 );
619 }
620
621 Ok(result)
622}
623
624#[must_use = "subtree should be written to the database after it is calculated"]
636#[instrument(skip(read_db, prev_tree, tree))]
637fn calculate_sapling_subtree(
638 read_db: &ZebraDb,
639 prev_end_height: Height,
640 prev_tree: Arc<sapling::tree::NoteCommitmentTree>,
641 end_height: Height,
642 tree: Arc<sapling::tree::NoteCommitmentTree>,
643) -> NoteCommitmentSubtree<sapling_crypto::Node> {
644 if let Some((index, node)) = tree.completed_subtree_index_and_root() {
647 NoteCommitmentSubtree::new(index, end_height, node)
650 } else {
651 let prev_position = prev_tree.position().unwrap_or_else(|| {
656 panic!(
657 "previous block must have a partial subtree:\n\
658 previous subtree:\n\
659 height: {prev_end_height:?}\n\
660 current subtree:\n\
661 height: {end_height:?}"
662 )
663 });
664 let prev_index = prev_tree
665 .subtree_index()
666 .expect("previous block must have a partial subtree");
667 let prev_remaining_notes = prev_tree.remaining_subtree_leaf_nodes();
668
669 let current_position = tree.position().unwrap_or_else(|| {
670 panic!(
671 "current block must have a subtree:\n\
672 previous subtree:\n\
673 height: {prev_end_height:?}\n\
674 index: {prev_index}\n\
675 position: {prev_position}\n\
676 remaining: {prev_remaining_notes}\n\
677 current subtree:\n\
678 height: {end_height:?}"
679 )
680 });
681 let current_index = tree
682 .subtree_index()
683 .expect("current block must have a subtree");
684 let current_remaining_notes = tree.remaining_subtree_leaf_nodes();
685
686 assert_eq!(
687 prev_index.0 + 1,
688 current_index.0,
689 "subtree must have been completed by the current block:\n\
690 previous subtree:\n\
691 height: {prev_end_height:?}\n\
692 index: {prev_index}\n\
693 position: {prev_position}\n\
694 remaining: {prev_remaining_notes}\n\
695 current subtree:\n\
696 height: {end_height:?}\n\
697 index: {current_index}\n\
698 position: {current_position}\n\
699 remaining: {current_remaining_notes}"
700 );
701
702 let block = read_db
707 .block(end_height.into())
708 .expect("height with note commitment tree should have block");
709 let sapling_note_commitments = block
710 .sapling_note_commitments()
711 .take(prev_remaining_notes)
712 .cloned()
713 .collect();
714
715 let (sapling_nct, subtree) = NoteCommitmentTrees::update_sapling_note_commitment_tree(
717 prev_tree,
718 sapling_note_commitments,
719 )
720 .expect("finalized notes should append successfully");
721
722 let (index, node) = subtree.unwrap_or_else(|| {
723 panic!(
724 "already checked that the block completed a subtree:\n\
725 updated subtree:\n\
726 index: {:?}\n\
727 position: {:?}\n\
728 remaining notes: {}\n\
729 original previous subtree:\n\
730 height: {prev_end_height:?}\n\
731 index: {prev_index}\n\
732 position: {prev_position}\n\
733 remaining: {prev_remaining_notes}\n\
734 original current subtree:\n\
735 height: {end_height:?}\n\
736 index: {current_index}\n\
737 position: {current_position}\n\
738 remaining: {current_remaining_notes}",
739 sapling_nct.subtree_index(),
740 sapling_nct.position(),
741 sapling_nct.remaining_subtree_leaf_nodes(),
742 )
743 });
744
745 NoteCommitmentSubtree::new(index, end_height, node)
746 }
747}
748
749#[must_use = "subtree should be written to the database after it is calculated"]
761#[instrument(skip(read_db, prev_tree, tree))]
762fn calculate_orchard_subtree(
763 read_db: &ZebraDb,
764 prev_end_height: Height,
765 prev_tree: Arc<orchard::tree::NoteCommitmentTree>,
766 end_height: Height,
767 tree: Arc<orchard::tree::NoteCommitmentTree>,
768) -> NoteCommitmentSubtree<orchard::tree::Node> {
769 if let Some((index, node)) = tree.completed_subtree_index_and_root() {
772 NoteCommitmentSubtree::new(index, end_height, node)
775 } else {
776 let prev_position = prev_tree.position().unwrap_or_else(|| {
781 panic!(
782 "previous block must have a partial subtree:\n\
783 previous subtree:\n\
784 height: {prev_end_height:?}\n\
785 current subtree:\n\
786 height: {end_height:?}"
787 )
788 });
789 let prev_index = prev_tree
790 .subtree_index()
791 .expect("previous block must have a partial subtree");
792 let prev_remaining_notes = prev_tree.remaining_subtree_leaf_nodes();
793
794 let current_position = tree.position().unwrap_or_else(|| {
795 panic!(
796 "current block must have a subtree:\n\
797 previous subtree:\n\
798 height: {prev_end_height:?}\n\
799 index: {prev_index}\n\
800 position: {prev_position}\n\
801 remaining: {prev_remaining_notes}\n\
802 current subtree:\n\
803 height: {end_height:?}"
804 )
805 });
806 let current_index = tree
807 .subtree_index()
808 .expect("current block must have a subtree");
809 let current_remaining_notes = tree.remaining_subtree_leaf_nodes();
810
811 assert_eq!(
812 prev_index.0 + 1,
813 current_index.0,
814 "subtree must have been completed by the current block:\n\
815 previous subtree:\n\
816 height: {prev_end_height:?}\n\
817 index: {prev_index}\n\
818 position: {prev_position}\n\
819 remaining: {prev_remaining_notes}\n\
820 current subtree:\n\
821 height: {end_height:?}\n\
822 index: {current_index}\n\
823 position: {current_position}\n\
824 remaining: {current_remaining_notes}"
825 );
826
827 let block = read_db
832 .block(end_height.into())
833 .expect("height with note commitment tree should have block");
834 let orchard_note_commitments = block
835 .orchard_note_commitments()
836 .take(prev_remaining_notes)
837 .cloned()
838 .collect();
839
840 let (orchard_nct, subtree) = NoteCommitmentTrees::update_orchard_note_commitment_tree(
842 prev_tree,
843 orchard_note_commitments,
844 )
845 .expect("finalized notes should append successfully");
846
847 let (index, node) = subtree.unwrap_or_else(|| {
848 panic!(
849 "already checked that the block completed a subtree:\n\
850 updated subtree:\n\
851 index: {:?}\n\
852 position: {:?}\n\
853 remaining notes: {}\n\
854 original previous subtree:\n\
855 height: {prev_end_height:?}\n\
856 index: {prev_index}\n\
857 position: {prev_position}\n\
858 remaining: {prev_remaining_notes}\n\
859 original current subtree:\n\
860 height: {end_height:?}\n\
861 index: {current_index}\n\
862 position: {current_position}\n\
863 remaining: {current_remaining_notes}",
864 orchard_nct.subtree_index(),
865 orchard_nct.position(),
866 orchard_nct.remaining_subtree_leaf_nodes(),
867 )
868 });
869
870 NoteCommitmentSubtree::new(index, end_height, node)
871 }
872}
873
874fn write_sapling_subtree(
876 upgrade_db: &ZebraDb,
877 subtree: NoteCommitmentSubtree<sapling_crypto::Node>,
878) {
879 let mut batch = DiskWriteBatch::new();
880
881 batch.insert_sapling_subtree(upgrade_db, &subtree);
882
883 upgrade_db
884 .write_batch(batch)
885 .expect("writing sapling note commitment subtrees should always succeed.");
886
887 if subtree.index.0 % 100 == 0 {
888 info!(end_height = ?subtree.end_height, index = ?subtree.index.0, "calculated and added sapling subtree");
889 }
890 debug!(end_height = ?subtree.end_height, index = ?subtree.index.0, "calculated and added sapling subtree");
892}
893
894fn write_orchard_subtree(
896 upgrade_db: &ZebraDb,
897 subtree: NoteCommitmentSubtree<orchard::tree::Node>,
898) {
899 let mut batch = DiskWriteBatch::new();
900
901 batch.insert_orchard_subtree(upgrade_db, &subtree);
902
903 upgrade_db
904 .write_batch(batch)
905 .expect("writing orchard note commitment subtrees should always succeed.");
906
907 if subtree.index.0 % 100 == 0 {
908 info!(end_height = ?subtree.end_height, index = ?subtree.index.0, "calculated and added orchard subtree");
909 }
910 debug!(end_height = ?subtree.end_height, index = ?subtree.index.0, "calculated and added orchard subtree");
912}