diff --git a/zcash_client_backend/CHANGELOG.md b/zcash_client_backend/CHANGELOG.md index 0e94b70de..214bfced1 100644 --- a/zcash_client_backend/CHANGELOG.md +++ b/zcash_client_backend/CHANGELOG.md @@ -35,6 +35,7 @@ and this library adheres to Rust's notion of - `Recipient` - `SentTransactionOutput` - `WalletRead::get_unified_full_viewing_keys` + - `WalletRead::get_account_for_ufvk` - `WalletRead::get_current_address` - `WalletRead::get_all_nullifiers` - `WalletWrite::create_account` @@ -132,6 +133,8 @@ and this library adheres to Rust's notion of - `decode_extended_spending_key` - `decode_extended_full_viewing_key` - `decode_payment_address` +- `data_api::wallet::create_spend_to_address` has been modified to use a unified + spending key rather than a Sapling extended spending key. ### Removed - `zcash_client_backend::data_api`: diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index 8f2040bc0..52e75a64d 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -128,6 +128,13 @@ pub trait WalletRead { &self, ) -> Result, Self::Error>; + /// 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. fn is_valid_account_extfvk( @@ -484,6 +491,13 @@ pub mod testing { Ok(HashMap::new()) } + fn get_account_for_ufvk( + &self, + _ufvk: &UnifiedFullViewingKey, + ) -> Result, Self::Error> { + Ok(None) + } + fn is_valid_account_extfvk( &self, _account: AccountId, diff --git a/zcash_client_backend/src/data_api/error.rs b/zcash_client_backend/src/data_api/error.rs index d116fc849..cc06149fe 100644 --- a/zcash_client_backend/src/data_api/error.rs +++ b/zcash_client_backend/src/data_api/error.rs @@ -24,6 +24,9 @@ pub enum ChainInvalid { #[derive(Debug)] pub enum Error { + /// No account could be found corresponding to a provided spending key. + KeyNotRecognized, + /// No account with the given identifier was found in the wallet. AccountNotFound(AccountId), @@ -90,6 +93,9 @@ impl ChainInvalid { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match &self { + Error::KeyNotRecognized => { + write!(f, "Wallet does not contain an account corresponding to the provided spending key") + } Error::AccountNotFound(account) => { write!(f, "Wallet does not contain account {}", u32::from(*account)) } diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index bfcb81ee2..a63fb240e 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -8,17 +8,10 @@ use zcash_primitives::{ components::{amount::DEFAULT_FEE, Amount}, Transaction, }, - zip32::{AccountId, ExtendedFullViewingKey, ExtendedSpendingKey}, }; #[cfg(feature = "transparent-inputs")] -use { - zcash_address::unified::Typecode, - zcash_primitives::{ - legacy::keys::{self as transparent, IncomingViewingKey}, - sapling::keys::OutgoingViewingKey, - }, -}; +use zcash_primitives::{legacy::keys::IncomingViewingKey, sapling::keys::OutgoingViewingKey}; use crate::{ address::RecipientAddress, @@ -27,6 +20,7 @@ use crate::{ SentTransactionOutput, WalletWrite, }, decrypt_transaction, + keys::UnifiedSpendingKey, wallet::OvkPolicy, zip321::{Payment, TransactionRequest}, }; @@ -129,7 +123,7 @@ where /// }; /// use zcash_proofs::prover::LocalTxProver; /// use zcash_client_backend::{ -/// keys::sapling, +/// keys::UnifiedSpendingKey, /// data_api::{wallet::create_spend_to_address, error::Error, testing}, /// wallet::OvkPolicy, /// }; @@ -148,8 +142,8 @@ where /// }; /// /// let account = AccountId::from(0); -/// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, account); -/// let to = extsk.default_address().1.into(); +/// let usk = UnifiedSpendingKey::from_seed(&Network::TestNetwork, &[0; 32][..], account).unwrap(); +/// let to = usk.to_unified_full_viewing_key().default_address().0.into(); /// /// let mut db_read = testing::MockWalletDb { /// network: Network::TestNetwork @@ -159,8 +153,7 @@ where /// &mut db_read, /// &Network::TestNetwork, /// tx_prover, -/// account, -/// &extsk, +/// &usk, /// &to, /// Amount::from_u64(1).unwrap(), /// None, @@ -176,8 +169,7 @@ pub fn create_spend_to_address( wallet_db: &mut D, params: &P, prover: impl TxProver, - account: AccountId, - extsk: &ExtendedSpendingKey, + usk: &UnifiedSpendingKey, to: &RecipientAddress, amount: Amount, memo: Option, @@ -206,8 +198,7 @@ where wallet_db, params, prover, - extsk, - account, + usk, &req, ovk_policy, min_confirmations, @@ -248,7 +239,7 @@ where /// * `wallet_db`: A read/write reference to the wallet database /// * `params`: Consensus parameters /// * `prover`: The TxProver to use in constructing the shielded transaction. -/// * `extsk`: The extended spending key that controls the funds that will be spent +/// * `usk`: The unified spending key that controls the funds that will be spent /// in the resulting transaction. /// * `account`: The ZIP32 account identifier associated with the extended spending /// key that controls the funds to be used in creating this transaction. This @@ -265,8 +256,7 @@ pub fn spend( wallet_db: &mut D, params: &P, prover: impl TxProver, - extsk: &ExtendedSpendingKey, - account: AccountId, + usk: &UnifiedSpendingKey, request: &TransactionRequest, ovk_policy: OvkPolicy, min_confirmations: u32, @@ -277,12 +267,11 @@ where R: Copy + Debug, D: WalletWrite, { - // Check that the ExtendedSpendingKey we have been given corresponds to the - // ExtendedFullViewingKey for the account we are spending from. - let extfvk = ExtendedFullViewingKey::from(extsk); - if !wallet_db.is_valid_account_extfvk(account, &extfvk)? { - return Err(E::from(Error::InvalidExtSk(account))); - } + let account = wallet_db + .get_account_for_ufvk(&usk.to_unified_full_viewing_key())? + .ok_or(Error::KeyNotRecognized)?; + + let extfvk = usk.sapling().to_extended_full_viewing_key(); // Apply the outgoing viewing key policy. let ovk = match ovk_policy { @@ -335,7 +324,12 @@ where let merkle_path = selected.witness.path().expect("the tree is not empty"); builder - .add_sapling_spend(extsk.clone(), selected.diversifier, note, merkle_path) + .add_sapling_spend( + usk.sapling().clone(), + selected.diversifier, + note, + merkle_path, + ) .map_err(Error::Builder)?; } @@ -452,8 +446,7 @@ pub fn shield_transparent_funds( wallet_db: &mut D, params: &P, prover: impl TxProver, - sk: &transparent::AccountPrivKey, - account: AccountId, + usk: &UnifiedSpendingKey, memo: &MemoBytes, min_confirmations: u32, ) -> Result @@ -463,28 +456,20 @@ where R: Copy + Debug, D: WalletWrite + WalletWriteTransparent, { - // Obtain the UFVK for the specified account & use its internal change address - // as the destination for shielded funds. - let shielding_address = wallet_db - .get_unified_full_viewing_keys() - .and_then(|ufvks| { - ufvks - .get(&account) - .ok_or_else(|| E::from(Error::AccountNotFound(account))) - .and_then(|ufvk| { - // TODO: select the most preferred shielded receiver once we have the ability to - // spend Orchard funds. - ufvk.sapling() - .map(|dfvk| dfvk.change_address().1) - .ok_or_else(|| E::from(Error::KeyNotFound(account, Typecode::Sapling))) - }) - })?; + let account = wallet_db + .get_account_for_ufvk(&usk.to_unified_full_viewing_key())? + .ok_or(Error::KeyNotRecognized)?; + let shielding_address = usk + .sapling() + .to_diversifiable_full_viewing_key() + .change_address() + .1; let (latest_scanned_height, latest_anchor) = wallet_db .get_target_and_anchor_heights(min_confirmations) .and_then(|x| x.ok_or_else(|| Error::ScanRequired.into()))?; - let account_pubkey = sk.to_account_pubkey(); + let account_pubkey = usk.transparent().to_account_pubkey(); let ovk = OutgoingViewingKey(account_pubkey.internal_ovk().as_bytes()); // derive the t-address for the extpubkey at the minimum valid child index @@ -510,7 +495,10 @@ where let mut builder = Builder::new_with_fee(params.clone(), latest_scanned_height, fee); - let secret_key = sk.derive_external_secret_key(child_index).unwrap(); + let secret_key = usk + .transparent() + .derive_external_secret_key(child_index) + .unwrap(); for utxo in &utxos { builder .add_transparent_input(secret_key, utxo.outpoint.clone(), utxo.txout.clone()) diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 19c5e29b3..23c9494cb 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -163,6 +163,13 @@ impl WalletRead for WalletDb

