1use std::{
4 collections::{BTreeMap, HashMap},
5 convert::Infallible,
6 fmt,
7 hash::Hash,
8 num::NonZeroU32,
9};
10
11use assert_matches::assert_matches;
12use group::ff::Field;
13use incrementalmerkletree::{Marking, Retention};
14use nonempty::NonEmpty;
15use rand::{CryptoRng, Rng, RngCore, SeedableRng};
16use rand_chacha::ChaChaRng;
17use secrecy::{ExposeSecret, Secret, SecretVec};
18use shardtree::{error::ShardTreeError, store::memory::MemoryShardStore, ShardTree};
19use subtle::ConditionallySelectable;
20
21use ::sapling::{
22 note_encryption::{sapling_note_encryption, SaplingDomain},
23 util::generate_random_rseed,
24 zip32::DiversifiableFullViewingKey,
25};
26use zcash_address::ZcashAddress;
27use zcash_keys::{
28 address::{Address, UnifiedAddress},
29 keys::{UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey},
30};
31use zcash_note_encryption::Domain;
32use zcash_primitives::{
33 block::BlockHash,
34 transaction::{components::sapling::zip212_enforcement, fees::FeeRule, Transaction, TxId},
35};
36use zcash_proofs::prover::LocalTxProver;
37use zcash_protocol::{
38 consensus::{self, BlockHeight, Network, NetworkUpgrade, Parameters as _},
39 local_consensus::LocalNetwork,
40 memo::{Memo, MemoBytes},
41 value::{ZatBalance, Zatoshis},
42 ShieldedProtocol,
43};
44use zip32::{fingerprint::SeedFingerprint, DiversifierIndex};
45use zip321::Payment;
46
47use super::{
48 chain::{scan_cached_blocks, BlockSource, ChainState, CommitmentTreeRoot, ScanSummary},
49 error::Error,
50 scanning::ScanRange,
51 wallet::{
52 create_proposed_transactions,
53 input_selection::{GreedyInputSelector, InputSelector},
54 propose_standard_transfer_to_address, propose_transfer,
55 },
56 Account, AccountBalance, AccountBirthday, AccountMeta, AccountPurpose, AccountSource,
57 AddressInfo, BlockMetadata, DecryptedTransaction, InputSource, NoteFilter, NullifierQuery,
58 ScannedBlock, SeedRelevance, SentTransaction, SpendableNotes, TransactionDataRequest,
59 TransactionStatus, WalletCommitmentTrees, WalletRead, WalletSummary, WalletTest, WalletWrite,
60 SAPLING_SHARD_HEIGHT,
61};
62use crate::{
63 fees::{
64 standard::{self, SingleOutputChangeStrategy},
65 ChangeStrategy, DustOutputPolicy, StandardFeeRule,
66 },
67 proposal::Proposal,
68 proto::compact_formats::{
69 self, CompactBlock, CompactSaplingOutput, CompactSaplingSpend, CompactTx,
70 },
71 wallet::{Note, NoteId, OvkPolicy, ReceivedNote, WalletTransparentOutput},
72};
73
74#[cfg(feature = "transparent-inputs")]
75use {
76 super::wallet::input_selection::ShieldingSelector,
77 crate::wallet::TransparentAddressMetadata,
78 ::transparent::{address::TransparentAddress, keys::NonHardenedChildIndex},
79 std::ops::Range,
80 transparent::GapLimits,
81};
82
83#[cfg(feature = "orchard")]
84use {
85 super::ORCHARD_SHARD_HEIGHT, crate::proto::compact_formats::CompactOrchardAction,
86 ::orchard::tree::MerkleHashOrchard, group::ff::PrimeField, pasta_curves::pallas,
87};
88
89pub mod pool;
90pub mod sapling;
91
92#[cfg(feature = "orchard")]
93pub mod orchard;
94#[cfg(feature = "transparent-inputs")]
95pub mod transparent;
96
97pub struct TransactionSummary<AccountId> {
99 account_id: AccountId,
100 txid: TxId,
101 expiry_height: Option<BlockHeight>,
102 mined_height: Option<BlockHeight>,
103 account_value_delta: ZatBalance,
104 total_spent: Zatoshis,
105 total_received: Zatoshis,
106 fee_paid: Option<Zatoshis>,
107 spent_note_count: usize,
108 has_change: bool,
109 sent_note_count: usize,
110 received_note_count: usize,
111 memo_count: usize,
112 expired_unmined: bool,
113 is_shielding: bool,
114}
115
116impl<AccountId> TransactionSummary<AccountId> {
117 #[allow(clippy::too_many_arguments)]
122 pub fn from_parts(
123 account_id: AccountId,
124 txid: TxId,
125 expiry_height: Option<BlockHeight>,
126 mined_height: Option<BlockHeight>,
127 account_value_delta: ZatBalance,
128 total_spent: Zatoshis,
129 total_received: Zatoshis,
130 fee_paid: Option<Zatoshis>,
131 spent_note_count: usize,
132 has_change: bool,
133 sent_note_count: usize,
134 received_note_count: usize,
135 memo_count: usize,
136 expired_unmined: bool,
137 is_shielding: bool,
138 ) -> Self {
139 Self {
140 account_id,
141 txid,
142 expiry_height,
143 mined_height,
144 account_value_delta,
145 total_spent,
146 total_received,
147 fee_paid,
148 spent_note_count,
149 has_change,
150 sent_note_count,
151 received_note_count,
152 memo_count,
153 expired_unmined,
154 is_shielding,
155 }
156 }
157
158 pub fn account_id(&self) -> &AccountId {
161 &self.account_id
162 }
163
164 pub fn txid(&self) -> TxId {
166 self.txid
167 }
168
169 pub fn expiry_height(&self) -> Option<BlockHeight> {
174 self.expiry_height
175 }
176
177 pub fn mined_height(&self) -> Option<BlockHeight> {
180 self.mined_height
181 }
182
183 pub fn account_value_delta(&self) -> ZatBalance {
188 self.account_value_delta
189 }
190
191 pub fn total_spent(&self) -> Zatoshis {
193 self.total_spent
194 }
195
196 pub fn total_received(&self) -> Zatoshis {
198 self.total_received
199 }
200
201 pub fn fee_paid(&self) -> Option<Zatoshis> {
203 self.fee_paid
204 }
205
206 pub fn spent_note_count(&self) -> usize {
208 self.spent_note_count
209 }
210
211 pub fn has_change(&self) -> bool {
215 self.has_change
216 }
217
218 pub fn sent_note_count(&self) -> usize {
221 self.sent_note_count
222 }
223
224 pub fn received_note_count(&self) -> usize {
227 self.received_note_count
228 }
229
230 pub fn expired_unmined(&self) -> bool {
233 self.expired_unmined
234 }
235
236 pub fn memo_count(&self) -> usize {
238 self.memo_count
239 }
240
241 pub fn is_shielding(&self) -> bool {
254 self.is_shielding
255 }
256}
257
258#[derive(Clone, Debug)]
260pub struct CachedBlock {
261 chain_state: ChainState,
262 sapling_end_size: u32,
263 orchard_end_size: u32,
264}
265
266impl CachedBlock {
267 pub fn none(block_height: BlockHeight) -> Self {
273 Self {
274 chain_state: ChainState::empty(block_height, BlockHash([0; 32])),
275 sapling_end_size: 0,
276 orchard_end_size: 0,
277 }
278 }
279
280 pub fn at(chain_state: ChainState, sapling_end_size: u32, orchard_end_size: u32) -> Self {
282 assert_eq!(
283 chain_state.final_sapling_tree().tree_size() as u32,
284 sapling_end_size
285 );
286 #[cfg(feature = "orchard")]
287 assert_eq!(
288 chain_state.final_orchard_tree().tree_size() as u32,
289 orchard_end_size
290 );
291
292 Self {
293 chain_state,
294 sapling_end_size,
295 orchard_end_size,
296 }
297 }
298
299 fn roll_forward(&self, cb: &CompactBlock) -> Self {
300 assert_eq!(self.chain_state.block_height() + 1, cb.height());
301
302 let sapling_final_tree = cb.vtx.iter().flat_map(|tx| tx.outputs.iter()).fold(
303 self.chain_state.final_sapling_tree().clone(),
304 |mut acc, c_out| {
305 acc.append(::sapling::Node::from_cmu(&c_out.cmu().unwrap()));
306 acc
307 },
308 );
309 let sapling_end_size = sapling_final_tree.tree_size() as u32;
310
311 #[cfg(feature = "orchard")]
312 let orchard_final_tree = cb.vtx.iter().flat_map(|tx| tx.actions.iter()).fold(
313 self.chain_state.final_orchard_tree().clone(),
314 |mut acc, c_act| {
315 acc.append(MerkleHashOrchard::from_cmx(&c_act.cmx().unwrap()));
316 acc
317 },
318 );
319 #[cfg(feature = "orchard")]
320 let orchard_end_size = orchard_final_tree.tree_size() as u32;
321 #[cfg(not(feature = "orchard"))]
322 let orchard_end_size = cb.vtx.iter().fold(self.orchard_end_size, |sz, tx| {
323 sz + (tx.actions.len() as u32)
324 });
325
326 Self {
327 chain_state: ChainState::new(
328 cb.height(),
329 cb.hash(),
330 sapling_final_tree,
331 #[cfg(feature = "orchard")]
332 orchard_final_tree,
333 ),
334 sapling_end_size,
335 orchard_end_size,
336 }
337 }
338
339 pub fn height(&self) -> BlockHeight {
341 self.chain_state.block_height()
342 }
343
344 pub fn sapling_end_size(&self) -> u32 {
346 self.sapling_end_size
347 }
348
349 pub fn orchard_end_size(&self) -> u32 {
351 self.orchard_end_size
352 }
353}
354
355#[derive(Clone)]
361pub struct TestAccount<A> {
362 account: A,
363 usk: UnifiedSpendingKey,
364 birthday: AccountBirthday,
365}
366
367impl<A> TestAccount<A> {
368 pub fn account(&self) -> &A {
370 &self.account
371 }
372
373 pub fn usk(&self) -> &UnifiedSpendingKey {
375 &self.usk
376 }
377
378 pub fn birthday(&self) -> &AccountBirthday {
380 &self.birthday
381 }
382}
383
384impl<A: Account> Account for TestAccount<A> {
385 type AccountId = A::AccountId;
386
387 fn id(&self) -> Self::AccountId {
388 self.account.id()
389 }
390
391 fn name(&self) -> Option<&str> {
392 self.account.name()
393 }
394
395 fn source(&self) -> &AccountSource {
396 self.account.source()
397 }
398
399 fn ufvk(&self) -> Option<&zcash_keys::keys::UnifiedFullViewingKey> {
400 self.account.ufvk()
401 }
402
403 fn uivk(&self) -> zcash_keys::keys::UnifiedIncomingViewingKey {
404 self.account.uivk()
405 }
406}
407
408pub trait Reset: WalletTest + Sized {
411 type Handle;
413
414 fn reset<C>(st: &mut TestState<C, Self, LocalNetwork>) -> Self::Handle;
421}
422
423pub struct TestState<Cache, DataStore: WalletTest, Network> {
425 cache: Cache,
426 cached_blocks: BTreeMap<BlockHeight, CachedBlock>,
427 latest_block_height: Option<BlockHeight>,
428 wallet_data: DataStore,
429 network: Network,
430 test_account: Option<(SecretVec<u8>, TestAccount<DataStore::Account>)>,
431 rng: ChaChaRng,
432}
433
434impl<Cache, DataStore: WalletTest, Network> TestState<Cache, DataStore, Network> {
435 pub fn wallet(&self) -> &DataStore {
437 &self.wallet_data
438 }
439
440 pub fn wallet_mut(&mut self) -> &mut DataStore {
442 &mut self.wallet_data
443 }
444
445 pub fn rng_mut(&mut self) -> &mut ChaChaRng {
447 &mut self.rng
448 }
449
450 pub fn network(&self) -> &Network {
452 &self.network
453 }
454}
455
456impl<Cache, DataStore: WalletTest, Network: consensus::Parameters>
457 TestState<Cache, DataStore, Network>
458{
459 pub fn sapling_activation_height(&self) -> BlockHeight {
461 self.network
462 .activation_height(NetworkUpgrade::Sapling)
463 .expect("Sapling activation height must be known.")
464 }
465
466 #[allow(dead_code)]
468 pub fn nu5_activation_height(&self) -> BlockHeight {
469 self.network
470 .activation_height(NetworkUpgrade::Nu5)
471 .expect("NU5 activation height must be known.")
472 }
473
474 pub fn test_seed(&self) -> Option<&SecretVec<u8>> {
476 self.test_account.as_ref().map(|(seed, _)| seed)
477 }
478
479 pub fn test_account(&self) -> Option<&TestAccount<<DataStore as WalletRead>::Account>> {
481 self.test_account.as_ref().map(|(_, acct)| acct)
482 }
483
484 pub fn test_account_sapling(&self) -> Option<&DiversifiableFullViewingKey> {
486 let (_, acct) = self.test_account.as_ref()?;
487 let ufvk = acct.ufvk()?;
488 ufvk.sapling()
489 }
490
491 #[cfg(feature = "orchard")]
493 pub fn test_account_orchard(&self) -> Option<&::orchard::keys::FullViewingKey> {
494 let (_, acct) = self.test_account.as_ref()?;
495 let ufvk = acct.ufvk()?;
496 ufvk.orchard()
497 }
498}
499
500impl<Cache: TestCache, DataStore, Network> TestState<Cache, DataStore, Network>
501where
502 Network: consensus::Parameters,
503 DataStore: WalletTest + WalletWrite,
504 <Cache::BlockSource as BlockSource>::Error: fmt::Debug,
505{
506 #[cfg(feature = "unstable")]
508 pub fn cache(&self) -> &Cache::BlockSource {
509 self.cache.block_source()
510 }
511
512 pub fn latest_cached_block(&self) -> Option<&CachedBlock> {
515 self.latest_block_height
516 .as_ref()
517 .and_then(|h| self.cached_blocks.get(h))
518 }
519
520 fn latest_cached_block_below_height(&self, height: BlockHeight) -> Option<&CachedBlock> {
521 self.cached_blocks.range(..height).last().map(|(_, b)| b)
522 }
523
524 fn cache_block(
525 &mut self,
526 prev_block: &CachedBlock,
527 compact_block: CompactBlock,
528 ) -> Cache::InsertResult {
529 self.cached_blocks.insert(
530 compact_block.height(),
531 prev_block.roll_forward(&compact_block),
532 );
533 self.cache.insert(&compact_block)
534 }
535
536 pub fn generate_next_block<Fvk: TestFvk>(
539 &mut self,
540 fvk: &Fvk,
541 address_type: AddressType,
542 value: Zatoshis,
543 ) -> (BlockHeight, Cache::InsertResult, Fvk::Nullifier) {
544 let pre_activation_block = CachedBlock::none(self.sapling_activation_height() - 1);
545 let prior_cached_block = self.latest_cached_block().unwrap_or(&pre_activation_block);
546 let height = prior_cached_block.height() + 1;
547
548 let (res, nfs) = self.generate_block_at(
549 height,
550 prior_cached_block.chain_state.block_hash(),
551 &[FakeCompactOutput::new(fvk, address_type, value)],
552 prior_cached_block.sapling_end_size,
553 prior_cached_block.orchard_end_size,
554 false,
555 );
556
557 (height, res, nfs[0])
558 }
559
560 #[allow(dead_code)]
563 pub fn generate_next_block_multi<Fvk: TestFvk>(
564 &mut self,
565 outputs: &[FakeCompactOutput<Fvk>],
566 ) -> (BlockHeight, Cache::InsertResult, Vec<Fvk::Nullifier>) {
567 let pre_activation_block = CachedBlock::none(self.sapling_activation_height() - 1);
568 let prior_cached_block = self.latest_cached_block().unwrap_or(&pre_activation_block);
569 let height = prior_cached_block.height() + 1;
570
571 let (res, nfs) = self.generate_block_at(
572 height,
573 prior_cached_block.chain_state.block_hash(),
574 outputs,
575 prior_cached_block.sapling_end_size,
576 prior_cached_block.orchard_end_size,
577 false,
578 );
579
580 (height, res, nfs)
581 }
582
583 #[allow(dead_code)] pub fn generate_empty_block(&mut self) -> (BlockHeight, Cache::InsertResult) {
586 let new_hash = {
587 let mut hash = vec![0; 32];
588 self.rng.fill_bytes(&mut hash);
589 hash
590 };
591
592 let pre_activation_block = CachedBlock::none(self.sapling_activation_height() - 1);
593 let prior_cached_block = self
594 .latest_cached_block()
595 .unwrap_or(&pre_activation_block)
596 .clone();
597 let new_height = prior_cached_block.height() + 1;
598
599 let mut cb = CompactBlock {
600 hash: new_hash,
601 height: new_height.into(),
602 ..Default::default()
603 };
604 cb.prev_hash
605 .extend_from_slice(&prior_cached_block.chain_state.block_hash().0);
606
607 cb.chain_metadata = Some(compact_formats::ChainMetadata {
608 sapling_commitment_tree_size: prior_cached_block.sapling_end_size,
609 orchard_commitment_tree_size: prior_cached_block.orchard_end_size,
610 });
611
612 let res = self.cache_block(&prior_cached_block, cb);
613 self.latest_block_height = Some(new_height);
614
615 (new_height, res)
616 }
617
618 #[allow(clippy::too_many_arguments)]
624 pub fn generate_block_at<Fvk: TestFvk>(
625 &mut self,
626 height: BlockHeight,
627 prev_hash: BlockHash,
628 outputs: &[FakeCompactOutput<Fvk>],
629 initial_sapling_tree_size: u32,
630 initial_orchard_tree_size: u32,
631 allow_broken_hash_chain: bool,
632 ) -> (Cache::InsertResult, Vec<Fvk::Nullifier>) {
633 let mut prior_cached_block = self
634 .latest_cached_block_below_height(height)
635 .cloned()
636 .unwrap_or_else(|| CachedBlock::none(self.sapling_activation_height() - 1));
637 assert!(prior_cached_block.chain_state.block_height() < height);
638 assert!(prior_cached_block.sapling_end_size <= initial_sapling_tree_size);
639 assert!(prior_cached_block.orchard_end_size <= initial_orchard_tree_size);
640
641 if prior_cached_block.chain_state.block_height() == height - 1 {
645 if !allow_broken_hash_chain {
646 assert_eq!(prev_hash, prior_cached_block.chain_state.block_hash());
647 }
648 } else {
649 let final_sapling_tree =
650 (prior_cached_block.sapling_end_size..initial_sapling_tree_size).fold(
651 prior_cached_block.chain_state.final_sapling_tree().clone(),
652 |mut acc, _| {
653 acc.append(::sapling::Node::from_scalar(bls12_381::Scalar::random(
654 &mut self.rng,
655 )));
656 acc
657 },
658 );
659
660 #[cfg(feature = "orchard")]
661 let final_orchard_tree =
662 (prior_cached_block.orchard_end_size..initial_orchard_tree_size).fold(
663 prior_cached_block.chain_state.final_orchard_tree().clone(),
664 |mut acc, _| {
665 acc.append(MerkleHashOrchard::random(&mut self.rng));
666 acc
667 },
668 );
669
670 prior_cached_block = CachedBlock::at(
671 ChainState::new(
672 height - 1,
673 prev_hash,
674 final_sapling_tree,
675 #[cfg(feature = "orchard")]
676 final_orchard_tree,
677 ),
678 initial_sapling_tree_size,
679 initial_orchard_tree_size,
680 );
681
682 self.cached_blocks
683 .insert(height - 1, prior_cached_block.clone());
684 }
685
686 let (cb, nfs) = fake_compact_block(
687 &self.network,
688 height,
689 prev_hash,
690 outputs,
691 initial_sapling_tree_size,
692 initial_orchard_tree_size,
693 &mut self.rng,
694 );
695 assert_eq!(cb.height(), height);
696
697 let res = self.cache_block(&prior_cached_block, cb);
698 self.latest_block_height = Some(height);
699
700 (res, nfs)
701 }
702
703 pub fn generate_next_block_spending<Fvk: TestFvk>(
706 &mut self,
707 fvk: &Fvk,
708 note: (Fvk::Nullifier, Zatoshis),
709 to: impl Into<Address>,
710 value: Zatoshis,
711 ) -> (BlockHeight, Cache::InsertResult) {
712 let prior_cached_block = self
713 .latest_cached_block()
714 .cloned()
715 .unwrap_or_else(|| CachedBlock::none(self.sapling_activation_height() - 1));
716 let height = prior_cached_block.height() + 1;
717
718 let cb = fake_compact_block_spending(
719 &self.network,
720 height,
721 prior_cached_block.chain_state.block_hash(),
722 note,
723 fvk,
724 to.into(),
725 value,
726 prior_cached_block.sapling_end_size,
727 prior_cached_block.orchard_end_size,
728 &mut self.rng,
729 );
730 assert_eq!(cb.height(), height);
731
732 let res = self.cache_block(&prior_cached_block, cb);
733 self.latest_block_height = Some(height);
734
735 (height, res)
736 }
737
738 pub fn generate_next_block_including(
744 &mut self,
745 txid: TxId,
746 ) -> (BlockHeight, Cache::InsertResult) {
747 let tx = self
748 .wallet()
749 .get_transaction(txid)
750 .unwrap()
751 .expect("TxId should exist in the wallet");
752
753 self.generate_next_block_from_tx(1, &tx)
757 }
758
759 pub fn generate_next_block_from_tx(
765 &mut self,
766 tx_index: usize,
767 tx: &Transaction,
768 ) -> (BlockHeight, Cache::InsertResult) {
769 let prior_cached_block = self
770 .latest_cached_block()
771 .cloned()
772 .unwrap_or_else(|| CachedBlock::none(self.sapling_activation_height() - 1));
773 let height = prior_cached_block.height() + 1;
774
775 let cb = fake_compact_block_from_tx(
776 height,
777 prior_cached_block.chain_state.block_hash(),
778 tx_index,
779 tx,
780 prior_cached_block.sapling_end_size,
781 prior_cached_block.orchard_end_size,
782 &mut self.rng,
783 );
784 assert_eq!(cb.height(), height);
785
786 let res = self.cache_block(&prior_cached_block, cb);
787 self.latest_block_height = Some(height);
788
789 (height, res)
790 }
791
792 pub fn truncate_to_height(&mut self, height: BlockHeight) {
796 self.wallet_mut().truncate_to_height(height).unwrap();
797 self.cache.truncate_to_height(height);
798 self.cached_blocks.split_off(&(height + 1));
799 self.latest_block_height = Some(height);
800 }
801
802 pub fn truncate_to_height_retaining_cache(&mut self, height: BlockHeight) {
806 self.wallet_mut().truncate_to_height(height).unwrap();
807 self.latest_block_height = Some(height);
808 }
809}
810
811impl<Cache, DbT, ParamsT> TestState<Cache, DbT, ParamsT>
812where
813 Cache: TestCache,
814 <Cache::BlockSource as BlockSource>::Error: fmt::Debug,
815 ParamsT: consensus::Parameters + Send + 'static,
816 DbT: InputSource + WalletTest + WalletWrite + WalletCommitmentTrees,
817 <DbT as WalletRead>::AccountId:
818 std::fmt::Debug + ConditionallySelectable + Default + Send + 'static,
819{
820 pub fn scan_cached_blocks(&mut self, from_height: BlockHeight, limit: usize) -> ScanSummary {
822 let result = self.try_scan_cached_blocks(from_height, limit);
823 assert_matches!(result, Ok(_));
824 result.unwrap()
825 }
826
827 pub fn try_scan_cached_blocks(
829 &mut self,
830 from_height: BlockHeight,
831 limit: usize,
832 ) -> Result<
833 ScanSummary,
834 super::chain::error::Error<
835 <DbT as WalletRead>::Error,
836 <Cache::BlockSource as BlockSource>::Error,
837 >,
838 > {
839 let prior_cached_block = self
840 .latest_cached_block_below_height(from_height)
841 .cloned()
842 .unwrap_or_else(|| CachedBlock::none(from_height - 1));
843
844 let result = scan_cached_blocks(
845 &self.network,
846 self.cache.block_source(),
847 &mut self.wallet_data,
848 from_height,
849 &prior_cached_block.chain_state,
850 limit,
851 );
852 result
853 }
854
855 pub fn put_subtree_roots(
857 &mut self,
858 sapling_start_index: u64,
859 sapling_roots: &[CommitmentTreeRoot<::sapling::Node>],
860 #[cfg(feature = "orchard")] orchard_start_index: u64,
861 #[cfg(feature = "orchard")] orchard_roots: &[CommitmentTreeRoot<MerkleHashOrchard>],
862 ) -> Result<(), ShardTreeError<<DbT as WalletCommitmentTrees>::Error>> {
863 self.wallet_mut()
864 .put_sapling_subtree_roots(sapling_start_index, sapling_roots)?;
865
866 #[cfg(feature = "orchard")]
867 self.wallet_mut()
868 .put_orchard_subtree_roots(orchard_start_index, orchard_roots)?;
869
870 Ok(())
871 }
872}
873
874impl<Cache, DbT, ParamsT, AccountIdT, ErrT> TestState<Cache, DbT, ParamsT>
875where
876 ParamsT: consensus::Parameters + Send + 'static,
877 AccountIdT: std::fmt::Debug + std::cmp::Eq + std::hash::Hash,
878 ErrT: std::fmt::Debug,
879 DbT: InputSource<AccountId = AccountIdT, Error = ErrT>
880 + WalletTest
881 + WalletWrite<AccountId = AccountIdT, Error = ErrT>
882 + WalletCommitmentTrees,
883 <DbT as WalletRead>::AccountId: ConditionallySelectable + Default + Send + 'static,
884{
885 pub fn create_standard_transaction(
889 &mut self,
890 from_account: &TestAccount<DbT::Account>,
891 to: ZcashAddress,
892 value: Zatoshis,
893 ) -> Result<
894 NonEmpty<TxId>,
895 super::wallet::TransferErrT<
896 DbT,
897 GreedyInputSelector<DbT>,
898 standard::MultiOutputChangeStrategy<DbT>,
899 >,
900 > {
901 let input_selector = GreedyInputSelector::new();
902
903 #[cfg(not(feature = "orchard"))]
904 let fallback_change_pool = ShieldedProtocol::Sapling;
905 #[cfg(feature = "orchard")]
906 let fallback_change_pool = ShieldedProtocol::Orchard;
907
908 let change_strategy = standard::SingleOutputChangeStrategy::new(
909 StandardFeeRule::Zip317,
910 None,
911 fallback_change_pool,
912 DustOutputPolicy::default(),
913 );
914
915 let request =
916 zip321::TransactionRequest::new(vec![Payment::without_memo(to, value)]).unwrap();
917
918 self.spend(
919 &input_selector,
920 &change_strategy,
921 from_account.usk(),
922 request,
923 OvkPolicy::Sender,
924 NonZeroU32::MIN,
925 )
926 }
927
928 #[allow(clippy::type_complexity)]
930 pub fn spend<InputsT, ChangeT>(
931 &mut self,
932 input_selector: &InputsT,
933 change_strategy: &ChangeT,
934 usk: &UnifiedSpendingKey,
935 request: zip321::TransactionRequest,
936 ovk_policy: OvkPolicy,
937 min_confirmations: NonZeroU32,
938 ) -> Result<NonEmpty<TxId>, super::wallet::TransferErrT<DbT, InputsT, ChangeT>>
939 where
940 InputsT: InputSelector<InputSource = DbT>,
941 ChangeT: ChangeStrategy<MetaSource = DbT>,
942 {
943 let prover = LocalTxProver::bundled();
944 let network = self.network().clone();
945
946 let account = self
947 .wallet()
948 .get_account_for_ufvk(&usk.to_unified_full_viewing_key())
949 .map_err(Error::DataSource)?
950 .ok_or(Error::KeyNotRecognized)?;
951
952 let proposal = propose_transfer(
953 self.wallet_mut(),
954 &network,
955 account.id(),
956 input_selector,
957 change_strategy,
958 request,
959 min_confirmations,
960 )?;
961
962 create_proposed_transactions(
963 self.wallet_mut(),
964 &network,
965 &prover,
966 &prover,
967 usk,
968 ovk_policy,
969 &proposal,
970 )
971 }
972
973 #[allow(clippy::type_complexity)]
975 pub fn propose_transfer<InputsT, ChangeT>(
976 &mut self,
977 spend_from_account: <DbT as InputSource>::AccountId,
978 input_selector: &InputsT,
979 change_strategy: &ChangeT,
980 request: zip321::TransactionRequest,
981 min_confirmations: NonZeroU32,
982 ) -> Result<
983 Proposal<ChangeT::FeeRule, <DbT as InputSource>::NoteRef>,
984 super::wallet::ProposeTransferErrT<DbT, Infallible, InputsT, ChangeT>,
985 >
986 where
987 InputsT: InputSelector<InputSource = DbT>,
988 ChangeT: ChangeStrategy<MetaSource = DbT>,
989 {
990 let network = self.network().clone();
991 propose_transfer::<_, _, _, _, Infallible>(
992 self.wallet_mut(),
993 &network,
994 spend_from_account,
995 input_selector,
996 change_strategy,
997 request,
998 min_confirmations,
999 )
1000 }
1001
1002 #[allow(clippy::type_complexity)]
1004 #[allow(clippy::too_many_arguments)]
1005 pub fn propose_standard_transfer<CommitmentTreeErrT>(
1006 &mut self,
1007 spend_from_account: <DbT as InputSource>::AccountId,
1008 fee_rule: StandardFeeRule,
1009 min_confirmations: NonZeroU32,
1010 to: &Address,
1011 amount: Zatoshis,
1012 memo: Option<MemoBytes>,
1013 change_memo: Option<MemoBytes>,
1014 fallback_change_pool: ShieldedProtocol,
1015 ) -> Result<
1016 Proposal<StandardFeeRule, <DbT as InputSource>::NoteRef>,
1017 super::wallet::ProposeTransferErrT<
1018 DbT,
1019 CommitmentTreeErrT,
1020 GreedyInputSelector<DbT>,
1021 SingleOutputChangeStrategy<DbT>,
1022 >,
1023 > {
1024 let network = self.network().clone();
1025 let result = propose_standard_transfer_to_address::<_, _, CommitmentTreeErrT>(
1026 self.wallet_mut(),
1027 &network,
1028 fee_rule,
1029 spend_from_account,
1030 min_confirmations,
1031 to,
1032 amount,
1033 memo,
1034 change_memo,
1035 fallback_change_pool,
1036 );
1037
1038 if let Ok(proposal) = &result {
1039 check_proposal_serialization_roundtrip(self.wallet(), proposal);
1040 }
1041
1042 result
1043 }
1044
1045 #[cfg(feature = "transparent-inputs")]
1049 #[allow(clippy::type_complexity)]
1050 #[allow(dead_code)]
1051 pub fn propose_shielding<InputsT, ChangeT>(
1052 &mut self,
1053 input_selector: &InputsT,
1054 change_strategy: &ChangeT,
1055 shielding_threshold: Zatoshis,
1056 from_addrs: &[TransparentAddress],
1057 to_account: <InputsT::InputSource as InputSource>::AccountId,
1058 min_confirmations: u32,
1059 ) -> Result<
1060 Proposal<ChangeT::FeeRule, Infallible>,
1061 super::wallet::ProposeShieldingErrT<DbT, Infallible, InputsT, ChangeT>,
1062 >
1063 where
1064 InputsT: ShieldingSelector<InputSource = DbT>,
1065 ChangeT: ChangeStrategy<MetaSource = DbT>,
1066 {
1067 use super::wallet::propose_shielding;
1068
1069 let network = self.network().clone();
1070 propose_shielding::<_, _, _, _, Infallible>(
1071 self.wallet_mut(),
1072 &network,
1073 input_selector,
1074 change_strategy,
1075 shielding_threshold,
1076 from_addrs,
1077 to_account,
1078 min_confirmations,
1079 )
1080 }
1081
1082 #[allow(clippy::type_complexity)]
1084 pub fn create_proposed_transactions<InputsErrT, FeeRuleT, ChangeErrT>(
1085 &mut self,
1086 usk: &UnifiedSpendingKey,
1087 ovk_policy: OvkPolicy,
1088 proposal: &Proposal<FeeRuleT, <DbT as InputSource>::NoteRef>,
1089 ) -> Result<
1090 NonEmpty<TxId>,
1091 super::wallet::CreateErrT<DbT, InputsErrT, FeeRuleT, ChangeErrT, DbT::NoteRef>,
1092 >
1093 where
1094 FeeRuleT: FeeRule,
1095 {
1096 let prover = LocalTxProver::bundled();
1097 let network = self.network().clone();
1098 create_proposed_transactions(
1099 self.wallet_mut(),
1100 &network,
1101 &prover,
1102 &prover,
1103 usk,
1104 ovk_policy,
1105 proposal,
1106 )
1107 }
1108
1109 #[cfg(feature = "pczt")]
1113 #[allow(clippy::type_complexity)]
1114 pub fn create_pczt_from_proposal<InputsErrT, FeeRuleT, ChangeErrT>(
1115 &mut self,
1116 spend_from_account: <DbT as InputSource>::AccountId,
1117 ovk_policy: OvkPolicy,
1118 proposal: &Proposal<FeeRuleT, <DbT as InputSource>::NoteRef>,
1119 ) -> Result<
1120 pczt::Pczt,
1121 super::wallet::CreateErrT<DbT, InputsErrT, FeeRuleT, ChangeErrT, DbT::NoteRef>,
1122 >
1123 where
1124 <DbT as WalletRead>::AccountId: serde::Serialize,
1125 FeeRuleT: FeeRule,
1126 {
1127 use super::wallet::create_pczt_from_proposal;
1128
1129 let network = self.network().clone();
1130
1131 create_pczt_from_proposal(
1132 self.wallet_mut(),
1133 &network,
1134 spend_from_account,
1135 ovk_policy,
1136 proposal,
1137 )
1138 }
1139
1140 #[cfg(feature = "pczt")]
1144 #[allow(clippy::type_complexity)]
1145 pub fn extract_and_store_transaction_from_pczt(
1146 &mut self,
1147 pczt: pczt::Pczt,
1148 ) -> Result<TxId, super::wallet::ExtractErrT<DbT, DbT::NoteRef>>
1149 where
1150 <DbT as WalletRead>::AccountId: serde::de::DeserializeOwned,
1151 {
1152 use super::wallet::extract_and_store_transaction_from_pczt;
1153
1154 let prover = LocalTxProver::bundled();
1155 let (spend_vk, output_vk) = prover.verifying_keys();
1156
1157 extract_and_store_transaction_from_pczt(
1158 self.wallet_mut(),
1159 pczt,
1160 Some((&spend_vk, &output_vk)),
1161 None,
1162 )
1163 }
1164
1165 #[cfg(feature = "transparent-inputs")]
1169 #[allow(clippy::type_complexity)]
1170 #[allow(clippy::too_many_arguments)]
1171 pub fn shield_transparent_funds<InputsT, ChangeT>(
1172 &mut self,
1173 input_selector: &InputsT,
1174 change_strategy: &ChangeT,
1175 shielding_threshold: Zatoshis,
1176 usk: &UnifiedSpendingKey,
1177 from_addrs: &[TransparentAddress],
1178 to_account: <DbT as InputSource>::AccountId,
1179 min_confirmations: u32,
1180 ) -> Result<NonEmpty<TxId>, super::wallet::ShieldErrT<DbT, InputsT, ChangeT>>
1181 where
1182 InputsT: ShieldingSelector<InputSource = DbT>,
1183 ChangeT: ChangeStrategy<MetaSource = DbT>,
1184 {
1185 use crate::data_api::wallet::shield_transparent_funds;
1186
1187 let prover = LocalTxProver::bundled();
1188 let network = self.network().clone();
1189 shield_transparent_funds(
1190 self.wallet_mut(),
1191 &network,
1192 &prover,
1193 &prover,
1194 input_selector,
1195 change_strategy,
1196 shielding_threshold,
1197 usk,
1198 from_addrs,
1199 to_account,
1200 min_confirmations,
1201 )
1202 }
1203
1204 fn with_account_balance<T, F: FnOnce(&AccountBalance) -> T>(
1205 &self,
1206 account: AccountIdT,
1207 min_confirmations: u32,
1208 f: F,
1209 ) -> T {
1210 let binding = self
1211 .wallet()
1212 .get_wallet_summary(min_confirmations)
1213 .unwrap()
1214 .unwrap();
1215 f(binding.account_balances().get(&account).unwrap())
1216 }
1217
1218 pub fn get_total_balance(&self, account: AccountIdT) -> Zatoshis {
1220 self.with_account_balance(account, 0, |balance| balance.total())
1221 }
1222
1223 pub fn get_spendable_balance(&self, account: AccountIdT, min_confirmations: u32) -> Zatoshis {
1226 self.with_account_balance(account, min_confirmations, |balance| {
1227 balance.spendable_value()
1228 })
1229 }
1230
1231 pub fn get_pending_shielded_balance(
1234 &self,
1235 account: AccountIdT,
1236 min_confirmations: u32,
1237 ) -> Zatoshis {
1238 self.with_account_balance(account, min_confirmations, |balance| {
1239 balance.value_pending_spendability() + balance.change_pending_confirmation()
1240 })
1241 .unwrap()
1242 }
1243
1244 #[allow(dead_code)]
1247 pub fn get_pending_change(&self, account: AccountIdT, min_confirmations: u32) -> Zatoshis {
1248 self.with_account_balance(account, min_confirmations, |balance| {
1249 balance.change_pending_confirmation()
1250 })
1251 }
1252
1253 pub fn get_wallet_summary(&self, min_confirmations: u32) -> Option<WalletSummary<AccountIdT>> {
1255 self.wallet().get_wallet_summary(min_confirmations).unwrap()
1256 }
1257}
1258
1259impl<Cache, DbT, ParamsT, AccountIdT, ErrT> TestState<Cache, DbT, ParamsT>
1260where
1261 ParamsT: consensus::Parameters + Send + 'static,
1262 AccountIdT: std::cmp::Eq + std::hash::Hash,
1263 ErrT: std::fmt::Debug,
1264 DbT: InputSource<AccountId = AccountIdT, Error = ErrT>
1265 + WalletTest
1266 + WalletWrite<AccountId = AccountIdT, Error = ErrT>
1267 + WalletCommitmentTrees,
1268 <DbT as WalletRead>::AccountId: ConditionallySelectable + Default + Send + 'static,
1269{
1270 #[allow(dead_code)]
1272 pub fn get_tx_from_history(
1273 &self,
1274 txid: TxId,
1275 ) -> Result<Option<TransactionSummary<AccountIdT>>, ErrT> {
1276 let history = self.wallet().get_tx_history()?;
1277 Ok(history.into_iter().find(|tx| tx.txid() == txid))
1278 }
1279}
1280
1281impl<Cache, DbT: WalletRead + Reset> TestState<Cache, DbT, LocalNetwork> {
1282 pub fn reset(&mut self) -> DbT::Handle {
1289 self.latest_block_height = None;
1290 self.test_account = None;
1291 DbT::reset(self)
1292 }
1293
1294 }
1312
1313pub fn single_output_change_strategy<DbT: InputSource>(
1314 fee_rule: StandardFeeRule,
1315 change_memo: Option<&str>,
1316 fallback_change_pool: ShieldedProtocol,
1317) -> standard::SingleOutputChangeStrategy<DbT> {
1318 let change_memo = change_memo.map(|m| MemoBytes::from(m.parse::<Memo>().unwrap()));
1319 standard::SingleOutputChangeStrategy::new(
1320 fee_rule,
1321 change_memo,
1322 fallback_change_pool,
1323 DustOutputPolicy::default(),
1324 )
1325}
1326
1327fn check_proposal_serialization_roundtrip<DbT: InputSource>(
1330 wallet_data: &DbT,
1331 proposal: &Proposal<StandardFeeRule, DbT::NoteRef>,
1332) {
1333 let proposal_proto = crate::proto::proposal::Proposal::from_standard_proposal(proposal);
1334 let deserialized_proposal = proposal_proto.try_into_standard_proposal(wallet_data);
1335 assert_matches!(deserialized_proposal, Ok(r) if &r == proposal);
1336}
1337
1338pub struct InitialChainState {
1344 pub chain_state: ChainState,
1346 pub prior_sapling_roots: Vec<CommitmentTreeRoot<::sapling::Node>>,
1348 #[cfg(feature = "orchard")]
1350 pub prior_orchard_roots: Vec<CommitmentTreeRoot<MerkleHashOrchard>>,
1351}
1352
1353pub trait DataStoreFactory {
1355 type Error: core::fmt::Debug;
1356 type AccountId: std::fmt::Debug + ConditionallySelectable + Default + Hash + Eq + Send + 'static;
1357 type Account: Account<AccountId = Self::AccountId> + Clone;
1358 type DsError: core::fmt::Debug;
1359 type DataStore: InputSource<AccountId = Self::AccountId, Error = Self::DsError>
1360 + WalletRead<AccountId = Self::AccountId, Account = Self::Account, Error = Self::DsError>
1361 + WalletTest
1362 + WalletWrite
1363 + WalletCommitmentTrees;
1364
1365 fn new_data_store(
1367 &self,
1368 network: LocalNetwork,
1369 #[cfg(feature = "transparent-inputs")] gap_limits: GapLimits,
1370 ) -> Result<Self::DataStore, Self::Error>;
1371}
1372
1373pub struct TestBuilder<Cache, DataStoreFactory> {
1375 rng: ChaChaRng,
1376 network: LocalNetwork,
1377 cache: Cache,
1378 ds_factory: DataStoreFactory,
1379 initial_chain_state: Option<InitialChainState>,
1380 account_birthday: Option<AccountBirthday>,
1381 account_index: Option<zip32::AccountId>,
1382 #[cfg(feature = "transparent-inputs")]
1383 gap_limits: GapLimits,
1384}
1385
1386impl TestBuilder<(), ()> {
1387 pub const DEFAULT_NETWORK: LocalNetwork = LocalNetwork {
1392 overwinter: Some(BlockHeight::from_u32(1)),
1393 sapling: Some(BlockHeight::from_u32(100_000)),
1394 blossom: Some(BlockHeight::from_u32(100_000)),
1395 heartwood: Some(BlockHeight::from_u32(100_000)),
1396 canopy: Some(BlockHeight::from_u32(100_000)),
1397 nu5: Some(BlockHeight::from_u32(100_000)),
1398 nu6: None,
1399 #[cfg(zcash_unstable = "nu7")]
1400 nu7: None,
1401 #[cfg(zcash_unstable = "zfuture")]
1402 z_future: None,
1403 };
1404
1405 pub fn new() -> Self {
1407 TestBuilder {
1408 rng: ChaChaRng::seed_from_u64(0),
1409 network: Self::DEFAULT_NETWORK,
1410 cache: (),
1411 ds_factory: (),
1412 initial_chain_state: None,
1413 account_birthday: None,
1414 account_index: None,
1415 #[cfg(feature = "transparent-inputs")]
1416 gap_limits: GapLimits::new(10, 5, 5),
1417 }
1418 }
1419}
1420
1421impl Default for TestBuilder<(), ()> {
1422 fn default() -> Self {
1423 Self::new()
1424 }
1425}
1426
1427impl<A> TestBuilder<(), A> {
1428 pub fn with_block_cache<C: TestCache>(self, cache: C) -> TestBuilder<C, A> {
1430 TestBuilder {
1431 rng: self.rng,
1432 network: self.network,
1433 cache,
1434 ds_factory: self.ds_factory,
1435 initial_chain_state: self.initial_chain_state,
1436 account_birthday: self.account_birthday,
1437 account_index: self.account_index,
1438 #[cfg(feature = "transparent-inputs")]
1439 gap_limits: self.gap_limits,
1440 }
1441 }
1442}
1443
1444impl<A> TestBuilder<A, ()> {
1445 pub fn with_data_store_factory<DsFactory>(
1447 self,
1448 ds_factory: DsFactory,
1449 ) -> TestBuilder<A, DsFactory> {
1450 TestBuilder {
1451 rng: self.rng,
1452 network: self.network,
1453 cache: self.cache,
1454 ds_factory,
1455 initial_chain_state: self.initial_chain_state,
1456 account_birthday: self.account_birthday,
1457 account_index: self.account_index,
1458 #[cfg(feature = "transparent-inputs")]
1459 gap_limits: self.gap_limits,
1460 }
1461 }
1462}
1463
1464impl<A, B> TestBuilder<A, B> {
1465 #[cfg(feature = "transparent-inputs")]
1466 pub fn with_gap_limits(self, gap_limits: GapLimits) -> TestBuilder<A, B> {
1467 TestBuilder {
1468 rng: self.rng,
1469 network: self.network,
1470 cache: self.cache,
1471 ds_factory: self.ds_factory,
1472 initial_chain_state: self.initial_chain_state,
1473 account_birthday: self.account_birthday,
1474 account_index: self.account_index,
1475 gap_limits,
1476 }
1477 }
1478}
1479
1480impl<Cache, DsFactory> TestBuilder<Cache, DsFactory> {
1481 pub fn with_initial_chain_state(
1564 mut self,
1565 chain_state: impl FnOnce(&mut ChaChaRng, &LocalNetwork) -> InitialChainState,
1566 ) -> Self {
1567 assert!(self.initial_chain_state.is_none());
1568 assert!(self.account_birthday.is_none());
1569 self.initial_chain_state = Some(chain_state(&mut self.rng, &self.network));
1570 self
1571 }
1572
1573 pub fn with_account_from_sapling_activation(mut self, prev_hash: BlockHash) -> Self {
1581 assert!(self.account_birthday.is_none());
1582 self.account_birthday = Some(AccountBirthday::from_parts(
1583 ChainState::empty(
1584 self.network
1585 .activation_height(NetworkUpgrade::Sapling)
1586 .unwrap()
1587 - 1,
1588 prev_hash,
1589 ),
1590 None,
1591 ));
1592 self
1593 }
1594
1595 pub fn with_account_having_current_birthday(mut self) -> Self {
1604 assert!(self.account_birthday.is_none());
1605 assert!(self.initial_chain_state.is_some());
1606 self.account_birthday = Some(AccountBirthday::from_parts(
1607 self.initial_chain_state
1608 .as_ref()
1609 .unwrap()
1610 .chain_state
1611 .clone(),
1612 None,
1613 ));
1614 self
1615 }
1616
1617 pub fn set_account_index(mut self, index: zip32::AccountId) -> Self {
1626 assert!(self.account_index.is_none());
1627 self.account_index = Some(index);
1628 self
1629 }
1630}
1631
1632impl<Cache, DsFactory: DataStoreFactory> TestBuilder<Cache, DsFactory> {
1633 pub fn build(self) -> TestState<Cache, DsFactory::DataStore, LocalNetwork> {
1635 let mut cached_blocks = BTreeMap::new();
1636 let mut wallet_data = self
1637 .ds_factory
1638 .new_data_store(
1639 self.network,
1640 #[cfg(feature = "transparent-inputs")]
1641 self.gap_limits,
1642 )
1643 .unwrap();
1644
1645 if let Some(initial_state) = &self.initial_chain_state {
1646 wallet_data
1647 .put_sapling_subtree_roots(0, &initial_state.prior_sapling_roots)
1648 .unwrap();
1649 wallet_data
1650 .with_sapling_tree_mut(|t| {
1651 t.insert_frontier(
1652 initial_state.chain_state.final_sapling_tree().clone(),
1653 Retention::Checkpoint {
1654 id: initial_state.chain_state.block_height(),
1655 marking: Marking::Reference,
1656 },
1657 )
1658 })
1659 .unwrap();
1660
1661 #[cfg(feature = "orchard")]
1662 {
1663 wallet_data
1664 .put_orchard_subtree_roots(0, &initial_state.prior_orchard_roots)
1665 .unwrap();
1666 wallet_data
1667 .with_orchard_tree_mut(|t| {
1668 t.insert_frontier(
1669 initial_state.chain_state.final_orchard_tree().clone(),
1670 Retention::Checkpoint {
1671 id: initial_state.chain_state.block_height(),
1672 marking: Marking::Reference,
1673 },
1674 )
1675 })
1676 .unwrap();
1677 }
1678
1679 let final_sapling_tree_size =
1680 initial_state.chain_state.final_sapling_tree().tree_size() as u32;
1681 let _final_orchard_tree_size = 0;
1682 #[cfg(feature = "orchard")]
1683 let _final_orchard_tree_size =
1684 initial_state.chain_state.final_orchard_tree().tree_size() as u32;
1685
1686 cached_blocks.insert(
1687 initial_state.chain_state.block_height(),
1688 CachedBlock {
1689 chain_state: initial_state.chain_state.clone(),
1690 sapling_end_size: final_sapling_tree_size,
1691 orchard_end_size: _final_orchard_tree_size,
1692 },
1693 );
1694 };
1695
1696 let test_account = self.account_birthday.map(|birthday| {
1697 let seed = Secret::new(vec![0u8; 32]);
1698 let (account, usk) = match self.account_index {
1699 Some(index) => wallet_data
1700 .import_account_hd("", &seed, index, &birthday, None)
1701 .unwrap(),
1702 None => {
1703 let result = wallet_data
1704 .create_account("", &seed, &birthday, None)
1705 .unwrap();
1706 (
1707 wallet_data.get_account(result.0).unwrap().unwrap(),
1708 result.1,
1709 )
1710 }
1711 };
1712 (
1713 seed,
1714 TestAccount {
1715 account,
1716 usk,
1717 birthday,
1718 },
1719 )
1720 });
1721
1722 TestState {
1723 cache: self.cache,
1724 cached_blocks,
1725 latest_block_height: self
1726 .initial_chain_state
1727 .map(|s| s.chain_state.block_height()),
1728 wallet_data,
1729 network: self.network,
1730 test_account,
1731 rng: self.rng,
1732 }
1733 }
1734}
1735
1736pub trait TestFvk: Clone {
1738 type Nullifier: Copy;
1741
1742 fn sapling_ovk(&self) -> Option<::sapling::keys::OutgoingViewingKey>;
1745
1746 #[cfg(feature = "orchard")]
1749 fn orchard_ovk(&self, scope: zip32::Scope) -> Option<::orchard::keys::OutgoingViewingKey>;
1750
1751 fn add_spend<R: RngCore + CryptoRng>(
1754 &self,
1755 ctx: &mut CompactTx,
1756 nf: Self::Nullifier,
1757 rng: &mut R,
1758 );
1759
1760 #[allow(clippy::too_many_arguments)]
1765 fn add_output<P: consensus::Parameters, R: RngCore + CryptoRng>(
1766 &self,
1767 ctx: &mut CompactTx,
1768 params: &P,
1769 height: BlockHeight,
1770 req: AddressType,
1771 value: Zatoshis,
1772 initial_sapling_tree_size: u32,
1773 rng: &mut R,
1776 ) -> Self::Nullifier;
1777
1778 #[allow(clippy::too_many_arguments)]
1786 fn add_logical_action<P: consensus::Parameters, R: RngCore + CryptoRng>(
1787 &self,
1788 ctx: &mut CompactTx,
1789 params: &P,
1790 height: BlockHeight,
1791 nf: Self::Nullifier,
1792 req: AddressType,
1793 value: Zatoshis,
1794 initial_sapling_tree_size: u32,
1795 rng: &mut R,
1798 ) -> Self::Nullifier;
1799}
1800
1801impl<A: TestFvk> TestFvk for &A {
1802 type Nullifier = A::Nullifier;
1803
1804 fn sapling_ovk(&self) -> Option<::sapling::keys::OutgoingViewingKey> {
1805 (*self).sapling_ovk()
1806 }
1807
1808 #[cfg(feature = "orchard")]
1809 fn orchard_ovk(&self, scope: zip32::Scope) -> Option<::orchard::keys::OutgoingViewingKey> {
1810 (*self).orchard_ovk(scope)
1811 }
1812
1813 fn add_spend<R: RngCore + CryptoRng>(
1814 &self,
1815 ctx: &mut CompactTx,
1816 nf: Self::Nullifier,
1817 rng: &mut R,
1818 ) {
1819 (*self).add_spend(ctx, nf, rng)
1820 }
1821
1822 fn add_output<P: consensus::Parameters, R: RngCore + CryptoRng>(
1823 &self,
1824 ctx: &mut CompactTx,
1825 params: &P,
1826 height: BlockHeight,
1827 req: AddressType,
1828 value: Zatoshis,
1829 initial_sapling_tree_size: u32,
1830 rng: &mut R,
1833 ) -> Self::Nullifier {
1834 (*self).add_output(
1835 ctx,
1836 params,
1837 height,
1838 req,
1839 value,
1840 initial_sapling_tree_size,
1841 rng,
1842 )
1843 }
1844
1845 fn add_logical_action<P: consensus::Parameters, R: RngCore + CryptoRng>(
1846 &self,
1847 ctx: &mut CompactTx,
1848 params: &P,
1849 height: BlockHeight,
1850 nf: Self::Nullifier,
1851 req: AddressType,
1852 value: Zatoshis,
1853 initial_sapling_tree_size: u32,
1854 rng: &mut R,
1857 ) -> Self::Nullifier {
1858 (*self).add_logical_action(
1859 ctx,
1860 params,
1861 height,
1862 nf,
1863 req,
1864 value,
1865 initial_sapling_tree_size,
1866 rng,
1867 )
1868 }
1869}
1870
1871impl TestFvk for DiversifiableFullViewingKey {
1872 type Nullifier = ::sapling::Nullifier;
1873
1874 fn sapling_ovk(&self) -> Option<::sapling::keys::OutgoingViewingKey> {
1875 Some(self.fvk().ovk)
1876 }
1877
1878 #[cfg(feature = "orchard")]
1879 fn orchard_ovk(&self, _: zip32::Scope) -> Option<::orchard::keys::OutgoingViewingKey> {
1880 None
1881 }
1882
1883 fn add_spend<R: RngCore + CryptoRng>(
1884 &self,
1885 ctx: &mut CompactTx,
1886 nf: Self::Nullifier,
1887 _: &mut R,
1888 ) {
1889 let cspend = CompactSaplingSpend { nf: nf.to_vec() };
1890 ctx.spends.push(cspend);
1891 }
1892
1893 fn add_output<P: consensus::Parameters, R: RngCore + CryptoRng>(
1894 &self,
1895 ctx: &mut CompactTx,
1896 params: &P,
1897 height: BlockHeight,
1898 req: AddressType,
1899 value: Zatoshis,
1900 initial_sapling_tree_size: u32,
1901 rng: &mut R,
1902 ) -> Self::Nullifier {
1903 let recipient = match req {
1904 AddressType::DefaultExternal => self.default_address().1,
1905 AddressType::DiversifiedExternal(idx) => self.find_address(idx).unwrap().1,
1906 AddressType::Internal => self.change_address().1,
1907 };
1908
1909 let position = initial_sapling_tree_size + ctx.outputs.len() as u32;
1910
1911 let (cout, note) =
1912 compact_sapling_output(params, height, recipient, value, self.sapling_ovk(), rng);
1913 ctx.outputs.push(cout);
1914
1915 note.nf(&self.fvk().vk.nk, position as u64)
1916 }
1917
1918 #[allow(clippy::too_many_arguments)]
1919 fn add_logical_action<P: consensus::Parameters, R: RngCore + CryptoRng>(
1920 &self,
1921 ctx: &mut CompactTx,
1922 params: &P,
1923 height: BlockHeight,
1924 nf: Self::Nullifier,
1925 req: AddressType,
1926 value: Zatoshis,
1927 initial_sapling_tree_size: u32,
1928 rng: &mut R,
1929 ) -> Self::Nullifier {
1930 self.add_spend(ctx, nf, rng);
1931 self.add_output(
1932 ctx,
1933 params,
1934 height,
1935 req,
1936 value,
1937 initial_sapling_tree_size,
1938 rng,
1939 )
1940 }
1941}
1942
1943#[cfg(feature = "orchard")]
1944impl TestFvk for ::orchard::keys::FullViewingKey {
1945 type Nullifier = ::orchard::note::Nullifier;
1946
1947 fn sapling_ovk(&self) -> Option<::sapling::keys::OutgoingViewingKey> {
1948 None
1949 }
1950
1951 fn orchard_ovk(&self, scope: zip32::Scope) -> Option<::orchard::keys::OutgoingViewingKey> {
1952 Some(self.to_ovk(scope))
1953 }
1954
1955 fn add_spend<R: RngCore + CryptoRng>(
1956 &self,
1957 ctx: &mut CompactTx,
1958 revealed_spent_note_nullifier: Self::Nullifier,
1959 rng: &mut R,
1960 ) {
1961 let recipient = loop {
1963 let mut bytes = [0; 32];
1964 rng.fill_bytes(&mut bytes);
1965 let sk = ::orchard::keys::SpendingKey::from_bytes(bytes);
1966 if sk.is_some().into() {
1967 break ::orchard::keys::FullViewingKey::from(&sk.unwrap())
1968 .address_at(0u32, zip32::Scope::External);
1969 }
1970 };
1971
1972 let (cact, _) = compact_orchard_action(
1973 revealed_spent_note_nullifier,
1974 recipient,
1975 Zatoshis::ZERO,
1976 self.orchard_ovk(zip32::Scope::Internal),
1977 rng,
1978 );
1979 ctx.actions.push(cact);
1980 }
1981
1982 fn add_output<P: consensus::Parameters, R: RngCore + CryptoRng>(
1983 &self,
1984 ctx: &mut CompactTx,
1985 _: &P,
1986 _: BlockHeight,
1987 req: AddressType,
1988 value: Zatoshis,
1989 _: u32, mut rng: &mut R,
1991 ) -> Self::Nullifier {
1992 let revealed_spent_note_nullifier =
1994 ::orchard::note::Nullifier::from_bytes(&pallas::Base::random(&mut rng).to_repr())
1995 .unwrap();
1996
1997 let (j, scope) = match req {
1998 AddressType::DefaultExternal => (0u32.into(), zip32::Scope::External),
1999 AddressType::DiversifiedExternal(idx) => (idx, zip32::Scope::External),
2000 AddressType::Internal => (0u32.into(), zip32::Scope::Internal),
2001 };
2002
2003 let (cact, note) = compact_orchard_action(
2004 revealed_spent_note_nullifier,
2005 self.address_at(j, scope),
2006 value,
2007 self.orchard_ovk(scope),
2008 rng,
2009 );
2010 ctx.actions.push(cact);
2011
2012 note.nullifier(self)
2013 }
2014
2015 fn add_logical_action<P: consensus::Parameters, R: RngCore + CryptoRng>(
2017 &self,
2018 ctx: &mut CompactTx,
2019 _: &P,
2020 _: BlockHeight,
2021 revealed_spent_note_nullifier: Self::Nullifier,
2022 address_type: AddressType,
2023 value: Zatoshis,
2024 _: u32, rng: &mut R,
2026 ) -> Self::Nullifier {
2027 let (j, scope) = match address_type {
2028 AddressType::DefaultExternal => (0u32.into(), zip32::Scope::External),
2029 AddressType::DiversifiedExternal(idx) => (idx, zip32::Scope::External),
2030 AddressType::Internal => (0u32.into(), zip32::Scope::Internal),
2031 };
2032
2033 let (cact, note) = compact_orchard_action(
2034 revealed_spent_note_nullifier,
2035 self.address_at(j, scope),
2036 value,
2037 self.orchard_ovk(scope),
2038 rng,
2039 );
2040 ctx.actions.push(cact);
2041
2042 note.nullifier(self)
2044 }
2045}
2046
2047#[derive(Clone, Copy)]
2051pub enum AddressType {
2052 DefaultExternal,
2054 #[allow(dead_code)]
2057 DiversifiedExternal(DiversifierIndex),
2058 Internal,
2063}
2064
2065fn compact_sapling_output<P: consensus::Parameters, R: RngCore + CryptoRng>(
2069 params: &P,
2070 height: BlockHeight,
2071 recipient: ::sapling::PaymentAddress,
2072 value: Zatoshis,
2073 ovk: Option<::sapling::keys::OutgoingViewingKey>,
2074 rng: &mut R,
2075) -> (CompactSaplingOutput, ::sapling::Note) {
2076 let rseed = generate_random_rseed(zip212_enforcement(params, height), rng);
2077 let note = ::sapling::Note::from_parts(
2078 recipient,
2079 ::sapling::value::NoteValue::from_raw(value.into_u64()),
2080 rseed,
2081 );
2082 let encryptor =
2083 sapling_note_encryption(ovk, note.clone(), MemoBytes::empty().into_bytes(), rng);
2084 let cmu = note.cmu().to_bytes().to_vec();
2085 let ephemeral_key = SaplingDomain::epk_bytes(encryptor.epk()).0.to_vec();
2086 let enc_ciphertext = encryptor.encrypt_note_plaintext();
2087
2088 (
2089 CompactSaplingOutput {
2090 cmu,
2091 ephemeral_key,
2092 ciphertext: enc_ciphertext[..52].to_vec(),
2093 },
2094 note,
2095 )
2096}
2097
2098#[cfg(feature = "orchard")]
2102fn compact_orchard_action<R: RngCore + CryptoRng>(
2103 nf_old: ::orchard::note::Nullifier,
2104 recipient: ::orchard::Address,
2105 value: Zatoshis,
2106 ovk: Option<::orchard::keys::OutgoingViewingKey>,
2107 rng: &mut R,
2108) -> (CompactOrchardAction, ::orchard::Note) {
2109 use zcash_note_encryption::ShieldedOutput;
2110
2111 let (compact_action, note) = ::orchard::note_encryption::testing::fake_compact_action(
2112 rng,
2113 nf_old,
2114 recipient,
2115 ::orchard::value::NoteValue::from_raw(value.into_u64()),
2116 ovk,
2117 );
2118
2119 (
2120 CompactOrchardAction {
2121 nullifier: compact_action.nullifier().to_bytes().to_vec(),
2122 cmx: compact_action.cmx().to_bytes().to_vec(),
2123 ephemeral_key: compact_action.ephemeral_key().0.to_vec(),
2124 ciphertext: compact_action.enc_ciphertext()[..52].to_vec(),
2125 },
2126 note,
2127 )
2128}
2129
2130fn fake_compact_tx<R: RngCore + CryptoRng>(rng: &mut R) -> CompactTx {
2132 let mut ctx = CompactTx::default();
2133 let mut txid = vec![0; 32];
2134 rng.fill_bytes(&mut txid);
2135 ctx.hash = txid;
2136
2137 ctx
2138}
2139
2140#[derive(Clone)]
2146pub struct FakeCompactOutput<Fvk> {
2147 fvk: Fvk,
2148 address_type: AddressType,
2149 value: Zatoshis,
2150}
2151
2152impl<Fvk> FakeCompactOutput<Fvk> {
2153 pub fn new(fvk: Fvk, address_type: AddressType, value: Zatoshis) -> Self {
2155 Self {
2156 fvk,
2157 address_type,
2158 value,
2159 }
2160 }
2161
2162 pub fn random<R: RngCore>(rng: &mut R, fvk: Fvk) -> Self {
2165 Self {
2166 fvk,
2167 address_type: AddressType::DefaultExternal,
2168 value: Zatoshis::const_from_u64(rng.gen_range(10000..1000000)),
2169 }
2170 }
2171}
2172
2173#[allow(clippy::too_many_arguments)]
2178fn fake_compact_block<P: consensus::Parameters, Fvk: TestFvk>(
2179 params: &P,
2180 height: BlockHeight,
2181 prev_hash: BlockHash,
2182 outputs: &[FakeCompactOutput<Fvk>],
2183 initial_sapling_tree_size: u32,
2184 initial_orchard_tree_size: u32,
2185 mut rng: impl RngCore + CryptoRng,
2186) -> (CompactBlock, Vec<Fvk::Nullifier>) {
2187 let mut ctx = fake_compact_tx(&mut rng);
2189 let mut nfs = vec![];
2190 for output in outputs {
2191 let nf = output.fvk.add_output(
2192 &mut ctx,
2193 params,
2194 height,
2195 output.address_type,
2196 output.value,
2197 initial_sapling_tree_size,
2198 &mut rng,
2199 );
2200 nfs.push(nf);
2201 }
2202
2203 let cb = fake_compact_block_from_compact_tx(
2204 ctx,
2205 height,
2206 prev_hash,
2207 initial_sapling_tree_size,
2208 initial_orchard_tree_size,
2209 rng,
2210 );
2211 (cb, nfs)
2212}
2213
2214fn fake_compact_block_from_tx(
2216 height: BlockHeight,
2217 prev_hash: BlockHash,
2218 tx_index: usize,
2219 tx: &Transaction,
2220 initial_sapling_tree_size: u32,
2221 initial_orchard_tree_size: u32,
2222 rng: impl RngCore,
2223) -> CompactBlock {
2224 let mut ctx = CompactTx {
2226 index: tx_index as u64,
2227 hash: tx.txid().as_ref().to_vec(),
2228 ..Default::default()
2229 };
2230
2231 if let Some(bundle) = tx.sapling_bundle() {
2232 for spend in bundle.shielded_spends() {
2233 ctx.spends.push(spend.into());
2234 }
2235 for output in bundle.shielded_outputs() {
2236 ctx.outputs.push(output.into());
2237 }
2238 }
2239
2240 #[cfg(feature = "orchard")]
2241 if let Some(bundle) = tx.orchard_bundle() {
2242 for action in bundle.actions() {
2243 ctx.actions.push(action.into());
2244 }
2245 }
2246
2247 fake_compact_block_from_compact_tx(
2248 ctx,
2249 height,
2250 prev_hash,
2251 initial_sapling_tree_size,
2252 initial_orchard_tree_size,
2253 rng,
2254 )
2255}
2256
2257#[allow(clippy::too_many_arguments)]
2260fn fake_compact_block_spending<P: consensus::Parameters, Fvk: TestFvk>(
2261 params: &P,
2262 height: BlockHeight,
2263 prev_hash: BlockHash,
2264 (nf, in_value): (Fvk::Nullifier, Zatoshis),
2265 fvk: &Fvk,
2266 to: Address,
2267 value: Zatoshis,
2268 initial_sapling_tree_size: u32,
2269 initial_orchard_tree_size: u32,
2270 mut rng: impl RngCore + CryptoRng,
2271) -> CompactBlock {
2272 let mut ctx = fake_compact_tx(&mut rng);
2273
2274 fvk.add_logical_action(
2276 &mut ctx,
2277 params,
2278 height,
2279 nf,
2280 AddressType::Internal,
2281 (in_value - value).unwrap(),
2282 initial_sapling_tree_size,
2283 &mut rng,
2284 );
2285
2286 match to {
2288 Address::Sapling(recipient) => ctx.outputs.push(
2289 compact_sapling_output(
2290 params,
2291 height,
2292 recipient,
2293 value,
2294 fvk.sapling_ovk(),
2295 &mut rng,
2296 )
2297 .0,
2298 ),
2299 Address::Transparent(_) | Address::Tex(_) => {
2300 panic!("transparent addresses not supported in compact blocks")
2301 }
2302 Address::Unified(ua) => {
2303 let mut done = false;
2306
2307 #[cfg(feature = "orchard")]
2308 if let Some(recipient) = ua.orchard() {
2309 let nullifier = ::orchard::note::Nullifier::from_bytes(
2311 &pallas::Base::random(&mut rng).to_repr(),
2312 )
2313 .unwrap();
2314
2315 ctx.actions.push(
2316 compact_orchard_action(
2317 nullifier,
2318 *recipient,
2319 value,
2320 fvk.orchard_ovk(zip32::Scope::External),
2321 &mut rng,
2322 )
2323 .0,
2324 );
2325 done = true;
2326 }
2327
2328 if !done {
2329 if let Some(recipient) = ua.sapling() {
2330 ctx.outputs.push(
2331 compact_sapling_output(
2332 params,
2333 height,
2334 *recipient,
2335 value,
2336 fvk.sapling_ovk(),
2337 &mut rng,
2338 )
2339 .0,
2340 );
2341 done = true;
2342 }
2343 }
2344 if !done {
2345 panic!("No supported shielded receiver to send funds to");
2346 }
2347 }
2348 }
2349
2350 fake_compact_block_from_compact_tx(
2351 ctx,
2352 height,
2353 prev_hash,
2354 initial_sapling_tree_size,
2355 initial_orchard_tree_size,
2356 rng,
2357 )
2358}
2359
2360fn fake_compact_block_from_compact_tx(
2361 ctx: CompactTx,
2362 height: BlockHeight,
2363 prev_hash: BlockHash,
2364 initial_sapling_tree_size: u32,
2365 initial_orchard_tree_size: u32,
2366 mut rng: impl RngCore,
2367) -> CompactBlock {
2368 let mut cb = CompactBlock {
2369 hash: {
2370 let mut hash = vec![0; 32];
2371 rng.fill_bytes(&mut hash);
2372 hash
2373 },
2374 height: height.into(),
2375 ..Default::default()
2376 };
2377 cb.prev_hash.extend_from_slice(&prev_hash.0);
2378 cb.vtx.push(ctx);
2379 cb.chain_metadata = Some(compact_formats::ChainMetadata {
2380 sapling_commitment_tree_size: initial_sapling_tree_size
2381 + cb.vtx.iter().map(|tx| tx.outputs.len() as u32).sum::<u32>(),
2382 orchard_commitment_tree_size: initial_orchard_tree_size
2383 + cb.vtx.iter().map(|tx| tx.actions.len() as u32).sum::<u32>(),
2384 });
2385 cb
2386}
2387
2388pub trait TestCache {
2390 type BsError: core::fmt::Debug;
2391 type BlockSource: BlockSource<Error = Self::BsError>;
2392 type InsertResult;
2393
2394 fn block_source(&self) -> &Self::BlockSource;
2396
2397 fn insert(&mut self, cb: &CompactBlock) -> Self::InsertResult;
2399
2400 fn truncate_to_height(&mut self, height: BlockHeight);
2403}
2404
2405pub struct NoteCommitments {
2409 sapling: Vec<::sapling::Node>,
2410 #[cfg(feature = "orchard")]
2411 orchard: Vec<MerkleHashOrchard>,
2412}
2413
2414impl NoteCommitments {
2415 pub fn from_compact_block(cb: &CompactBlock) -> Self {
2417 NoteCommitments {
2418 sapling: cb
2419 .vtx
2420 .iter()
2421 .flat_map(|tx| {
2422 tx.outputs
2423 .iter()
2424 .map(|out| ::sapling::Node::from_cmu(&out.cmu().unwrap()))
2425 })
2426 .collect(),
2427 #[cfg(feature = "orchard")]
2428 orchard: cb
2429 .vtx
2430 .iter()
2431 .flat_map(|tx| {
2432 tx.actions
2433 .iter()
2434 .map(|act| MerkleHashOrchard::from_cmx(&act.cmx().unwrap()))
2435 })
2436 .collect(),
2437 }
2438 }
2439
2440 #[allow(dead_code)]
2442 pub fn sapling(&self) -> &[::sapling::Node] {
2443 self.sapling.as_ref()
2444 }
2445
2446 #[cfg(feature = "orchard")]
2448 pub fn orchard(&self) -> &[MerkleHashOrchard] {
2449 self.orchard.as_ref()
2450 }
2451}
2452
2453pub struct MockWalletDb {
2455 pub network: Network,
2456 pub sapling_tree: ShardTree<
2457 MemoryShardStore<::sapling::Node, BlockHeight>,
2458 { SAPLING_SHARD_HEIGHT * 2 },
2459 SAPLING_SHARD_HEIGHT,
2460 >,
2461 #[cfg(feature = "orchard")]
2462 pub orchard_tree: ShardTree<
2463 MemoryShardStore<::orchard::tree::MerkleHashOrchard, BlockHeight>,
2464 { ORCHARD_SHARD_HEIGHT * 2 },
2465 ORCHARD_SHARD_HEIGHT,
2466 >,
2467}
2468
2469impl MockWalletDb {
2470 pub fn new(network: Network) -> Self {
2472 Self {
2473 network,
2474 sapling_tree: ShardTree::new(MemoryShardStore::empty(), 100),
2475 #[cfg(feature = "orchard")]
2476 orchard_tree: ShardTree::new(MemoryShardStore::empty(), 100),
2477 }
2478 }
2479}
2480
2481impl InputSource for MockWalletDb {
2482 type Error = ();
2483 type NoteRef = u32;
2484 type AccountId = u32;
2485
2486 fn get_spendable_note(
2487 &self,
2488 _txid: &TxId,
2489 _protocol: ShieldedProtocol,
2490 _index: u32,
2491 ) -> Result<Option<ReceivedNote<Self::NoteRef, Note>>, Self::Error> {
2492 Ok(None)
2493 }
2494
2495 fn select_spendable_notes(
2496 &self,
2497 _account: Self::AccountId,
2498 _target_value: Zatoshis,
2499 _sources: &[ShieldedProtocol],
2500 _anchor_height: BlockHeight,
2501 _exclude: &[Self::NoteRef],
2502 ) -> Result<SpendableNotes<Self::NoteRef>, Self::Error> {
2503 Ok(SpendableNotes::empty())
2504 }
2505
2506 fn get_account_metadata(
2507 &self,
2508 _account: Self::AccountId,
2509 _selector: &NoteFilter,
2510 _exclude: &[Self::NoteRef],
2511 ) -> Result<AccountMeta, Self::Error> {
2512 Err(())
2513 }
2514}
2515
2516impl WalletRead for MockWalletDb {
2517 type Error = ();
2518 type AccountId = u32;
2519 type Account = (Self::AccountId, UnifiedFullViewingKey);
2520
2521 fn get_account_ids(&self) -> Result<Vec<Self::AccountId>, Self::Error> {
2522 Ok(Vec::new())
2523 }
2524
2525 fn get_account(
2526 &self,
2527 _account_id: Self::AccountId,
2528 ) -> Result<Option<Self::Account>, Self::Error> {
2529 Ok(None)
2530 }
2531
2532 fn get_derived_account(
2533 &self,
2534 _seed: &SeedFingerprint,
2535 _account_id: zip32::AccountId,
2536 ) -> Result<Option<Self::Account>, Self::Error> {
2537 Ok(None)
2538 }
2539
2540 fn validate_seed(
2541 &self,
2542 _account_id: Self::AccountId,
2543 _seed: &SecretVec<u8>,
2544 ) -> Result<bool, Self::Error> {
2545 Ok(false)
2546 }
2547
2548 fn seed_relevance_to_derived_accounts(
2549 &self,
2550 _seed: &SecretVec<u8>,
2551 ) -> Result<SeedRelevance<Self::AccountId>, Self::Error> {
2552 Ok(SeedRelevance::NoAccounts)
2553 }
2554
2555 fn get_account_for_ufvk(
2556 &self,
2557 _ufvk: &UnifiedFullViewingKey,
2558 ) -> Result<Option<Self::Account>, Self::Error> {
2559 Ok(None)
2560 }
2561
2562 fn list_addresses(&self, _account: Self::AccountId) -> Result<Vec<AddressInfo>, Self::Error> {
2563 Ok(vec![])
2564 }
2565
2566 fn get_last_generated_address_matching(
2567 &self,
2568 _account: Self::AccountId,
2569 _request: UnifiedAddressRequest,
2570 ) -> Result<Option<UnifiedAddress>, Self::Error> {
2571 Ok(None)
2572 }
2573
2574 fn get_account_birthday(&self, _account: Self::AccountId) -> Result<BlockHeight, Self::Error> {
2575 Err(())
2576 }
2577
2578 fn get_wallet_birthday(&self) -> Result<Option<BlockHeight>, Self::Error> {
2579 Ok(None)
2580 }
2581
2582 fn get_wallet_summary(
2583 &self,
2584 _min_confirmations: u32,
2585 ) -> Result<Option<WalletSummary<Self::AccountId>>, Self::Error> {
2586 Ok(None)
2587 }
2588
2589 fn chain_height(&self) -> Result<Option<BlockHeight>, Self::Error> {
2590 Ok(None)
2591 }
2592
2593 fn get_block_hash(&self, _block_height: BlockHeight) -> Result<Option<BlockHash>, Self::Error> {
2594 Ok(None)
2595 }
2596
2597 fn block_metadata(&self, _height: BlockHeight) -> Result<Option<BlockMetadata>, Self::Error> {
2598 Ok(None)
2599 }
2600
2601 fn block_fully_scanned(&self) -> Result<Option<BlockMetadata>, Self::Error> {
2602 Ok(None)
2603 }
2604
2605 fn get_max_height_hash(&self) -> Result<Option<(BlockHeight, BlockHash)>, Self::Error> {
2606 Ok(None)
2607 }
2608
2609 fn block_max_scanned(&self) -> Result<Option<BlockMetadata>, Self::Error> {
2610 Ok(None)
2611 }
2612
2613 fn suggest_scan_ranges(&self) -> Result<Vec<ScanRange>, Self::Error> {
2614 Ok(vec![])
2615 }
2616
2617 fn get_target_and_anchor_heights(
2618 &self,
2619 _min_confirmations: NonZeroU32,
2620 ) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error> {
2621 Ok(None)
2622 }
2623
2624 fn get_tx_height(&self, _txid: TxId) -> Result<Option<BlockHeight>, Self::Error> {
2625 Ok(None)
2626 }
2627
2628 fn get_unified_full_viewing_keys(
2629 &self,
2630 ) -> Result<HashMap<Self::AccountId, UnifiedFullViewingKey>, Self::Error> {
2631 Ok(HashMap::new())
2632 }
2633
2634 fn get_memo(&self, _id_note: NoteId) -> Result<Option<Memo>, Self::Error> {
2635 Ok(None)
2636 }
2637
2638 fn get_transaction(&self, _txid: TxId) -> Result<Option<Transaction>, Self::Error> {
2639 Ok(None)
2640 }
2641
2642 fn get_sapling_nullifiers(
2643 &self,
2644 _query: NullifierQuery,
2645 ) -> Result<Vec<(Self::AccountId, ::sapling::Nullifier)>, Self::Error> {
2646 Ok(Vec::new())
2647 }
2648
2649 #[cfg(feature = "orchard")]
2650 fn get_orchard_nullifiers(
2651 &self,
2652 _query: NullifierQuery,
2653 ) -> Result<Vec<(Self::AccountId, ::orchard::note::Nullifier)>, Self::Error> {
2654 Ok(Vec::new())
2655 }
2656
2657 #[cfg(feature = "transparent-inputs")]
2658 fn get_transparent_receivers(
2659 &self,
2660 _account: Self::AccountId,
2661 _include_change: bool,
2662 ) -> Result<HashMap<TransparentAddress, Option<TransparentAddressMetadata>>, Self::Error> {
2663 Ok(HashMap::new())
2664 }
2665
2666 #[cfg(feature = "transparent-inputs")]
2667 fn get_transparent_balances(
2668 &self,
2669 _account: Self::AccountId,
2670 _max_height: BlockHeight,
2671 ) -> Result<HashMap<TransparentAddress, Zatoshis>, Self::Error> {
2672 Ok(HashMap::new())
2673 }
2674
2675 #[cfg(feature = "transparent-inputs")]
2676 fn get_transparent_address_metadata(
2677 &self,
2678 _account: Self::AccountId,
2679 _address: &TransparentAddress,
2680 ) -> Result<Option<TransparentAddressMetadata>, Self::Error> {
2681 Ok(None)
2682 }
2683
2684 #[cfg(feature = "transparent-inputs")]
2685 fn get_known_ephemeral_addresses(
2686 &self,
2687 _account: Self::AccountId,
2688 _index_range: Option<Range<NonHardenedChildIndex>>,
2689 ) -> Result<Vec<(TransparentAddress, TransparentAddressMetadata)>, Self::Error> {
2690 Ok(vec![])
2691 }
2692
2693 #[cfg(feature = "transparent-inputs")]
2694 fn utxo_query_height(&self, _account: Self::AccountId) -> Result<BlockHeight, Self::Error> {
2695 Ok(BlockHeight::from(0u32))
2696 }
2697
2698 #[cfg(feature = "transparent-inputs")]
2699 fn find_account_for_ephemeral_address(
2700 &self,
2701 _address: &TransparentAddress,
2702 ) -> Result<Option<Self::AccountId>, Self::Error> {
2703 Ok(None)
2704 }
2705
2706 fn transaction_data_requests(&self) -> Result<Vec<TransactionDataRequest>, Self::Error> {
2707 Ok(vec![])
2708 }
2709}
2710
2711impl WalletWrite for MockWalletDb {
2712 type UtxoRef = u32;
2713
2714 fn create_account(
2715 &mut self,
2716 _account_name: &str,
2717 seed: &SecretVec<u8>,
2718 _birthday: &AccountBirthday,
2719 _key_source: Option<&str>,
2720 ) -> Result<(Self::AccountId, UnifiedSpendingKey), Self::Error> {
2721 let account = zip32::AccountId::ZERO;
2722 UnifiedSpendingKey::from_seed(&self.network, seed.expose_secret(), account)
2723 .map(|k| (u32::from(account), k))
2724 .map_err(|_| ())
2725 }
2726
2727 fn import_account_hd(
2728 &mut self,
2729 _account_name: &str,
2730 _seed: &SecretVec<u8>,
2731 _account_index: zip32::AccountId,
2732 _birthday: &AccountBirthday,
2733 _key_source: Option<&str>,
2734 ) -> Result<(Self::Account, UnifiedSpendingKey), Self::Error> {
2735 todo!()
2736 }
2737
2738 fn import_account_ufvk(
2739 &mut self,
2740 _account_name: &str,
2741 _unified_key: &UnifiedFullViewingKey,
2742 _birthday: &AccountBirthday,
2743 _purpose: AccountPurpose,
2744 _key_source: Option<&str>,
2745 ) -> Result<Self::Account, Self::Error> {
2746 todo!()
2747 }
2748
2749 fn get_next_available_address(
2750 &mut self,
2751 _account: Self::AccountId,
2752 _request: UnifiedAddressRequest,
2753 ) -> Result<Option<(UnifiedAddress, DiversifierIndex)>, Self::Error> {
2754 Ok(None)
2755 }
2756
2757 fn get_address_for_index(
2758 &mut self,
2759 _account: Self::AccountId,
2760 _diversifier_index: DiversifierIndex,
2761 _request: UnifiedAddressRequest,
2762 ) -> Result<Option<UnifiedAddress>, Self::Error> {
2763 Ok(None)
2764 }
2765
2766 #[allow(clippy::type_complexity)]
2767 fn put_blocks(
2768 &mut self,
2769 _from_state: &ChainState,
2770 _blocks: Vec<ScannedBlock<Self::AccountId>>,
2771 ) -> Result<(), Self::Error> {
2772 Ok(())
2773 }
2774
2775 fn update_chain_tip(&mut self, _tip_height: BlockHeight) -> Result<(), Self::Error> {
2776 Ok(())
2777 }
2778
2779 fn store_decrypted_tx(
2780 &mut self,
2781 _received_tx: DecryptedTransaction<Self::AccountId>,
2782 ) -> Result<(), Self::Error> {
2783 Ok(())
2784 }
2785
2786 fn store_transactions_to_be_sent(
2787 &mut self,
2788 _transactions: &[SentTransaction<Self::AccountId>],
2789 ) -> Result<(), Self::Error> {
2790 Ok(())
2791 }
2792
2793 fn truncate_to_height(
2794 &mut self,
2795 _block_height: BlockHeight,
2796 ) -> Result<BlockHeight, Self::Error> {
2797 Err(())
2798 }
2799
2800 fn put_received_transparent_utxo(
2802 &mut self,
2803 _output: &WalletTransparentOutput,
2804 ) -> Result<Self::UtxoRef, Self::Error> {
2805 Ok(0)
2806 }
2807
2808 #[cfg(feature = "transparent-inputs")]
2809 fn reserve_next_n_ephemeral_addresses(
2810 &mut self,
2811 _account_id: Self::AccountId,
2812 _n: usize,
2813 ) -> Result<Vec<(TransparentAddress, TransparentAddressMetadata)>, Self::Error> {
2814 Err(())
2815 }
2816
2817 fn set_transaction_status(
2818 &mut self,
2819 _txid: TxId,
2820 _status: TransactionStatus,
2821 ) -> Result<(), Self::Error> {
2822 Ok(())
2823 }
2824}
2825
2826impl WalletCommitmentTrees for MockWalletDb {
2827 type Error = Infallible;
2828 type SaplingShardStore<'a> = MemoryShardStore<::sapling::Node, BlockHeight>;
2829
2830 fn with_sapling_tree_mut<F, A, E>(&mut self, mut callback: F) -> Result<A, E>
2831 where
2832 for<'a> F: FnMut(
2833 &'a mut ShardTree<
2834 Self::SaplingShardStore<'a>,
2835 { ::sapling::NOTE_COMMITMENT_TREE_DEPTH },
2836 SAPLING_SHARD_HEIGHT,
2837 >,
2838 ) -> Result<A, E>,
2839 E: From<ShardTreeError<Infallible>>,
2840 {
2841 callback(&mut self.sapling_tree)
2842 }
2843
2844 fn put_sapling_subtree_roots(
2845 &mut self,
2846 start_index: u64,
2847 roots: &[CommitmentTreeRoot<::sapling::Node>],
2848 ) -> Result<(), ShardTreeError<Self::Error>> {
2849 self.with_sapling_tree_mut(|t| {
2850 for (root, i) in roots.iter().zip(0u64..) {
2851 let root_addr = incrementalmerkletree::Address::from_parts(
2852 SAPLING_SHARD_HEIGHT.into(),
2853 start_index + i,
2854 );
2855 t.insert(root_addr, *root.root_hash())?;
2856 }
2857 Ok::<_, ShardTreeError<Self::Error>>(())
2858 })?;
2859
2860 Ok(())
2861 }
2862
2863 #[cfg(feature = "orchard")]
2864 type OrchardShardStore<'a> = MemoryShardStore<::orchard::tree::MerkleHashOrchard, BlockHeight>;
2865
2866 #[cfg(feature = "orchard")]
2867 fn with_orchard_tree_mut<F, A, E>(&mut self, mut callback: F) -> Result<A, E>
2868 where
2869 for<'a> F: FnMut(
2870 &'a mut ShardTree<
2871 Self::OrchardShardStore<'a>,
2872 { ORCHARD_SHARD_HEIGHT * 2 },
2873 ORCHARD_SHARD_HEIGHT,
2874 >,
2875 ) -> Result<A, E>,
2876 E: From<ShardTreeError<Self::Error>>,
2877 {
2878 callback(&mut self.orchard_tree)
2879 }
2880
2881 #[cfg(feature = "orchard")]
2883 fn put_orchard_subtree_roots(
2884 &mut self,
2885 start_index: u64,
2886 roots: &[CommitmentTreeRoot<::orchard::tree::MerkleHashOrchard>],
2887 ) -> Result<(), ShardTreeError<Self::Error>> {
2888 self.with_orchard_tree_mut(|t| {
2889 for (root, i) in roots.iter().zip(0u64..) {
2890 let root_addr = incrementalmerkletree::Address::from_parts(
2891 ORCHARD_SHARD_HEIGHT.into(),
2892 start_index + i,
2893 );
2894 t.insert(root_addr, *root.root_hash())?;
2895 }
2896 Ok::<_, ShardTreeError<Self::Error>>(())
2897 })?;
2898
2899 Ok(())
2900 }
2901}