Merge pull request #1530 from nuttycom/generalized_test_framework
Generalize the `zcash_client_sqlite` test framework and extract it to `zcash_client_backend`
This commit is contained in:
commit
c97e9a192b
|
@ -67,6 +67,18 @@ version = "0.2.18"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
|
||||
|
||||
[[package]]
|
||||
name = "ambassador"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b27ba24e4d8a188489d5a03c7fabc167a60809a383cdb4d15feb37479cd2a48"
|
||||
dependencies = [
|
||||
"itertools 0.10.5",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "amplify"
|
||||
version = "4.6.0"
|
||||
|
@ -5817,6 +5829,7 @@ dependencies = [
|
|||
name = "zcash_client_backend"
|
||||
version = "0.13.0"
|
||||
dependencies = [
|
||||
"ambassador",
|
||||
"arti-client",
|
||||
"assert_matches",
|
||||
"async-trait",
|
||||
|
@ -5841,10 +5854,12 @@ dependencies = [
|
|||
"nom",
|
||||
"nonempty",
|
||||
"orchard",
|
||||
"pasta_curves",
|
||||
"percent-encoding",
|
||||
"proptest",
|
||||
"prost",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
"rayon",
|
||||
"rust_decimal",
|
||||
|
@ -5879,6 +5894,7 @@ dependencies = [
|
|||
name = "zcash_client_sqlite"
|
||||
version = "0.11.2"
|
||||
dependencies = [
|
||||
"ambassador",
|
||||
"assert_matches",
|
||||
"bip32",
|
||||
"bls12_381",
|
||||
|
|
|
@ -133,6 +133,7 @@ lazy_static = "1"
|
|||
static_assertions = "1"
|
||||
|
||||
# Tests and benchmarks
|
||||
ambassador = "0.4"
|
||||
assert_matches = "1.5"
|
||||
criterion = "0.5"
|
||||
proptest = "1"
|
||||
|
|
|
@ -654,7 +654,7 @@ mod parse {
|
|||
)(input)
|
||||
}
|
||||
|
||||
/// The primary parser for <name>=<value> query-string parameter pair.
|
||||
/// The primary parser for `name=value` query-string parameter pairs.
|
||||
pub fn zcashparam(input: &str) -> IResult<&str, IndexedParam> {
|
||||
map_res(
|
||||
separated_pair(indexed_name, char('='), recognize(qchars)),
|
||||
|
|
|
@ -7,6 +7,12 @@ description = "The cryptographic code in this crate has been reviewed for correc
|
|||
[criteria.license-reviewed]
|
||||
description = "The license of this crate has been reviewed for compatibility with its usage in this repository."
|
||||
|
||||
[[audits.ambassador]]
|
||||
who = "Kris Nuttycombe <kris@nutty.land>"
|
||||
criteria = "safe-to-deploy"
|
||||
version = "0.4.1"
|
||||
notes = "Crate uses no unsafe code and the macros introduced by this crate generate the expected trait implementations without introducing additional unexpected operations."
|
||||
|
||||
[[audits.anyhow]]
|
||||
who = "Daira-Emma Hopwood <daira@jacaranda.org>"
|
||||
criteria = "safe-to-deploy"
|
||||
|
|
|
@ -7,6 +7,11 @@ and this library adheres to Rust's notion of
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
- The `Account` trait now uses an associated type for its `AccountId`
|
||||
type instead of a type parameter. This change allows for the simplification
|
||||
of some type signatures.
|
||||
|
||||
## [0.13.0] - 2024-08-20
|
||||
|
||||
`zcash_client_backend` now supports TEX (transparent-source-only) addresses as specified
|
||||
|
|
|
@ -89,8 +89,13 @@ incrementalmerkletree.workspace = true
|
|||
shardtree.workspace = true
|
||||
|
||||
# - Test dependencies
|
||||
ambassador = { workspace = true, optional = true }
|
||||
assert_matches = { workspace = true, optional = true }
|
||||
pasta_curves = { workspace = true, optional = true }
|
||||
proptest = { workspace = true, optional = true }
|
||||
jubjub = { workspace = true, optional = true }
|
||||
rand_chacha = { workspace = true, optional = true }
|
||||
zcash_proofs = { workspace = true, optional = true }
|
||||
|
||||
# - ZIP 321
|
||||
nom = "7"
|
||||
|
@ -137,17 +142,21 @@ tonic-build = { workspace = true, features = ["prost"] }
|
|||
which = "4"
|
||||
|
||||
[dev-dependencies]
|
||||
ambassador.workspace = true
|
||||
assert_matches.workspace = true
|
||||
gumdrop = "0.8"
|
||||
incrementalmerkletree = { workspace = true, features = ["test-dependencies"] }
|
||||
jubjub.workspace = true
|
||||
proptest.workspace = true
|
||||
rand_core.workspace = true
|
||||
rand.workspace = true
|
||||
rand_chacha.workspace = true
|
||||
shardtree = { workspace = true, features = ["test-dependencies"] }
|
||||
zcash_proofs.workspace = true
|
||||
tokio = { version = "1.21.0", features = ["rt-multi-thread"] }
|
||||
zcash_address = { workspace = true, features = ["test-dependencies"] }
|
||||
zcash_keys = { workspace = true, features = ["test-dependencies"] }
|
||||
tokio = { version = "1.21.0", features = ["rt-multi-thread"] }
|
||||
zcash_primitives = { workspace = true, features = ["test-dependencies"] }
|
||||
zcash_proofs = { workspace = true, features = ["bundled-prover"] }
|
||||
zcash_protocol = { workspace = true, features = ["local-consensus"] }
|
||||
|
||||
[features]
|
||||
## Enables the `tonic` gRPC client bindings for connecting to a `lightwalletd` server.
|
||||
|
@ -164,7 +173,7 @@ transparent-inputs = [
|
|||
]
|
||||
|
||||
## Enables receiving and spending Orchard funds.
|
||||
orchard = ["dep:orchard", "zcash_keys/orchard"]
|
||||
orchard = ["dep:orchard", "dep:pasta_curves", "zcash_keys/orchard"]
|
||||
|
||||
## Exposes a wallet synchronization function that implements the necessary state machine.
|
||||
sync = [
|
||||
|
@ -195,11 +204,17 @@ tor = [
|
|||
|
||||
## Exposes APIs that are useful for testing, such as `proptest` strategies.
|
||||
test-dependencies = [
|
||||
"dep:ambassador",
|
||||
"dep:assert_matches",
|
||||
"dep:proptest",
|
||||
"dep:jubjub",
|
||||
"dep:rand",
|
||||
"dep:rand_chacha",
|
||||
"orchard?/test-dependencies",
|
||||
"zcash_keys/test-dependencies",
|
||||
"zcash_primitives/test-dependencies",
|
||||
"zcash_proofs/bundled-prover",
|
||||
"zcash_protocol/local-consensus",
|
||||
"incrementalmerkletree/test-dependencies",
|
||||
]
|
||||
|
||||
|
@ -214,6 +229,14 @@ unstable-serialization = ["dep:byteorder"]
|
|||
## Exposes the [`data_api::scanning::spanning_tree`] module.
|
||||
unstable-spanning-tree = []
|
||||
|
||||
## Exposes access to the lightwalletd server via TOR
|
||||
tor-lightwalletd-tonic = [
|
||||
"tor",
|
||||
"lightwalletd-tonic",
|
||||
"tonic?/tls",
|
||||
"tonic?/tls-webpki-roots"
|
||||
]
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
|
|
|
@ -102,6 +102,9 @@ use {
|
|||
zcash_primitives::legacy::TransparentAddress,
|
||||
};
|
||||
|
||||
#[cfg(feature = "test-dependencies")]
|
||||
use ambassador::delegatable_trait;
|
||||
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
use zcash_primitives::consensus::NetworkUpgrade;
|
||||
|
||||
|
@ -110,6 +113,9 @@ pub mod error;
|
|||
pub mod scanning;
|
||||
pub mod wallet;
|
||||
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
pub mod testing;
|
||||
|
||||
/// The height of subtree roots in the Sapling note commitment tree.
|
||||
///
|
||||
/// This conforms to the structure of subtree data returned by
|
||||
|
@ -346,9 +352,11 @@ pub enum AccountSource {
|
|||
}
|
||||
|
||||
/// A set of capabilities that a client account must provide.
|
||||
pub trait Account<AccountId: Copy> {
|
||||
pub trait Account {
|
||||
type AccountId: Copy;
|
||||
|
||||
/// Returns the unique identifier for the account.
|
||||
fn id(&self) -> AccountId;
|
||||
fn id(&self) -> Self::AccountId;
|
||||
|
||||
/// Returns whether this account is derived or imported, and the derivation parameters
|
||||
/// if applicable.
|
||||
|
@ -377,7 +385,9 @@ pub trait Account<AccountId: Copy> {
|
|||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
impl<A: Copy> Account<A> for (A, UnifiedFullViewingKey) {
|
||||
impl<A: Copy> Account for (A, UnifiedFullViewingKey) {
|
||||
type AccountId = A;
|
||||
|
||||
fn id(&self) -> A {
|
||||
self.0
|
||||
}
|
||||
|
@ -398,7 +408,9 @@ impl<A: Copy> Account<A> for (A, UnifiedFullViewingKey) {
|
|||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
impl<A: Copy> Account<A> for (A, UnifiedIncomingViewingKey) {
|
||||
impl<A: Copy> Account for (A, UnifiedIncomingViewingKey) {
|
||||
type AccountId = A;
|
||||
|
||||
fn id(&self) -> A {
|
||||
self.0
|
||||
}
|
||||
|
@ -656,12 +668,23 @@ impl<NoteRef> SpendableNotes<NoteRef> {
|
|||
self.sapling.as_ref()
|
||||
}
|
||||
|
||||
/// Consumes this value and returns the Sapling notes contained within it.
|
||||
pub fn take_sapling(self) -> Vec<ReceivedNote<NoteRef, sapling::Note>> {
|
||||
self.sapling
|
||||
}
|
||||
|
||||
/// Returns the set of spendable Orchard notes.
|
||||
#[cfg(feature = "orchard")]
|
||||
pub fn orchard(&self) -> &[ReceivedNote<NoteRef, orchard::note::Note>] {
|
||||
self.orchard.as_ref()
|
||||
}
|
||||
|
||||
/// Consumes this value and returns the Orchard notes contained within it.
|
||||
#[cfg(feature = "orchard")]
|
||||
pub fn take_orchard(self) -> Vec<ReceivedNote<NoteRef, orchard::note::Note>> {
|
||||
self.orchard
|
||||
}
|
||||
|
||||
/// Computes the total value of Sapling notes.
|
||||
pub fn sapling_value(&self) -> Result<NonNegativeAmount, BalanceError> {
|
||||
self.sapling
|
||||
|
@ -715,6 +738,7 @@ impl<NoteRef> SpendableNotes<NoteRef> {
|
|||
|
||||
/// A trait representing the capability to query a data store for unspent transaction outputs
|
||||
/// belonging to a wallet.
|
||||
#[cfg_attr(feature = "test-dependencies", delegatable_trait)]
|
||||
pub trait InputSource {
|
||||
/// The type of errors produced by a wallet backend.
|
||||
type Error: Debug;
|
||||
|
@ -792,6 +816,7 @@ pub trait InputSource {
|
|||
/// This trait defines the read-only portion of the storage interface atop which
|
||||
/// higher-level wallet operations are implemented. It serves to allow wallet functions to
|
||||
/// be abstracted away from any particular data storage substrate.
|
||||
#[cfg_attr(feature = "test-dependencies", delegatable_trait)]
|
||||
pub trait WalletRead {
|
||||
/// The type of errors that may be generated when querying a wallet data store.
|
||||
type Error: Debug;
|
||||
|
@ -804,7 +829,7 @@ pub trait WalletRead {
|
|||
type AccountId: Copy + Debug + Eq + Hash;
|
||||
|
||||
/// The concrete account type used by this wallet backend.
|
||||
type Account: Account<Self::AccountId>;
|
||||
type Account: Account<AccountId = Self::AccountId>;
|
||||
|
||||
/// Returns a vector with the IDs of all accounts known to this wallet.
|
||||
fn get_account_ids(&self) -> Result<Vec<Self::AccountId>, Self::Error>;
|
||||
|
@ -1141,6 +1166,29 @@ pub trait WalletRead {
|
|||
/// transaction data requests, such as when it is necessary to fill in purely-transparent
|
||||
/// transaction history by walking the chain backwards via transparent inputs.
|
||||
fn transaction_data_requests(&self) -> Result<Vec<TransactionDataRequest>, Self::Error>;
|
||||
|
||||
/// Returns a vector of transaction summaries.
|
||||
///
|
||||
/// 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
|
||||
/// use.
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
fn get_tx_history(
|
||||
&self,
|
||||
) -> Result<Vec<testing::TransactionSummary<Self::AccountId>>, Self::Error> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
/// Returns the note IDs for shielded notes sent by the wallet in a particular
|
||||
/// transaction.
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
fn get_sent_note_ids(
|
||||
&self,
|
||||
_txid: &TxId,
|
||||
_protocol: ShieldedProtocol,
|
||||
) -> Result<Vec<NoteId>, Self::Error> {
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
/// The relevance of a seed to a given wallet.
|
||||
|
@ -1767,6 +1815,7 @@ impl AccountBirthday {
|
|||
/// [zcash/librustzcash#1284]: https://github.com/zcash/librustzcash/issues/1284
|
||||
/// [BIP 39]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
|
||||
/// [`bip0039`]: https://crates.io/crates/bip0039
|
||||
#[cfg_attr(feature = "test-dependencies", delegatable_trait)]
|
||||
pub trait WalletWrite: WalletRead {
|
||||
/// The type of identifiers used to look up transparent UTXOs.
|
||||
type UtxoRef;
|
||||
|
@ -1968,9 +2017,7 @@ pub trait WalletWrite: WalletRead {
|
|||
}
|
||||
|
||||
/// This trait describes a capability for manipulating wallet note commitment trees.
|
||||
///
|
||||
/// At present, this only serves the Sapling protocol, but it will be modified to
|
||||
/// also provide operations related to Orchard note commitment trees in the future.
|
||||
#[cfg_attr(feature = "test-dependencies", delegatable_trait)]
|
||||
pub trait WalletCommitmentTrees {
|
||||
type Error: Debug;
|
||||
|
||||
|
@ -2037,465 +2084,3 @@ pub trait WalletCommitmentTrees {
|
|||
roots: &[CommitmentTreeRoot<orchard::tree::MerkleHashOrchard>],
|
||||
) -> Result<(), ShardTreeError<Self::Error>>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-dependencies")]
|
||||
pub mod testing {
|
||||
use incrementalmerkletree::Address;
|
||||
use secrecy::{ExposeSecret, SecretVec};
|
||||
use shardtree::{error::ShardTreeError, store::memory::MemoryShardStore, ShardTree};
|
||||
use std::{collections::HashMap, convert::Infallible, num::NonZeroU32};
|
||||
use zip32::fingerprint::SeedFingerprint;
|
||||
|
||||
use zcash_primitives::{
|
||||
block::BlockHash,
|
||||
consensus::{BlockHeight, Network},
|
||||
memo::Memo,
|
||||
transaction::{components::amount::NonNegativeAmount, Transaction, TxId},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
address::UnifiedAddress,
|
||||
keys::{UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey},
|
||||
wallet::{Note, NoteId, ReceivedNote, WalletTransparentOutput},
|
||||
ShieldedProtocol,
|
||||
};
|
||||
|
||||
use super::{
|
||||
chain::{ChainState, CommitmentTreeRoot},
|
||||
scanning::ScanRange,
|
||||
AccountBirthday, AccountPurpose, BlockMetadata, DecryptedTransaction, InputSource,
|
||||
NullifierQuery, ScannedBlock, SeedRelevance, SentTransaction, SpendableNotes,
|
||||
TransactionDataRequest, TransactionStatus, WalletCommitmentTrees, WalletRead,
|
||||
WalletSummary, WalletWrite, SAPLING_SHARD_HEIGHT,
|
||||
};
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
use {
|
||||
crate::wallet::TransparentAddressMetadata, std::ops::Range,
|
||||
zcash_primitives::legacy::TransparentAddress,
|
||||
};
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
use super::ORCHARD_SHARD_HEIGHT;
|
||||
|
||||
pub struct MockWalletDb {
|
||||
pub network: Network,
|
||||
pub sapling_tree: ShardTree<
|
||||
MemoryShardStore<sapling::Node, BlockHeight>,
|
||||
{ SAPLING_SHARD_HEIGHT * 2 },
|
||||
SAPLING_SHARD_HEIGHT,
|
||||
>,
|
||||
#[cfg(feature = "orchard")]
|
||||
pub orchard_tree: ShardTree<
|
||||
MemoryShardStore<orchard::tree::MerkleHashOrchard, BlockHeight>,
|
||||
{ ORCHARD_SHARD_HEIGHT * 2 },
|
||||
ORCHARD_SHARD_HEIGHT,
|
||||
>,
|
||||
}
|
||||
|
||||
impl MockWalletDb {
|
||||
pub fn new(network: Network) -> Self {
|
||||
Self {
|
||||
network,
|
||||
sapling_tree: ShardTree::new(MemoryShardStore::empty(), 100),
|
||||
#[cfg(feature = "orchard")]
|
||||
orchard_tree: ShardTree::new(MemoryShardStore::empty(), 100),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InputSource for MockWalletDb {
|
||||
type Error = ();
|
||||
type NoteRef = u32;
|
||||
type AccountId = u32;
|
||||
|
||||
fn get_spendable_note(
|
||||
&self,
|
||||
_txid: &TxId,
|
||||
_protocol: ShieldedProtocol,
|
||||
_index: u32,
|
||||
) -> Result<Option<ReceivedNote<Self::NoteRef, Note>>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn select_spendable_notes(
|
||||
&self,
|
||||
_account: Self::AccountId,
|
||||
_target_value: NonNegativeAmount,
|
||||
_sources: &[ShieldedProtocol],
|
||||
_anchor_height: BlockHeight,
|
||||
_exclude: &[Self::NoteRef],
|
||||
) -> Result<SpendableNotes<Self::NoteRef>, Self::Error> {
|
||||
Ok(SpendableNotes::empty())
|
||||
}
|
||||
}
|
||||
|
||||
impl WalletRead for MockWalletDb {
|
||||
type Error = ();
|
||||
type AccountId = u32;
|
||||
type Account = (Self::AccountId, UnifiedFullViewingKey);
|
||||
|
||||
fn get_account_ids(&self) -> Result<Vec<Self::AccountId>, Self::Error> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
fn get_account(
|
||||
&self,
|
||||
_account_id: Self::AccountId,
|
||||
) -> Result<Option<Self::Account>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_derived_account(
|
||||
&self,
|
||||
_seed: &SeedFingerprint,
|
||||
_account_id: zip32::AccountId,
|
||||
) -> Result<Option<Self::Account>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn validate_seed(
|
||||
&self,
|
||||
_account_id: Self::AccountId,
|
||||
_seed: &SecretVec<u8>,
|
||||
) -> Result<bool, Self::Error> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn seed_relevance_to_derived_accounts(
|
||||
&self,
|
||||
_seed: &SecretVec<u8>,
|
||||
) -> Result<SeedRelevance<Self::AccountId>, Self::Error> {
|
||||
Ok(SeedRelevance::NoAccounts)
|
||||
}
|
||||
|
||||
fn get_account_for_ufvk(
|
||||
&self,
|
||||
_ufvk: &UnifiedFullViewingKey,
|
||||
) -> Result<Option<Self::Account>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_current_address(
|
||||
&self,
|
||||
_account: Self::AccountId,
|
||||
) -> Result<Option<UnifiedAddress>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_account_birthday(
|
||||
&self,
|
||||
_account: Self::AccountId,
|
||||
) -> Result<BlockHeight, Self::Error> {
|
||||
Err(())
|
||||
}
|
||||
|
||||
fn get_wallet_birthday(&self) -> Result<Option<BlockHeight>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_wallet_summary(
|
||||
&self,
|
||||
_min_confirmations: u32,
|
||||
) -> Result<Option<WalletSummary<Self::AccountId>>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn chain_height(&self) -> Result<Option<BlockHeight>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_block_hash(
|
||||
&self,
|
||||
_block_height: BlockHeight,
|
||||
) -> Result<Option<BlockHash>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn block_metadata(
|
||||
&self,
|
||||
_height: BlockHeight,
|
||||
) -> Result<Option<BlockMetadata>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn block_fully_scanned(&self) -> Result<Option<BlockMetadata>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_max_height_hash(&self) -> Result<Option<(BlockHeight, BlockHash)>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn block_max_scanned(&self) -> Result<Option<BlockMetadata>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn suggest_scan_ranges(&self) -> Result<Vec<ScanRange>, Self::Error> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn get_target_and_anchor_heights(
|
||||
&self,
|
||||
_min_confirmations: NonZeroU32,
|
||||
) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_min_unspent_height(&self) -> Result<Option<BlockHeight>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_tx_height(&self, _txid: TxId) -> Result<Option<BlockHeight>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_unified_full_viewing_keys(
|
||||
&self,
|
||||
) -> Result<HashMap<Self::AccountId, UnifiedFullViewingKey>, Self::Error> {
|
||||
Ok(HashMap::new())
|
||||
}
|
||||
|
||||
fn get_memo(&self, _id_note: NoteId) -> Result<Option<Memo>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_transaction(&self, _txid: TxId) -> Result<Option<Transaction>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_sapling_nullifiers(
|
||||
&self,
|
||||
_query: NullifierQuery,
|
||||
) -> Result<Vec<(Self::AccountId, sapling::Nullifier)>, Self::Error> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
fn get_orchard_nullifiers(
|
||||
&self,
|
||||
_query: NullifierQuery,
|
||||
) -> Result<Vec<(Self::AccountId, orchard::note::Nullifier)>, Self::Error> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
fn get_transparent_receivers(
|
||||
&self,
|
||||
_account: Self::AccountId,
|
||||
) -> Result<HashMap<TransparentAddress, Option<TransparentAddressMetadata>>, Self::Error>
|
||||
{
|
||||
Ok(HashMap::new())
|
||||
}
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
fn get_transparent_balances(
|
||||
&self,
|
||||
_account: Self::AccountId,
|
||||
_max_height: BlockHeight,
|
||||
) -> Result<HashMap<TransparentAddress, NonNegativeAmount>, Self::Error> {
|
||||
Ok(HashMap::new())
|
||||
}
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
fn get_transparent_address_metadata(
|
||||
&self,
|
||||
_account: Self::AccountId,
|
||||
_address: &TransparentAddress,
|
||||
) -> Result<Option<TransparentAddressMetadata>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
fn get_known_ephemeral_addresses(
|
||||
&self,
|
||||
_account: Self::AccountId,
|
||||
_index_range: Option<Range<u32>>,
|
||||
) -> Result<Vec<(TransparentAddress, TransparentAddressMetadata)>, Self::Error> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
fn find_account_for_ephemeral_address(
|
||||
&self,
|
||||
_address: &TransparentAddress,
|
||||
) -> Result<Option<Self::AccountId>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn transaction_data_requests(&self) -> Result<Vec<TransactionDataRequest>, Self::Error> {
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
impl WalletWrite for MockWalletDb {
|
||||
type UtxoRef = u32;
|
||||
|
||||
fn create_account(
|
||||
&mut self,
|
||||
seed: &SecretVec<u8>,
|
||||
_birthday: &AccountBirthday,
|
||||
) -> Result<(Self::AccountId, UnifiedSpendingKey), Self::Error> {
|
||||
let account = zip32::AccountId::ZERO;
|
||||
UnifiedSpendingKey::from_seed(&self.network, seed.expose_secret(), account)
|
||||
.map(|k| (u32::from(account), k))
|
||||
.map_err(|_| ())
|
||||
}
|
||||
|
||||
fn import_account_hd(
|
||||
&mut self,
|
||||
_seed: &SecretVec<u8>,
|
||||
_account_index: zip32::AccountId,
|
||||
_birthday: &AccountBirthday,
|
||||
) -> Result<(Self::Account, UnifiedSpendingKey), Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn import_account_ufvk(
|
||||
&mut self,
|
||||
_unified_key: &UnifiedFullViewingKey,
|
||||
_birthday: &AccountBirthday,
|
||||
_purpose: AccountPurpose,
|
||||
) -> Result<Self::Account, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_next_available_address(
|
||||
&mut self,
|
||||
_account: Self::AccountId,
|
||||
_request: UnifiedAddressRequest,
|
||||
) -> Result<Option<UnifiedAddress>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn put_blocks(
|
||||
&mut self,
|
||||
_from_state: &ChainState,
|
||||
_blocks: Vec<ScannedBlock<Self::AccountId>>,
|
||||
) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_chain_tip(&mut self, _tip_height: BlockHeight) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn store_decrypted_tx(
|
||||
&mut self,
|
||||
_received_tx: DecryptedTransaction<Self::AccountId>,
|
||||
) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn store_transactions_to_be_sent(
|
||||
&mut self,
|
||||
_transactions: &[SentTransaction<Self::AccountId>],
|
||||
) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn truncate_to_height(&mut self, _block_height: BlockHeight) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds a transparent UTXO received by the wallet to the data store.
|
||||
fn put_received_transparent_utxo(
|
||||
&mut self,
|
||||
_output: &WalletTransparentOutput,
|
||||
) -> Result<Self::UtxoRef, Self::Error> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
fn reserve_next_n_ephemeral_addresses(
|
||||
&mut self,
|
||||
_account_id: Self::AccountId,
|
||||
_n: usize,
|
||||
) -> Result<Vec<(TransparentAddress, TransparentAddressMetadata)>, Self::Error> {
|
||||
Err(())
|
||||
}
|
||||
|
||||
fn set_transaction_status(
|
||||
&mut self,
|
||||
_txid: TxId,
|
||||
_status: TransactionStatus,
|
||||
) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl WalletCommitmentTrees for MockWalletDb {
|
||||
type Error = Infallible;
|
||||
type SaplingShardStore<'a> = MemoryShardStore<sapling::Node, BlockHeight>;
|
||||
|
||||
fn with_sapling_tree_mut<F, A, E>(&mut self, mut callback: F) -> Result<A, E>
|
||||
where
|
||||
for<'a> F: FnMut(
|
||||
&'a mut ShardTree<
|
||||
Self::SaplingShardStore<'a>,
|
||||
{ sapling::NOTE_COMMITMENT_TREE_DEPTH },
|
||||
SAPLING_SHARD_HEIGHT,
|
||||
>,
|
||||
) -> Result<A, E>,
|
||||
E: From<ShardTreeError<Infallible>>,
|
||||
{
|
||||
callback(&mut self.sapling_tree)
|
||||
}
|
||||
|
||||
fn put_sapling_subtree_roots(
|
||||
&mut self,
|
||||
start_index: u64,
|
||||
roots: &[CommitmentTreeRoot<sapling::Node>],
|
||||
) -> Result<(), ShardTreeError<Self::Error>> {
|
||||
self.with_sapling_tree_mut(|t| {
|
||||
for (root, i) in roots.iter().zip(0u64..) {
|
||||
let root_addr =
|
||||
Address::from_parts(SAPLING_SHARD_HEIGHT.into(), start_index + i);
|
||||
t.insert(root_addr, *root.root_hash())?;
|
||||
}
|
||||
Ok::<_, ShardTreeError<Self::Error>>(())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
type OrchardShardStore<'a> =
|
||||
MemoryShardStore<orchard::tree::MerkleHashOrchard, BlockHeight>;
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
fn with_orchard_tree_mut<F, A, E>(&mut self, mut callback: F) -> Result<A, E>
|
||||
where
|
||||
for<'a> F: FnMut(
|
||||
&'a mut ShardTree<
|
||||
Self::OrchardShardStore<'a>,
|
||||
{ ORCHARD_SHARD_HEIGHT * 2 },
|
||||
ORCHARD_SHARD_HEIGHT,
|
||||
>,
|
||||
) -> Result<A, E>,
|
||||
E: From<ShardTreeError<Self::Error>>,
|
||||
{
|
||||
callback(&mut self.orchard_tree)
|
||||
}
|
||||
|
||||
/// Adds a sequence of note commitment tree subtree roots to the data store.
|
||||
#[cfg(feature = "orchard")]
|
||||
fn put_orchard_subtree_roots(
|
||||
&mut self,
|
||||
start_index: u64,
|
||||
roots: &[CommitmentTreeRoot<orchard::tree::MerkleHashOrchard>],
|
||||
) -> Result<(), ShardTreeError<Self::Error>> {
|
||||
self.with_orchard_tree_mut(|t| {
|
||||
for (root, i) in roots.iter().zip(0u64..) {
|
||||
let root_addr =
|
||||
Address::from_parts(ORCHARD_SHARD_HEIGHT.into(), start_index + i);
|
||||
t.insert(root_addr, *root.root_hash())?;
|
||||
}
|
||||
Ok::<_, ShardTreeError<Self::Error>>(())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,172 @@
|
|||
use std::hash::Hash;
|
||||
|
||||
use ::orchard::{
|
||||
keys::{FullViewingKey, SpendingKey},
|
||||
note_encryption::OrchardDomain,
|
||||
tree::MerkleHashOrchard,
|
||||
};
|
||||
use incrementalmerkletree::{Hashable, Level};
|
||||
use shardtree::error::ShardTreeError;
|
||||
|
||||
use zcash_keys::{
|
||||
address::{Address, UnifiedAddress},
|
||||
keys::UnifiedSpendingKey,
|
||||
};
|
||||
use zcash_note_encryption::try_output_recovery_with_ovk;
|
||||
use zcash_primitives::transaction::Transaction;
|
||||
use zcash_protocol::{
|
||||
consensus::{self, BlockHeight},
|
||||
memo::MemoBytes,
|
||||
value::Zatoshis,
|
||||
ShieldedProtocol,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
data_api::{
|
||||
chain::{CommitmentTreeRoot, ScanSummary},
|
||||
testing::{pool::ShieldedPoolTester, TestState},
|
||||
DecryptedTransaction, InputSource, WalletCommitmentTrees, WalletRead, WalletSummary,
|
||||
},
|
||||
wallet::{Note, ReceivedNote},
|
||||
};
|
||||
|
||||
pub struct OrchardPoolTester;
|
||||
impl ShieldedPoolTester for OrchardPoolTester {
|
||||
const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Orchard;
|
||||
// const MERKLE_TREE_DEPTH: u8 = {orchard::NOTE_COMMITMENT_TREE_DEPTH as u8};
|
||||
|
||||
type Sk = SpendingKey;
|
||||
type Fvk = FullViewingKey;
|
||||
type MerkleTreeHash = MerkleHashOrchard;
|
||||
type Note = orchard::note::Note;
|
||||
|
||||
fn test_account_fvk<Cache, DbT: WalletRead, P: consensus::Parameters>(
|
||||
st: &TestState<Cache, DbT, P>,
|
||||
) -> Self::Fvk {
|
||||
st.test_account_orchard().unwrap().clone()
|
||||
}
|
||||
|
||||
fn usk_to_sk(usk: &UnifiedSpendingKey) -> &Self::Sk {
|
||||
usk.orchard()
|
||||
}
|
||||
|
||||
fn sk(seed: &[u8]) -> Self::Sk {
|
||||
let mut account = zip32::AccountId::ZERO;
|
||||
loop {
|
||||
if let Ok(sk) = SpendingKey::from_zip32_seed(seed, 1, account) {
|
||||
break sk;
|
||||
}
|
||||
account = account.next().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn sk_to_fvk(sk: &Self::Sk) -> Self::Fvk {
|
||||
sk.into()
|
||||
}
|
||||
|
||||
fn sk_default_address(sk: &Self::Sk) -> Address {
|
||||
Self::fvk_default_address(&Self::sk_to_fvk(sk))
|
||||
}
|
||||
|
||||
fn fvk_default_address(fvk: &Self::Fvk) -> Address {
|
||||
UnifiedAddress::from_receivers(
|
||||
Some(fvk.address_at(0u32, zip32::Scope::External)),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
|
||||
fn fvks_equal(a: &Self::Fvk, b: &Self::Fvk) -> bool {
|
||||
a == b
|
||||
}
|
||||
|
||||
fn empty_tree_leaf() -> Self::MerkleTreeHash {
|
||||
MerkleHashOrchard::empty_leaf()
|
||||
}
|
||||
|
||||
fn empty_tree_root(level: Level) -> Self::MerkleTreeHash {
|
||||
MerkleHashOrchard::empty_root(level)
|
||||
}
|
||||
|
||||
fn put_subtree_roots<Cache, DbT: WalletRead + WalletCommitmentTrees, P>(
|
||||
st: &mut TestState<Cache, DbT, P>,
|
||||
start_index: u64,
|
||||
roots: &[CommitmentTreeRoot<Self::MerkleTreeHash>],
|
||||
) -> Result<(), ShardTreeError<<DbT as WalletCommitmentTrees>::Error>> {
|
||||
st.wallet_mut()
|
||||
.put_orchard_subtree_roots(start_index, roots)
|
||||
}
|
||||
|
||||
fn next_subtree_index<A: Hash + Eq>(s: &WalletSummary<A>) -> u64 {
|
||||
s.next_orchard_subtree_index()
|
||||
}
|
||||
|
||||
fn select_spendable_notes<Cache, DbT: InputSource + WalletRead, P>(
|
||||
st: &TestState<Cache, DbT, P>,
|
||||
account: <DbT as InputSource>::AccountId,
|
||||
target_value: Zatoshis,
|
||||
anchor_height: BlockHeight,
|
||||
exclude: &[DbT::NoteRef],
|
||||
) -> Result<Vec<ReceivedNote<DbT::NoteRef, Self::Note>>, <DbT as InputSource>::Error> {
|
||||
st.wallet()
|
||||
.select_spendable_notes(
|
||||
account,
|
||||
target_value,
|
||||
&[ShieldedProtocol::Orchard],
|
||||
anchor_height,
|
||||
exclude,
|
||||
)
|
||||
.map(|n| n.take_orchard())
|
||||
}
|
||||
|
||||
fn decrypted_pool_outputs_count<A>(d_tx: &DecryptedTransaction<'_, A>) -> usize {
|
||||
d_tx.orchard_outputs().len()
|
||||
}
|
||||
|
||||
fn with_decrypted_pool_memos<A>(
|
||||
d_tx: &DecryptedTransaction<'_, A>,
|
||||
mut f: impl FnMut(&MemoBytes),
|
||||
) {
|
||||
for output in d_tx.orchard_outputs() {
|
||||
f(output.memo());
|
||||
}
|
||||
}
|
||||
|
||||
fn try_output_recovery<P: consensus::Parameters>(
|
||||
_params: &P,
|
||||
_: BlockHeight,
|
||||
tx: &Transaction,
|
||||
fvk: &Self::Fvk,
|
||||
) -> Option<(Note, Address, MemoBytes)> {
|
||||
for action in tx.orchard_bundle().unwrap().actions() {
|
||||
// Find the output that decrypts with the external OVK
|
||||
let result = try_output_recovery_with_ovk(
|
||||
&OrchardDomain::for_action(action),
|
||||
&fvk.to_ovk(zip32::Scope::External),
|
||||
action,
|
||||
action.cv_net(),
|
||||
&action.encrypted_note().out_ciphertext,
|
||||
);
|
||||
|
||||
if result.is_some() {
|
||||
return result.map(|(note, addr, memo)| {
|
||||
(
|
||||
Note::Orchard(note),
|
||||
UnifiedAddress::from_receivers(Some(addr), None, None)
|
||||
.unwrap()
|
||||
.into(),
|
||||
MemoBytes::from_bytes(&memo).expect("correct length"),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn received_note_count(summary: &ScanSummary) -> usize {
|
||||
summary.received_orchard_note_count()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,240 @@
|
|||
use assert_matches::assert_matches;
|
||||
use incrementalmerkletree::Level;
|
||||
use rand::RngCore;
|
||||
use shardtree::error::ShardTreeError;
|
||||
use std::{cmp::Eq, convert::Infallible, hash::Hash, num::NonZeroU32};
|
||||
|
||||
use zcash_keys::{address::Address, keys::UnifiedSpendingKey};
|
||||
use zcash_primitives::{
|
||||
block::BlockHash,
|
||||
transaction::{fees::StandardFeeRule, Transaction},
|
||||
};
|
||||
use zcash_protocol::{
|
||||
consensus::{self, BlockHeight},
|
||||
memo::{Memo, MemoBytes},
|
||||
value::Zatoshis,
|
||||
ShieldedProtocol,
|
||||
};
|
||||
use zip321::Payment;
|
||||
|
||||
use crate::{
|
||||
data_api::{
|
||||
chain::{CommitmentTreeRoot, ScanSummary},
|
||||
testing::{AddressType, TestBuilder},
|
||||
wallet::{decrypt_and_store_transaction, input_selection::GreedyInputSelector},
|
||||
Account as _, DecryptedTransaction, InputSource, WalletCommitmentTrees, WalletRead,
|
||||
WalletSummary,
|
||||
},
|
||||
decrypt_transaction,
|
||||
fees::{standard, DustOutputPolicy},
|
||||
wallet::{Note, NoteId, OvkPolicy, ReceivedNote},
|
||||
};
|
||||
|
||||
use super::{DataStoreFactory, TestCache, TestFvk, TestState};
|
||||
|
||||
/// Trait that exposes the pool-specific types and operations necessary to run the
|
||||
/// single-shielded-pool tests on a given pool.
|
||||
pub trait ShieldedPoolTester {
|
||||
const SHIELDED_PROTOCOL: ShieldedProtocol;
|
||||
|
||||
type Sk;
|
||||
type Fvk: TestFvk;
|
||||
type MerkleTreeHash;
|
||||
type Note;
|
||||
|
||||
fn test_account_fvk<Cache, DbT: WalletRead, P: consensus::Parameters>(
|
||||
st: &TestState<Cache, DbT, P>,
|
||||
) -> Self::Fvk;
|
||||
fn usk_to_sk(usk: &UnifiedSpendingKey) -> &Self::Sk;
|
||||
fn sk(seed: &[u8]) -> Self::Sk;
|
||||
fn sk_to_fvk(sk: &Self::Sk) -> Self::Fvk;
|
||||
fn sk_default_address(sk: &Self::Sk) -> Address;
|
||||
fn fvk_default_address(fvk: &Self::Fvk) -> Address;
|
||||
fn fvks_equal(a: &Self::Fvk, b: &Self::Fvk) -> bool;
|
||||
|
||||
fn random_fvk(mut rng: impl RngCore) -> Self::Fvk {
|
||||
let sk = {
|
||||
let mut sk_bytes = vec![0; 32];
|
||||
rng.fill_bytes(&mut sk_bytes);
|
||||
Self::sk(&sk_bytes)
|
||||
};
|
||||
|
||||
Self::sk_to_fvk(&sk)
|
||||
}
|
||||
fn random_address(rng: impl RngCore) -> Address {
|
||||
Self::fvk_default_address(&Self::random_fvk(rng))
|
||||
}
|
||||
|
||||
fn empty_tree_leaf() -> Self::MerkleTreeHash;
|
||||
fn empty_tree_root(level: Level) -> Self::MerkleTreeHash;
|
||||
|
||||
fn put_subtree_roots<Cache, DbT: WalletRead + WalletCommitmentTrees, P>(
|
||||
st: &mut TestState<Cache, DbT, P>,
|
||||
start_index: u64,
|
||||
roots: &[CommitmentTreeRoot<Self::MerkleTreeHash>],
|
||||
) -> Result<(), ShardTreeError<<DbT as WalletCommitmentTrees>::Error>>;
|
||||
|
||||
fn next_subtree_index<A: Hash + Eq>(s: &WalletSummary<A>) -> u64;
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn select_spendable_notes<Cache, DbT: InputSource + WalletRead, P>(
|
||||
st: &TestState<Cache, DbT, P>,
|
||||
account: <DbT as InputSource>::AccountId,
|
||||
target_value: Zatoshis,
|
||||
anchor_height: BlockHeight,
|
||||
exclude: &[DbT::NoteRef],
|
||||
) -> Result<Vec<ReceivedNote<DbT::NoteRef, Self::Note>>, <DbT as InputSource>::Error>;
|
||||
|
||||
fn decrypted_pool_outputs_count<A>(d_tx: &DecryptedTransaction<'_, A>) -> usize;
|
||||
|
||||
fn with_decrypted_pool_memos<A>(d_tx: &DecryptedTransaction<'_, A>, f: impl FnMut(&MemoBytes));
|
||||
|
||||
fn try_output_recovery<P: consensus::Parameters>(
|
||||
params: &P,
|
||||
height: BlockHeight,
|
||||
tx: &Transaction,
|
||||
fvk: &Self::Fvk,
|
||||
) -> Option<(Note, Address, MemoBytes)>;
|
||||
|
||||
fn received_note_count(summary: &ScanSummary) -> usize;
|
||||
}
|
||||
|
||||
pub fn send_single_step_proposed_transfer<T: ShieldedPoolTester>(
|
||||
dsf: impl DataStoreFactory,
|
||||
cache: impl TestCache,
|
||||
) {
|
||||
let mut st = TestBuilder::new()
|
||||
.with_data_store_factory(dsf)
|
||||
.with_block_cache(cache)
|
||||
.with_account_from_sapling_activation(BlockHash([0; 32]))
|
||||
.build();
|
||||
|
||||
let account = st.test_account().cloned().unwrap();
|
||||
let dfvk = T::test_account_fvk(&st);
|
||||
|
||||
// Add funds to the wallet in a single note
|
||||
let value = Zatoshis::const_from_u64(60000);
|
||||
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||
st.scan_cached_blocks(h, 1);
|
||||
|
||||
// Spendable balance matches total balance
|
||||
assert_eq!(st.get_total_balance(account.id()), value);
|
||||
assert_eq!(st.get_spendable_balance(account.id(), 1), value);
|
||||
|
||||
assert_eq!(
|
||||
st.wallet()
|
||||
.block_max_scanned()
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.block_height(),
|
||||
h
|
||||
);
|
||||
|
||||
let to_extsk = T::sk(&[0xf5; 32]);
|
||||
let to: Address = T::sk_default_address(&to_extsk);
|
||||
let request = zip321::TransactionRequest::new(vec![Payment::without_memo(
|
||||
to.to_zcash_address(st.network()),
|
||||
Zatoshis::const_from_u64(10000),
|
||||
)])
|
||||
.unwrap();
|
||||
|
||||
let fee_rule = StandardFeeRule::Zip317;
|
||||
|
||||
let change_memo = "Test change memo".parse::<Memo>().unwrap();
|
||||
let change_strategy = standard::SingleOutputChangeStrategy::new(
|
||||
fee_rule,
|
||||
Some(change_memo.clone().into()),
|
||||
T::SHIELDED_PROTOCOL,
|
||||
);
|
||||
let input_selector = &GreedyInputSelector::new(change_strategy, DustOutputPolicy::default());
|
||||
|
||||
let proposal = st
|
||||
.propose_transfer(
|
||||
account.id(),
|
||||
input_selector,
|
||||
request,
|
||||
NonZeroU32::new(1).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let create_proposed_result = st.create_proposed_transactions::<Infallible, _>(
|
||||
account.usk(),
|
||||
OvkPolicy::Sender,
|
||||
&proposal,
|
||||
);
|
||||
assert_matches!(&create_proposed_result, Ok(txids) if txids.len() == 1);
|
||||
|
||||
let sent_tx_id = create_proposed_result.unwrap()[0];
|
||||
|
||||
// Verify that the sent transaction was stored and that we can decrypt the memos
|
||||
let tx = st
|
||||
.wallet()
|
||||
.get_transaction(sent_tx_id)
|
||||
.unwrap()
|
||||
.expect("Created transaction was stored.");
|
||||
let ufvks = [(account.id(), account.usk().to_unified_full_viewing_key())]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let d_tx = decrypt_transaction(st.network(), h + 1, &tx, &ufvks);
|
||||
assert_eq!(T::decrypted_pool_outputs_count(&d_tx), 2);
|
||||
|
||||
let mut found_tx_change_memo = false;
|
||||
let mut found_tx_empty_memo = false;
|
||||
T::with_decrypted_pool_memos(&d_tx, |memo| {
|
||||
if Memo::try_from(memo).unwrap() == change_memo {
|
||||
found_tx_change_memo = true
|
||||
}
|
||||
if Memo::try_from(memo).unwrap() == Memo::Empty {
|
||||
found_tx_empty_memo = true
|
||||
}
|
||||
});
|
||||
assert!(found_tx_change_memo);
|
||||
assert!(found_tx_empty_memo);
|
||||
|
||||
// Verify that the stored sent notes match what we're expecting
|
||||
let sent_note_ids = st
|
||||
.wallet()
|
||||
.get_sent_note_ids(&sent_tx_id, T::SHIELDED_PROTOCOL)
|
||||
.unwrap();
|
||||
assert_eq!(sent_note_ids.len(), 2);
|
||||
|
||||
// The sent memo should be the empty memo for the sent output, and the
|
||||
// change output's memo should be as specified.
|
||||
let mut found_sent_change_memo = false;
|
||||
let mut found_sent_empty_memo = false;
|
||||
for sent_note_id in sent_note_ids {
|
||||
match st
|
||||
.wallet()
|
||||
.get_memo(sent_note_id)
|
||||
.expect("Note id is valid")
|
||||
.as_ref()
|
||||
{
|
||||
Some(m) if m == &change_memo => {
|
||||
found_sent_change_memo = true;
|
||||
}
|
||||
Some(m) if m == &Memo::Empty => {
|
||||
found_sent_empty_memo = true;
|
||||
}
|
||||
Some(other) => panic!("Unexpected memo value: {:?}", other),
|
||||
None => panic!("Memo should not be stored as NULL"),
|
||||
}
|
||||
}
|
||||
assert!(found_sent_change_memo);
|
||||
assert!(found_sent_empty_memo);
|
||||
|
||||
// Check that querying for a nonexistent sent note returns None
|
||||
assert_matches!(
|
||||
st.wallet()
|
||||
.get_memo(NoteId::new(sent_tx_id, T::SHIELDED_PROTOCOL, 12345)),
|
||||
Ok(None)
|
||||
);
|
||||
|
||||
let tx_history = st.wallet().get_tx_history().unwrap();
|
||||
assert_eq!(tx_history.len(), 2);
|
||||
|
||||
let network = *st.network();
|
||||
assert_matches!(
|
||||
decrypt_and_store_transaction(&network, st.wallet_mut(), &tx, None),
|
||||
Ok(_)
|
||||
);
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
use std::hash::Hash;
|
||||
|
||||
use incrementalmerkletree::{Hashable, Level};
|
||||
use sapling::{
|
||||
note_encryption::try_sapling_output_recovery,
|
||||
zip32::{DiversifiableFullViewingKey, ExtendedSpendingKey},
|
||||
};
|
||||
use shardtree::error::ShardTreeError;
|
||||
use zcash_keys::{address::Address, keys::UnifiedSpendingKey};
|
||||
use zcash_primitives::transaction::{components::sapling::zip212_enforcement, Transaction};
|
||||
use zcash_protocol::{
|
||||
consensus::{self, BlockHeight},
|
||||
memo::MemoBytes,
|
||||
value::Zatoshis,
|
||||
ShieldedProtocol,
|
||||
};
|
||||
use zip32::Scope;
|
||||
|
||||
use crate::{
|
||||
data_api::{
|
||||
chain::{CommitmentTreeRoot, ScanSummary},
|
||||
DecryptedTransaction, InputSource, WalletCommitmentTrees, WalletRead, WalletSummary,
|
||||
},
|
||||
wallet::{Note, ReceivedNote},
|
||||
};
|
||||
|
||||
use super::{pool::ShieldedPoolTester, TestState};
|
||||
|
||||
pub struct SaplingPoolTester;
|
||||
impl ShieldedPoolTester for SaplingPoolTester {
|
||||
const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Sapling;
|
||||
// const MERKLE_TREE_DEPTH: u8 = sapling::NOTE_COMMITMENT_TREE_DEPTH;
|
||||
|
||||
type Sk = ExtendedSpendingKey;
|
||||
type Fvk = DiversifiableFullViewingKey;
|
||||
type MerkleTreeHash = sapling::Node;
|
||||
type Note = sapling::Note;
|
||||
|
||||
fn test_account_fvk<Cache, DbT: WalletRead, P: consensus::Parameters>(
|
||||
st: &TestState<Cache, DbT, P>,
|
||||
) -> Self::Fvk {
|
||||
st.test_account_sapling().unwrap().clone()
|
||||
}
|
||||
|
||||
fn usk_to_sk(usk: &UnifiedSpendingKey) -> &Self::Sk {
|
||||
usk.sapling()
|
||||
}
|
||||
|
||||
fn sk(seed: &[u8]) -> Self::Sk {
|
||||
ExtendedSpendingKey::master(seed)
|
||||
}
|
||||
|
||||
fn sk_to_fvk(sk: &Self::Sk) -> Self::Fvk {
|
||||
sk.to_diversifiable_full_viewing_key()
|
||||
}
|
||||
|
||||
fn sk_default_address(sk: &Self::Sk) -> Address {
|
||||
sk.default_address().1.into()
|
||||
}
|
||||
|
||||
fn fvk_default_address(fvk: &Self::Fvk) -> Address {
|
||||
fvk.default_address().1.into()
|
||||
}
|
||||
|
||||
fn fvks_equal(a: &Self::Fvk, b: &Self::Fvk) -> bool {
|
||||
a.to_bytes() == b.to_bytes()
|
||||
}
|
||||
|
||||
fn empty_tree_leaf() -> Self::MerkleTreeHash {
|
||||
::sapling::Node::empty_leaf()
|
||||
}
|
||||
|
||||
fn empty_tree_root(level: Level) -> Self::MerkleTreeHash {
|
||||
::sapling::Node::empty_root(level)
|
||||
}
|
||||
|
||||
fn put_subtree_roots<Cache, DbT: WalletRead + WalletCommitmentTrees, P>(
|
||||
st: &mut TestState<Cache, DbT, P>,
|
||||
start_index: u64,
|
||||
roots: &[CommitmentTreeRoot<Self::MerkleTreeHash>],
|
||||
) -> Result<(), ShardTreeError<<DbT as WalletCommitmentTrees>::Error>> {
|
||||
st.wallet_mut()
|
||||
.put_sapling_subtree_roots(start_index, roots)
|
||||
}
|
||||
|
||||
fn next_subtree_index<A: Hash + Eq>(s: &WalletSummary<A>) -> u64 {
|
||||
s.next_sapling_subtree_index()
|
||||
}
|
||||
|
||||
fn select_spendable_notes<Cache, DbT: InputSource + WalletRead, P>(
|
||||
st: &TestState<Cache, DbT, P>,
|
||||
account: <DbT as InputSource>::AccountId,
|
||||
target_value: Zatoshis,
|
||||
anchor_height: BlockHeight,
|
||||
exclude: &[DbT::NoteRef],
|
||||
) -> Result<Vec<ReceivedNote<DbT::NoteRef, Self::Note>>, <DbT as InputSource>::Error> {
|
||||
st.wallet()
|
||||
.select_spendable_notes(
|
||||
account,
|
||||
target_value,
|
||||
&[ShieldedProtocol::Sapling],
|
||||
anchor_height,
|
||||
exclude,
|
||||
)
|
||||
.map(|n| n.take_sapling())
|
||||
}
|
||||
|
||||
fn decrypted_pool_outputs_count<A>(d_tx: &DecryptedTransaction<'_, A>) -> usize {
|
||||
d_tx.sapling_outputs().len()
|
||||
}
|
||||
|
||||
fn with_decrypted_pool_memos<A>(
|
||||
d_tx: &DecryptedTransaction<'_, A>,
|
||||
mut f: impl FnMut(&MemoBytes),
|
||||
) {
|
||||
for output in d_tx.sapling_outputs() {
|
||||
f(output.memo());
|
||||
}
|
||||
}
|
||||
|
||||
fn try_output_recovery<P: consensus::Parameters>(
|
||||
params: &P,
|
||||
height: BlockHeight,
|
||||
tx: &Transaction,
|
||||
fvk: &Self::Fvk,
|
||||
) -> Option<(Note, Address, MemoBytes)> {
|
||||
for output in tx.sapling_bundle().unwrap().shielded_outputs() {
|
||||
// Find the output that decrypts with the external OVK
|
||||
let result = try_sapling_output_recovery(
|
||||
&fvk.to_ovk(Scope::External),
|
||||
output,
|
||||
zip212_enforcement(params, height),
|
||||
);
|
||||
|
||||
if result.is_some() {
|
||||
return result.map(|(note, addr, memo)| {
|
||||
(
|
||||
Note::Sapling(note),
|
||||
addr.into(),
|
||||
MemoBytes::from_bytes(&memo).expect("correct length"),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn received_note_count(summary: &ScanSummary) -> usize {
|
||||
summary.received_sapling_note_count()
|
||||
}
|
||||
}
|
|
@ -38,7 +38,7 @@ pub struct DecryptedOutput<Note, AccountId> {
|
|||
transfer_type: TransferType,
|
||||
}
|
||||
|
||||
impl<Note, AccountId: Copy> DecryptedOutput<Note, AccountId> {
|
||||
impl<Note, AccountId> DecryptedOutput<Note, AccountId> {
|
||||
pub fn new(
|
||||
index: usize,
|
||||
note: Note,
|
||||
|
|
|
@ -6,7 +6,7 @@ use arti_client::{config::TorClientConfigBuilder, TorClient};
|
|||
use tor_rtcompat::PreferredRuntime;
|
||||
use tracing::debug;
|
||||
|
||||
#[cfg(feature = "lightwalletd-tonic")]
|
||||
#[cfg(feature = "tor-lightwalletd-tonic")]
|
||||
mod grpc;
|
||||
|
||||
pub mod http;
|
||||
|
|
|
@ -79,6 +79,7 @@ document-features.workspace = true
|
|||
maybe-rayon.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
ambassador.workspace = true
|
||||
assert_matches.workspace = true
|
||||
bls12_381.workspace = true
|
||||
incrementalmerkletree = { workspace = true, features = ["test-dependencies"] }
|
||||
|
|
|
@ -322,10 +322,12 @@ where
|
|||
#[cfg(test)]
|
||||
#[allow(deprecated)]
|
||||
mod tests {
|
||||
use crate::{testing, wallet::sapling::tests::SaplingPoolTester};
|
||||
use zcash_client_backend::data_api::testing::sapling::SaplingPoolTester;
|
||||
|
||||
use crate::testing;
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
use crate::wallet::orchard::tests::OrchardPoolTester;
|
||||
use zcash_client_backend::data_api::testing::orchard::OrchardPoolTester;
|
||||
|
||||
#[test]
|
||||
fn valid_chain_states_sapling() {
|
||||
|
|
|
@ -74,7 +74,7 @@ use zip32::fingerprint::SeedFingerprint;
|
|||
|
||||
use crate::{error::SqliteClientError, wallet::commitment_tree::SqliteShardStore};
|
||||
|
||||
#[cfg(not(feature = "orchard"))]
|
||||
#[cfg(any(feature = "test-dependencies", not(feature = "orchard")))]
|
||||
use zcash_protocol::PoolType;
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
|
@ -98,6 +98,9 @@ use maybe_rayon::{
|
|||
slice::ParallelSliceMut,
|
||||
};
|
||||
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
use zcash_client_backend::data_api::testing::TransactionSummary;
|
||||
|
||||
/// `maybe-rayon` doesn't provide this as a fallback, so we have to.
|
||||
#[cfg(not(feature = "multicore"))]
|
||||
trait ParallelSliceMut<T> {
|
||||
|
@ -264,28 +267,36 @@ impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> InputSource for
|
|||
&self,
|
||||
account: AccountId,
|
||||
target_value: NonNegativeAmount,
|
||||
_sources: &[ShieldedProtocol],
|
||||
sources: &[ShieldedProtocol],
|
||||
anchor_height: BlockHeight,
|
||||
exclude: &[Self::NoteRef],
|
||||
) -> Result<SpendableNotes<Self::NoteRef>, Self::Error> {
|
||||
Ok(SpendableNotes::new(
|
||||
wallet::sapling::select_spendable_sapling_notes(
|
||||
self.conn.borrow(),
|
||||
&self.params,
|
||||
account,
|
||||
target_value,
|
||||
anchor_height,
|
||||
exclude,
|
||||
)?,
|
||||
if sources.contains(&ShieldedProtocol::Sapling) {
|
||||
wallet::sapling::select_spendable_sapling_notes(
|
||||
self.conn.borrow(),
|
||||
&self.params,
|
||||
account,
|
||||
target_value,
|
||||
anchor_height,
|
||||
exclude,
|
||||
)?
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
#[cfg(feature = "orchard")]
|
||||
wallet::orchard::select_spendable_orchard_notes(
|
||||
self.conn.borrow(),
|
||||
&self.params,
|
||||
account,
|
||||
target_value,
|
||||
anchor_height,
|
||||
exclude,
|
||||
)?,
|
||||
if sources.contains(&ShieldedProtocol::Orchard) {
|
||||
wallet::orchard::select_spendable_orchard_notes(
|
||||
self.conn.borrow(),
|
||||
&self.params,
|
||||
account,
|
||||
target_value,
|
||||
anchor_height,
|
||||
exclude,
|
||||
)?
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -603,6 +614,36 @@ impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> WalletRead for W
|
|||
|
||||
Ok(iter.collect())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
fn get_tx_history(&self) -> Result<Vec<TransactionSummary<Self::AccountId>>, Self::Error> {
|
||||
wallet::testing::get_tx_history(self.conn.borrow())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
fn get_sent_note_ids(
|
||||
&self,
|
||||
txid: &TxId,
|
||||
protocol: ShieldedProtocol,
|
||||
) -> Result<Vec<NoteId>, Self::Error> {
|
||||
use crate::wallet::pool_code;
|
||||
use rusqlite::named_params;
|
||||
|
||||
let mut stmt_sent_notes = self.conn.borrow().prepare(
|
||||
"SELECT output_index
|
||||
FROM sent_notes
|
||||
JOIN transactions ON transactions.id_tx = sent_notes.tx
|
||||
WHERE transactions.txid = :txid
|
||||
AND sent_notes.output_pool = :pool_code",
|
||||
)?;
|
||||
|
||||
stmt_sent_notes
|
||||
.query(named_params![":txid": txid.as_ref(), ":pool_code": pool_code(PoolType::Shielded(protocol))])
|
||||
.unwrap()
|
||||
.mapped(|row| Ok(NoteId::new(*txid, protocol, row.get(0)?)))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(SqliteClientError::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P> {
|
||||
|
@ -1682,34 +1723,35 @@ extern crate assert_matches;
|
|||
mod tests {
|
||||
use secrecy::{ExposeSecret, Secret, SecretVec};
|
||||
use zcash_client_backend::data_api::{
|
||||
chain::ChainState, Account, AccountBirthday, AccountPurpose, AccountSource, WalletRead,
|
||||
WalletWrite,
|
||||
chain::ChainState,
|
||||
testing::{TestBuilder, TestState},
|
||||
Account, AccountBirthday, AccountPurpose, AccountSource, WalletRead, WalletWrite,
|
||||
};
|
||||
use zcash_keys::keys::{UnifiedFullViewingKey, UnifiedSpendingKey};
|
||||
use zcash_primitives::block::BlockHash;
|
||||
use zcash_protocol::consensus;
|
||||
|
||||
use crate::{
|
||||
error::SqliteClientError,
|
||||
testing::{TestBuilder, TestState},
|
||||
AccountId, DEFAULT_UA_REQUEST,
|
||||
error::SqliteClientError, testing::db::TestDbFactory, AccountId, DEFAULT_UA_REQUEST,
|
||||
};
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
use {
|
||||
crate::testing::AddressType, zcash_client_backend::keys::sapling,
|
||||
zcash_client_backend::keys::sapling,
|
||||
zcash_primitives::transaction::components::amount::NonNegativeAmount,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn validate_seed() {
|
||||
let st = TestBuilder::new()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.with_account_from_sapling_activation(BlockHash([0; 32]))
|
||||
.build();
|
||||
let account = st.test_account().unwrap();
|
||||
|
||||
assert!({
|
||||
st.wallet()
|
||||
.validate_seed(account.account_id(), st.test_seed().unwrap())
|
||||
.validate_seed(account.id(), st.test_seed().unwrap())
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
|
@ -1724,7 +1766,7 @@ mod tests {
|
|||
// check that passing an invalid seed results in a failure
|
||||
assert!({
|
||||
!st.wallet()
|
||||
.validate_seed(account.account_id(), &SecretVec::new(vec![1u8; 32]))
|
||||
.validate_seed(account.id(), &SecretVec::new(vec![1u8; 32]))
|
||||
.unwrap()
|
||||
});
|
||||
}
|
||||
|
@ -1732,33 +1774,29 @@ mod tests {
|
|||
#[test]
|
||||
pub(crate) fn get_next_available_address() {
|
||||
let mut st = TestBuilder::new()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.with_account_from_sapling_activation(BlockHash([0; 32]))
|
||||
.build();
|
||||
let account = st.test_account().cloned().unwrap();
|
||||
|
||||
let current_addr = st
|
||||
.wallet()
|
||||
.get_current_address(account.account_id())
|
||||
.unwrap();
|
||||
let current_addr = st.wallet().get_current_address(account.id()).unwrap();
|
||||
assert!(current_addr.is_some());
|
||||
|
||||
let addr2 = st
|
||||
.wallet_mut()
|
||||
.get_next_available_address(account.account_id(), DEFAULT_UA_REQUEST)
|
||||
.get_next_available_address(account.id(), DEFAULT_UA_REQUEST)
|
||||
.unwrap();
|
||||
assert!(addr2.is_some());
|
||||
assert_ne!(current_addr, addr2);
|
||||
|
||||
let addr2_cur = st
|
||||
.wallet()
|
||||
.get_current_address(account.account_id())
|
||||
.unwrap();
|
||||
let addr2_cur = st.wallet().get_current_address(account.id()).unwrap();
|
||||
assert_eq!(addr2, addr2_cur);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub(crate) fn import_account_hd_0() {
|
||||
let st = TestBuilder::new()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.with_account_from_sapling_activation(BlockHash([0; 32]))
|
||||
.set_account_index(zip32::AccountId::ZERO)
|
||||
.build();
|
||||
|
@ -1769,10 +1807,12 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
pub(crate) fn import_account_hd_1_then_2() {
|
||||
let mut st = TestBuilder::new().build();
|
||||
let mut st = TestBuilder::new()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.build();
|
||||
|
||||
let birthday = AccountBirthday::from_parts(
|
||||
ChainState::empty(st.wallet().params.sapling.unwrap() - 1, BlockHash([0; 32])),
|
||||
ChainState::empty(st.network().sapling.unwrap() - 1, BlockHash([0; 32])),
|
||||
None,
|
||||
);
|
||||
|
||||
|
@ -1797,15 +1837,19 @@ mod tests {
|
|||
AccountSource::Derived { seed_fingerprint: _, account_index } if account_index == zip32_index_2);
|
||||
}
|
||||
|
||||
fn check_collisions<C>(
|
||||
st: &mut TestState<C>,
|
||||
fn check_collisions<C, DbT: WalletWrite, P: consensus::Parameters>(
|
||||
st: &mut TestState<C, DbT, P>,
|
||||
ufvk: &UnifiedFullViewingKey,
|
||||
birthday: &AccountBirthday,
|
||||
existing_id: AccountId,
|
||||
) {
|
||||
is_account_collision: impl Fn(&DbT::Error) -> bool,
|
||||
) where
|
||||
DbT::Account: core::fmt::Debug,
|
||||
{
|
||||
assert_matches!(
|
||||
st.wallet_mut().import_account_ufvk(ufvk, birthday, AccountPurpose::Spending),
|
||||
Err(SqliteClientError::AccountCollision(id)) if id == existing_id);
|
||||
st.wallet_mut()
|
||||
.import_account_ufvk(ufvk, birthday, AccountPurpose::Spending),
|
||||
Err(e) if is_account_collision(&e)
|
||||
);
|
||||
|
||||
// Remove the transparent component so that we don't have a match on the full UFVK.
|
||||
// That should still produce an AccountCollision error.
|
||||
|
@ -1820,8 +1864,13 @@ mod tests {
|
|||
)
|
||||
.unwrap();
|
||||
assert_matches!(
|
||||
st.wallet_mut().import_account_ufvk(&subset_ufvk, birthday, AccountPurpose::Spending),
|
||||
Err(SqliteClientError::AccountCollision(id)) if id == existing_id);
|
||||
st.wallet_mut().import_account_ufvk(
|
||||
&subset_ufvk,
|
||||
birthday,
|
||||
AccountPurpose::Spending
|
||||
),
|
||||
Err(e) if is_account_collision(&e)
|
||||
);
|
||||
}
|
||||
|
||||
// Remove the Orchard component so that we don't have a match on the full UFVK.
|
||||
|
@ -1837,17 +1886,24 @@ mod tests {
|
|||
)
|
||||
.unwrap();
|
||||
assert_matches!(
|
||||
st.wallet_mut().import_account_ufvk(&subset_ufvk, birthday, AccountPurpose::Spending),
|
||||
Err(SqliteClientError::AccountCollision(id)) if id == existing_id);
|
||||
st.wallet_mut().import_account_ufvk(
|
||||
&subset_ufvk,
|
||||
birthday,
|
||||
AccountPurpose::Spending
|
||||
),
|
||||
Err(e) if is_account_collision(&e)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub(crate) fn import_account_hd_1_then_conflicts() {
|
||||
let mut st = TestBuilder::new().build();
|
||||
let mut st = TestBuilder::new()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.build();
|
||||
|
||||
let birthday = AccountBirthday::from_parts(
|
||||
ChainState::empty(st.wallet().params.sapling.unwrap() - 1, BlockHash([0; 32])),
|
||||
ChainState::empty(st.network().sapling.unwrap() - 1, BlockHash([0; 32])),
|
||||
None,
|
||||
);
|
||||
|
||||
|
@ -1864,23 +1920,29 @@ mod tests {
|
|||
st.wallet_mut().import_account_hd(&seed, zip32_index_1, &birthday),
|
||||
Err(SqliteClientError::AccountCollision(id)) if id == first_account.id());
|
||||
|
||||
check_collisions(&mut st, ufvk, &birthday, first_account.id());
|
||||
check_collisions(
|
||||
&mut st,
|
||||
ufvk,
|
||||
&birthday,
|
||||
|e| matches!(e, SqliteClientError::AccountCollision(id) if *id == first_account.id()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub(crate) fn import_account_ufvk_then_conflicts() {
|
||||
let mut st = TestBuilder::new().build();
|
||||
let mut st = TestBuilder::new()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.build();
|
||||
|
||||
let birthday = AccountBirthday::from_parts(
|
||||
ChainState::empty(st.wallet().params.sapling.unwrap() - 1, BlockHash([0; 32])),
|
||||
ChainState::empty(st.network().sapling.unwrap() - 1, BlockHash([0; 32])),
|
||||
None,
|
||||
);
|
||||
|
||||
let seed = Secret::new(vec![0u8; 32]);
|
||||
let zip32_index_0 = zip32::AccountId::ZERO;
|
||||
let usk =
|
||||
UnifiedSpendingKey::from_seed(&st.wallet().params, seed.expose_secret(), zip32_index_0)
|
||||
.unwrap();
|
||||
let usk = UnifiedSpendingKey::from_seed(st.network(), seed.expose_secret(), zip32_index_0)
|
||||
.unwrap();
|
||||
let ufvk = usk.to_unified_full_viewing_key();
|
||||
|
||||
let account = st
|
||||
|
@ -1888,8 +1950,8 @@ mod tests {
|
|||
.import_account_ufvk(&ufvk, &birthday, AccountPurpose::Spending)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
ufvk.encode(&st.wallet().params),
|
||||
account.ufvk().unwrap().encode(&st.wallet().params)
|
||||
ufvk.encode(st.network()),
|
||||
account.ufvk().unwrap().encode(st.network())
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
|
@ -1903,15 +1965,22 @@ mod tests {
|
|||
st.wallet_mut().import_account_hd(&seed, zip32_index_0, &birthday),
|
||||
Err(SqliteClientError::AccountCollision(id)) if id == account.id());
|
||||
|
||||
check_collisions(&mut st, &ufvk, &birthday, account.id());
|
||||
check_collisions(
|
||||
&mut st,
|
||||
&ufvk,
|
||||
&birthday,
|
||||
|e| matches!(e, SqliteClientError::AccountCollision(id) if *id == account.id()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub(crate) fn create_account_then_conflicts() {
|
||||
let mut st = TestBuilder::new().build();
|
||||
let mut st = TestBuilder::new()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.build();
|
||||
|
||||
let birthday = AccountBirthday::from_parts(
|
||||
ChainState::empty(st.wallet().params.sapling.unwrap() - 1, BlockHash([0; 32])),
|
||||
ChainState::empty(st.network().sapling.unwrap() - 1, BlockHash([0; 32])),
|
||||
None,
|
||||
);
|
||||
|
||||
|
@ -1925,25 +1994,30 @@ mod tests {
|
|||
st.wallet_mut().import_account_hd(&seed, zip32_index_0, &birthday),
|
||||
Err(SqliteClientError::AccountCollision(id)) if id == seed_based.0);
|
||||
|
||||
check_collisions(&mut st, ufvk, &birthday, seed_based.0);
|
||||
check_collisions(
|
||||
&mut st,
|
||||
ufvk,
|
||||
&birthday,
|
||||
|e| matches!(e, SqliteClientError::AccountCollision(id) if *id == seed_based.0),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
#[test]
|
||||
fn transparent_receivers() {
|
||||
// Add an account to the wallet.
|
||||
|
||||
use crate::testing::BlockCache;
|
||||
let st = TestBuilder::new()
|
||||
.with_block_cache()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.with_block_cache(BlockCache::new())
|
||||
.with_account_from_sapling_activation(BlockHash([0; 32]))
|
||||
.build();
|
||||
let account = st.test_account().unwrap();
|
||||
let ufvk = account.usk().to_unified_full_viewing_key();
|
||||
let (taddr, _) = account.usk().default_transparent_address();
|
||||
|
||||
let receivers = st
|
||||
.wallet()
|
||||
.get_transparent_receivers(account.account_id())
|
||||
.unwrap();
|
||||
let receivers = st.wallet().get_transparent_receivers(account.id()).unwrap();
|
||||
|
||||
// The receiver for the default UA should be in the set.
|
||||
assert!(receivers.contains_key(
|
||||
|
@ -1961,10 +2035,16 @@ mod tests {
|
|||
#[cfg(feature = "unstable")]
|
||||
#[test]
|
||||
pub(crate) fn fsblockdb_api() {
|
||||
use zcash_primitives::consensus::NetworkConstants;
|
||||
use zcash_client_backend::data_api::testing::AddressType;
|
||||
use zcash_primitives::zip32;
|
||||
use zcash_protocol::consensus::NetworkConstants;
|
||||
|
||||
let mut st = TestBuilder::new().with_fs_block_cache().build();
|
||||
use crate::testing::FsBlockCache;
|
||||
|
||||
let mut st = TestBuilder::new()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.with_block_cache(FsBlockCache::new())
|
||||
.build();
|
||||
|
||||
// The BlockMeta DB starts off empty.
|
||||
assert_eq!(st.cache().get_max_cached_height().unwrap(), None);
|
||||
|
@ -1972,7 +2052,7 @@ mod tests {
|
|||
// Generate some fake CompactBlocks.
|
||||
let seed = [0u8; 32];
|
||||
let hd_account_index = zip32::AccountId::ZERO;
|
||||
let extsk = sapling::spending_key(&seed, st.wallet().params.coin_type(), hd_account_index);
|
||||
let extsk = sapling::spending_key(&seed, st.network().coin_type(), hd_account_index);
|
||||
let dfvk = extsk.to_diversifiable_full_viewing_key();
|
||||
let (h1, meta1, _) = st.generate_next_block(
|
||||
&dfvk,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,172 @@
|
|||
use ambassador::Delegate;
|
||||
use rusqlite::Connection;
|
||||
use std::collections::HashMap;
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
use rusqlite::{self};
|
||||
use secrecy::SecretVec;
|
||||
use shardtree::{error::ShardTreeError, ShardTree};
|
||||
use zip32::fingerprint::SeedFingerprint;
|
||||
|
||||
use zcash_client_backend::{
|
||||
data_api::{
|
||||
chain::{ChainState, CommitmentTreeRoot},
|
||||
scanning::ScanRange,
|
||||
testing::{DataStoreFactory, Reset, TestState},
|
||||
*,
|
||||
},
|
||||
keys::UnifiedFullViewingKey,
|
||||
wallet::{Note, NoteId, ReceivedNote, WalletTransparentOutput},
|
||||
ShieldedProtocol,
|
||||
};
|
||||
use zcash_keys::{
|
||||
address::UnifiedAddress,
|
||||
keys::{UnifiedAddressRequest, UnifiedSpendingKey},
|
||||
};
|
||||
use zcash_primitives::{
|
||||
block::BlockHash,
|
||||
transaction::{components::amount::NonNegativeAmount, Transaction, TxId},
|
||||
};
|
||||
use zcash_protocol::{consensus::BlockHeight, local_consensus::LocalNetwork, memo::Memo};
|
||||
|
||||
use crate::{error::SqliteClientError, wallet::init::init_wallet_db, AccountId, WalletDb};
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
use {
|
||||
crate::TransparentAddressMetadata,
|
||||
core::ops::Range,
|
||||
zcash_primitives::{legacy::TransparentAddress, transaction::components::OutPoint},
|
||||
};
|
||||
|
||||
#[derive(Delegate)]
|
||||
#[delegate(InputSource, target = "wallet_db")]
|
||||
#[delegate(WalletRead, target = "wallet_db")]
|
||||
#[delegate(WalletWrite, target = "wallet_db")]
|
||||
#[delegate(WalletCommitmentTrees, target = "wallet_db")]
|
||||
pub(crate) struct TestDb {
|
||||
wallet_db: WalletDb<Connection, LocalNetwork>,
|
||||
data_file: NamedTempFile,
|
||||
}
|
||||
|
||||
impl TestDb {
|
||||
fn from_parts(wallet_db: WalletDb<Connection, LocalNetwork>, data_file: NamedTempFile) -> Self {
|
||||
Self {
|
||||
wallet_db,
|
||||
data_file,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn db(&self) -> &WalletDb<Connection, LocalNetwork> {
|
||||
&self.wallet_db
|
||||
}
|
||||
|
||||
pub(crate) fn db_mut(&mut self) -> &mut WalletDb<Connection, LocalNetwork> {
|
||||
&mut self.wallet_db
|
||||
}
|
||||
|
||||
pub(crate) fn conn(&self) -> &Connection {
|
||||
&self.wallet_db.conn
|
||||
}
|
||||
|
||||
pub(crate) fn conn_mut(&mut self) -> &mut Connection {
|
||||
&mut self.wallet_db.conn
|
||||
}
|
||||
|
||||
pub(crate) fn take_data_file(self) -> NamedTempFile {
|
||||
self.data_file
|
||||
}
|
||||
|
||||
/// Dump the schema and contents of the given database table, in
|
||||
/// sqlite3 ".dump" format. The name of the table must be a static
|
||||
/// string. This assumes that `sqlite3` is on your path and that it
|
||||
/// invokes a compatible version of sqlite3.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `name` contains characters outside `[a-zA-Z_]`.
|
||||
#[allow(dead_code)]
|
||||
#[cfg(feature = "unstable")]
|
||||
pub(crate) fn dump_table(&self, name: &'static str) {
|
||||
assert!(name.chars().all(|c| c.is_ascii_alphabetic() || c == '_'));
|
||||
unsafe {
|
||||
run_sqlite3(self.data_file.path(), &format!(r#".dump "{name}""#));
|
||||
}
|
||||
}
|
||||
|
||||
/// Print the results of an arbitrary sqlite3 command (with "-safe"
|
||||
/// and "-readonly" flags) to stderr. This is completely insecure and
|
||||
/// should not be exposed in production. Use of the "-safe" and
|
||||
/// "-readonly" flags is intended only to limit *accidental* misuse.
|
||||
/// The output is unfiltered, and control codes could mess up your
|
||||
/// terminal. This assumes that `sqlite3` is on your path and that it
|
||||
/// invokes a compatible version of sqlite3.
|
||||
#[allow(dead_code)]
|
||||
#[cfg(feature = "unstable")]
|
||||
pub(crate) unsafe fn run_sqlite3(&self, command: &str) {
|
||||
run_sqlite3(self.data_file.path(), command)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
use std::{ffi::OsStr, process::Command};
|
||||
|
||||
// See the doc comment for `TestState::run_sqlite3` above.
|
||||
//
|
||||
// - `db_path` is the path to the database file.
|
||||
// - `command` may contain newlines.
|
||||
#[allow(dead_code)]
|
||||
#[cfg(feature = "unstable")]
|
||||
unsafe fn run_sqlite3<S: AsRef<OsStr>>(db_path: S, command: &str) {
|
||||
let output = Command::new("sqlite3")
|
||||
.arg(db_path)
|
||||
.arg("-safe")
|
||||
.arg("-readonly")
|
||||
.arg(command)
|
||||
.output()
|
||||
.expect("failed to execute sqlite3 process");
|
||||
|
||||
eprintln!(
|
||||
"{}\n------\n{}",
|
||||
command,
|
||||
String::from_utf8_lossy(&output.stdout)
|
||||
);
|
||||
if !output.stderr.is_empty() {
|
||||
eprintln!(
|
||||
"------ stderr:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
eprintln!("------");
|
||||
}
|
||||
|
||||
pub(crate) struct TestDbFactory;
|
||||
|
||||
impl DataStoreFactory for TestDbFactory {
|
||||
type Error = ();
|
||||
type AccountId = AccountId;
|
||||
type Account = crate::wallet::Account;
|
||||
type DsError = SqliteClientError;
|
||||
type DataStore = TestDb;
|
||||
|
||||
fn new_data_store(&self, network: LocalNetwork) -> Result<Self::DataStore, Self::Error> {
|
||||
let data_file = NamedTempFile::new().unwrap();
|
||||
let mut db_data = WalletDb::for_path(data_file.path(), network).unwrap();
|
||||
init_wallet_db(&mut db_data, None).unwrap();
|
||||
Ok(TestDb::from_parts(db_data, data_file))
|
||||
}
|
||||
}
|
||||
|
||||
impl Reset for TestDb {
|
||||
type Handle = NamedTempFile;
|
||||
|
||||
fn reset<C>(st: &mut TestState<C, Self, LocalNetwork>) -> NamedTempFile {
|
||||
let network = *st.network();
|
||||
let old_db = std::mem::replace(
|
||||
st.wallet_mut(),
|
||||
TestDbFactory.new_data_store(network).unwrap(),
|
||||
);
|
||||
old_db.take_data_file()
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -220,7 +220,9 @@ impl Account {
|
|||
}
|
||||
}
|
||||
|
||||
impl zcash_client_backend::data_api::Account<AccountId> for Account {
|
||||
impl zcash_client_backend::data_api::Account for Account {
|
||||
type AccountId = AccountId;
|
||||
|
||||
fn id(&self) -> AccountId {
|
||||
self.account_id
|
||||
}
|
||||
|
@ -3168,17 +3170,99 @@ pub(crate) fn prune_nullifier_map(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-dependencies"))]
|
||||
pub mod testing {
|
||||
use incrementalmerkletree::Position;
|
||||
use zcash_client_backend::data_api::testing::TransactionSummary;
|
||||
use zcash_primitives::transaction::TxId;
|
||||
use zcash_protocol::{
|
||||
consensus::BlockHeight,
|
||||
value::{ZatBalance, Zatoshis},
|
||||
ShieldedProtocol,
|
||||
};
|
||||
|
||||
use crate::{error::SqliteClientError, AccountId};
|
||||
|
||||
pub(crate) fn get_tx_history(
|
||||
conn: &rusqlite::Connection,
|
||||
) -> Result<Vec<TransactionSummary<AccountId>>, SqliteClientError> {
|
||||
let mut stmt = conn.prepare_cached(
|
||||
"SELECT *
|
||||
FROM v_transactions
|
||||
ORDER BY mined_height DESC, tx_index DESC",
|
||||
)?;
|
||||
|
||||
let results = stmt
|
||||
.query_and_then::<TransactionSummary<AccountId>, SqliteClientError, _, _>([], |row| {
|
||||
Ok(TransactionSummary::new(
|
||||
AccountId(row.get("account_id")?),
|
||||
TxId::from_bytes(row.get("txid")?),
|
||||
row.get::<_, Option<u32>>("expiry_height")?
|
||||
.map(BlockHeight::from),
|
||||
row.get::<_, Option<u32>>("mined_height")?
|
||||
.map(BlockHeight::from),
|
||||
ZatBalance::from_i64(row.get("account_balance_delta")?)?,
|
||||
row.get::<_, Option<i64>>("fee_paid")?
|
||||
.map(Zatoshis::from_nonnegative_i64)
|
||||
.transpose()?,
|
||||
row.get("spent_note_count")?,
|
||||
row.get("has_change")?,
|
||||
row.get("sent_note_count")?,
|
||||
row.get("received_note_count")?,
|
||||
row.get("memo_count")?,
|
||||
row.get("expired_unmined")?,
|
||||
row.get("is_shielding")?,
|
||||
))
|
||||
})?
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
/// Returns a vector of transaction summaries
|
||||
#[allow(dead_code)] // used only for tests that are flagged off by default
|
||||
pub(crate) fn get_checkpoint_history(
|
||||
conn: &rusqlite::Connection,
|
||||
) -> Result<Vec<(BlockHeight, ShieldedProtocol, Option<Position>)>, SqliteClientError> {
|
||||
let mut stmt = conn.prepare_cached(
|
||||
"SELECT checkpoint_id, 2 AS pool, position FROM sapling_tree_checkpoints
|
||||
UNION
|
||||
SELECT checkpoint_id, 3 AS pool, position FROM orchard_tree_checkpoints
|
||||
ORDER BY checkpoint_id",
|
||||
)?;
|
||||
|
||||
let results = stmt
|
||||
.query_and_then::<_, SqliteClientError, _, _>([], |row| {
|
||||
Ok((
|
||||
BlockHeight::from(row.get::<_, u32>(0)?),
|
||||
match row.get::<_, i64>(1)? {
|
||||
2 => ShieldedProtocol::Sapling,
|
||||
3 => ShieldedProtocol::Orchard,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
row.get::<_, Option<u64>>(2)?.map(Position::from),
|
||||
))
|
||||
})?
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
use sapling::zip32::ExtendedSpendingKey;
|
||||
use secrecy::{ExposeSecret, SecretVec};
|
||||
use zcash_client_backend::data_api::{AccountSource, WalletRead};
|
||||
use zcash_client_backend::data_api::{
|
||||
testing::{AddressType, DataStoreFactory, FakeCompactOutput, TestBuilder, TestState},
|
||||
Account as _, AccountSource, WalletRead, WalletWrite,
|
||||
};
|
||||
use zcash_primitives::{block::BlockHash, transaction::components::amount::NonNegativeAmount};
|
||||
|
||||
use crate::{
|
||||
testing::{AddressType, BlockCache, FakeCompactOutput, TestBuilder, TestState},
|
||||
testing::{db::TestDbFactory, BlockCache},
|
||||
AccountId,
|
||||
};
|
||||
|
||||
|
@ -3187,6 +3271,7 @@ mod tests {
|
|||
#[test]
|
||||
fn empty_database_has_no_balance() {
|
||||
let st = TestBuilder::new()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.with_account_from_sapling_activation(BlockHash([0; 32]))
|
||||
.build();
|
||||
let account = st.test_account().unwrap();
|
||||
|
@ -3203,27 +3288,23 @@ mod tests {
|
|||
);
|
||||
|
||||
// The default address is set for the test account
|
||||
assert_matches!(
|
||||
st.wallet().get_current_address(account.account_id()),
|
||||
Ok(Some(_))
|
||||
);
|
||||
assert_matches!(st.wallet().get_current_address(account.id()), Ok(Some(_)));
|
||||
|
||||
// No default address is set for an un-initialized account
|
||||
assert_matches!(
|
||||
st.wallet()
|
||||
.get_current_address(AccountId(account.account_id().0 + 1)),
|
||||
.get_current_address(AccountId(account.id().0 + 1)),
|
||||
Ok(None)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_default_account_index() {
|
||||
use crate::testing::TestBuilder;
|
||||
|
||||
let st = TestBuilder::new()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.with_account_from_sapling_activation(BlockHash([0; 32]))
|
||||
.build();
|
||||
let account_id = st.test_account().unwrap().account_id();
|
||||
let account_id = st.test_account().unwrap().id();
|
||||
let account_parameters = st.wallet().get_account(account_id).unwrap().unwrap();
|
||||
|
||||
let expected_account_index = zip32::AccountId::try_from(0).unwrap();
|
||||
|
@ -3235,10 +3316,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn get_account_ids() {
|
||||
use crate::testing::TestBuilder;
|
||||
use zcash_client_backend::data_api::WalletWrite;
|
||||
|
||||
let mut st = TestBuilder::new()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.with_account_from_sapling_activation(BlockHash([0; 32]))
|
||||
.build();
|
||||
|
||||
|
@ -3254,12 +3333,17 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn block_fully_scanned() {
|
||||
check_block_fully_scanned(TestDbFactory)
|
||||
}
|
||||
|
||||
fn check_block_fully_scanned<DsF: DataStoreFactory>(dsf: DsF) {
|
||||
let mut st = TestBuilder::new()
|
||||
.with_block_cache()
|
||||
.with_data_store_factory(dsf)
|
||||
.with_block_cache(BlockCache::new())
|
||||
.with_account_from_sapling_activation(BlockHash([0; 32]))
|
||||
.build();
|
||||
|
||||
let block_fully_scanned = |st: &TestState<BlockCache>| {
|
||||
let block_fully_scanned = |st: &TestState<_, DsF::DataStore, _>| {
|
||||
st.wallet()
|
||||
.block_fully_scanned()
|
||||
.unwrap()
|
||||
|
@ -3314,13 +3398,14 @@ mod tests {
|
|||
#[test]
|
||||
fn test_account_birthday() {
|
||||
let st = TestBuilder::new()
|
||||
.with_block_cache()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.with_block_cache(BlockCache::new())
|
||||
.with_account_from_sapling_activation(BlockHash([0; 32]))
|
||||
.build();
|
||||
|
||||
let account_id = st.test_account().unwrap().account_id();
|
||||
let account_id = st.test_account().unwrap().id();
|
||||
assert_matches!(
|
||||
account_birthday(&st.wallet().conn, account_id),
|
||||
account_birthday(st.wallet().conn(), account_id),
|
||||
Ok(birthday) if birthday == st.sapling_activation_height()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1081,17 +1081,16 @@ mod tests {
|
|||
Marking, Position, Retention,
|
||||
};
|
||||
use shardtree::ShardTree;
|
||||
use zcash_client_backend::data_api::chain::CommitmentTreeRoot;
|
||||
use zcash_client_backend::data_api::{
|
||||
chain::CommitmentTreeRoot,
|
||||
testing::{pool::ShieldedPoolTester, sapling::SaplingPoolTester},
|
||||
};
|
||||
use zcash_primitives::consensus::{BlockHeight, Network};
|
||||
|
||||
use super::SqliteShardStore;
|
||||
use crate::{
|
||||
testing::pool::ShieldedPoolTester,
|
||||
wallet::{init::init_wallet_db, sapling::tests::SaplingPoolTester},
|
||||
WalletDb,
|
||||
};
|
||||
use crate::{testing::pool::ShieldedPoolPersistence, wallet::init::init_wallet_db, WalletDb};
|
||||
|
||||
fn new_tree<T: ShieldedPoolTester>(
|
||||
fn new_tree<T: ShieldedPoolTester + ShieldedPoolPersistence>(
|
||||
m: usize,
|
||||
) -> ShardTree<SqliteShardStore<rusqlite::Connection, String, 3>, 4, 3> {
|
||||
let data_file = NamedTempFile::new().unwrap();
|
||||
|
@ -1108,7 +1107,7 @@ mod tests {
|
|||
#[cfg(feature = "orchard")]
|
||||
mod orchard {
|
||||
use super::new_tree;
|
||||
use crate::wallet::orchard::tests::OrchardPoolTester;
|
||||
use zcash_client_backend::data_api::testing::orchard::OrchardPoolTester;
|
||||
|
||||
#[test]
|
||||
fn append() {
|
||||
|
@ -1191,7 +1190,7 @@ mod tests {
|
|||
put_shard_roots::<SaplingPoolTester>()
|
||||
}
|
||||
|
||||
fn put_shard_roots<T: ShieldedPoolTester>() {
|
||||
fn put_shard_roots<T: ShieldedPoolTester + ShieldedPoolPersistence>() {
|
||||
let data_file = NamedTempFile::new().unwrap();
|
||||
let mut db_data = WalletDb::for_path(data_file.path(), Network::TestNetwork).unwrap();
|
||||
data_file.keep().unwrap();
|
||||
|
|
|
@ -418,6 +418,7 @@ mod tests {
|
|||
|
||||
use zcash_client_backend::{
|
||||
address::Address,
|
||||
data_api::testing::TestBuilder,
|
||||
encoding::{encode_extended_full_viewing_key, encode_payment_address},
|
||||
keys::{sapling, UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey},
|
||||
};
|
||||
|
@ -429,7 +430,7 @@ mod tests {
|
|||
zip32::AccountId,
|
||||
};
|
||||
|
||||
use crate::{testing::TestBuilder, wallet::db, WalletDb, UA_TRANSPARENT};
|
||||
use crate::{testing::db::TestDbFactory, wallet::db, WalletDb, UA_TRANSPARENT};
|
||||
|
||||
use super::init_wallet_db;
|
||||
|
||||
|
@ -453,7 +454,9 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn verify_schema() {
|
||||
let st = TestBuilder::new().build();
|
||||
let st = TestBuilder::new()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.build();
|
||||
|
||||
use regex::Regex;
|
||||
let re = Regex::new(r"\s+").unwrap();
|
||||
|
@ -489,7 +492,7 @@ mod tests {
|
|||
db::TABLE_TX_RETRIEVAL_QUEUE,
|
||||
];
|
||||
|
||||
let rows = describe_tables(&st.wallet().conn).unwrap();
|
||||
let rows = describe_tables(&st.wallet().db().conn).unwrap();
|
||||
assert_eq!(rows.len(), expected_tables.len());
|
||||
for (actual, expected) in rows.iter().zip(expected_tables.iter()) {
|
||||
assert_eq!(
|
||||
|
@ -515,6 +518,7 @@ mod tests {
|
|||
];
|
||||
let mut indices_query = st
|
||||
.wallet()
|
||||
.db()
|
||||
.conn
|
||||
.prepare("SELECT sql FROM sqlite_master WHERE type = 'index' AND sql != '' ORDER BY tbl_name, name")
|
||||
.unwrap();
|
||||
|
@ -530,12 +534,12 @@ mod tests {
|
|||
}
|
||||
|
||||
let expected_views = vec![
|
||||
db::view_orchard_shard_scan_ranges(&st.network()),
|
||||
db::view_orchard_shard_scan_ranges(st.network()),
|
||||
db::view_orchard_shard_unscanned_ranges(),
|
||||
db::VIEW_ORCHARD_SHARDS_SCAN_STATE.to_owned(),
|
||||
db::VIEW_RECEIVED_OUTPUT_SPENDS.to_owned(),
|
||||
db::VIEW_RECEIVED_OUTPUTS.to_owned(),
|
||||
db::view_sapling_shard_scan_ranges(&st.network()),
|
||||
db::view_sapling_shard_scan_ranges(st.network()),
|
||||
db::view_sapling_shard_unscanned_ranges(),
|
||||
db::VIEW_SAPLING_SHARDS_SCAN_STATE.to_owned(),
|
||||
db::VIEW_TRANSACTIONS.to_owned(),
|
||||
|
@ -544,6 +548,7 @@ mod tests {
|
|||
|
||||
let mut views_query = st
|
||||
.wallet()
|
||||
.db()
|
||||
.conn
|
||||
.prepare("SELECT sql FROM sqlite_schema WHERE type = 'view' ORDER BY tbl_name")
|
||||
.unwrap();
|
||||
|
|
|
@ -387,182 +387,12 @@ pub(crate) fn mark_orchard_note_spent(
|
|||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use incrementalmerkletree::{Hashable, Level};
|
||||
use orchard::{
|
||||
keys::{FullViewingKey, SpendingKey},
|
||||
note_encryption::OrchardDomain,
|
||||
tree::MerkleHashOrchard,
|
||||
};
|
||||
use shardtree::error::ShardTreeError;
|
||||
use zcash_client_backend::{
|
||||
data_api::{
|
||||
chain::CommitmentTreeRoot, DecryptedTransaction, WalletCommitmentTrees, WalletSummary,
|
||||
},
|
||||
wallet::{Note, ReceivedNote},
|
||||
};
|
||||
use zcash_keys::{
|
||||
address::{Address, UnifiedAddress},
|
||||
keys::UnifiedSpendingKey,
|
||||
};
|
||||
use zcash_note_encryption::try_output_recovery_with_ovk;
|
||||
use zcash_primitives::transaction::Transaction;
|
||||
use zcash_protocol::{consensus::BlockHeight, memo::MemoBytes, ShieldedProtocol};
|
||||
|
||||
use super::select_spendable_orchard_notes;
|
||||
use crate::{
|
||||
error::SqliteClientError,
|
||||
testing::{
|
||||
self,
|
||||
pool::{OutputRecoveryError, ShieldedPoolTester},
|
||||
TestState,
|
||||
},
|
||||
wallet::{commitment_tree, sapling::tests::SaplingPoolTester},
|
||||
ORCHARD_TABLES_PREFIX,
|
||||
use zcash_client_backend::data_api::testing::{
|
||||
orchard::OrchardPoolTester, sapling::SaplingPoolTester,
|
||||
};
|
||||
|
||||
pub(crate) struct OrchardPoolTester;
|
||||
impl ShieldedPoolTester for OrchardPoolTester {
|
||||
const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Orchard;
|
||||
const TABLES_PREFIX: &'static str = ORCHARD_TABLES_PREFIX;
|
||||
// const MERKLE_TREE_DEPTH: u8 = {orchard::NOTE_COMMITMENT_TREE_DEPTH as u8};
|
||||
|
||||
type Sk = SpendingKey;
|
||||
type Fvk = FullViewingKey;
|
||||
type MerkleTreeHash = MerkleHashOrchard;
|
||||
type Note = orchard::note::Note;
|
||||
|
||||
fn test_account_fvk<Cache>(st: &TestState<Cache>) -> Self::Fvk {
|
||||
st.test_account_orchard().unwrap()
|
||||
}
|
||||
|
||||
fn usk_to_sk(usk: &UnifiedSpendingKey) -> &Self::Sk {
|
||||
usk.orchard()
|
||||
}
|
||||
|
||||
fn sk(seed: &[u8]) -> Self::Sk {
|
||||
let mut account = zip32::AccountId::ZERO;
|
||||
loop {
|
||||
if let Ok(sk) = SpendingKey::from_zip32_seed(seed, 1, account) {
|
||||
break sk;
|
||||
}
|
||||
account = account.next().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn sk_to_fvk(sk: &Self::Sk) -> Self::Fvk {
|
||||
sk.into()
|
||||
}
|
||||
|
||||
fn sk_default_address(sk: &Self::Sk) -> Address {
|
||||
Self::fvk_default_address(&Self::sk_to_fvk(sk))
|
||||
}
|
||||
|
||||
fn fvk_default_address(fvk: &Self::Fvk) -> Address {
|
||||
UnifiedAddress::from_receivers(
|
||||
Some(fvk.address_at(0u32, zip32::Scope::External)),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
|
||||
fn fvks_equal(a: &Self::Fvk, b: &Self::Fvk) -> bool {
|
||||
a == b
|
||||
}
|
||||
|
||||
fn empty_tree_leaf() -> Self::MerkleTreeHash {
|
||||
MerkleHashOrchard::empty_leaf()
|
||||
}
|
||||
|
||||
fn empty_tree_root(level: Level) -> Self::MerkleTreeHash {
|
||||
MerkleHashOrchard::empty_root(level)
|
||||
}
|
||||
|
||||
fn put_subtree_roots<Cache>(
|
||||
st: &mut TestState<Cache>,
|
||||
start_index: u64,
|
||||
roots: &[CommitmentTreeRoot<Self::MerkleTreeHash>],
|
||||
) -> Result<(), ShardTreeError<commitment_tree::Error>> {
|
||||
st.wallet_mut()
|
||||
.put_orchard_subtree_roots(start_index, roots)
|
||||
}
|
||||
|
||||
fn next_subtree_index(s: &WalletSummary<crate::AccountId>) -> u64 {
|
||||
s.next_orchard_subtree_index()
|
||||
}
|
||||
|
||||
fn select_spendable_notes<Cache>(
|
||||
st: &TestState<Cache>,
|
||||
account: crate::AccountId,
|
||||
target_value: zcash_protocol::value::Zatoshis,
|
||||
anchor_height: BlockHeight,
|
||||
exclude: &[crate::ReceivedNoteId],
|
||||
) -> Result<Vec<ReceivedNote<crate::ReceivedNoteId, orchard::note::Note>>, SqliteClientError>
|
||||
{
|
||||
select_spendable_orchard_notes(
|
||||
&st.wallet().conn,
|
||||
&st.wallet().params,
|
||||
account,
|
||||
target_value,
|
||||
anchor_height,
|
||||
exclude,
|
||||
)
|
||||
}
|
||||
|
||||
fn decrypted_pool_outputs_count(
|
||||
d_tx: &DecryptedTransaction<'_, crate::AccountId>,
|
||||
) -> usize {
|
||||
d_tx.orchard_outputs().len()
|
||||
}
|
||||
|
||||
fn with_decrypted_pool_memos(
|
||||
d_tx: &DecryptedTransaction<'_, crate::AccountId>,
|
||||
mut f: impl FnMut(&MemoBytes),
|
||||
) {
|
||||
for output in d_tx.orchard_outputs() {
|
||||
f(output.memo());
|
||||
}
|
||||
}
|
||||
|
||||
fn try_output_recovery<Cache>(
|
||||
_: &TestState<Cache>,
|
||||
_: BlockHeight,
|
||||
tx: &Transaction,
|
||||
fvk: &Self::Fvk,
|
||||
) -> Result<Option<(Note, Address, MemoBytes)>, OutputRecoveryError> {
|
||||
for action in tx.orchard_bundle().unwrap().actions() {
|
||||
// Find the output that decrypts with the external OVK
|
||||
let result = try_output_recovery_with_ovk(
|
||||
&OrchardDomain::for_action(action),
|
||||
&fvk.to_ovk(zip32::Scope::External),
|
||||
action,
|
||||
action.cv_net(),
|
||||
&action.encrypted_note().out_ciphertext,
|
||||
);
|
||||
|
||||
if result.is_some() {
|
||||
return Ok(result.map(|(note, addr, memo)| {
|
||||
(
|
||||
Note::Orchard(note),
|
||||
UnifiedAddress::from_receivers(Some(addr), None, None)
|
||||
.unwrap()
|
||||
.into(),
|
||||
MemoBytes::from_bytes(&memo).expect("correct length"),
|
||||
)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn received_note_count(
|
||||
summary: &zcash_client_backend::data_api::chain::ScanSummary,
|
||||
) -> usize {
|
||||
summary.received_orchard_note_count()
|
||||
}
|
||||
}
|
||||
use crate::testing::{self};
|
||||
|
||||
#[test]
|
||||
fn send_single_step_proposed_transfer() {
|
||||
|
|
|
@ -400,175 +400,12 @@ pub(crate) fn put_received_note<T: ReceivedSaplingOutput>(
|
|||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use incrementalmerkletree::{Hashable, Level};
|
||||
use shardtree::error::ShardTreeError;
|
||||
use zcash_proofs::prover::LocalTxProver;
|
||||
use zcash_client_backend::data_api::testing::sapling::SaplingPoolTester;
|
||||
|
||||
use sapling::{
|
||||
self,
|
||||
note_encryption::try_sapling_output_recovery,
|
||||
prover::{OutputProver, SpendProver},
|
||||
zip32::{DiversifiableFullViewingKey, ExtendedSpendingKey},
|
||||
};
|
||||
use zcash_primitives::{
|
||||
consensus::BlockHeight,
|
||||
memo::MemoBytes,
|
||||
transaction::{
|
||||
components::{amount::NonNegativeAmount, sapling::zip212_enforcement},
|
||||
Transaction,
|
||||
},
|
||||
zip32::Scope,
|
||||
};
|
||||
use crate::testing;
|
||||
|
||||
use zcash_client_backend::{
|
||||
address::Address,
|
||||
data_api::{
|
||||
chain::CommitmentTreeRoot, DecryptedTransaction, WalletCommitmentTrees, WalletSummary,
|
||||
},
|
||||
keys::UnifiedSpendingKey,
|
||||
wallet::{Note, ReceivedNote},
|
||||
ShieldedProtocol,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::SqliteClientError,
|
||||
testing::{
|
||||
self,
|
||||
pool::{OutputRecoveryError, ShieldedPoolTester},
|
||||
TestState,
|
||||
},
|
||||
wallet::{commitment_tree, sapling::select_spendable_sapling_notes},
|
||||
AccountId, ReceivedNoteId, SAPLING_TABLES_PREFIX,
|
||||
};
|
||||
|
||||
pub(crate) struct SaplingPoolTester;
|
||||
impl ShieldedPoolTester for SaplingPoolTester {
|
||||
const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Sapling;
|
||||
const TABLES_PREFIX: &'static str = SAPLING_TABLES_PREFIX;
|
||||
// const MERKLE_TREE_DEPTH: u8 = sapling::NOTE_COMMITMENT_TREE_DEPTH;
|
||||
|
||||
type Sk = ExtendedSpendingKey;
|
||||
type Fvk = DiversifiableFullViewingKey;
|
||||
type MerkleTreeHash = sapling::Node;
|
||||
type Note = sapling::Note;
|
||||
|
||||
fn test_account_fvk<Cache>(st: &TestState<Cache>) -> Self::Fvk {
|
||||
st.test_account_sapling().unwrap()
|
||||
}
|
||||
|
||||
fn usk_to_sk(usk: &UnifiedSpendingKey) -> &Self::Sk {
|
||||
usk.sapling()
|
||||
}
|
||||
|
||||
fn sk(seed: &[u8]) -> Self::Sk {
|
||||
ExtendedSpendingKey::master(seed)
|
||||
}
|
||||
|
||||
fn sk_to_fvk(sk: &Self::Sk) -> Self::Fvk {
|
||||
sk.to_diversifiable_full_viewing_key()
|
||||
}
|
||||
|
||||
fn sk_default_address(sk: &Self::Sk) -> Address {
|
||||
sk.default_address().1.into()
|
||||
}
|
||||
|
||||
fn fvk_default_address(fvk: &Self::Fvk) -> Address {
|
||||
fvk.default_address().1.into()
|
||||
}
|
||||
|
||||
fn fvks_equal(a: &Self::Fvk, b: &Self::Fvk) -> bool {
|
||||
a.to_bytes() == b.to_bytes()
|
||||
}
|
||||
|
||||
fn empty_tree_leaf() -> Self::MerkleTreeHash {
|
||||
sapling::Node::empty_leaf()
|
||||
}
|
||||
|
||||
fn empty_tree_root(level: Level) -> Self::MerkleTreeHash {
|
||||
sapling::Node::empty_root(level)
|
||||
}
|
||||
|
||||
fn put_subtree_roots<Cache>(
|
||||
st: &mut TestState<Cache>,
|
||||
start_index: u64,
|
||||
roots: &[CommitmentTreeRoot<Self::MerkleTreeHash>],
|
||||
) -> Result<(), ShardTreeError<commitment_tree::Error>> {
|
||||
st.wallet_mut()
|
||||
.put_sapling_subtree_roots(start_index, roots)
|
||||
}
|
||||
|
||||
fn next_subtree_index(s: &WalletSummary<AccountId>) -> u64 {
|
||||
s.next_sapling_subtree_index()
|
||||
}
|
||||
|
||||
fn select_spendable_notes<Cache>(
|
||||
st: &TestState<Cache>,
|
||||
account: AccountId,
|
||||
target_value: NonNegativeAmount,
|
||||
anchor_height: BlockHeight,
|
||||
exclude: &[ReceivedNoteId],
|
||||
) -> Result<Vec<ReceivedNote<ReceivedNoteId, Self::Note>>, SqliteClientError> {
|
||||
select_spendable_sapling_notes(
|
||||
&st.wallet().conn,
|
||||
&st.wallet().params,
|
||||
account,
|
||||
target_value,
|
||||
anchor_height,
|
||||
exclude,
|
||||
)
|
||||
}
|
||||
|
||||
fn decrypted_pool_outputs_count(d_tx: &DecryptedTransaction<'_, AccountId>) -> usize {
|
||||
d_tx.sapling_outputs().len()
|
||||
}
|
||||
|
||||
fn with_decrypted_pool_memos(
|
||||
d_tx: &DecryptedTransaction<'_, AccountId>,
|
||||
mut f: impl FnMut(&MemoBytes),
|
||||
) {
|
||||
for output in d_tx.sapling_outputs() {
|
||||
f(output.memo());
|
||||
}
|
||||
}
|
||||
|
||||
fn try_output_recovery<Cache>(
|
||||
st: &TestState<Cache>,
|
||||
height: BlockHeight,
|
||||
tx: &Transaction,
|
||||
fvk: &Self::Fvk,
|
||||
) -> Result<Option<(Note, Address, MemoBytes)>, OutputRecoveryError> {
|
||||
for output in tx.sapling_bundle().unwrap().shielded_outputs() {
|
||||
// Find the output that decrypts with the external OVK
|
||||
let result = try_sapling_output_recovery(
|
||||
&fvk.to_ovk(Scope::External),
|
||||
output,
|
||||
zip212_enforcement(&st.network(), height),
|
||||
);
|
||||
|
||||
if result.is_some() {
|
||||
return Ok(result.map(|(note, addr, memo)| {
|
||||
(
|
||||
Note::Sapling(note),
|
||||
addr.into(),
|
||||
MemoBytes::from_bytes(&memo).expect("correct length"),
|
||||
)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
LocalTxProver::bundled()
|
||||
}
|
||||
#[cfg(feature = "orchard")]
|
||||
use zcash_client_backend::data_api::testing::orchard::OrchardPoolTester;
|
||||
|
||||
#[test]
|
||||
fn send_single_step_proposed_transfer() {
|
||||
|
@ -662,40 +499,30 @@ pub(crate) mod tests {
|
|||
#[test]
|
||||
#[cfg(feature = "orchard")]
|
||||
fn pool_crossing_required() {
|
||||
use crate::wallet::orchard::tests::OrchardPoolTester;
|
||||
|
||||
testing::pool::pool_crossing_required::<SaplingPoolTester, OrchardPoolTester>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "orchard")]
|
||||
fn fully_funded_fully_private() {
|
||||
use crate::wallet::orchard::tests::OrchardPoolTester;
|
||||
|
||||
testing::pool::fully_funded_fully_private::<SaplingPoolTester, OrchardPoolTester>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(feature = "orchard", feature = "transparent-inputs"))]
|
||||
fn fully_funded_send_to_t() {
|
||||
use crate::wallet::orchard::tests::OrchardPoolTester;
|
||||
|
||||
testing::pool::fully_funded_send_to_t::<SaplingPoolTester, OrchardPoolTester>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "orchard")]
|
||||
fn multi_pool_checkpoint() {
|
||||
use crate::wallet::orchard::tests::OrchardPoolTester;
|
||||
|
||||
testing::pool::multi_pool_checkpoint::<SaplingPoolTester, OrchardPoolTester>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "orchard")]
|
||||
fn multi_pool_checkpoints_with_pruning() {
|
||||
use crate::wallet::orchard::tests::OrchardPoolTester;
|
||||
|
||||
testing::pool::multi_pool_checkpoints_with_pruning::<SaplingPoolTester, OrchardPoolTester>()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -587,6 +587,10 @@ pub(crate) mod tests {
|
|||
use zcash_client_backend::data_api::{
|
||||
chain::{ChainState, CommitmentTreeRoot},
|
||||
scanning::{spanning_tree::testing::scan_range, ScanPriority},
|
||||
testing::{
|
||||
pool::ShieldedPoolTester, sapling::SaplingPoolTester, AddressType, FakeCompactOutput,
|
||||
InitialChainState, TestBuilder, TestState,
|
||||
},
|
||||
AccountBirthday, Ratio, WalletRead, WalletWrite, SAPLING_SHARD_HEIGHT,
|
||||
};
|
||||
use zcash_primitives::{
|
||||
|
@ -594,28 +598,28 @@ pub(crate) mod tests {
|
|||
consensus::{BlockHeight, NetworkUpgrade, Parameters},
|
||||
transaction::components::amount::NonNegativeAmount,
|
||||
};
|
||||
use zcash_protocol::local_consensus::LocalNetwork;
|
||||
|
||||
use crate::{
|
||||
error::SqliteClientError,
|
||||
testing::{
|
||||
pool::ShieldedPoolTester, AddressType, BlockCache, FakeCompactOutput,
|
||||
InitialChainState, TestBuilder, TestState,
|
||||
},
|
||||
wallet::{
|
||||
sapling::tests::SaplingPoolTester,
|
||||
scanning::{insert_queue_entries, replace_queue_entries, suggest_scan_ranges},
|
||||
db::{TestDb, TestDbFactory},
|
||||
BlockCache,
|
||||
},
|
||||
wallet::scanning::{insert_queue_entries, replace_queue_entries, suggest_scan_ranges},
|
||||
VERIFY_LOOKAHEAD,
|
||||
};
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
use {
|
||||
crate::wallet::orchard::tests::OrchardPoolTester,
|
||||
incrementalmerkletree::Level,
|
||||
orchard::tree::MerkleHashOrchard,
|
||||
std::{convert::Infallible, num::NonZeroU32},
|
||||
zcash_client_backend::{
|
||||
data_api::{wallet::input_selection::GreedyInputSelector, WalletCommitmentTrees},
|
||||
data_api::{
|
||||
testing::orchard::OrchardPoolTester, wallet::input_selection::GreedyInputSelector,
|
||||
WalletCommitmentTrees,
|
||||
},
|
||||
fees::{standard, DustOutputPolicy},
|
||||
wallet::OvkPolicy,
|
||||
},
|
||||
|
@ -646,7 +650,8 @@ pub(crate) mod tests {
|
|||
let initial_height_offset = 310;
|
||||
|
||||
let mut st = TestBuilder::new()
|
||||
.with_block_cache()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.with_block_cache(BlockCache::new())
|
||||
.with_initial_chain_state(|rng, network| {
|
||||
let sapling_activation_height =
|
||||
network.activation_height(NetworkUpgrade::Sapling).unwrap();
|
||||
|
@ -728,7 +733,7 @@ pub(crate) mod tests {
|
|||
// Verify the that adjacent range needed to make the note spendable has been prioritized.
|
||||
let sap_active = u32::from(sapling_activation_height);
|
||||
assert_matches!(
|
||||
st.wallet().suggest_scan_ranges(),
|
||||
suggest_scan_ranges(st.wallet().conn(), Historic),
|
||||
Ok(scan_ranges) if scan_ranges == vec![
|
||||
scan_range((sap_active + 300)..(sap_active + 310), FoundNote)
|
||||
]
|
||||
|
@ -736,7 +741,7 @@ pub(crate) mod tests {
|
|||
|
||||
// Check that the scanned range has been properly persisted.
|
||||
assert_matches!(
|
||||
suggest_scan_ranges(&st.wallet().conn, Scanned),
|
||||
suggest_scan_ranges(st.wallet().conn(), Scanned),
|
||||
Ok(scan_ranges) if scan_ranges == vec![
|
||||
scan_range((sap_active + 300)..(sap_active + 310), FoundNote),
|
||||
scan_range((sap_active + 310)..(sap_active + 320), Scanned)
|
||||
|
@ -754,7 +759,7 @@ pub(crate) mod tests {
|
|||
// Check the scan range again, we should see a `ChainTip` range for the period we've been
|
||||
// offline.
|
||||
assert_matches!(
|
||||
st.wallet().suggest_scan_ranges(),
|
||||
suggest_scan_ranges(st.wallet().conn(), Historic),
|
||||
Ok(scan_ranges) if scan_ranges == vec![
|
||||
scan_range((sap_active + 320)..(sap_active + 341), ChainTip),
|
||||
scan_range((sap_active + 300)..(sap_active + 310), ChainTip)
|
||||
|
@ -771,7 +776,7 @@ pub(crate) mod tests {
|
|||
// Check the scan range again, we should see a `Validate` range for the previous wallet
|
||||
// tip, and then a `ChainTip` for the remaining range.
|
||||
assert_matches!(
|
||||
st.wallet().suggest_scan_ranges(),
|
||||
suggest_scan_ranges(st.wallet().conn(), Historic),
|
||||
Ok(scan_ranges) if scan_ranges == vec![
|
||||
scan_range((sap_active + 320)..(sap_active + 330), Verify),
|
||||
scan_range((sap_active + 330)..(sap_active + 451), ChainTip),
|
||||
|
@ -805,9 +810,15 @@ pub(crate) mod tests {
|
|||
birthday_offset: u32,
|
||||
prior_block_hash: BlockHash,
|
||||
insert_prior_roots: bool,
|
||||
) -> (TestState<BlockCache>, T::Fvk, AccountBirthday, u32) {
|
||||
) -> (
|
||||
TestState<BlockCache, TestDb, LocalNetwork>,
|
||||
T::Fvk,
|
||||
AccountBirthday,
|
||||
u32,
|
||||
) {
|
||||
let st = TestBuilder::new()
|
||||
.with_block_cache()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.with_block_cache(BlockCache::new())
|
||||
.with_initial_chain_state(|rng, network| {
|
||||
// We set the Sapling and Orchard frontiers at the birthday height to be
|
||||
// 1234 notes into the second shard.
|
||||
|
@ -892,7 +903,7 @@ pub(crate) mod tests {
|
|||
// The range up to the wallet's birthday height is ignored.
|
||||
scan_range(sap_active..birthday_height, Ignored),
|
||||
];
|
||||
let actual = suggest_scan_ranges(&st.wallet().conn, Ignored).unwrap();
|
||||
let actual = suggest_scan_ranges(st.wallet().conn(), Ignored).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
|
@ -900,7 +911,10 @@ pub(crate) mod tests {
|
|||
fn update_chain_tip_before_create_account() {
|
||||
use ScanPriority::*;
|
||||
|
||||
let mut st = TestBuilder::new().with_block_cache().build();
|
||||
let mut st = TestBuilder::new()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.with_block_cache(BlockCache::new())
|
||||
.build();
|
||||
let sap_active = st.sapling_activation_height();
|
||||
|
||||
// Update the chain tip.
|
||||
|
@ -912,7 +926,7 @@ pub(crate) mod tests {
|
|||
// The range up to the chain end is ignored.
|
||||
scan_range(sap_active.into()..chain_end, Ignored),
|
||||
];
|
||||
let actual = suggest_scan_ranges(&st.wallet().conn, Ignored).unwrap();
|
||||
let actual = suggest_scan_ranges(st.wallet().conn(), Ignored).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
// Now add an account.
|
||||
|
@ -933,7 +947,7 @@ pub(crate) mod tests {
|
|||
// The range up to the wallet's birthday height is ignored.
|
||||
scan_range(sap_active.into()..wallet_birthday.into(), Ignored),
|
||||
];
|
||||
let actual = suggest_scan_ranges(&st.wallet().conn, Ignored).unwrap();
|
||||
let actual = suggest_scan_ranges(st.wallet().conn(), Ignored).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
|
@ -978,7 +992,7 @@ pub(crate) mod tests {
|
|||
scan_range(sap_active..wallet_birthday, Ignored),
|
||||
];
|
||||
|
||||
let actual = suggest_scan_ranges(&st.wallet().conn, Ignored).unwrap();
|
||||
let actual = suggest_scan_ranges(st.wallet().conn(), Ignored).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
|
@ -1022,7 +1036,7 @@ pub(crate) mod tests {
|
|||
scan_range(sap_active..birthday.height().into(), Ignored),
|
||||
];
|
||||
|
||||
let actual = suggest_scan_ranges(&st.wallet().conn, Ignored).unwrap();
|
||||
let actual = suggest_scan_ranges(st.wallet().conn(), Ignored).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
|
@ -1051,7 +1065,8 @@ pub(crate) mod tests {
|
|||
// notes beyond the end of the first shard.
|
||||
let frontier_tree_size: u32 = (0x1 << 16) + 1234;
|
||||
let mut st = TestBuilder::new()
|
||||
.with_block_cache()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.with_block_cache(BlockCache::new())
|
||||
.with_initial_chain_state(|rng, network| {
|
||||
let birthday_height =
|
||||
network.activation_height(NetworkUpgrade::Nu5).unwrap() + birthday_offset;
|
||||
|
@ -1123,7 +1138,7 @@ pub(crate) mod tests {
|
|||
),
|
||||
pre_birthday_range.clone(),
|
||||
];
|
||||
let actual = suggest_scan_ranges(&st.wallet().conn, Ignored).unwrap();
|
||||
let actual = suggest_scan_ranges(st.wallet().conn(), Ignored).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
// Simulate that in the blocks between the wallet birthday and the max_scanned height,
|
||||
|
@ -1154,7 +1169,7 @@ pub(crate) mod tests {
|
|||
pre_birthday_range.clone(),
|
||||
];
|
||||
|
||||
let actual = suggest_scan_ranges(&st.wallet().conn, Ignored).unwrap();
|
||||
let actual = suggest_scan_ranges(st.wallet().conn(), Ignored).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
// Now simulate shutting down, and then restarting 90 blocks later, after a shard
|
||||
|
@ -1180,7 +1195,7 @@ pub(crate) mod tests {
|
|||
.unwrap();
|
||||
|
||||
// Just inserting the subtree roots doesn't affect the scan ranges.
|
||||
let actual = suggest_scan_ranges(&st.wallet().conn, Ignored).unwrap();
|
||||
let actual = suggest_scan_ranges(st.wallet().conn(), Ignored).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let new_tip = last_shard_start + 20;
|
||||
|
@ -1213,7 +1228,7 @@ pub(crate) mod tests {
|
|||
pre_birthday_range,
|
||||
];
|
||||
|
||||
let actual = suggest_scan_ranges(&st.wallet().conn, Ignored).unwrap();
|
||||
let actual = suggest_scan_ranges(st.wallet().conn(), Ignored).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
|
@ -1243,7 +1258,8 @@ pub(crate) mod tests {
|
|||
// notes beyond the end of the first shard.
|
||||
let frontier_tree_size: u32 = (0x1 << 16) + 1234;
|
||||
let mut st = TestBuilder::new()
|
||||
.with_block_cache()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.with_block_cache(BlockCache::new())
|
||||
.with_initial_chain_state(|rng, network| {
|
||||
let birthday_height =
|
||||
network.activation_height(NetworkUpgrade::Nu5).unwrap() + birthday_offset;
|
||||
|
@ -1313,7 +1329,7 @@ pub(crate) mod tests {
|
|||
scan_range(sap_active.into()..birthday.height().into(), Ignored),
|
||||
];
|
||||
|
||||
let actual = suggest_scan_ranges(&st.wallet().conn, Ignored).unwrap();
|
||||
let actual = suggest_scan_ranges(st.wallet().conn(), Ignored).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
// Simulate that in the blocks between the wallet birthday and the max_scanned height,
|
||||
|
@ -1366,6 +1382,7 @@ pub(crate) mod tests {
|
|||
{
|
||||
let mut shard_stmt = st
|
||||
.wallet_mut()
|
||||
.db_mut()
|
||||
.conn
|
||||
.prepare("SELECT shard_index, subtree_end_height FROM sapling_tree_shards")
|
||||
.unwrap();
|
||||
|
@ -1381,6 +1398,7 @@ pub(crate) mod tests {
|
|||
{
|
||||
let mut shard_stmt = st
|
||||
.wallet_mut()
|
||||
.db_mut()
|
||||
.conn
|
||||
.prepare("SELECT shard_index, subtree_end_height FROM orchard_tree_shards")
|
||||
.unwrap();
|
||||
|
@ -1409,7 +1427,7 @@ pub(crate) mod tests {
|
|||
scan_range(sap_active.into()..birthday.height().into(), Ignored),
|
||||
];
|
||||
|
||||
let actual = suggest_scan_ranges(&st.wallet().conn, Ignored).unwrap();
|
||||
let actual = suggest_scan_ranges(st.wallet().conn(), Ignored).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
// We've crossed a subtree boundary, but only in one pool. We still only have one scanned
|
||||
|
@ -1427,7 +1445,9 @@ pub(crate) mod tests {
|
|||
fn replace_queue_entries_merges_previous_range() {
|
||||
use ScanPriority::*;
|
||||
|
||||
let mut st = TestBuilder::new().build();
|
||||
let mut st = TestBuilder::new()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.build();
|
||||
|
||||
let ranges = vec![
|
||||
scan_range(150..200, ChainTip),
|
||||
|
@ -1436,16 +1456,16 @@ pub(crate) mod tests {
|
|||
];
|
||||
|
||||
{
|
||||
let tx = st.wallet_mut().conn.transaction().unwrap();
|
||||
let tx = st.wallet_mut().conn_mut().transaction().unwrap();
|
||||
insert_queue_entries(&tx, ranges.iter()).unwrap();
|
||||
tx.commit().unwrap();
|
||||
}
|
||||
|
||||
let actual = suggest_scan_ranges(&st.wallet().conn, Ignored).unwrap();
|
||||
let actual = suggest_scan_ranges(st.wallet().conn(), Ignored).unwrap();
|
||||
assert_eq!(actual, ranges);
|
||||
|
||||
{
|
||||
let tx = st.wallet_mut().conn.transaction().unwrap();
|
||||
let tx = st.wallet_mut().conn_mut().transaction().unwrap();
|
||||
replace_queue_entries::<SqliteClientError>(
|
||||
&tx,
|
||||
&(BlockHeight::from(150)..BlockHeight::from(160)),
|
||||
|
@ -1462,7 +1482,7 @@ pub(crate) mod tests {
|
|||
scan_range(0..100, Ignored),
|
||||
];
|
||||
|
||||
let actual = suggest_scan_ranges(&st.wallet().conn, Ignored).unwrap();
|
||||
let actual = suggest_scan_ranges(st.wallet().conn(), Ignored).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
|
@ -1470,7 +1490,9 @@ pub(crate) mod tests {
|
|||
fn replace_queue_entries_merges_subsequent_range() {
|
||||
use ScanPriority::*;
|
||||
|
||||
let mut st = TestBuilder::new().build();
|
||||
let mut st = TestBuilder::new()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.build();
|
||||
|
||||
let ranges = vec![
|
||||
scan_range(150..200, ChainTip),
|
||||
|
@ -1479,16 +1501,16 @@ pub(crate) mod tests {
|
|||
];
|
||||
|
||||
{
|
||||
let tx = st.wallet_mut().conn.transaction().unwrap();
|
||||
let tx = st.wallet_mut().conn_mut().transaction().unwrap();
|
||||
insert_queue_entries(&tx, ranges.iter()).unwrap();
|
||||
tx.commit().unwrap();
|
||||
}
|
||||
|
||||
let actual = suggest_scan_ranges(&st.wallet().conn, Ignored).unwrap();
|
||||
let actual = suggest_scan_ranges(st.wallet().conn(), Ignored).unwrap();
|
||||
assert_eq!(actual, ranges);
|
||||
|
||||
{
|
||||
let tx = st.wallet_mut().conn.transaction().unwrap();
|
||||
let tx = st.wallet_mut().conn_mut().transaction().unwrap();
|
||||
replace_queue_entries::<SqliteClientError>(
|
||||
&tx,
|
||||
&(BlockHeight::from(90)..BlockHeight::from(100)),
|
||||
|
@ -1505,7 +1527,7 @@ pub(crate) mod tests {
|
|||
scan_range(0..90, Ignored),
|
||||
];
|
||||
|
||||
let actual = suggest_scan_ranges(&st.wallet().conn, Ignored).unwrap();
|
||||
let actual = suggest_scan_ranges(st.wallet().conn(), Ignored).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
|
@ -1534,14 +1556,15 @@ pub(crate) mod tests {
|
|||
#[cfg(feature = "orchard")]
|
||||
fn prepare_orchard_block_spanning_test(
|
||||
with_birthday_subtree_root: bool,
|
||||
) -> TestState<BlockCache> {
|
||||
) -> TestState<BlockCache, TestDb, LocalNetwork> {
|
||||
let birthday_nu5_offset = 5000;
|
||||
let birthday_prior_block_hash = BlockHash([0; 32]);
|
||||
// We set the Sapling and Orchard frontiers at the birthday block initial state to 50
|
||||
// notes back from the end of the second shard.
|
||||
let birthday_tree_size: u32 = (0x1 << 17) - 50;
|
||||
let mut st = TestBuilder::new()
|
||||
.with_block_cache()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.with_block_cache(BlockCache::new())
|
||||
.with_initial_chain_state(|rng, network| {
|
||||
let birthday_height =
|
||||
network.activation_height(NetworkUpgrade::Nu5).unwrap() + birthday_nu5_offset;
|
||||
|
@ -1674,6 +1697,8 @@ pub(crate) mod tests {
|
|||
#[test]
|
||||
#[cfg(feature = "orchard")]
|
||||
fn orchard_block_spanning_tip_boundary_complete() {
|
||||
use zcash_client_backend::data_api::Account as _;
|
||||
|
||||
let mut st = prepare_orchard_block_spanning_test(true);
|
||||
let account = st.test_account().cloned().unwrap();
|
||||
let birthday = account.birthday();
|
||||
|
@ -1701,27 +1726,24 @@ pub(crate) mod tests {
|
|||
),
|
||||
];
|
||||
|
||||
let actual = suggest_scan_ranges(&st.wallet().conn, ScanPriority::Ignored).unwrap();
|
||||
let actual = suggest_scan_ranges(st.wallet().conn(), ScanPriority::Ignored).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
// Scan the chain-tip range.
|
||||
st.scan_cached_blocks(birthday.height() + 12, 112);
|
||||
|
||||
// We haven't yet discovered our note, so balances should still be zero
|
||||
assert_eq!(
|
||||
st.get_total_balance(account.account_id()),
|
||||
NonNegativeAmount::ZERO
|
||||
);
|
||||
assert_eq!(st.get_total_balance(account.id()), NonNegativeAmount::ZERO);
|
||||
|
||||
// Now scan the historic range; this should discover our note, which should now be
|
||||
// spendable.
|
||||
st.scan_cached_blocks(birthday.height(), 12);
|
||||
assert_eq!(
|
||||
st.get_total_balance(account.account_id()),
|
||||
st.get_total_balance(account.id()),
|
||||
NonNegativeAmount::const_from_u64(100000)
|
||||
);
|
||||
assert_eq!(
|
||||
st.get_spendable_balance(account.account_id(), 10),
|
||||
st.get_spendable_balance(account.id(), 10),
|
||||
NonNegativeAmount::const_from_u64(100000)
|
||||
);
|
||||
|
||||
|
@ -1729,7 +1751,7 @@ pub(crate) mod tests {
|
|||
let to_extsk = OrchardPoolTester::sk(&[0xf5; 32]);
|
||||
let to = OrchardPoolTester::sk_default_address(&to_extsk);
|
||||
let request = zip321::TransactionRequest::new(vec![zip321::Payment::without_memo(
|
||||
to.to_zcash_address(&st.network()),
|
||||
to.to_zcash_address(st.network()),
|
||||
NonNegativeAmount::const_from_u64(10000),
|
||||
)])
|
||||
.unwrap();
|
||||
|
@ -1747,7 +1769,7 @@ pub(crate) mod tests {
|
|||
|
||||
let proposal = st
|
||||
.propose_transfer(
|
||||
account.account_id(),
|
||||
account.id(),
|
||||
input_selector,
|
||||
request,
|
||||
NonZeroU32::new(10).unwrap(),
|
||||
|
@ -1767,6 +1789,8 @@ pub(crate) mod tests {
|
|||
#[test]
|
||||
#[cfg(feature = "orchard")]
|
||||
fn orchard_block_spanning_tip_boundary_incomplete() {
|
||||
use zcash_client_backend::data_api::Account as _;
|
||||
|
||||
let mut st = prepare_orchard_block_spanning_test(false);
|
||||
let account = st.test_account().cloned().unwrap();
|
||||
let birthday = account.birthday();
|
||||
|
@ -1790,27 +1814,24 @@ pub(crate) mod tests {
|
|||
),
|
||||
];
|
||||
|
||||
let actual = suggest_scan_ranges(&st.wallet().conn, ScanPriority::Ignored).unwrap();
|
||||
let actual = suggest_scan_ranges(st.wallet().conn(), ScanPriority::Ignored).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
// Scan the chain-tip range, but omitting the spanning block.
|
||||
st.scan_cached_blocks(birthday.height() + 13, 112);
|
||||
|
||||
// We haven't yet discovered our note, so balances should still be zero
|
||||
assert_eq!(
|
||||
st.get_total_balance(account.account_id()),
|
||||
NonNegativeAmount::ZERO
|
||||
);
|
||||
assert_eq!(st.get_total_balance(account.id()), NonNegativeAmount::ZERO);
|
||||
|
||||
// Now scan the historic range; this should discover our note but not
|
||||
// complete the tree. The note should not be considered spendable.
|
||||
st.scan_cached_blocks(birthday.height(), 12);
|
||||
assert_eq!(
|
||||
st.get_total_balance(account.account_id()),
|
||||
st.get_total_balance(account.id()),
|
||||
NonNegativeAmount::const_from_u64(100000)
|
||||
);
|
||||
assert_eq!(
|
||||
st.get_spendable_balance(account.account_id(), 10),
|
||||
st.get_spendable_balance(account.id(), 10),
|
||||
NonNegativeAmount::ZERO
|
||||
);
|
||||
|
||||
|
@ -1818,7 +1839,7 @@ pub(crate) mod tests {
|
|||
let to_extsk = OrchardPoolTester::sk(&[0xf5; 32]);
|
||||
let to = OrchardPoolTester::sk_default_address(&to_extsk);
|
||||
let request = zip321::TransactionRequest::new(vec![zip321::Payment::without_memo(
|
||||
to.to_zcash_address(&st.network()),
|
||||
to.to_zcash_address(st.network()),
|
||||
NonNegativeAmount::const_from_u64(10000),
|
||||
)])
|
||||
.unwrap();
|
||||
|
@ -1835,7 +1856,7 @@ pub(crate) mod tests {
|
|||
&GreedyInputSelector::new(change_strategy, DustOutputPolicy::default());
|
||||
|
||||
let proposal = st.propose_transfer(
|
||||
account.account_id(),
|
||||
account.id(),
|
||||
input_selector,
|
||||
request.clone(),
|
||||
NonZeroU32::new(10).unwrap(),
|
||||
|
@ -1848,7 +1869,7 @@ pub(crate) mod tests {
|
|||
|
||||
// Verify that it's now possible to create the proposal
|
||||
let proposal = st.propose_transfer(
|
||||
account.account_id(),
|
||||
account.id(),
|
||||
input_selector,
|
||||
request,
|
||||
NonZeroU32::new(10).unwrap(),
|
||||
|
|
|
@ -822,11 +822,17 @@ pub(crate) fn queue_transparent_spend_detection<P: consensus::Parameters>(
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::testing::{AddressType, TestBuilder, TestState};
|
||||
use crate::testing::{
|
||||
db::{TestDb, TestDbFactory},
|
||||
BlockCache,
|
||||
};
|
||||
|
||||
use sapling::zip32::ExtendedSpendingKey;
|
||||
use zcash_client_backend::{
|
||||
data_api::{
|
||||
wallet::input_selection::GreedyInputSelector, InputSource, WalletRead, WalletWrite,
|
||||
testing::{AddressType, TestBuilder, TestState},
|
||||
wallet::input_selection::GreedyInputSelector,
|
||||
Account as _, InputSource, WalletRead, WalletWrite,
|
||||
},
|
||||
encoding::AddressCodec,
|
||||
fees::{fixed, DustOutputPolicy},
|
||||
|
@ -842,14 +848,13 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn put_received_transparent_utxo() {
|
||||
use crate::testing::TestBuilder;
|
||||
|
||||
let mut st = TestBuilder::new()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.with_account_from_sapling_activation(BlockHash([0; 32]))
|
||||
.build();
|
||||
|
||||
let birthday = st.test_account().unwrap().birthday().height();
|
||||
let account_id = st.test_account().unwrap().account_id();
|
||||
let account_id = st.test_account().unwrap().id();
|
||||
let uaddr = st
|
||||
.wallet()
|
||||
.get_current_address(account_id)
|
||||
|
@ -933,10 +938,10 @@ mod tests {
|
|||
// Artificially delete the address from the addresses table so that
|
||||
// we can ensure the update fails if the join doesn't work.
|
||||
st.wallet()
|
||||
.conn
|
||||
.conn()
|
||||
.execute(
|
||||
"DELETE FROM addresses WHERE cached_transparent_receiver_address = ?",
|
||||
[Some(taddr.encode(&st.wallet().params))],
|
||||
[Some(taddr.encode(st.network()))],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
@ -949,14 +954,15 @@ mod tests {
|
|||
use zcash_client_backend::ShieldedProtocol;
|
||||
|
||||
let mut st = TestBuilder::new()
|
||||
.with_block_cache()
|
||||
.with_data_store_factory(TestDbFactory)
|
||||
.with_block_cache(BlockCache::new())
|
||||
.with_account_from_sapling_activation(BlockHash([0; 32]))
|
||||
.build();
|
||||
|
||||
let account = st.test_account().cloned().unwrap();
|
||||
let uaddr = st
|
||||
.wallet()
|
||||
.get_current_address(account.account_id())
|
||||
.get_current_address(account.id())
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let taddr = uaddr.transparent().unwrap();
|
||||
|
@ -971,17 +977,14 @@ mod tests {
|
|||
}
|
||||
st.scan_cached_blocks(start_height, 10);
|
||||
|
||||
let check_balance = |st: &TestState<_>, min_confirmations: u32, expected| {
|
||||
let check_balance = |st: &TestState<_, TestDb, _>, min_confirmations: u32, expected| {
|
||||
// Check the wallet summary returns the expected transparent balance.
|
||||
let summary = st
|
||||
.wallet()
|
||||
.get_wallet_summary(min_confirmations)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let balance = summary
|
||||
.account_balances()
|
||||
.get(&account.account_id())
|
||||
.unwrap();
|
||||
let balance = summary.account_balances().get(&account.id()).unwrap();
|
||||
// TODO: in the future, we will distinguish between available and total
|
||||
// balance according to `min_confirmations`
|
||||
assert_eq!(balance.unshielded(), expected);
|
||||
|
@ -990,7 +993,7 @@ mod tests {
|
|||
let mempool_height = st.wallet().chain_height().unwrap().unwrap() + 1;
|
||||
assert_eq!(
|
||||
st.wallet()
|
||||
.get_transparent_balances(account.account_id(), mempool_height)
|
||||
.get_transparent_balances(account.id(), mempool_height)
|
||||
.unwrap()
|
||||
.get(taddr)
|
||||
.cloned()
|
||||
|
|
Loading…
Reference in New Issue