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}