{ wallet::get_unified_full_viewing_keys(self) } + fn get_account_for_ufvk( + &self, + ufvk: &UnifiedFullViewingKey, + ) -> Result, Self::Error> { + wallet::get_account_for_ufvk(self, ufvk) + } + fn get_current_address( &self, account: AccountId, @@ -289,6 +296,13 @@ impl<'a, P: consensus::Parameters> WalletRead for DataConnStmtCache<'a, P> { self.wallet_db.get_unified_full_viewing_keys() } + fn get_account_for_ufvk( + &self, + ufvk: &UnifiedFullViewingKey, + ) -> Result, Self::Error> { + self.wallet_db.get_account_for_ufvk(ufvk) + } + fn get_current_address( &self, account: AccountId, diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index 70fb64814..5e4b23691 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -342,6 +342,25 @@ pub(crate) fn get_unified_full_viewing_keys( Ok(res) } +/// Returns the account id corresponding to a given [`UnifiedFullViewingKey`], +/// if any. +pub(crate) fn get_account_for_ufvk( + wdb: &WalletDb

, + ufvk: &UnifiedFullViewingKey, +) -> Result, SqliteClientError> { + wdb.conn + .query_row( + "SELECT account FROM accounts WHERE ufvk = ?", + [&ufvk.encode(&wdb.params)], + |row| { + let acct: u32 = row.get(0)?; + Ok(AccountId::from(acct)) + }, + ) + .optional() + .map_err(SqliteClientError::from) +} + /// Checks whether the specified [`ExtendedFullViewingKey`] is valid and corresponds to the /// specified account. /// diff --git a/zcash_client_sqlite/src/wallet/transact.rs b/zcash_client_sqlite/src/wallet/transact.rs index bb1e927b3..b57fd6d3e 100644 --- a/zcash_client_sqlite/src/wallet/transact.rs +++ b/zcash_client_sqlite/src/wallet/transact.rs @@ -157,28 +157,25 @@ pub fn select_spendable_sapling_notes

( mod tests { use rusqlite::Connection; use secrecy::Secret; - use std::collections::HashMap; use tempfile::NamedTempFile; use zcash_proofs::prover::LocalTxProver; use zcash_primitives::{ block::BlockHash, - consensus::{BlockHeight, BranchId, Parameters}, + consensus::{BlockHeight, BranchId}, legacy::TransparentAddress, sapling::{note_encryption::try_sapling_output_recovery, prover::TxProver}, transaction::{components::Amount, Transaction}, - zip32::sapling::{ - DiversifiableFullViewingKey, ExtendedFullViewingKey, ExtendedSpendingKey, - }, + zip32::sapling::{ExtendedFullViewingKey, ExtendedSpendingKey}, }; - #[cfg(feature = "transparent-inputs")] - use zcash_primitives::legacy::keys as transparent; - use zcash_client_backend::{ - data_api::{chain::scan_cached_blocks, wallet::create_spend_to_address, WalletRead}, - keys::{sapling, UnifiedFullViewingKey}, + data_api::{ + self, chain::scan_cached_blocks, wallet::create_spend_to_address, WalletRead, + WalletWrite, + }, + keys::UnifiedSpendingKey, wallet::OvkPolicy, }; @@ -187,7 +184,7 @@ mod tests { tests::{self, fake_compact_block, insert_into_cache, network, sapling_activation_height}, wallet::{ get_balance, get_balance_at, - init::{init_accounts_table, init_blocks_table, init_wallet_db}, + init::{init_blocks_table, init_wallet_db}, }, AccountId, BlockDb, DataConnStmtCache, WalletDb, }; @@ -202,132 +199,80 @@ mod tests { } #[test] - fn create_to_address_fails_on_incorrect_extsk() { + fn create_to_address_fails_on_incorrect_usk() { let data_file = NamedTempFile::new().unwrap(); let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap(); init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap(); - let acct0 = AccountId::from(0); + // Add an account to the wallet + let mut ops = db_data.get_update_ops().unwrap(); + let seed = Secret::new([0u8; 32].to_vec()); + let (_, usk) = ops.create_account(&seed).unwrap(); + let dfvk = usk.sapling().to_diversifiable_full_viewing_key(); + let to = dfvk.default_address().1.into(); + + // Create a USK that doesn't exist in the wallet let acct1 = AccountId::from(1); + let usk1 = UnifiedSpendingKey::from_seed(&network(), &[1u8; 32], acct1).unwrap(); - // Add two accounts to the wallet - let extsk0 = sapling::spending_key(&[0u8; 32], network().coin_type(), acct0); - let extsk1 = sapling::spending_key(&[1u8; 32], network().coin_type(), acct1); - let dfvk0 = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk0)); - let dfvk1 = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk1)); - - #[cfg(feature = "transparent-inputs")] - let ufvks = { - let tsk0 = - transparent::AccountPrivKey::from_seed(&network(), &[0u8; 32], acct0).unwrap(); - let tsk1 = - transparent::AccountPrivKey::from_seed(&network(), &[1u8; 32], acct1).unwrap(); - HashMap::from([ - ( - acct0, - UnifiedFullViewingKey::new(Some(tsk0.to_account_pubkey()), Some(dfvk0), None) - .unwrap(), - ), - ( - acct1, - UnifiedFullViewingKey::new(Some(tsk1.to_account_pubkey()), Some(dfvk1), None) - .unwrap(), - ), - ]) - }; - #[cfg(not(feature = "transparent-inputs"))] - let ufvks = HashMap::from([ - ( - acct0, - UnifiedFullViewingKey::new(Some(dfvk0), None).unwrap(), - ), - ( - acct1, - UnifiedFullViewingKey::new(Some(dfvk1), None).unwrap(), - ), - ]); - - init_accounts_table(&db_data, &ufvks).unwrap(); - let to = extsk0.default_address().1.into(); - - // Invalid extsk for the given account should cause an error + // Attempting to spend with a USK that is not in the wallet results in an error let mut db_write = db_data.get_update_ops().unwrap(); - match create_spend_to_address( - &mut db_write, - &tests::network(), - test_prover(), - AccountId::from(0), - &extsk1, - &to, - Amount::from_u64(1).unwrap(), - None, - OvkPolicy::Sender, - 10, - ) { - Ok(_) => panic!("Should have failed"), - Err(e) => assert_eq!(e.to_string(), "Incorrect ExtendedSpendingKey for account 0"), - } - - match create_spend_to_address( - &mut db_write, - &tests::network(), - test_prover(), - AccountId::from(1), - &extsk0, - &to, - Amount::from_u64(1).unwrap(), - None, - OvkPolicy::Sender, - 10, - ) { - Ok(_) => panic!("Should have failed"), - Err(e) => assert_eq!(e.to_string(), "Incorrect ExtendedSpendingKey for account 1"), - } + assert!(matches!( + create_spend_to_address( + &mut db_write, + &tests::network(), + test_prover(), + &usk1, + &to, + Amount::from_u64(1).unwrap(), + None, + OvkPolicy::Sender, + 10, + ), + Err(crate::SqliteClientError::BackendError( + data_api::error::Error::KeyNotRecognized + )) + )); } #[test] fn create_to_address_fails_with_no_blocks() { let data_file = NamedTempFile::new().unwrap(); let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap(); - init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap(); + init_wallet_db(&mut db_data, None).unwrap(); // Add an account to the wallet - let account_id = AccountId::from(0); - let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), account_id); - let dfvk = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk)); - - #[cfg(feature = "transparent-inputs")] - let ufvk = UnifiedFullViewingKey::new(None, Some(dfvk), None).unwrap(); - #[cfg(not(feature = "transparent-inputs"))] - let ufvk = UnifiedFullViewingKey::new(Some(dfvk), None).unwrap(); - let ufvks = HashMap::from([(account_id, ufvk)]); - init_accounts_table(&db_data, &ufvks).unwrap(); - let to = extsk.default_address().1.into(); + let mut ops = db_data.get_update_ops().unwrap(); + let seed = Secret::new([0u8; 32].to_vec()); + let (_, usk) = ops.create_account(&seed).unwrap(); + let dfvk = usk.sapling().to_diversifiable_full_viewing_key(); + let to = dfvk.default_address().1.into(); // We cannot do anything if we aren't synchronised let mut db_write = db_data.get_update_ops().unwrap(); - match create_spend_to_address( - &mut db_write, - &tests::network(), - test_prover(), - AccountId::from(0), - &extsk, - &to, - Amount::from_u64(1).unwrap(), - None, - OvkPolicy::Sender, - 10, - ) { - Ok(_) => panic!("Should have failed"), - Err(e) => assert_eq!(e.to_string(), "Must scan blocks first"), - } + assert!(matches!( + create_spend_to_address( + &mut db_write, + &tests::network(), + test_prover(), + &usk, + &to, + Amount::from_u64(1).unwrap(), + None, + OvkPolicy::Sender, + 10, + ), + Err(crate::SqliteClientError::BackendError( + data_api::error::Error::ScanRequired + )) + )); } #[test] fn create_to_address_fails_on_insufficient_balance() { let data_file = NamedTempFile::new().unwrap(); let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap(); - init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap(); + init_wallet_db(&mut db_data, None).unwrap(); init_blocks_table( &db_data, BlockHeight::from(1u32), @@ -338,16 +283,11 @@ mod tests { .unwrap(); // Add an account to the wallet - let account_id = AccountId::from(0); - let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), account_id); - let dfvk = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk)); - #[cfg(feature = "transparent-inputs")] - let ufvk = UnifiedFullViewingKey::new(None, Some(dfvk), None).unwrap(); - #[cfg(not(feature = "transparent-inputs"))] - let ufvk = UnifiedFullViewingKey::new(Some(dfvk), None).unwrap(); - let ufvks = HashMap::from([(account_id, ufvk)]); - init_accounts_table(&db_data, &ufvks).unwrap(); - let to = extsk.default_address().1.into(); + let mut ops = db_data.get_update_ops().unwrap(); + let seed = Secret::new([0u8; 32].to_vec()); + let (_, usk) = ops.create_account(&seed).unwrap(); + let dfvk = usk.sapling().to_diversifiable_full_viewing_key(); + let to = dfvk.default_address().1.into(); // Account balance should be zero assert_eq!( @@ -357,24 +297,26 @@ mod tests { // We cannot spend anything let mut db_write = db_data.get_update_ops().unwrap(); - match create_spend_to_address( - &mut db_write, - &tests::network(), - test_prover(), - AccountId::from(0), - &extsk, - &to, - Amount::from_u64(1).unwrap(), - None, - OvkPolicy::Sender, - 10, - ) { - Ok(_) => panic!("Should have failed"), - Err(e) => assert_eq!( - e.to_string(), - "Insufficient balance (have 0, need 1001 including fee)" + assert!(matches!( + create_spend_to_address( + &mut db_write, + &tests::network(), + test_prover(), + &usk, + &to, + Amount::from_u64(1).unwrap(), + None, + OvkPolicy::Sender, + 10, ), - } + Err(crate::SqliteClientError::BackendError( + data_api::error::Error::InsufficientBalance( + available, + required + ) + )) + if available == Amount::zero() && required == Amount::from_u64(1001).unwrap() + )); } #[test] @@ -385,18 +327,13 @@ mod tests { let data_file = NamedTempFile::new().unwrap(); let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap(); - init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap(); + init_wallet_db(&mut db_data, None).unwrap(); // Add an account to the wallet - let account_id = AccountId::from(0); - let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), account_id); - let dfvk = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk)); - #[cfg(feature = "transparent-inputs")] - let ufvk = UnifiedFullViewingKey::new(None, Some(dfvk.clone()), None).unwrap(); - #[cfg(not(feature = "transparent-inputs"))] - let ufvk = UnifiedFullViewingKey::new(Some(dfvk.clone()), None).unwrap(); - let ufvks = HashMap::from([(account_id, ufvk)]); - init_accounts_table(&db_data, &ufvks).unwrap(); + let mut ops = db_data.get_update_ops().unwrap(); + let seed = Secret::new([0u8; 32].to_vec()); + let (_, usk) = ops.create_account(&seed).unwrap(); + let dfvk = usk.sapling().to_diversifiable_full_viewing_key(); // Add funds to the wallet in a single note let value = Amount::from_u64(50000).unwrap(); @@ -437,24 +374,27 @@ mod tests { // Spend fails because there are insufficient verified notes let extsk2 = ExtendedSpendingKey::master(&[]); let to = extsk2.default_address().1.into(); - match create_spend_to_address( - &mut db_write, - &tests::network(), - test_prover(), - AccountId::from(0), - &extsk, - &to, - Amount::from_u64(70000).unwrap(), - None, - OvkPolicy::Sender, - 10, - ) { - Ok(_) => panic!("Should have failed"), - Err(e) => assert_eq!( - e.to_string(), - "Insufficient balance (have 50000, need 71000 including fee)" + assert!(matches!( + create_spend_to_address( + &mut db_write, + &tests::network(), + test_prover(), + &usk, + &to, + Amount::from_u64(70000).unwrap(), + None, + OvkPolicy::Sender, + 10, ), - } + Err(crate::SqliteClientError::BackendError( + data_api::error::Error::InsufficientBalance( + available, + required + ) + )) + if available == Amount::from_u64(50000).unwrap() + && required == Amount::from_u64(71000).unwrap() + )); // Mine blocks SAPLING_ACTIVATION_HEIGHT + 2 to 9 until just before the second // note is verified @@ -466,24 +406,27 @@ mod tests { scan_cached_blocks(&tests::network(), &db_cache, &mut db_write, None).unwrap(); // Second spend still fails - match create_spend_to_address( - &mut db_write, - &tests::network(), - test_prover(), - AccountId::from(0), - &extsk, - &to, - Amount::from_u64(70000).unwrap(), - None, - OvkPolicy::Sender, - 10, - ) { - Ok(_) => panic!("Should have failed"), - Err(e) => assert_eq!( - e.to_string(), - "Insufficient balance (have 50000, need 71000 including fee)" + assert!(matches!( + create_spend_to_address( + &mut db_write, + &tests::network(), + test_prover(), + &usk, + &to, + Amount::from_u64(70000).unwrap(), + None, + OvkPolicy::Sender, + 10, ), - } + Err(crate::SqliteClientError::BackendError( + data_api::error::Error::InsufficientBalance( + available, + required + ) + )) + if available == Amount::from_u64(50000).unwrap() + && required == Amount::from_u64(71000).unwrap() + )); // Mine block 11 so that the second note becomes verified let (cb, _) = fake_compact_block(sapling_activation_height() + 10, cb.hash(), &dfvk, value); @@ -491,19 +434,20 @@ mod tests { scan_cached_blocks(&tests::network(), &db_cache, &mut db_write, None).unwrap(); // Second spend should now succeed - create_spend_to_address( - &mut db_write, - &tests::network(), - test_prover(), - AccountId::from(0), - &extsk, - &to, - Amount::from_u64(70000).unwrap(), - None, - OvkPolicy::Sender, - 10, - ) - .unwrap(); + assert!(matches!( + create_spend_to_address( + &mut db_write, + &tests::network(), + test_prover(), + &usk, + &to, + Amount::from_u64(70000).unwrap(), + None, + OvkPolicy::Sender, + 10, + ), + Ok(_) + )); } #[test] @@ -517,15 +461,10 @@ mod tests { init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap(); // Add an account to the wallet - let account_id = AccountId::from(0); - let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), account_id); - let dfvk = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk)); - #[cfg(feature = "transparent-inputs")] - let ufvk = UnifiedFullViewingKey::new(None, Some(dfvk.clone()), None).unwrap(); - #[cfg(not(feature = "transparent-inputs"))] - let ufvk = UnifiedFullViewingKey::new(Some(dfvk.clone()), None).unwrap(); - let ufvks = HashMap::from([(account_id, ufvk)]); - init_accounts_table(&db_data, &ufvks).unwrap(); + let mut ops = db_data.get_update_ops().unwrap(); + let seed = Secret::new([0u8; 32].to_vec()); + let (_, usk) = ops.create_account(&seed).unwrap(); + let dfvk = usk.sapling().to_diversifiable_full_viewing_key(); // Add funds to the wallet in a single note let value = Amount::from_u64(50000).unwrap(); @@ -543,39 +482,42 @@ mod tests { // Send some of the funds to another address let extsk2 = ExtendedSpendingKey::master(&[]); let to = extsk2.default_address().1.into(); - create_spend_to_address( - &mut db_write, - &tests::network(), - test_prover(), - AccountId::from(0), - &extsk, - &to, - Amount::from_u64(15000).unwrap(), - None, - OvkPolicy::Sender, - 10, - ) - .unwrap(); + assert!(matches!( + create_spend_to_address( + &mut db_write, + &tests::network(), + test_prover(), + &usk, + &to, + Amount::from_u64(15000).unwrap(), + None, + OvkPolicy::Sender, + 10, + ), + Ok(_) + )); // A second spend fails because there are no usable notes - match create_spend_to_address( - &mut db_write, - &tests::network(), - test_prover(), - AccountId::from(0), - &extsk, - &to, - Amount::from_u64(2000).unwrap(), - None, - OvkPolicy::Sender, - 10, - ) { - Ok(_) => panic!("Should have failed"), - Err(e) => assert_eq!( - e.to_string(), - "Insufficient balance (have 0, need 3000 including fee)" + assert!(matches!( + create_spend_to_address( + &mut db_write, + &tests::network(), + test_prover(), + &usk, + &to, + Amount::from_u64(2000).unwrap(), + None, + OvkPolicy::Sender, + 10, ), - } + Err(crate::SqliteClientError::BackendError( + data_api::error::Error::InsufficientBalance( + available, + required + ) + )) + if available == Amount::zero() && required == Amount::from_u64(3000).unwrap() + )); // Mine blocks SAPLING_ACTIVATION_HEIGHT + 1 to 21 (that don't send us funds) // until just before the first transaction expires @@ -591,24 +533,26 @@ mod tests { scan_cached_blocks(&tests::network(), &db_cache, &mut db_write, None).unwrap(); // Second spend still fails - match create_spend_to_address( - &mut db_write, - &tests::network(), - test_prover(), - AccountId::from(0), - &extsk, - &to, - Amount::from_u64(2000).unwrap(), - None, - OvkPolicy::Sender, - 10, - ) { - Ok(_) => panic!("Should have failed"), - Err(e) => assert_eq!( - e.to_string(), - "Insufficient balance (have 0, need 3000 including fee)" + assert!(matches!( + create_spend_to_address( + &mut db_write, + &tests::network(), + test_prover(), + &usk, + &to, + Amount::from_u64(2000).unwrap(), + None, + OvkPolicy::Sender, + 10, ), - } + Err(crate::SqliteClientError::BackendError( + data_api::error::Error::InsufficientBalance( + available, + required + ) + )) + if available == Amount::zero() && required == Amount::from_u64(3000).unwrap() + )); // Mine block SAPLING_ACTIVATION_HEIGHT + 22 so that the first transaction expires let (cb, _) = fake_compact_block( @@ -625,8 +569,7 @@ mod tests { &mut db_write, &tests::network(), test_prover(), - AccountId::from(0), - &extsk, + &usk, &to, Amount::from_u64(2000).unwrap(), None, @@ -645,18 +588,13 @@ mod tests { let data_file = NamedTempFile::new().unwrap(); let mut db_data = WalletDb::for_path(data_file.path(), network).unwrap(); - init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap(); + init_wallet_db(&mut db_data, None).unwrap(); // Add an account to the wallet - let account_id = AccountId::from(0); - let extsk = sapling::spending_key(&[0u8; 32], network.coin_type(), account_id); - let dfvk = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk)); - #[cfg(feature = "transparent-inputs")] - let ufvk = UnifiedFullViewingKey::new(None, Some(dfvk.clone()), None).unwrap(); - #[cfg(not(feature = "transparent-inputs"))] - let ufvk = UnifiedFullViewingKey::new(Some(dfvk.clone()), None).unwrap(); - let ufvks = HashMap::from([(account_id, ufvk)]); - init_accounts_table(&db_data, &ufvks).unwrap(); + let mut ops = db_data.get_update_ops().unwrap(); + let seed = Secret::new([0u8; 32].to_vec()); + let (_, usk) = ops.create_account(&seed).unwrap(); + let dfvk = usk.sapling().to_diversifiable_full_viewing_key(); // Add funds to the wallet in a single note let value = Amount::from_u64(50000).unwrap(); @@ -680,8 +618,7 @@ mod tests { db_write, &tests::network(), test_prover(), - AccountId::from(0), - &extsk, + &usk, &to, Amount::from_u64(15000).unwrap(), None, @@ -757,18 +694,13 @@ mod tests { let data_file = NamedTempFile::new().unwrap(); let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap(); - init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap(); + init_wallet_db(&mut db_data, None).unwrap(); // Add an account to the wallet - let account_id = AccountId::from(0); - let extsk = sapling::spending_key(&[0u8; 32], network().coin_type(), account_id); - let dfvk = DiversifiableFullViewingKey::from(ExtendedFullViewingKey::from(&extsk)); - #[cfg(feature = "transparent-inputs")] - let ufvk = UnifiedFullViewingKey::new(None, Some(dfvk.clone()), None).unwrap(); - #[cfg(not(feature = "transparent-inputs"))] - let ufvk = UnifiedFullViewingKey::new(Some(dfvk.clone()), None).unwrap(); - let ufvks = HashMap::from([(account_id, ufvk)]); - init_accounts_table(&db_data, &ufvks).unwrap(); + let mut ops = db_data.get_update_ops().unwrap(); + let seed = Secret::new([0u8; 32].to_vec()); + let (_, usk) = ops.create_account(&seed).unwrap(); + let dfvk = usk.sapling().to_diversifiable_full_viewing_key(); // Add funds to the wallet in a single note let value = Amount::from_u64(51000).unwrap(); @@ -791,18 +723,19 @@ mod tests { ); let to = TransparentAddress::PublicKey([7; 20]).into(); - create_spend_to_address( - &mut db_write, - &tests::network(), - test_prover(), - AccountId::from(0), - &extsk, - &to, - Amount::from_u64(50000).unwrap(), - None, - OvkPolicy::Sender, - 10, - ) - .unwrap(); + assert!(matches!( + create_spend_to_address( + &mut db_write, + &tests::network(), + test_prover(), + &usk, + &to, + Amount::from_u64(50000).unwrap(), + None, + OvkPolicy::Sender, + 10, + ), + Ok(_) + )); } }