Skip to main content

zcash_client_backend/data_api/
ll.rs

1//! Low-level data API
2//!
3//! This module provides default implementations for several common client operations that rely on
4//! lower-level and more granular access to data. Client implementers should consider using the
5//! utility functions in this module when implementing traits such as [`WalletRead`] and
6//! [`WalletWrite`] in order to provide a consistent and robust user experience.
7//!
8//! [`WalletRead`]: super::WalletRead
9//! [`WalletWrite`]: super::WalletWrite
10
11use core::hash::Hash;
12use std::{collections::HashSet, ops::Range};
13
14use incrementalmerkletree::Position;
15use transparent::bundle::OutPoint;
16use zcash_address::ZcashAddress;
17use zcash_keys::address::Receiver;
18use zcash_primitives::{
19    block::BlockHash,
20    transaction::{Transaction, TransactionData},
21};
22use zcash_protocol::{
23    ShieldedPool, TxId,
24    consensus::{BlockHeight, TxIndex},
25    memo::MemoBytes,
26    value::{BalanceError, Zatoshis},
27    zip318::Zip318Classification,
28};
29use zip32::Scope;
30
31use super::{Account, TransactionStatus, wallet::TargetHeight};
32use crate::{
33    DecryptedOutput, TransferType,
34    wallet::{Recipient, WalletSaplingOutput, WalletTx},
35};
36
37#[cfg(feature = "transparent-inputs")]
38use {
39    crate::wallet::WalletTransparentOutput,
40    transparent::{address::TransparentAddress, keys::TransparentKeyScope},
41    zcash_keys::keys::{UnifiedAddressRequest, transparent::gap_limits::GapLimits},
42};
43
44#[cfg(feature = "orchard")]
45use crate::wallet::WalletOrchardOutput;
46
47pub mod wallet;
48
49/// A trait for types that can provide information about outputs spent by and fees that were paid
50/// for a given transaction.
51pub trait TxMeta {
52    /// Returns an iterator over the references to transparent outputs spent in this transaction.
53    #[cfg(feature = "transparent-inputs")]
54    fn transparent_spends(&self) -> impl Iterator<Item = &OutPoint>;
55
56    /// Returns an iterator over the nullifiers of Sapling notes spent in this transaction.
57    fn sapling_spent_note_nullifiers(&self) -> impl Iterator<Item = &::sapling::Nullifier>;
58
59    /// Returns an iterator over the nullifiers of Orchard notes spent in this transaction.
60    #[cfg(feature = "orchard")]
61    fn orchard_spent_note_nullifiers(&self) -> impl Iterator<Item = &::orchard::note::Nullifier>;
62
63    /// Returns an iterator over the nullifiers of Ironwood notes spent in this transaction.
64    ///
65    /// Ironwood nullifiers share the Orchard nullifier type, but identify notes in the Ironwood
66    /// pool (the transaction's Ironwood bundle, distinct from its Orchard bundle).
67    #[cfg(feature = "orchard")]
68    fn ironwood_spent_note_nullifiers(&self) -> impl Iterator<Item = &::orchard::note::Nullifier>;
69
70    /// Returns the fee paid by this transaction, given a function that can retrieve the value of
71    /// prior transparent outputs spent in the transaction.
72    ///
73    /// Returns `Ok(None)` if insufficient information is available for computing the fee. This
74    /// can occur when:
75    /// - The transaction has transparent inputs whose values are not known to the wallet (e.g.,
76    ///   the wallet has not yet retrieved the transactions that created those outputs).
77    /// - The wallet scanned the chain using compact blocks, which do not include transparent
78    ///   input information. In this case, the wallet cannot determine whether the transaction
79    ///   has any transparent inputs, and thus cannot know if the fee is computable from
80    ///   shielded data alone.
81    fn fee_paid<E, F>(&self, get_prevout: F) -> Result<Option<Zatoshis>, E>
82    where
83        E: From<BalanceError>,
84        F: FnMut(&OutPoint) -> Result<Option<Zatoshis>, E>;
85}
86
87impl TxMeta for Transaction {
88    #[cfg(feature = "transparent-inputs")]
89    fn transparent_spends(&self) -> impl Iterator<Item = &OutPoint> {
90        self.transparent_bundle()
91            .into_iter()
92            .flat_map(|bundle| bundle.vin.iter().map(|txin| txin.prevout()))
93    }
94
95    fn sapling_spent_note_nullifiers(&self) -> impl Iterator<Item = &::sapling::Nullifier> {
96        self.sapling_bundle().into_iter().flat_map(|bundle| {
97            bundle
98                .shielded_spends()
99                .iter()
100                .map(|spend| spend.nullifier())
101        })
102    }
103
104    #[cfg(feature = "orchard")]
105    fn orchard_spent_note_nullifiers(&self) -> impl Iterator<Item = &::orchard::note::Nullifier> {
106        self.orchard_bundle()
107            .into_iter()
108            .flat_map(|bundle| bundle.actions().iter().map(|action| action.nullifier()))
109    }
110
111    #[cfg(feature = "orchard")]
112    fn ironwood_spent_note_nullifiers(&self) -> impl Iterator<Item = &::orchard::note::Nullifier> {
113        self.ironwood_bundle()
114            .into_iter()
115            .flat_map(|bundle| bundle.actions().iter().map(|action| action.nullifier()))
116    }
117
118    fn fee_paid<E, F>(&self, get_prevout: F) -> Result<Option<Zatoshis>, E>
119    where
120        E: From<BalanceError>,
121        F: FnMut(&OutPoint) -> Result<Option<Zatoshis>, E>,
122    {
123        TransactionData::fee_paid(self, get_prevout)
124    }
125}
126
127/// A capability trait that provides low-level wallet database read operations. These operations
128/// are used to provide standard implementations for certain [`WalletWrite`] trait methods.
129///
130/// [`WalletWrite`]: super::WalletWrite
131pub trait LowLevelWalletRead {
132    /// The type of errors that may be generated when querying a wallet data store.
133    type Error;
134
135    /// The type of the account identifier.
136    ///
137    /// An account identifier corresponds to at most a single unified spending key's worth of spend
138    /// authority, such that both received notes and change spendable by that spending authority
139    /// will be interpreted as belonging to that account.
140    type AccountId: Copy + Eq + Hash;
141
142    /// A wallet-internal account identifier, used for efficient lookups within the data store.
143    ///
144    /// Unlike [`AccountId`](Self::AccountId), this type is not intended for use in external
145    /// contexts; it is an ephemeral handle that may change across database migrations or
146    /// backend implementations.
147    type AccountRef: Copy + Eq + Hash;
148
149    /// The type of account records returned by the wallet backend, providing access to
150    /// the account's viewing keys and capabilities.
151    type Account: Account;
152
153    /// A wallet-internal transaction identifier.
154    type TxRef: Copy + Eq + Hash;
155
156    /// Returns the height to which the wallet has been fully scanned: the greatest height
157    /// for which the wallet has trial-decrypted this and all preceding blocks above the
158    /// wallet's birthday height, or `Ok(None)` if no such height exists.
159    fn block_fully_scanned_height(
160        &self,
161    ) -> Result<Option<zcash_protocol::consensus::BlockHeight>, Self::Error>;
162
163    /// Returns the set of account identifiers for accounts that spent notes and/or UTXOs in the
164    /// construction of the given transaction.
165    fn get_funding_accounts<T: TxMeta>(
166        &self,
167        tx: &T,
168    ) -> Result<HashSet<Self::AccountId>, Self::Error> {
169        let mut funding_accounts = HashSet::new();
170
171        #[cfg(feature = "transparent-inputs")]
172        funding_accounts.extend(self.detect_accounts_transparent(tx.transparent_spends())?);
173
174        funding_accounts.extend(self.detect_accounts_sapling(tx.sapling_spent_note_nullifiers())?);
175
176        #[cfg(feature = "orchard")]
177        funding_accounts.extend(self.detect_accounts_orchard(tx.orchard_spent_note_nullifiers())?);
178
179        #[cfg(feature = "orchard")]
180        funding_accounts
181            .extend(self.detect_accounts_ironwood(tx.ironwood_spent_note_nullifiers())?);
182
183        Ok(funding_accounts)
184    }
185
186    /// Returns the most likely wallet address that corresponds to the protocol-level receiver of a
187    /// note or UTXO.
188    ///
189    /// If the wallet database has stored a wallet address that contains the given receiver, then
190    /// that address is returned; otherwise, the most likely address containing that receiver
191    /// will be returned. The "most likely" address should be produced by generating the "standard"
192    /// address (a transparent address if the receiver is transparent, or the default Unified
193    /// address for the account) derived at the receiver's diviersifier index if the receiver is
194    /// for a shielded pool.
195    ///
196    /// Returns `Ok(None)` if the receiver cannot be determined to belong to an address produced by
197    /// this account.
198    fn select_receiving_address(
199        &self,
200        account: Self::AccountId,
201        receiver: &Receiver,
202    ) -> Result<Option<ZcashAddress>, Self::Error>;
203
204    /// Detects and returns the identifier for the account to which the address belongs, if any.
205    ///
206    /// In addition, for HD-derived addresses, the change-level key scope used to derive the
207    /// address is returned, so that the caller is able to determine whether any special handling
208    /// rules apply to the address for the purposes of preserving user privacy (by limiting address
209    /// linking, etc.).
210    #[cfg(feature = "transparent-inputs")]
211    #[allow(clippy::type_complexity)]
212    fn find_account_for_transparent_address(
213        &self,
214        address: &TransparentAddress,
215    ) -> Result<Option<(Self::AccountId, Option<TransparentKeyScope>)>, Self::Error>;
216
217    /// Finds the set of accounts that either provide inputs to or receive outputs from any of the
218    /// provided transactions.
219    #[cfg(feature = "transparent-inputs")]
220    #[allow(clippy::type_complexity)]
221    fn find_involved_accounts(
222        &self,
223        tx_refs: impl IntoIterator<Item = Self::TxRef>,
224    ) -> Result<HashSet<(Self::AccountId, Option<TransparentKeyScope>)>, Self::Error>;
225
226    /// Detects the set of accounts that received transparent outputs corresponding to the provided
227    /// [`OutPoint`]s. This is used to determine which account(s) funded a given transaction.
228    ///
229    /// [`OutPoint`]: transparent::bundle::OutPoint
230    #[cfg(feature = "transparent-inputs")]
231    fn detect_accounts_transparent<'a>(
232        &self,
233        spends: impl Iterator<Item = &'a transparent::bundle::OutPoint>,
234    ) -> Result<HashSet<Self::AccountId>, Self::Error>;
235
236    /// Detects the set of accounts that received Sapling outputs that, when spent, reveal(ed) the
237    /// given [`Nullifier`]s. This is used to determine which account(s) funded a given
238    /// transaction.
239    ///
240    /// [`Nullifier`]: sapling::Nullifier
241    fn detect_accounts_sapling<'a>(
242        &self,
243        spends: impl Iterator<Item = &'a sapling::Nullifier>,
244    ) -> Result<HashSet<Self::AccountId>, Self::Error>;
245
246    /// Detects the set of accounts that received Orchard outputs that, when spent, reveal(ed) the
247    /// given [`Nullifier`]s. This is used to determine which account(s) funded a given
248    /// transaction.
249    ///
250    /// [`Nullifier`]: orchard::note::Nullifier
251    #[cfg(feature = "orchard")]
252    fn detect_accounts_orchard<'a>(
253        &self,
254        spends: impl Iterator<Item = &'a orchard::note::Nullifier>,
255    ) -> Result<HashSet<Self::AccountId>, Self::Error>;
256
257    /// Detects the set of accounts that received Ironwood outputs that, when spent, reveal(ed) the
258    /// given [`Nullifier`]s. This is used to determine which account(s) funded a given
259    /// transaction.
260    ///
261    /// Ironwood notes share the Orchard nullifier type but are tracked separately, so this is
262    /// distinct from [`LowLevelWalletRead::detect_accounts_orchard`].
263    ///
264    /// [`Nullifier`]: orchard::note::Nullifier
265    #[cfg(feature = "orchard")]
266    fn detect_accounts_ironwood<'a>(
267        &self,
268        spends: impl Iterator<Item = &'a orchard::note::Nullifier>,
269    ) -> Result<HashSet<Self::AccountId>, Self::Error>;
270
271    /// Get information about a transparent output controlled by the wallet.
272    ///
273    /// # Parameters
274    /// - `outpoint`: The identifier for the output to be retrieved.
275    /// - `target_height`: The target height of a transaction under construction that will spend the
276    ///   returned output. If this is `None`, no spendability checks are performed.
277    #[cfg(feature = "transparent-inputs")]
278    fn get_wallet_transparent_output(
279        &self,
280        outpoint: &OutPoint,
281        target_height: Option<TargetHeight>,
282    ) -> Result<Option<WalletTransparentOutput<Self::AccountId>>, Self::Error>;
283
284    /// Returns the transactions that spend a transparent output of the referenced transaction,
285    /// for which the fee is unknown and the raw transaction data is available.
286    ///
287    /// Conflicted transactions, and transactions that expired without being mined, are included.
288    /// [`wallet::store_decrypted_tx`] uses this to set the fees of these transactions once the
289    /// values of all their inputs are known.
290    fn get_unknown_fee_spenders_of(
291        &self,
292        tx_ref: Self::TxRef,
293    ) -> Result<Vec<(Self::TxRef, Transaction)>, Self::Error>;
294
295    /// Finds the reference to the transaction that reveals the given Sapling nullifier in the
296    /// backing data store, if known.
297    fn detect_sapling_spend(
298        &self,
299        nf: &::sapling::Nullifier,
300    ) -> Result<Option<Self::TxRef>, Self::Error>;
301
302    /// Finds the reference to the transaction that reveals the given Orchard nullifier in the
303    /// backing data store, if known.
304    #[cfg(feature = "orchard")]
305    fn detect_orchard_spend(
306        &self,
307        nf: &::orchard::note::Nullifier,
308    ) -> Result<Option<Self::TxRef>, Self::Error>;
309
310    /// Finds the reference to the transaction that reveals the given Ironwood nullifier in the
311    /// backing data store, if known. Ironwood nullifiers are Orchard-shaped but are tracked as a
312    /// separate pool.
313    #[cfg(feature = "orchard")]
314    fn detect_ironwood_spend(
315        &self,
316        nf: &::orchard::note::Nullifier,
317    ) -> Result<Option<Self::TxRef>, Self::Error>;
318
319    /// Returns the wallet-internal account reference for the given external account identifier.
320    ///
321    /// This is used to translate between the stable external [`AccountId`](Self::AccountId)
322    /// and the ephemeral internal [`AccountRef`](Self::AccountRef) used for efficient lookups.
323    #[cfg(feature = "transparent-inputs")]
324    fn get_account_ref(
325        &self,
326        account_uuid: Self::AccountId,
327    ) -> Result<Self::AccountRef, Self::Error>;
328
329    /// Returns the full account record for the given wallet-internal account reference.
330    #[cfg(feature = "transparent-inputs")]
331    fn get_account_internal(
332        &self,
333        account_id: Self::AccountRef,
334    ) -> Result<Option<Self::Account>, Self::Error>;
335}
336
337/// A capability trait that provides low-level wallet write operations. These operations are used
338/// to provide standard implementations for certain [`WalletWrite`] trait methods.
339///
340/// [`WalletWrite`]: super::WalletWrite
341pub trait LowLevelWalletWrite: LowLevelWalletRead {
342    /// Add metadata about a block to the wallet data store.
343    #[allow(clippy::too_many_arguments)]
344    fn put_block_meta(
345        &mut self,
346        block_height: BlockHeight,
347        block_hash: BlockHash,
348        block_time: u32,
349        sapling_commitment_tree_size: u32,
350        sapling_output_count: u32,
351        #[cfg(feature = "orchard")] orchard_commitment_tree_size: u32,
352        #[cfg(feature = "orchard")] orchard_action_count: u32,
353        #[cfg(feature = "orchard")] ironwood_commitment_tree_size: u32,
354        #[cfg(feature = "orchard")] ironwood_action_count: u32,
355    ) -> Result<(), Self::Error>;
356
357    /// Add metadata about a transaction to the wallet data store.
358    fn put_tx_meta(
359        &mut self,
360        tx: &WalletTx<Self::AccountId>,
361        height: BlockHeight,
362    ) -> Result<Self::TxRef, Self::Error>;
363
364    /// Adds the given transaction to the wallet.
365    ///
366    /// # Parameters
367    /// - `tx`: The transaction to store.
368    /// - `fee`: The fee paid by the transaction, if known. This may be `None` if the wallet
369    ///   does not have sufficient information to compute the fee (see [`TxMeta::fee_paid`]).
370    /// - `created_at`: The time the transaction was created, if known.
371    /// - `target_height`: The target height for the transaction, if it was created by this
372    ///   wallet.
373    /// - `observed_height`: The height at which the transaction was first observed. For mined
374    ///   transactions, this is the mined height; for unmined transactions, this is typically
375    ///   the chain tip height at the time of observation.
376    fn put_tx_data(
377        &mut self,
378        tx: &Transaction,
379        fee: Option<Zatoshis>,
380        created_at: Option<time::OffsetDateTime>,
381        target_height: Option<TargetHeight>,
382        observed_height: BlockHeight,
383    ) -> Result<Self::TxRef, Self::Error>;
384
385    /// Updates transaction metadata to reflect that the given transaction status has been
386    /// observed.
387    fn set_transaction_status(
388        &mut self,
389        txid: TxId,
390        status: TransactionStatus,
391    ) -> Result<(), Self::Error>;
392
393    /// Records how a transaction classifies against [ZIP 318], so that a wallet can label a
394    /// migration transaction in its history without consulting a migration plan.
395    ///
396    /// A store persists this rather than recomputing it, because the evidence it rests on is only
397    /// all present at once while the transaction is being decrypted. A store that has not been
398    /// given a classification for a transaction MUST report it as
399    /// [`Zip318Classification::Unknown`] rather than as
400    /// [`Nonconforming`](Zip318Classification::Nonconforming): the two mean "we never looked" and
401    /// "we looked and it is not one", and presenting the first as the second asserts a judgement
402    /// the wallet has not made.
403    ///
404    /// This is called at most once per transaction. Every input to the classification is fixed by
405    /// the time a transaction is decrypted, so the value never needs revisiting; in particular it
406    /// does not depend on the mined height, which would otherwise make it change under the store.
407    ///
408    /// [ZIP 318]: https://zips.z.cash/zip-0318
409    fn put_zip318_classification(
410        &mut self,
411        tx_ref: Self::TxRef,
412        classification: Zip318Classification,
413    ) -> Result<(), Self::Error>;
414
415    /// Adds information about a received Sapling note to the wallet, or updates any existing
416    /// record for that output.
417    fn put_received_sapling_note<T: ReceivedSaplingOutput<AccountId = Self::AccountId>>(
418        &mut self,
419        output: &T,
420        tx_ref: Self::TxRef,
421        target_or_mined_height: Option<BlockHeight>,
422        spent_in: Option<Self::TxRef>,
423    ) -> Result<(), Self::Error>;
424
425    /// Updates the backing store to indicate that the Sapling output having the given nullifier is
426    /// spent in the transaction referenced by `spent_in_tx`. This may result in multiple distinct
427    /// transactions being recorded as having spent the note; only one of these transactions will
428    /// end up having been mined (by consensus). If an attempt is made to associate a nullifier
429    /// with a mined transaction, and another mined transaction reveals the same nullifier,
430    /// implementations of this method must return an error.
431    ///
432    /// Returns `Ok(true)` if a a new record was added to the data store.
433    fn mark_sapling_note_spent(
434        &mut self,
435        nf: &::sapling::Nullifier,
436        spent_in_tx: Self::TxRef,
437    ) -> Result<bool, Self::Error>;
438
439    /// Causes the given Sapling output nullifiers to be tracked by the wallet.
440    ///
441    /// When scanning the chain out-of-order, it is necessary to store any nullifiers observed
442    /// after a gap in the scanned blocks until the blocks in that gap have been fully scanned, in
443    /// order to be able to immediately detect that a received output has already been spent. The
444    /// data store should track a mapping from each nullifier to the block, the index of the
445    /// transaction in the block, and the transaction ID, so when a note is received within that
446    /// "gap" its spentness can be determined by checking in the store. For space efficiency, the
447    /// backing store may use the combination of block height and index within the block instead of
448    /// the txid for transaction identification.
449    ///
450    /// # Parameters
451    /// - `block_height`: The height of the block containing the nullifiers.
452    /// - `nfs`: A slice of tuples, where each tuple contains:
453    ///   - The transaction ID of the transaction revealing the nullifiers.
454    ///   - The index of the transaction within the block.
455    ///   - The vector of nullifiers revealed by the spends in that transaction.
456    fn track_block_sapling_nullifiers(
457        &mut self,
458        block_height: BlockHeight,
459        nfs: &[(TxIndex, TxId, Vec<::sapling::Nullifier>)],
460    ) -> Result<(), Self::Error>;
461
462    /// Adds information about a received Orchard note to the wallet, or updates any existing
463    /// record for that output.
464    #[cfg(feature = "orchard")]
465    fn put_received_orchard_note<T: ReceivedOrchardOutput<AccountId = Self::AccountId>>(
466        &mut self,
467        output: &T,
468        tx_ref: Self::TxRef,
469        target_or_mined_height: Option<BlockHeight>,
470        spent_in: Option<Self::TxRef>,
471    ) -> Result<(), Self::Error>;
472
473    /// Adds information about a received Ironwood note to the wallet, or updates any existing
474    /// record for that output. Ironwood notes are Orchard-shaped but are stored as a separate
475    /// pool.
476    #[cfg(feature = "orchard")]
477    fn put_received_ironwood_note<T: ReceivedOrchardOutput<AccountId = Self::AccountId>>(
478        &mut self,
479        output: &T,
480        tx_ref: Self::TxRef,
481        target_or_mined_height: Option<BlockHeight>,
482        spent_in: Option<Self::TxRef>,
483    ) -> Result<(), Self::Error>;
484
485    /// Updates the backing store to indicate that the Orchard output having the given nullifier is
486    /// spent in the transaction referenced by `spent_in_tx`. This may result in multiple distinct
487    /// transactions being recorded as having spent the note; only one of these transactions will
488    /// end up having been mined (by consensus). If an attempt is made to associate a nullifier
489    /// with a mined transaction, and another mined transaction reveals the same nullifier,
490    /// implementations of this method must return an error.
491    ///
492    /// Returns `Ok(true)` if a a new record was added to the data store.
493    #[cfg(feature = "orchard")]
494    fn mark_orchard_note_spent(
495        &mut self,
496        nf: &::orchard::note::Nullifier,
497        tx_ref: Self::TxRef,
498    ) -> Result<bool, Self::Error>;
499
500    /// Updates the backing store to indicate that the Ironwood output having the given nullifier is
501    /// spent in the transaction referenced by `spent_in_tx`. Behaves like
502    /// [`mark_orchard_note_spent`](Self::mark_orchard_note_spent) but for the Ironwood pool.
503    #[cfg(feature = "orchard")]
504    fn mark_ironwood_note_spent(
505        &mut self,
506        nf: &::orchard::note::Nullifier,
507        tx_ref: Self::TxRef,
508    ) -> Result<bool, Self::Error>;
509
510    /// Causes the given Orchard output nullifiers to be tracked by the wallet.
511    ///
512    /// When scanning the chain out-of-order, it is necessary to store any nullifiers observed
513    /// after a gap in the scanned blocks until the blocks in that gap have been fully scanned, in
514    /// order to be able to immediately detect that a received output has already been spent. The
515    /// data store should track a mapping from each nullifier to the block, the index of the
516    /// transaction in the block, and the transaction ID, so when a note is received within that
517    /// "gap" its spentness can be determined by checking in the store. For space efficiency, the
518    /// backing store may use the combination of block height and index within the block instead of
519    /// the txid for transaction identification.
520    ///
521    /// # Parameters
522    /// - `block_height`: The height of the block containing the nullifiers.
523    /// - `nfs`: A slice of tuples, where each tuple contains:
524    ///   - The transaction ID of the transaction revealing the nullifiers.
525    ///   - The index of the transaction within the block.
526    ///   - The vector of nullifiers revealed by the actions in that transaction.
527    #[cfg(feature = "orchard")]
528    fn track_block_orchard_nullifiers(
529        &mut self,
530        block_height: BlockHeight,
531        nfs: &[(TxIndex, TxId, Vec<::orchard::note::Nullifier>)],
532    ) -> Result<(), Self::Error>;
533
534    /// Causes the given Ironwood output nullifiers to be tracked by the wallet. Behaves like
535    /// [`track_block_orchard_nullifiers`](Self::track_block_orchard_nullifiers) but for the
536    /// Ironwood pool.
537    #[cfg(feature = "orchard")]
538    fn track_block_ironwood_nullifiers(
539        &mut self,
540        block_height: BlockHeight,
541        nfs: &[(TxIndex, TxId, Vec<::orchard::note::Nullifier>)],
542    ) -> Result<(), Self::Error>;
543
544    /// Causes the given transparent spends to be tracked by the wallet.
545    ///
546    /// This is the transparent counterpart of the `track_block_*_nullifiers` methods
547    /// ([`track_block_sapling_nullifiers`](Self::track_block_sapling_nullifiers) and its Orchard
548    /// and Ironwood equivalents), and exists for the same reason: a transparent output created
549    /// in a block range the wallet has not yet scanned cannot be recognized as its own at the
550    /// time its spend is observed, so the spending transaction must be recorded against the
551    /// outpoint and resolved if the output is later discovered.
552    ///
553    /// The spending transactions here are in general not transactions the wallet stores, so an
554    /// implementation must be able to record them without a corresponding transaction record; for
555    /// space efficiency it may use the combination of block height and index within the block
556    /// instead of the txid, as for the nullifier maps. Entries are removed by
557    /// [`prune_tracked_spends`](Self::prune_tracked_spends).
558    ///
559    /// # Parameters
560    /// - `block_height`: The height of the block containing the spending transactions.
561    /// - `spends`: A slice of tuples, where each tuple contains:
562    ///   - The index of the transaction within the block.
563    ///   - The transaction ID of the transaction effecting the spends.
564    ///   - The outpoints spent by that transaction's transparent inputs that do not spend any
565    ///     output the wallet already knows of.
566    #[cfg(feature = "transparent-inputs")]
567    fn track_block_transparent_spends(
568        &mut self,
569        block_height: BlockHeight,
570        spends: &[(TxIndex, TxId, Vec<OutPoint>)],
571    ) -> Result<(), Self::Error>;
572
573    /// Removes tracked spends that are no longer needed for spend detection.
574    ///
575    /// This function prunes the entries recorded by
576    /// [`track_block_sapling_nullifiers`](Self::track_block_sapling_nullifiers) and its
577    /// per-pool counterparts, and by
578    /// [`track_block_transparent_spends`](Self::track_block_transparent_spends), that were
579    /// recorded at block heights less than `(fully_scanned_height - pruning_depth)`, where
580    /// `fully_scanned_height` is the height of the wallet's fully scanned chain state. These
581    /// entries are no longer needed because any wallet output they could have spent would
582    /// already have been discovered during scanning.
583    ///
584    /// # Parameters
585    /// - `pruning_depth`: The number of blocks below the fully scanned height at which to
586    ///   prune tracked spends.
587    fn prune_tracked_spends(&mut self, pruning_depth: u32) -> Result<(), Self::Error>;
588
589    /// Records information about a transaction output that your wallet created, from the constituent
590    /// properties of that output.
591    ///
592    /// # Parameters
593    /// - `tx_ref`: The identifier for the transaction that produced the output.
594    /// - `output_index`: The index of the output in the bundle corresponding to the pool where the
595    ///   output was created.
596    /// - `recipient`: Information about the address or account that the output is being sent to.
597    /// - `value`: The value of the output.
598    /// - `memo`: The memo attached to the output, if any.
599    fn put_sent_output(
600        &mut self,
601        from_account_uuid: Self::AccountId,
602        tx_ref: Self::TxRef,
603        output_index: usize,
604        recipient: &Recipient<Self::AccountId>,
605        value: Zatoshis,
606        memo: Option<&MemoBytes>,
607    ) -> Result<(), Self::Error>;
608
609    /// Updates the wallet's view of a transaction to indicate the miner's fee paid by the
610    /// transaction.
611    fn update_tx_fee(&mut self, tx_ref: Self::TxRef, fee: Zatoshis) -> Result<(), Self::Error>;
612
613    /// Adds a transparent output observed by the wallet to the data store, or updates any existing
614    /// record for that output.
615    ///
616    /// # Parameters
617    /// - `output`: The output data.
618    /// - `observation_height`: This should be set to the mined height, if known. If the TXO is
619    ///   known to be mined but the height at which it was mined is unknown, this should be set to
620    ///   the chain tip height at the time of the observation; if the utxo is known to be unmined,
621    ///   this should be set to the mempool height.
622    /// - `known_unspent`: Set to `true` if the output is known to be a member of the UTXO set as
623    ///   of the given observation height.
624    #[cfg(feature = "transparent-inputs")]
625    fn put_transparent_output(
626        &mut self,
627        output: &crate::wallet::WalletTransparentOutput<Self::AccountId>,
628        observation_height: BlockHeight,
629        known_unspent: bool,
630    ) -> Result<(Self::AccountId, Option<TransparentKeyScope>), Self::Error>;
631
632    /// Updates the backing store to indicate that the UTXO referred to by `outpoint` is spent
633    /// in the transaction referenced by `spent_in_tx`.
634    #[cfg(feature = "transparent-inputs")]
635    fn mark_transparent_utxo_spent(
636        &mut self,
637        outpoint: &OutPoint,
638        spent_in_tx: Self::TxRef,
639    ) -> Result<bool, Self::Error>;
640
641    /// Updates the wallet backend by generating and caching addresses for the given key scope such
642    /// that at least the backend's configured gap limit worth of addresses exist at indices
643    /// successive from that of the last address that received a mined transaction.
644    ///
645    /// # Parameters
646    /// - `account_id`: The ID of the account holding the UFVK from which addresses should be
647    ///   generated.
648    /// - `key_scope`: The transparent key scope for addresses to generate. Implementations may
649    ///   choose to only support the `external`, `internal`, and `ephemeral` key scopes and return
650    ///   an error if an unrecognized scope is used.
651    /// - `request`: A request for the Unified Address that will be generated with a diversifier
652    ///   index equal to the [`BIP 44`] `address_index` of each generated address.
653    ///
654    /// [`BIP 44`]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
655    #[cfg(feature = "transparent-inputs")]
656    fn generate_transparent_gap_addresses(
657        &mut self,
658        account_id: Self::AccountId,
659        key_scope: TransparentKeyScope,
660        request: UnifiedAddressRequest,
661    ) -> Result<(), Self::Error>;
662
663    /// Adds a [`TransactionDataRequest::Enhancement`] request for the enhancement of the given
664    /// transaction to the transaction data request queue. The `dependent_tx_ref` parameter
665    /// specifies the transaction that caused this request to be generated, likely as part of the
666    /// process of traversing the transparent transaction graph by inspecting the inputs of a
667    /// transaction with outputs that were received by the wallet.
668    ///
669    /// [`TransactionDataRequest::Enhancement`]: super::TransactionDataRequest
670    fn queue_tx_retrieval(
671        &mut self,
672        txids: impl Iterator<Item = TxId>,
673        dependent_tx_ref: Option<Self::TxRef>,
674    ) -> Result<(), Self::Error>;
675
676    /// Adds a [`TransactionDataRequest::GetStatus`] request for a transaction whose mined status
677    /// cannot be learned through ordinary compact-block scanning.
678    ///
679    /// The request intent must remain durable while the transaction is mined, so that it becomes
680    /// active again if a chain rewind un-mines the transaction.
681    ///
682    /// [`TransactionDataRequest::GetStatus`]: super::TransactionDataRequest
683    fn queue_tx_status(&mut self, txid: TxId) -> Result<(), Self::Error>;
684
685    /// Adds a [`TransactionDataRequest::TransactionsInvolvingAddress`] request to the transaction
686    /// data request queue. When the transparent output of `tx_ref` at output index `output_index`
687    /// (which must have been received at `receiving_address`) is detected as having been spent,
688    /// this request will be considered fulfilled.
689    ///
690    /// NOTE: The somewhat awkward API of this method is a historical artifact; if the light wallet
691    /// protocol is in the future updated to expose a mechanism to find the transaction that spends
692    /// a particular `OutPoint`, the `TransactionsInvolvingAddress` variant and this method will
693    /// likely be removed.
694    ///
695    /// [`TransactionDataRequest::TransactionsInvolvingAddress`]: super::TransactionDataRequest
696    #[cfg(feature = "transparent-inputs")]
697    fn queue_transparent_spend_detection(
698        &mut self,
699        receiving_address: TransparentAddress,
700        tx_ref: Self::TxRef,
701        output_index: u32,
702    ) -> Result<(), Self::Error>;
703
704    /// Adds [`TransactionDataRequest::Enhancement`] requests for transactions that generated the
705    /// transparent inputs to the provided [`DecryptedTransaction`] to the transaction data request
706    /// queue.
707    ///
708    /// [`TransactionDataRequest::Enhancement`]: super::TransactionDataRequest
709    /// [`DecryptedTransaction`]: super::DecryptedTransaction
710    #[cfg(feature = "transparent-inputs")]
711    fn queue_transparent_input_retrieval(
712        &mut self,
713        tx_ref: Self::TxRef,
714        d_tx: &super::DecryptedTransaction<Transaction, Self::AccountId>,
715    ) -> Result<(), Self::Error>;
716
717    /// Deletes the [`TransactionDataRequest::Enhancement`] request for the given transaction ID
718    /// from the transaction data request queue, without removing any durable status-observation
719    /// intent for the transaction.
720    ///
721    /// [`TransactionDataRequest::Enhancement`]: super::TransactionDataRequest
722    fn delete_retrieval_queue_entries(&mut self, txid: TxId) -> Result<(), Self::Error>;
723
724    /// Updates the state of the wallet backend to indicate that the given range of blocks has been
725    /// fully scanned, identifying the position in the note commitment tree of any notes belonging
726    /// to the wallet that were discovered in the process of scanning.
727    fn notify_scan_complete(
728        &mut self,
729        range: Range<BlockHeight>,
730        wallet_note_positions: &[(ShieldedPool, Position)],
731    ) -> Result<(), Self::Error>;
732
733    #[cfg(feature = "transparent-inputs")]
734    fn update_gap_limits(
735        &mut self,
736        gap_limits: &GapLimits,
737        txid: TxId,
738        observation_height: BlockHeight,
739    ) -> Result<(), Self::Error>;
740}
741
742/// This trait provides a generalization over output representations.
743pub trait ReceivedShieldedOutput {
744    type AccountId;
745    type Note;
746    type Nullifier;
747
748    /// Returns the index of the output within its corresponding bundle.
749    fn index(&self) -> usize;
750    /// Returns the account ID for the account that received this output.
751    fn account_id(&self) -> Self::AccountId;
752    /// Returns the note.
753    fn note(&self) -> &Self::Note;
754    /// Returns the received note, as a [`Note`].
755    ///
756    /// [`Note`]: crate::wallet::Note
757    fn to_wallet_note(&self) -> crate::wallet::Note;
758    /// Returns any memo associated with the output.
759    fn memo(&self) -> Option<&MemoBytes>;
760    /// Returns a [`TransferType`] value that is determined based upon what type of key was used to
761    /// decrypt the transaction.
762    fn transfer_type(&self) -> TransferType;
763    /// Returns whether or not the received output is counted as wallet-internal change, for the
764    /// purpose of display.
765    fn is_change(&self) -> bool;
766    /// Returns the nullifier that will be revealed when the note is spent, if the output was
767    /// observed using a key that provides the capability for nullifier computation.
768    fn nullifier(&self) -> Option<&Self::Nullifier>;
769    /// Returns the position of the note in the note commitment tree, if the transaction that
770    /// produced the output has been mined.
771    fn note_commitment_tree_position(&self) -> Option<Position>;
772    /// Returns the HD derivation scope of the viewing key that decrypted the note, if known.
773    fn recipient_key_scope(&self) -> Option<Scope>;
774}
775
776/// This trait provides a generalization over shielded Sapling output representations.
777pub trait ReceivedSaplingOutput:
778    ReceivedShieldedOutput<Note = ::sapling::Note, Nullifier = ::sapling::Nullifier>
779{
780}
781impl<T: ReceivedShieldedOutput<Note = ::sapling::Note, Nullifier = ::sapling::Nullifier>>
782    ReceivedSaplingOutput for T
783{
784}
785
786impl<AccountId: Copy> ReceivedShieldedOutput for WalletSaplingOutput<AccountId> {
787    type AccountId = AccountId;
788    type Note = ::sapling::Note;
789    type Nullifier = ::sapling::Nullifier;
790
791    fn index(&self) -> usize {
792        self.index()
793    }
794    fn account_id(&self) -> Self::AccountId {
795        *WalletSaplingOutput::account_id(self)
796    }
797    fn note(&self) -> &Self::Note {
798        WalletSaplingOutput::note(self)
799    }
800    fn to_wallet_note(&self) -> crate::wallet::Note {
801        crate::wallet::Note::Sapling(self.note().clone())
802    }
803    fn memo(&self) -> Option<&MemoBytes> {
804        None
805    }
806    fn transfer_type(&self) -> TransferType {
807        if self.is_change() {
808            TransferType::AccountInternal
809        } else {
810            TransferType::Incoming
811        }
812    }
813    fn is_change(&self) -> bool {
814        WalletSaplingOutput::is_change(self)
815    }
816    fn nullifier(&self) -> Option<&::sapling::Nullifier> {
817        self.nf()
818    }
819    fn note_commitment_tree_position(&self) -> Option<Position> {
820        Some(WalletSaplingOutput::note_commitment_tree_position(self))
821    }
822    fn recipient_key_scope(&self) -> Option<Scope> {
823        self.recipient_key_scope()
824    }
825}
826
827impl<AccountId: Copy> ReceivedShieldedOutput for DecryptedOutput<::sapling::Note, AccountId> {
828    type AccountId = AccountId;
829    type Note = ::sapling::Note;
830    type Nullifier = ::sapling::Nullifier;
831
832    fn index(&self) -> usize {
833        self.index()
834    }
835    fn account_id(&self) -> Self::AccountId {
836        *self.account()
837    }
838    fn note(&self) -> &Self::Note {
839        DecryptedOutput::note(self)
840    }
841    fn to_wallet_note(&self) -> crate::wallet::Note {
842        crate::wallet::Note::Sapling(self.note().clone())
843    }
844    fn memo(&self) -> Option<&MemoBytes> {
845        Some(self.memo())
846    }
847    fn transfer_type(&self) -> TransferType {
848        self.transfer_type()
849    }
850    fn is_change(&self) -> bool {
851        self.transfer_type() == TransferType::AccountInternal
852    }
853    fn nullifier(&self) -> Option<&::sapling::Nullifier> {
854        None
855    }
856    fn note_commitment_tree_position(&self) -> Option<Position> {
857        None
858    }
859    fn recipient_key_scope(&self) -> Option<Scope> {
860        if self.transfer_type() == TransferType::AccountInternal {
861            Some(Scope::Internal)
862        } else {
863            Some(Scope::External)
864        }
865    }
866}
867
868/// This trait provides a generalization over shielded Orchard output representations.
869#[cfg(feature = "orchard")]
870pub trait ReceivedOrchardOutput:
871    ReceivedShieldedOutput<Note = ::orchard::Note, Nullifier = ::orchard::note::Nullifier>
872{
873}
874#[cfg(feature = "orchard")]
875impl<T: ReceivedShieldedOutput<Note = ::orchard::Note, Nullifier = ::orchard::note::Nullifier>>
876    ReceivedOrchardOutput for T
877{
878}
879
880#[cfg(feature = "orchard")]
881impl<AccountId: Copy> ReceivedShieldedOutput for WalletOrchardOutput<AccountId> {
882    type AccountId = AccountId;
883    type Note = ::orchard::Note;
884    type Nullifier = ::orchard::note::Nullifier;
885
886    fn index(&self) -> usize {
887        self.index()
888    }
889    fn account_id(&self) -> Self::AccountId {
890        *WalletOrchardOutput::account_id(self)
891    }
892    fn note(&self) -> &Self::Note {
893        &self.note().0
894    }
895    fn to_wallet_note(&self) -> crate::wallet::Note {
896        let (note, pool) = self.note();
897        crate::wallet::Note::Orchard {
898            note: *note,
899            pool: *pool,
900        }
901    }
902    fn memo(&self) -> Option<&MemoBytes> {
903        None
904    }
905    fn transfer_type(&self) -> TransferType {
906        if self.is_change() {
907            TransferType::AccountInternal
908        } else {
909            TransferType::Incoming
910        }
911    }
912    fn is_change(&self) -> bool {
913        WalletOrchardOutput::is_change(self)
914    }
915    fn nullifier(&self) -> Option<&::orchard::note::Nullifier> {
916        self.nf()
917    }
918    fn note_commitment_tree_position(&self) -> Option<Position> {
919        Some(WalletOrchardOutput::note_commitment_tree_position(self))
920    }
921    fn recipient_key_scope(&self) -> Option<Scope> {
922        self.recipient_key_scope()
923    }
924}
925
926#[cfg(feature = "orchard")]
927impl<AccountId: Copy> ReceivedShieldedOutput
928    for DecryptedOutput<(::orchard::Note, ::orchard::ValuePool), AccountId>
929{
930    type AccountId = AccountId;
931    type Note = ::orchard::Note;
932    type Nullifier = ::orchard::note::Nullifier;
933
934    fn index(&self) -> usize {
935        self.index()
936    }
937    fn account_id(&self) -> Self::AccountId {
938        *self.account()
939    }
940    fn note(&self) -> &Self::Note {
941        &self.note().0
942    }
943    fn to_wallet_note(&self) -> crate::wallet::Note {
944        let (note, pool) = self.note();
945        crate::wallet::Note::Orchard {
946            note: *note,
947            pool: *pool,
948        }
949    }
950    fn memo(&self) -> Option<&MemoBytes> {
951        Some(self.memo())
952    }
953    fn transfer_type(&self) -> TransferType {
954        self.transfer_type()
955    }
956    fn is_change(&self) -> bool {
957        self.transfer_type() == TransferType::AccountInternal
958    }
959    fn nullifier(&self) -> Option<&::orchard::note::Nullifier> {
960        None
961    }
962    fn note_commitment_tree_position(&self) -> Option<Position> {
963        None
964    }
965    fn recipient_key_scope(&self) -> Option<Scope> {
966        if self.transfer_type() == TransferType::AccountInternal {
967            Some(Scope::Internal)
968        } else {
969            Some(Scope::External)
970        }
971    }
972}