zcash_client_sqlite: Modify `TestState` to allow initialization with chain state.

This commit is contained in:
Kris Nuttycombe 2024-03-19 14:39:00 -06:00
parent 978f838aae
commit dc4fccf566
8 changed files with 322 additions and 278 deletions

View File

@ -71,6 +71,8 @@ and this library adheres to Rust's notion of
- `get_transaction` now returns `Result<Option<Transaction>, _>` rather
than returning an `Err` if the `txid` parameter does not correspond to
a transaction in the database.
- `WalletWrite::create_account` now takes its `AccountBirthday` argument by
reference.
- Changes to the `InputSource` trait:
- `select_spendable_notes` now takes its `target_value` argument as a
`NonNegativeAmount`. Also, it now returns a `SpendableNotes` data

View File

@ -1297,11 +1297,7 @@ impl<AccountId> SentTransactionOutput<AccountId> {
/// note commitment tree state is recorded at that height.
#[derive(Clone, Debug)]
pub struct AccountBirthday {
height: BlockHeight,
sapling_frontier: Frontier<sapling::Node, { sapling::NOTE_COMMITMENT_TREE_DEPTH }>,
#[cfg(feature = "orchard")]
orchard_frontier:
Frontier<orchard::tree::MerkleHashOrchard, { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }>,
prior_chain_state: ChainState,
recover_until: Option<BlockHeight>,
}
@ -1326,10 +1322,9 @@ impl From<io::Error> for BirthdayError {
impl AccountBirthday {
/// Constructs a new [`AccountBirthday`] from its constituent parts.
///
/// * `height`: The birthday height of the account. This is defined as the height of the first
/// block to be scanned in wallet recovery.
/// * `sapling_frontier`: The Sapling note commitment tree frontier as of the end of the block
/// prior to the birthday height.
/// * `prior_chain_state`: The chain state prior to the birthday height of the account. The
/// birthday height is defined as the height of the first block to be scanned in wallet
/// recovery.
/// * `recover_until`: An optional height at which the wallet should exit "recovery mode". In
/// order to avoid confusing shifts in wallet balance and spendability that may temporarily be
/// visible to a user during the process of recovering from seed, wallets may optionally set a
@ -1340,20 +1335,9 @@ impl AccountBirthday {
/// This API is intended primarily to be used in testing contexts; under normal circumstances,
/// [`AccountBirthday::from_treestate`] should be used instead.
#[cfg(feature = "test-dependencies")]
pub fn from_parts(
height: BlockHeight,
sapling_frontier: Frontier<sapling::Node, { sapling::NOTE_COMMITMENT_TREE_DEPTH }>,
#[cfg(feature = "orchard")] orchard_frontier: Frontier<
orchard::tree::MerkleHashOrchard,
{ orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 },
>,
recover_until: Option<BlockHeight>,
) -> Self {
pub fn from_parts(prior_chain_state: ChainState, recover_until: Option<BlockHeight>) -> Self {
Self {
height,
sapling_frontier,
#[cfg(feature = "orchard")]
orchard_frontier,
prior_chain_state,
recover_until,
}
}
@ -1373,10 +1357,7 @@ impl AccountBirthday {
recover_until: Option<BlockHeight>,
) -> Result<Self, BirthdayError> {
Ok(Self {
height: BlockHeight::try_from(treestate.height + 1)?,
sapling_frontier: treestate.sapling_tree()?.to_frontier(),
#[cfg(feature = "orchard")]
orchard_frontier: treestate.orchard_tree()?.to_frontier(),
prior_chain_state: treestate.to_chain_state()?,
recover_until,
})
}
@ -1386,7 +1367,7 @@ impl AccountBirthday {
pub fn sapling_frontier(
&self,
) -> &Frontier<sapling::Node, { sapling::NOTE_COMMITMENT_TREE_DEPTH }> {
&self.sapling_frontier
self.prior_chain_state.final_sapling_tree()
}
/// Returns the Orchard note commitment tree frontier as of the end of the block at
@ -1396,12 +1377,12 @@ impl AccountBirthday {
&self,
) -> &Frontier<orchard::tree::MerkleHashOrchard, { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }>
{
&self.orchard_frontier
self.prior_chain_state.final_orchard_tree()
}
/// Returns the birthday height of the account.
pub fn height(&self) -> BlockHeight {
self.height
self.prior_chain_state.block_height() + 1
}
/// Returns the height at which the wallet should exit "recovery mode".
@ -1409,7 +1390,7 @@ impl AccountBirthday {
self.recover_until
}
#[cfg(feature = "test-dependencies")]
#[cfg(any(test, feature = "test-dependencies"))]
/// Constructs a new [`AccountBirthday`] at the given network upgrade's activation,
/// with no "recover until" height.
///
@ -1419,17 +1400,18 @@ impl AccountBirthday {
pub fn from_activation<P: zcash_primitives::consensus::Parameters>(
params: &P,
network_upgrade: NetworkUpgrade,
prior_block_hash: BlockHash,
) -> AccountBirthday {
AccountBirthday::from_parts(
params.activation_height(network_upgrade).unwrap(),
Frontier::empty(),
#[cfg(feature = "orchard")]
Frontier::empty(),
ChainState::empty(
params.activation_height(network_upgrade).unwrap() - 1,
prior_block_hash,
),
None,
)
}
#[cfg(feature = "test-dependencies")]
#[cfg(any(test, feature = "test-dependencies"))]
/// Constructs a new [`AccountBirthday`] at Sapling activation, with no
/// "recover until" height.
///
@ -1438,8 +1420,9 @@ impl AccountBirthday {
/// Panics if the Sapling activation height is not set.
pub fn from_sapling_activation<P: zcash_primitives::consensus::Parameters>(
params: &P,
prior_block_hash: BlockHash,
) -> AccountBirthday {
Self::from_activation(params, NetworkUpgrade::Sapling)
Self::from_activation(params, NetworkUpgrade::Sapling, prior_block_hash)
}
}
@ -1482,7 +1465,7 @@ pub trait WalletWrite: WalletRead {
fn create_account(
&mut self,
seed: &SecretVec<u8>,
birthday: AccountBirthday,
birthday: &AccountBirthday,
) -> Result<(Self::AccountId, UnifiedSpendingKey), Self::Error>;
/// Generates and persists the next available diversified address, given the current
@ -1887,7 +1870,7 @@ pub mod testing {
fn create_account(
&mut self,
seed: &SecretVec<u8>,
_birthday: AccountBirthday,
_birthday: &AccountBirthday,
) -> Result<(Self::AccountId, UnifiedSpendingKey), Self::Error> {
let account = zip32::AccountId::ZERO;
UnifiedSpendingKey::from_seed(&self.network, seed.expose_secret(), account)

View File

@ -530,7 +530,7 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
fn create_account(
&mut self,
seed: &SecretVec<u8>,
birthday: AccountBirthday,
birthday: &AccountBirthday,
) -> Result<(AccountId, UnifiedSpendingKey), Self::Error> {
self.transactionally(|wdb| {
let seed_fingerprint =
@ -1690,7 +1690,8 @@ extern crate assert_matches;
#[cfg(test)]
mod tests {
use secrecy::SecretVec;
use zcash_client_backend::data_api::{AccountBirthday, WalletRead, WalletWrite};
use zcash_client_backend::data_api::{WalletRead, WalletWrite};
use zcash_primitives::block::BlockHash;
use crate::{testing::TestBuilder, AccountId, DEFAULT_UA_REQUEST};
@ -1703,7 +1704,7 @@ mod tests {
#[test]
fn validate_seed() {
let st = TestBuilder::new()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account = st.test_account().unwrap();
@ -1732,7 +1733,7 @@ mod tests {
#[test]
pub(crate) fn get_next_available_address() {
let mut st = TestBuilder::new()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account = st.test_account().cloned().unwrap();
@ -1763,7 +1764,7 @@ mod tests {
// Add an account to the wallet.
let st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account = st.test_account().unwrap();
let ufvk = account.usk().to_unified_full_viewing_key();

View File

@ -13,7 +13,7 @@ use rand_chacha::ChaChaRng;
use rand_core::{CryptoRng, RngCore, SeedableRng};
use rusqlite::{params, Connection};
use secrecy::{Secret, SecretVec};
use shardtree::error::ShardTreeError;
use tempfile::NamedTempFile;
#[cfg(feature = "unstable")]
@ -102,45 +102,57 @@ use crate::{
pub(crate) mod pool;
pub(crate) struct InitialChainState {
pub(crate) chain_state: ChainState,
pub(crate) prior_sapling_roots: Vec<CommitmentTreeRoot<sapling::Node>>,
#[cfg(feature = "orchard")]
pub(crate) prior_orchard_roots: Vec<CommitmentTreeRoot<MerkleHashOrchard>>,
}
/// A builder for a `zcash_client_sqlite` test.
pub(crate) struct TestBuilder<Cache> {
rng: ChaChaRng,
network: LocalNetwork,
cache: Cache,
test_account_birthday: Option<AccountBirthday>,
rng: ChaChaRng,
initial_chain_state: Option<InitialChainState>,
account_birthday: Option<AccountBirthday>,
}
impl TestBuilder<()> {
pub const DEFAULT_NETWORK: LocalNetwork = LocalNetwork {
overwinter: Some(BlockHeight::from_u32(1)),
sapling: Some(BlockHeight::from_u32(100_000)),
blossom: Some(BlockHeight::from_u32(100_000)),
heartwood: Some(BlockHeight::from_u32(100_000)),
canopy: Some(BlockHeight::from_u32(100_000)),
nu5: Some(BlockHeight::from_u32(100_000)),
#[cfg(zcash_unstable = "nu6")]
nu6: None,
#[cfg(zcash_unstable = "zfuture")]
z_future: None,
};
/// Constructs a new test.
pub(crate) fn new() -> Self {
TestBuilder {
rng: ChaChaRng::seed_from_u64(0),
// Use a fake network where Sapling through NU5 activate at the same height.
// We pick 100,000 to be large enough to handle any hard-coded test offsets.
network: LocalNetwork {
overwinter: Some(BlockHeight::from_u32(1)),
sapling: Some(BlockHeight::from_u32(100_000)),
blossom: Some(BlockHeight::from_u32(100_000)),
heartwood: Some(BlockHeight::from_u32(100_000)),
canopy: Some(BlockHeight::from_u32(100_000)),
nu5: Some(BlockHeight::from_u32(100_000)),
#[cfg(zcash_unstable = "nu6")]
nu6: None,
#[cfg(zcash_unstable = "zfuture")]
z_future: None,
},
network: Self::DEFAULT_NETWORK,
cache: (),
test_account_birthday: None,
rng: ChaChaRng::seed_from_u64(0),
initial_chain_state: None,
account_birthday: None,
}
}
/// Adds a [`BlockDb`] cache to the test.
pub(crate) fn with_block_cache(self) -> TestBuilder<BlockCache> {
TestBuilder {
rng: self.rng,
network: self.network,
cache: BlockCache::new(),
test_account_birthday: self.test_account_birthday,
rng: self.rng,
initial_chain_state: self.initial_chain_state,
account_birthday: self.account_birthday,
}
}
@ -148,20 +160,69 @@ impl TestBuilder<()> {
#[cfg(feature = "unstable")]
pub(crate) fn with_fs_block_cache(self) -> TestBuilder<FsBlockCache> {
TestBuilder {
rng: self.rng,
network: self.network,
cache: FsBlockCache::new(),
test_account_birthday: self.test_account_birthday,
rng: self.rng,
initial_chain_state: self.initial_chain_state,
account_birthday: self.account_birthday,
}
}
}
impl<Cache> TestBuilder<Cache> {
pub(crate) fn with_test_account<F: FnOnce(&LocalNetwork) -> AccountBirthday>(
pub(crate) fn with_initial_chain_state(
mut self,
birthday: F,
chain_state: impl FnOnce(&mut ChaChaRng, &LocalNetwork) -> InitialChainState,
) -> Self {
self.test_account_birthday = Some(birthday(&self.network));
assert!(self.initial_chain_state.is_none());
assert!(self.account_birthday.is_none());
self.initial_chain_state = Some(chain_state(&mut self.rng, &self.network));
self
}
pub(crate) fn with_account_birthday(
mut self,
birthday: impl FnOnce(
&mut ChaChaRng,
&LocalNetwork,
Option<&InitialChainState>,
) -> AccountBirthday,
) -> Self {
assert!(self.account_birthday.is_none());
self.account_birthday = Some(birthday(
&mut self.rng,
&self.network,
self.initial_chain_state.as_ref(),
));
self
}
pub(crate) fn with_account_from_sapling_activation(mut self, prev_hash: BlockHash) -> Self {
assert!(self.account_birthday.is_none());
self.account_birthday = Some(AccountBirthday::from_parts(
ChainState::empty(
self.network
.activation_height(NetworkUpgrade::Sapling)
.unwrap()
- 1,
prev_hash,
),
None,
));
self
}
pub(crate) fn with_account_having_current_birthday(mut self) -> Self {
assert!(self.account_birthday.is_none());
assert!(self.initial_chain_state.is_some());
self.account_birthday = Some(AccountBirthday::from_parts(
self.initial_chain_state
.as_ref()
.unwrap()
.chain_state
.clone(),
None,
));
self
}
@ -171,24 +232,75 @@ impl<Cache> TestBuilder<Cache> {
let mut db_data = WalletDb::for_path(data_file.path(), self.network).unwrap();
init_wallet_db(&mut db_data, None).unwrap();
let test_account = if let Some(birthday) = self.test_account_birthday {
let mut cached_blocks = BTreeMap::new();
if let Some(initial_state) = self.initial_chain_state {
db_data
.put_sapling_subtree_roots(0, &initial_state.prior_sapling_roots)
.unwrap();
db_data
.with_sapling_tree_mut(|t| {
t.insert_frontier(
initial_state.chain_state.final_sapling_tree().clone(),
Retention::Checkpoint {
id: initial_state.chain_state.block_height(),
is_marked: false,
},
)
})
.unwrap();
#[cfg(feature = "orchard")]
{
db_data
.put_orchard_subtree_roots(0, &initial_state.prior_orchard_roots)
.unwrap();
db_data
.with_orchard_tree_mut(|t| {
t.insert_frontier(
initial_state.chain_state.final_orchard_tree().clone(),
Retention::Checkpoint {
id: initial_state.chain_state.block_height(),
is_marked: false,
},
)
})
.unwrap();
}
let final_sapling_tree_size =
initial_state.chain_state.final_sapling_tree().tree_size() as u32;
let _final_orchard_tree_size = 0;
#[cfg(feature = "orchard")]
let _final_orchard_tree_size =
initial_state.chain_state.final_orchard_tree().tree_size() as u32;
cached_blocks.insert(
initial_state.chain_state.block_height(),
CachedBlock {
chain_state: initial_state.chain_state.clone(),
sapling_end_size: final_sapling_tree_size,
orchard_end_size: _final_orchard_tree_size,
},
);
};
let test_account = self.account_birthday.map(|birthday| {
let seed = Secret::new(vec![0u8; 32]);
let (account_id, usk) = db_data.create_account(&seed, birthday.clone()).unwrap();
Some((
let (account_id, usk) = db_data.create_account(&seed, &birthday).unwrap();
(
seed,
TestAccount {
account_id,
usk,
birthday,
},
))
} else {
None
};
)
});
TestState {
cache: self.cache,
cached_blocks: BTreeMap::new(),
cached_blocks,
latest_block_height: None,
_data_file: data_file,
db_data,
@ -341,67 +453,6 @@ where
self.cache.insert(&compact_block)
}
/// Ensure that the provided chain state and subtree roots exist in the wallet's note
/// commitment tree(s). This may result in a conflict if either the provided subtree roots or
/// the chain state conflict with existing note commitment tree data.
pub(crate) fn establish_chain_state(
&mut self,
state: ChainState,
prior_sapling_roots: &[CommitmentTreeRoot<sapling::Node>],
#[cfg(feature = "orchard")] prior_orchard_roots: &[CommitmentTreeRoot<MerkleHashOrchard>],
) -> Result<(), ShardTreeError<commitment_tree::Error>> {
self.wallet_mut()
.put_sapling_subtree_roots(0, prior_sapling_roots)?;
#[cfg(feature = "orchard")]
self.wallet_mut()
.put_orchard_subtree_roots(0, prior_orchard_roots)?;
self.wallet_mut().with_sapling_tree_mut(|t| {
t.insert_frontier(
state.final_sapling_tree().clone(),
Retention::Checkpoint {
id: state.block_height(),
is_marked: false,
},
)
})?;
let final_sapling_tree_size = state.final_sapling_tree().tree_size() as u32;
#[cfg(feature = "orchard")]
self.wallet_mut().with_orchard_tree_mut(|t| {
t.insert_frontier(
state.final_orchard_tree().clone(),
Retention::Checkpoint {
id: state.block_height(),
is_marked: false,
},
)
})?;
let _final_orchard_tree_size = 0;
#[cfg(feature = "orchard")]
let _final_orchard_tree_size = state.final_orchard_tree().tree_size() as u32;
self.insert_cached_block(state, final_sapling_tree_size, _final_orchard_tree_size);
Ok(())
}
fn insert_cached_block(
&mut self,
chain_state: ChainState,
sapling_end_size: u32,
orchard_end_size: u32,
) {
self.cached_blocks.insert(
chain_state.block_height(),
CachedBlock {
chain_state,
sapling_end_size,
orchard_end_size,
},
);
}
/// 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<Fvk: TestFvk>(
@ -692,11 +743,6 @@ impl<Cache> TestState<Cache> {
self.db_data.params
}
/// Exposes the random number source for the test state
pub(crate) fn rng(&mut self) -> &mut ChaChaRng {
&mut self.rng
}
/// Convenience method for obtaining the Sapling activation height for the network under test.
pub(crate) fn sapling_activation_height(&self) -> BlockHeight {
self.db_data

View File

@ -53,9 +53,6 @@ use crate::{
AccountId, NoteId, ReceivedNoteId,
};
#[cfg(feature = "orchard")]
use zcash_primitives::consensus::NetworkUpgrade;
#[cfg(feature = "transparent-inputs")]
use {
zcash_client_backend::{
@ -129,7 +126,7 @@ pub(crate) trait ShieldedPoolTester {
pub(crate) fn send_single_step_proposed_transfer<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account = st.test_account().cloned().unwrap();
@ -287,7 +284,7 @@ pub(crate) fn send_multi_step_proposed_transfer<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account = st.test_account().cloned().unwrap();
@ -439,7 +436,7 @@ pub(crate) fn send_multi_step_proposed_transfer<T: ShieldedPoolTester>() {
#[allow(deprecated)]
pub(crate) fn create_to_address_fails_on_incorrect_usk<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let dfvk = T::test_account_fvk(&st);
let to = T::fvk_default_address(&dfvk);
@ -467,7 +464,7 @@ pub(crate) fn create_to_address_fails_on_incorrect_usk<T: ShieldedPoolTester>()
#[allow(deprecated)]
pub(crate) fn proposal_fails_with_no_blocks<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account_id = st.test_account().unwrap().account_id();
@ -496,7 +493,7 @@ pub(crate) fn proposal_fails_with_no_blocks<T: ShieldedPoolTester>() {
pub(crate) fn spend_fails_on_unverified_notes<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account = st.test_account().cloned().unwrap();
@ -647,7 +644,7 @@ pub(crate) fn spend_fails_on_unverified_notes<T: ShieldedPoolTester>() {
pub(crate) fn spend_fails_on_locked_notes<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account = st.test_account().cloned().unwrap();
@ -785,7 +782,7 @@ pub(crate) fn spend_fails_on_locked_notes<T: ShieldedPoolTester>() {
pub(crate) fn ovk_policy_prevents_recovery_from_chain<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account = st.test_account().cloned().unwrap();
@ -881,7 +878,7 @@ pub(crate) fn ovk_policy_prevents_recovery_from_chain<T: ShieldedPoolTester>() {
pub(crate) fn spend_succeeds_to_t_addr_zero_change<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account = st.test_account().cloned().unwrap();
@ -928,7 +925,7 @@ pub(crate) fn spend_succeeds_to_t_addr_zero_change<T: ShieldedPoolTester>() {
pub(crate) fn change_note_spends_succeed<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account = st.test_account().cloned().unwrap();
@ -998,17 +995,11 @@ pub(crate) fn external_address_change_spends_detected_in_restore_from_seed<
// Add two accounts to the wallet.
let seed = Secret::new([0u8; 32].to_vec());
let birthday = AccountBirthday::from_sapling_activation(&st.network());
let (account_id, usk) = st
.wallet_mut()
.create_account(&seed, birthday.clone())
.unwrap();
let birthday = AccountBirthday::from_sapling_activation(&st.network(), BlockHash([0; 32]));
let (account_id, usk) = st.wallet_mut().create_account(&seed, &birthday).unwrap();
let dfvk = T::sk_to_fvk(T::usk_to_sk(&usk));
let (account2, usk2) = st
.wallet_mut()
.create_account(&seed, birthday.clone())
.unwrap();
let (account2, usk2) = st.wallet_mut().create_account(&seed, &birthday).unwrap();
let dfvk2 = T::sk_to_fvk(T::usk_to_sk(&usk2));
// Add funds to the wallet in a single note
@ -1081,16 +1072,13 @@ pub(crate) fn external_address_change_spends_detected_in_restore_from_seed<
st.reset();
// Account creation and DFVK derivation should be deterministic.
let (_, restored_usk) = st
.wallet_mut()
.create_account(&seed, birthday.clone())
.unwrap();
let (_, restored_usk) = st.wallet_mut().create_account(&seed, &birthday).unwrap();
assert!(T::fvks_equal(
&T::sk_to_fvk(T::usk_to_sk(&restored_usk)),
&dfvk,
));
let (_, restored_usk2) = st.wallet_mut().create_account(&seed, birthday).unwrap();
let (_, restored_usk2) = st.wallet_mut().create_account(&seed, &birthday).unwrap();
assert!(T::fvks_equal(
&T::sk_to_fvk(T::usk_to_sk(&restored_usk2)),
&dfvk2,
@ -1105,7 +1093,7 @@ pub(crate) fn external_address_change_spends_detected_in_restore_from_seed<
pub(crate) fn zip317_spend<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account = st.test_account().cloned().unwrap();
@ -1199,7 +1187,7 @@ pub(crate) fn zip317_spend<T: ShieldedPoolTester>() {
pub(crate) fn shield_transparent<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account = st.test_account().cloned().unwrap();
@ -1259,7 +1247,7 @@ pub(crate) fn shield_transparent<T: ShieldedPoolTester>() {
#[allow(dead_code)]
pub(crate) fn birthday_in_anchor_shard<T: ShieldedPoolTester>() {
// Use a non-zero birthday offset because Sapling and NU5 are activated at the same height.
let (mut st, dfvk, birthday, _) = test_with_nu5_birthday_offset::<T>(76);
let (mut st, dfvk, birthday, _) = test_with_nu5_birthday_offset::<T>(76, BlockHash([0; 32]));
// Set up the following situation:
//
@ -1364,7 +1352,7 @@ pub(crate) fn birthday_in_anchor_shard<T: ShieldedPoolTester>() {
pub(crate) fn checkpoint_gaps<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account = st.test_account().cloned().unwrap();
@ -1433,7 +1421,8 @@ pub(crate) fn checkpoint_gaps<T: ShieldedPoolTester>() {
pub(crate) fn pool_crossing_required<P0: ShieldedPoolTester, P1: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(|params| AccountBirthday::from_activation(params, NetworkUpgrade::Nu5))
.with_account_from_sapling_activation(BlockHash([0; 32])) // TODO: Allow for Orchard
// activation after Sapling
.build();
let account = st.test_account().cloned().unwrap();
@ -1522,7 +1511,8 @@ pub(crate) fn pool_crossing_required<P0: ShieldedPoolTester, P1: ShieldedPoolTes
pub(crate) fn fully_funded_fully_private<P0: ShieldedPoolTester, P1: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(|params| AccountBirthday::from_activation(params, NetworkUpgrade::Nu5))
.with_account_from_sapling_activation(BlockHash([0; 32])) // TODO: Allow for Orchard
// activation after Sapling
.build();
let account = st.test_account().cloned().unwrap();
@ -1612,7 +1602,7 @@ pub(crate) fn fully_funded_fully_private<P0: ShieldedPoolTester, P1: ShieldedPoo
pub(crate) fn valid_chain_states<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let dfvk = T::test_account_fvk(&st);
@ -1646,7 +1636,7 @@ pub(crate) fn valid_chain_states<T: ShieldedPoolTester>() {
pub(crate) fn invalid_chain_cache_disconnected<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let dfvk = T::test_account_fvk(&st);
@ -1697,7 +1687,7 @@ pub(crate) fn invalid_chain_cache_disconnected<T: ShieldedPoolTester>() {
pub(crate) fn data_db_truncation<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account = st.test_account().cloned().unwrap();
@ -1753,7 +1743,7 @@ pub(crate) fn data_db_truncation<T: ShieldedPoolTester>() {
pub(crate) fn scan_cached_blocks_allows_blocks_out_of_order<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account = st.test_account().cloned().unwrap();
@ -1813,7 +1803,7 @@ pub(crate) fn scan_cached_blocks_allows_blocks_out_of_order<T: ShieldedPoolTeste
pub(crate) fn scan_cached_blocks_finds_received_notes<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account = st.test_account().cloned().unwrap();
@ -1856,7 +1846,7 @@ pub(crate) fn scan_cached_blocks_finds_received_notes<T: ShieldedPoolTester>() {
pub(crate) fn scan_cached_blocks_finds_change_notes<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account = st.test_account().cloned().unwrap();
@ -1895,7 +1885,7 @@ pub(crate) fn scan_cached_blocks_finds_change_notes<T: ShieldedPoolTester>() {
pub(crate) fn scan_cached_blocks_detects_spends_out_of_order<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account = st.test_account().cloned().unwrap();

View File

@ -351,7 +351,7 @@ pub(crate) fn add_account<P: consensus::Parameters>(
params: &P,
kind: AccountSource,
viewing_key: ViewingKey,
birthday: AccountBirthday,
birthday: &AccountBirthday,
) -> Result<AccountId, SqliteClientError> {
let (hd_seed_fingerprint, hd_account_index) = match kind {
AccountSource::Derived {
@ -2720,7 +2720,7 @@ mod tests {
use std::num::NonZeroU32;
use sapling::zip32::ExtendedSpendingKey;
use zcash_client_backend::data_api::{AccountBirthday, AccountSource, WalletRead};
use zcash_client_backend::data_api::{AccountSource, WalletRead};
use zcash_primitives::{block::BlockHash, transaction::components::amount::NonNegativeAmount};
use crate::{
@ -2749,7 +2749,7 @@ mod tests {
#[test]
fn empty_database_has_no_balance() {
let st = TestBuilder::new()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account = st.test_account().unwrap();
@ -2784,7 +2784,7 @@ mod tests {
use crate::testing::TestBuilder;
let mut st = TestBuilder::new()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account_id = st.test_account().unwrap().account_id();
@ -2873,7 +2873,7 @@ mod tests {
use crate::testing::TestBuilder;
let st = TestBuilder::new()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account_id = st.test_account().unwrap().account_id();
let account_parameters = st.wallet().get_account(account_id).unwrap().unwrap();
@ -2892,10 +2892,10 @@ mod tests {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let account = st.test_account().unwrap().clone();
let account = st.test_account().cloned().unwrap();
let uaddr = st
.wallet()
.get_current_address(account.account_id())
@ -3043,7 +3043,7 @@ mod tests {
fn block_fully_scanned() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.with_account_from_sapling_activation(BlockHash([0; 32]))
.build();
let block_fully_scanned = |st: &TestState<BlockCache>| {

View File

@ -1427,6 +1427,7 @@ mod tests {
#[cfg(feature = "transparent-inputs")]
fn account_produces_expected_ua_sequence() {
use zcash_client_backend::data_api::{AccountBirthday, AccountSource, WalletRead};
use zcash_primitives::block::BlockHash;
let network = Network::MainNetwork;
let data_file = NamedTempFile::new().unwrap();
@ -1445,9 +1446,9 @@ mod tests {
Ok(())
);
let birthday = AccountBirthday::from_sapling_activation(&network);
let birthday = AccountBirthday::from_sapling_activation(&network, BlockHash([0; 32]));
let (account_id, _usk) = db_data
.create_account(&Secret::new(seed.to_vec()), birthday)
.create_account(&Secret::new(seed.to_vec()), &birthday)
.unwrap();
assert_matches!(
db_data.get_account(account_id),

View File

@ -581,9 +581,8 @@ pub(crate) fn update_chain_tip<P: consensus::Parameters>(
pub(crate) mod tests {
use std::num::NonZeroU8;
use incrementalmerkletree::{frontier::Frontier, Hashable, Position};
use incrementalmerkletree::{frontier::Frontier, Position};
use sapling::Node;
use secrecy::SecretVec;
use zcash_client_backend::data_api::{
chain::{ChainState, CommitmentTreeRoot},
@ -599,7 +598,10 @@ pub(crate) mod tests {
use crate::{
error::SqliteClientError,
testing::{pool::ShieldedPoolTester, AddressType, BlockCache, TestBuilder, TestState},
testing::{
pool::ShieldedPoolTester, AddressType, BlockCache, InitialChainState, TestBuilder,
TestState,
},
wallet::{
sapling::tests::SaplingPoolTester,
scanning::{insert_queue_entries, replace_queue_entries, suggest_scan_ranges},
@ -609,7 +611,7 @@ pub(crate) mod tests {
#[cfg(feature = "orchard")]
use {
crate::wallet::orchard::tests::OrchardPoolTester, orchard::tree::MerkleHashOrchard,
crate::wallet::orchard::tests::OrchardPoolTester,
zcash_client_backend::data_api::ORCHARD_SHARD_HEIGHT,
};
@ -626,66 +628,75 @@ pub(crate) mod tests {
fn scan_complete<T: ShieldedPoolTester>() {
use ScanPriority::*;
let initial_height_offset = 310;
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.build();
let sapling_activation_height = st.sapling_activation_height();
// We'll start inserting leaf notes 5 notes after the end of the third subtree, with a gap
// of 10 blocks. After `scan_cached_blocks`, the scan queue should have a requested scan
// range of 300..310 with `FoundNote` priority, 310..320 with `Scanned` priority.
// We set both Sapling and Orchard to the same initial tree size for simplicity.
let prior_block_hash = BlockHash([0; 32]);
let initial_sapling_tree_size: u32 = (0x1 << 16) * 3 + 5;
let initial_orchard_tree_size: u32 = (0x1 << 16) * 3 + 5;
let initial_height_offset = 310;
// Construct a fake chain state for the end of block 300
let (prior_sapling_frontiers, sapling_initial_tree) =
Frontier::random_with_prior_subtree_roots(
st.rng(),
initial_sapling_tree_size.into(),
NonZeroU8::new(16).unwrap(),
);
let sapling_subtree_roots = prior_sapling_frontiers
.into_iter()
.zip(0u32..)
.map(|(root, i)| {
CommitmentTreeRoot::from_parts(sapling_activation_height + (100 * (i + 1)), root)
})
.collect::<Vec<_>>();
let mut st = TestBuilder::new()
.with_block_cache()
.with_initial_chain_state(|rng, network| {
let sapling_activation_height =
network.activation_height(NetworkUpgrade::Sapling).unwrap();
// Construct a fake chain state for the end of block 300
let (prior_sapling_roots, sapling_initial_tree) =
Frontier::random_with_prior_subtree_roots(
rng,
initial_sapling_tree_size.into(),
NonZeroU8::new(16).unwrap(),
);
let prior_sapling_roots = prior_sapling_roots
.into_iter()
.zip(0u32..)
.map(|(root, i)| {
CommitmentTreeRoot::from_parts(
sapling_activation_height + (100 * (i + 1)),
root,
)
})
.collect::<Vec<_>>();
#[cfg(feature = "orchard")]
let (prior_orchard_frontiers, orchard_initial_tree) =
Frontier::random_with_prior_subtree_roots(
st.rng(),
initial_orchard_tree_size.into(),
NonZeroU8::new(16).unwrap(),
);
#[cfg(feature = "orchard")]
let orchard_subtree_roots = prior_orchard_frontiers
.into_iter()
.zip(0u32..)
.map(|(root, i)| {
CommitmentTreeRoot::from_parts(sapling_activation_height + (100 * (i + 1)), root)
})
.collect::<Vec<_>>();
let prior_block_hash = BlockHash([0; 32]);
st.establish_chain_state(
ChainState::new(
sapling_activation_height + initial_height_offset - 1,
prior_block_hash,
sapling_initial_tree,
#[cfg(feature = "orchard")]
orchard_initial_tree,
),
&sapling_subtree_roots,
#[cfg(feature = "orchard")]
&orchard_subtree_roots,
)
.unwrap();
let (prior_orchard_roots, orchard_initial_tree) =
Frontier::random_with_prior_subtree_roots(
rng,
initial_orchard_tree_size.into(),
NonZeroU8::new(16).unwrap(),
);
#[cfg(feature = "orchard")]
let prior_orchard_roots = prior_orchard_roots
.into_iter()
.zip(0u32..)
.map(|(root, i)| {
CommitmentTreeRoot::from_parts(
sapling_activation_height + (100 * (i + 1)),
root,
)
})
.collect::<Vec<_>>();
InitialChainState {
chain_state: ChainState::new(
sapling_activation_height + initial_height_offset - 1,
prior_block_hash,
sapling_initial_tree,
#[cfg(feature = "orchard")]
orchard_initial_tree,
),
prior_sapling_roots,
#[cfg(feature = "orchard")]
prior_orchard_roots,
}
})
.with_account_from_sapling_activation(BlockHash([3; 32]))
.build();
let sapling_activation_height = st.sapling_activation_height();
let dfvk = T::test_account_fvk(&st);
let value = NonNegativeAmount::const_from_u64(50000);
@ -776,37 +787,43 @@ pub(crate) mod tests {
}
pub(crate) fn test_with_nu5_birthday_offset<T: ShieldedPoolTester>(
offset: u32,
birthday_offset: u32,
prior_block_hash: BlockHash,
) -> (TestState<BlockCache>, T::Fvk, AccountBirthday, u32) {
let st = TestBuilder::new()
.with_block_cache()
.with_test_account(|network| {
.with_account_birthday(|rng, network, initial_chain_state| {
// We're constructing the birthday without adding any chain data.
assert!(initial_chain_state.is_none());
// We set the Sapling and Orchard frontiers at the birthday height to be
// 1234 notes into the second shard.
let birthday_height =
network.activation_height(NetworkUpgrade::Nu5).unwrap() + offset;
let frontier_position = Position::from((0x1 << 16) + 1234);
let sapling_frontier = Frontier::from_parts(
frontier_position,
Node::empty_leaf(),
vec![Node::empty_leaf(); frontier_position.past_ommer_count().into()],
)
.unwrap();
let birthday_height =
network.activation_height(NetworkUpgrade::Nu5).unwrap() + birthday_offset;
// Construct a fake chain state for the end of the block with the given
// birthday_offset from the Nu5 birthday.
let (_, sapling_initial_tree) = Frontier::random_with_prior_subtree_roots(
rng,
(frontier_position + 1).into(),
NonZeroU8::new(16).unwrap(),
);
#[cfg(feature = "orchard")]
let orchard_frontier = Frontier::from_parts(
frontier_position,
MerkleHashOrchard::empty_leaf(),
vec![
MerkleHashOrchard::empty_leaf();
frontier_position.past_ommer_count().into()
],
)
.unwrap();
let (_, orchard_initial_tree) = Frontier::random_with_prior_subtree_roots(
rng,
(frontier_position + 1).into(),
NonZeroU8::new(16).unwrap(),
);
AccountBirthday::from_parts(
birthday_height,
sapling_frontier,
#[cfg(feature = "orchard")]
orchard_frontier,
ChainState::new(
birthday_height,
prior_block_hash,
sapling_initial_tree,
#[cfg(feature = "orchard")]
orchard_initial_tree,
),
None,
)
})
@ -834,7 +851,8 @@ pub(crate) mod tests {
use ScanPriority::*;
// Use a non-zero birthday offset because Sapling and NU5 are activated at the same height.
let (st, _, birthday, sap_active) = test_with_nu5_birthday_offset::<T>(76);
let (st, _, birthday, sap_active) =
test_with_nu5_birthday_offset::<T>(76, BlockHash([0; 32]));
let birthday_height = birthday.height().into();
let expected = vec![
@ -869,11 +887,8 @@ pub(crate) mod tests {
st.wallet_mut()
.create_account(
&SecretVec::new(vec![0; 32]),
AccountBirthday::from_parts(
wallet_birthday,
Frontier::empty(),
#[cfg(feature = "orchard")]
Frontier::empty(),
&AccountBirthday::from_parts(
ChainState::empty(wallet_birthday - 1, BlockHash([0; 32])),
None,
),
)
@ -904,7 +919,8 @@ pub(crate) mod tests {
use ScanPriority::*;
// Use a non-zero birthday offset because Sapling and NU5 are activated at the same height.
let (mut st, _, birthday, sap_active) = test_with_nu5_birthday_offset::<T>(76);
let (mut st, _, birthday, sap_active) =
test_with_nu5_birthday_offset::<T>(76, BlockHash([0; 32]));
// Set up the following situation:
//
@ -921,7 +937,9 @@ pub(crate) mod tests {
// Verify that the suggested scan ranges match what is expected.
let expected = vec![
// The wallet's birthday onward is marked for recovery.
// The wallet's birthday onward is marked for recovery. Because we don't
// yet have any chain state, it is marked with `Historic` priority rather
// than `ChainTip`.
scan_range(wallet_birthday..chain_end, Historic),
// The range below the wallet's birthday height is ignored.
scan_range(sap_active..wallet_birthday, Ignored),
@ -946,7 +964,8 @@ pub(crate) mod tests {
use ScanPriority::*;
// Use a non-zero birthday offset because Sapling and NU5 are activated at the same height.
let (mut st, _, birthday, sap_active) = test_with_nu5_birthday_offset::<T>(76);
let (mut st, _, birthday, sap_active) =
test_with_nu5_birthday_offset::<T>(76, BlockHash([0; 32]));
// Set up the following situation:
//
@ -1007,7 +1026,8 @@ pub(crate) mod tests {
// Use a non-zero birthday offset because Sapling and NU5 are activated at the same height.
// this birthday is 1234 notes into the second shard
let (mut st, dfvk, birthday, sap_active) = test_with_nu5_birthday_offset::<T>(76);
let (mut st, dfvk, birthday, sap_active) =
test_with_nu5_birthday_offset::<T>(76, BlockHash([0; 32]));
// Set up the following situation:
//
@ -1149,7 +1169,8 @@ pub(crate) mod tests {
use ScanPriority::*;
// Use a non-zero birthday offset because Sapling and NU5 are activated at the same height.
let (mut st, dfvk, birthday, sap_active) = test_with_nu5_birthday_offset::<T>(76);
let (mut st, dfvk, birthday, sap_active) =
test_with_nu5_birthday_offset::<T>(76, BlockHash([0; 32]));
// Set up the following situation:
//