zcash_client_backend: Move `Recipient::EphemeralTransparent` behind the `transparent-inputs` feature flag.

This variant should not have been a part of the public API unless
`tranpsarent-inputs` was enabled, as it's necessary for the wallet to be
able to spend a transparent input in order for a ZIP 320 transaction to
be properly constructed and authorized.

In addition, this simplifies the `Recipient` API by removing its type
parameters in favor of concrete types, made possible by using a
separate type for the build process.
This commit is contained in:
Kris Nuttycombe 2024-12-23 17:09:54 -07:00
parent 736bfd555b
commit 5290d13942
5 changed files with 262 additions and 241 deletions

View File

@ -9,6 +9,18 @@ and this library adheres to Rust's notion of
### Changed ### Changed
- Migrated to `nonempty 0.11` - Migrated to `nonempty 0.11`
- `zcash_client_backend::wallet::Recipient` has changed:
- The `Recipient::External` variant is now a structured variant.
- The `Recipient::EphemeralTransparent` variant is now only available if
`zcash_client_backend` is built using the `transparent-inputs` feature flag.
- The `N` and `O` type pararameters to this type have been replaced by
concrete uses of `Box<Note>` and `Outpoint` instead. The
`map_internal_account_note` and `map_ephemeral_transparent_outpoint` and
`internal_account_note_transpose_option` methods have consequently been
removed.
- `zcash_client_backend::data_api::WalletRead::get_known_ephemeral_addresses`
now takes a `Range<zcash_transparent::keys::NonHardenedChildIndex>` as its
argument instead of a `Range<u32>`
### Deprecated ### Deprecated
- `zcash_client_backend::address` (use `zcash_keys::address` instead) - `zcash_client_backend::address` (use `zcash_keys::address` instead)

View File

