Merge pull request #916 from zcash/sqlite-testing-helper

`zcash_client_sqlite`: Introduce `TestBuilder` and `TestRunner`
This commit is contained in:
str4d 2023-08-28 20:43:44 +01:00 committed by GitHub
commit 8c30c6a066
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1228 additions and 1312 deletions

View File

@ -325,7 +325,6 @@ mod tests {
use std::num::NonZeroU32;
use secrecy::Secret;
use tempfile::NamedTempFile;
use zcash_primitives::{
block::BlockHash,
@ -336,9 +335,8 @@ mod tests {
use zcash_client_backend::{
address::RecipientAddress,
data_api::{
chain::{error::Error, scan_cached_blocks},
wallet::{input_selection::GreedyInputSelector, spend},
WalletRead, WalletWrite,
chain::error::Error, wallet::input_selection::GreedyInputSelector, WalletRead,
WalletWrite,
},
fees::{zip317::SingleOutputChangeStrategy, DustOutputPolicy},
scanning::ScanError,
@ -347,340 +345,192 @@ mod tests {
};
use crate::{
chain::init::init_cache_database,
tests::{
self, fake_compact_block, fake_compact_block_spending, init_test_accounts_table,
insert_into_cache, sapling_activation_height, AddressType,
},
wallet::{get_balance, init::init_wallet_db, truncate_to_height},
AccountId, BlockDb, WalletDb,
testing::{AddressType, TestBuilder},
wallet::{get_balance, truncate_to_height},
AccountId,
};
#[test]
fn valid_chain_states() {
let cache_file = NamedTempFile::new().unwrap();
let db_cache = BlockDb::for_path(cache_file.path()).unwrap();
init_cache_database(&db_cache).unwrap();
let mut st = TestBuilder::new()
.with_block_cache()
.with_seed(Secret::new(vec![]))
.with_test_account()
.build();
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();
// Add an account to the wallet
let (dfvk, _taddr) = init_test_accounts_table(&mut db_data);
let dfvk = st.test_account_sapling().unwrap();
// Empty chain should return None
assert_matches!(db_data.chain_height(), Ok(None));
assert_matches!(st.wallet().chain_height(), Ok(None));
// Create a fake CompactBlock sending value to the address
let (cb, _) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
let (h1, _, _) = st.generate_next_block(
&dfvk,
AddressType::DefaultExternal,
Amount::from_u64(5).unwrap(),
0,
);
insert_into_cache(&db_cache, &cb);
// Scan the cache
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
st.scan_cached_blocks(h1, 1);
// Create a second fake CompactBlock sending more value to the address
let (cb2, _) = fake_compact_block(
sapling_activation_height() + 1,
cb.hash(),
let (h2, _, _) = st.generate_next_block(
&dfvk,
AddressType::DefaultExternal,
Amount::from_u64(7).unwrap(),
1,
);
insert_into_cache(&db_cache, &cb2);
// Scanning should detect no inconsistencies
assert_matches!(
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 1,
1,
),
Ok(())
);
st.scan_cached_blocks(h2, 1);
}
#[test]
fn invalid_chain_cache_disconnected() {
let cache_file = NamedTempFile::new().unwrap();
let db_cache = BlockDb::for_path(cache_file.path()).unwrap();
init_cache_database(&db_cache).unwrap();
let mut st = TestBuilder::new()
.with_block_cache()
.with_seed(Secret::new(vec![]))
.with_test_account()
.build();
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();
// Add an account to the wallet
let (dfvk, _taddr) = init_test_accounts_table(&mut db_data);
let dfvk = st.test_account_sapling().unwrap();
// Create some fake CompactBlocks
let (cb, _) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
let (h, _, _) = st.generate_next_block(
&dfvk,
AddressType::DefaultExternal,
Amount::from_u64(5).unwrap(),
0,
);
let (cb2, _) = fake_compact_block(
sapling_activation_height() + 1,
cb.hash(),
let (last_contiguous_height, _, _) = st.generate_next_block(
&dfvk,
AddressType::DefaultExternal,
Amount::from_u64(7).unwrap(),
1,
);
insert_into_cache(&db_cache, &cb);
insert_into_cache(&db_cache, &cb2);
// Scanning the cache should find no inconsistencies
assert_matches!(
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
2,
),
Ok(())
);
st.scan_cached_blocks(h, 2);
// Create more fake CompactBlocks that don't connect to the scanned ones
let (cb3, _) = fake_compact_block(
sapling_activation_height() + 2,
let disconnect_height = last_contiguous_height + 1;
st.generate_block_at(
disconnect_height,
BlockHash([1; 32]),
&dfvk,
AddressType::DefaultExternal,
Amount::from_u64(8).unwrap(),
2,
);
let (cb4, _) = fake_compact_block(
sapling_activation_height() + 3,
cb3.hash(),
st.generate_next_block(
&dfvk,
AddressType::DefaultExternal,
Amount::from_u64(3).unwrap(),
3,
);
insert_into_cache(&db_cache, &cb3);
insert_into_cache(&db_cache, &cb4);
// Data+cache chain should be invalid at the data/cache boundary
assert_matches!(
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 2,
st.try_scan_cached_blocks(
disconnect_height,
2
),
Err(Error::Scan(ScanError::PrevHashMismatch { at_height }))
if at_height == sapling_activation_height() + 2
if at_height == disconnect_height
);
}
#[test]
fn data_db_truncation() {
let cache_file = NamedTempFile::new().unwrap();
let db_cache = BlockDb::for_path(cache_file.path()).unwrap();
init_cache_database(&db_cache).unwrap();
let mut st = TestBuilder::new()
.with_block_cache()
.with_seed(Secret::new(vec![]))
.with_test_account()
.build();
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();
// Add an account to the wallet
let (dfvk, _taddr) = init_test_accounts_table(&mut db_data);
let dfvk = st.test_account_sapling().unwrap();
// Account balance should be zero
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
Amount::zero()
);
// Create fake CompactBlocks sending value to the address
let value = Amount::from_u64(5).unwrap();
let value2 = Amount::from_u64(7).unwrap();
let (cb, _) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
&dfvk,
AddressType::DefaultExternal,
value,
0,
);
let (cb2, _) = fake_compact_block(
sapling_activation_height() + 1,
cb.hash(),
&dfvk,
AddressType::DefaultExternal,
value2,
1,
);
insert_into_cache(&db_cache, &cb);
insert_into_cache(&db_cache, &cb2);
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value2);
// Scan the cache
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
2,
)
.unwrap();
st.scan_cached_blocks(h, 2);
// Account balance should reflect both received notes
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
(value + value2).unwrap()
);
// "Rewind" to height of last scanned block
db_data
.transactionally(|wdb| {
truncate_to_height(wdb.conn.0, &wdb.params, sapling_activation_height() + 1)
})
st.wallet_mut()
.transactionally(|wdb| truncate_to_height(wdb.conn.0, &wdb.params, h + 1))
.unwrap();
// Account balance should be unaltered
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
(value + value2).unwrap()
);
// Rewind so that one block is dropped
db_data
.transactionally(|wdb| {
truncate_to_height(wdb.conn.0, &wdb.params, sapling_activation_height())
})
st.wallet_mut()
.transactionally(|wdb| truncate_to_height(wdb.conn.0, &wdb.params, h))
.unwrap();
// Account balance should only contain the first received note
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
value
);
// Scan the cache again
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
2,
)
.unwrap();
st.scan_cached_blocks(h, 2);
// Account balance should again reflect both received notes
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
(value + value2).unwrap()
);
}
#[test]
fn scan_cached_blocks_allows_blocks_out_of_order() {
let cache_file = NamedTempFile::new().unwrap();
let db_cache = BlockDb::for_path(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, Some(Secret::new(vec![]))).unwrap();
let mut st = TestBuilder::new()
.with_block_cache()
.with_seed(Secret::new(vec![]))
.build();
// Add an account to the wallet
let seed = Secret::new([0u8; 32].to_vec());
let (_, usk) = db_data.create_account(&seed).unwrap();
let (_, usk) = st.wallet_mut().create_account(&seed).unwrap();
let dfvk = usk.sapling().to_diversifiable_full_viewing_key();
// Create a block with height SAPLING_ACTIVATION_HEIGHT
let value = Amount::from_u64(50000).unwrap();
let (cb1, _) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
&dfvk,
AddressType::DefaultExternal,
value,
0,
);
insert_into_cache(&db_cache, &cb1);
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
st.scan_cached_blocks(h1, 1);
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
value
);
// Create blocks to reach SAPLING_ACTIVATION_HEIGHT + 2
let (cb2, _) = fake_compact_block(
sapling_activation_height() + 1,
cb1.hash(),
&dfvk,
AddressType::DefaultExternal,
value,
1,
);
let (cb3, _) = fake_compact_block(
sapling_activation_height() + 2,
cb2.hash(),
&dfvk,
AddressType::DefaultExternal,
value,
2,
);
let (h2, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
let (h3, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
// Scan the later block first
insert_into_cache(&db_cache, &cb3);
assert_matches!(
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 2,
1
),
Ok(_)
);
st.scan_cached_blocks(h3, 1);
// If we add a block of height SAPLING_ACTIVATION_HEIGHT + 1, we can now scan that
insert_into_cache(&db_cache, &cb2);
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 1,
1,
)
.unwrap();
// Now scan the block of height SAPLING_ACTIVATION_HEIGHT + 1
st.scan_cached_blocks(h2, 1);
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
Amount::from_u64(150_000).unwrap()
);
@ -699,10 +549,7 @@ mod tests {
DustOutputPolicy::default(),
);
assert_matches!(
spend(
&mut db_data,
&tests::network(),
crate::wallet::sapling::tests::test_prover(),
st.spend(
&input_selector,
&usk,
req,
@ -715,124 +562,74 @@ mod tests {
#[test]
fn scan_cached_blocks_finds_received_notes() {
let cache_file = NamedTempFile::new().unwrap();
let db_cache = BlockDb::for_path(cache_file.path()).unwrap();
init_cache_database(&db_cache).unwrap();
let mut st = TestBuilder::new()
.with_block_cache()
.with_seed(Secret::new(vec![]))
.with_test_account()
.build();
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();
// Add an account to the wallet
let (dfvk, _taddr) = init_test_accounts_table(&mut db_data);
let dfvk = st.test_account_sapling().unwrap();
// Account balance should be zero
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
Amount::zero()
);
// Create a fake CompactBlock sending value to the address
let value = Amount::from_u64(5).unwrap();
let (cb, _) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
&dfvk,
AddressType::DefaultExternal,
value,
0,
);
insert_into_cache(&db_cache, &cb);
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
// Scan the cache
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
st.scan_cached_blocks(h1, 1);
// Account balance should reflect the received note
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
value
);
// 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,
AddressType::DefaultExternal,
value2,
1,
);
insert_into_cache(&db_cache, &cb2);
let (h2, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value2);
// Scan the cache again
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 1,
1,
)
.unwrap();
st.scan_cached_blocks(h2, 1);
// Account balance should reflect both received notes
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
(value + value2).unwrap()
);
}
#[test]
fn scan_cached_blocks_finds_change_notes() {
let cache_file = NamedTempFile::new().unwrap();
let db_cache = BlockDb::for_path(cache_file.path()).unwrap();
init_cache_database(&db_cache).unwrap();
let mut st = TestBuilder::new()
.with_block_cache()
.with_seed(Secret::new(vec![]))
.with_test_account()
.build();
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();
// Add an account to the wallet
let (dfvk, _taddr) = init_test_accounts_table(&mut db_data);
let dfvk = st.test_account_sapling().unwrap();
// Account balance should be zero
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
Amount::zero()
);
// Create a fake CompactBlock sending value to the address
let value = Amount::from_u64(5).unwrap();
let (cb, nf) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
&dfvk,
AddressType::DefaultExternal,
value,
0,
);
insert_into_cache(&db_cache, &cb);
let (received_height, _, nf) =
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
// Scan the cache
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
st.scan_cached_blocks(received_height, 1);
// Account balance should reflect the received note
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
value
);
@ -840,113 +637,60 @@ mod tests {
let extsk2 = ExtendedSpendingKey::master(&[0]);
let to2 = extsk2.default_address().1;
let value2 = Amount::from_u64(2).unwrap();
insert_into_cache(
&db_cache,
&fake_compact_block_spending(
sapling_activation_height() + 1,
cb.hash(),
(nf, value),
&dfvk,
to2,
value2,
1,
),
);
let (spent_height, _) = st.generate_next_block_spending(&dfvk, (nf, value), to2, value2);
// Scan the cache again
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 1,
1,
)
.unwrap();
st.scan_cached_blocks(spent_height, 1);
// Account balance should equal the change
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
(value - value2).unwrap()
);
}
#[test]
fn scan_cached_blocks_detects_spends_out_of_order() {
let cache_file = NamedTempFile::new().unwrap();
let db_cache = BlockDb::for_path(cache_file.path()).unwrap();
init_cache_database(&db_cache).unwrap();
let mut st = TestBuilder::new()
.with_block_cache()
.with_seed(Secret::new(vec![]))
.with_test_account()
.build();
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();
// Add an account to the wallet
let (dfvk, _taddr) = init_test_accounts_table(&mut db_data);
let dfvk = st.test_account_sapling().unwrap();
// Account balance should be zero
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
Amount::zero()
);
// Create a fake CompactBlock sending value to the address
let value = Amount::from_u64(5).unwrap();
let (cb, nf) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
&dfvk,
AddressType::DefaultExternal,
value,
0,
);
insert_into_cache(&db_cache, &cb);
let (received_height, _, nf) =
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
// Create a second fake CompactBlock spending value from the address
let extsk2 = ExtendedSpendingKey::master(&[0]);
let to2 = extsk2.default_address().1;
let value2 = Amount::from_u64(2).unwrap();
insert_into_cache(
&db_cache,
&fake_compact_block_spending(
sapling_activation_height() + 1,
cb.hash(),
(nf, value),
&dfvk,
to2,
value2,
1,
),
);
let (spent_height, _) = st.generate_next_block_spending(&dfvk, (nf, value), to2, value2);
// Scan the spending block first.
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 1,
1,
)
.unwrap();
st.scan_cached_blocks(spent_height, 1);
// Account balance should equal the change
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
(value - value2).unwrap()
);
// Now scan the block in which we received the note that was spent.
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
st.scan_cached_blocks(received_height, 1);
// Account balance should be the same.
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
(value - value2).unwrap()
);
}

