zcash_client_sqlite: (testing) Initialize wallets using an `AccountBirthday` struct.

This commit is contained in:
Kris Nuttycombe 2023-08-16 11:15:10 -06:00
parent a238007e14
commit ff8104fa75
8 changed files with 104 additions and 127 deletions

View File

@ -11,6 +11,7 @@ and this library adheres to Rust's notion of
- `impl Eq for zcash_client_backend::zip321::{Payment, TransactionRequest}` - `impl Eq for zcash_client_backend::zip321::{Payment, TransactionRequest}`
- `impl Debug` for `zcash_client_backend::{data_api::wallet::input_selection::Proposal, wallet::ReceivedSaplingNote}` - `impl Debug` for `zcash_client_backend::{data_api::wallet::input_selection::Proposal, wallet::ReceivedSaplingNote}`
- `zcash_client_backend::data_api`: - `zcash_client_backend::data_api`:
- `AccountBirthday`
- `BlockMetadata` - `BlockMetadata`
- `NoteId` - `NoteId`
- `NullifierQuery` for use with `WalletRead::get_sapling_nullifiers` - `NullifierQuery` for use with `WalletRead::get_sapling_nullifiers`

View File

@ -4,7 +4,7 @@ use std::collections::HashMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::num::NonZeroU32; use std::num::NonZeroU32;
use incrementalmerkletree::Retention; use incrementalmerkletree::{frontier::Frontier, Retention};
use secrecy::SecretVec; use secrecy::SecretVec;
use shardtree::{error::ShardTreeError, store::ShardStore, ShardTree}; use shardtree::{error::ShardTreeError, store::ShardStore, ShardTree};
use zcash_primitives::{ use zcash_primitives::{
@ -12,7 +12,7 @@ use zcash_primitives::{
consensus::BlockHeight, consensus::BlockHeight,
legacy::TransparentAddress, legacy::TransparentAddress,
memo::{Memo, MemoBytes}, memo::{Memo, MemoBytes},
sapling, sapling::{self, Node, NOTE_COMMITMENT_TREE_DEPTH},
transaction::{ transaction::{
components::{amount::Amount, OutPoint}, components::{amount::Amount, OutPoint},
Transaction, TxId, Transaction, TxId,
@ -466,6 +466,32 @@ impl SentTransactionOutput {
} }
} }
#[derive(Clone, Debug)]
pub struct AccountBirthday {
height: BlockHeight,
sapling_frontier: Frontier<Node, NOTE_COMMITMENT_TREE_DEPTH>,
}
impl AccountBirthday {
pub fn from_parts(
height: BlockHeight,
sapling_frontier: Frontier<Node, NOTE_COMMITMENT_TREE_DEPTH>,
) -> Self {
Self {
height,
sapling_frontier,
}
}
pub fn sapling_frontier(&self) -> &Frontier<Node, NOTE_COMMITMENT_TREE_DEPTH> {
&self.sapling_frontier
}
pub fn height(&self) -> BlockHeight {
self.height
}
}
/// This trait encapsulates the write capabilities required to update stored /// This trait encapsulates the write capabilities required to update stored
/// wallet data. /// wallet data.
pub trait WalletWrite: WalletRead { pub trait WalletWrite: WalletRead {

View File

@ -345,7 +345,7 @@ mod tests {
}; };
use crate::{ use crate::{
testing::{AddressType, TestBuilder}, testing::{birthday_at_sapling_activation, AddressType, TestBuilder},
wallet::{get_balance, truncate_to_height}, wallet::{get_balance, truncate_to_height},
AccountId, AccountId,
}; };
@ -354,8 +354,7 @@ mod tests {
fn valid_chain_states() { fn valid_chain_states() {
let mut st = TestBuilder::new() let mut st = TestBuilder::new()
.with_block_cache() .with_block_cache()
.with_seed(Secret::new(vec![])) .with_test_account(birthday_at_sapling_activation)
.with_test_account()
.build(); .build();
let dfvk = st.test_account_sapling().unwrap(); let dfvk = st.test_account_sapling().unwrap();
@ -388,8 +387,7 @@ mod tests {
fn invalid_chain_cache_disconnected() { fn invalid_chain_cache_disconnected() {
let mut st = TestBuilder::new() let mut st = TestBuilder::new()
.with_block_cache() .with_block_cache()
.with_seed(Secret::new(vec![])) .with_test_account(birthday_at_sapling_activation)
.with_test_account()
.build(); .build();
let dfvk = st.test_account_sapling().unwrap(); let dfvk = st.test_account_sapling().unwrap();
@ -440,8 +438,7 @@ mod tests {
fn data_db_truncation() { fn data_db_truncation() {
let mut st = TestBuilder::new() let mut st = TestBuilder::new()
.with_block_cache() .with_block_cache()
.with_seed(Secret::new(vec![])) .with_test_account(birthday_at_sapling_activation)
.with_test_account()
.build(); .build();
let dfvk = st.test_account_sapling().unwrap(); let dfvk = st.test_account_sapling().unwrap();
@ -501,10 +498,7 @@ mod tests {
#[test] #[test]
fn scan_cached_blocks_allows_blocks_out_of_order() { fn scan_cached_blocks_allows_blocks_out_of_order() {
let mut st = TestBuilder::new() let mut st = TestBuilder::new().with_block_cache().build();
.with_block_cache()
.with_seed(Secret::new(vec![]))
.build();
// 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());
@ -564,8 +558,7 @@ mod tests {
fn scan_cached_blocks_finds_received_notes() { fn scan_cached_blocks_finds_received_notes() {
let mut st = TestBuilder::new() let mut st = TestBuilder::new()
.with_block_cache() .with_block_cache()
.with_seed(Secret::new(vec![])) .with_test_account(birthday_at_sapling_activation)
.with_test_account()
.build(); .build();
let dfvk = st.test_account_sapling().unwrap(); let dfvk = st.test_account_sapling().unwrap();
@ -607,8 +600,7 @@ mod tests {
fn scan_cached_blocks_finds_change_notes() { fn scan_cached_blocks_finds_change_notes() {
let mut st = TestBuilder::new() let mut st = TestBuilder::new()
.with_block_cache() .with_block_cache()
.with_seed(Secret::new(vec![])) .with_test_account(birthday_at_sapling_activation)
.with_test_account()
.build(); .build();
let dfvk = st.test_account_sapling().unwrap(); let dfvk = st.test_account_sapling().unwrap();
@ -653,8 +645,7 @@ mod tests {
fn scan_cached_blocks_detects_spends_out_of_order() { fn scan_cached_blocks_detects_spends_out_of_order() {
let mut st = TestBuilder::new() let mut st = TestBuilder::new()
.with_block_cache() .with_block_cache()
.with_seed(Secret::new(vec![])) .with_test_account(birthday_at_sapling_activation)
.with_test_account()
.build(); .build();
let dfvk = st.test_account_sapling().unwrap(); let dfvk = st.test_account_sapling().unwrap();

View File

@ -1087,7 +1087,10 @@ extern crate assert_matches;
mod tests { mod tests {
use zcash_client_backend::data_api::{WalletRead, WalletWrite}; use zcash_client_backend::data_api::{WalletRead, WalletWrite};
use crate::{testing::TestBuilder, AccountId}; use crate::{
testing::{birthday_at_sapling_activation, TestBuilder},
AccountId,
};
#[cfg(feature = "unstable")] #[cfg(feature = "unstable")]
use zcash_primitives::{consensus::Parameters, transaction::components::Amount}; use zcash_primitives::{consensus::Parameters, transaction::components::Amount};
@ -1100,7 +1103,9 @@ mod tests {
#[test] #[test]
pub(crate) fn get_next_available_address() { pub(crate) fn get_next_available_address() {
let mut st = TestBuilder::new().with_test_account().build(); let mut st = TestBuilder::new()
.with_test_account(birthday_at_sapling_activation)
.build();
let account = AccountId::from(0); let account = AccountId::from(0);
let current_addr = st.wallet().get_current_address(account).unwrap(); let current_addr = st.wallet().get_current_address(account).unwrap();
@ -1117,17 +1122,15 @@ mod tests {
#[cfg(feature = "transparent-inputs")] #[cfg(feature = "transparent-inputs")]
#[test] #[test]
fn transparent_receivers() { fn transparent_receivers() {
use secrecy::Secret; // Add an account to the wallet.
let st = TestBuilder::new() let st = TestBuilder::new()
.with_block_cache() .with_block_cache()
.with_seed(Secret::new(vec![])) .with_test_account(birthday_at_sapling_activation)
.with_test_account()
.build(); .build();
// Add an account to the wallet. let (_, usk, _) = st.test_account().unwrap();
let (ufvk, taddr) = st.test_account().unwrap(); let ufvk = usk.to_unified_full_viewing_key();
let taddr = taddr.unwrap(); let (taddr, _) = usk.default_transparent_address();
let receivers = st.wallet().get_transparent_receivers(0.into()).unwrap(); let receivers = st.wallet().get_transparent_receivers(0.into()).unwrap();

View File

@ -8,7 +8,7 @@ use std::fs::File;
use prost::Message; use prost::Message;
use rand_core::{OsRng, RngCore}; use rand_core::{OsRng, RngCore};
use rusqlite::{params, Connection}; use rusqlite::{params, Connection};
use secrecy::SecretVec; use secrecy::Secret;
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
#[cfg(feature = "unstable")] #[cfg(feature = "unstable")]
@ -25,8 +25,9 @@ use zcash_client_backend::{
input_selection::{GreedyInputSelectorError, InputSelector, Proposal}, input_selection::{GreedyInputSelectorError, InputSelector, Proposal},
propose_transfer, spend, propose_transfer, spend,
}, },
AccountBirthday, WalletWrite,
}, },
keys::{sapling, UnifiedFullViewingKey, UnifiedSpendingKey}, keys::UnifiedSpendingKey,
proto::compact_formats::{ proto::compact_formats::{
self as compact, CompactBlock, CompactSaplingOutput, CompactSaplingSpend, CompactTx, self as compact, CompactBlock, CompactSaplingOutput, CompactSaplingSpend, CompactTx,
}, },
@ -56,11 +57,7 @@ use zcash_primitives::{
use crate::{ use crate::{
chain::init::init_cache_database, chain::init::init_cache_database,
error::SqliteClientError, error::SqliteClientError,
wallet::{ wallet::{commitment_tree, init::init_wallet_db, sapling::tests::test_prover},
commitment_tree,
init::{init_accounts_table, init_wallet_db},
sapling::tests::test_prover,
},
AccountId, ReceivedNoteId, WalletDb, AccountId, ReceivedNoteId, WalletDb,
}; };
@ -69,10 +66,7 @@ use super::BlockDb;
#[cfg(feature = "transparent-inputs")] #[cfg(feature = "transparent-inputs")]
use { use {
zcash_client_backend::data_api::wallet::{propose_shielding, shield_transparent_funds}, zcash_client_backend::data_api::wallet::{propose_shielding, shield_transparent_funds},
zcash_primitives::{ zcash_primitives::transaction::components::amount::NonNegativeAmount,
legacy::{self, keys::IncomingViewingKey},
transaction::components::amount::NonNegativeAmount,
},
}; };
#[cfg(feature = "unstable")] #[cfg(feature = "unstable")]
@ -85,8 +79,7 @@ use crate::{
pub(crate) struct TestBuilder<Cache> { pub(crate) struct TestBuilder<Cache> {
network: Network, network: Network,
cache: Cache, cache: Cache,
seed: Option<SecretVec<u8>>, test_account_birthday: Option<AccountBirthday>,
with_test_account: bool,
} }
impl TestBuilder<()> { impl TestBuilder<()> {
@ -95,8 +88,7 @@ impl TestBuilder<()> {
TestBuilder { TestBuilder {
network: Network::TestNetwork, network: Network::TestNetwork,
cache: (), cache: (),
seed: None, test_account_birthday: None,
with_test_account: false,
} }
} }
@ -105,8 +97,7 @@ impl TestBuilder<()> {
TestBuilder { TestBuilder {
network: self.network, network: self.network,
cache: BlockCache::new(), cache: BlockCache::new(),
seed: self.seed, test_account_birthday: self.test_account_birthday,
with_test_account: self.with_test_account,
} }
} }
@ -116,22 +107,17 @@ impl TestBuilder<()> {
TestBuilder { TestBuilder {
network: self.network, network: self.network,
cache: FsBlockCache::new(), cache: FsBlockCache::new(),
seed: self.seed, test_account_birthday: self.test_account_birthday,
with_test_account: self.with_test_account,
} }
} }
} }
impl<Cache> TestBuilder<Cache> { impl<Cache> TestBuilder<Cache> {
/// Gives the test knowledge of the wallet seed for initialization. pub(crate) fn with_test_account<F: FnOnce(&Network) -> AccountBirthday>(
pub(crate) fn with_seed(mut self, seed: SecretVec<u8>) -> Self { mut self,
// TODO remove birthday: F,
self.seed = Some(seed); ) -> Self {
self self.test_account_birthday = Some(birthday(&self.network));
}
pub(crate) fn with_test_account(mut self) -> Self {
self.with_test_account = true;
self self
} }
@ -139,11 +125,12 @@ impl<Cache> TestBuilder<Cache> {
pub(crate) fn build(self) -> TestState<Cache> { pub(crate) fn build(self) -> TestState<Cache> {
let data_file = NamedTempFile::new().unwrap(); let data_file = NamedTempFile::new().unwrap();
let mut db_data = WalletDb::for_path(data_file.path(), self.network).unwrap(); let mut db_data = WalletDb::for_path(data_file.path(), self.network).unwrap();
init_wallet_db(&mut db_data, self.seed).unwrap(); init_wallet_db(&mut db_data, None).unwrap();
let test_account = if self.with_test_account { let test_account = if let Some(birthday) = self.test_account_birthday {
// Add an account to the wallet let seed = Secret::new(vec![0u8; 32]);
Some(init_test_accounts_table_ufvk(&mut db_data)) let (account, usk) = db_data.create_account(&seed).unwrap();
Some((account, usk, birthday))
} else { } else {
None None
}; };
@ -166,7 +153,7 @@ pub(crate) struct TestState<Cache> {
latest_cached_block: Option<(BlockHeight, BlockHash, u32)>, latest_cached_block: Option<(BlockHeight, BlockHash, u32)>,
_data_file: NamedTempFile, _data_file: NamedTempFile,
db_data: WalletDb<Connection, Network>, db_data: WalletDb<Connection, Network>,
test_account: Option<(UnifiedFullViewingKey, Option<TransparentAddress>)>, test_account: Option<(AccountId, UnifiedSpendingKey, AccountBirthday)>,
} }
impl<Cache: TestCache> TestState<Cache> impl<Cache: TestCache> TestState<Cache>
@ -293,8 +280,7 @@ where
/// Invokes [`scan_cached_blocks`] with the given arguments, expecting success. /// Invokes [`scan_cached_blocks`] with the given arguments, expecting success.
pub(crate) fn scan_cached_blocks(&mut self, from_height: BlockHeight, limit: usize) { pub(crate) fn scan_cached_blocks(&mut self, from_height: BlockHeight, limit: usize) {
self.try_scan_cached_blocks(from_height, limit) assert_matches!(self.try_scan_cached_blocks(from_height, limit), Ok(_));
.expect("should succeed for this test");
} }
/// Invokes [`scan_cached_blocks`] with the given arguments. /// Invokes [`scan_cached_blocks`] with the given arguments.
@ -344,10 +330,7 @@ impl<Cache> TestState<Cache> {
} }
/// Exposes the test account, if enabled via [`TestBuilder::with_test_account`]. /// Exposes the test account, if enabled via [`TestBuilder::with_test_account`].
#[cfg(feature = "unstable")] pub(crate) fn test_account(&self) -> Option<(AccountId, UnifiedSpendingKey, AccountBirthday)> {
pub(crate) fn test_account(
&self,
) -> Option<(UnifiedFullViewingKey, Option<TransparentAddress>)> {
self.test_account.as_ref().cloned() self.test_account.as_ref().cloned()
} }
@ -355,7 +338,7 @@ impl<Cache> TestState<Cache> {
pub(crate) fn test_account_sapling(&self) -> Option<DiversifiableFullViewingKey> { pub(crate) fn test_account_sapling(&self) -> Option<DiversifiableFullViewingKey> {
self.test_account self.test_account
.as_ref() .as_ref()
.map(|(ufvk, _)| ufvk.sapling().unwrap().clone()) .and_then(|(_, usk, _)| usk.to_unified_full_viewing_key().sapling().cloned())
} }
/// Invokes [`create_spend_to_address`] with the given arguments. /// Invokes [`create_spend_to_address`] with the given arguments.
@ -561,40 +544,14 @@ impl<Cache> TestState<Cache> {
} }
#[cfg(test)] #[cfg(test)]
pub(crate) fn init_test_accounts_table_ufvk( pub(crate) fn birthday_at_sapling_activation<P: consensus::Parameters>(
db_data: &mut WalletDb<rusqlite::Connection, Network>, params: &P,
) -> (UnifiedFullViewingKey, Option<TransparentAddress>) { ) -> AccountBirthday {
use std::collections::HashMap; use incrementalmerkletree::frontier::Frontier;
AccountBirthday::from_parts(
let seed = [0u8; 32]; params.activation_height(NetworkUpgrade::Sapling).unwrap(),
let account = AccountId::from(0); Frontier::empty(),
let extsk = sapling::spending_key(&seed, db_data.params.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(&db_data.params, &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)] #[allow(dead_code)]

View File

@ -1577,7 +1577,10 @@ mod tests {
use zcash_client_backend::data_api::WalletRead; use zcash_client_backend::data_api::WalletRead;
use crate::{testing::TestBuilder, AccountId}; use crate::{
testing::{birthday_at_sapling_activation, TestBuilder},
AccountId,
};
use super::get_balance; use super::get_balance;
@ -1595,8 +1598,7 @@ mod tests {
#[test] #[test]
fn empty_database_has_no_balance() { fn empty_database_has_no_balance() {
let st = TestBuilder::new() let st = TestBuilder::new()
.with_seed(Secret::new(vec![])) .with_test_account(birthday_at_sapling_activation)
.with_test_account()
.build(); .build();
// The account should be empty // The account should be empty

View File

@ -609,7 +609,7 @@ pub(crate) mod tests {
#[test] #[test]
fn create_to_address_fails_on_incorrect_usk() { fn create_to_address_fails_on_incorrect_usk() {
let mut st = TestBuilder::new().with_seed(Secret::new(vec![])).build(); let mut st = TestBuilder::new().build();
// 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());
@ -778,10 +778,7 @@ pub(crate) mod tests {
#[test] #[test]
fn create_to_address_fails_on_locked_notes() { fn create_to_address_fails_on_locked_notes() {
let mut st = TestBuilder::new() let mut st = TestBuilder::new().with_block_cache().build();
.with_block_cache()
.with_seed(Secret::new(vec![]))
.build();
// 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());

View File

@ -739,6 +739,7 @@ mod tests {
use incrementalmerkletree::{Hashable, Level}; use incrementalmerkletree::{Hashable, Level};
use secrecy::Secret; use secrecy::Secret;
use zcash_client_backend::data_api::{ use zcash_client_backend::data_api::{
chain::CommitmentTreeRoot, chain::CommitmentTreeRoot,
scanning::{ScanPriority, ScanRange}, scanning::{ScanPriority, ScanRange},
@ -752,7 +753,7 @@ mod tests {
}; };
use crate::{ use crate::{
testing::{AddressType, TestBuilder}, testing::{birthday_at_sapling_activation, AddressType, TestBuilder},
wallet::{init::init_blocks_table, scanning::suggest_scan_ranges}, wallet::{init::init_blocks_table, scanning::suggest_scan_ranges},
}; };
@ -1085,8 +1086,7 @@ mod tests {
let mut st = TestBuilder::new() let mut st = TestBuilder::new()
.with_block_cache() .with_block_cache()
.with_seed(Secret::new(vec![])) .with_test_account(birthday_at_sapling_activation)
.with_test_account()
.build(); .build();
let dfvk = st.test_account_sapling().unwrap(); let dfvk = st.test_account_sapling().unwrap();
@ -1197,23 +1197,21 @@ mod tests {
} }
#[test] #[test]
fn init_blocks_table_creates_ignored_range() { fn create_account_creates_ignored_range() {
use ScanPriority::*; use ScanPriority::*;
let mut st = TestBuilder::new().with_seed(Secret::new(vec![])).build(); let mut st = TestBuilder::new().with_block_cache().build();
let sap_active = st let sap_active = st.sapling_activation_height();
.wallet()
.params // We use Canopy activation as an arbitrary birthday height that's greater than Sapling
.activation_height(NetworkUpgrade::Sapling) // activation.
.unwrap();
// Initialise the blocks table. We use Canopy activation as an arbitrary birthday height
// that's greater than Sapling activation.
let birthday_height = st let birthday_height = st
.wallet() .network()
.params
.activation_height(NetworkUpgrade::Canopy) .activation_height(NetworkUpgrade::Canopy)
.unwrap(); .unwrap();
// call `init_blocks_table` to initialize the scan queue
init_blocks_table( init_blocks_table(
st.wallet_mut(), st.wallet_mut(),
birthday_height, birthday_height,
@ -1223,6 +1221,10 @@ mod tests {
) )
.unwrap(); .unwrap();
let seed = Secret::new(vec![0u8; 32]);
let (_, usk) = st.wallet_mut().create_account(&seed).unwrap();
let _dfvk = usk.to_unified_full_viewing_key().sapling().unwrap().clone();
let expected = vec![ let expected = vec![
// The range up to and including the wallet's birthday height is ignored. // The range up to and including the wallet's birthday height is ignored.
scan_range( scan_range(
@ -1290,9 +1292,7 @@ mod tests {
), ),
]; ];
assert_matches!( let actual = suggest_scan_ranges(&st.wallet().conn, Ignored).unwrap();
suggest_scan_ranges(&st.wallet().conn, Ignored), assert_eq!(actual, expected);
Ok(scan_ranges) if scan_ranges == expected
);
} }
} }