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::{
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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
62pub struct ScanTarget {
63 pub block_height: BlockHeight,
65 pub txid: TxId,
67 pub narrow_scan_area: bool,
69}
70
71#[derive(Debug, Clone)]
75pub struct InitialSyncState {
76 pub(crate) sync_start_height: BlockHeight,
81 pub(crate) wallet_tree_bounds: TreeBounds,
83 pub(crate) previously_scanned_blocks: u32,
85 pub(crate) previously_scanned_sapling_outputs: u32,
87 pub(crate) previously_scanned_orchard_outputs: u32,
89}
90
91impl InitialSyncState {
92 #[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#[derive(Debug, Clone)]
118pub struct SyncState {
119 pub(crate) scan_ranges: Vec<ScanRange>,
122 pub(crate) sapling_shard_ranges: Vec<Range<BlockHeight>>,
127 pub(crate) orchard_shard_ranges: Vec<Range<BlockHeight>>,
132 pub(crate) scan_targets: BTreeSet<ScanTarget>,
134 pub(crate) initial_sync_state: InitialSyncState,
136}
137
138impl SyncState {
139 #[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 #[must_use]
153 pub fn scan_ranges(&self) -> &[ScanRange] {
154 &self.scan_ranges
155 }
156
157 #[must_use]
159 pub fn sapling_shard_ranges(&self) -> &[Range<BlockHeight>] {
160 &self.sapling_shard_ranges
161 }
162
163 #[must_use]
165 pub fn orchard_shard_ranges(&self) -> &[Range<BlockHeight>] {
166 &self.orchard_shard_ranges
167 }
168
169 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 #[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 #[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 #[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 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
240pub enum SyncMode {
241 NotRunning,
243 Paused,
245 Running,
247 Shutdown,
249}
250
251impl SyncMode {
252 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 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#[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#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
289pub struct OutputId {
290 txid: TxId,
292 output_index: u16,
294}
295
296impl OutputId {
297 #[must_use]
299 pub fn new(txid: TxId, output_index: u16) -> Self {
300 OutputId { txid, output_index }
301 }
302
303 #[must_use]
305 pub fn txid(&self) -> TxId {
306 self.txid
307 }
308
309 #[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#[derive(Debug)]
343pub struct NullifierMap {
344 pub sapling: BTreeMap<sapling_crypto::Nullifier, ScanTarget>,
346 pub orchard: BTreeMap<orchard::note::Nullifier, ScanTarget>,
348}
349
350impl NullifierMap {
351 #[must_use]
353 pub fn new() -> Self {
354 Self {
355 sapling: BTreeMap::new(),
356 orchard: BTreeMap::new(),
357 }
358 }
359
360 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#[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 #[must_use]
409 pub fn block_height(&self) -> BlockHeight {
410 self.block_height
411 }
412
413 #[must_use]
415 pub fn block_hash(&self) -> BlockHash {
416 self.block_hash
417 }
418
419 #[must_use]
421 pub fn prev_hash(&self) -> BlockHash {
422 self.prev_hash
423 }
424
425 #[must_use]
427 pub fn time(&self) -> u32 {
428 self.time
429 }
430
431 #[must_use]
433 pub fn txids(&self) -> &[TxId] {
434 &self.txids
435 }
436
437 #[must_use]
439 pub fn tree_bounds(&self) -> TreeBounds {
440 self.tree_bounds
441 }
442}
443
444pub 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 #[must_use]
460 pub fn txid(&self) -> TxId {
461 self.txid
462 }
463
464 #[must_use]
466 pub fn status(&self) -> ConfirmationStatus {
467 self.status
468 }
469
470 #[must_use]
472 pub fn transaction(&self) -> &zcash_primitives::transaction::Transaction {
473 &self.transaction
474 }
475
476 #[must_use]
478 pub fn datetime(&self) -> u32 {
479 self.datetime
480 }
481
482 #[must_use]
484 pub fn transparent_coins(&self) -> &[TransparentCoin] {
485 &self.transparent_coins
486 }
487
488 pub fn transparent_coins_mut(&mut self) -> Vec<&mut TransparentCoin> {
490 self.transparent_coins.iter_mut().collect()
491 }
492
493 #[must_use]
495 pub fn sapling_notes(&self) -> &[SaplingNote] {
496 &self.sapling_notes
497 }
498
499 pub fn sapling_notes_mut(&mut self) -> Vec<&mut SaplingNote> {
501 self.sapling_notes.iter_mut().collect()
502 }
503
504 #[must_use]
506 pub fn orchard_notes(&self) -> &[OrchardNote] {
507 &self.orchard_notes
508 }
509
510 pub fn orchard_notes_mut(&mut self) -> Vec<&mut OrchardNote> {
512 self.orchard_notes.iter_mut().collect()
513 }
514
515 #[must_use]
517 pub fn outgoing_sapling_notes(&self) -> &[OutgoingSaplingNote] {
518 &self.outgoing_sapling_notes
519 }
520
521 #[must_use]
523 pub fn outgoing_orchard_notes(&self) -> &[OutgoingOrchardNote] {
524 &self.outgoing_orchard_notes
525 }
526
527 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 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 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 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 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 #[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 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 #[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 #[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 #[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
721pub trait KeyIdInterface {
723 fn account_id(&self) -> zip32::AccountId;
725}
726
727pub trait OutputInterface: Sized {
729 type KeyId: KeyIdInterface;
731 type Input: Clone + Debug + PartialEq + Eq + PartialOrd + Ord;
733
734 const POOL_TYPE: PoolType;
736
737 fn output_id(&self) -> OutputId;
739
740 fn key_id(&self) -> Self::KeyId;
742
743 fn spending_transaction(&self) -> Option<TxId>;
746
747 fn set_spending_transaction(&mut self, spending_transaction: Option<TxId>);
749
750 fn value(&self) -> u64;
753
754 fn spend_link(&self) -> Option<Self::Input>;
760
761 fn transaction_inputs(transaction: &WalletTransaction) -> Vec<&Self::Input>;
766
767 fn transaction_outputs(transaction: &WalletTransaction) -> &[Self];
769}
770
771pub trait NoteInterface: OutputInterface {
773 type ZcashNote;
775 type Nullifier: Copy;
777
778 const SHIELDED_PROTOCOL: ShieldedProtocol;
780
781 fn note(&self) -> &Self::ZcashNote;
783
784 fn nullifier(&self) -> Option<Self::Nullifier>;
786
787 fn position(&self) -> Option<Position>;
789
790 fn memo(&self) -> &Memo;
792
793 fn refetch_nullifier_ranges(&self) -> &[Range<BlockHeight>];
798}
799
800#[derive(Debug, Clone)]
802pub struct TransparentCoin {
803 pub(crate) output_id: OutputId,
805 pub(crate) key_id: TransparentAddressId,
807 pub(crate) address: String,
809 pub(crate) script: Script,
811 pub(crate) value: Zatoshis,
813 pub(crate) spending_transaction: Option<TxId>,
816}
817
818impl TransparentCoin {
819 #[must_use]
821 pub fn address(&self) -> &str {
822 &self.address
823 }
824
825 #[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#[derive(Debug, Clone)]
873pub struct WalletNote<N, Nf: Copy> {
874 pub(crate) output_id: OutputId,
876 pub(crate) key_id: KeyId,
878 pub(crate) note: N,
880 pub(crate) nullifier: Option<Nf>, pub(crate) position: Option<Position>,
884 pub(crate) memo: Memo,
886 pub(crate) spending_transaction: Option<TxId>,
889 pub(crate) refetch_nullifier_ranges: Vec<Range<BlockHeight>>,
894}
895
896pub 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
965pub 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
1034pub trait OutgoingNoteInterface: Sized {
1036 type ZcashNote;
1038 type Address: Clone + Copy + Debug + PartialEq + Eq;
1040 type Error: Debug + std::error::Error;
1042
1043 const SHIELDED_PROTOCOL: ShieldedProtocol;
1045
1046 fn output_id(&self) -> OutputId;
1048
1049 fn key_id(&self) -> KeyId;
1051
1052 fn value(&self) -> u64;
1054
1055 fn note(&self) -> &Self::ZcashNote;
1057
1058 fn memo(&self) -> &Memo;
1060
1061 fn recipient(&self) -> Self::Address;
1063
1064 fn recipient_full_unified_address(&self) -> Option<&UnifiedAddress>;
1066
1067 fn encoded_recipient<P>(&self, parameters: &P) -> Result<String, Self::Error>
1069 where
1070 P: consensus::Parameters + consensus::NetworkConstants;
1071
1072 fn encoded_recipient_full_unified_address<P>(&self, consensus_parameters: &P) -> Option<String>
1074 where
1075 P: consensus::Parameters + consensus::NetworkConstants;
1076
1077 fn transaction_outgoing_notes(transaction: &WalletTransaction) -> &[Self];
1079}
1080
1081#[derive(Debug, Clone, PartialEq)]
1083pub struct OutgoingNote<N> {
1084 pub(crate) output_id: OutputId,
1086 pub(crate) key_id: KeyId,
1088 pub(crate) note: N,
1090 pub(crate) memo: Memo,
1092 pub(crate) recipient_full_unified_address: Option<UnifiedAddress>,
1094}
1095
1096pub 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
1158pub 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
1217pub type SaplingShardStore = MemoryShardStore<sapling_crypto::Node, BlockHeight>;
1221
1222pub type OrchardShardStore = MemoryShardStore<MerkleHashOrchard, BlockHeight>;
1224
1225#[derive(Debug)]
1227pub struct ShardTrees {
1228 pub sapling: ShardTree<
1230 SaplingShardStore,
1231 { sapling_crypto::NOTE_COMMITMENT_TREE_DEPTH },
1232 { witness::SHARD_HEIGHT },
1233 >,
1234 pub orchard: ShardTree<
1236 OrchardShardStore,
1237 { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 },
1238 { witness::SHARD_HEIGHT },
1239 >,
1240}
1241
1242impl ShardTrees {
1243 #[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}