1#![doc = document_features::document_features!()]
23#![cfg_attr(docsrs, feature(doc_cfg))]
31#![cfg_attr(docsrs, feature(doc_auto_cfg))]
32#![deny(rustdoc::broken_intra_doc_links)]
34
35use incrementalmerkletree::{Marking, Position, Retention};
36use nonempty::NonEmpty;
37use rusqlite::{self, Connection};
38use secrecy::{ExposeSecret, SecretVec};
39use shardtree::{error::ShardTreeError, store::ShardStore, ShardTree};
40use std::{
41 borrow::{Borrow, BorrowMut},
42 cmp::{max, min},
43 collections::HashMap,
44 convert::AsRef,
45 fmt,
46 num::NonZeroU32,
47 ops::Range,
48 path::Path,
49};
50use subtle::ConditionallySelectable;
51use tracing::{debug, trace, warn};
52use util::Clock;
53use uuid::Uuid;
54
55use zcash_client_backend::{
56 data_api::{
57 self,
58 chain::{BlockSource, ChainState, CommitmentTreeRoot},
59 scanning::{ScanPriority, ScanRange},
60 Account, AccountBirthday, AccountMeta, AccountPurpose, AccountSource, AddressInfo,
61 BlockMetadata, DecryptedTransaction, InputSource, NoteFilter, NullifierQuery, ScannedBlock,
62 SeedRelevance, SentTransaction, SpendableNotes, TargetValue, TransactionDataRequest,
63 WalletCommitmentTrees, WalletRead, WalletSummary, WalletWrite, Zip32Derivation,
64 SAPLING_SHARD_HEIGHT,
65 },
66 proto::compact_formats::CompactBlock,
67 wallet::{Note, NoteId, ReceivedNote, WalletTransparentOutput},
68 TransferType,
69};
70use zcash_keys::{
71 address::UnifiedAddress,
72 keys::{ReceiverRequirement, UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey},
73};
74use zcash_primitives::{
75 block::BlockHash,
76 transaction::{Transaction, TxId},
77};
78use zcash_protocol::{
79 consensus::{self, BlockHeight},
80 memo::Memo,
81 ShieldedProtocol,
82};
83use zip32::{fingerprint::SeedFingerprint, DiversifierIndex};
84
85use crate::{error::SqliteClientError, wallet::commitment_tree::SqliteShardStore};
86use wallet::{
87 commitment_tree::{self, put_shard_roots},
88 common::spendable_notes_meta,
89 scanning::replace_queue_entries,
90 upsert_address, SubtreeProgressEstimator,
91};
92
93#[cfg(feature = "orchard")]
94use {
95 incrementalmerkletree::frontier::Frontier, shardtree::store::Checkpoint,
96 std::collections::BTreeMap, zcash_client_backend::data_api::ORCHARD_SHARD_HEIGHT,
97};
98
99#[cfg(feature = "transparent-inputs")]
100use {
101 crate::wallet::transparent::ephemeral::schedule_ephemeral_address_checks,
102 ::transparent::{address::TransparentAddress, bundle::OutPoint, keys::NonHardenedChildIndex},
103 std::collections::BTreeSet,
104 zcash_client_backend::wallet::TransparentAddressMetadata,
105 zcash_keys::encoding::AddressCodec,
106 zcash_protocol::value::Zatoshis,
107};
108
109#[cfg(feature = "multicore")]
110use maybe_rayon::{
111 prelude::{IndexedParallelIterator, ParallelIterator},
112 slice::ParallelSliceMut,
113};
114
115#[cfg(any(test, feature = "test-dependencies"))]
116use {
117 rusqlite::named_params,
118 zcash_client_backend::data_api::{testing::TransactionSummary, OutputOfSentTx, WalletTest},
119 zcash_keys::address::Address,
120};
121
122#[cfg(any(test, feature = "test-dependencies", feature = "transparent-inputs"))]
123use crate::wallet::encoding::KeyScope;
124
125#[cfg(any(test, feature = "test-dependencies", not(feature = "orchard")))]
126use zcash_protocol::PoolType;
127
128#[cfg(not(feature = "multicore"))]
130trait ParallelSliceMut<T> {
131 fn par_chunks_mut(&mut self, chunk_size: usize) -> std::slice::ChunksMut<'_, T>;
132}
133#[cfg(not(feature = "multicore"))]
134impl<T> ParallelSliceMut<T> for [T] {
135 fn par_chunks_mut(&mut self, chunk_size: usize) -> std::slice::ChunksMut<'_, T> {
136 self.chunks_mut(chunk_size)
137 }
138}
139
140#[cfg(feature = "unstable")]
141use {
142 crate::chain::{fsblockdb_with_blocks, BlockMeta},
143 std::path::PathBuf,
144 std::{fs, io},
145};
146
147pub mod chain;
148pub mod error;
149pub mod util;
150pub mod wallet;
151
152#[cfg(test)]
153mod testing;
154
155pub(crate) const PRUNING_DEPTH: u32 = 100;
159
160pub(crate) const VERIFY_LOOKAHEAD: u32 = 10;
162
163pub(crate) const SAPLING_TABLES_PREFIX: &str = "sapling";
164
165#[cfg(feature = "orchard")]
166pub(crate) const ORCHARD_TABLES_PREFIX: &str = "orchard";
167
168#[cfg(not(feature = "orchard"))]
169pub(crate) const UA_ORCHARD: ReceiverRequirement = ReceiverRequirement::Omit;
170#[cfg(feature = "orchard")]
171pub(crate) const UA_ORCHARD: ReceiverRequirement = ReceiverRequirement::Require;
172
173#[cfg(not(feature = "transparent-inputs"))]
174pub(crate) const UA_TRANSPARENT: ReceiverRequirement = ReceiverRequirement::Omit;
175#[cfg(feature = "transparent-inputs")]
176pub(crate) const UA_TRANSPARENT: ReceiverRequirement = ReceiverRequirement::Require;
177
178#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
196#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
197pub struct AccountUuid(#[cfg_attr(feature = "serde", serde(with = "uuid::serde::compact"))] Uuid);
198
199impl ConditionallySelectable for AccountUuid {
200 fn conditional_select(a: &Self, b: &Self, choice: subtle::Choice) -> Self {
201 AccountUuid(Uuid::from_u128(
202 ConditionallySelectable::conditional_select(&a.0.as_u128(), &b.0.as_u128(), choice),
203 ))
204 }
205}
206
207impl AccountUuid {
208 pub fn from_uuid(value: Uuid) -> Self {
213 AccountUuid(value)
214 }
215
216 pub fn expose_uuid(&self) -> Uuid {
218 self.0
219 }
220}
221
222#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
227pub(crate) struct AccountRef(i64);
228
229#[cfg(test)]
231impl ConditionallySelectable for AccountRef {
232 fn conditional_select(a: &Self, b: &Self, choice: subtle::Choice) -> Self {
233 AccountRef(ConditionallySelectable::conditional_select(
234 &a.0, &b.0, choice,
235 ))
236 }
237}
238
239#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
241pub struct ReceivedNoteId(pub(crate) ShieldedProtocol, pub(crate) i64);
242
243impl fmt::Display for ReceivedNoteId {
244 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
245 match self {
246 ReceivedNoteId(protocol, id) => write!(f, "Received {:?} Note: {}", protocol, id),
247 }
248 }
249}
250
251#[derive(Debug, Copy, Clone, PartialEq, Eq)]
253pub struct UtxoId(pub i64);
254
255#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
257struct TxRef(pub i64);
258
259#[derive(Debug, Copy, Clone, PartialEq, Eq)]
261struct AddressRef(pub(crate) i64);
262
263#[derive(Debug, Copy, Clone, PartialEq, Eq)]
266#[cfg(feature = "transparent-inputs")]
267pub struct GapLimits {
268 external: u32,
269 internal: u32,
270 ephemeral: u32,
271}
272
273#[cfg(feature = "transparent-inputs")]
274impl GapLimits {
275 #[cfg(any(test, feature = "test-dependencies", feature = "unstable"))]
283 pub fn from_parts(external: u32, internal: u32, ephemeral: u32) -> Self {
284 Self {
285 external,
286 internal,
287 ephemeral,
288 }
289 }
290
291 pub(crate) fn external(&self) -> u32 {
292 self.external
293 }
294
295 pub(crate) fn internal(&self) -> u32 {
296 self.internal
297 }
298
299 pub(crate) fn ephemeral(&self) -> u32 {
300 self.ephemeral
301 }
302}
303
304#[cfg(feature = "transparent-inputs")]
328impl Default for GapLimits {
329 fn default() -> Self {
330 Self {
331 external: 10,
332 internal: 5,
333 ephemeral: 5,
334 }
335 }
336}
337
338#[cfg(all(
339 any(test, feature = "test-dependencies"),
340 feature = "transparent-inputs"
341))]
342impl From<GapLimits> for zcash_client_backend::data_api::testing::transparent::GapLimits {
343 fn from(value: GapLimits) -> Self {
344 zcash_client_backend::data_api::testing::transparent::GapLimits::new(
345 value.external,
346 value.internal,
347 value.ephemeral,
348 )
349 }
350}
351
352#[cfg(all(
353 any(test, feature = "test-dependencies"),
354 feature = "transparent-inputs"
355))]
356impl From<zcash_client_backend::data_api::testing::transparent::GapLimits> for GapLimits {
357 fn from(value: zcash_client_backend::data_api::testing::transparent::GapLimits) -> Self {
358 GapLimits::from_parts(value.external(), value.internal(), value.ephemeral())
359 }
360}
361
362pub struct WalletDb<C, P, CL, R> {
366 conn: C,
367 params: P,
368 clock: CL,
369 rng: R,
370 #[cfg(feature = "transparent-inputs")]
371 gap_limits: GapLimits,
372}
373
374pub struct SqlTransaction<'conn>(pub(crate) &'conn rusqlite::Transaction<'conn>);
376
377impl Borrow<rusqlite::Connection> for SqlTransaction<'_> {
378 fn borrow(&self) -> &rusqlite::Connection {
379 self.0
380 }
381}
382
383impl<P, CL, R> WalletDb<Connection, P, CL, R> {
384 pub fn for_path<F: AsRef<Path>>(
394 path: F,
395 params: P,
396 clock: CL,
397 rng: R,
398 ) -> Result<Self, rusqlite::Error> {
399 Connection::open(path).and_then(move |conn| {
400 rusqlite::vtab::array::load_module(&conn)?;
401 Ok(WalletDb {
402 conn,
403 params,
404 clock,
405 rng,
406 #[cfg(feature = "transparent-inputs")]
407 gap_limits: GapLimits::default(),
408 })
409 })
410 }
411}
412
413#[cfg(feature = "transparent-inputs")]
414impl<C, P, CL, R> WalletDb<C, P, CL, R> {
415 pub fn with_gap_limits(mut self, gap_limits: GapLimits) -> Self {
417 self.gap_limits = gap_limits;
418 self
419 }
420}
421
422impl<C: Borrow<rusqlite::Connection>, P, CL, R> WalletDb<C, P, CL, R> {
423 pub fn from_connection(conn: C, params: P, clock: CL, rng: R) -> Self {
438 WalletDb {
439 conn,
440 params,
441 clock,
442 rng,
443 #[cfg(feature = "transparent-inputs")]
444 gap_limits: GapLimits::default(),
445 }
446 }
447}
448
449impl<C: BorrowMut<Connection>, P, CL, R> WalletDb<C, P, CL, R> {
450 pub fn transactionally<F, A, E: From<rusqlite::Error>>(&mut self, f: F) -> Result<A, E>
451 where
452 F: FnOnce(&mut WalletDb<SqlTransaction<'_>, &P, &CL, &mut R>) -> Result<A, E>,
453 {
454 let tx = self.conn.borrow_mut().transaction()?;
455 let mut wdb = WalletDb {
456 conn: SqlTransaction(&tx),
457 params: &self.params,
458 clock: &self.clock,
459 rng: &mut self.rng,
460 #[cfg(feature = "transparent-inputs")]
461 gap_limits: self.gap_limits,
462 };
463 let result = f(&mut wdb)?;
464 tx.commit()?;
465 Ok(result)
466 }
467
468 pub fn check_witnesses(&mut self) -> Result<Vec<Range<BlockHeight>>, SqliteClientError> {
474 self.transactionally(|wdb| wallet::commitment_tree::check_witnesses(wdb.conn.0))
475 }
476
477 pub fn queue_rescans(
480 &mut self,
481 rescan_ranges: NonEmpty<Range<BlockHeight>>,
482 priority: ScanPriority,
483 ) -> Result<(), SqliteClientError> {
484 let query_range = rescan_ranges
485 .iter()
486 .fold(None, |acc: Option<Range<BlockHeight>>, scan_range| {
487 if let Some(range) = acc {
488 Some(min(range.start, scan_range.start)..max(range.end, scan_range.end))
489 } else {
490 Some(scan_range.clone())
491 }
492 })
493 .expect("rescan_ranges is nonempty");
494
495 self.transactionally::<_, _, SqliteClientError>(|wdb| {
496 replace_queue_entries(
497 wdb.conn.0,
498 &query_range,
499 rescan_ranges
500 .into_iter()
501 .map(|r| ScanRange::from_parts(r, priority)),
502 true,
503 )
504 })?;
505
506 Ok(())
507 }
508}
509
510#[cfg(feature = "transparent-inputs")]
511impl<C: BorrowMut<Connection>, P, CL: Clock, R: rand::RngCore> WalletDb<C, P, CL, R> {
512 pub fn schedule_ephemeral_address_checks(&mut self) -> Result<(), SqliteClientError> {
520 self.borrow_mut().transactionally(|wdb| {
521 schedule_ephemeral_address_checks(wdb.conn.0, wdb.clock, &mut wdb.rng)
522 })
523 }
524}
525
526impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters, CL, R> InputSource
527 for WalletDb<C, P, CL, R>
528{
529 type Error = SqliteClientError;
530 type NoteRef = ReceivedNoteId;
531 type AccountId = AccountUuid;
532
533 fn get_spendable_note(
534 &self,
535 txid: &TxId,
536 protocol: ShieldedProtocol,
537 index: u32,
538 ) -> Result<Option<ReceivedNote<Self::NoteRef, Note>>, Self::Error> {
539 match protocol {
540 ShieldedProtocol::Sapling => wallet::sapling::get_spendable_sapling_note(
541 self.conn.borrow(),
542 &self.params,
543 txid,
544 index,
545 )
546 .map(|opt| opt.map(|n| n.map_note(Note::Sapling))),
547 ShieldedProtocol::Orchard => {
548 #[cfg(feature = "orchard")]
549 return wallet::orchard::get_spendable_orchard_note(
550 self.conn.borrow(),
551 &self.params,
552 txid,
553 index,
554 )
555 .map(|opt| opt.map(|n| n.map_note(Note::Orchard)));
556
557 #[cfg(not(feature = "orchard"))]
558 return Err(SqliteClientError::UnsupportedPoolType(PoolType::ORCHARD));
559 }
560 }
561 }
562
563 fn select_spendable_notes(
564 &self,
565 account: Self::AccountId,
566 target_value: TargetValue,
567 sources: &[ShieldedProtocol],
568 anchor_height: BlockHeight,
569 exclude: &[Self::NoteRef],
570 ) -> Result<SpendableNotes<Self::NoteRef>, Self::Error> {
571 Ok(SpendableNotes::new(
572 if sources.contains(&ShieldedProtocol::Sapling) {
573 wallet::sapling::select_spendable_sapling_notes(
574 self.conn.borrow(),
575 &self.params,
576 account,
577 target_value,
578 anchor_height,
579 exclude,
580 )?
581 } else {
582 vec![]
583 },
584 #[cfg(feature = "orchard")]
585 if sources.contains(&ShieldedProtocol::Orchard) {
586 wallet::orchard::select_spendable_orchard_notes(
587 self.conn.borrow(),
588 &self.params,
589 account,
590 target_value,
591 anchor_height,
592 exclude,
593 )?
594 } else {
595 vec![]
596 },
597 ))
598 }
599
600 #[cfg(feature = "transparent-inputs")]
601 fn get_unspent_transparent_output(
602 &self,
603 outpoint: &OutPoint,
604 ) -> Result<Option<WalletTransparentOutput>, Self::Error> {
605 wallet::transparent::get_wallet_transparent_output(self.conn.borrow(), outpoint, false)
606 }
607
608 #[cfg(feature = "transparent-inputs")]
609 fn get_spendable_transparent_outputs(
610 &self,
611 address: &TransparentAddress,
612 target_height: BlockHeight,
613 min_confirmations: u32,
614 ) -> Result<Vec<WalletTransparentOutput>, Self::Error> {
615 wallet::transparent::get_spendable_transparent_outputs(
616 self.conn.borrow(),
617 &self.params,
618 address,
619 target_height,
620 min_confirmations,
621 )
622 }
623
624 fn get_account_metadata(
626 &self,
627 account_id: Self::AccountId,
628 selector: &NoteFilter,
629 exclude: &[Self::NoteRef],
630 ) -> Result<AccountMeta, Self::Error> {
631 let chain_tip_height = wallet::chain_tip_height(self.conn.borrow())?
632 .ok_or(SqliteClientError::ChainHeightUnknown)?;
633
634 let sapling_pool_meta = spendable_notes_meta(
635 self.conn.borrow(),
636 ShieldedProtocol::Sapling,
637 chain_tip_height,
638 account_id,
639 selector,
640 exclude,
641 )?;
642
643 #[cfg(feature = "orchard")]
644 let orchard_pool_meta = spendable_notes_meta(
645 self.conn.borrow(),
646 ShieldedProtocol::Orchard,
647 chain_tip_height,
648 account_id,
649 selector,
650 exclude,
651 )?;
652 #[cfg(not(feature = "orchard"))]
653 let orchard_pool_meta = None;
654
655 Ok(AccountMeta::new(sapling_pool_meta, orchard_pool_meta))
656 }
657}
658
659impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters, CL, R> WalletRead
660 for WalletDb<C, P, CL, R>
661{
662 type Error = SqliteClientError;
663 type AccountId = AccountUuid;
664 type Account = wallet::Account;
665
666 fn get_account_ids(&self) -> Result<Vec<Self::AccountId>, Self::Error> {
667 Ok(wallet::get_account_ids(self.conn.borrow())?)
668 }
669
670 fn get_account(
671 &self,
672 account_id: Self::AccountId,
673 ) -> Result<Option<Self::Account>, Self::Error> {
674 wallet::get_account(self.conn.borrow(), &self.params, account_id)
675 }
676
677 fn get_derived_account(
678 &self,
679 seed: &SeedFingerprint,
680 account_id: zip32::AccountId,
681 ) -> Result<Option<Self::Account>, Self::Error> {
682 wallet::get_derived_account(self.conn.borrow(), &self.params, seed, account_id)
683 }
684
685 fn validate_seed(
686 &self,
687 account_id: Self::AccountId,
688 seed: &SecretVec<u8>,
689 ) -> Result<bool, Self::Error> {
690 if let Some(account) = self.get_account(account_id)? {
691 if let AccountSource::Derived { derivation, .. } = account.source() {
692 wallet::seed_matches_derived_account(
693 &self.params,
694 seed,
695 derivation.seed_fingerprint(),
696 derivation.account_index(),
697 &account.uivk(),
698 )
699 } else {
700 Err(SqliteClientError::UnknownZip32Derivation)
701 }
702 } else {
703 Ok(false)
705 }
706 }
707
708 fn seed_relevance_to_derived_accounts(
709 &self,
710 seed: &SecretVec<u8>,
711 ) -> Result<SeedRelevance<Self::AccountId>, Self::Error> {
712 let mut has_accounts = false;
713 let mut has_derived = false;
714 let mut relevant_account_ids = vec![];
715
716 for account_id in self.get_account_ids()? {
717 has_accounts = true;
718 let account = self.get_account(account_id)?.expect("account ID exists");
719
720 if let AccountSource::Derived { derivation, .. } = account.source() {
725 has_derived = true;
726
727 if wallet::seed_matches_derived_account(
728 &self.params,
729 seed,
730 derivation.seed_fingerprint(),
731 derivation.account_index(),
732 &account.uivk(),
733 )? {
734 relevant_account_ids.push(account_id);
736 }
737 }
738 }
739
740 Ok(
741 if let Some(account_ids) = NonEmpty::from_vec(relevant_account_ids) {
742 SeedRelevance::Relevant { account_ids }
743 } else if has_derived {
744 SeedRelevance::NotRelevant
745 } else if has_accounts {
746 SeedRelevance::NoDerivedAccounts
747 } else {
748 SeedRelevance::NoAccounts
749 },
750 )
751 }
752
753 fn get_account_for_ufvk(
754 &self,
755 ufvk: &UnifiedFullViewingKey,
756 ) -> Result<Option<Self::Account>, Self::Error> {
757 wallet::get_account_for_ufvk(self.conn.borrow(), &self.params, ufvk)
758 }
759
760 fn list_addresses(&self, account: Self::AccountId) -> Result<Vec<AddressInfo>, Self::Error> {
761 wallet::list_addresses(self.conn.borrow(), &self.params, account)
762 }
763
764 fn get_last_generated_address_matching(
765 &self,
766 account: Self::AccountId,
767 request: UnifiedAddressRequest,
768 ) -> Result<Option<UnifiedAddress>, Self::Error> {
769 wallet::get_last_generated_address_matching(
770 self.conn.borrow(),
771 &self.params,
772 account,
773 request,
774 )
775 .map(|res| res.map(|(addr, _)| addr))
776 }
777
778 fn get_account_birthday(&self, account: Self::AccountId) -> Result<BlockHeight, Self::Error> {
779 wallet::account_birthday(self.conn.borrow(), account)
780 }
781
782 fn get_wallet_birthday(&self) -> Result<Option<BlockHeight>, Self::Error> {
783 wallet::wallet_birthday(self.conn.borrow()).map_err(SqliteClientError::from)
784 }
785
786 fn get_wallet_summary(
787 &self,
788 min_confirmations: u32,
789 ) -> Result<Option<WalletSummary<Self::AccountId>>, Self::Error> {
790 wallet::get_wallet_summary(
793 &self.conn.borrow().unchecked_transaction()?,
794 &self.params,
795 min_confirmations,
796 &SubtreeProgressEstimator,
797 )
798 }
799
800 fn chain_height(&self) -> Result<Option<BlockHeight>, Self::Error> {
801 wallet::chain_tip_height(self.conn.borrow()).map_err(SqliteClientError::from)
802 }
803
804 fn get_block_hash(&self, block_height: BlockHeight) -> Result<Option<BlockHash>, Self::Error> {
805 wallet::get_block_hash(self.conn.borrow(), block_height).map_err(SqliteClientError::from)
806 }
807
808 fn block_metadata(&self, height: BlockHeight) -> Result<Option<BlockMetadata>, Self::Error> {
809 wallet::block_metadata(self.conn.borrow(), &self.params, height)
810 }
811
812 fn block_fully_scanned(&self) -> Result<Option<BlockMetadata>, Self::Error> {
813 wallet::block_fully_scanned(self.conn.borrow(), &self.params)
814 }
815
816 fn get_max_height_hash(&self) -> Result<Option<(BlockHeight, BlockHash)>, Self::Error> {
817 wallet::get_max_height_hash(self.conn.borrow()).map_err(SqliteClientError::from)
818 }
819
820 fn block_max_scanned(&self) -> Result<Option<BlockMetadata>, Self::Error> {
821 wallet::block_max_scanned(self.conn.borrow(), &self.params)
822 }
823
824 fn suggest_scan_ranges(&self) -> Result<Vec<ScanRange>, Self::Error> {
825 wallet::scanning::suggest_scan_ranges(self.conn.borrow(), ScanPriority::Historic)
826 }
827
828 fn get_target_and_anchor_heights(
829 &self,
830 min_confirmations: NonZeroU32,
831 ) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error> {
832 wallet::get_target_and_anchor_heights(self.conn.borrow(), min_confirmations)
833 .map_err(SqliteClientError::from)
834 }
835
836 fn get_tx_height(&self, txid: TxId) -> Result<Option<BlockHeight>, Self::Error> {
837 wallet::get_tx_height(self.conn.borrow(), txid).map_err(SqliteClientError::from)
838 }
839
840 fn get_unified_full_viewing_keys(
841 &self,
842 ) -> Result<HashMap<Self::AccountId, UnifiedFullViewingKey>, Self::Error> {
843 wallet::get_unified_full_viewing_keys(self.conn.borrow(), &self.params)
844 }
845
846 fn get_memo(&self, note_id: NoteId) -> Result<Option<Memo>, Self::Error> {
847 let sent_memo = wallet::get_sent_memo(self.conn.borrow(), note_id)?;
848 if sent_memo.is_some() {
849 Ok(sent_memo)
850 } else {
851 wallet::get_received_memo(self.conn.borrow(), note_id)
852 }
853 }
854
855 fn get_transaction(&self, txid: TxId) -> Result<Option<Transaction>, Self::Error> {
856 wallet::get_transaction(self.conn.borrow(), &self.params, txid)
857 .map(|res| res.map(|(_, tx)| tx))
858 }
859
860 fn get_sapling_nullifiers(
861 &self,
862 query: NullifierQuery,
863 ) -> Result<Vec<(Self::AccountId, sapling::Nullifier)>, Self::Error> {
864 wallet::sapling::get_sapling_nullifiers(self.conn.borrow(), query)
865 }
866
867 #[cfg(feature = "orchard")]
868 fn get_orchard_nullifiers(
869 &self,
870 query: NullifierQuery,
871 ) -> Result<Vec<(Self::AccountId, orchard::note::Nullifier)>, Self::Error> {
872 wallet::orchard::get_orchard_nullifiers(self.conn.borrow(), query)
873 }
874
875 #[cfg(feature = "transparent-inputs")]
876 fn get_transparent_receivers(
877 &self,
878 account: Self::AccountId,
879 include_change: bool,
880 ) -> Result<HashMap<TransparentAddress, Option<TransparentAddressMetadata>>, Self::Error> {
881 let key_scopes: &[KeyScope] = if include_change {
882 &[KeyScope::EXTERNAL, KeyScope::INTERNAL]
883 } else {
884 &[KeyScope::EXTERNAL]
885 };
886
887 wallet::transparent::get_transparent_receivers(
888 self.conn.borrow(),
889 &self.params,
890 account,
891 key_scopes,
892 )
893 }
894
895 #[cfg(feature = "transparent-inputs")]
896 fn get_transparent_balances(
897 &self,
898 account: Self::AccountId,
899 max_height: BlockHeight,
900 ) -> Result<HashMap<TransparentAddress, Zatoshis>, Self::Error> {
901 wallet::transparent::get_transparent_balances(
902 self.conn.borrow(),
903 &self.params,
904 account,
905 max_height,
906 )
907 }
908
909 #[cfg(feature = "transparent-inputs")]
910 fn get_transparent_address_metadata(
911 &self,
912 account: Self::AccountId,
913 address: &TransparentAddress,
914 ) -> Result<Option<TransparentAddressMetadata>, Self::Error> {
915 wallet::transparent::get_transparent_address_metadata(
916 self.conn.borrow(),
917 &self.params,
918 account,
919 address,
920 )
921 }
922
923 #[cfg(feature = "transparent-inputs")]
924 fn utxo_query_height(&self, account: Self::AccountId) -> Result<BlockHeight, Self::Error> {
925 let account_ref = wallet::get_account_ref(self.conn.borrow(), account)?;
926 wallet::transparent::utxo_query_height(self.conn.borrow(), account_ref, &self.gap_limits)
927 }
928
929 #[cfg(feature = "transparent-inputs")]
930 fn get_known_ephemeral_addresses(
931 &self,
932 account: Self::AccountId,
933 index_range: Option<Range<NonHardenedChildIndex>>,
934 ) -> Result<Vec<(TransparentAddress, TransparentAddressMetadata)>, Self::Error> {
935 let account_id = wallet::get_account_ref(self.conn.borrow(), account)?;
936 wallet::transparent::ephemeral::get_known_ephemeral_addresses(
937 self.conn.borrow(),
938 &self.params,
939 account_id,
940 index_range,
941 )
942 }
943
944 #[cfg(feature = "transparent-inputs")]
945 fn find_account_for_ephemeral_address(
946 &self,
947 address: &TransparentAddress,
948 ) -> Result<Option<Self::AccountId>, Self::Error> {
949 wallet::transparent::ephemeral::find_account_for_ephemeral_address_str(
950 self.conn.borrow(),
951 &address.encode(&self.params),
952 )
953 }
954
955 fn transaction_data_requests(&self) -> Result<Vec<TransactionDataRequest>, Self::Error> {
956 let iter = wallet::transaction_data_requests(self.conn.borrow())?.into_iter();
957
958 #[cfg(feature = "transparent-inputs")]
959 let iter = iter.chain(wallet::transparent::transaction_data_requests(
960 self.conn.borrow(),
961 &self.params,
962 )?);
963
964 Ok(iter.collect())
965 }
966}
967
968#[cfg(any(test, feature = "test-dependencies"))]
969impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters, CL, R> WalletTest
970 for WalletDb<C, P, CL, R>
971{
972 fn get_tx_history(
973 &self,
974 ) -> Result<Vec<TransactionSummary<<Self as WalletRead>::AccountId>>, <Self as WalletRead>::Error>
975 {
976 wallet::testing::get_tx_history(self.conn.borrow())
977 }
978
979 fn get_sent_note_ids(
980 &self,
981 txid: &TxId,
982 protocol: ShieldedProtocol,
983 ) -> Result<Vec<NoteId>, <Self as WalletRead>::Error> {
984 use crate::wallet::encoding::pool_code;
985
986 let mut stmt_sent_notes = self.conn.borrow().prepare(
987 "SELECT output_index
988 FROM sent_notes
989 JOIN transactions ON transactions.id_tx = sent_notes.tx
990 WHERE transactions.txid = :txid
991 AND sent_notes.output_pool = :pool_code",
992 )?;
993
994 let note_ids = stmt_sent_notes
995 .query_map(
996 named_params! {
997 ":txid": txid.as_ref(),
998 ":pool_code": pool_code(PoolType::Shielded(protocol)),
999 },
1000 |row| Ok(NoteId::new(*txid, protocol, row.get(0)?)),
1001 )?
1002 .collect::<Result<_, _>>()?;
1003
1004 Ok(note_ids)
1005 }
1006
1007 fn get_sent_outputs(
1008 &self,
1009 txid: &TxId,
1010 ) -> Result<Vec<OutputOfSentTx>, <Self as WalletRead>::Error> {
1011 use zcash_protocol::value::Zatoshis;
1012
1013 let mut stmt_sent = self.conn.borrow().prepare(
1014 "SELECT value, to_address,
1015 a.cached_transparent_receiver_address, a.transparent_child_index
1016 FROM sent_notes
1017 JOIN transactions t ON t.id_tx = sent_notes.tx
1018 LEFT JOIN transparent_received_outputs tro ON tro.transaction_id = t.id_tx
1019 LEFT JOIN addresses a ON a.id = tro.address_id AND a.key_scope = :key_scope
1020 WHERE t.txid = :txid
1021 ORDER BY value",
1022 )?;
1023
1024 let sends = stmt_sent
1025 .query_map(
1026 named_params![
1027 ":txid": txid.as_ref(),
1028 ":key_scope": KeyScope::Ephemeral.encode()
1029 ],
1030 |row| {
1031 let v = row.get(0)?;
1032 let to_address = row
1033 .get::<_, Option<String>>(1)?
1034 .and_then(|s| Address::decode(&self.params, &s));
1035 let ephemeral_address = row
1036 .get::<_, Option<String>>(2)?
1037 .and_then(|s| Address::decode(&self.params, &s));
1038 let address_index: Option<u32> = row.get(3)?;
1039 Ok((v, to_address, ephemeral_address.zip(address_index)))
1040 },
1041 )?
1042 .map(|res| {
1043 let (amount, external_recipient, ephemeral_address) = res?;
1044 Ok::<_, <Self as WalletRead>::Error>(OutputOfSentTx::from_parts(
1045 Zatoshis::from_u64(amount)?,
1046 external_recipient,
1047 ephemeral_address,
1048 ))
1049 })
1050 .collect::<Result<_, _>>()?;
1051
1052 Ok(sends)
1053 }
1054
1055 fn get_checkpoint_history(
1056 &self,
1057 protocol: &ShieldedProtocol,
1058 ) -> Result<
1059 Vec<(BlockHeight, Option<incrementalmerkletree::Position>)>,
1060 <Self as WalletRead>::Error,
1061 > {
1062 wallet::testing::get_checkpoint_history(self.conn.borrow(), protocol)
1063 }
1064
1065 #[cfg(feature = "transparent-inputs")]
1066 fn get_transparent_output(
1067 &self,
1068 outpoint: &OutPoint,
1069 allow_unspendable: bool,
1070 ) -> Result<Option<WalletTransparentOutput>, <Self as InputSource>::Error> {
1071 wallet::transparent::get_wallet_transparent_output(
1072 self.conn.borrow(),
1073 outpoint,
1074 allow_unspendable,
1075 )
1076 }
1077
1078 fn get_notes(
1079 &self,
1080 protocol: ShieldedProtocol,
1081 ) -> Result<Vec<ReceivedNote<Self::NoteRef, Note>>, <Self as InputSource>::Error> {
1082 let (table_prefix, index_col, _) = wallet::common::per_protocol_names(protocol);
1083 let mut stmt_received_notes = self.conn.borrow().prepare(&format!(
1084 "SELECT txid, {index_col}
1085 FROM {table_prefix}_received_notes rn
1086 INNER JOIN transactions ON transactions.id_tx = rn.tx
1087 WHERE transactions.block IS NOT NULL
1088 AND recipient_key_scope IS NOT NULL
1089 AND nf IS NOT NULL
1090 AND commitment_tree_position IS NOT NULL"
1091 ))?;
1092
1093 let result = stmt_received_notes
1094 .query_map([], |row| {
1095 let txid: [u8; 32] = row.get(0)?;
1096 let output_index: u32 = row.get(1)?;
1097 let note = self
1098 .get_spendable_note(&TxId::from_bytes(txid), protocol, output_index)
1099 .unwrap()
1100 .unwrap();
1101 Ok(note)
1102 })?
1103 .collect::<Result<Vec<_>, _>>()?;
1104
1105 Ok(result)
1106 }
1107}
1108
1109impl<C: BorrowMut<rusqlite::Connection>, P: consensus::Parameters, CL: Clock, R> WalletWrite
1110 for WalletDb<C, P, CL, R>
1111{
1112 type UtxoRef = UtxoId;
1113
1114 fn create_account(
1115 &mut self,
1116 account_name: &str,
1117 seed: &SecretVec<u8>,
1118 birthday: &AccountBirthday,
1119 key_source: Option<&str>,
1120 ) -> Result<(Self::AccountId, UnifiedSpendingKey), Self::Error> {
1121 self.borrow_mut().transactionally(|wdb| {
1122 let seed_fingerprint =
1123 SeedFingerprint::from_seed(seed.expose_secret()).ok_or_else(|| {
1124 SqliteClientError::BadAccountData(
1125 "Seed must be between 32 and 252 bytes in length.".to_owned(),
1126 )
1127 })?;
1128 let zip32_account_index =
1129 wallet::max_zip32_account_index(wdb.conn.0, &seed_fingerprint)?
1130 .map(|a| {
1131 a.next()
1132 .ok_or(SqliteClientError::Zip32AccountIndexOutOfRange)
1133 })
1134 .transpose()?
1135 .unwrap_or(zip32::AccountId::ZERO);
1136
1137 let usk = UnifiedSpendingKey::from_seed(
1138 &wdb.params,
1139 seed.expose_secret(),
1140 zip32_account_index,
1141 )
1142 .map_err(|_| SqliteClientError::KeyDerivationError(zip32_account_index))?;
1143 let ufvk = usk.to_unified_full_viewing_key();
1144
1145 let account = wallet::add_account(
1146 wdb.conn.0,
1147 &wdb.params,
1148 account_name,
1149 &AccountSource::Derived {
1150 derivation: Zip32Derivation::new(seed_fingerprint, zip32_account_index),
1151 key_source: key_source.map(|s| s.to_owned()),
1152 },
1153 wallet::ViewingKey::Full(Box::new(ufvk)),
1154 birthday,
1155 #[cfg(feature = "transparent-inputs")]
1156 &wdb.gap_limits,
1157 )?;
1158
1159 Ok((account.id(), usk))
1160 })
1161 }
1162
1163 fn import_account_hd(
1164 &mut self,
1165 account_name: &str,
1166 seed: &SecretVec<u8>,
1167 account_index: zip32::AccountId,
1168 birthday: &AccountBirthday,
1169 key_source: Option<&str>,
1170 ) -> Result<(Self::Account, UnifiedSpendingKey), Self::Error> {
1171 self.transactionally(|wdb| {
1172 let seed_fingerprint =
1173 SeedFingerprint::from_seed(seed.expose_secret()).ok_or_else(|| {
1174 SqliteClientError::BadAccountData(
1175 "Seed must be between 32 and 252 bytes in length.".to_owned(),
1176 )
1177 })?;
1178
1179 let usk =
1180 UnifiedSpendingKey::from_seed(&wdb.params, seed.expose_secret(), account_index)
1181 .map_err(|_| SqliteClientError::KeyDerivationError(account_index))?;
1182 let ufvk = usk.to_unified_full_viewing_key();
1183
1184 let account = wallet::add_account(
1185 wdb.conn.0,
1186 &wdb.params,
1187 account_name,
1188 &AccountSource::Derived {
1189 derivation: Zip32Derivation::new(seed_fingerprint, account_index),
1190 key_source: key_source.map(|s| s.to_owned()),
1191 },
1192 wallet::ViewingKey::Full(Box::new(ufvk)),
1193 birthday,
1194 #[cfg(feature = "transparent-inputs")]
1195 &wdb.gap_limits,
1196 )?;
1197
1198 Ok((account, usk))
1199 })
1200 }
1201
1202 fn import_account_ufvk(
1203 &mut self,
1204 account_name: &str,
1205 ufvk: &UnifiedFullViewingKey,
1206 birthday: &AccountBirthday,
1207 purpose: AccountPurpose,
1208 key_source: Option<&str>,
1209 ) -> Result<Self::Account, Self::Error> {
1210 self.transactionally(|wdb| {
1211 wallet::add_account(
1212 wdb.conn.0,
1213 &wdb.params,
1214 account_name,
1215 &AccountSource::Imported {
1216 purpose,
1217 key_source: key_source.map(|s| s.to_owned()),
1218 },
1219 wallet::ViewingKey::Full(Box::new(ufvk.to_owned())),
1220 birthday,
1221 #[cfg(feature = "transparent-inputs")]
1222 &wdb.gap_limits,
1223 )
1224 })
1225 }
1226
1227 fn get_next_available_address(
1228 &mut self,
1229 account_uuid: Self::AccountId,
1230 request: UnifiedAddressRequest,
1231 ) -> Result<Option<(UnifiedAddress, DiversifierIndex)>, Self::Error> {
1232 self.transactionally(|wdb| {
1233 wallet::get_next_available_address(
1234 wdb.conn.0,
1235 &wdb.params,
1236 &wdb.clock,
1237 account_uuid,
1238 request,
1239 #[cfg(feature = "transparent-inputs")]
1240 &wdb.gap_limits,
1241 )
1242 })
1243 }
1244
1245 fn get_address_for_index(
1246 &mut self,
1247 account: Self::AccountId,
1248 diversifier_index: DiversifierIndex,
1249 request: UnifiedAddressRequest,
1250 ) -> Result<Option<UnifiedAddress>, Self::Error> {
1251 if let Some(account) = self.get_account(account)? {
1252 use zcash_keys::keys::AddressGenerationError::*;
1253
1254 match account.uivk().address(diversifier_index, request) {
1255 Ok(address) => {
1256 let chain_tip_height = wallet::chain_tip_height(self.conn.borrow())?;
1257 upsert_address(
1258 self.conn.borrow(),
1259 &self.params,
1260 account.internal_id(),
1261 diversifier_index,
1262 &address,
1263 Some(chain_tip_height.unwrap_or(account.birthday())),
1264 true,
1265 )?;
1266
1267 Ok(Some(address))
1268 }
1269 #[cfg(feature = "transparent-inputs")]
1270 Err(InvalidTransparentChildIndex(_)) => Ok(None),
1271 Err(InvalidSaplingDiversifierIndex(_)) => Ok(None),
1272 Err(e) => Err(SqliteClientError::AddressGeneration(e)),
1273 }
1274 } else {
1275 Err(SqliteClientError::AccountUnknown)
1276 }
1277 }
1278
1279 fn update_chain_tip(&mut self, tip_height: BlockHeight) -> Result<(), Self::Error> {
1280 let tx = self.conn.borrow_mut().transaction()?;
1281 wallet::scanning::update_chain_tip(&tx, &self.params, tip_height)?;
1282 tx.commit()?;
1283 Ok(())
1284 }
1285
1286 #[tracing::instrument(skip_all, fields(height = blocks.first().map(|b| u32::from(b.height())), count = blocks.len()))]
1287 #[allow(clippy::type_complexity)]
1288 fn put_blocks(
1289 &mut self,
1290 from_state: &ChainState,
1291 blocks: Vec<ScannedBlock<Self::AccountId>>,
1292 ) -> Result<(), Self::Error> {
1293 struct BlockPositions {
1294 height: BlockHeight,
1295 sapling_start_position: Position,
1296 #[cfg(feature = "orchard")]
1297 orchard_start_position: Position,
1298 }
1299
1300 if blocks.is_empty() {
1301 return Ok(());
1302 }
1303
1304 self.transactionally(|wdb| {
1305 let initial_block = blocks.first().expect("blocks is known to be nonempty");
1306 assert!(from_state.block_height() + 1 == initial_block.height());
1307
1308 let start_positions = BlockPositions {
1309 height: initial_block.height(),
1310 sapling_start_position: Position::from(
1311 u64::from(initial_block.sapling().final_tree_size())
1312 - u64::try_from(initial_block.sapling().commitments().len()).unwrap(),
1313 ),
1314 #[cfg(feature = "orchard")]
1315 orchard_start_position: Position::from(
1316 u64::from(initial_block.orchard().final_tree_size())
1317 - u64::try_from(initial_block.orchard().commitments().len()).unwrap(),
1318 ),
1319 };
1320
1321 let mut sapling_commitments = vec![];
1322 #[cfg(feature = "orchard")]
1323 let mut orchard_commitments = vec![];
1324 let mut last_scanned_height = None;
1325 let mut note_positions = vec![];
1326
1327 #[cfg(feature = "transparent-inputs")]
1328 let mut tx_refs = BTreeSet::new();
1329
1330 for block in blocks.into_iter() {
1331 if last_scanned_height
1332 .iter()
1333 .any(|prev| block.height() != *prev + 1)
1334 {
1335 return Err(SqliteClientError::NonSequentialBlocks);
1336 }
1337
1338 wallet::put_block(
1340 wdb.conn.0,
1341 block.height(),
1342 block.block_hash(),
1343 block.block_time(),
1344 block.sapling().final_tree_size(),
1345 block.sapling().commitments().len().try_into().unwrap(),
1346 #[cfg(feature = "orchard")]
1347 block.orchard().final_tree_size(),
1348 #[cfg(feature = "orchard")]
1349 block.orchard().commitments().len().try_into().unwrap(),
1350 )?;
1351
1352 for tx in block.transactions() {
1353 let tx_ref = wallet::put_tx_meta(wdb.conn.0, tx, block.height())?;
1354
1355 #[cfg(feature = "transparent-inputs")]
1356 tx_refs.insert(tx_ref);
1357
1358 wallet::queue_tx_retrieval(wdb.conn.0, std::iter::once(tx.txid()), None)?;
1359
1360 for spend in tx.sapling_spends() {
1362 wallet::sapling::mark_sapling_note_spent(wdb.conn.0, tx_ref, spend.nf())?;
1363 }
1364 #[cfg(feature = "orchard")]
1365 for spend in tx.orchard_spends() {
1366 wallet::orchard::mark_orchard_note_spent(wdb.conn.0, tx_ref, spend.nf())?;
1367 }
1368
1369 for output in tx.sapling_outputs() {
1370 let spent_in = output
1373 .nf()
1374 .map(|nf| {
1375 wallet::query_nullifier_map(
1376 wdb.conn.0,
1377 ShieldedProtocol::Sapling,
1378 nf,
1379 )
1380 })
1381 .transpose()?
1382 .flatten();
1383
1384 wallet::sapling::put_received_note(
1385 wdb.conn.0,
1386 &wdb.params,
1387 output,
1388 tx_ref,
1389 Some(block.height()),
1390 spent_in,
1391 )?;
1392 }
1393 #[cfg(feature = "orchard")]
1394 for output in tx.orchard_outputs() {
1395 let spent_in = output
1398 .nf()
1399 .map(|nf| {
1400 wallet::query_nullifier_map(
1401 wdb.conn.0,
1402 ShieldedProtocol::Orchard,
1403 &nf.to_bytes(),
1404 )
1405 })
1406 .transpose()?
1407 .flatten();
1408
1409 wallet::orchard::put_received_note(
1410 wdb.conn.0,
1411 &wdb.params,
1412 output,
1413 tx_ref,
1414 Some(block.height()),
1415 spent_in,
1416 )?;
1417 }
1418 }
1419
1420 wallet::insert_nullifier_map(
1422 wdb.conn.0,
1423 block.height(),
1424 ShieldedProtocol::Sapling,
1425 block.sapling().nullifier_map(),
1426 )?;
1427 #[cfg(feature = "orchard")]
1428 wallet::insert_nullifier_map(
1429 wdb.conn.0,
1430 block.height(),
1431 ShieldedProtocol::Orchard,
1432 &block
1433 .orchard()
1434 .nullifier_map()
1435 .iter()
1436 .map(|(txid, idx, nfs)| {
1437 (*txid, *idx, nfs.iter().map(|nf| nf.to_bytes()).collect())
1438 })
1439 .collect::<Vec<_>>(),
1440 )?;
1441
1442 note_positions.extend(block.transactions().iter().flat_map(|wtx| {
1443 let iter = wtx.sapling_outputs().iter().map(|out| {
1444 (
1445 ShieldedProtocol::Sapling,
1446 out.note_commitment_tree_position(),
1447 )
1448 });
1449 #[cfg(feature = "orchard")]
1450 let iter = iter.chain(wtx.orchard_outputs().iter().map(|out| {
1451 (
1452 ShieldedProtocol::Orchard,
1453 out.note_commitment_tree_position(),
1454 )
1455 }));
1456
1457 iter
1458 }));
1459
1460 last_scanned_height = Some(block.height());
1461 let block_commitments = block.into_commitments();
1462 trace!(
1463 "Sapling commitments for {:?}: {:?}",
1464 last_scanned_height,
1465 block_commitments
1466 .sapling
1467 .iter()
1468 .map(|(_, r)| *r)
1469 .collect::<Vec<_>>()
1470 );
1471 #[cfg(feature = "orchard")]
1472 trace!(
1473 "Orchard commitments for {:?}: {:?}",
1474 last_scanned_height,
1475 block_commitments
1476 .orchard
1477 .iter()
1478 .map(|(_, r)| *r)
1479 .collect::<Vec<_>>()
1480 );
1481
1482 sapling_commitments.extend(block_commitments.sapling.into_iter().map(Some));
1483 #[cfg(feature = "orchard")]
1484 orchard_commitments.extend(block_commitments.orchard.into_iter().map(Some));
1485 }
1486
1487 #[cfg(feature = "transparent-inputs")]
1488 for (account_id, key_scope) in wallet::involved_accounts(wdb.conn.0, tx_refs)? {
1489 use ReceiverRequirement::*;
1490 wallet::transparent::generate_gap_addresses(
1491 wdb.conn.0,
1492 &wdb.params,
1493 account_id,
1494 key_scope,
1495 &wdb.gap_limits,
1496 UnifiedAddressRequest::unsafe_custom(Allow, Allow, Require),
1497 false,
1498 )?;
1499 }
1500
1501 if let Some(meta) = wdb.block_fully_scanned()? {
1503 wallet::prune_nullifier_map(
1504 wdb.conn.0,
1505 meta.block_height().saturating_sub(PRUNING_DEPTH),
1506 )?;
1507 }
1508
1509 if let Some(last_scanned_height) = last_scanned_height {
1512 const CHUNK_SIZE: usize = 1024;
1514 let sapling_subtrees = sapling_commitments
1515 .par_chunks_mut(CHUNK_SIZE)
1516 .enumerate()
1517 .filter_map(|(i, chunk)| {
1518 let start =
1519 start_positions.sapling_start_position + (i * CHUNK_SIZE) as u64;
1520 let end = start + chunk.len() as u64;
1521
1522 shardtree::LocatedTree::from_iter(
1523 start..end,
1524 SAPLING_SHARD_HEIGHT.into(),
1525 chunk.iter_mut().map(|n| n.take().expect("always Some")),
1526 )
1527 })
1528 .map(|res| (res.subtree, res.checkpoints))
1529 .collect::<Vec<_>>();
1530
1531 #[cfg(feature = "orchard")]
1532 let orchard_subtrees = orchard_commitments
1533 .par_chunks_mut(CHUNK_SIZE)
1534 .enumerate()
1535 .filter_map(|(i, chunk)| {
1536 let start =
1537 start_positions.orchard_start_position + (i * CHUNK_SIZE) as u64;
1538 let end = start + chunk.len() as u64;
1539
1540 shardtree::LocatedTree::from_iter(
1541 start..end,
1542 ORCHARD_SHARD_HEIGHT.into(),
1543 chunk.iter_mut().map(|n| n.take().expect("always Some")),
1544 )
1545 })
1546 .map(|res| (res.subtree, res.checkpoints))
1547 .collect::<Vec<_>>();
1548
1549 #[cfg(feature = "orchard")]
1551 let sapling_checkpoint_positions: BTreeMap<BlockHeight, Position> =
1552 sapling_subtrees
1553 .iter()
1554 .flat_map(|(_, checkpoints)| checkpoints.iter())
1555 .map(|(k, v)| (*k, *v))
1556 .collect();
1557
1558 #[cfg(feature = "orchard")]
1559 let orchard_checkpoint_positions: BTreeMap<BlockHeight, Position> =
1560 orchard_subtrees
1561 .iter()
1562 .flat_map(|(_, checkpoints)| checkpoints.iter())
1563 .map(|(k, v)| (*k, *v))
1564 .collect();
1565
1566 #[cfg(feature = "orchard")]
1567 fn ensure_checkpoints<
1568 'a,
1569 H,
1570 I: Iterator<Item = &'a BlockHeight>,
1571 const DEPTH: u8,
1572 >(
1573 ensure_heights: I,
1576 existing_checkpoint_positions: &BTreeMap<BlockHeight, Position>,
1579 state_final_tree: &Frontier<H, DEPTH>,
1582 ) -> Vec<(BlockHeight, Checkpoint)> {
1583 ensure_heights
1584 .flat_map(|ensure_height| {
1585 existing_checkpoint_positions
1586 .range::<BlockHeight, _>(..=*ensure_height)
1587 .last()
1588 .map_or_else(
1589 || {
1590 Some((
1591 *ensure_height,
1592 state_final_tree
1593 .value()
1594 .map_or_else(Checkpoint::tree_empty, |t| {
1595 Checkpoint::at_position(t.position())
1596 }),
1597 ))
1598 },
1599 |(existing_checkpoint_height, position)| {
1600 if *existing_checkpoint_height < *ensure_height {
1601 Some((
1602 *ensure_height,
1603 Checkpoint::at_position(*position),
1604 ))
1605 } else {
1606 None
1609 }
1610 },
1611 )
1612 .into_iter()
1613 })
1614 .collect::<Vec<_>>()
1615 }
1616
1617 #[cfg(feature = "orchard")]
1618 let (missing_sapling_checkpoints, missing_orchard_checkpoints) = (
1619 ensure_checkpoints(
1620 orchard_checkpoint_positions.keys(),
1621 &sapling_checkpoint_positions,
1622 from_state.final_sapling_tree(),
1623 ),
1624 ensure_checkpoints(
1625 sapling_checkpoint_positions.keys(),
1626 &orchard_checkpoint_positions,
1627 from_state.final_orchard_tree(),
1628 ),
1629 );
1630
1631 {
1633 let mut sapling_subtrees_iter = sapling_subtrees.into_iter();
1634 wdb.with_sapling_tree_mut::<_, _, Self::Error>(|sapling_tree| {
1635 debug!(
1636 "Sapling initial tree size at {:?}: {:?}",
1637 from_state.block_height(),
1638 from_state.final_sapling_tree().tree_size()
1639 );
1640 sapling_tree.insert_frontier(
1643 from_state.final_sapling_tree().clone(),
1644 Retention::Checkpoint {
1645 id: from_state.block_height(),
1646 marking: Marking::Reference,
1647 },
1648 )?;
1649
1650 for (tree, checkpoints) in &mut sapling_subtrees_iter {
1651 sapling_tree.insert_tree(tree, checkpoints)?;
1652 }
1653
1654 #[cfg(feature = "orchard")]
1658 {
1659 let min_checkpoint_height = sapling_tree
1660 .store()
1661 .min_checkpoint_id()
1662 .map_err(ShardTreeError::Storage)?
1663 .expect(
1664 "At least one checkpoint was inserted (by insert_frontier)",
1665 );
1666
1667 for (height, checkpoint) in &missing_sapling_checkpoints {
1668 if *height > min_checkpoint_height {
1669 sapling_tree
1670 .store_mut()
1671 .add_checkpoint(*height, checkpoint.clone())
1672 .map_err(ShardTreeError::Storage)?;
1673 }
1674 }
1675 }
1676
1677 Ok(())
1678 })?;
1679 }
1680
1681 #[cfg(feature = "orchard")]
1683 {
1684 let mut orchard_subtrees = orchard_subtrees.into_iter();
1685 wdb.with_orchard_tree_mut::<_, _, Self::Error>(|orchard_tree| {
1686 debug!(
1687 "Orchard initial tree size at {:?}: {:?}",
1688 from_state.block_height(),
1689 from_state.final_orchard_tree().tree_size()
1690 );
1691 orchard_tree.insert_frontier(
1694 from_state.final_orchard_tree().clone(),
1695 Retention::Checkpoint {
1696 id: from_state.block_height(),
1697 marking: Marking::Reference,
1698 },
1699 )?;
1700
1701 for (tree, checkpoints) in &mut orchard_subtrees {
1702 orchard_tree.insert_tree(tree, checkpoints)?;
1703 }
1704
1705 {
1709 let min_checkpoint_height = orchard_tree
1710 .store()
1711 .min_checkpoint_id()
1712 .map_err(ShardTreeError::Storage)?
1713 .expect(
1714 "At least one checkpoint was inserted (by insert_frontier)",
1715 );
1716
1717 for (height, checkpoint) in &missing_orchard_checkpoints {
1718 if *height > min_checkpoint_height {
1719 debug!(
1720 "Adding missing Orchard checkpoint for height: {:?}: {:?}",
1721 height,
1722 checkpoint.position()
1723 );
1724 orchard_tree
1725 .store_mut()
1726 .add_checkpoint(*height, checkpoint.clone())
1727 .map_err(ShardTreeError::Storage)?;
1728 }
1729 }
1730 }
1731 Ok(())
1732 })?;
1733 }
1734
1735 wallet::scanning::scan_complete(
1736 wdb.conn.0,
1737 &wdb.params,
1738 Range {
1739 start: start_positions.height,
1740 end: last_scanned_height + 1,
1741 },
1742 ¬e_positions,
1743 )?;
1744 }
1745
1746 Ok(())
1747 })
1748 }
1749
1750 fn put_received_transparent_utxo(
1751 &mut self,
1752 _output: &WalletTransparentOutput,
1753 ) -> Result<Self::UtxoRef, Self::Error> {
1754 #[cfg(feature = "transparent-inputs")]
1755 return self.transactionally(|wdb| {
1756 let (account_id, key_scope, utxo_id) =
1757 wallet::transparent::put_received_transparent_utxo(
1758 wdb.conn.0,
1759 &wdb.params,
1760 _output,
1761 )?;
1762
1763 use ReceiverRequirement::*;
1764 wallet::transparent::generate_gap_addresses(
1765 wdb.conn.0,
1766 &wdb.params,
1767 account_id,
1768 key_scope,
1769 &wdb.gap_limits,
1770 UnifiedAddressRequest::unsafe_custom(Allow, Allow, Require),
1771 true,
1772 )?;
1773
1774 Ok(utxo_id)
1775 });
1776
1777 #[cfg(not(feature = "transparent-inputs"))]
1778 panic!(
1779 "The wallet must be compiled with the transparent-inputs feature to use this method."
1780 );
1781 }
1782
1783 fn store_decrypted_tx(
1784 &mut self,
1785 d_tx: DecryptedTransaction<Self::AccountId>,
1786 ) -> Result<(), Self::Error> {
1787 self.transactionally(|wdb| {
1788 wallet::store_decrypted_tx(
1789 wdb.conn.0,
1790 &wdb.params,
1791 d_tx,
1792 #[cfg(feature = "transparent-inputs")]
1793 &wdb.gap_limits,
1794 )
1795 })
1796 }
1797
1798 fn store_transactions_to_be_sent(
1799 &mut self,
1800 transactions: &[SentTransaction<Self::AccountId>],
1801 ) -> Result<(), Self::Error> {
1802 self.transactionally(|wdb| {
1803 for sent_tx in transactions {
1804 wallet::store_transaction_to_be_sent(wdb.conn.0, &wdb.params, sent_tx)?;
1805 }
1806 Ok(())
1807 })
1808 }
1809
1810 fn truncate_to_height(&mut self, max_height: BlockHeight) -> Result<BlockHeight, Self::Error> {
1811 self.transactionally(|wdb| {
1812 wallet::truncate_to_height(
1813 wdb.conn.0,
1814 &wdb.params,
1815 #[cfg(feature = "transparent-inputs")]
1816 &wdb.gap_limits,
1817 max_height,
1818 )
1819 })
1820 }
1821
1822 #[cfg(feature = "transparent-inputs")]
1823 fn reserve_next_n_ephemeral_addresses(
1824 &mut self,
1825 account_id: Self::AccountId,
1826 n: usize,
1827 ) -> Result<Vec<(TransparentAddress, TransparentAddressMetadata)>, Self::Error> {
1828 self.transactionally(|wdb| {
1829 let account_id = wallet::get_account_ref(wdb.conn.0, account_id)?;
1830 let reserved = wallet::transparent::reserve_next_n_addresses(
1831 wdb.conn.0,
1832 &wdb.params,
1833 account_id,
1834 KeyScope::Ephemeral,
1835 wdb.gap_limits.ephemeral(),
1836 n,
1837 )?;
1838
1839 Ok(reserved.into_iter().map(|(_, a, m)| (a, m)).collect())
1840 })
1841 }
1842
1843 fn set_transaction_status(
1844 &mut self,
1845 txid: TxId,
1846 status: data_api::TransactionStatus,
1847 ) -> Result<(), Self::Error> {
1848 self.transactionally(|wdb| wallet::set_transaction_status(wdb.conn.0, txid, status))
1849 }
1850}
1851
1852pub(crate) type SaplingShardStore<C> = SqliteShardStore<C, sapling::Node, SAPLING_SHARD_HEIGHT>;
1853pub(crate) type SaplingCommitmentTree<C> =
1854 ShardTree<SaplingShardStore<C>, { sapling::NOTE_COMMITMENT_TREE_DEPTH }, SAPLING_SHARD_HEIGHT>;
1855
1856pub(crate) fn sapling_tree<C>(
1857 conn: C,
1858) -> Result<SaplingCommitmentTree<C>, ShardTreeError<commitment_tree::Error>>
1859where
1860 SaplingShardStore<C>: ShardStore<H = sapling::Node, CheckpointId = BlockHeight>,
1861{
1862 Ok(ShardTree::new(
1863 SqliteShardStore::from_connection(conn, SAPLING_TABLES_PREFIX)
1864 .map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?,
1865 PRUNING_DEPTH.try_into().unwrap(),
1866 ))
1867}
1868
1869#[cfg(feature = "orchard")]
1870pub(crate) type OrchardShardStore<C> =
1871 SqliteShardStore<C, orchard::tree::MerkleHashOrchard, ORCHARD_SHARD_HEIGHT>;
1872
1873#[cfg(feature = "orchard")]
1874pub(crate) type OrchardCommitmentTree<C> = ShardTree<
1875 OrchardShardStore<C>,
1876 { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 },
1877 ORCHARD_SHARD_HEIGHT,
1878>;
1879
1880#[cfg(feature = "orchard")]
1881pub(crate) fn orchard_tree<C>(
1882 conn: C,
1883) -> Result<OrchardCommitmentTree<C>, ShardTreeError<commitment_tree::Error>>
1884where
1885 OrchardShardStore<C>:
1886 ShardStore<H = orchard::tree::MerkleHashOrchard, CheckpointId = BlockHeight>,
1887{
1888 Ok(ShardTree::new(
1889 SqliteShardStore::from_connection(conn, ORCHARD_TABLES_PREFIX)
1890 .map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?,
1891 PRUNING_DEPTH.try_into().unwrap(),
1892 ))
1893}
1894
1895impl<C: BorrowMut<rusqlite::Connection>, P: consensus::Parameters, CL, R> WalletCommitmentTrees
1896 for WalletDb<C, P, CL, R>
1897{
1898 type Error = commitment_tree::Error;
1899 type SaplingShardStore<'a> = SaplingShardStore<&'a rusqlite::Transaction<'a>>;
1900
1901 fn with_sapling_tree_mut<F, A, E>(&mut self, mut callback: F) -> Result<A, E>
1902 where
1903 for<'a> F:
1904 FnMut(&'a mut SaplingCommitmentTree<&'a rusqlite::Transaction<'a>>) -> Result<A, E>,
1905 E: From<ShardTreeError<Self::Error>>,
1906 {
1907 let tx = self
1908 .conn
1909 .borrow_mut()
1910 .transaction()
1911 .map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
1912 let result = {
1913 let mut shardtree = sapling_tree(&tx)?;
1914 callback(&mut shardtree)?
1915 };
1916
1917 tx.commit()
1918 .map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
1919 Ok(result)
1920 }
1921
1922 fn put_sapling_subtree_roots(
1923 &mut self,
1924 start_index: u64,
1925 roots: &[CommitmentTreeRoot<sapling::Node>],
1926 ) -> Result<(), ShardTreeError<Self::Error>> {
1927 let tx = self
1928 .conn
1929 .borrow_mut()
1930 .transaction()
1931 .map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
1932 put_shard_roots::<_, { sapling::NOTE_COMMITMENT_TREE_DEPTH }, SAPLING_SHARD_HEIGHT>(
1933 &tx,
1934 SAPLING_TABLES_PREFIX,
1935 start_index,
1936 roots,
1937 )?;
1938 tx.commit()
1939 .map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
1940 Ok(())
1941 }
1942
1943 #[cfg(feature = "orchard")]
1944 type OrchardShardStore<'a> = SqliteShardStore<
1945 &'a rusqlite::Transaction<'a>,
1946 orchard::tree::MerkleHashOrchard,
1947 ORCHARD_SHARD_HEIGHT,
1948 >;
1949
1950 #[cfg(feature = "orchard")]
1951 fn with_orchard_tree_mut<F, A, E>(&mut self, mut callback: F) -> Result<A, E>
1952 where
1953 for<'a> F:
1954 FnMut(&'a mut OrchardCommitmentTree<&'a rusqlite::Transaction<'a>>) -> Result<A, E>,
1955 E: From<ShardTreeError<Self::Error>>,
1956 {
1957 let tx = self
1958 .conn
1959 .borrow_mut()
1960 .transaction()
1961 .map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
1962 let result = {
1963 let mut shardtree = orchard_tree(&tx)?;
1964 callback(&mut shardtree)?
1965 };
1966
1967 tx.commit()
1968 .map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
1969 Ok(result)
1970 }
1971
1972 #[cfg(feature = "orchard")]
1973 fn put_orchard_subtree_roots(
1974 &mut self,
1975 start_index: u64,
1976 roots: &[CommitmentTreeRoot<orchard::tree::MerkleHashOrchard>],
1977 ) -> Result<(), ShardTreeError<Self::Error>> {
1978 let tx = self
1979 .conn
1980 .borrow_mut()
1981 .transaction()
1982 .map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
1983 put_shard_roots::<_, { ORCHARD_SHARD_HEIGHT * 2 }, ORCHARD_SHARD_HEIGHT>(
1984 &tx,
1985 ORCHARD_TABLES_PREFIX,
1986 start_index,
1987 roots,
1988 )?;
1989 tx.commit()
1990 .map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
1991 Ok(())
1992 }
1993}
1994
1995impl<P: consensus::Parameters, CL, R> WalletCommitmentTrees
1996 for WalletDb<SqlTransaction<'_>, P, CL, R>
1997{
1998 type Error = commitment_tree::Error;
1999 type SaplingShardStore<'a> = crate::SaplingShardStore<&'a rusqlite::Transaction<'a>>;
2000
2001 fn with_sapling_tree_mut<F, A, E>(&mut self, mut callback: F) -> Result<A, E>
2002 where
2003 for<'a> F:
2004 FnMut(&'a mut SaplingCommitmentTree<&'a rusqlite::Transaction<'a>>) -> Result<A, E>,
2005 E: From<ShardTreeError<commitment_tree::Error>>,
2006 {
2007 let mut shardtree = sapling_tree(self.conn.0)?;
2008 let result = callback(&mut shardtree)?;
2009
2010 Ok(result)
2011 }
2012
2013 fn put_sapling_subtree_roots(
2014 &mut self,
2015 start_index: u64,
2016 roots: &[CommitmentTreeRoot<sapling::Node>],
2017 ) -> Result<(), ShardTreeError<Self::Error>> {
2018 put_shard_roots::<_, { sapling::NOTE_COMMITMENT_TREE_DEPTH }, SAPLING_SHARD_HEIGHT>(
2019 self.conn.0,
2020 SAPLING_TABLES_PREFIX,
2021 start_index,
2022 roots,
2023 )
2024 }
2025
2026 #[cfg(feature = "orchard")]
2027 type OrchardShardStore<'a> = crate::OrchardShardStore<&'a rusqlite::Transaction<'a>>;
2028
2029 #[cfg(feature = "orchard")]
2030 fn with_orchard_tree_mut<F, A, E>(&mut self, mut callback: F) -> Result<A, E>
2031 where
2032 for<'a> F:
2033 FnMut(&'a mut OrchardCommitmentTree<&'a rusqlite::Transaction<'a>>) -> Result<A, E>,
2034 E: From<ShardTreeError<Self::Error>>,
2035 {
2036 let mut shardtree = orchard_tree(self.conn.0)?;
2037 let result = callback(&mut shardtree)?;
2038
2039 Ok(result)
2040 }
2041
2042 #[cfg(feature = "orchard")]
2043 fn put_orchard_subtree_roots(
2044 &mut self,
2045 start_index: u64,
2046 roots: &[CommitmentTreeRoot<orchard::tree::MerkleHashOrchard>],
2047 ) -> Result<(), ShardTreeError<Self::Error>> {
2048 put_shard_roots::<_, { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }, ORCHARD_SHARD_HEIGHT>(
2049 self.conn.0,
2050 ORCHARD_TABLES_PREFIX,
2051 start_index,
2052 roots,
2053 )
2054 }
2055}
2056
2057pub struct BlockDb(Connection);
2059
2060impl BlockDb {
2061 pub fn for_path<P: AsRef<Path>>(path: P) -> Result<Self, rusqlite::Error> {
2063 Connection::open(path).map(BlockDb)
2064 }
2065}
2066
2067impl BlockSource for BlockDb {
2068 type Error = SqliteClientError;
2069
2070 fn with_blocks<F, DbErrT>(
2071 &self,
2072 from_height: Option<BlockHeight>,
2073 limit: Option<usize>,
2074 with_row: F,
2075 ) -> Result<(), data_api::chain::error::Error<DbErrT, Self::Error>>
2076 where
2077 F: FnMut(CompactBlock) -> Result<(), data_api::chain::error::Error<DbErrT, Self::Error>>,
2078 {
2079 chain::blockdb_with_blocks(self, from_height, limit, with_row)
2080 }
2081}
2082
2083#[cfg(feature = "unstable")]
2122pub struct FsBlockDb {
2123 conn: Connection,
2124 blocks_dir: PathBuf,
2125}
2126
2127#[derive(Debug)]
2130#[cfg(feature = "unstable")]
2131pub enum FsBlockDbError {
2132 Fs(io::Error),
2133 Db(rusqlite::Error),
2134 Protobuf(prost::DecodeError),
2135 MissingBlockPath(PathBuf),
2136 InvalidBlockstoreRoot(PathBuf),
2137 InvalidBlockPath(PathBuf),
2138 CorruptedData(String),
2139 CacheMiss(BlockHeight),
2140}
2141
2142#[cfg(feature = "unstable")]
2143impl From<io::Error> for FsBlockDbError {
2144 fn from(err: io::Error) -> Self {
2145 FsBlockDbError::Fs(err)
2146 }
2147}
2148
2149#[cfg(feature = "unstable")]
2150impl From<rusqlite::Error> for FsBlockDbError {
2151 fn from(err: rusqlite::Error) -> Self {
2152 FsBlockDbError::Db(err)
2153 }
2154}
2155
2156#[cfg(feature = "unstable")]
2157impl From<prost::DecodeError> for FsBlockDbError {
2158 fn from(e: prost::DecodeError) -> Self {
2159 FsBlockDbError::Protobuf(e)
2160 }
2161}
2162
2163#[cfg(feature = "unstable")]
2164impl FsBlockDb {
2165 pub fn for_path<P: AsRef<Path>>(fsblockdb_root: P) -> Result<Self, FsBlockDbError> {
2177 let meta = fs::metadata(&fsblockdb_root).map_err(FsBlockDbError::Fs)?;
2178 if meta.is_dir() {
2179 let db_path = fsblockdb_root.as_ref().join("blockmeta.sqlite");
2180 let blocks_dir = fsblockdb_root.as_ref().join("blocks");
2181 fs::create_dir_all(&blocks_dir)?;
2182 Ok(FsBlockDb {
2183 conn: Connection::open(db_path).map_err(FsBlockDbError::Db)?,
2184 blocks_dir,
2185 })
2186 } else {
2187 Err(FsBlockDbError::InvalidBlockstoreRoot(
2188 fsblockdb_root.as_ref().to_path_buf(),
2189 ))
2190 }
2191 }
2192
2193 pub fn get_max_cached_height(&self) -> Result<Option<BlockHeight>, FsBlockDbError> {
2195 Ok(chain::blockmetadb_get_max_cached_height(&self.conn)?)
2196 }
2197
2198 pub fn write_block_metadata(&self, block_meta: &[BlockMeta]) -> Result<(), FsBlockDbError> {
2204 for m in block_meta {
2205 let block_path = m.block_file_path(&self.blocks_dir);
2206 match fs::metadata(&block_path) {
2207 Err(e) => {
2208 return Err(match e.kind() {
2209 io::ErrorKind::NotFound => FsBlockDbError::MissingBlockPath(block_path),
2210 _ => FsBlockDbError::Fs(e),
2211 });
2212 }
2213 Ok(meta) => {
2214 if !meta.is_file() {
2215 return Err(FsBlockDbError::InvalidBlockPath(block_path));
2216 }
2217 }
2218 }
2219 }
2220
2221 Ok(chain::blockmetadb_insert(&self.conn, block_meta)?)
2222 }
2223
2224 pub fn find_block(&self, height: BlockHeight) -> Result<Option<BlockMeta>, FsBlockDbError> {
2227 Ok(chain::blockmetadb_find_block(&self.conn, height)?)
2228 }
2229
2230 pub fn truncate_to_height(&self, block_height: BlockHeight) -> Result<(), FsBlockDbError> {
2239 Ok(chain::blockmetadb_truncate_to_height(
2240 &self.conn,
2241 block_height,
2242 )?)
2243 }
2244}
2245
2246#[cfg(feature = "unstable")]
2247impl BlockSource for FsBlockDb {
2248 type Error = FsBlockDbError;
2249
2250 fn with_blocks<F, DbErrT>(
2251 &self,
2252 from_height: Option<BlockHeight>,
2253 limit: Option<usize>,
2254 with_row: F,
2255 ) -> Result<(), data_api::chain::error::Error<DbErrT, Self::Error>>
2256 where
2257 F: FnMut(CompactBlock) -> Result<(), data_api::chain::error::Error<DbErrT, Self::Error>>,
2258 {
2259 fsblockdb_with_blocks(self, from_height, limit, with_row)
2260 }
2261}
2262
2263#[cfg(feature = "unstable")]
2264impl std::fmt::Display for FsBlockDbError {
2265 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
2266 match self {
2267 FsBlockDbError::Fs(io_error) => {
2268 write!(f, "Failed to access the file system: {}", io_error)
2269 }
2270 FsBlockDbError::Db(e) => {
2271 write!(f, "There was a problem with the sqlite db: {}", e)
2272 }
2273 FsBlockDbError::Protobuf(e) => {
2274 write!(f, "Failed to parse protobuf-encoded record: {}", e)
2275 }
2276 FsBlockDbError::MissingBlockPath(block_path) => {
2277 write!(
2278 f,
2279 "CompactBlock file expected but not found at {}",
2280 block_path.display(),
2281 )
2282 }
2283 FsBlockDbError::InvalidBlockstoreRoot(fsblockdb_root) => {
2284 write!(
2285 f,
2286 "The block storage root {} is not a directory",
2287 fsblockdb_root.display(),
2288 )
2289 }
2290 FsBlockDbError::InvalidBlockPath(block_path) => {
2291 write!(
2292 f,
2293 "CompactBlock path {} is not a file",
2294 block_path.display(),
2295 )
2296 }
2297 FsBlockDbError::CorruptedData(e) => {
2298 write!(
2299 f,
2300 "The block cache has corrupted data and this caused an error: {}",
2301 e,
2302 )
2303 }
2304 FsBlockDbError::CacheMiss(height) => {
2305 write!(
2306 f,
2307 "Requested height {} does not exist in the block cache",
2308 height
2309 )
2310 }
2311 }
2312 }
2313}
2314
2315#[cfg(test)]
2316#[macro_use]
2317extern crate assert_matches;
2318
2319#[cfg(test)]
2320mod tests {
2321 use std::time::{Duration, SystemTime};
2322
2323 use secrecy::{ExposeSecret, Secret, SecretVec};
2324 use uuid::Uuid;
2325 use zcash_client_backend::data_api::{
2326 chain::ChainState,
2327 testing::{TestBuilder, TestState},
2328 Account, AccountBirthday, AccountPurpose, AccountSource, WalletRead, WalletTest,
2329 WalletWrite,
2330 };
2331 use zcash_keys::keys::{UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey};
2332 use zcash_primitives::block::BlockHash;
2333 use zcash_protocol::consensus;
2334
2335 use crate::{
2336 error::SqliteClientError, testing::db::TestDbFactory, util::Clock as _,
2337 wallet::MIN_SHIELDED_DIVERSIFIER_OFFSET, AccountUuid,
2338 };
2339
2340 #[cfg(feature = "unstable")]
2341 use zcash_keys::keys::sapling;
2342
2343 #[test]
2344 fn validate_seed() {
2345 let st = TestBuilder::new()
2346 .with_data_store_factory(TestDbFactory::default())
2347 .with_account_from_sapling_activation(BlockHash([0; 32]))
2348 .build();
2349 let account = st.test_account().unwrap();
2350
2351 assert!({
2352 st.wallet()
2353 .validate_seed(account.id(), st.test_seed().unwrap())
2354 .unwrap()
2355 });
2356
2357 assert!({
2359 let wrong_account_uuid = AccountUuid(Uuid::nil());
2360 !st.wallet()
2361 .validate_seed(wrong_account_uuid, st.test_seed().unwrap())
2362 .unwrap()
2363 });
2364
2365 assert!({
2367 !st.wallet()
2368 .validate_seed(account.id(), &SecretVec::new(vec![1u8; 32]))
2369 .unwrap()
2370 });
2371 }
2372
2373 #[test]
2374 pub(crate) fn get_next_available_address() {
2375 let mut st = TestBuilder::new()
2376 .with_data_store_factory(TestDbFactory::default())
2377 .with_account_from_sapling_activation(BlockHash([0; 32]))
2378 .build();
2379 let account = st.test_account().cloned().unwrap();
2380
2381 st.wallet_mut()
2384 .update_chain_tip(account.birthday().height())
2385 .unwrap();
2386
2387 let current_addr = st
2388 .wallet()
2389 .get_last_generated_address_matching(
2390 account.id(),
2391 UnifiedAddressRequest::AllAvailableKeys,
2392 )
2393 .unwrap();
2394 assert!(current_addr.is_some());
2395
2396 let addr2 = st
2397 .wallet_mut()
2398 .get_next_available_address(account.id(), UnifiedAddressRequest::AllAvailableKeys)
2399 .unwrap()
2400 .map(|(a, _)| a);
2401 assert!(addr2.is_some());
2402 assert_ne!(current_addr, addr2);
2403
2404 let addr2_cur = st
2405 .wallet()
2406 .get_last_generated_address_matching(
2407 account.id(),
2408 UnifiedAddressRequest::AllAvailableKeys,
2409 )
2410 .unwrap();
2411 assert_eq!(addr2, addr2_cur);
2412
2413 use zcash_keys::keys::ReceiverRequirement::*;
2416 #[cfg(feature = "orchard")]
2417 let shielded_only_request = UnifiedAddressRequest::unsafe_custom(Require, Require, Omit);
2418 #[cfg(not(feature = "orchard"))]
2419 let shielded_only_request = UnifiedAddressRequest::unsafe_custom(Omit, Require, Omit);
2420
2421 let cur_shielded_only = st
2422 .wallet()
2423 .get_last_generated_address_matching(account.id(), shielded_only_request)
2424 .unwrap();
2425 #[cfg(not(feature = "transparent-inputs"))]
2428 assert_eq!(cur_shielded_only, addr2);
2429 #[cfg(feature = "transparent-inputs")]
2431 assert!(cur_shielded_only.is_none());
2432
2433 let di_lower = st
2434 .wallet()
2435 .db()
2436 .clock
2437 .now()
2438 .duration_since(SystemTime::UNIX_EPOCH)
2439 .expect("current time is valid")
2440 .as_secs()
2441 .saturating_add(MIN_SHIELDED_DIVERSIFIER_OFFSET);
2442
2443 let (shielded_only, di) = st
2444 .wallet_mut()
2445 .get_next_available_address(account.id(), shielded_only_request)
2446 .unwrap()
2447 .expect("generated a shielded-only address");
2448
2449 assert!(u128::from(di) >= u128::from(di_lower));
2452
2453 let cur_shielded_only = st
2454 .wallet()
2455 .get_last_generated_address_matching(account.id(), shielded_only_request)
2456 .unwrap()
2457 .expect("retrieved the last-generated shielded-only address");
2458 assert_eq!(cur_shielded_only, shielded_only);
2459
2460 let collision_offset = 32;
2463
2464 st.wallet_mut()
2465 .db_mut()
2466 .clock
2467 .tick(Duration::from_secs(collision_offset));
2468
2469 let (shielded_only_2, di_2) = st
2470 .wallet_mut()
2471 .get_next_available_address(account.id(), shielded_only_request)
2472 .unwrap()
2473 .expect("generated a shielded-only address");
2474 assert_ne!(shielded_only_2, shielded_only);
2475 assert!(u128::from(di_2) >= u128::from(di_lower) + u128::from(collision_offset));
2476 }
2477
2478 #[test]
2479 pub(crate) fn import_account_hd_0() {
2480 let st = TestBuilder::new()
2481 .with_data_store_factory(TestDbFactory::default())
2482 .with_account_from_sapling_activation(BlockHash([0; 32]))
2483 .set_account_index(zip32::AccountId::ZERO)
2484 .build();
2485 assert_matches!(
2486 st.test_account().unwrap().account().source(),
2487 AccountSource::Derived { derivation, .. } if derivation.account_index() == zip32::AccountId::ZERO);
2488 }
2489
2490 #[test]
2491 pub(crate) fn import_account_hd_1_then_2() {
2492 let mut st = TestBuilder::new()
2493 .with_data_store_factory(TestDbFactory::default())
2494 .build();
2495
2496 let birthday = AccountBirthday::from_parts(
2497 ChainState::empty(st.network().sapling.unwrap() - 1, BlockHash([0; 32])),
2498 None,
2499 );
2500
2501 let seed = Secret::new(vec![0u8; 32]);
2502 let zip32_index_1 = zip32::AccountId::ZERO.next().unwrap();
2503
2504 let first = st
2505 .wallet_mut()
2506 .import_account_hd("", &seed, zip32_index_1, &birthday, None)
2507 .unwrap();
2508 assert_matches!(
2509 first.0.source(),
2510 AccountSource::Derived { derivation, .. } if derivation.account_index() == zip32_index_1);
2511
2512 let zip32_index_2 = zip32_index_1.next().unwrap();
2513 let second = st
2514 .wallet_mut()
2515 .import_account_hd("", &seed, zip32_index_2, &birthday, None)
2516 .unwrap();
2517 assert_matches!(
2518 second.0.source(),
2519 AccountSource::Derived { derivation, .. } if derivation.account_index() == zip32_index_2);
2520 }
2521
2522 fn check_collisions<C, DbT: WalletTest + WalletWrite, P: consensus::Parameters>(
2523 st: &mut TestState<C, DbT, P>,
2524 ufvk: &UnifiedFullViewingKey,
2525 birthday: &AccountBirthday,
2526 is_account_collision: impl Fn(&<DbT as WalletRead>::Error) -> bool,
2527 ) where
2528 DbT::Account: core::fmt::Debug,
2529 {
2530 assert_matches!(
2531 st.wallet_mut()
2532 .import_account_ufvk("", ufvk, birthday, AccountPurpose::Spending { derivation: None }, None),
2533 Err(e) if is_account_collision(&e)
2534 );
2535
2536 #[cfg(feature = "transparent-inputs")]
2539 {
2540 assert!(ufvk.transparent().is_some());
2541 let subset_ufvk = UnifiedFullViewingKey::new(
2542 None,
2543 ufvk.sapling().cloned(),
2544 #[cfg(feature = "orchard")]
2545 ufvk.orchard().cloned(),
2546 )
2547 .unwrap();
2548 assert_matches!(
2549 st.wallet_mut().import_account_ufvk(
2550 "",
2551 &subset_ufvk,
2552 birthday,
2553 AccountPurpose::Spending { derivation: None },
2554 None,
2555 ),
2556 Err(e) if is_account_collision(&e)
2557 );
2558 }
2559
2560 #[cfg(feature = "orchard")]
2563 {
2564 assert!(ufvk.orchard().is_some());
2565 let subset_ufvk = UnifiedFullViewingKey::new(
2566 #[cfg(feature = "transparent-inputs")]
2567 ufvk.transparent().cloned(),
2568 ufvk.sapling().cloned(),
2569 None,
2570 )
2571 .unwrap();
2572 assert_matches!(
2573 st.wallet_mut().import_account_ufvk(
2574 "",
2575 &subset_ufvk,
2576 birthday,
2577 AccountPurpose::Spending { derivation: None },
2578 None,
2579 ),
2580 Err(e) if is_account_collision(&e)
2581 );
2582 }
2583 }
2584
2585 #[test]
2586 pub(crate) fn import_account_hd_1_then_conflicts() {
2587 let mut st = TestBuilder::new()
2588 .with_data_store_factory(TestDbFactory::default())
2589 .build();
2590
2591 let birthday = AccountBirthday::from_parts(
2592 ChainState::empty(st.network().sapling.unwrap() - 1, BlockHash([0; 32])),
2593 None,
2594 );
2595
2596 let seed = Secret::new(vec![0u8; 32]);
2597 let zip32_index_1 = zip32::AccountId::ZERO.next().unwrap();
2598
2599 let (first_account, _) = st
2600 .wallet_mut()
2601 .import_account_hd("", &seed, zip32_index_1, &birthday, None)
2602 .unwrap();
2603 let ufvk = first_account.ufvk().unwrap();
2604
2605 assert_matches!(
2606 st.wallet_mut().import_account_hd("", &seed, zip32_index_1, &birthday, None),
2607 Err(SqliteClientError::AccountCollision(id)) if id == first_account.id());
2608
2609 check_collisions(
2610 &mut st,
2611 ufvk,
2612 &birthday,
2613 |e| matches!(e, SqliteClientError::AccountCollision(id) if *id == first_account.id()),
2614 );
2615 }
2616
2617 #[test]
2618 pub(crate) fn import_account_ufvk_then_conflicts() {
2619 let mut st = TestBuilder::new()
2620 .with_data_store_factory(TestDbFactory::default())
2621 .build();
2622
2623 let birthday = AccountBirthday::from_parts(
2624 ChainState::empty(st.network().sapling.unwrap() - 1, BlockHash([0; 32])),
2625 None,
2626 );
2627
2628 let seed = Secret::new(vec![0u8; 32]);
2629 let zip32_index_0 = zip32::AccountId::ZERO;
2630 let usk = UnifiedSpendingKey::from_seed(st.network(), seed.expose_secret(), zip32_index_0)
2631 .unwrap();
2632 let ufvk = usk.to_unified_full_viewing_key();
2633
2634 let account = st
2635 .wallet_mut()
2636 .import_account_ufvk(
2637 "",
2638 &ufvk,
2639 &birthday,
2640 AccountPurpose::Spending { derivation: None },
2641 None,
2642 )
2643 .unwrap();
2644 assert_eq!(
2645 ufvk.encode(st.network()),
2646 account.ufvk().unwrap().encode(st.network())
2647 );
2648
2649 assert_matches!(
2650 account.source(),
2651 AccountSource::Imported {
2652 purpose: AccountPurpose::Spending { .. },
2653 ..
2654 }
2655 );
2656
2657 assert_matches!(
2658 st.wallet_mut().import_account_hd("", &seed, zip32_index_0, &birthday, None),
2659 Err(SqliteClientError::AccountCollision(id)) if id == account.id());
2660
2661 check_collisions(
2662 &mut st,
2663 &ufvk,
2664 &birthday,
2665 |e| matches!(e, SqliteClientError::AccountCollision(id) if *id == account.id()),
2666 );
2667 }
2668
2669 #[test]
2670 pub(crate) fn create_account_then_conflicts() {
2671 let mut st = TestBuilder::new()
2672 .with_data_store_factory(TestDbFactory::default())
2673 .build();
2674
2675 let birthday = AccountBirthday::from_parts(
2676 ChainState::empty(st.network().sapling.unwrap() - 1, BlockHash([0; 32])),
2677 None,
2678 );
2679
2680 let seed = Secret::new(vec![0u8; 32]);
2681 let zip32_index_0 = zip32::AccountId::ZERO;
2682 let seed_based = st
2683 .wallet_mut()
2684 .create_account("", &seed, &birthday, None)
2685 .unwrap();
2686 let seed_based_account = st.wallet().get_account(seed_based.0).unwrap().unwrap();
2687 let ufvk = seed_based_account.ufvk().unwrap();
2688
2689 assert_matches!(
2690 st.wallet_mut().import_account_hd("", &seed, zip32_index_0, &birthday, None),
2691 Err(SqliteClientError::AccountCollision(id)) if id == seed_based.0);
2692
2693 check_collisions(
2694 &mut st,
2695 ufvk,
2696 &birthday,
2697 |e| matches!(e, SqliteClientError::AccountCollision(id) if *id == seed_based.0),
2698 );
2699 }
2700
2701 #[cfg(feature = "transparent-inputs")]
2702 #[test]
2703 fn transparent_receivers() {
2704 use std::collections::BTreeSet;
2705
2706 use crate::{
2707 testing::BlockCache, wallet::transparent::transaction_data_requests, GapLimits,
2708 };
2709 use zcash_client_backend::data_api::TransactionDataRequest;
2710
2711 let mut st = TestBuilder::new()
2712 .with_data_store_factory(TestDbFactory::default())
2713 .with_block_cache(BlockCache::new())
2714 .with_account_from_sapling_activation(BlockHash([0; 32]))
2715 .build();
2716 let account = st.test_account().unwrap();
2717 let ufvk = account.usk().to_unified_full_viewing_key();
2718 let (taddr, _) = account.usk().default_transparent_address();
2719 let birthday = account.birthday().height();
2720 let account_id = account.id();
2721
2722 let receivers = st
2723 .wallet()
2724 .get_transparent_receivers(account.id(), false)
2725 .unwrap();
2726
2727 assert!(receivers.contains_key(
2729 ufvk.default_address(UnifiedAddressRequest::AllAvailableKeys)
2730 .expect("A valid default address exists for the UFVK")
2731 .0
2732 .transparent()
2733 .unwrap()
2734 ));
2735
2736 assert!(receivers.contains_key(&taddr));
2738
2739 st.wallet_mut().update_chain_tip(birthday).unwrap();
2741
2742 let ephemeral_addrs = st
2744 .wallet()
2745 .get_known_ephemeral_addresses(account_id, None)
2746 .unwrap();
2747
2748 assert_eq!(
2749 ephemeral_addrs.len(),
2750 GapLimits::default().ephemeral() as usize
2751 );
2752
2753 st.wallet_mut()
2754 .db_mut()
2755 .schedule_ephemeral_address_checks()
2756 .unwrap();
2757 let data_requests =
2758 transaction_data_requests(st.wallet().conn(), &st.wallet().db().params).unwrap();
2759
2760 let base_time = st.wallet().db().clock.now();
2761 let day = Duration::from_secs(60 * 60 * 24);
2762 let mut check_times = BTreeSet::new();
2763 for (addr, _) in ephemeral_addrs {
2764 let has_valid_request = data_requests.iter().any(|req| match req {
2765 TransactionDataRequest::TransactionsInvolvingAddress {
2766 address,
2767 request_at: Some(t),
2768 ..
2769 } => {
2770 *address == addr && *t > base_time && {
2771 let t_delta = t.duration_since(base_time).unwrap();
2772 let result = t_delta < day && !check_times.contains(t);
2776 check_times.insert(*t);
2777 result
2778 }
2779 }
2780 _ => false,
2781 });
2782
2783 assert!(has_valid_request);
2784 }
2785 }
2786
2787 #[cfg(feature = "unstable")]
2788 #[test]
2789 pub(crate) fn fsblockdb_api() {
2790 use zcash_client_backend::data_api::testing::AddressType;
2791 use zcash_protocol::{consensus::NetworkConstants, value::Zatoshis};
2792
2793 use crate::testing::FsBlockCache;
2794
2795 let mut st = TestBuilder::new()
2796 .with_data_store_factory(TestDbFactory::default())
2797 .with_block_cache(FsBlockCache::new())
2798 .build();
2799
2800 assert_eq!(st.cache().get_max_cached_height().unwrap(), None);
2802
2803 let seed = [0u8; 32];
2805 let hd_account_index = zip32::AccountId::ZERO;
2806 let extsk = sapling::spending_key(&seed, st.network().coin_type(), hd_account_index);
2807 let dfvk = extsk.to_diversifiable_full_viewing_key();
2808 let (h1, meta1, _) = st.generate_next_block(
2809 &dfvk,
2810 AddressType::DefaultExternal,
2811 Zatoshis::const_from_u64(5),
2812 );
2813 let (h2, meta2, _) = st.generate_next_block(
2814 &dfvk,
2815 AddressType::DefaultExternal,
2816 Zatoshis::const_from_u64(10),
2817 );
2818
2819 assert_eq!(st.cache().get_max_cached_height().unwrap(), None);
2821
2822 st.cache().write_block_metadata(&[meta1, meta2]).unwrap();
2824
2825 assert_eq!(st.cache().get_max_cached_height().unwrap(), Some(h2),);
2827 assert_eq!(st.cache().find_block(h1).unwrap(), Some(meta1));
2828 assert_eq!(st.cache().find_block(h2).unwrap(), Some(meta2));
2829 assert_eq!(st.cache().find_block(h2 + 1).unwrap(), None);
2830
2831 st.cache().truncate_to_height(h1).unwrap();
2833 assert_eq!(st.cache().get_max_cached_height().unwrap(), Some(h1));
2834 assert_eq!(st.cache().find_block(h1).unwrap(), Some(meta1));
2835 assert_eq!(st.cache().find_block(h2).unwrap(), None);
2836 assert_eq!(st.cache().find_block(h2 + 1).unwrap(), None);
2837 }
2838}