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}