Merge pull request #916 from zcash/sqlite-testing-helper
`zcash_client_sqlite`: Introduce `TestBuilder` and `TestRunner`
This commit is contained in:
commit
8c30c6a066
|
@ -325,7 +325,6 @@ mod tests {
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
use secrecy::Secret;
|
use secrecy::Secret;
|
||||||
use tempfile::NamedTempFile;
|
|
||||||
|
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
block::BlockHash,
|
block::BlockHash,
|
||||||
|
@ -336,9 +335,8 @@ mod tests {
|
||||||
use zcash_client_backend::{
|
use zcash_client_backend::{
|
||||||
address::RecipientAddress,
|
address::RecipientAddress,
|
||||||
data_api::{
|
data_api::{
|
||||||
chain::{error::Error, scan_cached_blocks},
|
chain::error::Error, wallet::input_selection::GreedyInputSelector, WalletRead,
|
||||||
wallet::{input_selection::GreedyInputSelector, spend},
|
WalletWrite,
|
||||||
WalletRead, WalletWrite,
|
|
||||||
},
|
},
|
||||||
fees::{zip317::SingleOutputChangeStrategy, DustOutputPolicy},
|
fees::{zip317::SingleOutputChangeStrategy, DustOutputPolicy},
|
||||||
scanning::ScanError,
|
scanning::ScanError,
|
||||||
|
@ -347,340 +345,192 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
chain::init::init_cache_database,
|
testing::{AddressType, TestBuilder},
|
||||||
tests::{
|
wallet::{get_balance, truncate_to_height},
|
||||||
self, fake_compact_block, fake_compact_block_spending, init_test_accounts_table,
|
AccountId,
|
||||||
insert_into_cache, sapling_activation_height, AddressType,
|
|
||||||
},
|
|
||||||
wallet::{get_balance, init::init_wallet_db, truncate_to_height},
|
|
||||||
AccountId, BlockDb, WalletDb,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn valid_chain_states() {
|
fn valid_chain_states() {
|
||||||
let cache_file = NamedTempFile::new().unwrap();
|
let mut st = TestBuilder::new()
|
||||||
let db_cache = BlockDb::for_path(cache_file.path()).unwrap();
|
.with_block_cache()
|
||||||
init_cache_database(&db_cache).unwrap();
|
.with_seed(Secret::new(vec![]))
|
||||||
|
.with_test_account()
|
||||||
|
.build();
|
||||||
|
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let dfvk = st.test_account_sapling().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);
|
|
||||||
|
|
||||||
// Empty chain should return None
|
// 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
|
// Create a fake CompactBlock sending value to the address
|
||||||
let (cb, _) = fake_compact_block(
|
let (h1, _, _) = st.generate_next_block(
|
||||||
sapling_activation_height(),
|
|
||||||
BlockHash([0; 32]),
|
|
||||||
&dfvk,
|
&dfvk,
|
||||||
AddressType::DefaultExternal,
|
AddressType::DefaultExternal,
|
||||||
Amount::from_u64(5).unwrap(),
|
Amount::from_u64(5).unwrap(),
|
||||||
0,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
insert_into_cache(&db_cache, &cb);
|
|
||||||
|
|
||||||
// Scan the cache
|
// Scan the cache
|
||||||
scan_cached_blocks(
|
st.scan_cached_blocks(h1, 1);
|
||||||
&tests::network(),
|
|
||||||
&db_cache,
|
|
||||||
&mut db_data,
|
|
||||||
sapling_activation_height(),
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Create a second fake CompactBlock sending more value to the address
|
// Create a second fake CompactBlock sending more value to the address
|
||||||
let (cb2, _) = fake_compact_block(
|
let (h2, _, _) = st.generate_next_block(
|
||||||
sapling_activation_height() + 1,
|
|
||||||
cb.hash(),
|
|
||||||
&dfvk,
|
&dfvk,
|
||||||
AddressType::DefaultExternal,
|
AddressType::DefaultExternal,
|
||||||
Amount::from_u64(7).unwrap(),
|
Amount::from_u64(7).unwrap(),
|
||||||
1,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
insert_into_cache(&db_cache, &cb2);
|
|
||||||
|
|
||||||
// Scanning should detect no inconsistencies
|
// Scanning should detect no inconsistencies
|
||||||
assert_matches!(
|
st.scan_cached_blocks(h2, 1);
|
||||||
scan_cached_blocks(
|
|
||||||
&tests::network(),
|
|
||||||
&db_cache,
|
|
||||||
&mut db_data,
|
|
||||||
sapling_activation_height() + 1,
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
Ok(())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_chain_cache_disconnected() {
|
fn invalid_chain_cache_disconnected() {
|
||||||
let cache_file = NamedTempFile::new().unwrap();
|
let mut st = TestBuilder::new()
|
||||||
let db_cache = BlockDb::for_path(cache_file.path()).unwrap();
|
.with_block_cache()
|
||||||
init_cache_database(&db_cache).unwrap();
|
.with_seed(Secret::new(vec![]))
|
||||||
|
.with_test_account()
|
||||||
|
.build();
|
||||||
|
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let dfvk = st.test_account_sapling().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);
|
|
||||||
|
|
||||||
// Create some fake CompactBlocks
|
// Create some fake CompactBlocks
|
||||||
let (cb, _) = fake_compact_block(
|
let (h, _, _) = st.generate_next_block(
|
||||||
sapling_activation_height(),
|
|
||||||
BlockHash([0; 32]),
|
|
||||||
&dfvk,
|
&dfvk,
|
||||||
AddressType::DefaultExternal,
|
AddressType::DefaultExternal,
|
||||||
Amount::from_u64(5).unwrap(),
|
Amount::from_u64(5).unwrap(),
|
||||||
0,
|
|
||||||
);
|
);
|
||||||
let (cb2, _) = fake_compact_block(
|
let (last_contiguous_height, _, _) = st.generate_next_block(
|
||||||
sapling_activation_height() + 1,
|
|
||||||
cb.hash(),
|
|
||||||
&dfvk,
|
&dfvk,
|
||||||
AddressType::DefaultExternal,
|
AddressType::DefaultExternal,
|
||||||
Amount::from_u64(7).unwrap(),
|
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
|
// Scanning the cache should find no inconsistencies
|
||||||
assert_matches!(
|
st.scan_cached_blocks(h, 2);
|
||||||
scan_cached_blocks(
|
|
||||||
&tests::network(),
|
|
||||||
&db_cache,
|
|
||||||
&mut db_data,
|
|
||||||
sapling_activation_height(),
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
Ok(())
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create more fake CompactBlocks that don't connect to the scanned ones
|
// Create more fake CompactBlocks that don't connect to the scanned ones
|
||||||
let (cb3, _) = fake_compact_block(
|
let disconnect_height = last_contiguous_height + 1;
|
||||||
sapling_activation_height() + 2,
|
st.generate_block_at(
|
||||||
|
disconnect_height,
|
||||||
BlockHash([1; 32]),
|
BlockHash([1; 32]),
|
||||||
&dfvk,
|
&dfvk,
|
||||||
AddressType::DefaultExternal,
|
AddressType::DefaultExternal,
|
||||||
Amount::from_u64(8).unwrap(),
|
Amount::from_u64(8).unwrap(),
|
||||||
2,
|
2,
|
||||||
);
|
);
|
||||||
let (cb4, _) = fake_compact_block(
|
st.generate_next_block(
|
||||||
sapling_activation_height() + 3,
|
|
||||||
cb3.hash(),
|
|
||||||
&dfvk,
|
&dfvk,
|
||||||
AddressType::DefaultExternal,
|
AddressType::DefaultExternal,
|
||||||
Amount::from_u64(3).unwrap(),
|
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
|
// Data+cache chain should be invalid at the data/cache boundary
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
scan_cached_blocks(
|
st.try_scan_cached_blocks(
|
||||||
&tests::network(),
|
disconnect_height,
|
||||||
&db_cache,
|
|
||||||
&mut db_data,
|
|
||||||
sapling_activation_height() + 2,
|
|
||||||
2
|
2
|
||||||
),
|
),
|
||||||
Err(Error::Scan(ScanError::PrevHashMismatch { at_height }))
|
Err(Error::Scan(ScanError::PrevHashMismatch { at_height }))
|
||||||
if at_height == sapling_activation_height() + 2
|
if at_height == disconnect_height
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn data_db_truncation() {
|
fn data_db_truncation() {
|
||||||
let cache_file = NamedTempFile::new().unwrap();
|
let mut st = TestBuilder::new()
|
||||||
let db_cache = BlockDb::for_path(cache_file.path()).unwrap();
|
.with_block_cache()
|
||||||
init_cache_database(&db_cache).unwrap();
|
.with_seed(Secret::new(vec![]))
|
||||||
|
.with_test_account()
|
||||||
|
.build();
|
||||||
|
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let dfvk = st.test_account_sapling().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);
|
|
||||||
|
|
||||||
// Account balance should be zero
|
// Account balance should be zero
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
Amount::zero()
|
Amount::zero()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create fake CompactBlocks sending value to the address
|
// Create fake CompactBlocks sending value to the address
|
||||||
let value = Amount::from_u64(5).unwrap();
|
let value = Amount::from_u64(5).unwrap();
|
||||||
let value2 = Amount::from_u64(7).unwrap();
|
let value2 = Amount::from_u64(7).unwrap();
|
||||||
let (cb, _) = fake_compact_block(
|
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||||
sapling_activation_height(),
|
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value2);
|
||||||
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);
|
|
||||||
|
|
||||||
// Scan the cache
|
// Scan the cache
|
||||||
scan_cached_blocks(
|
st.scan_cached_blocks(h, 2);
|
||||||
&tests::network(),
|
|
||||||
&db_cache,
|
|
||||||
&mut db_data,
|
|
||||||
sapling_activation_height(),
|
|
||||||
2,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Account balance should reflect both received notes
|
// Account balance should reflect both received notes
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
(value + value2).unwrap()
|
(value + value2).unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
// "Rewind" to height of last scanned block
|
// "Rewind" to height of last scanned block
|
||||||
db_data
|
st.wallet_mut()
|
||||||
.transactionally(|wdb| {
|
.transactionally(|wdb| truncate_to_height(wdb.conn.0, &wdb.params, h + 1))
|
||||||
truncate_to_height(wdb.conn.0, &wdb.params, sapling_activation_height() + 1)
|
|
||||||
})
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Account balance should be unaltered
|
// Account balance should be unaltered
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
(value + value2).unwrap()
|
(value + value2).unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Rewind so that one block is dropped
|
// Rewind so that one block is dropped
|
||||||
db_data
|
st.wallet_mut()
|
||||||
.transactionally(|wdb| {
|
.transactionally(|wdb| truncate_to_height(wdb.conn.0, &wdb.params, h))
|
||||||
truncate_to_height(wdb.conn.0, &wdb.params, sapling_activation_height())
|
|
||||||
})
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Account balance should only contain the first received note
|
// Account balance should only contain the first received note
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
value
|
value
|
||||||
);
|
);
|
||||||
|
|
||||||
// Scan the cache again
|
// Scan the cache again
|
||||||
scan_cached_blocks(
|
st.scan_cached_blocks(h, 2);
|
||||||
&tests::network(),
|
|
||||||
&db_cache,
|
|
||||||
&mut db_data,
|
|
||||||
sapling_activation_height(),
|
|
||||||
2,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Account balance should again reflect both received notes
|
// Account balance should again reflect both received notes
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
(value + value2).unwrap()
|
(value + value2).unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn scan_cached_blocks_allows_blocks_out_of_order() {
|
fn scan_cached_blocks_allows_blocks_out_of_order() {
|
||||||
let cache_file = NamedTempFile::new().unwrap();
|
let mut st = TestBuilder::new()
|
||||||
let db_cache = BlockDb::for_path(cache_file.path()).unwrap();
|
.with_block_cache()
|
||||||
init_cache_database(&db_cache).unwrap();
|
.with_seed(Secret::new(vec![]))
|
||||||
|
.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
|
// Add an account to the wallet
|
||||||
let seed = Secret::new([0u8; 32].to_vec());
|
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 dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
||||||
|
|
||||||
// Create a block with height SAPLING_ACTIVATION_HEIGHT
|
// Create a block with height SAPLING_ACTIVATION_HEIGHT
|
||||||
let value = Amount::from_u64(50000).unwrap();
|
let value = Amount::from_u64(50000).unwrap();
|
||||||
let (cb1, _) = fake_compact_block(
|
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||||
sapling_activation_height(),
|
st.scan_cached_blocks(h1, 1);
|
||||||
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();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
value
|
value
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create blocks to reach SAPLING_ACTIVATION_HEIGHT + 2
|
// Create blocks to reach SAPLING_ACTIVATION_HEIGHT + 2
|
||||||
let (cb2, _) = fake_compact_block(
|
let (h2, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||||
sapling_activation_height() + 1,
|
let (h3, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||||
cb1.hash(),
|
|
||||||
&dfvk,
|
|
||||||
AddressType::DefaultExternal,
|
|
||||||
value,
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
let (cb3, _) = fake_compact_block(
|
|
||||||
sapling_activation_height() + 2,
|
|
||||||
cb2.hash(),
|
|
||||||
&dfvk,
|
|
||||||
AddressType::DefaultExternal,
|
|
||||||
value,
|
|
||||||
2,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Scan the later block first
|
// Scan the later block first
|
||||||
insert_into_cache(&db_cache, &cb3);
|
st.scan_cached_blocks(h3, 1);
|
||||||
assert_matches!(
|
|
||||||
scan_cached_blocks(
|
|
||||||
&tests::network(),
|
|
||||||
&db_cache,
|
|
||||||
&mut db_data,
|
|
||||||
sapling_activation_height() + 2,
|
|
||||||
1
|
|
||||||
),
|
|
||||||
Ok(_)
|
|
||||||
);
|
|
||||||
|
|
||||||
// If we add a block of height SAPLING_ACTIVATION_HEIGHT + 1, we can now scan that
|
// Now scan the block of height SAPLING_ACTIVATION_HEIGHT + 1
|
||||||
insert_into_cache(&db_cache, &cb2);
|
st.scan_cached_blocks(h2, 1);
|
||||||
scan_cached_blocks(
|
|
||||||
&tests::network(),
|
|
||||||
&db_cache,
|
|
||||||
&mut db_data,
|
|
||||||
sapling_activation_height() + 1,
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
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()
|
Amount::from_u64(150_000).unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -699,10 +549,7 @@ mod tests {
|
||||||
DustOutputPolicy::default(),
|
DustOutputPolicy::default(),
|
||||||
);
|
);
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
spend(
|
st.spend(
|
||||||
&mut db_data,
|
|
||||||
&tests::network(),
|
|
||||||
crate::wallet::sapling::tests::test_prover(),
|
|
||||||
&input_selector,
|
&input_selector,
|
||||||
&usk,
|
&usk,
|
||||||
req,
|
req,
|
||||||
|
@ -715,124 +562,74 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn scan_cached_blocks_finds_received_notes() {
|
fn scan_cached_blocks_finds_received_notes() {
|
||||||
let cache_file = NamedTempFile::new().unwrap();
|
let mut st = TestBuilder::new()
|
||||||
let db_cache = BlockDb::for_path(cache_file.path()).unwrap();
|
.with_block_cache()
|
||||||
init_cache_database(&db_cache).unwrap();
|
.with_seed(Secret::new(vec![]))
|
||||||
|
.with_test_account()
|
||||||
|
.build();
|
||||||
|
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let dfvk = st.test_account_sapling().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);
|
|
||||||
|
|
||||||
// Account balance should be zero
|
// Account balance should be zero
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
Amount::zero()
|
Amount::zero()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create a fake CompactBlock sending value to the address
|
// Create a fake CompactBlock sending value to the address
|
||||||
let value = Amount::from_u64(5).unwrap();
|
let value = Amount::from_u64(5).unwrap();
|
||||||
let (cb, _) = fake_compact_block(
|
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||||
sapling_activation_height(),
|
|
||||||
BlockHash([0; 32]),
|
|
||||||
&dfvk,
|
|
||||||
AddressType::DefaultExternal,
|
|
||||||
value,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
insert_into_cache(&db_cache, &cb);
|
|
||||||
|
|
||||||
// Scan the cache
|
// Scan the cache
|
||||||
scan_cached_blocks(
|
st.scan_cached_blocks(h1, 1);
|
||||||
&tests::network(),
|
|
||||||
&db_cache,
|
|
||||||
&mut db_data,
|
|
||||||
sapling_activation_height(),
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Account balance should reflect the received note
|
// Account balance should reflect the received note
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
value
|
value
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create a second fake CompactBlock sending more value to the address
|
// Create a second fake CompactBlock sending more value to the address
|
||||||
let value2 = Amount::from_u64(7).unwrap();
|
let value2 = Amount::from_u64(7).unwrap();
|
||||||
let (cb2, _) = fake_compact_block(
|
let (h2, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value2);
|
||||||
sapling_activation_height() + 1,
|
|
||||||
cb.hash(),
|
|
||||||
&dfvk,
|
|
||||||
AddressType::DefaultExternal,
|
|
||||||
value2,
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
insert_into_cache(&db_cache, &cb2);
|
|
||||||
|
|
||||||
// Scan the cache again
|
// Scan the cache again
|
||||||
scan_cached_blocks(
|
st.scan_cached_blocks(h2, 1);
|
||||||
&tests::network(),
|
|
||||||
&db_cache,
|
|
||||||
&mut db_data,
|
|
||||||
sapling_activation_height() + 1,
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Account balance should reflect both received notes
|
// Account balance should reflect both received notes
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
(value + value2).unwrap()
|
(value + value2).unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn scan_cached_blocks_finds_change_notes() {
|
fn scan_cached_blocks_finds_change_notes() {
|
||||||
let cache_file = NamedTempFile::new().unwrap();
|
let mut st = TestBuilder::new()
|
||||||
let db_cache = BlockDb::for_path(cache_file.path()).unwrap();
|
.with_block_cache()
|
||||||
init_cache_database(&db_cache).unwrap();
|
.with_seed(Secret::new(vec![]))
|
||||||
|
.with_test_account()
|
||||||
|
.build();
|
||||||
|
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let dfvk = st.test_account_sapling().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);
|
|
||||||
|
|
||||||
// Account balance should be zero
|
// Account balance should be zero
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
Amount::zero()
|
Amount::zero()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create a fake CompactBlock sending value to the address
|
// Create a fake CompactBlock sending value to the address
|
||||||
let value = Amount::from_u64(5).unwrap();
|
let value = Amount::from_u64(5).unwrap();
|
||||||
let (cb, nf) = fake_compact_block(
|
let (received_height, _, nf) =
|
||||||
sapling_activation_height(),
|
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||||
BlockHash([0; 32]),
|
|
||||||
&dfvk,
|
|
||||||
AddressType::DefaultExternal,
|
|
||||||
value,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
insert_into_cache(&db_cache, &cb);
|
|
||||||
|
|
||||||
// Scan the cache
|
// Scan the cache
|
||||||
scan_cached_blocks(
|
st.scan_cached_blocks(received_height, 1);
|
||||||
&tests::network(),
|
|
||||||
&db_cache,
|
|
||||||
&mut db_data,
|
|
||||||
sapling_activation_height(),
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Account balance should reflect the received note
|
// Account balance should reflect the received note
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
value
|
value
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -840,113 +637,60 @@ mod tests {
|
||||||
let extsk2 = ExtendedSpendingKey::master(&[0]);
|
let extsk2 = ExtendedSpendingKey::master(&[0]);
|
||||||
let to2 = extsk2.default_address().1;
|
let to2 = extsk2.default_address().1;
|
||||||
let value2 = Amount::from_u64(2).unwrap();
|
let value2 = Amount::from_u64(2).unwrap();
|
||||||
insert_into_cache(
|
let (spent_height, _) = st.generate_next_block_spending(&dfvk, (nf, value), to2, value2);
|
||||||
&db_cache,
|
|
||||||
&fake_compact_block_spending(
|
|
||||||
sapling_activation_height() + 1,
|
|
||||||
cb.hash(),
|
|
||||||
(nf, value),
|
|
||||||
&dfvk,
|
|
||||||
to2,
|
|
||||||
value2,
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Scan the cache again
|
// Scan the cache again
|
||||||
scan_cached_blocks(
|
st.scan_cached_blocks(spent_height, 1);
|
||||||
&tests::network(),
|
|
||||||
&db_cache,
|
|
||||||
&mut db_data,
|
|
||||||
sapling_activation_height() + 1,
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Account balance should equal the change
|
// Account balance should equal the change
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
(value - value2).unwrap()
|
(value - value2).unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn scan_cached_blocks_detects_spends_out_of_order() {
|
fn scan_cached_blocks_detects_spends_out_of_order() {
|
||||||
let cache_file = NamedTempFile::new().unwrap();
|
let mut st = TestBuilder::new()
|
||||||
let db_cache = BlockDb::for_path(cache_file.path()).unwrap();
|
.with_block_cache()
|
||||||
init_cache_database(&db_cache).unwrap();
|
.with_seed(Secret::new(vec![]))
|
||||||
|
.with_test_account()
|
||||||
|
.build();
|
||||||
|
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let dfvk = st.test_account_sapling().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);
|
|
||||||
|
|
||||||
// Account balance should be zero
|
// Account balance should be zero
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
Amount::zero()
|
Amount::zero()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create a fake CompactBlock sending value to the address
|
// Create a fake CompactBlock sending value to the address
|
||||||
let value = Amount::from_u64(5).unwrap();
|
let value = Amount::from_u64(5).unwrap();
|
||||||
let (cb, nf) = fake_compact_block(
|
let (received_height, _, nf) =
|
||||||
sapling_activation_height(),
|
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||||
BlockHash([0; 32]),
|
|
||||||
&dfvk,
|
|
||||||
AddressType::DefaultExternal,
|
|
||||||
value,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
insert_into_cache(&db_cache, &cb);
|
|
||||||
|
|
||||||
// Create a second fake CompactBlock spending value from the address
|
// Create a second fake CompactBlock spending value from the address
|
||||||
let extsk2 = ExtendedSpendingKey::master(&[0]);
|
let extsk2 = ExtendedSpendingKey::master(&[0]);
|
||||||
let to2 = extsk2.default_address().1;
|
let to2 = extsk2.default_address().1;
|
||||||
let value2 = Amount::from_u64(2).unwrap();
|
let value2 = Amount::from_u64(2).unwrap();
|
||||||
insert_into_cache(
|
let (spent_height, _) = st.generate_next_block_spending(&dfvk, (nf, value), to2, value2);
|
||||||
&db_cache,
|
|
||||||
&fake_compact_block_spending(
|
|
||||||
sapling_activation_height() + 1,
|
|
||||||
cb.hash(),
|
|
||||||
(nf, value),
|
|
||||||
&dfvk,
|
|
||||||
to2,
|
|
||||||
value2,
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Scan the spending block first.
|
// Scan the spending block first.
|
||||||
scan_cached_blocks(
|
st.scan_cached_blocks(spent_height, 1);
|
||||||
&tests::network(),
|
|
||||||
&db_cache,
|
|
||||||
&mut db_data,
|
|
||||||
sapling_activation_height() + 1,
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Account balance should equal the change
|
// Account balance should equal the change
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
(value - value2).unwrap()
|
(value - value2).unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Now scan the block in which we received the note that was spent.
|
// Now scan the block in which we received the note that was spent.
|
||||||
scan_cached_blocks(
|
st.scan_cached_blocks(received_height, 1);
|
||||||
&tests::network(),
|
|
||||||
&db_cache,
|
|
||||||
&mut db_data,
|
|
||||||
sapling_activation_height(),
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Account balance should be the same.
|
// Account balance should be the same.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
(value - value2).unwrap()
|
(value - value2).unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,9 @@ pub mod serialization;
|
||||||
pub mod wallet;
|
pub mod wallet;
|
||||||
use wallet::commitment_tree::{self, put_shard_roots};
|
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
|
/// 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
|
/// consistent with the bound in zcashd, and allows block data deeper than
|
||||||
/// this delta from the chain tip to be pruned.
|
/// this delta from the chain tip to be pruned.
|
||||||
|
@ -1082,337 +1085,32 @@ extern crate assert_matches;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use prost::Message;
|
use zcash_client_backend::data_api::{WalletRead, WalletWrite};
|
||||||
use rand_core::{OsRng, RngCore};
|
|
||||||
use rusqlite::params;
|
use crate::{testing::TestBuilder, AccountId};
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
#[cfg(feature = "unstable")]
|
#[cfg(feature = "unstable")]
|
||||||
use std::{fs::File, path::Path};
|
use zcash_primitives::{consensus::Parameters, transaction::components::Amount};
|
||||||
|
|
||||||
#[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;
|
|
||||||
|
|
||||||
#[cfg(feature = "unstable")]
|
#[cfg(feature = "unstable")]
|
||||||
use super::{
|
use zcash_client_backend::keys::sapling;
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "unstable")]
|
#[cfg(feature = "unstable")]
|
||||||
pub(crate) fn store_in_fsblockdb<P: AsRef<Path>>(
|
use crate::testing::AddressType;
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub(crate) fn get_next_available_address() {
|
pub(crate) fn get_next_available_address() {
|
||||||
use tempfile::NamedTempFile;
|
let mut st = TestBuilder::new().with_test_account().build();
|
||||||
|
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
|
||||||
let mut db_data = WalletDb::for_path(data_file.path(), network()).unwrap();
|
|
||||||
|
|
||||||
let account = AccountId::from(0);
|
let account = AccountId::from(0);
|
||||||
init_wallet_db(&mut db_data, None).unwrap();
|
let current_addr = st.wallet().get_current_address(account).unwrap();
|
||||||
init_test_accounts_table_ufvk(&mut db_data);
|
|
||||||
|
|
||||||
let current_addr = db_data.get_current_address(account).unwrap();
|
|
||||||
assert!(current_addr.is_some());
|
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!(addr2.is_some());
|
||||||
assert_ne!(current_addr, addr2);
|
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);
|
assert_eq!(addr2, addr2_cur);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1420,23 +1118,18 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn transparent_receivers() {
|
fn transparent_receivers() {
|
||||||
use secrecy::Secret;
|
use secrecy::Secret;
|
||||||
use tempfile::NamedTempFile;
|
|
||||||
|
|
||||||
use crate::{chain::init::init_cache_database, wallet::init::init_wallet_db};
|
let st = TestBuilder::new()
|
||||||
|
.with_block_cache()
|
||||||
let cache_file = NamedTempFile::new().unwrap();
|
.with_seed(Secret::new(vec![]))
|
||||||
let db_cache = BlockDb::for_path(cache_file.path()).unwrap();
|
.with_test_account()
|
||||||
init_cache_database(&db_cache).unwrap();
|
.build();
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
// Add an account to the wallet.
|
// 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 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.
|
// The receiver for the default UA should be in the set.
|
||||||
assert!(receivers.contains_key(ufvk.default_address().0.transparent().unwrap()));
|
assert!(receivers.contains_key(ufvk.default_address().0.transparent().unwrap()));
|
||||||
|
@ -1448,74 +1141,44 @@ mod tests {
|
||||||
#[cfg(feature = "unstable")]
|
#[cfg(feature = "unstable")]
|
||||||
#[test]
|
#[test]
|
||||||
pub(crate) fn fsblockdb_api() {
|
pub(crate) fn fsblockdb_api() {
|
||||||
// Initialise a BlockMeta DB in a new directory.
|
let mut st = TestBuilder::new().with_fs_block_cache().build();
|
||||||
let fsblockdb_root = tempfile::tempdir().unwrap();
|
|
||||||
let mut db_meta = FsBlockDb::for_path(&fsblockdb_root).unwrap();
|
|
||||||
init_blockmeta_db(&mut db_meta).unwrap();
|
|
||||||
|
|
||||||
// The BlockMeta DB starts off empty.
|
// 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.
|
// Generate some fake CompactBlocks.
|
||||||
let seed = [0u8; 32];
|
let seed = [0u8; 32];
|
||||||
let account = AccountId::from(0);
|
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 dfvk = extsk.to_diversifiable_full_viewing_key();
|
||||||
let (cb1, _) = fake_compact_block(
|
let (h1, meta1, _) = st.generate_next_block(
|
||||||
BlockHeight::from_u32(1),
|
|
||||||
BlockHash([1; 32]),
|
|
||||||
&dfvk,
|
&dfvk,
|
||||||
AddressType::DefaultExternal,
|
AddressType::DefaultExternal,
|
||||||
Amount::from_u64(5).unwrap(),
|
Amount::from_u64(5).unwrap(),
|
||||||
0,
|
|
||||||
);
|
);
|
||||||
let (cb2, _) = fake_compact_block(
|
let (h2, meta2, _) = st.generate_next_block(
|
||||||
BlockHeight::from_u32(2),
|
|
||||||
BlockHash([2; 32]),
|
|
||||||
&dfvk,
|
&dfvk,
|
||||||
AddressType::DefaultExternal,
|
AddressType::DefaultExternal,
|
||||||
Amount::from_u64(10).unwrap(),
|
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.
|
// 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.
|
// 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.
|
// The BlockMeta DB now sees blocks up to height 2.
|
||||||
assert_eq!(
|
assert_eq!(st.cache().get_max_cached_height().unwrap(), Some(h2),);
|
||||||
db_meta.get_max_cached_height().unwrap(),
|
assert_eq!(st.cache().find_block(h1).unwrap(), Some(meta1));
|
||||||
Some(BlockHeight::from_u32(2)),
|
assert_eq!(st.cache().find_block(h2).unwrap(), Some(meta2));
|
||||||
);
|
assert_eq!(st.cache().find_block(h2 + 1).unwrap(), None);
|
||||||
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);
|
|
||||||
|
|
||||||
// Rewinding to height 1 should cause the metadata for height 2 to be deleted.
|
// Rewinding to height 1 should cause the metadata for height 2 to be deleted.
|
||||||
db_meta
|
st.cache().truncate_to_height(h1).unwrap();
|
||||||
.truncate_to_height(BlockHeight::from_u32(1))
|
assert_eq!(st.cache().get_max_cached_height().unwrap(), Some(h1));
|
||||||
.unwrap();
|
assert_eq!(st.cache().find_block(h1).unwrap(), Some(meta1));
|
||||||
assert_eq!(
|
assert_eq!(st.cache().find_block(h2).unwrap(), None);
|
||||||
db_meta.get_max_cached_height().unwrap(),
|
assert_eq!(st.cache().find_block(h2 + 1).unwrap(), None);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -1572,17 +1572,12 @@ mod tests {
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
use secrecy::Secret;
|
use secrecy::Secret;
|
||||||
use tempfile::NamedTempFile;
|
|
||||||
|
|
||||||
use zcash_primitives::transaction::components::Amount;
|
use zcash_primitives::transaction::components::Amount;
|
||||||
|
|
||||||
use zcash_client_backend::data_api::WalletRead;
|
use zcash_client_backend::data_api::WalletRead;
|
||||||
|
|
||||||
use crate::{
|
use crate::{testing::TestBuilder, AccountId};
|
||||||
tests,
|
|
||||||
wallet::{get_current_address, init::init_wallet_db},
|
|
||||||
AccountId, WalletDb,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::get_balance;
|
use super::get_balance;
|
||||||
|
|
||||||
|
@ -1599,22 +1594,20 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_database_has_no_balance() {
|
fn empty_database_has_no_balance() {
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let st = TestBuilder::new()
|
||||||
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
|
.with_seed(Secret::new(vec![]))
|
||||||
init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap();
|
.with_test_account()
|
||||||
|
.build();
|
||||||
// Add an account to the wallet
|
|
||||||
tests::init_test_accounts_table(&mut db_data);
|
|
||||||
|
|
||||||
// The account should be empty
|
// The account should be empty
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
Amount::zero()
|
Amount::zero()
|
||||||
);
|
);
|
||||||
|
|
||||||
// We can't get an anchor height, as we have not scanned any blocks.
|
// We can't get an anchor height, as we have not scanned any blocks.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
db_data
|
st.wallet()
|
||||||
.get_target_and_anchor_heights(NonZeroU32::new(10).unwrap())
|
.get_target_and_anchor_heights(NonZeroU32::new(10).unwrap())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
None
|
None
|
||||||
|
@ -1622,11 +1615,11 @@ mod tests {
|
||||||
|
|
||||||
// An invalid account has zero balance
|
// An invalid account has zero balance
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
get_current_address(&db_data.conn, &db_data.params, AccountId::from(1)),
|
st.wallet().get_current_address(AccountId::from(1)),
|
||||||
Ok(None)
|
Ok(None)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
Amount::zero()
|
Amount::zero()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1634,17 +1627,20 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
fn put_received_transparent_utxo() {
|
fn put_received_transparent_utxo() {
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let mut st = TestBuilder::new().build();
|
||||||
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
|
|
||||||
init_wallet_db(&mut db_data, None).unwrap();
|
|
||||||
|
|
||||||
// Add an account to the wallet
|
// Add an account to the wallet
|
||||||
let seed = Secret::new([0u8; 32].to_vec());
|
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 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();
|
let taddr = uaddr.transparent().unwrap();
|
||||||
|
|
||||||
let bal_absent = db_data
|
let bal_absent = st
|
||||||
|
.wallet()
|
||||||
.get_transparent_balances(account_id, BlockHeight::from_u32(12345))
|
.get_transparent_balances(account_id, BlockHeight::from_u32(12345))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(bal_absent.is_empty());
|
assert!(bal_absent.is_empty());
|
||||||
|
@ -1659,7 +1655,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.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(_));
|
assert_matches!(res0, Ok(_));
|
||||||
|
|
||||||
// Change the mined height of the UTXO and upsert; we should get back
|
// Change the mined height of the UTXO and upsert; we should get back
|
||||||
|
@ -1673,13 +1669,11 @@ mod tests {
|
||||||
BlockHeight::from_u32(34567),
|
BlockHeight::from_u32(34567),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.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!(res1, Ok(id) if id == res0.unwrap());
|
||||||
|
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
super::get_unspent_transparent_outputs(
|
st.wallet().get_unspent_transparent_outputs(
|
||||||
&db_data.conn,
|
|
||||||
&db_data.params,
|
|
||||||
taddr,
|
taddr,
|
||||||
BlockHeight::from_u32(12345),
|
BlockHeight::from_u32(12345),
|
||||||
&[]
|
&[]
|
||||||
|
@ -1688,9 +1682,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
super::get_unspent_transparent_outputs(
|
st.wallet().get_unspent_transparent_outputs(
|
||||||
&db_data.conn,
|
|
||||||
&db_data.params,
|
|
||||||
taddr,
|
taddr,
|
||||||
BlockHeight::from_u32(34567),
|
BlockHeight::from_u32(34567),
|
||||||
&[]
|
&[]
|
||||||
|
@ -1702,21 +1694,21 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_matches!(
|
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()
|
Ok(h) if h.get(taddr) == Amount::from_u64(100000).ok().as_ref()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Artificially delete the address from the addresses table so that
|
// Artificially delete the address from the addresses table so that
|
||||||
// we can ensure the update fails if the join doesn't work.
|
// we can ensure the update fails if the join doesn't work.
|
||||||
db_data
|
st.wallet()
|
||||||
.conn
|
.conn
|
||||||
.execute(
|
.execute(
|
||||||
"DELETE FROM addresses WHERE cached_transparent_receiver_address = ?",
|
"DELETE FROM addresses WHERE cached_transparent_receiver_address = ?",
|
||||||
[Some(taddr.encode(&db_data.params))],
|
[Some(taddr.encode(&st.wallet().params))],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.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(_));
|
assert_matches!(res2, Err(_));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -988,11 +988,11 @@ mod tests {
|
||||||
use zcash_primitives::consensus::BlockHeight;
|
use zcash_primitives::consensus::BlockHeight;
|
||||||
|
|
||||||
use super::SqliteShardStore;
|
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> {
|
fn new_tree(m: usize) -> ShardTree<SqliteShardStore<rusqlite::Connection, String, 3>, 4, 3> {
|
||||||
let data_file = NamedTempFile::new().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();
|
||||||
data_file.keep().unwrap();
|
data_file.keep().unwrap();
|
||||||
|
|
||||||
init_wallet_db(&mut db_data, None).unwrap();
|
init_wallet_db(&mut db_data, None).unwrap();
|
||||||
|
@ -1040,7 +1040,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn put_shard_roots() {
|
fn put_shard_roots() {
|
||||||
let data_file = NamedTempFile::new().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();
|
||||||
data_file.keep().unwrap();
|
data_file.keep().unwrap();
|
||||||
|
|
||||||
init_wallet_db(&mut db_data, None).unwrap();
|
init_wallet_db(&mut db_data, None).unwrap();
|
||||||
|
|
|
@ -393,7 +393,7 @@ mod tests {
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::SqliteClientError,
|
error::SqliteClientError,
|
||||||
tests::{self, network},
|
testing::{self, network},
|
||||||
wallet::scanning::priority_code,
|
wallet::scanning::priority_code,
|
||||||
AccountId, WalletDb,
|
AccountId, WalletDb,
|
||||||
};
|
};
|
||||||
|
@ -415,7 +415,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn verify_schema() {
|
fn verify_schema() {
|
||||||
let data_file = NamedTempFile::new().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_wallet_db(&mut db_data, None).unwrap();
|
init_wallet_db(&mut db_data, None).unwrap();
|
||||||
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
@ -609,7 +609,7 @@ mod tests {
|
||||||
AND (scan_queue.block_range_end - 1) >= shard.subtree_end_height
|
AND (scan_queue.block_range_end - 1) >= shard.subtree_end_height
|
||||||
)
|
)
|
||||||
WHERE scan_queue.priority > {}",
|
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),
|
priority_code(&ScanPriority::Scanned),
|
||||||
),
|
),
|
||||||
// v_transactions
|
// v_transactions
|
||||||
|
@ -852,11 +852,11 @@ mod tests {
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let address = encode_payment_address(
|
let address = encode_payment_address(
|
||||||
tests::network().hrp_sapling_payment_address(),
|
testing::network().hrp_sapling_payment_address(),
|
||||||
&extfvk.default_address().1,
|
&extfvk.default_address().1,
|
||||||
);
|
);
|
||||||
let extfvk = encode_extended_full_viewing_key(
|
let extfvk = encode_extended_full_viewing_key(
|
||||||
tests::network().hrp_sapling_extended_full_viewing_key(),
|
testing::network().hrp_sapling_extended_full_viewing_key(),
|
||||||
extfvk,
|
extfvk,
|
||||||
);
|
);
|
||||||
wdb.conn.execute(
|
wdb.conn.execute(
|
||||||
|
@ -874,10 +874,10 @@ mod tests {
|
||||||
|
|
||||||
let seed = [0xab; 32];
|
let seed = [0xab; 32];
|
||||||
let account = AccountId::from(0);
|
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 extfvk = secret_key.to_extended_full_viewing_key();
|
||||||
let data_file = NamedTempFile::new().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_0_3_0(&mut db_data, &extfvk, account).unwrap();
|
init_0_3_0(&mut db_data, &extfvk, account).unwrap();
|
||||||
init_wallet_db(&mut db_data, Some(Secret::new(seed.to_vec()))).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(
|
let address = encode_payment_address(
|
||||||
tests::network().hrp_sapling_payment_address(),
|
testing::network().hrp_sapling_payment_address(),
|
||||||
&extfvk.default_address().1,
|
&extfvk.default_address().1,
|
||||||
);
|
);
|
||||||
let extfvk = encode_extended_full_viewing_key(
|
let extfvk = encode_extended_full_viewing_key(
|
||||||
tests::network().hrp_sapling_extended_full_viewing_key(),
|
testing::network().hrp_sapling_extended_full_viewing_key(),
|
||||||
extfvk,
|
extfvk,
|
||||||
);
|
);
|
||||||
wdb.conn.execute(
|
wdb.conn.execute(
|
||||||
|
@ -1040,10 +1040,10 @@ mod tests {
|
||||||
|
|
||||||
let seed = [0xab; 32];
|
let seed = [0xab; 32];
|
||||||
let account = AccountId::from(0);
|
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 extfvk = secret_key.to_extended_full_viewing_key();
|
||||||
let data_file = NamedTempFile::new().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_autoshielding(&mut db_data, &extfvk, account).unwrap();
|
init_autoshielding(&mut db_data, &extfvk, account).unwrap();
|
||||||
init_wallet_db(&mut db_data, Some(Secret::new(seed.to_vec()))).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 =
|
let address_str =
|
||||||
RecipientAddress::Unified(ufvk.default_address().0).encode(&tests::network());
|
RecipientAddress::Unified(ufvk.default_address().0).encode(&testing::network());
|
||||||
wdb.conn.execute(
|
wdb.conn.execute(
|
||||||
"INSERT INTO accounts (account, ufvk, address, transparent_address)
|
"INSERT INTO accounts (account, ufvk, address, transparent_address)
|
||||||
VALUES (?, ?, ?, '')",
|
VALUES (?, ?, ?, '')",
|
||||||
|
@ -1168,7 +1168,7 @@ mod tests {
|
||||||
{
|
{
|
||||||
let taddr =
|
let taddr =
|
||||||
RecipientAddress::Transparent(*ufvk.default_address().0.transparent().unwrap())
|
RecipientAddress::Transparent(*ufvk.default_address().0.transparent().unwrap())
|
||||||
.encode(&tests::network());
|
.encode(&testing::network());
|
||||||
wdb.conn.execute(
|
wdb.conn.execute(
|
||||||
"INSERT INTO blocks (height, hash, time, sapling_tree) VALUES (0, 0, 0, x'000000')",
|
"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 seed = [0xab; 32];
|
||||||
let account = AccountId::from(0);
|
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 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(
|
init_main(
|
||||||
&mut db_data,
|
&mut db_data,
|
||||||
&secret_key.to_unified_full_viewing_key(),
|
&secret_key.to_unified_full_viewing_key(),
|
||||||
|
@ -1203,7 +1204,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn init_accounts_table_only_works_once() {
|
fn init_accounts_table_only_works_once() {
|
||||||
let data_file = NamedTempFile::new().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_wallet_db(&mut db_data, Some(Secret::new(vec![]))).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
|
// We can call the function as many times as we want with no data
|
||||||
|
@ -1272,7 +1273,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn init_blocks_table_only_works_once() {
|
fn init_blocks_table_only_works_once() {
|
||||||
let data_file = NamedTempFile::new().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_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap();
|
init_wallet_db(&mut db_data, Some(Secret::new(vec![]))).unwrap();
|
||||||
|
|
||||||
// First call with data should initialise the blocks table
|
// First call with data should initialise the blocks table
|
||||||
|
@ -1299,14 +1300,14 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn init_accounts_table_stores_correct_address() {
|
fn init_accounts_table_stores_correct_address() {
|
||||||
let data_file = NamedTempFile::new().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_wallet_db(&mut db_data, None).unwrap();
|
init_wallet_db(&mut db_data, None).unwrap();
|
||||||
|
|
||||||
let seed = [0u8; 32];
|
let seed = [0u8; 32];
|
||||||
|
|
||||||
// Add an account to the wallet
|
// Add an account to the wallet
|
||||||
let account_id = AccountId::from(0);
|
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 ufvk = usk.to_unified_full_viewing_key();
|
||||||
let expected_address = ufvk.sapling().unwrap().default_address().1;
|
let expected_address = ufvk.sapling().unwrap().default_address().1;
|
||||||
let ufvks = HashMap::from([(account_id, ufvk)]);
|
let ufvks = HashMap::from([(account_id, ufvk)]);
|
||||||
|
|
|
@ -286,7 +286,7 @@ mod tests {
|
||||||
use zcash_primitives::zip32::AccountId;
|
use zcash_primitives::zip32::AccountId;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
tests,
|
testing,
|
||||||
wallet::init::{init_wallet_db_internal, migrations::addresses_table},
|
wallet::init::{init_wallet_db_internal, migrations::addresses_table},
|
||||||
WalletDb,
|
WalletDb,
|
||||||
};
|
};
|
||||||
|
@ -311,10 +311,10 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn transaction_views() {
|
fn transaction_views() {
|
||||||
let data_file = NamedTempFile::new().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_wallet_db_internal(&mut db_data, None, &[addresses_table::MIGRATION_ID]).unwrap();
|
init_wallet_db_internal(&mut db_data, None, &[addresses_table::MIGRATION_ID]).unwrap();
|
||||||
let usk =
|
let usk =
|
||||||
UnifiedSpendingKey::from_seed(&tests::network(), &[0u8; 32][..], AccountId::from(0))
|
UnifiedSpendingKey::from_seed(&testing::network(), &[0u8; 32][..], AccountId::from(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let ufvk = usk.to_unified_full_viewing_key();
|
let ufvk = usk.to_unified_full_viewing_key();
|
||||||
|
|
||||||
|
@ -322,7 +322,7 @@ mod tests {
|
||||||
.conn
|
.conn
|
||||||
.execute(
|
.execute(
|
||||||
"INSERT INTO accounts (account, ufvk) VALUES (0, ?)",
|
"INSERT INTO accounts (account, ufvk) VALUES (0, ?)",
|
||||||
params![ufvk.encode(&tests::network())],
|
params![ufvk.encode(&testing::network())],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -403,7 +403,7 @@ mod tests {
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
fn migrate_from_wm2() {
|
fn migrate_from_wm2() {
|
||||||
let data_file = NamedTempFile::new().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_wallet_db_internal(
|
init_wallet_db_internal(
|
||||||
&mut db_data,
|
&mut db_data,
|
||||||
None,
|
None,
|
||||||
|
@ -440,7 +440,7 @@ mod tests {
|
||||||
tx.write(&mut tx_bytes).unwrap();
|
tx.write(&mut tx_bytes).unwrap();
|
||||||
|
|
||||||
let usk =
|
let usk =
|
||||||
UnifiedSpendingKey::from_seed(&tests::network(), &[0u8; 32][..], AccountId::from(0))
|
UnifiedSpendingKey::from_seed(&testing::network(), &[0u8; 32][..], AccountId::from(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let ufvk = usk.to_unified_full_viewing_key();
|
let ufvk = usk.to_unified_full_viewing_key();
|
||||||
let (ua, _) = ufvk.default_address();
|
let (ua, _) = ufvk.default_address();
|
||||||
|
@ -451,11 +451,11 @@ mod tests {
|
||||||
.ok()
|
.ok()
|
||||||
.map(|k| k.derive_address(0).unwrap())
|
.map(|k| k.derive_address(0).unwrap())
|
||||||
})
|
})
|
||||||
.map(|a| a.encode(&tests::network()));
|
.map(|a| a.encode(&testing::network()));
|
||||||
|
|
||||||
db_data.conn.execute(
|
db_data.conn.execute(
|
||||||
"INSERT INTO accounts (account, ufvk, address, transparent_address) VALUES (0, ?, ?, ?)",
|
"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();
|
).unwrap();
|
||||||
db_data
|
db_data
|
||||||
.conn
|
.conn
|
||||||
|
|
|
@ -236,7 +236,7 @@ mod tests {
|
||||||
use zcash_primitives::zip32::AccountId;
|
use zcash_primitives::zip32::AccountId;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
tests,
|
testing,
|
||||||
wallet::init::{init_wallet_db_internal, migrations::v_transactions_net},
|
wallet::init::{init_wallet_db_internal, migrations::v_transactions_net},
|
||||||
WalletDb,
|
WalletDb,
|
||||||
};
|
};
|
||||||
|
@ -244,19 +244,19 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn received_notes_nullable_migration() {
|
fn received_notes_nullable_migration() {
|
||||||
let data_file = NamedTempFile::new().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_wallet_db_internal(&mut db_data, None, &[v_transactions_net::MIGRATION_ID]).unwrap();
|
init_wallet_db_internal(&mut db_data, None, &[v_transactions_net::MIGRATION_ID]).unwrap();
|
||||||
|
|
||||||
// Create an account in the wallet
|
// Create an account in the wallet
|
||||||
let usk0 =
|
let usk0 =
|
||||||
UnifiedSpendingKey::from_seed(&tests::network(), &[0u8; 32][..], AccountId::from(0))
|
UnifiedSpendingKey::from_seed(&testing::network(), &[0u8; 32][..], AccountId::from(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let ufvk0 = usk0.to_unified_full_viewing_key();
|
let ufvk0 = usk0.to_unified_full_viewing_key();
|
||||||
db_data
|
db_data
|
||||||
.conn
|
.conn
|
||||||
.execute(
|
.execute(
|
||||||
"INSERT INTO accounts (account, ufvk) VALUES (0, ?)",
|
"INSERT INTO accounts (account, ufvk) VALUES (0, ?)",
|
||||||
params![ufvk0.encode(&tests::network())],
|
params![ufvk0.encode(&testing::network())],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -215,7 +215,7 @@ mod tests {
|
||||||
use zcash_primitives::zip32::AccountId;
|
use zcash_primitives::zip32::AccountId;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
tests,
|
testing,
|
||||||
wallet::init::{init_wallet_db_internal, migrations::add_transaction_views},
|
wallet::init::{init_wallet_db_internal, migrations::add_transaction_views},
|
||||||
WalletDb,
|
WalletDb,
|
||||||
};
|
};
|
||||||
|
@ -223,32 +223,32 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn v_transactions_net() {
|
fn v_transactions_net() {
|
||||||
let data_file = NamedTempFile::new().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_wallet_db_internal(&mut db_data, None, &[add_transaction_views::MIGRATION_ID])
|
init_wallet_db_internal(&mut db_data, None, &[add_transaction_views::MIGRATION_ID])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Create two accounts in the wallet.
|
// Create two accounts in the wallet.
|
||||||
let usk0 =
|
let usk0 =
|
||||||
UnifiedSpendingKey::from_seed(&tests::network(), &[0u8; 32][..], AccountId::from(0))
|
UnifiedSpendingKey::from_seed(&testing::network(), &[0u8; 32][..], AccountId::from(0))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let ufvk0 = usk0.to_unified_full_viewing_key();
|
let ufvk0 = usk0.to_unified_full_viewing_key();
|
||||||
db_data
|
db_data
|
||||||
.conn
|
.conn
|
||||||
.execute(
|
.execute(
|
||||||
"INSERT INTO accounts (account, ufvk) VALUES (0, ?)",
|
"INSERT INTO accounts (account, ufvk) VALUES (0, ?)",
|
||||||
params![ufvk0.encode(&tests::network())],
|
params![ufvk0.encode(&testing::network())],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let usk1 =
|
let usk1 =
|
||||||
UnifiedSpendingKey::from_seed(&tests::network(), &[1u8; 32][..], AccountId::from(1))
|
UnifiedSpendingKey::from_seed(&testing::network(), &[1u8; 32][..], AccountId::from(1))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let ufvk1 = usk1.to_unified_full_viewing_key();
|
let ufvk1 = usk1.to_unified_full_viewing_key();
|
||||||
db_data
|
db_data
|
||||||
.conn
|
.conn
|
||||||
.execute(
|
.execute(
|
||||||
"INSERT INTO accounts (account, ufvk) VALUES (1, ?)",
|
"INSERT INTO accounts (account, ufvk) VALUES (1, ?)",
|
||||||
params![ufvk1.encode(&tests::network())],
|
params![ufvk1.encode(&testing::network())],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -403,14 +403,11 @@ pub(crate) fn put_received_note<T: ReceivedSaplingOutput>(
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use std::{convert::Infallible, num::NonZeroU32};
|
use std::{convert::Infallible, num::NonZeroU32};
|
||||||
|
|
||||||
use rusqlite::Connection;
|
|
||||||
use secrecy::Secret;
|
use secrecy::Secret;
|
||||||
use tempfile::NamedTempFile;
|
|
||||||
|
|
||||||
use zcash_proofs::prover::LocalTxProver;
|
use zcash_proofs::prover::LocalTxProver;
|
||||||
|
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
block::BlockHash,
|
|
||||||
consensus::BranchId,
|
consensus::BranchId,
|
||||||
legacy::TransparentAddress,
|
legacy::TransparentAddress,
|
||||||
memo::{Memo, MemoBytes},
|
memo::{Memo, MemoBytes},
|
||||||
|
@ -429,13 +426,8 @@ pub(crate) mod tests {
|
||||||
address::RecipientAddress,
|
address::RecipientAddress,
|
||||||
data_api::{
|
data_api::{
|
||||||
self,
|
self,
|
||||||
chain::scan_cached_blocks,
|
|
||||||
error::Error,
|
error::Error,
|
||||||
wallet::{
|
wallet::input_selection::{GreedyInputSelector, GreedyInputSelectorError},
|
||||||
create_proposed_transaction, create_spend_to_address,
|
|
||||||
input_selection::{GreedyInputSelector, GreedyInputSelectorError},
|
|
||||||
propose_transfer, spend,
|
|
||||||
},
|
|
||||||
ShieldedProtocol, WalletRead, WalletWrite,
|
ShieldedProtocol, WalletRead, WalletWrite,
|
||||||
},
|
},
|
||||||
decrypt_transaction,
|
decrypt_transaction,
|
||||||
|
@ -446,21 +438,15 @@ pub(crate) mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
chain::init::init_cache_database,
|
|
||||||
error::SqliteClientError,
|
error::SqliteClientError,
|
||||||
tests::{
|
testing::{self, network, AddressType, BlockCache, TestBuilder, TestState},
|
||||||
self, fake_compact_block, insert_into_cache, network, sapling_activation_height,
|
wallet::{commitment_tree, get_balance, get_balance_at},
|
||||||
AddressType,
|
AccountId, NoteId, ReceivedNoteId,
|
||||||
},
|
|
||||||
wallet::{commitment_tree, get_balance, get_balance_at, init::init_wallet_db},
|
|
||||||
AccountId, BlockDb, NoteId, ReceivedNoteId, WalletDb,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
use {
|
use {
|
||||||
zcash_client_backend::{
|
zcash_client_backend::wallet::WalletTransparentOutput,
|
||||||
data_api::wallet::shield_transparent_funds, wallet::WalletTransparentOutput,
|
|
||||||
},
|
|
||||||
zcash_primitives::transaction::components::{amount::NonNegativeAmount, OutPoint, TxOut},
|
zcash_primitives::transaction::components::{amount::NonNegativeAmount, OutPoint, TxOut},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -475,50 +461,30 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn send_proposed_transfer() {
|
fn send_proposed_transfer() {
|
||||||
let cache_file = NamedTempFile::new().unwrap();
|
let mut st = TestBuilder::new().with_block_cache().build();
|
||||||
let db_cache = BlockDb(Connection::open(cache_file.path()).unwrap());
|
|
||||||
init_cache_database(&db_cache).unwrap();
|
|
||||||
|
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
|
||||||
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
|
|
||||||
init_wallet_db(&mut db_data, None).unwrap();
|
|
||||||
|
|
||||||
// Add an account to the wallet
|
// Add an account to the wallet
|
||||||
let seed = Secret::new([0u8; 32].to_vec());
|
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();
|
let dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
||||||
|
|
||||||
// Add funds to the wallet in a single note
|
// Add funds to the wallet in a single note
|
||||||
let value = Amount::from_u64(60000).unwrap();
|
let value = Amount::from_u64(60000).unwrap();
|
||||||
let (cb, _) = fake_compact_block(
|
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||||
sapling_activation_height(),
|
st.scan_cached_blocks(h, 1);
|
||||||
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();
|
|
||||||
|
|
||||||
// Verified balance matches total balance
|
// Verified balance matches total balance
|
||||||
let (_, anchor_height) = db_data
|
let (_, anchor_height) = st
|
||||||
|
.wallet()
|
||||||
.get_target_and_anchor_heights(NonZeroU32::new(1).unwrap())
|
.get_target_and_anchor_heights(NonZeroU32::new(1).unwrap())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
value
|
value
|
||||||
);
|
);
|
||||||
assert_eq!(
|
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
|
value
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -538,9 +504,7 @@ pub(crate) mod tests {
|
||||||
let change_strategy = fixed::SingleOutputChangeStrategy::new(fee_rule);
|
let change_strategy = fixed::SingleOutputChangeStrategy::new(fee_rule);
|
||||||
let input_selector =
|
let input_selector =
|
||||||
&GreedyInputSelector::new(change_strategy, DustOutputPolicy::default());
|
&GreedyInputSelector::new(change_strategy, DustOutputPolicy::default());
|
||||||
let proposal_result = propose_transfer::<_, _, _, Infallible>(
|
let proposal_result = st.propose_transfer(
|
||||||
&mut db_data,
|
|
||||||
&tests::network(),
|
|
||||||
account,
|
account,
|
||||||
input_selector,
|
input_selector,
|
||||||
request,
|
request,
|
||||||
|
@ -549,10 +513,7 @@ pub(crate) mod tests {
|
||||||
assert_matches!(proposal_result, Ok(_));
|
assert_matches!(proposal_result, Ok(_));
|
||||||
|
|
||||||
let change_memo = "Test change memo".parse::<Memo>().unwrap();
|
let change_memo = "Test change memo".parse::<Memo>().unwrap();
|
||||||
let create_proposed_result = create_proposed_transaction::<_, _, Infallible, _>(
|
let create_proposed_result = st.create_proposed_transaction(
|
||||||
&mut db_data,
|
|
||||||
&tests::network(),
|
|
||||||
test_prover(),
|
|
||||||
&usk,
|
&usk,
|
||||||
OvkPolicy::Sender,
|
OvkPolicy::Sender,
|
||||||
proposal_result.unwrap(),
|
proposal_result.unwrap(),
|
||||||
|
@ -564,18 +525,14 @@ pub(crate) mod tests {
|
||||||
let sent_tx_id = create_proposed_result.unwrap();
|
let sent_tx_id = create_proposed_result.unwrap();
|
||||||
|
|
||||||
// Verify that the sent transaction was stored and that we can decrypt the memos
|
// 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)
|
.get_transaction(sent_tx_id)
|
||||||
.expect("Created transaction was stored.");
|
.expect("Created transaction was stored.");
|
||||||
let ufvks = [(account, usk.to_unified_full_viewing_key())]
|
let ufvks = [(account, usk.to_unified_full_viewing_key())]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
let decrypted_outputs = decrypt_transaction(
|
let decrypted_outputs = decrypt_transaction(&testing::network(), h + 1, &tx, &ufvks);
|
||||||
&tests::network(),
|
|
||||||
sapling_activation_height() + 1,
|
|
||||||
&tx,
|
|
||||||
&ufvks,
|
|
||||||
);
|
|
||||||
assert_eq!(decrypted_outputs.len(), 2);
|
assert_eq!(decrypted_outputs.len(), 2);
|
||||||
|
|
||||||
let mut found_tx_change_memo = false;
|
let mut found_tx_change_memo = false;
|
||||||
|
@ -592,7 +549,8 @@ pub(crate) mod tests {
|
||||||
assert!(found_tx_empty_memo);
|
assert!(found_tx_empty_memo);
|
||||||
|
|
||||||
// Verify that the stored sent notes match what we're expecting
|
// 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
|
.conn
|
||||||
.prepare(
|
.prepare(
|
||||||
"SELECT output_index
|
"SELECT output_index
|
||||||
|
@ -622,7 +580,8 @@ pub(crate) mod tests {
|
||||||
let mut found_sent_change_memo = false;
|
let mut found_sent_change_memo = false;
|
||||||
let mut found_sent_empty_memo = false;
|
let mut found_sent_empty_memo = false;
|
||||||
for sent_note_id in sent_note_ids {
|
for sent_note_id in sent_note_ids {
|
||||||
match db_data
|
match st
|
||||||
|
.wallet()
|
||||||
.get_memo(sent_note_id)
|
.get_memo(sent_note_id)
|
||||||
.expect("Note id is valid")
|
.expect("Note id is valid")
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -642,20 +601,19 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
// Check that querying for a nonexistent sent note returns None
|
// Check that querying for a nonexistent sent note returns None
|
||||||
assert_matches!(
|
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)
|
Ok(None)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_to_address_fails_on_incorrect_usk() {
|
fn create_to_address_fails_on_incorrect_usk() {
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let mut st = TestBuilder::new().with_seed(Secret::new(vec![])).build();
|
||||||
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
|
// Add an account to the wallet
|
||||||
let seed = Secret::new([0u8; 32].to_vec());
|
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 dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
||||||
let to = dfvk.default_address().1.into();
|
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
|
// Attempting to spend with a USK that is not in the wallet results in an error
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
create_spend_to_address(
|
st.create_spend_to_address(
|
||||||
&mut db_data,
|
|
||||||
&tests::network(),
|
|
||||||
test_prover(),
|
|
||||||
&usk1,
|
&usk1,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(1).unwrap(),
|
Amount::from_u64(1).unwrap(),
|
||||||
|
@ -682,28 +637,23 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_to_address_fails_with_no_blocks() {
|
fn create_to_address_fails_with_no_blocks() {
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let mut st = TestBuilder::new().build();
|
||||||
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
|
|
||||||
init_wallet_db(&mut db_data, None).unwrap();
|
|
||||||
|
|
||||||
// Add an account to the wallet
|
// Add an account to the wallet
|
||||||
let seed = Secret::new([0u8; 32].to_vec());
|
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 dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
||||||
let to = dfvk.default_address().1.into();
|
let to = dfvk.default_address().1.into();
|
||||||
|
|
||||||
// Account balance should be zero
|
// Account balance should be zero
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
Amount::zero()
|
Amount::zero()
|
||||||
);
|
);
|
||||||
|
|
||||||
// We cannot do anything if we aren't synchronised
|
// We cannot do anything if we aren't synchronised
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
create_spend_to_address(
|
st.create_spend_to_address(
|
||||||
&mut db_data,
|
|
||||||
&tests::network(),
|
|
||||||
test_prover(),
|
|
||||||
&usk,
|
&usk,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(1).unwrap(),
|
Amount::from_u64(1).unwrap(),
|
||||||
|
@ -717,84 +667,49 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_to_address_fails_on_unverified_notes() {
|
fn create_to_address_fails_on_unverified_notes() {
|
||||||
let cache_file = NamedTempFile::new().unwrap();
|
let mut st = TestBuilder::new().with_block_cache().build();
|
||||||
let db_cache = BlockDb(Connection::open(cache_file.path()).unwrap());
|
|
||||||
init_cache_database(&db_cache).unwrap();
|
|
||||||
|
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
|
||||||
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
|
|
||||||
init_wallet_db(&mut db_data, None).unwrap();
|
|
||||||
|
|
||||||
// Add an account to the wallet
|
// Add an account to the wallet
|
||||||
let seed = Secret::new([0u8; 32].to_vec());
|
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 dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
||||||
|
|
||||||
// Add funds to the wallet in a single note
|
// Add funds to the wallet in a single note
|
||||||
let value = Amount::from_u64(50000).unwrap();
|
let value = Amount::from_u64(50000).unwrap();
|
||||||
let (mut cb, _) = fake_compact_block(
|
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||||
sapling_activation_height(),
|
st.scan_cached_blocks(h1, 1);
|
||||||
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();
|
|
||||||
|
|
||||||
// Verified balance matches total balance
|
// Verified balance matches total balance
|
||||||
let (_, anchor_height) = db_data
|
let (_, anchor_height) = st
|
||||||
|
.wallet()
|
||||||
.get_target_and_anchor_heights(NonZeroU32::new(10).unwrap())
|
.get_target_and_anchor_heights(NonZeroU32::new(10).unwrap())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
value
|
value
|
||||||
);
|
);
|
||||||
assert_eq!(
|
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
|
value
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add more funds to the wallet in a second note
|
// Add more funds to the wallet in a second note
|
||||||
cb = fake_compact_block(
|
let (h2, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||||
sapling_activation_height() + 1,
|
st.scan_cached_blocks(h2, 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();
|
|
||||||
|
|
||||||
// Verified balance does not include the second note
|
// 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())
|
.get_target_and_anchor_heights(NonZeroU32::new(10).unwrap())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
(value + value).unwrap()
|
(value + value).unwrap()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
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
|
value
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -802,10 +717,7 @@ pub(crate) mod tests {
|
||||||
let extsk2 = ExtendedSpendingKey::master(&[]);
|
let extsk2 = ExtendedSpendingKey::master(&[]);
|
||||||
let to = extsk2.default_address().1.into();
|
let to = extsk2.default_address().1.into();
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
create_spend_to_address(
|
st.create_spend_to_address(
|
||||||
&mut db_data,
|
|
||||||
&tests::network(),
|
|
||||||
test_prover(),
|
|
||||||
&usk,
|
&usk,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(70000).unwrap(),
|
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
|
// Mine blocks SAPLING_ACTIVATION_HEIGHT + 2 to 9 until just before the second
|
||||||
// note is verified
|
// note is verified
|
||||||
for i in 2..10 {
|
for _ in 2..10 {
|
||||||
cb = fake_compact_block(
|
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||||
sapling_activation_height() + i,
|
|
||||||
cb.hash(),
|
|
||||||
&dfvk,
|
|
||||||
AddressType::DefaultExternal,
|
|
||||||
value,
|
|
||||||
i,
|
|
||||||
)
|
|
||||||
.0;
|
|
||||||
insert_into_cache(&db_cache, &cb);
|
|
||||||
}
|
}
|
||||||
scan_cached_blocks(
|
st.scan_cached_blocks(h2 + 1, 8);
|
||||||
&tests::network(),
|
|
||||||
&db_cache,
|
|
||||||
&mut db_data,
|
|
||||||
sapling_activation_height() + 2,
|
|
||||||
8,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Second spend still fails
|
// Second spend still fails
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
create_spend_to_address(
|
st.create_spend_to_address(
|
||||||
&mut db_data,
|
|
||||||
&tests::network(),
|
|
||||||
test_prover(),
|
|
||||||
&usk,
|
&usk,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(70000).unwrap(),
|
Amount::from_u64(70000).unwrap(),
|
||||||
|
@ -866,31 +759,12 @@ pub(crate) mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Mine block 11 so that the second note becomes verified
|
// Mine block 11 so that the second note becomes verified
|
||||||
cb = fake_compact_block(
|
let (h11, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||||
sapling_activation_height() + 10,
|
st.scan_cached_blocks(h11, 1);
|
||||||
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();
|
|
||||||
|
|
||||||
// Second spend should now succeed
|
// Second spend should now succeed
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
create_spend_to_address(
|
st.create_spend_to_address(
|
||||||
&mut db_data,
|
|
||||||
&tests::network(),
|
|
||||||
test_prover(),
|
|
||||||
&usk,
|
&usk,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(70000).unwrap(),
|
Amount::from_u64(70000).unwrap(),
|
||||||
|
@ -904,40 +778,22 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_to_address_fails_on_locked_notes() {
|
fn create_to_address_fails_on_locked_notes() {
|
||||||
let cache_file = NamedTempFile::new().unwrap();
|
let mut st = TestBuilder::new()
|
||||||
let db_cache = BlockDb(Connection::open(cache_file.path()).unwrap());
|
.with_block_cache()
|
||||||
init_cache_database(&db_cache).unwrap();
|
.with_seed(Secret::new(vec![]))
|
||||||
|
.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
|
// Add an account to the wallet
|
||||||
let seed = Secret::new([0u8; 32].to_vec());
|
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 dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
||||||
|
|
||||||
// Add funds to the wallet in a single note
|
// Add funds to the wallet in a single note
|
||||||
let value = Amount::from_u64(50000).unwrap();
|
let value = Amount::from_u64(50000).unwrap();
|
||||||
let (mut cb, _) = fake_compact_block(
|
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||||
sapling_activation_height(),
|
st.scan_cached_blocks(h1, 1);
|
||||||
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();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
value
|
value
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -945,10 +801,7 @@ pub(crate) mod tests {
|
||||||
let extsk2 = ExtendedSpendingKey::master(&[]);
|
let extsk2 = ExtendedSpendingKey::master(&[]);
|
||||||
let to = extsk2.default_address().1.into();
|
let to = extsk2.default_address().1.into();
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
create_spend_to_address(
|
st.create_spend_to_address(
|
||||||
&mut db_data,
|
|
||||||
&tests::network(),
|
|
||||||
test_prover(),
|
|
||||||
&usk,
|
&usk,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(15000).unwrap(),
|
Amount::from_u64(15000).unwrap(),
|
||||||
|
@ -961,10 +814,7 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
// A second spend fails because there are no usable notes
|
// A second spend fails because there are no usable notes
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
create_spend_to_address(
|
st.create_spend_to_address(
|
||||||
&mut db_data,
|
|
||||||
&tests::network(),
|
|
||||||
test_prover(),
|
|
||||||
&usk,
|
&usk,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(2000).unwrap(),
|
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)
|
// Mine blocks SAPLING_ACTIVATION_HEIGHT + 1 to 41 (that don't send us funds)
|
||||||
// until just before the first transaction expires
|
// until just before the first transaction expires
|
||||||
for i in 1..42 {
|
for i in 1..42 {
|
||||||
cb = fake_compact_block(
|
st.generate_next_block(
|
||||||
sapling_activation_height() + i,
|
|
||||||
cb.hash(),
|
|
||||||
&ExtendedSpendingKey::master(&[i as u8]).to_diversifiable_full_viewing_key(),
|
&ExtendedSpendingKey::master(&[i as u8]).to_diversifiable_full_viewing_key(),
|
||||||
AddressType::DefaultExternal,
|
AddressType::DefaultExternal,
|
||||||
value,
|
value,
|
||||||
i,
|
);
|
||||||
)
|
|
||||||
.0;
|
|
||||||
insert_into_cache(&db_cache, &cb);
|
|
||||||
}
|
}
|
||||||
scan_cached_blocks(
|
st.scan_cached_blocks(h1 + 1, 41);
|
||||||
&tests::network(),
|
|
||||||
&db_cache,
|
|
||||||
&mut db_data,
|
|
||||||
sapling_activation_height() + 1,
|
|
||||||
41,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Second spend still fails
|
// Second spend still fails
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
create_spend_to_address(
|
st.create_spend_to_address(
|
||||||
&mut db_data,
|
|
||||||
&tests::network(),
|
|
||||||
test_prover(),
|
|
||||||
&usk,
|
&usk,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(2000).unwrap(),
|
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
|
// Mine block SAPLING_ACTIVATION_HEIGHT + 42 so that the first transaction expires
|
||||||
cb = fake_compact_block(
|
let (h43, _, _) = st.generate_next_block(
|
||||||
sapling_activation_height() + 42,
|
|
||||||
cb.hash(),
|
|
||||||
&ExtendedSpendingKey::master(&[42]).to_diversifiable_full_viewing_key(),
|
&ExtendedSpendingKey::master(&[42]).to_diversifiable_full_viewing_key(),
|
||||||
AddressType::DefaultExternal,
|
AddressType::DefaultExternal,
|
||||||
value,
|
value,
|
||||||
42,
|
);
|
||||||
)
|
st.scan_cached_blocks(h43, 1);
|
||||||
.0;
|
|
||||||
insert_into_cache(&db_cache, &cb);
|
|
||||||
scan_cached_blocks(
|
|
||||||
&tests::network(),
|
|
||||||
&db_cache,
|
|
||||||
&mut db_data,
|
|
||||||
sapling_activation_height() + 42,
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Second spend should now succeed
|
// Second spend should now succeed
|
||||||
create_spend_to_address(
|
st.create_spend_to_address(
|
||||||
&mut db_data,
|
|
||||||
&tests::network(),
|
|
||||||
test_prover(),
|
|
||||||
&usk,
|
&usk,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(2000).unwrap(),
|
Amount::from_u64(2000).unwrap(),
|
||||||
|
@ -1059,41 +879,19 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ovk_policy_prevents_recovery_from_chain() {
|
fn ovk_policy_prevents_recovery_from_chain() {
|
||||||
let network = tests::network();
|
let mut st = TestBuilder::new().with_block_cache().build();
|
||||||
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();
|
|
||||||
|
|
||||||
// Add an account to the wallet
|
// Add an account to the wallet
|
||||||
let seed = Secret::new([0u8; 32].to_vec());
|
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 dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
||||||
|
|
||||||
// Add funds to the wallet in a single note
|
// Add funds to the wallet in a single note
|
||||||
let value = Amount::from_u64(50000).unwrap();
|
let value = Amount::from_u64(50000).unwrap();
|
||||||
let (mut cb, _) = fake_compact_block(
|
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||||
sapling_activation_height(),
|
st.scan_cached_blocks(h1, 1);
|
||||||
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();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
value
|
value
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1102,7 +900,7 @@ pub(crate) mod tests {
|
||||||
let to = addr2.into();
|
let to = addr2.into();
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[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|
|
ovk_policy|
|
||||||
-> Result<
|
-> Result<
|
||||||
Option<(Note, PaymentAddress, MemoBytes)>,
|
Option<(Note, PaymentAddress, MemoBytes)>,
|
||||||
|
@ -1114,10 +912,7 @@ pub(crate) mod tests {
|
||||||
ReceivedNoteId,
|
ReceivedNoteId,
|
||||||
>,
|
>,
|
||||||
> {
|
> {
|
||||||
let txid = create_spend_to_address(
|
let txid = test.create_spend_to_address(
|
||||||
db_data,
|
|
||||||
&tests::network(),
|
|
||||||
test_prover(),
|
|
||||||
&usk,
|
&usk,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(15000).unwrap(),
|
Amount::from_u64(15000).unwrap(),
|
||||||
|
@ -1127,7 +922,8 @@ pub(crate) mod tests {
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Fetch the transaction from the database
|
// Fetch the transaction from the database
|
||||||
let raw_tx: Vec<_> = db_data
|
let raw_tx: Vec<_> = test
|
||||||
|
.wallet()
|
||||||
.conn
|
.conn
|
||||||
.query_row(
|
.query_row(
|
||||||
"SELECT raw FROM transactions
|
"SELECT raw FROM transactions
|
||||||
|
@ -1141,8 +937,8 @@ pub(crate) mod tests {
|
||||||
for output in tx.sapling_bundle().unwrap().shielded_outputs() {
|
for output in tx.sapling_bundle().unwrap().shielded_outputs() {
|
||||||
// Find the output that decrypts with the external OVK
|
// Find the output that decrypts with the external OVK
|
||||||
let result = try_sapling_output_recovery(
|
let result = try_sapling_output_recovery(
|
||||||
&network,
|
&network(),
|
||||||
sapling_activation_height(),
|
h1,
|
||||||
&dfvk.to_ovk(Scope::External),
|
&dfvk.to_ovk(Scope::External),
|
||||||
output,
|
output,
|
||||||
);
|
);
|
||||||
|
@ -1158,96 +954,61 @@ pub(crate) mod tests {
|
||||||
// Send some of the funds to another address, keeping history.
|
// Send some of the funds to another address, keeping history.
|
||||||
// The recipient output is decryptable by the sender.
|
// The recipient output is decryptable by the sender.
|
||||||
assert_matches!(
|
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
|
Ok(Some((_, recovered_to, _))) if recovered_to == addr2
|
||||||
);
|
);
|
||||||
|
|
||||||
// Mine blocks SAPLING_ACTIVATION_HEIGHT + 1 to 42 (that don't send us funds)
|
// Mine blocks SAPLING_ACTIVATION_HEIGHT + 1 to 42 (that don't send us funds)
|
||||||
// so that the first transaction expires
|
// so that the first transaction expires
|
||||||
for i in 1..=42 {
|
for i in 1..=42 {
|
||||||
cb = fake_compact_block(
|
st.generate_next_block(
|
||||||
sapling_activation_height() + i,
|
|
||||||
cb.hash(),
|
|
||||||
&ExtendedSpendingKey::master(&[i as u8]).to_diversifiable_full_viewing_key(),
|
&ExtendedSpendingKey::master(&[i as u8]).to_diversifiable_full_viewing_key(),
|
||||||
AddressType::DefaultExternal,
|
AddressType::DefaultExternal,
|
||||||
value,
|
value,
|
||||||
i,
|
);
|
||||||
)
|
|
||||||
.0;
|
|
||||||
insert_into_cache(&db_cache, &cb);
|
|
||||||
}
|
}
|
||||||
scan_cached_blocks(
|
st.scan_cached_blocks(h1 + 1, 42);
|
||||||
&network,
|
|
||||||
&db_cache,
|
|
||||||
&mut db_data,
|
|
||||||
sapling_activation_height() + 1,
|
|
||||||
42,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Send the funds again, discarding history.
|
// Send the funds again, discarding history.
|
||||||
// Neither transaction output is decryptable by the sender.
|
// Neither transaction output is decryptable by the sender.
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
send_and_recover_with_policy(&mut db_data, OvkPolicy::Discard),
|
send_and_recover_with_policy(&mut st, OvkPolicy::Discard),
|
||||||
Ok(None)
|
Ok(None)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_to_address_succeeds_to_t_addr_zero_change() {
|
fn create_to_address_succeeds_to_t_addr_zero_change() {
|
||||||
let cache_file = NamedTempFile::new().unwrap();
|
let mut st = TestBuilder::new().with_block_cache().build();
|
||||||
let db_cache = BlockDb(Connection::open(cache_file.path()).unwrap());
|
|
||||||
init_cache_database(&db_cache).unwrap();
|
|
||||||
|
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
|
||||||
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
|
|
||||||
init_wallet_db(&mut db_data, None).unwrap();
|
|
||||||
|
|
||||||
// Add an account to the wallet
|
// Add an account to the wallet
|
||||||
let seed = Secret::new([0u8; 32].to_vec());
|
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 dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
||||||
|
|
||||||
// Add funds to the wallet in a single note
|
// Add funds to the wallet in a single note
|
||||||
let value = Amount::from_u64(60000).unwrap();
|
let value = Amount::from_u64(60000).unwrap();
|
||||||
let (cb, _) = fake_compact_block(
|
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||||
sapling_activation_height(),
|
st.scan_cached_blocks(h, 1);
|
||||||
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();
|
|
||||||
|
|
||||||
// Verified balance matches total balance
|
// Verified balance matches total balance
|
||||||
let (_, anchor_height) = db_data
|
let (_, anchor_height) = st
|
||||||
|
.wallet()
|
||||||
.get_target_and_anchor_heights(NonZeroU32::new(1).unwrap())
|
.get_target_and_anchor_heights(NonZeroU32::new(1).unwrap())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
value
|
value
|
||||||
);
|
);
|
||||||
assert_eq!(
|
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
|
value
|
||||||
);
|
);
|
||||||
|
|
||||||
let to = TransparentAddress::PublicKey([7; 20]).into();
|
let to = TransparentAddress::PublicKey([7; 20]).into();
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
create_spend_to_address(
|
st.create_spend_to_address(
|
||||||
&mut db_data,
|
|
||||||
&tests::network(),
|
|
||||||
test_prover(),
|
|
||||||
&usk,
|
&usk,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(50000).unwrap(),
|
Amount::from_u64(50000).unwrap(),
|
||||||
|
@ -1261,59 +1022,36 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_to_address_spends_a_change_note() {
|
fn create_to_address_spends_a_change_note() {
|
||||||
let cache_file = NamedTempFile::new().unwrap();
|
let mut st = TestBuilder::new().with_block_cache().build();
|
||||||
let db_cache = BlockDb(Connection::open(cache_file.path()).unwrap());
|
|
||||||
init_cache_database(&db_cache).unwrap();
|
|
||||||
|
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
|
||||||
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
|
|
||||||
init_wallet_db(&mut db_data, None).unwrap();
|
|
||||||
|
|
||||||
// Add an account to the wallet
|
// Add an account to the wallet
|
||||||
let seed = Secret::new([0u8; 32].to_vec());
|
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 dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
||||||
|
|
||||||
// Add funds to the wallet in a single note
|
// Add funds to the wallet in a single note
|
||||||
let value = Amount::from_u64(60000).unwrap();
|
let value = Amount::from_u64(60000).unwrap();
|
||||||
let (cb, _) = fake_compact_block(
|
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::Internal, value);
|
||||||
sapling_activation_height(),
|
st.scan_cached_blocks(h, 1);
|
||||||
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();
|
|
||||||
|
|
||||||
// Verified balance matches total balance
|
// Verified balance matches total balance
|
||||||
let (_, anchor_height) = db_data
|
let (_, anchor_height) = st
|
||||||
|
.wallet()
|
||||||
.get_target_and_anchor_heights(NonZeroU32::new(10).unwrap())
|
.get_target_and_anchor_heights(NonZeroU32::new(10).unwrap())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
value
|
value
|
||||||
);
|
);
|
||||||
assert_eq!(
|
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
|
value
|
||||||
);
|
);
|
||||||
|
|
||||||
let to = TransparentAddress::PublicKey([7; 20]).into();
|
let to = TransparentAddress::PublicKey([7; 20]).into();
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
create_spend_to_address(
|
st.create_spend_to_address(
|
||||||
&mut db_data,
|
|
||||||
&tests::network(),
|
|
||||||
test_prover(),
|
|
||||||
&usk,
|
&usk,
|
||||||
&to,
|
&to,
|
||||||
Amount::from_u64(50000).unwrap(),
|
Amount::from_u64(50000).unwrap(),
|
||||||
|
@ -1327,65 +1065,44 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn zip317_spend() {
|
fn zip317_spend() {
|
||||||
let cache_file = NamedTempFile::new().unwrap();
|
let mut st = TestBuilder::new().with_block_cache().build();
|
||||||
let db_cache = BlockDb(Connection::open(cache_file.path()).unwrap());
|
|
||||||
init_cache_database(&db_cache).unwrap();
|
|
||||||
|
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
|
||||||
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
|
|
||||||
init_wallet_db(&mut db_data, None).unwrap();
|
|
||||||
|
|
||||||
// Add an account to the wallet
|
// Add an account to the wallet
|
||||||
let seed = Secret::new([0u8; 32].to_vec());
|
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 dfvk = usk.sapling().to_diversifiable_full_viewing_key();
|
||||||
|
|
||||||
// Add funds to the wallet
|
// Add funds to the wallet
|
||||||
let (mut cb, _) = fake_compact_block(
|
let (h1, _, _) = st.generate_next_block(
|
||||||
sapling_activation_height(),
|
|
||||||
BlockHash([0; 32]),
|
|
||||||
&dfvk,
|
&dfvk,
|
||||||
AddressType::Internal,
|
AddressType::Internal,
|
||||||
Amount::from_u64(50000).unwrap(),
|
Amount::from_u64(50000).unwrap(),
|
||||||
0,
|
|
||||||
);
|
);
|
||||||
insert_into_cache(&db_cache, &cb);
|
|
||||||
|
|
||||||
// Add 10 dust notes to the wallet
|
// Add 10 dust notes to the wallet
|
||||||
for i in 1..=10 {
|
for _ in 1..=10 {
|
||||||
cb = fake_compact_block(
|
st.generate_next_block(
|
||||||
sapling_activation_height() + i,
|
|
||||||
cb.hash(),
|
|
||||||
&dfvk,
|
&dfvk,
|
||||||
AddressType::DefaultExternal,
|
AddressType::DefaultExternal,
|
||||||
Amount::from_u64(1000).unwrap(),
|
Amount::from_u64(1000).unwrap(),
|
||||||
i,
|
);
|
||||||
)
|
|
||||||
.0;
|
|
||||||
insert_into_cache(&db_cache, &cb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scan_cached_blocks(
|
st.scan_cached_blocks(h1, 11);
|
||||||
&tests::network(),
|
|
||||||
&db_cache,
|
|
||||||
&mut db_data,
|
|
||||||
sapling_activation_height(),
|
|
||||||
11,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Verified balance matches total balance
|
// Verified balance matches total balance
|
||||||
let total = Amount::from_u64(60000).unwrap();
|
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())
|
.get_target_and_anchor_heights(NonZeroU32::new(1).unwrap())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_balance(&db_data.conn, AccountId::from(0)).unwrap(),
|
get_balance(&st.wallet().conn, AccountId::from(0)).unwrap(),
|
||||||
total
|
total
|
||||||
);
|
);
|
||||||
assert_eq!(
|
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
|
total
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1406,10 +1123,7 @@ pub(crate) mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
spend(
|
st.spend(
|
||||||
&mut db_data,
|
|
||||||
&tests::network(),
|
|
||||||
test_prover(),
|
|
||||||
&input_selector,
|
&input_selector,
|
||||||
&usk,
|
&usk,
|
||||||
req,
|
req,
|
||||||
|
@ -1434,10 +1148,7 @@ pub(crate) mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
spend(
|
st.spend(
|
||||||
&mut db_data,
|
|
||||||
&tests::network(),
|
|
||||||
test_prover(),
|
|
||||||
&input_selector,
|
&input_selector,
|
||||||
&usk,
|
&usk,
|
||||||
req,
|
req,
|
||||||
|
@ -1451,32 +1162,38 @@ pub(crate) mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
fn shield_transparent() {
|
fn shield_transparent() {
|
||||||
let cache_file = NamedTempFile::new().unwrap();
|
let mut st = TestBuilder::new().with_block_cache().build();
|
||||||
let db_cache = BlockDb(Connection::open(cache_file.path()).unwrap());
|
|
||||||
init_cache_database(&db_cache).unwrap();
|
|
||||||
|
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
|
||||||
let mut db_data = WalletDb::for_path(data_file.path(), tests::network()).unwrap();
|
|
||||||
init_wallet_db(&mut db_data, None).unwrap();
|
|
||||||
|
|
||||||
// Add an account to the wallet
|
// Add an account to the wallet
|
||||||
let seed = Secret::new([0u8; 32].to_vec());
|
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 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();
|
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(
|
let utxo = WalletTransparentOutput::from_parts(
|
||||||
OutPoint::new([1u8; 32], 1),
|
OutPoint::new([1u8; 32], 1),
|
||||||
TxOut {
|
TxOut {
|
||||||
value: Amount::from_u64(10000).unwrap(),
|
value: Amount::from_u64(10000).unwrap(),
|
||||||
script_pubkey: taddr.script(),
|
script_pubkey: taddr.script(),
|
||||||
},
|
},
|
||||||
sapling_activation_height(),
|
h,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let res0 = db_data.put_received_transparent_utxo(&utxo);
|
let res0 = st.wallet_mut().put_received_transparent_utxo(&utxo);
|
||||||
assert!(matches!(res0, Ok(_)));
|
assert!(matches!(res0, Ok(_)));
|
||||||
|
|
||||||
let input_selector = GreedyInputSelector::new(
|
let input_selector = GreedyInputSelector::new(
|
||||||
|
@ -1484,30 +1201,8 @@ pub(crate) mod tests {
|
||||||
DustOutputPolicy::default(),
|
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!(
|
assert_matches!(
|
||||||
shield_transparent_funds(
|
st.shield_transparent_funds(
|
||||||
&mut db_data,
|
|
||||||
&tests::network(),
|
|
||||||
test_prover(),
|
|
||||||
&input_selector,
|
&input_selector,
|
||||||
NonNegativeAmount::from_u64(10000).unwrap(),
|
NonNegativeAmount::from_u64(10000).unwrap(),
|
||||||
&usk,
|
&usk,
|
||||||
|
|
|
@ -738,11 +738,9 @@ mod tests {
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use incrementalmerkletree::{Hashable, Level};
|
use incrementalmerkletree::{Hashable, Level};
|
||||||
use rusqlite::Connection;
|
|
||||||
use secrecy::Secret;
|
use secrecy::Secret;
|
||||||
use tempfile::NamedTempFile;
|
|
||||||
use zcash_client_backend::data_api::{
|
use zcash_client_backend::data_api::{
|
||||||
chain::{scan_cached_blocks, CommitmentTreeRoot},
|
chain::CommitmentTreeRoot,
|
||||||
scanning::{ScanPriority, ScanRange},
|
scanning::{ScanPriority, ScanRange},
|
||||||
WalletCommitmentTrees, WalletRead, WalletWrite,
|
WalletCommitmentTrees, WalletRead, WalletWrite,
|
||||||
};
|
};
|
||||||
|
@ -754,16 +752,8 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
chain::init::init_cache_database,
|
testing::{sapling_activation_height, AddressType, TestBuilder},
|
||||||
tests::{
|
wallet::{init::init_blocks_table, scanning::suggest_scan_ranges},
|
||||||
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,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{join_nonoverlapping, Joined, RangeOrdering, SpanningTree};
|
use super::{join_nonoverlapping, Joined, RangeOrdering, SpanningTree};
|
||||||
|
@ -1093,21 +1083,18 @@ mod tests {
|
||||||
fn scan_complete() {
|
fn scan_complete() {
|
||||||
use ScanPriority::*;
|
use ScanPriority::*;
|
||||||
|
|
||||||
let cache_file = NamedTempFile::new().unwrap();
|
let mut st = TestBuilder::new()
|
||||||
let db_cache = BlockDb(Connection::open(cache_file.path()).unwrap());
|
.with_block_cache()
|
||||||
init_cache_database(&db_cache).unwrap();
|
.with_seed(Secret::new(vec![]))
|
||||||
|
.with_test_account()
|
||||||
|
.build();
|
||||||
|
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let dfvk = st.test_account_sapling().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);
|
|
||||||
|
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
// In the following, we don't care what the root hashes are, they just need to be
|
// In the following, we don't care what the root hashes are, they just need to be
|
||||||
// distinct.
|
// distinct.
|
||||||
db_data.put_sapling_subtree_roots(
|
st.wallet_mut().put_sapling_subtree_roots(
|
||||||
0,
|
0,
|
||||||
&[
|
&[
|
||||||
CommitmentTreeRoot::from_parts(
|
CommitmentTreeRoot::from_parts(
|
||||||
|
@ -1134,7 +1121,7 @@ mod tests {
|
||||||
let initial_height = sapling_activation_height() + 310;
|
let initial_height = sapling_activation_height() + 310;
|
||||||
|
|
||||||
let value = Amount::from_u64(50000).unwrap();
|
let value = Amount::from_u64(50000).unwrap();
|
||||||
let (mut cb, _) = fake_compact_block(
|
st.generate_block_at(
|
||||||
initial_height,
|
initial_height,
|
||||||
BlockHash([0; 32]),
|
BlockHash([0; 32]),
|
||||||
&dfvk,
|
&dfvk,
|
||||||
|
@ -1142,36 +1129,21 @@ mod tests {
|
||||||
value,
|
value,
|
||||||
initial_sapling_tree_size,
|
initial_sapling_tree_size,
|
||||||
);
|
);
|
||||||
insert_into_cache(&db_cache, &cb);
|
|
||||||
|
|
||||||
for i in 1..=10 {
|
for _ in 1..=10 {
|
||||||
cb = fake_compact_block(
|
st.generate_next_block(
|
||||||
initial_height + i,
|
|
||||||
cb.hash(),
|
|
||||||
&dfvk,
|
&dfvk,
|
||||||
AddressType::DefaultExternal,
|
AddressType::DefaultExternal,
|
||||||
Amount::from_u64(10000).unwrap(),
|
Amount::from_u64(10000).unwrap(),
|
||||||
initial_sapling_tree_size + i,
|
);
|
||||||
)
|
|
||||||
.0;
|
|
||||||
insert_into_cache(&db_cache, &cb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_matches!(
|
st.scan_cached_blocks(initial_height, 10);
|
||||||
scan_cached_blocks(
|
|
||||||
&tests::network(),
|
|
||||||
&db_cache,
|
|
||||||
&mut db_data,
|
|
||||||
initial_height,
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
Ok(())
|
|
||||||
);
|
|
||||||
|
|
||||||
// Verify the that adjacent range needed to make the note spendable has been prioritized.
|
// Verify the that adjacent range needed to make the note spendable has been prioritized.
|
||||||
let sap_active = u32::from(sapling_activation_height());
|
let sap_active = u32::from(sapling_activation_height());
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
db_data.suggest_scan_ranges(),
|
st.wallet().suggest_scan_ranges(),
|
||||||
Ok(scan_ranges) if scan_ranges == vec![
|
Ok(scan_ranges) if scan_ranges == vec![
|
||||||
scan_range((sap_active + 300)..(sap_active + 310), FoundNote)
|
scan_range((sap_active + 300)..(sap_active + 310), FoundNote)
|
||||||
]
|
]
|
||||||
|
@ -1179,7 +1151,7 @@ mod tests {
|
||||||
|
|
||||||
// Check that the scanned range has been properly persisted.
|
// Check that the scanned range has been properly persisted.
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
suggest_scan_ranges(&db_data.conn, Scanned),
|
suggest_scan_ranges(&st.wallet().conn, Scanned),
|
||||||
Ok(scan_ranges) if scan_ranges == vec![
|
Ok(scan_ranges) if scan_ranges == vec![
|
||||||
scan_range((sap_active + 300)..(sap_active + 310), FoundNote),
|
scan_range((sap_active + 300)..(sap_active + 310), FoundNote),
|
||||||
scan_range((sap_active + 310)..(sap_active + 320), Scanned)
|
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
|
// Simulate the wallet going offline for a bit, update the chain tip to 20 blocks in the
|
||||||
// future.
|
// future.
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
db_data.update_chain_tip(sapling_activation_height() + 340),
|
st.wallet_mut()
|
||||||
|
.update_chain_tip(sapling_activation_height() + 340),
|
||||||
Ok(())
|
Ok(())
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check the scan range again, we should see a `ChainTip` range for the period we've been
|
// Check the scan range again, we should see a `ChainTip` range for the period we've been
|
||||||
// offline.
|
// offline.
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
db_data.suggest_scan_ranges(),
|
st.wallet().suggest_scan_ranges(),
|
||||||
Ok(scan_ranges) if scan_ranges == vec![
|
Ok(scan_ranges) if scan_ranges == vec![
|
||||||
scan_range((sap_active + 320)..(sap_active + 341), ChainTip),
|
scan_range((sap_active + 320)..(sap_active + 341), ChainTip),
|
||||||
scan_range((sap_active + 300)..(sap_active + 310), 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.
|
// Now simulate a jump ahead more than 100 blocks.
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
db_data.update_chain_tip(sapling_activation_height() + 450),
|
st.wallet_mut()
|
||||||
|
.update_chain_tip(sapling_activation_height() + 450),
|
||||||
Ok(())
|
Ok(())
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check the scan range again, we should see a `Validate` range for the previous wallet
|
// Check the scan range again, we should see a `Validate` range for the previous wallet
|
||||||
// tip, and then a `ChainTip` for the remaining range.
|
// tip, and then a `ChainTip` for the remaining range.
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
db_data.suggest_scan_ranges(),
|
st.wallet().suggest_scan_ranges(),
|
||||||
Ok(scan_ranges) if scan_ranges == vec![
|
Ok(scan_ranges) if scan_ranges == vec![
|
||||||
scan_range((sap_active + 319)..(sap_active + 329), Verify),
|
scan_range((sap_active + 319)..(sap_active + 329), Verify),
|
||||||
scan_range((sap_active + 329)..(sap_active + 451), ChainTip),
|
scan_range((sap_active + 329)..(sap_active + 451), ChainTip),
|
||||||
|
@ -1225,22 +1199,22 @@ mod tests {
|
||||||
fn init_blocks_table_creates_ignored_range() {
|
fn init_blocks_table_creates_ignored_range() {
|
||||||
use ScanPriority::*;
|
use ScanPriority::*;
|
||||||
|
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let mut st = TestBuilder::new().with_seed(Secret::new(vec![])).build();
|
||||||
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 sap_active = db_data
|
let sap_active = st
|
||||||
|
.wallet()
|
||||||
.params
|
.params
|
||||||
.activation_height(NetworkUpgrade::Sapling)
|
.activation_height(NetworkUpgrade::Sapling)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// Initialise the blocks table. We use Canopy activation as an arbitrary birthday height
|
// Initialise the blocks table. We use Canopy activation as an arbitrary birthday height
|
||||||
// that's greater than Sapling activation.
|
// that's greater than Sapling activation.
|
||||||
let birthday_height = db_data
|
let birthday_height = st
|
||||||
|
.wallet()
|
||||||
.params
|
.params
|
||||||
.activation_height(NetworkUpgrade::Canopy)
|
.activation_height(NetworkUpgrade::Canopy)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
init_blocks_table(
|
init_blocks_table(
|
||||||
&mut db_data,
|
st.wallet_mut(),
|
||||||
birthday_height,
|
birthday_height,
|
||||||
BlockHash([1; 32]),
|
BlockHash([1; 32]),
|
||||||
1,
|
1,
|
||||||
|
@ -1256,12 +1230,12 @@ mod tests {
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
suggest_scan_ranges(&db_data.conn, Ignored),
|
suggest_scan_ranges(&st.wallet().conn, Ignored),
|
||||||
Ok(scan_ranges) if scan_ranges == expected
|
Ok(scan_ranges) if scan_ranges == expected
|
||||||
);
|
);
|
||||||
|
|
||||||
// Set up some shard history
|
// Set up some shard history
|
||||||
db_data
|
st.wallet_mut()
|
||||||
.put_sapling_subtree_roots(
|
.put_sapling_subtree_roots(
|
||||||
0,
|
0,
|
||||||
&[
|
&[
|
||||||
|
@ -1280,11 +1254,12 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Update the chain tip
|
// Update the chain tip
|
||||||
let tip_height = db_data
|
let tip_height = st
|
||||||
|
.wallet()
|
||||||
.params
|
.params
|
||||||
.activation_height(NetworkUpgrade::Nu5)
|
.activation_height(NetworkUpgrade::Nu5)
|
||||||
.unwrap();
|
.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
|
// Verify that the suggested scan ranges match what is expected
|
||||||
let expected = vec![
|
let expected = vec![
|
||||||
|
@ -1315,7 +1290,7 @@ mod tests {
|
||||||
];
|
];
|
||||||
|
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
suggest_scan_ranges(&db_data.conn, Ignored),
|
suggest_scan_ranges(&st.wallet().conn, Ignored),
|
||||||
Ok(scan_ranges) if scan_ranges == expected
|
Ok(scan_ranges) if scan_ranges == expected
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue