Skip to content

Commit

Permalink
Add passing test for current behaviour of wallet restore from seed.
Browse files Browse the repository at this point in the history
refs zcash#936

Signed-off-by: Daira Emma Hopwood <[email protected]>
  • Loading branch information
daira committed Sep 11, 2023
1 parent 2f5a28e commit 9bbdb75
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 0 deletions.
30 changes: 30 additions & 0 deletions zcash_client_sqlite/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,36 @@ where
limit,
)
}

/// Reset the wallet using a new wallet database, but with the same cache of blocks.
///
/// This does not recreate accounts, nor does it rescan the cached blocks.
/// The resulting wallet has no test account.
/// Before using any `generate_*` method on the reset state, call `reset_latest_cached_block()`.
pub(crate) fn reset(&mut self) {
let network = self.network();
self.latest_cached_block = None;
self._data_file = NamedTempFile::new().unwrap();
self.db_data = WalletDb::for_path(self._data_file.path(), network).unwrap();
self.test_account = None;
init_wallet_db(&mut self.db_data, None).unwrap();
}

/// Reset the latest cached block to the most recent one in the cache database.
#[allow(dead_code)]
pub(crate) fn reset_latest_cached_block(&mut self) {
self.cache
.block_source()
.with_blocks::<_, Infallible>(None, None, |block: CompactBlock| {
self.latest_cached_block = Some((
BlockHeight::from_u32(block.height.try_into().unwrap()),
BlockHash::from_slice(block.hash.as_slice()),
block.chain_metadata.unwrap().sapling_commitment_tree_size,
));
Ok(())
})
.unwrap();
}
}

impl<Cache> TestState<Cache> {
Expand Down
122 changes: 122 additions & 0 deletions zcash_client_sqlite/src/wallet/sapling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ pub(crate) mod tests {
use std::{convert::Infallible, num::NonZeroU32};

use incrementalmerkletree::Hashable;
use secrecy::Secret;
use zcash_proofs::prover::LocalTxProver;

use zcash_primitives::{
Expand Down Expand Up @@ -464,6 +465,7 @@ pub(crate) mod tests {
error::Error,
wallet::input_selection::{GreedyInputSelector, GreedyInputSelectorError},
AccountBirthday, Ratio, ShieldedProtocol, WalletCommitmentTrees, WalletRead,
WalletWrite,
},
decrypt_transaction,
fees::{fixed, zip317, DustOutputPolicy},
Expand Down Expand Up @@ -1125,6 +1127,126 @@ pub(crate) mod tests {
);
}

#[test]
fn external_address_change_spends_detected_in_restore_from_seed() {
let mut st = TestBuilder::new().with_block_cache().build();

// Add two accounts to the wallet.
let seed = Secret::new([0u8; 32].to_vec());
let birthday = AccountBirthday::from_sapling_activation(&st.network());
let (_, usk) = st
.wallet_mut()
.create_account(&seed, birthday.clone())
.unwrap();
let dfvk = usk.sapling().to_diversifiable_full_viewing_key();

let (_, usk2) = st
.wallet_mut()
.create_account(&seed, birthday.clone())
.unwrap();
let dfvk2 = usk2.sapling().to_diversifiable_full_viewing_key();

// Add funds to the wallet in a single note
let value = NonNegativeAmount::from_u64(100000).unwrap();
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value.into());
st.scan_cached_blocks(h, 1);

// Spendable balance matches total balance
assert_eq!(st.get_total_balance(AccountId::from(0)), value);
assert_eq!(st.get_spendable_balance(AccountId::from(0), 1), value);
assert_eq!(
st.get_total_balance(AccountId::from(1)),
NonNegativeAmount::ZERO
);

let amount_sent = NonNegativeAmount::from_u64(20000).unwrap();
let amount_legacy_change = NonNegativeAmount::from_u64(30000).unwrap();
let addr = dfvk.default_address().1;
let addr2 = dfvk2.default_address().1;
let req = TransactionRequest::new(vec![
// payment to an external recipient
Payment {
recipient_address: RecipientAddress::Shielded(addr2),
amount: amount_sent.into(),
memo: None,
label: None,
message: None,
other_params: vec![],
},
// payment back to the originating wallet, simulating legacy change
Payment {
recipient_address: RecipientAddress::Shielded(addr),
amount: amount_legacy_change.into(),
memo: None,
label: None,
message: None,
other_params: vec![],
},
])
.unwrap();

let fee_rule = FixedFeeRule::standard();
let input_selector = GreedyInputSelector::new(
fixed::SingleOutputChangeStrategy::new(fee_rule),
DustOutputPolicy::default(),
);

let txid = st
.spend(
&input_selector,
&usk,
req,
OvkPolicy::Sender,
NonZeroU32::new(1).unwrap(),
)
.unwrap();
let tx = &st.wallet().get_transaction(txid).unwrap();

let amount_left =
(value - (amount_sent + fee_rule.fixed_fee().try_into().unwrap()).unwrap()).unwrap();
let pending_change = (amount_left - amount_legacy_change).unwrap();

// The "legacy change" is not counted by get_pending_change().
assert_eq!(st.get_pending_change(AccountId::from(0), 1), pending_change);
// We spent the only note so we only have pending change.
assert_eq!(st.get_total_balance(AccountId::from(0)), pending_change);

let (h, _) = st.generate_next_block_from_tx(tx);
st.scan_cached_blocks(h, 1);

assert_eq!(st.get_total_balance(AccountId::from(1)), amount_sent);
assert_eq!(st.get_total_balance(AccountId::from(0)), amount_left);

st.reset();

// Account creation and DFVK derivation should be deterministic.
let (_, restored_usk) = st
.wallet_mut()
.create_account(&seed, birthday.clone())
.unwrap();
assert_eq!(
restored_usk
.sapling()
.to_diversifiable_full_viewing_key()
.to_bytes(),
dfvk.to_bytes()
);

let (_, restored_usk2) = st.wallet_mut().create_account(&seed, birthday).unwrap();
assert_eq!(
restored_usk2
.sapling()
.to_diversifiable_full_viewing_key()
.to_bytes(),
dfvk2.to_bytes()
);

st.scan_cached_blocks(st.sapling_activation_height(), 2);

assert_eq!(st.get_total_balance(AccountId::from(1)), amount_sent);
assert_eq!(st.get_total_balance(AccountId::from(0)), amount_left);
}

#[test]
fn zip317_spend() {
let mut st = TestBuilder::new()
Expand Down

0 comments on commit 9bbdb75

Please sign in to comment.