diff --git a/Cargo.toml b/Cargo.toml index aeb85816b..d69194a35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,5 +21,5 @@ codegen-units = 1 [patch.crates-io] zcash_encoding = { path = "components/zcash_encoding" } zcash_note_encryption = { path = "components/zcash_note_encryption" } -incrementalmerkletree = { git = "https://github.com/zcash/incrementalmerkletree.git", rev = "62f0c9039b0bee94c16c40c272e19c5922290664" } -orchard = { git = "https://github.com/zcash/orchard.git", rev = "2a4f27c937fbcbdb66163e1bb426ce1fcb5bc4f8" } +incrementalmerkletree = { git = "https://github.com/zcash/incrementalmerkletree.git", rev = "2dbdd345670ea22337a0efa6734272d54551285f" } +orchard = { git = "https://github.com/zcash/orchard.git", rev = "35054e85b85dc144b4572ed0fd57ea164f50c26a" } diff --git a/zcash_client_backend/Cargo.toml b/zcash_client_backend/Cargo.toml index 51f5111a7..5a98291c8 100644 --- a/zcash_client_backend/Cargo.toml +++ b/zcash_client_backend/Cargo.toml @@ -20,6 +20,7 @@ exclude = ["*.proto"] development = ["zcash_proofs"] [dependencies] +incrementalmerkletree = { version = "0.3", features = ["legacy-api"] } zcash_address = { version = "0.2", path = "../components/zcash_address" } zcash_encoding = { version = "0.2", path = "../components/zcash_encoding" } zcash_note_encryption = "0.3" @@ -94,6 +95,7 @@ test-dependencies = [ "proptest", "orchard/test-dependencies", "zcash_primitives/test-dependencies", + "incrementalmerkletree/test-dependencies" ] unstable = ["byteorder"] diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index ae19e4161..1b3dff2a7 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -10,7 +10,7 @@ use zcash_primitives::{ consensus::BlockHeight, legacy::TransparentAddress, memo::{Memo, MemoBytes}, - sapling::{self, Nullifier, PaymentAddress}, + sapling, transaction::{ components::{amount::Amount, OutPoint}, Transaction, TxId, @@ -22,13 +22,18 @@ use crate::{ address::{AddressMetadata, UnifiedAddress}, decrypt::DecryptedOutput, keys::{UnifiedFullViewingKey, UnifiedSpendingKey}, - wallet::{SpendableNote, WalletTransparentOutput, WalletTx}, + wallet::{ReceivedSaplingNote, WalletTransparentOutput, WalletTx}, }; pub mod chain; pub mod error; pub mod wallet; +pub enum NullifierQuery { + Unspent, + All, +} + /// Read-only operations required for light wallet functions. /// /// This trait defines the read-only portion of the storage interface atop which @@ -107,15 +112,15 @@ pub trait WalletRead { .map(|oo| oo.flatten()) } - /// Returns the block height in which the specified transaction was mined, - /// or `Ok(None)` if the transaction is not mined in the main chain. + /// Returns the block height in which the specified transaction was mined, or `Ok(None)` if the + /// transaction is not in the main chain. fn get_tx_height(&self, txid: TxId) -> Result, Self::Error>; /// Returns the most recently generated unified address for the specified account, if the /// account identifier specified refers to a valid account for this wallet. /// - /// This will return `Ok(None)` if the account identifier does not correspond - /// to a known account. + /// This will return `Ok(None)` if the account identifier does not correspond to a known + /// account. fn get_current_address( &self, account: AccountId, @@ -126,26 +131,23 @@ pub trait WalletRead { &self, ) -> Result, Self::Error>; - /// Returns the account id corresponding to a given [`UnifiedFullViewingKey`], - /// if any. + /// Returns the account id corresponding to a given [`UnifiedFullViewingKey`], if any. fn get_account_for_ufvk( &self, ufvk: &UnifiedFullViewingKey, ) -> Result, Self::Error>; - /// Checks whether the specified extended full viewing key is - /// associated with the account. + /// Checks whether the specified extended full viewing key is associated with the account. fn is_valid_account_extfvk( &self, account: AccountId, extfvk: &ExtendedFullViewingKey, ) -> Result; - /// Returns the wallet balance for an account as of the specified block - /// height. + /// Returns the wallet balance for an account as of the specified block height. /// - /// This may be used to obtain a balance that ignores notes that have been - /// received so recently that they are not yet deemed spendable. + /// This may be used to obtain a balance that ignores notes that have been received so recently + /// that they are not yet deemed spendable. fn get_balance_at( &self, account: AccountId, @@ -176,15 +178,13 @@ pub trait WalletRead { block_height: BlockHeight, ) -> Result, Self::Error>; - /// Returns the nullifiers for notes that the wallet is tracking, along with their - /// associated account IDs, that are either unspent or have not yet been confirmed as - /// spent (in that the spending transaction has not yet been included in a block). - fn get_nullifiers(&self) -> Result, Self::Error>; - - /// Returns all nullifiers for notes that the wallet is tracking - /// (including those for notes that have been previously spent), - /// along with the account identifiers with which they are associated. - fn get_all_nullifiers(&self) -> Result, Self::Error>; + /// Returns the nullifiers for notes that the wallet is tracking, along with their associated + /// account IDs, that are either unspent or have not yet been confirmed as spent (in that a + /// spending transaction known to the wallet has not yet been included in a block). + fn get_sapling_nullifiers( + &self, + query: NullifierQuery, + ) -> Result, Self::Error>; /// Return all unspent Sapling notes. fn get_spendable_sapling_notes( @@ -192,17 +192,17 @@ pub trait WalletRead { account: AccountId, anchor_height: BlockHeight, exclude: &[Self::NoteRef], - ) -> Result>, Self::Error>; + ) -> Result>, Self::Error>; - /// Returns a list of spendable Sapling notes sufficient to cover the specified - /// target value, if possible. + /// Returns a list of spendable Sapling notes sufficient to cover the specified target value, + /// if possible. fn select_spendable_sapling_notes( &self, account: AccountId, target_value: Amount, anchor_height: BlockHeight, exclude: &[Self::NoteRef], - ) -> Result>, Self::Error>; + ) -> Result>, Self::Error>; /// Returns the set of all transparent receivers associated with the given account. /// @@ -241,7 +241,7 @@ pub struct PrunedBlock<'a> { pub block_hash: BlockHash, pub block_time: u32, pub commitment_tree: &'a sapling::CommitmentTree, - pub transactions: &'a Vec>, + pub transactions: &'a Vec>, } /// A transaction that was detected during scanning of the blockchain, @@ -286,7 +286,7 @@ pub enum PoolType { #[derive(Debug, Clone)] pub enum Recipient { Transparent(TransparentAddress), - Sapling(PaymentAddress), + Sapling(sapling::PaymentAddress), Unified(UnifiedAddress, PoolType), InternalAccount(AccountId, PoolType), } @@ -395,7 +395,7 @@ pub trait WalletWrite: WalletRead { /// Caches a decrypted transaction in the persistent wallet store. fn store_decrypted_tx( &mut self, - received_tx: &DecryptedTransaction, + received_tx: DecryptedTransaction, ) -> Result; /// Saves information about a transaction that was constructed and sent by the wallet to the @@ -434,7 +434,7 @@ pub mod testing { consensus::{BlockHeight, Network}, legacy::TransparentAddress, memo::Memo, - sapling::{self, Nullifier}, + sapling, transaction::{ components::{Amount, OutPoint}, Transaction, TxId, @@ -445,10 +445,12 @@ pub mod testing { use crate::{ address::{AddressMetadata, UnifiedAddress}, keys::{UnifiedFullViewingKey, UnifiedSpendingKey}, - wallet::{SpendableNote, WalletTransparentOutput}, + wallet::{ReceivedSaplingNote, WalletTransparentOutput}, }; - use super::{DecryptedTransaction, PrunedBlock, SentTransaction, WalletRead, WalletWrite}; + use super::{ + DecryptedTransaction, NullifierQuery, PrunedBlock, SentTransaction, WalletRead, WalletWrite, + }; pub struct MockWalletDb { pub network: Network, @@ -537,11 +539,10 @@ pub mod testing { Ok(Vec::new()) } - fn get_nullifiers(&self) -> Result, Self::Error> { - Ok(Vec::new()) - } - - fn get_all_nullifiers(&self) -> Result, Self::Error> { + fn get_sapling_nullifiers( + &self, + _query: NullifierQuery, + ) -> Result, Self::Error> { Ok(Vec::new()) } @@ -550,7 +551,7 @@ pub mod testing { _account: AccountId, _anchor_height: BlockHeight, _exclude: &[Self::NoteRef], - ) -> Result>, Self::Error> { + ) -> Result>, Self::Error> { Ok(Vec::new()) } @@ -560,7 +561,7 @@ pub mod testing { _target_value: Amount, _anchor_height: BlockHeight, _exclude: &[Self::NoteRef], - ) -> Result>, Self::Error> { + ) -> Result>, Self::Error> { Ok(Vec::new()) } @@ -620,7 +621,7 @@ pub mod testing { fn store_decrypted_tx( &mut self, - _received_tx: &DecryptedTransaction, + _received_tx: DecryptedTransaction, ) -> Result { Ok(TxId::from_bytes([0u8; 32])) } diff --git a/zcash_client_backend/src/data_api/chain.rs b/zcash_client_backend/src/data_api/chain.rs index 1d2edff45..44736228d 100644 --- a/zcash_client_backend/src/data_api/chain.rs +++ b/zcash_client_backend/src/data_api/chain.rs @@ -104,6 +104,8 @@ use crate::{ pub mod error; use error::{ChainError, Error}; +use super::NullifierQuery; + /// This trait provides sequential access to raw blockchain data via a callback-oriented /// API. pub trait BlockSource { @@ -252,7 +254,9 @@ where )?; // Get the nullifiers for the notes we are tracking - let mut nullifiers = data_db.get_nullifiers().map_err(Error::Wallet)?; + let mut nullifiers = data_db + .get_sapling_nullifiers(NullifierQuery::Unspent) + .map_err(Error::Wallet)?; let mut batch_runner = BatchRunner::<_, _, _, ()>::new( 100, diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 3c950d341..d96a47e28 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -28,7 +28,7 @@ use crate::{ decrypt_transaction, fees::{self, ChangeValue, DustOutputPolicy}, keys::UnifiedSpendingKey, - wallet::{OvkPolicy, SpendableNote}, + wallet::{OvkPolicy, ReceivedSaplingNote}, zip321::{self, Payment}, }; @@ -68,7 +68,7 @@ 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 { tx, sapling_outputs: &decrypt_transaction(params, height, tx, &ufvks), })?; @@ -701,7 +701,7 @@ where } fn select_key_for_note( - selected: &SpendableNote, + selected: &ReceivedSaplingNote, extsk: &ExtendedSpendingKey, dfvk: &DiversifiableFullViewingKey, ) -> Option<(sapling::Note, ExtendedSpendingKey, sapling::MerklePath)> { diff --git a/zcash_client_backend/src/data_api/wallet/input_selection.rs b/zcash_client_backend/src/data_api/wallet/input_selection.rs index f9ea49c68..798b83450 100644 --- a/zcash_client_backend/src/data_api/wallet/input_selection.rs +++ b/zcash_client_backend/src/data_api/wallet/input_selection.rs @@ -22,7 +22,7 @@ use crate::{ address::{RecipientAddress, UnifiedAddress}, data_api::WalletRead, fees::{ChangeError, ChangeStrategy, DustOutputPolicy, TransactionBalance}, - wallet::{SpendableNote, WalletTransparentOutput}, + wallet::{ReceivedSaplingNote, WalletTransparentOutput}, zip321::TransactionRequest, }; @@ -68,7 +68,7 @@ impl fmt::Display for InputSelectorError { transaction_request: TransactionRequest, transparent_inputs: Vec, - sapling_inputs: Vec>, + sapling_inputs: Vec>, balance: TransactionBalance, fee_rule: FeeRuleT, target_height: BlockHeight, @@ -85,7 +85,7 @@ impl Proposal { &self.transparent_inputs } /// Returns the Sapling inputs that have been selected to fund the transaction. - pub fn sapling_inputs(&self) -> &[SpendableNote] { + pub fn sapling_inputs(&self) -> &[ReceivedSaplingNote] { &self.sapling_inputs } /// Returns the change outputs to be added to the transaction and the fee to be paid. @@ -336,7 +336,7 @@ where } } - let mut sapling_inputs: Vec> = vec![]; + let mut sapling_inputs: Vec> = vec![]; let mut prior_available = Amount::zero(); let mut amount_required = Amount::zero(); let mut exclude: Vec = vec![]; @@ -425,7 +425,7 @@ where target_height, &transparent_inputs, &Vec::::new(), - &Vec::>::new(), + &Vec::>::new(), &Vec::::new(), &self.dust_output_policy, ); @@ -441,7 +441,7 @@ where target_height, &transparent_inputs, &Vec::::new(), - &Vec::>::new(), + &Vec::>::new(), &Vec::::new(), &self.dust_output_policy, )? diff --git a/zcash_client_backend/src/wallet.rs b/zcash_client_backend/src/wallet.rs index dc63f5fa7..ba58340b3 100644 --- a/zcash_client_backend/src/wallet.rs +++ b/zcash_client_backend/src/wallet.rs @@ -177,7 +177,7 @@ impl WalletSaplingOutput { /// Information about a note that is tracked by the wallet that is available for spending, /// with sufficient information for use in note selection. -pub struct SpendableNote { +pub struct ReceivedSaplingNote { pub note_id: NoteRef, pub diversifier: sapling::Diversifier, pub note_value: Amount, @@ -185,7 +185,7 @@ pub struct SpendableNote { pub witness: sapling::IncrementalWitness, } -impl sapling_fees::InputView for SpendableNote { +impl sapling_fees::InputView for ReceivedSaplingNote { fn note_id(&self) -> &NoteRef { &self.note_id } diff --git a/zcash_client_backend/src/welding_rig.rs b/zcash_client_backend/src/welding_rig.rs index 1906c133e..83e880c3d 100644 --- a/zcash_client_backend/src/welding_rig.rs +++ b/zcash_client_backend/src/welding_rig.rs @@ -91,7 +91,7 @@ impl ScanningKey for DiversifiableFullViewingKey { ) -> Self::Nf { note.nf( key, - u64::try_from(witness.position()) + u64::try_from(witness.tip_position()) .expect("Sapling note commitment tree position must fit into a u64"), ) } diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 24a623b3c..b8c438c74 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -43,7 +43,7 @@ use zcash_primitives::{ consensus::{self, BlockHeight}, legacy::TransparentAddress, memo::{Memo, MemoBytes}, - sapling::{self, Nullifier}, + sapling::{self}, transaction::{ components::{amount::Amount, OutPoint}, Transaction, TxId, @@ -54,12 +54,12 @@ use zcash_primitives::{ use zcash_client_backend::{ address::{AddressMetadata, UnifiedAddress}, data_api::{ - self, chain::BlockSource, DecryptedTransaction, PoolType, PrunedBlock, Recipient, - SentTransaction, WalletRead, WalletWrite, + self, chain::BlockSource, DecryptedTransaction, NullifierQuery, PoolType, PrunedBlock, + Recipient, SentTransaction, WalletRead, WalletWrite, }, keys::{UnifiedFullViewingKey, UnifiedSpendingKey}, proto::compact_formats::CompactBlock, - wallet::{SpendableNote, WalletTransparentOutput}, + wallet::{ReceivedSaplingNote, WalletTransparentOutput}, DecryptedOutput, TransferType, }; @@ -212,12 +212,14 @@ impl WalletRead for WalletDb

{ wallet::get_sapling_witnesses(self, block_height) } - fn get_nullifiers(&self) -> Result, Self::Error> { - wallet::get_sapling_nullifiers(self) - } - - fn get_all_nullifiers(&self) -> Result, Self::Error> { - wallet::get_all_sapling_nullifiers(self) + fn get_sapling_nullifiers( + &self, + query: data_api::NullifierQuery, + ) -> Result, Self::Error> { + match query { + NullifierQuery::Unspent => wallet::get_sapling_nullifiers(self), + NullifierQuery::All => wallet::get_all_sapling_nullifiers(self), + } } fn get_spendable_sapling_notes( @@ -225,7 +227,7 @@ impl WalletRead for WalletDb

{ account: AccountId, anchor_height: BlockHeight, exclude: &[Self::NoteRef], - ) -> Result>, Self::Error> { + ) -> Result>, Self::Error> { wallet::transact::get_spendable_sapling_notes(self, account, anchor_height, exclude) } @@ -235,7 +237,7 @@ impl WalletRead for WalletDb

{ target_value: Amount, anchor_height: BlockHeight, exclude: &[Self::NoteRef], - ) -> Result>, Self::Error> { + ) -> Result>, Self::Error> { wallet::transact::select_spendable_sapling_notes( self, account, @@ -368,12 +370,11 @@ impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> { self.wallet_db.get_witnesses(block_height) } - fn get_nullifiers(&self) -> Result, Self::Error> { - self.wallet_db.get_nullifiers() - } - - fn get_all_nullifiers(&self) -> Result, Self::Error> { - self.wallet_db.get_all_nullifiers() + fn get_sapling_nullifiers( + &self, + query: data_api::NullifierQuery, + ) -> Result, Self::Error> { + self.wallet_db.get_sapling_nullifiers(query) } fn get_spendable_sapling_notes( @@ -381,7 +382,7 @@ impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> { account: AccountId, anchor_height: BlockHeight, exclude: &[Self::NoteRef], - ) -> Result>, Self::Error> { + ) -> Result>, Self::Error> { self.wallet_db .get_spendable_sapling_notes(account, anchor_height, exclude) } @@ -392,7 +393,7 @@ impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> { target_value: Amount, anchor_height: BlockHeight, exclude: &[Self::NoteRef], - ) -> Result>, Self::Error> { + ) -> Result>, Self::Error> { self.wallet_db .select_spendable_sapling_notes(account, target_value, anchor_height, exclude) } @@ -566,7 +567,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { fn store_decrypted_tx( &mut self, - d_tx: &DecryptedTransaction, + d_tx: DecryptedTransaction, ) -> Result { self.transactionally(|up| { let tx_ref = wallet::put_tx_data(up, d_tx.tx, None, None)?; @@ -620,7 +621,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> { // If we have some transparent outputs: if !d_tx.tx.transparent_bundle().iter().any(|b| b.vout.is_empty()) { - let nullifiers = self.wallet_db.get_all_nullifiers()?; + let nullifiers = self.wallet_db.get_sapling_nullifiers(data_api::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. diff --git a/zcash_client_sqlite/src/wallet/transact.rs b/zcash_client_sqlite/src/wallet/transact.rs index cb58e99c2..77a0cf6ed 100644 --- a/zcash_client_sqlite/src/wallet/transact.rs +++ b/zcash_client_sqlite/src/wallet/transact.rs @@ -13,11 +13,11 @@ use zcash_primitives::{ zip32::AccountId, }; -use zcash_client_backend::wallet::SpendableNote; +use zcash_client_backend::wallet::ReceivedSaplingNote; use crate::{error::SqliteClientError, NoteId, WalletDb}; -fn to_spendable_note(row: &Row) -> Result, SqliteClientError> { +fn to_spendable_note(row: &Row) -> Result, SqliteClientError> { let note_id = NoteId::ReceivedNoteId(row.get(0)?); let diversifier = { let d: Vec<_> = row.get(1)?; @@ -53,7 +53,7 @@ fn to_spendable_note(row: &Row) -> Result, SqliteClientErr read_incremental_witness(&d[..])? }; - Ok(SpendableNote { + Ok(ReceivedSaplingNote { note_id, diversifier, note_value, @@ -67,7 +67,7 @@ pub(crate) fn get_spendable_sapling_notes

( account: AccountId, anchor_height: BlockHeight, exclude: &[NoteId], -) -> Result>, SqliteClientError> { +) -> Result>, SqliteClientError> { let mut stmt_select_notes = wdb.conn.prepare( "SELECT id_note, diversifier, value, rcm, witness FROM sapling_received_notes @@ -107,7 +107,7 @@ pub(crate) fn select_spendable_sapling_notes

( target_value: Amount, anchor_height: BlockHeight, exclude: &[NoteId], -) -> Result>, SqliteClientError> { +) -> Result>, SqliteClientError> { // The goal of this SQL statement is to select the oldest notes until the required // value has been reached, and then fetch the witnesses at the desired height for the // selected notes. This is achieved in several steps: diff --git a/zcash_primitives/src/sapling/tree.rs b/zcash_primitives/src/sapling/tree.rs index 53d06a726..5bacb51e2 100644 --- a/zcash_primitives/src/sapling/tree.rs +++ b/zcash_primitives/src/sapling/tree.rs @@ -90,7 +90,7 @@ impl Node { } } -impl incrementalmerkletree::Hashable for Node { +impl Hashable for Node { fn empty_leaf() -> Self { Node { repr: UNCOMMITTED_SAPLING.to_repr(),