zcash_client_backend: Add support for creation of Orchard outputs.
This commit is contained in:
parent
f27f601b7d
commit
daf88a12e5
|
@ -65,6 +65,7 @@ and this library adheres to Rust's notion of
|
|||
- `zcash_client_backend::wallet`:
|
||||
- `Note`
|
||||
- `ReceivedNote`
|
||||
- `Recipient::{map, transpose}`
|
||||
- `WalletSaplingOutput::recipient_key_scope`
|
||||
- `TransparentAddressMetadata` (which replaces `zcash_keys::address::AddressMetadata`).
|
||||
- `impl {Debug, Clone} for OvkPolicy`
|
||||
|
@ -127,7 +128,7 @@ and this library adheres to Rust's notion of
|
|||
backend-specific note identifier. The related `NoteRef` type parameter has
|
||||
been removed from `error::Error`.
|
||||
- New variants have been added:
|
||||
- `Error::UnsupportedPoolType`
|
||||
- `Error::UnsupportedChangeType`
|
||||
- `Error::NoSupportedReceivers`
|
||||
- `Error::NoSpendingKey`
|
||||
- `Error::Proposal`
|
||||
|
@ -232,6 +233,11 @@ and this library adheres to Rust's notion of
|
|||
- The fields of `ReceivedSaplingNote` are now private. Use
|
||||
`ReceivedSaplingNote::from_parts` for construction instead. Accessor methods
|
||||
are provided for each previously public field.
|
||||
- `Recipient` is now polymorphic in the type of the payload for wallet-internal
|
||||
recipients. This simplifies the handling of wallet-internal outputs.
|
||||
- `SentTransactionOutput::from_parts` now takes a `Recipient<Note>`
|
||||
- `SentTransactionOutput::recipient` now returns a `Recipient<Note>`
|
||||
- `OvkPolicy::Custom` now wraps a bare `[u8; 32]` instead of a Sapling `OutgoingViewingKey`.
|
||||
- `zcash_client_backend::scanning::ScanError` has a new variant, `TreeSizeInvalid`.
|
||||
- `zcash_client_backend::zip321::TransactionRequest::payments` now returns a
|
||||
`BTreeMap<usize, Payment>` instead of `&[Payment]` so that parameter
|
||||
|
@ -273,6 +279,8 @@ and this library adheres to Rust's notion of
|
|||
`zcash_client_backend::ReceivedNote`.
|
||||
- `zcash_client_backend::::wallet::input_selection::{Proposal, ShieldedInputs, ProposalError}`
|
||||
have been moved to `zcash_client_backend::proposal`.
|
||||
- `zcash_client_backend::wallet::SentTransactionOutput::sapling_change_to` - the
|
||||
note created by an internal transfer is now conveyed in the `recipient` field.
|
||||
- `zcash_client_backend::data_api`
|
||||
- `zcash_client_backend::data_api::ScannedBlock::from_parts` has been made crate-private.
|
||||
- `zcash_client_backend::data_api::ScannedBlock::into_sapling_commitments` has been
|
||||
|
|
|
@ -818,26 +818,32 @@ pub struct SentTransaction<'a> {
|
|||
/// A type that represents an output (either Sapling or transparent) that was sent by the wallet.
|
||||
pub struct SentTransactionOutput {
|
||||
output_index: usize,
|
||||
recipient: Recipient,
|
||||
recipient: Recipient<Note>,
|
||||
value: NonNegativeAmount,
|
||||
memo: Option<MemoBytes>,
|
||||
sapling_change_to: Option<(AccountId, sapling::Note)>,
|
||||
}
|
||||
|
||||
impl SentTransactionOutput {
|
||||
/// Constructs a new [`SentTransactionOutput`] from its constituent parts.
|
||||
///
|
||||
/// ### Fields:
|
||||
/// * `output_index` - the index of the output or action in the sent transaction
|
||||
/// * `recipient` - the recipient of the output, either a Zcash address or a
|
||||
/// wallet-internal account and the note belonging to the wallet created by
|
||||
/// the output
|
||||
/// * `value` - the value of the output, in zatoshis
|
||||
/// * `memo` - the memo that was sent with this output
|
||||
pub fn from_parts(
|
||||
output_index: usize,
|
||||
recipient: Recipient,
|
||||
recipient: Recipient<Note>,
|
||||
value: NonNegativeAmount,
|
||||
memo: Option<MemoBytes>,
|
||||
sapling_change_to: Option<(AccountId, sapling::Note)>,
|
||||
) -> Self {
|
||||
Self {
|
||||
output_index,
|
||||
recipient,
|
||||
value,
|
||||
memo,
|
||||
sapling_change_to,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -850,9 +856,9 @@ impl SentTransactionOutput {
|
|||
pub fn output_index(&self) -> usize {
|
||||
self.output_index
|
||||
}
|
||||
/// Returns the recipient address of the transaction, or the account id for wallet-internal
|
||||
/// transactions.
|
||||
pub fn recipient(&self) -> &Recipient {
|
||||
/// Returns the recipient address of the transaction, or the account id and
|
||||
/// resulting note for wallet-internal outputs.
|
||||
pub fn recipient(&self) -> &Recipient<Note> {
|
||||
&self.recipient
|
||||
}
|
||||
/// Returns the value of the newly created output.
|
||||
|
@ -864,12 +870,6 @@ impl SentTransactionOutput {
|
|||
pub fn memo(&self) -> Option<&MemoBytes> {
|
||||
self.memo.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the account to which change (or wallet-internal value in the case of a shielding
|
||||
/// transaction) was sent, along with the change note.
|
||||
pub fn sapling_change_to(&self) -> Option<&(AccountId, sapling::Note)> {
|
||||
self.sapling_change_to.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// A data structure used to set the birthday height for an account, and ensure that the initial
|
||||
|
|
|
@ -13,6 +13,7 @@ use zcash_primitives::{
|
|||
zip32::AccountId,
|
||||
};
|
||||
|
||||
use crate::address::UnifiedAddress;
|
||||
use crate::data_api::wallet::input_selection::InputSelectorError;
|
||||
use crate::proposal::ProposalError;
|
||||
use crate::PoolType;
|
||||
|
@ -66,11 +67,11 @@ pub enum Error<DataSourceError, CommitmentTreeError, SelectionError, FeeError> {
|
|||
/// It is forbidden to provide a memo when constructing a transparent output.
|
||||
MemoForbidden,
|
||||
|
||||
/// Attempted to create a spend to an unsupported pool type (currently, Orchard).
|
||||
UnsupportedPoolType(PoolType),
|
||||
/// Attempted to create a send change to an unsupported pool.
|
||||
UnsupportedChangeType(PoolType),
|
||||
|
||||
/// Attempted to create a spend to an unsupported Unified Address receiver
|
||||
NoSupportedReceivers(Vec<u32>),
|
||||
NoSupportedReceivers(Box<UnifiedAddress>),
|
||||
|
||||
/// A proposed transaction cannot be built because it requires spending an input
|
||||
/// for which no spending key is available.
|
||||
|
@ -140,8 +141,8 @@ where
|
|||
Error::ScanRequired => write!(f, "Must scan blocks first"),
|
||||
Error::Builder(e) => write!(f, "An error occurred building the transaction: {}", e),
|
||||
Error::MemoForbidden => write!(f, "It is not possible to send a memo to a transparent address."),
|
||||
Error::UnsupportedPoolType(t) => write!(f, "Attempted to send to an unsupported pool: {}", t),
|
||||
Error::NoSupportedReceivers(t) => write!(f, "Unified address contained only unsupported receiver types: {:?}", &t[..]),
|
||||
Error::UnsupportedChangeType(t) => write!(f, "Attempted to send change to an unsupported pool type: {}", t),
|
||||
Error::NoSupportedReceivers(_) => write!(f, "A recipient's unified address does not contain any receivers to which the wallet can send funds."),
|
||||
Error::NoSpendingKey(addr) => write!(f, "No spending key available for address: {}", addr),
|
||||
Error::NoteMismatch(n) => write!(f, "A note being spent ({:?}) does not correspond to either the internal or external full viewing key for the provided spending key.", n),
|
||||
|
||||
|
|
|
@ -44,7 +44,6 @@ use super::InputSource;
|
|||
#[cfg(feature = "transparent-inputs")]
|
||||
use {
|
||||
input_selection::ShieldingSelector,
|
||||
sapling::keys::OutgoingViewingKey,
|
||||
std::convert::Infallible,
|
||||
zcash_keys::encoding::AddressCodec,
|
||||
zcash_primitives::legacy::TransparentAddress,
|
||||
|
@ -645,32 +644,6 @@ where
|
|||
.map_err(Error::DataSource)?
|
||||
.ok_or(Error::KeyNotRecognized)?;
|
||||
|
||||
let dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
||||
|
||||
// Apply the outgoing viewing key policy.
|
||||
let external_ovk = match ovk_policy {
|
||||
OvkPolicy::Sender => Some(dfvk.to_ovk(Scope::External)),
|
||||
OvkPolicy::Custom(ovk) => Some(ovk),
|
||||
OvkPolicy::Discard => None,
|
||||
};
|
||||
|
||||
let internal_ovk = || {
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
return if proposal_step.is_shielding() {
|
||||
Some(OutgoingViewingKey(
|
||||
usk.transparent()
|
||||
.to_account_pubkey()
|
||||
.internal_ovk()
|
||||
.as_bytes(),
|
||||
))
|
||||
} else {
|
||||
Some(dfvk.to_ovk(Scope::Internal))
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "transparent-inputs"))]
|
||||
Some(dfvk.to_ovk(Scope::Internal))
|
||||
};
|
||||
|
||||
let (sapling_anchor, sapling_inputs) = proposal_step.shielded_inputs().map_or_else(
|
||||
|| Ok((sapling::Anchor::empty_tree(), vec![])),
|
||||
|inputs| {
|
||||
|
@ -851,6 +824,62 @@ where
|
|||
utxos_spent
|
||||
};
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
let orchard_fvk: orchard::keys::FullViewingKey = usk.orchard().into();
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
let orchard_external_ovk = match ovk_policy {
|
||||
OvkPolicy::Sender => Some(orchard_fvk.to_ovk(orchard::keys::Scope::External)),
|
||||
OvkPolicy::Custom(ovk) => Some(orchard::keys::OutgoingViewingKey::from(ovk)),
|
||||
OvkPolicy::Discard => None,
|
||||
};
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
let orchard_internal_ovk = || {
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
return if proposal_step.is_shielding() {
|
||||
Some(orchard::keys::OutgoingViewingKey::from(
|
||||
usk.transparent()
|
||||
.to_account_pubkey()
|
||||
.internal_ovk()
|
||||
.as_bytes(),
|
||||
))
|
||||
} else {
|
||||
Some(orchard_fvk.to_ovk(orchard::keys::Scope::Internal))
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "transparent-inputs"))]
|
||||
Some(orchard_fvk.to_ovk(Scope::Internal))
|
||||
};
|
||||
|
||||
let sapling_dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
||||
|
||||
// Apply the outgoing viewing key policy.
|
||||
let sapling_external_ovk = match ovk_policy {
|
||||
OvkPolicy::Sender => Some(sapling_dfvk.to_ovk(Scope::External)),
|
||||
OvkPolicy::Custom(ovk) => Some(sapling::keys::OutgoingViewingKey(ovk)),
|
||||
OvkPolicy::Discard => None,
|
||||
};
|
||||
|
||||
let sapling_internal_ovk = || {
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
return if proposal_step.is_shielding() {
|
||||
Some(sapling::keys::OutgoingViewingKey(
|
||||
usk.transparent()
|
||||
.to_account_pubkey()
|
||||
.internal_ovk()
|
||||
.as_bytes(),
|
||||
))
|
||||
} else {
|
||||
Some(sapling_dfvk.to_ovk(Scope::Internal))
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "transparent-inputs"))]
|
||||
Some(sapling_dfvk.to_ovk(Scope::Internal))
|
||||
};
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
let mut orchard_output_meta = vec![];
|
||||
let mut sapling_output_meta = vec![];
|
||||
let mut transparent_output_meta = vec![];
|
||||
for payment in proposal_step.transaction_request().payments().values() {
|
||||
|
@ -861,9 +890,28 @@ where
|
|||
.as_ref()
|
||||
.map_or_else(MemoBytes::empty, |m| m.clone());
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
if let Some(orchard_receiver) = ua.orchard() {
|
||||
builder.add_orchard_output(
|
||||
orchard_external_ovk.clone(),
|
||||
*orchard_receiver,
|
||||
payment.amount.into(),
|
||||
memo.clone(),
|
||||
)?;
|
||||
orchard_output_meta.push((
|
||||
Recipient::Unified(
|
||||
ua.clone(),
|
||||
PoolType::Shielded(ShieldedProtocol::Orchard),
|
||||
),
|
||||
payment.amount,
|
||||
Some(memo),
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(sapling_receiver) = ua.sapling() {
|
||||
builder.add_sapling_output(
|
||||
external_ovk,
|
||||
sapling_external_ovk,
|
||||
*sapling_receiver,
|
||||
payment.amount,
|
||||
memo.clone(),
|
||||
|
@ -876,24 +924,32 @@ where
|
|||
payment.amount,
|
||||
Some(memo),
|
||||
));
|
||||
} else if let Some(taddr) = ua.transparent() {
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(taddr) = ua.transparent() {
|
||||
if payment.memo.is_some() {
|
||||
return Err(Error::MemoForbidden);
|
||||
} else {
|
||||
builder.add_transparent_output(taddr, payment.amount)?;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
return Err(Error::NoSupportedReceivers(
|
||||
ua.unknown().iter().map(|(tc, _)| *tc).collect(),
|
||||
));
|
||||
}
|
||||
|
||||
return Err(Error::NoSupportedReceivers(Box::new(ua.clone())));
|
||||
}
|
||||
Address::Sapling(addr) => {
|
||||
let memo = payment
|
||||
.memo
|
||||
.as_ref()
|
||||
.map_or_else(MemoBytes::empty, |m| m.clone());
|
||||
builder.add_sapling_output(external_ovk, *addr, payment.amount, memo.clone())?;
|
||||
builder.add_sapling_output(
|
||||
sapling_external_ovk,
|
||||
*addr,
|
||||
payment.amount,
|
||||
memo.clone(),
|
||||
)?;
|
||||
sapling_output_meta.push((Recipient::Sapling(*addr), payment.amount, Some(memo)));
|
||||
}
|
||||
Address::Transparent(to) => {
|
||||
|
@ -914,8 +970,8 @@ where
|
|||
match change_value.output_pool() {
|
||||
ShieldedProtocol::Sapling => {
|
||||
builder.add_sapling_output(
|
||||
internal_ovk(),
|
||||
dfvk.change_address().1,
|
||||
sapling_internal_ovk(),
|
||||
sapling_dfvk.change_address().1,
|
||||
change_value.value(),
|
||||
memo.clone(),
|
||||
)?;
|
||||
|
@ -931,12 +987,27 @@ where
|
|||
#[cfg(zcash_unstable = "orchard")]
|
||||
ShieldedProtocol::Orchard => {
|
||||
#[cfg(not(feature = "orchard"))]
|
||||
return Err(Error::UnsupportedPoolType(PoolType::Shielded(
|
||||
return Err(Error::UnsupportedChangeType(PoolType::Shielded(
|
||||
ShieldedProtocol::Orchard,
|
||||
)));
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
unimplemented!("FIXME: implement Orchard change output creation.")
|
||||
{
|
||||
builder.add_orchard_output(
|
||||
orchard_internal_ovk(),
|
||||
orchard_fvk.address_at(0u32, orchard::keys::Scope::Internal),
|
||||
change_value.value().into(),
|
||||
memo.clone(),
|
||||
)?;
|
||||
orchard_output_meta.push((
|
||||
Recipient::InternalAccount(
|
||||
account,
|
||||
PoolType::Shielded(ShieldedProtocol::Orchard),
|
||||
),
|
||||
change_value.value(),
|
||||
Some(memo),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -944,7 +1015,39 @@ where
|
|||
// Build the transaction with the specified fee rule
|
||||
let build_result = builder.build(OsRng, spend_prover, output_prover, fee_rule)?;
|
||||
|
||||
let internal_ivk = PreparedIncomingViewingKey::new(&dfvk.to_ivk(Scope::Internal));
|
||||
#[cfg(feature = "orchard")]
|
||||
let orchard_internal_ivk = orchard_fvk.to_ivk(orchard::keys::Scope::Internal);
|
||||
#[cfg(feature = "orchard")]
|
||||
let orchard_outputs =
|
||||
orchard_output_meta
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, (recipient, value, memo))| {
|
||||
let output_index = build_result
|
||||
.orchard_meta()
|
||||
.output_action_index(i)
|
||||
.expect("An action should exist in the transaction for each Orchard output.");
|
||||
|
||||
let recipient = recipient
|
||||
.map(|pool| {
|
||||
assert!(pool == PoolType::Shielded(ShieldedProtocol::Orchard));
|
||||
build_result
|
||||
.transaction()
|
||||
.orchard_bundle()
|
||||
.and_then(|bundle| {
|
||||
bundle
|
||||
.decrypt_output_with_key(output_index, &orchard_internal_ivk)
|
||||
.map(|(note, _, _)| Note::Orchard(note))
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
.expect("Wallet-internal outputs must be decryptable with the wallet's IVK");
|
||||
|
||||
SentTransactionOutput::from_parts(output_index, recipient, value, memo)
|
||||
});
|
||||
|
||||
let sapling_internal_ivk =
|
||||
PreparedIncomingViewingKey::new(&sapling_dfvk.to_ivk(Scope::Internal));
|
||||
let sapling_outputs =
|
||||
sapling_output_meta
|
||||
.into_iter()
|
||||
|
@ -955,27 +1058,28 @@ where
|
|||
.output_index(i)
|
||||
.expect("An output should exist in the transaction for each Sapling payment.");
|
||||
|
||||
let received_as = if let Recipient::InternalAccount(
|
||||
account,
|
||||
PoolType::Shielded(ShieldedProtocol::Sapling),
|
||||
) = recipient
|
||||
{
|
||||
build_result
|
||||
.transaction()
|
||||
.sapling_bundle()
|
||||
.and_then(|bundle| {
|
||||
try_sapling_note_decryption(
|
||||
&internal_ivk,
|
||||
&bundle.shielded_outputs()[output_index],
|
||||
consensus::sapling_zip212_enforcement(params, min_target_height),
|
||||
)
|
||||
.map(|(note, _, _)| (account, note))
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let recipient = recipient
|
||||
.map(|pool| {
|
||||
assert!(pool == PoolType::Shielded(ShieldedProtocol::Sapling));
|
||||
build_result
|
||||
.transaction()
|
||||
.sapling_bundle()
|
||||
.and_then(|bundle| {
|
||||
try_sapling_note_decryption(
|
||||
&sapling_internal_ivk,
|
||||
&bundle.shielded_outputs()[output_index],
|
||||
consensus::sapling_zip212_enforcement(
|
||||
params,
|
||||
min_target_height,
|
||||
),
|
||||
)
|
||||
.map(|(note, _, _)| Note::Sapling(note))
|
||||
})
|
||||
})
|
||||
.transpose()
|
||||
.expect("Wallet-internal outputs must be decryptable with the wallet's IVK");
|
||||
|
||||
SentTransactionOutput::from_parts(output_index, recipient, value, memo, received_as)
|
||||
SentTransactionOutput::from_parts(output_index, recipient, value, memo)
|
||||
});
|
||||
|
||||
let transparent_outputs = transparent_output_meta.into_iter().map(|(addr, value)| {
|
||||
|
@ -992,21 +1096,21 @@ where
|
|||
.map(|(index, _)| index)
|
||||
.expect("An output should exist in the transaction for each transparent payment.");
|
||||
|
||||
SentTransactionOutput::from_parts(
|
||||
output_index,
|
||||
Recipient::Transparent(*addr),
|
||||
value,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
SentTransactionOutput::from_parts(output_index, Recipient::Transparent(*addr), value, None)
|
||||
});
|
||||
|
||||
let mut outputs = vec![];
|
||||
#[cfg(feature = "orchard")]
|
||||
outputs.extend(orchard_outputs);
|
||||
outputs.extend(sapling_outputs);
|
||||
outputs.extend(transparent_outputs);
|
||||
|
||||
wallet_db
|
||||
.store_sent_tx(&SentTransaction {
|
||||
tx: build_result.transaction(),
|
||||
created: time::OffsetDateTime::now_utc(),
|
||||
account,
|
||||
outputs: sapling_outputs.chain(transparent_outputs).collect(),
|
||||
outputs,
|
||||
fee_amount: Amount::from(proposal_step.balance().fee_required()),
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
utxos_spent,
|
||||
|
|
|
@ -96,9 +96,8 @@ where
|
|||
// Sapling outputs, so that we avoid pool-crossing.
|
||||
(ShieldedProtocol::Sapling, 1, 0)
|
||||
} else {
|
||||
// For all other transactions, send change to Sapling.
|
||||
// FIXME: Change this to Orchard once Orchard outputs are enabled.
|
||||
(ShieldedProtocol::Sapling, 1, 0)
|
||||
// For all other transactions, send change to Orchard.
|
||||
(ShieldedProtocol::Orchard, 0, 1)
|
||||
};
|
||||
#[cfg(not(feature = "orchard"))]
|
||||
let (change_pool, sapling_change) = (ShieldedProtocol::Sapling, 1);
|
||||
|
|
|
@ -65,11 +65,33 @@ impl NoteId {
|
|||
/// internal account ID and the pool to which funds were sent in the case of a wallet-internal
|
||||
/// output.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Recipient {
|
||||
pub enum Recipient<N> {
|
||||
Transparent(TransparentAddress),
|
||||
Sapling(sapling::PaymentAddress),
|
||||
Unified(UnifiedAddress, PoolType),
|
||||
InternalAccount(AccountId, PoolType),
|
||||
InternalAccount(AccountId, N),
|
||||
}
|
||||
|
||||
impl<N> Recipient<N> {
|
||||
pub fn map<B, F: FnOnce(N) -> B>(self, f: F) -> Recipient<B> {
|
||||
match self {
|
||||
Recipient::Transparent(t) => Recipient::Transparent(t),
|
||||
Recipient::Sapling(s) => Recipient::Sapling(s),
|
||||
Recipient::Unified(u, p) => Recipient::Unified(u, p),
|
||||
Recipient::InternalAccount(a, n) => Recipient::InternalAccount(a, f(n)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<N> Recipient<Option<N>> {
|
||||
pub fn transpose(self) -> Option<Recipient<N>> {
|
||||
match self {
|
||||
Recipient::Transparent(t) => Some(Recipient::Transparent(t)),
|
||||
Recipient::Sapling(s) => Some(Recipient::Sapling(s)),
|
||||
Recipient::Unified(u, p) => Some(Recipient::Unified(u, p)),
|
||||
Recipient::InternalAccount(a, n) => n.map(|n0| Recipient::InternalAccount(a, n0)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A subset of a [`Transaction`] relevant to wallets and light clients.
|
||||
|
@ -393,7 +415,7 @@ pub enum OvkPolicy {
|
|||
///
|
||||
/// Transaction outputs will be decryptable by the recipients, and whoever controls
|
||||
/// the provided outgoing viewing key.
|
||||
Custom(sapling::keys::OutgoingViewingKey),
|
||||
Custom([u8; 32]),
|
||||
|
||||
/// Use no outgoing viewing key. Transaction outputs will be decryptable by their
|
||||
/// recipients, but not by the sender.
|
||||
|
|
|
@ -44,7 +44,7 @@ jubjub.workspace = true
|
|||
secrecy.workspace = true
|
||||
|
||||
# - Shielded protocols
|
||||
orchard.workspace = true
|
||||
orchard = { workspace = true, optional = true }
|
||||
sapling.workspace = true
|
||||
|
||||
# - Note commitment trees
|
||||
|
@ -90,7 +90,7 @@ multicore = ["maybe-rayon/threads", "zcash_primitives/multicore"]
|
|||
|
||||
## Enables support for storing data related to the sending and receiving of
|
||||
## Orchard funds.
|
||||
orchard = ["zcash_client_backend/orchard"]
|
||||
orchard = ["dep:orchard", "zcash_client_backend/orchard"]
|
||||
|
||||
## Exposes APIs that are useful for testing, such as `proptest` strategies.
|
||||
test-dependencies = [
|
||||
|
|
|
@ -69,7 +69,7 @@ use zcash_client_backend::{
|
|||
keys::{UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey},
|
||||
proto::compact_formats::CompactBlock,
|
||||
wallet::{Note, NoteId, ReceivedNote, Recipient, WalletTransparentOutput},
|
||||
DecryptedOutput, PoolType, ShieldedProtocol, TransferType,
|
||||
DecryptedOutput, ShieldedProtocol, TransferType,
|
||||
};
|
||||
|
||||
use crate::{error::SqliteClientError, wallet::commitment_tree::SqliteShardStore};
|
||||
|
@ -591,12 +591,13 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
|||
for output in d_tx.sapling_outputs {
|
||||
match output.transfer_type {
|
||||
TransferType::Outgoing | TransferType::WalletInternal => {
|
||||
let value = output.note.value();
|
||||
let recipient = if output.transfer_type == TransferType::Outgoing {
|
||||
Recipient::Sapling(output.note.recipient())
|
||||
} else {
|
||||
Recipient::InternalAccount(
|
||||
output.account,
|
||||
PoolType::Shielded(ShieldedProtocol::Sapling)
|
||||
Note::Sapling(output.note.clone()),
|
||||
)
|
||||
};
|
||||
|
||||
|
@ -607,7 +608,7 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
|||
tx_ref,
|
||||
output.index,
|
||||
&recipient,
|
||||
NonNegativeAmount::from_u64(output.note.value().inner()).map_err(|_| {
|
||||
NonNegativeAmount::from_u64(value.inner()).map_err(|_| {
|
||||
SqliteClientError::CorruptedData(
|
||||
"Note value is not a valid Zcash amount.".to_string(),
|
||||
)
|
||||
|
@ -715,21 +716,28 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
|||
output,
|
||||
)?;
|
||||
|
||||
if let Some((account, note)) = output.sapling_change_to() {
|
||||
wallet::sapling::put_received_note(
|
||||
wdb.conn.0,
|
||||
&DecryptedOutput {
|
||||
index: output.output_index(),
|
||||
note: note.clone(),
|
||||
account: *account,
|
||||
memo: output
|
||||
.memo()
|
||||
.map_or_else(MemoBytes::empty, |memo| memo.clone()),
|
||||
transfer_type: TransferType::WalletInternal,
|
||||
},
|
||||
tx_ref,
|
||||
None,
|
||||
)?;
|
||||
match output.recipient() {
|
||||
Recipient::InternalAccount(account, Note::Sapling(note)) => {
|
||||
wallet::sapling::put_received_note(
|
||||
wdb.conn.0,
|
||||
&DecryptedOutput {
|
||||
index: output.output_index(),
|
||||
note: note.clone(),
|
||||
account: *account,
|
||||
memo: output
|
||||
.memo()
|
||||
.map_or_else(MemoBytes::empty, |memo| memo.clone()),
|
||||
transfer_type: TransferType::WalletInternal,
|
||||
},
|
||||
tx_ref,
|
||||
None,
|
||||
)?;
|
||||
}
|
||||
#[cfg(feature = "orchard")]
|
||||
Recipient::InternalAccount(_account, Note::Orchard(_note)) => {
|
||||
todo!();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -74,6 +74,7 @@ use std::num::NonZeroU32;
|
|||
use std::ops::RangeInclusive;
|
||||
use tracing::debug;
|
||||
use zcash_client_backend::data_api::{AccountBalance, Ratio, WalletSummary};
|
||||
use zcash_client_backend::wallet::Note;
|
||||
use zcash_primitives::transaction::components::amount::NonNegativeAmount;
|
||||
use zcash_primitives::zip32::Scope;
|
||||
|
||||
|
@ -1796,7 +1797,7 @@ pub(crate) fn update_expired_notes(
|
|||
// and `put_sent_output`
|
||||
fn recipient_params<P: consensus::Parameters>(
|
||||
params: &P,
|
||||
to: &Recipient,
|
||||
to: &Recipient<Note>,
|
||||
) -> (Option<String>, Option<u32>, PoolType) {
|
||||
match to {
|
||||
Recipient::Transparent(addr) => (Some(addr.encode(params)), None, PoolType::Transparent),
|
||||
|
@ -1806,7 +1807,11 @@ fn recipient_params<P: consensus::Parameters>(
|
|||
PoolType::Shielded(ShieldedProtocol::Sapling),
|
||||
),
|
||||
Recipient::Unified(addr, pool) => (Some(addr.encode(params)), None, *pool),
|
||||
Recipient::InternalAccount(id, pool) => (None, Some(u32::from(*id)), *pool),
|
||||
Recipient::InternalAccount(id, note) => (
|
||||
None,
|
||||
Some(u32::from(*id)),
|
||||
PoolType::Shielded(note.protocol()),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1862,7 +1867,7 @@ pub(crate) fn put_sent_output<P: consensus::Parameters>(
|
|||
from_account: AccountId,
|
||||
tx_ref: i64,
|
||||
output_index: usize,
|
||||
recipient: &Recipient,
|
||||
recipient: &Recipient<Note>,
|
||||
value: NonNegativeAmount,
|
||||
memo: Option<&MemoBytes>,
|
||||
) -> Result<(), SqliteClientError> {
|
||||
|
|
Loading…
Reference in New Issue