Skip to content

Commit

Permalink
zcash_client_sqlite: Generalize chain tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
nuttycom committed Mar 11, 2024
1 parent a606340 commit 8cd2f2b
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 143 deletions.
173 changes: 37 additions & 136 deletions zcash_client_sqlite/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,32 +322,22 @@ where
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use std::num::NonZeroU32;

use sapling::zip32::ExtendedSpendingKey;
use zcash_primitives::{
block::BlockHash,
transaction::{components::amount::NonNegativeAmount, fees::zip317::FeeRule},
};
use zcash_primitives::{block::BlockHash, transaction::components::amount::NonNegativeAmount};

use zcash_client_backend::{
address::Address,
data_api::{
chain::error::Error, wallet::input_selection::GreedyInputSelector, AccountBirthday,
WalletRead,
},
fees::{zip317::SingleOutputChangeStrategy, DustOutputPolicy},
data_api::{chain::error::Error, AccountBirthday, WalletRead},
scanning::ScanError,
wallet::OvkPolicy,
zip321::{Payment, TransactionRequest},
ShieldedProtocol,
};

use crate::{
testing::{AddressType, TestBuilder},
wallet::truncate_to_height,
testing::{self, AddressType, TestBuilder},
wallet::{sapling::tests::SaplingPoolTester, truncate_to_height},
};

#[cfg(feature = "orchard")]
use crate::wallet::orchard::tests::OrchardPoolTester;

