zcash_client_backend/
data_api.rs

1//! # Utilities for Zcash wallet construction
2//!
3//! This module defines a set of APIs for wallet data persistence, and provides a suite of methods
4//! based upon these APIs that can be used to implement a fully functional Zcash wallet. At
5//! present, the interfaces provided here are built primarily around the use of a source of
6//! [`CompactBlock`] data such as the Zcash Light Client Protocol as defined in
7//! [ZIP 307](https://zips.z.cash/zip-0307) but they may be generalized to full-block use cases in
8//! the future.
9//!
10//! ## Important Concepts
11//!
12//! There are several important operations that a Zcash wallet must perform that distinguish Zcash
13//! wallet design from wallets for other cryptocurrencies.
14//!
15//! * Viewing Keys: Wallets based upon this module are built around the capabilities of Zcash
16//!   [`UnifiedFullViewingKey`]s; the wallet backend provides no facilities for the storage
17//!   of spending keys, and spending keys must be provided by the caller in order to perform
18//!   transaction creation operations.
19//! * Blockchain Scanning: A Zcash wallet must download and trial-decrypt each transaction on the
20//!   Zcash blockchain using one or more Viewing Keys in order to find new shielded transaction
21//!   outputs (generally termed "notes") belonging to the wallet. The primary entrypoint for this
22//!   functionality is the [`scan_cached_blocks`] method. See the [`chain`] module for additional
23//!   details.
24//! * Witness Updates: In order to spend a shielded note, the wallet must be able to compute the
25//!   Merkle path to that note in the global note commitment tree. When [`scan_cached_blocks`] is
26//!   used to process a range of blocks, the note commitment tree is updated with the note
27//!   commitments for the blocks in that range.
28//! * Transaction Construction: The [`wallet`] module provides functions for creating Zcash
29//!   transactions that spend funds belonging to the wallet.
30//!
31//! ## Core Traits
32//!
33//! The utility functions described above depend upon four important traits defined in this
34//! module, which between them encompass the data storage requirements of a light wallet.
35//! The relevant traits are [`InputSource`], [`WalletRead`], [`WalletWrite`], and
36//! [`WalletCommitmentTrees`]. A complete implementation of the data storage layer for a wallet
37//! will include an implementation of all four of these traits. See the [`zcash_client_sqlite`]
38//! crate for a complete example of the implementation of these traits.
39//!
40//! ## Accounts
41//!
42//! The operation of the [`InputSource`], [`WalletRead`] and [`WalletWrite`] traits is built around
43//! the concept of a wallet having one or more accounts, with a unique `AccountId` for each
44//! account.
45//!
46//! An account identifier corresponds to at most a single [`UnifiedSpendingKey`]'s worth of spend
47//! authority, with the received and spent notes of that account tracked via the corresponding
48//! [`UnifiedFullViewingKey`]. Both received notes and change spendable by that spending authority
49//! (both the external and internal parts of that key, as defined by
50//! [ZIP 316](https://zips.z.cash/zip-0316)) will be interpreted as belonging to that account.
51//!
52//! [`CompactBlock`]: crate::proto::compact_formats::CompactBlock
53//! [`scan_cached_blocks`]: crate::data_api::chain::scan_cached_blocks
54//! [`zcash_client_sqlite`]: https://crates.io/crates/zcash_client_sqlite
55//! [`TransactionRequest`]: crate::zip321::TransactionRequest
56//! [`propose_shielding`]: crate::data_api::wallet::propose_shielding
57
58use nonempty::NonEmpty;
59use secrecy::SecretVec;
60use std::{
61    collections::HashMap,
62    fmt::Debug,
63    hash::Hash,
64    io,
65    num::{NonZeroU32, TryFromIntError},
66};
67
68use incrementalmerkletree::{frontier::Frontier, Retention};
69use shardtree::{error::ShardTreeError, store::ShardStore, ShardTree};
70
71use zcash_keys::{
72    address::{Address, UnifiedAddress},
73    keys::{
74        UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedIncomingViewingKey, UnifiedSpendingKey,
75    },
76};
77use zcash_primitives::{block::BlockHash, transaction::Transaction};
78use zcash_protocol::{
79    consensus::BlockHeight,
80    memo::{Memo, MemoBytes},
81    value::{BalanceError, Zatoshis},
82    ShieldedProtocol, TxId,
83};
84use zip32::{fingerprint::SeedFingerprint, DiversifierIndex};
85
86use self::{
87    chain::{ChainState, CommitmentTreeRoot},
88    scanning::ScanRange,
89};
90use crate::{
91    decrypt::DecryptedOutput,
92    proto::service::TreeState,
93    wallet::{Note, NoteId, ReceivedNote, Recipient, WalletTransparentOutput, WalletTx},
94};
95
96#[cfg(feature = "transparent-inputs")]
97use {
98    crate::wallet::TransparentAddressMetadata,
99    std::ops::Range,
100    std::time::SystemTime,
101    transparent::{
102        address::TransparentAddress,
103        bundle::OutPoint,
104        keys::{NonHardenedChildIndex, TransparentKeyScope},
105    },
106};
107
108#[cfg(feature = "test-dependencies")]
109use ambassador::delegatable_trait;
110
111#[cfg(any(test, feature = "test-dependencies"))]
112use zcash_protocol::consensus::NetworkUpgrade;
113
114pub mod chain;
115pub mod error;
116pub mod scanning;
117pub mod wallet;
118
119#[cfg(any(test, feature = "test-dependencies"))]
120pub mod testing;
121
122/// The height of subtree roots in the Sapling note commitment tree.
123///
124/// This conforms to the structure of subtree data returned by
125/// `lightwalletd` when using the `GetSubtreeRoots` GRPC call.
126pub const SAPLING_SHARD_HEIGHT: u8 = sapling::NOTE_COMMITMENT_TREE_DEPTH / 2;
127
128/// The height of subtree roots in the Orchard note commitment tree.
129///
130/// This conforms to the structure of subtree data returned by
131/// `lightwalletd` when using the `GetSubtreeRoots` GRPC call.
132#[cfg(feature = "orchard")]
133pub const ORCHARD_SHARD_HEIGHT: u8 = { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 } / 2;
134
135/// An enumeration of constraints that can be applied when querying for nullifiers for notes
136/// belonging to the wallet.
137pub enum NullifierQuery {
138    Unspent,
139    All,
140}
141
142/// Balance information for a value within a single pool in an account.
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub struct Balance {
145    spendable_value: Zatoshis,
146    change_pending_confirmation: Zatoshis,
147    value_pending_spendability: Zatoshis,
148}
149
150impl Balance {
151    /// The [`Balance`] value having zero values for all its fields.
152    pub const ZERO: Self = Self {
153        spendable_value: Zatoshis::ZERO,
154        change_pending_confirmation: Zatoshis::ZERO,
155        value_pending_spendability: Zatoshis::ZERO,
156    };
157
158    fn check_total_adding(&self, value: Zatoshis) -> Result<Zatoshis, BalanceError> {
159        (self.spendable_value
160            + self.change_pending_confirmation
161            + self.value_pending_spendability
162            + value)
163            .ok_or(BalanceError::Overflow)
164    }
165
166    /// Returns the value in the account that may currently be spent; it is possible to compute
167    /// witnesses for all the notes that comprise this value, and all of this value is confirmed to
168    /// the required confirmation depth.
169    pub fn spendable_value(&self) -> Zatoshis {
170        self.spendable_value
171    }
172
173    /// Adds the specified value to the spendable total, checking for overflow.
174    pub fn add_spendable_value(&mut self, value: Zatoshis) -> Result<(), BalanceError> {
175        self.check_total_adding(value)?;
176        self.spendable_value = (self.spendable_value + value).unwrap();
177        Ok(())
178    }
179
180    /// Returns the value in the account of shielded change notes that do not yet have sufficient
181    /// confirmations to be spendable.
182    pub fn change_pending_confirmation(&self) -> Zatoshis {
183        self.change_pending_confirmation
184    }
185
186    /// Adds the specified value to the pending change total, checking for overflow.
187    pub fn add_pending_change_value(&mut self, value: Zatoshis) -> Result<(), BalanceError> {
188        self.check_total_adding(value)?;
189        self.change_pending_confirmation = (self.change_pending_confirmation + value).unwrap();
190        Ok(())
191    }
192
193    /// Returns the value in the account of all remaining received notes that either do not have
194    /// sufficient confirmations to be spendable, or for which witnesses cannot yet be constructed
195    /// without additional scanning.
196    pub fn value_pending_spendability(&self) -> Zatoshis {
197        self.value_pending_spendability
198    }
199
200    /// Adds the specified value to the pending spendable total, checking for overflow.
201    pub fn add_pending_spendable_value(&mut self, value: Zatoshis) -> Result<(), BalanceError> {
202        self.check_total_adding(value)?;
203        self.value_pending_spendability = (self.value_pending_spendability + value).unwrap();
204        Ok(())
205    }
206
207    /// Returns the total value of funds represented by this [`Balance`].
208    pub fn total(&self) -> Zatoshis {
209        (self.spendable_value + self.change_pending_confirmation + self.value_pending_spendability)
210            .expect("Balance cannot overflow MAX_MONEY")
211    }
212}
213
214/// Balance information for a single account. The sum of this struct's fields is the total balance
215/// of the wallet.
216#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217pub struct AccountBalance {
218    /// The value of unspent Sapling outputs belonging to the account.
219    sapling_balance: Balance,
220
221    /// The value of unspent Orchard outputs belonging to the account.
222    orchard_balance: Balance,
223
224    /// The value of all unspent transparent outputs belonging to the account.
225    unshielded_balance: Balance,
226}
227
228impl AccountBalance {
229    /// The [`Balance`] value having zero values for all its fields.
230    pub const ZERO: Self = Self {
231        sapling_balance: Balance::ZERO,
232        orchard_balance: Balance::ZERO,
233        unshielded_balance: Balance::ZERO,
234    };
235
236    fn check_total(&self) -> Result<Zatoshis, BalanceError> {
237        (self.sapling_balance.total()
238            + self.orchard_balance.total()
239            + self.unshielded_balance.total())
240        .ok_or(BalanceError::Overflow)
241    }
242
243    /// Returns the [`Balance`] of Sapling funds in the account.
244    pub fn sapling_balance(&self) -> &Balance {
245        &self.sapling_balance
246    }
247
248    /// Provides a mutable reference to the [`Balance`] of Sapling funds in the account
249    /// to the specified callback, checking invariants after the callback's action has been
250    /// evaluated.
251    pub fn with_sapling_balance_mut<A, E: From<BalanceError>>(
252        &mut self,
253        f: impl FnOnce(&mut Balance) -> Result<A, E>,
254    ) -> Result<A, E> {
255        let result = f(&mut self.sapling_balance)?;
256        self.check_total()?;
257        Ok(result)
258    }
259
260    /// Returns the [`Balance`] of Orchard funds in the account.
261    pub fn orchard_balance(&self) -> &Balance {
262        &self.orchard_balance
263    }
264
265    /// Provides a mutable reference to the [`Balance`] of Orchard funds in the account
266    /// to the specified callback, checking invariants after the callback's action has been
267    /// evaluated.
268    pub fn with_orchard_balance_mut<A, E: From<BalanceError>>(
269        &mut self,
270        f: impl FnOnce(&mut Balance) -> Result<A, E>,
271    ) -> Result<A, E> {
272        let result = f(&mut self.orchard_balance)?;
273        self.check_total()?;
274        Ok(result)
275    }
276
277    /// Returns the total value of unspent transparent transaction outputs belonging to the wallet.
278    #[deprecated(
279        note = "this function is deprecated. Please use [`AccountBalance::unshielded_balance`] instead."
280    )]
281    pub fn unshielded(&self) -> Zatoshis {
282        self.unshielded_balance.total()
283    }
284
285    /// Returns the [`Balance`] of unshielded funds in the account.
286    pub fn unshielded_balance(&self) -> &Balance {
287        &self.unshielded_balance
288    }
289
290    /// Provides a mutable reference to the [`Balance`] of transparent funds in the account
291    /// to the specified callback, checking invariants after the callback's action has been
292    /// evaluated.
293    pub fn with_unshielded_balance_mut<A, E: From<BalanceError>>(
294        &mut self,
295        f: impl FnOnce(&mut Balance) -> Result<A, E>,
296    ) -> Result<A, E> {
297        let result = f(&mut self.unshielded_balance)?;
298        self.check_total()?;
299        Ok(result)
300    }
301
302    /// Returns the total value of funds belonging to the account.
303    pub fn total(&self) -> Zatoshis {
304        (self.sapling_balance.total()
305            + self.orchard_balance.total()
306            + self.unshielded_balance.total())
307        .expect("Account balance cannot overflow MAX_MONEY")
308    }
309
310    /// Returns the total value of shielded (Sapling and Orchard) funds that may immediately be
311    /// spent.
312    pub fn spendable_value(&self) -> Zatoshis {
313        (self.sapling_balance.spendable_value + self.orchard_balance.spendable_value)
314            .expect("Account balance cannot overflow MAX_MONEY")
315    }
316
317    /// Returns the total value of change and/or shielding transaction outputs that are awaiting
318    /// sufficient confirmations for spendability.
319    pub fn change_pending_confirmation(&self) -> Zatoshis {
320        (self.sapling_balance.change_pending_confirmation
321            + self.orchard_balance.change_pending_confirmation)
322            .expect("Account balance cannot overflow MAX_MONEY")
323    }
324
325    /// Returns the value of shielded funds that are not yet spendable because additional scanning
326    /// is required before it will be possible to derive witnesses for the associated notes.
327    pub fn value_pending_spendability(&self) -> Zatoshis {
328        (self.sapling_balance.value_pending_spendability
329            + self.orchard_balance.value_pending_spendability)
330            .expect("Account balance cannot overflow MAX_MONEY")
331    }
332}
333
334/// Source metadata for a ZIP 32-derived key.
335#[derive(Clone, Debug, PartialEq, Eq, Hash)]
336pub struct Zip32Derivation {
337    seed_fingerprint: SeedFingerprint,
338    account_index: zip32::AccountId,
339}
340
341impl Zip32Derivation {
342    /// Constructs new derivation metadata from its constituent parts.
343    pub fn new(seed_fingerprint: SeedFingerprint, account_index: zip32::AccountId) -> Self {
344        Self {
345            seed_fingerprint,
346            account_index,
347        }
348    }
349
350    /// Returns the seed fingerprint.
351    pub fn seed_fingerprint(&self) -> &SeedFingerprint {
352        &self.seed_fingerprint
353    }
354
355    /// Returns the account-level index in the ZIP 32 derivation path.
356    pub fn account_index(&self) -> zip32::AccountId {
357        self.account_index
358    }
359}
360
361/// An enumeration used to control what information is tracked by the wallet for
362/// notes received by a given account.
363#[derive(Clone, Debug, PartialEq, Eq, Hash)]
364pub enum AccountPurpose {
365    /// For spending accounts, the wallet will track information needed to spend
366    /// received notes.
367    Spending { derivation: Option<Zip32Derivation> },
368    /// For view-only accounts, the wallet will not track spend information.
369    ViewOnly,
370}
371
372/// The kinds of accounts supported by `zcash_client_backend`.
373#[derive(Clone, Debug, PartialEq, Eq, Hash)]
374pub enum AccountSource {
375    /// An account derived from a known seed.
376    Derived {
377        derivation: Zip32Derivation,
378        key_source: Option<String>,
379    },
380
381    /// An account imported from a viewing key.
382    Imported {
383        purpose: AccountPurpose,
384        key_source: Option<String>,
385    },
386}
387
388impl AccountSource {
389    /// Returns the key derivation metadata for the account source, if any is available.
390    pub fn key_derivation(&self) -> Option<&Zip32Derivation> {
391        match self {
392            AccountSource::Derived { derivation, .. } => Some(derivation),
393            AccountSource::Imported {
394                purpose: AccountPurpose::Spending { derivation },
395                ..
396            } => derivation.as_ref(),
397            _ => None,
398        }
399    }
400
401    /// Returns the application-level key source identifier.
402    pub fn key_source(&self) -> Option<&str> {
403        match self {
404            AccountSource::Derived { key_source, .. } => key_source.as_ref().map(|s| s.as_str()),
405            AccountSource::Imported { key_source, .. } => key_source.as_ref().map(|s| s.as_str()),
406        }
407    }
408}
409
410/// A set of capabilities that a client account must provide.
411pub trait Account {
412    type AccountId: Copy;
413
414    /// Returns the unique identifier for the account.
415    fn id(&self) -> Self::AccountId;
416
417    /// Returns the human-readable name for the account, if any has been configured.
418    fn name(&self) -> Option<&str>;
419
420    /// Returns whether this account is derived or imported, and the derivation parameters
421    /// if applicable.
422    fn source(&self) -> &AccountSource;
423
424    /// Returns whether the account is a spending account or a view-only account.
425    fn purpose(&self) -> AccountPurpose {
426        match self.source() {
427            AccountSource::Derived { derivation, .. } => AccountPurpose::Spending {
428                derivation: Some(derivation.clone()),
429            },
430            AccountSource::Imported { purpose, .. } => purpose.clone(),
431        }
432    }
433
434    /// Returns the UFVK that the wallet backend has stored for the account, if any.
435    ///
436    /// Accounts for which this returns `None` cannot be used in wallet contexts, because
437    /// they are unable to maintain an accurate balance.
438    fn ufvk(&self) -> Option<&UnifiedFullViewingKey>;
439
440    /// Returns the UIVK that the wallet backend has stored for the account.
441    ///
442    /// All accounts are required to have at least an incoming viewing key. This gives no
443    /// indication about whether an account can be used in a wallet context; for that, use
444    /// [`Account::ufvk`].
445    fn uivk(&self) -> UnifiedIncomingViewingKey;
446}
447
448#[cfg(any(test, feature = "test-dependencies"))]
449impl<A: Copy> Account for (A, UnifiedFullViewingKey) {
450    type AccountId = A;
451
452    fn id(&self) -> A {
453        self.0
454    }
455
456    fn source(&self) -> &AccountSource {
457        &AccountSource::Imported {
458            purpose: AccountPurpose::ViewOnly,
459            key_source: None,
460        }
461    }
462
463    fn ufvk(&self) -> Option<&UnifiedFullViewingKey> {
464        Some(&self.1)
465    }
466
467    fn uivk(&self) -> UnifiedIncomingViewingKey {
468        self.1.to_unified_incoming_viewing_key()
469    }
470
471    fn name(&self) -> Option<&str> {
472        None
473    }
474}
475
476#[cfg(any(test, feature = "test-dependencies"))]
477impl<A: Copy> Account for (A, UnifiedIncomingViewingKey) {
478    type AccountId = A;
479
480    fn id(&self) -> A {
481        self.0
482    }
483
484    fn name(&self) -> Option<&str> {
485        None
486    }
487
488    fn source(&self) -> &AccountSource {
489        &AccountSource::Imported {
490            purpose: AccountPurpose::ViewOnly,
491            key_source: None,
492        }
493    }
494
495    fn ufvk(&self) -> Option<&UnifiedFullViewingKey> {
496        None
497    }
498
499    fn uivk(&self) -> UnifiedIncomingViewingKey {
500        self.1.clone()
501    }
502}
503
504/// Information about an address in the wallet.
505pub struct AddressInfo {
506    address: Address,
507    diversifier_index: DiversifierIndex,
508    #[cfg(feature = "transparent-inputs")]
509    transparent_key_scope: Option<TransparentKeyScope>,
510}
511
512impl AddressInfo {
513    /// Constructs an `AddressInfo` from its constituent parts.
514    pub fn from_parts(
515        address: Address,
516        diversifier_index: DiversifierIndex,
517        #[cfg(feature = "transparent-inputs")] transparent_key_scope: Option<TransparentKeyScope>,
518    ) -> Option<Self> {
519        // Only allow `transparent_key_scope` to be set for transparent addresses.
520        #[cfg(feature = "transparent-inputs")]
521        let valid = transparent_key_scope.is_none()
522            || matches!(address, Address::Transparent(_) | Address::Tex(_));
523        #[cfg(not(feature = "transparent-inputs"))]
524        let valid = true;
525
526        valid.then_some(Self {
527            address,
528            diversifier_index,
529            #[cfg(feature = "transparent-inputs")]
530            transparent_key_scope,
531        })
532    }
533
534    /// Returns the address this information is about.
535    pub fn address(&self) -> &Address {
536        &self.address
537    }
538
539    /// Returns the diversifier index the address was derived at.
540    pub fn diversifier_index(&self) -> DiversifierIndex {
541        self.diversifier_index
542    }
543
544    /// Returns the key scope if this is a transparent address.
545    #[cfg(feature = "transparent-inputs")]
546    pub fn transparent_key_scope(&self) -> Option<TransparentKeyScope> {
547        self.transparent_key_scope
548    }
549}
550
551/// A polymorphic ratio type, usually used for rational numbers.
552#[derive(Clone, Copy, Debug, PartialEq, Eq)]
553pub struct Ratio<T> {
554    numerator: T,
555    denominator: T,
556}
557
558impl<T> Ratio<T> {
559    /// Constructs a new Ratio from a numerator and a denominator.
560    pub fn new(numerator: T, denominator: T) -> Self {
561        Self {
562            numerator,
563            denominator,
564        }
565    }
566
567    /// Returns the numerator of the ratio.
568    pub fn numerator(&self) -> &T {
569        &self.numerator
570    }
571
572    /// Returns the denominator of the ratio.
573    pub fn denominator(&self) -> &T {
574        &self.denominator
575    }
576}
577
578/// A type representing the progress the wallet has made toward detecting all of the funds
579/// belonging to the wallet.
580///
581/// The window over which progress is computed spans from the wallet's birthday to the current
582/// chain tip. It is divided into two regions, the "Scan Window" which covers the region from the
583/// wallet recovery height to the current chain tip, and the "Recovery Window" which covers the
584/// range from the wallet birthday to the wallet recovery height. If no wallet recovery height is
585/// available, the scan window will cover the entire range from the wallet birthday to the chain
586/// tip.
587///
588/// Progress for both scanning and recovery is represented in terms of the ratio between notes
589/// scanned and the total number of notes added to the chain in the relevant window. This ratio
590/// should only be used to compute progress percentages for display, and the numerator and
591/// denominator should not be treated as authoritative note counts. In the case that there are no
592/// notes in a given block range, the denominator of these values will be zero, so callers should always
593/// use checked division when converting the resulting values to percentages.
594#[derive(Debug, Clone, Copy, PartialEq, Eq)]
595pub struct Progress {
596    scan: Ratio<u64>,
597    recovery: Option<Ratio<u64>>,
598}
599
600impl Progress {
601    /// Constructs a new progress value from its constituent parts.
602    pub fn new(scan: Ratio<u64>, recovery: Option<Ratio<u64>>) -> Self {
603        Self { scan, recovery }
604    }
605
606    /// Returns the progress the wallet has made in scanning blocks for shielded notes belonging to
607    /// the wallet between the wallet recovery height (or the wallet birthday if no recovery height
608    /// is set) and the chain tip.
609    pub fn scan(&self) -> Ratio<u64> {
610        self.scan
611    }
612
613    /// Returns the progress the wallet has made in scanning blocks for shielded notes belonging to
614    /// the wallet between the wallet birthday and the block height at which recovery from seed was
615    /// initiated.
616    ///
617    /// Returns `None` if no recovery height is set for the wallet.
618    pub fn recovery(&self) -> Option<Ratio<u64>> {
619        self.recovery
620    }
621}
622
623/// A type representing the potentially-spendable value of unspent outputs in the wallet.
624///
625/// The balances reported using this data structure may overestimate the total spendable value of
626/// the wallet, in the case that the spend of a previously received shielded note has not yet been
627/// detected by the process of scanning the chain. The balances reported using this data structure
628/// can only be certain to be unspent in the case that [`Self::is_synced`] is true, and even in
629/// this circumstance it is possible that a newly created transaction could conflict with a
630/// not-yet-mined transaction in the mempool.
631#[derive(Debug, Clone, PartialEq, Eq)]
632pub struct WalletSummary<AccountId: Eq + Hash> {
633    account_balances: HashMap<AccountId, AccountBalance>,
634    chain_tip_height: BlockHeight,
635    fully_scanned_height: BlockHeight,
636    progress: Progress,
637    next_sapling_subtree_index: u64,
638    #[cfg(feature = "orchard")]
639    next_orchard_subtree_index: u64,
640}
641
642impl<AccountId: Eq + Hash> WalletSummary<AccountId> {
643    /// Constructs a new [`WalletSummary`] from its constituent parts.
644    pub fn new(
645        account_balances: HashMap<AccountId, AccountBalance>,
646        chain_tip_height: BlockHeight,
647        fully_scanned_height: BlockHeight,
648        progress: Progress,
649        next_sapling_subtree_index: u64,
650        #[cfg(feature = "orchard")] next_orchard_subtree_index: u64,
651    ) -> Self {
652        Self {
653            account_balances,
654            chain_tip_height,
655            fully_scanned_height,
656            progress,
657            next_sapling_subtree_index,
658            #[cfg(feature = "orchard")]
659            next_orchard_subtree_index,
660        }
661    }
662
663    /// Returns the balances of accounts in the wallet, keyed by account ID.
664    pub fn account_balances(&self) -> &HashMap<AccountId, AccountBalance> {
665        &self.account_balances
666    }
667
668    /// Returns the height of the current chain tip.
669    pub fn chain_tip_height(&self) -> BlockHeight {
670        self.chain_tip_height
671    }
672
673    /// Returns the height below which all blocks have been scanned by the wallet, ignoring blocks
674    /// below the wallet birthday.
675    pub fn fully_scanned_height(&self) -> BlockHeight {
676        self.fully_scanned_height
677    }
678
679    /// Returns the progress of scanning the chain to bring the wallet up to date.
680    ///
681    /// This progress metric is intended as an indicator of how close the wallet is to
682    /// general usability, including the ability to spend existing funds that were
683    /// previously spendable.
684    ///
685    /// The window over which progress is computed spans from the wallet's birthday to the current
686    /// chain tip. It is divided into two segments: a "recovery" segment, between the wallet
687    /// birthday and the recovery height (currently the height at which recovery from seed was
688    /// initiated, but how this boundary is computed may change in the future), and a "scan"
689    /// segment, between the recovery height and the current chain tip.
690    ///
691    /// When converting the ratios returned here to percentages, checked division must be used in
692    /// order to avoid divide-by-zero errors. A zero denominator in a returned ratio indicates that
693    /// there are no shielded notes to be scanned in the associated block range.
694    pub fn progress(&self) -> Progress {
695        self.progress
696    }
697
698    /// Returns the Sapling subtree index that should start the next range of subtree
699    /// roots passed to [`WalletCommitmentTrees::put_sapling_subtree_roots`].
700    pub fn next_sapling_subtree_index(&self) -> u64 {
701        self.next_sapling_subtree_index
702    }
703
704    /// Returns the Orchard subtree index that should start the next range of subtree
705    /// roots passed to [`WalletCommitmentTrees::put_orchard_subtree_roots`].
706    #[cfg(feature = "orchard")]
707    pub fn next_orchard_subtree_index(&self) -> u64 {
708        self.next_orchard_subtree_index
709    }
710
711    /// Returns whether or not wallet scanning is complete.
712    pub fn is_synced(&self) -> bool {
713        self.chain_tip_height == self.fully_scanned_height
714    }
715}
716
717/// A predicate that can be used to choose whether or not a particular note is retained in note
718/// selection.
719pub trait NoteRetention<NoteRef> {
720    /// Returns whether the specified Sapling note should be retained.
721    fn should_retain_sapling(&self, note: &ReceivedNote<NoteRef, sapling::Note>) -> bool;
722    /// Returns whether the specified Orchard note should be retained.
723    #[cfg(feature = "orchard")]
724    fn should_retain_orchard(&self, note: &ReceivedNote<NoteRef, orchard::note::Note>) -> bool;
725}
726
727pub(crate) struct SimpleNoteRetention {
728    pub(crate) sapling: bool,
729    #[cfg(feature = "orchard")]
730    pub(crate) orchard: bool,
731}
732
733impl<NoteRef> NoteRetention<NoteRef> for SimpleNoteRetention {
734    fn should_retain_sapling(&self, _: &ReceivedNote<NoteRef, sapling::Note>) -> bool {
735        self.sapling
736    }
737
738    #[cfg(feature = "orchard")]
739    fn should_retain_orchard(&self, _: &ReceivedNote<NoteRef, orchard::note::Note>) -> bool {
740        self.orchard
741    }
742}
743
744/// Spendable shielded outputs controlled by the wallet.
745pub struct SpendableNotes<NoteRef> {
746    sapling: Vec<ReceivedNote<NoteRef, sapling::Note>>,
747    #[cfg(feature = "orchard")]
748    orchard: Vec<ReceivedNote<NoteRef, orchard::note::Note>>,
749}
750
751/// A type describing the mined-ness of transactions that should be returned in response to a
752/// [`TransactionDataRequest`].
753#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
754#[cfg(feature = "transparent-inputs")]
755pub enum TransactionStatusFilter {
756    /// Only mined transactions should be returned.
757    Mined,
758    /// Only mempool transactions should be returned.
759    Mempool,
760    /// Both mined transactions and transactions in the mempool should be returned.
761    All,
762}
763
764/// A type used to filter transactions to be returned in response to a [`TransactionDataRequest`],
765/// in terms of the spentness of the transaction's transparent outputs.
766#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
767#[cfg(feature = "transparent-inputs")]
768pub enum OutputStatusFilter {
769    /// Only transactions that have currently-unspent transparent outputs should be returned.
770    Unspent,
771    /// All transactions corresponding to the data request should be returned, irrespective of
772    /// whether or not those transactions produce transparent outputs that are currently unspent.
773    All,
774}
775
776/// A request for transaction data enhancement, spentness check, or discovery
777/// of spends from a given transparent address within a specific block range.
778#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
779pub enum TransactionDataRequest {
780    /// Information about the chain's view of a transaction is requested.
781    ///
782    /// The caller evaluating this request on behalf of the wallet backend should respond to this
783    /// request by determining the status of the specified transaction with respect to the main
784    /// chain; if using `lightwalletd` for access to chain data, this may be obtained by
785    /// interpreting the results of the [`GetTransaction`] RPC method. It should then call
786    /// [`WalletWrite::set_transaction_status`] to provide the resulting transaction status
787    /// information to the wallet backend.
788    ///
789    /// [`GetTransaction`]: crate::proto::service::compact_tx_streamer_client::CompactTxStreamerClient::get_transaction
790    GetStatus(TxId),
791    /// Transaction enhancement (download of complete raw transaction data) is requested.
792    ///
793    /// The caller evaluating this request on behalf of the wallet backend should respond to this
794    /// request by providing complete data for the specified transaction to
795    /// [`wallet::decrypt_and_store_transaction`]; if using `lightwalletd` for access to chain
796    /// state, this may be obtained via the [`GetTransaction`] RPC method. If no data is available
797    /// for the specified transaction, this should be reported to the backend using
798    /// [`WalletWrite::set_transaction_status`]. A [`TransactionDataRequest::Enhancement`] request
799    /// subsumes any previously existing [`TransactionDataRequest::GetStatus`] request.
800    ///
801    /// [`GetTransaction`]: crate::proto::service::compact_tx_streamer_client::CompactTxStreamerClient::get_transaction
802    Enhancement(TxId),
803    /// Information about transactions that receive or spend funds belonging to the specified
804    /// transparent address is requested.
805    ///
806    /// Fully transparent transactions, and transactions that do not contain either shielded inputs
807    /// or shielded outputs belonging to the wallet, may not be discovered by the process of chain
808    /// scanning; as a consequence, the wallet must actively query to find transactions that spend
809    /// such funds. Ideally we'd be able to query by [`OutPoint`] but this is not currently
810    /// functionality that is supported by the light wallet server.
811    ///
812    /// The caller evaluating this request on behalf of the wallet backend should respond to this
813    /// request by detecting transactions involving the specified address within the provided block
814    /// range; if using `lightwalletd` for access to chain data, this may be performed using the
815    /// [`GetTaddressTxids`] RPC method. It should then call [`wallet::decrypt_and_store_transaction`]
816    /// for each transaction so detected.
817    ///
818    /// [`GetTaddressTxids`]: crate::proto::service::compact_tx_streamer_client::CompactTxStreamerClient::get_taddress_txids
819    #[cfg(feature = "transparent-inputs")]
820    TransactionsInvolvingAddress {
821        /// The address to request transactions and/or UTXOs for.
822        address: TransparentAddress,
823        /// Only transactions mined at heights greater than or equal to this height should be
824        /// returned.
825        block_range_start: BlockHeight,
826        /// Only transactions mined at heights less than this height should be returned.
827        block_range_end: Option<BlockHeight>,
828        /// If a `request_at` time is set, the caller evaluating this request should attempt to
829        /// retrieve transaction data related to the specified address at a time that is as close
830        /// as practical to the specified instant, and in a fashion that decorrelates this request
831        /// to a light wallet server from other requests made by the same caller.
832        ///
833        /// This may be ignored by callers that are able to satisfy the request without exposing
834        /// correlations between addresses to untrusted parties; for example, a wallet application
835        /// that uses a private, trusted-for-privacy supplier of chain data can safely ignore this
836        /// field.
837        request_at: Option<SystemTime>,
838        /// The caller should respond to this request only with transactions that conform to the
839        /// specified transaction status filter.
840        tx_status_filter: TransactionStatusFilter,
841        /// The caller should respond to this request only with transactions containing outputs
842        /// that conform to the specified output status filter.
843        output_status_filter: OutputStatusFilter,
844    },
845}
846
847/// Metadata about the status of a transaction obtained by inspecting the chain state.
848#[derive(Clone, Copy, Debug)]
849pub enum TransactionStatus {
850    /// The requested transaction ID was not recognized by the node.
851    TxidNotRecognized,
852    /// The requested transaction ID corresponds to a transaction that is recognized by the node,
853    /// but is in the mempool or is otherwise not mined in the main chain (but may have been mined
854    /// on a fork that was reorged away).
855    NotInMainChain,
856    /// The requested transaction ID corresponds to a transaction that has been included in the
857    /// block at the provided height.
858    Mined(BlockHeight),
859}
860
861impl<NoteRef> SpendableNotes<NoteRef> {
862    /// Construct a new empty [`SpendableNotes`].
863    pub fn empty() -> Self {
864        Self::new(
865            vec![],
866            #[cfg(feature = "orchard")]
867            vec![],
868        )
869    }
870
871    /// Construct a new [`SpendableNotes`] from its constituent parts.
872    pub fn new(
873        sapling: Vec<ReceivedNote<NoteRef, sapling::Note>>,
874        #[cfg(feature = "orchard")] orchard: Vec<ReceivedNote<NoteRef, orchard::note::Note>>,
875    ) -> Self {
876        Self {
877            sapling,
878            #[cfg(feature = "orchard")]
879            orchard,
880        }
881    }
882
883    /// Returns the set of spendable Sapling notes.
884    pub fn sapling(&self) -> &[ReceivedNote<NoteRef, sapling::Note>] {
885        self.sapling.as_ref()
886    }
887
888    /// Consumes this value and returns the Sapling notes contained within it.
889    pub fn take_sapling(self) -> Vec<ReceivedNote<NoteRef, sapling::Note>> {
890        self.sapling
891    }
892
893    /// Returns the set of spendable Orchard notes.
894    #[cfg(feature = "orchard")]
895    pub fn orchard(&self) -> &[ReceivedNote<NoteRef, orchard::note::Note>] {
896        self.orchard.as_ref()
897    }
898
899    /// Consumes this value and returns the Orchard notes contained within it.
900    #[cfg(feature = "orchard")]
901    pub fn take_orchard(self) -> Vec<ReceivedNote<NoteRef, orchard::note::Note>> {
902        self.orchard
903    }
904
905    /// Computes the total value of Sapling notes.
906    pub fn sapling_value(&self) -> Result<Zatoshis, BalanceError> {
907        self.sapling.iter().try_fold(Zatoshis::ZERO, |acc, n| {
908            (acc + n.note_value()?).ok_or(BalanceError::Overflow)
909        })
910    }
911
912    /// Computes the total value of Sapling notes.
913    #[cfg(feature = "orchard")]
914    pub fn orchard_value(&self) -> Result<Zatoshis, BalanceError> {
915        self.orchard.iter().try_fold(Zatoshis::ZERO, |acc, n| {
916            (acc + n.note_value()?).ok_or(BalanceError::Overflow)
917        })
918    }
919
920    /// Computes the total value of spendable inputs
921    pub fn total_value(&self) -> Result<Zatoshis, BalanceError> {
922        #[cfg(not(feature = "orchard"))]
923        return self.sapling_value();
924
925        #[cfg(feature = "orchard")]
926        return (self.sapling_value()? + self.orchard_value()?).ok_or(BalanceError::Overflow);
927    }
928
929    /// Consumes this [`SpendableNotes`] value and produces a vector of
930    /// [`ReceivedNote<NoteRef, Note>`] values.
931    pub fn into_vec(
932        self,
933        retention: &impl NoteRetention<NoteRef>,
934    ) -> Vec<ReceivedNote<NoteRef, Note>> {
935        let iter = self.sapling.into_iter().filter_map(|n| {
936            retention
937                .should_retain_sapling(&n)
938                .then(|| n.map_note(Note::Sapling))
939        });
940
941        #[cfg(feature = "orchard")]
942        let iter = iter.chain(self.orchard.into_iter().filter_map(|n| {
943            retention
944                .should_retain_orchard(&n)
945                .then(|| n.map_note(Note::Orchard))
946        }));
947
948        iter.collect()
949    }
950}
951
952/// Metadata about the structure of unspent outputs in a single pool within a wallet account.
953///
954/// This type is often used to represent a filtered view of outputs in the account that were
955/// selected according to the conditions imposed by a [`NoteFilter`].
956#[derive(Debug, Clone)]
957pub struct PoolMeta {
958    note_count: usize,
959    value: Zatoshis,
960}
961
962impl PoolMeta {
963    /// Constructs a new [`PoolMeta`] value from its constituent parts.
964    pub fn new(note_count: usize, value: Zatoshis) -> Self {
965        Self { note_count, value }
966    }
967
968    /// Returns the number of unspent outputs in the account, potentially selected in accordance
969    /// with some [`NoteFilter`].
970    pub fn note_count(&self) -> usize {
971        self.note_count
972    }
973
974    /// Returns the total value of unspent outputs in the account that are accounted for in
975    /// [`Self::note_count`].
976    pub fn value(&self) -> Zatoshis {
977        self.value
978    }
979}
980
981/// Metadata about the structure of the wallet for a particular account.
982///
983/// At present this just contains counts of unspent outputs in each pool, but it may be extended in
984/// the future to contain note values or other more detailed information about wallet structure.
985///
986/// Values of this type are intended to be used in selection of change output values. A value of
987/// this type may represent filtered data, and may therefore not count all of the unspent notes in
988/// the wallet.
989///
990/// A [`AccountMeta`] value is normally produced by querying the wallet database via passing a
991/// [`NoteFilter`] to [`InputSource::get_account_metadata`].
992#[derive(Debug, Clone)]
993pub struct AccountMeta {
994    sapling: Option<PoolMeta>,
995    orchard: Option<PoolMeta>,
996}
997
998impl AccountMeta {
999    /// Constructs a new [`AccountMeta`] value from its constituent parts.
1000    pub fn new(sapling: Option<PoolMeta>, orchard: Option<PoolMeta>) -> Self {
1001        Self { sapling, orchard }
1002    }
1003
1004    /// Returns metadata about Sapling notes belonging to the account for which this was generated.
1005    ///
1006    /// Returns [`None`] if no metadata is available or it was not possible to evaluate the query
1007    /// described by a [`NoteFilter`] given the available wallet data.
1008    pub fn sapling(&self) -> Option<&PoolMeta> {
1009        self.sapling.as_ref()
1010    }
1011
1012    /// Returns metadata about Orchard notes belonging to the account for which this was generated.
1013    ///
1014    /// Returns [`None`] if no metadata is available or it was not possible to evaluate the query
1015    /// described by a [`NoteFilter`] given the available wallet data.
1016    pub fn orchard(&self) -> Option<&PoolMeta> {
1017        self.orchard.as_ref()
1018    }
1019
1020    fn sapling_note_count(&self) -> Option<usize> {
1021        self.sapling.as_ref().map(|m| m.note_count)
1022    }
1023
1024    fn orchard_note_count(&self) -> Option<usize> {
1025        self.orchard.as_ref().map(|m| m.note_count)
1026    }
1027
1028    /// Returns the number of unspent notes in the wallet for the given shielded protocol.
1029    pub fn note_count(&self, protocol: ShieldedProtocol) -> Option<usize> {
1030        match protocol {
1031            ShieldedProtocol::Sapling => self.sapling_note_count(),
1032            ShieldedProtocol::Orchard => self.orchard_note_count(),
1033        }
1034    }
1035
1036    /// Returns the total number of unspent shielded notes belonging to the account for which this
1037    /// was generated.
1038    ///
1039    /// Returns [`None`] if no metadata is available or it was not possible to evaluate the query
1040    /// described by a [`NoteFilter`] given the available wallet data. If metadata is available
1041    /// only for a single pool, the metadata for that pool will be returned.
1042    pub fn total_note_count(&self) -> Option<usize> {
1043        let s = self.sapling_note_count();
1044        let o = self.orchard_note_count();
1045        s.zip(o).map(|(s, o)| s + o).or(s).or(o)
1046    }
1047
1048    fn sapling_value(&self) -> Option<Zatoshis> {
1049        self.sapling.as_ref().map(|m| m.value)
1050    }
1051
1052    fn orchard_value(&self) -> Option<Zatoshis> {
1053        self.orchard.as_ref().map(|m| m.value)
1054    }
1055
1056    /// Returns the total value of shielded notes represented by [`Self::total_note_count`]
1057    ///
1058    /// Returns [`None`] if no metadata is available or it was not possible to evaluate the query
1059    /// described by a [`NoteFilter`] given the available wallet data. If metadata is available
1060    /// only for a single pool, the metadata for that pool will be returned.
1061    pub fn total_value(&self) -> Option<Zatoshis> {
1062        let s = self.sapling_value();
1063        let o = self.orchard_value();
1064        s.zip(o)
1065            .map(|(s, o)| (s + o).expect("Does not overflow Zcash maximum value."))
1066            .or(s)
1067            .or(o)
1068    }
1069}
1070
1071/// A `u8` value in the range 0..=MAX
1072#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1073pub struct BoundedU8<const MAX: u8>(u8);
1074
1075impl<const MAX: u8> BoundedU8<MAX> {
1076    /// Creates a constant `BoundedU8` from a [`u8`] value.
1077    ///
1078    /// Panics: if the value is outside the range `0..=MAX`.
1079    pub const fn new_const(value: u8) -> Self {
1080        assert!(value <= MAX);
1081        Self(value)
1082    }
1083
1084    /// Creates a `BoundedU8` from a [`u8`] value.
1085    ///
1086    /// Returns `None` if the provided value is outside the range `0..=MAX`.
1087    pub fn new(value: u8) -> Option<Self> {
1088        if value <= MAX {
1089            Some(Self(value))
1090        } else {
1091            None
1092        }
1093    }
1094
1095    /// Returns the wrapped [`u8`] value.
1096    pub fn value(&self) -> u8 {
1097        self.0
1098    }
1099}
1100
1101impl<const MAX: u8> From<BoundedU8<MAX>> for u8 {
1102    fn from(value: BoundedU8<MAX>) -> Self {
1103        value.0
1104    }
1105}
1106
1107impl<const MAX: u8> From<BoundedU8<MAX>> for usize {
1108    fn from(value: BoundedU8<MAX>) -> Self {
1109        usize::from(value.0)
1110    }
1111}
1112
1113/// A small query language for filtering notes belonging to an account.
1114///
1115/// A filter described using this language is applied to notes individually. It is primarily
1116/// intended for retrieval of account metadata in service of making determinations for how to
1117/// allocate change notes, and is not currently intended for use in broader note selection
1118/// contexts.
1119#[derive(Clone, Debug, PartialEq, Eq)]
1120pub enum NoteFilter {
1121    /// Selects notes having value greater than or equal to the provided value.
1122    ExceedsMinValue(Zatoshis),
1123    /// Selects notes having value greater than or equal to approximately the n'th percentile of
1124    /// previously sent notes in the account, irrespective of pool. The wrapped value must be in
1125    /// the range `1..=99`. The value `n` is respected in a best-effort fashion; results are likely
1126    /// to be inaccurate if the account has not yet completed scanning or if insufficient send data
1127    /// is available to establish a distribution.
1128    // TODO: it might be worthwhile to add an optional parameter here that can be used to ignore
1129    // low-valued (test/memo-only) sends when constructing the distribution to be drawn from.
1130    ExceedsPriorSendPercentile(BoundedU8<99>),
1131    /// Selects notes having value greater than or equal to the specified percentage of the account
1132    /// balance across all shielded pools. The wrapped value must be in the range `1..=99`
1133    ExceedsBalancePercentage(BoundedU8<99>),
1134    /// A note will be selected if it satisfies both of the specified conditions.
1135    ///
1136    /// If it is not possible to evaluate one of the conditions (for example,
1137    /// [`NoteFilter::ExceedsPriorSendPercentile`] cannot be evaluated if no sends have been
1138    /// performed) then that condition will be ignored. If neither condition can be evaluated,
1139    /// then the entire condition cannot be evaluated.
1140    Combine(Box<NoteFilter>, Box<NoteFilter>),
1141    /// A note will be selected if it satisfies the first condition; if it is not possible to
1142    /// evaluate that condition (for example, [`NoteFilter::ExceedsPriorSendPercentile`] cannot
1143    /// be evaluated if no sends have been performed) then the second condition will be used for
1144    /// evaluation.
1145    Attempt {
1146        condition: Box<NoteFilter>,
1147        fallback: Box<NoteFilter>,
1148    },
1149}
1150
1151impl NoteFilter {
1152    /// Constructs a [`NoteFilter::Combine`] query node.
1153    pub fn combine(l: NoteFilter, r: NoteFilter) -> Self {
1154        Self::Combine(Box::new(l), Box::new(r))
1155    }
1156
1157    /// Constructs a [`NoteFilter::Attempt`] query node.
1158    pub fn attempt(condition: NoteFilter, fallback: NoteFilter) -> Self {
1159        Self::Attempt {
1160            condition: Box::new(condition),
1161            fallback: Box::new(fallback),
1162        }
1163    }
1164}
1165
1166/// A trait representing the capability to query a data store for unspent transaction outputs
1167/// belonging to a account.
1168#[cfg_attr(feature = "test-dependencies", delegatable_trait)]
1169pub trait InputSource {
1170    /// The type of errors produced by a wallet backend.
1171    type Error: Debug;
1172
1173    /// Backend-specific account identifier.
1174    ///
1175    /// An account identifier corresponds to at most a single unified spending key's worth of spend
1176    /// authority, such that both received notes and change spendable by that spending authority
1177    /// will be interpreted as belonging to that account. This might be a database identifier type
1178    /// or a UUID.
1179    type AccountId: Copy + Debug + Eq + Hash;
1180
1181    /// Backend-specific note identifier.
1182    ///
1183    /// For example, this might be a database identifier type or a UUID.
1184    type NoteRef: Copy + Debug + Eq + Ord;
1185
1186    /// Fetches a spendable note by indexing into a transaction's shielded outputs for the
1187    /// specified shielded protocol.
1188    ///
1189    /// Returns `Ok(None)` if the note is not known to belong to the wallet or if the note
1190    /// is not spendable.
1191    fn get_spendable_note(
1192        &self,
1193        txid: &TxId,
1194        protocol: ShieldedProtocol,
1195        index: u32,
1196    ) -> Result<Option<ReceivedNote<Self::NoteRef, Note>>, Self::Error>;
1197
1198    /// Returns a list of spendable notes sufficient to cover the specified target value, if
1199    /// possible. Only spendable notes corresponding to the specified shielded protocol will
1200    /// be included.
1201    fn select_spendable_notes(
1202        &self,
1203        account: Self::AccountId,
1204        target_value: Zatoshis,
1205        sources: &[ShieldedProtocol],
1206        anchor_height: BlockHeight,
1207        exclude: &[Self::NoteRef],
1208    ) -> Result<SpendableNotes<Self::NoteRef>, Self::Error>;
1209
1210    /// Returns metadata describing the structure of the wallet for the specified account.
1211    ///
1212    /// The returned metadata value must exclude:
1213    /// - spent notes;
1214    /// - unspent notes excluded by the provided selector;
1215    /// - unspent notes identified in the given `exclude` list.
1216    ///
1217    /// Implementations of this method may limit the complexity of supported queries. Such
1218    /// limitations should be clearly documented for the implementing type.
1219    fn get_account_metadata(
1220        &self,
1221        account: Self::AccountId,
1222        selector: &NoteFilter,
1223        exclude: &[Self::NoteRef],
1224    ) -> Result<AccountMeta, Self::Error>;
1225
1226    /// Fetches the transparent output corresponding to the provided `outpoint`.
1227    ///
1228    /// Returns `Ok(None)` if the UTXO is not known to belong to the wallet or is not
1229    /// spendable as of the chain tip height.
1230    #[cfg(feature = "transparent-inputs")]
1231    fn get_unspent_transparent_output(
1232        &self,
1233        _outpoint: &OutPoint,
1234    ) -> Result<Option<WalletTransparentOutput>, Self::Error> {
1235        Ok(None)
1236    }
1237
1238    /// Returns the list of spendable transparent outputs received by this wallet at `address`
1239    /// such that, at height `target_height`:
1240    /// * the transaction that produced the output had or will have at least `min_confirmations`
1241    ///   confirmations; and
1242    /// * the output is unspent as of the current chain tip.
1243    ///
1244    /// An output that is potentially spent by an unmined transaction in the mempool is excluded
1245    /// iff the spending transaction will not be expired at `target_height`.
1246    #[cfg(feature = "transparent-inputs")]
1247    fn get_spendable_transparent_outputs(
1248        &self,
1249        _address: &TransparentAddress,
1250        _target_height: BlockHeight,
1251        _min_confirmations: u32,
1252    ) -> Result<Vec<WalletTransparentOutput>, Self::Error> {
1253        Ok(vec![])
1254    }
1255}
1256
1257/// Read-only operations required for light wallet functions.
1258///
1259/// This trait defines the read-only portion of the storage interface atop which
1260/// higher-level wallet operations are implemented. It serves to allow wallet functions to
1261/// be abstracted away from any particular data storage substrate.
1262#[cfg_attr(feature = "test-dependencies", delegatable_trait)]
1263pub trait WalletRead {
1264    /// The type of errors that may be generated when querying a wallet data store.
1265    type Error: Debug;
1266
1267    /// The type of the account identifier.
1268    ///
1269    /// An account identifier corresponds to at most a single unified spending key's worth of spend
1270    /// authority, such that both received notes and change spendable by that spending authority
1271    /// will be interpreted as belonging to that account.
1272    type AccountId: Copy + Debug + Eq + Hash;
1273
1274    /// The concrete account type used by this wallet backend.
1275    type Account: Account<AccountId = Self::AccountId>;
1276
1277    /// Returns a vector with the IDs of all accounts known to this wallet.
1278    fn get_account_ids(&self) -> Result<Vec<Self::AccountId>, Self::Error>;
1279
1280    /// Returns the account corresponding to the given ID, if any.
1281    fn get_account(
1282        &self,
1283        account_id: Self::AccountId,
1284    ) -> Result<Option<Self::Account>, Self::Error>;
1285
1286    /// Returns the account corresponding to a given [`SeedFingerprint`] and
1287    /// [`zip32::AccountId`], if any.
1288    fn get_derived_account(
1289        &self,
1290        seed: &SeedFingerprint,
1291        account_id: zip32::AccountId,
1292    ) -> Result<Option<Self::Account>, Self::Error>;
1293
1294    /// Verifies that the given seed corresponds to the viewing key for the specified account.
1295    ///
1296    /// Returns:
1297    /// - `Ok(true)` if the viewing key for the specified account can be derived from the
1298    ///   provided seed.
1299    /// - `Ok(false)` if the derived viewing key does not match, or the specified account is not
1300    ///   present in the database.
1301    /// - `Err(_)` if a Unified Spending Key cannot be derived from the seed for the
1302    ///   specified account or the account has no known ZIP-32 derivation.
1303    fn validate_seed(
1304        &self,
1305        account_id: Self::AccountId,
1306        seed: &SecretVec<u8>,
1307    ) -> Result<bool, Self::Error>;
1308
1309    /// Checks whether the given seed is relevant to any of the derived accounts (where
1310    /// [`Account::source`] is [`AccountSource::Derived`]) in the wallet.
1311    ///
1312    /// This API does not check whether the seed is relevant to any imported account,
1313    /// because that would require brute-forcing the ZIP 32 account index space.
1314    fn seed_relevance_to_derived_accounts(
1315        &self,
1316        seed: &SecretVec<u8>,
1317    ) -> Result<SeedRelevance<Self::AccountId>, Self::Error>;
1318
1319    /// Returns the account corresponding to a given [`UnifiedFullViewingKey`], if any.
1320    fn get_account_for_ufvk(
1321        &self,
1322        ufvk: &UnifiedFullViewingKey,
1323    ) -> Result<Option<Self::Account>, Self::Error>;
1324
1325    /// Returns information about every address tracked for this account.
1326    fn list_addresses(&self, account: Self::AccountId) -> Result<Vec<AddressInfo>, Self::Error>;
1327
1328    /// Returns the most recently generated unified address for the specified account that conforms
1329    /// to the specified address filter, if the account identifier specified refers to a valid
1330    /// account for this wallet.
1331    ///
1332    /// This will return `Ok(None)` if no previously generated address conforms to the specified
1333    /// request.
1334    fn get_last_generated_address_matching(
1335        &self,
1336        account: Self::AccountId,
1337        address_filter: UnifiedAddressRequest,
1338    ) -> Result<Option<UnifiedAddress>, Self::Error>;
1339
1340    /// Returns the birthday height for the given account, or an error if the account is not known
1341    /// to the wallet.
1342    fn get_account_birthday(&self, account: Self::AccountId) -> Result<BlockHeight, Self::Error>;
1343
1344    /// Returns the birthday height for the wallet.
1345    ///
1346    /// This returns the earliest birthday height among accounts maintained by this wallet,
1347    /// or `Ok(None)` if the wallet has no initialized accounts.
1348    fn get_wallet_birthday(&self) -> Result<Option<BlockHeight>, Self::Error>;
1349
1350    /// Returns a [`WalletSummary`] that represents the sync status, and the wallet balances
1351    /// given the specified minimum number of confirmations for all accounts known to the
1352    /// wallet; or `Ok(None)` if the wallet has no summary data available.
1353    fn get_wallet_summary(
1354        &self,
1355        min_confirmations: u32,
1356    ) -> Result<Option<WalletSummary<Self::AccountId>>, Self::Error>;
1357
1358    /// Returns the height of the chain as known to the wallet as of the most recent call to
1359    /// [`WalletWrite::update_chain_tip`].
1360    ///
1361    /// This will return `Ok(None)` if the height of the current consensus chain tip is unknown.
1362    fn chain_height(&self) -> Result<Option<BlockHeight>, Self::Error>;
1363
1364    /// Returns the block hash for the block at the given height, if the
1365    /// associated block data is available. Returns `Ok(None)` if the hash
1366    /// is not found in the database.
1367    fn get_block_hash(&self, block_height: BlockHeight) -> Result<Option<BlockHash>, Self::Error>;
1368
1369    /// Returns the available block metadata for the block at the specified height, if any.
1370    fn block_metadata(&self, height: BlockHeight) -> Result<Option<BlockMetadata>, Self::Error>;
1371
1372    /// Returns the metadata for the block at the height to which the wallet has been fully
1373    /// scanned.
1374    ///
1375    /// This is the height for which the wallet has fully trial-decrypted this and all preceding
1376    /// blocks above the wallet's birthday height. Along with this height, this method returns
1377    /// metadata describing the state of the wallet's note commitment trees as of the end of that
1378    /// block.
1379    fn block_fully_scanned(&self) -> Result<Option<BlockMetadata>, Self::Error>;
1380
1381    /// Returns the block height and hash for the block at the maximum scanned block height.
1382    ///
1383    /// This will return `Ok(None)` if no blocks have been scanned.
1384    fn get_max_height_hash(&self) -> Result<Option<(BlockHeight, BlockHash)>, Self::Error>;
1385
1386    /// Returns block metadata for the maximum height that the wallet has scanned.
1387    ///
1388    /// If the wallet is fully synced, this will be equivalent to `block_fully_scanned`;
1389    /// otherwise the maximal scanned height is likely to be greater than the fully scanned height
1390    /// due to the fact that out-of-order scanning can leave gaps.
1391    fn block_max_scanned(&self) -> Result<Option<BlockMetadata>, Self::Error>;
1392
1393    /// Returns a vector of suggested scan ranges based upon the current wallet state.
1394    ///
1395    /// This method should only be used in cases where the [`CompactBlock`] data that will be made
1396    /// available to `scan_cached_blocks` for the requested block ranges includes note commitment
1397    /// tree size information for each block; or else the scan is likely to fail if notes belonging
1398    /// to the wallet are detected.
1399    ///
1400    /// The returned range(s) may include block heights beyond the current chain tip. Ranges are
1401    /// returned in order of descending priority, and higher-priority ranges should always be
1402    /// scanned before lower-priority ranges; in particular, ranges with [`ScanPriority::Verify`]
1403    /// priority must always be scanned first in order to avoid blockchain continuity errors in the
1404    /// case of a reorg.
1405    ///
1406    /// [`CompactBlock`]: crate::proto::compact_formats::CompactBlock
1407    /// [`ScanPriority::Verify`]: crate::data_api::scanning::ScanPriority
1408    fn suggest_scan_ranges(&self) -> Result<Vec<ScanRange>, Self::Error>;
1409
1410    /// Returns the default target height (for the block in which a new
1411    /// transaction would be mined) and anchor height (to use for a new
1412    /// transaction), given the range of block heights that the backend
1413    /// knows about.
1414    ///
1415    /// This will return `Ok(None)` if no block data is present in the database.
1416    fn get_target_and_anchor_heights(
1417        &self,
1418        min_confirmations: NonZeroU32,
1419    ) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error>;
1420
1421    /// Returns the block height in which the specified transaction was mined, or `Ok(None)` if the
1422    /// transaction is not in the main chain.
1423    fn get_tx_height(&self, txid: TxId) -> Result<Option<BlockHeight>, Self::Error>;
1424
1425    /// Returns all unified full viewing keys known to this wallet.
1426    fn get_unified_full_viewing_keys(
1427        &self,
1428    ) -> Result<HashMap<Self::AccountId, UnifiedFullViewingKey>, Self::Error>;
1429
1430    /// Returns the memo for a note.
1431    ///
1432    /// Returns `Ok(None)` if the note is known to the wallet but memo data has not yet been
1433    /// populated for that note, or if the note identifier does not correspond to a note
1434    /// that is known to the wallet.
1435    fn get_memo(&self, note_id: NoteId) -> Result<Option<Memo>, Self::Error>;
1436
1437    /// Returns a transaction.
1438    fn get_transaction(&self, txid: TxId) -> Result<Option<Transaction>, Self::Error>;
1439
1440    /// Returns the nullifiers for Sapling notes that the wallet is tracking, along with their
1441    /// associated account IDs, that are either unspent or have not yet been confirmed as spent (in
1442    /// that a spending transaction known to the wallet has not yet been included in a block).
1443    fn get_sapling_nullifiers(
1444        &self,
1445        query: NullifierQuery,
1446    ) -> Result<Vec<(Self::AccountId, sapling::Nullifier)>, Self::Error>;
1447
1448    /// Returns the nullifiers for Orchard notes that the wallet is tracking, along with their
1449    /// associated account IDs, that are either unspent or have not yet been confirmed as spent (in
1450    /// that a spending transaction known to the wallet has not yet been included in a block).
1451    #[cfg(feature = "orchard")]
1452    fn get_orchard_nullifiers(
1453        &self,
1454        query: NullifierQuery,
1455    ) -> Result<Vec<(Self::AccountId, orchard::note::Nullifier)>, Self::Error>;
1456
1457    /// Returns the set of non-ephemeral transparent receivers associated with the given
1458    /// account controlled by this wallet.
1459    ///
1460    /// The set contains all non-ephemeral transparent receivers that are known to have
1461    /// been derived under this account. Wallets should scan the chain for UTXOs sent to
1462    /// these receivers.
1463    ///
1464    /// Use [`Self::get_known_ephemeral_addresses`] to obtain the ephemeral transparent
1465    /// receivers.
1466    #[cfg(feature = "transparent-inputs")]
1467    fn get_transparent_receivers(
1468        &self,
1469        _account: Self::AccountId,
1470        _include_change: bool,
1471    ) -> Result<HashMap<TransparentAddress, Option<TransparentAddressMetadata>>, Self::Error> {
1472        Ok(HashMap::new())
1473    }
1474
1475    /// Returns a mapping from each transparent receiver associated with the specified account
1476    /// to its not-yet-shielded UTXO balance as of the end of the block at the provided
1477    /// `max_height`, when that balance is non-zero.
1478    ///
1479    /// Balances of ephemeral transparent addresses will not be included.
1480    #[cfg(feature = "transparent-inputs")]
1481    fn get_transparent_balances(
1482        &self,
1483        _account: Self::AccountId,
1484        _max_height: BlockHeight,
1485    ) -> Result<HashMap<TransparentAddress, Zatoshis>, Self::Error> {
1486        Ok(HashMap::new())
1487    }
1488
1489    /// Returns the metadata associated with a given transparent receiver in an account
1490    /// controlled by this wallet, if available.
1491    ///
1492    /// This is equivalent to (but may be implemented more efficiently than):
1493    /// ```compile_fail
1494    /// Ok(
1495    ///     if let Some(result) = self.get_transparent_receivers(account, true)?.get(address) {
1496    ///         result.clone()
1497    ///     } else {
1498    ///         self.get_known_ephemeral_addresses(account, None)?
1499    ///             .into_iter()
1500    ///             .find(|(known_addr, _)| known_addr == address)
1501    ///             .map(|(_, metadata)| metadata)
1502    ///     },
1503    /// )
1504    /// ```
1505    ///
1506    /// Returns `Ok(None)` if the address is not recognized, or we do not have metadata for it.
1507    /// Returns `Ok(Some(metadata))` if we have the metadata.
1508    #[cfg(feature = "transparent-inputs")]
1509    fn get_transparent_address_metadata(
1510        &self,
1511        account: Self::AccountId,
1512        address: &TransparentAddress,
1513    ) -> Result<Option<TransparentAddressMetadata>, Self::Error> {
1514        // This should be overridden.
1515        Ok(
1516            if let Some(result) = self.get_transparent_receivers(account, true)?.get(address) {
1517                result.clone()
1518            } else {
1519                self.get_known_ephemeral_addresses(account, None)?
1520                    .into_iter()
1521                    .find(|(known_addr, _)| known_addr == address)
1522                    .map(|(_, metadata)| metadata)
1523            },
1524        )
1525    }
1526
1527    /// Returns the maximum block height at which a transparent output belonging to the wallet has
1528    /// been observed.
1529    ///
1530    /// We must start looking for UTXOs for addresses within the current gap limit as of the block
1531    /// height at which they might have first been revealed. This would have occurred when the gap
1532    /// advanced as a consequence of a transaction being mined. The address at the start of the current
1533    /// gap was potentially first revealed after the address at index `gap_start - (gap_limit + 1)`
1534    /// received an output in a mined transaction; therefore, we take that height to be where we
1535    /// should start searching for UTXOs.
1536    #[cfg(feature = "transparent-inputs")]
1537    fn utxo_query_height(&self, account: Self::AccountId) -> Result<BlockHeight, Self::Error>;
1538
1539    /// Returns a vector of ephemeral transparent addresses associated with the given
1540    /// account controlled by this wallet, along with their metadata. The result includes
1541    /// reserved addresses, and addresses for the backend's configured gap limit worth
1542    /// of additional indices (capped to the maximum index).
1543    ///
1544    /// If `index_range` is some `Range`, it limits the result to addresses with indices
1545    /// in that range. An `index_range` of `None` is defined to be equivalent to
1546    /// `0..(1u32 << 31)`.
1547    ///
1548    /// Wallets should scan the chain for UTXOs sent to these ephemeral transparent
1549    /// receivers, but do not need to do so regularly. Under expected usage, outputs
1550    /// would only be detected with these receivers in the following situations:
1551    ///
1552    /// - This wallet created a payment to a ZIP 320 (TEX) address, but the second
1553    ///   transaction (that spent the output sent to the ephemeral address) did not get
1554    ///   mined before it expired.
1555    ///   - In this case the output will already be known to the wallet (because it
1556    ///     stores the transactions that it creates).
1557    ///
1558    /// - Another wallet app using the same seed phrase created a payment to a ZIP 320
1559    ///   address, and this wallet queried for the ephemeral UTXOs after the first
1560    ///   transaction was mined but before the second transaction was mined.
1561    ///   - In this case, the output should not be considered unspent until the expiry
1562    ///     height of the transaction it was received in has passed. Wallets creating
1563    ///     payments to TEX addresses generally set the same expiry height for the first
1564    ///     and second transactions, meaning that this wallet does not need to observe
1565    ///     the second transaction to determine when it would have expired.
1566    ///
1567    /// - A TEX address recipient decided to return funds that the wallet had sent to
1568    ///   them.
1569    ///
1570    /// In all cases, the wallet should re-shield the unspent outputs, in a separate
1571    /// transaction per ephemeral address, before re-spending the funds.
1572    #[cfg(feature = "transparent-inputs")]
1573    fn get_known_ephemeral_addresses(
1574        &self,
1575        _account: Self::AccountId,
1576        _index_range: Option<Range<NonHardenedChildIndex>>,
1577    ) -> Result<Vec<(TransparentAddress, TransparentAddressMetadata)>, Self::Error> {
1578        Ok(vec![])
1579    }
1580
1581    /// If a given ephemeral address might have been reserved, i.e. would be included in
1582    /// the result of `get_known_ephemeral_addresses(account_id, None)` for any of the
1583    /// wallet's accounts, then return `Ok(Some(account_id))`. Otherwise return `Ok(None)`.
1584    ///
1585    /// This is equivalent to (but may be implemented more efficiently than):
1586    /// ```compile_fail
1587    /// for account_id in self.get_account_ids()? {
1588    ///     if self
1589    ///         .get_known_ephemeral_addresses(account_id, None)?
1590    ///         .into_iter()
1591    ///         .any(|(known_addr, _)| &known_addr == address)
1592    ///     {
1593    ///         return Ok(Some(account_id));
1594    ///     }
1595    /// }
1596    /// Ok(None)
1597    /// ```
1598    #[cfg(feature = "transparent-inputs")]
1599    fn find_account_for_ephemeral_address(
1600        &self,
1601        address: &TransparentAddress,
1602    ) -> Result<Option<Self::AccountId>, Self::Error> {
1603        for account_id in self.get_account_ids()? {
1604            if self
1605                .get_known_ephemeral_addresses(account_id, None)?
1606                .into_iter()
1607                .any(|(known_addr, _)| &known_addr == address)
1608            {
1609                return Ok(Some(account_id));
1610            }
1611        }
1612        Ok(None)
1613    }
1614
1615    /// Returns a vector of [`TransactionDataRequest`] values that describe information needed by
1616    /// the wallet to complete its view of transaction history.
1617    ///
1618    /// Requests for the same transaction data may be returned repeatedly by successive data
1619    /// requests. The caller of this method should consider the latest set of requests returned
1620    /// by this method to be authoritative and to subsume that returned by previous calls.
1621    ///
1622    /// Callers should poll this method on a regular interval, not as part of ordinary chain
1623    /// scanning, which already produces requests for transaction data enhancement. Note that
1624    /// responding to a set of transaction data requests may result in the creation of new
1625    /// transaction data requests, such as when it is necessary to fill in purely-transparent
1626    /// transaction history by walking the chain backwards via transparent inputs.
1627    fn transaction_data_requests(&self) -> Result<Vec<TransactionDataRequest>, Self::Error>;
1628}
1629
1630/// Read-only operations required for testing light wallet functions.
1631///
1632/// These methods expose internal details or unstable interfaces, primarily to enable use
1633/// of the [`testing`] framework. They should not be used in production software.
1634#[cfg(any(test, feature = "test-dependencies"))]
1635#[cfg_attr(feature = "test-dependencies", delegatable_trait)]
1636pub trait WalletTest: InputSource + WalletRead {
1637    /// Returns a vector of transaction summaries.
1638    ///
1639    /// Currently test-only, as production use could return a very large number of results; either
1640    /// pagination or a streaming design will be necessary to stabilize this feature for production
1641    /// use.
1642    fn get_tx_history(
1643        &self,
1644    ) -> Result<
1645        Vec<testing::TransactionSummary<<Self as WalletRead>::AccountId>>,
1646        <Self as WalletRead>::Error,
1647    >;
1648
1649    /// Returns the note IDs for shielded notes sent by the wallet in a particular
1650    /// transaction.
1651    fn get_sent_note_ids(
1652        &self,
1653        _txid: &TxId,
1654        _protocol: ShieldedProtocol,
1655    ) -> Result<Vec<NoteId>, <Self as WalletRead>::Error>;
1656
1657    /// Returns the outputs for a transaction sent by the wallet.
1658    #[allow(clippy::type_complexity)]
1659    fn get_sent_outputs(
1660        &self,
1661        txid: &TxId,
1662    ) -> Result<Vec<OutputOfSentTx>, <Self as WalletRead>::Error>;
1663
1664    #[allow(clippy::type_complexity)]
1665    fn get_checkpoint_history(
1666        &self,
1667        protocol: &ShieldedProtocol,
1668    ) -> Result<
1669        Vec<(BlockHeight, Option<incrementalmerkletree::Position>)>,
1670        <Self as WalletRead>::Error,
1671    >;
1672
1673    /// Fetches the transparent output corresponding to the provided `outpoint`.
1674    /// Allows selecting unspendable outputs for testing purposes.
1675    ///
1676    /// Returns `Ok(None)` if the UTXO is not known to belong to the wallet or is not
1677    /// spendable as of the chain tip height.
1678    #[cfg(feature = "transparent-inputs")]
1679    fn get_transparent_output(
1680        &self,
1681        outpoint: &OutPoint,
1682        allow_unspendable: bool,
1683    ) -> Result<Option<WalletTransparentOutput>, <Self as InputSource>::Error>;
1684
1685    /// Returns all the notes that have been received by the wallet.
1686    fn get_notes(
1687        &self,
1688        protocol: ShieldedProtocol,
1689    ) -> Result<Vec<ReceivedNote<Self::NoteRef, Note>>, <Self as InputSource>::Error>;
1690}
1691
1692/// The output of a transaction sent by the wallet.
1693///
1694/// This type is opaque, and exists for use by tests defined in this crate.
1695#[cfg(any(test, feature = "test-dependencies"))]
1696#[allow(dead_code)]
1697#[derive(Clone, Debug)]
1698pub struct OutputOfSentTx {
1699    value: Zatoshis,
1700    external_recipient: Option<Address>,
1701    ephemeral_address: Option<(Address, u32)>,
1702}
1703
1704#[cfg(any(test, feature = "test-dependencies"))]
1705impl OutputOfSentTx {
1706    /// Constructs an output from its test-relevant parts.
1707    ///
1708    /// If the output is to an ephemeral address, `ephemeral_address` should contain the
1709    /// address along with the `address_index` it was derived from under the BIP 32 path
1710    /// `m/44'/<coin_type>'/<account>'/2/<address_index>`.
1711    pub fn from_parts(
1712        value: Zatoshis,
1713        external_recipient: Option<Address>,
1714        ephemeral_address: Option<(Address, u32)>,
1715    ) -> Self {
1716        Self {
1717            value,
1718            external_recipient,
1719            ephemeral_address,
1720        }
1721    }
1722}
1723
1724/// The relevance of a seed to a given wallet.
1725///
1726/// This is the return type for [`WalletRead::seed_relevance_to_derived_accounts`].
1727#[derive(Clone, Debug, PartialEq, Eq)]
1728pub enum SeedRelevance<A: Copy> {
1729    /// The seed is relevant to at least one derived account within the wallet.
1730    Relevant { account_ids: NonEmpty<A> },
1731    /// The seed is not relevant to any of the derived accounts within the wallet.
1732    NotRelevant,
1733    /// The wallet contains no derived accounts.
1734    NoDerivedAccounts,
1735    /// The wallet contains no accounts.
1736    NoAccounts,
1737}
1738
1739/// Metadata describing the sizes of the zcash note commitment trees as of a particular block.
1740#[derive(Debug, Clone, Copy)]
1741pub struct BlockMetadata {
1742    block_height: BlockHeight,
1743    block_hash: BlockHash,
1744    sapling_tree_size: Option<u32>,
1745    #[cfg(feature = "orchard")]
1746    orchard_tree_size: Option<u32>,
1747}
1748
1749impl BlockMetadata {
1750    /// Constructs a new [`BlockMetadata`] value from its constituent parts.
1751    pub fn from_parts(
1752        block_height: BlockHeight,
1753        block_hash: BlockHash,
1754        sapling_tree_size: Option<u32>,
1755        #[cfg(feature = "orchard")] orchard_tree_size: Option<u32>,
1756    ) -> Self {
1757        Self {
1758            block_height,
1759            block_hash,
1760            sapling_tree_size,
1761            #[cfg(feature = "orchard")]
1762            orchard_tree_size,
1763        }
1764    }
1765
1766    /// Returns the block height.
1767    pub fn block_height(&self) -> BlockHeight {
1768        self.block_height
1769    }
1770
1771    /// Returns the hash of the block
1772    pub fn block_hash(&self) -> BlockHash {
1773        self.block_hash
1774    }
1775
1776    /// Returns the size of the Sapling note commitment tree for the final treestate of the block
1777    /// that this [`BlockMetadata`] describes, if available.
1778    pub fn sapling_tree_size(&self) -> Option<u32> {
1779        self.sapling_tree_size
1780    }
1781
1782    /// Returns the size of the Orchard note commitment tree for the final treestate of the block
1783    /// that this [`BlockMetadata`] describes, if available.
1784    #[cfg(feature = "orchard")]
1785    pub fn orchard_tree_size(&self) -> Option<u32> {
1786        self.orchard_tree_size
1787    }
1788}
1789
1790/// The protocol-specific note commitment and nullifier data extracted from the per-transaction
1791/// shielded bundles in [`CompactBlock`], used by the wallet for note commitment tree maintenance
1792/// and spend detection.
1793///
1794/// [`CompactBlock`]: crate::proto::compact_formats::CompactBlock
1795pub struct ScannedBundles<NoteCommitment, NF> {
1796    final_tree_size: u32,
1797    commitments: Vec<(NoteCommitment, Retention<BlockHeight>)>,
1798    nullifier_map: Vec<(TxId, u16, Vec<NF>)>,
1799}
1800
1801impl<NoteCommitment, NF> ScannedBundles<NoteCommitment, NF> {
1802    pub(crate) fn new(
1803        final_tree_size: u32,
1804        commitments: Vec<(NoteCommitment, Retention<BlockHeight>)>,
1805        nullifier_map: Vec<(TxId, u16, Vec<NF>)>,
1806    ) -> Self {
1807        Self {
1808            final_tree_size,
1809            nullifier_map,
1810            commitments,
1811        }
1812    }
1813
1814    /// Returns the size of the note commitment tree as of the end of the scanned block.
1815    pub fn final_tree_size(&self) -> u32 {
1816        self.final_tree_size
1817    }
1818
1819    /// Returns the vector of nullifiers for each transaction in the block.
1820    ///
1821    /// The returned tuple is keyed by both transaction ID and the index of the transaction within
1822    /// the block, so that either the txid or the combination of the block hash available from
1823    /// [`ScannedBlock::block_hash`] and returned transaction index may be used to uniquely
1824    /// identify the transaction, depending upon the needs of the caller.
1825    pub fn nullifier_map(&self) -> &[(TxId, u16, Vec<NF>)] {
1826        &self.nullifier_map
1827    }
1828
1829    /// Returns the ordered list of note commitments to be added to the note commitment
1830    /// tree.
1831    pub fn commitments(&self) -> &[(NoteCommitment, Retention<BlockHeight>)] {
1832        &self.commitments
1833    }
1834}
1835
1836/// A struct used to return the vectors of note commitments for a [`ScannedBlock`]
1837/// as owned values.
1838pub struct ScannedBlockCommitments {
1839    /// The ordered vector of note commitments for Sapling outputs of the block.
1840    pub sapling: Vec<(sapling::Node, Retention<BlockHeight>)>,
1841    /// The ordered vector of note commitments for Orchard outputs of the block.
1842    /// Present only when the `orchard` feature is enabled.
1843    #[cfg(feature = "orchard")]
1844    pub orchard: Vec<(orchard::tree::MerkleHashOrchard, Retention<BlockHeight>)>,
1845}
1846
1847/// The subset of information that is relevant to this wallet that has been
1848/// decrypted and extracted from a [`CompactBlock`].
1849///
1850/// [`CompactBlock`]: crate::proto::compact_formats::CompactBlock
1851pub struct ScannedBlock<A> {
1852    block_height: BlockHeight,
1853    block_hash: BlockHash,
1854    block_time: u32,
1855    transactions: Vec<WalletTx<A>>,
1856    sapling: ScannedBundles<sapling::Node, sapling::Nullifier>,
1857    #[cfg(feature = "orchard")]
1858    orchard: ScannedBundles<orchard::tree::MerkleHashOrchard, orchard::note::Nullifier>,
1859}
1860
1861impl<A> ScannedBlock<A> {
1862    /// Constructs a new `ScannedBlock`
1863    pub(crate) fn from_parts(
1864        block_height: BlockHeight,
1865        block_hash: BlockHash,
1866        block_time: u32,
1867        transactions: Vec<WalletTx<A>>,
1868        sapling: ScannedBundles<sapling::Node, sapling::Nullifier>,
1869        #[cfg(feature = "orchard")] orchard: ScannedBundles<
1870            orchard::tree::MerkleHashOrchard,
1871            orchard::note::Nullifier,
1872        >,
1873    ) -> Self {
1874        Self {
1875            block_height,
1876            block_hash,
1877            block_time,
1878            transactions,
1879            sapling,
1880            #[cfg(feature = "orchard")]
1881            orchard,
1882        }
1883    }
1884
1885    /// Returns the height of the block that was scanned.
1886    pub fn height(&self) -> BlockHeight {
1887        self.block_height
1888    }
1889
1890    /// Returns the block hash of the block that was scanned.
1891    pub fn block_hash(&self) -> BlockHash {
1892        self.block_hash
1893    }
1894
1895    /// Returns the block time of the block that was scanned, as a Unix timestamp in seconds.
1896    pub fn block_time(&self) -> u32 {
1897        self.block_time
1898    }
1899
1900    /// Returns the list of transactions from this block that are relevant to the wallet.
1901    pub fn transactions(&self) -> &[WalletTx<A>] {
1902        &self.transactions
1903    }
1904
1905    /// Returns the Sapling note commitment tree and nullifier data for the block.
1906    pub fn sapling(&self) -> &ScannedBundles<sapling::Node, sapling::Nullifier> {
1907        &self.sapling
1908    }
1909
1910    /// Returns the Orchard note commitment tree and nullifier data for the block.
1911    #[cfg(feature = "orchard")]
1912    pub fn orchard(
1913        &self,
1914    ) -> &ScannedBundles<orchard::tree::MerkleHashOrchard, orchard::note::Nullifier> {
1915        &self.orchard
1916    }
1917
1918    /// Consumes `self` and returns the lists of Sapling and Orchard note commitments associated
1919    /// with the scanned block as an owned value.
1920    pub fn into_commitments(self) -> ScannedBlockCommitments {
1921        ScannedBlockCommitments {
1922            sapling: self.sapling.commitments,
1923            #[cfg(feature = "orchard")]
1924            orchard: self.orchard.commitments,
1925        }
1926    }
1927
1928    /// Returns the [`BlockMetadata`] corresponding to the scanned block.
1929    pub fn to_block_metadata(&self) -> BlockMetadata {
1930        BlockMetadata {
1931            block_height: self.block_height,
1932            block_hash: self.block_hash,
1933            sapling_tree_size: Some(self.sapling.final_tree_size),
1934            #[cfg(feature = "orchard")]
1935            orchard_tree_size: Some(self.orchard.final_tree_size),
1936        }
1937    }
1938}
1939
1940/// A transaction that was detected during scanning of the blockchain,
1941/// including its decrypted Sapling and/or Orchard outputs.
1942///
1943/// The purpose of this struct is to permit atomic updates of the
1944/// wallet database when transactions are successfully decrypted.
1945pub struct DecryptedTransaction<'a, AccountId> {
1946    mined_height: Option<BlockHeight>,
1947    tx: &'a Transaction,
1948    sapling_outputs: Vec<DecryptedOutput<sapling::Note, AccountId>>,
1949    #[cfg(feature = "orchard")]
1950    orchard_outputs: Vec<DecryptedOutput<orchard::note::Note, AccountId>>,
1951}
1952
1953impl<'a, AccountId> DecryptedTransaction<'a, AccountId> {
1954    /// Constructs a new [`DecryptedTransaction`] from its constituent parts.
1955    pub fn new(
1956        mined_height: Option<BlockHeight>,
1957        tx: &'a Transaction,
1958        sapling_outputs: Vec<DecryptedOutput<sapling::Note, AccountId>>,
1959        #[cfg(feature = "orchard")] orchard_outputs: Vec<
1960            DecryptedOutput<orchard::note::Note, AccountId>,
1961        >,
1962    ) -> Self {
1963        Self {
1964            mined_height,
1965            tx,
1966            sapling_outputs,
1967            #[cfg(feature = "orchard")]
1968            orchard_outputs,
1969        }
1970    }
1971
1972    /// Returns the height at which the transaction was mined, if known.
1973    pub fn mined_height(&self) -> Option<BlockHeight> {
1974        self.mined_height
1975    }
1976    /// Returns the raw transaction data.
1977    pub fn tx(&self) -> &Transaction {
1978        self.tx
1979    }
1980    /// Returns the Sapling outputs that were decrypted from the transaction.
1981    pub fn sapling_outputs(&self) -> &[DecryptedOutput<sapling::Note, AccountId>] {
1982        &self.sapling_outputs
1983    }
1984    /// Returns the Orchard outputs that were decrypted from the transaction.
1985    #[cfg(feature = "orchard")]
1986    pub fn orchard_outputs(&self) -> &[DecryptedOutput<orchard::note::Note, AccountId>] {
1987        &self.orchard_outputs
1988    }
1989}
1990
1991/// A transaction that was constructed and sent by the wallet.
1992///
1993/// The purpose of this struct is to permit atomic updates of the
1994/// wallet database when transactions are created and submitted
1995/// to the network.
1996pub struct SentTransaction<'a, AccountId> {
1997    tx: &'a Transaction,
1998    created: time::OffsetDateTime,
1999    target_height: BlockHeight,
2000    account: AccountId,
2001    outputs: &'a [SentTransactionOutput<AccountId>],
2002    fee_amount: Zatoshis,
2003    #[cfg(feature = "transparent-inputs")]
2004    utxos_spent: &'a [OutPoint],
2005}
2006
2007impl<'a, AccountId> SentTransaction<'a, AccountId> {
2008    /// Constructs a new [`SentTransaction`] from its constituent parts.
2009    ///
2010    /// ### Parameters
2011    /// - `tx`: the raw transaction data
2012    /// - `created`: the system time at which the transaction was created
2013    /// - `target_height`: the target height that was used in the construction of the transaction
2014    /// - `account`: the account that spent funds in creation of the transaction
2015    /// - `outputs`: the outputs created by the transaction, including those sent to external
2016    ///   recipients which may not otherwise be recoverable
2017    /// - `fee_amount`: the fee value paid by the transaction
2018    /// - `utxos_spent`: the UTXOs controlled by the wallet that were spent in this transaction
2019    pub fn new(
2020        tx: &'a Transaction,
2021        created: time::OffsetDateTime,
2022        target_height: BlockHeight,
2023        account: AccountId,
2024        outputs: &'a [SentTransactionOutput<AccountId>],
2025        fee_amount: Zatoshis,
2026        #[cfg(feature = "transparent-inputs")] utxos_spent: &'a [OutPoint],
2027    ) -> Self {
2028        Self {
2029            tx,
2030            created,
2031            target_height,
2032            account,
2033            outputs,
2034            fee_amount,
2035            #[cfg(feature = "transparent-inputs")]
2036            utxos_spent,
2037        }
2038    }
2039
2040    /// Returns the transaction that was sent.
2041    pub fn tx(&self) -> &Transaction {
2042        self.tx
2043    }
2044    /// Returns the timestamp of the transaction's creation.
2045    pub fn created(&self) -> time::OffsetDateTime {
2046        self.created
2047    }
2048    /// Returns the id for the account that created the outputs.
2049    pub fn account_id(&self) -> &AccountId {
2050        &self.account
2051    }
2052    /// Returns the outputs of the transaction.
2053    pub fn outputs(&self) -> &[SentTransactionOutput<AccountId>] {
2054        self.outputs
2055    }
2056    /// Returns the fee paid by the transaction.
2057    pub fn fee_amount(&self) -> Zatoshis {
2058        self.fee_amount
2059    }
2060    /// Returns the list of UTXOs spent in the created transaction.
2061    #[cfg(feature = "transparent-inputs")]
2062    pub fn utxos_spent(&self) -> &[OutPoint] {
2063        self.utxos_spent
2064    }
2065
2066    /// Returns the block height that this transaction was created to target.
2067    pub fn target_height(&self) -> BlockHeight {
2068        self.target_height
2069    }
2070}
2071
2072/// An output of a transaction generated by the wallet.
2073///
2074/// This type is capable of representing both shielded and transparent outputs.
2075pub struct SentTransactionOutput<AccountId> {
2076    output_index: usize,
2077    recipient: Recipient<AccountId>,
2078    value: Zatoshis,
2079    memo: Option<MemoBytes>,
2080}
2081
2082impl<AccountId> SentTransactionOutput<AccountId> {
2083    /// Constructs a new [`SentTransactionOutput`] from its constituent parts.
2084    ///
2085    /// ### Fields:
2086    /// * `output_index` - the index of the output or action in the sent transaction
2087    /// * `recipient` - the recipient of the output, either a Zcash address or a
2088    ///   wallet-internal account and the note belonging to the wallet created by
2089    ///   the output
2090    /// * `value` - the value of the output, in zatoshis
2091    /// * `memo` - the memo that was sent with this output
2092    pub fn from_parts(
2093        output_index: usize,
2094        recipient: Recipient<AccountId>,
2095        value: Zatoshis,
2096        memo: Option<MemoBytes>,
2097    ) -> Self {
2098        Self {
2099            output_index,
2100            recipient,
2101            value,
2102            memo,
2103        }
2104    }
2105
2106    /// Returns the index within the transaction that contains the recipient output.
2107    ///
2108    /// - If `recipient_address` is a Sapling address, this is an index into the Sapling
2109    ///   outputs of the transaction.
2110    /// - If `recipient_address` is a transparent address, this is an index into the
2111    ///   transparent outputs of the transaction.
2112    pub fn output_index(&self) -> usize {
2113        self.output_index
2114    }
2115    /// Returns the recipient address of the transaction, or the account id and
2116    /// resulting note/outpoint for wallet-internal outputs.
2117    pub fn recipient(&self) -> &Recipient<AccountId> {
2118        &self.recipient
2119    }
2120    /// Returns the value of the newly created output.
2121    pub fn value(&self) -> Zatoshis {
2122        self.value
2123    }
2124    /// Returns the memo that was attached to the output, if any. This will only be `None`
2125    /// for transparent outputs.
2126    pub fn memo(&self) -> Option<&MemoBytes> {
2127        self.memo.as_ref()
2128    }
2129}
2130
2131/// A data structure used to set the birthday height for an account, and ensure that the initial
2132/// note commitment tree state is recorded at that height.
2133#[derive(Clone, Debug)]
2134pub struct AccountBirthday {
2135    prior_chain_state: ChainState,
2136    recover_until: Option<BlockHeight>,
2137}
2138
2139/// Errors that can occur in the construction of an [`AccountBirthday`] from a [`TreeState`].
2140pub enum BirthdayError {
2141    HeightInvalid(TryFromIntError),
2142    Decode(io::Error),
2143}
2144
2145impl From<TryFromIntError> for BirthdayError {
2146    fn from(value: TryFromIntError) -> Self {
2147        Self::HeightInvalid(value)
2148    }
2149}
2150
2151impl From<io::Error> for BirthdayError {
2152    fn from(value: io::Error) -> Self {
2153        Self::Decode(value)
2154    }
2155}
2156
2157impl AccountBirthday {
2158    /// Constructs a new [`AccountBirthday`] from its constituent parts.
2159    ///
2160    /// * `prior_chain_state`: The chain state prior to the birthday height of the account. The
2161    ///   birthday height is defined as the height of the first block to be scanned in wallet
2162    ///   recovery.
2163    /// * `recover_until`: An optional height at which the wallet should exit "recovery mode". In
2164    ///   order to avoid confusing shifts in wallet balance and spendability that may temporarily be
2165    ///   visible to a user during the process of recovering from seed, wallets may optionally set a
2166    ///   "recover until" height. The wallet is considered to be in "recovery mode" until there
2167    ///   exist no unscanned ranges between the wallet's birthday height and the provided
2168    ///   `recover_until` height, exclusive.
2169    ///
2170    /// This API is intended primarily to be used in testing contexts; under normal circumstances,
2171    /// [`AccountBirthday::from_treestate`] should be used instead.
2172    #[cfg(any(test, feature = "test-dependencies"))]
2173    pub fn from_parts(prior_chain_state: ChainState, recover_until: Option<BlockHeight>) -> Self {
2174        Self {
2175            prior_chain_state,
2176            recover_until,
2177        }
2178    }
2179
2180    /// Constructs a new [`AccountBirthday`] from a [`TreeState`] returned from `lightwalletd`.
2181    ///
2182    /// * `treestate`: The tree state corresponding to the last block prior to the wallet's
2183    ///   birthday height.
2184    /// * `recover_until`: An optional height at which the wallet should exit "recovery mode". In
2185    ///   order to avoid confusing shifts in wallet balance and spendability that may temporarily be
2186    ///   visible to a user during the process of recovering from seed, wallets may optionally set a
2187    ///   "recover until" height. The wallet is considered to be in "recovery mode" until there
2188    ///   exist no unscanned ranges between the wallet's birthday height and the provided
2189    ///   `recover_until` height, exclusive.
2190    pub fn from_treestate(
2191        treestate: TreeState,
2192        recover_until: Option<BlockHeight>,
2193    ) -> Result<Self, BirthdayError> {
2194        Ok(Self {
2195            prior_chain_state: treestate.to_chain_state()?,
2196            recover_until,
2197        })
2198    }
2199
2200    /// Returns the Sapling note commitment tree frontier as of the end of the block at
2201    /// [`Self::height`].
2202    pub fn sapling_frontier(
2203        &self,
2204    ) -> &Frontier<sapling::Node, { sapling::NOTE_COMMITMENT_TREE_DEPTH }> {
2205        self.prior_chain_state.final_sapling_tree()
2206    }
2207
2208    /// Returns the Orchard note commitment tree frontier as of the end of the block at
2209    /// [`Self::height`].
2210    #[cfg(feature = "orchard")]
2211    pub fn orchard_frontier(
2212        &self,
2213    ) -> &Frontier<orchard::tree::MerkleHashOrchard, { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }>
2214    {
2215        self.prior_chain_state.final_orchard_tree()
2216    }
2217
2218    /// Returns the birthday height of the account.
2219    pub fn height(&self) -> BlockHeight {
2220        self.prior_chain_state.block_height() + 1
2221    }
2222
2223    /// Returns the height at which the wallet should exit "recovery mode".
2224    pub fn recover_until(&self) -> Option<BlockHeight> {
2225        self.recover_until
2226    }
2227
2228    #[cfg(any(test, feature = "test-dependencies"))]
2229    /// Constructs a new [`AccountBirthday`] at the given network upgrade's activation,
2230    /// with no "recover until" height.
2231    ///
2232    /// # Panics
2233    ///
2234    /// Panics if the activation height for the given network upgrade is not set.
2235    pub fn from_activation<P: zcash_protocol::consensus::Parameters>(
2236        params: &P,
2237        network_upgrade: NetworkUpgrade,
2238        prior_block_hash: BlockHash,
2239    ) -> AccountBirthday {
2240        AccountBirthday::from_parts(
2241            ChainState::empty(
2242                params.activation_height(network_upgrade).unwrap() - 1,
2243                prior_block_hash,
2244            ),
2245            None,
2246        )
2247    }
2248
2249    #[cfg(any(test, feature = "test-dependencies"))]
2250    /// Constructs a new [`AccountBirthday`] at Sapling activation, with no
2251    /// "recover until" height.
2252    ///
2253    /// # Panics
2254    ///
2255    /// Panics if the Sapling activation height is not set.
2256    pub fn from_sapling_activation<P: zcash_protocol::consensus::Parameters>(
2257        params: &P,
2258        prior_block_hash: BlockHash,
2259    ) -> AccountBirthday {
2260        Self::from_activation(params, NetworkUpgrade::Sapling, prior_block_hash)
2261    }
2262}
2263
2264/// This trait encapsulates the write capabilities required to update stored wallet data.
2265///
2266/// # Adding accounts
2267///
2268/// This trait provides several methods for adding accounts to the wallet data:
2269/// - [`WalletWrite::create_account`]
2270/// - [`WalletWrite::import_account_hd`]
2271/// - [`WalletWrite::import_account_ufvk`]
2272///
2273/// All of these methods take an [`AccountBirthday`]. The birthday height is defined as
2274/// the minimum block height that will be scanned for funds belonging to the wallet. If
2275/// `birthday.height()` is below the current chain tip, the account addition operation
2276/// will trigger a re-scan of the blocks at and above the provided height.
2277///
2278/// The order in which you call these methods will affect the resulting wallet structure:
2279/// - If only [`WalletWrite::create_account`] is used, the resulting accounts will have
2280///   sequential [ZIP 32] account indices within each given seed.
2281/// - If [`WalletWrite::import_account_hd`] is used to import accounts with non-sequential
2282///   ZIP 32 account indices from the same seed, a call to [`WalletWrite::create_account`]
2283///   will use the ZIP 32 account index just after the highest-numbered existing account.
2284/// - If an account is added to the wallet, and then a later call to one of the methods
2285///   would produce a UFVK that collides with that account on any FVK component (i.e.
2286///   Sapling, Orchard, or transparent), an error will be returned. This can occur in the
2287///   following cases:
2288///   - An account is created via [`WalletWrite::create_account`] with an auto-selected
2289///     ZIP 32 account index, and that index is later imported explicitly via either
2290///     [`WalletWrite::import_account_ufvk`] or [`WalletWrite::import_account_hd`].
2291///   - An account is imported via [`WalletWrite::import_account_ufvk`] or
2292///     [`WalletWrite::import_account_hd`], and then the ZIP 32 account index
2293///     corresponding to that account's UFVK is later imported either implicitly
2294///     via [`WalletWrite::create_account`], or explicitly via a call to
2295///     [`WalletWrite::import_account_ufvk`] or [`WalletWrite::import_account_hd`].
2296///
2297/// Note that an error will be returned on an FVK collision even if the UFVKs do not
2298/// match exactly, e.g. if they have different subsets of components.
2299///
2300/// A future change to this trait might introduce a method to "upgrade" an imported
2301/// account with derivation information. See [zcash/librustzcash#1284] for details.
2302///
2303/// Users of the `WalletWrite` trait should generally distinguish in their APIs and wallet
2304/// UIs between creating a new account, and importing an account that previously existed.
2305/// By convention, wallets should only allow a new account to be generated after confirmed
2306/// funds have been received by the newest existing account; this allows automated account
2307/// recovery to discover and recover all funds within a particular seed.
2308///
2309/// # Creating a new wallet
2310///
2311/// To create a new wallet:
2312/// - Generate a new [BIP 39] mnemonic phrase, using a crate like [`bip0039`].
2313/// - Derive the corresponding seed from the mnemonic phrase.
2314/// - Use [`WalletWrite::create_account`] with the resulting seed.
2315///
2316/// Callers should construct the [`AccountBirthday`] using [`AccountBirthday::from_treestate`] for
2317/// the block at height `chain_tip_height - 100`. Setting the birthday height to a tree state below
2318/// the pruning depth ensures that reorgs cannot cause funds intended for the wallet to be missed;
2319/// otherwise, if the chain tip height were used for the wallet birthday, a transaction targeted at
2320/// a height greater than the chain tip could be mined at a height below that tip as part of a
2321/// reorg.
2322///
2323/// # Restoring a wallet from backup
2324///
2325/// To restore a backed-up wallet:
2326/// - Derive the seed from its BIP 39 mnemonic phrase.
2327/// - Use [`WalletWrite::import_account_hd`] once for each ZIP 32 account index that the
2328///   user wants to restore.
2329/// - If the highest previously-used ZIP 32 account index was _not_ restored by the user,
2330///   remember this index separately as `index_max`. The first time the user wants to
2331///   generate a new account, use [`WalletWrite::import_account_hd`] to create the account
2332///   `index_max + 1`.
2333/// - [`WalletWrite::create_account`] can be used to generate subsequent new accounts in
2334///   the restored wallet.
2335///
2336/// Automated account recovery has not yet been implemented by this crate. A wallet app
2337/// that supports multiple accounts can implement it manually by tracking account balances
2338/// relative to [`WalletSummary::fully_scanned_height`], and creating new accounts as
2339/// funds appear in existing accounts.
2340///
2341/// If the number of accounts is known in advance, the wallet should create all accounts before
2342/// scanning the chain so that the scan can be done in a single pass for all accounts.
2343///
2344/// [ZIP 32]: https://zips.z.cash/zip-0032
2345/// [zcash/librustzcash#1284]: https://github.com/zcash/librustzcash/issues/1284
2346/// [BIP 39]: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
2347/// [`bip0039`]: https://crates.io/crates/bip0039
2348#[cfg_attr(feature = "test-dependencies", delegatable_trait)]
2349pub trait WalletWrite: WalletRead {
2350    /// The type of identifiers used to look up transparent UTXOs.
2351    type UtxoRef;
2352
2353    /// Tells the wallet to track the next available account-level spend authority, given the
2354    /// current set of [ZIP 316] account identifiers known to the wallet database.
2355    ///
2356    /// The "next available account" is defined as the ZIP-32 account index immediately following
2357    /// the highest existing account index among all accounts in the wallet that share the given
2358    /// seed. Users of the [`WalletWrite`] trait that only call this method are guaranteed to have
2359    /// accounts with sequential indices.
2360    ///
2361    /// Returns the account identifier for the newly-created wallet database entry, along with the
2362    /// associated [`UnifiedSpendingKey`]. Note that the unique account identifier should *not* be
2363    /// assumed equivalent to the ZIP 32 account index. It is an opaque identifier for a pool of
2364    /// funds or set of outputs controlled by a single spending authority.
2365    ///
2366    /// The ZIP-32 account index may be obtained by calling [`WalletRead::get_account`]
2367    /// with the returned account identifier.
2368    ///
2369    /// The [`WalletWrite`] trait documentation has more details about account creation and import.
2370    ///
2371    /// # Arguments
2372    /// - `account_name`: A human-readable name for the account.
2373    /// - `seed`: The 256-byte (at least) HD seed from which to derive the account UFVK.
2374    /// - `birthday`: Metadata about where to start scanning blocks to find transactions intended
2375    ///   for the account.
2376    /// - `key_source`: A string identifier or other metadata describing the source of the seed.
2377    ///   This is treated as opaque metadata by the wallet backend; it is provided for use by
2378    ///   applications which need to track additional identifying information for an account.
2379    ///
2380    /// # Implementation notes
2381    ///
2382    /// Implementations of this method **MUST NOT** "fill in gaps" by selecting an account index
2383    /// that is lower than any existing account index among all accounts in the wallet that share
2384    /// the given seed.
2385    ///
2386    /// # Panics
2387    ///
2388    /// Panics if the length of the seed is not between 32 and 252 bytes inclusive.
2389    ///
2390    /// [ZIP 316]: https://zips.z.cash/zip-0316
2391    fn create_account(
2392        &mut self,
2393        account_name: &str,
2394        seed: &SecretVec<u8>,
2395        birthday: &AccountBirthday,
2396        key_source: Option<&str>,
2397    ) -> Result<(Self::AccountId, UnifiedSpendingKey), Self::Error>;
2398
2399    /// Tells the wallet to track a specific account index for a given seed.
2400    ///
2401    /// Returns details about the imported account, including the unique account identifier for
2402    /// the newly-created wallet database entry, along with the associated [`UnifiedSpendingKey`].
2403    /// Note that the unique account identifier should *not* be assumed equivalent to the ZIP 32
2404    /// account index. It is an opaque identifier for a pool of funds or set of outputs controlled
2405    /// by a single spending authority.
2406    ///
2407    /// Import accounts with indices that are exactly one greater than the highest existing account
2408    /// index to ensure account indices are contiguous, thereby facilitating automated account
2409    /// recovery.
2410    ///
2411    /// The [`WalletWrite`] trait documentation has more details about account creation and import.
2412    ///
2413    /// # Arguments
2414    /// - `account_name`: A human-readable name for the account.
2415    /// - `seed`: The 256-byte (at least) HD seed from which to derive the account UFVK.
2416    /// - `account_index`: The ZIP 32 account-level component of the HD derivation path at
2417    ///   which to derive the account's UFVK.
2418    /// - `birthday`: Metadata about where to start scanning blocks to find transactions intended
2419    ///   for the account.
2420    /// - `key_source`: A string identifier or other metadata describing the source of the seed.
2421    ///   This is treated as opaque metadata by the wallet backend; it is provided for use by
2422    ///   applications which need to track additional identifying information for an account.
2423    ///
2424    /// # Panics
2425    ///
2426    /// Panics if the length of the seed is not between 32 and 252 bytes inclusive.
2427    ///
2428    /// [ZIP 316]: https://zips.z.cash/zip-0316
2429    fn import_account_hd(
2430        &mut self,
2431        account_name: &str,
2432        seed: &SecretVec<u8>,
2433        account_index: zip32::AccountId,
2434        birthday: &AccountBirthday,
2435        key_source: Option<&str>,
2436    ) -> Result<(Self::Account, UnifiedSpendingKey), Self::Error>;
2437
2438    /// Tells the wallet to track an account using a unified full viewing key.
2439    ///
2440    /// Returns details about the imported account, including the unique account identifier for
2441    /// the newly-created wallet database entry. Unlike the other account creation APIs
2442    /// ([`Self::create_account`] and [`Self::import_account_hd`]), no spending key is returned
2443    /// because the wallet has no information about how the UFVK was derived.
2444    ///
2445    /// Certain optimizations are possible for accounts which will never be used to spend funds. If
2446    /// `spending_key_available` is `false`, the wallet may choose to optimize for this case, in
2447    /// which case any attempt to spend funds from the account will result in an error.
2448    ///
2449    /// The [`WalletWrite`] trait documentation has more details about account creation and import.
2450    ///
2451    /// # Arguments
2452    /// - `account_name`: A human-readable name for the account.
2453    /// - `unified_key`: The UFVK used to detect transactions involving the account.
2454    /// - `birthday`: Metadata about where to start scanning blocks to find transactions intended
2455    ///   for the account.
2456    /// - `purpose`: Metadata describing whether or not data required for spending should be
2457    ///   tracked by the wallet.
2458    /// - `key_source`: A string identifier or other metadata describing the source of the seed.
2459    ///   This is treated as opaque metadata by the wallet backend; it is provided for use by
2460    ///   applications which need to track additional identifying information for an account.
2461    ///
2462    /// # Panics
2463    ///
2464    /// Panics if the length of the seed is not between 32 and 252 bytes inclusive.
2465    fn import_account_ufvk(
2466        &mut self,
2467        account_name: &str,
2468        unified_key: &UnifiedFullViewingKey,
2469        birthday: &AccountBirthday,
2470        purpose: AccountPurpose,
2471        key_source: Option<&str>,
2472    ) -> Result<Self::Account, Self::Error>;
2473
2474    /// Generates, persists, and marks as exposed the next available diversified address for the
2475    /// specified account, given the current addresses known to the wallet.
2476    ///
2477    /// Returns `Ok(None)` if the account identifier does not correspond to a known
2478    /// account.
2479    fn get_next_available_address(
2480        &mut self,
2481        account: Self::AccountId,
2482        request: UnifiedAddressRequest,
2483    ) -> Result<Option<(UnifiedAddress, DiversifierIndex)>, Self::Error>;
2484
2485    /// Generates, persists, and marks as exposed a diversified address for the specified account
2486    /// at the provided diversifier index.
2487    ///
2488    /// Returns `Ok(None)` in the case that it is not possible to generate an address conforming
2489    /// to the provided request at the specified diversifier index. Such a result might arise from
2490    /// the diversifier index not being valid for a [`ReceiverRequirement::Require`]'ed receiver.
2491    /// Some implementations of this trait may return `Err(_)` in some cases to expose more
2492    /// information, which is only accessible in a backend-specific context.
2493    ///
2494    /// Address generation should fail if an address has already been exposed for the given
2495    /// diversifier index and the given request produced an address having different receivers than
2496    /// what was originally exposed.
2497    ///
2498    /// # WARNINGS
2499    /// If an address generated using this method has a transparent receiver and the
2500    /// chosen diversifier index would be outside the wallet's internally-configured gap limit,
2501    /// funds sent to these address are **likely to not be discovered on recovery from seed**. It
2502    /// up to the caller of this method to either ensure that they only request transparent
2503    /// receivers with indices within the range of a reasonable gap limit, or that they ensure that
2504    /// their wallet provides backup facilities that can be used to ensure that funds sent to such
2505    /// addresses are recoverable after a loss of wallet data.
2506    ///
2507    /// [`ReceiverRequirement::Require`]: zcash_keys::keys::ReceiverRequirement::Require
2508    fn get_address_for_index(
2509        &mut self,
2510        account: Self::AccountId,
2511        diversifier_index: DiversifierIndex,
2512        request: UnifiedAddressRequest,
2513    ) -> Result<Option<UnifiedAddress>, Self::Error>;
2514
2515    /// Updates the wallet's view of the blockchain.
2516    ///
2517    /// This method is used to provide the wallet with information about the state of the
2518    /// blockchain, and detect any previously scanned data that needs to be re-validated
2519    /// before proceeding with scanning. It should be called at wallet startup prior to calling
2520    /// [`WalletRead::suggest_scan_ranges`] in order to provide the wallet with the information it
2521    /// needs to correctly prioritize scanning operations.
2522    fn update_chain_tip(&mut self, tip_height: BlockHeight) -> Result<(), Self::Error>;
2523
2524    /// Updates the state of the wallet database by persisting the provided block information,
2525    /// along with the note commitments that were detected when scanning the block for transactions
2526    /// pertaining to this wallet.
2527    ///
2528    /// ### Arguments
2529    /// - `from_state` must be the chain state for the block height prior to the first
2530    ///   block in `blocks`.
2531    /// - `blocks` must be sequential, in order of increasing block height.
2532    fn put_blocks(
2533        &mut self,
2534        from_state: &ChainState,
2535        blocks: Vec<ScannedBlock<Self::AccountId>>,
2536    ) -> Result<(), Self::Error>;
2537
2538    /// Adds a transparent UTXO received by the wallet to the data store.
2539    fn put_received_transparent_utxo(
2540        &mut self,
2541        output: &WalletTransparentOutput,
2542    ) -> Result<Self::UtxoRef, Self::Error>;
2543
2544    /// Caches a decrypted transaction in the persistent wallet store.
2545    fn store_decrypted_tx(
2546        &mut self,
2547        received_tx: DecryptedTransaction<Self::AccountId>,
2548    ) -> Result<(), Self::Error>;
2549
2550    /// Saves information about transactions constructed by the wallet to the persistent
2551    /// wallet store.
2552    ///
2553    /// This must be called before the transactions are sent to the network.
2554    ///
2555    /// Transactions that have been stored by this method should be retransmitted while it
2556    /// is still possible that they could be mined.
2557    fn store_transactions_to_be_sent(
2558        &mut self,
2559        transactions: &[SentTransaction<Self::AccountId>],
2560    ) -> Result<(), Self::Error>;
2561
2562    /// Truncates the wallet database to at most the specified height.
2563    ///
2564    /// Implementations of this method may choose a lower block height to which the data store will
2565    /// be truncated if it is not possible to truncate exactly to the specified height. Upon
2566    /// successful truncation, this method returns the height to which the data store was actually
2567    /// truncated.
2568    ///
2569    /// This method assumes that the state of the underlying data store is consistent up to a
2570    /// particular block height. Since it is possible that a chain reorg might invalidate some
2571    /// stored state, this method must be implemented in order to allow users of this API to
2572    /// "reset" the data store to correctly represent chainstate as of at most the requested block
2573    /// height.
2574    ///
2575    /// After calling this method, the block at the returned height will be the most recent block
2576    /// and all other operations will treat this block as the chain tip for balance determination
2577    /// purposes.
2578    ///
2579    /// There may be restrictions on heights to which it is possible to truncate. Specifically, it
2580    /// will only be possible to truncate to heights at which is is possible to create a witness
2581    /// given the current state of the wallet's note commitment tree.
2582    fn truncate_to_height(&mut self, max_height: BlockHeight) -> Result<BlockHeight, Self::Error>;
2583
2584    /// Reserves the next `n` available ephemeral addresses for the given account.
2585    /// This cannot be undone, so as far as possible, errors associated with transaction
2586    /// construction should have been reported before calling this method.
2587    ///
2588    /// To ensure that sufficient information is stored on-chain to allow recovering
2589    /// funds sent back to any of the used addresses, a "gap limit" of 20 addresses
2590    /// should be observed as described in [BIP 44].
2591    ///
2592    /// Returns an error if there is insufficient space within the gap limit to allocate
2593    /// the given number of addresses, or if the account identifier does not correspond
2594    /// to a known account.
2595    ///
2596    /// [BIP 44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#user-content-Address_gap_limit
2597    #[cfg(feature = "transparent-inputs")]
2598    fn reserve_next_n_ephemeral_addresses(
2599        &mut self,
2600        _account_id: Self::AccountId,
2601        _n: usize,
2602    ) -> Result<Vec<(TransparentAddress, TransparentAddressMetadata)>, Self::Error> {
2603        // Default impl is required for feature-flagged trait methods to prevent
2604        // breakage due to inadvertent activation of features by transitive dependencies
2605        // of the implementing crate.
2606        Ok(vec![])
2607    }
2608
2609    /// Updates the wallet backend with respect to the status of a specific transaction, from the
2610    /// perspective of the main chain.
2611    ///
2612    /// Fully transparent transactions, and transactions that do not contain either shielded inputs
2613    /// or shielded outputs belonging to the wallet, may not be discovered by the process of chain
2614    /// scanning; as a consequence, the wallet must actively query to determine whether such
2615    /// transactions have been mined.
2616    fn set_transaction_status(
2617        &mut self,
2618        _txid: TxId,
2619        _status: TransactionStatus,
2620    ) -> Result<(), Self::Error>;
2621}
2622
2623/// This trait describes a capability for manipulating wallet note commitment trees.
2624#[cfg_attr(feature = "test-dependencies", delegatable_trait)]
2625pub trait WalletCommitmentTrees {
2626    type Error: Debug;
2627
2628    /// The type of the backing [`ShardStore`] for the Sapling note commitment tree.
2629    type SaplingShardStore<'a>: ShardStore<
2630        H = sapling::Node,
2631        CheckpointId = BlockHeight,
2632        Error = Self::Error,
2633    >;
2634
2635    /// Evaluates the given callback function with a reference to the Sapling
2636    /// note commitment tree maintained by the wallet.
2637    fn with_sapling_tree_mut<F, A, E>(&mut self, callback: F) -> Result<A, E>
2638    where
2639        for<'a> F: FnMut(
2640            &'a mut ShardTree<
2641                Self::SaplingShardStore<'a>,
2642                { sapling::NOTE_COMMITMENT_TREE_DEPTH },
2643                SAPLING_SHARD_HEIGHT,
2644            >,
2645        ) -> Result<A, E>,
2646        E: From<ShardTreeError<Self::Error>>;
2647
2648    /// Adds a sequence of Sapling note commitment tree subtree roots to the data store.
2649    ///
2650    /// Each such value should be the Merkle root of a subtree of the Sapling note commitment tree
2651    /// containing 2^[`SAPLING_SHARD_HEIGHT`] note commitments.
2652    fn put_sapling_subtree_roots(
2653        &mut self,
2654        start_index: u64,
2655        roots: &[CommitmentTreeRoot<sapling::Node>],
2656    ) -> Result<(), ShardTreeError<Self::Error>>;
2657
2658    /// The type of the backing [`ShardStore`] for the Orchard note commitment tree.
2659    #[cfg(feature = "orchard")]
2660    type OrchardShardStore<'a>: ShardStore<
2661        H = orchard::tree::MerkleHashOrchard,
2662        CheckpointId = BlockHeight,
2663        Error = Self::Error,
2664    >;
2665
2666    /// Evaluates the given callback function with a reference to the Orchard
2667    /// note commitment tree maintained by the wallet.
2668    #[cfg(feature = "orchard")]
2669    fn with_orchard_tree_mut<F, A, E>(&mut self, callback: F) -> Result<A, E>
2670    where
2671        for<'a> F: FnMut(
2672            &'a mut ShardTree<
2673                Self::OrchardShardStore<'a>,
2674                { ORCHARD_SHARD_HEIGHT * 2 },
2675                ORCHARD_SHARD_HEIGHT,
2676            >,
2677        ) -> Result<A, E>,
2678        E: From<ShardTreeError<Self::Error>>;
2679
2680    /// Adds a sequence of Orchard note commitment tree subtree roots to the data store.
2681    ///
2682    /// Each such value should be the Merkle root of a subtree of the Orchard note commitment tree
2683    /// containing 2^[`ORCHARD_SHARD_HEIGHT`] note commitments.
2684    #[cfg(feature = "orchard")]
2685    fn put_orchard_subtree_roots(
2686        &mut self,
2687        start_index: u64,
2688        roots: &[CommitmentTreeRoot<orchard::tree::MerkleHashOrchard>],
2689    ) -> Result<(), ShardTreeError<Self::Error>>;
2690}