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    legacy::Script,
27    memo::Memo,
28    transaction::{TxId, components::transparent::OutPoint},
29};
30use zcash_protocol::{
31    PoolType, ShieldedProtocol,
32    consensus::{self, BlockHeight},
33    value::Zatoshis,
34};
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_VERIFICATION_WINDOW, 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 to scanned in previous sync sessions.
86    pub(crate) previously_scanned_sapling_outputs: u32,
87    /// Total number of orchard outputs to 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            })
205            .next_back()
206        {
207            Some(last_scanned_range.block_range().end - 1)
208        } else {
209            self.wallet_birthday().map(|birthday| birthday - 1)
210        }
211    }
212
213    /// Returns the wallet birthday or `None` if `self.scan_ranges` is empty.
214    ///
215    /// If the wallet birthday is below the sapling activation height, returns the sapling activation height instead.
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 wallet_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
570#[cfg(feature = "wallet_essentials")]
571impl WalletTransaction {
572    /// Returns the total value sent to receivers, excluding value sent to the wallet's own addresses.
573    #[must_use]
574    pub fn total_value_sent(&self) -> u64 {
575        let transparent_value_sent = self
576            .transaction
577            .transparent_bundle()
578            .map_or(0, |bundle| {
579                bundle
580                    .vout
581                    .iter()
582                    .map(|output| output.value().into_u64())
583                    .sum()
584            })
585            .saturating_sub(self.total_output_value::<TransparentCoin>());
586
587        // TODO: it is not intended behaviour to create outgoing change notes. the logic must be changed to be resilient
588        // to this fix to zcash client backend
589        let sapling_value_sent = self
590            .total_outgoing_note_value::<OutgoingSaplingNote>()
591            .saturating_sub(self.total_output_value::<SaplingNote>());
592        let orchard_value_sent = self
593            .total_outgoing_note_value::<OutgoingOrchardNote>()
594            .saturating_sub(self.total_output_value::<OrchardNote>());
595
596        transparent_value_sent + sapling_value_sent + orchard_value_sent
597    }
598
599    /// Returns total sum of all output values.
600    #[must_use]
601    pub fn total_value_received(&self) -> u64 {
602        self.total_output_value::<TransparentCoin>()
603            + self.total_output_value::<SaplingNote>()
604            + self.total_output_value::<OrchardNote>()
605    }
606
607    /// Returns total sum of output values for a given pool.
608    #[must_use]
609    pub fn total_output_value<Op: OutputInterface>(&self) -> u64 {
610        Op::transaction_outputs(self)
611            .iter()
612            .map(OutputInterface::value)
613            .sum()
614    }
615
616    /// Returns total sum of outgoing note values for a given shielded pool.
617    #[must_use]
618    pub fn total_outgoing_note_value<Op: OutgoingNoteInterface>(&self) -> u64 {
619        Op::transaction_outgoing_notes(self)
620            .iter()
621            .map(OutgoingNoteInterface::value)
622            .sum()
623    }
624}
625
626impl std::fmt::Debug for WalletTransaction {
627    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
628        f.debug_struct("WalletTransaction")
629            .field("txid", &self.txid)
630            .field("confirmation_status", &self.status)
631            .field("datetime", &self.datetime)
632            .field("transparent_coins", &self.transparent_coins)
633            .field("sapling_notes", &self.sapling_notes)
634            .field("orchard_notes", &self.orchard_notes)
635            .field("outgoing_sapling_notes", &self.outgoing_sapling_notes)
636            .field("outgoing_orchard_notes", &self.outgoing_orchard_notes)
637            .finish()
638    }
639}
640
641/// Provides a common API for all key identifiers.
642pub trait KeyIdInterface {
643    /// Account ID.
644    fn account_id(&self) -> zip32::AccountId;
645}
646
647/// Provides a common API for all output types.
648pub trait OutputInterface: Sized {
649    /// Identifier for key used to decrypt output.
650    type KeyId: KeyIdInterface;
651    /// Transaction input type associated with spend detection of output.
652    type Input: Clone + Debug + PartialEq + Eq + PartialOrd + Ord;
653
654    /// Output's associated pool type.
655    const POOL_TYPE: PoolType;
656
657    /// Output ID.
658    fn output_id(&self) -> OutputId;
659
660    /// Identifier for key used to decrypt output.
661    fn key_id(&self) -> Self::KeyId;
662
663    /// Transaction ID of transaction this output was spent.
664    /// If `None`, output is not spent.
665    fn spending_transaction(&self) -> Option<TxId>;
666
667    /// Sets spending transaction.
668    fn set_spending_transaction(&mut self, spending_transaction: Option<TxId>);
669
670    /// Note value..
671    // TODO: change to Zatoshis checked type
672    fn value(&self) -> u64;
673
674    /// Returns the type used to link with transaction inputs for spend detection.
675    /// Returns `None` in the case the nullifier is not available for shielded outputs.
676    ///
677    /// Nullifier for shielded outputs.
678    /// Outpoint for transparent outputs.
679    fn spend_link(&self) -> Option<Self::Input>;
680
681    /// Inputs within `transaction` used to detect an output's spend status.
682    ///
683    /// Nullifiers for shielded outputs.
684    /// Out points for transparent outputs.
685    fn transaction_inputs(transaction: &WalletTransaction) -> Vec<&Self::Input>;
686
687    /// Outputs within `transaction`.
688    fn transaction_outputs(transaction: &WalletTransaction) -> &[Self];
689}
690
691/// Provides a common API for all shielded output types.
692pub trait NoteInterface: OutputInterface {
693    /// Decrypted note type.
694    type ZcashNote;
695    /// Nullifier type.
696    type Nullifier: Copy;
697
698    /// Note's associated shielded protocol.
699    const SHIELDED_PROTOCOL: ShieldedProtocol;
700
701    /// Decrypted note with recipient and value
702    fn note(&self) -> &Self::ZcashNote;
703
704    /// Derived nullifier
705    fn nullifier(&self) -> Option<Self::Nullifier>;
706
707    /// Commitment tree leaf position
708    fn position(&self) -> Option<Position>;
709
710    /// Memo
711    fn memo(&self) -> &Memo;
712}
713
714///  Transparent coin (output) with metadata relevant to the wallet.
715#[derive(Debug, Clone)]
716pub struct TransparentCoin {
717    /// Output ID.
718    pub(crate) output_id: OutputId,
719    /// Identifier for key used to derive address.
720    pub(crate) key_id: TransparentAddressId,
721    /// Encoded transparent address.
722    pub(crate) address: String,
723    /// Script.
724    pub(crate) script: Script,
725    /// Coin value.
726    pub(crate) value: Zatoshis,
727    /// Transaction ID of transaction this output was spent.
728    /// If `None`, output is not spent.
729    pub(crate) spending_transaction: Option<TxId>,
730}
731
732impl TransparentCoin {
733    /// Address received to.
734    #[must_use]
735    pub fn address(&self) -> &str {
736        &self.address
737    }
738
739    /// Script.
740    #[must_use]
741    pub fn script(&self) -> &Script {
742        &self.script
743    }
744}
745
746impl OutputInterface for TransparentCoin {
747    type KeyId = TransparentAddressId;
748    type Input = OutPoint;
749
750    const POOL_TYPE: PoolType = PoolType::Transparent;
751
752    fn output_id(&self) -> OutputId {
753        self.output_id
754    }
755
756    fn key_id(&self) -> Self::KeyId {
757        self.key_id
758    }
759
760    fn spending_transaction(&self) -> Option<TxId> {
761        self.spending_transaction
762    }
763
764    fn set_spending_transaction(&mut self, spending_transaction: Option<TxId>) {
765        self.spending_transaction = spending_transaction;
766    }
767
768    fn value(&self) -> u64 {
769        self.value.into_u64()
770    }
771
772    fn spend_link(&self) -> Option<Self::Input> {
773        Some(self.output_id.into())
774    }
775
776    fn transaction_inputs(transaction: &WalletTransaction) -> Vec<&Self::Input> {
777        transaction.outpoints()
778    }
779
780    fn transaction_outputs(transaction: &WalletTransaction) -> &[Self] {
781        &transaction.transparent_coins
782    }
783}
784
785/// Wallet note, shielded output with metadata relevant to the wallet.
786#[derive(Debug, Clone)]
787pub struct WalletNote<N, Nf: Copy> {
788    /// Output ID.
789    pub(crate) output_id: OutputId,
790    /// Identifier for key used to decrypt output.
791    pub(crate) key_id: KeyId,
792    /// Decrypted note with recipient and value.
793    pub(crate) note: N,
794    /// Derived nullifier.
795    pub(crate) nullifier: Option<Nf>, //TODO: syncing without nullifier deriving key
796    /// Commitment tree leaf position.
797    pub(crate) position: Option<Position>,
798    /// Memo.
799    pub(crate) memo: Memo,
800    /// Transaction ID of transaction this output was spent.
801    /// If `None`, output is not spent.
802    pub(crate) spending_transaction: Option<TxId>,
803}
804
805/// Sapling note.
806pub type SaplingNote = WalletNote<sapling_crypto::Note, sapling_crypto::Nullifier>;
807
808impl OutputInterface for SaplingNote {
809    type KeyId = KeyId;
810    type Input = sapling_crypto::Nullifier;
811
812    const POOL_TYPE: PoolType = PoolType::Shielded(ShieldedProtocol::Sapling);
813
814    fn output_id(&self) -> OutputId {
815        self.output_id
816    }
817
818    fn key_id(&self) -> KeyId {
819        self.key_id
820    }
821
822    fn spending_transaction(&self) -> Option<TxId> {
823        self.spending_transaction
824    }
825
826    fn set_spending_transaction(&mut self, spending_transaction: Option<TxId>) {
827        self.spending_transaction = spending_transaction;
828    }
829
830    fn value(&self) -> u64 {
831        self.note.value().inner()
832    }
833
834    fn spend_link(&self) -> Option<Self::Input> {
835        self.nullifier
836    }
837
838    fn transaction_inputs(transaction: &WalletTransaction) -> Vec<&Self::Input> {
839        transaction.sapling_nullifiers()
840    }
841
842    fn transaction_outputs(transaction: &WalletTransaction) -> &[Self] {
843        &transaction.sapling_notes
844    }
845}
846
847impl NoteInterface for SaplingNote {
848    type ZcashNote = sapling_crypto::Note;
849    type Nullifier = Self::Input;
850
851    const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Sapling;
852
853    fn note(&self) -> &Self::ZcashNote {
854        &self.note
855    }
856
857    fn nullifier(&self) -> Option<Self::Nullifier> {
858        self.nullifier
859    }
860
861    fn position(&self) -> Option<Position> {
862        self.position
863    }
864
865    fn memo(&self) -> &Memo {
866        &self.memo
867    }
868}
869
870/// Orchard note.
871pub type OrchardNote = WalletNote<orchard::Note, orchard::note::Nullifier>;
872
873impl OutputInterface for OrchardNote {
874    type KeyId = KeyId;
875    type Input = orchard::note::Nullifier;
876
877    const POOL_TYPE: PoolType = PoolType::Shielded(ShieldedProtocol::Orchard);
878
879    fn output_id(&self) -> OutputId {
880        self.output_id
881    }
882
883    fn key_id(&self) -> KeyId {
884        self.key_id
885    }
886
887    fn spending_transaction(&self) -> Option<TxId> {
888        self.spending_transaction
889    }
890
891    fn set_spending_transaction(&mut self, spending_transaction: Option<TxId>) {
892        self.spending_transaction = spending_transaction;
893    }
894
895    fn value(&self) -> u64 {
896        self.note.value().inner()
897    }
898
899    fn spend_link(&self) -> Option<Self::Input> {
900        self.nullifier
901    }
902
903    fn transaction_inputs(transaction: &WalletTransaction) -> Vec<&Self::Input> {
904        transaction.orchard_nullifiers()
905    }
906
907    fn transaction_outputs(transaction: &WalletTransaction) -> &[Self] {
908        &transaction.orchard_notes
909    }
910}
911
912impl NoteInterface for OrchardNote {
913    type ZcashNote = orchard::Note;
914    type Nullifier = Self::Input;
915
916    const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Orchard;
917
918    fn note(&self) -> &Self::ZcashNote {
919        &self.note
920    }
921
922    fn nullifier(&self) -> Option<Self::Nullifier> {
923        self.spend_link()
924    }
925
926    fn position(&self) -> Option<Position> {
927        self.position
928    }
929
930    fn memo(&self) -> &Memo {
931        &self.memo
932    }
933}
934
935/// Provides a common API for all outgoing note types.
936pub trait OutgoingNoteInterface: Sized {
937    /// Decrypted note type.
938    type ZcashNote;
939    /// Address type.
940    type Address: Clone + Copy + Debug + PartialEq + Eq;
941    /// Encoding error
942    type Error: Debug + std::error::Error;
943
944    /// Note's associated shielded protocol.
945    const SHIELDED_PROTOCOL: ShieldedProtocol;
946
947    /// Output ID.
948    fn output_id(&self) -> OutputId;
949
950    /// Identifier for key used to decrypt outgoing note.
951    fn key_id(&self) -> KeyId;
952
953    /// Note value.
954    fn value(&self) -> u64;
955
956    /// Decrypted note with recipient and value.
957    fn note(&self) -> &Self::ZcashNote;
958
959    /// Memo.
960    fn memo(&self) -> &Memo;
961
962    /// Recipient address.
963    fn recipient(&self) -> Self::Address;
964
965    /// Recipient unified address as given by recipient and recorded in an encoded memo (all original receivers).
966    fn recipient_full_unified_address(&self) -> Option<&UnifiedAddress>;
967
968    /// Encoded recipient address recorded in note on chain (single receiver).
969    fn encoded_recipient<P>(&self, parameters: &P) -> Result<String, Self::Error>
970    where
971        P: consensus::Parameters + consensus::NetworkConstants;
972
973    /// Encoded recipient unified address as given by recipient and recorded in an encoded memo (all original receivers).
974    fn encoded_recipient_full_unified_address<P>(&self, consensus_parameters: &P) -> Option<String>
975    where
976        P: consensus::Parameters + consensus::NetworkConstants;
977
978    /// Outgoing notes within `transaction`.
979    fn transaction_outgoing_notes(transaction: &WalletTransaction) -> &[Self];
980}
981
982/// Note sent from this capability to a recipient.
983#[derive(Debug, Clone, PartialEq)]
984pub struct OutgoingNote<N> {
985    /// Output ID.
986    pub(crate) output_id: OutputId,
987    /// Identifier for key used to decrypt output.
988    pub(crate) key_id: KeyId,
989    /// Decrypted note with recipient and value.
990    pub(crate) note: N,
991    /// Memo.
992    pub(crate) memo: Memo,
993    /// Recipient's full unified address from encoded memo.
994    pub(crate) recipient_full_unified_address: Option<UnifiedAddress>,
995}
996
997/// Outgoing sapling note.
998pub type OutgoingSaplingNote = OutgoingNote<sapling_crypto::Note>;
999
1000impl OutgoingNoteInterface for OutgoingSaplingNote {
1001    type ZcashNote = sapling_crypto::Note;
1002    type Address = sapling_crypto::PaymentAddress;
1003    type Error = Infallible;
1004
1005    const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Sapling;
1006
1007    fn output_id(&self) -> OutputId {
1008        self.output_id
1009    }
1010
1011    fn key_id(&self) -> KeyId {
1012        self.key_id
1013    }
1014
1015    fn value(&self) -> u64 {
1016        self.note.value().inner()
1017    }
1018
1019    fn note(&self) -> &Self::ZcashNote {
1020        &self.note
1021    }
1022
1023    fn memo(&self) -> &Memo {
1024        &self.memo
1025    }
1026
1027    fn recipient(&self) -> Self::Address {
1028        self.note.recipient()
1029    }
1030
1031    fn recipient_full_unified_address(&self) -> Option<&UnifiedAddress> {
1032        self.recipient_full_unified_address.as_ref()
1033    }
1034
1035    fn encoded_recipient<P>(&self, consensus_parameters: &P) -> Result<String, Self::Error>
1036    where
1037        P: consensus::Parameters + consensus::NetworkConstants,
1038    {
1039        Ok(encode_payment_address(
1040            consensus_parameters.hrp_sapling_payment_address(),
1041            &self.note().recipient(),
1042        ))
1043    }
1044
1045    fn encoded_recipient_full_unified_address<P>(&self, consensus_parameters: &P) -> Option<String>
1046    where
1047        P: consensus::Parameters + consensus::NetworkConstants,
1048    {
1049        self.recipient_full_unified_address
1050            .as_ref()
1051            .map(|unified_address| unified_address.encode(consensus_parameters))
1052    }
1053
1054    fn transaction_outgoing_notes(transaction: &WalletTransaction) -> &[Self] {
1055        &transaction.outgoing_sapling_notes
1056    }
1057}
1058
1059/// Outgoing orchard note.
1060pub type OutgoingOrchardNote = OutgoingNote<orchard::Note>;
1061
1062impl OutgoingNoteInterface for OutgoingOrchardNote {
1063    type ZcashNote = orchard::Note;
1064    type Address = orchard::Address;
1065    type Error = ParseError;
1066
1067    const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Orchard;
1068
1069    fn output_id(&self) -> OutputId {
1070        self.output_id
1071    }
1072
1073    fn key_id(&self) -> KeyId {
1074        self.key_id
1075    }
1076
1077    fn value(&self) -> u64 {
1078        self.note.value().inner()
1079    }
1080
1081    fn note(&self) -> &Self::ZcashNote {
1082        &self.note
1083    }
1084
1085    fn memo(&self) -> &Memo {
1086        &self.memo
1087    }
1088
1089    fn recipient(&self) -> Self::Address {
1090        self.note.recipient()
1091    }
1092
1093    fn recipient_full_unified_address(&self) -> Option<&UnifiedAddress> {
1094        self.recipient_full_unified_address.as_ref()
1095    }
1096
1097    fn encoded_recipient<P>(&self, parameters: &P) -> Result<String, Self::Error>
1098    where
1099        P: consensus::Parameters + consensus::NetworkConstants,
1100    {
1101        keys::encode_orchard_receiver(parameters, &self.note().recipient())
1102    }
1103
1104    fn encoded_recipient_full_unified_address<P>(&self, consensus_parameters: &P) -> Option<String>
1105    where
1106        P: consensus::Parameters + consensus::NetworkConstants,
1107    {
1108        self.recipient_full_unified_address
1109            .as_ref()
1110            .map(|unified_address| unified_address.encode(consensus_parameters))
1111    }
1112
1113    fn transaction_outgoing_notes(transaction: &WalletTransaction) -> &[Self] {
1114        &transaction.outgoing_orchard_notes
1115    }
1116}
1117
1118// TODO: allow consumer to define shard store. memory shard store has infallible error type but other may not so error
1119// handling will need to replace expects
1120/// Type alias for sapling memory shard store
1121pub type SaplingShardStore = MemoryShardStore<sapling_crypto::Node, BlockHeight>;
1122
1123/// Type alias for orchard memory shard store
1124pub type OrchardShardStore = MemoryShardStore<MerkleHashOrchard, BlockHeight>;
1125
1126/// Shard tree wallet data struct
1127#[derive(Debug)]
1128pub struct ShardTrees {
1129    /// Sapling shard tree
1130    pub sapling: ShardTree<
1131        SaplingShardStore,
1132        { sapling_crypto::NOTE_COMMITMENT_TREE_DEPTH },
1133        { witness::SHARD_HEIGHT },
1134    >,
1135    /// Orchard shard tree
1136    pub orchard: ShardTree<
1137        OrchardShardStore,
1138        { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 },
1139        { witness::SHARD_HEIGHT },
1140    >,
1141}
1142
1143impl ShardTrees {
1144    /// Create new `ShardTrees`
1145    #[must_use]
1146    pub fn new() -> Self {
1147        let mut sapling =
1148            ShardTree::new(MemoryShardStore::empty(), MAX_VERIFICATION_WINDOW as usize);
1149        let mut orchard =
1150            ShardTree::new(MemoryShardStore::empty(), MAX_VERIFICATION_WINDOW as usize);
1151
1152        sapling
1153            .checkpoint(BlockHeight::from_u32(0))
1154            .expect("should never fail");
1155        orchard
1156            .checkpoint(BlockHeight::from_u32(0))
1157            .expect("should never fail");
1158
1159        Self { sapling, orchard }
1160    }
1161}
1162
1163impl Default for ShardTrees {
1164    fn default() -> Self {
1165        Self::new()
1166    }
1167}