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},
|
zcash_primitives::{legacy::TransparentAddress, transaction::components::OutPoint},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "test-dependencies")]
|
||||||
|
use zcash_primitives::consensus::NetworkUpgrade;
|
||||||
|
|
||||||
pub mod chain;
|
pub mod chain;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod scanning;
|
pub mod scanning;
|
||||||
|
@ -1153,6 +1156,26 @@ impl AccountBirthday {
|
||||||
self.recover_until
|
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")]
|
#[cfg(feature = "test-dependencies")]
|
||||||
/// Constructs a new [`AccountBirthday`] at Sapling activation, with no
|
/// Constructs a new [`AccountBirthday`] at Sapling activation, with no
|
||||||
/// "recover until" height.
|
/// "recover until" height.
|
||||||
|
@ -1163,15 +1186,7 @@ impl AccountBirthday {
|
||||||
pub fn from_sapling_activation<P: zcash_primitives::consensus::Parameters>(
|
pub fn from_sapling_activation<P: zcash_primitives::consensus::Parameters>(
|
||||||
params: &P,
|
params: &P,
|
||||||
) -> AccountBirthday {
|
) -> AccountBirthday {
|
||||||
use zcash_primitives::consensus::NetworkUpgrade;
|
Self::from_activation(params, NetworkUpgrade::Sapling)
|
||||||
|
|
||||||
AccountBirthday::from_parts(
|
|
||||||
params.activation_height(NetworkUpgrade::Sapling).unwrap(),
|
|
||||||
Frontier::empty(),
|
|
||||||
#[cfg(feature = "orchard")]
|
|
||||||
Frontier::empty(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -322,332 +322,85 @@ where
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::num::NonZeroU32;
|
use crate::{testing, wallet::sapling::tests::SaplingPoolTester};
|
||||||
|
|
||||||
use sapling::zip32::ExtendedSpendingKey;
|
#[cfg(feature = "orchard")]
|
||||||
use zcash_primitives::{
|
use crate::wallet::orchard::tests::OrchardPoolTester;
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn valid_chain_states() {
|
fn valid_chain_states_sapling() {
|
||||||
let mut st = TestBuilder::new()
|
testing::pool::valid_chain_states::<SaplingPoolTester>()
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_chain_cache_disconnected() {
|
#[cfg(feature = "orchard")]
|
||||||
let mut st = TestBuilder::new()
|
fn valid_chain_states_orchard() {
|
||||||
.with_block_cache()
|
testing::pool::valid_chain_states::<OrchardPoolTester>()
|
||||||
.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
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn data_db_truncation() {
|
fn invalid_chain_cache_disconnected_sapling() {
|
||||||
let mut st = TestBuilder::new()
|
testing::pool::invalid_chain_cache_disconnected::<SaplingPoolTester>()
|
||||||
.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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn scan_cached_blocks_allows_blocks_out_of_order() {
|
fn invalid_chain_cache_disconnected_orchard() {
|
||||||
let mut st = TestBuilder::new()
|
#[cfg(feature = "orchard")]
|
||||||
.with_block_cache()
|
testing::pool::invalid_chain_cache_disconnected::<OrchardPoolTester>()
|
||||||
.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(_)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn scan_cached_blocks_finds_received_notes() {
|
fn data_db_truncation_sapling() {
|
||||||
let mut st = TestBuilder::new()
|
testing::pool::data_db_truncation::<SaplingPoolTester>()
|
||||||
.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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn scan_cached_blocks_finds_change_notes() {
|
#[cfg(feature = "orchard")]
|
||||||
let mut st = TestBuilder::new()
|
fn data_db_truncation_orchard() {
|
||||||
.with_block_cache()
|
testing::pool::data_db_truncation::<OrchardPoolTester>()
|
||||||
.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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn scan_cached_blocks_detects_spends_out_of_order() {
|
fn scan_cached_blocks_allows_blocks_out_of_order_sapling() {
|
||||||
let mut st = TestBuilder::new()
|
testing::pool::scan_cached_blocks_allows_blocks_out_of_order::<SaplingPoolTester>()
|
||||||
.with_block_cache()
|
}
|
||||||
.with_test_account(AccountBirthday::from_sapling_activation)
|
|
||||||
.build();
|
|
||||||
let account = st.test_account().unwrap();
|
|
||||||
|
|
||||||
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
|
#[test]
|
||||||
assert_eq!(st.get_wallet_summary(0), None);
|
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
|
#[test]
|
||||||
let value = NonNegativeAmount::const_from_u64(5);
|
#[cfg(feature = "orchard")]
|
||||||
let (received_height, _, nf) =
|
fn scan_cached_blocks_finds_received_notes_orchard() {
|
||||||
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
testing::pool::scan_cached_blocks_finds_received_notes::<OrchardPoolTester>()
|
||||||
|
}
|
||||||
|
|
||||||
// Create a second fake CompactBlock spending value from the address
|
#[test]
|
||||||
let extsk2 = ExtendedSpendingKey::master(&[0]);
|
fn scan_cached_blocks_finds_change_notes_sapling() {
|
||||||
let to2 = extsk2.default_address().1;
|
testing::pool::scan_cached_blocks_finds_change_notes::<SaplingPoolTester>()
|
||||||
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.
|
#[test]
|
||||||
st.scan_cached_blocks(spent_height, 1);
|
#[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
|
#[test]
|
||||||
assert_eq!(st.get_total_balance(account.0), (value - value2).unwrap());
|
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.
|
#[test]
|
||||||
st.scan_cached_blocks(received_height, 1);
|
#[cfg(feature = "orchard")]
|
||||||
|
fn scan_cached_blocks_detects_spends_out_of_order_orchard() {
|
||||||
// Account balance should be the same.
|
testing::pool::scan_cached_blocks_detects_spends_out_of_order::<OrchardPoolTester>()
|
||||||
assert_eq!(st.get_total_balance(account.0), (value - value2).unwrap());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ use zcash_client_backend::{
|
||||||
address::Address,
|
address::Address,
|
||||||
data_api::{
|
data_api::{
|
||||||
self,
|
self,
|
||||||
chain::CommitmentTreeRoot,
|
chain::{self, CommitmentTreeRoot, ScanSummary},
|
||||||
error::Error,
|
error::Error,
|
||||||
wallet::input_selection::{GreedyInputSelector, GreedyInputSelectorError},
|
wallet::input_selection::{GreedyInputSelector, GreedyInputSelectorError},
|
||||||
AccountBirthday, DecryptedTransaction, Ratio, WalletRead, WalletSummary, WalletWrite,
|
AccountBirthday, DecryptedTransaction, Ratio, WalletRead, WalletSummary, WalletWrite,
|
||||||
|
@ -35,6 +35,7 @@ use zcash_client_backend::{
|
||||||
decrypt_transaction,
|
decrypt_transaction,
|
||||||
fees::{fixed, standard, DustOutputPolicy},
|
fees::{fixed, standard, DustOutputPolicy},
|
||||||
keys::UnifiedSpendingKey,
|
keys::UnifiedSpendingKey,
|
||||||
|
scanning::ScanError,
|
||||||
wallet::{Note, OvkPolicy, ReceivedNote},
|
wallet::{Note, OvkPolicy, ReceivedNote},
|
||||||
zip321::{self, Payment, TransactionRequest},
|
zip321::{self, Payment, TransactionRequest},
|
||||||
ShieldedProtocol,
|
ShieldedProtocol,
|
||||||
|
@ -47,11 +48,14 @@ use crate::{
|
||||||
testing::{input_selector, AddressType, BlockCache, TestBuilder, TestState},
|
testing::{input_selector, AddressType, BlockCache, TestBuilder, TestState},
|
||||||
wallet::{
|
wallet::{
|
||||||
block_max_scanned, commitment_tree, parse_scope,
|
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,
|
AccountId, NoteId, ReceivedNoteId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
use zcash_primitives::consensus::NetworkUpgrade;
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
use {
|
use {
|
||||||
zcash_client_backend::{
|
zcash_client_backend::{
|
||||||
|
@ -120,6 +124,8 @@ pub(crate) trait ShieldedPoolTester {
|
||||||
tx: &Transaction,
|
tx: &Transaction,
|
||||||
fvk: &Self::Fvk,
|
fvk: &Self::Fvk,
|
||||||
) -> Result<Option<(Note, Address, MemoBytes)>, OutputRecoveryError>;
|
) -> Result<Option<(Note, Address, MemoBytes)>, OutputRecoveryError>;
|
||||||
|
|
||||||
|
fn received_note_count(summary: &ScanSummary) -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn send_single_step_proposed_transfer<T: ShieldedPoolTester>() {
|
pub(crate) fn send_single_step_proposed_transfer<T: ShieldedPoolTester>() {
|
||||||
|
@ -1396,3 +1402,390 @@ pub(crate) fn checkpoint_gaps<T: ShieldedPoolTester>() {
|
||||||
Ok(_)
|
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)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn received_note_count(
|
||||||
|
summary: &zcash_client_backend::data_api::chain::ScanSummary,
|
||||||
|
) -> usize {
|
||||||
|
summary.received_orchard_note_count()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -235,4 +241,16 @@ pub(crate) mod tests {
|
||||||
fn checkpoint_gaps() {
|
fn checkpoint_gaps() {
|
||||||
testing::pool::checkpoint_gaps::<OrchardPoolTester>()
|
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)
|
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 {
|
pub(crate) fn test_prover() -> impl SpendProver + OutputProver {
|
||||||
|
@ -701,4 +707,17 @@ pub(crate) mod tests {
|
||||||
fn checkpoint_gaps() {
|
fn checkpoint_gaps() {
|
||||||
testing::pool::checkpoint_gaps::<SaplingPoolTester>()
|
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