Merge pull request #1253 from zcash/more-orchard-tests

zcash_client_sqlite: More Orchard tests
This commit is contained in:
Kris Nuttycombe 2024-03-11 08:31:09 -06:00 committed by GitHub
commit cc3f05a0d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 508 additions and 310 deletions

View File

@ -92,6 +92,9 @@ use {
zcash_primitives::{legacy::TransparentAddress, transaction::components::OutPoint},
};
#[cfg(feature = "test-dependencies")]
use zcash_primitives::consensus::NetworkUpgrade;
pub mod chain;
pub mod error;
pub mod scanning;
@ -1153,6 +1156,26 @@ impl AccountBirthday {
self.recover_until
}
#[cfg(feature = "test-dependencies")]
/// Constructs a new [`AccountBirthday`] at the given network upgrade's activation,
/// with no "recover until" height.
///
/// # Panics
///
/// Panics if the activation height for the given network upgrade is not set.
pub fn from_activation<P: zcash_primitives::consensus::Parameters>(
params: &P,
network_upgrade: NetworkUpgrade,
) -> AccountBirthday {
AccountBirthday::from_parts(
params.activation_height(network_upgrade).unwrap(),
Frontier::empty(),
#[cfg(feature = "orchard")]
Frontier::empty(),
None,
)
}
#[cfg(feature = "test-dependencies")]
/// Constructs a new [`AccountBirthday`] at Sapling activation, with no
/// "recover until" height.
@ -1163,15 +1186,7 @@ impl AccountBirthday {
pub fn from_sapling_activation<P: zcash_primitives::consensus::Parameters>(
params: &P,
) -> AccountBirthday {
use zcash_primitives::consensus::NetworkUpgrade;
AccountBirthday::from_parts(
params.activation_height(NetworkUpgrade::Sapling).unwrap(),
Frontier::empty(),
#[cfg(feature = "orchard")]
Frontier::empty(),
None,
)
Self::from_activation(params, NetworkUpgrade::Sapling)
}
}

View File