View File

@ -90,6 +90,9 @@ pub mod serialization;
pub mod wallet;
use wallet::commitment_tree::{self, put_shard_roots};
#[cfg(test)]
mod testing;
/// The maximum number of blocks the wallet is allowed to rewind. This is
/// consistent with the bound in zcashd, and allows block data deeper than
/// this delta from the chain tip to be pruned.
@ -1082,337 +1085,32 @@ extern crate assert_matches;
#[cfg(test)]
mod tests {
use prost::Message;
use rand_core::{OsRng, RngCore};
use rusqlite::params;
use std::collections::HashMap;
use zcash_client_backend::data_api::{WalletRead, WalletWrite};
use crate::{testing::TestBuilder, AccountId};
#[cfg(feature = "unstable")]
use std::{fs::File, path::Path};
#[cfg(feature = "transparent-inputs")]
use zcash_primitives::{legacy, legacy::keys::IncomingViewingKey};
use zcash_note_encryption::Domain;
use zcash_primitives::{
block::BlockHash,
consensus::{BlockHeight, Network, NetworkUpgrade, Parameters},
legacy::TransparentAddress,
memo::MemoBytes,
sapling::{
note_encryption::{sapling_note_encryption, SaplingDomain},
util::generate_random_rseed,
value::NoteValue,
Note, Nullifier, PaymentAddress,
},
transaction::components::Amount,
zip32::{sapling::DiversifiableFullViewingKey, DiversifierIndex},
};
use zcash_client_backend::{
data_api::{WalletRead, WalletWrite},
keys::{sapling, UnifiedFullViewingKey},
proto::compact_formats::{
self as compact, CompactBlock, CompactSaplingOutput, CompactSaplingSpend, CompactTx,
},
};
use crate::{
wallet::init::{init_accounts_table, init_wallet_db},
AccountId, WalletDb,
};
use super::BlockDb;
use zcash_primitives::{consensus::Parameters, transaction::components::Amount};
#[cfg(feature = "unstable")]
use super::{
chain::{init::init_blockmeta_db, BlockMeta},
FsBlockDb,
};
#[cfg(feature = "mainnet")]
pub(crate) fn network() -> Network {
Network::MainNetwork
}
#[cfg(not(feature = "mainnet"))]
pub(crate) fn network() -> Network {
Network::TestNetwork
}
#[cfg(feature = "mainnet")]
pub(crate) fn sapling_activation_height() -> BlockHeight {
Network::MainNetwork
.activation_height(NetworkUpgrade::Sapling)
.unwrap()
}
#[cfg(not(feature = "mainnet"))]
pub(crate) fn sapling_activation_height() -> BlockHeight {
Network::TestNetwork
.activation_height(NetworkUpgrade::Sapling)
.unwrap()
}
#[cfg(test)]
pub(crate) fn init_test_accounts_table(
db_data: &mut WalletDb<rusqlite::Connection, Network>,
) -> (DiversifiableFullViewingKey, Option<TransparentAddress>) {
let (ufvk, taddr) = init_test_accounts_table_ufvk(db_data);
(ufvk.sapling().unwrap().clone(), taddr)
}
#[cfg(test)]
pub(crate) fn init_test_accounts_table_ufvk(
db_data: &mut WalletDb<rusqlite::Connection, Network>,
) -> (UnifiedFullViewingKey, Option<TransparentAddress>) {
let seed = [0u8; 32];
let account = AccountId::from(0);
let extsk = sapling::spending_key(&seed, network().coin_type(), account);
let dfvk = extsk.to_diversifiable_full_viewing_key();
#[cfg(feature = "transparent-inputs")]
let (tkey, taddr) = {
let tkey = legacy::keys::AccountPrivKey::from_seed(&network(), &seed, account)
.unwrap()
.to_account_pubkey();
let taddr = tkey.derive_external_ivk().unwrap().default_address().0;
(Some(tkey), Some(taddr))
};
#[cfg(not(feature = "transparent-inputs"))]
let taddr = None;
let ufvk = UnifiedFullViewingKey::new(
#[cfg(feature = "transparent-inputs")]
tkey,
Some(dfvk),
None,
)
.unwrap();
let ufvks = HashMap::from([(account, ufvk.clone())]);
init_accounts_table(db_data, &ufvks).unwrap();
(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
/// 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,
initial_sapling_tree_size: u32,
) -> (CompactBlock, Nullifier) {
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;
let rseed = generate_random_rseed(&network(), height, &mut rng);
let note = Note::from_parts(to, NoteValue::from_raw(value.into()), rseed);
let encryptor = sapling_note_encryption::<_, Network>(
Some(dfvk.fvk().ovk),
note.clone(),
MemoBytes::empty(),
&mut rng,
);
let cmu = note.cmu().to_bytes().to_vec();
let ephemeral_key = SaplingDomain::<Network>::epk_bytes(encryptor.epk())
.0
.to_vec();
let enc_ciphertext = encryptor.encrypt_note_plaintext();
// Create a fake CompactBlock containing the note
let cout = CompactSaplingOutput {
cmu,
ephemeral_key,
ciphertext: enc_ciphertext.as_ref()[..52].to_vec(),
};
let mut ctx = CompactTx::default();
let mut txid = vec![0; 32];
rng.fill_bytes(&mut txid);
ctx.hash = txid;
ctx.outputs.push(cout);
let mut cb = CompactBlock {
hash: {
let mut hash = vec![0; 32];
rng.fill_bytes(&mut hash);
hash
},
height: height.into(),
..Default::default()
};
cb.prev_hash.extend_from_slice(&prev_hash.0);
cb.vtx.push(ctx);
cb.chain_metadata = Some(compact::ChainMetadata {
sapling_commitment_tree_size: initial_sapling_tree_size
+ cb.vtx.iter().map(|tx| tx.outputs.len() as u32).sum::<u32>(),
..Default::default()
});
(cb, note.nf(&dfvk.fvk().vk.nk, 0))
}
/// Create a fake CompactBlock at the given height, spending a single note from the
/// given address.
pub(crate) fn fake_compact_block_spending(
height: BlockHeight,
prev_hash: BlockHash,
(nf, in_value): (Nullifier, Amount),
dfvk: &DiversifiableFullViewingKey,
to: PaymentAddress,
value: Amount,
initial_sapling_tree_size: u32,
) -> CompactBlock {
let mut rng = OsRng;
let rseed = generate_random_rseed(&network(), height, &mut rng);
// Create a fake CompactBlock containing the note
let cspend = CompactSaplingSpend { nf: nf.to_vec() };
let mut ctx = CompactTx::default();
let mut txid = vec![0; 32];
rng.fill_bytes(&mut txid);
ctx.hash = txid;
ctx.spends.push(cspend);
// Create a fake Note for the payment
ctx.outputs.push({
let note = Note::from_parts(to, NoteValue::from_raw(value.into()), rseed);
let encryptor = sapling_note_encryption::<_, Network>(
Some(dfvk.fvk().ovk),
note.clone(),
MemoBytes::empty(),
&mut rng,
);
let cmu = note.cmu().to_bytes().to_vec();
let ephemeral_key = SaplingDomain::<Network>::epk_bytes(encryptor.epk())
.0
.to_vec();
let enc_ciphertext = encryptor.encrypt_note_plaintext();
CompactSaplingOutput {
cmu,
ephemeral_key,
ciphertext: enc_ciphertext.as_ref()[..52].to_vec(),
}
});
// Create a fake Note for the change
ctx.outputs.push({
let change_addr = dfvk.default_address().1;
let rseed = generate_random_rseed(&network(), height, &mut rng);
let note = Note::from_parts(
change_addr,
NoteValue::from_raw((in_value - value).unwrap().into()),
rseed,
);
let encryptor = sapling_note_encryption::<_, Network>(
Some(dfvk.fvk().ovk),
note.clone(),
MemoBytes::empty(),
&mut rng,
);
let cmu = note.cmu().to_bytes().to_vec();
let ephemeral_key = SaplingDomain::<Network>::epk_bytes(encryptor.epk())
.0
.to_vec();
let enc_ciphertext = encryptor.encrypt_note_plaintext();
CompactSaplingOutput {
cmu,
ephemeral_key,
ciphertext: enc_ciphertext.as_ref()[..52].to_vec(),
}
});
let mut cb = CompactBlock {
hash: {
let mut hash = vec![0; 32];
rng.fill_bytes(&mut hash);
hash
},
height: height.into(),
..Default::default()
};
cb.prev_hash.extend_from_slice(&prev_hash.0);
cb.vtx.push(ctx);
cb.chain_metadata = Some(compact::ChainMetadata {
sapling_commitment_tree_size: initial_sapling_tree_size
+ cb.vtx.iter().map(|tx| tx.outputs.len() as u32).sum::<u32>(),
..Default::default()
});
cb
}
/// Insert a fake CompactBlock into the cache DB.
pub(crate) fn insert_into_cache(db_cache: &BlockDb, cb: &CompactBlock) {
let cb_bytes = cb.encode_to_vec();
db_cache
.0
.prepare("INSERT INTO compactblocks (height, data) VALUES (?, ?)")
.unwrap()
.execute(params![u32::from(cb.height()), cb_bytes,])
.unwrap();
}
use zcash_client_backend::keys::sapling;
#[cfg(feature = "unstable")]
pub(crate) fn store_in_fsblockdb<P: AsRef<Path>>(
fsblockdb_root: P,
cb: &CompactBlock,
) -> BlockMeta {
use std::io::Write;
let meta = BlockMeta {
height: cb.height(),
block_hash: cb.hash(),
block_time: cb.time,
sapling_outputs_count: cb.vtx.iter().map(|tx| tx.outputs.len() as u32).sum(),
orchard_actions_count: cb.vtx.iter().map(|tx| tx.actions.len() as u32).sum(),
};
let blocks_dir = fsblockdb_root.as_ref().join("blocks");
let block_path = meta.block_file_path(&blocks_dir);
File::create(block_path)
.unwrap()
.write_all(&cb.encode_to_vec())
.unwrap();
meta
}
use crate::testing::AddressType;
#[test]
pub(crate) fn get_next_available_address() {
use tempfile::NamedTempFile;
let data_file = NamedTempFile::new().unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), network()).unwrap();
let mut st = TestBuilder::new().with_test_account().build();
let account = AccountId::from(0);
init_wallet_db(&mut db_data, None).unwrap();
init_test_accounts_table_ufvk(&mut db_data);
let current_addr = db_data.get_current_address(account).unwrap();
let current_addr = st.wallet().get_current_address(account).unwrap();
assert!(current_addr.is_some());
let addr2 = db_data.get_next_available_address(account).unwrap();
let addr2 = st.wallet_mut().get_next_available_address(account).unwrap();
assert!(addr2.is_some());
assert_ne!(current_addr, addr2);
let addr2_cur = db_data.get_current_address(account).unwrap();
let addr2_cur = st.wallet().get_current_address(account).unwrap();
assert_eq!(addr2, addr2_cur);
}
@ -1420,23 +1118,18 @@ mod tests {
#[test]
fn transparent_receivers() {
use secrecy::Secret;
use tempfile::NamedTempFile;
use crate::{chain::init::init_cache_database, wallet::init::init_wallet_db};
let cache_file = NamedTempFile::new().unwrap();
let db_cache = BlockDb::for_path(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(), network()).unwrap();
init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap();
let st = TestBuilder::new()
.with_block_cache()
.with_seed(Secret::new(vec![]))
.with_test_account()
.build();
// Add an account to the wallet.
let (ufvk, taddr) = init_test_accounts_table_ufvk(&mut db_data);
let (ufvk, taddr) = st.test_account().unwrap();
let taddr = taddr.unwrap();
let receivers = db_data.get_transparent_receivers(0.into()).unwrap();
let receivers = st.wallet().get_transparent_receivers(0.into()).unwrap();
// The receiver for the default UA should be in the set.
assert!(receivers.contains_key(ufvk.default_address().0.transparent().unwrap()));
@ -1448,74 +1141,44 @@ mod tests {
#[cfg(feature = "unstable")]
#[test]
pub(crate) fn fsblockdb_api() {
// Initialise a BlockMeta DB in a new directory.
let fsblockdb_root = tempfile::tempdir().unwrap();
let mut db_meta = FsBlockDb::for_path(&fsblockdb_root).unwrap();
init_blockmeta_db(&mut db_meta).unwrap();
let mut st = TestBuilder::new().with_fs_block_cache().build();
// The BlockMeta DB starts off empty.
assert_eq!(db_meta.get_max_cached_height().unwrap(), None);
assert_eq!(st.cache().get_max_cached_height().unwrap(), None);
// Generate some fake CompactBlocks.
let seed = [0u8; 32];
let account = AccountId::from(0);
let extsk = sapling::spending_key(&seed, network().coin_type(), account);
let extsk = sapling::spending_key(&seed, st.wallet().params.coin_type(), account);
let dfvk = extsk.to_diversifiable_full_viewing_key();
let (cb1, _) = fake_compact_block(
BlockHeight::from_u32(1),
BlockHash([1; 32]),
let (h1, meta1, _) = st.generate_next_block(
&dfvk,
AddressType::DefaultExternal,
Amount::from_u64(5).unwrap(),
0,
);
let (cb2, _) = fake_compact_block(
BlockHeight::from_u32(2),
BlockHash([2; 32]),
let (h2, meta2, _) = st.generate_next_block(
&dfvk,
AddressType::DefaultExternal,
Amount::from_u64(10).unwrap(),
1,
);
// Write the CompactBlocks to the BlockMeta DB's corresponding disk storage.
let meta1 = store_in_fsblockdb(&fsblockdb_root, &cb1);
let meta2 = store_in_fsblockdb(&fsblockdb_root, &cb2);
// The BlockMeta DB is not updated until we do so explicitly.
assert_eq!(db_meta.get_max_cached_height().unwrap(), None);
assert_eq!(st.cache().get_max_cached_height().unwrap(), None);
// Inform the BlockMeta DB about the newly-persisted CompactBlocks.
db_meta.write_block_metadata(&[meta1, meta2]).unwrap();
st.cache().write_block_metadata(&[meta1, meta2]).unwrap();
// The BlockMeta DB now sees blocks up to height 2.
assert_eq!(
db_meta.get_max_cached_height().unwrap(),
Some(BlockHeight::from_u32(2)),
);
assert_eq!(
db_meta.find_block(BlockHeight::from_u32(1)).unwrap(),
Some(meta1),
);
assert_eq!(
db_meta.find_block(BlockHeight::from_u32(2)).unwrap(),
Some(meta2),
);
assert_eq!(db_meta.find_block(BlockHeight::from_u32(3)).unwrap(), None);
assert_eq!(st.cache().get_max_cached_height().unwrap(), Some(h2),);
assert_eq!(st.cache().find_block(h1).unwrap(), Some(meta1));
assert_eq!(st.cache().find_block(h2).unwrap(), Some(meta2));
assert_eq!(st.cache().find_block(h2 + 1).unwrap(), None);
// Rewinding to height 1 should cause the metadata for height 2 to be deleted.
db_meta
.truncate_to_height(BlockHeight::from_u32(1))
.unwrap();
assert_eq!(
db_meta.get_max_cached_height().unwrap(),
Some(BlockHeight::from_u32(1)),
);
assert_eq!(
db_meta.find_block(BlockHeight::from_u32(1)).unwrap(),
Some(meta1),
);
assert_eq!(db_meta.find_block(BlockHeight::from_u32(2)).unwrap(), None);
assert_eq!(db_meta.find_block(BlockHeight::from_u32(3)).unwrap(), None);
st.cache().truncate_to_height(h1).unwrap();
assert_eq!(st.cache().get_max_cached_height().unwrap(), Some(h1));
assert_eq!(st.cache().find_block(h1).unwrap(), Some(meta1));
assert_eq!(st.cache().find_block(h2).unwrap(), None);
assert_eq!(st.cache().find_block(h2 + 1).unwrap(), None);
}
}

View File

@ -0,0 +1,846 @@
use std::convert::Infallible;
use std::fmt;
use std::{collections::HashMap, num::NonZeroU32};
#[cfg(feature = "unstable")]
use std::fs::File;
use prost::Message;
use rand_core::{OsRng, RngCore};
use rusqlite::{params, Connection};
use secrecy::SecretVec;
use tempfile::NamedTempFile;
#[cfg(feature = "unstable")]
use tempfile::TempDir;
#[allow(deprecated)]
use zcash_client_backend::data_api::wallet::create_spend_to_address;
use zcash_client_backend::{
address::RecipientAddress,
data_api::{
self,
chain::{scan_cached_blocks, BlockSource},
wallet::{
create_proposed_transaction,
input_selection::{GreedyInputSelectorError, InputSelector, Proposal},
propose_transfer, spend,
},
},
keys::{sapling, UnifiedFullViewingKey, UnifiedSpendingKey},
proto::compact_formats::{
self as compact, CompactBlock, CompactSaplingOutput, CompactSaplingSpend, CompactTx,
},
wallet::OvkPolicy,
zip321,
};
use zcash_note_encryption::Domain;
use zcash_primitives::{
block::BlockHash,
consensus::{BlockHeight, Network, NetworkUpgrade, Parameters},
legacy::TransparentAddress,
memo::MemoBytes,
sapling::{
note_encryption::{sapling_note_encryption, SaplingDomain},
util::generate_random_rseed,
value::NoteValue,
Note, Nullifier, PaymentAddress,
},
transaction::{
components::{amount::BalanceError, Amount},
fees::FeeRule,
TxId,
},
zip32::{sapling::DiversifiableFullViewingKey, DiversifierIndex},
};
#[cfg(feature = "transparent-inputs")]
use zcash_client_backend::data_api::wallet::{propose_shielding, shield_transparent_funds};
#[cfg(feature = "transparent-inputs")]
use zcash_primitives::{
legacy, legacy::keys::IncomingViewingKey, transaction::components::amount::NonNegativeAmount,
};
use crate::{
chain::init::init_cache_database,
error::SqliteClientError,
wallet::{
commitment_tree,
init::{init_accounts_table, init_wallet_db},
sapling::tests::test_prover,
},
AccountId, ReceivedNoteId, WalletDb,
};
use super::BlockDb;
#[cfg(feature = "unstable")]
use crate::{
chain::{init::init_blockmeta_db, BlockMeta},
FsBlockDb,
};
/// A builder for a `zcash_client_sqlite` test.
pub(crate) struct TestBuilder<Cache> {
cache: Cache,
seed: Option<SecretVec<u8>>,
with_test_account: bool,
}
impl TestBuilder<()> {
/// Constructs a new test.
pub(crate) fn new() -> Self {
TestBuilder {
cache: (),
seed: None,
with_test_account: false,
}
}
/// Adds a [`BlockDb`] cache to the test.
pub(crate) fn with_block_cache(self) -> TestBuilder<BlockCache> {
TestBuilder {
cache: BlockCache::new(),
seed: self.seed,
with_test_account: self.with_test_account,
}
}
/// Adds a [`FsBlockDb`] cache to the test.
#[cfg(feature = "unstable")]
pub(crate) fn with_fs_block_cache(self) -> TestBuilder<FsBlockCache> {
TestBuilder {
cache: FsBlockCache::new(),
seed: self.seed,
with_test_account: self.with_test_account,
}
}
}
impl<Cache> TestBuilder<Cache> {
/// Gives the test knowledge of the wallet seed for initialization.
pub(crate) fn with_seed(mut self, seed: SecretVec<u8>) -> Self {
// TODO remove
self.seed = Some(seed);
self
}
pub(crate) fn with_test_account(mut self) -> Self {
self.with_test_account = true;
self
}
/// Builds the state for this test.
pub(crate) fn build(self) -> TestState<Cache> {
let params = network();
let data_file = NamedTempFile::new().unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), params).unwrap();
init_wallet_db(&mut db_data, self.seed).unwrap();
let test_account = if self.with_test_account {
// Add an account to the wallet
Some(init_test_accounts_table_ufvk(&mut db_data))
} else {
None
};
TestState {
params,
cache: self.cache,
latest_cached_block: None,
_data_file: data_file,
db_data,
test_account,
}
}
}
/// The state for a `zcash_client_sqlite` test.
pub(crate) struct TestState<Cache> {
params: Network,
cache: Cache,
latest_cached_block: Option<(BlockHeight, BlockHash, u32)>,
_data_file: NamedTempFile,
db_data: WalletDb<Connection, Network>,
test_account: Option<(UnifiedFullViewingKey, Option<TransparentAddress>)>,
}
impl<Cache: TestCache> TestState<Cache>
where
<Cache::BlockSource as BlockSource>::Error: fmt::Debug,
{
/// Exposes an immutable reference to the test's [`BlockSource`].
#[cfg(feature = "unstable")]
pub(crate) fn cache(&self) -> &Cache::BlockSource {
self.cache.block_source()
}
/// Creates a fake block at the expected next height containing a single output of the
/// given value, and inserts it into the cache.
pub(crate) fn generate_next_block(
&mut self,
dfvk: &DiversifiableFullViewingKey,
req: AddressType,
value: Amount,
) -> (BlockHeight, Cache::InsertResult, Nullifier) {
let (height, prev_hash, initial_sapling_tree_size) = self
.latest_cached_block
.map(|(prev_height, prev_hash, end_size)| (prev_height + 1, prev_hash, end_size))
.unwrap_or_else(|| (sapling_activation_height(), BlockHash([0; 32]), 0));
let (res, nf) = self.generate_block_at(
height,
prev_hash,
dfvk,
req,
value,
initial_sapling_tree_size,
);
(height, res, nf)
}
/// Creates a fake block with the given height and hash containing a single output of
/// the given value, and inserts it into the cache.
///
/// This generated block will be treated as the latest block, and subsequent calls to
/// [`Self::generate_next_block`] will build on it.
pub(crate) fn generate_block_at(
&mut self,
height: BlockHeight,
prev_hash: BlockHash,
dfvk: &DiversifiableFullViewingKey,
req: AddressType,
value: Amount,
initial_sapling_tree_size: u32,
) -> (Cache::InsertResult, Nullifier) {
let (cb, nf) = fake_compact_block(
height,
prev_hash,
dfvk,
req,
value,
initial_sapling_tree_size,
);
let res = self.cache.insert(&cb);
self.latest_cached_block = Some((
height,
cb.hash(),
initial_sapling_tree_size
+ cb.vtx.iter().map(|tx| tx.outputs.len() as u32).sum::<u32>(),
));
(res, nf)
}
/// Creates a fake block at the expected next height spending the given note, and
/// inserts it into the cache.
pub(crate) fn generate_next_block_spending(
&mut self,
dfvk: &DiversifiableFullViewingKey,
note: (Nullifier, Amount),
to: PaymentAddress,
value: Amount,
) -> (BlockHeight, Cache::InsertResult) {
let (height, prev_hash, initial_sapling_tree_size) = self
.latest_cached_block
.map(|(prev_height, prev_hash, end_size)| (prev_height + 1, prev_hash, end_size))
.unwrap_or_else(|| (sapling_activation_height(), BlockHash([0; 32]), 0));
let cb = fake_compact_block_spending(
height,
prev_hash,
note,
dfvk,
to,
value,
initial_sapling_tree_size,
);
let res = self.cache.insert(&cb);
self.latest_cached_block = Some((
height,
cb.hash(),
initial_sapling_tree_size
+ cb.vtx.iter().map(|tx| tx.outputs.len() as u32).sum::<u32>(),
));
(height, res)
}
/// Invokes [`scan_cached_blocks`] with the given arguments, expecting success.
pub(crate) fn scan_cached_blocks(&mut self, from_height: BlockHeight, limit: usize) {
self.try_scan_cached_blocks(from_height, limit)
.expect("should succeed for this test");
}
/// Invokes [`scan_cached_blocks`] with the given arguments.
pub(crate) fn try_scan_cached_blocks(
&mut self,
from_height: BlockHeight,
limit: usize,
) -> Result<
(),
data_api::chain::error::Error<
SqliteClientError,
<Cache::BlockSource as BlockSource>::Error,
>,
> {
scan_cached_blocks(
&self.params,
self.cache.block_source(),
&mut self.db_data,
from_height,
limit,
)
}
}
impl<Cache> TestState<Cache> {
/// Exposes an immutable reference to the test's [`WalletDb`].
pub(crate) fn wallet(&self) -> &WalletDb<Connection, Network> {
&self.db_data
}
/// Exposes a mutable reference to the test's [`WalletDb`].
pub(crate) fn wallet_mut(&mut self) -> &mut WalletDb<Connection, Network> {
&mut self.db_data
}
/// Exposes the test account, if enabled via [`TestBuilder::with_test_account`].
#[cfg(feature = "unstable")]
pub(crate) fn test_account(
&self,
) -> Option<(UnifiedFullViewingKey, Option<TransparentAddress>)> {
self.test_account.as_ref().cloned()
}
/// Exposes the test account's Sapling DFVK, if enabled via [`TestBuilder::with_test_account`].
pub(crate) fn test_account_sapling(&self) -> Option<DiversifiableFullViewingKey> {
self.test_account
.as_ref()
.map(|(ufvk, _)| ufvk.sapling().unwrap().clone())
}
/// Invokes [`create_spend_to_address`] with the given arguments.
#[allow(deprecated)]
#[allow(clippy::type_complexity)]
pub(crate) fn create_spend_to_address(
&mut self,
usk: &UnifiedSpendingKey,
to: &RecipientAddress,
amount: Amount,
memo: Option<MemoBytes>,
ovk_policy: OvkPolicy,
min_confirmations: NonZeroU32,
) -> Result<
TxId,
data_api::error::Error<
SqliteClientError,
commitment_tree::Error,
GreedyInputSelectorError<BalanceError, ReceivedNoteId>,
Infallible,
ReceivedNoteId,
>,
> {
create_spend_to_address(
&mut self.db_data,
&self.params,
test_prover(),
usk,
to,
amount,
memo,
ovk_policy,
min_confirmations,
)
}
/// Invokes [`spend`] with the given arguments.
#[allow(clippy::type_complexity)]
pub(crate) fn spend<InputsT>(
&mut self,
input_selector: &InputsT,
usk: &UnifiedSpendingKey,
request: zip321::TransactionRequest,
ovk_policy: OvkPolicy,
min_confirmations: NonZeroU32,
) -> Result<
TxId,
data_api::error::Error<
SqliteClientError,
commitment_tree::Error,
InputsT::Error,
<InputsT::FeeRule as FeeRule>::Error,
ReceivedNoteId,
>,
>
where
InputsT: InputSelector<DataSource = WalletDb<Connection, Network>>,
{
spend(
&mut self.db_data,
&self.params,
test_prover(),
input_selector,
usk,
request,
ovk_policy,
min_confirmations,
)
}
/// Invokes [`propose_transfer`] with the given arguments.
#[allow(clippy::type_complexity)]
pub(crate) fn propose_transfer<InputsT>(
&mut self,
spend_from_account: AccountId,
input_selector: &InputsT,
request: zip321::TransactionRequest,
min_confirmations: NonZeroU32,
) -> Result<
Proposal<InputsT::FeeRule, ReceivedNoteId>,
data_api::error::Error<
SqliteClientError,
Infallible,
InputsT::Error,
<InputsT::FeeRule as FeeRule>::Error,
ReceivedNoteId,
>,
>
where
InputsT: InputSelector<DataSource = WalletDb<Connection, Network>>,
{
propose_transfer::<_, _, _, Infallible>(
&mut self.db_data,
&self.params,
spend_from_account,
input_selector,
request,
min_confirmations,
)
}
/// Invokes [`propose_shielding`] with the given arguments.
#[cfg(feature = "transparent-inputs")]
#[allow(clippy::type_complexity)]
pub(crate) fn propose_shielding<InputsT>(
&mut self,
input_selector: &InputsT,
shielding_threshold: NonNegativeAmount,
from_addrs: &[TransparentAddress],
min_confirmations: NonZeroU32,
) -> Result<
Proposal<InputsT::FeeRule, ReceivedNoteId>,
data_api::error::Error<
SqliteClientError,
Infallible,
InputsT::Error,
<InputsT::FeeRule as FeeRule>::Error,
ReceivedNoteId,
>,
>
where
InputsT: InputSelector<DataSource = WalletDb<Connection, Network>>,
{
propose_shielding::<_, _, _, Infallible>(
&mut self.db_data,
&self.params,
input_selector,
shielding_threshold,
from_addrs,
min_confirmations,
)
}
/// Invokes [`create_proposed_transaction`] with the given arguments.
pub(crate) fn create_proposed_transaction<FeeRuleT>(
&mut self,
usk: &UnifiedSpendingKey,
ovk_policy: OvkPolicy,
proposal: Proposal<FeeRuleT, ReceivedNoteId>,
min_confirmations: NonZeroU32,
change_memo: Option<MemoBytes>,
) -> Result<
TxId,
data_api::error::Error<
SqliteClientError,
commitment_tree::Error,
Infallible,
FeeRuleT::Error,
ReceivedNoteId,
>,
>
where
FeeRuleT: FeeRule,
{
create_proposed_transaction::<_, _, Infallible, _>(
&mut self.db_data,
&self.params,
test_prover(),
usk,
ovk_policy,
proposal,
min_confirmations,
change_memo,
)
}
/// Invokes [`shield_transparent_funds`] with the given arguments.
#[cfg(feature = "transparent-inputs")]
#[allow(clippy::type_complexity)]
pub(crate) fn shield_transparent_funds<InputsT>(
&mut self,
input_selector: &InputsT,
shielding_threshold: NonNegativeAmount,
usk: &UnifiedSpendingKey,
from_addrs: &[TransparentAddress],
memo: &MemoBytes,
min_confirmations: NonZeroU32,
) -> Result<
TxId,
data_api::error::Error<
SqliteClientError,
commitment_tree::Error,
InputsT::Error,
<InputsT::FeeRule as FeeRule>::Error,
ReceivedNoteId,
>,
>
where
InputsT: InputSelector<DataSource = WalletDb<Connection, Network>>,
{
shield_transparent_funds(
&mut self.db_data,
&self.params,
test_prover(),
input_selector,
shielding_threshold,
usk,
from_addrs,
memo,
min_confirmations,
)
}
}
#[cfg(feature = "mainnet")]
pub(crate) fn network() -> Network {
Network::MainNetwork
}
#[cfg(not(feature = "mainnet"))]
pub(crate) fn network() -> Network {
Network::TestNetwork
}
#[cfg(feature = "mainnet")]
pub(crate) fn sapling_activation_height() -> BlockHeight {
Network::MainNetwork
.activation_height(NetworkUpgrade::Sapling)
.unwrap()
}
#[cfg(not(feature = "mainnet"))]
pub(crate) fn sapling_activation_height() -> BlockHeight {
Network::TestNetwork
.activation_height(NetworkUpgrade::Sapling)
.unwrap()
}
#[cfg(test)]
pub(crate) fn init_test_accounts_table_ufvk(
db_data: &mut WalletDb<rusqlite::Connection, Network>,
) -> (UnifiedFullViewingKey, Option<TransparentAddress>) {
let seed = [0u8; 32];
let account = AccountId::from(0);
let extsk = sapling::spending_key(&seed, network().coin_type(), account);
let dfvk = extsk.to_diversifiable_full_viewing_key();
#[cfg(feature = "transparent-inputs")]
let (tkey, taddr) = {
let tkey = legacy::keys::AccountPrivKey::from_seed(&network(), &seed, account)
.unwrap()
.to_account_pubkey();
let taddr = tkey.derive_external_ivk().unwrap().default_address().0;
(Some(tkey), Some(taddr))
};
#[cfg(not(feature = "transparent-inputs"))]
let taddr = None;
let ufvk = UnifiedFullViewingKey::new(
#[cfg(feature = "transparent-inputs")]
tkey,
Some(dfvk),
None,
)
.unwrap();
let ufvks = HashMap::from([(account, ufvk.clone())]);
init_accounts_table(db_data, &ufvks).unwrap();
(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
/// 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,
initial_sapling_tree_size: u32,
) -> (CompactBlock, Nullifier) {
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;
let rseed = generate_random_rseed(&network(), height, &mut rng);
let note = Note::from_parts(to, NoteValue::from_raw(value.into()), rseed);
let encryptor = sapling_note_encryption::<_, Network>(
Some(dfvk.fvk().ovk),
note.clone(),
MemoBytes::empty(),
&mut rng,
);
let cmu = note.cmu().to_bytes().to_vec();
let ephemeral_key = SaplingDomain::<Network>::epk_bytes(encryptor.epk())
.0
.to_vec();
let enc_ciphertext = encryptor.encrypt_note_plaintext();
// Create a fake CompactBlock containing the note
let cout = CompactSaplingOutput {
cmu,
ephemeral_key,
ciphertext: enc_ciphertext.as_ref()[..52].to_vec(),
};
let mut ctx = CompactTx::default();
let mut txid = vec![0; 32];
rng.fill_bytes(&mut txid);
ctx.hash = txid;
ctx.outputs.push(cout);
let mut cb = CompactBlock {
hash: {
let mut hash = vec![0; 32];
rng.fill_bytes(&mut hash);
hash
},
height: height.into(),
..Default::default()
};
cb.prev_hash.extend_from_slice(&prev_hash.0);
cb.vtx.push(ctx);
cb.chain_metadata = Some(compact::ChainMetadata {
sapling_commitment_tree_size: initial_sapling_tree_size
+ cb.vtx.iter().map(|tx| tx.outputs.len() as u32).sum::<u32>(),
..Default::default()
});
(cb, note.nf(&dfvk.fvk().vk.nk, 0))
}
/// Create a fake CompactBlock at the given height, spending a single note from the
/// given address.
pub(crate) fn fake_compact_block_spending(
height: BlockHeight,
prev_hash: BlockHash,
(nf, in_value): (Nullifier, Amount),
dfvk: &DiversifiableFullViewingKey,
to: PaymentAddress,
value: Amount,
initial_sapling_tree_size: u32,
) -> CompactBlock {
let mut rng = OsRng;
let rseed = generate_random_rseed(&network(), height, &mut rng);
// Create a fake CompactBlock containing the note
let cspend = CompactSaplingSpend { nf: nf.to_vec() };
let mut ctx = CompactTx::default();
let mut txid = vec![0; 32];
rng.fill_bytes(&mut txid);
ctx.hash = txid;
ctx.spends.push(cspend);
// Create a fake Note for the payment
ctx.outputs.push({
let note = Note::from_parts(to, NoteValue::from_raw(value.into()), rseed);
let encryptor = sapling_note_encryption::<_, Network>(
Some(dfvk.fvk().ovk),
note.clone(),
MemoBytes::empty(),
&mut rng,
);
let cmu = note.cmu().to_bytes().to_vec();
let ephemeral_key = SaplingDomain::<Network>::epk_bytes(encryptor.epk())
.0
.to_vec();
let enc_ciphertext = encryptor.encrypt_note_plaintext();
CompactSaplingOutput {
cmu,
ephemeral_key,
ciphertext: enc_ciphertext.as_ref()[..52].to_vec(),
}
});
// Create a fake Note for the change
ctx.outputs.push({
let change_addr = dfvk.default_address().1;
let rseed = generate_random_rseed(&network(), height, &mut rng);
let note = Note::from_parts(
change_addr,
NoteValue::from_raw((in_value - value).unwrap().into()),
rseed,
);
let encryptor = sapling_note_encryption::<_, Network>(
Some(dfvk.fvk().ovk),
note.clone(),
MemoBytes::empty(),
&mut rng,
);
let cmu = note.cmu().to_bytes().to_vec();
let ephemeral_key = SaplingDomain::<Network>::epk_bytes(encryptor.epk())
.0
.to_vec();
let enc_ciphertext = encryptor.encrypt_note_plaintext();
CompactSaplingOutput {
cmu,
ephemeral_key,
ciphertext: enc_ciphertext.as_ref()[..52].to_vec(),
}
});
let mut cb = CompactBlock {
hash: {
let mut hash = vec![0; 32];
rng.fill_bytes(&mut hash);
hash
},
height: height.into(),
..Default::default()
};
cb.prev_hash.extend_from_slice(&prev_hash.0);
cb.vtx.push(ctx);
cb.chain_metadata = Some(compact::ChainMetadata {
sapling_commitment_tree_size: initial_sapling_tree_size
+ cb.vtx.iter().map(|tx| tx.outputs.len() as u32).sum::<u32>(),
..Default::default()
});
cb
}
/// Trait used by tests that require a block cache.
pub(crate) trait TestCache {
type BlockSource: BlockSource;
type InsertResult;
/// Exposes the block cache as a [`BlockSource`].
fn block_source(&self) -> &Self::BlockSource;
/// Inserts a CompactBlock into the cache DB.
fn insert(&self, cb: &CompactBlock) -> Self::InsertResult;
}
pub(crate) struct BlockCache {
_cache_file: NamedTempFile,
db_cache: BlockDb,
}
impl BlockCache {
fn new() -> Self {
let cache_file = NamedTempFile::new().unwrap();
let db_cache = BlockDb::for_path(cache_file.path()).unwrap();
init_cache_database(&db_cache).unwrap();
BlockCache {
_cache_file: cache_file,
db_cache,
}
}
}
impl TestCache for BlockCache {
type BlockSource = BlockDb;
type InsertResult = ();
fn block_source(&self) -> &Self::BlockSource {
&self.db_cache
}
fn insert(&self, cb: &CompactBlock) {
let cb_bytes = cb.encode_to_vec();
self.db_cache
.0
.prepare("INSERT INTO compactblocks (height, data) VALUES (?, ?)")
.unwrap()
.execute(params![u32::from(cb.height()), cb_bytes,])
.unwrap();
}
}
#[cfg(feature = "unstable")]
pub(crate) struct FsBlockCache {
fsblockdb_root: TempDir,
db_meta: FsBlockDb,
}
#[cfg(feature = "unstable")]
impl FsBlockCache {
fn new() -> Self {
let fsblockdb_root = tempfile::tempdir().unwrap();
let mut db_meta = FsBlockDb::for_path(&fsblockdb_root).unwrap();
init_blockmeta_db(&mut db_meta).unwrap();
FsBlockCache {
fsblockdb_root,
db_meta,
}
}
}
#[cfg(feature = "unstable")]
impl TestCache for FsBlockCache {
type BlockSource = FsBlockDb;
type InsertResult = BlockMeta;
fn block_source(&self) -> &Self::BlockSource {
&self.db_meta
}
fn insert(&self, cb: &CompactBlock) -> Self::InsertResult {
use std::io::Write;
let meta = BlockMeta {
height: cb.height(),
block_hash: cb.hash(),
block_time: cb.time,
sapling_outputs_count: cb.vtx.iter().map(|tx| tx.outputs.len() as u32).sum(),
orchard_actions_count: cb.vtx.iter().map(|tx| tx.actions.len() as u32).sum(),
};
let blocks_dir = self.fsblockdb_root.as_ref().join("blocks");
let block_path = meta.block_file_path(&blocks_dir);
File::create(block_path)
.unwrap()
.write_all(&cb.encode_to_vec())
.unwrap();
meta
}
}

View File

@ -1572,17 +1572,12 @@ mod tests {
use std::num::NonZeroU32;
use secrecy::Secret;
use tempfile::NamedTempFile;
use zcash_primitives::transaction::components::Amount;
use zcash_client_backend::data_api::WalletRead;
use crate::{
tests,
wallet::{get_current_address, init::init_wallet_db},
AccountId, WalletDb,
};
use crate::{testing::TestBuilder, AccountId};
use super::get_balance;
@ -1599,22 +1594,20 @@ mod tests {
#[test]
fn empty_database_has_no_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();
// Add an account to the wallet
tests::init_test_accounts_table(&mut db_data);
let st = TestBuilder::new()
.with_seed(Secret::new(vec![]))
.with_test_account()
.build();
// The account should be empty
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
Amount::zero()
);
// We can't get an anchor height, as we have not scanned any blocks.
assert_eq!(
db_data
st.wallet()
.get_target_and_anchor_heights(NonZeroU32::new(10).unwrap())
.unwrap(),
None
@ -1622,11 +1615,11 @@ mod tests {
// An invalid account has zero balance
assert_matches!(
get_current_address(&db_data.conn, &db_data.params, AccountId::from(1)),
st.wallet().get_current_address(AccountId::from(1)),
Ok(None)
);
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
Amount::zero()
);
}
@ -1634,17 +1627,20 @@ mod tests {
#[test]
#[cfg(feature = "transparent-inputs")]
fn put_received_transparent_utxo() {
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();
let mut st = TestBuilder::new().build();
// Add an account to the wallet
let seed = Secret::new([0u8; 32].to_vec());
let (account_id, _usk) = db_data.create_account(&seed).unwrap();
let uaddr = db_data.get_current_address(account_id).unwrap().unwrap();
let (account_id, _usk) = st.wallet_mut().create_account(&seed).unwrap();
let uaddr = st
.wallet()
.get_current_address(account_id)
.unwrap()
.unwrap();
let taddr = uaddr.transparent().unwrap();
let bal_absent = db_data
let bal_absent = st
.wallet()
.get_transparent_balances(account_id, BlockHeight::from_u32(12345))
.unwrap();
assert!(bal_absent.is_empty());
@ -1659,7 +1655,7 @@ mod tests {
)
.unwrap();
let res0 = super::put_received_transparent_utxo(&db_data.conn, &db_data.params, &utxo);
let res0 = st.wallet_mut().put_received_transparent_utxo(&utxo);
assert_matches!(res0, Ok(_));
// Change the mined height of the UTXO and upsert; we should get back
@ -1673,13 +1669,11 @@ mod tests {
BlockHeight::from_u32(34567),
)
.unwrap();
let res1 = super::put_received_transparent_utxo(&db_data.conn, &db_data.params, &utxo2);
let res1 = st.wallet_mut().put_received_transparent_utxo(&utxo2);
assert_matches!(res1, Ok(id) if id == res0.unwrap());
assert_matches!(
super::get_unspent_transparent_outputs(
&db_data.conn,
&db_data.params,
st.wallet().get_unspent_transparent_outputs(
taddr,
BlockHeight::from_u32(12345),
&[]
@ -1688,9 +1682,7 @@ mod tests {
);
assert_matches!(
super::get_unspent_transparent_outputs(
&db_data.conn,
&db_data.params,
st.wallet().get_unspent_transparent_outputs(
taddr,
BlockHeight::from_u32(34567),
&[]
@ -1702,21 +1694,21 @@ mod tests {
);
assert_matches!(
db_data.get_transparent_balances(account_id, BlockHeight::from_u32(34567)),
st.wallet().get_transparent_balances(account_id, BlockHeight::from_u32(34567)),
Ok(h) if h.get(taddr) == Amount::from_u64(100000).ok().as_ref()
);
// Artificially delete the address from the addresses table so that
// we can ensure the update fails if the join doesn't work.
db_data
st.wallet()
.conn
.execute(
"DELETE FROM addresses WHERE cached_transparent_receiver_address = ?",
[Some(taddr.encode(&db_data.params))],
[Some(taddr.encode(&st.wallet().params))],
)
.unwrap();
let res2 = super::put_received_transparent_utxo(&db_data.conn, &db_data.params, &utxo2);
let res2 = st.wallet_mut().put_received_transparent_utxo(&utxo2);
assert_matches!(res2, Err(_));
}
}

View File

@ -988,11 +988,11 @@ mod tests {
use zcash_primitives::consensus::BlockHeight;
use super::SqliteShardStore;
use crate::{tests, wallet::init::init_wallet_db, WalletDb, SAPLING_TABLES_PREFIX};
use crate::{testing, wallet::init::init_wallet_db, WalletDb, SAPLING_TABLES_PREFIX};
fn new_tree(m: usize) -> ShardTree<SqliteShardStore<rusqlite::Connection, String, 3>, 4, 3> {
let data_file = NamedTempFile::new().unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), testing::network()).unwrap();
data_file.keep().unwrap();
init_wallet_db(&mut db_data, None).unwrap();
@ -1040,7 +1040,7 @@ mod tests {
#[test]
fn put_shard_roots() {
let data_file = NamedTempFile::new().unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), testing::network()).unwrap();
data_file.keep().unwrap();
init_wallet_db(&mut db_data, None).unwrap();

View File

@ -393,7 +393,7 @@ mod tests {
use crate::{
error::SqliteClientError,
tests::{self, network},
testing::{self, network},
wallet::scanning::priority_code,
AccountId, WalletDb,
};
@ -415,7 +415,7 @@ mod tests {
#[test]
fn verify_schema() {
let data_file = NamedTempFile::new().unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), testing::network()).unwrap();
init_wallet_db(&mut db_data, None).unwrap();
use regex::Regex;
@ -609,7 +609,7 @@ mod tests {
AND (scan_queue.block_range_end - 1) >= shard.subtree_end_height
)
WHERE scan_queue.priority > {}",
u32::from(tests::network().activation_height(NetworkUpgrade::Sapling).unwrap()),
u32::from(testing::network().activation_height(NetworkUpgrade::Sapling).unwrap()),
priority_code(&ScanPriority::Scanned),
),
// v_transactions
@ -852,11 +852,11 @@ mod tests {
)?;
let address = encode_payment_address(
tests::network().hrp_sapling_payment_address(),
testing::network().hrp_sapling_payment_address(),
&extfvk.default_address().1,
);
let extfvk = encode_extended_full_viewing_key(
tests::network().hrp_sapling_extended_full_viewing_key(),
testing::network().hrp_sapling_extended_full_viewing_key(),
extfvk,
);
wdb.conn.execute(
@ -874,10 +874,10 @@ mod tests {
let seed = [0xab; 32];
let account = AccountId::from(0);
let secret_key = sapling::spending_key(&seed, tests::network().coin_type(), account);
let secret_key = sapling::spending_key(&seed, testing::network().coin_type(), account);
let extfvk = secret_key.to_extended_full_viewing_key();
let data_file = NamedTempFile::new().unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), testing::network()).unwrap();
init_0_3_0(&mut db_data, &extfvk, account).unwrap();
init_wallet_db(&mut db_data, Some(Secret::new(seed.to_vec()))).unwrap();
}
@ -984,11 +984,11 @@ mod tests {
)?;
let address = encode_payment_address(
tests::network().hrp_sapling_payment_address(),
testing::network().hrp_sapling_payment_address(),
&extfvk.default_address().1,
);
let extfvk = encode_extended_full_viewing_key(
tests::network().hrp_sapling_extended_full_viewing_key(),
testing::network().hrp_sapling_extended_full_viewing_key(),
extfvk,
);
wdb.conn.execute(
@ -1040,10 +1040,10 @@ mod tests {
let seed = [0xab; 32];
let account = AccountId::from(0);
let secret_key = sapling::spending_key(&seed, tests::network().coin_type(), account);
let secret_key = sapling::spending_key(&seed, testing::network().coin_type(), account);
let extfvk = secret_key.to_extended_full_viewing_key();
let data_file = NamedTempFile::new().unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), testing::network()).unwrap();
init_autoshielding(&mut db_data, &extfvk, account).unwrap();
init_wallet_db(&mut db_data, Some(Secret::new(seed.to_vec()))).unwrap();
}
@ -1150,9 +1150,9 @@ mod tests {
[],
)?;
let ufvk_str = ufvk.encode(&tests::network());
let ufvk_str = ufvk.encode(&testing::network());
let address_str =
RecipientAddress::Unified(ufvk.default_address().0).encode(&tests::network());
RecipientAddress::Unified(ufvk.default_address().0).encode(&testing::network());
wdb.conn.execute(
"INSERT INTO accounts (account, ufvk, address, transparent_address)
VALUES (?, ?, ?, '')",
@ -1168,7 +1168,7 @@ mod tests {
{
let taddr =
RecipientAddress::Transparent(*ufvk.default_address().0.transparent().unwrap())
.encode(&tests::network());
.encode(&testing::network());
wdb.conn.execute(
"INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (0, 0, 0, x'000000')",
[],
@ -1188,9 +1188,10 @@ mod tests {
let seed = [0xab; 32];
let account = AccountId::from(0);
let secret_key = UnifiedSpendingKey::from_seed(&tests::network(), &seed, account).unwrap();
let secret_key =
UnifiedSpendingKey::from_seed(&testing::network(), &seed, account).unwrap();
let data_file = NamedTempFile::new().unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), testing::network()).unwrap();
init_main(
&mut db_data,
&secret_key.to_unified_full_viewing_key(),
@ -1203,7 +1204,7 @@ mod tests {
#[test]
fn init_accounts_table_only_works_once() {
let data_file = NamedTempFile::new().unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), testing::network()).unwrap();
init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap();
// We can call the function as many times as we want with no data
@ -1272,7 +1273,7 @@ mod tests {
#[test]
fn init_blocks_table_only_works_once() {
let data_file = NamedTempFile::new().unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), testing::network()).unwrap();
init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap();
// First call with data should initialise the blocks table
@ -1299,14 +1300,14 @@ mod tests {
#[test]
fn init_accounts_table_stores_correct_address() {
let data_file = NamedTempFile::new().unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), testing::network()).unwrap();
init_wallet_db(&mut db_data, None).unwrap();
let seed = [0u8; 32];
// Add an account to the wallet
let account_id = AccountId::from(0);
let usk = UnifiedSpendingKey::from_seed(&tests::network(), &seed, account_id).unwrap();
let usk = UnifiedSpendingKey::from_seed(&testing::network(), &seed, account_id).unwrap();
let ufvk = usk.to_unified_full_viewing_key();
let expected_address = ufvk.sapling().unwrap().default_address().1;
let ufvks = HashMap::from([(account_id, ufvk)]);

View File

@ -286,7 +286,7 @@ mod tests {
use zcash_primitives::zip32::AccountId;
use crate::{
tests,
testing,
wallet::init::{init_wallet_db_internal, migrations::addresses_table},
WalletDb,
};
@ -311,10 +311,10 @@ mod tests {
#[test]
fn transaction_views() {
let data_file = NamedTempFile::new().unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), testing::network()).unwrap();
init_wallet_db_internal(&mut db_data, None, &[addresses_table::MIGRATION_ID]).unwrap();
let usk =
UnifiedSpendingKey::from_seed(&tests::network(), &[0u8; 32][..], AccountId::from(0))
UnifiedSpendingKey::from_seed(&testing::network(), &[0u8; 32][..], AccountId::from(0))
.unwrap();
let ufvk = usk.to_unified_full_viewing_key();
@ -322,7 +322,7 @@ mod tests {
.conn
.execute(
"INSERT INTO accounts (account, ufvk) VALUES (0, ?)",
params![ufvk.encode(&tests::network())],
params![ufvk.encode(&testing::network())],
)
.unwrap();
@ -403,7 +403,7 @@ mod tests {
#[cfg(feature = "transparent-inputs")]
fn migrate_from_wm2() {
let data_file = NamedTempFile::new().unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), testing::network()).unwrap();
init_wallet_db_internal(
&mut db_data,
None,
@ -440,7 +440,7 @@ mod tests {
tx.write(&mut tx_bytes).unwrap();
let usk =
UnifiedSpendingKey::from_seed(&tests::network(), &[0u8; 32][..], AccountId::from(0))
UnifiedSpendingKey::from_seed(&testing::network(), &[0u8; 32][..], AccountId::from(0))
.unwrap();
let ufvk = usk.to_unified_full_viewing_key();
let (ua, _) = ufvk.default_address();
@ -451,11 +451,11 @@ mod tests {
.ok()
.map(|k| k.derive_address(0).unwrap())
})
.map(|a| a.encode(&tests::network()));
.map(|a| a.encode(&testing::network()));
db_data.conn.execute(
"INSERT INTO accounts (account, ufvk, address, transparent_address) VALUES (0, ?, ?, ?)",
params![ufvk.encode(&tests::network()), ua.encode(&tests::network()), &taddr]
params![ufvk.encode(&testing::network()), ua.encode(&testing::network()), &taddr]
).unwrap();
db_data
.conn

View File

@ -236,7 +236,7 @@ mod tests {
use zcash_primitives::zip32::AccountId;
use crate::{
tests,
testing,
wallet::init::{init_wallet_db_internal, migrations::v_transactions_net},
WalletDb,
};
@ -244,19 +244,19 @@ mod tests {
#[test]
fn received_notes_nullable_migration() {
let data_file = NamedTempFile::new().unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), testing::network()).unwrap();
init_wallet_db_internal(&mut db_data, None, &[v_transactions_net::MIGRATION_ID]).unwrap();
// Create an account in the wallet
let usk0 =
UnifiedSpendingKey::from_seed(&tests::network(), &[0u8; 32][..], AccountId::from(0))
UnifiedSpendingKey::from_seed(&testing::network(), &[0u8; 32][..], AccountId::from(0))
.unwrap();
let ufvk0 = usk0.to_unified_full_viewing_key();
db_data
.conn
.execute(
"INSERT INTO accounts (account, ufvk) VALUES (0, ?)",
params![ufvk0.encode(&tests::network())],
params![ufvk0.encode(&testing::network())],
)
.unwrap();

View File

@ -215,7 +215,7 @@ mod tests {
use zcash_primitives::zip32::AccountId;
use crate::{
tests,
testing,
wallet::init::{init_wallet_db_internal, migrations::add_transaction_views},
WalletDb,
};
@ -223,32 +223,32 @@ mod tests {
#[test]
fn v_transactions_net() {
let data_file = NamedTempFile::new().unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), testing::network()).unwrap();
init_wallet_db_internal(&mut db_data, None, &[add_transaction_views::MIGRATION_ID])
.unwrap();
// Create two accounts in the wallet.
let usk0 =
UnifiedSpendingKey::from_seed(&tests::network(), &[0u8; 32][..], AccountId::from(0))
UnifiedSpendingKey::from_seed(&testing::network(), &[0u8; 32][..], AccountId::from(0))
.unwrap();
let ufvk0 = usk0.to_unified_full_viewing_key();
db_data
.conn
.execute(
"INSERT INTO accounts (account, ufvk) VALUES (0, ?)",
params![ufvk0.encode(&tests::network())],
params![ufvk0.encode(&testing::network())],
)
.unwrap();
let usk1 =
UnifiedSpendingKey::from_seed(&tests::network(), &[1u8; 32][..], AccountId::from(1))
UnifiedSpendingKey::from_seed(&testing::network(), &[1u8; 32][..], AccountId::from(1))
.unwrap();
let ufvk1 = usk1.to_unified_full_viewing_key();
db_data
.conn
.execute(
"INSERT INTO accounts (account, ufvk) VALUES (1, ?)",
params![ufvk1.encode(&tests::network())],
params![ufvk1.encode(&testing::network())],
)
.unwrap();

View File

@ -403,14 +403,11 @@ pub(crate) fn put_received_note<T: ReceivedSaplingOutput>(
pub(crate) mod tests {
use std::{convert::Infallible, num::NonZeroU32};
use rusqlite::Connection;
use secrecy::Secret;
use tempfile::NamedTempFile;
use zcash_proofs::prover::LocalTxProver;
use zcash_primitives::{
block::BlockHash,
consensus::BranchId,
legacy::TransparentAddress,
memo::{Memo, MemoBytes},
@ -429,13 +426,8 @@ pub(crate) mod tests {
address::RecipientAddress,
data_api::{
self,
chain::scan_cached_blocks,
error::Error,
wallet::{
create_proposed_transaction, create_spend_to_address,
input_selection::{GreedyInputSelector, GreedyInputSelectorError},
propose_transfer, spend,
},
wallet::input_selection::{GreedyInputSelector, GreedyInputSelectorError},
ShieldedProtocol, WalletRead, WalletWrite,
},
decrypt_transaction,
@ -446,21 +438,15 @@ pub(crate) mod tests {
};
use crate::{
chain::init::init_cache_database,
error::SqliteClientError,
tests::{
self, fake_compact_block, insert_into_cache, network, sapling_activation_height,
AddressType,
},
wallet::{commitment_tree, get_balance, get_balance_at, init::init_wallet_db},
AccountId, BlockDb, NoteId, ReceivedNoteId, WalletDb,
testing::{self, network, AddressType, BlockCache, TestBuilder, TestState},
wallet::{commitment_tree, get_balance, get_balance_at},
AccountId, NoteId, ReceivedNoteId,
};
#[cfg(feature = "transparent-inputs")]
use {
zcash_client_backend::{
data_api::wallet::shield_transparent_funds, wallet::WalletTransparentOutput,
},
zcash_client_backend::wallet::WalletTransparentOutput,
zcash_primitives::transaction::components::{amount::NonNegativeAmount, OutPoint, TxOut},
};
@ -475,50 +461,30 @@ pub(crate) mod tests {
#[test]
fn send_proposed_transfer() {
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();
let mut st = TestBuilder::new().with_block_cache().build();
// Add an account to the wallet
let seed = Secret::new([0u8; 32].to_vec());
let (account, usk) = db_data.create_account(&seed).unwrap();
let (account, usk) = st.wallet_mut().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(60000).unwrap();
let (cb, _) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
&dfvk,
AddressType::DefaultExternal,
value,
0,
);
insert_into_cache(&db_cache, &cb);
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
st.scan_cached_blocks(h, 1);
// Verified balance matches total balance
let (_, anchor_height) = db_data
let (_, anchor_height) = st
.wallet()
.get_target_and_anchor_heights(NonZeroU32::new(1).unwrap())
.unwrap()
.unwrap();
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
value
);
assert_eq!(
get_balance_at(&db_data.conn, AccountId::from(0), anchor_height).unwrap(),
get_balance_at(&st.wallet().conn, AccountId::from(0), anchor_height).unwrap(),
value
);
@ -538,9 +504,7 @@ pub(crate) mod tests {
let change_strategy = fixed::SingleOutputChangeStrategy::new(fee_rule);
let input_selector =
&GreedyInputSelector::new(change_strategy, DustOutputPolicy::default());
let proposal_result = propose_transfer::<_, _, _, Infallible>(
&mut db_data,
&tests::network(),
let proposal_result = st.propose_transfer(
account,
input_selector,
request,
@ -549,10 +513,7 @@ pub(crate) mod tests {
assert_matches!(proposal_result, Ok(_));
let change_memo = "Test change memo".parse::<Memo>().unwrap();
let create_proposed_result = create_proposed_transaction::<_, _, Infallible, _>(
&mut db_data,
&tests::network(),
test_prover(),
let create_proposed_result = st.create_proposed_transaction(
&usk,
OvkPolicy::Sender,
proposal_result.unwrap(),
@ -564,18 +525,14 @@ pub(crate) mod tests {
let sent_tx_id = create_proposed_result.unwrap();
// Verify that the sent transaction was stored and that we can decrypt the memos
let tx = db_data
let tx = st
.wallet()
.get_transaction(sent_tx_id)
.expect("Created transaction was stored.");
let ufvks = [(account, usk.to_unified_full_viewing_key())]
.into_iter()
.collect();
let decrypted_outputs = decrypt_transaction(
&tests::network(),
sapling_activation_height() + 1,
&tx,
&ufvks,
);
let decrypted_outputs = decrypt_transaction(&testing::network(), h + 1, &tx, &ufvks);
assert_eq!(decrypted_outputs.len(), 2);
let mut found_tx_change_memo = false;
@ -592,7 +549,8 @@ pub(crate) mod tests {
assert!(found_tx_empty_memo);
// Verify that the stored sent notes match what we're expecting
let mut stmt_sent_notes = db_data
let mut stmt_sent_notes = st
.wallet()
.conn
.prepare(
"SELECT output_index
@ -622,7 +580,8 @@ pub(crate) mod tests {
let mut found_sent_change_memo = false;
let mut found_sent_empty_memo = false;
for sent_note_id in sent_note_ids {
match db_data
match st
.wallet()
.get_memo(sent_note_id)
.expect("Note id is valid")
.as_ref()
@ -642,20 +601,19 @@ pub(crate) mod tests {
// Check that querying for a nonexistent sent note returns None
assert_matches!(
db_data.get_memo(NoteId::new(sent_tx_id, ShieldedProtocol::Sapling, 12345)),
st.wallet()
.get_memo(NoteId::new(sent_tx_id, ShieldedProtocol::Sapling, 12345)),
Ok(None)
);
}
#[test]
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 mut st = TestBuilder::new().with_seed(Secret::new(vec![])).build();
// Add an account to the wallet
let seed = Secret::new([0u8; 32].to_vec());
let (_, usk) = db_data.create_account(&seed).unwrap();
let (_, usk) = st.wallet_mut().create_account(&seed).unwrap();
let dfvk = usk.sapling().to_diversifiable_full_viewing_key();
let to = dfvk.default_address().1.into();
@ -665,10 +623,7 @@ pub(crate) mod tests {
// Attempting to spend with a USK that is not in the wallet results in an error
assert_matches!(
create_spend_to_address(
&mut db_data,
&tests::network(),
test_prover(),
st.create_spend_to_address(
&usk1,
&to,
Amount::from_u64(1).unwrap(),
@ -682,28 +637,23 @@ pub(crate) mod tests {
#[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, None).unwrap();
let mut st = TestBuilder::new().build();
// Add an account to the wallet
let seed = Secret::new([0u8; 32].to_vec());
let (_, usk) = db_data.create_account(&seed).unwrap();
let (_, usk) = st.wallet_mut().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!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
Amount::zero()
);
// We cannot do anything if we aren't synchronised
assert_matches!(
create_spend_to_address(
&mut db_data,
&tests::network(),
test_prover(),
st.create_spend_to_address(
&usk,
&to,
Amount::from_u64(1).unwrap(),
@ -717,84 +667,49 @@ pub(crate) mod tests {
#[test]
fn create_to_address_fails_on_unverified_notes() {
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();
let mut st = TestBuilder::new().with_block_cache().build();
// Add an account to the wallet
let seed = Secret::new([0u8; 32].to_vec());
let (_, usk) = db_data.create_account(&seed).unwrap();
let (_, usk) = st.wallet_mut().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();
let (mut cb, _) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
&dfvk,
AddressType::DefaultExternal,
value,
0,
);
insert_into_cache(&db_cache, &cb);
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
st.scan_cached_blocks(h1, 1);
// Verified balance matches total balance
let (_, anchor_height) = db_data
let (_, anchor_height) = st
.wallet()
.get_target_and_anchor_heights(NonZeroU32::new(10).unwrap())
.unwrap()
.unwrap();
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
value
);
assert_eq!(
get_balance_at(&db_data.conn, AccountId::from(0), anchor_height).unwrap(),
get_balance_at(&st.wallet().conn, AccountId::from(0), anchor_height).unwrap(),
value
);
// Add more funds to the wallet in a second note
cb = fake_compact_block(
sapling_activation_height() + 1,
cb.hash(),
&dfvk,
AddressType::DefaultExternal,
value,
1,
)
.0;
insert_into_cache(&db_cache, &cb);
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 1,
1,
)
.unwrap();
let (h2, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
st.scan_cached_blocks(h2, 1);
// Verified balance does not include the second note
let (_, anchor_height2) = db_data
let (_, anchor_height2) = st
.wallet()
.get_target_and_anchor_heights(NonZeroU32::new(10).unwrap())
.unwrap()
.unwrap();
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
(value + value).unwrap()
);
assert_eq!(
get_balance_at(&db_data.conn, AccountId::from(0), anchor_height2).unwrap(),
get_balance_at(&st.wallet().conn, AccountId::from(0), anchor_height2).unwrap(),
value
);
@ -802,10 +717,7 @@ pub(crate) mod tests {
let extsk2 = ExtendedSpendingKey::master(&[]);
let to = extsk2.default_address().1.into();
assert_matches!(
create_spend_to_address(
&mut db_data,
&tests::network(),
test_prover(),
st.create_spend_to_address(
&usk,
&to,
Amount::from_u64(70000).unwrap(),
@ -823,33 +735,14 @@ pub(crate) mod tests {
// Mine blocks SAPLING_ACTIVATION_HEIGHT + 2 to 9 until just before the second
// note is verified
for i in 2..10 {
cb = fake_compact_block(
sapling_activation_height() + i,
cb.hash(),
&dfvk,
AddressType::DefaultExternal,
value,
i,
)
.0;
insert_into_cache(&db_cache, &cb);
for _ in 2..10 {
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
}
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 2,
8,
)
.unwrap();
st.scan_cached_blocks(h2 + 1, 8);
// Second spend still fails
assert_matches!(
create_spend_to_address(
&mut db_data,
&tests::network(),
test_prover(),
st.create_spend_to_address(
&usk,
&to,
Amount::from_u64(70000).unwrap(),
@ -866,31 +759,12 @@ pub(crate) mod tests {
);
// Mine block 11 so that the second note becomes verified
cb = fake_compact_block(
sapling_activation_height() + 10,
cb.hash(),
&dfvk,
AddressType::DefaultExternal,
value,
10,
)
.0;
insert_into_cache(&db_cache, &cb);
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 10,
1,
)
.unwrap();
let (h11, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
st.scan_cached_blocks(h11, 1);
// Second spend should now succeed
assert_matches!(
create_spend_to_address(
&mut db_data,
&tests::network(),
test_prover(),
st.create_spend_to_address(
&usk,
&to,
Amount::from_u64(70000).unwrap(),
@ -904,40 +778,22 @@ pub(crate) mod tests {
#[test]
fn create_to_address_fails_on_locked_notes() {
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, Some(Secret::new(vec![]))).unwrap();
let mut st = TestBuilder::new()
.with_block_cache()
.with_seed(Secret::new(vec![]))
.build();
// Add an account to the wallet
let seed = Secret::new([0u8; 32].to_vec());
let (_, usk) = db_data.create_account(&seed).unwrap();
let (_, usk) = st.wallet_mut().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();
let (mut cb, _) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
&dfvk,
AddressType::DefaultExternal,
value,
0,
);
insert_into_cache(&db_cache, &cb);
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
st.scan_cached_blocks(h1, 1);
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
value
);
@ -945,10 +801,7 @@ pub(crate) mod tests {
let extsk2 = ExtendedSpendingKey::master(&[]);
let to = extsk2.default_address().1.into();
assert_matches!(
create_spend_to_address(
&mut db_data,
&tests::network(),
test_prover(),
st.create_spend_to_address(
&usk,
&to,
Amount::from_u64(15000).unwrap(),
@ -961,10 +814,7 @@ pub(crate) mod tests {
// A second spend fails because there are no usable notes
assert_matches!(
create_spend_to_address(
&mut db_data,
&tests::network(),
test_prover(),
st.create_spend_to_address(
&usk,
&to,
Amount::from_u64(2000).unwrap(),
@ -982,32 +832,17 @@ pub(crate) mod tests {
// Mine blocks SAPLING_ACTIVATION_HEIGHT + 1 to 41 (that don't send us funds)
// until just before the first transaction expires
for i in 1..42 {
cb = fake_compact_block(
sapling_activation_height() + i,
cb.hash(),
st.generate_next_block(
&ExtendedSpendingKey::master(&[i as u8]).to_diversifiable_full_viewing_key(),
AddressType::DefaultExternal,
value,
i,
)
.0;
insert_into_cache(&db_cache, &cb);
);
}
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 1,
41,
)
.unwrap();
st.scan_cached_blocks(h1 + 1, 41);
// Second spend still fails
assert_matches!(
create_spend_to_address(
&mut db_data,
&tests::network(),
test_prover(),
st.create_spend_to_address(
&usk,
&to,
Amount::from_u64(2000).unwrap(),
@ -1023,30 +858,15 @@ pub(crate) mod tests {
);
// Mine block SAPLING_ACTIVATION_HEIGHT + 42 so that the first transaction expires
cb = fake_compact_block(
sapling_activation_height() + 42,
cb.hash(),
let (h43, _, _) = st.generate_next_block(
&ExtendedSpendingKey::master(&[42]).to_diversifiable_full_viewing_key(),
AddressType::DefaultExternal,
value,
42,
)
.0;
insert_into_cache(&db_cache, &cb);
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height() + 42,
1,
)
.unwrap();
);
st.scan_cached_blocks(h43, 1);
// Second spend should now succeed
create_spend_to_address(
&mut db_data,
&tests::network(),
test_prover(),
st.create_spend_to_address(
&usk,
&to,
Amount::from_u64(2000).unwrap(),
@ -1059,41 +879,19 @@ pub(crate) mod tests {
#[test]
fn ovk_policy_prevents_recovery_from_chain() {
let network = tests::network();
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(), network).unwrap();
init_wallet_db(&mut db_data, None).unwrap();
let mut st = TestBuilder::new().with_block_cache().build();
// Add an account to the wallet
let seed = Secret::new([0u8; 32].to_vec());
let (_, usk) = db_data.create_account(&seed).unwrap();
let (_, usk) = st.wallet_mut().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();
let (mut cb, _) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
&dfvk,
AddressType::DefaultExternal,
value,
0,
);
insert_into_cache(&db_cache, &cb);
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
st.scan_cached_blocks(h1, 1);
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
value
);
@ -1102,7 +900,7 @@ pub(crate) mod tests {
let to = addr2.into();
#[allow(clippy::type_complexity)]
let send_and_recover_with_policy = |db_data: &mut WalletDb<Connection, _>,
let send_and_recover_with_policy = |test: &mut TestState<BlockCache>,
ovk_policy|
-> Result<
Option<(Note, PaymentAddress, MemoBytes)>,
@ -1114,10 +912,7 @@ pub(crate) mod tests {
ReceivedNoteId,
>,
> {
let txid = create_spend_to_address(
db_data,
&tests::network(),
test_prover(),
let txid = test.create_spend_to_address(
&usk,
&to,
Amount::from_u64(15000).unwrap(),
@ -1127,7 +922,8 @@ pub(crate) mod tests {
)?;
// Fetch the transaction from the database
let raw_tx: Vec<_> = db_data
let raw_tx: Vec<_> = test
.wallet()
.conn
.query_row(
"SELECT raw FROM transactions
@ -1141,8 +937,8 @@ pub(crate) mod tests {
for output in tx.sapling_bundle().unwrap().shielded_outputs() {
// Find the output that decrypts with the external OVK
let result = try_sapling_output_recovery(
&network,
sapling_activation_height(),
&network(),
h1,
&dfvk.to_ovk(Scope::External),
output,
);
@ -1158,96 +954,61 @@ pub(crate) mod tests {
// Send some of the funds to another address, keeping history.
// The recipient output is decryptable by the sender.
assert_matches!(
send_and_recover_with_policy(&mut db_data, OvkPolicy::Sender),
send_and_recover_with_policy(&mut st, OvkPolicy::Sender),
Ok(Some((_, recovered_to, _))) if recovered_to == addr2
);
// Mine blocks SAPLING_ACTIVATION_HEIGHT + 1 to 42 (that don't send us funds)
// so that the first transaction expires
for i in 1..=42 {
cb = fake_compact_block(
sapling_activation_height() + i,
cb.hash(),
st.generate_next_block(
&ExtendedSpendingKey::master(&[i as u8]).to_diversifiable_full_viewing_key(),
AddressType::DefaultExternal,
value,
i,
)
.0;
insert_into_cache(&db_cache, &cb);
);
}
scan_cached_blocks(
&network,
&db_cache,
&mut db_data,
sapling_activation_height() + 1,
42,
)
.unwrap();
st.scan_cached_blocks(h1 + 1, 42);
// Send the funds again, discarding history.
// Neither transaction output is decryptable by the sender.
assert_matches!(
send_and_recover_with_policy(&mut db_data, OvkPolicy::Discard),
send_and_recover_with_policy(&mut st, OvkPolicy::Discard),
Ok(None)
);
}
#[test]
fn create_to_address_succeeds_to_t_addr_zero_change() {
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();
let mut st = TestBuilder::new().with_block_cache().build();
// Add an account to the wallet
let seed = Secret::new([0u8; 32].to_vec());
let (_, usk) = db_data.create_account(&seed).unwrap();
let (_, usk) = st.wallet_mut().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(60000).unwrap();
let (cb, _) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
&dfvk,
AddressType::DefaultExternal,
value,
0,
);
insert_into_cache(&db_cache, &cb);
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
st.scan_cached_blocks(h, 1);
// Verified balance matches total balance
let (_, anchor_height) = db_data
let (_, anchor_height) = st
.wallet()
.get_target_and_anchor_heights(NonZeroU32::new(1).unwrap())
.unwrap()
.unwrap();
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
value
);
assert_eq!(
get_balance_at(&db_data.conn, AccountId::from(0), anchor_height).unwrap(),
get_balance_at(&st.wallet().conn, AccountId::from(0), anchor_height).unwrap(),
value
);
let to = TransparentAddress::PublicKey([7; 20]).into();
assert_matches!(
create_spend_to_address(
&mut db_data,
&tests::network(),
test_prover(),
st.create_spend_to_address(
&usk,
&to,
Amount::from_u64(50000).unwrap(),
@ -1261,59 +1022,36 @@ pub(crate) mod tests {
#[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();
let mut st = TestBuilder::new().with_block_cache().build();
// Add an account to the wallet
let seed = Secret::new([0u8; 32].to_vec());
let (_, usk) = db_data.create_account(&seed).unwrap();
let (_, usk) = st.wallet_mut().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(60000).unwrap();
let (cb, _) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
&dfvk,
AddressType::Internal,
value,
0,
);
insert_into_cache(&db_cache, &cb);
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::Internal, value);
st.scan_cached_blocks(h, 1);
// Verified balance matches total balance
let (_, anchor_height) = db_data
let (_, anchor_height) = st
.wallet()
.get_target_and_anchor_heights(NonZeroU32::new(10).unwrap())
.unwrap()
.unwrap();
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
value
);
assert_eq!(
get_balance_at(&db_data.conn, AccountId::from(0), anchor_height).unwrap(),
get_balance_at(&st.wallet().conn, AccountId::from(0), anchor_height).unwrap(),
value
);
let to = TransparentAddress::PublicKey([7; 20]).into();
assert_matches!(
create_spend_to_address(
&mut db_data,
&tests::network(),
test_prover(),
st.create_spend_to_address(
&usk,
&to,
Amount::from_u64(50000).unwrap(),
@ -1327,65 +1065,44 @@ pub(crate) mod tests {
#[test]
fn zip317_spend() {
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();
let mut st = TestBuilder::new().with_block_cache().build();
// Add an account to the wallet
let seed = Secret::new([0u8; 32].to_vec());
let (_, usk) = db_data.create_account(&seed).unwrap();
let (_, usk) = st.wallet_mut().create_account(&seed).unwrap();
let dfvk = usk.sapling().to_diversifiable_full_viewing_key();
// Add funds to the wallet
let (mut cb, _) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
let (h1, _, _) = st.generate_next_block(
&dfvk,
AddressType::Internal,
Amount::from_u64(50000).unwrap(),
0,
);
insert_into_cache(&db_cache, &cb);
// Add 10 dust notes to the wallet
for i in 1..=10 {
cb = fake_compact_block(
sapling_activation_height() + i,
cb.hash(),
for _ in 1..=10 {
st.generate_next_block(
&dfvk,
AddressType::DefaultExternal,
Amount::from_u64(1000).unwrap(),
i,
)
.0;
insert_into_cache(&db_cache, &cb);
);
}
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
11,
)
.unwrap();
st.scan_cached_blocks(h1, 11);
// Verified balance matches total balance
let total = Amount::from_u64(60000).unwrap();
let (_, anchor_height) = db_data
let (_, anchor_height) = st
.wallet()
.get_target_and_anchor_heights(NonZeroU32::new(1).unwrap())
.unwrap()
.unwrap();
assert_eq!(
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
total
);
assert_eq!(
get_balance_at(&db_data.conn, AccountId::from(0), anchor_height).unwrap(),
get_balance_at(&st.wallet().conn, AccountId::from(0), anchor_height).unwrap(),
total
);
@ -1406,10 +1123,7 @@ pub(crate) mod tests {
.unwrap();
assert_matches!(
spend(
&mut db_data,
&tests::network(),
test_prover(),
st.spend(
&input_selector,
&usk,
req,
@ -1434,10 +1148,7 @@ pub(crate) mod tests {
.unwrap();
assert_matches!(
spend(
&mut db_data,
&tests::network(),
test_prover(),
st.spend(
&input_selector,
&usk,
req,
@ -1451,32 +1162,38 @@ pub(crate) mod tests {
#[test]
#[cfg(feature = "transparent-inputs")]
fn shield_transparent() {
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();
let mut st = TestBuilder::new().with_block_cache().build();
// Add an account to the wallet
let seed = Secret::new([0u8; 32].to_vec());
let (account_id, usk) = db_data.create_account(&seed).unwrap();
let (account_id, usk) = st.wallet_mut().create_account(&seed).unwrap();
let dfvk = usk.sapling().to_diversifiable_full_viewing_key();
let uaddr = db_data.get_current_address(account_id).unwrap().unwrap();
let uaddr = st
.wallet()
.get_current_address(account_id)
.unwrap()
.unwrap();
let taddr = uaddr.transparent().unwrap();
// Ensure that the wallet has at least one block
let (h, _, _) = st.generate_next_block(
&dfvk,
AddressType::Internal,
Amount::from_u64(50000).unwrap(),
);
st.scan_cached_blocks(h, 1);
let utxo = WalletTransparentOutput::from_parts(
OutPoint::new([1u8; 32], 1),
TxOut {
value: Amount::from_u64(10000).unwrap(),
script_pubkey: taddr.script(),
},
sapling_activation_height(),
h,
)
.unwrap();
let res0 = db_data.put_received_transparent_utxo(&utxo);
let res0 = st.wallet_mut().put_received_transparent_utxo(&utxo);
assert!(matches!(res0, Ok(_)));
let input_selector = GreedyInputSelector::new(
@ -1484,30 +1201,8 @@ pub(crate) mod tests {
DustOutputPolicy::default(),
);
// Ensure that the wallet has at least one block
let (cb, _) = fake_compact_block(
sapling_activation_height(),
BlockHash([0; 32]),
&dfvk,
AddressType::Internal,
Amount::from_u64(50000).unwrap(),
0,
);
insert_into_cache(&db_cache, &cb);
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
sapling_activation_height(),
1,
)
.unwrap();
assert_matches!(
shield_transparent_funds(
&mut db_data,
&tests::network(),
test_prover(),
st.shield_transparent_funds(
&input_selector,
NonNegativeAmount::from_u64(10000).unwrap(),
&usk,

View File

@ -738,11 +738,9 @@ mod tests {
use std::ops::Range;
use incrementalmerkletree::{Hashable, Level};
use rusqlite::Connection;
use secrecy::Secret;
use tempfile::NamedTempFile;
use zcash_client_backend::data_api::{
chain::{scan_cached_blocks, CommitmentTreeRoot},
chain::CommitmentTreeRoot,
scanning::{ScanPriority, ScanRange},
WalletCommitmentTrees, WalletRead, WalletWrite,
};
@ -754,16 +752,8 @@ mod tests {
};
use crate::{
chain::init::init_cache_database,
tests::{
self, fake_compact_block, init_test_accounts_table, insert_into_cache,
sapling_activation_height, AddressType,
},
wallet::{
init::{init_blocks_table, init_wallet_db},
scanning::suggest_scan_ranges,
},
BlockDb, WalletDb,
testing::{sapling_activation_height, AddressType, TestBuilder},
wallet::{init::init_blocks_table, scanning::suggest_scan_ranges},
};
use super::{join_nonoverlapping, Joined, RangeOrdering, SpanningTree};
@ -1093,21 +1083,18 @@ mod tests {
fn scan_complete() {
use ScanPriority::*;
let cache_file = NamedTempFile::new().unwrap();
let db_cache = BlockDb(Connection::open(cache_file.path()).unwrap());
init_cache_database(&db_cache).unwrap();
let mut st = TestBuilder::new()
.with_block_cache()
.with_seed(Secret::new(vec![]))
.with_test_account()
.build();
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();
// Add an account to the wallet.
let (dfvk, _taddr) = init_test_accounts_table(&mut db_data);
let dfvk = st.test_account_sapling().unwrap();
assert_matches!(
// In the following, we don't care what the root hashes are, they just need to be
// distinct.
db_data.put_sapling_subtree_roots(
st.wallet_mut().put_sapling_subtree_roots(
0,
&[
CommitmentTreeRoot::from_parts(
@ -1134,7 +1121,7 @@ mod tests {
let initial_height = sapling_activation_height() + 310;
let value = Amount::from_u64(50000).unwrap();
let (mut cb, _) = fake_compact_block(
st.generate_block_at(
initial_height,
BlockHash([0; 32]),
&dfvk,
@ -1142,36 +1129,21 @@ mod tests {
value,
initial_sapling_tree_size,
);
insert_into_cache(&db_cache, &cb);
for i in 1..=10 {
cb = fake_compact_block(
initial_height + i,
cb.hash(),
for _ in 1..=10 {
st.generate_next_block(
&dfvk,
AddressType::DefaultExternal,
Amount::from_u64(10000).unwrap(),
initial_sapling_tree_size + i,
)
.0;
insert_into_cache(&db_cache, &cb);
);
}
assert_matches!(
scan_cached_blocks(
&tests::network(),
&db_cache,
&mut db_data,
initial_height,
10,
),
Ok(())
);
st.scan_cached_blocks(initial_height, 10);
// Verify the that adjacent range needed to make the note spendable has been prioritized.
let sap_active = u32::from(sapling_activation_height());
assert_matches!(
db_data.suggest_scan_ranges(),
st.wallet().suggest_scan_ranges(),
Ok(scan_ranges) if scan_ranges == vec![
scan_range((sap_active + 300)..(sap_active + 310), FoundNote)
]
@ -1179,7 +1151,7 @@ mod tests {
// Check that the scanned range has been properly persisted.
assert_matches!(
suggest_scan_ranges(&db_data.conn, Scanned),
suggest_scan_ranges(&st.wallet().conn, Scanned),
Ok(scan_ranges) if scan_ranges == vec![
scan_range((sap_active + 300)..(sap_active + 310), FoundNote),
scan_range((sap_active + 310)..(sap_active + 320), Scanned)
@ -1189,14 +1161,15 @@ mod tests {
// Simulate the wallet going offline for a bit, update the chain tip to 20 blocks in the
// future.
assert_matches!(
db_data.update_chain_tip(sapling_activation_height() + 340),
st.wallet_mut()
.update_chain_tip(sapling_activation_height() + 340),
Ok(())
);
// Check the scan range again, we should see a `ChainTip` range for the period we've been
// offline.
assert_matches!(
db_data.suggest_scan_ranges(),
st.wallet().suggest_scan_ranges(),
Ok(scan_ranges) if scan_ranges == vec![
scan_range((sap_active + 320)..(sap_active + 341), ChainTip),
scan_range((sap_active + 300)..(sap_active + 310), ChainTip)
@ -1205,14 +1178,15 @@ mod tests {
// Now simulate a jump ahead more than 100 blocks.
assert_matches!(
db_data.update_chain_tip(sapling_activation_height() + 450),
st.wallet_mut()
.update_chain_tip(sapling_activation_height() + 450),
Ok(())
);
// Check the scan range again, we should see a `Validate` range for the previous wallet
// tip, and then a `ChainTip` for the remaining range.
assert_matches!(
db_data.suggest_scan_ranges(),
st.wallet().suggest_scan_ranges(),
Ok(scan_ranges) if scan_ranges == vec![
scan_range((sap_active + 319)..(sap_active + 329), Verify),
scan_range((sap_active + 329)..(sap_active + 451), ChainTip),
@ -1225,22 +1199,22 @@ mod tests {
fn init_blocks_table_creates_ignored_range() {
use ScanPriority::*;
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 mut st = TestBuilder::new().with_seed(Secret::new(vec![])).build();
let sap_active = db_data
let sap_active = st
.wallet()
.params
.activation_height(NetworkUpgrade::Sapling)
.unwrap();
// Initialise the blocks table. We use Canopy activation as an arbitrary birthday height
// that's greater than Sapling activation.
let birthday_height = db_data
let birthday_height = st
.wallet()
.params
.activation_height(NetworkUpgrade::Canopy)
.unwrap();
init_blocks_table(
&mut db_data,
st.wallet_mut(),
birthday_height,
BlockHash([1; 32]),
1,
@ -1256,12 +1230,12 @@ mod tests {
),
];
assert_matches!(
suggest_scan_ranges(&db_data.conn, Ignored),
suggest_scan_ranges(&st.wallet().conn, Ignored),
Ok(scan_ranges) if scan_ranges == expected
);
// Set up some shard history
db_data
st.wallet_mut()
.put_sapling_subtree_roots(
0,
&[
@ -1280,11 +1254,12 @@ mod tests {
.unwrap();
// Update the chain tip
let tip_height = db_data
let tip_height = st
.wallet()
.params
.activation_height(NetworkUpgrade::Nu5)
.unwrap();
db_data.update_chain_tip(tip_height).unwrap();
st.wallet_mut().update_chain_tip(tip_height).unwrap();
// Verify that the suggested scan ranges match what is expected
let expected = vec![
@ -1315,7 +1290,7 @@ mod tests {
];
assert_matches!(
suggest_scan_ranges(&db_data.conn, Ignored),
suggest_scan_ranges(&st.wallet().conn, Ignored),
Ok(scan_ranges) if scan_ranges == expected
);
}