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.
This commit is contained in:
Kris Nuttycombe 2024-01-03 09:17:24 -07:00
parent 4bd6520013
commit f18d082aa3
18 changed files with 507 additions and 290 deletions

View File

@ -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.

View File

@ -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
@ -109,10 +109,13 @@ lightwalletd-tonic = ["dep:tonic"]
## Enables receiving transparent funds and shielding them.
transparent-inputs = ["dep:hdwallet", "zcash_primitives/transparent-inputs"]
## Enables receiving and spending Orchard funds.
orchard = ["dep:orchard"]
## Exposes APIs that are useful for testing, such as `proptest` strategies.
test-dependencies = [
"dep:proptest",
"orchard/test-dependencies",
"orchard?/test-dependencies",
"zcash_primitives/test-dependencies",
"incrementalmerkletree/test-dependencies",
]

View File

@ -38,6 +38,7 @@ impl AddressMetadata {
/// A Unified Address.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UnifiedAddress {
#[cfg(feature = "orchard")]
orchard: Option<orchard::Address>,
sapling: Option<PaymentAddress>,
transparent: Option<TransparentAddress>,
@ -48,6 +49,7 @@ impl TryFrom<unified::Address> for UnifiedAddress {
type Error = &'static str;
fn try_from(ua: unified::Address) -> Result<Self, Self::Error> {
#[cfg(feature = "orchard")]
let mut orchard = None;
let mut sapling = None;
let mut transparent = None;
@ -58,6 +60,7 @@ impl TryFrom<unified::Address> 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<unified::Address> 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<unified::Address> for UnifiedAddress {
.collect::<Result<_, _>>()?;
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<orchard::Address>,
#[cfg(feature = "orchard")] orchard: Option<orchard::Address>,
sapling: Option<PaymentAddress>,
transparent: Option<TransparentAddress>,
) -> Option<Self> {
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(_) => {

View File

@ -556,6 +556,7 @@ pub struct BlockMetadata {
block_height: BlockHeight,
block_hash: BlockHash,
sapling_tree_size: Option<u32>,
#[cfg(feature = "orchard")]
orchard_tree_size: Option<u32>,
}
@ -565,12 +566,13 @@ impl BlockMetadata {
block_height: BlockHeight,
block_hash: BlockHash,
sapling_tree_size: Option<u32>,
orchard_tree_size: Option<u32>,
#[cfg(feature = "orchard")] orchard_tree_size: Option<u32>,
) -> 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<u32> {
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<BlockHeight>)>,
nullifier_map: Vec<(TxId, u16, Vec<sapling::Nullifier>)>,
}
impl ScannedBlockSapling {
pub(crate) fn new(
final_tree_size: u32,
commitments: Vec<(sapling::Node, Retention<BlockHeight>)>,
nullifier_map: Vec<(TxId, u16, Vec<sapling::Nullifier>)>,
) -> 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<sapling::Nullifier>)] {
&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<BlockHeight>)] {
&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<orchard::note::Nullifier>)>,
commitments: Vec<(orchard::note::NoteCommitment, Retention<BlockHeight>)>,
}
#[cfg(feature = "orchard")]
impl ScannedBlockOrchard {
pub(crate) fn new(
final_tree_size: u32,
nullifier_map: Vec<(TxId, u16, Vec<orchard::note::Nullifier>)>,
commitments: Vec<(orchard::note::NoteCommitment, Retention<BlockHeight>)>,
) -> 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<orchard::note::Nullifier>)] {
&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<BlockHeight>)] {
&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<BlockHeight>)>,
/// 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<BlockHeight>)>,
}
/// 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<Nf, S> {
block_height: BlockHeight,
block_hash: BlockHash,
block_time: u32,
sapling_tree_size: u32,
orchard_tree_size: u32,
transactions: Vec<WalletTx<Nf, S>>,
sapling_nullifier_map: Vec<(TxId, u16, Vec<sapling::Nullifier>)>,
sapling_commitments: Vec<(sapling::Node, Retention<BlockHeight>)>,
orchard_nullifier_map: Vec<(TxId, u16, Vec<orchard::note::Nullifier>)>,
orchard_commitments: Vec<(orchard::note::NoteCommitment, Retention<BlockHeight>)>,
sapling: ScannedBlockSapling,
#[cfg(feature = "orchard")]
orchard: ScannedBlockOrchard,
}
impl<Nf, S> ScannedBlock<Nf, S> {
/// 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<WalletTx<Nf, S>>,
sapling_nullifier_map: Vec<(TxId, u16, Vec<sapling::Nullifier>)>,
sapling_commitments: Vec<(sapling::Node, Retention<BlockHeight>)>,
orchard_nullifier_map: Vec<(TxId, u16, Vec<orchard::note::Nullifier>)>,
orchard_commitments: Vec<(orchard::note::NoteCommitment, Retention<BlockHeight>)>,
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<Nf, S> ScannedBlock<Nf, S> {
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<Nf, S>] {
&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<sapling::Nullifier>)] {
&self.sapling_nullifier_map
/// Returns the Sapling note commitment tree and nullifier data for the block.
pub fn sapling(&self) -> &ScannedBlockSapling {
&self.sapling
}
/// Returns the ordered list of Sapling note commitments to be added to the note commitment
/// tree.
pub fn sapling_commitments(&self) -> &[(sapling::Node, Retention<BlockHeight>)] {
&self.sapling_commitments
}
/// 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<orchard::note::Nullifier>)] {
&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<BlockHeight>)] {
&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<BlockHeight>)>,
Vec<(orchard::note::NoteCommitment, Retention<BlockHeight>)>,
) {
(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<Nf, S> ScannedBlock<Nf, S> {
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),
}
}
}

