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 } ,
@@ -86,18 +87,19 @@ use crate::{
86
87
use zcash_primitives:: {
87
88
block:: BlockHash ,
88
89
consensus:: BlockHeight ,
90
+ legacy:: TransparentAddress ,
89
91
memo:: { Memo , MemoBytes } ,
90
92
transaction:: {
91
- components:: amount:: { BalanceError , NonNegativeAmount } ,
93
+ components:: {
94
+ amount:: { BalanceError , NonNegativeAmount } ,
95
+ OutPoint ,
96
+ } ,
92
97
Transaction , TxId ,
93
98
} ,
94
99
} ;
95
100
96
101
#[ cfg( feature = "transparent-inputs" ) ]
97
- use {
98
- crate :: wallet:: TransparentAddressMetadata ,
99
- zcash_primitives:: { legacy:: TransparentAddress , transaction:: components:: OutPoint } ,
100
- } ;
102
+ use crate :: wallet:: TransparentAddressMetadata ;
101
103
102
104
#[ cfg( any( test, feature = "test-dependencies" ) ) ]
103
105
use zcash_primitives:: consensus:: NetworkUpgrade ;
@@ -1239,7 +1241,7 @@ impl<'a, AccountId> SentTransaction<'a, AccountId> {
1239
1241
/// This type is capable of representing both shielded and transparent outputs.
1240
1242
pub struct SentTransactionOutput < AccountId > {
1241
1243
output_index : usize ,
1242
- recipient : Recipient < AccountId , Note > ,
1244
+ recipient : Recipient < AccountId , Note , OutPoint > ,
1243
1245
value : NonNegativeAmount ,
1244
1246
memo : Option < MemoBytes > ,
1245
1247
}
@@ -1256,7 +1258,7 @@ impl<AccountId> SentTransactionOutput<AccountId> {
1256
1258
/// * `memo` - the memo that was sent with this output
1257
1259
pub fn from_parts (
1258
1260
output_index : usize ,
1259
- recipient : Recipient < AccountId , Note > ,
1261
+ recipient : Recipient < AccountId , Note , OutPoint > ,
1260
1262
value : NonNegativeAmount ,
1261
1263
memo : Option < MemoBytes > ,
1262
1264
) -> Self {
@@ -1278,8 +1280,8 @@ impl<AccountId> SentTransactionOutput<AccountId> {
1278
1280
self . output_index
1279
1281
}
1280
1282
/// Returns the recipient address of the transaction, or the account id and
1281
- /// resulting note for wallet-internal outputs.
1282
- pub fn recipient ( & self ) -> & Recipient < AccountId , Note > {
1283
+ /// resulting note/outpoint for wallet-internal outputs.
1284
+ pub fn recipient ( & self ) -> & Recipient < AccountId , Note , OutPoint > {
1283
1285
& self . recipient
1284
1286
}
1285
1287
/// Returns the value of the newly created output.
@@ -1514,8 +1516,11 @@ pub trait WalletWrite: WalletRead {
1514
1516
received_tx : DecryptedTransaction < Self :: AccountId > ,
1515
1517
) -> Result < ( ) , Self :: Error > ;
1516
1518
1517
- /// Saves information about a transaction that was constructed and sent by the wallet to the
1518
- /// persistent wallet store.
1519
+ /// Saves information about a transaction constructed by the wallet to the persistent
1520
+ /// wallet store.
1521
+ ///
1522
+ /// The name `store_sent_tx` is somewhat misleading; this must be called *before* the
1523
+ /// transaction is sent to the network.
1519
1524
fn store_sent_tx (
1520
1525
& mut self ,
1521
1526
sent_tx : & SentTransaction < Self :: AccountId > ,
@@ -1608,6 +1613,97 @@ pub trait WalletCommitmentTrees {
1608
1613
) -> Result < ( ) , ShardTreeError < Self :: Error > > ;
1609
1614
}
1610
1615
1616
+ /// An error related to tracking of ephemeral transparent addresses.
1617
+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
1618
+ pub enum AddressTrackingError {
1619
+ /// The account id could not be found.
1620
+ AccountNotFound ,
1621
+
1622
+ /// The proposal cannot be constructed until transactions with previously reserved
1623
+ /// ephemeral address outputs have been mined.
1624
+ ReachedGapLimit ,
1625
+
1626
+ /// Internal error.
1627
+ Internal ( String ) ,
1628
+ }
1629
+
1630
+ impl Display for AddressTrackingError {
1631
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
1632
+ match self {
1633
+ AddressTrackingError :: AccountNotFound => write ! (
1634
+ f,
1635
+ "The account id could not be found."
1636
+ ) ,
1637
+ AddressTrackingError :: ReachedGapLimit => write ! (
1638
+ f,
1639
+ "The proposal cannot be constructed until transactions with previously reserved ephemeral address outputs have been mined."
1640
+ ) ,
1641
+ AddressTrackingError :: Internal ( e) => write ! (
1642
+ f,
1643
+ "Internal address tracking error: {}" , e
1644
+ ) ,
1645
+ }
1646
+ }
1647
+ }
1648
+
1649
+ /// Wallet operations required for tracking of ephemeral transparent addresses.
1650
+ ///
1651
+ /// This trait serves to allow the corresponding wallet functions to be abstracted
1652
+ /// away from any particular data storage substrate.
1653
+ pub trait WalletAddressTracking {
1654
+ /// The type of the account identifier.
1655
+ type AccountId : Copy + Debug + Eq + Hash ;
1656
+
1657
+ /// Reserves the next available address.
1658
+ ///
1659
+ /// To ensure that sufficient information is stored on-chain to allow recovering
1660
+ /// funds sent back to any of the used addresses, a "gap limit" of 20 addresses
1661
+ /// should be observed as described in [BIP 44]. An implementation should record
1662
+ /// the index of the first unmined address, and update it for addresses that have
1663
+ /// been observed as outputs in mined transactions when `addresses_are_mined` is
1664
+ /// called.
1665
+ ///
1666
+ /// Returns an error if all addresses within the gap limit have already been
1667
+ /// reserved.
1668
+ ///
1669
+ /// [BIP 44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#user-content-Address_gap_limit
1670
+ fn reserve_next_address (
1671
+ & self ,
1672
+ account_id : Self :: AccountId ,
1673
+ ) -> Result < TransparentAddress , AddressTrackingError > ;
1674
+
1675
+ /// Frees previously reserved ephemeral transparent addresses.
1676
+ ///
1677
+ /// This should only be used in the case that an error occurs in transaction
1678
+ /// construction after the address was reserved. It is sufficient for an
1679
+ /// implementation to only be able to unreserve the addresses that were last
1680
+ /// reserved in the given account.
1681
+ ///
1682
+ /// Returns an error if the account identifier does not correspond to a known
1683
+ /// account.
1684
+ fn unreserve_addresses (
1685
+ & self ,
1686
+ account_id : Self :: AccountId ,
1687
+ address : & [ TransparentAddress ] ,
1688
+ ) -> Result < ( ) , AddressTrackingError > ;
1689
+
1690
+ /// Mark addresses as having been used.
1691
+ fn mark_addresses_as_used (
1692
+ & self ,
1693
+ account_id : Self :: AccountId ,
1694
+ address : & [ TransparentAddress ] ,
1695
+ ) -> Result < ( ) , AddressTrackingError > ;
1696
+
1697
+ /// Checks the set of ephemeral transparent addresses within the gap limit for the
1698
+ /// given mined t-addresses, in order to update the first unmined ephemeral t-address
1699
+ /// index if necessary.
1700
+ fn mark_addresses_as_mined (
1701
+ & self ,
1702
+ account_id : Self :: AccountId ,
1703
+ addresses : & [ TransparentAddress ] ,
1704
+ ) -> Result < ( ) , AddressTrackingError > ;
1705
+ }
1706
+
1611
1707
#[ cfg( feature = "test-dependencies" ) ]
1612
1708
pub mod testing {
1613
1709
use incrementalmerkletree:: Address ;
@@ -1619,6 +1715,7 @@ pub mod testing {
1619
1715
use zcash_primitives:: {
1620
1716
block:: BlockHash ,
1621
1717
consensus:: { BlockHeight , Network } ,
1718
+ legacy:: TransparentAddress ,
1622
1719
memo:: Memo ,
1623
1720
transaction:: { components:: amount:: NonNegativeAmount , Transaction , TxId } ,
1624
1721
} ;
@@ -1633,13 +1730,14 @@ pub mod testing {
1633
1730
use super :: {
1634
1731
chain:: { ChainState , CommitmentTreeRoot } ,
1635
1732
scanning:: ScanRange ,
1636
- AccountBirthday , BlockMetadata , DecryptedTransaction , InputSource , NullifierQuery ,
1637
- ScannedBlock , SeedRelevance , SentTransaction , SpendableNotes , WalletCommitmentTrees ,
1638
- WalletRead , WalletSummary , WalletWrite , SAPLING_SHARD_HEIGHT ,
1733
+ AccountBirthday , AddressTrackingError , BlockMetadata , DecryptedTransaction , InputSource ,
1734
+ NullifierQuery , ScannedBlock , SeedRelevance , SentTransaction , SpendableNotes ,
1735
+ WalletAddressTracking , WalletCommitmentTrees , WalletRead , WalletSummary , WalletWrite ,
1736
+ SAPLING_SHARD_HEIGHT ,
1639
1737
} ;
1640
1738
1641
1739
#[ cfg( feature = "transparent-inputs" ) ]
1642
- use { crate :: wallet:: TransparentAddressMetadata , zcash_primitives :: legacy :: TransparentAddress } ;
1740
+ use crate :: wallet:: TransparentAddressMetadata ;
1643
1741
1644
1742
#[ cfg( feature = "orchard" ) ]
1645
1743
use super :: ORCHARD_SHARD_HEIGHT ;
@@ -1926,6 +2024,41 @@ pub mod testing {
1926
2024
}
1927
2025
}
1928
2026
2027
+ impl WalletAddressTracking for MockWalletDb {
2028
+ type AccountId = u32 ;
2029
+
2030
+ fn reserve_next_address (
2031
+ & self ,
2032
+ _account_id : Self :: AccountId ,
2033
+ ) -> Result < TransparentAddress , AddressTrackingError > {
2034
+ Err ( AddressTrackingError :: ReachedGapLimit )
2035
+ }
2036
+
2037
+ fn unreserve_addresses (
2038
+ & self ,
2039
+ _account_id : Self :: AccountId ,
2040
+ _addresses : & [ TransparentAddress ] ,
2041
+ ) -> Result < ( ) , AddressTrackingError > {
2042
+ Ok ( ( ) )
2043
+ }
2044
+
2045
+ fn mark_addresses_as_used (
2046
+ & self ,
2047
+ _account_id : Self :: AccountId ,
2048
+ _addresses : & [ TransparentAddress ] ,
2049
+ ) -> Result < ( ) , AddressTrackingError > {
2050
+ Ok ( ( ) )
2051
+ }
2052
+
2053
+ fn mark_addresses_as_mined (
2054
+ & self ,
2055
+ _account_id : Self :: AccountId ,
2056
+ _addresses : & [ TransparentAddress ] ,
2057
+ ) -> Result < ( ) , AddressTrackingError > {
2058
+ Ok ( ( ) )
2059
+ }
2060
+ }
2061
+
1929
2062
impl WalletCommitmentTrees for MockWalletDb {
1930
2063
type Error = Infallible ;
1931
2064
type SaplingShardStore < ' a > = MemoryShardStore < sapling:: Node , BlockHeight > ;
0 commit comments