@ -68,7 +68,6 @@ use std::{
use incrementalmerkletree::{frontier::Frontier, Retention}; use incrementalmerkletree::{frontier::Frontier, Retention};
use shardtree::{error::ShardTreeError, store::ShardStore, ShardTree}; use shardtree::{error::ShardTreeError, store::ShardStore, ShardTree};
use ::transparent::bundle::OutPoint;
use zcash_keys::{ use zcash_keys::{
address::UnifiedAddress, address::UnifiedAddress,
keys::{ keys::{
@ -99,7 +98,8 @@ use crate::{
#[cfg(feature = "transparent-inputs")] #[cfg(feature = "transparent-inputs")]
use { use {
crate::wallet::TransparentAddressMetadata, ::transparent::address::TransparentAddress, crate::wallet::TransparentAddressMetadata,
::transparent::{address::TransparentAddress, bundle::OutPoint},
std::ops::Range, std::ops::Range,
}; };
@ -1966,7 +1966,7 @@ impl<'a, AccountId> SentTransaction<'a, AccountId> {
/// This type is capable of representing both shielded and transparent outputs. /// This type is capable of representing both shielded and transparent outputs.
pub struct SentTransactionOutput<AccountId> { pub struct SentTransactionOutput<AccountId> {
output_index: usize, output_index: usize,
recipient: Recipient<AccountId, Note, OutPoint>, recipient: Recipient<AccountId>,
value: Zatoshis, value: Zatoshis,
memo: Option<MemoBytes>, memo: Option<MemoBytes>,
} }
@ -1983,7 +1983,7 @@ impl<AccountId> SentTransactionOutput<AccountId> {
/// * `memo` - the memo that was sent with this output /// * `memo` - the memo that was sent with this output
pub fn from_parts( pub fn from_parts(
output_index: usize, output_index: usize,
recipient: Recipient<AccountId, Note, OutPoint>, recipient: Recipient<AccountId>,
value: Zatoshis, value: Zatoshis,
memo: Option<MemoBytes>, memo: Option<MemoBytes>,
) -> Self { ) -> Self {
@ -2006,7 +2006,7 @@ impl<AccountId> SentTransactionOutput<AccountId> {
} }
/// Returns the recipient address of the transaction, or the account id and /// Returns the recipient address of the transaction, or the account id and
/// resulting note/outpoint for wallet-internal outputs. /// resulting note/outpoint for wallet-internal outputs.
pub fn recipient(&self) -> &Recipient<AccountId, Note, OutPoint> { pub fn recipient(&self) -> &Recipient<AccountId> {
&self.recipient &self.recipient
} }
/// Returns the value of the newly created output. /// Returns the value of the newly created output.

View File

@ -35,18 +35,9 @@ to a wallet-internal shielded address, as described in [ZIP 316](https://zips.z.
use nonempty::NonEmpty; use nonempty::NonEmpty;
use rand_core::OsRng; use rand_core::OsRng;
use sapling::{
note_encryption::{try_sapling_note_decryption, PreparedIncomingViewingKey},
prover::{OutputProver, SpendProver},
};
use shardtree::error::{QueryError, ShardTreeError};
use std::num::NonZeroU32; use std::num::NonZeroU32;
use zcash_keys::{
address::Address, use shardtree::error::{QueryError, ShardTreeError};
keys::{UnifiedFullViewingKey, UnifiedSpendingKey},
};
use zcash_protocol::{PoolType, ShieldedProtocol};
use zip321::Payment;
use super::InputSource; use super::InputSource;
use crate::{ use crate::{
@ -61,10 +52,18 @@ use crate::{
proposal::{Proposal, ProposalError, Step, StepOutputIndex}, proposal::{Proposal, ProposalError, Step, StepOutputIndex},
wallet::{Note, OvkPolicy, Recipient}, wallet::{Note, OvkPolicy, Recipient},
}; };
use ::sapling::{
note_encryption::{try_sapling_note_decryption, PreparedIncomingViewingKey},
prover::{OutputProver, SpendProver},
};
use ::transparent::{ use ::transparent::{
address::TransparentAddress, builder::TransparentSigningSet, bundle::OutPoint, address::TransparentAddress, builder::TransparentSigningSet, bundle::OutPoint,
}; };
use zcash_address::ZcashAddress;
use zcash_keys::{
address::Address,
keys::{UnifiedFullViewingKey, UnifiedSpendingKey},
};
use zcash_primitives::transaction::{ use zcash_primitives::transaction::{
builder::{BuildConfig, BuildResult, Builder}, builder::{BuildConfig, BuildResult, Builder},
components::sapling::zip212_enforcement, components::sapling::zip212_enforcement,
@ -75,8 +74,10 @@ use zcash_protocol::{
consensus::{self, BlockHeight, NetworkUpgrade}, consensus::{self, BlockHeight, NetworkUpgrade},
memo::MemoBytes, memo::MemoBytes,
value::Zatoshis, value::Zatoshis,
PoolType, ShieldedProtocol,
}; };
use zip32::Scope; use zip32::Scope;
use zip321::Payment;
#[cfg(feature = "transparent-inputs")] #[cfg(feature = "transparent-inputs")]
use { use {
@ -100,7 +101,6 @@ use {
}, },
sapling::note_encryption::SaplingDomain, sapling::note_encryption::SaplingDomain,
serde::{Deserialize, Serialize}, serde::{Deserialize, Serialize},
zcash_address::ZcashAddress,
zcash_note_encryption::try_output_recovery_with_pkd_esk, zcash_note_encryption::try_output_recovery_with_pkd_esk,
zcash_protocol::{ zcash_protocol::{
consensus::NetworkConstants, consensus::NetworkConstants,
@ -133,25 +133,32 @@ struct ProposalInfo<AccountId> {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
enum PcztRecipient<AccountId> { enum PcztRecipient<AccountId> {
External, External,
EphemeralTransparent { receiving_account: AccountId }, #[cfg(feature = "transparent-inputs")]
InternalAccount { receiving_account: AccountId }, EphemeralTransparent {
receiving_account: AccountId,
},
InternalAccount {
receiving_account: AccountId,
},
} }
#[cfg(feature = "pczt")] #[cfg(feature = "pczt")]
impl<AccountId: Copy> PcztRecipient<AccountId> { impl<AccountId: Copy> PcztRecipient<AccountId> {
fn from_recipient<N, O>(recipient: Recipient<AccountId, N, O>) -> (Self, Option<ZcashAddress>) { fn from_recipient(recipient: BuildRecipient<AccountId>) -> (Self, Option<ZcashAddress>) {
match recipient { match recipient {
Recipient::External(addr, _) => (PcztRecipient::External, Some(addr)), BuildRecipient::External {
Recipient::EphemeralTransparent { recipient_address, ..
} => (PcztRecipient::External, Some(recipient_address)),
#[cfg(feature = "transparent-inputs")]
BuildRecipient::EphemeralTransparent {
receiving_account, .. receiving_account, ..
} => ( } => (
PcztRecipient::EphemeralTransparent { receiving_account }, PcztRecipient::EphemeralTransparent { receiving_account },
None, None,
), ),
Recipient::InternalAccount { BuildRecipient::InternalAccount {
receiving_account, receiving_account,
external_address, external_address,
..
} => ( } => (
PcztRecipient::InternalAccount { receiving_account }, PcztRecipient::InternalAccount { receiving_account },
external_address, external_address,
@ -536,6 +543,72 @@ where
Ok(NonEmpty::from_vec(txids).expect("proposal.steps is NonEmpty")) Ok(NonEmpty::from_vec(txids).expect("proposal.steps is NonEmpty"))
} }
#[derive(Debug, Clone)]
enum BuildRecipient<AccountId> {
External {
recipient_address: ZcashAddress,
output_pool: PoolType,
},
#[cfg(feature = "transparent-inputs")]
EphemeralTransparent {
receiving_account: AccountId,
ephemeral_address: TransparentAddress,
},
InternalAccount {
receiving_account: AccountId,
external_address: Option<ZcashAddress>,
},
}
impl<AccountId> BuildRecipient<AccountId> {
fn into_recipient_with_note(self, note: impl FnOnce() -> Note) -> Recipient<AccountId> {
match self {
BuildRecipient::External {
recipient_address,
output_pool,
} => Recipient::External {
recipient_address,
output_pool,
},
#[cfg(feature = "transparent-inputs")]
BuildRecipient::EphemeralTransparent { .. } => unreachable!(),
BuildRecipient::InternalAccount {
receiving_account,
external_address,
} => Recipient::InternalAccount {
receiving_account,
external_address,
note: Box::new(note()),
},
}
}
fn into_recipient_with_outpoint(
self,
#[cfg(feature = "transparent-inputs")] outpoint: OutPoint,
) -> Recipient<AccountId> {
match self {
BuildRecipient::External {
recipient_address,
output_pool,
} => Recipient::External {
recipient_address,
output_pool,
},
#[cfg(feature = "transparent-inputs")]
BuildRecipient::EphemeralTransparent {
receiving_account,
ephemeral_address,
} => Recipient::EphemeralTransparent {
receiving_account,
ephemeral_address,
outpoint,
},
BuildRecipient::InternalAccount { .. } => unreachable!(),
}
}
}
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
struct BuildState<'a, P, AccountId> { struct BuildState<'a, P, AccountId> {
#[cfg(feature = "transparent-inputs")] #[cfg(feature = "transparent-inputs")]
@ -544,18 +617,10 @@ struct BuildState<'a, P, AccountId> {
#[cfg(feature = "transparent-inputs")] #[cfg(feature = "transparent-inputs")]
transparent_input_addresses: HashMap<TransparentAddress, TransparentAddressMetadata>, transparent_input_addresses: HashMap<TransparentAddress, TransparentAddressMetadata>,
#[cfg(feature = "orchard")] #[cfg(feature = "orchard")]
orchard_output_meta: Vec<( orchard_output_meta: Vec<(BuildRecipient<AccountId>, Zatoshis, Option<MemoBytes>)>,
Recipient<AccountId, PoolType, OutPoint>, sapling_output_meta: Vec<(BuildRecipient<AccountId>, Zatoshis, Option<MemoBytes>)>,
Zatoshis,
Option<MemoBytes>,
)>,
sapling_output_meta: Vec<(
Recipient<AccountId, PoolType, OutPoint>,
Zatoshis,
Option<MemoBytes>,
)>,
transparent_output_meta: Vec<( transparent_output_meta: Vec<(
Recipient<AccountId, Note, ()>, BuildRecipient<AccountId>,
TransparentAddress, TransparentAddress,
Zatoshis, Zatoshis,
StepOutputIndex, StepOutputIndex,
@ -884,12 +949,10 @@ where
}; };
#[cfg(feature = "orchard")] #[cfg(feature = "orchard")]
let mut orchard_output_meta: Vec<(Recipient<_, PoolType, _>, Zatoshis, Option<MemoBytes>)> = let mut orchard_output_meta: Vec<(BuildRecipient<_>, Zatoshis, Option<MemoBytes>)> = vec![];
vec![]; let mut sapling_output_meta: Vec<(BuildRecipient<_>, Zatoshis, Option<MemoBytes>)> = vec![];
let mut sapling_output_meta: Vec<(Recipient<_, PoolType, _>, Zatoshis, Option<MemoBytes>)> =
vec![];
let mut transparent_output_meta: Vec<( let mut transparent_output_meta: Vec<(
Recipient<_, _, ()>, BuildRecipient<_>,
TransparentAddress, TransparentAddress,
Zatoshis, Zatoshis,
StepOutputIndex, StepOutputIndex,
@ -915,7 +978,10 @@ where
let memo = payment.memo().map_or_else(MemoBytes::empty, |m| m.clone()); let memo = payment.memo().map_or_else(MemoBytes::empty, |m| m.clone());
builder.add_sapling_output(sapling_external_ovk, to, payment.amount(), memo.clone())?; builder.add_sapling_output(sapling_external_ovk, to, payment.amount(), memo.clone())?;
sapling_output_meta.push(( sapling_output_meta.push((
Recipient::External(recipient_address.clone(), PoolType::SAPLING), BuildRecipient::External {
recipient_address: recipient_address.clone(),
output_pool: PoolType::SAPLING,
},
payment.amount(), payment.amount(),
Some(memo), Some(memo),
)); ));
@ -936,7 +1002,10 @@ where
memo.clone(), memo.clone(),
)?; )?;
orchard_output_meta.push(( orchard_output_meta.push((
Recipient::External(recipient_address.clone(), PoolType::ORCHARD), BuildRecipient::External {
recipient_address: recipient_address.clone(),
output_pool: PoolType::ORCHARD,
},
payment.amount(), payment.amount(),
Some(memo), Some(memo),
)); ));
@ -962,7 +1031,10 @@ where
} }
builder.add_transparent_output(&to, payment.amount())?; builder.add_transparent_output(&to, payment.amount())?;
transparent_output_meta.push(( transparent_output_meta.push((
Recipient::External(recipient_address.clone(), PoolType::TRANSPARENT), BuildRecipient::External {
recipient_address: recipient_address.clone(),
output_pool: PoolType::TRANSPARENT,
},
to, to,
payment.amount(), payment.amount(),
StepOutputIndex::Payment(payment_index), StepOutputIndex::Payment(payment_index),
@ -1031,10 +1103,9 @@ where
memo.clone(), memo.clone(),
)?; )?;
sapling_output_meta.push(( sapling_output_meta.push((
Recipient::InternalAccount { BuildRecipient::InternalAccount {
receiving_account: account_id, receiving_account: account_id,
external_address: None, external_address: None,
note: output_pool,
}, },
change_value.value(), change_value.value(),
Some(memo), Some(memo),
@ -1055,10 +1126,9 @@ where
memo.clone(), memo.clone(),
)?; )?;
orchard_output_meta.push(( orchard_output_meta.push((
Recipient::InternalAccount { BuildRecipient::InternalAccount {
receiving_account: account_id, receiving_account: account_id,
external_address: None, external_address: None,
note: output_pool,
}, },
change_value.value(), change_value.value(),
Some(memo), Some(memo),
@ -1100,10 +1170,9 @@ where
// if a later step does not consume it. // if a later step does not consume it.
builder.add_transparent_output(&ephemeral_address, change_value.value())?; builder.add_transparent_output(&ephemeral_address, change_value.value())?;
transparent_output_meta.push(( transparent_output_meta.push((
Recipient::EphemeralTransparent { BuildRecipient::EphemeralTransparent {
receiving_account: account_id, receiving_account: account_id,
ephemeral_address, ephemeral_address,
outpoint_metadata: (),
}, },
ephemeral_address, ephemeral_address,
change_value.value(), change_value.value(),
@ -1208,20 +1277,17 @@ where
.output_action_index(i) .output_action_index(i)
.expect("An action should exist in the transaction for each Orchard output."); .expect("An action should exist in the transaction for each Orchard output.");
let recipient = recipient let recipient = recipient.into_recipient_with_note(|| {
.map_internal_account_note(|pool| { build_result
assert!(pool == PoolType::ORCHARD); .transaction()
build_result .orchard_bundle()
.transaction() .and_then(|bundle| {
.orchard_bundle() bundle
.and_then(|bundle| { .decrypt_output_with_key(output_index, &orchard_internal_ivk)
bundle .map(|(note, _, _)| Note::Orchard(note))
.decrypt_output_with_key(output_index, &orchard_internal_ivk) })
.map(|(note, _, _)| Note::Orchard(note)) .expect("Wallet-internal outputs must be decryptable with the wallet's IVK")
}) });
})
.internal_account_note_transpose_option()
.expect("Wallet-internal outputs must be decryptable with the wallet's IVK");
SentTransactionOutput::from_parts(output_index, recipient, value, memo) SentTransactionOutput::from_parts(output_index, recipient, value, memo)
}, },
@ -1237,23 +1303,20 @@ where
.output_index(i) .output_index(i)
.expect("An output should exist in the transaction for each Sapling payment."); .expect("An output should exist in the transaction for each Sapling payment.");
let recipient = recipient let recipient = recipient.into_recipient_with_note(|| {
.map_internal_account_note(|pool| { build_result
assert!(pool == PoolType::SAPLING); .transaction()
build_result .sapling_bundle()
.transaction() .and_then(|bundle| {
.sapling_bundle() try_sapling_note_decryption(
.and_then(|bundle| { &sapling_internal_ivk,
try_sapling_note_decryption( &bundle.shielded_outputs()[output_index],
&sapling_internal_ivk, zip212_enforcement(params, min_target_height),
&bundle.shielded_outputs()[output_index], )
zip212_enforcement(params, min_target_height), .map(|(note, _, _)| Note::Sapling(note))
) })
.map(|(note, _, _)| Note::Sapling(note)) .expect("Wallet-internal outputs must be decryptable with the wallet's IVK")
}) });
})
.internal_account_note_transpose_option()
.expect("Wallet-internal outputs must be decryptable with the wallet's IVK");
SentTransactionOutput::from_parts(output_index, recipient, value, memo) SentTransactionOutput::from_parts(output_index, recipient, value, memo)
}, },
@ -1280,7 +1343,11 @@ where
// would not usefully improve privacy. // would not usefully improve privacy.
let outpoint = OutPoint::new(txid, n as u32); let outpoint = OutPoint::new(txid, n as u32);
let recipient = recipient.map_ephemeral_transparent_outpoint(|()| outpoint.clone()); let recipient = recipient.into_recipient_with_outpoint(
#[cfg(feature = "transparent-inputs")]
outpoint.clone(),
);
#[cfg(feature = "transparent-inputs")] #[cfg(feature = "transparent-inputs")]
unused_transparent_outputs.insert( unused_transparent_outputs.insert(
StepOutput::new(build_state.step_index, step_output_index), StepOutput::new(build_state.step_index, step_output_index),
@ -1819,12 +1886,14 @@ where
let note_value = Zatoshis::try_from(note_value(&note))?; let note_value = Zatoshis::try_from(note_value(&note))?;
let recipient = match (pczt_recipient, external_address) { let recipient = match (pczt_recipient, external_address) {
(PcztRecipient::External, Some(addr)) => { (PcztRecipient::External, Some(addr)) => Ok(Recipient::External {
Ok(Recipient::External(addr, PoolType::Shielded(output_pool))) recipient_address: addr,
} output_pool: PoolType::Shielded(output_pool),
}),
(PcztRecipient::External, None) => Err(PcztError::Invalid( (PcztRecipient::External, None) => Err(PcztError::Invalid(
"external recipient needs to have its user_address field set".into(), "external recipient needs to have its user_address field set".into(),
)), )),
#[cfg(feature = "transparent-inputs")]
(PcztRecipient::EphemeralTransparent { .. }, _) => Err(PcztError::Invalid( (PcztRecipient::EphemeralTransparent { .. }, _) => Err(PcztError::Invalid(
"shielded output cannot be EphemeralTransparent".into(), "shielded output cannot be EphemeralTransparent".into(),
)), )),
@ -1832,7 +1901,7 @@ where
Ok(Recipient::InternalAccount { Ok(Recipient::InternalAccount {
receiving_account, receiving_account,
external_address, external_address,
note: wallet_note(note), note: Box::new(wallet_note(note)),
}) })
} }
}?; }?;
@ -1927,11 +1996,15 @@ where
let recipient = match (pczt_recipient, external_address) { let recipient = match (pczt_recipient, external_address) {
(PcztRecipient::External, Some(addr)) => { (PcztRecipient::External, Some(addr)) => {
Ok(Recipient::External(addr, PoolType::Transparent)) Ok(Recipient::External {
recipient_address: addr,
output_pool: PoolType::Transparent,
})
} }
(PcztRecipient::External, None) => Err(PcztError::Invalid( (PcztRecipient::External, None) => Err(PcztError::Invalid(
"external recipient needs to have its user_address field set".into(), "external recipient needs to have its user_address field set".into(),
)), )),
#[cfg(feature = "transparent-inputs")]
(PcztRecipient::EphemeralTransparent { receiving_account }, _) => output (PcztRecipient::EphemeralTransparent { receiving_account }, _) => output
.recipient_address() .recipient_address()
.ok_or(PcztError::Invalid( .ok_or(PcztError::Invalid(
@ -1941,7 +2014,7 @@ where
.map(|ephemeral_address| Recipient::EphemeralTransparent { .map(|ephemeral_address| Recipient::EphemeralTransparent {
receiving_account, receiving_account,
ephemeral_address, ephemeral_address,
outpoint_metadata: outpoint, outpoint,
}), }),
( (
PcztRecipient::InternalAccount { PcztRecipient::InternalAccount {

View File

@ -1,11 +1,12 @@
//! Structs representing transaction data scanned from the block chain by a wallet or //! Structs representing transaction data scanned from the block chain by a wallet or
//! light client. //! light client.
use incrementalmerkletree::Position;
use ::transparent::{ use ::transparent::{
address::TransparentAddress, address::TransparentAddress,
bundle::{OutPoint, TxOut}, bundle::{OutPoint, TxOut},
}; };
use incrementalmerkletree::Position;
use zcash_address::ZcashAddress; use zcash_address::ZcashAddress;
use zcash_note_encryption::EphemeralKeyBytes; use zcash_note_encryption::EphemeralKeyBytes;
use zcash_primitives::transaction::{fees::transparent as transparent_fees, TxId}; use zcash_primitives::transaction::{fees::transparent as transparent_fees, TxId};
@ -60,111 +61,31 @@ impl NoteId {
} }
/// A type that represents the recipient of a transaction output: /// A type that represents the recipient of a transaction output:
///
/// * a recipient address; /// * a recipient address;
/// * for external unified addresses, the pool to which the payment is sent; /// * for external unified addresses, the pool to which the payment is sent;
/// * for ephemeral transparent addresses, the internal account ID and metadata about the outpoint;
/// * for wallet-internal outputs, the internal account ID and metadata about the note. /// * for wallet-internal outputs, the internal account ID and metadata about the note.
/// * if the `transparent-inputs` feature is enabled, for ephemeral transparent outputs, the
/// internal account ID and metadata about the outpoint;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Recipient<AccountId, N, O> { pub enum Recipient<AccountId> {
External(ZcashAddress, PoolType), External {
recipient_address: ZcashAddress,
output_pool: PoolType,
},
#[cfg(feature = "transparent-inputs")]
EphemeralTransparent { EphemeralTransparent {
receiving_account: AccountId, receiving_account: AccountId,
ephemeral_address: TransparentAddress, ephemeral_address: TransparentAddress,
outpoint_metadata: O, outpoint: OutPoint,
}, },
InternalAccount { InternalAccount {
receiving_account: AccountId, receiving_account: AccountId,
external_address: Option<ZcashAddress>, external_address: Option<ZcashAddress>,
note: N, note: Box<Note>,
}, },
} }
impl<AccountId, N, O> Recipient<AccountId, N, O> {
/// Return a copy of this `Recipient` with `f` applied to the note metadata, if any.
pub fn map_internal_account_note<B, F: FnOnce(N) -> B>(
self,
f: F,
) -> Recipient<AccountId, B, O> {
match self {
Recipient::External(addr, pool) => Recipient::External(addr, pool),
Recipient::EphemeralTransparent {
receiving_account,
ephemeral_address,
outpoint_metadata,
} => Recipient::EphemeralTransparent {
receiving_account,
ephemeral_address,
outpoint_metadata,
},
Recipient::InternalAccount {
receiving_account,
external_address,
note,
} => Recipient::InternalAccount {
receiving_account,
external_address,
note: f(note),
},
}
}
/// Return a copy of this `Recipient` with `f` applied to the output metadata, if any.
pub fn map_ephemeral_transparent_outpoint<B, F: FnOnce(O) -> B>(
self,
f: F,
) -> Recipient<AccountId, N, B> {
match self {
Recipient::External(addr, pool) => Recipient::External(addr, pool),
Recipient::EphemeralTransparent {
receiving_account,
ephemeral_address,
outpoint_metadata,
} => Recipient::EphemeralTransparent {
receiving_account,
ephemeral_address,
outpoint_metadata: f(outpoint_metadata),
},
Recipient::InternalAccount {
receiving_account,
external_address,
note,
} => Recipient::InternalAccount {
receiving_account,
external_address,
note,
},
}
}
}
impl<AccountId, N, O> Recipient<AccountId, Option<N>, O> {
/// Return a copy of this `Recipient` with optional note metadata transposed to
/// an optional result.
pub fn internal_account_note_transpose_option(self) -> Option<Recipient<AccountId, N, O>> {
match self {
Recipient::External(addr, pool) => Some(Recipient::External(addr, pool)),
Recipient::EphemeralTransparent {
receiving_account,
ephemeral_address,
outpoint_metadata,
} => Some(Recipient::EphemeralTransparent {
receiving_account,
ephemeral_address,
outpoint_metadata,
}),
Recipient::InternalAccount {
receiving_account,
external_address,
note,
} => note.map(|n0| Recipient::InternalAccount {
receiving_account,
external_address,
note: n0,
}),
}
}
}
/// The shielded subset of a [`Transaction`]'s data that is relevant to a particular wallet. /// The shielded subset of a [`Transaction`]'s data that is relevant to a particular wallet.
/// ///
/// [`Transaction`]: zcash_primitives::transaction::Transaction /// [`Transaction`]: zcash_primitives::transaction::Transaction

View File

@ -84,7 +84,6 @@ use std::ops::RangeInclusive;
use tracing::{debug, warn}; use tracing::{debug, warn};
use ::transparent::bundle::OutPoint;
use zcash_address::ZcashAddress; use zcash_address::ZcashAddress;
use zcash_client_backend::{ use zcash_client_backend::{
data_api::{ data_api::{
@ -114,8 +113,9 @@ use zcash_protocol::{
value::{ZatBalance, Zatoshis}, value::{ZatBalance, Zatoshis},
PoolType, ShieldedProtocol, PoolType, ShieldedProtocol,
}; };
use zip32::{self, DiversifierIndex, Scope}; use zip32::{DiversifierIndex, Scope};
use self::scanning::{parse_priority_code, priority_code, replace_queue_entries};
use crate::{ use crate::{
error::SqliteClientError, error::SqliteClientError,
wallet::commitment_tree::{get_max_checkpointed_height, SqliteShardStore}, wallet::commitment_tree::{get_max_checkpointed_height, SqliteShardStore},
@ -125,9 +125,7 @@ use crate::{
use crate::{AccountUuid, TxRef, VERIFY_LOOKAHEAD}; use crate::{AccountUuid, TxRef, VERIFY_LOOKAHEAD};
#[cfg(feature = "transparent-inputs")] #[cfg(feature = "transparent-inputs")]
use ::transparent::bundle::TxOut; use ::transparent::bundle::{OutPoint, TxOut};
use self::scanning::{parse_priority_code, priority_code, replace_queue_entries};
#[cfg(feature = "orchard")] #[cfg(feature = "orchard")]
use {crate::ORCHARD_TABLES_PREFIX, zcash_client_backend::data_api::ORCHARD_SHARD_HEIGHT}; use {crate::ORCHARD_TABLES_PREFIX, zcash_client_backend::data_api::ORCHARD_SHARD_HEIGHT};
@ -2269,55 +2267,53 @@ pub(crate) fn store_transaction_to_be_sent<P: consensus::Parameters>(
match output.recipient() { match output.recipient() {
Recipient::InternalAccount { Recipient::InternalAccount {
receiving_account, receiving_account,
note: Note::Sapling(note), note,
.. ..
} => { } => match note.as_ref() {
sapling::put_received_note( Note::Sapling(note) => {
wdb.conn.0, sapling::put_received_note(
&DecryptedOutput::new( wdb.conn.0,
output.output_index(), &DecryptedOutput::new(
note.clone(), output.output_index(),
*receiving_account, note.clone(),
output *receiving_account,
.memo() output
.map_or_else(MemoBytes::empty, |memo| memo.clone()), .memo()
TransferType::WalletInternal, .map_or_else(MemoBytes::empty, |memo| memo.clone()),
), TransferType::WalletInternal,
tx_ref, ),
None, tx_ref,
)?; None,
} )?;
#[cfg(feature = "orchard")] }
Recipient::InternalAccount { #[cfg(feature = "orchard")]
receiving_account, Note::Orchard(note) => {
note: Note::Orchard(note), orchard::put_received_note(
.. wdb.conn.0,
} => { &DecryptedOutput::new(
orchard::put_received_note( output.output_index(),
wdb.conn.0, *note,
&DecryptedOutput::new( *receiving_account,
output.output_index(), output
*note, .memo()
*receiving_account, .map_or_else(MemoBytes::empty, |memo| memo.clone()),
output TransferType::WalletInternal,
.memo() ),
.map_or_else(MemoBytes::empty, |memo| memo.clone()), tx_ref,
TransferType::WalletInternal, None,
), )?;
tx_ref, }
None, },
)?;
}
#[cfg(feature = "transparent-inputs")] #[cfg(feature = "transparent-inputs")]
Recipient::EphemeralTransparent { Recipient::EphemeralTransparent {
receiving_account, receiving_account,
ephemeral_address, ephemeral_address,
outpoint_metadata, outpoint,
} => { } => {
transparent::put_transparent_output( transparent::put_transparent_output(
wdb.conn.0, wdb.conn.0,
&wdb.params, &wdb.params,
outpoint_metadata, outpoint,
&TxOut { &TxOut {
value: output.value(), value: output.value(),
script_pubkey: ephemeral_address.script(), script_pubkey: ephemeral_address.script(),
@ -2733,11 +2729,14 @@ pub(crate) fn store_decrypted_tx<P: consensus::Parameters>(
TransferType::Outgoing => { TransferType::Outgoing => {
let recipient = { let recipient = {
let receiver = Receiver::Sapling(output.note().recipient()); let receiver = Receiver::Sapling(output.note().recipient());
let wallet_address = let recipient_address =
select_receiving_address(params, conn, *output.account(), &receiver)? select_receiving_address(params, conn, *output.account(), &receiver)?
.unwrap_or_else(|| receiver.to_zcash_address(params.network_type())); .unwrap_or_else(|| receiver.to_zcash_address(params.network_type()));
Recipient::External(wallet_address, PoolType::SAPLING) Recipient::External {
recipient_address,
output_pool: PoolType::SAPLING,
}
}; };
put_sent_output( put_sent_output(
@ -2757,7 +2756,7 @@ pub(crate) fn store_decrypted_tx<P: consensus::Parameters>(
let recipient = Recipient::InternalAccount { let recipient = Recipient::InternalAccount {
receiving_account: *output.account(), receiving_account: *output.account(),
external_address: None, external_address: None,
note: Note::Sapling(output.note().clone()), note: Box::new(Note::Sapling(output.note().clone())),
}; };
put_sent_output( put_sent_output(
@ -2791,7 +2790,7 @@ pub(crate) fn store_decrypted_tx<P: consensus::Parameters>(
}), }),
) )
}, },
note: Note::Sapling(output.note().clone()), note: Box::new(Note::Sapling(output.note().clone())),
}; };
put_sent_output( put_sent_output(
@ -2819,11 +2818,14 @@ pub(crate) fn store_decrypted_tx<P: consensus::Parameters>(
TransferType::Outgoing => { TransferType::Outgoing => {
let recipient = { let recipient = {
let receiver = Receiver::Orchard(output.note().recipient()); let receiver = Receiver::Orchard(output.note().recipient());
let wallet_address = let recipient_address =
select_receiving_address(params, conn, *output.account(), &receiver)? select_receiving_address(params, conn, *output.account(), &receiver)?
.unwrap_or_else(|| receiver.to_zcash_address(params.network_type())); .unwrap_or_else(|| receiver.to_zcash_address(params.network_type()));
Recipient::External(wallet_address, PoolType::ORCHARD) Recipient::External {
recipient_address,
output_pool: PoolType::ORCHARD,
}
}; };
put_sent_output( put_sent_output(
@ -2843,7 +2845,7 @@ pub(crate) fn store_decrypted_tx<P: consensus::Parameters>(
let recipient = Recipient::InternalAccount { let recipient = Recipient::InternalAccount {
receiving_account: *output.account(), receiving_account: *output.account(),
external_address: None, external_address: None,
note: Note::Orchard(*output.note()), note: Box::new(Note::Orchard(*output.note())),
}; };
put_sent_output( put_sent_output(
@ -2878,7 +2880,7 @@ pub(crate) fn store_decrypted_tx<P: consensus::Parameters>(
}), }),
) )
}, },
note: Note::Orchard(*output.note()), note: Box::new(Note::Orchard(*output.note())),
}; };
put_sent_output( put_sent_output(
@ -2989,14 +2991,17 @@ pub(crate) fn store_decrypted_tx<P: consensus::Parameters>(
let receiver = Receiver::Transparent(address); let receiver = Receiver::Transparent(address);
#[cfg(feature = "transparent-inputs")] #[cfg(feature = "transparent-inputs")]
let recipient_addr = let recipient_address =
select_receiving_address(params, conn, account_uuid, &receiver)? select_receiving_address(params, conn, account_uuid, &receiver)?
.unwrap_or_else(|| receiver.to_zcash_address(params.network_type())); .unwrap_or_else(|| receiver.to_zcash_address(params.network_type()));
#[cfg(not(feature = "transparent-inputs"))] #[cfg(not(feature = "transparent-inputs"))]
let recipient_addr = receiver.to_zcash_address(params.network_type()); let recipient_address = receiver.to_zcash_address(params.network_type());
let recipient = Recipient::External(recipient_addr, PoolType::TRANSPARENT); let recipient = Recipient::External {
recipient_address,
output_pool: PoolType::TRANSPARENT,
};
put_sent_output( put_sent_output(
conn, conn,
@ -3295,13 +3300,23 @@ pub(crate) fn notify_tx_retrieved(
// and `put_sent_output` // and `put_sent_output`
fn recipient_params<P: consensus::Parameters>( fn recipient_params<P: consensus::Parameters>(
conn: &Connection, conn: &Connection,
params: &P, _params: &P,
from: AccountUuid, from: AccountUuid,
to: &Recipient<AccountUuid, Note, OutPoint>, to: &Recipient<AccountUuid>,
) -> Result<(AccountRef, Option<String>, Option<AccountRef>, PoolType), SqliteClientError> { ) -> Result<(AccountRef, Option<String>, Option<AccountRef>, PoolType), SqliteClientError> {
let from_account_id = get_account_ref(conn, from)?; let from_account_id = get_account_ref(conn, from)?;
match to { match to {
Recipient::External(addr, pool) => Ok((from_account_id, Some(addr.encode()), None, *pool)), Recipient::External {
recipient_address,
output_pool,
..
} => Ok((
from_account_id,
Some(recipient_address.encode()),
None,
*output_pool,
)),
#[cfg(feature = "transparent-inputs")]
Recipient::EphemeralTransparent { Recipient::EphemeralTransparent {
receiving_account, receiving_account,
ephemeral_address, ephemeral_address,
@ -3310,7 +3325,7 @@ fn recipient_params<P: consensus::Parameters>(
let to_account = get_account_ref(conn, *receiving_account)?; let to_account = get_account_ref(conn, *receiving_account)?;
Ok(( Ok((
from_account_id, from_account_id,
Some(ephemeral_address.encode(params)), Some(ephemeral_address.encode(_params)),
Some(to_account), Some(to_account),
PoolType::TRANSPARENT, PoolType::TRANSPARENT,
)) ))
@ -3414,7 +3429,7 @@ pub(crate) fn put_sent_output<P: consensus::Parameters>(
from_account_uuid: AccountUuid, from_account_uuid: AccountUuid,
tx_ref: TxRef, tx_ref: TxRef,
output_index: usize, output_index: usize,
recipient: &Recipient<AccountUuid, Note, OutPoint>, recipient: &Recipient<AccountUuid>,
value: Zatoshis, value: Zatoshis,
memo: Option<&MemoBytes>, memo: Option<&MemoBytes>,
) -> Result<(), SqliteClientError> { ) -> Result<(), SqliteClientError> {