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}