@ -322,332 +322,85 @@ where
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use std::num::NonZeroU32;
use crate::{testing, wallet::sapling::tests::SaplingPoolTester};
use sapling::zip32::ExtendedSpendingKey;
use zcash_primitives::{
block::BlockHash,
transaction::{components::amount::NonNegativeAmount, fees::zip317::FeeRule},
};
use zcash_client_backend::{
address::Address,
data_api::{
chain::error::Error, wallet::input_selection::GreedyInputSelector, AccountBirthday,
WalletRead,
},
fees::{zip317::SingleOutputChangeStrategy, DustOutputPolicy},
scanning::ScanError,
wallet::OvkPolicy,
zip321::{Payment, TransactionRequest},
ShieldedProtocol,
};
use crate::{
testing::{AddressType, TestBuilder},
wallet::truncate_to_height,
};
#[cfg(feature = "orchard")]
use crate::wallet::orchard::tests::OrchardPoolTester;
#[test]
fn valid_chain_states() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.build();
let dfvk = st.test_account_sapling().unwrap();
// Empty chain should return None
assert_matches!(st.wallet().chain_height(), Ok(None));
// Create a fake CompactBlock sending value to the address
let (h1, _, _) = st.generate_next_block(
&dfvk,
AddressType::DefaultExternal,
NonNegativeAmount::const_from_u64(5),
);
// Scan the cache
st.scan_cached_blocks(h1, 1);
// Create a second fake CompactBlock sending more value to the address
let (h2, _, _) = st.generate_next_block(
&dfvk,
AddressType::DefaultExternal,
NonNegativeAmount::const_from_u64(7),
);
// Scanning should detect no inconsistencies
st.scan_cached_blocks(h2, 1);
fn valid_chain_states_sapling() {
testing::pool::valid_chain_states::<SaplingPoolTester>()
}
#[test]
fn invalid_chain_cache_disconnected() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.build();
let dfvk = st.test_account_sapling().unwrap();
// Create some fake CompactBlocks
let (h, _, _) = st.generate_next_block(
&dfvk,
AddressType::DefaultExternal,
NonNegativeAmount::const_from_u64(5),
);
let (last_contiguous_height, _, _) = st.generate_next_block(
&dfvk,
AddressType::DefaultExternal,
NonNegativeAmount::const_from_u64(7),
);
// Scanning the cache should find no inconsistencies
st.scan_cached_blocks(h, 2);
// Create more fake CompactBlocks that don't connect to the scanned ones
let disconnect_height = last_contiguous_height + 1;
st.generate_block_at(
disconnect_height,
BlockHash([1; 32]),
&dfvk,
AddressType::DefaultExternal,
NonNegativeAmount::const_from_u64(8),
2,
2,
);
st.generate_next_block(
&dfvk,
AddressType::DefaultExternal,
NonNegativeAmount::const_from_u64(3),
);
// Data+cache chain should be invalid at the data/cache boundary
assert_matches!(
st.try_scan_cached_blocks(
disconnect_height,
2
),
Err(Error::Scan(ScanError::PrevHashMismatch { at_height }))
if at_height == disconnect_height
);
#[cfg(feature = "orchard")]
fn valid_chain_states_orchard() {
testing::pool::valid_chain_states::<OrchardPoolTester>()
}
#[test]
fn data_db_truncation() {
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 fake CompactBlocks sending value to the address
let value = NonNegativeAmount::const_from_u64(5);
let value2 = NonNegativeAmount::const_from_u64(7);
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value2);
// Scan the cache
st.scan_cached_blocks(h, 2);
// Account balance should reflect both received notes
assert_eq!(st.get_total_balance(account.0), (value + value2).unwrap());
// "Rewind" to height of last scanned block
st.wallet_mut()
.transactionally(|wdb| truncate_to_height(wdb.conn.0, &wdb.params, h + 1))
.unwrap();
// Account balance should be unaltered
assert_eq!(st.get_total_balance(account.0), (value + value2).unwrap());
// Rewind so that one block is dropped
st.wallet_mut()
.transactionally(|wdb| truncate_to_height(wdb.conn.0, &wdb.params, h))
.unwrap();
// Account balance should only contain the first received note
assert_eq!(st.get_total_balance(account.0), value);
// Scan the cache again
st.scan_cached_blocks(h, 2);
// Account balance should again reflect both received notes
assert_eq!(st.get_total_balance(account.0), (value + value2).unwrap());
fn invalid_chain_cache_disconnected_sapling() {
testing::pool::invalid_chain_cache_disconnected::<SaplingPoolTester>()
}
#[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 invalid_chain_cache_disconnected_orchard() {
#[cfg(feature = "orchard")]
testing::pool::invalid_chain_cache_disconnected::<OrchardPoolTester>()
}
#[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());
fn data_db_truncation_sapling() {
testing::pool::data_db_truncation::<SaplingPoolTester>()
}
#[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);
// 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 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);
// 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());
#[cfg(feature = "orchard")]
fn data_db_truncation_orchard() {
testing::pool::data_db_truncation::<OrchardPoolTester>()
}
#[test]
fn scan_cached_blocks_detects_spends_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();
fn scan_cached_blocks_allows_blocks_out_of_order_sapling() {
testing::pool::scan_cached_blocks_allows_blocks_out_of_order::<SaplingPoolTester>()
}
let dfvk = st.test_account_sapling().unwrap();
#[test]
#[cfg(feature = "orchard")]
fn scan_cached_blocks_allows_blocks_out_of_order_orchard() {
testing::pool::scan_cached_blocks_allows_blocks_out_of_order::<OrchardPoolTester>()
}
// Wallet summary is not yet available
assert_eq!(st.get_wallet_summary(0), None);
#[test]
fn scan_cached_blocks_finds_received_notes_sapling() {
testing::pool::scan_cached_blocks_finds_received_notes::<SaplingPoolTester>()
}
// 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);
#[test]
#[cfg(feature = "orchard")]
fn scan_cached_blocks_finds_received_notes_orchard() {
testing::pool::scan_cached_blocks_finds_received_notes::<OrchardPoolTester>()
}
// 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]
fn scan_cached_blocks_finds_change_notes_sapling() {
testing::pool::scan_cached_blocks_finds_change_notes::<SaplingPoolTester>()
}
// Scan the spending block first.
st.scan_cached_blocks(spent_height, 1);
#[test]
#[cfg(feature = "orchard")]
fn scan_cached_blocks_finds_change_notes_orchard() {
testing::pool::scan_cached_blocks_finds_change_notes::<OrchardPoolTester>()
}
// Account balance should equal the change
assert_eq!(st.get_total_balance(account.0), (value - value2).unwrap());
#[test]
fn scan_cached_blocks_detects_spends_out_of_order_sapling() {
testing::pool::scan_cached_blocks_detects_spends_out_of_order::<SaplingPoolTester>()
}
// Now scan the block in which we received the note that was spent.
st.scan_cached_blocks(received_height, 1);
// Account balance should be the same.
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>()
}
}

