Skip to main content

zebra_state/service/finalized_state/disk_format/upgrade/
add_subtrees.rs

1//! Fully populate the Sapling and Orchard note commitment subtrees for existing blocks in the database.
2
3use 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
25/// Implements [`DiskFormatUpgrade`] for populating Sapling and Orchard note commitment subtrees.
26pub 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            // Clear previous upgrade data, because it was incorrect.
47            reset(initial_tip_height, upgrade_db, cancel_receiver)?;
48        }
49
50        Ok(())
51    }
52
53    /// Runs disk format upgrade for adding Sapling and Orchard note commitment subtrees to database.
54    ///
55    /// Trees are added to the database in reverse height order, so that wallets can sync correctly
56    /// while the upgrade is running.
57    ///
58    /// Returns `Ok` if the upgrade completed, and `Err` if it was cancelled.
59    fn run(
60        &self,
61        initial_tip_height: Height,
62        upgrade_db: &ZebraDb,
63        cancel_receiver: &Receiver<CancelFormatChange>,
64    ) -> Result<(), CancelFormatChange> {
65        // # Consensus
66        //
67        // Zebra stores exactly one note commitment tree for every block with sapling notes.
68        // (It also stores the empty note commitment tree for the genesis block, but we skip that.)
69        //
70        // The consensus rules limit blocks to less than 2^16 sapling and 2^16 orchard outputs. So a
71        // block can't complete multiple level 16 subtrees (or complete an entire subtree by itself).
72        // Currently, with 2MB blocks and v4/v5 sapling and orchard output sizes, the subtree index can
73        // increase by at most 1 every ~20 blocks.
74        //
75        // # Compatibility
76        //
77        // Because wallets search backwards from the chain tip, subtrees need to be added to the
78        // database in reverse height order. (Tip first, genesis last.)
79        //
80        // Otherwise, wallets that sync during the upgrade will be missing some notes.
81
82        // Generate a list of sapling subtree inputs: previous and current trees, and their end heights.
83        let subtrees = upgrade_db
84            .sapling_tree_by_reversed_height_range(..=initial_tip_height)
85            // We need both the tree and its previous tree for each shielded block.
86            .tuple_windows()
87            // Because the iterator is reversed, the larger tree is first.
88            .map(|((end_height, tree), (prev_end_height, prev_tree))| {
89                (prev_end_height, prev_tree, end_height, tree)
90            })
91            // Find new subtrees.
92            .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            // Return early if the upgrade is cancelled.
98            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        // Generate a list of orchard subtree inputs: previous and current trees, and their end heights.
108        let subtrees = upgrade_db
109            .orchard_tree_by_reversed_height_range(..=initial_tip_height)
110            // We need both the tree and its previous tree for each shielded block.
111            .tuple_windows()
112            // Because the iterator is reversed, the larger tree is first.
113            .map(|((end_height, tree), (prev_end_height, prev_tree))| {
114                (prev_end_height, prev_tree, end_height, tree)
115            })
116            // Find new subtrees.
117            .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            // Return early if the upgrade is cancelled.
123            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        // This is redundant in some code paths, but not in others. But it's quick anyway.
142        let quick_result = subtree_format_calculation_pre_checks(db);
143
144        // Check the entire format before returning any errors.
145        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/// Reset data from previous upgrades. This data can be complete or incomplete.
162///
163/// Returns `Ok` if the upgrade completed, and `Err` if it was cancelled.
164#[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    // Return early if the upgrade is cancelled.
172    if !matches!(cancel_receiver.try_recv(), Err(TryRecvError::Empty)) {
173        return Err(CancelFormatChange);
174    }
175
176    // This doesn't delete the maximum index, but the consensus rules make that subtree impossible.
177    // (Adding a note to a full note commitment tree is an error.)
178    //
179    // TODO: convert zs_delete_range() to take std::ops::RangeBounds, and delete the upper bound.
180    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
199/// Quickly check that the first calculated subtree is correct.
200///
201/// This allows us to fail the upgrade quickly in tests and during development,
202/// rather than waiting ~20 minutes to see if it failed.
203///
204/// This check runs the first subtree calculation, but it doesn't read the subtree data in the
205/// database. So it can be run before the upgrade is started.
206pub fn subtree_format_calculation_pre_checks(db: &ZebraDb) -> Result<(), String> {
207    // Check the entire format before returning any errors.
208    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
222/// A quick test vector that allows us to fail an incorrect upgrade within a few seconds.
223fn first_sapling_mainnet_subtree() -> NoteCommitmentSubtree<sapling_crypto::Node> {
224    // This test vector was generated using the command:
225    // ```sh
226    // zcash-cli z_getsubtreesbyindex sapling 0 1
227    // ```
228    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
238/// A quick test vector that allows us to fail an incorrect upgrade within a few seconds.
239fn first_orchard_mainnet_subtree() -> NoteCommitmentSubtree<orchard::tree::Node> {
240    // This test vector was generated using the command:
241    // ```sh
242    // zcash-cli z_getsubtreesbyindex orchard 0 1
243    // ```
244    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
254/// Quickly check that the first calculated sapling subtree is correct.
255///
256/// This allows us to fail the upgrade quickly in tests and during development,
257/// rather than waiting ~20 minutes to see if it failed.
258///
259/// Returns an error if a note commitment subtree is missing or incorrect.
260fn quick_check_sapling_subtrees(db: &ZebraDb) -> Result<(), &'static str> {
261    // We check the first sapling subtree on mainnet, so skip this check if it isn't available.
262    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    // Find the first complete subtree: previous and current trees, and their end heights.
277    let first_complete_subtree = db
278        .sapling_tree_by_height_range(..)
279        // We need both the tree and its previous tree for each shielded block.
280        .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    // Creating this test vector involves a cryptographic check, so only do it once.
295    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
308/// Quickly check that the first calculated orchard subtree is correct.
309///
310/// This allows us to fail the upgrade quickly in tests and during development,
311/// rather than waiting ~20 minutes to see if it failed.
312///
313/// Returns an error if a note commitment subtree is missing or incorrect.
314fn quick_check_orchard_subtrees(db: &ZebraDb) -> Result<(), &'static str> {
315    // We check the first orchard subtree on mainnet, so skip this check if it isn't available.
316    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    // Find the first complete subtree: previous and current trees, and their end heights.
331    let first_complete_subtree = db
332        .orchard_tree_by_height_range(..)
333        // We need both the tree and its previous tree for each shielded block.
334        .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    // Creating this test vector involves a cryptographic check, so only do it once.
349    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
362/// Check that Sapling note commitment subtrees were correctly added.
363///
364/// Returns an error if a note commitment subtree is missing or incorrect.
365fn 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 there are no incomplete subtrees in the tree, also expect a subtree for the final index.
376    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        // Return early if the format check is cancelled.
383        if !matches!(cancel_receiver.try_recv(), Err(TryRecvError::Empty)) {
384            return Err(CancelFormatChange);
385        }
386
387        // Check that there's a continuous range of subtrees from index [0, first_incomplete_subtree_index)
388        let Some(subtree) = db.sapling_subtree_by_index(index) else {
389            result = Err("missing subtree");
390            error!(?result, index);
391            continue;
392        };
393
394        // Check that there was a sapling note at the subtree's end height.
395        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        // Check the index and root if the sapling note commitment tree at this height is a complete subtree.
402        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        // Check that the final note has a greater subtree index if it didn't complete a subtree.
414        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        // Exclude empty sapling tree and add subtree indexes
441        .filter_map(|(height, tree)| Some((tree.subtree_index()?, height, tree)))
442        // Exclude heights that don't complete a subtree and count completed subtrees
443        .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        // Return early if the format check is cancelled.
454        if !matches!(cancel_receiver.try_recv(), Err(TryRecvError::Empty)) {
455            return Err(CancelFormatChange);
456        }
457
458        // Check that there's an entry for every completed sapling subtree root in all sapling trees
459        let Some(subtree) = db.sapling_subtree_by_index(index) else {
460            result = Err("missing subtree");
461            error!(?result, index);
462            continue;
463        };
464
465        // Check that the subtree end height matches that in the sapling trees.
466        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        // Check the root if the sapling note commitment tree at this height is a complete subtree.
473        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
493/// Check that Orchard note commitment subtrees were correctly added.
494///
495/// Returns an error if a note commitment subtree is missing or incorrect.
496fn 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 there are no incomplete subtrees in the tree, also expect a subtree for the final index.
507    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        // Return early if the format check is cancelled.
514        if !matches!(cancel_receiver.try_recv(), Err(TryRecvError::Empty)) {
515            return Err(CancelFormatChange);
516        }
517
518        // Check that there's a continuous range of subtrees from index [0, first_incomplete_subtree_index)
519        let Some(subtree) = db.orchard_subtree_by_index(index) else {
520            result = Err("missing subtree");
521            error!(?result, index);
522            continue;
523        };
524
525        // Check that there was a orchard note at the subtree's end height.
526        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        // Check the index and root if the orchard note commitment tree at this height is a complete subtree.
533        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        // Check that the final note has a greater subtree index if it didn't complete a subtree.
545        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        // Exclude empty orchard tree and add subtree indexes
572        .filter_map(|(height, tree)| Some((tree.subtree_index()?, height, tree)))
573        // Exclude heights that don't complete a subtree and count completed subtrees
574        .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        // Return early if the format check is cancelled.
585        if !matches!(cancel_receiver.try_recv(), Err(TryRecvError::Empty)) {
586            return Err(CancelFormatChange);
587        }
588
589        // Check that there's an entry for every completed orchard subtree root in all orchard trees
590        let Some(subtree) = db.orchard_subtree_by_index(index) else {
591            result = Err("missing subtree");
592            error!(?result, index);
593            continue;
594        };
595
596        // Check that the subtree end height matches that in the orchard trees.
597        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        // Check the root if the orchard note commitment tree at this height is a complete subtree.
604        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/// Calculates a note commitment subtree for Sapling, reading blocks from `read_db` if needed.
625///
626/// The subtree must be completed by a note commitment in the block at `end_height`.
627/// `tree` is the tree for that block, and `prev_tree` is the tree for the previous block.
628///
629/// `prev_tree` is only used to rebuild the subtree if it was completed without using the last
630/// note commitment in the block at `end_height`.
631///
632/// # Panics
633///
634/// If a subtree is not completed by a note commitment in the block at `end_height`.
635#[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 a subtree is completed by a note commitment in the block at `end_height`,
645    // then that subtree can be completed in two different ways:
646    if let Some((index, node)) = tree.completed_subtree_index_and_root() {
647        // If the subtree is completed by the last note commitment in that block,
648        // we already have that subtree root available in the tree.
649        NoteCommitmentSubtree::new(index, end_height, node)
650    } else {
651        // If the subtree is completed without using the last note commitment in the block,
652        // we need to calculate the subtree root, starting with the tree from the previous block.
653
654        // TODO: move the assertion/panic log string formatting into a separate function?
655        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        // Get the missing notes needed to complete the subtree.
703        //
704        // TODO: consider just reading the block's transactions from the database file,
705        //       because we don't use the block header data at all.
706        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        // This takes less than 1 second per tree, so we don't need to make it cancellable.
716        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/// Calculates a note commitment subtree for Orchard, reading blocks from `read_db` if needed.
750///
751/// The subtree must be completed by a note commitment in the block at `end_height`.
752/// `tree` is the tree for that block, and `prev_tree` is the tree for the previous block.
753///
754/// `prev_tree` is only used to rebuild the subtree if it was completed without using the last
755/// note commitment in the block at `end_height`.
756///
757/// # Panics
758///
759/// If a subtree is not completed by a note commitment in the block at `end_height`.
760#[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 a subtree is completed by a note commitment in the block at `end_height`,
770    // then that subtree can be completed in two different ways:
771    if let Some((index, node)) = tree.completed_subtree_index_and_root() {
772        // If the subtree is completed by the last note commitment in that block,
773        // we already have that subtree root available in the tree.
774        NoteCommitmentSubtree::new(index, end_height, node)
775    } else {
776        // If the subtree is completed without using the last note commitment in the block,
777        // we need to calculate the subtree root, starting with the tree from the previous block.
778
779        // TODO: move the assertion/panic log string formatting into a separate function?
780        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        // Get the missing notes needed to complete the subtree.
828        //
829        // TODO: consider just reading the block's transactions from the database file,
830        //       because we don't use the block header data at all.
831        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        // This takes less than 1 second per tree, so we don't need to make it cancellable.
841        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
874/// Writes a Sapling note commitment subtree to `upgrade_db`.
875fn 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    // This log happens about once per second on recent machines with SSD disks.
891    debug!(end_height = ?subtree.end_height, index = ?subtree.index.0, "calculated and added sapling subtree");
892}
893
894/// Writes an Orchard note commitment subtree to `upgrade_db`.
895fn 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    // This log happens about once per second on recent machines with SSD disks.
911    debug!(end_height = ?subtree.end_height, index = ?subtree.index.0, "calculated and added orchard subtree");
912}