Merge pull request #1539 from zcash/zcb-test-cleanups
zcash_client_backend: Post-merge cleanups to test framework extraction
This commit is contained in:
commit
4b3bc8fc9d
|
@ -1166,13 +1166,20 @@ pub trait WalletRead {
|
||||||
/// transaction data requests, such as when it is necessary to fill in purely-transparent
|
/// transaction data requests, such as when it is necessary to fill in purely-transparent
|
||||||
/// transaction history by walking the chain backwards via transparent inputs.
|
/// transaction history by walking the chain backwards via transparent inputs.
|
||||||
fn transaction_data_requests(&self) -> Result<Vec<TransactionDataRequest>, Self::Error>;
|
fn transaction_data_requests(&self) -> Result<Vec<TransactionDataRequest>, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read-only operations required for testing light wallet functions.
|
||||||
|
///
|
||||||
|
/// These methods expose internal details or unstable interfaces, primarily to enable use
|
||||||
|
/// of the [`testing`] framework. They should not be used in production software.
|
||||||
|
#[cfg(any(test, feature = "test-dependencies"))]
|
||||||
|
#[delegatable_trait]
|
||||||
|
pub trait WalletTest: WalletRead {
|
||||||
/// Returns a vector of transaction summaries.
|
/// Returns a vector of transaction summaries.
|
||||||
///
|
///
|
||||||
/// Currently test-only, as production use could return a very large number of results; either
|
/// Currently test-only, as production use could return a very large number of results; either
|
||||||
/// pagination or a streaming design will be necessary to stabilize this feature for production
|
/// pagination or a streaming design will be necessary to stabilize this feature for production
|
||||||
/// use.
|
/// use.
|
||||||
#[cfg(any(test, feature = "test-dependencies"))]
|
|
||||||
fn get_tx_history(
|
fn get_tx_history(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<Vec<testing::TransactionSummary<Self::AccountId>>, Self::Error> {
|
) -> Result<Vec<testing::TransactionSummary<Self::AccountId>>, Self::Error> {
|
||||||
|
@ -1181,7 +1188,6 @@ pub trait WalletRead {
|
||||||
|
|
||||||
/// Returns the note IDs for shielded notes sent by the wallet in a particular
|
/// Returns the note IDs for shielded notes sent by the wallet in a particular
|
||||||
/// transaction.
|
/// transaction.
|
||||||
#[cfg(any(test, feature = "test-dependencies"))]
|
|
||||||
fn get_sent_note_ids(
|
fn get_sent_note_ids(
|
||||||
&self,
|
&self,
|
||||||
_txid: &TxId,
|
_txid: &TxId,
|
||||||
|
|
|
@ -56,6 +56,7 @@ use crate::{
|
||||||
ShieldedProtocol,
|
ShieldedProtocol,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::WalletTest;
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
use super::{
|
use super::{
|
||||||
chain::{scan_cached_blocks, BlockSource, ChainState, CommitmentTreeRoot, ScanSummary},
|
chain::{scan_cached_blocks, BlockSource, ChainState, CommitmentTreeRoot, ScanSummary},
|
||||||
|
@ -88,6 +89,7 @@ pub mod orchard;
|
||||||
pub mod pool;
|
pub mod pool;
|
||||||
pub mod sapling;
|
pub mod sapling;
|
||||||
|
|
||||||
|
/// Information about a transaction that the wallet is interested in.
|
||||||
pub struct TransactionSummary<AccountId> {
|
pub struct TransactionSummary<AccountId> {
|
||||||
account_id: AccountId,
|
account_id: AccountId,
|
||||||
txid: TxId,
|
txid: TxId,
|
||||||
|
@ -105,8 +107,12 @@ pub struct TransactionSummary<AccountId> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<AccountId> TransactionSummary<AccountId> {
|
impl<AccountId> TransactionSummary<AccountId> {
|
||||||
|
/// Constructs a `TransactionSummary` from its parts.
|
||||||
|
///
|
||||||
|
/// See the documentation for each getter method below to determine how each method
|
||||||
|
/// argument should be prepared.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn from_parts(
|
||||||
account_id: AccountId,
|
account_id: AccountId,
|
||||||
txid: TxId,
|
txid: TxId,
|
||||||
expiry_height: Option<BlockHeight>,
|
expiry_height: Option<BlockHeight>,
|
||||||
|
@ -138,59 +144,97 @@ impl<AccountId> TransactionSummary<AccountId> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the wallet-internal ID for the account that this transaction was received
|
||||||
|
/// by or sent from.
|
||||||
pub fn account_id(&self) -> &AccountId {
|
pub fn account_id(&self) -> &AccountId {
|
||||||
&self.account_id
|
&self.account_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the transaction's ID.
|
||||||
pub fn txid(&self) -> TxId {
|
pub fn txid(&self) -> TxId {
|
||||||
self.txid
|
self.txid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the expiry height of the transaction, if known.
|
||||||
|
///
|
||||||
|
/// - `None` means that the expiry height is unknown.
|
||||||
|
/// - `Some(0)` means that the transaction does not expire.
|
||||||
pub fn expiry_height(&self) -> Option<BlockHeight> {
|
pub fn expiry_height(&self) -> Option<BlockHeight> {
|
||||||
self.expiry_height
|
self.expiry_height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the height of the mined block containing this transaction, or `None` if
|
||||||
|
/// the wallet has not yet observed the transaction to be mined.
|
||||||
pub fn mined_height(&self) -> Option<BlockHeight> {
|
pub fn mined_height(&self) -> Option<BlockHeight> {
|
||||||
self.mined_height
|
self.mined_height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the net change in balance that this transaction caused to the account.
|
||||||
|
///
|
||||||
|
/// For example, an account-internal transaction (such as a shielding operation) would
|
||||||
|
/// show `-fee_paid` as the account value delta.
|
||||||
pub fn account_value_delta(&self) -> ZatBalance {
|
pub fn account_value_delta(&self) -> ZatBalance {
|
||||||
self.account_value_delta
|
self.account_value_delta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the fee paid by this transaction, if known.
|
||||||
pub fn fee_paid(&self) -> Option<Zatoshis> {
|
pub fn fee_paid(&self) -> Option<Zatoshis> {
|
||||||
self.fee_paid
|
self.fee_paid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the number of notes spent by the account in this transaction.
|
||||||
pub fn spent_note_count(&self) -> usize {
|
pub fn spent_note_count(&self) -> usize {
|
||||||
self.spent_note_count
|
self.spent_note_count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the account received a change note as part of this transaction.
|
||||||
|
///
|
||||||
|
/// This implies that the transaction was (at least in part) sent from the account.
|
||||||
pub fn has_change(&self) -> bool {
|
pub fn has_change(&self) -> bool {
|
||||||
self.has_change
|
self.has_change
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the number of notes created in this transaction that were sent to a
|
||||||
|
/// wallet-external address.
|
||||||
pub fn sent_note_count(&self) -> usize {
|
pub fn sent_note_count(&self) -> usize {
|
||||||
self.sent_note_count
|
self.sent_note_count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the number of notes created in this transaction that were received by the
|
||||||
|
/// account.
|
||||||
pub fn received_note_count(&self) -> usize {
|
pub fn received_note_count(&self) -> usize {
|
||||||
self.received_note_count
|
self.received_note_count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if, from the wallet's current view of the chain, this transaction
|
||||||
|
/// expired before it was mined.
|
||||||
pub fn expired_unmined(&self) -> bool {
|
pub fn expired_unmined(&self) -> bool {
|
||||||
self.expired_unmined
|
self.expired_unmined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the number of non-empty memos viewable by the account in this transaction.
|
||||||
pub fn memo_count(&self) -> usize {
|
pub fn memo_count(&self) -> usize {
|
||||||
self.memo_count
|
self.memo_count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this is detectably a shielding transaction.
|
||||||
|
///
|
||||||
|
/// Specifically, `true` means that at a minimum:
|
||||||
|
/// - All of the wallet-spent and wallet-received notes are consistent with a
|
||||||
|
/// shielding transaction.
|
||||||
|
/// - The transaction contains at least one wallet-spent output.
|
||||||
|
/// - The transaction contains at least one wallet-received note.
|
||||||
|
/// - We do not know about any external outputs of the transaction.
|
||||||
|
///
|
||||||
|
/// There may be some shielding transactions for which this method returns `false`,
|
||||||
|
/// due to them not being detectable by the wallet as shielding transactions under the
|
||||||
|
/// above metrics.
|
||||||
pub fn is_shielding(&self) -> bool {
|
pub fn is_shielding(&self) -> bool {
|
||||||
self.is_shielding
|
self.is_shielding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Metadata about a block generated by [`TestState`].
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct CachedBlock {
|
pub struct CachedBlock {
|
||||||
chain_state: ChainState,
|
chain_state: ChainState,
|
||||||
|
@ -199,14 +243,20 @@ pub struct CachedBlock {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CachedBlock {
|
impl CachedBlock {
|
||||||
pub fn none(sapling_activation_height: BlockHeight) -> Self {
|
/// Produces metadata for a block "before shielded time", when the Sapling and Orchard
|
||||||
|
/// trees were (by definition) empty.
|
||||||
|
///
|
||||||
|
/// `block_height` must be a height before Sapling activation (and therefore also
|
||||||
|
/// before NU5 activation).
|
||||||
|
pub fn none(block_height: BlockHeight) -> Self {
|
||||||
Self {
|
Self {
|
||||||
chain_state: ChainState::empty(sapling_activation_height, BlockHash([0; 32])),
|
chain_state: ChainState::empty(block_height, BlockHash([0; 32])),
|
||||||
sapling_end_size: 0,
|
sapling_end_size: 0,
|
||||||
orchard_end_size: 0,
|
orchard_end_size: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Produces metadata for a block as of the given chain state.
|
||||||
pub fn at(chain_state: ChainState, sapling_end_size: u32, orchard_end_size: u32) -> Self {
|
pub fn at(chain_state: ChainState, sapling_end_size: u32, orchard_end_size: u32) -> Self {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
chain_state.final_sapling_tree().tree_size() as u32,
|
chain_state.final_sapling_tree().tree_size() as u32,
|
||||||
|
@ -265,19 +315,27 @@ impl CachedBlock {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the height of this block.
|
||||||
pub fn height(&self) -> BlockHeight {
|
pub fn height(&self) -> BlockHeight {
|
||||||
self.chain_state.block_height()
|
self.chain_state.block_height()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the size of the Sapling note commitment tree as of the end of this block.
|
||||||
pub fn sapling_end_size(&self) -> u32 {
|
pub fn sapling_end_size(&self) -> u32 {
|
||||||
self.sapling_end_size
|
self.sapling_end_size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the size of the Orchard note commitment tree as of the end of this block.
|
||||||
pub fn orchard_end_size(&self) -> u32 {
|
pub fn orchard_end_size(&self) -> u32 {
|
||||||
self.orchard_end_size
|
self.orchard_end_size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The test account configured for a [`TestState`].
|
||||||
|
///
|
||||||
|
/// Create this by calling either [`TestBuilder::with_account_from_sapling_activation`] or
|
||||||
|
/// [`TestBuilder::with_account_having_current_birthday`] while setting up a test, and
|
||||||
|
/// then access it with [`TestState::test_account`].
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TestAccount<A> {
|
pub struct TestAccount<A> {
|
||||||
account: A,
|
account: A,
|
||||||
|
@ -286,14 +344,17 @@ pub struct TestAccount<A> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A> TestAccount<A> {
|
impl<A> TestAccount<A> {
|
||||||
|
/// Returns the underlying wallet account.
|
||||||
pub fn account(&self) -> &A {
|
pub fn account(&self) -> &A {
|
||||||
&self.account
|
&self.account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the account's unified spending key.
|
||||||
pub fn usk(&self) -> &UnifiedSpendingKey {
|
pub fn usk(&self) -> &UnifiedSpendingKey {
|
||||||
&self.usk
|
&self.usk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the birthday that was configured for the account.
|
||||||
pub fn birthday(&self) -> &AccountBirthday {
|
pub fn birthday(&self) -> &AccountBirthday {
|
||||||
&self.birthday
|
&self.birthday
|
||||||
}
|
}
|
||||||
|
@ -319,14 +380,23 @@ impl<A: Account> Account for TestAccount<A> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Reset: WalletRead + Sized {
|
/// Trait method exposing the ability to reset the wallet within a test.
|
||||||
|
// TODO: Does this need to exist separately from DataStoreFactory?
|
||||||
|
pub trait Reset: WalletTest + Sized {
|
||||||
|
/// A handle that confers ownership of a specific wallet instance.
|
||||||
type Handle;
|
type Handle;
|
||||||
|
|
||||||
|
/// Replaces the wallet in `st` (via [`TestState::wallet_mut`]) with a new wallet
|
||||||
|
/// database.
|
||||||
|
///
|
||||||
|
/// This does not recreate accounts. The resulting wallet in `st` has no test account.
|
||||||
|
///
|
||||||
|
/// Returns the old wallet.
|
||||||
fn reset<C>(st: &mut TestState<C, Self, LocalNetwork>) -> Self::Handle;
|
fn reset<C>(st: &mut TestState<C, Self, LocalNetwork>) -> Self::Handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The state for a `zcash_client_sqlite` test.
|
/// The state for a `zcash_client_backend` test.
|
||||||
pub struct TestState<Cache, DataStore: WalletRead, Network> {
|
pub struct TestState<Cache, DataStore: WalletTest, Network> {
|
||||||
cache: Cache,
|
cache: Cache,
|
||||||
cached_blocks: BTreeMap<BlockHeight, CachedBlock>,
|
cached_blocks: BTreeMap<BlockHeight, CachedBlock>,
|
||||||
latest_block_height: Option<BlockHeight>,
|
latest_block_height: Option<BlockHeight>,
|
||||||
|
@ -336,7 +406,7 @@ pub struct TestState<Cache, DataStore: WalletRead, Network> {
|
||||||
rng: ChaChaRng,
|
rng: ChaChaRng,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Cache, DataStore: WalletRead, Network> TestState<Cache, DataStore, Network> {
|
impl<Cache, DataStore: WalletTest, Network> TestState<Cache, DataStore, Network> {
|
||||||
/// Exposes an immutable reference to the test's `DataStore`.
|
/// Exposes an immutable reference to the test's `DataStore`.
|
||||||
pub fn wallet(&self) -> &DataStore {
|
pub fn wallet(&self) -> &DataStore {
|
||||||
&self.wallet_data
|
&self.wallet_data
|
||||||
|
@ -358,7 +428,7 @@ impl<Cache, DataStore: WalletRead, Network> TestState<Cache, DataStore, Network>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Cache, DataStore: WalletRead, Network: consensus::Parameters>
|
impl<Cache, DataStore: WalletTest, Network: consensus::Parameters>
|
||||||
TestState<Cache, DataStore, Network>
|
TestState<Cache, DataStore, Network>
|
||||||
{
|
{
|
||||||
/// Convenience method for obtaining the Sapling activation height for the network under test.
|
/// Convenience method for obtaining the Sapling activation height for the network under test.
|
||||||
|
@ -405,7 +475,7 @@ impl<Cache, DataStore: WalletRead, Network: consensus::Parameters>
|
||||||
impl<Cache: TestCache, DataStore, Network> TestState<Cache, DataStore, Network>
|
impl<Cache: TestCache, DataStore, Network> TestState<Cache, DataStore, Network>
|
||||||
where
|
where
|
||||||
Network: consensus::Parameters,
|
Network: consensus::Parameters,
|
||||||
DataStore: WalletWrite,
|
DataStore: WalletTest + WalletWrite,
|
||||||
<Cache::BlockSource as BlockSource>::Error: fmt::Debug,
|
<Cache::BlockSource as BlockSource>::Error: fmt::Debug,
|
||||||
{
|
{
|
||||||
/// Exposes an immutable reference to the test's [`BlockSource`].
|
/// Exposes an immutable reference to the test's [`BlockSource`].
|
||||||
|
@ -414,6 +484,8 @@ where
|
||||||
self.cache.block_source()
|
self.cache.block_source()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the cached chain state corresponding to the latest block generated by this
|
||||||
|
/// `TestState`.
|
||||||
pub fn latest_cached_block(&self) -> Option<&CachedBlock> {
|
pub fn latest_cached_block(&self) -> Option<&CachedBlock> {
|
||||||
self.latest_block_height
|
self.latest_block_height
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -697,7 +769,7 @@ where
|
||||||
Cache: TestCache,
|
Cache: TestCache,
|
||||||
<Cache::BlockSource as BlockSource>::Error: fmt::Debug,
|
<Cache::BlockSource as BlockSource>::Error: fmt::Debug,
|
||||||
ParamsT: consensus::Parameters + Send + 'static,
|
ParamsT: consensus::Parameters + Send + 'static,
|
||||||
DbT: InputSource + WalletWrite + WalletCommitmentTrees,
|
DbT: InputSource + WalletTest + WalletWrite + WalletCommitmentTrees,
|
||||||
<DbT as WalletRead>::AccountId: ConditionallySelectable + Default + Send + 'static,
|
<DbT as WalletRead>::AccountId: ConditionallySelectable + Default + Send + 'static,
|
||||||
{
|
{
|
||||||
/// Invokes [`scan_cached_blocks`] with the given arguments, expecting success.
|
/// Invokes [`scan_cached_blocks`] with the given arguments, expecting success.
|
||||||
|
@ -760,6 +832,7 @@ where
|
||||||
AccountIdT: std::cmp::Eq + std::hash::Hash,
|
AccountIdT: std::cmp::Eq + std::hash::Hash,
|
||||||
ErrT: std::fmt::Debug,
|
ErrT: std::fmt::Debug,
|
||||||
DbT: InputSource<AccountId = AccountIdT, Error = ErrT>
|
DbT: InputSource<AccountId = AccountIdT, Error = ErrT>
|
||||||
|
+ WalletTest
|
||||||
+ WalletWrite<AccountId = AccountIdT, Error = ErrT>
|
+ WalletWrite<AccountId = AccountIdT, Error = ErrT>
|
||||||
+ WalletCommitmentTrees,
|
+ WalletCommitmentTrees,
|
||||||
<DbT as WalletRead>::AccountId: ConditionallySelectable + Default + Send + 'static,
|
<DbT as WalletRead>::AccountId: ConditionallySelectable + Default + Send + 'static,
|
||||||
|
@ -1030,10 +1103,13 @@ where
|
||||||
f(binding.account_balances().get(&account).unwrap())
|
f(binding.account_balances().get(&account).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the total balance in the given account at this point in the test.
|
||||||
pub fn get_total_balance(&self, account: AccountIdT) -> NonNegativeAmount {
|
pub fn get_total_balance(&self, account: AccountIdT) -> NonNegativeAmount {
|
||||||
self.with_account_balance(account, 0, |balance| balance.total())
|
self.with_account_balance(account, 0, |balance| balance.total())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the balance in the given account that is spendable with the given number
|
||||||
|
/// of confirmations at this point in the test.
|
||||||
pub fn get_spendable_balance(
|
pub fn get_spendable_balance(
|
||||||
&self,
|
&self,
|
||||||
account: AccountIdT,
|
account: AccountIdT,
|
||||||
|
@ -1044,6 +1120,8 @@ where
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the balance in the given account that is detected but not yet spendable
|
||||||
|
/// with the given number of confirmations at this point in the test.
|
||||||
pub fn get_pending_shielded_balance(
|
pub fn get_pending_shielded_balance(
|
||||||
&self,
|
&self,
|
||||||
account: AccountIdT,
|
account: AccountIdT,
|
||||||
|
@ -1055,6 +1133,8 @@ where
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the amount of change in the given account that is not yet spendable with
|
||||||
|
/// the given number of confirmations at this point in the test.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn get_pending_change(
|
pub fn get_pending_change(
|
||||||
&self,
|
&self,
|
||||||
|
@ -1066,10 +1146,23 @@ where
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a summary of the wallet at this point in the test.
|
||||||
pub fn get_wallet_summary(&self, min_confirmations: u32) -> Option<WalletSummary<AccountIdT>> {
|
pub fn get_wallet_summary(&self, min_confirmations: u32) -> Option<WalletSummary<AccountIdT>> {
|
||||||
self.wallet().get_wallet_summary(min_confirmations).unwrap()
|
self.wallet().get_wallet_summary(min_confirmations).unwrap()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Cache, DbT, ParamsT, AccountIdT, ErrT> TestState<Cache, DbT, ParamsT>
|
||||||
|
where
|
||||||
|
ParamsT: consensus::Parameters + Send + 'static,
|
||||||
|
AccountIdT: std::cmp::Eq + std::hash::Hash,
|
||||||
|
ErrT: std::fmt::Debug,
|
||||||
|
DbT: InputSource<AccountId = AccountIdT, Error = ErrT>
|
||||||
|
+ WalletTest
|
||||||
|
+ WalletWrite<AccountId = AccountIdT, Error = ErrT>
|
||||||
|
+ WalletCommitmentTrees,
|
||||||
|
<DbT as WalletRead>::AccountId: ConditionallySelectable + Default + Send + 'static,
|
||||||
|
{
|
||||||
/// Returns a transaction from the history.
|
/// Returns a transaction from the history.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn get_tx_from_history(
|
pub fn get_tx_from_history(
|
||||||
|
@ -1113,6 +1206,8 @@ impl<Cache, DbT: WalletRead + Reset> TestState<Cache, DbT, LocalNetwork> {
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper method for constructing a [`GreedyInputSelector`] with a
|
||||||
|
/// [`standard::SingleOutputChangeStrategy`].
|
||||||
pub fn input_selector<DbT: InputSource>(
|
pub fn input_selector<DbT: InputSource>(
|
||||||
fee_rule: StandardFeeRule,
|
fee_rule: StandardFeeRule,
|
||||||
change_memo: Option<&str>,
|
change_memo: Option<&str>,
|
||||||
|
@ -1135,13 +1230,22 @@ fn check_proposal_serialization_roundtrip<DbT: InputSource>(
|
||||||
assert_matches!(deserialized_proposal, Ok(r) if &r == proposal);
|
assert_matches!(deserialized_proposal, Ok(r) if &r == proposal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The initial chain state for a test.
|
||||||
|
///
|
||||||
|
/// This is returned from the closure passed to [`TestBuilder::with_initial_chain_state`]
|
||||||
|
/// to configure the test state with a starting chain position, to which subsequent test
|
||||||
|
/// activity is applied.
|
||||||
pub struct InitialChainState {
|
pub struct InitialChainState {
|
||||||
|
/// Information about the chain's state as of the chain tip.
|
||||||
pub chain_state: ChainState,
|
pub chain_state: ChainState,
|
||||||
|
/// Roots of the completed Sapling subtrees as of this chain state.
|
||||||
pub prior_sapling_roots: Vec<CommitmentTreeRoot<::sapling::Node>>,
|
pub prior_sapling_roots: Vec<CommitmentTreeRoot<::sapling::Node>>,
|
||||||
|
/// Roots of the completed Orchard subtrees as of this chain state.
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
pub prior_orchard_roots: Vec<CommitmentTreeRoot<MerkleHashOrchard>>,
|
pub prior_orchard_roots: Vec<CommitmentTreeRoot<MerkleHashOrchard>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trait representing the ability to construct a new data store for use in a test.
|
||||||
pub trait DataStoreFactory {
|
pub trait DataStoreFactory {
|
||||||
type Error: core::fmt::Debug;
|
type Error: core::fmt::Debug;
|
||||||
type AccountId: ConditionallySelectable + Default + Hash + Eq + Send + 'static;
|
type AccountId: ConditionallySelectable + Default + Hash + Eq + Send + 'static;
|
||||||
|
@ -1149,13 +1253,15 @@ pub trait DataStoreFactory {
|
||||||
type DsError: core::fmt::Debug;
|
type DsError: core::fmt::Debug;
|
||||||
type DataStore: InputSource<AccountId = Self::AccountId, Error = Self::DsError>
|
type DataStore: InputSource<AccountId = Self::AccountId, Error = Self::DsError>
|
||||||
+ WalletRead<AccountId = Self::AccountId, Account = Self::Account, Error = Self::DsError>
|
+ WalletRead<AccountId = Self::AccountId, Account = Self::Account, Error = Self::DsError>
|
||||||
|
+ WalletTest
|
||||||
+ WalletWrite
|
+ WalletWrite
|
||||||
+ WalletCommitmentTrees;
|
+ WalletCommitmentTrees;
|
||||||
|
|
||||||
|
/// Constructs a new data store.
|
||||||
fn new_data_store(&self, network: LocalNetwork) -> Result<Self::DataStore, Self::Error>;
|
fn new_data_store(&self, network: LocalNetwork) -> Result<Self::DataStore, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A builder for a `zcash_client_sqlite` test.
|
/// A [`TestState`] builder, that configures the environment for a test.
|
||||||
pub struct TestBuilder<Cache, DataStoreFactory> {
|
pub struct TestBuilder<Cache, DataStoreFactory> {
|
||||||
rng: ChaChaRng,
|
rng: ChaChaRng,
|
||||||
network: LocalNetwork,
|
network: LocalNetwork,
|
||||||
|
@ -1167,6 +1273,10 @@ pub struct TestBuilder<Cache, DataStoreFactory> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestBuilder<(), ()> {
|
impl TestBuilder<(), ()> {
|
||||||
|
/// The default network used by [`TestBuilder::new`].
|
||||||
|
///
|
||||||
|
/// This is a fake network where Sapling through NU5 activate at the same height. We
|
||||||
|
/// pick height 100,000 to be large enough to handle any hard-coded test offsets.
|
||||||
pub const DEFAULT_NETWORK: LocalNetwork = LocalNetwork {
|
pub const DEFAULT_NETWORK: LocalNetwork = LocalNetwork {
|
||||||
overwinter: Some(BlockHeight::from_u32(1)),
|
overwinter: Some(BlockHeight::from_u32(1)),
|
||||||
sapling: Some(BlockHeight::from_u32(100_000)),
|
sapling: Some(BlockHeight::from_u32(100_000)),
|
||||||
|
@ -1183,8 +1293,6 @@ impl TestBuilder<(), ()> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
TestBuilder {
|
TestBuilder {
|
||||||
rng: ChaChaRng::seed_from_u64(0),
|
rng: ChaChaRng::seed_from_u64(0),
|
||||||
// Use a fake network where Sapling through NU5 activate at the same height.
|
|
||||||
// We pick 100,000 to be large enough to handle any hard-coded test offsets.
|
|
||||||
network: Self::DEFAULT_NETWORK,
|
network: Self::DEFAULT_NETWORK,
|
||||||
cache: (),
|
cache: (),
|
||||||
ds_factory: (),
|
ds_factory: (),
|
||||||
|
@ -1217,6 +1325,7 @@ impl<A> TestBuilder<(), A> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A> TestBuilder<A, ()> {
|
impl<A> TestBuilder<A, ()> {
|
||||||
|
/// Adds a wallet data store to the test environment.
|
||||||
pub fn with_data_store_factory<DsFactory>(
|
pub fn with_data_store_factory<DsFactory>(
|
||||||
self,
|
self,
|
||||||
ds_factory: DsFactory,
|
ds_factory: DsFactory,
|
||||||
|
@ -1234,6 +1343,88 @@ impl<A> TestBuilder<A, ()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Cache, DsFactory> TestBuilder<Cache, DsFactory> {
|
impl<Cache, DsFactory> TestBuilder<Cache, DsFactory> {
|
||||||
|
/// Configures the test to start with the given initial chain state.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// - Must not be called twice.
|
||||||
|
/// - Must be called before [`Self::with_account_from_sapling_activation`] or
|
||||||
|
/// [`Self::with_account_having_current_birthday`].
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use std::num::NonZeroU8;
|
||||||
|
///
|
||||||
|
/// use incrementalmerkletree::frontier::Frontier;
|
||||||
|
/// use zcash_primitives::{block::BlockHash, consensus::Parameters};
|
||||||
|
/// use zcash_protocol::consensus::NetworkUpgrade;
|
||||||
|
/// use zcash_client_backend::data_api::{
|
||||||
|
/// chain::{ChainState, CommitmentTreeRoot},
|
||||||
|
/// testing::{InitialChainState, TestBuilder},
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// // For this test, we'll start inserting leaf notes 5 notes after the end of the
|
||||||
|
/// // third subtree, with a gap of 10 blocks. After `scan_cached_blocks`, the scan
|
||||||
|
/// // queue should have a requested scan range of 300..310 with `FoundNote` priority,
|
||||||
|
/// // 310..320 with `Scanned` priority. We set both Sapling and Orchard to the same
|
||||||
|
/// // initial tree size for simplicity.
|
||||||
|
/// let prior_block_hash = BlockHash([0; 32]);
|
||||||
|
/// let initial_sapling_tree_size: u32 = (0x1 << 16) * 3 + 5;
|
||||||
|
/// let initial_orchard_tree_size: u32 = (0x1 << 16) * 3 + 5;
|
||||||
|
/// let initial_height_offset = 310;
|
||||||
|
///
|
||||||
|
/// let mut st = TestBuilder::new()
|
||||||
|
/// .with_initial_chain_state(|rng, network| {
|
||||||
|
/// // For simplicity, assume Sapling and NU5 activated at the same height.
|
||||||
|
/// let sapling_activation_height =
|
||||||
|
/// network.activation_height(NetworkUpgrade::Sapling).unwrap();
|
||||||
|
///
|
||||||
|
/// // Construct a fake chain state for the end of block 300
|
||||||
|
/// let (prior_sapling_roots, sapling_initial_tree) =
|
||||||
|
/// Frontier::random_with_prior_subtree_roots(
|
||||||
|
/// rng,
|
||||||
|
/// initial_sapling_tree_size.into(),
|
||||||
|
/// NonZeroU8::new(16).unwrap(),
|
||||||
|
/// );
|
||||||
|
/// let prior_sapling_roots = prior_sapling_roots
|
||||||
|
/// .into_iter()
|
||||||
|
/// .zip(1u32..)
|
||||||
|
/// .map(|(root, i)| {
|
||||||
|
/// CommitmentTreeRoot::from_parts(sapling_activation_height + (100 * i), root)
|
||||||
|
/// })
|
||||||
|
/// .collect::<Vec<_>>();
|
||||||
|
///
|
||||||
|
/// #[cfg(feature = "orchard")]
|
||||||
|
/// let (prior_orchard_roots, orchard_initial_tree) =
|
||||||
|
/// Frontier::random_with_prior_subtree_roots(
|
||||||
|
/// rng,
|
||||||
|
/// initial_orchard_tree_size.into(),
|
||||||
|
/// NonZeroU8::new(16).unwrap(),
|
||||||
|
/// );
|
||||||
|
/// #[cfg(feature = "orchard")]
|
||||||
|
/// let prior_orchard_roots = prior_orchard_roots
|
||||||
|
/// .into_iter()
|
||||||
|
/// .zip(1u32..)
|
||||||
|
/// .map(|(root, i)| {
|
||||||
|
/// CommitmentTreeRoot::from_parts(sapling_activation_height + (100 * i), root)
|
||||||
|
/// })
|
||||||
|
/// .collect::<Vec<_>>();
|
||||||
|
///
|
||||||
|
/// InitialChainState {
|
||||||
|
/// chain_state: ChainState::new(
|
||||||
|
/// sapling_activation_height + initial_height_offset - 1,
|
||||||
|
/// prior_block_hash,
|
||||||
|
/// sapling_initial_tree,
|
||||||
|
/// #[cfg(feature = "orchard")]
|
||||||
|
/// orchard_initial_tree,
|
||||||
|
/// ),
|
||||||
|
/// prior_sapling_roots,
|
||||||
|
/// #[cfg(feature = "orchard")]
|
||||||
|
/// prior_orchard_roots,
|
||||||
|
/// }
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
pub fn with_initial_chain_state(
|
pub fn with_initial_chain_state(
|
||||||
mut self,
|
mut self,
|
||||||
chain_state: impl FnOnce(&mut ChaChaRng, &LocalNetwork) -> InitialChainState,
|
chain_state: impl FnOnce(&mut ChaChaRng, &LocalNetwork) -> InitialChainState,
|
||||||
|
@ -1244,6 +1435,13 @@ impl<Cache, DsFactory> TestBuilder<Cache, DsFactory> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configures the environment with a [`TestAccount`] that has a birthday at Sapling
|
||||||
|
/// activation.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// - Must not be called twice.
|
||||||
|
/// - Do not call both [`Self::with_account_having_current_birthday`] and this method.
|
||||||
pub fn with_account_from_sapling_activation(mut self, prev_hash: BlockHash) -> Self {
|
pub fn with_account_from_sapling_activation(mut self, prev_hash: BlockHash) -> Self {
|
||||||
assert!(self.account_birthday.is_none());
|
assert!(self.account_birthday.is_none());
|
||||||
self.account_birthday = Some(AccountBirthday::from_parts(
|
self.account_birthday = Some(AccountBirthday::from_parts(
|
||||||
|
@ -1259,6 +1457,14 @@ impl<Cache, DsFactory> TestBuilder<Cache, DsFactory> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configures the environment with a [`TestAccount`] that has a birthday one block
|
||||||
|
/// after the initial chain state.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// - Must not be called twice.
|
||||||
|
/// - Must call [`Self::with_initial_chain_state`] before calling this method.
|
||||||
|
/// - Do not call both [`Self::with_account_from_sapling_activation`] and this method.
|
||||||
pub fn with_account_having_current_birthday(mut self) -> Self {
|
pub fn with_account_having_current_birthday(mut self) -> Self {
|
||||||
assert!(self.account_birthday.is_none());
|
assert!(self.account_birthday.is_none());
|
||||||
assert!(self.initial_chain_state.is_some());
|
assert!(self.initial_chain_state.is_some());
|
||||||
|
@ -1275,8 +1481,12 @@ impl<Cache, DsFactory> TestBuilder<Cache, DsFactory> {
|
||||||
|
|
||||||
/// Sets the account index for the test account.
|
/// Sets the account index for the test account.
|
||||||
///
|
///
|
||||||
/// Call either [`Self::with_account_from_sapling_activation`] or
|
/// Does nothing unless either [`Self::with_account_from_sapling_activation`] or
|
||||||
/// [`Self::with_account_having_current_birthday`] before calling this method.
|
/// [`Self::with_account_having_current_birthday`] is also called.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// - Must not be called twice.
|
||||||
pub fn set_account_index(mut self, index: zip32::AccountId) -> Self {
|
pub fn set_account_index(mut self, index: zip32::AccountId) -> Self {
|
||||||
assert!(self.account_index.is_none());
|
assert!(self.account_index.is_none());
|
||||||
self.account_index = Some(index);
|
self.account_index = Some(index);
|
||||||
|
@ -1381,13 +1591,21 @@ impl<Cache, DsFactory: DataStoreFactory> TestBuilder<Cache, DsFactory> {
|
||||||
|
|
||||||
/// Trait used by tests that require a full viewing key.
|
/// Trait used by tests that require a full viewing key.
|
||||||
pub trait TestFvk {
|
pub trait TestFvk {
|
||||||
|
/// The type of nullifier corresponding to the kind of note that this full viewing key
|
||||||
|
/// can detect (and that its corresponding spending key can spend).
|
||||||
type Nullifier: Copy;
|
type Nullifier: Copy;
|
||||||
|
|
||||||
|
/// Returns the Sapling outgoing viewing key corresponding to this full viewing key,
|
||||||
|
/// if any.
|
||||||
fn sapling_ovk(&self) -> Option<::sapling::keys::OutgoingViewingKey>;
|
fn sapling_ovk(&self) -> Option<::sapling::keys::OutgoingViewingKey>;
|
||||||
|
|
||||||
|
/// Returns the Orchard outgoing viewing key corresponding to this full viewing key,
|
||||||
|
/// if any.
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
fn orchard_ovk(&self, scope: zip32::Scope) -> Option<::orchard::keys::OutgoingViewingKey>;
|
fn orchard_ovk(&self, scope: zip32::Scope) -> Option<::orchard::keys::OutgoingViewingKey>;
|
||||||
|
|
||||||
|
/// Adds a single spend to the given [`CompactTx`] of a note previously received by
|
||||||
|
/// this full viewing key.
|
||||||
fn add_spend<R: RngCore + CryptoRng>(
|
fn add_spend<R: RngCore + CryptoRng>(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut CompactTx,
|
ctx: &mut CompactTx,
|
||||||
|
@ -1395,6 +1613,10 @@ pub trait TestFvk {
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Adds a single output to the given [`CompactTx`] that will be received by this full
|
||||||
|
/// viewing key.
|
||||||
|
///
|
||||||
|
/// `req` allows configuring how the full viewing key will detect the output.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn add_output<P: consensus::Parameters, R: RngCore + CryptoRng>(
|
fn add_output<P: consensus::Parameters, R: RngCore + CryptoRng>(
|
||||||
&self,
|
&self,
|
||||||
|
@ -1409,6 +1631,13 @@ pub trait TestFvk {
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
) -> Self::Nullifier;
|
) -> Self::Nullifier;
|
||||||
|
|
||||||
|
/// Adds both a spend and an output to the given [`CompactTx`].
|
||||||
|
///
|
||||||
|
/// - If this is a Sapling full viewing key, the transaction will gain both a Spend
|
||||||
|
/// and an Output.
|
||||||
|
/// - If this is an Orchard full viewing key, the transaction will gain an Action.
|
||||||
|
///
|
||||||
|
/// `req` allows configuring how the full viewing key will detect the output.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn add_logical_action<P: consensus::Parameters, R: RngCore + CryptoRng>(
|
fn add_logical_action<P: consensus::Parameters, R: RngCore + CryptoRng>(
|
||||||
&self,
|
&self,
|
||||||
|
@ -1671,11 +1900,21 @@ impl TestFvk for ::orchard::keys::FullViewingKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configures how a [`TestFvk`] receives a particular output.
|
||||||
|
///
|
||||||
|
/// Used with [`TestFvk::add_output`] and [`TestFvk::add_logical_action`].
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub enum AddressType {
|
pub enum AddressType {
|
||||||
|
/// The output will be sent to the default address of the full viewing key.
|
||||||
DefaultExternal,
|
DefaultExternal,
|
||||||
|
/// The output will be sent to the specified diversified address of the full viewing
|
||||||
|
/// key.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
DiversifiedExternal(DiversifierIndex),
|
DiversifiedExternal(DiversifierIndex),
|
||||||
|
/// The output will be sent to the internal receiver of the full viewing key.
|
||||||
|
///
|
||||||
|
/// Such outputs are treated as "wallet-internal". A "recipient address" is **NEVER**
|
||||||
|
/// exposed to users.
|
||||||
Internal,
|
Internal,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1753,6 +1992,11 @@ fn fake_compact_tx<R: RngCore + CryptoRng>(rng: &mut R) -> CompactTx {
|
||||||
ctx
|
ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A fake output of a [`CompactTx`].
|
||||||
|
///
|
||||||
|
/// Used with the following block generators:
|
||||||
|
/// - [`TestState::generate_next_block_multi`]
|
||||||
|
/// - [`TestState::generate_block_at`]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FakeCompactOutput<Fvk> {
|
pub struct FakeCompactOutput<Fvk> {
|
||||||
fvk: Fvk,
|
fvk: Fvk,
|
||||||
|
@ -1761,6 +2005,7 @@ pub struct FakeCompactOutput<Fvk> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Fvk> FakeCompactOutput<Fvk> {
|
impl<Fvk> FakeCompactOutput<Fvk> {
|
||||||
|
/// Constructs a new fake output with the given properties.
|
||||||
pub fn new(fvk: Fvk, address_type: AddressType, value: NonNegativeAmount) -> Self {
|
pub fn new(fvk: Fvk, address_type: AddressType, value: NonNegativeAmount) -> Self {
|
||||||
Self {
|
Self {
|
||||||
fvk,
|
fvk,
|
||||||
|
@ -1998,6 +2243,9 @@ pub trait TestCache {
|
||||||
fn insert(&mut self, cb: &CompactBlock) -> Self::InsertResult;
|
fn insert(&mut self, cb: &CompactBlock) -> Self::InsertResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A convenience type for the note commitments contained within a [`CompactBlock`].
|
||||||
|
///
|
||||||
|
/// Indended for use as (part of) the [`TestCache::InsertResult`] associated type.
|
||||||
pub struct NoteCommitments {
|
pub struct NoteCommitments {
|
||||||
sapling: Vec<::sapling::Node>,
|
sapling: Vec<::sapling::Node>,
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
|
@ -2005,6 +2253,7 @@ pub struct NoteCommitments {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NoteCommitments {
|
impl NoteCommitments {
|
||||||
|
/// Extracts the note commitments from the given compact block.
|
||||||
pub fn from_compact_block(cb: &CompactBlock) -> Self {
|
pub fn from_compact_block(cb: &CompactBlock) -> Self {
|
||||||
NoteCommitments {
|
NoteCommitments {
|
||||||
sapling: cb
|
sapling: cb
|
||||||
|
@ -2029,17 +2278,20 @@ impl NoteCommitments {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the Sapling note commitments.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn sapling(&self) -> &[::sapling::Node] {
|
pub fn sapling(&self) -> &[::sapling::Node] {
|
||||||
self.sapling.as_ref()
|
self.sapling.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the Orchard note commitments.
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
pub fn orchard(&self) -> &[MerkleHashOrchard] {
|
pub fn orchard(&self) -> &[MerkleHashOrchard] {
|
||||||
self.orchard.as_ref()
|
self.orchard.as_ref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A mock wallet data source that implements the bare minimum necessary to function.
|
||||||
pub struct MockWalletDb {
|
pub struct MockWalletDb {
|
||||||
pub network: Network,
|
pub network: Network,
|
||||||
pub sapling_tree: ShardTree<
|
pub sapling_tree: ShardTree<
|
||||||
|
@ -2056,6 +2308,7 @@ pub struct MockWalletDb {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MockWalletDb {
|
impl MockWalletDb {
|
||||||
|
/// Constructs a new mock wallet data source.
|
||||||
pub fn new(network: Network) -> Self {
|
pub fn new(network: Network) -> Self {
|
||||||
Self {
|
Self {
|
||||||
network,
|
network,
|
||||||
|
|
|
@ -25,11 +25,12 @@ use crate::{
|
||||||
data_api::{
|
data_api::{
|
||||||
chain::{CommitmentTreeRoot, ScanSummary},
|
chain::{CommitmentTreeRoot, ScanSummary},
|
||||||
testing::{pool::ShieldedPoolTester, TestState},
|
testing::{pool::ShieldedPoolTester, TestState},
|
||||||
DecryptedTransaction, InputSource, WalletCommitmentTrees, WalletRead, WalletSummary,
|
DecryptedTransaction, InputSource, WalletCommitmentTrees, WalletSummary, WalletTest,
|
||||||
},
|
},
|
||||||
wallet::{Note, ReceivedNote},
|
wallet::{Note, ReceivedNote},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Type for running pool-agnostic tests on the Orchard pool.
|
||||||
pub struct OrchardPoolTester;
|
pub struct OrchardPoolTester;
|
||||||
impl ShieldedPoolTester for OrchardPoolTester {
|
impl ShieldedPoolTester for OrchardPoolTester {
|
||||||
const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Orchard;
|
const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Orchard;
|
||||||
|
@ -40,7 +41,7 @@ impl ShieldedPoolTester for OrchardPoolTester {
|
||||||
type MerkleTreeHash = MerkleHashOrchard;
|
type MerkleTreeHash = MerkleHashOrchard;
|
||||||
type Note = orchard::note::Note;
|
type Note = orchard::note::Note;
|
||||||
|
|
||||||
fn test_account_fvk<Cache, DbT: WalletRead, P: consensus::Parameters>(
|
fn test_account_fvk<Cache, DbT: WalletTest, P: consensus::Parameters>(
|
||||||
st: &TestState<Cache, DbT, P>,
|
st: &TestState<Cache, DbT, P>,
|
||||||
) -> Self::Fvk {
|
) -> Self::Fvk {
|
||||||
st.test_account_orchard().unwrap().clone()
|
st.test_account_orchard().unwrap().clone()
|
||||||
|
@ -90,7 +91,7 @@ impl ShieldedPoolTester for OrchardPoolTester {
|
||||||
MerkleHashOrchard::empty_root(level)
|
MerkleHashOrchard::empty_root(level)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn put_subtree_roots<Cache, DbT: WalletRead + WalletCommitmentTrees, P>(
|
fn put_subtree_roots<Cache, DbT: WalletTest + WalletCommitmentTrees, P>(
|
||||||
st: &mut TestState<Cache, DbT, P>,
|
st: &mut TestState<Cache, DbT, P>,
|
||||||
start_index: u64,
|
start_index: u64,
|
||||||
roots: &[CommitmentTreeRoot<Self::MerkleTreeHash>],
|
roots: &[CommitmentTreeRoot<Self::MerkleTreeHash>],
|
||||||
|
@ -103,7 +104,7 @@ impl ShieldedPoolTester for OrchardPoolTester {
|
||||||
s.next_orchard_subtree_index()
|
s.next_orchard_subtree_index()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_spendable_notes<Cache, DbT: InputSource + WalletRead, P>(
|
fn select_spendable_notes<Cache, DbT: InputSource + WalletTest, P>(
|
||||||
st: &TestState<Cache, DbT, P>,
|
st: &TestState<Cache, DbT, P>,
|
||||||
account: <DbT as InputSource>::AccountId,
|
account: <DbT as InputSource>::AccountId,
|
||||||
target_value: Zatoshis,
|
target_value: Zatoshis,
|
||||||
|
|
|
@ -23,7 +23,7 @@ use crate::{
|
||||||
testing::{AddressType, TestBuilder},
|
testing::{AddressType, TestBuilder},
|
||||||
wallet::{decrypt_and_store_transaction, input_selection::GreedyInputSelector},
|
wallet::{decrypt_and_store_transaction, input_selection::GreedyInputSelector},
|
||||||
Account as _, DecryptedTransaction, InputSource, WalletCommitmentTrees, WalletRead,
|
Account as _, DecryptedTransaction, InputSource, WalletCommitmentTrees, WalletRead,
|
||||||
WalletSummary,
|
WalletSummary, WalletTest,
|
||||||
},
|
},
|
||||||
decrypt_transaction,
|
decrypt_transaction,
|
||||||
fees::{standard, DustOutputPolicy},
|
fees::{standard, DustOutputPolicy},
|
||||||
|
@ -34,6 +34,19 @@ use super::{DataStoreFactory, TestCache, TestFvk, TestState};
|
||||||
|
|
||||||
/// Trait that exposes the pool-specific types and operations necessary to run the
|
/// Trait that exposes the pool-specific types and operations necessary to run the
|
||||||
/// single-shielded-pool tests on a given pool.
|
/// single-shielded-pool tests on a given pool.
|
||||||
|
///
|
||||||
|
/// You should not need to implement this yourself; instead use [`SaplingPoolTester`] or
|
||||||
|
/// [`OrchardPoolTester`] as appropriate.
|
||||||
|
///
|
||||||
|
/// [`SaplingPoolTester`]: super::sapling::SaplingPoolTester
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "orchard",
|
||||||
|
doc = "[`OrchardPoolTester`]: super::orchard::OrchardPoolTester"
|
||||||
|
)]
|
||||||
|
#[cfg_attr(
|
||||||
|
not(feature = "orchard"),
|
||||||
|
doc = "[`OrchardPoolTester`]: https://github.com/zcash/librustzcash/blob/0777cbc2def6ba6b99f96333eaf96c314c1f3a37/zcash_client_backend/src/data_api/testing/orchard.rs#L33"
|
||||||
|
)]
|
||||||
pub trait ShieldedPoolTester {
|
pub trait ShieldedPoolTester {
|
||||||
const SHIELDED_PROTOCOL: ShieldedProtocol;
|
const SHIELDED_PROTOCOL: ShieldedProtocol;
|
||||||
|
|
||||||
|
@ -42,7 +55,7 @@ pub trait ShieldedPoolTester {
|
||||||
type MerkleTreeHash;
|
type MerkleTreeHash;
|
||||||
type Note;
|
type Note;
|
||||||
|
|
||||||
fn test_account_fvk<Cache, DbT: WalletRead, P: consensus::Parameters>(
|
fn test_account_fvk<Cache, DbT: WalletTest, P: consensus::Parameters>(
|
||||||
st: &TestState<Cache, DbT, P>,
|
st: &TestState<Cache, DbT, P>,
|
||||||
) -> Self::Fvk;
|
) -> Self::Fvk;
|
||||||
fn usk_to_sk(usk: &UnifiedSpendingKey) -> &Self::Sk;
|
fn usk_to_sk(usk: &UnifiedSpendingKey) -> &Self::Sk;
|
||||||
|
@ -68,7 +81,7 @@ pub trait ShieldedPoolTester {
|
||||||
fn empty_tree_leaf() -> Self::MerkleTreeHash;
|
fn empty_tree_leaf() -> Self::MerkleTreeHash;
|
||||||
fn empty_tree_root(level: Level) -> Self::MerkleTreeHash;
|
fn empty_tree_root(level: Level) -> Self::MerkleTreeHash;
|
||||||
|
|
||||||
fn put_subtree_roots<Cache, DbT: WalletRead + WalletCommitmentTrees, P>(
|
fn put_subtree_roots<Cache, DbT: WalletTest + WalletCommitmentTrees, P>(
|
||||||
st: &mut TestState<Cache, DbT, P>,
|
st: &mut TestState<Cache, DbT, P>,
|
||||||
start_index: u64,
|
start_index: u64,
|
||||||
roots: &[CommitmentTreeRoot<Self::MerkleTreeHash>],
|
roots: &[CommitmentTreeRoot<Self::MerkleTreeHash>],
|
||||||
|
@ -77,7 +90,7 @@ pub trait ShieldedPoolTester {
|
||||||
fn next_subtree_index<A: Hash + Eq>(s: &WalletSummary<A>) -> u64;
|
fn next_subtree_index<A: Hash + Eq>(s: &WalletSummary<A>) -> u64;
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
fn select_spendable_notes<Cache, DbT: InputSource + WalletRead, P>(
|
fn select_spendable_notes<Cache, DbT: InputSource + WalletTest, P>(
|
||||||
st: &TestState<Cache, DbT, P>,
|
st: &TestState<Cache, DbT, P>,
|
||||||
account: <DbT as InputSource>::AccountId,
|
account: <DbT as InputSource>::AccountId,
|
||||||
target_value: Zatoshis,
|
target_value: Zatoshis,
|
||||||
|
@ -99,6 +112,16 @@ pub trait ShieldedPoolTester {
|
||||||
fn received_note_count(summary: &ScanSummary) -> usize;
|
fn received_note_count(summary: &ScanSummary) -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tests sending funds within the given shielded pool in a single transaction.
|
||||||
|
///
|
||||||
|
/// The test:
|
||||||
|
/// - Adds funds to the wallet in a single note.
|
||||||
|
/// - Checks that the wallet balances are correct.
|
||||||
|
/// - Constructs a request to spend part of that balance to an external address in the
|
||||||
|
/// same pool.
|
||||||
|
/// - Builds the transaction.
|
||||||
|
/// - Checks that the transaction was stored, and that the outputs are decryptable and
|
||||||
|
/// have the expected details.
|
||||||
pub fn send_single_step_proposed_transfer<T: ShieldedPoolTester>(
|
pub fn send_single_step_proposed_transfer<T: ShieldedPoolTester>(
|
||||||
dsf: impl DataStoreFactory,
|
dsf: impl DataStoreFactory,
|
||||||
cache: impl TestCache,
|
cache: impl TestCache,
|
||||||
|
|
|
@ -19,13 +19,14 @@ use zip32::Scope;
|
||||||
use crate::{
|
use crate::{
|
||||||
data_api::{
|
data_api::{
|
||||||
chain::{CommitmentTreeRoot, ScanSummary},
|
chain::{CommitmentTreeRoot, ScanSummary},
|
||||||
DecryptedTransaction, InputSource, WalletCommitmentTrees, WalletRead, WalletSummary,
|
DecryptedTransaction, InputSource, WalletCommitmentTrees, WalletSummary, WalletTest,
|
||||||
},
|
},
|
||||||
wallet::{Note, ReceivedNote},
|
wallet::{Note, ReceivedNote},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{pool::ShieldedPoolTester, TestState};
|
use super::{pool::ShieldedPoolTester, TestState};
|
||||||
|
|
||||||
|
/// Type for running pool-agnostic tests on the Sapling pool.
|
||||||
pub struct SaplingPoolTester;
|
pub struct SaplingPoolTester;
|
||||||
impl ShieldedPoolTester for SaplingPoolTester {
|
impl ShieldedPoolTester for SaplingPoolTester {
|
||||||
const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Sapling;
|
const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Sapling;
|
||||||
|
@ -36,7 +37,7 @@ impl ShieldedPoolTester for SaplingPoolTester {
|
||||||
type MerkleTreeHash = sapling::Node;
|
type MerkleTreeHash = sapling::Node;
|
||||||
type Note = sapling::Note;
|
type Note = sapling::Note;
|
||||||
|
|
||||||
fn test_account_fvk<Cache, DbT: WalletRead, P: consensus::Parameters>(
|
fn test_account_fvk<Cache, DbT: WalletTest, P: consensus::Parameters>(
|
||||||
st: &TestState<Cache, DbT, P>,
|
st: &TestState<Cache, DbT, P>,
|
||||||
) -> Self::Fvk {
|
) -> Self::Fvk {
|
||||||
st.test_account_sapling().unwrap().clone()
|
st.test_account_sapling().unwrap().clone()
|
||||||
|
@ -74,7 +75,7 @@ impl ShieldedPoolTester for SaplingPoolTester {
|
||||||
::sapling::Node::empty_root(level)
|
::sapling::Node::empty_root(level)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn put_subtree_roots<Cache, DbT: WalletRead + WalletCommitmentTrees, P>(
|
fn put_subtree_roots<Cache, DbT: WalletTest + WalletCommitmentTrees, P>(
|
||||||
st: &mut TestState<Cache, DbT, P>,
|
st: &mut TestState<Cache, DbT, P>,
|
||||||
start_index: u64,
|
start_index: u64,
|
||||||
roots: &[CommitmentTreeRoot<Self::MerkleTreeHash>],
|
roots: &[CommitmentTreeRoot<Self::MerkleTreeHash>],
|
||||||
|
@ -87,7 +88,7 @@ impl ShieldedPoolTester for SaplingPoolTester {
|
||||||
s.next_sapling_subtree_index()
|
s.next_sapling_subtree_index()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_spendable_notes<Cache, DbT: InputSource + WalletRead, P>(
|
fn select_spendable_notes<Cache, DbT: InputSource + WalletTest, P>(
|
||||||
st: &TestState<Cache, DbT, P>,
|
st: &TestState<Cache, DbT, P>,
|
||||||
account: <DbT as InputSource>::AccountId,
|
account: <DbT as InputSource>::AccountId,
|
||||||
target_value: Zatoshis,
|
target_value: Zatoshis,
|
||||||
|
|
|
@ -53,7 +53,7 @@ use zcash_client_backend::{
|
||||||
Account, AccountBirthday, AccountPurpose, AccountSource, BlockMetadata,
|
Account, AccountBirthday, AccountPurpose, AccountSource, BlockMetadata,
|
||||||
DecryptedTransaction, InputSource, NullifierQuery, ScannedBlock, SeedRelevance,
|
DecryptedTransaction, InputSource, NullifierQuery, ScannedBlock, SeedRelevance,
|
||||||
SentTransaction, SpendableNotes, TransactionDataRequest, WalletCommitmentTrees, WalletRead,
|
SentTransaction, SpendableNotes, TransactionDataRequest, WalletCommitmentTrees, WalletRead,
|
||||||
WalletSummary, WalletWrite, SAPLING_SHARD_HEIGHT,
|
WalletSummary, WalletTest, WalletWrite, SAPLING_SHARD_HEIGHT,
|
||||||
},
|
},
|
||||||
keys::{
|
keys::{
|
||||||
AddressGenerationError, UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey,
|
AddressGenerationError, UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey,
|
||||||
|
@ -614,13 +614,14 @@ impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> WalletRead for W
|
||||||
|
|
||||||
Ok(iter.collect())
|
Ok(iter.collect())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-dependencies"))]
|
#[cfg(any(test, feature = "test-dependencies"))]
|
||||||
|
impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> WalletTest for WalletDb<C, P> {
|
||||||
fn get_tx_history(&self) -> Result<Vec<TransactionSummary<Self::AccountId>>, Self::Error> {
|
fn get_tx_history(&self) -> Result<Vec<TransactionSummary<Self::AccountId>>, Self::Error> {
|
||||||
wallet::testing::get_tx_history(self.conn.borrow())
|
wallet::testing::get_tx_history(self.conn.borrow())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-dependencies"))]
|
|
||||||
fn get_sent_note_ids(
|
fn get_sent_note_ids(
|
||||||
&self,
|
&self,
|
||||||
txid: &TxId,
|
txid: &TxId,
|
||||||
|
@ -1725,7 +1726,8 @@ mod tests {
|
||||||
use zcash_client_backend::data_api::{
|
use zcash_client_backend::data_api::{
|
||||||
chain::ChainState,
|
chain::ChainState,
|
||||||
testing::{TestBuilder, TestState},
|
testing::{TestBuilder, TestState},
|
||||||
Account, AccountBirthday, AccountPurpose, AccountSource, WalletRead, WalletWrite,
|
Account, AccountBirthday, AccountPurpose, AccountSource, WalletRead, WalletTest,
|
||||||
|
WalletWrite,
|
||||||
};
|
};
|
||||||
use zcash_keys::keys::{UnifiedFullViewingKey, UnifiedSpendingKey};
|
use zcash_keys::keys::{UnifiedFullViewingKey, UnifiedSpendingKey};
|
||||||
use zcash_primitives::block::BlockHash;
|
use zcash_primitives::block::BlockHash;
|
||||||
|
@ -1837,7 +1839,7 @@ mod tests {
|
||||||
AccountSource::Derived { seed_fingerprint: _, account_index } if account_index == zip32_index_2);
|
AccountSource::Derived { seed_fingerprint: _, account_index } if account_index == zip32_index_2);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_collisions<C, DbT: WalletWrite, P: consensus::Parameters>(
|
fn check_collisions<C, DbT: WalletTest + WalletWrite, P: consensus::Parameters>(
|
||||||
st: &mut TestState<C, DbT, P>,
|
st: &mut TestState<C, DbT, P>,
|
||||||
ufvk: &UnifiedFullViewingKey,
|
ufvk: &UnifiedFullViewingKey,
|
||||||
birthday: &AccountBirthday,
|
birthday: &AccountBirthday,
|
||||||
|
|
|
@ -43,6 +43,7 @@ use {
|
||||||
#[derive(Delegate)]
|
#[derive(Delegate)]
|
||||||
#[delegate(InputSource, target = "wallet_db")]
|
#[delegate(InputSource, target = "wallet_db")]
|
||||||
#[delegate(WalletRead, target = "wallet_db")]
|
#[delegate(WalletRead, target = "wallet_db")]
|
||||||
|
#[delegate(WalletTest, target = "wallet_db")]
|
||||||
#[delegate(WalletWrite, target = "wallet_db")]
|
#[delegate(WalletWrite, target = "wallet_db")]
|
||||||
#[delegate(WalletCommitmentTrees, target = "wallet_db")]
|
#[delegate(WalletCommitmentTrees, target = "wallet_db")]
|
||||||
pub(crate) struct TestDb {
|
pub(crate) struct TestDb {
|
||||||
|
|
|
@ -100,7 +100,7 @@ pub(crate) fn send_multi_step_proposed_transfer<T: ShieldedPoolTester>() {
|
||||||
|
|
||||||
use rand_core::OsRng;
|
use rand_core::OsRng;
|
||||||
use zcash_client_backend::{
|
use zcash_client_backend::{
|
||||||
data_api::{TransactionDataRequest, TransactionStatus},
|
data_api::{TransactionDataRequest, TransactionStatus, WalletTest},
|
||||||
fees::ChangeValue,
|
fees::ChangeValue,
|
||||||
wallet::TransparentAddressMetadata,
|
wallet::TransparentAddressMetadata,
|
||||||
};
|
};
|
||||||
|
|
|
@ -3194,7 +3194,7 @@ pub mod testing {
|
||||||
|
|
||||||
let results = stmt
|
let results = stmt
|
||||||
.query_and_then::<TransactionSummary<AccountId>, SqliteClientError, _, _>([], |row| {
|
.query_and_then::<TransactionSummary<AccountId>, SqliteClientError, _, _>([], |row| {
|
||||||
Ok(TransactionSummary::new(
|
Ok(TransactionSummary::from_parts(
|
||||||
AccountId(row.get("account_id")?),
|
AccountId(row.get("account_id")?),
|
||||||
TxId::from_bytes(row.get("txid")?),
|
TxId::from_bytes(row.get("txid")?),
|
||||||
row.get::<_, Option<u32>>("expiry_height")?
|
row.get::<_, Option<u32>>("expiry_height")?
|
||||||
|
|
Loading…
Reference in New Issue