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 vector of transactions in the wallet that spend the transparent outputs of the
285    /// referenced transaction, but for which the amount of fee paid is unknown. This should
286    /// include conflicted transactions and transactions that have expired without having been
287    /// mined.
288    ///
289    /// This is used as part of [`wallet::store_decrypted_tx`] to allow downstream transactions'
290    /// fee amounts to be updated once the value of all their inputs are known.
291    fn get_txs_spending_transparent_outputs_of(
292        &self,
293        tx_ref: Self::TxRef,
294    ) -> Result<Vec<(Self::TxRef, Transaction)>, Self::Error>;
295
296    /// Finds the reference to the transaction that reveals the given Sapling nullifier in the
297    /// backing data store, if known.
298    fn detect_sapling_spend(
299        &self,
300        nf: &::sapling::Nullifier,
301    ) -> Result<Option<Self::TxRef>, Self::Error>;
302
303    /// Finds the reference to the transaction that reveals the given Orchard nullifier in the
304    /// backing data store, if known.
305    #[cfg(feature = "orchard")]
306    fn detect_orchard_spend(
307        &self,
308        nf: &::orchard::note::Nullifier,
309    ) -> Result<Option<Self::TxRef>, Self::Error>;
310
311    /// Finds the reference to the transaction that reveals the given Ironwood nullifier in the
312    /// backing data store, if known. Ironwood nullifiers are Orchard-shaped but are tracked as a
313    /// separate pool.
314    #[cfg(feature = "orchard")]
315    fn detect_ironwood_spend(
316        &self,
317        nf: &::orchard::note::Nullifier,
318    ) -> Result<Option<Self::TxRef>, Self::Error>;
319
320    /// Returns the wallet-internal account reference for the given external account identifier.
321    ///
322    /// This is used to translate between the stable external [`AccountId`](Self::AccountId)
323    /// and the ephemeral internal [`AccountRef`](Self::AccountRef) used for efficient lookups.
324    #[cfg(feature = "transparent-inputs")]
325    fn get_account_ref(
326        &self,
327        account_uuid: Self::AccountId,
328    ) -> Result<Self::AccountRef, Self::Error>;
329
330    /// Returns the full account record for the given wallet-internal account reference.
331    #[cfg(feature = "transparent-inputs")]
332    fn get_account_internal(
333        &self,
334        account_id: Self::AccountRef,
335    ) -> Result<Option<Self::Account>, Self::Error>;
336}
337
338/// A capability trait that provides low-level wallet write operations. These operations are used
339/// to provide standard implementations for certain [`WalletWrite`] trait methods.
340///
341/// [`WalletWrite`]: super::WalletWrite
342pub trait LowLevelWalletWrite: LowLevelWalletRead {
343    /// Add metadata about a block to the wallet data store.
344    #[allow(clippy::too_many_arguments)]
345    fn put_block_meta(
346        &mut self,
347        block_height: BlockHeight,
348        block_hash: BlockHash,
349        block_time: u32,
350        sapling_commitment_tree_size: u32,
351        sapling_output_count: u32,
352        #[cfg(feature = "orchard")] orchard_commitment_tree_size: u32,
353        #[cfg(feature = "orchard")] orchard_action_count: u32,
354        #[cfg(feature = "orchard")] ironwood_commitment_tree_size: u32,
355        #[cfg(feature = "orchard")] ironwood_action_count: u32,
356    ) -> Result<(), Self::Error>;
357
358    /// Add metadata about a transaction to the wallet data store.
359    fn put_tx_meta(
360        &mut self,
361        tx: &WalletTx<Self::AccountId>,
362        height: BlockHeight,
363    ) -> Result<Self::TxRef, Self::Error>;
364
365    /// Adds the given transaction to the wallet.
366    ///
367    /// # Parameters
368    /// - `tx`: The transaction to store.
369    /// - `fee`: The fee paid by the transaction, if known. This may be `None` if the wallet
370    ///   does not have sufficient information to compute the fee (see [`TxMeta::fee_paid`]).
371    /// - `created_at`: The time the transaction was created, if known.
372    /// - `target_height`: The target height for the transaction, if it was created by this
373    ///   wallet.
374    /// - `observed_height`: The height at which the transaction was first observed. For mined
375    ///   transactions, this is the mined height; for unmined transactions, this is typically
376    ///   the chain tip height at the time of observation.
377    fn put_tx_data(
378        &mut self,
379        tx: &Transaction,
380        fee: Option<Zatoshis>,
381        created_at: Option<time::OffsetDateTime>,
382        target_height: Option<TargetHeight>,
383        observed_height: BlockHeight,
384    ) -> Result<Self::TxRef, Self::Error>;
385
386    /// Updates transaction metadata to reflect that the given transaction status has been
387    /// observed.
388    fn set_transaction_status(
389        &mut self,
390        txid: TxId,
391        status: TransactionStatus,
392    ) -> Result<(), Self::Error>;
393
394    /// Records how a transaction classifies against [ZIP 318], so that a wallet can label a
395    /// migration transaction in its history without consulting a migration plan.
396    ///
397    /// A store persists this rather than recomputing it, because the evidence it rests on is only
398    /// all present at once while the transaction is being decrypted. A store that has not been
399    /// given a classification for a transaction MUST report it as
400    /// [`Zip318Classification::Unknown`] rather than as
401    /// [`Nonconforming`](Zip318Classification::Nonconforming): the two mean "we never looked" and
402    /// "we looked and it is not one", and presenting the first as the second asserts a judgement
403    /// the wallet has not made.
404    ///
405    /// This is called at most once per transaction. Every input to the classification is fixed by
406    /// the time a transaction is decrypted, so the value never needs revisiting; in particular it
407    /// does not depend on the mined height, which would otherwise make it change under the store.
408    ///
409    /// [ZIP 318]: https://zips.z.cash/zip-0318
410    fn put_zip318_classification(
411        &mut self,
412        tx_ref: Self::TxRef,
413        classification: Zip318Classification,
414    ) -> Result<(), Self::Error>;
415
416    /// Adds information about a received Sapling note to the wallet, or updates any existing
417    /// record for that output.
418    fn put_received_sapling_note<T: ReceivedSaplingOutput<AccountId = Self::AccountId>>(
419        &mut self,
420        output: &T,
421        tx_ref: Self::TxRef,
422        target_or_mined_height: Option<BlockHeight>,
423        spent_in: Option<Self::TxRef>,
424    ) -> Result<(), Self::Error>;
425
426    /// Updates the backing store to indicate that the Sapling output having the given nullifier is
427    /// spent in the transaction referenced by `spent_in_tx`. This may result in multiple distinct
428    /// transactions being recorded as having spent the note; only one of these transactions will
429    /// end up having been mined (by consensus). If an attempt is made to associate a nullifier
430    /// with a mined transaction, and another mined transaction reveals the same nullifier,
431    /// implementations of this method must return an error.
432    ///
433    /// Returns `Ok(true)` if a a new record was added to the data store.
434    fn mark_sapling_note_spent(
435        &mut self,
436        nf: &::sapling::Nullifier,
437        spent_in_tx: Self::TxRef,
438    ) -> Result<bool, Self::Error>;
439
440    /// Causes the given Sapling output nullifiers to be tracked by the wallet.
441    ///
442    /// When scanning the chain out-of-order, it is necessary to store any nullifiers observed
443    /// after a gap in the scanned blocks until the blocks in that gap have been fully scanned, in
444    /// order to be able to immediately detect that a received output has already been spent. The
445    /// data store should track a mapping from each nullifier to the block, the index of the
446    /// transaction in the block, and the transaction ID, so when a note is received within that
447    /// "gap" its spentness can be determined by checking in the store. For space efficiency, the
448    /// backing store may use the combination of block height and index within the block instead of
449    /// the txid for transaction identification.
450    ///
451    /// # Parameters
452    /// - `block_height`: The height of the block containing the nullifiers.
453    /// - `nfs`: A slice of tuples, where each tuple contains:
454    ///   - The transaction ID of the transaction revealing the nullifiers.
455    ///   - The index of the transaction within the block.
456    ///   - The vector of nullifiers revealed by the spends in that transaction.
457    fn track_block_sapling_nullifiers(
458        &mut self,
459        block_height: BlockHeight,
460        nfs: &[(TxIndex, TxId, Vec<::sapling::Nullifier>)],
461    ) -> Result<(), Self::Error>;
462
463    /// Adds information about a received Orchard note to the wallet, or updates any existing
464    /// record for that output.
465    #[cfg(feature = "orchard")]
466    fn put_received_orchard_note<T: ReceivedOrchardOutput<AccountId = Self::AccountId>>(
467        &mut self,
468        output: &T,
469        tx_ref: Self::TxRef,
470        target_or_mined_height: Option<BlockHeight>,
471        spent_in: Option<Self::TxRef>,
472    ) -> Result<(), Self::Error>;
473
474    /// Adds information about a received Ironwood note to the wallet, or updates any existing
475    /// record for that output. Ironwood notes are Orchard-shaped but are stored as a separate
476    /// pool.
477    #[cfg(feature = "orchard")]
478    fn put_received_ironwood_note<T: ReceivedOrchardOutput<AccountId = Self::AccountId>>(
479        &mut self,
480        output: &T,
481        tx_ref: Self::TxRef,
482        target_or_mined_height: Option<BlockHeight>,
483        spent_in: Option<Self::TxRef>,
484    ) -> Result<(), Self::Error>;
485
486    /// Updates the backing store to indicate that the Orchard output having the given nullifier is
487    /// spent in the transaction referenced by `spent_in_tx`. This may result in multiple distinct
488    /// transactions being recorded as having spent the note; only one of these transactions will
489    /// end up having been mined (by consensus). If an attempt is made to associate a nullifier
490    /// with a mined transaction, and another mined transaction reveals the same nullifier,
491    /// implementations of this method must return an error.
492    ///
493    /// Returns `Ok(true)` if a a new record was added to the data store.
494    #[cfg(feature = "orchard")]
495    fn mark_orchard_note_spent(
496        &mut self,
497        nf: &::orchard::note::Nullifier,
498        tx_ref: Self::TxRef,
499    ) -> Result<bool, Self::Error>;
500
501    /// Updates the backing store to indicate that the Ironwood output having the given nullifier is
502    /// spent in the transaction referenced by `spent_in_tx`. Behaves like
503    /// [`mark_orchard_note_spent`](Self::mark_orchard_note_spent) but for the Ironwood pool.
504    #[cfg(feature = "orchard")]
505    fn mark_ironwood_note_spent(
506        &mut self,
507        nf: &::orchard::note::Nullifier,
508        tx_ref: Self::TxRef,
509    ) -> Result<bool, Self::Error>;
510
511    /// Causes the given Orchard output nullifiers to be tracked by the wallet.
512    ///
513    /// When scanning the chain out-of-order, it is necessary to store any nullifiers observed
514    /// after a gap in the scanned blocks until the blocks in that gap have been fully scanned, in
515    /// order to be able to immediately detect that a received output has already been spent. The
516    /// data store should track a mapping from each nullifier to the block, the index of the
517    /// transaction in the block, and the transaction ID, so when a note is received within that
518    /// "gap" its spentness can be determined by checking in the store. For space efficiency, the
519    /// backing store may use the combination of block height and index within the block instead of
520    /// the txid for transaction identification.
521    ///
522    /// # Parameters
523    /// - `block_height`: The height of the block containing the nullifiers.
524    /// - `nfs`: A slice of tuples, where each tuple contains:
525    ///   - The transaction ID of the transaction revealing the nullifiers.
526    ///   - The index of the transaction within the block.
527    ///   - The vector of nullifiers revealed by the actions in that transaction.
528    #[cfg(feature = "orchard")]
529    fn track_block_orchard_nullifiers(
530        &mut self,
531        block_height: BlockHeight,
532        nfs: &[(TxIndex, TxId, Vec<::orchard::note::Nullifier>)],
533    ) -> Result<(), Self::Error>;
534
535    /// Causes the given Ironwood output nullifiers to be tracked by the wallet. Behaves like
536    /// [`track_block_orchard_nullifiers`](Self::track_block_orchard_nullifiers) but for the
537    /// Ironwood pool.
538    #[cfg(feature = "orchard")]
539    fn track_block_ironwood_nullifiers(
540        &mut self,
541        block_height: BlockHeight,
542        nfs: &[(TxIndex, TxId, Vec<::orchard::note::Nullifier>)],
543    ) -> Result<(), Self::Error>;
544
545    /// Removes tracked nullifiers that are no longer needed for spend detection.
546    ///
547    /// This function prunes nullifiers that were recorded at block heights less than
548    /// `(fully_scanned_height - pruning_depth)`, where `fully_scanned_height` is the height
549    /// of the wallet's fully scanned chain state. These nullifiers are no longer needed because
550    /// any notes they could have spent would have already been discovered during scanning.
551    ///
552    /// # Parameters
553    /// - `pruning_depth`: The number of blocks below the fully scanned height at which to
554    ///   prune tracked nullifiers.
555    fn prune_tracked_nullifiers(&mut self, pruning_depth: u32) -> Result<(), Self::Error>;
556
557    /// Records information about a transaction output that your wallet created, from the constituent
558    /// properties of that output.
559    ///
560    /// # Parameters
561    /// - `tx_ref`: The identifier for the transaction that produced the output.
562    /// - `output_index`: The index of the output in the bundle corresponding to the pool where the
563    ///   output was created.
564    /// - `recipient`: Information about the address or account that the output is being sent to.
565    /// - `value`: The value of the output.
566    /// - `memo`: The memo attached to the output, if any.
567    fn put_sent_output(
568        &mut self,
569        from_account_uuid: Self::AccountId,
570        tx_ref: Self::TxRef,
571        output_index: usize,
572        recipient: &Recipient<Self::AccountId>,
573        value: Zatoshis,
574        memo: Option<&MemoBytes>,
575    ) -> Result<(), Self::Error>;
576
577    /// Updates the wallet's view of a transaction to indicate the miner's fee paid by the
578    /// transaction.
579    fn update_tx_fee(&mut self, tx_ref: Self::TxRef, fee: Zatoshis) -> Result<(), Self::Error>;
580
581    /// Adds a transparent output observed by the wallet to the data store, or updates any existing
582    /// record for that output.
583    ///
584    /// # Parameters
585    /// - `output`: The output data.
586    /// - `observation_height`: This should be set to the mined height, if known. If the TXO is
587    ///   known to be mined but the height at which it was mined is unknown, this should be set to
588    ///   the chain tip height at the time of the observation; if the utxo is known to be unmined,
589    ///   this should be set to the mempool height.
590    /// - `known_unspent`: Set to `true` if the output is known to be a member of the UTXO set as
591    ///   of the given observation height.
592    #[cfg(feature = "transparent-inputs")]
593    fn put_transparent_output(
594        &mut self,
595        output: &crate::wallet::WalletTransparentOutput<Self::AccountId>,
596        observation_height: BlockHeight,
597        known_unspent: bool,
598    ) -> Result<(Self::AccountId, Option<TransparentKeyScope>), Self::Error>;
599
600    /// Updates the backing store to indicate that the UTXO referred to by `outpoint` is spent
601    /// in the transaction referenced by `spent_in_tx`.
602    #[cfg(feature = "transparent-inputs")]
603    fn mark_transparent_utxo_spent(
604        &mut self,
605        outpoint: &OutPoint,
606        spent_in_tx: Self::TxRef,
607    ) -> Result<bool, Self::Error>;
608
609    /// Updates the wallet backend by generating and caching addresses for the given key scope such
610    /// that at least the backend's configured gap limit worth of addresses exist at indices
611    /// successive from that of the last address that received a mined transaction.
612    ///
613    /// # Parameters
614    /// - `account_id`: The ID of the account holding the UFVK from which addresses should be
615    ///   generated.
616    /// - `key_scope`: The transparent key scope for addresses to generate. Implementations may
617    ///   choose to only support the `external`, `internal`, and `ephemeral` key scopes and return
618    ///   an error if an unrecognized scope is used.
619    /// - `request`: A request for the Unified Address that will be generated with a diversifier
620    ///   index equal to the [`BIP 44`] `address_index` of each generated address.
621    ///
622    /// [`BIP 44`]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
623    #[cfg(feature = "transparent-inputs")]
624    fn generate_transparent_gap_addresses(
625        &mut self,
626        account_id: Self::AccountId,
627        key_scope: TransparentKeyScope,
628        request: UnifiedAddressRequest,
629    ) -> Result<(), Self::Error>;
630
631    /// Adds a [`TransactionDataRequest::Enhancement`] request for the enhancement of the given
632    /// transaction to the transaction data request queue. The `dependent_tx_ref` parameter
633    /// specifies the transaction that caused this request to be generated, likely as part of the
634    /// process of traversing the transparent transaction graph by inspecting the inputs of a
635    /// transaction with outputs that were received by the wallet.
636    ///
637    /// [`TransactionDataRequest::Enhancement`]: super::TransactionDataRequest
638    fn queue_tx_retrieval(
639        &mut self,
640        txids: impl Iterator<Item = TxId>,
641        dependent_tx_ref: Option<Self::TxRef>,
642    ) -> Result<(), Self::Error>;
643
644    /// Adds a [`TransactionDataRequest::GetStatus`] request for a transaction whose mined status
645    /// cannot be learned through ordinary compact-block scanning.
646    ///
647    /// The request intent must remain durable while the transaction is mined, so that it becomes
648    /// active again if a chain rewind un-mines the transaction.
649    ///
650    /// [`TransactionDataRequest::GetStatus`]: super::TransactionDataRequest
651    fn queue_tx_status(&mut self, txid: TxId) -> Result<(), Self::Error>;
652
653    /// Adds a [`TransactionDataRequest::TransactionsInvolvingAddress`] request to the transaction
654    /// data request queue. When the transparent output of `tx_ref` at output index `output_index`
655    /// (which must have been received at `receiving_address`) is detected as having been spent,
656    /// this request will be considered fulfilled.
657    ///
658    /// NOTE: The somewhat awkward API of this method is a historical artifact; if the light wallet
659    /// protocol is in the future updated to expose a mechanism to find the transaction that spends
660    /// a particular `OutPoint`, the `TransactionsInvolvingAddress` variant and this method will
661    /// likely be removed.
662    ///
663    /// [`TransactionDataRequest::TransactionsInvolvingAddress`]: super::TransactionDataRequest
664    #[cfg(feature = "transparent-inputs")]
665    fn queue_transparent_spend_detection(
666        &mut self,
667        receiving_address: TransparentAddress,
668        tx_ref: Self::TxRef,
669        output_index: u32,
670    ) -> Result<(), Self::Error>;
671
672    /// Adds [`TransactionDataRequest::Enhancement`] requests for transactions that generated the
673    /// transparent inputs to the provided [`DecryptedTransaction`] to the transaction data request
674    /// queue.
675    ///
676    /// [`TransactionDataRequest::Enhancement`]: super::TransactionDataRequest
677    /// [`DecryptedTransaction`]: super::DecryptedTransaction
678    #[cfg(feature = "transparent-inputs")]
679    fn queue_transparent_input_retrieval(
680        &mut self,
681        tx_ref: Self::TxRef,
682        d_tx: &super::DecryptedTransaction<Transaction, Self::AccountId>,
683    ) -> Result<(), Self::Error>;
684
685    /// Deletes the [`TransactionDataRequest::Enhancement`] request for the given transaction ID
686    /// from the transaction data request queue, without removing any durable status-observation
687    /// intent for the transaction.
688    ///
689    /// [`TransactionDataRequest::Enhancement`]: super::TransactionDataRequest
690    fn delete_retrieval_queue_entries(&mut self, txid: TxId) -> Result<(), Self::Error>;
691
692    /// Updates the state of the wallet backend to indicate that the given range of blocks has been
693    /// fully scanned, identifying the position in the note commitment tree of any notes belonging
694    /// to the wallet that were discovered in the process of scanning.
695    fn notify_scan_complete(
696        &mut self,
697        range: Range<BlockHeight>,
698        wallet_note_positions: &[(ShieldedPool, Position)],
699    ) -> Result<(), Self::Error>;
700
701    #[cfg(feature = "transparent-inputs")]
702    fn update_gap_limits(
703        &mut self,
704        gap_limits: &GapLimits,
705        txid: TxId,
706        observation_height: BlockHeight,
707    ) -> Result<(), Self::Error>;
708}
709
710/// This trait provides a generalization over output representations.
711pub trait ReceivedShieldedOutput {
712    type AccountId;
713    type Note;
714    type Nullifier;
715
716    /// Returns the index of the output within its corresponding bundle.
717    fn index(&self) -> usize;
718    /// Returns the account ID for the account that received this output.
719    fn account_id(&self) -> Self::AccountId;
720    /// Returns the note.
721    fn note(&self) -> &Self::Note;
722    /// Returns the received note, as a [`Note`].
723    ///
724    /// [`Note`]: crate::wallet::Note
725    fn to_wallet_note(&self) -> crate::wallet::Note;
726    /// Returns any memo associated with the output.
727    fn memo(&self) -> Option<&MemoBytes>;
728    /// Returns a [`TransferType`] value that is determined based upon what type of key was used to
729    /// decrypt the transaction.
730    fn transfer_type(&self) -> TransferType;
731    /// Returns whether or not the received output is counted as wallet-internal change, for the
732    /// purpose of display.
733    fn is_change(&self) -> bool;
734    /// Returns the nullifier that will be revealed when the note is spent, if the output was
735    /// observed using a key that provides the capability for nullifier computation.
736    fn nullifier(&self) -> Option<&Self::Nullifier>;
737    /// Returns the position of the note in the note commitment tree, if the transaction that
738    /// produced the output has been mined.
739    fn note_commitment_tree_position(&self) -> Option<Position>;
740    /// Returns the HD derivation scope of the viewing key that decrypted the note, if known.
741    fn recipient_key_scope(&self) -> Option<Scope>;
742}
743
744/// This trait provides a generalization over shielded Sapling output representations.
745pub trait ReceivedSaplingOutput:
746    ReceivedShieldedOutput<Note = ::sapling::Note, Nullifier = ::sapling::Nullifier>
747{
748}
749impl<T: ReceivedShieldedOutput<Note = ::sapling::Note, Nullifier = ::sapling::Nullifier>>
750    ReceivedSaplingOutput for T
751{
752}
753
754impl<AccountId: Copy> ReceivedShieldedOutput for WalletSaplingOutput<AccountId> {
755    type AccountId = AccountId;
756    type Note = ::sapling::Note;
757    type Nullifier = ::sapling::Nullifier;
758
759    fn index(&self) -> usize {
760        self.index()
761    }
762    fn account_id(&self) -> Self::AccountId {
763        *WalletSaplingOutput::account_id(self)
764    }
765    fn note(&self) -> &Self::Note {
766        WalletSaplingOutput::note(self)
767    }
768    fn to_wallet_note(&self) -> crate::wallet::Note {
769        crate::wallet::Note::Sapling(self.note().clone())
770    }
771    fn memo(&self) -> Option<&MemoBytes> {
772        None
773    }
774    fn transfer_type(&self) -> TransferType {
775        if self.is_change() {
776            TransferType::AccountInternal
777        } else {
778            TransferType::Incoming
779        }
780    }
781    fn is_change(&self) -> bool {
782        WalletSaplingOutput::is_change(self)
783    }
784    fn nullifier(&self) -> Option<&::sapling::Nullifier> {
785        self.nf()
786    }
787    fn note_commitment_tree_position(&self) -> Option<Position> {
788        Some(WalletSaplingOutput::note_commitment_tree_position(self))
789    }
790    fn recipient_key_scope(&self) -> Option<Scope> {
791        self.recipient_key_scope()
792    }
793}
794
795impl<AccountId: Copy> ReceivedShieldedOutput for DecryptedOutput<::sapling::Note, AccountId> {
796    type AccountId = AccountId;
797    type Note = ::sapling::Note;
798    type Nullifier = ::sapling::Nullifier;
799
800    fn index(&self) -> usize {
801        self.index()
802    }
803    fn account_id(&self) -> Self::AccountId {
804        *self.account()
805    }
806    fn note(&self) -> &Self::Note {
807        DecryptedOutput::note(self)
808    }
809    fn to_wallet_note(&self) -> crate::wallet::Note {
810        crate::wallet::Note::Sapling(self.note().clone())
811    }
812    fn memo(&self) -> Option<&MemoBytes> {
813        Some(self.memo())
814    }
815    fn transfer_type(&self) -> TransferType {
816        self.transfer_type()
817    }
818    fn is_change(&self) -> bool {
819        self.transfer_type() == TransferType::AccountInternal
820    }
821    fn nullifier(&self) -> Option<&::sapling::Nullifier> {
822        None
823    }
824    fn note_commitment_tree_position(&self) -> Option<Position> {
825        None
826    }
827    fn recipient_key_scope(&self) -> Option<Scope> {
828        if self.transfer_type() == TransferType::AccountInternal {
829            Some(Scope::Internal)
830        } else {
831            Some(Scope::External)
832        }
833    }
834}
835
836/// This trait provides a generalization over shielded Orchard output representations.
837#[cfg(feature = "orchard")]
838pub trait ReceivedOrchardOutput:
839    ReceivedShieldedOutput<Note = ::orchard::Note, Nullifier = ::orchard::note::Nullifier>
840{
841}
842#[cfg(feature = "orchard")]
843impl<T: ReceivedShieldedOutput<Note = ::orchard::Note, Nullifier = ::orchard::note::Nullifier>>
844    ReceivedOrchardOutput for T
845{
846}
847
848#[cfg(feature = "orchard")]
849impl<AccountId: Copy> ReceivedShieldedOutput for WalletOrchardOutput<AccountId> {
850    type AccountId = AccountId;
851    type Note = ::orchard::Note;
852    type Nullifier = ::orchard::note::Nullifier;
853
854    fn index(&self) -> usize {
855        self.index()
856    }
857    fn account_id(&self) -> Self::AccountId {
858        *WalletOrchardOutput::account_id(self)
859    }
860    fn note(&self) -> &Self::Note {
861        &self.note().0
862    }
863    fn to_wallet_note(&self) -> crate::wallet::Note {
864        let (note, pool) = self.note();
865        crate::wallet::Note::Orchard {
866            note: *note,
867            pool: *pool,
868        }
869    }
870    fn memo(&self) -> Option<&MemoBytes> {
871        None
872    }
873    fn transfer_type(&self) -> TransferType {
874        if self.is_change() {
875            TransferType::AccountInternal
876        } else {
877            TransferType::Incoming
878        }
879    }
880    fn is_change(&self) -> bool {
881        WalletOrchardOutput::is_change(self)
882    }
883    fn nullifier(&self) -> Option<&::orchard::note::Nullifier> {
884        self.nf()
885    }
886    fn note_commitment_tree_position(&self) -> Option<Position> {
887        Some(WalletOrchardOutput::note_commitment_tree_position(self))
888    }
889    fn recipient_key_scope(&self) -> Option<Scope> {
890        self.recipient_key_scope()
891    }
892}
893
894#[cfg(feature = "orchard")]
895impl<AccountId: Copy> ReceivedShieldedOutput
896    for DecryptedOutput<(::orchard::Note, ::orchard::ValuePool), AccountId>
897{
898    type AccountId = AccountId;
899    type Note = ::orchard::Note;
900    type Nullifier = ::orchard::note::Nullifier;
901
902    fn index(&self) -> usize {
903        self.index()
904    }
905    fn account_id(&self) -> Self::AccountId {
906        *self.account()
907    }
908    fn note(&self) -> &Self::Note {
909        &self.note().0
910    }
911    fn to_wallet_note(&self) -> crate::wallet::Note {
912        let (note, pool) = self.note();
913        crate::wallet::Note::Orchard {
914            note: *note,
915            pool: *pool,
916        }
917    }
918    fn memo(&self) -> Option<&MemoBytes> {
919        Some(self.memo())
920    }
921    fn transfer_type(&self) -> TransferType {
922        self.transfer_type()
923    }
924    fn is_change(&self) -> bool {
925        self.transfer_type() == TransferType::AccountInternal
926    }
927    fn nullifier(&self) -> Option<&::orchard::note::Nullifier> {
928        None
929    }
930    fn note_commitment_tree_position(&self) -> Option<Position> {
931        None
932    }
933    fn recipient_key_scope(&self) -> Option<Scope> {
934        if self.transfer_type() == TransferType::AccountInternal {
935            Some(Scope::Internal)
936        } else {
937            Some(Scope::External)
938        }
939    }
940}