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, 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 value::Zatoshis,
82 ShieldedProtocol,
83};
84use zip32::{fingerprint::SeedFingerprint, DiversifierIndex};
85
86use crate::{error::SqliteClientError, wallet::commitment_tree::SqliteShardStore};
87use wallet::{
88 commitment_tree::{self, put_shard_roots},
89 common::spendable_notes_meta,
90 scanning::replace_queue_entries,
91 upsert_address, SubtreeProgressEstimator,
92};
93
94#[cfg(feature = "orchard")]
95use {
96 incrementalmerkletree::frontier::Frontier, shardtree::store::Checkpoint,
97 std::collections::BTreeMap, zcash_client_backend::data_api::ORCHARD_SHARD_HEIGHT,
98};
99
100#[cfg(feature = "transparent-inputs")]
101use {
102 crate::wallet::transparent::ephemeral::schedule_ephemeral_address_checks,
103 ::transparent::{address::TransparentAddress, bundle::OutPoint, keys::NonHardenedChildIndex},
104 std::collections::BTreeSet,
105 zcash_client_backend::wallet::TransparentAddressMetadata,
106 zcash_keys::encoding::AddressCodec,
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: Zatoshis,
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 let mut stmt_sent = self.conn.borrow().prepare(
1012 "SELECT value, to_address,
1013 a.cached_transparent_receiver_address, a.transparent_child_index
1014 FROM sent_notes
1015 JOIN transactions t ON t.id_tx = sent_notes.tx
1016 LEFT JOIN transparent_received_outputs tro ON tro.transaction_id = t.id_tx
1017 LEFT JOIN addresses a ON a.id = tro.address_id AND a.key_scope = :key_scope
1018 WHERE t.txid = :txid
1019 ORDER BY value",
1020 )?;
1021
1022 let sends = stmt_sent
1023 .query_map(
1024 named_params![
1025 ":txid": txid.as_ref(),
1026 ":key_scope": KeyScope::Ephemeral.encode()
1027 ],
1028 |row| {
1029 let v = row.get(0)?;
1030 let to_address = row
1031 .get::<_, Option<String>>(1)?
1032 .and_then(|s| Address::decode(&self.params, &s));
1033 let ephemeral_address = row
1034 .get::<_, Option<String>>(2)?
1035 .and_then(|s| Address::decode(&self.params, &s));
1036 let address_index: Option<u32> = row.get(3)?;
1037 Ok((v, to_address, ephemeral_address.zip(address_index)))
1038 },
1039 )?
1040 .map(|res| {
1041 let (amount, external_recipient, ephemeral_address) = res?;
1042 Ok::<_, <Self as WalletRead>::Error>(OutputOfSentTx::from_parts(
1043 Zatoshis::from_u64(amount)?,
1044 external_recipient,
1045 ephemeral_address,
1046 ))
1047 })
1048 .collect::<Result<_, _>>()?;
1049
1050 Ok(sends)
1051 }
1052
1053 fn get_checkpoint_history(
1054 &self,
1055 protocol: &ShieldedProtocol,
1056 ) -> Result<
1057 Vec<(BlockHeight, Option<incrementalmerkletree::Position>)>,
1058 <Self as WalletRead>::Error,
1059 > {
1060 wallet::testing::get_checkpoint_history(self.conn.borrow(), protocol)
1061 }
1062
1063 #[cfg(feature = "transparent-inputs")]
1064 fn get_transparent_output(
1065 &self,
1066 outpoint: &OutPoint,
1067 allow_unspendable: bool,
1068 ) -> Result<Option<WalletTransparentOutput>, <Self as InputSource>::Error> {
1069 wallet::transparent::get_wallet_transparent_output(
1070 self.conn.borrow(),
1071 outpoint,
1072 allow_unspendable,
1073 )
1074 }
1075
1076 fn get_notes(
1077 &self,
1078 protocol: ShieldedProtocol,
1079 ) -> Result<Vec<ReceivedNote<Self::NoteRef, Note>>, <Self as InputSource>::Error> {
1080 let (table_prefix, index_col, _) = wallet::common::per_protocol_names(protocol);
1081 let mut stmt_received_notes = self.conn.borrow().prepare(&format!(
1082 "SELECT txid, {index_col}
1083 FROM {table_prefix}_received_notes rn
1084 INNER JOIN transactions ON transactions.id_tx = rn.tx
1085 WHERE transactions.block IS NOT NULL
1086 AND recipient_key_scope IS NOT NULL
1087 AND nf IS NOT NULL
1088 AND commitment_tree_position IS NOT NULL"
1089 ))?;
1090
1091 let result = stmt_received_notes
1092 .query_map([], |row| {
1093 let txid: [u8; 32] = row.get(0)?;
1094 let output_index: u32 = row.get(1)?;
1095 let note = self
1096 .get_spendable_note(&TxId::from_bytes(txid), protocol, output_index)
1097 .unwrap()
1098 .unwrap();
1099 Ok(note)
1100 })?
1101 .collect::<Result<Vec<_>, _>>()?;
1102
1103 Ok(result)
1104 }
1105}
1106
1107impl<C: BorrowMut<rusqlite::Connection>, P: consensus::Parameters, CL: Clock, R> WalletWrite
1108 for WalletDb<C, P, CL, R>
1109{
1110 type UtxoRef = UtxoId;
1111
1112 fn create_account(
1113 &mut self,
1114 account_name: &str,
1115 seed: &SecretVec<u8>,
1116 birthday: &AccountBirthday,
1117 key_source: Option<&str>,
1118 ) -> Result<(Self::AccountId, UnifiedSpendingKey), Self::Error> {
1119 self.borrow_mut().transactionally(|wdb| {
1120 let seed_fingerprint =
1121 SeedFingerprint::from_seed(seed.expose_secret()).ok_or_else(|| {
1122 SqliteClientError::BadAccountData(
1123 "Seed must be between 32 and 252 bytes in length.".to_owned(),
1124 )
1125 })?;
1126 let zip32_account_index =
1127 wallet::max_zip32_account_index(wdb.conn.0, &seed_fingerprint)?
1128 .map(|a| {
1129 a.next()
1130 .ok_or(SqliteClientError::Zip32AccountIndexOutOfRange)
1131 })
1132 .transpose()?
1133 .unwrap_or(zip32::AccountId::ZERO);
1134
1135 let usk = UnifiedSpendingKey::from_seed(
1136 &wdb.params,
1137 seed.expose_secret(),
1138 zip32_account_index,
1139 )
1140 .map_err(|_| SqliteClientError::KeyDerivationError(zip32_account_index))?;
1141 let ufvk = usk.to_unified_full_viewing_key();
1142
1143 let account = wallet::add_account(
1144 wdb.conn.0,
1145 &wdb.params,
1146 account_name,
1147 &AccountSource::Derived {
1148 derivation: Zip32Derivation::new(seed_fingerprint, zip32_account_index),
1149 key_source: key_source.map(|s| s.to_owned()),
1150 },
1151 wallet::ViewingKey::Full(Box::new(ufvk)),
1152 birthday,
1153 #[cfg(feature = "transparent-inputs")]
1154 &wdb.gap_limits,
1155 )?;
1156
1157 Ok((account.id(), usk))
1158 })
1159 }
1160
1161 fn import_account_hd(
1162 &mut self,
1163 account_name: &str,
1164 seed: &SecretVec<u8>,
1165 account_index: zip32::AccountId,
1166 birthday: &AccountBirthday,
1167 key_source: Option<&str>,
1168 ) -> Result<(Self::Account, UnifiedSpendingKey), Self::Error> {
1169 self.transactionally(|wdb| {
1170 let seed_fingerprint =
1171 SeedFingerprint::from_seed(seed.expose_secret()).ok_or_else(|| {
1172 SqliteClientError::BadAccountData(
1173 "Seed must be between 32 and 252 bytes in length.".to_owned(),
1174 )
1175 })?;
1176
1177 let usk =
1178 UnifiedSpendingKey::from_seed(&wdb.params, seed.expose_secret(), account_index)
1179 .map_err(|_| SqliteClientError::KeyDerivationError(account_index))?;
1180 let ufvk = usk.to_unified_full_viewing_key();
1181
1182 let account = wallet::add_account(
1183 wdb.conn.0,
1184 &wdb.params,
1185 account_name,
1186 &AccountSource::Derived {
1187 derivation: Zip32Derivation::new(seed_fingerprint, account_index),
1188 key_source: key_source.map(|s| s.to_owned()),
1189 },
1190 wallet::ViewingKey::Full(Box::new(ufvk)),
1191 birthday,
1192 #[cfg(feature = "transparent-inputs")]
1193 &wdb.gap_limits,
1194 )?;
1195
1196 Ok((account, usk))
1197 })
1198 }
1199
1200 fn import_account_ufvk(
1201 &mut self,
1202 account_name: &str,
1203 ufvk: &UnifiedFullViewingKey,
1204 birthday: &AccountBirthday,
1205 purpose: AccountPurpose,
1206 key_source: Option<&str>,
1207 ) -> Result<Self::Account, Self::Error> {
1208 self.transactionally(|wdb| {
1209 wallet::add_account(
1210 wdb.conn.0,
1211 &wdb.params,
1212 account_name,
1213 &AccountSource::Imported {
1214 purpose,
1215 key_source: key_source.map(|s| s.to_owned()),
1216 },
1217 wallet::ViewingKey::Full(Box::new(ufvk.to_owned())),
1218 birthday,
1219 #[cfg(feature = "transparent-inputs")]
1220 &wdb.gap_limits,
1221 )
1222 })
1223 }
1224
1225 fn get_next_available_address(
1226 &mut self,
1227 account_uuid: Self::AccountId,
1228 request: UnifiedAddressRequest,
1229 ) -> Result<Option<(UnifiedAddress, DiversifierIndex)>, Self::Error> {
1230 self.transactionally(|wdb| {
1231 wallet::get_next_available_address(
1232 wdb.conn.0,
1233 &wdb.params,
1234 &wdb.clock,
1235 account_uuid,
1236 request,
1237 #[cfg(feature = "transparent-inputs")]
1238 &wdb.gap_limits,
1239 )
1240 })
1241 }
1242
1243 fn get_address_for_index(
1244 &mut self,
1245 account: Self::AccountId,
1246 diversifier_index: DiversifierIndex,
1247 request: UnifiedAddressRequest,
1248 ) -> Result<Option<UnifiedAddress>, Self::Error> {
1249 if let Some(account) = self.get_account(account)? {
1250 use zcash_keys::keys::AddressGenerationError::*;
1251
1252 match account.uivk().address(diversifier_index, request) {
1253 Ok(address) => {
1254 let chain_tip_height = wallet::chain_tip_height(self.conn.borrow())?;
1255 upsert_address(
1256 self.conn.borrow(),
1257 &self.params,
1258 account.internal_id(),
1259 diversifier_index,
1260 &address,
1261 Some(chain_tip_height.unwrap_or(account.birthday())),
1262 true,
1263 )?;
1264
1265 Ok(Some(address))
1266 }
1267 #[cfg(feature = "transparent-inputs")]
1268 Err(InvalidTransparentChildIndex(_)) => Ok(None),
1269 Err(InvalidSaplingDiversifierIndex(_)) => Ok(None),
1270 Err(e) => Err(SqliteClientError::AddressGeneration(e)),
1271 }
1272 } else {
1273 Err(SqliteClientError::AccountUnknown)
1274 }
1275 }
1276
1277 fn update_chain_tip(&mut self, tip_height: BlockHeight) -> Result<(), Self::Error> {
1278 let tx = self.conn.borrow_mut().transaction()?;
1279 wallet::scanning::update_chain_tip(&tx, &self.params, tip_height)?;
1280 tx.commit()?;
1281 Ok(())
1282 }
1283
1284 #[tracing::instrument(skip_all, fields(height = blocks.first().map(|b| u32::from(b.height())), count = blocks.len()))]
1285 #[allow(clippy::type_complexity)]
1286 fn put_blocks(
1287 &mut self,
1288 from_state: &ChainState,
1289 blocks: Vec<ScannedBlock<Self::AccountId>>,
1290 ) -> Result<(), Self::Error> {
1291 struct BlockPositions {
1292 height: BlockHeight,
1293 sapling_start_position: Position,
1294 #[cfg(feature = "orchard")]
1295 orchard_start_position: Position,
1296 }
1297
1298 if blocks.is_empty() {
1299 return Ok(());
1300 }
1301
1302 self.transactionally(|wdb| {
1303 let initial_block = blocks.first().expect("blocks is known to be nonempty");
1304 assert!(from_state.block_height() + 1 == initial_block.height());
1305
1306 let start_positions = BlockPositions {
1307 height: initial_block.height(),
1308 sapling_start_position: Position::from(
1309 u64::from(initial_block.sapling().final_tree_size())
1310 - u64::try_from(initial_block.sapling().commitments().len()).unwrap(),
1311 ),
1312 #[cfg(feature = "orchard")]
1313 orchard_start_position: Position::from(
1314 u64::from(initial_block.orchard().final_tree_size())
1315 - u64::try_from(initial_block.orchard().commitments().len()).unwrap(),
1316 ),
1317 };
1318
1319 let mut sapling_commitments = vec![];
1320 #[cfg(feature = "orchard")]
1321 let mut orchard_commitments = vec![];
1322 let mut last_scanned_height = None;
1323 let mut note_positions = vec![];
1324
1325 #[cfg(feature = "transparent-inputs")]
1326 let mut tx_refs = BTreeSet::new();
1327
1328 for block in blocks.into_iter() {
1329 if last_scanned_height
1330 .iter()
1331 .any(|prev| block.height() != *prev + 1)
1332 {
1333 return Err(SqliteClientError::NonSequentialBlocks);
1334 }
1335
1336 wallet::put_block(
1338 wdb.conn.0,
1339 block.height(),
1340 block.block_hash(),
1341 block.block_time(),
1342 block.sapling().final_tree_size(),
1343 block.sapling().commitments().len().try_into().unwrap(),
1344 #[cfg(feature = "orchard")]
1345 block.orchard().final_tree_size(),
1346 #[cfg(feature = "orchard")]
1347 block.orchard().commitments().len().try_into().unwrap(),
1348 )?;
1349
1350 for tx in block.transactions() {
1351 let tx_ref = wallet::put_tx_meta(wdb.conn.0, tx, block.height())?;
1352
1353 #[cfg(feature = "transparent-inputs")]
1354 tx_refs.insert(tx_ref);
1355
1356 wallet::queue_tx_retrieval(wdb.conn.0, std::iter::once(tx.txid()), None)?;
1357
1358 for spend in tx.sapling_spends() {
1360 wallet::sapling::mark_sapling_note_spent(wdb.conn.0, tx_ref, spend.nf())?;
1361 }
1362 #[cfg(feature = "orchard")]
1363 for spend in tx.orchard_spends() {
1364 wallet::orchard::mark_orchard_note_spent(wdb.conn.0, tx_ref, spend.nf())?;
1365 }
1366
1367 for output in tx.sapling_outputs() {
1368 let spent_in = output
1371 .nf()
1372 .map(|nf| {
1373 wallet::query_nullifier_map(
1374 wdb.conn.0,
1375 ShieldedProtocol::Sapling,
1376 nf,
1377 )
1378 })
1379 .transpose()?
1380 .flatten();
1381
1382 wallet::sapling::put_received_note(
1383 wdb.conn.0,
1384 &wdb.params,
1385 output,
1386 tx_ref,
1387 Some(block.height()),
1388 spent_in,
1389 )?;
1390 }
1391 #[cfg(feature = "orchard")]
1392 for output in tx.orchard_outputs() {
1393 let spent_in = output
1396 .nf()
1397 .map(|nf| {
1398 wallet::query_nullifier_map(
1399 wdb.conn.0,
1400 ShieldedProtocol::Orchard,
1401 &nf.to_bytes(),
1402 )
1403 })
1404 .transpose()?
1405 .flatten();
1406
1407 wallet::orchard::put_received_note(
1408 wdb.conn.0,
1409 &wdb.params,
1410 output,
1411 tx_ref,
1412 Some(block.height()),
1413 spent_in,
1414 )?;
1415 }
1416 }
1417
1418 wallet::insert_nullifier_map(
1420 wdb.conn.0,
1421 block.height(),
1422 ShieldedProtocol::Sapling,
1423 block.sapling().nullifier_map(),
1424 )?;
1425 #[cfg(feature = "orchard")]
1426 wallet::insert_nullifier_map(
1427 wdb.conn.0,
1428 block.height(),
1429 ShieldedProtocol::Orchard,
1430 &block
1431 .orchard()
1432 .nullifier_map()
1433 .iter()
1434 .map(|(txid, idx, nfs)| {
1435 (*txid, *idx, nfs.iter().map(|nf| nf.to_bytes()).collect())
1436 })
1437 .collect::<Vec<_>>(),
1438 )?;
1439
1440 note_positions.extend(block.transactions().iter().flat_map(|wtx| {
1441 let iter = wtx.sapling_outputs().iter().map(|out| {
1442 (
1443 ShieldedProtocol::Sapling,
1444 out.note_commitment_tree_position(),
1445 )
1446 });
1447 #[cfg(feature = "orchard")]
1448 let iter = iter.chain(wtx.orchard_outputs().iter().map(|out| {
1449 (
1450 ShieldedProtocol::Orchard,
1451 out.note_commitment_tree_position(),
1452 )
1453 }));
1454
1455 iter
1456 }));
1457
1458 last_scanned_height = Some(block.height());
1459 let block_commitments = block.into_commitments();
1460 trace!(
1461 "Sapling commitments for {:?}: {:?}",
1462 last_scanned_height,
1463 block_commitments
1464 .sapling
1465 .iter()
1466 .map(|(_, r)| *r)
1467 .collect::<Vec<_>>()
1468 );
1469 #[cfg(feature = "orchard")]
1470 trace!(
1471 "Orchard commitments for {:?}: {:?}",
1472 last_scanned_height,
1473 block_commitments
1474 .orchard
1475 .iter()
1476 .map(|(_, r)| *r)
1477 .collect::<Vec<_>>()
1478 );
1479
1480 sapling_commitments.extend(block_commitments.sapling.into_iter().map(Some));
1481 #[cfg(feature = "orchard")]
1482 orchard_commitments.extend(block_commitments.orchard.into_iter().map(Some));
1483 }
1484
1485 #[cfg(feature = "transparent-inputs")]
1486 for (account_id, key_scope) in wallet::involved_accounts(wdb.conn.0, tx_refs)? {
1487 use ReceiverRequirement::*;
1488 wallet::transparent::generate_gap_addresses(
1489 wdb.conn.0,
1490 &wdb.params,
1491 account_id,
1492 key_scope,
1493 &wdb.gap_limits,
1494 UnifiedAddressRequest::unsafe_custom(Allow, Allow, Require),
1495 false,
1496 )?;
1497 }
1498
1499 if let Some(meta) = wdb.block_fully_scanned()? {
1501 wallet::prune_nullifier_map(
1502 wdb.conn.0,
1503 meta.block_height().saturating_sub(PRUNING_DEPTH),
1504 )?;
1505 }
1506
1507 if let Some(last_scanned_height) = last_scanned_height {
1510 const CHUNK_SIZE: usize = 1024;
1512 let sapling_subtrees = sapling_commitments
1513 .par_chunks_mut(CHUNK_SIZE)
1514 .enumerate()
1515 .filter_map(|(i, chunk)| {
1516 let start =
1517 start_positions.sapling_start_position + (i * CHUNK_SIZE) as u64;
1518 let end = start + chunk.len() as u64;
1519
1520 shardtree::LocatedTree::from_iter(
1521 start..end,
1522 SAPLING_SHARD_HEIGHT.into(),
1523 chunk.iter_mut().map(|n| n.take().expect("always Some")),
1524 )
1525 })
1526 .map(|res| (res.subtree, res.checkpoints))
1527 .collect::<Vec<_>>();
1528
1529 #[cfg(feature = "orchard")]
1530 let orchard_subtrees = orchard_commitments
1531 .par_chunks_mut(CHUNK_SIZE)
1532 .enumerate()
1533 .filter_map(|(i, chunk)| {
1534 let start =
1535 start_positions.orchard_start_position + (i * CHUNK_SIZE) as u64;
1536 let end = start + chunk.len() as u64;
1537
1538 shardtree::LocatedTree::from_iter(
1539 start..end,
1540 ORCHARD_SHARD_HEIGHT.into(),
1541 chunk.iter_mut().map(|n| n.take().expect("always Some")),
1542 )
1543 })
1544 .map(|res| (res.subtree, res.checkpoints))
1545 .collect::<Vec<_>>();
1546
1547 #[cfg(feature = "orchard")]
1549 let sapling_checkpoint_positions: BTreeMap<BlockHeight, Position> =
1550 sapling_subtrees
1551 .iter()
1552 .flat_map(|(_, checkpoints)| checkpoints.iter())
1553 .map(|(k, v)| (*k, *v))
1554 .collect();
1555
1556 #[cfg(feature = "orchard")]
1557 let orchard_checkpoint_positions: BTreeMap<BlockHeight, Position> =
1558 orchard_subtrees
1559 .iter()
1560 .flat_map(|(_, checkpoints)| checkpoints.iter())
1561 .map(|(k, v)| (*k, *v))
1562 .collect();
1563
1564 #[cfg(feature = "orchard")]
1565 fn ensure_checkpoints<
1566 'a,
1567 H,
1568 I: Iterator<Item = &'a BlockHeight>,
1569 const DEPTH: u8,
1570 >(
1571 ensure_heights: I,
1574 existing_checkpoint_positions: &BTreeMap<BlockHeight, Position>,
1577 state_final_tree: &Frontier<H, DEPTH>,
1580 ) -> Vec<(BlockHeight, Checkpoint)> {
1581 ensure_heights
1582 .flat_map(|ensure_height| {
1583 existing_checkpoint_positions
1584 .range::<BlockHeight, _>(..=*ensure_height)
1585 .last()
1586 .map_or_else(
1587 || {
1588 Some((
1589 *ensure_height,
1590 state_final_tree
1591 .value()
1592 .map_or_else(Checkpoint::tree_empty, |t| {
1593 Checkpoint::at_position(t.position())
1594 }),
1595 ))
1596 },
1597 |(existing_checkpoint_height, position)| {
1598 if *existing_checkpoint_height < *ensure_height {
1599 Some((
1600 *ensure_height,
1601 Checkpoint::at_position(*position),
1602 ))
1603 } else {
1604 None
1607 }
1608 },
1609 )
1610 .into_iter()
1611 })
1612 .collect::<Vec<_>>()
1613 }
1614
1615 #[cfg(feature = "orchard")]
1616 let (missing_sapling_checkpoints, missing_orchard_checkpoints) = (
1617 ensure_checkpoints(
1618 orchard_checkpoint_positions.keys(),
1619 &sapling_checkpoint_positions,
1620 from_state.final_sapling_tree(),
1621 ),
1622 ensure_checkpoints(
1623 sapling_checkpoint_positions.keys(),
1624 &orchard_checkpoint_positions,
1625 from_state.final_orchard_tree(),
1626 ),
1627 );
1628
1629 {
1631 let mut sapling_subtrees_iter = sapling_subtrees.into_iter();
1632 wdb.with_sapling_tree_mut::<_, _, Self::Error>(|sapling_tree| {
1633 debug!(
1634 "Sapling initial tree size at {:?}: {:?}",
1635 from_state.block_height(),
1636 from_state.final_sapling_tree().tree_size()
1637 );
1638 sapling_tree.insert_frontier(
1641 from_state.final_sapling_tree().clone(),
1642 Retention::Checkpoint {
1643 id: from_state.block_height(),
1644 marking: Marking::Reference,
1645 },
1646 )?;
1647
1648 for (tree, checkpoints) in &mut sapling_subtrees_iter {
1649 sapling_tree.insert_tree(tree, checkpoints)?;
1650 }
1651
1652 #[cfg(feature = "orchard")]
1656 {
1657 let min_checkpoint_height = sapling_tree
1658 .store()
1659 .min_checkpoint_id()
1660 .map_err(ShardTreeError::Storage)?
1661 .expect(
1662 "At least one checkpoint was inserted (by insert_frontier)",
1663 );
1664
1665 for (height, checkpoint) in &missing_sapling_checkpoints {
1666 if *height > min_checkpoint_height {
1667 sapling_tree
1668 .store_mut()
1669 .add_checkpoint(*height, checkpoint.clone())
1670 .map_err(ShardTreeError::Storage)?;
1671 }
1672 }
1673 }
1674
1675 Ok(())
1676 })?;
1677 }
1678
1679 #[cfg(feature = "orchard")]
1681 {
1682 let mut orchard_subtrees = orchard_subtrees.into_iter();
1683 wdb.with_orchard_tree_mut::<_, _, Self::Error>(|orchard_tree| {
1684 debug!(
1685 "Orchard initial tree size at {:?}: {:?}",
1686 from_state.block_height(),
1687 from_state.final_orchard_tree().tree_size()
1688 );
1689 orchard_tree.insert_frontier(
1692 from_state.final_orchard_tree().clone(),
1693 Retention::Checkpoint {
1694 id: from_state.block_height(),
1695 marking: Marking::Reference,
1696 },
1697 )?;
1698
1699 for (tree, checkpoints) in &mut orchard_subtrees {
1700 orchard_tree.insert_tree(tree, checkpoints)?;
1701 }
1702
1703 {
1707 let min_checkpoint_height = orchard_tree
1708 .store()
1709 .min_checkpoint_id()
1710 .map_err(ShardTreeError::Storage)?
1711 .expect(
1712 "At least one checkpoint was inserted (by insert_frontier)",
1713 );
1714
1715 for (height, checkpoint) in &missing_orchard_checkpoints {
1716 if *height > min_checkpoint_height {
1717 debug!(
1718 "Adding missing Orchard checkpoint for height: {:?}: {:?}",
1719 height,
1720 checkpoint.position()
1721 );
1722 orchard_tree
1723 .store_mut()
1724 .add_checkpoint(*height, checkpoint.clone())
1725 .map_err(ShardTreeError::Storage)?;
1726 }
1727 }
1728 }
1729 Ok(())
1730 })?;
1731 }
1732
1733 wallet::scanning::scan_complete(
1734 wdb.conn.0,
1735 &wdb.params,
1736 Range {
1737 start: start_positions.height,
1738 end: last_scanned_height + 1,
1739 },
1740 ¬e_positions,
1741 )?;
1742 }
1743
1744 Ok(())
1745 })
1746 }
1747
1748 fn put_received_transparent_utxo(
1749 &mut self,
1750 _output: &WalletTransparentOutput,
1751 ) -> Result<Self::UtxoRef, Self::Error> {
1752 #[cfg(feature = "transparent-inputs")]
1753 return self.transactionally(|wdb| {
1754 let (account_id, key_scope, utxo_id) =
1755 wallet::transparent::put_received_transparent_utxo(
1756 wdb.conn.0,
1757 &wdb.params,
1758 _output,
1759 )?;
1760
1761 use ReceiverRequirement::*;
1762 wallet::transparent::generate_gap_addresses(
1763 wdb.conn.0,
1764 &wdb.params,
1765 account_id,
1766 key_scope,
1767 &wdb.gap_limits,
1768 UnifiedAddressRequest::unsafe_custom(Allow, Allow, Require),
1769 true,
1770 )?;
1771
1772 Ok(utxo_id)
1773 });
1774
1775 #[cfg(not(feature = "transparent-inputs"))]
1776 panic!(
1777 "The wallet must be compiled with the transparent-inputs feature to use this method."
1778 );
1779 }
1780
1781 fn store_decrypted_tx(
1782 &mut self,
1783 d_tx: DecryptedTransaction<Self::AccountId>,
1784 ) -> Result<(), Self::Error> {
1785 self.transactionally(|wdb| {
1786 wallet::store_decrypted_tx(
1787 wdb.conn.0,
1788 &wdb.params,
1789 d_tx,
1790 #[cfg(feature = "transparent-inputs")]
1791 &wdb.gap_limits,
1792 )
1793 })
1794 }
1795
1796 fn store_transactions_to_be_sent(
1797 &mut self,
1798 transactions: &[SentTransaction<Self::AccountId>],
1799 ) -> Result<(), Self::Error> {
1800 self.transactionally(|wdb| {
1801 for sent_tx in transactions {
1802 wallet::store_transaction_to_be_sent(wdb.conn.0, &wdb.params, sent_tx)?;
1803 }
1804 Ok(())
1805 })
1806 }
1807
1808 fn truncate_to_height(&mut self, max_height: BlockHeight) -> Result<BlockHeight, Self::Error> {
1809 self.transactionally(|wdb| {
1810 wallet::truncate_to_height(
1811 wdb.conn.0,
1812 &wdb.params,
1813 #[cfg(feature = "transparent-inputs")]
1814 &wdb.gap_limits,
1815 max_height,
1816 )
1817 })
1818 }
1819
1820 #[cfg(feature = "transparent-inputs")]
1821 fn reserve_next_n_ephemeral_addresses(
1822 &mut self,
1823 account_id: Self::AccountId,
1824 n: usize,
1825 ) -> Result<Vec<(TransparentAddress, TransparentAddressMetadata)>, Self::Error> {
1826 self.transactionally(|wdb| {
1827 let account_id = wallet::get_account_ref(wdb.conn.0, account_id)?;
1828 let reserved = wallet::transparent::reserve_next_n_addresses(
1829 wdb.conn.0,
1830 &wdb.params,
1831 account_id,
1832 KeyScope::Ephemeral,
1833 wdb.gap_limits.ephemeral(),
1834 n,
1835 )?;
1836
1837 Ok(reserved.into_iter().map(|(_, a, m)| (a, m)).collect())
1838 })
1839 }
1840
1841 fn set_transaction_status(
1842 &mut self,
1843 txid: TxId,
1844 status: data_api::TransactionStatus,
1845 ) -> Result<(), Self::Error> {
1846 self.transactionally(|wdb| wallet::set_transaction_status(wdb.conn.0, txid, status))
1847 }
1848}
1849
1850pub(crate) type SaplingShardStore<C> = SqliteShardStore<C, sapling::Node, SAPLING_SHARD_HEIGHT>;
1851pub(crate) type SaplingCommitmentTree<C> =
1852 ShardTree<SaplingShardStore<C>, { sapling::NOTE_COMMITMENT_TREE_DEPTH }, SAPLING_SHARD_HEIGHT>;
1853
1854pub(crate) fn sapling_tree<C>(
1855 conn: C,
1856) -> Result<SaplingCommitmentTree<C>, ShardTreeError<commitment_tree::Error>>
1857where
1858 SaplingShardStore<C>: ShardStore<H = sapling::Node, CheckpointId = BlockHeight>,
1859{
1860 Ok(ShardTree::new(
1861 SqliteShardStore::from_connection(conn, SAPLING_TABLES_PREFIX)
1862 .map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?,
1863 PRUNING_DEPTH.try_into().unwrap(),
1864 ))
1865}
1866
1867#[cfg(feature = "orchard")]
1868pub(crate) type OrchardShardStore<C> =
1869 SqliteShardStore<C, orchard::tree::MerkleHashOrchard, ORCHARD_SHARD_HEIGHT>;
1870
1871#[cfg(feature = "orchard")]
1872pub(crate) type OrchardCommitmentTree<C> = ShardTree<
1873 OrchardShardStore<C>,
1874 { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 },
1875 ORCHARD_SHARD_HEIGHT,
1876>;
1877
1878#[cfg(feature = "orchard")]
1879pub(crate) fn orchard_tree<C>(
1880 conn: C,
1881) -> Result<OrchardCommitmentTree<C>, ShardTreeError<commitment_tree::Error>>
1882where
1883 OrchardShardStore<C>:
1884 ShardStore<H = orchard::tree::MerkleHashOrchard, CheckpointId = BlockHeight>,
1885{
1886 Ok(ShardTree::new(
1887 SqliteShardStore::from_connection(conn, ORCHARD_TABLES_PREFIX)
1888 .map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?,
1889 PRUNING_DEPTH.try_into().unwrap(),
1890 ))
1891}
1892
1893impl<C: BorrowMut<rusqlite::Connection>, P: consensus::Parameters, CL, R> WalletCommitmentTrees
1894 for WalletDb<C, P, CL, R>
1895{
1896 type Error = commitment_tree::Error;
1897 type SaplingShardStore<'a> = SaplingShardStore<&'a rusqlite::Transaction<'a>>;
1898
1899 fn with_sapling_tree_mut<F, A, E>(&mut self, mut callback: F) -> Result<A, E>
1900 where
1901 for<'a> F:
1902 FnMut(&'a mut SaplingCommitmentTree<&'a rusqlite::Transaction<'a>>) -> Result<A, E>,
1903 E: From<ShardTreeError<Self::Error>>,
1904 {
1905 let tx = self
1906 .conn
1907 .borrow_mut()
1908 .transaction()
1909 .map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
1910 let result = {
1911 let mut shardtree = sapling_tree(&tx)?;
1912 callback(&mut shardtree)?
1913 };
1914
1915 tx.commit()
1916 .map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
1917 Ok(result)
1918 }
1919
1920 fn put_sapling_subtree_roots(
1921 &mut self,
1922 start_index: u64,
1923 roots: &[CommitmentTreeRoot<sapling::Node>],
1924 ) -> Result<(), ShardTreeError<Self::Error>> {
1925 let tx = self
1926 .conn
1927 .borrow_mut()
1928 .transaction()
1929 .map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
1930 put_shard_roots::<_, { sapling::NOTE_COMMITMENT_TREE_DEPTH }, SAPLING_SHARD_HEIGHT>(
1931 &tx,
1932 SAPLING_TABLES_PREFIX,
1933 start_index,
1934 roots,
1935 )?;
1936 tx.commit()
1937 .map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
1938 Ok(())
1939 }
1940
1941 #[cfg(feature = "orchard")]
1942 type OrchardShardStore<'a> = SqliteShardStore<
1943 &'a rusqlite::Transaction<'a>,
1944 orchard::tree::MerkleHashOrchard,
1945 ORCHARD_SHARD_HEIGHT,
1946 >;
1947
1948 #[cfg(feature = "orchard")]
1949 fn with_orchard_tree_mut<F, A, E>(&mut self, mut callback: F) -> Result<A, E>
1950 where
1951 for<'a> F:
1952 FnMut(&'a mut OrchardCommitmentTree<&'a rusqlite::Transaction<'a>>) -> Result<A, E>,
1953 E: From<ShardTreeError<Self::Error>>,
1954 {
1955 let tx = self
1956 .conn
1957 .borrow_mut()
1958 .transaction()
1959 .map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
1960 let result = {
1961 let mut shardtree = orchard_tree(&tx)?;
1962 callback(&mut shardtree)?
1963 };
1964
1965 tx.commit()
1966 .map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
1967 Ok(result)
1968 }
1969
1970 #[cfg(feature = "orchard")]
1971 fn put_orchard_subtree_roots(
1972 &mut self,
1973 start_index: u64,
1974 roots: &[CommitmentTreeRoot<orchard::tree::MerkleHashOrchard>],
1975 ) -> Result<(), ShardTreeError<Self::Error>> {
1976 let tx = self
1977 .conn
1978 .borrow_mut()
1979 .transaction()
1980 .map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
1981 put_shard_roots::<_, { ORCHARD_SHARD_HEIGHT * 2 }, ORCHARD_SHARD_HEIGHT>(
1982 &tx,
1983 ORCHARD_TABLES_PREFIX,
1984 start_index,
1985 roots,
1986 )?;
1987 tx.commit()
1988 .map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?;
1989 Ok(())
1990 }
1991}
1992
1993impl<P: consensus::Parameters, CL, R> WalletCommitmentTrees
1994 for WalletDb<SqlTransaction<'_>, P, CL, R>
1995{
1996 type Error = commitment_tree::Error;
1997 type SaplingShardStore<'a> = crate::SaplingShardStore<&'a rusqlite::Transaction<'a>>;
1998
1999 fn with_sapling_tree_mut<F, A, E>(&mut self, mut callback: F) -> Result<A, E>
2000 where
2001 for<'a> F:
2002 FnMut(&'a mut SaplingCommitmentTree<&'a rusqlite::Transaction<'a>>) -> Result<A, E>,
2003 E: From<ShardTreeError<commitment_tree::Error>>,
2004 {
2005 let mut shardtree = sapling_tree(self.conn.0)?;
2006 let result = callback(&mut shardtree)?;
2007
2008 Ok(result)
2009 }
2010
2011 fn put_sapling_subtree_roots(
2012 &mut self,
2013 start_index: u64,
2014 roots: &[CommitmentTreeRoot<sapling::Node>],
2015 ) -> Result<(), ShardTreeError<Self::Error>> {
2016 put_shard_roots::<_, { sapling::NOTE_COMMITMENT_TREE_DEPTH }, SAPLING_SHARD_HEIGHT>(
2017 self.conn.0,
2018 SAPLING_TABLES_PREFIX,
2019 start_index,
2020 roots,
2021 )
2022 }
2023
2024 #[cfg(feature = "orchard")]
2025 type OrchardShardStore<'a> = crate::OrchardShardStore<&'a rusqlite::Transaction<'a>>;
2026
2027 #[cfg(feature = "orchard")]
2028 fn with_orchard_tree_mut<F, A, E>(&mut self, mut callback: F) -> Result<A, E>
2029 where
2030 for<'a> F:
2031 FnMut(&'a mut OrchardCommitmentTree<&'a rusqlite::Transaction<'a>>) -> Result<A, E>,
2032 E: From<ShardTreeError<Self::Error>>,
2033 {
2034 let mut shardtree = orchard_tree(self.conn.0)?;
2035 let result = callback(&mut shardtree)?;
2036
2037 Ok(result)
2038 }
2039
2040 #[cfg(feature = "orchard")]
2041 fn put_orchard_subtree_roots(
2042 &mut self,
2043 start_index: u64,
2044 roots: &[CommitmentTreeRoot<orchard::tree::MerkleHashOrchard>],
2045 ) -> Result<(), ShardTreeError<Self::Error>> {
2046 put_shard_roots::<_, { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }, ORCHARD_SHARD_HEIGHT>(
2047 self.conn.0,
2048 ORCHARD_TABLES_PREFIX,
2049 start_index,
2050 roots,
2051 )
2052 }
2053}
2054
2055pub struct BlockDb(Connection);
2057
2058impl BlockDb {
2059 pub fn for_path<P: AsRef<Path>>(path: P) -> Result<Self, rusqlite::Error> {
2061 Connection::open(path).map(BlockDb)
2062 }
2063}
2064
2065impl BlockSource for BlockDb {
2066 type Error = SqliteClientError;
2067
2068 fn with_blocks<F, DbErrT>(
2069 &self,
2070 from_height: Option<BlockHeight>,
2071 limit: Option<usize>,
2072 with_row: F,
2073 ) -> Result<(), data_api::chain::error::Error<DbErrT, Self::Error>>
2074 where
2075 F: FnMut(CompactBlock) -> Result<(), data_api::chain::error::Error<DbErrT, Self::Error>>,
2076 {
2077 chain::blockdb_with_blocks(self, from_height, limit, with_row)
2078 }
2079}
2080
2081#[cfg(feature = "unstable")]
2120pub struct FsBlockDb {
2121 conn: Connection,
2122 blocks_dir: PathBuf,
2123}
2124
2125#[derive(Debug)]
2128#[cfg(feature = "unstable")]
2129pub enum FsBlockDbError {
2130 Fs(io::Error),
2131 Db(rusqlite::Error),
2132 Protobuf(prost::DecodeError),
2133 MissingBlockPath(PathBuf),
2134 InvalidBlockstoreRoot(PathBuf),
2135 InvalidBlockPath(PathBuf),
2136 CorruptedData(String),
2137 CacheMiss(BlockHeight),
2138}
2139
2140#[cfg(feature = "unstable")]
2141impl From<io::Error> for FsBlockDbError {
2142 fn from(err: io::Error) -> Self {
2143 FsBlockDbError::Fs(err)
2144 }
2145}
2146
2147#[cfg(feature = "unstable")]
2148impl From<rusqlite::Error> for FsBlockDbError {
2149 fn from(err: rusqlite::Error) -> Self {
2150 FsBlockDbError::Db(err)
2151 }
2152}
2153
2154#[cfg(feature = "unstable")]
2155impl From<prost::DecodeError> for FsBlockDbError {
2156 fn from(e: prost::DecodeError) -> Self {
2157 FsBlockDbError::Protobuf(e)
2158 }
2159}
2160
2161#[cfg(feature = "unstable")]
2162impl FsBlockDb {
2163 pub fn for_path<P: AsRef<Path>>(fsblockdb_root: P) -> Result<Self, FsBlockDbError> {
2175 let meta = fs::metadata(&fsblockdb_root).map_err(FsBlockDbError::Fs)?;
2176 if meta.is_dir() {
2177 let db_path = fsblockdb_root.as_ref().join("blockmeta.sqlite");
2178 let blocks_dir = fsblockdb_root.as_ref().join("blocks");
2179 fs::create_dir_all(&blocks_dir)?;
2180 Ok(FsBlockDb {
2181 conn: Connection::open(db_path).map_err(FsBlockDbError::Db)?,
2182 blocks_dir,
2183 })
2184 } else {
2185 Err(FsBlockDbError::InvalidBlockstoreRoot(
2186 fsblockdb_root.as_ref().to_path_buf(),
2187 ))
2188 }
2189 }
2190
2191 pub fn get_max_cached_height(&self) -> Result<Option<BlockHeight>, FsBlockDbError> {
2193 Ok(chain::blockmetadb_get_max_cached_height(&self.conn)?)
2194 }
2195
2196 pub fn write_block_metadata(&self, block_meta: &[BlockMeta]) -> Result<(), FsBlockDbError> {
2202 for m in block_meta {
2203 let block_path = m.block_file_path(&self.blocks_dir);
2204 match fs::metadata(&block_path) {
2205 Err(e) => {
2206 return Err(match e.kind() {
2207 io::ErrorKind::NotFound => FsBlockDbError::MissingBlockPath(block_path),
2208 _ => FsBlockDbError::Fs(e),
2209 });
2210 }
2211 Ok(meta) => {
2212 if !meta.is_file() {
2213 return Err(FsBlockDbError::InvalidBlockPath(block_path));
2214 }
2215 }
2216 }
2217 }
2218
2219 Ok(chain::blockmetadb_insert(&self.conn, block_meta)?)
2220 }
2221
2222 pub fn find_block(&self, height: BlockHeight) -> Result<Option<BlockMeta>, FsBlockDbError> {
2225 Ok(chain::blockmetadb_find_block(&self.conn, height)?)
2226 }
2227
2228 pub fn truncate_to_height(&self, block_height: BlockHeight) -> Result<(), FsBlockDbError> {
2237 Ok(chain::blockmetadb_truncate_to_height(
2238 &self.conn,
2239 block_height,
2240 )?)
2241 }
2242}
2243
2244#[cfg(feature = "unstable")]
2245impl BlockSource for FsBlockDb {
2246 type Error = FsBlockDbError;
2247
2248 fn with_blocks<F, DbErrT>(
2249 &self,
2250 from_height: Option<BlockHeight>,
2251 limit: Option<usize>,
2252 with_row: F,
2253 ) -> Result<(), data_api::chain::error::Error<DbErrT, Self::Error>>
2254 where
2255 F: FnMut(CompactBlock) -> Result<(), data_api::chain::error::Error<DbErrT, Self::Error>>,
2256 {
2257 fsblockdb_with_blocks(self, from_height, limit, with_row)
2258 }
2259}
2260
2261#[cfg(feature = "unstable")]
2262impl std::fmt::Display for FsBlockDbError {
2263 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
2264 match self {
2265 FsBlockDbError::Fs(io_error) => {
2266 write!(f, "Failed to access the file system: {}", io_error)
2267 }
2268 FsBlockDbError::Db(e) => {
2269 write!(f, "There was a problem with the sqlite db: {}", e)
2270 }
2271 FsBlockDbError::Protobuf(e) => {
2272 write!(f, "Failed to parse protobuf-encoded record: {}", e)
2273 }
2274 FsBlockDbError::MissingBlockPath(block_path) => {
2275 write!(
2276 f,
2277 "CompactBlock file expected but not found at {}",
2278 block_path.display(),
2279 )
2280 }
2281 FsBlockDbError::InvalidBlockstoreRoot(fsblockdb_root) => {
2282 write!(
2283 f,
2284 "The block storage root {} is not a directory",
2285 fsblockdb_root.display(),
2286 )
2287 }
2288 FsBlockDbError::InvalidBlockPath(block_path) => {
2289 write!(
2290 f,
2291 "CompactBlock path {} is not a file",
2292 block_path.display(),
2293 )
2294 }
2295 FsBlockDbError::CorruptedData(e) => {
2296 write!(
2297 f,
2298 "The block cache has corrupted data and this caused an error: {}",
2299 e,
2300 )
2301 }
2302 FsBlockDbError::CacheMiss(height) => {
2303 write!(
2304 f,
2305 "Requested height {} does not exist in the block cache",
2306 height
2307 )
2308 }
2309 }
2310 }
2311}
2312
2313#[cfg(test)]
2314#[macro_use]
2315extern crate assert_matches;
2316
2317#[cfg(test)]
2318mod tests {
2319 use std::time::{Duration, SystemTime};
2320
2321 use secrecy::{ExposeSecret, Secret, SecretVec};
2322 use uuid::Uuid;
2323 use zcash_client_backend::data_api::{
2324 chain::ChainState,
2325 testing::{TestBuilder, TestState},
2326 Account, AccountBirthday, AccountPurpose, AccountSource, WalletRead, WalletTest,
2327 WalletWrite,
2328 };
2329 use zcash_keys::keys::{UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey};
2330 use zcash_primitives::block::BlockHash;
2331 use zcash_protocol::consensus;
2332
2333 use crate::{
2334 error::SqliteClientError, testing::db::TestDbFactory, util::Clock as _,
2335 wallet::MIN_SHIELDED_DIVERSIFIER_OFFSET, AccountUuid,
2336 };
2337
2338 #[cfg(feature = "unstable")]
2339 use zcash_keys::keys::sapling;
2340
2341 #[test]
2342 fn validate_seed() {
2343 let st = TestBuilder::new()
2344 .with_data_store_factory(TestDbFactory::default())
2345 .with_account_from_sapling_activation(BlockHash([0; 32]))
2346 .build();
2347 let account = st.test_account().unwrap();
2348
2349 assert!({
2350 st.wallet()
2351 .validate_seed(account.id(), st.test_seed().unwrap())
2352 .unwrap()
2353 });
2354
2355 assert!({
2357 let wrong_account_uuid = AccountUuid(Uuid::nil());
2358 !st.wallet()
2359 .validate_seed(wrong_account_uuid, st.test_seed().unwrap())
2360 .unwrap()
2361 });
2362
2363 assert!({
2365 !st.wallet()
2366 .validate_seed(account.id(), &SecretVec::new(vec![1u8; 32]))
2367 .unwrap()
2368 });
2369 }
2370
2371 #[test]
2372 pub(crate) fn get_next_available_address() {
2373 let mut st = TestBuilder::new()
2374 .with_data_store_factory(TestDbFactory::default())
2375 .with_account_from_sapling_activation(BlockHash([0; 32]))
2376 .build();
2377 let account = st.test_account().cloned().unwrap();
2378
2379 st.wallet_mut()
2382 .update_chain_tip(account.birthday().height())
2383 .unwrap();
2384
2385 let current_addr = st
2386 .wallet()
2387 .get_last_generated_address_matching(
2388 account.id(),
2389 UnifiedAddressRequest::AllAvailableKeys,
2390 )
2391 .unwrap();
2392 assert!(current_addr.is_some());
2393
2394 let addr2 = st
2395 .wallet_mut()
2396 .get_next_available_address(account.id(), UnifiedAddressRequest::AllAvailableKeys)
2397 .unwrap()
2398 .map(|(a, _)| a);
2399 assert!(addr2.is_some());
2400 assert_ne!(current_addr, addr2);
2401
2402 let addr2_cur = st
2403 .wallet()
2404 .get_last_generated_address_matching(
2405 account.id(),
2406 UnifiedAddressRequest::AllAvailableKeys,
2407 )
2408 .unwrap();
2409 assert_eq!(addr2, addr2_cur);
2410
2411 use zcash_keys::keys::ReceiverRequirement::*;
2414 #[cfg(feature = "orchard")]
2415 let shielded_only_request = UnifiedAddressRequest::unsafe_custom(Require, Require, Omit);
2416 #[cfg(not(feature = "orchard"))]
2417 let shielded_only_request = UnifiedAddressRequest::unsafe_custom(Omit, Require, Omit);
2418
2419 let cur_shielded_only = st
2420 .wallet()
2421 .get_last_generated_address_matching(account.id(), shielded_only_request)
2422 .unwrap();
2423 #[cfg(not(feature = "transparent-inputs"))]
2426 assert_eq!(cur_shielded_only, addr2);
2427 #[cfg(feature = "transparent-inputs")]
2429 assert!(cur_shielded_only.is_none());
2430
2431 let di_lower = st
2432 .wallet()
2433 .db()
2434 .clock
2435 .now()
2436 .duration_since(SystemTime::UNIX_EPOCH)
2437 .expect("current time is valid")
2438 .as_secs()
2439 .saturating_add(MIN_SHIELDED_DIVERSIFIER_OFFSET);
2440
2441 let (shielded_only, di) = st
2442 .wallet_mut()
2443 .get_next_available_address(account.id(), shielded_only_request)
2444 .unwrap()
2445 .expect("generated a shielded-only address");
2446
2447 assert!(u128::from(di) >= u128::from(di_lower));
2450
2451 let cur_shielded_only = st
2452 .wallet()
2453 .get_last_generated_address_matching(account.id(), shielded_only_request)
2454 .unwrap()
2455 .expect("retrieved the last-generated shielded-only address");
2456 assert_eq!(cur_shielded_only, shielded_only);
2457
2458 let collision_offset = 32;
2461
2462 st.wallet_mut()
2463 .db_mut()
2464 .clock
2465 .tick(Duration::from_secs(collision_offset));
2466
2467 let (shielded_only_2, di_2) = st
2468 .wallet_mut()
2469 .get_next_available_address(account.id(), shielded_only_request)
2470 .unwrap()
2471 .expect("generated a shielded-only address");
2472 assert_ne!(shielded_only_2, shielded_only);
2473 assert!(u128::from(di_2) >= u128::from(di_lower) + u128::from(collision_offset));
2474 }
2475
2476 #[test]
2477 pub(crate) fn import_account_hd_0() {
2478 let st = TestBuilder::new()
2479 .with_data_store_factory(TestDbFactory::default())
2480 .with_account_from_sapling_activation(BlockHash([0; 32]))
2481 .set_account_index(zip32::AccountId::ZERO)
2482 .build();
2483 assert_matches!(
2484 st.test_account().unwrap().account().source(),
2485 AccountSource::Derived { derivation, .. } if derivation.account_index() == zip32::AccountId::ZERO);
2486 }
2487
2488 #[test]
2489 pub(crate) fn import_account_hd_1_then_2() {
2490 let mut st = TestBuilder::new()
2491 .with_data_store_factory(TestDbFactory::default())
2492 .build();
2493
2494 let birthday = AccountBirthday::from_parts(
2495 ChainState::empty(st.network().sapling.unwrap() - 1, BlockHash([0; 32])),
2496 None,
2497 );
2498
2499 let seed = Secret::new(vec![0u8; 32]);
2500 let zip32_index_1 = zip32::AccountId::ZERO.next().unwrap();
2501
2502 let first = st
2503 .wallet_mut()
2504 .import_account_hd("", &seed, zip32_index_1, &birthday, None)
2505 .unwrap();
2506 assert_matches!(
2507 first.0.source(),
2508 AccountSource::Derived { derivation, .. } if derivation.account_index() == zip32_index_1);
2509
2510 let zip32_index_2 = zip32_index_1.next().unwrap();
2511 let second = st
2512 .wallet_mut()
2513 .import_account_hd("", &seed, zip32_index_2, &birthday, None)
2514 .unwrap();
2515 assert_matches!(
2516 second.0.source(),
2517 AccountSource::Derived { derivation, .. } if derivation.account_index() == zip32_index_2);
2518 }
2519
2520 fn check_collisions<C, DbT: WalletTest + WalletWrite, P: consensus::Parameters>(
2521 st: &mut TestState<C, DbT, P>,
2522 ufvk: &UnifiedFullViewingKey,
2523 birthday: &AccountBirthday,
2524 is_account_collision: impl Fn(&<DbT as WalletRead>::Error) -> bool,
2525 ) where
2526 DbT::Account: core::fmt::Debug,
2527 {
2528 assert_matches!(
2529 st.wallet_mut()
2530 .import_account_ufvk("", ufvk, birthday, AccountPurpose::Spending { derivation: None }, None),
2531 Err(e) if is_account_collision(&e)
2532 );
2533
2534 #[cfg(feature = "transparent-inputs")]
2537 {
2538 assert!(ufvk.transparent().is_some());
2539 let subset_ufvk = UnifiedFullViewingKey::new(
2540 None,
2541 ufvk.sapling().cloned(),
2542 #[cfg(feature = "orchard")]
2543 ufvk.orchard().cloned(),
2544 )
2545 .unwrap();
2546 assert_matches!(
2547 st.wallet_mut().import_account_ufvk(
2548 "",
2549 &subset_ufvk,
2550 birthday,
2551 AccountPurpose::Spending { derivation: None },
2552 None,
2553 ),
2554 Err(e) if is_account_collision(&e)
2555 );
2556 }
2557
2558 #[cfg(feature = "orchard")]
2561 {
2562 assert!(ufvk.orchard().is_some());
2563 let subset_ufvk = UnifiedFullViewingKey::new(
2564 #[cfg(feature = "transparent-inputs")]
2565 ufvk.transparent().cloned(),
2566 ufvk.sapling().cloned(),
2567 None,
2568 )
2569 .unwrap();
2570 assert_matches!(
2571 st.wallet_mut().import_account_ufvk(
2572 "",
2573 &subset_ufvk,
2574 birthday,
2575 AccountPurpose::Spending { derivation: None },
2576 None,
2577 ),
2578 Err(e) if is_account_collision(&e)
2579 );
2580 }
2581 }
2582
2583 #[test]
2584 pub(crate) fn import_account_hd_1_then_conflicts() {
2585 let mut st = TestBuilder::new()
2586 .with_data_store_factory(TestDbFactory::default())
2587 .build();
2588
2589 let birthday = AccountBirthday::from_parts(
2590 ChainState::empty(st.network().sapling.unwrap() - 1, BlockHash([0; 32])),
2591 None,
2592 );
2593
2594 let seed = Secret::new(vec![0u8; 32]);
2595 let zip32_index_1 = zip32::AccountId::ZERO.next().unwrap();
2596
2597 let (first_account, _) = st
2598 .wallet_mut()
2599 .import_account_hd("", &seed, zip32_index_1, &birthday, None)
2600 .unwrap();
2601 let ufvk = first_account.ufvk().unwrap();
2602
2603 assert_matches!(
2604 st.wallet_mut().import_account_hd("", &seed, zip32_index_1, &birthday, None),
2605 Err(SqliteClientError::AccountCollision(id)) if id == first_account.id());
2606
2607 check_collisions(
2608 &mut st,
2609 ufvk,
2610 &birthday,
2611 |e| matches!(e, SqliteClientError::AccountCollision(id) if *id == first_account.id()),
2612 );
2613 }
2614
2615 #[test]
2616 pub(crate) fn import_account_ufvk_then_conflicts() {
2617 let mut st = TestBuilder::new()
2618 .with_data_store_factory(TestDbFactory::default())
2619 .build();
2620
2621 let birthday = AccountBirthday::from_parts(
2622 ChainState::empty(st.network().sapling.unwrap() - 1, BlockHash([0; 32])),
2623 None,
2624 );
2625
2626 let seed = Secret::new(vec![0u8; 32]);
2627 let zip32_index_0 = zip32::AccountId::ZERO;
2628 let usk = UnifiedSpendingKey::from_seed(st.network(), seed.expose_secret(), zip32_index_0)
2629 .unwrap();
2630 let ufvk = usk.to_unified_full_viewing_key();
2631
2632 let account = st
2633 .wallet_mut()
2634 .import_account_ufvk(
2635 "",
2636 &ufvk,
2637 &birthday,
2638 AccountPurpose::Spending { derivation: None },
2639 None,
2640 )
2641 .unwrap();
2642 assert_eq!(
2643 ufvk.encode(st.network()),
2644 account.ufvk().unwrap().encode(st.network())
2645 );
2646
2647 assert_matches!(
2648 account.source(),
2649 AccountSource::Imported {
2650 purpose: AccountPurpose::Spending { .. },
2651 ..
2652 }
2653 );
2654
2655 assert_matches!(
2656 st.wallet_mut().import_account_hd("", &seed, zip32_index_0, &birthday, None),
2657 Err(SqliteClientError::AccountCollision(id)) if id == account.id());
2658
2659 check_collisions(
2660 &mut st,
2661 &ufvk,
2662 &birthday,
2663 |e| matches!(e, SqliteClientError::AccountCollision(id) if *id == account.id()),
2664 );
2665 }
2666
2667 #[test]
2668 pub(crate) fn create_account_then_conflicts() {
2669 let mut st = TestBuilder::new()
2670 .with_data_store_factory(TestDbFactory::default())
2671 .build();
2672
2673 let birthday = AccountBirthday::from_parts(
2674 ChainState::empty(st.network().sapling.unwrap() - 1, BlockHash([0; 32])),
2675 None,
2676 );
2677
2678 let seed = Secret::new(vec![0u8; 32]);
2679 let zip32_index_0 = zip32::AccountId::ZERO;
2680 let seed_based = st
2681 .wallet_mut()
2682 .create_account("", &seed, &birthday, None)
2683 .unwrap();
2684 let seed_based_account = st.wallet().get_account(seed_based.0).unwrap().unwrap();
2685 let ufvk = seed_based_account.ufvk().unwrap();
2686
2687 assert_matches!(
2688 st.wallet_mut().import_account_hd("", &seed, zip32_index_0, &birthday, None),
2689 Err(SqliteClientError::AccountCollision(id)) if id == seed_based.0);
2690
2691 check_collisions(
2692 &mut st,
2693 ufvk,
2694 &birthday,
2695 |e| matches!(e, SqliteClientError::AccountCollision(id) if *id == seed_based.0),
2696 );
2697 }
2698
2699 #[cfg(feature = "transparent-inputs")]
2700 #[test]
2701 fn transparent_receivers() {
2702 use std::collections::BTreeSet;
2703
2704 use crate::{
2705 testing::BlockCache, wallet::transparent::transaction_data_requests, GapLimits,
2706 };
2707 use zcash_client_backend::data_api::TransactionDataRequest;
2708
2709 let mut st = TestBuilder::new()
2710 .with_data_store_factory(TestDbFactory::default())
2711 .with_block_cache(BlockCache::new())
2712 .with_account_from_sapling_activation(BlockHash([0; 32]))
2713 .build();
2714 let account = st.test_account().unwrap();
2715 let ufvk = account.usk().to_unified_full_viewing_key();
2716 let (taddr, _) = account.usk().default_transparent_address();
2717 let birthday = account.birthday().height();
2718 let account_id = account.id();
2719
2720 let receivers = st
2721 .wallet()
2722 .get_transparent_receivers(account.id(), false)
2723 .unwrap();
2724
2725 assert!(receivers.contains_key(
2727 ufvk.default_address(UnifiedAddressRequest::AllAvailableKeys)
2728 .expect("A valid default address exists for the UFVK")
2729 .0
2730 .transparent()
2731 .unwrap()
2732 ));
2733
2734 assert!(receivers.contains_key(&taddr));
2736
2737 st.wallet_mut().update_chain_tip(birthday).unwrap();
2739
2740 let ephemeral_addrs = st
2742 .wallet()
2743 .get_known_ephemeral_addresses(account_id, None)
2744 .unwrap();
2745
2746 assert_eq!(
2747 ephemeral_addrs.len(),
2748 GapLimits::default().ephemeral() as usize
2749 );
2750
2751 st.wallet_mut()
2752 .db_mut()
2753 .schedule_ephemeral_address_checks()
2754 .unwrap();
2755 let data_requests =
2756 transaction_data_requests(st.wallet().conn(), &st.wallet().db().params).unwrap();
2757
2758 let base_time = st.wallet().db().clock.now();
2759 let day = Duration::from_secs(60 * 60 * 24);
2760 let mut check_times = BTreeSet::new();
2761 for (addr, _) in ephemeral_addrs {
2762 let has_valid_request = data_requests.iter().any(|req| match req {
2763 TransactionDataRequest::TransactionsInvolvingAddress {
2764 address,
2765 request_at: Some(t),
2766 ..
2767 } => {
2768 *address == addr && *t > base_time && {
2769 let t_delta = t.duration_since(base_time).unwrap();
2770 let result = t_delta < day && !check_times.contains(t);
2774 check_times.insert(*t);
2775 result
2776 }
2777 }
2778 _ => false,
2779 });
2780
2781 assert!(has_valid_request);
2782 }
2783 }
2784
2785 #[cfg(feature = "unstable")]
2786 #[test]
2787 pub(crate) fn fsblockdb_api() {
2788 use zcash_client_backend::data_api::testing::AddressType;
2789 use zcash_protocol::{consensus::NetworkConstants, value::Zatoshis};
2790
2791 use crate::testing::FsBlockCache;
2792
2793 let mut st = TestBuilder::new()
2794 .with_data_store_factory(TestDbFactory::default())
2795 .with_block_cache(FsBlockCache::new())
2796 .build();
2797
2798 assert_eq!(st.cache().get_max_cached_height().unwrap(), None);
2800
2801 let seed = [0u8; 32];
2803 let hd_account_index = zip32::AccountId::ZERO;
2804 let extsk = sapling::spending_key(&seed, st.network().coin_type(), hd_account_index);
2805 let dfvk = extsk.to_diversifiable_full_viewing_key();
2806 let (h1, meta1, _) = st.generate_next_block(
2807 &dfvk,
2808 AddressType::DefaultExternal,
2809 Zatoshis::const_from_u64(5),
2810 );
2811 let (h2, meta2, _) = st.generate_next_block(
2812 &dfvk,
2813 AddressType::DefaultExternal,
2814 Zatoshis::const_from_u64(10),
2815 );
2816
2817 assert_eq!(st.cache().get_max_cached_height().unwrap(), None);
2819
2820 st.cache().write_block_metadata(&[meta1, meta2]).unwrap();
2822
2823 assert_eq!(st.cache().get_max_cached_height().unwrap(), Some(h2),);
2825 assert_eq!(st.cache().find_block(h1).unwrap(), Some(meta1));
2826 assert_eq!(st.cache().find_block(h2).unwrap(), Some(meta2));
2827 assert_eq!(st.cache().find_block(h2 + 1).unwrap(), None);
2828
2829 st.cache().truncate_to_height(h1).unwrap();
2831 assert_eq!(st.cache().get_max_cached_height().unwrap(), Some(h1));
2832 assert_eq!(st.cache().find_block(h1).unwrap(), Some(meta1));
2833 assert_eq!(st.cache().find_block(h2).unwrap(), None);
2834 assert_eq!(st.cache().find_block(h2 + 1).unwrap(), None);
2835 }
2836}