//! Fully populate the Sapling and Orchard note commitment subtrees for existing blocks in the database. use std::sync::{mpsc, Arc}; use hex_literal::hex; use itertools::Itertools; use tracing::instrument; use zebra_chain::{ block::Height, orchard, parallel::tree::NoteCommitmentTrees, parameters::Network::*, sapling, subtree::{NoteCommitmentSubtree, NoteCommitmentSubtreeIndex}, }; use crate::service::finalized_state::{ disk_format::upgrade::CancelFormatChange, DiskWriteBatch, ZebraDb, }; /// Runs disk format upgrade for adding Sapling and Orchard note commitment subtrees to database. /// /// Trees are added to the database in reverse height order, so that wallets can sync correctly /// while the upgrade is running. /// /// Returns `Ok` if the upgrade completed, and `Err` if it was cancelled. #[allow(clippy::unwrap_in_result)] #[instrument(skip(upgrade_db, cancel_receiver))] pub fn run( initial_tip_height: Height, upgrade_db: &ZebraDb, cancel_receiver: &mpsc::Receiver, ) -> Result<(), CancelFormatChange> { // # Consensus // // Zebra stores exactly one note commitment tree for every block with sapling notes. // (It also stores the empty note commitment tree for the genesis block, but we skip that.) // // The consensus rules limit blocks to less than 2^16 sapling and 2^16 orchard outputs. So a // block can't complete multiple level 16 subtrees (or complete an entire subtree by itself). // Currently, with 2MB blocks and v4/v5 sapling and orchard output sizes, the subtree index can // increase by at most 1 every ~20 blocks. // // # Compatibility // // Because wallets search backwards from the chain tip, subtrees need to be added to the // database in reverse height order. (Tip first, genesis last.) // // Otherwise, wallets that sync during the upgrade will be missing some notes. // Generate a list of sapling subtree inputs: previous and current trees, and their end heights. let subtrees = upgrade_db .sapling_tree_by_reversed_height_range(..=initial_tip_height) // We need both the tree and its previous tree for each shielded block. .tuple_windows() // Because the iterator is reversed, the larger tree is first. .map(|((end_height, tree), (prev_end_height, prev_tree))| { (prev_end_height, prev_tree, end_height, tree) }) // Find new subtrees. .filter(|(_prev_end_height, prev_tree, _end_height, tree)| { tree.contains_new_subtree(prev_tree) }); for (prev_end_height, prev_tree, end_height, tree) in subtrees { // Return early if the upgrade is cancelled. if !matches!(cancel_receiver.try_recv(), Err(mpsc::TryRecvError::Empty)) { return Err(CancelFormatChange); } let subtree = calculate_sapling_subtree(upgrade_db, prev_end_height, prev_tree, end_height, tree); write_sapling_subtree(upgrade_db, subtree); } // Generate a list of orchard subtree inputs: previous and current trees, and their end heights. let subtrees = upgrade_db .orchard_tree_by_reversed_height_range(..=initial_tip_height) // We need both the tree and its previous tree for each shielded block. .tuple_windows() // Because the iterator is reversed, the larger tree is first. .map(|((end_height, tree), (prev_end_height, prev_tree))| { (prev_end_height, prev_tree, end_height, tree) }) // Find new subtrees. .filter(|(_prev_end_height, prev_tree, _end_height, tree)| { tree.contains_new_subtree(prev_tree) }); for (prev_end_height, prev_tree, end_height, tree) in subtrees { // Return early if the upgrade is cancelled. if !matches!(cancel_receiver.try_recv(), Err(mpsc::TryRecvError::Empty)) { return Err(CancelFormatChange); } let subtree = calculate_orchard_subtree(upgrade_db, prev_end_height, prev_tree, end_height, tree); write_orchard_subtree(upgrade_db, subtree); } Ok(()) } /// Reset data from previous upgrades. This data can be complete or incomplete. /// /// Returns `Ok` if the upgrade completed, and `Err` if it was cancelled. #[allow(clippy::unwrap_in_result)] #[instrument(skip(upgrade_db, cancel_receiver))] pub fn reset( _initial_tip_height: Height, upgrade_db: &ZebraDb, cancel_receiver: &mpsc::Receiver, ) -> Result<(), CancelFormatChange> { // Return early if the upgrade is cancelled. if !matches!(cancel_receiver.try_recv(), Err(mpsc::TryRecvError::Empty)) { return Err(CancelFormatChange); } // This doesn't delete the maximum index, but the consensus rules make that subtree impossible. // (Adding a note to a full note commitment tree is an error.) // // TODO: convert zs_delete_range() to take std::ops::RangeBounds, and delete the upper bound. let mut batch = DiskWriteBatch::new(); batch.delete_range_sapling_subtree(upgrade_db, 0.into(), u16::MAX.into()); upgrade_db .write_batch(batch) .expect("deleting old sapling note commitment subtrees is a valid database operation"); if !matches!(cancel_receiver.try_recv(), Err(mpsc::TryRecvError::Empty)) { return Err(CancelFormatChange); } let mut batch = DiskWriteBatch::new(); batch.delete_range_orchard_subtree(upgrade_db, 0.into(), u16::MAX.into()); upgrade_db .write_batch(batch) .expect("deleting old orchard note commitment subtrees is a valid database operation"); Ok(()) } /// Quickly check that the first calculated subtree is correct. /// /// This allows us to fail the upgrade quickly in tests and during development, /// rather than waiting ~20 minutes to see if it failed. /// /// This check runs the first subtree calculation, but it doesn't read the subtree data in the /// database. So it can be run before the upgrade is started. pub fn subtree_format_calculation_pre_checks(db: &ZebraDb) -> Result<(), String> { // Check the entire format before returning any errors. let sapling_result = quick_check_sapling_subtrees(db); let orchard_result = quick_check_orchard_subtrees(db); if sapling_result.is_err() || orchard_result.is_err() { let err = Err(format!( "missing or bad first subtree: sapling: {sapling_result:?}, orchard: {orchard_result:?}" )); warn!(?err); return err; } Ok(()) } /// A quick test vector that allows us to fail an incorrect upgrade within a few seconds. fn first_sapling_mainnet_subtree() -> NoteCommitmentSubtree { // This test vector was generated using the command: // ```sh // zcash-cli z_getsubtreesbyindex sapling 0 1 // ``` NoteCommitmentSubtree { index: 0.into(), root: hex!("754bb593ea42d231a7ddf367640f09bbf59dc00f2c1d2003cc340e0c016b5b13") .as_slice() .try_into() .expect("test vector is valid"), end_height: Height(558822), } } /// A quick test vector that allows us to fail an incorrect upgrade within a few seconds. fn first_orchard_mainnet_subtree() -> NoteCommitmentSubtree { // This test vector was generated using the command: // ```sh // zcash-cli z_getsubtreesbyindex orchard 0 1 // ``` NoteCommitmentSubtree { index: 0.into(), root: hex!("d4e323b3ae0cabfb6be4087fec8c66d9a9bbfc354bf1d9588b6620448182063b") .as_slice() .try_into() .expect("test vector is valid"), end_height: Height(1707429), } } /// Quickly check that the first calculated sapling subtree is correct. /// /// This allows us to fail the upgrade quickly in tests and during development, /// rather than waiting ~20 minutes to see if it failed. /// /// Returns an error if a note commitment subtree is missing or incorrect. fn quick_check_sapling_subtrees(db: &ZebraDb) -> Result<(), &'static str> { // We check the first sapling subtree on mainnet, so skip this check if it isn't available. if db.network() != Mainnet { return Ok(()); } let Some(NoteCommitmentSubtreeIndex(tip_subtree_index)) = db.sapling_tree_for_tip().subtree_index() else { return Ok(()); }; if tip_subtree_index == 0 && !db.sapling_tree_for_tip().is_complete_subtree() { return Ok(()); } // Find the first complete subtree: previous and current trees, and their end heights. let first_complete_subtree = db .sapling_tree_by_height_range(..) // We need both the tree and its previous tree for each shielded block. .tuple_windows() .map(|((prev_end_height, prev_tree), (end_height, tree))| { (prev_end_height, prev_tree, end_height, tree) }) .find(|(_prev_end_height, prev_tree, _end_height, tree)| { tree.contains_new_subtree(prev_tree) }); let Some((prev_end_height, prev_tree, end_height, tree)) = first_complete_subtree else { let result = Err("iterator did not find complete subtree, but the tree has it"); error!(?result); return result; }; // Creating this test vector involves a cryptographic check, so only do it once. let expected_subtree = first_sapling_mainnet_subtree(); let db_subtree = calculate_sapling_subtree(db, prev_end_height, prev_tree, end_height, tree); if db_subtree != expected_subtree { let result = Err("first subtree did not match expected test vector"); error!(?result, ?db_subtree, ?expected_subtree); return result; } Ok(()) } /// Quickly check that the first calculated orchard subtree is correct. /// /// This allows us to fail the upgrade quickly in tests and during development, /// rather than waiting ~20 minutes to see if it failed. /// /// Returns an error if a note commitment subtree is missing or incorrect. fn quick_check_orchard_subtrees(db: &ZebraDb) -> Result<(), &'static str> { // We check the first orchard subtree on mainnet, so skip this check if it isn't available. if db.network() != Mainnet { return Ok(()); } let Some(NoteCommitmentSubtreeIndex(tip_subtree_index)) = db.orchard_tree_for_tip().subtree_index() else { return Ok(()); }; if tip_subtree_index == 0 && !db.orchard_tree_for_tip().is_complete_subtree() { return Ok(()); } // Find the first complete subtree: previous and current trees, and their end heights. let first_complete_subtree = db .orchard_tree_by_height_range(..) // We need both the tree and its previous tree for each shielded block. .tuple_windows() .map(|((prev_end_height, prev_tree), (end_height, tree))| { (prev_end_height, prev_tree, end_height, tree) }) .find(|(_prev_end_height, prev_tree, _end_height, tree)| { tree.contains_new_subtree(prev_tree) }); let Some((prev_end_height, prev_tree, end_height, tree)) = first_complete_subtree else { let result = Err("iterator did not find complete subtree, but the tree has it"); error!(?result); return result; }; // Creating this test vector involves a cryptographic check, so only do it once. let expected_subtree = first_orchard_mainnet_subtree(); let db_subtree = calculate_orchard_subtree(db, prev_end_height, prev_tree, end_height, tree); if db_subtree != expected_subtree { let result = Err("first subtree did not match expected test vector"); error!(?result, ?db_subtree, ?expected_subtree); return result; } Ok(()) } /// Check that note commitment subtrees were correctly added. pub fn subtree_format_validity_checks_detailed( db: &ZebraDb, cancel_receiver: &mpsc::Receiver, ) -> Result, CancelFormatChange> { // This is redundant in some code paths, but not in others. But it's quick anyway. let quick_result = subtree_format_calculation_pre_checks(db); // Check the entire format before returning any errors. let sapling_result = check_sapling_subtrees(db, cancel_receiver)?; let orchard_result = check_orchard_subtrees(db, cancel_receiver)?; if quick_result.is_err() || sapling_result.is_err() || orchard_result.is_err() { let err = Err(format!( "missing or invalid subtree(s): \ quick: {quick_result:?}, sapling: {sapling_result:?}, orchard: {orchard_result:?}" )); warn!(?err); return Ok(err); } Ok(Ok(())) } /// Check that Sapling note commitment subtrees were correctly added. /// /// Returns an error if a note commitment subtree is missing or incorrect. fn check_sapling_subtrees( db: &ZebraDb, cancel_receiver: &mpsc::Receiver, ) -> Result, CancelFormatChange> { let Some(NoteCommitmentSubtreeIndex(mut first_incomplete_subtree_index)) = db.sapling_tree_for_tip().subtree_index() else { return Ok(Ok(())); }; // If there are no incomplete subtrees in the tree, also expect a subtree for the final index. if db.sapling_tree_for_tip().is_complete_subtree() { first_incomplete_subtree_index += 1; } let mut result = Ok(()); for index in 0..first_incomplete_subtree_index { // Return early if the format check is cancelled. if !matches!(cancel_receiver.try_recv(), Err(mpsc::TryRecvError::Empty)) { return Err(CancelFormatChange); } // Check that there's a continuous range of subtrees from index [0, first_incomplete_subtree_index) let Some(subtree) = db.sapling_subtree_by_index(index) else { result = Err("missing subtree"); error!(?result, index); continue; }; // Check that there was a sapling note at the subtree's end height. let Some(tree) = db.sapling_tree_by_height(&subtree.end_height) else { result = Err("missing note commitment tree at subtree completion height"); error!(?result, ?subtree.end_height); continue; }; // Check the index and root if the sapling note commitment tree at this height is a complete subtree. if let Some((index, node)) = tree.completed_subtree_index_and_root() { if subtree.index != index { result = Err("completed subtree indexes should match"); error!(?result); } if subtree.root != node { result = Err("completed subtree roots should match"); error!(?result); } } // Check that the final note has a greater subtree index if it didn't complete a subtree. else { let prev_height = subtree .end_height .previous() .expect("Note commitment subtrees should not end at the minimal height."); let Some(prev_tree) = db.sapling_tree_by_height(&prev_height) else { result = Err("missing note commitment tree below subtree completion height"); error!(?result, ?subtree.end_height); continue; }; let prev_subtree_index = prev_tree.subtree_index(); let subtree_index = tree.subtree_index(); if subtree_index <= prev_subtree_index { result = Err("note commitment tree at end height should have incremented subtree index"); error!(?result, ?subtree_index, ?prev_subtree_index,); } } } let mut subtree_count = 0; for (index, height, tree) in db .sapling_tree_by_height_range(..) // Exclude empty sapling tree and add subtree indexes .filter_map(|(height, tree)| Some((tree.subtree_index()?, height, tree))) // Exclude heights that don't complete a subtree and count completed subtrees .filter_map(|(subtree_index, height, tree)| { if tree.is_complete_subtree() || subtree_index.0 > subtree_count { let subtree_index = subtree_count; subtree_count += 1; Some((subtree_index, height, tree)) } else { None } }) { // Return early if the format check is cancelled. if !matches!(cancel_receiver.try_recv(), Err(mpsc::TryRecvError::Empty)) { return Err(CancelFormatChange); } // Check that there's an entry for every completed sapling subtree root in all sapling trees let Some(subtree) = db.sapling_subtree_by_index(index) else { result = Err("missing subtree"); error!(?result, index); continue; }; // Check that the subtree end height matches that in the sapling trees. if subtree.end_height != height { let is_complete = tree.is_complete_subtree(); result = Err("bad sapling subtree end height"); error!(?result, ?subtree.end_height, ?height, ?index, ?is_complete, ); } // Check the root if the sapling note commitment tree at this height is a complete subtree. if let Some((_index, node)) = tree.completed_subtree_index_and_root() { if subtree.root != node { result = Err("completed subtree roots should match"); error!(?result); } } } if result.is_err() { error!( ?result, ?subtree_count, first_incomplete_subtree_index, "missing or bad sapling subtrees" ); } Ok(result) } /// Check that Orchard note commitment subtrees were correctly added. /// /// Returns an error if a note commitment subtree is missing or incorrect. fn check_orchard_subtrees( db: &ZebraDb, cancel_receiver: &mpsc::Receiver, ) -> Result, CancelFormatChange> { let Some(NoteCommitmentSubtreeIndex(mut first_incomplete_subtree_index)) = db.orchard_tree_for_tip().subtree_index() else { return Ok(Ok(())); }; // If there are no incomplete subtrees in the tree, also expect a subtree for the final index. if db.orchard_tree_for_tip().is_complete_subtree() { first_incomplete_subtree_index += 1; } let mut result = Ok(()); for index in 0..first_incomplete_subtree_index { // Return early if the format check is cancelled. if !matches!(cancel_receiver.try_recv(), Err(mpsc::TryRecvError::Empty)) { return Err(CancelFormatChange); } // Check that there's a continuous range of subtrees from index [0, first_incomplete_subtree_index) let Some(subtree) = db.orchard_subtree_by_index(index) else { result = Err("missing subtree"); error!(?result, index); continue; }; // Check that there was a orchard note at the subtree's end height. let Some(tree) = db.orchard_tree_by_height(&subtree.end_height) else { result = Err("missing note commitment tree at subtree completion height"); error!(?result, ?subtree.end_height); continue; }; // Check the index and root if the orchard note commitment tree at this height is a complete subtree. if let Some((index, node)) = tree.completed_subtree_index_and_root() { if subtree.index != index { result = Err("completed subtree indexes should match"); error!(?result); } if subtree.root != node { result = Err("completed subtree roots should match"); error!(?result); } } // Check that the final note has a greater subtree index if it didn't complete a subtree. else { let prev_height = subtree .end_height .previous() .expect("Note commitment subtrees should not end at the minimal height."); let Some(prev_tree) = db.orchard_tree_by_height(&prev_height) else { result = Err("missing note commitment tree below subtree completion height"); error!(?result, ?subtree.end_height); continue; }; let prev_subtree_index = prev_tree.subtree_index(); let subtree_index = tree.subtree_index(); if subtree_index <= prev_subtree_index { result = Err("note commitment tree at end height should have incremented subtree index"); error!(?result, ?subtree_index, ?prev_subtree_index,); } } } let mut subtree_count = 0; for (index, height, tree) in db .orchard_tree_by_height_range(..) // Exclude empty orchard tree and add subtree indexes .filter_map(|(height, tree)| Some((tree.subtree_index()?, height, tree))) // Exclude heights that don't complete a subtree and count completed subtrees .filter_map(|(subtree_index, height, tree)| { if tree.is_complete_subtree() || subtree_index.0 > subtree_count { let subtree_index = subtree_count; subtree_count += 1; Some((subtree_index, height, tree)) } else { None } }) { // Return early if the format check is cancelled. if !matches!(cancel_receiver.try_recv(), Err(mpsc::TryRecvError::Empty)) { return Err(CancelFormatChange); } // Check that there's an entry for every completed orchard subtree root in all orchard trees let Some(subtree) = db.orchard_subtree_by_index(index) else { result = Err("missing subtree"); error!(?result, index); continue; }; // Check that the subtree end height matches that in the orchard trees. if subtree.end_height != height { let is_complete = tree.is_complete_subtree(); result = Err("bad orchard subtree end height"); error!(?result, ?subtree.end_height, ?height, ?index, ?is_complete, ); } // Check the root if the orchard note commitment tree at this height is a complete subtree. if let Some((_index, node)) = tree.completed_subtree_index_and_root() { if subtree.root != node { result = Err("completed subtree roots should match"); error!(?result); } } } if result.is_err() { error!( ?result, ?subtree_count, first_incomplete_subtree_index, "missing or bad orchard subtrees" ); } Ok(result) } /// Calculates a note commitment subtree for Sapling, reading blocks from `read_db` if needed. /// /// The subtree must be completed by a note commitment in the block at `end_height`. /// `tree` is the tree for that block, and `prev_tree` is the tree for the previous block. /// /// `prev_tree` is only used to rebuild the subtree if it was completed without using the last /// note commitment in the block at `end_height`. /// /// # Panics /// /// If a subtree is not completed by a note commitment in the block at `end_height`. #[must_use = "subtree should be written to the database after it is calculated"] #[instrument(skip(read_db, prev_tree, tree))] fn calculate_sapling_subtree( read_db: &ZebraDb, prev_end_height: Height, prev_tree: Arc, end_height: Height, tree: Arc, ) -> NoteCommitmentSubtree { // If a subtree is completed by a note commitment in the block at `end_height`, // then that subtree can be completed in two different ways: if let Some((index, node)) = tree.completed_subtree_index_and_root() { // If the subtree is completed by the last note commitment in that block, // we already have that subtree root available in the tree. NoteCommitmentSubtree::new(index, end_height, node) } else { // If the subtree is completed without using the last note commitment in the block, // we need to calculate the subtree root, starting with the tree from the previous block. // TODO: move the assertion/panic log string formatting into a separate function? let prev_position = prev_tree.position().unwrap_or_else(|| { panic!( "previous block must have a partial subtree:\n\ previous subtree:\n\ height: {prev_end_height:?}\n\ current subtree:\n\ height: {end_height:?}" ) }); let prev_index = prev_tree .subtree_index() .expect("previous block must have a partial subtree"); let prev_remaining_notes = prev_tree.remaining_subtree_leaf_nodes(); let current_position = tree.position().unwrap_or_else(|| { panic!( "current block must have a subtree:\n\ previous subtree:\n\ height: {prev_end_height:?}\n\ index: {prev_index}\n\ position: {prev_position}\n\ remaining: {prev_remaining_notes}\n\ current subtree:\n\ height: {end_height:?}" ) }); let current_index = tree .subtree_index() .expect("current block must have a subtree"); let current_remaining_notes = tree.remaining_subtree_leaf_nodes(); assert_eq!( prev_index.0 + 1, current_index.0, "subtree must have been completed by the current block:\n\ previous subtree:\n\ height: {prev_end_height:?}\n\ index: {prev_index}\n\ position: {prev_position}\n\ remaining: {prev_remaining_notes}\n\ current subtree:\n\ height: {end_height:?}\n\ index: {current_index}\n\ position: {current_position}\n\ remaining: {current_remaining_notes}" ); // Get the missing notes needed to complete the subtree. // // TODO: consider just reading the block's transactions from the database file, // because we don't use the block header data at all. let block = read_db .block(end_height.into()) .expect("height with note commitment tree should have block"); let sapling_note_commitments = block .sapling_note_commitments() .take(prev_remaining_notes) .cloned() .collect(); // This takes less than 1 second per tree, so we don't need to make it cancellable. let (sapling_nct, subtree) = NoteCommitmentTrees::update_sapling_note_commitment_tree( prev_tree, sapling_note_commitments, ) .expect("finalized notes should append successfully"); let (index, node) = subtree.unwrap_or_else(|| { panic!( "already checked that the block completed a subtree:\n\ updated subtree:\n\ index: {:?}\n\ position: {:?}\n\ remaining notes: {}\n\ original previous subtree:\n\ height: {prev_end_height:?}\n\ index: {prev_index}\n\ position: {prev_position}\n\ remaining: {prev_remaining_notes}\n\ original current subtree:\n\ height: {end_height:?}\n\ index: {current_index}\n\ position: {current_position}\n\ remaining: {current_remaining_notes}", sapling_nct.subtree_index(), sapling_nct.position(), sapling_nct.remaining_subtree_leaf_nodes(), ) }); NoteCommitmentSubtree::new(index, end_height, node) } } /// Calculates a note commitment subtree for Orchard, reading blocks from `read_db` if needed. /// /// The subtree must be completed by a note commitment in the block at `end_height`. /// `tree` is the tree for that block, and `prev_tree` is the tree for the previous block. /// /// `prev_tree` is only used to rebuild the subtree if it was completed without using the last /// note commitment in the block at `end_height`. /// /// # Panics /// /// If a subtree is not completed by a note commitment in the block at `end_height`. #[must_use = "subtree should be written to the database after it is calculated"] #[instrument(skip(read_db, prev_tree, tree))] fn calculate_orchard_subtree( read_db: &ZebraDb, prev_end_height: Height, prev_tree: Arc, end_height: Height, tree: Arc, ) -> NoteCommitmentSubtree { // If a subtree is completed by a note commitment in the block at `end_height`, // then that subtree can be completed in two different ways: if let Some((index, node)) = tree.completed_subtree_index_and_root() { // If the subtree is completed by the last note commitment in that block, // we already have that subtree root available in the tree. NoteCommitmentSubtree::new(index, end_height, node) } else { // If the subtree is completed without using the last note commitment in the block, // we need to calculate the subtree root, starting with the tree from the previous block. // TODO: move the assertion/panic log string formatting into a separate function? let prev_position = prev_tree.position().unwrap_or_else(|| { panic!( "previous block must have a partial subtree:\n\ previous subtree:\n\ height: {prev_end_height:?}\n\ current subtree:\n\ height: {end_height:?}" ) }); let prev_index = prev_tree .subtree_index() .expect("previous block must have a partial subtree"); let prev_remaining_notes = prev_tree.remaining_subtree_leaf_nodes(); let current_position = tree.position().unwrap_or_else(|| { panic!( "current block must have a subtree:\n\ previous subtree:\n\ height: {prev_end_height:?}\n\ index: {prev_index}\n\ position: {prev_position}\n\ remaining: {prev_remaining_notes}\n\ current subtree:\n\ height: {end_height:?}" ) }); let current_index = tree .subtree_index() .expect("current block must have a subtree"); let current_remaining_notes = tree.remaining_subtree_leaf_nodes(); assert_eq!( prev_index.0 + 1, current_index.0, "subtree must have been completed by the current block:\n\ previous subtree:\n\ height: {prev_end_height:?}\n\ index: {prev_index}\n\ position: {prev_position}\n\ remaining: {prev_remaining_notes}\n\ current subtree:\n\ height: {end_height:?}\n\ index: {current_index}\n\ position: {current_position}\n\ remaining: {current_remaining_notes}" ); // Get the missing notes needed to complete the subtree. // // TODO: consider just reading the block's transactions from the database file, // because we don't use the block header data at all. let block = read_db .block(end_height.into()) .expect("height with note commitment tree should have block"); let orchard_note_commitments = block .orchard_note_commitments() .take(prev_remaining_notes) .cloned() .collect(); // This takes less than 1 second per tree, so we don't need to make it cancellable. let (orchard_nct, subtree) = NoteCommitmentTrees::update_orchard_note_commitment_tree( prev_tree, orchard_note_commitments, ) .expect("finalized notes should append successfully"); let (index, node) = subtree.unwrap_or_else(|| { panic!( "already checked that the block completed a subtree:\n\ updated subtree:\n\ index: {:?}\n\ position: {:?}\n\ remaining notes: {}\n\ original previous subtree:\n\ height: {prev_end_height:?}\n\ index: {prev_index}\n\ position: {prev_position}\n\ remaining: {prev_remaining_notes}\n\ original current subtree:\n\ height: {end_height:?}\n\ index: {current_index}\n\ position: {current_position}\n\ remaining: {current_remaining_notes}", orchard_nct.subtree_index(), orchard_nct.position(), orchard_nct.remaining_subtree_leaf_nodes(), ) }); NoteCommitmentSubtree::new(index, end_height, node) } } /// Writes a Sapling note commitment subtree to `upgrade_db`. fn write_sapling_subtree( upgrade_db: &ZebraDb, subtree: NoteCommitmentSubtree, ) { let mut batch = DiskWriteBatch::new(); batch.insert_sapling_subtree(upgrade_db, &subtree); upgrade_db .write_batch(batch) .expect("writing sapling note commitment subtrees should always succeed."); if subtree.index.0 % 100 == 0 { info!(end_height = ?subtree.end_height, index = ?subtree.index.0, "calculated and added sapling subtree"); } // This log happens about once per second on recent machines with SSD disks. debug!(end_height = ?subtree.end_height, index = ?subtree.index.0, "calculated and added sapling subtree"); } /// Writes an Orchard note commitment subtree to `upgrade_db`. fn write_orchard_subtree( upgrade_db: &ZebraDb, subtree: NoteCommitmentSubtree, ) { let mut batch = DiskWriteBatch::new(); batch.insert_orchard_subtree(upgrade_db, &subtree); upgrade_db .write_batch(batch) .expect("writing orchard note commitment subtrees should always succeed."); if subtree.index.0 % 100 == 0 { info!(end_height = ?subtree.end_height, index = ?subtree.index.0, "calculated and added orchard subtree"); } // This log happens about once per second on recent machines with SSD disks. debug!(end_height = ?subtree.end_height, index = ?subtree.index.0, "calculated and added orchard subtree"); }