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