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