Merge pull request #1234 from nuttycom/wallet/spendable_value
zcash_client_backend: Update `DecryptedTransaction` to support Orchard
This commit is contained in:
commit
54addb6ca6
|
@ -3089,6 +3089,7 @@ dependencies = [
|
|||
"zcash_note_encryption",
|
||||
"zcash_primitives",
|
||||
"zcash_proofs",
|
||||
"zcash_protocol",
|
||||
"zip32",
|
||||
]
|
||||
|
||||
|
|
|
@ -7,6 +7,10 @@ and this library adheres to Rust's notion of
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- `zcash_protocol::memo`:
|
||||
- `impl TryFrom<&MemoBytes> for Memo`
|
||||
|
||||
## [0.1.0] - 2024-03-06
|
||||
The entries below are relative to the `zcash_primitives` crate as of the tag
|
||||
`zcash_primitives-0.14.0`.
|
||||
|
|
|
@ -197,13 +197,25 @@ impl TryFrom<MemoBytes> for Memo {
|
|||
/// Returns an error if the provided slice does not represent a valid `Memo` (for
|
||||
/// example, if the slice is not 512 bytes, or the encoded `Memo` is non-canonical).
|
||||
fn try_from(bytes: MemoBytes) -> Result<Self, Self::Error> {
|
||||
Self::try_from(&bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&MemoBytes> for Memo {
|
||||
type Error = Error;
|
||||
|
||||
/// Parses a `Memo` from its ZIP 302 serialization.
|
||||
///
|
||||
/// Returns an error if the provided slice does not represent a valid `Memo` (for
|
||||
/// example, if the slice is not 512 bytes, or the encoded `Memo` is non-canonical).
|
||||
fn try_from(bytes: &MemoBytes) -> Result<Self, Self::Error> {
|
||||
match bytes.0[0] {
|
||||
0xF6 if bytes.0.iter().skip(1).all(|&b| b == 0) => Ok(Memo::Empty),
|
||||
0xFF => Ok(Memo::Arbitrary(Box::new(bytes.0[1..].try_into().unwrap()))),
|
||||
b if b <= 0xF4 => str::from_utf8(bytes.as_slice())
|
||||
.map(|r| Memo::Text(TextMemo(r.to_owned())))
|
||||
.map_err(Error::InvalidUtf8),
|
||||
_ => Ok(Memo::Future(bytes)),
|
||||
_ => Ok(Memo::Future(bytes.clone())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,10 @@ and this library adheres to Rust's notion of
|
|||
- `AccountBalance::with_orchard_balance_mut`
|
||||
- `AccountBirthday::orchard_frontier`
|
||||
- `BlockMetadata::orchard_tree_size`
|
||||
- `DecryptedTransaction::{new, tx(), orchard_outputs()}`
|
||||
- `ScannedBlock::orchard`
|
||||
- `ScannedBlockCommitments::orchard`
|
||||
- `SentTransaction::new`
|
||||
- `ORCHARD_SHARD_HEIGHT`
|
||||
- `BlockMetadata::orchard_tree_size`
|
||||
- `chain::ScanSummary::{spent_orchard_note_count, received_orchard_note_count}`
|
||||
|
@ -47,6 +49,14 @@ and this library adheres to Rust's notion of
|
|||
- Arguments to `ScannedBlock::from_parts` have changed.
|
||||
- Changes to the `WalletRead` trait:
|
||||
- Added `get_orchard_nullifiers`
|
||||
- Changes to the `InputSource` trait:
|
||||
- `select_spendable_notes` now takes its `target_value` argument as a
|
||||
`NonNegativeAmount`. Also, the values of the returned map are also
|
||||
`NonNegativeAmount`s instead of `Amount`s.
|
||||
- Fields of `DecryptedTransaction` are now private. Use `DecryptedTransaction::new`
|
||||
and the newly provided accessors instead.
|
||||
- Fields of `SentTransaction` are now private. Use `SentTransaction::new`
|
||||
and the newly provided accessors instead.
|
||||
- `ShieldedProtocol` has a new `Orchard` variant.
|
||||
- `WalletCommitmentTrees`
|
||||
- `type OrchardShardStore`
|
||||
|
@ -54,6 +64,13 @@ and this library adheres to Rust's notion of
|
|||
- `fn put_orchard_subtree_roots`
|
||||
- Added method `WalletRead::validate_seed`
|
||||
- Removed `Error::AccountNotFound` variant.
|
||||
- `zcash_client_backend::decrypt`:
|
||||
- Fields of `DecryptedOutput` are now private. Use `DecryptedOutput::new`
|
||||
and the newly provided accessors instead.
|
||||
- `decrypt_transaction` now returns a `DecryptedTransaction<AccountId>`
|
||||
instead of a `DecryptedOutput<sapling::Note>` and will decrypt Orchard
|
||||
outputs when the `orchard` feature is enabled. In addition, the type
|
||||
constraint on its `<AccountId>` parameter has been strengthened to `Copy`.
|
||||
- `zcash_client_backend::fees`:
|
||||
- Arguments to `ChangeStrategy::compute_balance` have changed.
|
||||
- `zcash_client_backend::zip321::render::amount_str` now takes a
|
||||
|
|
|
@ -81,7 +81,7 @@ use zcash_primitives::{
|
|||
consensus::BlockHeight,
|
||||
memo::{Memo, MemoBytes},
|
||||
transaction::{
|
||||
components::amount::{Amount, BalanceError, NonNegativeAmount},
|
||||
components::amount::{BalanceError, NonNegativeAmount},
|
||||
Transaction, TxId,
|
||||
},
|
||||
};
|
||||
|
@ -445,7 +445,7 @@ pub trait InputSource {
|
|||
fn select_spendable_notes(
|
||||
&self,
|
||||
account: Self::AccountId,
|
||||
target_value: Amount,
|
||||
target_value: NonNegativeAmount,
|
||||
sources: &[ShieldedProtocol],
|
||||
anchor_height: BlockHeight,
|
||||
exclude: &[Self::NoteRef],
|
||||
|
@ -662,7 +662,7 @@ pub trait WalletRead {
|
|||
&self,
|
||||
_account: Self::AccountId,
|
||||
_max_height: BlockHeight,
|
||||
) -> Result<HashMap<TransparentAddress, Amount>, Self::Error> {
|
||||
) -> Result<HashMap<TransparentAddress, NonNegativeAmount>, Self::Error> {
|
||||
Ok(HashMap::new())
|
||||
}
|
||||
|
||||
|
@ -872,13 +872,47 @@ impl<A> ScannedBlock<A> {
|
|||
}
|
||||
|
||||
/// A transaction that was detected during scanning of the blockchain,
|
||||
/// including its decrypted Sapling outputs.
|
||||
/// including its decrypted Sapling and/or Orchard outputs.
|
||||
///
|
||||
/// The purpose of this struct is to permit atomic updates of the
|
||||
/// wallet database when transactions are successfully decrypted.
|
||||
pub struct DecryptedTransaction<'a, AccountId> {
|
||||
pub tx: &'a Transaction,
|
||||
pub sapling_outputs: &'a Vec<DecryptedOutput<sapling::Note, AccountId>>,
|
||||
tx: &'a Transaction,
|
||||
sapling_outputs: Vec<DecryptedOutput<sapling::Note, AccountId>>,
|
||||
#[cfg(feature = "orchard")]
|
||||
orchard_outputs: Vec<DecryptedOutput<orchard::note::Note, AccountId>>,
|
||||
}
|
||||
|
||||
impl<'a, AccountId> DecryptedTransaction<'a, AccountId> {
|
||||
/// Constructs a new [`DecryptedTransaction`] from its constituent parts.
|
||||
pub fn new(
|
||||
tx: &'a Transaction,
|
||||
sapling_outputs: Vec<DecryptedOutput<sapling::Note, AccountId>>,
|
||||
#[cfg(feature = "orchard")] orchard_outputs: Vec<
|
||||
DecryptedOutput<orchard::note::Note, AccountId>,
|
||||
>,
|
||||
) -> Self {
|
||||
Self {
|
||||
tx,
|
||||
sapling_outputs,
|
||||
#[cfg(feature = "orchard")]
|
||||
orchard_outputs,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the raw transaction data.
|
||||
pub fn tx(&self) -> &Transaction {
|
||||
self.tx
|
||||
}
|
||||
/// Returns the Sapling outputs that were decrypted from the transaction.
|
||||
pub fn sapling_outputs(&self) -> &[DecryptedOutput<sapling::Note, AccountId>] {
|
||||
&self.sapling_outputs
|
||||
}
|
||||
/// Returns the Orchard outputs that were decrypted from the transaction.
|
||||
#[cfg(feature = "orchard")]
|
||||
pub fn orchard_outputs(&self) -> &[DecryptedOutput<orchard::note::Note, AccountId>] {
|
||||
&self.orchard_outputs
|
||||
}
|
||||
}
|
||||
|
||||
/// A transaction that was constructed and sent by the wallet.
|
||||
|
@ -887,13 +921,61 @@ pub struct DecryptedTransaction<'a, AccountId> {
|
|||
/// wallet database when transactions are created and submitted
|
||||
/// to the network.
|
||||
pub struct SentTransaction<'a, AccountId> {
|
||||
pub tx: &'a Transaction,
|
||||
pub created: time::OffsetDateTime,
|
||||
pub account: AccountId,
|
||||
pub outputs: Vec<SentTransactionOutput<AccountId>>,
|
||||
pub fee_amount: Amount,
|
||||
tx: &'a Transaction,
|
||||
created: time::OffsetDateTime,
|
||||
account: AccountId,
|
||||
outputs: Vec<SentTransactionOutput<AccountId>>,
|
||||
fee_amount: NonNegativeAmount,
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
pub utxos_spent: Vec<OutPoint>,
|
||||
utxos_spent: Vec<OutPoint>,
|
||||
}
|
||||
|
||||
impl<'a, AccountId> SentTransaction<'a, AccountId> {
|
||||
/// Constructs a new [`SentTransaction`] from its constituent parts.
|
||||
pub fn new(
|
||||
tx: &'a Transaction,
|
||||
created: time::OffsetDateTime,
|
||||
account: AccountId,
|
||||
outputs: Vec<SentTransactionOutput<AccountId>>,
|
||||
fee_amount: NonNegativeAmount,
|
||||
#[cfg(feature = "transparent-inputs")] utxos_spent: Vec<OutPoint>,
|
||||
) -> Self {
|
||||
Self {
|
||||
tx,
|
||||
created,
|
||||
account,
|
||||
outputs,
|
||||
fee_amount,
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
utxos_spent,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the transaction that was sent.
|
||||
pub fn tx(&self) -> &Transaction {
|
||||
self.tx
|
||||
}
|
||||
/// Returns the timestamp of the transaction's creation.
|
||||
pub fn created(&self) -> time::OffsetDateTime {
|
||||
self.created
|
||||
}
|
||||
/// Returns the id for the account that created the outputs.
|
||||
pub fn account_id(&self) -> &AccountId {
|
||||
&self.account
|
||||
}
|
||||
/// Returns the outputs of the transaction.
|
||||
pub fn outputs(&self) -> &[SentTransactionOutput<AccountId>] {
|
||||
self.outputs.as_ref()
|
||||
}
|
||||
/// Returns the fee paid by the transaction.
|
||||
pub fn fee_amount(&self) -> NonNegativeAmount {
|
||||
self.fee_amount
|
||||
}
|
||||
/// Returns the list of UTXOs spent in the created transaction.
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
pub fn utxos_spent(&self) -> &[OutPoint] {
|
||||
self.utxos_spent.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// An output of a transaction generated by the wallet.
|
||||
|
@ -1279,7 +1361,7 @@ pub mod testing {
|
|||
block::BlockHash,
|
||||
consensus::{BlockHeight, Network},
|
||||
memo::Memo,
|
||||
transaction::{components::Amount, Transaction, TxId},
|
||||
transaction::{components::amount::NonNegativeAmount, Transaction, TxId},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
@ -1344,7 +1426,7 @@ pub mod testing {
|
|||
fn select_spendable_notes(
|
||||
&self,
|
||||
_account: Self::AccountId,
|
||||
_target_value: Amount,
|
||||
_target_value: NonNegativeAmount,
|
||||
_sources: &[ShieldedProtocol],
|
||||
_anchor_height: BlockHeight,
|
||||
_exclude: &[Self::NoteRef],
|
||||
|
@ -1489,7 +1571,7 @@ pub mod testing {
|
|||
&self,
|
||||
_account: Self::AccountId,
|
||||
_max_height: BlockHeight,
|
||||
) -> Result<HashMap<TransparentAddress, Amount>, Self::Error> {
|
||||
) -> Result<HashMap<TransparentAddress, NonNegativeAmount>, Self::Error> {
|
||||
Ok(HashMap::new())
|
||||
}
|
||||
|
||||
|
|
|
@ -40,8 +40,8 @@ use super::InputSource;
|
|||
use crate::{
|
||||
address::Address,
|
||||
data_api::{
|
||||
error::Error, DecryptedTransaction, SentTransaction, SentTransactionOutput,
|
||||
WalletCommitmentTrees, WalletRead, WalletWrite,
|
||||
error::Error, SentTransaction, SentTransactionOutput, WalletCommitmentTrees, WalletRead,
|
||||
WalletWrite,
|
||||
},
|
||||
decrypt_transaction,
|
||||
fees::{self, DustOutputPolicy},
|
||||
|
@ -51,20 +51,17 @@ use crate::{
|
|||
zip321::{self, Payment},
|
||||
PoolType, ShieldedProtocol,
|
||||
};
|
||||
use zcash_primitives::{
|
||||
use zcash_primitives::transaction::{
|
||||
builder::{BuildConfig, BuildResult, Builder},
|
||||
components::{amount::NonNegativeAmount, sapling::zip212_enforcement},
|
||||
fees::{zip317::FeeError as Zip317FeeError, FeeRule, StandardFeeRule},
|
||||
Transaction, TxId,
|
||||
};
|
||||
use zcash_protocol::{
|
||||
consensus::{self, BlockHeight, NetworkUpgrade},
|
||||
memo::MemoBytes,
|
||||
transaction::{
|
||||
builder::{BuildConfig, BuildResult, Builder},
|
||||
components::{
|
||||
amount::{Amount, NonNegativeAmount},
|
||||
sapling::zip212_enforcement,
|
||||
},
|
||||
fees::{zip317::FeeError as Zip317FeeError, FeeRule, StandardFeeRule},
|
||||
Transaction, TxId,
|
||||
},
|
||||
zip32::Scope,
|
||||
};
|
||||
use zip32::Scope;
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
use {
|
||||
|
@ -102,10 +99,7 @@ where
|
|||
.or_else(|| params.activation_height(NetworkUpgrade::Sapling))
|
||||
.expect("Sapling activation height must be known.");
|
||||
|
||||
data.store_decrypted_tx(DecryptedTransaction {
|
||||
tx,
|
||||
sapling_outputs: &decrypt_transaction(params, height, tx, &ufvks),
|
||||
})?;
|
||||
data.store_decrypted_tx(decrypt_transaction(params, height, tx, &ufvks))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1180,7 +1174,7 @@ where
|
|||
created: time::OffsetDateTime::now_utc(),
|
||||
account,
|
||||
outputs,
|
||||
fee_amount: Amount::from(proposal_step.balance().fee_required()),
|
||||
fee_amount: proposal_step.balance().fee_required(),
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
utxos_spent,
|
||||
})
|
||||
|
|
|
@ -462,7 +462,7 @@ where
|
|||
shielded_inputs = wallet_db
|
||||
.select_spendable_notes(
|
||||
account,
|
||||
amount_required.into(),
|
||||
amount_required,
|
||||
selectable_pools,
|
||||
anchor_height,
|
||||
&exclude,
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use sapling::note_encryption::{
|
||||
try_sapling_note_decryption, try_sapling_output_recovery, PreparedIncomingViewingKey,
|
||||
};
|
||||
use sapling::note_encryption::{PreparedIncomingViewingKey, SaplingDomain};
|
||||
use zcash_note_encryption::{try_note_decryption, try_output_recovery_with_ovk};
|
||||
use zcash_primitives::{
|
||||
consensus::{self, BlockHeight},
|
||||
memo::MemoBytes,
|
||||
transaction::components::sapling::zip212_enforcement,
|
||||
transaction::components::{amount::NonNegativeAmount, sapling::zip212_enforcement},
|
||||
transaction::Transaction,
|
||||
zip32::Scope,
|
||||
};
|
||||
|
||||
use crate::keys::UnifiedFullViewingKey;
|
||||
use crate::{data_api::DecryptedTransaction, keys::UnifiedFullViewingKey};
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
use orchard::note_encryption::OrchardDomain;
|
||||
|
||||
/// An enumeration of the possible relationships a TXO can have to the wallet.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
|
@ -29,43 +31,91 @@ pub enum TransferType {
|
|||
|
||||
/// A decrypted shielded output.
|
||||
pub struct DecryptedOutput<Note, AccountId> {
|
||||
/// The index of the output within [`shielded_outputs`].
|
||||
///
|
||||
/// [`shielded_outputs`]: zcash_primitives::transaction::TransactionData
|
||||
pub index: usize,
|
||||
index: usize,
|
||||
note: Note,
|
||||
account: AccountId,
|
||||
memo: MemoBytes,
|
||||
transfer_type: TransferType,
|
||||
}
|
||||
|
||||
impl<Note, AccountId: Copy> DecryptedOutput<Note, AccountId> {
|
||||
pub fn new(
|
||||
index: usize,
|
||||
note: Note,
|
||||
account: AccountId,
|
||||
memo: MemoBytes,
|
||||
transfer_type: TransferType,
|
||||
) -> Self {
|
||||
Self {
|
||||
index,
|
||||
note,
|
||||
account,
|
||||
memo,
|
||||
transfer_type,
|
||||
}
|
||||
}
|
||||
|
||||
/// The index of the output within the shielded outputs of the Sapling bundle or the actions of
|
||||
/// the Orchard bundle, depending upon the type of [`Self::note`].
|
||||
pub fn index(&self) -> usize {
|
||||
self.index
|
||||
}
|
||||
|
||||
/// The note within the output.
|
||||
pub note: Note,
|
||||
pub fn note(&self) -> &Note {
|
||||
&self.note
|
||||
}
|
||||
|
||||
/// The account that decrypted the note.
|
||||
pub account: AccountId,
|
||||
pub fn account(&self) -> &AccountId {
|
||||
&self.account
|
||||
}
|
||||
|
||||
/// The memo bytes included with the note.
|
||||
pub memo: MemoBytes,
|
||||
/// True if this output was recovered using an [`OutgoingViewingKey`], meaning that
|
||||
/// this is a logical output of the transaction.
|
||||
///
|
||||
/// [`OutgoingViewingKey`]: sapling::keys::OutgoingViewingKey
|
||||
pub transfer_type: TransferType,
|
||||
pub fn memo(&self) -> &MemoBytes {
|
||||
&self.memo
|
||||
}
|
||||
|
||||
/// Returns a [`TransferType`] value that is determined based upon what type of key was used to
|
||||
/// decrypt the transaction.
|
||||
pub fn transfer_type(&self) -> TransferType {
|
||||
self.transfer_type
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> DecryptedOutput<sapling::Note, A> {
|
||||
pub fn note_value(&self) -> NonNegativeAmount {
|
||||
NonNegativeAmount::from_u64(self.note.value().inner())
|
||||
.expect("Sapling note value is expected to have been validated by consensus.")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
impl<A> DecryptedOutput<orchard::note::Note, A> {
|
||||
pub fn note_value(&self) -> NonNegativeAmount {
|
||||
NonNegativeAmount::from_u64(self.note.value().inner())
|
||||
.expect("Orchard note value is expected to have been validated by consensus.")
|
||||
}
|
||||
}
|
||||
|
||||
/// Scans a [`Transaction`] for any information that can be decrypted by the set of
|
||||
/// [`UnifiedFullViewingKey`]s.
|
||||
pub fn decrypt_transaction<P: consensus::Parameters, A: Clone>(
|
||||
pub fn decrypt_transaction<'a, P: consensus::Parameters, AccountId: Copy>(
|
||||
params: &P,
|
||||
height: BlockHeight,
|
||||
tx: &Transaction,
|
||||
ufvks: &HashMap<A, UnifiedFullViewingKey>,
|
||||
) -> Vec<DecryptedOutput<sapling::Note, A>> {
|
||||
tx: &'a Transaction,
|
||||
ufvks: &HashMap<AccountId, UnifiedFullViewingKey>,
|
||||
) -> DecryptedTransaction<'a, AccountId> {
|
||||
let zip212_enforcement = zip212_enforcement(params, height);
|
||||
tx.sapling_bundle()
|
||||
let sapling_bundle = tx.sapling_bundle();
|
||||
let sapling_outputs = sapling_bundle
|
||||
.iter()
|
||||
.flat_map(|bundle| {
|
||||
ufvks
|
||||
.iter()
|
||||
.flat_map(move |(account, ufvk)| {
|
||||
ufvk.sapling()
|
||||
.into_iter()
|
||||
.map(|dfvk| (account.to_owned(), dfvk))
|
||||
})
|
||||
.flat_map(move |(account, dfvk)| {
|
||||
.flat_map(|(account, ufvk)| ufvk.sapling().into_iter().map(|dfvk| (*account, dfvk)))
|
||||
.flat_map(|(account, dfvk)| {
|
||||
let sapling_domain = SaplingDomain::new(zip212_enforcement);
|
||||
let ivk_external =
|
||||
PreparedIncomingViewingKey::new(&dfvk.to_ivk(Scope::External));
|
||||
let ivk_internal =
|
||||
|
@ -77,31 +127,101 @@ pub fn decrypt_transaction<P: consensus::Parameters, A: Clone>(
|
|||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(move |(index, output)| {
|
||||
let account = account.clone();
|
||||
try_sapling_note_decryption(&ivk_external, output, zip212_enforcement)
|
||||
try_note_decryption(&sapling_domain, &ivk_external, output)
|
||||
.map(|ret| (ret, TransferType::Incoming))
|
||||
.or_else(|| {
|
||||
try_sapling_note_decryption(
|
||||
&ivk_internal,
|
||||
output,
|
||||
zip212_enforcement,
|
||||
)
|
||||
.map(|ret| (ret, TransferType::WalletInternal))
|
||||
try_note_decryption(&sapling_domain, &ivk_internal, output)
|
||||
.map(|ret| (ret, TransferType::WalletInternal))
|
||||
})
|
||||
.or_else(|| {
|
||||
try_sapling_output_recovery(&ovk, output, zip212_enforcement)
|
||||
.map(|ret| (ret, TransferType::Outgoing))
|
||||
try_output_recovery_with_ovk(
|
||||
&sapling_domain,
|
||||
&ovk,
|
||||
output,
|
||||
output.cv(),
|
||||
output.out_ciphertext(),
|
||||
)
|
||||
.map(|ret| (ret, TransferType::Outgoing))
|
||||
})
|
||||
.into_iter()
|
||||
.map(move |((note, _, memo), transfer_type)| DecryptedOutput {
|
||||
index,
|
||||
note,
|
||||
account: account.clone(),
|
||||
memo: MemoBytes::from_bytes(&memo).expect("correct length"),
|
||||
transfer_type,
|
||||
.map(move |((note, _, memo), transfer_type)| {
|
||||
DecryptedOutput::new(
|
||||
index,
|
||||
note,
|
||||
account,
|
||||
MemoBytes::from_bytes(&memo).expect("correct length"),
|
||||
transfer_type,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
.collect();
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
let orchard_bundle = tx.orchard_bundle();
|
||||
#[cfg(feature = "orchard")]
|
||||
let orchard_outputs = orchard_bundle
|
||||
.iter()
|
||||
.flat_map(|bundle| {
|
||||
ufvks
|
||||
.iter()
|
||||
.flat_map(move |(account, ufvk)| {
|
||||
ufvk.orchard()
|
||||
.into_iter()
|
||||
.map(|fvk| (account.to_owned(), fvk))
|
||||
})
|
||||
.flat_map(move |(account, fvk)| {
|
||||
let ivk_external = orchard::keys::PreparedIncomingViewingKey::new(
|
||||
&fvk.to_ivk(Scope::External),
|
||||
);
|
||||
let ivk_internal = orchard::keys::PreparedIncomingViewingKey::new(
|
||||
&fvk.to_ivk(Scope::Internal),
|
||||
);
|
||||
let ovk = fvk.to_ovk(Scope::External);
|
||||
|
||||
bundle
|
||||
.actions()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(move |(index, action)| {
|
||||
let domain = OrchardDomain::for_nullifier(*action.nullifier());
|
||||
let account = account;
|
||||
try_note_decryption(&domain, &ivk_external, action)
|
||||
.map(|ret| (ret, TransferType::Incoming))
|
||||
.or_else(|| {
|
||||
try_note_decryption(&domain, &ivk_internal, action)
|
||||
.map(|ret| (ret, TransferType::WalletInternal))
|
||||
})
|
||||
.or_else(|| {
|
||||
try_output_recovery_with_ovk(
|
||||
&domain,
|
||||
&ovk,
|
||||
action,
|
||||
action.cv_net(),
|
||||
&action.encrypted_note().out_ciphertext,
|
||||
)
|
||||
.map(|ret| (ret, TransferType::Outgoing))
|
||||
})
|
||||
.into_iter()
|
||||
.map(move |((note, _, memo), transfer_type)| {
|
||||
DecryptedOutput::new(
|
||||
index,
|
||||
note,
|
||||
account,
|
||||
MemoBytes::from_bytes(&memo).expect("correct length"),
|
||||
transfer_type,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
DecryptedTransaction::new(
|
||||
tx,
|
||||
sapling_outputs,
|
||||
#[cfg(feature = "orchard")]
|
||||
orchard_outputs,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ zcash_client_backend = { workspace = true, features = ["unstable-serialization",
|
|||
zcash_encoding.workspace = true
|
||||
zcash_keys = { workspace = true, features = ["orchard", "sapling"] }
|
||||
zcash_primitives.workspace = true
|
||||
zcash_protocol.workspace = true
|
||||
zip32.workspace = true
|
||||
|
||||
# Dependencies exposed in a public API:
|
||||
|
|
|
@ -50,10 +50,7 @@ use zcash_primitives::{
|
|||
block::BlockHash,
|
||||
consensus::{self, BlockHeight},
|
||||
memo::{Memo, MemoBytes},
|
||||
transaction::{
|
||||
components::amount::{Amount, NonNegativeAmount},
|
||||
Transaction, TxId,
|
||||
},
|
||||
transaction::{components::amount::NonNegativeAmount, Transaction, TxId},
|
||||
zip32::{self, DiversifierIndex, Scope},
|
||||
};
|
||||
|
||||
|
@ -78,7 +75,7 @@ use zcash_client_backend::{
|
|||
use crate::{error::SqliteClientError, wallet::commitment_tree::SqliteShardStore};
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
use zcash_client_backend::data_api::ORCHARD_SHARD_HEIGHT;
|
||||
use zcash_client_backend::{data_api::ORCHARD_SHARD_HEIGHT, PoolType};
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
use {
|
||||
|
@ -216,7 +213,7 @@ impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> InputSource for
|
|||
fn select_spendable_notes(
|
||||
&self,
|
||||
account: AccountId,
|
||||
target_value: Amount,
|
||||
target_value: NonNegativeAmount,
|
||||
_sources: &[ShieldedProtocol],
|
||||
anchor_height: BlockHeight,
|
||||
exclude: &[Self::NoteRef],
|
||||
|
@ -439,7 +436,7 @@ impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> WalletRead for W
|
|||
&self,
|
||||
account: AccountId,
|
||||
max_height: BlockHeight,
|
||||
) -> Result<HashMap<TransparentAddress, Amount>, Self::Error> {
|
||||
) -> Result<HashMap<TransparentAddress, NonNegativeAmount>, Self::Error> {
|
||||
wallet::get_transparent_balances(self.conn.borrow(), &self.params, account, max_height)
|
||||
}
|
||||
|
||||
|
@ -665,35 +662,31 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
|||
d_tx: DecryptedTransaction<AccountId>,
|
||||
) -> Result<(), Self::Error> {
|
||||
self.transactionally(|wdb| {
|
||||
let tx_ref = wallet::put_tx_data(wdb.conn.0, d_tx.tx, None, None)?;
|
||||
let tx_ref = wallet::put_tx_data(wdb.conn.0, d_tx.tx(), None, None)?;
|
||||
|
||||
let mut spending_account_id: Option<AccountId> = None;
|
||||
for output in d_tx.sapling_outputs {
|
||||
match output.transfer_type {
|
||||
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())
|
||||
let recipient = if output.transfer_type() == TransferType::Outgoing {
|
||||
//TODO: Recover the UA, if possible.
|
||||
Recipient::Sapling(output.note().recipient())
|
||||
} else {
|
||||
Recipient::InternalAccount(
|
||||
output.account,
|
||||
Note::Sapling(output.note.clone()),
|
||||
*output.account(),
|
||||
Note::Sapling(output.note().clone()),
|
||||
)
|
||||
};
|
||||
|
||||
wallet::put_sent_output(
|
||||
wdb.conn.0,
|
||||
&wdb.params,
|
||||
output.account,
|
||||
*output.account(),
|
||||
tx_ref,
|
||||
output.index,
|
||||
output.index(),
|
||||
&recipient,
|
||||
NonNegativeAmount::from_u64(value.inner()).map_err(|_| {
|
||||
SqliteClientError::CorruptedData(
|
||||
"Note value is not a valid Zcash amount.".to_string(),
|
||||
)
|
||||
})?,
|
||||
Some(&output.memo),
|
||||
output.note_value(),
|
||||
Some(output.memo()),
|
||||
)?;
|
||||
|
||||
if matches!(recipient, Recipient::InternalAccount(_, _)) {
|
||||
|
@ -703,12 +696,12 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
|||
TransferType::Incoming => {
|
||||
match spending_account_id {
|
||||
Some(id) => {
|
||||
if id != output.account {
|
||||
if id != *output.account() {
|
||||
panic!("Unable to determine a unique account identifier for z->t spend.");
|
||||
}
|
||||
}
|
||||
None => {
|
||||
spending_account_id = Some(output.account);
|
||||
spending_account_id = Some(*output.account());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -717,24 +710,80 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
#[allow(unused_assignments)] // Remove this when the todo!()s below are implemented.
|
||||
for output in d_tx.orchard_outputs() {
|
||||
match output.transfer_type() {
|
||||
TransferType::Outgoing | TransferType::WalletInternal => {
|
||||
let recipient = if output.transfer_type() == TransferType::Outgoing {
|
||||
// TODO: Recover the actual UA, if possible.
|
||||
Recipient::Unified(
|
||||
UnifiedAddress::from_receivers(
|
||||
Some(output.note().recipient()),
|
||||
None,
|
||||
None
|
||||
).expect("UA has an Orchard receiver by construction."),
|
||||
PoolType::Shielded(ShieldedProtocol::Orchard)
|
||||
)
|
||||
} else {
|
||||
Recipient::InternalAccount(
|
||||
*output.account(),
|
||||
Note::Orchard(*output.note()),
|
||||
)
|
||||
};
|
||||
|
||||
wallet::put_sent_output(
|
||||
wdb.conn.0,
|
||||
&wdb.params,
|
||||
*output.account(),
|
||||
tx_ref,
|
||||
output.index(),
|
||||
&recipient,
|
||||
output.note_value(),
|
||||
Some(output.memo()),
|
||||
)?;
|
||||
|
||||
if matches!(recipient, Recipient::InternalAccount(_, _)) {
|
||||
todo!();
|
||||
//wallet::orchard::put_received_note(wdb.conn.0, output, tx_ref, None)?;
|
||||
}
|
||||
}
|
||||
TransferType::Incoming => {
|
||||
match spending_account_id {
|
||||
Some(id) => {
|
||||
if id != *output.account() {
|
||||
panic!("Unable to determine a unique account identifier for z->t spend.");
|
||||
}
|
||||
}
|
||||
None => {
|
||||
spending_account_id = Some(*output.account());
|
||||
}
|
||||
}
|
||||
|
||||
todo!()
|
||||
//wallet::orchard::put_received_note(wdb.conn.0, output, tx_ref, None)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If any of the utxos spent in the transaction are ours, mark them as spent.
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
for txin in d_tx.tx.transparent_bundle().iter().flat_map(|b| b.vin.iter()) {
|
||||
for txin in d_tx.tx().transparent_bundle().iter().flat_map(|b| b.vin.iter()) {
|
||||
wallet::mark_transparent_utxo_spent(wdb.conn.0, tx_ref, &txin.prevout)?;
|
||||
}
|
||||
|
||||
// If we have some transparent outputs:
|
||||
if d_tx.tx.transparent_bundle().iter().any(|b| !b.vout.is_empty()) {
|
||||
if d_tx.tx().transparent_bundle().iter().any(|b| !b.vout.is_empty()) {
|
||||
let nullifiers = wdb.get_sapling_nullifiers(NullifierQuery::All)?;
|
||||
// If the transaction contains shielded spends from our wallet, we will store z->t
|
||||
// transactions we observe in the same way they would be stored by
|
||||
// create_spend_to_address.
|
||||
if let Some((account_id, _)) = nullifiers.iter().find(
|
||||
|(_, nf)|
|
||||
d_tx.tx.sapling_bundle().iter().flat_map(|b| b.shielded_spends().iter())
|
||||
d_tx.tx().sapling_bundle().iter().flat_map(|b| b.shielded_spends().iter())
|
||||
.any(|input| nf == input.nullifier())
|
||||
) {
|
||||
for (output_index, txout) in d_tx.tx.transparent_bundle().iter().flat_map(|b| b.vout.iter()).enumerate() {
|
||||
for (output_index, txout) in d_tx.tx().transparent_bundle().iter().flat_map(|b| b.vout.iter()).enumerate() {
|
||||
if let Some(address) = txout.recipient_address() {
|
||||
wallet::put_sent_output(
|
||||
wdb.conn.0,
|
||||
|
@ -759,9 +808,9 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
|||
self.transactionally(|wdb| {
|
||||
let tx_ref = wallet::put_tx_data(
|
||||
wdb.conn.0,
|
||||
sent_tx.tx,
|
||||
Some(sent_tx.fee_amount),
|
||||
Some(sent_tx.created),
|
||||
sent_tx.tx(),
|
||||
Some(sent_tx.fee_amount()),
|
||||
Some(sent_tx.created()),
|
||||
)?;
|
||||
|
||||
// Mark notes as spent.
|
||||
|
@ -772,7 +821,7 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
|||
//
|
||||
// Assumes that create_spend_to_address() will never be called in parallel, which is a
|
||||
// reasonable assumption for a light client such as a mobile phone.
|
||||
if let Some(bundle) = sent_tx.tx.sapling_bundle() {
|
||||
if let Some(bundle) = sent_tx.tx().sapling_bundle() {
|
||||
for spend in bundle.shielded_spends() {
|
||||
wallet::sapling::mark_sapling_note_spent(
|
||||
wdb.conn.0,
|
||||
|
@ -783,16 +832,16 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
|||
}
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
for utxo_outpoint in &sent_tx.utxos_spent {
|
||||
for utxo_outpoint in sent_tx.utxos_spent() {
|
||||
wallet::mark_transparent_utxo_spent(wdb.conn.0, tx_ref, utxo_outpoint)?;
|
||||
}
|
||||
|
||||
for output in &sent_tx.outputs {
|
||||
for output in sent_tx.outputs() {
|
||||
wallet::insert_sent_output(
|
||||
wdb.conn.0,
|
||||
&wdb.params,
|
||||
tx_ref,
|
||||
sent_tx.account,
|
||||
*sent_tx.account_id(),
|
||||
output,
|
||||
)?;
|
||||
|
||||
|
@ -800,15 +849,15 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
|||
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
|
||||
&DecryptedOutput::new(
|
||||
output.output_index(),
|
||||
note.clone(),
|
||||
*account,
|
||||
output
|
||||
.memo()
|
||||
.map_or_else(MemoBytes::empty, |memo| memo.clone()),
|
||||
transfer_type: TransferType::WalletInternal,
|
||||
},
|
||||
TransferType::WalletInternal,
|
||||
),
|
||||
tx_ref,
|
||||
None,
|
||||
)?;
|
||||
|
|
|
@ -1727,7 +1727,7 @@ pub(crate) fn get_transparent_balances<P: consensus::Parameters>(
|
|||
params: &P,
|
||||
account: AccountId,
|
||||
max_height: BlockHeight,
|
||||
) -> Result<HashMap<TransparentAddress, Amount>, SqliteClientError> {
|
||||
) -> Result<HashMap<TransparentAddress, NonNegativeAmount>, SqliteClientError> {
|
||||
let chain_tip_height = scan_queue_extrema(conn)?.map(|range| *range.end());
|
||||
let stable_height = chain_tip_height
|
||||
.unwrap_or(max_height)
|
||||
|
@ -1753,7 +1753,7 @@ pub(crate) fn get_transparent_balances<P: consensus::Parameters>(
|
|||
while let Some(row) = rows.next()? {
|
||||
let taddr_str: String = row.get(0)?;
|
||||
let taddr = TransparentAddress::decode(params, &taddr_str)?;
|
||||
let value = Amount::from_i64(row.get(1)?).unwrap();
|
||||
let value = NonNegativeAmount::from_nonnegative_i64(row.get(1)?)?;
|
||||
|
||||
res.insert(taddr, value);
|
||||
}
|
||||
|
@ -1874,7 +1874,7 @@ pub(crate) fn put_tx_meta(
|
|||
pub(crate) fn put_tx_data(
|
||||
conn: &rusqlite::Connection,
|
||||
tx: &Transaction,
|
||||
fee: Option<Amount>,
|
||||
fee: Option<NonNegativeAmount>,
|
||||
created_at: Option<time::OffsetDateTime>,
|
||||
) -> Result<i64, SqliteClientError> {
|
||||
let mut stmt_upsert_tx_data = conn.prepare_cached(
|
||||
|
@ -1896,7 +1896,7 @@ pub(crate) fn put_tx_data(
|
|||
":created_at": created_at,
|
||||
":expiry_height": u32::from(tx.expiry_height()),
|
||||
":raw": raw_tx,
|
||||
":fee": fee.map(i64::from),
|
||||
":fee": fee.map(u64::from),
|
||||
];
|
||||
|
||||
stmt_upsert_tx_data
|
||||
|
@ -2330,7 +2330,7 @@ mod tests {
|
|||
zcash_primitives::{
|
||||
consensus::BlockHeight,
|
||||
transaction::{
|
||||
components::{Amount, OutPoint, TxOut},
|
||||
components::{OutPoint, TxOut},
|
||||
fees::fixed::FeeRule as FixedFeeRule,
|
||||
},
|
||||
},
|
||||
|
@ -2437,7 +2437,7 @@ mod tests {
|
|||
|
||||
assert_matches!(
|
||||
st.wallet().get_transparent_balances(account_id, height_2),
|
||||
Ok(h) if h.get(taddr) == Some(&value.into())
|
||||
Ok(h) if h.get(taddr) == Some(&value)
|
||||
);
|
||||
|
||||
// Artificially delete the address from the addresses table so that
|
||||
|
@ -2517,8 +2517,8 @@ mod tests {
|
|||
.unwrap()
|
||||
.get(taddr)
|
||||
.cloned()
|
||||
.unwrap_or(Amount::zero()),
|
||||
Amount::from(expected),
|
||||
.unwrap_or(NonNegativeAmount::ZERO),
|
||||
expected,
|
||||
);
|
||||
assert_eq!(
|
||||
st.wallet()
|
||||
|
|
|
@ -290,14 +290,11 @@ mod tests {
|
|||
use rusqlite::{named_params, params, Connection};
|
||||
use tempfile::NamedTempFile;
|
||||
use zcash_client_backend::{
|
||||
data_api::{
|
||||
BlockMetadata, DecryptedTransaction, WalletCommitmentTrees, SAPLING_SHARD_HEIGHT,
|
||||
},
|
||||
data_api::{BlockMetadata, WalletCommitmentTrees, SAPLING_SHARD_HEIGHT},
|
||||
decrypt_transaction,
|
||||
proto::compact_formats::{CompactBlock, CompactTx},
|
||||
scanning::{scan_block, Nullifiers, ScanningKeys},
|
||||
wallet::Recipient,
|
||||
PoolType, ShieldedProtocol, TransferType,
|
||||
TransferType,
|
||||
};
|
||||
use zcash_keys::keys::{UnifiedFullViewingKey, UnifiedSpendingKey};
|
||||
use zcash_primitives::{
|
||||
|
@ -496,34 +493,26 @@ mod tests {
|
|||
|
||||
// We can't use `decrypt_and_store_transaction` because we haven't migrated yet.
|
||||
// Replicate its relevant innards here.
|
||||
let d_tx = DecryptedTransaction {
|
||||
let d_tx = decrypt_transaction(
|
||||
¶ms,
|
||||
height,
|
||||
tx,
|
||||
sapling_outputs: &decrypt_transaction(
|
||||
¶ms,
|
||||
height,
|
||||
tx,
|
||||
&[(account_id, ufvk0)].into_iter().collect(),
|
||||
),
|
||||
};
|
||||
&[(account_id, ufvk0)].into_iter().collect(),
|
||||
);
|
||||
|
||||
db_data
|
||||
.transactionally::<_, _, rusqlite::Error>(|wdb| {
|
||||
let tx_ref = crate::wallet::put_tx_data(wdb.conn.0, d_tx.tx, None, None).unwrap();
|
||||
let tx_ref = crate::wallet::put_tx_data(wdb.conn.0, d_tx.tx(), None, None).unwrap();
|
||||
|
||||
let mut spending_account_id: Option<AccountId> = None;
|
||||
for output in d_tx.sapling_outputs {
|
||||
match output.transfer_type {
|
||||
TransferType::Outgoing | TransferType::WalletInternal => {
|
||||
let recipient = if output.transfer_type == TransferType::Outgoing {
|
||||
Recipient::Sapling(output.note.recipient())
|
||||
} else {
|
||||
Recipient::InternalAccount(
|
||||
output.account,
|
||||
PoolType::Shielded(ShieldedProtocol::Sapling),
|
||||
)
|
||||
};
|
||||
|
||||
// Orchard outputs were not supported as of the wallet states that could require this
|
||||
// migration.
|
||||
for output in d_tx.sapling_outputs() {
|
||||
match output.transfer_type() {
|
||||
TransferType::Outgoing | TransferType::WalletInternal => {
|
||||
// Don't need to bother with sent outputs for this test.
|
||||
if matches!(recipient, Recipient::InternalAccount(_, _)) {
|
||||
if output.transfer_type() != TransferType::Outgoing {
|
||||
put_received_note_before_migration(
|
||||
wdb.conn.0, output, tx_ref, None,
|
||||
)
|
||||
|
@ -532,11 +521,12 @@ mod tests {
|
|||
}
|
||||
TransferType::Incoming => {
|
||||
match spending_account_id {
|
||||
Some(id) => assert_eq!(id, output.account),
|
||||
Some(id) => assert_eq!(id, *output.account()),
|
||||
None => {
|
||||
spending_account_id = Some(output.account);
|
||||
spending_account_id = Some(*output.account());
|
||||
}
|
||||
}
|
||||
|
||||
put_received_note_before_migration(wdb.conn.0, output, tx_ref, None)
|
||||
.unwrap();
|
||||
}
|
||||
|
|
|
@ -100,11 +100,14 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
|||
})?;
|
||||
|
||||
let decrypted_outputs = decrypt_transaction(&self.params, block_height, &tx, &ufvks);
|
||||
for d_out in decrypted_outputs {
|
||||
|
||||
// Orchard outputs were not supported as of the wallet states that could require this
|
||||
// migration.
|
||||
for d_out in decrypted_outputs.sapling_outputs() {
|
||||
stmt_update_sent_memo.execute(named_params![
|
||||
":id_tx": id_tx,
|
||||
":output_index": d_out.index,
|
||||
":memo": memo_repr(Some(&d_out.memo))
|
||||
":output_index": d_out.index(),
|
||||
":memo": memo_repr(Some(d_out.memo()))
|
||||
])?;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,10 +11,10 @@ use zcash_client_backend::{
|
|||
wallet::{Note, ReceivedNote, WalletSaplingOutput},
|
||||
DecryptedOutput, TransferType,
|
||||
};
|
||||
use zcash_primitives::{
|
||||
use zcash_primitives::transaction::{components::amount::NonNegativeAmount, TxId};
|
||||
use zcash_protocol::{
|
||||
consensus::{self, BlockHeight},
|
||||
memo::MemoBytes,
|
||||
transaction::{components::Amount, TxId},
|
||||
};
|
||||
use zip32::Scope;
|
||||
|
||||
|
@ -63,19 +63,19 @@ impl ReceivedSaplingOutput for WalletSaplingOutput<AccountId> {
|
|||
|
||||
impl ReceivedSaplingOutput for DecryptedOutput<sapling::Note, AccountId> {
|
||||
fn index(&self) -> usize {
|
||||
self.index
|
||||
self.index()
|
||||
}
|
||||
fn account_id(&self) -> AccountId {
|
||||
self.account
|
||||
*self.account()
|
||||
}
|
||||
fn note(&self) -> &sapling::Note {
|
||||
&self.note
|
||||
self.note()
|
||||
}
|
||||
fn memo(&self) -> Option<&MemoBytes> {
|
||||
Some(&self.memo)
|
||||
Some(self.memo())
|
||||
}
|
||||
fn is_change(&self) -> bool {
|
||||
self.transfer_type == TransferType::WalletInternal
|
||||
self.transfer_type() == TransferType::WalletInternal
|
||||
}
|
||||
fn nullifier(&self) -> Option<&sapling::Nullifier> {
|
||||
None
|
||||
|
@ -84,7 +84,7 @@ impl ReceivedSaplingOutput for DecryptedOutput<sapling::Note, AccountId> {
|
|||
None
|
||||
}
|
||||
fn recipient_key_scope(&self) -> Option<Scope> {
|
||||
if self.transfer_type == TransferType::WalletInternal {
|
||||
if self.transfer_type() == TransferType::WalletInternal {
|
||||
Some(Scope::Internal)
|
||||
} else {
|
||||
Some(Scope::External)
|
||||
|
@ -231,7 +231,7 @@ pub(crate) fn select_spendable_sapling_notes<P: consensus::Parameters>(
|
|||
conn: &Connection,
|
||||
params: &P,
|
||||
account: AccountId,
|
||||
target_value: Amount,
|
||||
target_value: NonNegativeAmount,
|
||||
anchor_height: BlockHeight,
|
||||
exclude: &[ReceivedNoteId],
|
||||
) -> Result<Vec<ReceivedNote<ReceivedNoteId, Note>>, SqliteClientError> {
|
||||
|
@ -305,7 +305,7 @@ pub(crate) fn select_spendable_sapling_notes<P: consensus::Parameters>(
|
|||
named_params![
|
||||
":account": account.0,
|
||||
":anchor_height": &u32::from(anchor_height),
|
||||
":target_value": &i64::from(target_value),
|
||||
":target_value": &u64::from(target_value),
|
||||
":exclude": &excluded_ptr,
|
||||
":wallet_birthday": u32::from(birthday_height)
|
||||
],
|
||||
|
@ -480,7 +480,7 @@ pub(crate) mod tests {
|
|||
legacy::TransparentAddress,
|
||||
memo::{Memo, MemoBytes},
|
||||
transaction::{
|
||||
components::{amount::NonNegativeAmount, sapling::zip212_enforcement, Amount},
|
||||
components::{amount::NonNegativeAmount, sapling::zip212_enforcement},
|
||||
fees::{
|
||||
fixed::FeeRule as FixedFeeRule, zip317::FeeError as Zip317FeeError, StandardFeeRule,
|
||||
},
|
||||
|
@ -607,16 +607,16 @@ pub(crate) mod tests {
|
|||
let ufvks = [(account, usk.to_unified_full_viewing_key())]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let decrypted_outputs = decrypt_transaction(&st.network(), h + 1, &tx, &ufvks);
|
||||
assert_eq!(decrypted_outputs.len(), 2);
|
||||
let d_tx = decrypt_transaction(&st.network(), h + 1, &tx, &ufvks);
|
||||
assert_eq!(d_tx.sapling_outputs().len(), 2);
|
||||
|
||||
let mut found_tx_change_memo = false;
|
||||
let mut found_tx_empty_memo = false;
|
||||
for output in decrypted_outputs {
|
||||
if output.memo == change_memo.clone().into() {
|
||||
for output in d_tx.sapling_outputs() {
|
||||
if Memo::try_from(output.memo()).unwrap() == change_memo {
|
||||
found_tx_change_memo = true
|
||||
}
|
||||
if output.memo == Memo::Empty.into() {
|
||||
if Memo::try_from(output.memo()).unwrap() == Memo::Empty {
|
||||
found_tx_empty_memo = true
|
||||
}
|
||||
}
|
||||
|
@ -1743,7 +1743,7 @@ pub(crate) mod tests {
|
|||
&st.wallet().conn,
|
||||
&st.wallet().params,
|
||||
account.0,
|
||||
Amount::const_from_i64(300000),
|
||||
NonNegativeAmount::const_from_u64(300000),
|
||||
received_tx_height + 10,
|
||||
&[],
|
||||
)
|
||||
|
@ -1759,7 +1759,7 @@ pub(crate) mod tests {
|
|||
&st.wallet().conn,
|
||||
&st.wallet().params,
|
||||
account.0,
|
||||
Amount::const_from_i64(300000),
|
||||
NonNegativeAmount::const_from_u64(300000),
|
||||
received_tx_height + 10,
|
||||
&[],
|
||||
)
|
||||
|
@ -1813,7 +1813,7 @@ pub(crate) mod tests {
|
|||
&st.wallet().conn,
|
||||
&st.wallet().params,
|
||||
account,
|
||||
Amount::const_from_i64(300000),
|
||||
NonNegativeAmount::const_from_u64(300000),
|
||||
birthday.height() + 5,
|
||||
&[],
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue