diff --git a/zcash_client_backend/CHANGELOG.md b/zcash_client_backend/CHANGELOG.md index b06c540af..80042cead 100644 --- a/zcash_client_backend/CHANGELOG.md +++ b/zcash_client_backend/CHANGELOG.md @@ -29,7 +29,8 @@ and this library adheres to Rust's notion of - `ScannedBlock` - `ShieldedProtocol` - `WalletCommitmentTrees` - - `WalletRead::{chain_height, block_metadata, block_fully_scanned, suggest_scan_ranges}` + - `WalletRead::{chain_height, block_metadata, block_fully_scanned, suggest_scan_ranges, + get_wallet_birthday, get_account_birthday}` - `WalletWrite::{put_blocks, update_chain_tip}` - `chain::CommitmentTreeRoot` - `scanning` A new module containing types required for `suggest_scan_ranges` diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index 9909ace18..3ef4eb2af 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -119,6 +119,16 @@ pub trait WalletRead { /// transaction is not in the main chain. fn get_tx_height(&self, txid: TxId) -> Result, Self::Error>; + /// Returns the birthday height for the wallet. + /// + /// This returns earliest birthday height among accounts maintained by this wallet, or + /// `Ok(None)` if the wallet has no initialized accounts. + fn get_wallet_birthday(&self) -> Result, Self::Error>; + + /// Returns the birthday height for the given account, or an error if the account is not known + /// to the wallet. + fn get_account_birthday(&self, account: AccountId) -> Result; + /// Returns the most recently generated unified address for the specified account, if the /// account identifier specified refers to a valid account for this wallet. /// @@ -801,6 +811,14 @@ pub mod testing { Ok(None) } + fn get_wallet_birthday(&self) -> Result, Self::Error> { + Ok(None) + } + + fn get_account_birthday(&self, _account: AccountId) -> Result { + Err(()) + } + fn get_current_address( &self, _account: AccountId, diff --git a/zcash_client_sqlite/CHANGELOG.md b/zcash_client_sqlite/CHANGELOG.md index 5037090d8..33b7e128e 100644 --- a/zcash_client_sqlite/CHANGELOG.md +++ b/zcash_client_sqlite/CHANGELOG.md @@ -26,6 +26,7 @@ and this library adheres to Rust's notion of wallet did not contain enough observed blocks to satisfy the `min_confirmations` value specified; this situation is now treated as an error. - `zcash_client_sqlite::error::SqliteClientError` has new error variants: + - `SqliteClientError::AccountUnknown` - `SqliteClientError::BlockConflict` - `SqliteClientError::CacheMiss` - `SqliteClientError::ChainHeightUnknown` @@ -37,7 +38,7 @@ and this library adheres to Rust's notion of ### Removed - The empty `wallet::transact` module has been removed. - `zcash_client_sqlite::NoteId` has been replaced with `zcash_client_sqlite::ReceivedNoteId` - as the `SentNoteId` variant of is now unused following changes to + as the `SentNoteId` variant of is now unused following changes to `zcash_client_backend::data_api::WalletRead`. - `zcash_client_sqlite::wallet::init::{init_blocks_table, init_accounts_table}` have been removed. `zcash_client_backend::data_api::WalletWrite::create_account` diff --git a/zcash_client_sqlite/src/error.rs b/zcash_client_sqlite/src/error.rs index d7682aed2..54d00639b 100644 --- a/zcash_client_sqlite/src/error.rs +++ b/zcash_client_sqlite/src/error.rs @@ -66,6 +66,9 @@ pub enum SqliteClientError { /// The space of allocatable diversifier indices has been exhausted for the given account. DiversifierIndexOutOfRange, + /// The account for which information was requested does not belong to the wallet. + AccountUnknown(AccountId), + /// An error occurred deriving a spending key from a seed and an account /// identifier. KeyDerivationError(AccountId), @@ -132,6 +135,8 @@ impl fmt::Display for SqliteClientError { SqliteClientError::BlockConflict(h) => write!(f, "A block hash conflict occurred at height {}; rewind required.", u32::from(*h)), SqliteClientError::NonSequentialBlocks => write!(f, "`put_blocks` requires that the provided block range be sequential"), SqliteClientError::DiversifierIndexOutOfRange => write!(f, "The space of available diversifier indices is exhausted"), + SqliteClientError::AccountUnknown(id) => write!(f, "Account {} does not belong to this wallet.", u32::from(*id)), + SqliteClientError::KeyDerivationError(acct_id) => write!(f, "Key derivation failed for account {:?}", acct_id), SqliteClientError::AccountIdDiscontinuity => write!(f, "Wallet account identifiers must be sequential."), SqliteClientError::AccountIdOutOfRange => write!(f, "Wallet account identifiers must be less than 0x7FFFFFFF."), diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 829abbbe6..318fc16ab 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -206,6 +206,14 @@ impl, P: consensus::Parameters> WalletRead for W wallet::get_tx_height(self.conn.borrow(), txid).map_err(SqliteClientError::from) } + fn get_wallet_birthday(&self) -> Result, Self::Error> { + wallet::wallet_birthday(self.conn.borrow()).map_err(SqliteClientError::from) + } + + fn get_account_birthday(&self, account: AccountId) -> Result { + wallet::account_birthday(self.conn.borrow(), account).map_err(SqliteClientError::from) + } + fn get_current_address( &self, account: AccountId, diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index a2bf0433e..4cd5423c6 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -675,6 +675,40 @@ pub(crate) fn get_sent_memo( .transpose() } +/// Returns the minimum birthday height for accounts in the wallet. +/// +/// TODO ORCHARD: we should consider whether we want to permit protocol-restricted accounts; if so, +/// we would then want this method to take a protocol identifier to be able to learn the wallet's +/// "Orchard birthday" which might be different from the overall wallet birthday. +pub(crate) fn wallet_birthday( + conn: &rusqlite::Connection, +) -> Result, rusqlite::Error> { + conn.query_row( + "SELECT MIN(birthday_height) AS wallet_birthday FROM accounts", + [], + |row| { + row.get::<_, Option>(0) + .map(|opt| opt.map(BlockHeight::from)) + }, + ) +} + +pub(crate) fn account_birthday( + conn: &rusqlite::Connection, + account: AccountId, +) -> Result { + conn.query_row( + "SELECT birthday_height + FROM accounts + WHERE account = :account_id", + named_params![":account_id": u32::from(account)], + |row| row.get::<_, u32>(0).map(BlockHeight::from), + ) + .optional() + .map_err(SqliteClientError::from) + .and_then(|opt| opt.ok_or(SqliteClientError::AccountUnknown(account))) +} + /// Returns the minimum and maximum heights for blocks stored in the wallet database. pub(crate) fn block_height_extrema( conn: &rusqlite::Connection,