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_keys::{address::UnifiedAddress, encoding::encode_payment_address};
23use zcash_primitives::{block::BlockHash, transaction::TxId};
24use zcash_protocol::{
25 PoolType, ShieldedProtocol,
26 consensus::{self, BlockHeight},
27 memo::Memo,
28 value::Zatoshis,
29};
30use zcash_transparent::address::Script;
31use zcash_transparent::bundle::OutPoint;
32
33use zingo_netutils::lightwallet_protocol::CompactBlock;
34use zingo_status::confirmation_status::ConfirmationStatus;
35
36use crate::{
37 client::FetchRequest,
38 error::{ServerError, SyncModeError},
39 keys::{self, KeyId, transparent::TransparentAddressId},
40 scan::compact_blocks::calculate_block_tree_bounds,
41 sync::{MAX_REORG_ALLOWANCE, ScanPriority, ScanRange},
42 utils::{
43 get_compact_block_hash, get_compact_block_height, get_compact_block_prev_hash,
44 get_compact_tx_txid,
45 },
46 witness,
47};
48
49pub mod traits;
50
51#[cfg(feature = "wallet_essentials")]
52pub mod serialization;
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
64pub struct ScanTarget {
65 pub block_height: BlockHeight,
67 pub txid: TxId,
69 pub narrow_scan_area: bool,
71}
72
73#[derive(Debug, Clone)]
77pub struct InitialSyncState {
78 pub(crate) sync_start_height: BlockHeight,
83 pub(crate) wallet_tree_bounds: TreeBounds,
85 pub(crate) previously_scanned_blocks: u32,
87 pub(crate) previously_scanned_sapling_outputs: u32,
89 pub(crate) previously_scanned_orchard_outputs: u32,
91}
92
93impl InitialSyncState {
94 #[must_use]
96 pub fn new() -> Self {
97 InitialSyncState {
98 sync_start_height: 0.into(),
99 wallet_tree_bounds: TreeBounds {
100 sapling_initial_tree_size: 0,
101 sapling_final_tree_size: 0,
102 orchard_initial_tree_size: 0,
103 orchard_final_tree_size: 0,
104 },
105 previously_scanned_blocks: 0,
106 previously_scanned_sapling_outputs: 0,
107 previously_scanned_orchard_outputs: 0,
108 }
109 }
110}
111
112impl Default for InitialSyncState {
113 fn default() -> Self {
114 Self::new()
115 }
116}
117
118#[derive(Debug, Clone)]
120pub struct SyncState {
121 pub(crate) scan_ranges: Vec<ScanRange>,
124 pub(crate) sapling_shard_ranges: Vec<Range<BlockHeight>>,
129 pub(crate) orchard_shard_ranges: Vec<Range<BlockHeight>>,
134 pub(crate) scan_targets: BTreeSet<ScanTarget>,
136 pub(crate) initial_sync_state: InitialSyncState,
138}
139
140impl SyncState {
141 #[must_use]
143 pub fn new() -> Self {
144 SyncState {
145 scan_ranges: Vec::new(),
146 sapling_shard_ranges: Vec::new(),
147 orchard_shard_ranges: Vec::new(),
148 scan_targets: BTreeSet::new(),
149 initial_sync_state: InitialSyncState::new(),
150 }
151 }
152
153 #[must_use]
155 pub fn scan_ranges(&self) -> &[ScanRange] {
156 &self.scan_ranges
157 }
158
159 #[must_use]
161 pub fn sapling_shard_ranges(&self) -> &[Range<BlockHeight>] {
162 &self.sapling_shard_ranges
163 }
164
165 #[must_use]
167 pub fn orchard_shard_ranges(&self) -> &[Range<BlockHeight>] {
168 &self.orchard_shard_ranges
169 }
170
171 pub(crate) fn scan_complete(&self) -> bool {
173 self.scan_ranges
174 .iter()
175 .all(|scan_range| scan_range.priority() == ScanPriority::Scanned)
176 }
177
178 #[must_use]
181 pub fn fully_scanned_height(&self) -> Option<BlockHeight> {
182 if let Some(scan_range) = self
183 .scan_ranges
184 .iter()
185 .find(|scan_range| scan_range.priority() != ScanPriority::Scanned)
186 {
187 Some(scan_range.block_range().start - 1)
188 } else {
189 self.scan_ranges
190 .last()
191 .map(|range| range.block_range().end - 1)
192 }
193 }
194
195 #[must_use]
199 pub fn highest_scanned_height(&self) -> Option<BlockHeight> {
200 if let Some(last_scanned_range) = self
201 .scan_ranges
202 .iter()
203 .filter(|scan_range| {
204 scan_range.priority() == ScanPriority::Scanned
205 || scan_range.priority() == ScanPriority::ScannedWithoutMapping
206 || scan_range.priority() == ScanPriority::RefetchingNullifiers
207 })
208 .next_back()
209 {
210 Some(last_scanned_range.block_range().end - 1)
211 } else {
212 self.wallet_birthday().map(|start| start - 1)
213 }
214 }
215
216 #[must_use]
219 pub fn wallet_birthday(&self) -> Option<BlockHeight> {
220 self.scan_ranges
221 .first()
222 .map(|range| range.block_range().start)
223 }
224
225 #[must_use]
227 pub fn last_known_chain_height(&self) -> Option<BlockHeight> {
228 self.scan_ranges
229 .last()
230 .map(|range| range.block_range().end - 1)
231 }
232}
233
234impl Default for SyncState {
235 fn default() -> Self {
236 Self::new()
237 }
238}
239
240#[derive(Debug, Clone, Copy, PartialEq, Eq)]
242pub enum SyncMode {
243 NotRunning,
245 Paused,
247 Running,
249 Shutdown,
251}
252
253impl SyncMode {
254 pub fn from_u8(mode: u8) -> Result<Self, SyncModeError> {
258 match mode {
259 0 => Ok(Self::NotRunning),
260 1 => Ok(Self::Paused),
261 2 => Ok(Self::Running),
262 3 => Ok(Self::Shutdown),
263 _ => Err(SyncModeError::InvalidSyncMode(mode)),
264 }
265 }
266
267 pub fn from_atomic_u8(atomic_sync_mode: Arc<AtomicU8>) -> Result<SyncMode, SyncModeError> {
275 SyncMode::from_u8(atomic_sync_mode.load(atomic::Ordering::Acquire))
276 }
277}
278
279#[derive(Debug, Clone, Copy)]
281#[allow(missing_docs)]
282pub struct TreeBounds {
283 pub sapling_initial_tree_size: u32,
284 pub sapling_final_tree_size: u32,
285 pub orchard_initial_tree_size: u32,
286 pub orchard_final_tree_size: u32,
287}
288
289#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
291pub struct OutputId {
292 txid: TxId,
294 output_index: u32,
296}
297
298impl OutputId {
299 #[must_use]
301 pub fn new(txid: TxId, output_index: u32) -> Self {
302 OutputId { txid, output_index }
303 }
304
305 #[must_use]
307 pub fn txid(&self) -> TxId {
308 self.txid
309 }
310
311 #[must_use]
313 pub fn output_index(&self) -> u32 {
314 self.output_index
315 }
316}
317
318impl std::fmt::Display for OutputId {
319 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
320 write!(
321 f,
322 "{{
323 txid: {}
324 output index: {}
325 }}",
326 self.txid, self.output_index
327 )
328 }
329}
330
331impl From<&OutPoint> for OutputId {
332 fn from(value: &OutPoint) -> Self {
333 OutputId::new(*value.txid(), value.n())
334 }
335}
336
337impl From<OutputId> for OutPoint {
338 fn from(value: OutputId) -> Self {
339 OutPoint::new(value.txid.into(), value.output_index)
340 }
341}
342
343#[derive(Debug)]
345pub struct NullifierMap {
346 pub sapling: BTreeMap<sapling_crypto::Nullifier, ScanTarget>,
348 pub orchard: BTreeMap<orchard::note::Nullifier, ScanTarget>,
350}
351
352impl NullifierMap {
353 #[must_use]
355 pub fn new() -> Self {
356 Self {
357 sapling: BTreeMap::new(),
358 orchard: BTreeMap::new(),
359 }
360 }
361
362 pub fn clear(&mut self) {
364 self.sapling.clear();
365 self.orchard.clear();
366 }
367}
368
369impl Default for NullifierMap {
370 fn default() -> Self {
371 Self::new()
372 }
373}
374
375#[derive(Debug, Clone)]
377pub struct WalletBlock {
378 pub(crate) block_height: BlockHeight,
379 pub(crate) block_hash: BlockHash,
380 pub(crate) prev_hash: BlockHash,
381 pub(crate) time: u32,
382 pub(crate) txids: Vec<TxId>,
383 pub(crate) tree_bounds: TreeBounds,
384}
385
386impl WalletBlock {
387 pub(crate) async fn from_compact_block(
388 consensus_parameters: &impl consensus::Parameters,
389 fetch_request_sender: mpsc::UnboundedSender<FetchRequest>,
390 block: &CompactBlock,
391 ) -> Result<Self, ServerError> {
392 let tree_bounds =
393 calculate_block_tree_bounds(consensus_parameters, fetch_request_sender, block).await?;
394
395 Ok(Self {
396 block_height: get_compact_block_height(block),
397 block_hash: get_compact_block_hash(block),
398 prev_hash: get_compact_block_prev_hash(block),
399 time: block.time,
400 txids: block.vtx.iter().map(get_compact_tx_txid).collect(),
401 tree_bounds,
402 })
403 }
404
405 #[must_use]
407 pub fn block_height(&self) -> BlockHeight {
408 self.block_height
409 }
410
411 #[must_use]
413 pub fn block_hash(&self) -> BlockHash {
414 self.block_hash
415 }
416
417 #[must_use]
419 pub fn prev_hash(&self) -> BlockHash {
420 self.prev_hash
421 }
422
423 #[must_use]
425 pub fn time(&self) -> u32 {
426 self.time
427 }
428
429 #[must_use]
431 pub fn txids(&self) -> &[TxId] {
432 &self.txids
433 }
434
435 #[must_use]
437 pub fn tree_bounds(&self) -> TreeBounds {
438 self.tree_bounds
439 }
440}
441
442pub struct WalletTransaction {
444 pub(crate) txid: TxId,
445 pub(crate) status: ConfirmationStatus,
446 pub(crate) transaction: zcash_primitives::transaction::Transaction,
447 pub(crate) datetime: u32,
448 pub(crate) transparent_coins: Vec<TransparentCoin>,
449 pub(crate) sapling_notes: Vec<SaplingNote>,
450 pub(crate) orchard_notes: Vec<OrchardNote>,
451 pub(crate) outgoing_sapling_notes: Vec<OutgoingSaplingNote>,
452 pub(crate) outgoing_orchard_notes: Vec<OutgoingOrchardNote>,
453}
454
455impl WalletTransaction {
456 #[must_use]
458 pub fn txid(&self) -> TxId {
459 self.txid
460 }
461
462 #[must_use]
464 pub fn status(&self) -> ConfirmationStatus {
465 self.status
466 }
467
468 #[must_use]
470 pub fn transaction(&self) -> &zcash_primitives::transaction::Transaction {
471 &self.transaction
472 }
473
474 #[must_use]
476 pub fn datetime(&self) -> u32 {
477 self.datetime
478 }
479
480 #[must_use]
482 pub fn transparent_coins(&self) -> &[TransparentCoin] {
483 &self.transparent_coins
484 }
485
486 pub fn transparent_coins_mut(&mut self) -> Vec<&mut TransparentCoin> {
488 self.transparent_coins.iter_mut().collect()
489 }
490
491 #[must_use]
493 pub fn sapling_notes(&self) -> &[SaplingNote] {
494 &self.sapling_notes
495 }
496
497 pub fn sapling_notes_mut(&mut self) -> Vec<&mut SaplingNote> {
499 self.sapling_notes.iter_mut().collect()
500 }
501
502 #[must_use]
504 pub fn orchard_notes(&self) -> &[OrchardNote] {
505 &self.orchard_notes
506 }
507
508 pub fn orchard_notes_mut(&mut self) -> Vec<&mut OrchardNote> {
510 self.orchard_notes.iter_mut().collect()
511 }
512
513 #[must_use]
515 pub fn outgoing_sapling_notes(&self) -> &[OutgoingSaplingNote] {
516 &self.outgoing_sapling_notes
517 }
518
519 #[must_use]
521 pub fn outgoing_orchard_notes(&self) -> &[OutgoingOrchardNote] {
522 &self.outgoing_orchard_notes
523 }
524
525 pub fn sapling_nullifiers(&self) -> Vec<&sapling_crypto::Nullifier> {
528 self.transaction
529 .sapling_bundle()
530 .map_or_else(Vec::new, |bundle| {
531 bundle
532 .shielded_spends()
533 .iter()
534 .map(|spend| spend.nullifier())
535 .collect::<Vec<_>>()
536 })
537 }
538
539 pub fn orchard_nullifiers(&self) -> Vec<&orchard::note::Nullifier> {
542 self.transaction
543 .orchard_bundle()
544 .map_or_else(Vec::new, |bundle| {
545 bundle
546 .actions()
547 .iter()
548 .map(orchard::Action::nullifier)
549 .collect::<Vec<_>>()
550 })
551 }
552
553 pub fn outpoints(&self) -> Vec<&OutPoint> {
556 self.transaction
557 .transparent_bundle()
558 .map_or_else(Vec::new, |bundle| {
559 bundle
560 .vin
561 .iter()
562 .map(zcash_transparent::bundle::TxIn::prevout)
563 .collect::<Vec<_>>()
564 })
565 }
566
567 pub fn update_status(&mut self, status: ConfirmationStatus, datetime: u32) {
573 match status {
574 ConfirmationStatus::Transmitted(_)
575 if matches!(self.status(), ConfirmationStatus::Calculated(_)) =>
576 {
577 self.status = status;
578 self.datetime = datetime;
579 }
580 ConfirmationStatus::Mempool(_)
581 if matches!(
582 self.status(),
583 ConfirmationStatus::Calculated(_) | ConfirmationStatus::Transmitted(_)
584 ) =>
585 {
586 self.status = status;
587 self.datetime = datetime;
588 }
589 ConfirmationStatus::Confirmed(_)
590 if matches!(
591 self.status(),
592 ConfirmationStatus::Calculated(_)
593 | ConfirmationStatus::Transmitted(_)
594 | ConfirmationStatus::Mempool(_)
595 ) =>
596 {
597 self.status = status;
598 self.datetime = datetime;
599 }
600
601 ConfirmationStatus::Failed(_)
602 if !matches!(self.status(), ConfirmationStatus::Failed(_)) =>
603 {
604 self.status = status;
605 self.datetime = datetime;
606 }
607 _ => (),
608 }
609 }
610}
611
612#[cfg(feature = "test-features")]
613impl WalletTransaction {
614 pub fn new_for_test(txid: TxId, status: ConfirmationStatus) -> Self {
618 use zcash_primitives::transaction::{TransactionData, TxVersion};
619 use zcash_protocol::consensus::BranchId;
620
621 let transaction = TransactionData::from_parts(
622 TxVersion::V5,
623 BranchId::Nu5,
624 0,
625 BlockHeight::from_u32(0),
626 None,
627 None,
628 None,
629 None,
630 )
631 .freeze()
632 .expect("empty v5 transaction should always be valid");
633
634 Self {
635 txid,
636 status,
637 transaction,
638 datetime: 0,
639 transparent_coins: Vec::new(),
640 sapling_notes: Vec::new(),
641 orchard_notes: Vec::new(),
642 outgoing_sapling_notes: Vec::new(),
643 outgoing_orchard_notes: Vec::new(),
644 }
645 }
646}
647
648#[cfg(feature = "wallet_essentials")]
649impl WalletTransaction {
650 #[must_use]
652 pub fn total_value_sent(&self) -> u64 {
653 let transparent_value_sent = self
654 .transaction
655 .transparent_bundle()
656 .map_or(0, |bundle| {
657 bundle
658 .vout
659 .iter()
660 .map(|output| output.value().into_u64())
661 .sum()
662 })
663 .saturating_sub(self.total_output_value::<TransparentCoin>());
664
665 let sapling_value_sent =
666 self.total_external_outgoing_note_value::<OutgoingSaplingNote, SaplingNote>();
667 let orchard_value_sent =
668 self.total_external_outgoing_note_value::<OutgoingOrchardNote, OrchardNote>();
669
670 transparent_value_sent + sapling_value_sent + orchard_value_sent
671 }
672
673 #[must_use]
675 pub fn total_value_received(&self) -> u64 {
676 self.total_output_value::<TransparentCoin>()
677 + self.total_output_value::<SaplingNote>()
678 + self.total_output_value::<OrchardNote>()
679 }
680
681 #[must_use]
683 pub fn total_output_value<Op: OutputInterface>(&self) -> u64 {
684 Op::transaction_outputs(self)
685 .iter()
686 .map(OutputInterface::value)
687 .sum()
688 }
689
690 #[must_use]
692 pub fn total_outgoing_note_value<Op: OutgoingNoteInterface>(&self) -> u64 {
693 Op::transaction_outgoing_notes(self)
694 .iter()
695 .map(OutgoingNoteInterface::value)
696 .sum()
697 }
698
699 #[must_use]
701 pub fn total_external_outgoing_note_value<Outgoing, Incoming>(&self) -> u64
702 where
703 Outgoing: OutgoingNoteInterface,
704 Incoming: OutputInterface,
705 {
706 Outgoing::transaction_outgoing_notes(self)
707 .iter()
708 .filter(|outgoing_note| {
709 !Incoming::transaction_outputs(self)
710 .iter()
711 .any(|wallet_note| wallet_note.output_id() == outgoing_note.output_id())
712 })
713 .map(OutgoingNoteInterface::value)
714 .sum()
715 }
716}
717
718impl std::fmt::Debug for WalletTransaction {
719 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
720 f.debug_struct("WalletTransaction")
721 .field("txid", &self.txid)
722 .field("confirmation_status", &self.status)
723 .field("datetime", &self.datetime)
724 .field("transparent_coins", &self.transparent_coins)
725 .field("sapling_notes", &self.sapling_notes)
726 .field("orchard_notes", &self.orchard_notes)
727 .field("outgoing_sapling_notes", &self.outgoing_sapling_notes)
728 .field("outgoing_orchard_notes", &self.outgoing_orchard_notes)
729 .finish()
730 }
731}
732
733pub trait KeyIdInterface {
735 fn account_id(&self) -> zip32::AccountId;
737}
738
739pub trait OutputInterface: Sized {
741 type KeyId: KeyIdInterface;
743 type Input: Clone + Debug + PartialEq + Eq + PartialOrd + Ord;
745
746 const POOL_TYPE: PoolType;
748
749 fn output_id(&self) -> OutputId;
751
752 fn key_id(&self) -> Self::KeyId;
754
755 fn spending_transaction(&self) -> Option<TxId>;
758
759 fn set_spending_transaction(&mut self, spending_transaction: Option<TxId>);
761
762 fn value(&self) -> u64;
765
766 fn spend_link(&self) -> Option<Self::Input>;
772
773 fn transaction_inputs(transaction: &WalletTransaction) -> Vec<&Self::Input>;
778
779 fn transaction_outputs(transaction: &WalletTransaction) -> &[Self];
781}
782
783pub trait NoteInterface: OutputInterface {
785 type ZcashNote;
787 type Nullifier: Copy + Clone + PartialEq + Eq + PartialOrd + Ord;
789
790 const SHIELDED_PROTOCOL: ShieldedProtocol;
792
793 fn note(&self) -> &Self::ZcashNote;
795
796 fn nullifier(&self) -> Option<Self::Nullifier>;
798
799 fn position(&self) -> Option<Position>;
801
802 fn memo(&self) -> &Memo;
804
805 fn refetch_nullifier_ranges(&self) -> &[Range<BlockHeight>];
810}
811
812#[derive(Debug, Clone)]
814pub struct TransparentCoin {
815 pub(crate) output_id: OutputId,
817 pub(crate) key_id: TransparentAddressId,
819 pub(crate) address: String,
821 pub(crate) script: Script,
823 pub(crate) value: Zatoshis,
825 pub(crate) spending_transaction: Option<TxId>,
828}
829
830impl TransparentCoin {
831 #[must_use]
833 pub fn address(&self) -> &str {
834 &self.address
835 }
836
837 #[must_use]
839 pub fn script(&self) -> &Script {
840 &self.script
841 }
842}
843
844impl OutputInterface for TransparentCoin {
845 type KeyId = TransparentAddressId;
846 type Input = OutPoint;
847
848 const POOL_TYPE: PoolType = PoolType::Transparent;
849
850 fn output_id(&self) -> OutputId {
851 self.output_id
852 }
853
854 fn key_id(&self) -> Self::KeyId {
855 self.key_id
856 }
857
858 fn spending_transaction(&self) -> Option<TxId> {
859 self.spending_transaction
860 }
861
862 fn set_spending_transaction(&mut self, spending_transaction: Option<TxId>) {
863 self.spending_transaction = spending_transaction;
864 }
865
866 fn value(&self) -> u64 {
867 self.value.into_u64()
868 }
869
870 fn spend_link(&self) -> Option<Self::Input> {
871 Some(self.output_id.into())
872 }
873
874 fn transaction_inputs(transaction: &WalletTransaction) -> Vec<&Self::Input> {
875 transaction.outpoints()
876 }
877
878 fn transaction_outputs(transaction: &WalletTransaction) -> &[Self] {
879 &transaction.transparent_coins
880 }
881}
882
883#[derive(Debug, Clone)]
885pub struct WalletNote<N, Nf: Copy> {
886 pub(crate) output_id: OutputId,
888 pub(crate) key_id: KeyId,
890 pub(crate) note: N,
892 pub(crate) nullifier: Option<Nf>, pub(crate) position: Option<Position>,
896 pub(crate) memo: Memo,
898 pub(crate) spending_transaction: Option<TxId>,
901 pub(crate) refetch_nullifier_ranges: Vec<Range<BlockHeight>>,
906}
907
908pub type SaplingNote = WalletNote<sapling_crypto::Note, sapling_crypto::Nullifier>;
910
911impl OutputInterface for SaplingNote {
912 type KeyId = KeyId;
913 type Input = sapling_crypto::Nullifier;
914
915 const POOL_TYPE: PoolType = PoolType::Shielded(ShieldedProtocol::Sapling);
916
917 fn output_id(&self) -> OutputId {
918 self.output_id
919 }
920
921 fn key_id(&self) -> KeyId {
922 self.key_id
923 }
924
925 fn spending_transaction(&self) -> Option<TxId> {
926 self.spending_transaction
927 }
928
929 fn set_spending_transaction(&mut self, spending_transaction: Option<TxId>) {
930 self.spending_transaction = spending_transaction;
931 }
932
933 fn value(&self) -> u64 {
934 self.note.value().inner()
935 }
936
937 fn spend_link(&self) -> Option<Self::Input> {
938 self.nullifier
939 }
940
941 fn transaction_inputs(transaction: &WalletTransaction) -> Vec<&Self::Input> {
942 transaction.sapling_nullifiers()
943 }
944
945 fn transaction_outputs(transaction: &WalletTransaction) -> &[Self] {
946 &transaction.sapling_notes
947 }
948}
949
950impl NoteInterface for SaplingNote {
951 type ZcashNote = sapling_crypto::Note;
952 type Nullifier = Self::Input;
953
954 const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Sapling;
955
956 fn note(&self) -> &Self::ZcashNote {
957 &self.note
958 }
959
960 fn nullifier(&self) -> Option<Self::Nullifier> {
961 self.nullifier
962 }
963
964 fn position(&self) -> Option<Position> {
965 self.position
966 }
967
968 fn memo(&self) -> &Memo {
969 &self.memo
970 }
971
972 fn refetch_nullifier_ranges(&self) -> &[Range<BlockHeight>] {
973 &self.refetch_nullifier_ranges
974 }
975}
976
977pub type OrchardNote = WalletNote<orchard::Note, orchard::note::Nullifier>;
979
980impl OutputInterface for OrchardNote {
981 type KeyId = KeyId;
982 type Input = orchard::note::Nullifier;
983
984 const POOL_TYPE: PoolType = PoolType::Shielded(ShieldedProtocol::Orchard);
985
986 fn output_id(&self) -> OutputId {
987 self.output_id
988 }
989
990 fn key_id(&self) -> KeyId {
991 self.key_id
992 }
993
994 fn spending_transaction(&self) -> Option<TxId> {
995 self.spending_transaction
996 }
997
998 fn set_spending_transaction(&mut self, spending_transaction: Option<TxId>) {
999 self.spending_transaction = spending_transaction;
1000 }
1001
1002 fn value(&self) -> u64 {
1003 self.note.value().inner()
1004 }
1005
1006 fn spend_link(&self) -> Option<Self::Input> {
1007 self.nullifier
1008 }
1009
1010 fn transaction_inputs(transaction: &WalletTransaction) -> Vec<&Self::Input> {
1011 transaction.orchard_nullifiers()
1012 }
1013
1014 fn transaction_outputs(transaction: &WalletTransaction) -> &[Self] {
1015 &transaction.orchard_notes
1016 }
1017}
1018
1019impl NoteInterface for OrchardNote {
1020 type ZcashNote = orchard::Note;
1021 type Nullifier = Self::Input;
1022
1023 const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Orchard;
1024
1025 fn note(&self) -> &Self::ZcashNote {
1026 &self.note
1027 }
1028
1029 fn nullifier(&self) -> Option<Self::Nullifier> {
1030 self.spend_link()
1031 }
1032
1033 fn position(&self) -> Option<Position> {
1034 self.position
1035 }
1036
1037 fn memo(&self) -> &Memo {
1038 &self.memo
1039 }
1040
1041 fn refetch_nullifier_ranges(&self) -> &[Range<BlockHeight>] {
1042 &self.refetch_nullifier_ranges
1043 }
1044}
1045
1046pub trait OutgoingNoteInterface: Sized {
1048 type ZcashNote;
1050 type Address: Clone + Copy + Debug + PartialEq + Eq;
1052 type Error: Debug + std::error::Error;
1054
1055 const SHIELDED_PROTOCOL: ShieldedProtocol;
1057
1058 fn output_id(&self) -> OutputId;
1060
1061 fn key_id(&self) -> KeyId;
1063
1064 fn value(&self) -> u64;
1066
1067 fn note(&self) -> &Self::ZcashNote;
1069
1070 fn memo(&self) -> &Memo;
1072
1073 fn recipient(&self) -> Self::Address;
1075
1076 fn recipient_full_unified_address(&self) -> Option<&UnifiedAddress>;
1078
1079 fn encoded_recipient<P>(&self, parameters: &P) -> Result<String, Self::Error>
1081 where
1082 P: consensus::Parameters + consensus::NetworkConstants;
1083
1084 fn encoded_recipient_full_unified_address<P>(&self, consensus_parameters: &P) -> Option<String>
1086 where
1087 P: consensus::Parameters + consensus::NetworkConstants;
1088
1089 fn transaction_outgoing_notes(transaction: &WalletTransaction) -> &[Self];
1091}
1092
1093#[derive(Debug, Clone, PartialEq)]
1095pub struct OutgoingNote<N> {
1096 pub(crate) output_id: OutputId,
1098 pub(crate) key_id: KeyId,
1100 pub(crate) note: N,
1102 pub(crate) memo: Memo,
1104 pub(crate) recipient_full_unified_address: Option<UnifiedAddress>,
1106}
1107
1108pub type OutgoingSaplingNote = OutgoingNote<sapling_crypto::Note>;
1110
1111impl OutgoingNoteInterface for OutgoingSaplingNote {
1112 type ZcashNote = sapling_crypto::Note;
1113 type Address = sapling_crypto::PaymentAddress;
1114 type Error = Infallible;
1115
1116 const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Sapling;
1117
1118 fn output_id(&self) -> OutputId {
1119 self.output_id
1120 }
1121
1122 fn key_id(&self) -> KeyId {
1123 self.key_id
1124 }
1125
1126 fn value(&self) -> u64 {
1127 self.note.value().inner()
1128 }
1129
1130 fn note(&self) -> &Self::ZcashNote {
1131 &self.note
1132 }
1133
1134 fn memo(&self) -> &Memo {
1135 &self.memo
1136 }
1137
1138 fn recipient(&self) -> Self::Address {
1139 self.note.recipient()
1140 }
1141
1142 fn recipient_full_unified_address(&self) -> Option<&UnifiedAddress> {
1143 self.recipient_full_unified_address.as_ref()
1144 }
1145
1146 fn encoded_recipient<P>(&self, consensus_parameters: &P) -> Result<String, Self::Error>
1147 where
1148 P: consensus::Parameters + consensus::NetworkConstants,
1149 {
1150 Ok(encode_payment_address(
1151 consensus_parameters.hrp_sapling_payment_address(),
1152 &self.note().recipient(),
1153 ))
1154 }
1155
1156 fn encoded_recipient_full_unified_address<P>(&self, consensus_parameters: &P) -> Option<String>
1157 where
1158 P: consensus::Parameters + consensus::NetworkConstants,
1159 {
1160 self.recipient_full_unified_address
1161 .as_ref()
1162 .map(|unified_address| unified_address.encode(consensus_parameters))
1163 }
1164
1165 fn transaction_outgoing_notes(transaction: &WalletTransaction) -> &[Self] {
1166 &transaction.outgoing_sapling_notes
1167 }
1168}
1169
1170pub type OutgoingOrchardNote = OutgoingNote<orchard::Note>;
1172
1173impl OutgoingNoteInterface for OutgoingOrchardNote {
1174 type ZcashNote = orchard::Note;
1175 type Address = orchard::Address;
1176 type Error = ParseError;
1177
1178 const SHIELDED_PROTOCOL: ShieldedProtocol = ShieldedProtocol::Orchard;
1179
1180 fn output_id(&self) -> OutputId {
1181 self.output_id
1182 }
1183
1184 fn key_id(&self) -> KeyId {
1185 self.key_id
1186 }
1187
1188 fn value(&self) -> u64 {
1189 self.note.value().inner()
1190 }
1191
1192 fn note(&self) -> &Self::ZcashNote {
1193 &self.note
1194 }
1195
1196 fn memo(&self) -> &Memo {
1197 &self.memo
1198 }
1199
1200 fn recipient(&self) -> Self::Address {
1201 self.note.recipient()
1202 }
1203
1204 fn recipient_full_unified_address(&self) -> Option<&UnifiedAddress> {
1205 self.recipient_full_unified_address.as_ref()
1206 }
1207
1208 fn encoded_recipient<P>(&self, parameters: &P) -> Result<String, Self::Error>
1209 where
1210 P: consensus::Parameters + consensus::NetworkConstants,
1211 {
1212 keys::encode_orchard_receiver(parameters, &self.note().recipient())
1213 }
1214
1215 fn encoded_recipient_full_unified_address<P>(&self, consensus_parameters: &P) -> Option<String>
1216 where
1217 P: consensus::Parameters + consensus::NetworkConstants,
1218 {
1219 self.recipient_full_unified_address
1220 .as_ref()
1221 .map(|unified_address| unified_address.encode(consensus_parameters))
1222 }
1223
1224 fn transaction_outgoing_notes(transaction: &WalletTransaction) -> &[Self] {
1225 &transaction.outgoing_orchard_notes
1226 }
1227}
1228
1229pub type SaplingShardStore = MemoryShardStore<sapling_crypto::Node, BlockHeight>;
1233
1234pub type OrchardShardStore = MemoryShardStore<MerkleHashOrchard, BlockHeight>;
1236
1237#[derive(Debug)]
1239pub struct ShardTrees {
1240 pub sapling: ShardTree<
1242 SaplingShardStore,
1243 { sapling_crypto::NOTE_COMMITMENT_TREE_DEPTH },
1244 { witness::SHARD_HEIGHT },
1245 >,
1246 pub orchard: ShardTree<
1248 OrchardShardStore,
1249 { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 },
1250 { witness::SHARD_HEIGHT },
1251 >,
1252}
1253
1254impl ShardTrees {
1255 #[must_use]
1257 pub fn new() -> Self {
1258 let mut sapling = ShardTree::new(MemoryShardStore::empty(), MAX_REORG_ALLOWANCE as usize);
1259 let mut orchard = ShardTree::new(MemoryShardStore::empty(), MAX_REORG_ALLOWANCE as usize);
1260
1261 sapling
1262 .checkpoint(BlockHeight::from_u32(0))
1263 .expect("should never fail");
1264 orchard
1265 .checkpoint(BlockHeight::from_u32(0))
1266 .expect("should never fail");
1267
1268 Self { sapling, orchard }
1269 }
1270}
1271
1272impl Default for ShardTrees {
1273 fn default() -> Self {
1274 Self::new()
1275 }
1276}