From 45e2d633ade12e525df4f12ca0ae8a05fc3248d9 Mon Sep 17 00:00:00 2001 From: Marek Date: Thu, 12 Oct 2023 13:42:42 +0200 Subject: [PATCH 01/10] Fix the combining of Sapling subtrees This commit fixes a bug due to which the function `sapling_subtrees` used to always include all subtrees from the non-finalized state without respecting the given limit. --- zebra-state/src/service/read/tree.rs | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/zebra-state/src/service/read/tree.rs b/zebra-state/src/service/read/tree.rs index 3be272477fe..29e1e8a1d09 100644 --- a/zebra-state/src/service/read/tree.rs +++ b/zebra-state/src/service/read/tree.rs @@ -74,6 +74,19 @@ where { let mut db_list = db.sapling_subtree_list_by_index_for_rpc(start_index, limit); + if let Some(limit) = limit { + let subtrees_num = u16::try_from(db_list.len()) + .expect("There can't be more than `u16::MAX` Sapling subtrees."); + + // Return the subtrees if the amount of them reached the given limit. + if subtrees_num == limit.0 { + return db_list; + } + + // If not, make sure the amount is below the limit. + debug_assert!(subtrees_num < limit.0); + } + // If there's no chain, then we have the complete list. let Some(chain) = chain else { return db_list; @@ -93,6 +106,17 @@ where // If there's no matching index, just update the list of trees. let Some(db_subtree) = db_list.get(&fork_index) else { db_list.insert(fork_index, fork_subtree); + + // Stop adding new subtrees once their amount reaches the given limit. + if let Some(limit) = limit { + let subtrees_num = u16::try_from(db_list.len()) + .expect("There can't be more than `u16::MAX` Sapling subtrees."); + + if subtrees_num == limit.0 { + break; + } + } + continue; }; @@ -104,6 +128,12 @@ where // Otherwise, the subtree is already in the list, so we don't need to add it. } + // Make sure the amount of retrieved subtrees does not exceed the given limit. + #[cfg(debug_assertions)] + if let Some(limit) = limit { + assert!(db_list.len() <= limit.0.into()); + } + // Check that we got the start subtree from the non-finalized or finalized state. // (The non-finalized state doesn't do this check.) if db_list.get(&start_index).is_some() { From b69c8c3b9c336ce9baafbb118859a0051341fba8 Mon Sep 17 00:00:00 2001 From: Marek Date: Thu, 12 Oct 2023 13:46:56 +0200 Subject: [PATCH 02/10] Fix the combining of Orchard subtrees This commit fixes a bug due to which the function `orchard_subtrees` used to always include all subtrees from the non-finalized state without respecting the given limit. --- zebra-state/src/service/read/tree.rs | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/zebra-state/src/service/read/tree.rs b/zebra-state/src/service/read/tree.rs index 29e1e8a1d09..98869546952 100644 --- a/zebra-state/src/service/read/tree.rs +++ b/zebra-state/src/service/read/tree.rs @@ -190,6 +190,19 @@ where { let mut db_list = db.orchard_subtree_list_by_index_for_rpc(start_index, limit); + if let Some(limit) = limit { + let subtrees_num = u16::try_from(db_list.len()) + .expect("There can't be more than `u16::MAX` Orchard subtrees."); + + // Return the subtrees if the amount of them reached the given limit. + if subtrees_num == limit.0 { + return db_list; + } + + // If not, make sure the amount is below the limit. + debug_assert!(subtrees_num < limit.0); + } + // If there's no chain, then we have the complete list. let Some(chain) = chain else { return db_list; @@ -209,6 +222,17 @@ where // If there's no matching index, just update the list of trees. let Some(db_subtree) = db_list.get(&fork_index) else { db_list.insert(fork_index, fork_subtree); + + // Stop adding new subtrees once their amount reaches the given limit. + if let Some(limit) = limit { + let subtrees_num = u16::try_from(db_list.len()) + .expect("There can't be more than `u16::MAX` Orchard subtrees."); + + if subtrees_num == limit.0 { + break; + } + } + continue; }; @@ -220,6 +244,12 @@ where // Otherwise, the subtree is already in the list, so we don't need to add it. } + // Make sure the amount of retrieved subtrees does not exceed the given limit. + #[cfg(debug_assertions)] + if let Some(limit) = limit { + assert!(db_list.len() <= limit.0.into()); + } + // Check that we got the start subtree from the non-finalized or finalized state. // (The non-finalized state doesn't do this check.) if db_list.get(&start_index).is_some() { From 6b4f1eb3d3987c5d9864424b5760fcb8e5fdd966 Mon Sep 17 00:00:00 2001 From: Marek Date: Thu, 12 Oct 2023 14:25:51 +0200 Subject: [PATCH 03/10] Add additional checks when retrieving subtrees --- .../src/service/finalized_state/zebra_db/shielded.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/zebra-state/src/service/finalized_state/zebra_db/shielded.rs b/zebra-state/src/service/finalized_state/zebra_db/shielded.rs index 75b5db8da64..1075438ffff 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/shielded.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/shielded.rs @@ -295,6 +295,12 @@ impl ZebraDb { .collect(); } + // Make sure the amount of retrieved subtrees does not exceed the given limit. + #[cfg(debug_assertions)] + if let Some(limit) = limit { + assert!(list.len() <= limit.0.into()); + } + // Check that we got the start subtree. if list.get(&start_index).is_some() { list @@ -462,6 +468,12 @@ impl ZebraDb { .collect(); } + // Make sure the amount of retrieved subtrees does not exceed the given limit. + #[cfg(debug_assertions)] + if let Some(limit) = limit { + assert!(list.len() <= limit.0.into()); + } + // Check that we got the start subtree. if list.get(&start_index).is_some() { list From 42eeffef2cb157f6a64f2dea8ed62c6cd47140db Mon Sep 17 00:00:00 2001 From: Marek Date: Thu, 12 Oct 2023 14:27:29 +0200 Subject: [PATCH 04/10] Allow raw subtree insertions into `Chain` in tests --- .../src/service/non_finalized_state/chain.rs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/zebra-state/src/service/non_finalized_state/chain.rs b/zebra-state/src/service/non_finalized_state/chain.rs index ef1e54215e8..c12324c7cc2 100644 --- a/zebra-state/src/service/non_finalized_state/chain.rs +++ b/zebra-state/src/service/non_finalized_state/chain.rs @@ -2224,3 +2224,26 @@ impl PartialEq for Chain { } impl Eq for Chain {} + +#[cfg(test)] +impl Chain { + /// Inserts the supplied Sapling note commitment subtree into the chain. + pub(crate) fn insert_sapling_subtree( + &mut self, + subtree: NoteCommitmentSubtree, + ) { + self.inner + .sapling_subtrees + .insert(subtree.index, subtree.into_data()); + } + + /// Inserts the supplied Orchard note commitment subtree into the chain. + pub(crate) fn insert_orchard_subtree( + &mut self, + subtree: NoteCommitmentSubtree, + ) { + self.inner + .orchard_subtrees + .insert(subtree.index, subtree.into_data()); + } +} From e7a5684d76b6f5af8b303ff720795d17cda091e8 Mon Sep 17 00:00:00 2001 From: Marek Date: Thu, 12 Oct 2023 14:30:37 +0200 Subject: [PATCH 05/10] Derive `Default` for `Chain` --- zebra-chain/src/amount.rs | 4 ++-- zebra-chain/src/value_balance.rs | 2 +- zebra-state/src/service/non_finalized_state/chain.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/zebra-chain/src/amount.rs b/zebra-chain/src/amount.rs index ce36c043172..b07a59642bd 100644 --- a/zebra-chain/src/amount.rs +++ b/zebra-chain/src/amount.rs @@ -30,7 +30,7 @@ pub type Result = std::result::Result; // TODO: // - remove the default NegativeAllowed bound, to make consensus rule reviews easier // - put a Constraint bound on the type generic, not just some implementations -#[derive(Clone, Copy, Serialize, Deserialize)] +#[derive(Clone, Copy, Serialize, Deserialize, Default)] #[serde(try_from = "i64")] #[serde(into = "i64")] #[serde(bound = "C: Constraint + Clone")] @@ -509,7 +509,7 @@ impl Constraint for NegativeAllowed { /// 0..=MAX_MONEY, /// ); /// ``` -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Default)] pub struct NonNegative; impl Constraint for NonNegative { diff --git a/zebra-chain/src/value_balance.rs b/zebra-chain/src/value_balance.rs index f523135b7c8..43f7b11b469 100644 --- a/zebra-chain/src/value_balance.rs +++ b/zebra-chain/src/value_balance.rs @@ -20,7 +20,7 @@ mod tests; use ValueBalanceError::*; /// An amount spread between different Zcash pools. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] pub struct ValueBalance { transparent: Amount, sprout: Amount, diff --git a/zebra-state/src/service/non_finalized_state/chain.rs b/zebra-state/src/service/non_finalized_state/chain.rs index c12324c7cc2..b415b41d1fa 100644 --- a/zebra-state/src/service/non_finalized_state/chain.rs +++ b/zebra-state/src/service/non_finalized_state/chain.rs @@ -39,7 +39,7 @@ pub mod index; /// A single non-finalized partial chain, from the child of the finalized tip, /// to a non-finalized chain tip. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct Chain { // Config // @@ -67,7 +67,7 @@ pub struct Chain { } /// The internal state of [`Chain`]. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Default)] pub struct ChainInner { // Blocks, heights, hashes, and transaction locations // From 802409180a6017d959b1584b0c59e0027d9c66f0 Mon Sep 17 00:00:00 2001 From: Marek Date: Thu, 12 Oct 2023 14:32:43 +0200 Subject: [PATCH 06/10] Derive `Default` for note commitment tree nodes --- zebra-chain/src/orchard/tree.rs | 2 +- zebra-chain/src/sapling/tree.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zebra-chain/src/orchard/tree.rs b/zebra-chain/src/orchard/tree.rs index 3e571f424da..3b5cb768107 100644 --- a/zebra-chain/src/orchard/tree.rs +++ b/zebra-chain/src/orchard/tree.rs @@ -171,7 +171,7 @@ impl ZcashDeserialize for Root { } /// A node of the Orchard Incremental Note Commitment Tree. -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq, Default)] pub struct Node(pallas::Base); impl Node { diff --git a/zebra-chain/src/sapling/tree.rs b/zebra-chain/src/sapling/tree.rs index 80b77ed28f1..bef003243c2 100644 --- a/zebra-chain/src/sapling/tree.rs +++ b/zebra-chain/src/sapling/tree.rs @@ -166,7 +166,7 @@ impl ZcashDeserialize for Root { /// /// Note that it's handled as a byte buffer and not a point coordinate (jubjub::Fq) /// because that's how the spec handles the MerkleCRH^Sapling function inputs and outputs. -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq, Default)] pub struct Node([u8; 32]); impl AsRef<[u8; 32]> for Node { From a5d6937fc37ed6a24c0192f26f32937d3075a9ab Mon Sep 17 00:00:00 2001 From: Marek Date: Thu, 12 Oct 2023 14:33:15 +0200 Subject: [PATCH 07/10] Use `pub(super)` for `DiskWriteBatch` Set the visibility of `DiskWriteBatch` to `pub(super)`. --- zebra-state/src/service/finalized_state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zebra-state/src/service/finalized_state.rs b/zebra-state/src/service/finalized_state.rs index acafda7b2f8..e092f0610b3 100644 --- a/zebra-state/src/service/finalized_state.rs +++ b/zebra-state/src/service/finalized_state.rs @@ -42,7 +42,7 @@ pub use disk_format::{OutputIndex, OutputLocation, TransactionLocation, MAX_ON_D pub(super) use zebra_db::ZebraDb; -use disk_db::DiskWriteBatch; +pub(super) use disk_db::DiskWriteBatch; /// The finalized part of the chain state, stored in the db. /// From 3adf6bfc5e0b48267e024d3d1a69fe38cf78a62a Mon Sep 17 00:00:00 2001 From: Marek Date: Thu, 12 Oct 2023 14:35:26 +0200 Subject: [PATCH 08/10] Add tests for retrieving subtrees --- zebra-state/src/service/read/tests/vectors.rs | 156 +++++++++++++++++- 1 file changed, 155 insertions(+), 1 deletion(-) diff --git a/zebra-state/src/service/read/tests/vectors.rs b/zebra-state/src/service/read/tests/vectors.rs index 6b9bfb72cc8..2ac96578cc7 100644 --- a/zebra-state/src/service/read/tests/vectors.rs +++ b/zebra-state/src/service/read/tests/vectors.rs @@ -4,8 +4,11 @@ use std::sync::Arc; use zebra_chain::{ block::{Block, Height}, + orchard, parameters::Network::*, + sapling, serialization::ZcashDeserializeInto, + subtree::{NoteCommitmentSubtree, NoteCommitmentSubtreeData, NoteCommitmentSubtreeIndex}, transaction, }; @@ -14,7 +17,16 @@ use zebra_test::{ transcript::{ExpectedTranscriptError, Transcript}, }; -use crate::{init_test_services, populated_state, response::MinedTx, ReadRequest, ReadResponse}; +use crate::{ + init_test_services, populated_state, + response::MinedTx, + service::{ + finalized_state::{DiskWriteBatch, ZebraDb}, + non_finalized_state::Chain, + read::{orchard_subtrees, sapling_subtrees}, + }, + Config, ReadRequest, ReadResponse, +}; /// Test that ReadStateService responds correctly when empty. #[tokio::test] @@ -88,6 +100,136 @@ async fn populated_read_state_responds_correctly() -> Result<()> { Ok(()) } +/// Tests if Zebra combines the Sapling note commitment subtrees from the finalized and +/// non-finalized states correctly. +#[tokio::test] +async fn test_sapling_subtrees() -> Result<()> { + let dummy_subtree_root = sapling::tree::Node::default(); + + // Prepare the finalized state. + let db_subtree = NoteCommitmentSubtree::new(0, Height(1), dummy_subtree_root); + let db = ZebraDb::new(&Config::ephemeral(), Mainnet, true); + let mut db_batch = DiskWriteBatch::new(); + db_batch.insert_sapling_subtree(&db, &db_subtree); + db.write(db_batch) + .expect("Writing a batch with a Sapling subtree should succeed."); + + // Prepare the non-fianlized state. + let chain_subtree = NoteCommitmentSubtree::new(1, Height(3), dummy_subtree_root); + let mut chain = Chain::default(); + chain.insert_sapling_subtree(chain_subtree); + let chain = Some(Arc::new(chain)); + + // At this point, we have one Sapling subtree in the finalized state and one Sapling subtree in + // the non-finalized state. + + // Retrieve only the first subtree and check its properties. + let subtrees = sapling_subtrees(chain.clone(), &db, 0.into(), Some(1.into())); + let mut subtrees = subtrees.iter(); + assert_eq!(subtrees.len(), 1); + assert!(subtrees_eq(subtrees.next().unwrap(), &db_subtree)); + + // Retrieve both subtrees using a limit and check their properties. + let subtrees = sapling_subtrees(chain.clone(), &db, 0.into(), Some(2.into())); + let mut subtrees = subtrees.iter(); + assert_eq!(subtrees.len(), 2); + assert!(subtrees_eq(subtrees.next().unwrap(), &db_subtree)); + assert!(subtrees_eq(subtrees.next().unwrap(), &chain_subtree)); + + // Retrieve both subtrees without using a limit and check their properties. + let subtrees = sapling_subtrees(chain.clone(), &db, 0.into(), None); + let mut subtrees = subtrees.iter(); + assert_eq!(subtrees.len(), 2); + assert!(subtrees_eq(subtrees.next().unwrap(), &db_subtree)); + assert!(subtrees_eq(subtrees.next().unwrap(), &chain_subtree)); + + // Retrieve only the second subtree and check its properties. + let subtrees = sapling_subtrees(chain.clone(), &db, 1.into(), Some(1.into())); + let mut subtrees = subtrees.iter(); + assert_eq!(subtrees.len(), 1); + assert!(subtrees_eq(subtrees.next().unwrap(), &chain_subtree)); + + // Retrieve only the second subtree, using a limit that would allow for more trees if they were + // present, and check its properties. + let subtrees = sapling_subtrees(chain.clone(), &db, 1.into(), Some(2.into())); + let mut subtrees = subtrees.iter(); + assert_eq!(subtrees.len(), 1); + assert!(subtrees_eq(subtrees.next().unwrap(), &chain_subtree)); + + // Retrieve only the second subtree, without using any limit, and check its properties. + let subtrees = sapling_subtrees(chain, &db, 1.into(), None); + let mut subtrees = subtrees.iter(); + assert_eq!(subtrees.len(), 1); + assert!(subtrees_eq(subtrees.next().unwrap(), &chain_subtree)); + + Ok(()) +} + +/// Tests if Zebra combines the Orchard note commitment subtrees from the finalized and +/// non-finalized states correctly. +#[tokio::test] +async fn test_orchard_subtrees() -> Result<()> { + let dummy_subtree_root = orchard::tree::Node::default(); + + // Prepare the finalized state. + let db_subtree = NoteCommitmentSubtree::new(0, Height(1), dummy_subtree_root); + let db = ZebraDb::new(&Config::ephemeral(), Mainnet, true); + let mut db_batch = DiskWriteBatch::new(); + db_batch.insert_orchard_subtree(&db, &db_subtree); + db.write(db_batch) + .expect("Writing a batch with an Orchard subtree should succeed."); + + // Prepare the non-fianlized state. + let chain_subtree = NoteCommitmentSubtree::new(1, Height(3), dummy_subtree_root); + let mut chain = Chain::default(); + chain.insert_orchard_subtree(chain_subtree); + let chain = Some(Arc::new(chain)); + + // At this point, we have one Orchard subtree in the finalized state and one Orchard subtree in + // the non-finalized state. + + // Retrieve only the first subtree and check its properties. + let subtrees = orchard_subtrees(chain.clone(), &db, 0.into(), Some(1.into())); + let mut subtrees = subtrees.iter(); + assert_eq!(subtrees.len(), 1); + assert!(subtrees_eq(subtrees.next().unwrap(), &db_subtree)); + + // Retrieve both subtrees using a limit and check their properties. + let subtrees = orchard_subtrees(chain.clone(), &db, 0.into(), Some(2.into())); + let mut subtrees = subtrees.iter(); + assert_eq!(subtrees.len(), 2); + assert!(subtrees_eq(subtrees.next().unwrap(), &db_subtree)); + assert!(subtrees_eq(subtrees.next().unwrap(), &chain_subtree)); + + // Retrieve both subtrees without using a limit and check their properties. + let subtrees = orchard_subtrees(chain.clone(), &db, 0.into(), None); + let mut subtrees = subtrees.iter(); + assert_eq!(subtrees.len(), 2); + assert!(subtrees_eq(subtrees.next().unwrap(), &db_subtree)); + assert!(subtrees_eq(subtrees.next().unwrap(), &chain_subtree)); + + // Retrieve only the second subtree and check its properties. + let subtrees = orchard_subtrees(chain.clone(), &db, 1.into(), Some(1.into())); + let mut subtrees = subtrees.iter(); + assert_eq!(subtrees.len(), 1); + assert!(subtrees_eq(subtrees.next().unwrap(), &chain_subtree)); + + // Retrieve only the second subtree, using a limit that would allow for more trees if they were + // present, and check its properties. + let subtrees = orchard_subtrees(chain.clone(), &db, 1.into(), Some(2.into())); + let mut subtrees = subtrees.iter(); + assert_eq!(subtrees.len(), 1); + assert!(subtrees_eq(subtrees.next().unwrap(), &chain_subtree)); + + // Retrieve only the second subtree, without using any limit, and check its properties. + let subtrees = orchard_subtrees(chain, &db, 1.into(), None); + let mut subtrees = subtrees.iter(); + assert_eq!(subtrees.len(), 1); + assert!(subtrees_eq(subtrees.next().unwrap(), &chain_subtree)); + + Ok(()) +} + /// Returns test cases for the empty state and missing blocks. fn empty_state_test_cases() -> Vec<(ReadRequest, Result)> { let block: Arc = zebra_test::vectors::BLOCK_MAINNET_419200_BYTES @@ -109,3 +251,15 @@ fn empty_state_test_cases() -> Vec<(ReadRequest, Result( + (index, subtree_data): (&NoteCommitmentSubtreeIndex, &NoteCommitmentSubtreeData), + subtree: &NoteCommitmentSubtree, +) -> bool +where + N: PartialEq + Copy, +{ + index == &subtree.index && subtree_data == &subtree.into_data() +} From 77fe4714120e7fedd0912628a09ed872d49d9439 Mon Sep 17 00:00:00 2001 From: Marek Date: Thu, 12 Oct 2023 14:35:53 +0200 Subject: [PATCH 09/10] Use `default()` for subtree roots in tests This change is unrelated to the PR. --- zebra-rpc/src/methods/tests/snapshot.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/zebra-rpc/src/methods/tests/snapshot.rs b/zebra-rpc/src/methods/tests/snapshot.rs index efa89ac5b13..97fe46b6ef7 100644 --- a/zebra-rpc/src/methods/tests/snapshot.rs +++ b/zebra-rpc/src/methods/tests/snapshot.rs @@ -347,9 +347,7 @@ async fn test_mocked_rpc_response_data_for_network(network: Network) { // Mock the data for the response. let mut subtrees = BTreeMap::new(); - let subtree_root = [0u8; 32].as_slice().try_into().expect( - "The array [0u8; 32] should be convertible to a Sapling note commitment tree node.", - ); + let subtree_root = sapling::tree::Node::default(); for i in 0..2u16 { let subtree = NoteCommitmentSubtreeData::new(Height(i.into()), subtree_root); @@ -377,9 +375,7 @@ async fn test_mocked_rpc_response_data_for_network(network: Network) { // Mock the data for the response. let mut subtrees = BTreeMap::new(); - let subtree_root = [0u8; 32].try_into().expect( - "The array [0u8; 32] should be convertible to a Sapling note commitment tree node.", - ); + let subtree_root = orchard::tree::Node::default(); for i in 0..2u16 { let subtree = NoteCommitmentSubtreeData::new(Height(i.into()), subtree_root); From d251e7b76763efd3f52d7c724d2d93d6b0a7e0f9 Mon Sep 17 00:00:00 2001 From: Marek Date: Thu, 12 Oct 2023 14:37:02 +0200 Subject: [PATCH 10/10] Refactor docs for inserting subtrees This change is unrelated to the PR. --- zebra-state/src/service/finalized_state/zebra_db/shielded.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zebra-state/src/service/finalized_state/zebra_db/shielded.rs b/zebra-state/src/service/finalized_state/zebra_db/shielded.rs index 1075438ffff..58d9e43a6c1 100644 --- a/zebra-state/src/service/finalized_state/zebra_db/shielded.rs +++ b/zebra-state/src/service/finalized_state/zebra_db/shielded.rs @@ -657,7 +657,7 @@ impl DiskWriteBatch { // Sapling tree methods - /// Inserts the Sapling note commitment subtree. + /// Inserts the Sapling note commitment subtree into the batch. pub fn insert_sapling_subtree( &mut self, zebra_db: &ZebraDb, @@ -710,7 +710,7 @@ impl DiskWriteBatch { // Orchard tree methods - /// Inserts the Orchard note commitment subtree. + /// Inserts the Orchard note commitment subtree into the batch. pub fn insert_orchard_subtree( &mut self, zebra_db: &ZebraDb,