View File

@ -27,7 +27,7 @@ use zcash_client_backend::{
address::Address,
data_api::{
self,
chain::CommitmentTreeRoot,
chain::{self, CommitmentTreeRoot, ScanSummary},
error::Error,
wallet::input_selection::{GreedyInputSelector, GreedyInputSelectorError},
AccountBirthday, DecryptedTransaction, Ratio, WalletRead, WalletSummary, WalletWrite,
@ -35,6 +35,7 @@ use zcash_client_backend::{
decrypt_transaction,
fees::{fixed, standard, DustOutputPolicy},
keys::UnifiedSpendingKey,
scanning::ScanError,
wallet::{Note, OvkPolicy, ReceivedNote},
zip321::{self, Payment, TransactionRequest},
ShieldedProtocol,
@ -47,11 +48,14 @@ use crate::{
testing::{input_selector, AddressType, BlockCache, TestBuilder, TestState},
wallet::{
block_max_scanned, commitment_tree, parse_scope,
scanning::tests::test_with_nu5_birthday_offset,
scanning::tests::test_with_nu5_birthday_offset, truncate_to_height,
},
AccountId, NoteId, ReceivedNoteId,
};
#[cfg(feature = "orchard")]
use zcash_primitives::consensus::NetworkUpgrade;
#[cfg(feature = "transparent-inputs")]
use {
zcash_client_backend::{
@ -120,6 +124,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>() {
@ -1396,3 +1402,390 @@ pub(crate) fn checkpoint_gaps<T: ShieldedPoolTester>() {
Ok(_)
);
}
#[cfg(feature = "orchard")]
pub(crate) fn cross_pool_exchange<P0: ShieldedPoolTester, P1: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(|params| AccountBirthday::from_activation(params, NetworkUpgrade::Nu5))
.build();
let (account, usk, birthday) = st.test_account().unwrap();
let p0_fvk = P0::test_account_fvk(&st);
let p1_fvk = P1::test_account_fvk(&st);
let p1_to = P1::fvk_default_address(&p1_fvk);
let note_value = NonNegativeAmount::const_from_u64(300000);
st.generate_next_block(&p0_fvk, AddressType::DefaultExternal, note_value);
st.generate_next_block(&p1_fvk, AddressType::DefaultExternal, note_value);
st.scan_cached_blocks(birthday.height(), 2);
let initial_balance = (note_value * 2).unwrap();
assert_eq!(st.get_total_balance(account), initial_balance);
assert_eq!(st.get_spendable_balance(account, 1), initial_balance);
let transfer_amount = NonNegativeAmount::const_from_u64(200000);
let p0_to_p1 = zip321::TransactionRequest::new(vec![Payment {
recipient_address: p1_to,
amount: transfer_amount,
memo: None,
label: None,
message: None,
other_params: vec![],
}])
.unwrap();
let fee_rule = StandardFeeRule::Zip317;
let input_selector = GreedyInputSelector::new(
standard::SingleOutputChangeStrategy::new(fee_rule, None, P1::SHIELDED_PROTOCOL),
DustOutputPolicy::default(),
);
let proposal0 = st
.propose_transfer(
account,
&input_selector,
p0_to_p1,
NonZeroU32::new(1).unwrap(),
)
.unwrap();
let _min_target_height = proposal0.min_target_height();
assert_eq!(proposal0.steps().len(), 1);
let step0 = &proposal0.steps().head;
// We expect 4 logical actions, two per pool (due to padding).
let expected_fee = NonNegativeAmount::const_from_u64(20000);
assert_eq!(step0.balance().fee_required(), expected_fee);
let expected_change = (note_value - transfer_amount - expected_fee).unwrap();
let proposed_change = step0.balance().proposed_change();
assert_eq!(proposed_change.len(), 1);
let change_output = proposed_change.get(0).unwrap();
// Since this is a cross-pool transfer, change will be sent to the preferred pool.
assert_eq!(
change_output.output_pool(),
std::cmp::max(ShieldedProtocol::Sapling, ShieldedProtocol::Orchard)
);
assert_eq!(change_output.value(), expected_change);
let create_proposed_result =
st.create_proposed_transactions::<Infallible, _>(&usk, OvkPolicy::Sender, &proposal0);
assert_matches!(&create_proposed_result, Ok(txids) if txids.len() == 1);
let (h, _) = st.generate_next_block_including(create_proposed_result.unwrap()[0]);
st.scan_cached_blocks(h, 1);
assert_eq!(
st.get_total_balance(account),
(initial_balance - expected_fee).unwrap()
);
assert_eq!(
st.get_spendable_balance(account, 1),
(initial_balance - expected_fee).unwrap()
);
}
pub(crate) fn valid_chain_states<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.build();
let dfvk = T::test_account_fvk(&st);
// Empty chain should return None
assert_matches!(st.wallet().chain_height(), Ok(None));
// Create a fake CompactBlock sending value to the address
let (h1, _, _) = st.generate_next_block(
&dfvk,
AddressType::DefaultExternal,
NonNegativeAmount::const_from_u64(5),
);
// Scan the cache
st.scan_cached_blocks(h1, 1);
// Create a second fake CompactBlock sending more value to the address
let (h2, _, _) = st.generate_next_block(
&dfvk,
AddressType::DefaultExternal,
NonNegativeAmount::const_from_u64(7),
);
// Scanning should detect no inconsistencies
st.scan_cached_blocks(h2, 1);
}
pub(crate) fn invalid_chain_cache_disconnected<T: ShieldedPoolTester>() {
let mut st = TestBuilder::new()
.with_block_cache()
.with_test_account(AccountBirthday::from_sapling_activation)
.build();
let dfvk = T::test_account_fvk(&st);
// Create some fake CompactBlocks
let (h, _, _) = st.generate_next_block(
&dfvk,
AddressType::DefaultExternal,
NonNegativeAmount::const_from_u64(5),
);
let (last_contiguous_height, _, _) = st.generate_next_block(
&dfvk,
AddressType::DefaultExternal,
NonNegativeAmount::const_from_u64(7),
);
// Scanning the cache should find no inconsistencies
st.scan_cached_blocks(h, 2);
// Create more fake CompactBlocks that don't connect to the scanned ones
let disconnect_height = last_contiguous_height + 1;
st.generate_block_at(
disconnect_height,
BlockHash([1; 32]),
&dfvk,
AddressType::DefaultExternal,
NonNegativeAmount::const_from_u64(8),
2,
2,
);
st.generate_next_block(
&dfvk,
AddressType::DefaultExternal,
NonNegativeAmount::const_from_u64(3),
);
// Data+cache chain should be invalid at the data/cache boundary
assert_matches!(
st.try_scan_cached_blocks(
disconnect_height,
2
),
Err(chain::error::Error::Scan(ScanError::PrevHashMismatch { at_height }))
if at_height == disconnect_height
);
}
pub(crate) fn data_db_truncation<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 fake CompactBlocks sending value to the address
let value = NonNegativeAmount::const_from_u64(5);
let value2 = NonNegativeAmount::const_from_u64(7);
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value2);
// Scan the cache
st.scan_cached_blocks(h, 2);
// Account balance should reflect both received notes
assert_eq!(st.get_total_balance(account.0), (value + value2).unwrap());
// "Rewind" to height of last scanned block
st.wallet_mut()
.transactionally(|wdb| truncate_to_height(wdb.conn.0, &wdb.params, h + 1))
.unwrap();
// Account balance should be unaltered
assert_eq!(st.get_total_balance(account.0), (value + value2).unwrap());
// Rewind so that one block is dropped
st.wallet_mut()
.transactionally(|wdb| truncate_to_height(wdb.conn.0, &wdb.params, h))
.unwrap();
// Account balance should only contain the first received note
assert_eq!(st.get_total_balance(account.0), value);
// Scan the cache again
st.scan_cached_blocks(h, 2);
// Account balance should again reflect both received notes
assert_eq!(st.get_total_balance(account.0), (value + value2).unwrap());
}
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()
.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);
// 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 spending block first.
st.scan_cached_blocks(spent_height, 1);
// Account balance should equal the change
assert_eq!(st.get_total_balance(account.0), (value - value2).unwrap());
// Now scan the block in which we received the note that was spent.
st.scan_cached_blocks(received_height, 1);
// Account balance should be the same.
assert_eq!(st.get_total_balance(account.0), (value - value2).unwrap());
}