#[test]
fn valid_chain_states() {
let mut st = TestBuilder::new()
Expand Down Expand Up @@ -482,135 +472,46 @@ mod tests {
}

#[test]
fn scan_cached_blocks_allows_blocks_out_of_order() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.build();
let account = st.test_account().unwrap();

let (_, usk, _) = st.test_account().unwrap();
let dfvk = st.test_account_sapling().unwrap();

// Create a block with height SAPLING_ACTIVATION_HEIGHT
let value = NonNegativeAmount::const_from_u64(50000);
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
st.scan_cached_blocks(h1, 1);
assert_eq!(st.get_total_balance(account.0), value);

// Create blocks to reach SAPLING_ACTIVATION_HEIGHT + 2
let (h2, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
let (h3, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);

// Scan the later block first
st.scan_cached_blocks(h3, 1);

// Now scan the block of height SAPLING_ACTIVATION_HEIGHT + 1
st.scan_cached_blocks(h2, 1);
assert_eq!(
st.get_total_balance(account.0),
NonNegativeAmount::const_from_u64(150_000)
);

// We can spend the received notes
let req = TransactionRequest::new(vec![Payment {
recipient_address: Address::Sapling(dfvk.default_address().1),
amount: NonNegativeAmount::const_from_u64(110_000),
memo: None,
label: None,
message: None,
other_params: vec![],
}])
.unwrap();
let input_selector = GreedyInputSelector::new(
SingleOutputChangeStrategy::new(FeeRule::standard(), None, ShieldedProtocol::Sapling),
DustOutputPolicy::default(),
);
assert_matches!(
st.spend(
&input_selector,
&usk,
req,
OvkPolicy::Sender,
NonZeroU32::new(1).unwrap(),
),
Ok(_)
);
fn scan_cached_blocks_allows_blocks_out_of_order_sapling() {
testing::pool::scan_cached_blocks_allows_blocks_out_of_order::<SaplingPoolTester>()
}

#[test]
fn scan_cached_blocks_finds_received_notes() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.build();
let account = st.test_account().unwrap();

let dfvk = st.test_account_sapling().unwrap();

// Wallet summary is not yet available
assert_eq!(st.get_wallet_summary(0), None);

// Create a fake CompactBlock sending value to the address
let value = NonNegativeAmount::const_from_u64(5);
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);

// Scan the cache
let summary = st.scan_cached_blocks(h1, 1);
assert_eq!(summary.scanned_range().start, h1);
assert_eq!(summary.scanned_range().end, h1 + 1);
assert_eq!(summary.received_sapling_note_count(), 1);

// Account balance should reflect the received note
assert_eq!(st.get_total_balance(account.0), value);

// Create a second fake CompactBlock sending more value to the address
let value2 = NonNegativeAmount::const_from_u64(7);
let (h2, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value2);

// Scan the cache again
let summary = st.scan_cached_blocks(h2, 1);
assert_eq!(summary.scanned_range().start, h2);
assert_eq!(summary.scanned_range().end, h2 + 1);
assert_eq!(summary.received_sapling_note_count(), 1);

// Account balance should reflect both received notes
assert_eq!(st.get_total_balance(account.0), (value + value2).unwrap());
#[cfg(feature = "orchard")]
fn scan_cached_blocks_allows_blocks_out_of_order_orchard() {
testing::pool::scan_cached_blocks_allows_blocks_out_of_order::<OrchardPoolTester>()
}

#[test]
fn scan_cached_blocks_finds_change_notes() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.build();
let account = st.test_account().unwrap();
let dfvk = st.test_account_sapling().unwrap();

// Wallet summary is not yet available
assert_eq!(st.get_wallet_summary(0), None);

// Create a fake CompactBlock sending value to the address
let value = NonNegativeAmount::const_from_u64(5);
let (received_height, _, nf) =
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
fn scan_cached_blocks_finds_received_notes_sapling() {
testing::pool::scan_cached_blocks_finds_received_notes::<SaplingPoolTester>()
}

// Scan the cache
st.scan_cached_blocks(received_height, 1);
#[test]
#[cfg(feature = "orchard")]
fn scan_cached_blocks_finds_received_notes_orchard() {
testing::pool::scan_cached_blocks_finds_received_notes::<OrchardPoolTester>()
}

// Account balance should reflect the received note
assert_eq!(st.get_total_balance(account.0), value);
#[test]
fn scan_cached_blocks_finds_change_notes_sapling() {
testing::pool::scan_cached_blocks_finds_change_notes::<SaplingPoolTester>()
}

// Create a second fake CompactBlock spending value from the address
let extsk2 = ExtendedSpendingKey::master(&[0]);
let to2 = extsk2.default_address().1;
let value2 = NonNegativeAmount::const_from_u64(2);
let (spent_height, _) = st.generate_next_block_spending(&dfvk, (nf, value), to2, value2);
#[test]
#[cfg(feature = "orchard")]
fn scan_cached_blocks_finds_change_notes_orchard() {
testing::pool::scan_cached_blocks_finds_change_notes::<OrchardPoolTester>()
}

// Scan the cache again
st.scan_cached_blocks(spent_height, 1);
#[test]
fn scan_cached_blocks_detects_spends_out_of_order_sapling() {
testing::pool::scan_cached_blocks_detects_spends_out_of_order::<SaplingPoolTester>()
}

// Account balance should equal the change
assert_eq!(st.get_total_balance(account.0), (value - value2).unwrap());
#[test]
#[cfg(feature = "orchard")]
fn scan_cached_blocks_detects_spends_out_of_order_orchard() {
testing::pool::scan_cached_blocks_detects_spends_out_of_order::<OrchardPoolTester>()
}
}
153 changes: 146 additions & 7 deletions zcash_client_sqlite/src/testing/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,16 @@ use zcash_client_backend::{
address::Address,
data_api::{
self,
chain::CommitmentTreeRoot,
chain::{CommitmentTreeRoot, ScanSummary},
error::Error,
wallet::input_selection::{GreedyInputSelector, GreedyInputSelectorError},
AccountBirthday, DecryptedTransaction, Ratio, WalletRead, WalletSummary, WalletWrite,
},
decrypt_transaction,
fees::{fixed, standard, DustOutputPolicy},
fees::{
fixed::{self},
standard, DustOutputPolicy,
},
keys::UnifiedSpendingKey,
wallet::{Note, OvkPolicy, ReceivedNote},
zip321::{self, Payment, TransactionRequest},
Expand Down Expand Up @@ -120,6 +123,8 @@ pub(crate) trait ShieldedPoolTester {
tx: &Transaction,
fvk: &Self::Fvk,
) -> Result<Option<(Note, Address, MemoBytes)>, OutputRecoveryError>;

fn received_note_count(summary: &ScanSummary) -> usize;
}

pub(crate) fn send_single_step_proposed_transfer<T: ShieldedPoolTester>() {
Expand Down Expand Up @@ -1397,6 +1402,143 @@ pub(crate) fn checkpoint_gaps<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)
.build();

let account = st.test_account().unwrap();
let (_, usk, _) = st.test_account().unwrap();
let dfvk = T::test_account_fvk(&st);

