Skip to main content

pepper_sync/
wallet.rs

1//! Module for wallet structs and types generated by the sync engine from block chain data or to track the wallet's
2//! sync status.
3//! The structs will be (or be transposed into) the fundamental wallet components for the wallet interfacing with this
4//! sync engine.
5
6use std::{
7    collections::{BTreeMap, BTreeSet},
8    convert::Infallible,
9    fmt::Debug,
10    ops::Range,
11    sync::{
12        Arc,
13        atomic::{self, AtomicU8},
14    },
15};
16
17use incrementalmerkletree::Position;
18use orchard::tree::MerkleHashOrchard;
19use shardtree::{ShardTree, store::memory::MemoryShardStore};
20use tokio::sync::mpsc;
21use zcash_address::unified::ParseError;
22use zcash_client_backend::proto::compact_formats::CompactBlock;
23use zcash_keys::{address::UnifiedAddress, encoding::encode_payment_address};
24use zcash_primitives::{block::BlockHash, transaction::TxId};
25use zcash_protocol::{
26    PoolType, ShieldedProtocol,
27    consensus::{self, BlockHeight},
28    memo::Memo,
29    value::Zatoshis,
30};
31use zcash_transparent::{address::Script, bundle::OutPoint};
32
33use zingo_status::confirmation_status::ConfirmationStatus;
34
35use crate::{
36    client::FetchRequest,
37    error::{ServerError, SyncModeError},
38    keys::{self, KeyId, transparent::TransparentAddressId},
39    scan::compact_blocks::calculate_block_tree_bounds,
40    sync::{MAX_REORG_ALLOWANCE, ScanPriority, ScanRange},
41    witness,
42};
43
44pub mod traits;
45
46#[cfg(feature = "wallet_essentials")]
47pub mod serialization;
48
49/// Block height and txid of relevant transactions that have yet to be scanned. These may be added due to transparent
50/// output/spend discovery or for targetted rescan.
51///
52/// `narrow_scan_area` is used to narrow the surrounding area scanned around the target from a shard to 100 blocks.
53/// For example, this is useful when targetting transparent outputs as scanning the whole shard will not affect the
54/// spendability of the scan target but will significantly reduce memory usage and/or storage as well as prioritise
55/// creating spendable notes.
56///
57/// Scan targets with block heights below sapling activation height are not supported.
58#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
59pub struct ScanTarget {
60    /// Block height.
61    pub block_height: BlockHeight,
62    /// Txid.
63    pub txid: TxId,
64    /// Narrow surrounding scan area of target.
65    pub narrow_scan_area: bool,
66}
67
68/// Initial sync state.
69///
70/// All fields will be reset when a new sync session starts.
71#[derive(Debug, Clone)]
72pub struct InitialSyncState {
73    /// One block above the fully scanned wallet height at start of sync session.
74    ///
75    /// If chain height is not larger than fully scanned height when sync is called, this value will be set to chain
76    /// height instead.
77    pub(crate) sync_start_height: BlockHeight,
78    /// The tree sizes of the fully scanned height and chain tip at start of sync session.
79    pub(crate) wallet_tree_bounds: TreeBounds,
80    /// Total number of blocks scanned in previous sync sessions.
81    pub(crate) previously_scanned_blocks: u32,
82    /// Total number of sapling outputs scanned in previous sync sessions.
83    pub(crate) previously_scanned_sapling_outputs: u32,
84    /// Total number of orchard outputs scanned in previous sync sessions.
85    pub(crate) previously_scanned_orchard_outputs: u32,
86}
87
88impl InitialSyncState {
89    /// Create new `InitialSyncState`
90    #[must_use]
91    pub fn new() -> Self {
92        InitialSyncState {
93            sync_start_height: 0.into(),
94            wallet_tree_bounds: TreeBounds {
95                sapling_initial_tree_size: 0,
96                sapling_final_tree_size: 0,
97                orchard_initial_tree_size: 0,
98                orchard_final_tree_size: 0,
99            },
100            previously_scanned_blocks: 0,
101            previously_scanned_sapling_outputs: 0,
102            previously_scanned_orchard_outputs: 0,
103        }
104    }
105}
106
107impl Default for InitialSyncState {
108    fn default() -> Self {
109        Self::new()
110    }
111}
112
113/// Encapsulates the current state of sync
114#[derive(Debug, Clone)]
115pub struct SyncState {
116    /// A vec of block ranges with scan priorities from wallet birthday to chain tip.
117    /// In block height order with no overlaps or gaps.
118    pub(crate) scan_ranges: Vec<ScanRange>,
119    /// The block ranges that contain all sapling outputs of complete sapling shards.
120    ///
121    /// There is an edge case where a range may include two (or more) shards. However, this only occurs when the lower
122    /// shards are already scanned so will cause no issues when punching in the higher scan priorites.
123    pub(crate) sapling_shard_ranges: Vec<Range<BlockHeight>>,
124    /// The block ranges that contain all orchard outputs of complete orchard shards.
125    ///
126    /// There is an edge case where a range may include two (or more) shards. However, this only occurs when the lower
127    /// shards are already scanned so will cause no issues when punching in the higher scan priorites.
128    pub(crate) orchard_shard_ranges: Vec<Range<BlockHeight>>,
129    /// Scan targets for relevant transactions to the wallet.
130    pub(crate) scan_targets: BTreeSet<ScanTarget>,
131    /// Initial sync state.
132    pub(crate) initial_sync_state: InitialSyncState,
133}
134
135impl SyncState {
136    /// Create new `SyncState`
137    #[must_use]
138    pub fn new() -> Self {
139        SyncState {
140            scan_ranges: Vec::new(),
141            sapling_shard_ranges: Vec::new(),
142            orchard_shard_ranges: Vec::new(),
143            scan_targets: BTreeSet::new(),
144            initial_sync_state: InitialSyncState::new(),
145        }
146    }
147
148    /// Scan ranges
149    #[must_use]
150    pub fn scan_ranges(&self) -> &[ScanRange] {
151        &self.scan_ranges
152    }
153
154    /// Sapling shard ranges
155    #[must_use]
156    pub fn sapling_shard_ranges(&self) -> &[Range<BlockHeight>] {
157        &self.sapling_shard_ranges
158    }
159
160    /// Orchard shard ranges
161    #[must_use]
162    pub fn orchard_shard_ranges(&self) -> &[Range<BlockHeight>] {
163        &self.orchard_shard_ranges
164    }
165
166    /// Returns true if all scan ranges are scanned.
167    pub(crate) fn scan_complete(&self) -> bool {
168        self.scan_ranges
169            .iter()
170            .all(|scan_range| scan_range.priority() == ScanPriority::Scanned)
171    }
172
173    /// Returns the block height at which all blocks equal to and below this height are scanned.
174    /// Returns `None` if `self.scan_ranges` is empty.
175    #[must_use]
176    pub fn fully_scanned_height(&self) -> Option<BlockHeight> {
177        if let Some(scan_range) = self
178            .scan_ranges
179            .iter()
180            .find(|scan_range| scan_range.priority() != ScanPriority::Scanned)
181        {
182            Some(scan_range.block_range().start - 1)
183        } else {
184            self.scan_ranges
185                .last()
186                .map(|range| range.block_range().end - 1)
187        }
188    }
189
190    /// Returns the highest block height that has been scanned.
191    /// If no scan ranges have been scanned, returns the block below the wallet birthday.
192    /// Returns `None` if `self.scan_ranges` is empty.
193    #[must_use]
194    pub fn highest_scanned_height(&self) -> Option<BlockHeight> {
195        if let Some(last_scanned_range) = self
196            .scan_ranges
197            .iter()
198            .filter(|scan_range| {
199                scan_range.priority() == ScanPriority::Scanned
200                    || scan_range.priority() == ScanPriority::ScannedWithoutMapping
201                    || scan_range.priority() == ScanPriority::RefetchingNullifiers
202            })
203            .next_back()
204        {
205            Some(last_scanned_range.block_range().end - 1)
206        } else {
207            self.wallet_birthday().map(|start| start - 1)
208        }
209    }
210
211    /// Returns the wallet birthday or `None` if `self.scan_ranges` is empty.
212    ///
213    #[must_use]
214    pub fn wallet_birthday(&self) -> Option<BlockHeight> {
215        self.scan_ranges
216            .first()
217            .map(|range| range.block_range().start)
218    }
219
220    /// Returns the last known chain height to the wallet or `None` if `self.scan_ranges` is empty.
221    #[must_use]
222    pub fn last_known_chain_height(&self) -> Option<BlockHeight> {
223        self.scan_ranges
224            .last()
225            .map(|range| range.block_range().end - 1)
226    }
227}
228
229impl Default for SyncState {
230    fn default() -> Self {
231        Self::new()
232    }
233}
234
235/// Sync modes.
236#[derive(Debug, Clone, Copy, PartialEq, Eq)]
237pub enum SyncMode {
238    /// Sync is not running.
239    NotRunning,
240    /// Sync is held in a paused state and the wallet guard is dropped.
241    Paused,
242    /// Sync is running.
243    Running,
244    /// Sync is shutting down.
245    Shutdown,
246}
247
248impl SyncMode {
249    /// Constructor from u8.
250    ///
251    /// Returns `None` if `mode` is not a valid enum variant.
252    pub fn from_u8(mode: u8) -> Result<Self, SyncModeError> {
253        match mode {
254            0 => Ok(Self::NotRunning),
255            1 => Ok(Self::Paused),
256            2 => Ok(Self::Running),
257            3 => Ok(Self::Shutdown),
258            _ => Err(SyncModeError::InvalidSyncMode(mode)),
259        }
260    }
261
262    /// Creates [`crate::wallet::SyncMode`] from an atomic u8.
263    ///
264    /// # Panic
265    ///
266    /// Panics if `atomic_sync_mode` corresponds to an invalid enum variant.
267    /// It is the consumers responsibility to ensure the library restricts the user API to only set valid values via
268    /// [`crate::wallet::SyncMode`].
269    pub fn from_atomic_u8(atomic_sync_mode: Arc<AtomicU8>) -> Result<SyncMode, SyncModeError> {
270        SyncMode::from_u8(atomic_sync_mode.load(atomic::Ordering::Acquire))
271    }
272}
273
274/// Initial and final tree sizes.
275#[derive(Debug, Clone, Copy)]
276#[allow(missing_docs)]
277pub struct TreeBounds {
278    pub sapling_initial_tree_size: u32,
279    pub sapling_final_tree_size: u32,
280    pub orchard_initial_tree_size: u32,
281    pub orchard_final_tree_size: u32,
282}
283
284/// Output ID for a given pool type.
285#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
286pub struct OutputId {
287    /// ID of associated transaction.
288    txid: TxId,
289    /// Index of output within the transactions bundle of the given pool type.
290    output_index: u16,
291}
292
293impl OutputId {
294    /// Creates new `OutputId` from parts.
295    #[must_use]
296    pub fn new(txid: TxId, output_index: u16) -> Self {
297        OutputId { txid, output_index }
298    }
299
300    /// Transaction ID of output's associated transaction.
301    #[must_use]
302    pub fn txid(&self) -> TxId {
303        self.txid
304    }
305
306    /// Index of output within the transactions bundle of the given pool type.
307    #[must_use]
308    pub fn output_index(&self) -> u16 {
309        self.output_index
310    }
311}
312
313impl std::fmt::Display for OutputId {
314    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
315        write!(
316            f,
317            "{{
318                txid: {}
319                output index: {}
320            }}",
321            self.txid, self.output_index
322        )
323    }
324}
325
326impl From<&OutPoint> for OutputId {
327    fn from(value: &OutPoint) -> Self {
328        OutputId::new(*value.txid(), value.n() as u16)
329    }
330}
331
332impl From<OutputId> for OutPoint {
333    fn from(value: OutputId) -> Self {
334        OutPoint::new(value.txid.into(), u32::from(value.output_index))
335    }
336}
337
338/// Binary tree map of nullifiers from transaction spends or actions
339#[derive(Debug)]
340pub struct NullifierMap {
341    /// Sapling nullifer map
342    pub sapling: BTreeMap<sapling_crypto::Nullifier, ScanTarget>,
343    /// Orchard nullifer map
344    pub orchard: BTreeMap<orchard::note::Nullifier, ScanTarget>,
345}
346
347impl NullifierMap {
348    /// Construct new nullifier map.
349    #[must_use]
350    pub fn new() -> Self {
351        Self {
352            sapling: BTreeMap::new(),
353            orchard: BTreeMap::new(),
354        }
355    }
356
357    /// Clear nullifier map.
358    pub fn clear(&mut self) {
359        self.sapling.clear();
360        self.orchard.clear();
361    }
362}
363
364impl Default for NullifierMap {
365    fn default() -> Self {
366        Self::new()
367    }
368}
369
370/// Wallet block data
371#[derive(Debug, Clone)]
372pub struct WalletBlock {
373    pub(crate) block_height: BlockHeight,
374    pub(crate) block_hash: BlockHash,
375    pub(crate) prev_hash: BlockHash,
376    pub(crate) time: u32,
377    pub(crate) txids: Vec<TxId>,
378    pub(crate) tree_bounds: TreeBounds,
379}
380
381impl WalletBlock {
382    pub(crate) async fn from_compact_block(
383        consensus_parameters: &impl consensus::Parameters,
384        fetch_request_sender: mpsc::UnboundedSender<FetchRequest>,
385        block: &CompactBlock,
386    ) -> Result<Self, ServerError> {
387        let tree_bounds =
388            calculate_block_tree_bounds(consensus_parameters, fetch_request_sender, block).await?;
389
390        Ok(Self {
391            block_height: block.height(),
392            block_hash: block.hash(),
393            prev_hash: block.prev_hash(),
394            time: block.time,
395            txids: block
396                .vtx
397                .iter()
398                .map(zcash_client_backend::proto::compact_formats::CompactTx::txid)
399                .collect(),
400            tree_bounds,
401        })
402    }
403
404    /// Block height.
405    #[must_use]
406    pub fn block_height(&self) -> BlockHeight {
407        self.block_height
408    }
409
410    /// Block hash.
411    #[must_use]
412    pub fn block_hash(&self) -> BlockHash {
413        self.block_hash
414    }
415
416    /// Previous block hash.
417    #[must_use]
418    pub fn prev_hash(&self) -> BlockHash {
419        self.prev_hash
420    }
421
422    /// Time block was mined.
423    #[must_use]
424    pub fn time(&self) -> u32 {
425        self.time
426    }
427
428    /// Transaction IDs of transactions in the block.
429    #[must_use]
430    pub fn txids(&self) -> &[TxId] {
431        &self.txids
432    }
433
434    /// Tree size bounds
435    #[must_use]
436    pub fn tree_bounds(&self) -> TreeBounds {
437        self.tree_bounds
438    }
439}
440
441/// Wallet transaction
442pub struct WalletTransaction {
443    pub(crate) txid: TxId,
444    pub(crate) status: ConfirmationStatus,
445    pub(crate) transaction: zcash_primitives::transaction::Transaction,
446    pub(crate) datetime: u32,
447    pub(crate) transparent_coins: Vec<TransparentCoin>,
448    pub(crate) sapling_notes: Vec<SaplingNote>,
449    pub(crate) orchard_notes: Vec<OrchardNote>,
450    pub(crate) outgoing_sapling_notes: Vec<OutgoingSaplingNote>,
451    pub(crate) outgoing_orchard_notes: Vec<OutgoingOrchardNote>,
452}
453
454impl WalletTransaction {
455    /// Transaction ID
456    #[must_use]
457    pub fn txid(&self) -> TxId {
458        self.txid
459    }
460
461    /// Confirmation status
462    #[must_use]
463    pub fn status(&self) -> ConfirmationStatus {
464        self.status
465    }
466
467    /// [`zcash_primitives::transaction::Transaction`]
468    #[must_use]
469    pub fn transaction(&self) -> &zcash_primitives::transaction::Transaction {
470        &self.transaction
471    }
472
473    /// Datetime. In form of seconds since unix epoch.
474    #[must_use]
475    pub fn datetime(&self) -> u32 {
476        self.datetime
477    }
478
479    /// Transparent coins
480    #[must_use]
481    pub fn transparent_coins(&self) -> &[TransparentCoin] {
482        &self.transparent_coins
483    }
484
485    /// Transparent coins mutable
486    pub fn transparent_coins_mut(&mut self) -> Vec<&mut TransparentCoin> {
487        self.transparent_coins.iter_mut().collect()
488    }
489
490    /// Sapling notes
491    #[must_use]
492    pub fn sapling_notes(&self) -> &[SaplingNote] {
493        &self.sapling_notes
494    }
495
496    /// Sapling notes mutable
497    pub fn sapling_notes_mut(&mut self) -> Vec<&mut SaplingNote> {
498        self.sapling_notes.iter_mut().collect()
499    }
500
501    /// Orchard notes
502    #[must_use]
503    pub fn orchard_notes(&self) -> &[OrchardNote] {
504        &self.orchard_notes
505    }
506
507    /// Orchard notes mutable
508    pub fn orchard_notes_mut(&mut self) -> Vec<&mut OrchardNote> {
509        self.orchard_notes.iter_mut().collect()
510    }
511
512    /// Outgoing sapling notes
513    #[must_use]
514    pub fn outgoing_sapling_notes(&self) -> &[OutgoingSaplingNote] {
515        &self.outgoing_sapling_notes
516    }
517
518    /// Outgoing orchard notes
519    #[must_use]
520    pub fn outgoing_orchard_notes(&self) -> &[OutgoingOrchardNote] {
521        &self.outgoing_orchard_notes
522    }
523
524    /// Returns nullifers from sapling bundle.
525    /// Returns empty vec if bundle is `None`.
526    pub fn sapling_nullifiers(&self) -> Vec<&sapling_crypto::Nullifier> {
527        self.transaction
528            .sapling_bundle()
529            .map_or_else(Vec::new, |bundle| {
530                bundle
531                    .shielded_spends()
532                    .iter()
533                    .map(sapling_crypto::bundle::SpendDescription::nullifier)
534                    .collect::<Vec<_>>()
535            })
536    }
537
538    /// Returns nullifers from orchard bundle.
539    /// Returns empty vec if bundle is `None`.
540    pub fn orchard_nullifiers(&self) -> Vec<&orchard::note::Nullifier> {
541        self.transaction
542            .orchard_bundle()
543            .map_or_else(Vec::new, |bundle| {
544                bundle
545                    .actions()
546                    .iter()
547                    .map(orchard::Action::nullifier)
548                    .collect::<Vec<_>>()
549            })
550    }
551
552    /// Returns outpoints from transparent bundle.
553    /// Returns empty vec if bundle is `None`.
554    pub fn outpoints(&self) -> Vec<&OutPoint> {
555        self.transaction
556            .transparent_bundle()
557            .map_or_else(Vec::new, |bundle| {
558                bundle
559                    .vin
560                    .iter()
561                    .map(zcash_transparent::bundle::TxIn::prevout)
562                    .collect::<Vec<_>>()
563            })
564    }
565
566    /// Updates transaction status if `status` is a valid update for the current transaction status.
567    /// For example, if `status` is `Mempool` but the current transaction status is `Confirmed`, the status will remain
568    /// unchanged.
569    /// `datetime` refers to the time in which the status was updated, or the time the block was mined when updating
570    /// to `Confirmed` status.
571    pub fn update_status(&mut self, status: ConfirmationStatus, datetime: u32) {
572        match status {
573            ConfirmationStatus::Transmitted(_)
574                if matches!(self.status(), ConfirmationStatus::Calculated(_)) =>
575            {
576                self.status = status;
577                self.datetime = datetime;
578            }
579            ConfirmationStatus::Mempool(_)
580                if matches!(
581                    self.status(),
582                    ConfirmationStatus::Calculated(_) | ConfirmationStatus::Transmitted(_)
583                ) =>
584            {
585                self.status = status;
586                self.datetime = datetime;
587            }
588            ConfirmationStatus::Confirmed(_)
589                if matches!(
590                    self.status(),
591                    ConfirmationStatus::Calculated(_)
592                        | ConfirmationStatus::Transmitted(_)
593                        | ConfirmationStatus::Mempool(_)
594                ) =>
595            {
596                self.status = status;
597                self.datetime = datetime;
598            }
599
600            ConfirmationStatus::Failed(_)
601                if !matches!(self.status(), ConfirmationStatus::Failed(_)) =>
602            {
603                self.status = status;
604                self.datetime = datetime;
605            }
606            _ => (),
607        }
608    }
609}
610
611#[cfg(feature = "test-features")]
612impl WalletTransaction {
613    /// Creates a minimal `WalletTransaction` for testing purposes.
614    ///
615    /// Constructs a valid v5 transaction with empty bundles and the given `txid` and `status`.
616    pub fn new_for_test(txid: TxId, status: ConfirmationStatus) -> Self {
617        use zcash_primitives::transaction::{TransactionData, TxVersion};
618        use zcash_protocol::consensus::BranchId;
619
620        let transaction = TransactionData::from_parts(
621            TxVersion::V5,
622            BranchId::Nu5,
623            0,
624            BlockHeight::from_u32(0),
625            None,
626            None,
627            None,
628            None,
629        )
630        .freeze()
631        .expect("empty v5 transaction should always be valid");
632
633        Self {
634            txid,
635            status,
636            transaction,
637            datetime: 0,
638            transparent_coins: Vec::new(),
639            sapling_notes: Vec::new(),
640            orchard_notes: Vec::new(),
641            outgoing_sapling_notes: Vec::new(),
642            outgoing_orchard_notes: Vec::new(),
643        }
644    }
645}
646
647#[cfg(feature = "wallet_essentials")]
648impl WalletTransaction {
649    /// Returns the total value sent to receivers, excluding value sent to the wallet's own addresses.
650    #[must_use]
651    pub fn total_value_sent(&self) -> u64 {
652        let transparent_value_sent = self
653            .transaction
654            .transparent_bundle()
655            .map_or(0, |bundle| {
656                bundle
657                    .vout
658                    .iter()
659                    .map(|output| output.value().into_u64())
660                    .sum()
661            })
662            .saturating_sub(self.total_output_value::<TransparentCoin>());
663
664        let sapling_value_sent =
665            self.total_external_outgoing_note_value::<OutgoingSaplingNote, SaplingNote>();
666        let orchard_value_sent =
667            self.total_external_outgoing_note_value::<OutgoingOrchardNote, OrchardNote>();
668
669        transparent_value_sent + sapling_value_sent + orchard_value_sent
670    }
671
672    /// Returns total sum of all output values.
673    #[must_use]
674    pub fn total_value_received(&self) -> u64 {
675        self.total_output_value::<TransparentCoin>()
676            + self.total_output_value::<SaplingNote>()
677            + self.total_output_value::<OrchardNote>()
678    }
679
680    /// Returns total sum of output values for a given pool.
681    #[must_use]
682    pub fn total_output_value<Op: OutputInterface>(&self) -> u64 {
683        Op::transaction_outputs(self)
684            .iter()
685            .map(OutputInterface::value)
686            .sum()
687    }
688
689    /// Returns total sum of outgoing note values for a given shielded pool.
690    #[must_use]
691    pub fn total_outgoing_note_value<Op: OutgoingNoteInterface>(&self) -> u64 {
692        Op::transaction_outgoing_notes(self)
693            .iter()
694            .map(OutgoingNoteInterface::value)
695            .sum()
696    }
697
698    /// Returns total sum of outgoing note values for outputs that are not wallet-owned.
699    #[must_use]
700    pub fn total_external_outgoing_note_value<Outgoing, Incoming>(&self) -> u64
701    where
702        Outgoing: OutgoingNoteInterface,
703        Incoming: OutputInterface,
704    {
705        Outgoing::transaction_outgoing_notes(self)
706            .iter()
707            .filter(|outgoing_note| {
708                !Incoming::transaction_outputs(self)
709                    .iter()
710                    .any(|wallet_note| wallet_note.output_id() == outgoing_note.output_id())
711            })
712            .map(OutgoingNoteInterface::value)
713            .sum()
714    }
715}
716
717impl std::fmt::Debug for WalletTransaction {
718    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
719        f.debug_struct("WalletTransaction")
720            .field("txid", &self.txid)
721            .field("confirmation_status", &self.status)
722            .field("datetime", &self.datetime)
723            .field("transparent_coins", &self.transparent_coins)
724            .field("sapling_notes", &self.sapling_notes)
725            .field("orchard_notes", &self.orchard_notes)
726            .field("outgoing_sapling_notes", &self.outgoing_sapling_notes)
727            .field("outgoing_orchard_notes", &self.outgoing_orchard_notes)
728            .finish()
729    }
730}
731
732/// Provides a common API for all key identifiers.
733pub trait KeyIdInterface {
734    /// Account ID.
735    fn account_id(&self) -> zip32::AccountId;
736}
737
738/// Provides a common API for all output types.
739pub trait OutputInterface: Sized {
740    /// Identifier for key used to decrypt output.
741    type KeyId: KeyIdInterface;
742    /// Transaction input type associated with spend detection of output.
743    type Input: Clone + Debug + PartialEq + Eq + PartialOrd + Ord;
744
745    /// Output's associated pool type.
746    const POOL_TYPE: PoolType;
747
748    /// Output ID.
749    fn output_id(&self) -> OutputId;
750
751    /// Identifier for key used to decrypt output.
752    fn key_id(&self) -> Self::KeyId;
753
754    /// Transaction ID of transaction this output was spent.
755    /// If `None`, output is not spent.
756    fn spending_transaction(&self) -> Option<TxId>;
757
758    /// Sets spending transaction.
759    fn set_spending_transaction(&mut self, spending_transaction: Option<TxId>);
760
761    /// Note value..
762    // TODO: change to Zatoshis checked type
763    fn value(&self) -> u64;
764
765    /// Returns the type used to link with transaction inputs for spend detection.
766    /// Returns `None` in the case the nullifier is not available for shielded outputs.
767    ///
768    /// Nullifier for shielded outputs.
769    /// Outpoint for transparent outputs.
770    fn spend_link(&self) -> Option<Self::Input>;
771
772    /// Inputs within `transaction` used to detect an output's spend status.
773    ///
774    /// Nullifiers for shielded outputs.
775    /// Out points for transparent outputs.
776    fn transaction_inputs(transaction: &WalletTransaction) -> Vec<&Self::Input>;
777
778    /// Outputs within `transaction`.
779    fn transaction_outputs(transaction: &WalletTransaction) -> &[Self];
780}
781
782/// Provides a common API for all shielded output types.
783pub trait NoteInterface: OutputInterface {
784    /// Decrypted note type.
785    type ZcashNote;
786    /// Nullifier type.
787    type Nullifier: Copy + Clone + PartialEq + Eq + PartialOrd + Ord;
788
789    /// Note's associated shielded protocol.
790    const SHIELDED_PROTOCOL: ShieldedProtocol;
791
792    /// Decrypted note with recipient and value
793    fn note(&self) -> &Self::ZcashNote;
794
795    /// Derived nullifier
796    fn nullifier(&self) -> Option<Self::Nullifier>;
797
798    /// Commitment tree leaf position
799    fn position(&self) -> Option<Position>;
800
801    /// Memo
802    fn memo(&self) -> &Memo;
803
804    /// List of block ranges where the nullifiers must be re-fetched to guarantee the note has not been spent.
805    /// These scan ranges were marked `ScannedWithoutMapping` or `RefetchingNullifiers` priority before this note was
806    /// scanned, meaning the nullifiers were discarded due to memory constraints and will be re-fetched later in the
807    /// sync process.
808    fn refetch_nullifier_ranges(&self) -> &[Range<BlockHeight>];
809}
810
811///  Transparent coin (output) with metadata relevant to the wallet.
812#[derive(Debug, Clone)]
813pub struct TransparentCoin {
814    /// Output ID.
815    pub(crate) output_id: OutputId,
816    /// Identifier for key used to derive address.
817    pub(crate) key_id: TransparentAddressId,
818    /// Encoded transparent address.
819    pub(crate) address: String,
820    /// Script.
821    pub(crate) script: Script,
822    /// Coin value.
823    pub(crate) value: Zatoshis,
824    /// Transaction ID of transaction this output was spent.
825    /// If `None`, output is not spent.
826    pub(crate) spending_transaction: Option<TxId>,
827}
828
829impl TransparentCoin {
830    /// Address received to.
831    #[must_use]
832    pub fn address(&self) -> &str {
833        &self.address
834    }
835
836    /// Script.
837    #[must_use]
838    pub fn script(&self) -> &Script {
839        &self.script
840    }
841}
842
843impl OutputInterface for TransparentCoin {
844    type KeyId = TransparentAddressId;
845    type Input = OutPoint;
846
847    const POOL_TYPE: PoolType = PoolType::Transparent;
848
849    fn output_id(&self) -> OutputId {
850        self.output_id
851    }
852
853    fn key_id(&self) -> Self::KeyId {
854        self.key_id
855    }
856
857    fn spending_transaction(&self) -> Option<TxId> {
858        self.spending_transaction
859    }
860
861    fn set_spending_transaction(&mut self, spending_transaction: Option<TxId>) {
862        self.spending_transaction = spending_transaction;
863    }
864
865    fn value(&self) -> u64 {
866        self.value.into_u64()
867    }
868
869    fn spend_link(&self) -> Option<Self::Input> {
870        Some(self.output_id.into())
871    }
872
873    fn transaction_inputs(transaction: &WalletTransaction) -> Vec<&Self::Input> {
874        transaction.outpoints()
875    }
876
877    fn transaction_outputs(transaction: &WalletTransaction) -> &[Self] {
878        &transaction.transparent_coins
879    }
880}
881
882/// Wallet note, shielded output with metadata relevant to the wallet.
883#[derive(Debug, Clone)]
884pub struct WalletNote<N, Nf: Copy> {
885    /// Output ID.
886    pub(crate) output_id: OutputId,
887    /// Identifier for key used to decrypt output.
888    pub(crate) key_id: KeyId,
889    /// Decrypted note with recipient and value.
890    pub(crate) note: N,
891    /// Derived nullifier.
892    pub(crate) nullifier: Option<Nf>, //TODO: syncing without nullifier deriving key
893    /// Commitment tree leaf position.
894    pub(crate) position: Option<Position>,
895    /// Memo.
896    pub(crate) memo: Memo,
897    /// Transaction ID of transaction this output was spent.
898    /// If `None`, output is not spent.
899    pub(crate) spending_transaction: Option<TxId>,
900    /// List of block ranges where the nullifiers must be re-fetched to guarantee the note has not been spent.
901    /// These scan ranges were marked `ScannedWithoutMapping` or `RefetchingNullifiers` priority before this note was
902    /// scanned, meaning the nullifiers were discarded due to memory constraints and will be re-fetched later in the
903    /// sync process.
904    pub(crate) refetch_nullifier_ranges: Vec<Range<BlockHeight>>,
905}
906
907/// Sapling note.
908pub type SaplingNote = WalletNote<sapling_crypto::Note, sapling_crypto::Nullifier>;
909
910impl OutputInterface for SaplingNote {
911    type KeyId = KeyId;
912    type Input = sapling_crypto::Nullifier;
913
914    const POOL_TYPE: PoolType = PoolType::Shielded(ShieldedProtocol::Sapling);
915
916    fn output_id(&self) -> OutputId {
917        self.output_id
918    }
919
920    fn key_id(&self) -> KeyId {
921        self.key_id
922    }
923
924    fn spending_transaction(&self) -> Option<TxId> {
925        self.spending_transaction
926    }
927
928    fn set_spending_transaction(&mut self, spending_transaction: Option<TxId>) {
929        self.spending_transaction = spending_transaction;
930    }
931
932    fn value(&self) -> u64 {
933        self.note.value().inner()
934    }
935
936    fn spend_link(&self) -> Option<Self::Input> {
937        self.nullifier
938    }
939
940    fn transaction_inputs(transaction: &WalletTransaction) -> Vec<&Self::Input> {
941        transaction.sapling_nullifiers()
942    }
943
944    fn transaction_outputs(transaction: &WalletTransaction) -> &[Self] {
945        &transaction.sapling_notes
946    }
947}
948
949impl NoteInterface for SaplingNote {
950    type ZcashNote = sapling_crypto::Note;
951    type Nullifier = Self::Input;
952
953    const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Sapling;
954
955    fn note(&self) -> &Self::ZcashNote {
956        &self.note
957    }
958
959    fn nullifier(&self) -> Option<Self::Nullifier> {
960        self.nullifier
961    }
962
963    fn position(&self) -> Option<Position> {
964        self.position
965    }
966
967    fn memo(&self) -> &Memo {
968        &self.memo
969    }
970
971    fn refetch_nullifier_ranges(&self) -> &[Range<BlockHeight>] {
972        &self.refetch_nullifier_ranges
973    }
974}
975
976/// Orchard note.
977pub type OrchardNote = WalletNote<orchard::Note, orchard::note::Nullifier>;
978
979impl OutputInterface for OrchardNote {
980    type KeyId = KeyId;
981    type Input = orchard::note::Nullifier;
982
983    const POOL_TYPE: PoolType = PoolType::Shielded(ShieldedProtocol::Orchard);
984
985    fn output_id(&self) -> OutputId {
986        self.output_id
987    }
988
989    fn key_id(&self) -> KeyId {
990        self.key_id
991    }
992
993    fn spending_transaction(&self) -> Option<TxId> {
994        self.spending_transaction
995    }
996
997    fn set_spending_transaction(&mut self, spending_transaction: Option<TxId>) {
998        self.spending_transaction = spending_transaction;
999    }
1000
1001    fn value(&self) -> u64 {
1002        self.note.value().inner()
1003    }
1004
1005    fn spend_link(&self) -> Option<Self::Input> {
1006        self.nullifier
1007    }
1008
1009    fn transaction_inputs(transaction: &WalletTransaction) -> Vec<&Self::Input> {
1010        transaction.orchard_nullifiers()
1011    }
1012
1013    fn transaction_outputs(transaction: &WalletTransaction) -> &[Self] {
1014        &transaction.orchard_notes
1015    }
1016}
1017
1018impl NoteInterface for OrchardNote {
1019    type ZcashNote = orchard::Note;
1020    type Nullifier = Self::Input;
1021
1022    const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Orchard;
1023
1024    fn note(&self) -> &Self::ZcashNote {
1025        &self.note
1026    }
1027
1028    fn nullifier(&self) -> Option<Self::Nullifier> {
1029        self.spend_link()
1030    }
1031
1032    fn position(&self) -> Option<Position> {
1033        self.position
1034    }
1035
1036    fn memo(&self) -> &Memo {
1037        &self.memo
1038    }
1039
1040    fn refetch_nullifier_ranges(&self) -> &[Range<BlockHeight>] {
1041        &self.refetch_nullifier_ranges
1042    }
1043}
1044
1045/// Provides a common API for all outgoing note types.
1046pub trait OutgoingNoteInterface: Sized {
1047    /// Decrypted note type.
1048    type ZcashNote;
1049    /// Address type.
1050    type Address: Clone + Copy + Debug + PartialEq + Eq;
1051    /// Encoding error
1052    type Error: Debug + std::error::Error;
1053
1054    /// Note's associated shielded protocol.
1055    const SHIELDED_PROTOCOL: ShieldedProtocol;
1056
1057    /// Output ID.
1058    fn output_id(&self) -> OutputId;
1059
1060    /// Identifier for key used to decrypt outgoing note.
1061    fn key_id(&self) -> KeyId;
1062
1063    /// Note value.
1064    fn value(&self) -> u64;
1065
1066    /// Decrypted note with recipient and value.
1067    fn note(&self) -> &Self::ZcashNote;
1068
1069    /// Memo.
1070    fn memo(&self) -> &Memo;
1071
1072    /// Recipient address.
1073    fn recipient(&self) -> Self::Address;
1074
1075    /// Recipient unified address as given by recipient and recorded in an encoded memo (all original receivers).
1076    fn recipient_full_unified_address(&self) -> Option<&UnifiedAddress>;
1077
1078    /// Encoded recipient address recorded in note on chain (single receiver).
1079    fn encoded_recipient<P>(&self, parameters: &P) -> Result<String, Self::Error>
1080    where
1081        P: consensus::Parameters + consensus::NetworkConstants;
1082
1083    /// Encoded recipient unified address as given by recipient and recorded in an encoded memo (all original receivers).
1084    fn encoded_recipient_full_unified_address<P>(&self, consensus_parameters: &P) -> Option<String>
1085    where
1086        P: consensus::Parameters + consensus::NetworkConstants;
1087
1088    /// Outgoing notes within `transaction`.
1089    fn transaction_outgoing_notes(transaction: &WalletTransaction) -> &[Self];
1090}
1091
1092/// Note sent from this capability to a recipient.
1093#[derive(Debug, Clone, PartialEq)]
1094pub struct OutgoingNote<N> {
1095    /// Output ID.
1096    pub(crate) output_id: OutputId,
1097    /// Identifier for key used to decrypt output.
1098    pub(crate) key_id: KeyId,
1099    /// Decrypted note with recipient and value.
1100    pub(crate) note: N,
1101    /// Memo.
1102    pub(crate) memo: Memo,
1103    /// Recipient's full unified address from encoded memo.
1104    pub(crate) recipient_full_unified_address: Option<UnifiedAddress>,
1105}
1106
1107/// Outgoing sapling note.
1108pub type OutgoingSaplingNote = OutgoingNote<sapling_crypto::Note>;
1109
1110impl OutgoingNoteInterface for OutgoingSaplingNote {
1111    type ZcashNote = sapling_crypto::Note;
1112    type Address = sapling_crypto::PaymentAddress;
1113    type Error = Infallible;
1114
1115    const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Sapling;
1116
1117    fn output_id(&self) -> OutputId {
1118        self.output_id
1119    }
1120
1121    fn key_id(&self) -> KeyId {
1122        self.key_id
1123    }
1124
1125    fn value(&self) -> u64 {
1126        self.note.value().inner()
1127    }
1128
1129    fn note(&self) -> &Self::ZcashNote {
1130        &self.note
1131    }
1132
1133    fn memo(&self) -> &Memo {
1134        &self.memo
1135    }
1136
1137    fn recipient(&self) -> Self::Address {
1138        self.note.recipient()
1139    }
1140
1141    fn recipient_full_unified_address(&self) -> Option<&UnifiedAddress> {
1142        self.recipient_full_unified_address.as_ref()
1143    }
1144
1145    fn encoded_recipient<P>(&self, consensus_parameters: &P) -> Result<String, Self::Error>
1146    where
1147        P: consensus::Parameters + consensus::NetworkConstants,
1148    {
1149        Ok(encode_payment_address(
1150            consensus_parameters.hrp_sapling_payment_address(),
1151            &self.note().recipient(),
1152        ))
1153    }
1154
1155    fn encoded_recipient_full_unified_address<P>(&self, consensus_parameters: &P) -> Option<String>
1156    where
1157        P: consensus::Parameters + consensus::NetworkConstants,
1158    {
1159        self.recipient_full_unified_address
1160            .as_ref()
1161            .map(|unified_address| unified_address.encode(consensus_parameters))
1162    }
1163
1164    fn transaction_outgoing_notes(transaction: &WalletTransaction) -> &[Self] {
1165        &transaction.outgoing_sapling_notes
1166    }
1167}
1168
1169/// Outgoing orchard note.
1170pub type OutgoingOrchardNote = OutgoingNote<orchard::Note>;
1171
1172impl OutgoingNoteInterface for OutgoingOrchardNote {
1173    type ZcashNote = orchard::Note;
1174    type Address = orchard::Address;
1175    type Error = ParseError;
1176
1177    const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Orchard;
1178
1179    fn output_id(&self) -> OutputId {
1180        self.output_id
1181    }
1182
1183    fn key_id(&self) -> KeyId {
1184        self.key_id
1185    }
1186
1187    fn value(&self) -> u64 {
1188        self.note.value().inner()
1189    }
1190
1191    fn note(&self) -> &Self::ZcashNote {
1192        &self.note
1193    }
1194
1195    fn memo(&self) -> &Memo {
1196        &self.memo
1197    }
1198
1199    fn recipient(&self) -> Self::Address {
1200        self.note.recipient()
1201    }
1202
1203    fn recipient_full_unified_address(&self) -> Option<&UnifiedAddress> {
1204        self.recipient_full_unified_address.as_ref()
1205    }
1206
1207    fn encoded_recipient<P>(&self, parameters: &P) -> Result<String, Self::Error>
1208    where
1209        P: consensus::Parameters + consensus::NetworkConstants,
1210    {
1211        keys::encode_orchard_receiver(parameters, &self.note().recipient())
1212    }
1213
1214    fn encoded_recipient_full_unified_address<P>(&self, consensus_parameters: &P) -> Option<String>
1215    where
1216        P: consensus::Parameters + consensus::NetworkConstants,
1217    {
1218        self.recipient_full_unified_address
1219            .as_ref()
1220            .map(|unified_address| unified_address.encode(consensus_parameters))
1221    }
1222
1223    fn transaction_outgoing_notes(transaction: &WalletTransaction) -> &[Self] {
1224        &transaction.outgoing_orchard_notes
1225    }
1226}
1227
1228// TODO: allow consumer to define shard store. memory shard store has infallible error type but other may not so error
1229// handling will need to replace expects
1230/// Type alias for sapling memory shard store
1231pub type SaplingShardStore = MemoryShardStore<sapling_crypto::Node, BlockHeight>;
1232
1233/// Type alias for orchard memory shard store
1234pub type OrchardShardStore = MemoryShardStore<MerkleHashOrchard, BlockHeight>;
1235
1236/// Shard tree wallet data struct
1237#[derive(Debug)]
1238pub struct ShardTrees {
1239    /// Sapling shard tree
1240    pub sapling: ShardTree<
1241        SaplingShardStore,
1242        { sapling_crypto::NOTE_COMMITMENT_TREE_DEPTH },
1243        { witness::SHARD_HEIGHT },
1244    >,
1245    /// Orchard shard tree
1246    pub orchard: ShardTree<
1247        OrchardShardStore,
1248        { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 },
1249        { witness::SHARD_HEIGHT },
1250    >,
1251}
1252
1253impl ShardTrees {
1254    /// Create new `ShardTrees`
1255    #[must_use]
1256    pub fn new() -> Self {
1257        let mut sapling = ShardTree::new(MemoryShardStore::empty(), MAX_REORG_ALLOWANCE as usize);
1258        let mut orchard = ShardTree::new(MemoryShardStore::empty(), MAX_REORG_ALLOWANCE as usize);
1259
1260        sapling
1261            .checkpoint(BlockHeight::from_u32(0))
1262            .expect("should never fail");
1263        orchard
1264            .checkpoint(BlockHeight::from_u32(0))
1265            .expect("should never fail");
1266
1267        Self { sapling, orchard }
1268    }
1269}
1270
1271impl Default for ShardTrees {
1272    fn default() -> Self {
1273        Self::new()
1274    }
1275}