View File

@ -158,6 +158,12 @@ pub(crate) mod tests {
Ok(None)
}
fn received_note_count(
summary: &zcash_client_backend::data_api::chain::ScanSummary,
) -> usize {
summary.received_orchard_note_count()
}
}
#[test]
@ -235,4 +241,16 @@ pub(crate) mod tests {
fn checkpoint_gaps() {
testing::pool::checkpoint_gaps::<OrchardPoolTester>()
}
#[test]
fn scan_cached_blocks_detects_spends_out_of_order() {
testing::pool::scan_cached_blocks_detects_spends_out_of_order::<OrchardPoolTester>()
}
#[test]
fn cross_pool_exchange() {
use crate::wallet::sapling::tests::SaplingPoolTester;
testing::pool::cross_pool_exchange::<OrchardPoolTester, SaplingPoolTester>()
}
}

View File

@ -620,6 +620,12 @@ pub(crate) mod tests {
Ok(None)
}
fn received_note_count(
summary: &zcash_client_backend::data_api::chain::ScanSummary,
) -> usize {
summary.received_sapling_note_count()
}
}
pub(crate) fn test_prover() -> impl SpendProver + OutputProver {
@ -701,4 +707,17 @@ pub(crate) mod tests {
fn checkpoint_gaps() {
testing::pool::checkpoint_gaps::<SaplingPoolTester>()
}
#[test]
fn scan_cached_blocks_detects_spends_out_of_order() {
testing::pool::scan_cached_blocks_detects_spends_out_of_order::<SaplingPoolTester>()
}
#[test]
#[cfg(feature = "orchard")]
fn cross_pool_exchange() {
use crate::wallet::orchard::tests::OrchardPoolTester;
testing::pool::cross_pool_exchange::<SaplingPoolTester, OrchardPoolTester>()
}
}