View File

@ -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,
},
);

View File

@ -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<u32> {
#[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::<LittleEndian>(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::<orchard::keys::SpendingKey>::from(
orchard::keys::SpendingKey::from_bytes(key),
)
.ok_or(DecodingError::KeyDataInvalid(Typecode::Orchard))?,
);
#[cfg(feature = "orchard")]
{
orchard = Some(
Option::<orchard::keys::SpendingKey>::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<legacy::AccountPubKey>,
sapling: Option<sapling::DiversifiableFullViewingKey>,
#[cfg(feature = "orchard")]
orchard: Option<orchard::keys::FullViewingKey>,
unknown: Vec<(u32, Vec<u8>)>,
}
@ -372,7 +394,7 @@ impl UnifiedFullViewingKey {
pub fn new(
#[cfg(feature = "transparent-inputs")] transparent: Option<legacy::AccountPubKey>,
sapling: Option<sapling::DiversifiableFullViewingKey>,
orchard: Option<orchard::keys::FullViewingKey>,
#[cfg(feature = "orchard")] orchard: Option<orchard::keys::FullViewingKey>,
) -> Option<UnifiedFullViewingKey> {
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<P: consensus::Parameters>(&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,19 +716,28 @@ mod tests {
#[cfg(feature = "transparent-inputs")]
transparent,
sapling,
#[cfg(feature = "orchard")]
orchard,
)
.unwrap();
let encoded = ufvk.encode(&MAIN_NETWORK);
// test encoded form against known values
// Test encoded form against known values; these test vectors contain Orchard receivers
// that will be treated as unknown if the `orchard` feature is not enabled.
let encoded_with_t = "uview1tg6rpjgju2s2j37gkgjq79qrh5lvzr6e0ed3n4sf4hu5qd35vmsh7avl80xa6mx7ryqce9hztwaqwrdthetpy4pc0kce25x453hwcmax02p80pg5savlg865sft9reat07c5vlactr6l2pxtlqtqunt2j9gmvr8spcuzf07af80h5qmut38h0gvcfa9k4rwujacwwca9vu8jev7wq6c725huv8qjmhss3hdj2vh8cfxhpqcm2qzc34msyrfxk5u6dqttt4vv2mr0aajreww5yufpk0gn4xkfm888467k7v6fmw7syqq6cceu078yw8xja502jxr0jgum43lhvpzmf7eu5dmnn6cr6f7p43yw8znzgxg598mllewnx076hljlvynhzwn5es94yrv65tdg3utuz2u3sras0wfcq4adxwdvlk387d22g3q98t5z74quw2fa4wed32escx8dwh4mw35t4jwf35xyfxnu83mk5s4kw2glkgsshmxk";
let _encoded_no_t = "uview12z384wdq76ceewlsu0esk7d97qnd23v2qnvhujxtcf2lsq8g4hwzpx44fwxssnm5tg8skyh4tnc8gydwxefnnm0hd0a6c6etmj0pp9jqkdsllkr70u8gpf7ndsfqcjlqn6dec3faumzqlqcmtjf8vp92h7kj38ph2786zx30hq2wru8ae3excdwc8w0z3t9fuw7mt7xy5sn6s4e45kwm0cjp70wytnensgdnev286t3vew3yuwt2hcz865y037k30e428dvgne37xvyeal2vu8yjnznphf9t2rw3gdp0hk5zwq00ws8f3l3j5n3qkqgsyzrwx4qzmgq0xwwk4vz2r6vtsykgz089jncvycmem3535zjwvvtvjw8v98y0d5ydwte575gjm7a7k";
#[cfg(feature = "transparent-inputs")]
assert_eq!(encoded, encoded_with_t);
#[cfg(not(feature = "transparent-inputs"))]
assert_eq!(encoded, _encoded_no_t);
// We test the full roundtrip only with the `orchard` feature enabled, because we will
// not generate the `orchard` part of the encoding if the UFVK does not have an Orchard
// part.
#[cfg(feature = "orchard")]
{
#[cfg(feature = "transparent-inputs")]
assert_eq!(encoded, encoded_with_t);
#[cfg(not(feature = "transparent-inputs"))]
assert_eq!(encoded, _encoded_no_t);
}
let decoded = UnifiedFullViewingKey::decode(&MAIN_NETWORK, &encoded).unwrap();
let reencoded = decoded.encode(&MAIN_NETWORK);
@ -705,6 +752,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 +764,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 +824,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());
}

View File

@ -35,7 +35,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

View File

@ -186,6 +186,7 @@ impl<A: sapling::bundle::Authorization> From<&sapling::bundle::SpendDescription<
}
}
#[cfg(feature = "orchard")]
impl<SpendAuth> From<&orchard::Action<SpendAuth>> for compact_formats::CompactOrchardAction {
fn from(action: &orchard::Action<SpendAuth>) -> compact_formats::CompactOrchardAction {
compact_formats::CompactOrchardAction {

View File

@ -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::<usize>()
.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::<usize>()
.try_into()
.expect("Sapling output count cannot exceed a u32");
},
|m| {
let orchard_action_count: u32 = block
.vtx
.iter()
.map(|tx| tx.actions.len())
.sum::<usize>()
.try_into()
.expect("Orchard action 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::<usize>()
.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,
)?;
// 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<WalletTx<K::Nf, K::Scope>> = 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::<Vec<_>>(),
@ -944,7 +962,8 @@ mod tests {
assert_eq!(
scanned_block
.sapling_commitments()
.sapling()
.commitments()
.iter()
.map(|(_, retention)| *retention)
.collect::<Vec<_>>(),
@ -997,7 +1016,8 @@ mod tests {
assert_eq!(
scanned_block
.sapling_commitments()
.sapling()
.commitments()
.iter()
.map(|(_, retention)| *retention)
.collect::<Vec<_>>(),

View File

@ -239,6 +239,7 @@ impl<N, S> WalletSaplingOutput<N, S> {
#[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.",
),

View File

@ -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()
}
}

View File

@ -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`

View File

@ -80,6 +80,10 @@ multicore = ["maybe-rayon/threads", "zcash_primitives/multicore"]
## client is configured for use with the Zcash testnet.
mainnet = []
## Enables support for storing data related to the sending and receiving of
## Orchard funds.
orchard = ["zcash_client_backend/orchard"]
## Exposes APIs that are useful for testing, such as `proptest` strategies.
test-dependencies = [
"incrementalmerkletree/test-dependencies",

View File

@ -444,8 +444,8 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
(
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(),
),
)
});
@ -466,8 +466,8 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
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() {
@ -496,7 +496,7 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
wdb.conn.0,
block.height(),
ShieldedProtocol::Sapling,
block.sapling_nullifier_map(),
block.sapling().nullifier_map(),
)?;
note_positions.extend(block.transactions().iter().flat_map(|wtx| {
@ -506,8 +506,8 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
}));
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.

View File

@ -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());

View File

@ -967,10 +967,11 @@ pub(crate) fn get_target_and_anchor_heights(
}
fn parse_block_metadata<P: consensus::Parameters>(
params: &P,
_params: &P,
row: (BlockHeight, Vec<u8>, Option<u32>, Vec<u8>, Option<u32>),
) -> Result<BlockMetadata, SqliteClientError> {
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<P: consensus::Parameters>(
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)
},

View File

@ -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,

View File

@ -201,36 +201,40 @@ impl Progress {
#[derive(Clone, Copy)]
pub enum BuildConfig {
Standard {
sapling_anchor: sapling::Anchor,
orchard_anchor: orchard::Anchor,
sapling_anchor: Option<sapling::Anchor>,
orchard_anchor: Option<orchard::Anchor>,
},
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(&params, 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(&params, 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