Merge pull request #1253 from zcash/more-orchard-tests
zcash_client_sqlite: More Orchard tests
This commit is contained in:
commit
cc3f05a0d9
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(¬_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(¬_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());
|
||||
}
|
||||
|
|
|
@ -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>()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue