diff --git a/zcash_client_backend/CHANGELOG.md b/zcash_client_backend/CHANGELOG.md index f0f3f06a85..d54112ab65 100644 --- a/zcash_client_backend/CHANGELOG.md +++ b/zcash_client_backend/CHANGELOG.md @@ -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(), outputs()}` - `ScannedBlock::orchard` - `ScannedBlockCommitments::orchard` + - `SentTransaction::new` - `ORCHARD_SHARD_HEIGHT` - `BlockMetadata::orchard_tree_size` - `chain::ScanSummary::{spent_orchard_note_count, received_orchard_note_count}` @@ -51,12 +53,23 @@ and this library adheres to Rust's notion of - `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` - `fn with_orchard_tree_mut` - `fn put_orchard_subtree_roots` - Added method `WalletRead::validate_seed` +- `zcash_client_backend::decrypt`: + - Fields of `DecryptedOutput` are now private. Use `DecryptedOutput::new` + and the newly provided accessors instead. + - `decrypt_transaction` now returns a `DecryptedOutput` + instead of a `DecryptedOutput` and will decrypt Orchard + outputs when the `orchard` feature is enabled. In addition, the type + constraint on its `` 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 @@ -69,6 +82,8 @@ and this library adheres to Rust's notion of ### Removed - `zcash_client_backend::PoolType::is_receiver`: use `zcash_keys::Address::has_receiver` instead. +- `zcash_client_backend::DecryptedTransaction::sapling_outputs` use + the `DecryptedTransaction::outputs` method instead. ### Fixed - This release fixes an error in amount parsing in `zip321` that previously diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index 95cb100e68..c5f73612c0 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -872,13 +872,29 @@ impl ScannedBlock { } /// 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>, + tx: &'a Transaction, + outputs: &'a Vec>, +} + +impl<'a, AccountId> DecryptedTransaction<'a, AccountId> { + /// Constructs a new [`DecryptedTransaction`] from its constituent parts. + pub fn new(tx: &'a Transaction, outputs: &'a Vec>) -> Self { + Self { tx, 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 outputs(&self) -> &Vec> { + self.outputs + } } /// A transaction that was constructed and sent by the wallet. @@ -887,13 +903,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>, - pub fee_amount: NonNegativeAmount, + tx: &'a Transaction, + created: time::OffsetDateTime, + account: AccountId, + outputs: Vec>, + fee_amount: NonNegativeAmount, #[cfg(feature = "transparent-inputs")] - pub utxos_spent: Vec, + utxos_spent: Vec, +} + +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>, + fee_amount: NonNegativeAmount, + #[cfg(feature = "transparent-inputs")] utxos_spent: Vec, + ) -> 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] { + 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. diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 813fdec419..bc8f3868f7 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -100,10 +100,10 @@ where .or_else(|| params.activation_height(NetworkUpgrade::Sapling)) .expect("Sapling activation height must be known."); - data.store_decrypted_tx(DecryptedTransaction { + data.store_decrypted_tx(DecryptedTransaction::new( tx, - sapling_outputs: &decrypt_transaction(params, height, tx, &ufvks), - })?; + &decrypt_transaction(params, height, tx, &ufvks), + ))?; Ok(()) } diff --git a/zcash_client_backend/src/decrypt.rs b/zcash_client_backend/src/decrypt.rs index 91a8e2a8ab..0c465f5c12 100644 --- a/zcash_client_backend/src/decrypt.rs +++ b/zcash_client_backend/src/decrypt.rs @@ -1,8 +1,7 @@ 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, @@ -11,7 +10,10 @@ use zcash_primitives::{ zip32::Scope, }; -use crate::keys::UnifiedFullViewingKey; +use crate::{keys::UnifiedFullViewingKey, wallet::Note}; + +#[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,79 +31,180 @@ pub enum TransferType { /// A decrypted shielded output. pub struct DecryptedOutput { - /// 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 DecryptedOutput { + 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 + } + pub fn map_note(&self, f: impl FnOnce(&Note) -> B) -> DecryptedOutput { + DecryptedOutput { + index: self.index, + note: f(&self.note), + account: self.account, + memo: self.memo.clone(), + transfer_type: self.transfer_type, + } + } } /// Scans a [`Transaction`] for any information that can be decrypted by the set of /// [`UnifiedFullViewingKey`]s. -pub fn decrypt_transaction( +pub fn decrypt_transaction( params: &P, height: BlockHeight, tx: &Transaction, ufvks: &HashMap, -) -> Vec> { +) -> Vec> { let zip212_enforcement = zip212_enforcement(params, height); - tx.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)| { - let ivk_external = - PreparedIncomingViewingKey::new(&dfvk.to_ivk(Scope::External)); - let ivk_internal = - PreparedIncomingViewingKey::new(&dfvk.to_ivk(Scope::Internal)); - let ovk = dfvk.fvk().ovk; - - bundle - .shielded_outputs() - .iter() - .enumerate() - .flat_map(move |(index, output)| { - let account = account.clone(); - try_sapling_note_decryption(&ivk_external, output, zip212_enforcement) - .map(|ret| (ret, TransferType::Incoming)) - .or_else(|| { - try_sapling_note_decryption( - &ivk_internal, - output, - zip212_enforcement, - ) + let sapling_bundle = tx.sapling_bundle(); + let iter = sapling_bundle.iter().flat_map(|bundle| { + ufvks + .iter() + .flat_map(|(account, ufvk)| { + ufvk.sapling() + .into_iter() + .map(|dfvk| (account.clone(), 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 = PreparedIncomingViewingKey::new(&dfvk.to_ivk(Scope::Internal)); + let ovk = dfvk.fvk().ovk; + + bundle + .shielded_outputs() + .iter() + .enumerate() + .flat_map(move |(index, output)| { + try_note_decryption(&sapling_domain, &ivk_external, output) + .map(|ret| (ret, TransferType::Incoming)) + .or_else(|| { + try_note_decryption(&sapling_domain, &ivk_internal, output) + .map(|ret| (ret, TransferType::WalletInternal)) + }) + .or_else(|| { + 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::new( + index, + Note::Sapling(note), + account.clone(), + MemoBytes::from_bytes(&memo).expect("correct length"), + transfer_type, + ) + }) + }) + }) + }); + + #[cfg(feature = "orchard")] + let orchard_bundle = tx.orchard_bundle(); + #[cfg(feature = "orchard")] + let iter = iter.chain(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.clone(); + 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_sapling_output_recovery(&ovk, output, zip212_enforcement) - .map(|ret| (ret, TransferType::Outgoing)) - }) - .into_iter() - .map(move |((note, _, memo), transfer_type)| DecryptedOutput { + }) + .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: account.clone(), - memo: MemoBytes::from_bytes(&memo).expect("correct length"), + Note::Orchard(note), + account.clone(), + MemoBytes::from_bytes(&memo).expect("correct length"), transfer_type, - }) - }) - }) - }) - .collect() + ) + }) + }) + }) + })); + + iter.collect() } diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 7dde0e07fe..1a09ab6082 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -74,7 +74,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 { @@ -628,76 +628,100 @@ impl WalletWrite for WalletDb d_tx: DecryptedTransaction, ) -> 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 = None; - for output in d_tx.sapling_outputs { - match output.transfer_type { + for output in d_tx.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 { + match output.note() { + Note::Sapling(n) => { + //TODO: Recover the UA, if possible. + Recipient::Sapling(n.recipient()) + } + #[cfg(feature = "orchard")] + Note::Orchard(n) => { + // TODO: Recover the actual UA, if possible. + Recipient::Unified( + UnifiedAddress::from_receivers( + Some(n.recipient()), + None, + None + ).expect("UA has an Orchard receiver by construction."), + PoolType::Shielded(ShieldedProtocol::Orchard) + ) + } + } } else { Recipient::InternalAccount( - output.account, - Note::Sapling(output.note.clone()), + *output.account(), + 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(_, _)) { - wallet::sapling::put_received_note(wdb.conn.0, output, tx_ref, None)?; + match output.note() { + Note::Sapling(n) => { + wallet::sapling::put_received_note(wdb.conn.0, &output.map_note(|_| n.clone()), tx_ref, None)?; + } + #[cfg(feature = "orchard")] + Note::Orchard(_) => todo!(), + } } } 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()); } } - wallet::sapling::put_received_note(wdb.conn.0, output, tx_ref, None)?; + match output.note() { + Note::Sapling(n) => { + wallet::sapling::put_received_note(wdb.conn.0, &output.map_note(|_| n.clone()), tx_ref, None)?; + } + #[cfg(feature = "orchard")] + Note::Orchard(_) => todo!(), + } } } } // 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, @@ -722,9 +746,9 @@ impl WalletWrite for WalletDb 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. @@ -735,7 +759,7 @@ impl WalletWrite for WalletDb // // 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, @@ -746,16 +770,16 @@ impl WalletWrite for WalletDb } #[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, )?; @@ -763,15 +787,15 @@ impl WalletWrite for WalletDb 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, )?; diff --git a/zcash_client_sqlite/src/wallet/init/migrations/receiving_key_scopes.rs b/zcash_client_sqlite/src/wallet/init/migrations/receiving_key_scopes.rs index c45697e34b..ff3fa61208 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/receiving_key_scopes.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/receiving_key_scopes.rs @@ -296,8 +296,7 @@ mod tests { 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::{ @@ -494,49 +493,57 @@ 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 outputs = decrypt_transaction( + ¶ms, + height, tx, - sapling_outputs: &decrypt_transaction( - ¶ms, - height, - tx, - &[(AccountId::ZERO, ufvk0)].into_iter().collect(), - ), - }; + &[(AccountId::ZERO, ufvk0)].into_iter().collect(), + ); + + let d_tx = DecryptedTransaction::new(tx, &outputs); 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 = None; - for output in d_tx.sapling_outputs { - match output.transfer_type { + for output in d_tx.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), - ) - }; - // Don't need to bother with sent outputs for this test. - if matches!(recipient, Recipient::InternalAccount(_, _)) { - put_received_note_before_migration( - wdb.conn.0, output, tx_ref, None, - ) - .unwrap(); + if output.transfer_type() != TransferType::Outgoing{ + match output.note() { + zcash_client_backend::wallet::Note::Sapling(n) => { + put_received_note_before_migration( + wdb.conn.0, &output.map_note(|_| n.clone()), tx_ref, None, + ) + .unwrap(); + } + + zcash_client_backend::wallet::Note::Orchard(_) => { + unimplemented!("Orchard was not supported at the time of this migration."); + } + } } } 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()); + } + } + match output.note() { + zcash_client_backend::wallet::Note::Sapling(n) => { + put_received_note_before_migration( + wdb.conn.0, &output.map_note(|_| n.clone()), tx_ref, None, + ) + .unwrap(); + } + + zcash_client_backend::wallet::Note::Orchard(_) => { + unimplemented!("Orchard was not supported at the time of this migration."); } } - put_received_note_before_migration(wdb.conn.0, output, tx_ref, None) - .unwrap(); } } } diff --git a/zcash_client_sqlite/src/wallet/init/migrations/sapling_memo_consistency.rs b/zcash_client_sqlite/src/wallet/init/migrations/sapling_memo_consistency.rs index 07b4faa0a9..2bf8410934 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/sapling_memo_consistency.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/sapling_memo_consistency.rs @@ -103,8 +103,8 @@ impl RusqliteMigration for Migration

{ for d_out in decrypted_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())) ])?; } } diff --git a/zcash_client_sqlite/src/wallet/sapling.rs b/zcash_client_sqlite/src/wallet/sapling.rs index 517ed79759..fd17f12365 100644 --- a/zcash_client_sqlite/src/wallet/sapling.rs +++ b/zcash_client_sqlite/src/wallet/sapling.rs @@ -64,19 +64,19 @@ impl ReceivedSaplingOutput for WalletSaplingOutput { impl ReceivedSaplingOutput for DecryptedOutput { 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 @@ -85,7 +85,7 @@ impl ReceivedSaplingOutput for DecryptedOutput { None } fn recipient_key_scope(&self) -> Option { - if self.transfer_type == TransferType::WalletInternal { + if self.transfer_type() == TransferType::WalletInternal { Some(Scope::Internal) } else { Some(Scope::External) @@ -618,10 +618,10 @@ pub(crate) mod tests { 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() { + 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 } }