From 97f31e2ae62e45b6bf5729ec4551ad4556215abe Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 3 Jan 2024 09:17:24 -0700 Subject: [PATCH] zcash_client_backend: Introduce an "orchard-client" feature flag. We plan to also introduce a similar flag to gate access to Sapling functionality. Since introduction of Orchard functionality is still nascent, it's the correct time to introduce this isolation, before there's more functionality that needs to be isolated in this fashion. --- zcash_client_backend/CHANGELOG.md | 30 ++- zcash_client_backend/Cargo.toml | 6 +- zcash_client_backend/src/address.rs | 41 +++- zcash_client_backend/src/data_api.rs | 205 +++++++++++++------- zcash_client_backend/src/data_api/wallet.rs | 5 +- zcash_client_backend/src/keys.rs | 97 ++++++--- zcash_client_backend/src/lib.rs | 2 +- zcash_client_backend/src/proto.rs | 1 + zcash_client_backend/src/scanning.rs | 204 ++++++++++--------- zcash_client_backend/src/wallet.rs | 2 + zcash_client_backend/src/zip321.rs | 7 +- zcash_client_sqlite/CHANGELOG.md | 4 + zcash_client_sqlite/Cargo.toml | 4 +- zcash_client_sqlite/src/lib.rs | 19 +- zcash_client_sqlite/src/testing.rs | 2 + zcash_client_sqlite/src/wallet.rs | 10 +- zcash_extensions/src/transparent/demo.rs | 4 +- zcash_primitives/src/transaction/builder.rs | 142 +++++++------- 18 files changed, 495 insertions(+), 290 deletions(-) diff --git a/zcash_client_backend/CHANGELOG.md b/zcash_client_backend/CHANGELOG.md index 273a22e0be..064f9e58a3 100644 --- a/zcash_client_backend/CHANGELOG.md +++ b/zcash_client_backend/CHANGELOG.md @@ -9,13 +9,14 @@ and this library adheres to Rust's notion of ### Added - `zcash_client_backend::data_api`: - - `BlockMetadata::orchard_tree_size`. + - `BlockMetadata::orchard_tree_size` (when the `orchard` feature is enabled). - `TransparentInputSource` - `SaplingInputSource` - - `ScannedBlock::{ - sapling_tree_size, orchard_tree_size, orchard_nullifier_map, - orchard_commitments, into_commitments - }` + - `ScannedBlock::{into_commitments, sapling}` + - `ScannedBlock::orchard` (when the `orchard` feature is enabled.) + - `ScannedBlockSapling` + - `ScannedBlockOrchard` (when the `orchard` feature is enabled.) + - `ScannedBlockCommitments` - `Balance::{add_spendable_value, add_pending_change_value, add_pending_spendable_value}` - `AccountBalance::{ with_sapling_balance_mut, @@ -49,12 +50,17 @@ and this library adheres to Rust's notion of wallet::{ReceivedSaplingNote, WalletTransparentOutput}, wallet::input_selection::{Proposal, SaplingInputs}, }` +- A new `orchard` feature flag has been added to make it possible to + build client code without `orchard` dependendencies. ### Moved - `zcash_client_backend::data_api::{PoolType, ShieldedProtocol}` have been moved into the `zcash_client_backend` root module. - `zcash_client_backend::data_api::{NoteId, Recipient}` have been moved into the `zcash_client_backend::wallet` module. +- `ScannedBlock::{sapling_tree_size, sapling_nullifier_map, sapling_commitments}` + have been moved to `ScannedBlockSapling` and in that context are now + named `{tree_size, nullifier_map, commitments}` respectively. ### Changed - `zcash_client_backend::data_api`: @@ -68,7 +74,6 @@ and this library adheres to Rust's notion of `WalletShieldedOutput` change. - `ScannedBlock` has an additional type parameter as a consequence of the `WalletTx` change. - - Arguments to `ScannedBlock::from_parts` have changed. - `ScannedBlock::metadata` has been renamed to `to_block_metadata` and now returns an owned value rather than a reference. - `ShieldedProtocol` has a new variant for `Orchard`, allowing for better @@ -184,6 +189,18 @@ and this library adheres to Rust's notion of - `zcash_client_backend::address`: - `RecipientAddress` has been renamed to `Address` - `Address::Shielded` has been renamed to `Address::Sapling` + - `UnifiedAddress::from_receivers` no longer takes an Orchard receiver + argument uless the `orchard` feature is enabled. + - `UnifiedAddress::orchard` is now only available when the `orchard` feature + is enabled. + +- `zcash_client_backend::keys`: + - `DerivationError::Orchard` is now only available when the `orchard` feature + is enabled. + - `UnifiedSpendingKey::orchard` is now only available when the `orchard` + feature is enabled. + - `UnifiedFullViewingKey::new` no longer takes an Orchard full viewing key + argument uless the `orchard` feature is enabled. ### Removed - `zcash_client_backend::wallet::ReceivedSaplingNote` has been replaced by @@ -195,6 +212,7 @@ and this library adheres to Rust's notion of removed without replacement as it was unused, and its functionality will be fully reproduced by `SaplingInputSource::select_spendable_sapling_notes` in a future change. +- `zcash_client_backend::data_api::ScannedBlock::from_parts` has been made crate-private. - `zcash_client_backend::data_api::ScannedBlock::into_sapling_commitments` has been replaced by `into_commitments` which returns both Sapling and Orchard note commitments and associated note commitment retention information for the block. diff --git a/zcash_client_backend/Cargo.toml b/zcash_client_backend/Cargo.toml index d2820d64f6..a3867a2e06 100644 --- a/zcash_client_backend/Cargo.toml +++ b/zcash_client_backend/Cargo.toml @@ -59,7 +59,7 @@ subtle.workspace = true # - Shielded protocols bls12_381.workspace = true group.workspace = true -orchard.workspace = true +orchard = { workspace = true, optional = true } sapling.workspace = true # - Note commitment trees @@ -100,11 +100,13 @@ zcash_address = { workspace = true, features = ["test-dependencies"] } time = ">=0.3.22, <0.3.24" # time 0.3.24 has MSRV 1.67 [features] +default = ["transparent-inputs"] lightwalletd-tonic = ["tonic"] +orchard = ["dep:orchard"] transparent-inputs = ["hdwallet", "zcash_primitives/transparent-inputs"] test-dependencies = [ "proptest", - "orchard/test-dependencies", + "orchard?/test-dependencies", "zcash_primitives/test-dependencies", "incrementalmerkletree/test-dependencies" ] diff --git a/zcash_client_backend/src/address.rs b/zcash_client_backend/src/address.rs index f91ee34493..a93952da1f 100644 --- a/zcash_client_backend/src/address.rs +++ b/zcash_client_backend/src/address.rs @@ -38,6 +38,7 @@ impl AddressMetadata { /// A Unified Address. #[derive(Clone, Debug, PartialEq, Eq)] pub struct UnifiedAddress { + #[cfg(feature = "orchard")] orchard: Option, sapling: Option, transparent: Option, @@ -48,6 +49,7 @@ impl TryFrom for UnifiedAddress { type Error = &'static str; fn try_from(ua: unified::Address) -> Result { + #[cfg(feature = "orchard")] let mut orchard = None; let mut sapling = None; let mut transparent = None; @@ -58,6 +60,7 @@ impl TryFrom for UnifiedAddress { .items_as_parsed() .iter() .filter_map(|receiver| match receiver { + #[cfg(feature = "orchard")] unified::Receiver::Orchard(data) => { Option::from(orchard::Address::from_raw_address_bytes(data)) .ok_or("Invalid Orchard receiver in Unified Address") @@ -67,6 +70,10 @@ impl TryFrom for UnifiedAddress { }) .transpose() } + #[cfg(not(feature = "orchard"))] + unified::Receiver::Orchard(data) => { + Some(Ok((unified::Typecode::Orchard.into(), data.to_vec()))) + } unified::Receiver::Sapling(data) => PaymentAddress::from_bytes(data) .ok_or("Invalid Sapling receiver in Unified Address") .map(|pa| { @@ -89,6 +96,7 @@ impl TryFrom for UnifiedAddress { .collect::>()?; Ok(Self { + #[cfg(feature = "orchard")] orchard, sapling, transparent, @@ -103,12 +111,18 @@ impl UnifiedAddress { /// Returns `None` if the receivers would produce an invalid Unified Address (namely, /// if no shielded receiver is provided). pub fn from_receivers( - orchard: Option, + #[cfg(feature = "orchard")] orchard: Option, sapling: Option, transparent: Option, ) -> Option { - if orchard.is_some() || sapling.is_some() { + #[cfg(feature = "orchard")] + let has_orchard = orchard.is_some(); + #[cfg(not(feature = "orchard"))] + let has_orchard = false; + + if has_orchard || sapling.is_some() { Some(Self { + #[cfg(feature = "orchard")] orchard, sapling, transparent, @@ -121,6 +135,7 @@ impl UnifiedAddress { } /// Returns the Orchard receiver within this Unified Address, if any. + #[cfg(feature = "orchard")] pub fn orchard(&self) -> Option<&orchard::Address> { self.orchard.as_ref() } @@ -136,6 +151,15 @@ impl UnifiedAddress { } fn to_address(&self, net: Network) -> ZcashAddress { + #[cfg(feature = "orchard")] + let orchard_receiver = self + .orchard + .as_ref() + .map(|addr| addr.to_raw_address_bytes()) + .map(unified::Receiver::Orchard); + #[cfg(not(feature = "orchard"))] + let orchard_receiver = None; + let ua = unified::Address::try_from_items( self.unknown .iter() @@ -153,12 +177,7 @@ impl UnifiedAddress { .map(|pa| pa.to_bytes()) .map(unified::Receiver::Sapling), ) - .chain( - self.orchard - .as_ref() - .map(|addr| addr.to_raw_address_bytes()) - .map(unified::Receiver::Orchard), - ) + .chain(orchard_receiver) .collect(), ) .expect("UnifiedAddress should only be constructed safely"); @@ -259,6 +278,7 @@ mod tests { #[test] fn ua_round_trip() { + #[cfg(feature = "orchard")] let orchard = { let sk = orchard::keys::SpendingKey::from_zip32_seed(&[0; 32], 0, 0).unwrap(); let fvk = orchard::keys::FullViewingKey::from(&sk); @@ -273,8 +293,12 @@ mod tests { let transparent = { None }; + #[cfg(feature = "orchard")] let ua = UnifiedAddress::from_receivers(orchard, sapling, transparent).unwrap(); + #[cfg(not(feature = "orchard"))] + let ua = UnifiedAddress::from_receivers(sapling, transparent).unwrap(); + let addr = Address::Unified(ua); let addr_str = addr.encode(&MAIN_NETWORK); assert_eq!(Address::decode(&MAIN_NETWORK, &addr_str), Some(addr)); @@ -290,6 +314,7 @@ mod tests { tv.p2pkh_bytes.is_some() || tv.p2sh_bytes.is_some() ); assert_eq!(ua.sapling().is_some(), tv.sapling_raw_addr.is_some()); + #[cfg(feature = "orchard")] assert_eq!(ua.orchard().is_some(), tv.orchard_raw_addr.is_some()); } Some(_) => { diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index 0a39001f22..4b028dfbec 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -556,6 +556,7 @@ pub struct BlockMetadata { block_height: BlockHeight, block_hash: BlockHash, sapling_tree_size: Option, + #[cfg(feature = "orchard")] orchard_tree_size: Option, } @@ -565,12 +566,13 @@ impl BlockMetadata { block_height: BlockHeight, block_hash: BlockHash, sapling_tree_size: Option, - orchard_tree_size: Option, + #[cfg(feature = "orchard")] orchard_tree_size: Option, ) -> Self { Self { block_height, block_hash, sapling_tree_size, + #[cfg(feature = "orchard")] orchard_tree_size, } } @@ -593,11 +595,115 @@ impl BlockMetadata { /// Returns the size of the Orchard note commitment tree for the final treestate of the block /// that this [`BlockMetadata`] describes, if available. + #[cfg(feature = "orchard")] pub fn orchard_tree_size(&self) -> Option { self.orchard_tree_size } } +/// The Sapling note commitment and nullifier data extracted from a [`CompactBlock`] that is +/// required by the wallet for note commitment tree maintenance and spend detection. +/// +/// [`CompactBlock`]: crate::proto::compact_formats::CompactBlock +pub struct ScannedBlockSapling { + final_tree_size: u32, + commitments: Vec<(sapling::Node, Retention)>, + nullifier_map: Vec<(TxId, u16, Vec)>, +} + +impl ScannedBlockSapling { + pub(crate) fn new( + final_tree_size: u32, + commitments: Vec<(sapling::Node, Retention)>, + nullifier_map: Vec<(TxId, u16, Vec)>, + ) -> Self { + Self { + final_tree_size, + nullifier_map, + commitments, + } + } + + /// Returns the size of the Sapling note commitment tree as of the end of the scanned block. + pub fn final_tree_size(&self) -> u32 { + self.final_tree_size + } + + /// Returns the vector of Sapling nullifiers for each transaction in the block. + /// + /// The returned tuple is keyed by both transaction ID and the index of the transaction within + /// the block, so that either the txid or the combination of the block hash available from + /// [`Self::block_hash`] and returned transaction index may be used to uniquely identify the + /// transaction, depending upon the needs of the caller. + pub fn nullifier_map(&self) -> &[(TxId, u16, Vec)] { + &self.nullifier_map + } + + /// Returns the ordered list of Sapling note commitments to be added to the note commitment + /// tree. + pub fn commitments(&self) -> &[(sapling::Node, Retention)] { + &self.commitments + } +} + +/// The Orchard note commitment and nullifier data extracted from a [`CompactBlock`] that is +/// required by the wallet for note commitment tree maintenance and spend detection. +/// +/// [`CompactBlock`]: crate::proto::compact_formats::CompactBlock +#[cfg(feature = "orchard")] +pub struct ScannedBlockOrchard { + final_tree_size: u32, + nullifier_map: Vec<(TxId, u16, Vec)>, + commitments: Vec<(orchard::note::NoteCommitment, Retention)>, +} + +#[cfg(feature = "orchard")] +impl ScannedBlockOrchard { + pub(crate) fn new( + final_tree_size: u32, + nullifier_map: Vec<(TxId, u16, Vec)>, + commitments: Vec<(orchard::note::NoteCommitment, Retention)>, + ) -> Self { + Self { + final_tree_size, + nullifier_map, + commitments, + } + } + + /// Returns the size of the Orchard note commitment tree as of the end of the scanned block. + pub fn final_tree_size(&self) -> u32 { + self.final_tree_size + } + + /// Returns the vector of Orchard nullifiers for each transaction in the block. + /// + /// The returned tuple is keyed by both transaction ID and the index of the transaction within + /// the block, so that either the txid or the combination of the block hash available from + /// [`Self::block_hash`] and returned transaction index may be used to uniquely identify the + /// transaction, depending upon the needs of the caller. + pub fn nullifier_map(&self) -> &[(TxId, u16, Vec)] { + &self.nullifier_map + } + + /// Returns the ordered list of Orchard note commitments to be added to the note commitment + /// tree. + pub fn commitments(&self) -> &[(orchard::note::NoteCommitment, Retention)] { + &self.commitments + } +} + +/// A struct used to return the vectors of note commitments for a [`ScannedBlock`] +/// as owned values. +pub struct ScannedBlockCommitments { + /// The ordered vector of note commitments for Sapling outputs of the block. + pub sapling: Vec<(sapling::Node, Retention)>, + /// The ordered vector of note commitments for Orchard outputs of the block. + /// Present only when the `orchard` feature is enabled. + #[cfg(feature = "orchard")] + pub orchard: Vec<(orchard::note::NoteCommitment, Retention)>, +} + /// The subset of information that is relevant to this wallet that has been /// decrypted and extracted from a [`CompactBlock`]. /// @@ -606,41 +712,30 @@ pub struct ScannedBlock { block_height: BlockHeight, block_hash: BlockHash, block_time: u32, - sapling_tree_size: u32, - orchard_tree_size: u32, transactions: Vec>, - sapling_nullifier_map: Vec<(TxId, u16, Vec)>, - sapling_commitments: Vec<(sapling::Node, Retention)>, - orchard_nullifier_map: Vec<(TxId, u16, Vec)>, - orchard_commitments: Vec<(orchard::note::NoteCommitment, Retention)>, + sapling: ScannedBlockSapling, + #[cfg(feature = "orchard")] + orchard: ScannedBlockOrchard, } impl ScannedBlock { /// Constructs a new `ScannedBlock` - #[allow(clippy::too_many_arguments)] - pub fn from_parts( + pub(crate) fn from_parts( block_height: BlockHeight, block_hash: BlockHash, block_time: u32, - sapling_tree_size: u32, - orchard_tree_size: u32, transactions: Vec>, - sapling_nullifier_map: Vec<(TxId, u16, Vec)>, - sapling_commitments: Vec<(sapling::Node, Retention)>, - orchard_nullifier_map: Vec<(TxId, u16, Vec)>, - orchard_commitments: Vec<(orchard::note::NoteCommitment, Retention)>, + sapling: ScannedBlockSapling, + #[cfg(feature = "orchard")] orchard: ScannedBlockOrchard, ) -> Self { Self { block_height, block_hash, block_time, - sapling_tree_size, - orchard_tree_size, transactions, - sapling_nullifier_map, - sapling_commitments, - orchard_nullifier_map, - orchard_commitments, + sapling, + #[cfg(feature = "orchard")] + orchard, } } @@ -659,65 +754,30 @@ impl ScannedBlock { self.block_time } - /// Returns the size of the Sapling note commitment tree as of the end of the scanned block. - pub fn sapling_tree_size(&self) -> u32 { - self.sapling_tree_size - } - - /// Returns the size of the Orchard note commitment tree as of the end of the scanned block. - pub fn orchard_tree_size(&self) -> u32 { - self.orchard_tree_size - } - - /// Returns the list of transactions from the block that are relevant to the wallet. + /// Returns the list of transactions from this block that are relevant to the wallet. pub fn transactions(&self) -> &[WalletTx] { &self.transactions } - /// Returns the vector of Sapling nullifiers for each transaction in the block. - /// - /// The returned tuple is keyed by both transaction ID and the index of the transaction within - /// the block, so that either the txid or the combination of the block hash available from - /// [`Self::block_hash`] and returned transaction index may be used to uniquely identify the - /// transaction, depending upon the needs of the caller. - pub fn sapling_nullifier_map(&self) -> &[(TxId, u16, Vec)] { - &self.sapling_nullifier_map - } - - /// Returns the ordered list of Sapling note commitments to be added to the note commitment - /// tree. - pub fn sapling_commitments(&self) -> &[(sapling::Node, Retention)] { - &self.sapling_commitments + /// Returns the Sapling note commitment tree and nullifier data for the block. + pub fn sapling(&self) -> &ScannedBlockSapling { + &self.sapling } - /// Returns the vector of Orchard nullifiers for each transaction in the block. - /// - /// The returned tuple is keyed by both transaction ID and the index of the transaction within - /// the block, so that either the txid or the combination of the block hash available from - /// [`Self::block_hash`] and returned transaction index may be used to uniquely identify the - /// transaction, depending upon the needs of the caller. - pub fn orchard_nullifier_map(&self) -> &[(TxId, u16, Vec)] { - &self.orchard_nullifier_map - } - - /// Returns the ordered list of Orchard note commitments to be added to the note commitment - /// tree. - pub fn orchard_commitments( - &self, - ) -> &[(orchard::note::NoteCommitment, Retention)] { - &self.orchard_commitments + /// Returns the Orchard note commitment tree and nullifier data for the block. + #[cfg(feature = "orchard")] + pub fn orchard(&self) -> &ScannedBlockOrchard { + &self.orchard } /// Consumes `self` and returns the lists of Sapling and Orchard note commitments associated /// with the scanned block as an owned value. - #[allow(clippy::type_complexity)] - pub fn into_commitments( - self, - ) -> ( - Vec<(sapling::Node, Retention)>, - Vec<(orchard::note::NoteCommitment, Retention)>, - ) { - (self.sapling_commitments, self.orchard_commitments) + pub fn into_commitments(self) -> ScannedBlockCommitments { + ScannedBlockCommitments { + sapling: self.sapling.commitments, + #[cfg(feature = "orchard")] + orchard: self.orchard.commitments, + } } /// Returns the [`BlockMetadata`] corresponding to the scanned block. @@ -725,8 +785,9 @@ impl ScannedBlock { BlockMetadata { block_height: self.block_height, block_hash: self.block_hash, - sapling_tree_size: Some(self.sapling_tree_size), - orchard_tree_size: Some(self.orchard_tree_size), + sapling_tree_size: Some(self.sapling.final_tree_size), + #[cfg(feature = "orchard")] + orchard_tree_size: Some(self.orchard.final_tree_size), } } } diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index db7a25e410..4dca3679fe 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -603,6 +603,7 @@ where Ok((key, note, merkle_path)) } + #[cfg(feature = "orchard")] WalletNote::Orchard(_) => { // FIXME: Implement this once `Proposal` has been refactored to // include Orchard notes. @@ -623,8 +624,8 @@ where params.clone(), proposal.min_target_height(), BuildConfig::Standard { - sapling_anchor, - orchard_anchor: orchard::Anchor::empty_tree(), + sapling_anchor: Some(sapling_anchor), + orchard_anchor: None, }, ); diff --git a/zcash_client_backend/src/keys.rs b/zcash_client_backend/src/keys.rs index 852195abf2..b9725e9cdc 100644 --- a/zcash_client_backend/src/keys.rs +++ b/zcash_client_backend/src/keys.rs @@ -1,5 +1,4 @@ //! Helper functions for managing light client key material. -use orchard; use zcash_address::unified::{self, Container, Encoding}; use zcash_primitives::{ consensus, @@ -27,6 +26,9 @@ use { zcash_primitives::consensus::BranchId, }; +#[cfg(feature = "orchard")] +use orchard; + pub mod sapling { pub use sapling::zip32::{ DiversifiableFullViewingKey, ExtendedFullViewingKey, ExtendedSpendingKey, @@ -84,6 +86,7 @@ fn to_transparent_child_index(j: DiversifierIndex) -> Option { #[derive(Debug)] #[doc(hidden)] pub enum DerivationError { + #[cfg(feature = "orchard")] Orchard(orchard::zip32::Error), #[cfg(feature = "transparent-inputs")] Transparent(hdwallet::error::Error), @@ -144,6 +147,7 @@ pub struct UnifiedSpendingKey { #[cfg(feature = "transparent-inputs")] transparent: legacy::AccountPrivKey, sapling: sapling::ExtendedSpendingKey, + #[cfg(feature = "orchard")] orchard: orchard::keys::SpendingKey, } @@ -158,6 +162,7 @@ impl UnifiedSpendingKey { panic!("ZIP 32 seeds MUST be at least 32 bytes"); } + #[cfg(feature = "orchard")] let orchard = orchard::keys::SpendingKey::from_zip32_seed(seed, params.coin_type(), account.into()) .map_err(DerivationError::Orchard)?; @@ -170,6 +175,7 @@ impl UnifiedSpendingKey { #[cfg(feature = "transparent-inputs")] transparent, sapling: sapling::spending_key(seed, params.coin_type(), account), + #[cfg(feature = "orchard")] orchard, }) } @@ -179,6 +185,7 @@ impl UnifiedSpendingKey { #[cfg(feature = "transparent-inputs")] transparent: Some(self.transparent.to_account_pubkey()), sapling: Some(self.sapling.to_diversifiable_full_viewing_key()), + #[cfg(feature = "orchard")] orchard: Some((&self.orchard).into()), unknown: vec![], } @@ -197,6 +204,7 @@ impl UnifiedSpendingKey { } /// Returns the Orchard spending key component of this unified spending key. + #[cfg(feature = "orchard")] pub fn orchard(&self) -> &orchard::keys::SpendingKey { &self.orchard } @@ -215,13 +223,15 @@ impl UnifiedSpendingKey { let mut result = vec![]; result.write_u32::(era.id()).unwrap(); - // orchard - let orchard_key = self.orchard(); - CompactSize::write(&mut result, usize::try_from(Typecode::Orchard).unwrap()).unwrap(); + #[cfg(feature = "orchard")] + { + let orchard_key = self.orchard(); + CompactSize::write(&mut result, usize::try_from(Typecode::Orchard).unwrap()).unwrap(); - let orchard_key_bytes = orchard_key.to_bytes(); - CompactSize::write(&mut result, orchard_key_bytes.len()).unwrap(); - result.write_all(orchard_key_bytes).unwrap(); + let orchard_key_bytes = orchard_key.to_bytes(); + CompactSize::write(&mut result, orchard_key_bytes.len()).unwrap(); + result.write_all(orchard_key_bytes).unwrap(); + } // sapling let sapling_key = self.sapling(); @@ -261,6 +271,7 @@ impl UnifiedSpendingKey { return Err(DecodingError::EraMismatch(decoded_era)); } + #[cfg(feature = "orchard")] let mut orchard = None; let mut sapling = None; #[cfg(feature = "transparent-inputs")] @@ -283,12 +294,16 @@ impl UnifiedSpendingKey { source .read_exact(&mut key) .map_err(|_| DecodingError::InsufficientData(Typecode::Orchard))?; - orchard = Some( - Option::::from( - orchard::keys::SpendingKey::from_bytes(key), - ) - .ok_or(DecodingError::KeyDataInvalid(Typecode::Orchard))?, - ); + + #[cfg(feature = "orchard")] + { + orchard = Some( + Option::::from( + orchard::keys::SpendingKey::from_bytes(key), + ) + .ok_or(DecodingError::KeyDataInvalid(Typecode::Orchard))?, + ); + } } Typecode::Sapling => { if len != 169 { @@ -324,13 +339,19 @@ impl UnifiedSpendingKey { } } + #[cfg(feature = "orchard")] + let has_orchard = orchard.is_some(); + #[cfg(not(feature = "orchard"))] + let has_orchard = true; + #[cfg(feature = "transparent-inputs")] let has_transparent = transparent.is_some(); #[cfg(not(feature = "transparent-inputs"))] let has_transparent = true; - if orchard.is_some() && sapling.is_some() && has_transparent { + if has_orchard && sapling.is_some() && has_transparent { return Ok(UnifiedSpendingKey { + #[cfg(feature = "orchard")] orchard: orchard.unwrap(), sapling: sapling.unwrap(), #[cfg(feature = "transparent-inputs")] @@ -362,6 +383,7 @@ pub struct UnifiedFullViewingKey { #[cfg(feature = "transparent-inputs")] transparent: Option, sapling: Option, + #[cfg(feature = "orchard")] orchard: Option, unknown: Vec<(u32, Vec)>, } @@ -372,7 +394,7 @@ impl UnifiedFullViewingKey { pub fn new( #[cfg(feature = "transparent-inputs")] transparent: Option, sapling: Option, - orchard: Option, + #[cfg(feature = "orchard")] orchard: Option, ) -> Option { if sapling.is_none() { None @@ -381,6 +403,7 @@ impl UnifiedFullViewingKey { #[cfg(feature = "transparent-inputs")] transparent, sapling, + #[cfg(feature = "orchard")] orchard, // We don't allow constructing new UFVKs with unknown items, but we store // this to allow parsing such UFVKs. @@ -402,6 +425,7 @@ impl UnifiedFullViewingKey { )); } + #[cfg(feature = "orchard")] let mut orchard = None; let mut sapling = None; #[cfg(feature = "transparent-inputs")] @@ -413,6 +437,7 @@ impl UnifiedFullViewingKey { .items_as_parsed() .iter() .filter_map(|receiver| match receiver { + #[cfg(feature = "orchard")] unified::Fvk::Orchard(data) => orchard::keys::FullViewingKey::from_bytes(data) .ok_or("Invalid Orchard FVK in Unified FVK") .map(|addr| { @@ -420,6 +445,10 @@ impl UnifiedFullViewingKey { None }) .transpose(), + #[cfg(not(feature = "orchard"))] + unified::Fvk::Orchard(data) => { + Some(Ok((unified::Typecode::Orchard.into(), data.to_vec()))) + } unified::Fvk::Sapling(data) => { sapling::DiversifiableFullViewingKey::from_bytes(data) .ok_or("Invalid Sapling FVK in Unified FVK") @@ -449,6 +478,7 @@ impl UnifiedFullViewingKey { #[cfg(feature = "transparent-inputs")] transparent, sapling, + #[cfg(feature = "orchard")] orchard, unknown, }) @@ -457,12 +487,6 @@ impl UnifiedFullViewingKey { /// Returns the string encoding of this `UnifiedFullViewingKey` for the given network. pub fn encode(&self, params: &P) -> String { let items = std::iter::empty() - .chain( - self.orchard - .as_ref() - .map(|fvk| fvk.to_bytes()) - .map(unified::Fvk::Orchard), - ) .chain( self.sapling .as_ref() @@ -477,6 +501,13 @@ impl UnifiedFullViewingKey { data: data.clone(), }), ); + #[cfg(feature = "orchard")] + let items = items.chain( + self.orchard + .as_ref() + .map(|fvk| fvk.to_bytes()) + .map(unified::Fvk::Orchard), + ); #[cfg(feature = "transparent-inputs")] let items = items.chain( self.transparent @@ -503,6 +534,7 @@ impl UnifiedFullViewingKey { } /// Returns the Orchard full viewing key component of this unified key. + #[cfg(feature = "orchard")] pub fn orchard(&self) -> Option<&orchard::keys::FullViewingKey> { self.orchard.as_ref() } @@ -537,7 +569,12 @@ impl UnifiedFullViewingKey { #[cfg(not(feature = "transparent-inputs"))] let transparent = None; - UnifiedAddress::from_receivers(None, sapling, transparent) + UnifiedAddress::from_receivers( + #[cfg(feature = "orchard")] + None, + sapling, + transparent, + ) } /// Searches the diversifier space starting at diversifier index `j` for one which will @@ -658,6 +695,7 @@ mod tests { fn ufvk_round_trip() { let account = AccountId::ZERO; + #[cfg(feature = "orchard")] let orchard = { let sk = orchard::keys::SpendingKey::from_zip32_seed(&[0; 32], 0, 0).unwrap(); Some(orchard::keys::FullViewingKey::from(&sk)) @@ -678,6 +716,7 @@ mod tests { #[cfg(feature = "transparent-inputs")] transparent, sapling, + #[cfg(feature = "orchard")] orchard, ) .unwrap(); @@ -705,6 +744,7 @@ mod tests { decoded.sapling.map(|s| s.to_bytes()), ufvk.sapling.map(|s| s.to_bytes()), ); + #[cfg(feature = "orchard")] assert_eq!( decoded.orchard.map(|o| o.to_bytes()), ufvk.orchard.map(|o| o.to_bytes()), @@ -716,7 +756,12 @@ mod tests { decoded_with_t.transparent.map(|t| t.serialize()), ufvk.transparent.as_ref().map(|t| t.serialize()), ); - #[cfg(not(feature = "transparent-inputs"))] + + #[cfg(all(not(feature = "orchard"), not(feature = "transparent-inputs")))] + assert_eq!(decoded_with_t.unknown.len(), 2); + #[cfg(all(feature = "transparent-inputs", not(feature = "orchard")))] + assert_eq!(decoded_with_t.unknown.len(), 1); + #[cfg(all(feature = "orchard", not(feature = "transparent-inputs")))] assert_eq!(decoded_with_t.unknown.len(), 1); } @@ -771,14 +816,20 @@ mod tests { #[cfg(feature = "unstable")] fn prop_usk_roundtrip(usk in arb_unified_spending_key(Network::MainNetwork)) { let encoded = usk.to_bytes(Era::Orchard); + #[cfg(not(feature = "transparent-inputs"))] assert_eq!(encoded.len(), 4 + 2 + 32 + 2 + 169); #[cfg(feature = "transparent-inputs")] assert_eq!(encoded.len(), 4 + 2 + 32 + 2 + 169 + 2 + 64); + let decoded = UnifiedSpendingKey::from_bytes(Era::Orchard, &encoded); let decoded = decoded.unwrap_or_else(|e| panic!("Error decoding USK: {:?}", e)); + + #[cfg(feature = "orchard")] assert!(bool::from(decoded.orchard().ct_eq(usk.orchard()))); + assert_eq!(decoded.sapling(), usk.sapling()); + #[cfg(feature = "transparent-inputs")] assert_eq!(decoded.transparent().to_bytes(), usk.transparent().to_bytes()); } diff --git a/zcash_client_backend/src/lib.rs b/zcash_client_backend/src/lib.rs index b8a45ba895..acb908b6af 100644 --- a/zcash_client_backend/src/lib.rs +++ b/zcash_client_backend/src/lib.rs @@ -31,7 +31,7 @@ pub use decrypt::{decrypt_transaction, DecryptedOutput, TransferType}; #[macro_use] extern crate assert_matches; -/// A shielded transfer protocol supported by the wallet. +/// A shielded transfer protocol known to the wallet. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum ShieldedProtocol { /// The Sapling protocol diff --git a/zcash_client_backend/src/proto.rs b/zcash_client_backend/src/proto.rs index e55321ceb8..52592c0a62 100644 --- a/zcash_client_backend/src/proto.rs +++ b/zcash_client_backend/src/proto.rs @@ -186,6 +186,7 @@ impl From<&sapling::bundle::SpendDescription< } } +#[cfg(feature = "orchard")] impl From<&orchard::Action> for compact_formats::CompactOrchardAction { fn from(action: &orchard::Action) -> compact_formats::CompactOrchardAction { compact_formats::CompactOrchardAction { diff --git a/zcash_client_backend/src/scanning.rs b/zcash_client_backend/src/scanning.rs index 39472f0968..513b6653b3 100644 --- a/zcash_client_backend/src/scanning.rs +++ b/zcash_client_backend/src/scanning.rs @@ -18,7 +18,7 @@ use zcash_primitives::{ zip32::{AccountId, Scope}, }; -use crate::data_api::{BlockMetadata, ScannedBlock}; +use crate::data_api::{BlockMetadata, ScannedBlock, ScannedBlockSapling}; use crate::{ proto::compact_formats::CompactBlock, scan::{Batch, BatchRunner, Tasks}, @@ -26,6 +26,9 @@ use crate::{ ShieldedProtocol, }; +#[cfg(feature = "orchard")] +use crate::data_api::ScannedBlockOrchard; + /// A key that can be used to perform trial decryption and nullifier /// computation for a Sapling [`CompactSaplingOutput`] /// @@ -349,95 +352,98 @@ pub(crate) fn scan_block_with_runner< let cur_hash = block.hash(); let zip212_enforcement = consensus::sapling_zip212_enforcement(params, cur_height); - let initial_sapling_tree_size = prior_block_metadata.and_then(|m| m.sapling_tree_size()); - let mut sapling_commitment_tree_size = initial_sapling_tree_size.map_or_else( - || { - block.chain_metadata.as_ref().map_or_else( - || { - // If we're below Sapling activation, or Sapling activation is not set, the tree size is zero - params - .activation_height(NetworkUpgrade::Sapling) - .map_or_else( + let mut sapling_commitment_tree_size = prior_block_metadata + .and_then(|m| m.sapling_tree_size()) + .map_or_else( + || { + block.chain_metadata.as_ref().map_or_else( + || { + // If we're below Sapling activation, or Sapling activation is not set, the tree size is zero + params + .activation_height(NetworkUpgrade::Sapling) + .map_or_else( + || Ok(0), + |sapling_activation| { + if cur_height < sapling_activation { + Ok(0) + } else { + Err(ScanError::TreeSizeUnknown { + protocol: ShieldedProtocol::Sapling, + at_height: cur_height, + }) + } + }, + ) + }, + |m| { + let sapling_output_count: u32 = block + .vtx + .iter() + .map(|tx| tx.outputs.len()) + .sum::() + .try_into() + .expect("Sapling output count cannot exceed a u32"); + + // The default for m.sapling_commitment_tree_size is zero, so we need to check + // that the subtraction will not underflow; if it would do so, we were given + // invalid chain metadata for a block with Sapling outputs. + m.sapling_commitment_tree_size + .checked_sub(sapling_output_count) + .ok_or(ScanError::TreeSizeInvalid { + protocol: ShieldedProtocol::Sapling, + at_height: cur_height, + }) + }, + ) + }, + Ok, + )?; + + #[cfg(feature = "orchard")] + let mut orchard_commitment_tree_size = prior_block_metadata + .and_then(|m| m.orchard_tree_size()) + .map_or_else( + || { + block.chain_metadata.as_ref().map_or_else( + || { + // If we're below Orchard activation, or Orchard activation is not set, the tree size is zero + params.activation_height(NetworkUpgrade::Nu5).map_or_else( || Ok(0), - |sapling_activation| { - if cur_height < sapling_activation { + |orchard_activation| { + if cur_height < orchard_activation { Ok(0) } else { Err(ScanError::TreeSizeUnknown { - protocol: ShieldedProtocol::Sapling, + protocol: ShieldedProtocol::Orchard, at_height: cur_height, }) } }, ) - }, - |m| { - let sapling_output_count: u32 = block - .vtx - .iter() - .map(|tx| tx.outputs.len()) - .sum::() - .try_into() - .expect("Sapling output count cannot exceed a u32"); - - // The default for m.sapling_commitment_tree_size is zero, so we need to check - // that the subtraction will not underflow; if it would do so, we were given - // invalid chain metadata for a block with Sapling outputs. - m.sapling_commitment_tree_size - .checked_sub(sapling_output_count) - .ok_or(ScanError::TreeSizeInvalid { - protocol: ShieldedProtocol::Sapling, - at_height: cur_height, - }) - }, - ) - }, - Ok, - )?; - - let initial_orchard_tree_size = prior_block_metadata.and_then(|m| m.orchard_tree_size()); - let mut orchard_commitment_tree_size = initial_orchard_tree_size.map_or_else( - || { - block.chain_metadata.as_ref().map_or_else( - || { - // If we're below Orchard activation, or Orchard activation is not set, the tree size is zero - params.activation_height(NetworkUpgrade::Nu5).map_or_else( - || Ok(0), - |orchard_activation| { - if cur_height < orchard_activation { - Ok(0) - } else { - Err(ScanError::TreeSizeUnknown { - protocol: ShieldedProtocol::Orchard, - at_height: cur_height, - }) - } - }, - ) - }, - |m| { - let orchard_action_count: u32 = block - .vtx - .iter() - .map(|tx| tx.actions.len()) - .sum::() - .try_into() - .expect("Orchard action count cannot exceed a u32"); - - // The default for m.orchard_commitment_tree_size is zero, so we need to check - // that the subtraction will not underflow; if it would do so, we were given - // invalid chain metadata for a block with Orchard actions. - m.orchard_commitment_tree_size - .checked_sub(orchard_action_count) - .ok_or(ScanError::TreeSizeInvalid { - protocol: ShieldedProtocol::Orchard, - at_height: cur_height, - }) - }, - ) - }, - Ok, - )?; + }, + |m| { + let orchard_action_count: u32 = block + .vtx + .iter() + .map(|tx| tx.actions.len()) + .sum::() + .try_into() + .expect("Orchard action count cannot exceed a u32"); + + // The default for m.orchard_commitment_tree_size is zero, so we need to check + // that the subtraction will not underflow; if it would do so, we were given + // invalid chain metadata for a block with Orchard actions. + m.orchard_commitment_tree_size + .checked_sub(orchard_action_count) + .ok_or(ScanError::TreeSizeInvalid { + protocol: ShieldedProtocol::Orchard, + at_height: cur_height, + }) + }, + ) + }, + Ok, + )?; let compact_block_tx_count = block.vtx.len(); let mut wtxs: Vec> = vec![]; @@ -489,6 +495,7 @@ pub(crate) fn scan_block_with_runner< // and tx.actions end up being moved. let tx_outputs_len = u32::try_from(tx.outputs.len()).expect("Sapling output count cannot exceed a u32"); + #[cfg(feature = "orchard")] let tx_actions_len = u32::try_from(tx.actions.len()).expect("Orchard action count cannot exceed a u32"); @@ -612,7 +619,10 @@ pub(crate) fn scan_block_with_runner< } sapling_commitment_tree_size += tx_outputs_len; - orchard_commitment_tree_size += tx_actions_len; + #[cfg(feature = "orchard")] + { + orchard_commitment_tree_size += tx_actions_len; + } } if let Some(chain_meta) = block.chain_metadata { @@ -625,6 +635,7 @@ pub(crate) fn scan_block_with_runner< }); } + #[cfg(feature = "orchard")] if chain_meta.orchard_commitment_tree_size != orchard_commitment_tree_size { return Err(ScanError::TreeSizeMismatch { protocol: ShieldedProtocol::Orchard, @@ -639,13 +650,18 @@ pub(crate) fn scan_block_with_runner< cur_height, cur_hash, block.time, - sapling_commitment_tree_size, - orchard_commitment_tree_size, wtxs, - sapling_nullifier_map, - sapling_note_commitments, - vec![], // FIXME: collect the Orchard nullifiers - vec![], // FIXME: collect the Orchard note commitments + ScannedBlockSapling::new( + sapling_commitment_tree_size, + sapling_note_commitments, + sapling_nullifier_map, + ), + #[cfg(feature = "orchard")] + ScannedBlockOrchard::new( + orchard_commitment_tree_size, + vec![], // FIXME: collect the Orchard nullifiers + vec![], // FIXME: collect the Orchard note commitments + ), )) } @@ -846,6 +862,7 @@ mod tests { BlockHeight::from(0), BlockHash([0u8; 32]), Some(0), + #[cfg(feature = "orchard")] Some(0), )), batch_runner.as_mut(), @@ -866,10 +883,11 @@ mod tests { Position::from(1) ); - assert_eq!(scanned_block.sapling_tree_size(), 2); + assert_eq!(scanned_block.sapling().final_tree_size(), 2); assert_eq!( scanned_block - .sapling_commitments() + .sapling() + .commitments() .iter() .map(|(_, retention)| *retention) .collect::>(), @@ -944,7 +962,8 @@ mod tests { assert_eq!( scanned_block - .sapling_commitments() + .sapling() + .commitments() .iter() .map(|(_, retention)| *retention) .collect::>(), @@ -997,7 +1016,8 @@ mod tests { assert_eq!( scanned_block - .sapling_commitments() + .sapling() + .commitments() .iter() .map(|(_, retention)| *retention) .collect::>(), diff --git a/zcash_client_backend/src/wallet.rs b/zcash_client_backend/src/wallet.rs index 8b6ab62992..8e7242a657 100644 --- a/zcash_client_backend/src/wallet.rs +++ b/zcash_client_backend/src/wallet.rs @@ -239,6 +239,7 @@ impl WalletSaplingOutput { #[derive(Debug, Clone, PartialEq, Eq)] pub enum WalletNote { Sapling(sapling::Note), + #[cfg(feature = "orchard")] Orchard(orchard::Note), } @@ -248,6 +249,7 @@ impl WalletNote { WalletNote::Sapling(n) => n.value().try_into().expect( "Sapling notes must have values in the range of valid non-negative ZEC values.", ), + #[cfg(feature = "orchard")] WalletNote::Orchard(n) => NonNegativeAmount::from_u64(n.value().inner()).expect( "Orchard notes must have values in the range of valid non-negative ZEC values.", ), diff --git a/zcash_client_backend/src/zip321.rs b/zcash_client_backend/src/zip321.rs index 14fe08e3d9..6cf104d5e7 100644 --- a/zcash_client_backend/src/zip321.rs +++ b/zcash_client_backend/src/zip321.rs @@ -760,7 +760,12 @@ pub mod testing { sapling in arb_payment_address(), transparent in option::of(arb_transparent_addr()), ) -> UnifiedAddress { - UnifiedAddress::from_receivers(None, Some(sapling), transparent).unwrap() + UnifiedAddress::from_receivers( + #[cfg(feature = "orchard")] + None, + Some(sapling), + transparent + ).unwrap() } } diff --git a/zcash_client_sqlite/CHANGELOG.md b/zcash_client_sqlite/CHANGELOG.md index a2ded0be83..88f8cb82f5 100644 --- a/zcash_client_sqlite/CHANGELOG.md +++ b/zcash_client_sqlite/CHANGELOG.md @@ -7,6 +7,10 @@ and this library adheres to Rust's notion of ## [Unreleased] +### Added +- A new `orchard` feature flag has been added to make it possible to + build client code without `orchard` dependendencies. + ### Changed - `zcash_client_sqlite::error::SqliteClientError` has new error variants: - `SqliteClientError::UnsupportedPoolType` diff --git a/zcash_client_sqlite/Cargo.toml b/zcash_client_sqlite/Cargo.toml index 9345ccba8f..fdcd28cc4a 100644 --- a/zcash_client_sqlite/Cargo.toml +++ b/zcash_client_sqlite/Cargo.toml @@ -70,9 +70,9 @@ zcash_client_backend = { workspace = true, features = ["test-dependencies", "uns zcash_address = { workspace = true, features = ["test-dependencies"] } [features] -default = ["multicore"] +default = ["multicore", "transparent-inputs"] multicore = ["maybe-rayon/threads", "zcash_primitives/multicore"] -mainnet = [] +orchard = ["zcash_client_backend/orchard"] test-dependencies = [ "incrementalmerkletree/test-dependencies", "zcash_primitives/test-dependencies", diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index f000eebe84..6fb2e49c83 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -20,8 +20,9 @@ //! //! # Features //! -//! The `mainnet` feature configures the light client for use with the Zcash mainnet. By -//! default, the light client is configured for use with the Zcash testnet. +//! - `transparent-inputs` enables receiving and spending transparent UTXOs. +//! - `orchard` enables spending Orchard notes and creating Orchard outputs. +//! - `multicore` enables multithreaded trial decryption. //! //! [`WalletRead`]: zcash_client_backend::data_api::WalletRead //! [`WalletWrite`]: zcash_client_backend::data_api::WalletWrite @@ -446,8 +447,8 @@ impl WalletWrite for WalletDb ( block.height(), Position::from( - u64::from(block.sapling_tree_size()) - - u64::try_from(block.sapling_commitments().len()).unwrap(), + u64::from(block.sapling().final_tree_size()) + - u64::try_from(block.sapling().commitments().len()).unwrap(), ), ) }); @@ -468,8 +469,8 @@ impl WalletWrite for WalletDb block.height(), block.block_hash(), block.block_time(), - block.sapling_tree_size(), - block.sapling_commitments().len().try_into().unwrap(), + block.sapling().final_tree_size(), + block.sapling().commitments().len().try_into().unwrap(), )?; for tx in block.transactions() { @@ -498,7 +499,7 @@ impl WalletWrite for WalletDb wdb.conn.0, block.height(), ShieldedProtocol::Sapling, - block.sapling_nullifier_map(), + block.sapling().nullifier_map(), )?; note_positions.extend(block.transactions().iter().flat_map(|wtx| { @@ -508,8 +509,8 @@ impl WalletWrite for WalletDb })); last_scanned_height = Some(block.height()); - let (block_sapling_commitments, _) = block.into_commitments(); - sapling_commitments.extend(block_sapling_commitments.into_iter().map(Some)); + let block_commitments = block.into_commitments(); + sapling_commitments.extend(block_commitments.sapling.into_iter().map(Some)); } // Prune the nullifier map of entries we no longer need. diff --git a/zcash_client_sqlite/src/testing.rs b/zcash_client_sqlite/src/testing.rs index a9b8eb9fc9..ec6c0dcddf 100644 --- a/zcash_client_sqlite/src/testing.rs +++ b/zcash_client_sqlite/src/testing.rs @@ -841,6 +841,8 @@ pub(crate) fn fake_compact_block_from_tx( ctx.outputs.push(output.into()); } } + + #[cfg(feature = "orchard")] if let Some(bundle) = tx.orchard_bundle() { for action in bundle.actions() { ctx.actions.push(action.into()); diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index 12ed1af45c..22fc8f275f 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -967,10 +967,11 @@ pub(crate) fn get_target_and_anchor_heights( } fn parse_block_metadata( - params: &P, + _params: &P, row: (BlockHeight, Vec, Option, Vec, Option), ) -> Result { - let (block_height, hash_data, sapling_tree_size_opt, sapling_tree, orchard_tree_size_opt) = row; + let (block_height, hash_data, sapling_tree_size_opt, sapling_tree, _orchard_tree_size_opt) = + row; let sapling_tree_size = sapling_tree_size_opt.map_or_else(|| { if sapling_tree == BLOCK_SAPLING_FRONTIER_ABSENT { Err(SqliteClientError::CorruptedData("One of either the Sapling tree size or the legacy Sapling commitment tree must be present.".to_owned())) @@ -997,12 +998,13 @@ fn parse_block_metadata( block_height, block_hash, Some(sapling_tree_size), - if params + #[cfg(feature = "orchard")] + if _params .activation_height(NetworkUpgrade::Nu5) .iter() .any(|nu5_activation| &block_height >= nu5_activation) { - orchard_tree_size_opt + _orchard_tree_size_opt } else { Some(0) }, diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index dfeedb2c90..d3c295ae8f 100644 --- a/zcash_extensions/src/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -648,8 +648,8 @@ mod tests { FutureNetwork, height, BuildConfig::Standard { - sapling_anchor, - orchard_anchor: orchard::Anchor::empty_tree(), + sapling_anchor: Some(sapling_anchor), + orchard_anchor: Some(orchard::Anchor::empty_tree()), }, ), extension_id: 0, diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index b6b143b9e9..980f1bb6de 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -201,36 +201,40 @@ impl Progress { #[derive(Clone, Copy)] pub enum BuildConfig { Standard { - sapling_anchor: sapling::Anchor, - orchard_anchor: orchard::Anchor, + sapling_anchor: Option, + orchard_anchor: Option, }, Coinbase, } impl BuildConfig { /// Returns the Sapling bundle type and anchor for this configuration. - pub fn sapling_builder_config(&self) -> (sapling::builder::BundleType, sapling::Anchor) { + pub fn sapling_builder_config( + &self, + ) -> Option<(sapling::builder::BundleType, sapling::Anchor)> { match self { - BuildConfig::Standard { sapling_anchor, .. } => { - (sapling::builder::BundleType::DEFAULT, *sapling_anchor) - } - BuildConfig::Coinbase => ( + BuildConfig::Standard { sapling_anchor, .. } => sapling_anchor + .as_ref() + .map(|a| (sapling::builder::BundleType::DEFAULT, *a)), + BuildConfig::Coinbase => Some(( sapling::builder::BundleType::Coinbase, sapling::Anchor::empty_tree(), - ), + )), } } /// Returns the Orchard bundle type and anchor for this configuration. - pub fn orchard_builder_config(&self) -> (orchard::builder::BundleType, orchard::Anchor) { + pub fn orchard_builder_config( + &self, + ) -> Option<(orchard::builder::BundleType, orchard::Anchor)> { match self { - BuildConfig::Standard { orchard_anchor, .. } => { - (orchard::builder::BundleType::DEFAULT, *orchard_anchor) - } - BuildConfig::Coinbase => ( + BuildConfig::Standard { orchard_anchor, .. } => orchard_anchor + .as_ref() + .map(|a| (orchard::builder::BundleType::DEFAULT, *a)), + BuildConfig::Coinbase => Some(( orchard::builder::BundleType::Coinbase, orchard::Anchor::empty_tree(), - ), + )), } } } @@ -308,20 +312,22 @@ impl<'a, P: consensus::Parameters> Builder<'a, P, ()> { /// expiry delta (20 blocks). pub fn new(params: P, target_height: BlockHeight, build_config: BuildConfig) -> Self { let orchard_builder = if params.is_nu_active(NetworkUpgrade::Nu5, target_height) { - let (bundle_type, anchor) = build_config.orchard_builder_config(); - Some(orchard::builder::Builder::new(bundle_type, anchor)) + build_config + .orchard_builder_config() + .map(|(bundle_type, anchor)| orchard::builder::Builder::new(bundle_type, anchor)) } else { None }; - let sapling_builder = Some({ - let (bundle_type, anchor) = build_config.sapling_builder_config(); - sapling::builder::Builder::new( - consensus::sapling_zip212_enforcement(¶ms, target_height), - bundle_type, - anchor, - ) - }); + let sapling_builder = build_config + .sapling_builder_config() + .map(|(bundle_type, anchor)| { + sapling::builder::Builder::new( + consensus::sapling_zip212_enforcement(¶ms, target_height), + bundle_type, + anchor, + ) + }); Builder { params, @@ -522,20 +528,22 @@ impl<'a, P: consensus::Parameters, U: sapling::builder::ProverProgress> Builder< transparent_inputs, self.transparent_builder.outputs(), sapling_spends, - self.sapling_builder.as_ref().map_or(Ok(0), |builder| { - self.build_config - .sapling_builder_config() - .0 - .num_outputs(sapling_spends, builder.outputs().len()) - .map_err(FeeError::Bundle) - })?, - self.orchard_builder.as_ref().map_or(Ok(0), |builder| { - self.build_config - .orchard_builder_config() - .0 - .num_actions(builder.spends().len(), builder.outputs().len()) - .map_err(FeeError::Bundle) - })?, + self.sapling_builder + .as_ref() + .zip(self.build_config.sapling_builder_config()) + .map_or(Ok(0), |(builder, (bundle_type, _))| { + bundle_type + .num_outputs(sapling_spends, builder.outputs().len()) + .map_err(FeeError::Bundle) + })?, + self.orchard_builder + .as_ref() + .zip(self.build_config.orchard_builder_config()) + .map_or(Ok(0), |(builder, (bundle_type, _))| { + bundle_type + .num_actions(builder.spends().len(), builder.outputs().len()) + .map_err(FeeError::Bundle) + })?, ) .map_err(FeeError::FeeRule) } @@ -563,20 +571,22 @@ impl<'a, P: consensus::Parameters, U: sapling::builder::ProverProgress> Builder< transparent_inputs, self.transparent_builder.outputs(), sapling_spends, - self.sapling_builder.as_ref().map_or(Ok(0), |builder| { - self.build_config - .sapling_builder_config() - .0 - .num_outputs(sapling_spends, builder.outputs().len()) - .map_err(FeeError::Bundle) - })?, - self.orchard_builder.as_ref().map_or(Ok(0), |builder| { - self.build_config - .orchard_builder_config() - .0 - .num_actions(builder.spends().len(), builder.outputs().len()) - .map_err(FeeError::Bundle) - })?, + self.sapling_builder + .as_ref() + .zip(self.build_config.sapling_builder_config()) + .map_or(Ok(0), |(builder, (bundle_type, _))| { + bundle_type + .num_outputs(sapling_spends, builder.outputs().len()) + .map_err(FeeError::Bundle) + })?, + self.orchard_builder + .as_ref() + .zip(self.build_config.orchard_builder_config()) + .map_or(Ok(0), |(builder, (bundle_type, _))| { + bundle_type + .num_actions(builder.spends().len(), builder.outputs().len()) + .map_err(FeeError::Bundle) + })?, self.tze_builder.inputs(), self.tze_builder.outputs(), ) @@ -918,8 +928,8 @@ mod tests { let mut builder = builder::Builder { params: TEST_NETWORK, build_config: BuildConfig::Standard { - sapling_anchor: sapling::Anchor::empty_tree(), - orchard_anchor: orchard::Anchor::empty_tree(), + sapling_anchor: Some(sapling::Anchor::empty_tree()), + orchard_anchor: Some(orchard::Anchor::empty_tree()), }, target_height: sapling_activation_height, expiry_height: sapling_activation_height + DEFAULT_TX_EXPIRY_DELTA, @@ -989,8 +999,8 @@ mod tests { .unwrap(); let build_config = BuildConfig::Standard { - sapling_anchor: witness1.root().into(), - orchard_anchor: orchard::Anchor::empty_tree(), + sapling_anchor: Some(witness1.root().into()), + orchard_anchor: None, }; let mut builder = Builder::new(TEST_NETWORK, tx_height, build_config); @@ -1027,8 +1037,8 @@ mod tests { // 0.0001 t-ZEC fee { let build_config = BuildConfig::Standard { - sapling_anchor: sapling::Anchor::empty_tree(), - orchard_anchor: orchard::Anchor::empty_tree(), + sapling_anchor: None, + orchard_anchor: None, }; let builder = Builder::new(TEST_NETWORK, tx_height, build_config); assert_matches!( @@ -1045,8 +1055,8 @@ mod tests { // 0.0005 z-ZEC out, 0.0001 t-ZEC fee { let build_config = BuildConfig::Standard { - sapling_anchor: sapling::Anchor::empty_tree(), - orchard_anchor: orchard::Anchor::empty_tree(), + sapling_anchor: Some(sapling::Anchor::empty_tree()), + orchard_anchor: Some(orchard::Anchor::empty_tree()), }; let mut builder = Builder::new(TEST_NETWORK, tx_height, build_config); builder @@ -1068,8 +1078,8 @@ mod tests { // 0.0005 t-ZEC out, 0.0001 t-ZEC fee { let build_config = BuildConfig::Standard { - sapling_anchor: sapling::Anchor::empty_tree(), - orchard_anchor: orchard::Anchor::empty_tree(), + sapling_anchor: Some(sapling::Anchor::empty_tree()), + orchard_anchor: Some(orchard::Anchor::empty_tree()), }; let mut builder = Builder::new(TEST_NETWORK, tx_height, build_config); builder @@ -1098,8 +1108,8 @@ mod tests { // 0.0003 z-ZEC out, 0.0002 t-ZEC out, 0.0001 t-ZEC fee, 0.00059999 z-ZEC in { let build_config = BuildConfig::Standard { - sapling_anchor: witness1.root().into(), - orchard_anchor: orchard::Anchor::empty_tree(), + sapling_anchor: Some(witness1.root().into()), + orchard_anchor: Some(orchard::Anchor::empty_tree()), }; let mut builder = Builder::new(TEST_NETWORK, tx_height, build_config); builder @@ -1138,8 +1148,8 @@ mod tests { // 0.0003 z-ZEC out, 0.0002 t-ZEC out, 0.0001 t-ZEC fee, 0.0006 z-ZEC in { let build_config = BuildConfig::Standard { - sapling_anchor: witness1.root().into(), - orchard_anchor: orchard::Anchor::empty_tree(), + sapling_anchor: Some(witness1.root().into()), + orchard_anchor: Some(orchard::Anchor::empty_tree()), }; let mut builder = Builder::new(TEST_NETWORK, tx_height, build_config); builder