30
30
//!
31
31
//! ## Core Traits
32
32
//!
33
- //! The utility functions described above depend upon four important traits defined in this
33
+ //! The utility functions described above depend upon five important traits defined in this
34
34
//! module, which between them encompass the data storage requirements of a light wallet.
35
- //! The relevant traits are [`InputSource`], [`WalletRead`], [`WalletWrite`], and
36
- //! [`WalletCommitmentTrees`]. A complete implementation of the data storage layer for a wallet
37
- //! will include an implementation of all four of these traits. See the [`zcash_client_sqlite`]
38
- //! crate for a complete example of the implementation of these traits.
35
+ //! The relevant traits are [`InputSource`], [`WalletRead`], [`WalletWrite`],
36
+ //! [`WalletCommitmentTrees`], and [`WalletAddressTracking`]. A complete implementation of the
37
+ //! data storage layer for a wallet will include an implementation of all five of these traits.
38
+ //! See the [`zcash_client_sqlite`] crate for a complete example of the implementation of these
39
+ //! traits.
39
40
//!
40
41
//! ## Accounts
41
42
//!
42
- //! The operation of the [`InputSource`], [`WalletRead`] and [`WalletWrite`] traits is built around
43
- //! the concept of a wallet having one or more accounts, with a unique `AccountId` for each
44
- //! account.
43
+ //! The operation of the [`InputSource`], [`WalletRead`], [`WalletWrite`], and
44
+ //! [`WalletAddressTracking`] traits is built around the concept of a wallet having one or more
45
+ //! accounts, with a unique `AccountId` for each account.
45
46
//!
46
47
//! An account identifier corresponds to at most a single [`UnifiedSpendingKey`]'s worth of spend
47
48
//! authority, with the received and spent notes of that account tracked via the corresponding
57
58
58
59
use std:: {
59
60
collections:: HashMap ,
60
- fmt:: Debug ,
61
+ fmt:: { self , Debug , Display } ,
61
62
hash:: Hash ,
62
63
io,
63
64
num:: { NonZeroU32 , TryFromIntError } ,
@@ -85,6 +86,7 @@ use crate::{
85
86
use zcash_primitives:: {
86
87
block:: BlockHash ,
87
88
consensus:: BlockHeight ,
89
+ legacy:: TransparentAddress ,
88
90
memo:: { Memo , MemoBytes } ,
89
91
transaction:: {
90
92
components:: amount:: { BalanceError , NonNegativeAmount } ,
@@ -94,8 +96,7 @@ use zcash_primitives::{
94
96
95
97
#[ cfg( feature = "transparent-inputs" ) ]
96
98
use {
97
- crate :: wallet:: TransparentAddressMetadata ,
98
- zcash_primitives:: { legacy:: TransparentAddress , transaction:: components:: OutPoint } ,
99
+ crate :: wallet:: TransparentAddressMetadata , zcash_primitives:: transaction:: components:: OutPoint ,
99
100
} ;
100
101
101
102
#[ cfg( feature = "test-dependencies" ) ]
@@ -1512,8 +1513,11 @@ pub trait WalletWrite: WalletRead {
1512
1513
received_tx : DecryptedTransaction < Self :: AccountId > ,
1513
1514
) -> Result < ( ) , Self :: Error > ;
1514
1515
1515
- /// Saves information about a transaction that was constructed and sent by the wallet to the
1516
- /// persistent wallet store.
1516
+ /// Saves information about a transaction constructed by the wallet to the persistent
1517
+ /// wallet store.
1518
+ ///
1519
+ /// The name `store_sent_tx` is somewhat misleading; this must be called *before* the
1520
+ /// transaction is sent to the network.
1517
1521
fn store_sent_tx (
1518
1522
& mut self ,
1519
1523
sent_tx : & SentTransaction < Self :: AccountId > ,
@@ -1606,6 +1610,87 @@ pub trait WalletCommitmentTrees {
1606
1610
) -> Result < ( ) , ShardTreeError < Self :: Error > > ;
1607
1611
}
1608
1612
1613
+ /// An error related to tracking of ephemeral transparent addresses.
1614
+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
1615
+ pub enum AddressTrackingError {
1616
+ /// Only ZIP 32 accounts are supported for ZIP 320 (TEX Addresses).
1617
+ UnsupportedAccountType ,
1618
+
1619
+ /// The proposal cannot be constructed until transactions with previously reserved
1620
+ /// ephemeral address outputs have been mined.
1621
+ ReachedGapLimit ,
1622
+
1623
+ /// Internal error.
1624
+ Internal ( String ) ,
1625
+ }
1626
+
1627
+ impl Display for AddressTrackingError {
1628
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
1629
+ match self {
1630
+ AddressTrackingError :: UnsupportedAccountType => write ! (
1631
+ f,
1632
+ "Only ZIP 32 accounts are supported for ZIP 320 (TEX Addresses)."
1633
+ ) ,
1634
+ AddressTrackingError :: ReachedGapLimit => write ! (
1635
+ f,
1636
+ "The proposal cannot be constructed until transactions with previously reserved ephemeral address outputs have been mined."
1637
+ ) ,
1638
+ AddressTrackingError :: Internal ( e) => write ! (
1639
+ f,
1640
+ "Internal address tracking error: {}" , e
1641
+ ) ,
1642
+ }
1643
+ }
1644
+ }
1645
+
1646
+ pub trait WalletAddressTracking {
1647
+ /// Reserves the next available address.
1648
+ ///
1649
+ /// To ensure that sufficient information is stored on-chain to allow recovering
1650
+ /// funds sent back to any of the used addresses, a "gap limit" of 20 addresses
1651
+ /// should be observed as described in [BIP 44]. An implementation should record
1652
+ /// the index of the first unmined address, and update it for addresses that have
1653
+ /// been observed as outputs in mined transactions when `addresses_are_mined` is
1654
+ /// called.
1655
+ ///
1656
+ /// Returns an error if all addresses within the gap limit have already been
1657
+ /// reserved.
1658
+ ///
1659
+ /// [BIP 44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#user-content-Address_gap_limit
1660
+ fn reserve_next_address (
1661
+ & self ,
1662
+ account : zip32:: AccountId ,
1663
+ ) -> Result < TransparentAddress , AddressTrackingError > ;
1664
+
1665
+ /// Frees previously reserved ephemeral transparent addresses.
1666
+ ///
1667
+ /// This should only be used in the case that an error occurs in transaction
1668
+ /// construction after the address was reserved. It is sufficient for an
1669
+ /// implementation to only be able to unreserve the last reserved address from
1670
+ /// the given account.
1671
+ ///
1672
+ /// Returns an error if the account identifier does not correspond to a known
1673
+ /// account.
1674
+ fn unreserve_addresses (
1675
+ & self ,
1676
+ address : & [ TransparentAddress ] ,
1677
+ ) -> Result < ( ) , AddressTrackingError > ;
1678
+
1679
+ /// Mark addresses as having been used.
1680
+ fn mark_addresses_as_used (
1681
+ & self ,
1682
+ address : & [ TransparentAddress ] ,
1683
+ ) -> Result < ( ) , AddressTrackingError > ;
1684
+
1685
+ /// Checks the set of ephemeral transparent addresses within the gap limit for the
1686
+ /// given mined t-addresses, in order to update the first unmined ephemeral t-address
1687
+ /// index if necessary.
1688
+ fn mark_addresses_as_mined (
1689
+ & self ,
1690
+ addresses : & [ TransparentAddress ] ,
1691
+ ) -> Result < ( ) , AddressTrackingError > ;
1692
+ }
1693
+
1609
1694
#[ cfg( feature = "test-dependencies" ) ]
1610
1695
pub mod testing {
1611
1696
use incrementalmerkletree:: Address ;
@@ -1617,6 +1702,7 @@ pub mod testing {
1617
1702
use zcash_primitives:: {
1618
1703
block:: BlockHash ,
1619
1704
consensus:: { BlockHeight , Network } ,
1705
+ legacy:: TransparentAddress ,
1620
1706
memo:: Memo ,
1621
1707
transaction:: { components:: amount:: NonNegativeAmount , Transaction , TxId } ,
1622
1708
} ;
@@ -1631,13 +1717,13 @@ pub mod testing {
1631
1717
use super :: {
1632
1718
chain:: { ChainState , CommitmentTreeRoot } ,
1633
1719
scanning:: ScanRange ,
1634
- AccountBirthday , BlockMetadata , DecryptedTransaction , InputSource , NullifierQuery ,
1635
- ScannedBlock , SentTransaction , SpendableNotes , WalletCommitmentTrees , WalletRead ,
1636
- WalletSummary , WalletWrite , SAPLING_SHARD_HEIGHT ,
1720
+ AccountBirthday , AddressTrackingError , BlockMetadata , DecryptedTransaction , InputSource ,
1721
+ NullifierQuery , ScannedBlock , SentTransaction , SpendableNotes , WalletAddressTracking ,
1722
+ WalletCommitmentTrees , WalletRead , WalletSummary , WalletWrite , SAPLING_SHARD_HEIGHT ,
1637
1723
} ;
1638
1724
1639
1725
#[ cfg( feature = "transparent-inputs" ) ]
1640
- use { crate :: wallet:: TransparentAddressMetadata , zcash_primitives :: legacy :: TransparentAddress } ;
1726
+ use crate :: wallet:: TransparentAddressMetadata ;
1641
1727
1642
1728
#[ cfg( feature = "orchard" ) ]
1643
1729
use super :: ORCHARD_SHARD_HEIGHT ;
@@ -1924,6 +2010,36 @@ pub mod testing {
1924
2010
}
1925
2011
}
1926
2012
2013
+ impl WalletAddressTracking for MockWalletDb {
2014
+ fn reserve_next_address (
2015
+ & self ,
2016
+ _account : zip32:: AccountId ,
2017
+ ) -> Result < TransparentAddress , AddressTrackingError > {
2018
+ Err ( AddressTrackingError :: ReachedGapLimit )
2019
+ }
2020
+
2021
+ fn unreserve_addresses (
2022
+ & self ,
2023
+ _addresses : & [ TransparentAddress ] ,
2024
+ ) -> Result < ( ) , AddressTrackingError > {
2025
+ Ok ( ( ) )
2026
+ }
2027
+
2028
+ fn mark_addresses_as_used (
2029
+ & self ,
2030
+ _addresses : & [ TransparentAddress ] ,
2031
+ ) -> Result < ( ) , AddressTrackingError > {
2032
+ Ok ( ( ) )
2033
+ }
2034
+
2035
+ fn mark_addresses_as_mined (
2036
+ & self ,
2037
+ _addresses : & [ TransparentAddress ] ,
2038
+ ) -> Result < ( ) , AddressTrackingError > {
2039
+ Ok ( ( ) )
2040
+ }
2041
+ }
2042
+
1927
2043
impl WalletCommitmentTrees for MockWalletDb {
1928
2044
type Error = Infallible ;
1929
2045
type SaplingShardStore < ' a > = MemoryShardStore < sapling:: Node , BlockHeight > ;
0 commit comments