diff --git a/zcash_client_sqlite/src/chain.rs b/zcash_client_sqlite/src/chain.rs index c3f3ad093..6a2ae177e 100644 --- a/zcash_client_sqlite/src/chain.rs +++ b/zcash_client_sqlite/src/chain.rs @@ -236,7 +236,7 @@ mod tests { error::SqliteClientError, tests::{ self, fake_compact_block, fake_compact_block_spending, init_test_accounts_table, - insert_into_cache, sapling_activation_height, + insert_into_cache, sapling_activation_height, AddressType, }, wallet::{get_balance, init::init_wallet_db, rewind_to_height}, AccountId, BlockDb, NoteId, WalletDb, @@ -268,6 +268,7 @@ mod tests { sapling_activation_height(), BlockHash([0; 32]), &dfvk, + AddressType::DefaultExternal, Amount::from_u64(5).unwrap(), ); insert_into_cache(&db_cache, &cb); @@ -297,6 +298,7 @@ mod tests { sapling_activation_height() + 1, cb.hash(), &dfvk, + AddressType::DefaultExternal, Amount::from_u64(7).unwrap(), ); insert_into_cache(&db_cache, &cb2); @@ -339,12 +341,14 @@ mod tests { sapling_activation_height(), BlockHash([0; 32]), &dfvk, + AddressType::DefaultExternal, Amount::from_u64(5).unwrap(), ); let (cb2, _) = fake_compact_block( sapling_activation_height() + 1, cb.hash(), &dfvk, + AddressType::DefaultExternal, Amount::from_u64(7).unwrap(), ); insert_into_cache(&db_cache, &cb); @@ -367,12 +371,14 @@ mod tests { sapling_activation_height() + 2, BlockHash([1; 32]), &dfvk, + AddressType::DefaultExternal, Amount::from_u64(8).unwrap(), ); let (cb4, _) = fake_compact_block( sapling_activation_height() + 3, cb3.hash(), &dfvk, + AddressType::DefaultExternal, Amount::from_u64(3).unwrap(), ); insert_into_cache(&db_cache, &cb3); @@ -409,12 +415,14 @@ mod tests { sapling_activation_height(), BlockHash([0; 32]), &dfvk, + AddressType::DefaultExternal, Amount::from_u64(5).unwrap(), ); let (cb2, _) = fake_compact_block( sapling_activation_height() + 1, cb.hash(), &dfvk, + AddressType::DefaultExternal, Amount::from_u64(7).unwrap(), ); insert_into_cache(&db_cache, &cb); @@ -437,12 +445,14 @@ mod tests { sapling_activation_height() + 2, cb2.hash(), &dfvk, + AddressType::DefaultExternal, Amount::from_u64(8).unwrap(), ); let (cb4, _) = fake_compact_block( sapling_activation_height() + 3, BlockHash([1; 32]), &dfvk, + AddressType::DefaultExternal, Amount::from_u64(3).unwrap(), ); insert_into_cache(&db_cache, &cb3); @@ -487,11 +497,17 @@ mod tests { sapling_activation_height(), BlockHash([0; 32]), &dfvk, + AddressType::DefaultExternal, value, ); - let (cb2, _) = - fake_compact_block(sapling_activation_height() + 1, cb.hash(), &dfvk, value2); + let (cb2, _) = fake_compact_block( + sapling_activation_height() + 1, + cb.hash(), + &dfvk, + AddressType::DefaultExternal, + value2, + ); insert_into_cache(&db_cache, &cb); insert_into_cache(&db_cache, &cb2); @@ -549,6 +565,7 @@ mod tests { sapling_activation_height(), BlockHash([0; 32]), &dfvk, + AddressType::DefaultExternal, value, ); insert_into_cache(&db_cache, &cb1); @@ -557,10 +574,20 @@ mod tests { assert_eq!(get_balance(&db_data, AccountId::from(0)).unwrap(), value); // We cannot scan a block of height SAPLING_ACTIVATION_HEIGHT + 2 next - let (cb2, _) = - fake_compact_block(sapling_activation_height() + 1, cb1.hash(), &dfvk, value); - let (cb3, _) = - fake_compact_block(sapling_activation_height() + 2, cb2.hash(), &dfvk, value); + let (cb2, _) = fake_compact_block( + sapling_activation_height() + 1, + cb1.hash(), + &dfvk, + AddressType::DefaultExternal, + value, + ); + let (cb3, _) = fake_compact_block( + sapling_activation_height() + 2, + cb2.hash(), + &dfvk, + AddressType::DefaultExternal, + value, + ); insert_into_cache(&db_cache, &cb3); match scan_cached_blocks(&tests::network(), &db_cache, &mut db_write, None) { Err(SqliteClientError::BackendError(e)) => { @@ -610,6 +637,7 @@ mod tests { sapling_activation_height(), BlockHash([0; 32]), &dfvk, + AddressType::DefaultExternal, value, ); insert_into_cache(&db_cache, &cb); @@ -623,8 +651,13 @@ mod tests { // Create a second fake CompactBlock sending more value to the address let value2 = Amount::from_u64(7).unwrap(); - let (cb2, _) = - fake_compact_block(sapling_activation_height() + 1, cb.hash(), &dfvk, value2); + let (cb2, _) = fake_compact_block( + sapling_activation_height() + 1, + cb.hash(), + &dfvk, + AddressType::DefaultExternal, + value2, + ); insert_into_cache(&db_cache, &cb2); // Scan the cache again @@ -662,6 +695,7 @@ mod tests { sapling_activation_height(), BlockHash([0; 32]), &dfvk, + AddressType::DefaultExternal, value, ); insert_into_cache(&db_cache, &cb); diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 57363b4e8..1c2fa0e52 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -933,7 +933,7 @@ mod tests { PaymentAddress, }, transaction::components::Amount, - zip32::sapling::DiversifiableFullViewingKey, + zip32::{sapling::DiversifiableFullViewingKey, DiversifierIndex}, }; use zcash_client_backend::{ @@ -1018,15 +1018,27 @@ mod tests { (ufvk, taddr) } + #[allow(dead_code)] + pub(crate) enum AddressType { + DefaultExternal, + DiversifiedExternal(DiversifierIndex), + Internal, + } + /// Create a fake CompactBlock at the given height, containing a single output paying - /// the given address. Returns the CompactBlock and the nullifier for the new note. + /// an address. Returns the CompactBlock and the nullifier for the new note. pub(crate) fn fake_compact_block( height: BlockHeight, prev_hash: BlockHash, dfvk: &DiversifiableFullViewingKey, + req: AddressType, value: Amount, ) -> (CompactBlock, Nullifier) { - let to = dfvk.default_address().1; + let to = match req { + AddressType::DefaultExternal => dfvk.default_address().1, + AddressType::DiversifiedExternal(idx) => dfvk.find_address(idx).unwrap().1, + AddressType::Internal => dfvk.change_address().1, + }; // Create a fake Note for the account let mut rng = OsRng; @@ -1262,6 +1274,7 @@ mod tests { sapling_activation_height(), BlockHash([0; 32]), &dfvk, + AddressType::DefaultExternal, value, ); insert_into_cache(&db_cache, &cb); diff --git a/zcash_client_sqlite/src/wallet/transact.rs b/zcash_client_sqlite/src/wallet/transact.rs index 923bf14f0..ba386bcd0 100644 --- a/zcash_client_sqlite/src/wallet/transact.rs +++ b/zcash_client_sqlite/src/wallet/transact.rs @@ -181,7 +181,10 @@ mod tests { use crate::{ chain::init::init_cache_database, - tests::{self, fake_compact_block, insert_into_cache, network, sapling_activation_height}, + tests::{ + self, fake_compact_block, insert_into_cache, network, sapling_activation_height, + AddressType, + }, wallet::{ get_balance, get_balance_at, init::{init_blocks_table, init_wallet_db}, @@ -341,6 +344,7 @@ mod tests { sapling_activation_height(), BlockHash([0; 32]), &dfvk, + AddressType::DefaultExternal, value, ); insert_into_cache(&db_cache, &cb); @@ -356,7 +360,13 @@ mod tests { ); // Add more funds to the wallet in a second note - let (cb, _) = fake_compact_block(sapling_activation_height() + 1, cb.hash(), &dfvk, value); + let (cb, _) = fake_compact_block( + sapling_activation_height() + 1, + cb.hash(), + &dfvk, + AddressType::DefaultExternal, + value, + ); insert_into_cache(&db_cache, &cb); scan_cached_blocks(&tests::network(), &db_cache, &mut db_write, None).unwrap(); @@ -399,8 +409,13 @@ mod tests { // Mine blocks SAPLING_ACTIVATION_HEIGHT + 2 to 9 until just before the second // note is verified for i in 2..10 { - let (cb, _) = - fake_compact_block(sapling_activation_height() + i, cb.hash(), &dfvk, value); + let (cb, _) = fake_compact_block( + sapling_activation_height() + i, + cb.hash(), + &dfvk, + AddressType::DefaultExternal, + value, + ); insert_into_cache(&db_cache, &cb); } scan_cached_blocks(&tests::network(), &db_cache, &mut db_write, None).unwrap(); @@ -429,7 +444,13 @@ mod tests { )); // Mine block 11 so that the second note becomes verified - let (cb, _) = fake_compact_block(sapling_activation_height() + 10, cb.hash(), &dfvk, value); + let (cb, _) = fake_compact_block( + sapling_activation_height() + 10, + cb.hash(), + &dfvk, + AddressType::DefaultExternal, + value, + ); insert_into_cache(&db_cache, &cb); scan_cached_blocks(&tests::network(), &db_cache, &mut db_write, None).unwrap(); @@ -472,6 +493,7 @@ mod tests { sapling_activation_height(), BlockHash([0; 32]), &dfvk, + AddressType::DefaultExternal, value, ); insert_into_cache(&db_cache, &cb); @@ -526,6 +548,7 @@ mod tests { sapling_activation_height() + i, cb.hash(), &ExtendedSpendingKey::master(&[i as u8]).to_diversifiable_full_viewing_key(), + AddressType::DefaultExternal, value, ); insert_into_cache(&db_cache, &cb); @@ -559,6 +582,7 @@ mod tests { sapling_activation_height() + 22, cb.hash(), &ExtendedSpendingKey::master(&[22]).to_diversifiable_full_viewing_key(), + AddressType::DefaultExternal, value, ); insert_into_cache(&db_cache, &cb); @@ -602,6 +626,7 @@ mod tests { sapling_activation_height(), BlockHash([0; 32]), &dfvk, + AddressType::DefaultExternal, value, ); insert_into_cache(&db_cache, &cb); @@ -675,6 +700,7 @@ mod tests { sapling_activation_height() + i, cb.hash(), &ExtendedSpendingKey::master(&[i as u8]).to_diversifiable_full_viewing_key(), + AddressType::DefaultExternal, value, ); insert_into_cache(&db_cache, &cb); @@ -708,6 +734,61 @@ mod tests { sapling_activation_height(), BlockHash([0; 32]), &dfvk, + AddressType::DefaultExternal, + value, + ); + insert_into_cache(&db_cache, &cb); + let mut db_write = db_data.get_update_ops().unwrap(); + scan_cached_blocks(&tests::network(), &db_cache, &mut db_write, None).unwrap(); + + // Verified balance matches total balance + let (_, anchor_height) = db_data.get_target_and_anchor_heights(10).unwrap().unwrap(); + assert_eq!(get_balance(&db_data, AccountId::from(0)).unwrap(), value); + assert_eq!( + get_balance_at(&db_data, AccountId::from(0), anchor_height).unwrap(), + value + ); + + let to = TransparentAddress::PublicKey([7; 20]).into(); + 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(_) + )); + } + + #[test] + fn create_to_address_spends_a_change_note() { + let cache_file = NamedTempFile::new().unwrap(); + let db_cache = BlockDb(Connection::open(cache_file.path()).unwrap()); + init_cache_database(&db_cache).unwrap(); + + 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, None).unwrap(); + + // 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(); + + // Add funds to the wallet in a single note + let value = Amount::from_u64(51000).unwrap(); + let (cb, _) = fake_compact_block( + sapling_activation_height(), + BlockHash([0; 32]), + &dfvk, + AddressType::Internal, value, ); insert_into_cache(&db_cache, &cb);