let value = NonNegativeAmount::const_from_u64(50000);
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
st.scan_cached_blocks(h1, 1);
assert_eq!(st.get_total_balance(account.0), value);

// Create blocks to reach height + 2
let (h2, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
let (h3, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);

// Scan the later block first
st.scan_cached_blocks(h3, 1);

// Now scan the block of height height + 1
st.scan_cached_blocks(h2, 1);
assert_eq!(
st.get_total_balance(account.0),
NonNegativeAmount::const_from_u64(150_000)
);

// We can spend the received notes
let req = TransactionRequest::new(vec![Payment {
recipient_address: T::fvk_default_address(&dfvk),
amount: NonNegativeAmount::const_from_u64(110_000),
memo: None,
label: None,
message: None,
other_params: vec![],
}])
.unwrap();

#[allow(deprecated)]
let input_selector = GreedyInputSelector::new(
standard::SingleOutputChangeStrategy::new(
StandardFeeRule::Zip317,
None,
T::SHIELDED_PROTOCOL,
),
DustOutputPolicy::default(),
);
assert_matches!(
st.spend(
&input_selector,
&usk,
req,
OvkPolicy::Sender,
NonZeroU32::new(1).unwrap(),
),
Ok(_)
);
}

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)
.build();

let account = st.test_account().unwrap();
let dfvk = T::test_account_fvk(&st);

// Wallet summary is not yet available
assert_eq!(st.get_wallet_summary(0), None);

// Create a fake CompactBlock sending value to the address
let value = NonNegativeAmount::const_from_u64(5);
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);

// Scan the cache
let summary = st.scan_cached_blocks(h1, 1);
assert_eq!(summary.scanned_range().start, h1);
assert_eq!(summary.scanned_range().end, h1 + 1);
assert_eq!(T::received_note_count(&summary), 1);

// Account balance should reflect the received note
assert_eq!(st.get_total_balance(account.0), value);

// Create a second fake CompactBlock sending more value to the address
let value2 = NonNegativeAmount::const_from_u64(7);
let (h2, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value2);

// Scan the cache again
let summary = st.scan_cached_blocks(h2, 1);
assert_eq!(summary.scanned_range().start, h2);
assert_eq!(summary.scanned_range().end, h2 + 1);
assert_eq!(T::received_note_count(&summary), 1);

// Account balance should reflect both received notes
assert_eq!(st.get_total_balance(account.0), (value + value2).unwrap());
}

// TODO: This test can probably be entirely removed, as the following test duplicates it entirely.
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)
.build();

let account = st.test_account().unwrap();
let dfvk = T::test_account_fvk(&st);

// Wallet summary is not yet available
assert_eq!(st.get_wallet_summary(0), None);

// Create a fake CompactBlock sending value to the address
let value = NonNegativeAmount::const_from_u64(5);
let (received_height, _, nf) =
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);

// Scan the cache
st.scan_cached_blocks(received_height, 1);

// Account balance should reflect the received note
assert_eq!(st.get_total_balance(account.0), value);

// Create a second fake CompactBlock spending value from the address
let not_our_key = T::sk_to_fvk(&T::sk(&[0xf5; 32]));
let to2 = T::fvk_default_address(&not_our_key);
let value2 = NonNegativeAmount::const_from_u64(2);
let (spent_height, _) = st.generate_next_block_spending(&dfvk, (nf, value), to2, value2);

// Scan the cache again
st.scan_cached_blocks(spent_height, 1);

// Account balance should equal the change
assert_eq!(st.get_total_balance(account.0), (value - value2).unwrap());
}

pub(crate) fn scan_cached_blocks_detects_spends_out_of_order<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
Expand All @@ -1411,11 +1553,8 @@ pub(crate) fn scan_cached_blocks_detects_spends_out_of_order<T: ShieldedPoolTest

// Create a fake CompactBlock sending value to the address
let value = NonNegativeAmount::const_from_u64(5);
let (received_height, _, nf) = st.generate_next_block(
&dfvk,
AddressType::DefaultExternal,
value
);
let (received_height, _, nf) =
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);

// Create a second fake CompactBlock spending value from the address
let not_our_key = T::sk_to_fvk(&T::sk(&[0xf5; 32]));
Expand Down
Loading

0 comments on commit 8cd2f2b

Please sign in to comment.