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