1use std::{
7 collections::{BTreeMap, BTreeSet},
8 convert::Infallible,
9 fmt::Debug,
10 ops::Range,
11 sync::{
12 Arc,
13 atomic::{self, AtomicU8},
14 },
15};
16
17use incrementalmerkletree::Position;
18use orchard::tree::MerkleHashOrchard;
19use shardtree::{ShardTree, store::memory::MemoryShardStore};
20use tokio::sync::mpsc;
21use zcash_address::unified::ParseError;
22use zcash_client_backend::proto::compact_formats::CompactBlock;
23use zcash_keys::{address::UnifiedAddress, encoding::encode_payment_address};
24use zcash_primitives::{block::BlockHash, transaction::TxId};
25use zcash_protocol::{
26 PoolType, ShieldedProtocol,
27 consensus::{self, BlockHeight},
28 memo::Memo,
29 value::Zatoshis,
30};
31use zcash_transparent::{address::Script, bundle::OutPoint};
32
33use zingo_status::confirmation_status::ConfirmationStatus;
34
35use crate::{
36 client::FetchRequest,
37 error::{ServerError, SyncModeError},
38 keys::{self, KeyId, transparent::TransparentAddressId},
39 scan::compact_blocks::calculate_block_tree_bounds,
40 sync::{MAX_REORG_ALLOWANCE, ScanPriority, ScanRange},
41 witness,
42};
43
44pub mod traits;
45
46#[cfg(feature = "wallet_essentials")]
47pub mod serialization;
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
59pub struct ScanTarget {
60 pub block_height: BlockHeight,
62 pub txid: TxId,
64 pub narrow_scan_area: bool,
66}
67
68#[derive(Debug, Clone)]
72pub struct InitialSyncState {
73 pub(crate) sync_start_height: BlockHeight,
78 pub(crate) wallet_tree_bounds: TreeBounds,
80 pub(crate) previously_scanned_blocks: u32,
82 pub(crate) previously_scanned_sapling_outputs: u32,
84 pub(crate) previously_scanned_orchard_outputs: u32,
86}
87
88impl InitialSyncState {
89 #[must_use]
91 pub fn new() -> Self {
92 InitialSyncState {
93 sync_start_height: 0.into(),
94 wallet_tree_bounds: TreeBounds {
95 sapling_initial_tree_size: 0,
96 sapling_final_tree_size: 0,
97 orchard_initial_tree_size: 0,
98 orchard_final_tree_size: 0,
99 },
100 previously_scanned_blocks: 0,
101 previously_scanned_sapling_outputs: 0,
102 previously_scanned_orchard_outputs: 0,
103 }
104 }
105}
106
107impl Default for InitialSyncState {
108 fn default() -> Self {
109 Self::new()
110 }
111}
112
113#[derive(Debug, Clone)]
115pub struct SyncState {
116 pub(crate) scan_ranges: Vec<ScanRange>,
119 pub(crate) sapling_shard_ranges: Vec<Range<BlockHeight>>,
124 pub(crate) orchard_shard_ranges: Vec<Range<BlockHeight>>,
129 pub(crate) scan_targets: BTreeSet<ScanTarget>,
131 pub(crate) initial_sync_state: InitialSyncState,
133}
134
135impl SyncState {
136 #[must_use]
138 pub fn new() -> Self {
139 SyncState {
140 scan_ranges: Vec::new(),
141 sapling_shard_ranges: Vec::new(),
142 orchard_shard_ranges: Vec::new(),
143 scan_targets: BTreeSet::new(),
144 initial_sync_state: InitialSyncState::new(),
145 }
146 }
147
148 #[must_use]
150 pub fn scan_ranges(&self) -> &[ScanRange] {
151 &self.scan_ranges
152 }
153
154 #[must_use]
156 pub fn sapling_shard_ranges(&self) -> &[Range<BlockHeight>] {
157 &self.sapling_shard_ranges
158 }
159
160 #[must_use]
162 pub fn orchard_shard_ranges(&self) -> &[Range<BlockHeight>] {
163 &self.orchard_shard_ranges
164 }
165
166 pub(crate) fn scan_complete(&self) -> bool {
168 self.scan_ranges
169 .iter()
170 .all(|scan_range| scan_range.priority() == ScanPriority::Scanned)
171 }
172
173 #[must_use]
176 pub fn fully_scanned_height(&self) -> Option<BlockHeight> {
177 if let Some(scan_range) = self
178 .scan_ranges
179 .iter()
180 .find(|scan_range| scan_range.priority() != ScanPriority::Scanned)
181 {
182 Some(scan_range.block_range().start - 1)
183 } else {
184 self.scan_ranges
185 .last()
186 .map(|range| range.block_range().end - 1)
187 }
188 }
189
190 #[must_use]
194 pub fn highest_scanned_height(&self) -> Option<BlockHeight> {
195 if let Some(last_scanned_range) = self
196 .scan_ranges
197 .iter()
198 .filter(|scan_range| {
199 scan_range.priority() == ScanPriority::Scanned
200 || scan_range.priority() == ScanPriority::ScannedWithoutMapping
201 || scan_range.priority() == ScanPriority::RefetchingNullifiers
202 })
203 .next_back()
204 {
205 Some(last_scanned_range.block_range().end - 1)
206 } else {
207 self.wallet_birthday().map(|start| start - 1)
208 }
209 }
210
211 #[must_use]
214 pub fn wallet_birthday(&self) -> Option<BlockHeight> {
215 self.scan_ranges
216 .first()
217 .map(|range| range.block_range().start)
218 }
219
220 #[must_use]
222 pub fn last_known_chain_height(&self) -> Option<BlockHeight> {
223 self.scan_ranges
224 .last()
225 .map(|range| range.block_range().end - 1)
226 }
227}
228
229impl Default for SyncState {
230 fn default() -> Self {
231 Self::new()
232 }
233}
234
235#[derive(Debug, Clone, Copy, PartialEq, Eq)]
237pub enum SyncMode {
238 NotRunning,
240 Paused,
242 Running,
244 Shutdown,
246}
247
248impl SyncMode {
249 pub fn from_u8(mode: u8) -> Result<Self, SyncModeError> {
253 match mode {
254 0 => Ok(Self::NotRunning),
255 1 => Ok(Self::Paused),
256 2 => Ok(Self::Running),
257 3 => Ok(Self::Shutdown),
258 _ => Err(SyncModeError::InvalidSyncMode(mode)),
259 }
260 }
261
262 pub fn from_atomic_u8(atomic_sync_mode: Arc<AtomicU8>) -> Result<SyncMode, SyncModeError> {
270 SyncMode::from_u8(atomic_sync_mode.load(atomic::Ordering::Acquire))
271 }
272}
273
274#[derive(Debug, Clone, Copy)]
276#[allow(missing_docs)]
277pub struct TreeBounds {
278 pub sapling_initial_tree_size: u32,
279 pub sapling_final_tree_size: u32,
280 pub orchard_initial_tree_size: u32,
281 pub orchard_final_tree_size: u32,
282}
283
284#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
286pub struct OutputId {
287 txid: TxId,
289 output_index: u16,
291}
292
293impl OutputId {
294 #[must_use]
296 pub fn new(txid: TxId, output_index: u16) -> Self {
297 OutputId { txid, output_index }
298 }
299
300 #[must_use]
302 pub fn txid(&self) -> TxId {
303 self.txid
304 }
305
306 #[must_use]
308 pub fn output_index(&self) -> u16 {
309 self.output_index
310 }
311}
312
313impl std::fmt::Display for OutputId {
314 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
315 write!(
316 f,
317 "{{
318 txid: {}
319 output index: {}
320 }}",
321 self.txid, self.output_index
322 )
323 }
324}
325
326impl From<&OutPoint> for OutputId {
327 fn from(value: &OutPoint) -> Self {
328 OutputId::new(*value.txid(), value.n() as u16)
329 }
330}
331
332impl From<OutputId> for OutPoint {
333 fn from(value: OutputId) -> Self {
334 OutPoint::new(value.txid.into(), u32::from(value.output_index))
335 }
336}
337
338#[derive(Debug)]
340pub struct NullifierMap {
341 pub sapling: BTreeMap<sapling_crypto::Nullifier, ScanTarget>,
343 pub orchard: BTreeMap<orchard::note::Nullifier, ScanTarget>,
345}
346
347impl NullifierMap {
348 #[must_use]
350 pub fn new() -> Self {
351 Self {
352 sapling: BTreeMap::new(),
353 orchard: BTreeMap::new(),
354 }
355 }
356
357 pub fn clear(&mut self) {
359 self.sapling.clear();
360 self.orchard.clear();
361 }
362}
363
364impl Default for NullifierMap {
365 fn default() -> Self {
366 Self::new()
367 }
368}
369
370#[derive(Debug, Clone)]
372pub struct WalletBlock {
373 pub(crate) block_height: BlockHeight,
374 pub(crate) block_hash: BlockHash,
375 pub(crate) prev_hash: BlockHash,
376 pub(crate) time: u32,
377 pub(crate) txids: Vec<TxId>,
378 pub(crate) tree_bounds: TreeBounds,
379}
380
381impl WalletBlock {
382 pub(crate) async fn from_compact_block(
383 consensus_parameters: &impl consensus::Parameters,
384 fetch_request_sender: mpsc::UnboundedSender<FetchRequest>,
385 block: &CompactBlock,
386 ) -> Result<Self, ServerError> {
387 let tree_bounds =
388 calculate_block_tree_bounds(consensus_parameters, fetch_request_sender, block).await?;
389
390 Ok(Self {
391 block_height: block.height(),
392 block_hash: block.hash(),
393 prev_hash: block.prev_hash(),
394 time: block.time,
395 txids: block
396 .vtx
397 .iter()
398 .map(zcash_client_backend::proto::compact_formats::CompactTx::txid)
399 .collect(),
400 tree_bounds,
401 })
402 }
403
404 #[must_use]
406 pub fn block_height(&self) -> BlockHeight {
407 self.block_height
408 }
409
410 #[must_use]
412 pub fn block_hash(&self) -> BlockHash {
413 self.block_hash
414 }
415
416 #[must_use]
418 pub fn prev_hash(&self) -> BlockHash {
419 self.prev_hash
420 }
421
422 #[must_use]
424 pub fn time(&self) -> u32 {
425 self.time
426 }
427
428 #[must_use]
430 pub fn txids(&self) -> &[TxId] {
431 &self.txids
432 }
433
434 #[must_use]
436 pub fn tree_bounds(&self) -> TreeBounds {
437 self.tree_bounds
438 }
439}
440
441pub struct WalletTransaction {
443 pub(crate) txid: TxId,
444 pub(crate) status: ConfirmationStatus,
445 pub(crate) transaction: zcash_primitives::transaction::Transaction,
446 pub(crate) datetime: u32,
447 pub(crate) transparent_coins: Vec<TransparentCoin>,
448 pub(crate) sapling_notes: Vec<SaplingNote>,
449 pub(crate) orchard_notes: Vec<OrchardNote>,
450 pub(crate) outgoing_sapling_notes: Vec<OutgoingSaplingNote>,
451 pub(crate) outgoing_orchard_notes: Vec<OutgoingOrchardNote>,
452}
453
454impl WalletTransaction {
455 #[must_use]
457 pub fn txid(&self) -> TxId {
458 self.txid
459 }
460
461 #[must_use]
463 pub fn status(&self) -> ConfirmationStatus {
464 self.status
465 }
466
467 #[must_use]
469 pub fn transaction(&self) -> &zcash_primitives::transaction::Transaction {
470 &self.transaction
471 }
472
473 #[must_use]
475 pub fn datetime(&self) -> u32 {
476 self.datetime
477 }
478
479 #[must_use]
481 pub fn transparent_coins(&self) -> &[TransparentCoin] {
482 &self.transparent_coins
483 }
484
485 pub fn transparent_coins_mut(&mut self) -> Vec<&mut TransparentCoin> {
487 self.transparent_coins.iter_mut().collect()
488 }
489
490 #[must_use]
492 pub fn sapling_notes(&self) -> &[SaplingNote] {
493 &self.sapling_notes
494 }
495
496 pub fn sapling_notes_mut(&mut self) -> Vec<&mut SaplingNote> {
498 self.sapling_notes.iter_mut().collect()
499 }
500
501 #[must_use]
503 pub fn orchard_notes(&self) -> &[OrchardNote] {
504 &self.orchard_notes
505 }
506
507 pub fn orchard_notes_mut(&mut self) -> Vec<&mut OrchardNote> {
509 self.orchard_notes.iter_mut().collect()
510 }
511
512 #[must_use]
514 pub fn outgoing_sapling_notes(&self) -> &[OutgoingSaplingNote] {
515 &self.outgoing_sapling_notes
516 }
517
518 #[must_use]
520 pub fn outgoing_orchard_notes(&self) -> &[OutgoingOrchardNote] {
521 &self.outgoing_orchard_notes
522 }
523
524 pub fn sapling_nullifiers(&self) -> Vec<&sapling_crypto::Nullifier> {
527 self.transaction
528 .sapling_bundle()
529 .map_or_else(Vec::new, |bundle| {
530 bundle
531 .shielded_spends()
532 .iter()
533 .map(sapling_crypto::bundle::SpendDescription::nullifier)
534 .collect::<Vec<_>>()
535 })
536 }
537
538 pub fn orchard_nullifiers(&self) -> Vec<&orchard::note::Nullifier> {
541 self.transaction
542 .orchard_bundle()
543 .map_or_else(Vec::new, |bundle| {
544 bundle
545 .actions()
546 .iter()
547 .map(orchard::Action::nullifier)
548 .collect::<Vec<_>>()
549 })
550 }
551
552 pub fn outpoints(&self) -> Vec<&OutPoint> {
555 self.transaction
556 .transparent_bundle()
557 .map_or_else(Vec::new, |bundle| {
558 bundle
559 .vin
560 .iter()
561 .map(zcash_transparent::bundle::TxIn::prevout)
562 .collect::<Vec<_>>()
563 })
564 }
565
566 pub fn update_status(&mut self, status: ConfirmationStatus, datetime: u32) {
572 match status {
573 ConfirmationStatus::Transmitted(_)
574 if matches!(self.status(), ConfirmationStatus::Calculated(_)) =>
575 {
576 self.status = status;
577 self.datetime = datetime;
578 }
579 ConfirmationStatus::Mempool(_)
580 if matches!(
581 self.status(),
582 ConfirmationStatus::Calculated(_) | ConfirmationStatus::Transmitted(_)
583 ) =>
584 {
585 self.status = status;
586 self.datetime = datetime;
587 }
588 ConfirmationStatus::Confirmed(_)
589 if matches!(
590 self.status(),
591 ConfirmationStatus::Calculated(_)
592 | ConfirmationStatus::Transmitted(_)
593 | ConfirmationStatus::Mempool(_)
594 ) =>
595 {
596 self.status = status;
597 self.datetime = datetime;
598 }
599
600 ConfirmationStatus::Failed(_)
601 if !matches!(self.status(), ConfirmationStatus::Failed(_)) =>
602 {
603 self.status = status;
604 self.datetime = datetime;
605 }
606 _ => (),
607 }
608 }
609}
610
611#[cfg(feature = "test-features")]
612impl WalletTransaction {
613 pub fn new_for_test(txid: TxId, status: ConfirmationStatus) -> Self {
617 use zcash_primitives::transaction::{TransactionData, TxVersion};
618 use zcash_protocol::consensus::BranchId;
619
620 let transaction = TransactionData::from_parts(
621 TxVersion::V5,
622 BranchId::Nu5,
623 0,
624 BlockHeight::from_u32(0),
625 None,
626 None,
627 None,
628 None,
629 )
630 .freeze()
631 .expect("empty v5 transaction should always be valid");
632
633 Self {
634 txid,
635 status,
636 transaction,
637 datetime: 0,
638 transparent_coins: Vec::new(),
639 sapling_notes: Vec::new(),
640 orchard_notes: Vec::new(),
641 outgoing_sapling_notes: Vec::new(),
642 outgoing_orchard_notes: Vec::new(),
643 }
644 }
645}
646
647#[cfg(feature = "wallet_essentials")]
648impl WalletTransaction {
649 #[must_use]
651 pub fn total_value_sent(&self) -> u64 {
652 let transparent_value_sent = self
653 .transaction
654 .transparent_bundle()
655 .map_or(0, |bundle| {
656 bundle
657 .vout
658 .iter()
659 .map(|output| output.value().into_u64())
660 .sum()
661 })
662 .saturating_sub(self.total_output_value::<TransparentCoin>());
663
664 let sapling_value_sent =
665 self.total_external_outgoing_note_value::<OutgoingSaplingNote, SaplingNote>();
666 let orchard_value_sent =
667 self.total_external_outgoing_note_value::<OutgoingOrchardNote, OrchardNote>();
668
669 transparent_value_sent + sapling_value_sent + orchard_value_sent
670 }
671
672 #[must_use]
674 pub fn total_value_received(&self) -> u64 {
675 self.total_output_value::<TransparentCoin>()
676 + self.total_output_value::<SaplingNote>()
677 + self.total_output_value::<OrchardNote>()
678 }
679
680 #[must_use]
682 pub fn total_output_value<Op: OutputInterface>(&self) -> u64 {
683 Op::transaction_outputs(self)
684 .iter()
685 .map(OutputInterface::value)
686 .sum()
687 }
688
689 #[must_use]
691 pub fn total_outgoing_note_value<Op: OutgoingNoteInterface>(&self) -> u64 {
692 Op::transaction_outgoing_notes(self)
693 .iter()
694 .map(OutgoingNoteInterface::value)
695 .sum()
696 }
697
698 #[must_use]
700 pub fn total_external_outgoing_note_value<Outgoing, Incoming>(&self) -> u64
701 where
702 Outgoing: OutgoingNoteInterface,
703 Incoming: OutputInterface,
704 {
705 Outgoing::transaction_outgoing_notes(self)
706 .iter()
707 .filter(|outgoing_note| {
708 !Incoming::transaction_outputs(self)
709 .iter()
710 .any(|wallet_note| wallet_note.output_id() == outgoing_note.output_id())
711 })
712 .map(OutgoingNoteInterface::value)
713 .sum()
714 }
715}
716
717impl std::fmt::Debug for WalletTransaction {
718 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
719 f.debug_struct("WalletTransaction")
720 .field("txid", &self.txid)
721 .field("confirmation_status", &self.status)
722 .field("datetime", &self.datetime)
723 .field("transparent_coins", &self.transparent_coins)
724 .field("sapling_notes", &self.sapling_notes)
725 .field("orchard_notes", &self.orchard_notes)
726 .field("outgoing_sapling_notes", &self.outgoing_sapling_notes)
727 .field("outgoing_orchard_notes", &self.outgoing_orchard_notes)
728 .finish()
729 }
730}
731
732pub trait KeyIdInterface {
734 fn account_id(&self) -> zip32::AccountId;
736}
737
738pub trait OutputInterface: Sized {
740 type KeyId: KeyIdInterface;
742 type Input: Clone + Debug + PartialEq + Eq + PartialOrd + Ord;
744
745 const POOL_TYPE: PoolType;
747
748 fn output_id(&self) -> OutputId;
750
751 fn key_id(&self) -> Self::KeyId;
753
754 fn spending_transaction(&self) -> Option<TxId>;
757
758 fn set_spending_transaction(&mut self, spending_transaction: Option<TxId>);
760
761 fn value(&self) -> u64;
764
765 fn spend_link(&self) -> Option<Self::Input>;
771
772 fn transaction_inputs(transaction: &WalletTransaction) -> Vec<&Self::Input>;
777
778 fn transaction_outputs(transaction: &WalletTransaction) -> &[Self];
780}
781
782pub trait NoteInterface: OutputInterface {
784 type ZcashNote;
786 type Nullifier: Copy + Clone + PartialEq + Eq + PartialOrd + Ord;
788
789 const SHIELDED_PROTOCOL: ShieldedProtocol;
791
792 fn note(&self) -> &Self::ZcashNote;
794
795 fn nullifier(&self) -> Option<Self::Nullifier>;
797
798 fn position(&self) -> Option<Position>;
800
801 fn memo(&self) -> &Memo;
803
804 fn refetch_nullifier_ranges(&self) -> &[Range<BlockHeight>];
809}
810
811#[derive(Debug, Clone)]
813pub struct TransparentCoin {
814 pub(crate) output_id: OutputId,
816 pub(crate) key_id: TransparentAddressId,
818 pub(crate) address: String,
820 pub(crate) script: Script,
822 pub(crate) value: Zatoshis,
824 pub(crate) spending_transaction: Option<TxId>,
827}
828
829impl TransparentCoin {
830 #[must_use]
832 pub fn address(&self) -> &str {
833 &self.address
834 }
835
836 #[must_use]
838 pub fn script(&self) -> &Script {
839 &self.script
840 }
841}
842
843impl OutputInterface for TransparentCoin {
844 type KeyId = TransparentAddressId;
845 type Input = OutPoint;
846
847 const POOL_TYPE: PoolType = PoolType::Transparent;
848
849 fn output_id(&self) -> OutputId {
850 self.output_id
851 }
852
853 fn key_id(&self) -> Self::KeyId {
854 self.key_id
855 }
856
857 fn spending_transaction(&self) -> Option<TxId> {
858 self.spending_transaction
859 }
860
861 fn set_spending_transaction(&mut self, spending_transaction: Option<TxId>) {
862 self.spending_transaction = spending_transaction;
863 }
864
865 fn value(&self) -> u64 {
866 self.value.into_u64()
867 }
868
869 fn spend_link(&self) -> Option<Self::Input> {
870 Some(self.output_id.into())
871 }
872
873 fn transaction_inputs(transaction: &WalletTransaction) -> Vec<&Self::Input> {
874 transaction.outpoints()
875 }
876
877 fn transaction_outputs(transaction: &WalletTransaction) -> &[Self] {
878 &transaction.transparent_coins
879 }
880}
881
882#[derive(Debug, Clone)]
884pub struct WalletNote<N, Nf: Copy> {
885 pub(crate) output_id: OutputId,
887 pub(crate) key_id: KeyId,
889 pub(crate) note: N,
891 pub(crate) nullifier: Option<Nf>, pub(crate) position: Option<Position>,
895 pub(crate) memo: Memo,
897 pub(crate) spending_transaction: Option<TxId>,
900 pub(crate) refetch_nullifier_ranges: Vec<Range<BlockHeight>>,
905}
906
907pub type SaplingNote = WalletNote<sapling_crypto::Note, sapling_crypto::Nullifier>;
909
910impl OutputInterface for SaplingNote {
911 type KeyId = KeyId;
912 type Input = sapling_crypto::Nullifier;
913
914 const POOL_TYPE: PoolType = PoolType::Shielded(ShieldedProtocol::Sapling);
915
916 fn output_id(&self) -> OutputId {
917 self.output_id
918 }
919
920 fn key_id(&self) -> KeyId {
921 self.key_id
922 }
923
924 fn spending_transaction(&self) -> Option<TxId> {
925 self.spending_transaction
926 }
927
928 fn set_spending_transaction(&mut self, spending_transaction: Option<TxId>) {
929 self.spending_transaction = spending_transaction;
930 }
931
932 fn value(&self) -> u64 {
933 self.note.value().inner()
934 }
935
936 fn spend_link(&self) -> Option<Self::Input> {
937 self.nullifier
938 }
939
940 fn transaction_inputs(transaction: &WalletTransaction) -> Vec<&Self::Input> {
941 transaction.sapling_nullifiers()
942 }
943
944 fn transaction_outputs(transaction: &WalletTransaction) -> &[Self] {
945 &transaction.sapling_notes
946 }
947}
948
949impl NoteInterface for SaplingNote {
950 type ZcashNote = sapling_crypto::Note;
951 type Nullifier = Self::Input;
952
953 const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Sapling;
954
955 fn note(&self) -> &Self::ZcashNote {
956 &self.note
957 }
958
959 fn nullifier(&self) -> Option<Self::Nullifier> {
960 self.nullifier
961 }
962
963 fn position(&self) -> Option<Position> {
964 self.position
965 }
966
967 fn memo(&self) -> &Memo {
968 &self.memo
969 }
970
971 fn refetch_nullifier_ranges(&self) -> &[Range<BlockHeight>] {
972 &self.refetch_nullifier_ranges
973 }
974}
975
976pub type OrchardNote = WalletNote<orchard::Note, orchard::note::Nullifier>;
978
979impl OutputInterface for OrchardNote {
980 type KeyId = KeyId;
981 type Input = orchard::note::Nullifier;
982
983 const POOL_TYPE: PoolType = PoolType::Shielded(ShieldedProtocol::Orchard);
984
985 fn output_id(&self) -> OutputId {
986 self.output_id
987 }
988
989 fn key_id(&self) -> KeyId {
990 self.key_id
991 }
992
993 fn spending_transaction(&self) -> Option<TxId> {
994 self.spending_transaction
995 }
996
997 fn set_spending_transaction(&mut self, spending_transaction: Option<TxId>) {
998 self.spending_transaction = spending_transaction;
999 }
1000
1001 fn value(&self) -> u64 {
1002 self.note.value().inner()
1003 }
1004
1005 fn spend_link(&self) -> Option<Self::Input> {
1006 self.nullifier
1007 }
1008
1009 fn transaction_inputs(transaction: &WalletTransaction) -> Vec<&Self::Input> {
1010 transaction.orchard_nullifiers()
1011 }
1012
1013 fn transaction_outputs(transaction: &WalletTransaction) -> &[Self] {
1014 &transaction.orchard_notes
1015 }
1016}
1017
1018impl NoteInterface for OrchardNote {
1019 type ZcashNote = orchard::Note;
1020 type Nullifier = Self::Input;
1021
1022 const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Orchard;
1023
1024 fn note(&self) -> &Self::ZcashNote {
1025 &self.note
1026 }
1027
1028 fn nullifier(&self) -> Option<Self::Nullifier> {
1029 self.spend_link()
1030 }
1031
1032 fn position(&self) -> Option<Position> {
1033 self.position
1034 }
1035
1036 fn memo(&self) -> &Memo {
1037 &self.memo
1038 }
1039
1040 fn refetch_nullifier_ranges(&self) -> &[Range<BlockHeight>] {
1041 &self.refetch_nullifier_ranges
1042 }
1043}
1044
1045pub trait OutgoingNoteInterface: Sized {
1047 type ZcashNote;
1049 type Address: Clone + Copy + Debug + PartialEq + Eq;
1051 type Error: Debug + std::error::Error;
1053
1054 const SHIELDED_PROTOCOL: ShieldedProtocol;
1056
1057 fn output_id(&self) -> OutputId;
1059
1060 fn key_id(&self) -> KeyId;
1062
1063 fn value(&self) -> u64;
1065
1066 fn note(&self) -> &Self::ZcashNote;
1068
1069 fn memo(&self) -> &Memo;
1071
1072 fn recipient(&self) -> Self::Address;
1074
1075 fn recipient_full_unified_address(&self) -> Option<&UnifiedAddress>;
1077
1078 fn encoded_recipient<P>(&self, parameters: &P) -> Result<String, Self::Error>
1080 where
1081 P: consensus::Parameters + consensus::NetworkConstants;
1082
1083 fn encoded_recipient_full_unified_address<P>(&self, consensus_parameters: &P) -> Option<String>
1085 where
1086 P: consensus::Parameters + consensus::NetworkConstants;
1087
1088 fn transaction_outgoing_notes(transaction: &WalletTransaction) -> &[Self];
1090}
1091
1092#[derive(Debug, Clone, PartialEq)]
1094pub struct OutgoingNote<N> {
1095 pub(crate) output_id: OutputId,
1097 pub(crate) key_id: KeyId,
1099 pub(crate) note: N,
1101 pub(crate) memo: Memo,
1103 pub(crate) recipient_full_unified_address: Option<UnifiedAddress>,
1105}
1106
1107pub type OutgoingSaplingNote = OutgoingNote<sapling_crypto::Note>;
1109
1110impl OutgoingNoteInterface for OutgoingSaplingNote {
1111 type ZcashNote = sapling_crypto::Note;
1112 type Address = sapling_crypto::PaymentAddress;
1113 type Error = Infallible;
1114
1115 const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Sapling;
1116
1117 fn output_id(&self) -> OutputId {
1118 self.output_id
1119 }
1120
1121 fn key_id(&self) -> KeyId {
1122 self.key_id
1123 }
1124
1125 fn value(&self) -> u64 {
1126 self.note.value().inner()
1127 }
1128
1129 fn note(&self) -> &Self::ZcashNote {
1130 &self.note
1131 }
1132
1133 fn memo(&self) -> &Memo {
1134 &self.memo
1135 }
1136
1137 fn recipient(&self) -> Self::Address {
1138 self.note.recipient()
1139 }
1140
1141 fn recipient_full_unified_address(&self) -> Option<&UnifiedAddress> {
1142 self.recipient_full_unified_address.as_ref()
1143 }
1144
1145 fn encoded_recipient<P>(&self, consensus_parameters: &P) -> Result<String, Self::Error>
1146 where
1147 P: consensus::Parameters + consensus::NetworkConstants,
1148 {
1149 Ok(encode_payment_address(
1150 consensus_parameters.hrp_sapling_payment_address(),
1151 &self.note().recipient(),
1152 ))
1153 }
1154
1155 fn encoded_recipient_full_unified_address<P>(&self, consensus_parameters: &P) -> Option<String>
1156 where
1157 P: consensus::Parameters + consensus::NetworkConstants,
1158 {
1159 self.recipient_full_unified_address
1160 .as_ref()
1161 .map(|unified_address| unified_address.encode(consensus_parameters))
1162 }
1163
1164 fn transaction_outgoing_notes(transaction: &WalletTransaction) -> &[Self] {
1165 &transaction.outgoing_sapling_notes
1166 }
1167}
1168
1169pub type OutgoingOrchardNote = OutgoingNote<orchard::Note>;
1171
1172impl OutgoingNoteInterface for OutgoingOrchardNote {
1173 type ZcashNote = orchard::Note;
1174 type Address = orchard::Address;
1175 type Error = ParseError;
1176
1177 const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Orchard;
1178
1179 fn output_id(&self) -> OutputId {
1180 self.output_id
1181 }
1182
1183 fn key_id(&self) -> KeyId {
1184 self.key_id
1185 }
1186
1187 fn value(&self) -> u64 {
1188 self.note.value().inner()
1189 }
1190
1191 fn note(&self) -> &Self::ZcashNote {
1192 &self.note
1193 }
1194
1195 fn memo(&self) -> &Memo {
1196 &self.memo
1197 }
1198
1199 fn recipient(&self) -> Self::Address {
1200 self.note.recipient()
1201 }
1202
1203 fn recipient_full_unified_address(&self) -> Option<&UnifiedAddress> {
1204 self.recipient_full_unified_address.as_ref()
1205 }
1206
1207 fn encoded_recipient<P>(&self, parameters: &P) -> Result<String, Self::Error>
1208 where
1209 P: consensus::Parameters + consensus::NetworkConstants,
1210 {
1211 keys::encode_orchard_receiver(parameters, &self.note().recipient())
1212 }
1213
1214 fn encoded_recipient_full_unified_address<P>(&self, consensus_parameters: &P) -> Option<String>
1215 where
1216 P: consensus::Parameters + consensus::NetworkConstants,
1217 {
1218 self.recipient_full_unified_address
1219 .as_ref()
1220 .map(|unified_address| unified_address.encode(consensus_parameters))
1221 }
1222
1223 fn transaction_outgoing_notes(transaction: &WalletTransaction) -> &[Self] {
1224 &transaction.outgoing_orchard_notes
1225 }
1226}
1227
1228pub type SaplingShardStore = MemoryShardStore<sapling_crypto::Node, BlockHeight>;
1232
1233pub type OrchardShardStore = MemoryShardStore<MerkleHashOrchard, BlockHeight>;
1235
1236#[derive(Debug)]
1238pub struct ShardTrees {
1239 pub sapling: ShardTree<
1241 SaplingShardStore,
1242 { sapling_crypto::NOTE_COMMITMENT_TREE_DEPTH },
1243 { witness::SHARD_HEIGHT },
1244 >,
1245 pub orchard: ShardTree<
1247 OrchardShardStore,
1248 { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 },
1249 { witness::SHARD_HEIGHT },
1250 >,
1251}
1252
1253impl ShardTrees {
1254 #[must_use]
1256 pub fn new() -> Self {
1257 let mut sapling = ShardTree::new(MemoryShardStore::empty(), MAX_REORG_ALLOWANCE as usize);
1258 let mut orchard = ShardTree::new(MemoryShardStore::empty(), MAX_REORG_ALLOWANCE as usize);
1259
1260 sapling
1261 .checkpoint(BlockHeight::from_u32(0))
1262 .expect("should never fail");
1263 orchard
1264 .checkpoint(BlockHeight::from_u32(0))
1265 .expect("should never fail");
1266
1267 Self { sapling, orchard }
1268 }
1269}
1270
1271impl Default for ShardTrees {
1272 fn default() -> Self {
1273 Self::new()
1274 }
1275}