From 7d603b8c598b0f9dc69d5f77b954181321317519 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 13 Mar 2024 12:52:47 +0000 Subject: [PATCH 1/2] zcash_client_backend: Add `proto::service::TreeState::to_chain_state` --- zcash_client_backend/CHANGELOG.md | 2 ++ zcash_client_backend/src/proto.rs | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/zcash_client_backend/CHANGELOG.md b/zcash_client_backend/CHANGELOG.md index 7aa804d5f..15c08646b 100644 --- a/zcash_client_backend/CHANGELOG.md +++ b/zcash_client_backend/CHANGELOG.md @@ -24,12 +24,14 @@ and this library adheres to Rust's notion of - `ORCHARD_SHARD_HEIGHT` - `BlockMetadata::orchard_tree_size` - `WalletSummary::next_orchard_subtree_index` + - `chain::ChainState` - `chain::ScanSummary::{spent_orchard_note_count, received_orchard_note_count}` - `zcash_client_backend::fees`: - `orchard` - `ChangeValue::orchard` - `zcash_client_backend::proto`: - `service::TreeState::orchard_tree` + - `service::TreeState::to_chain_state` - `impl TryFrom<&CompactOrchardAction> for CompactAction` - `CompactOrchardAction::{cmx, nf, ephemeral_key}` - `zcash_client_backend::scanning`: diff --git a/zcash_client_backend/src/proto.rs b/zcash_client_backend/src/proto.rs index f020e731d..5277d599c 100644 --- a/zcash_client_backend/src/proto.rs +++ b/zcash_client_backend/src/proto.rs @@ -20,7 +20,7 @@ use zcash_primitives::{ }; use crate::{ - data_api::InputSource, + data_api::{chain::ChainState, InputSource}, fees::{ChangeValue, TransactionBalance}, proposal::{Proposal, ProposalError, ShieldedInputs, Step, StepOutput, StepOutputIndex}, zip321::{TransactionRequest, Zip321Error}, @@ -290,6 +290,20 @@ impl service::TreeState { &orchard_tree_bytes[..], ) } + + /// Parses this tree state into a [`ChainState`] for use with [`scan_cached_blocks`]. + /// + /// [`scan_cached_blocks`]: crate::data_api::chain::scan_cached_blocks + pub fn to_chain_state(&self) -> io::Result { + Ok(ChainState::new( + self.height + .try_into() + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid block height"))?, + self.sapling_tree()?.to_frontier(), + #[cfg(feature = "orchard")] + self.orchard_tree()?.to_frontier(), + )) + } } /// Constant for the V1 proposal serialization version. From 634ebf51ef6dae513ac65f1e992287c087625d70 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 13 Mar 2024 14:07:09 +0000 Subject: [PATCH 2/2] Reorder `WalletRead` and `WalletWrite` trait methods for clarity --- zcash_client_backend/src/data_api.rs | 246 +++++++++++++-------------- zcash_client_sqlite/src/lib.rs | 150 ++++++++-------- 2 files changed, 198 insertions(+), 198 deletions(-) diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index be9177766..a1b5493ad 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -543,6 +543,17 @@ pub trait WalletRead { /// The concrete account type used by this wallet backend. type Account: Account; + /// Returns a vector with the IDs of all accounts known to this wallet. + fn get_account_ids(&self) -> Result, Self::Error>; + + /// Returns the account corresponding to a given [`HdSeedFingerprint`] and + /// [`zip32::AccountId`], if any. + fn get_seed_account( + &self, + seed: &HdSeedFingerprint, + account_id: zip32::AccountId, + ) -> Result, Self::Error>; + /// Verifies that the given seed corresponds to the viewing key for the specified account. /// /// Returns: @@ -558,12 +569,50 @@ pub trait WalletRead { seed: &SecretVec, ) -> Result; + /// Returns the account corresponding to a given [`UnifiedFullViewingKey`], if any. + fn get_account_for_ufvk( + &self, + ufvk: &UnifiedFullViewingKey, + ) -> Result, Self::Error>; + + /// Returns the most recently generated unified address for the specified account, if the + /// account identifier specified refers to a valid account for this wallet. + /// + /// This will return `Ok(None)` if the account identifier does not correspond to a known + /// account. + fn get_current_address( + &self, + account: Self::AccountId, + ) -> 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: Self::AccountId) -> Result; + + /// Returns the birthday height for the wallet. + /// + /// This returns the 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 wallet balances and sync status for an account given the specified minimum + /// number of confirmations, or `Ok(None)` if the wallet has no balance data available. + fn get_wallet_summary( + &self, + min_confirmations: u32, + ) -> Result>, Self::Error>; + /// Returns the height of the chain as known to the wallet as of the most recent call to /// [`WalletWrite::update_chain_tip`]. /// /// This will return `Ok(None)` if the height of the current consensus chain tip is unknown. fn chain_height(&self) -> Result, Self::Error>; + /// Returns the block hash for the block at the given height, if the + /// associated block data is available. Returns `Ok(None)` if the hash + /// is not found in the database. + fn get_block_hash(&self, block_height: BlockHeight) -> Result, Self::Error>; + /// Returns the available block metadata for the block at the specified height, if any. fn block_metadata(&self, height: BlockHeight) -> Result, Self::Error>; @@ -576,6 +625,11 @@ pub trait WalletRead { /// block. fn block_fully_scanned(&self) -> Result, Self::Error>; + /// Returns the block height and hash for the block at the maximum scanned block height. + /// + /// This will return `Ok(None)` if no blocks have been scanned. + fn get_max_height_hash(&self) -> Result, Self::Error>; + /// Returns block metadata for the maximum height that the wallet has scanned. /// /// If the wallet is fully synced, this will be equivalent to `block_fully_scanned`; @@ -614,66 +668,15 @@ pub trait WalletRead { /// Returns the minimum block height corresponding to an unspent note in the wallet. fn get_min_unspent_height(&self) -> Result, Self::Error>; - /// Returns the block hash for the block at the given height, if the - /// associated block data is available. Returns `Ok(None)` if the hash - /// is not found in the database. - fn get_block_hash(&self, block_height: BlockHeight) -> Result, Self::Error>; - - /// Returns the block height and hash for the block at the maximum scanned block height. - /// - /// This will return `Ok(None)` if no blocks have been scanned. - fn get_max_height_hash(&self) -> Result, Self::Error>; - /// Returns the block height in which the specified transaction was mined, or `Ok(None)` if the /// 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 the 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: Self::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. - /// - /// This will return `Ok(None)` if the account identifier does not correspond to a known - /// account. - fn get_current_address( - &self, - account: Self::AccountId, - ) -> Result, Self::Error>; - /// Returns all unified full viewing keys known to this wallet. fn get_unified_full_viewing_keys( &self, ) -> Result, Self::Error>; - /// Returns the account corresponding to a given [`UnifiedFullViewingKey`], if any. - fn get_account_for_ufvk( - &self, - ufvk: &UnifiedFullViewingKey, - ) -> Result, Self::Error>; - - /// Returns the account corresponding to a given [`HdSeedFingerprint`] and - /// [`zip32::AccountId`], if any. - fn get_seed_account( - &self, - seed: &HdSeedFingerprint, - account_id: zip32::AccountId, - ) -> Result, Self::Error>; - - /// Returns the wallet balances and sync status for an account given the specified minimum - /// number of confirmations, or `Ok(None)` if the wallet has no balance data available. - fn get_wallet_summary( - &self, - min_confirmations: u32, - ) -> Result>, Self::Error>; - /// Returns the memo for a note. /// /// Returns `Ok(None)` if the note is known to the wallet but memo data has not yet been @@ -724,9 +727,6 @@ pub trait WalletRead { ) -> Result, Self::Error> { Ok(HashMap::new()) } - - /// Returns a vector with the IDs of all accounts known to this wallet. - fn get_account_ids(&self) -> Result, Self::Error>; } /// Metadata describing the sizes of the zcash note commitment trees as of a particular block. @@ -1299,6 +1299,15 @@ pub trait WalletWrite: WalletRead { request: UnifiedAddressRequest, ) -> Result, Self::Error>; + /// Updates the wallet's view of the blockchain. + /// + /// This method is used to provide the wallet with information about the state of the + /// blockchain, and detect any previously scanned data that needs to be re-validated + /// before proceeding with scanning. It should be called at wallet startup prior to calling + /// [`WalletRead::suggest_scan_ranges`] in order to provide the wallet with the information it + /// needs to correctly prioritize scanning operations. + fn update_chain_tip(&mut self, tip_height: BlockHeight) -> Result<(), Self::Error>; + /// Updates the state of the wallet database by persisting the provided block information, /// along with the note commitments that were detected when scanning the block for transactions /// pertaining to this wallet. @@ -1313,14 +1322,11 @@ pub trait WalletWrite: WalletRead { blocks: Vec>, ) -> Result<(), Self::Error>; - /// Updates the wallet's view of the blockchain. - /// - /// This method is used to provide the wallet with information about the state of the - /// blockchain, and detect any previously scanned data that needs to be re-validated - /// before proceeding with scanning. It should be called at wallet startup prior to calling - /// [`WalletRead::suggest_scan_ranges`] in order to provide the wallet with the information it - /// needs to correctly prioritize scanning operations. - fn update_chain_tip(&mut self, tip_height: BlockHeight) -> Result<(), Self::Error>; + /// Adds a transparent UTXO received by the wallet to the data store. + fn put_received_transparent_utxo( + &mut self, + output: &WalletTransparentOutput, + ) -> Result; /// Caches a decrypted transaction in the persistent wallet store. fn store_decrypted_tx( @@ -1349,12 +1355,6 @@ pub trait WalletWrite: WalletRead { /// /// There may be restrictions on heights to which it is possible to truncate. fn truncate_to_height(&mut self, block_height: BlockHeight) -> Result<(), Self::Error>; - - /// Adds a transparent UTXO received by the wallet to the data store. - fn put_received_transparent_utxo( - &mut self, - output: &WalletTransparentOutput, - ) -> Result; } /// This trait describes a capability for manipulating wallet note commitment trees. @@ -1521,6 +1521,18 @@ pub mod testing { type AccountId = u32; type Account = (Self::AccountId, UnifiedFullViewingKey); + fn get_account_ids(&self) -> Result, Self::Error> { + Ok(Vec::new()) + } + + fn get_seed_account( + &self, + _seed: &HdSeedFingerprint, + _account_id: zip32::AccountId, + ) -> Result, Self::Error> { + Ok(None) + } + fn validate_seed( &self, _account_id: Self::AccountId, @@ -1529,10 +1541,49 @@ pub mod testing { Ok(false) } + fn get_account_for_ufvk( + &self, + _ufvk: &UnifiedFullViewingKey, + ) -> Result, Self::Error> { + Ok(None) + } + + fn get_current_address( + &self, + _account: Self::AccountId, + ) -> Result, Self::Error> { + Ok(None) + } + + fn get_account_birthday( + &self, + _account: Self::AccountId, + ) -> Result { + Err(()) + } + + fn get_wallet_birthday(&self) -> Result, Self::Error> { + Ok(None) + } + + fn get_wallet_summary( + &self, + _min_confirmations: u32, + ) -> Result>, Self::Error> { + Ok(None) + } + fn chain_height(&self) -> Result, Self::Error> { Ok(None) } + fn get_block_hash( + &self, + _block_height: BlockHeight, + ) -> Result, Self::Error> { + Ok(None) + } + fn block_metadata( &self, _height: BlockHeight, @@ -1544,6 +1595,10 @@ pub mod testing { Ok(None) } + fn get_max_height_hash(&self) -> Result, Self::Error> { + Ok(None) + } + fn block_max_scanned(&self) -> Result, Self::Error> { Ok(None) } @@ -1563,67 +1618,16 @@ pub mod testing { Ok(None) } - fn get_block_hash( - &self, - _block_height: BlockHeight, - ) -> Result, Self::Error> { - Ok(None) - } - - fn get_max_height_hash(&self) -> Result, Self::Error> { - Ok(None) - } - fn get_tx_height(&self, _txid: TxId) -> Result, Self::Error> { Ok(None) } - fn get_wallet_birthday(&self) -> Result, Self::Error> { - Ok(None) - } - - fn get_account_birthday( - &self, - _account: Self::AccountId, - ) -> Result { - Err(()) - } - - fn get_current_address( - &self, - _account: Self::AccountId, - ) -> Result, Self::Error> { - Ok(None) - } - fn get_unified_full_viewing_keys( &self, ) -> Result, Self::Error> { Ok(HashMap::new()) } - fn get_account_for_ufvk( - &self, - _ufvk: &UnifiedFullViewingKey, - ) -> Result, Self::Error> { - Ok(None) - } - - fn get_seed_account( - &self, - _seed: &HdSeedFingerprint, - _account_id: zip32::AccountId, - ) -> Result, Self::Error> { - Ok(None) - } - - fn get_wallet_summary( - &self, - _min_confirmations: u32, - ) -> Result>, Self::Error> { - Ok(None) - } - fn get_memo(&self, _id_note: NoteId) -> Result, Self::Error> { Ok(None) } @@ -1664,10 +1668,6 @@ pub mod testing { ) -> Result, Self::Error> { Ok(HashMap::new()) } - - fn get_account_ids(&self) -> Result, Self::Error> { - Ok(Vec::new()) - } } impl WalletWrite for MockWalletDb { diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 12c08f054..9e4c83252 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -289,6 +289,18 @@ impl, P: consensus::Parameters> WalletRead for W type AccountId = AccountId; type Account = (AccountId, Option); + fn get_account_ids(&self) -> Result, Self::Error> { + wallet::get_account_ids(self.conn.borrow()) + } + + fn get_seed_account( + &self, + seed: &HdSeedFingerprint, + account_id: zip32::AccountId, + ) -> Result, Self::Error> { + wallet::get_seed_account(self.conn.borrow(), &self.params, seed, account_id) + } + fn validate_seed( &self, account_id: Self::AccountId, @@ -333,12 +345,53 @@ impl, P: consensus::Parameters> WalletRead for W } } + fn get_account_for_ufvk( + &self, + ufvk: &UnifiedFullViewingKey, + ) -> Result, Self::Error> { + wallet::get_account_for_ufvk(self.conn.borrow(), &self.params, ufvk) + } + + fn get_current_address( + &self, + account: AccountId, + ) -> Result, Self::Error> { + wallet::get_current_address(self.conn.borrow(), &self.params, account) + .map(|res| res.map(|(addr, _)| addr)) + } + + fn get_account_birthday(&self, account: AccountId) -> Result { + wallet::account_birthday(self.conn.borrow(), account).map_err(SqliteClientError::from) + } + + fn get_wallet_birthday(&self) -> Result, Self::Error> { + wallet::wallet_birthday(self.conn.borrow()).map_err(SqliteClientError::from) + } + + fn get_wallet_summary( + &self, + min_confirmations: u32, + ) -> Result>, Self::Error> { + // This will return a runtime error if we call `get_wallet_summary` from two + // threads at the same time, as transactions cannot nest. + wallet::get_wallet_summary( + &self.conn.borrow().unchecked_transaction()?, + &self.params, + min_confirmations, + &SubtreeScanProgress, + ) + } + fn chain_height(&self) -> Result, Self::Error> { wallet::scan_queue_extrema(self.conn.borrow()) .map(|h| h.map(|range| *range.end())) .map_err(SqliteClientError::from) } + fn get_block_hash(&self, block_height: BlockHeight) -> Result, Self::Error> { + wallet::get_block_hash(self.conn.borrow(), block_height).map_err(SqliteClientError::from) + } + fn block_metadata(&self, height: BlockHeight) -> Result, Self::Error> { wallet::block_metadata(self.conn.borrow(), &self.params, height) } @@ -347,6 +400,10 @@ impl, P: consensus::Parameters> WalletRead for W wallet::block_fully_scanned(self.conn.borrow(), &self.params) } + fn get_max_height_hash(&self) -> Result, Self::Error> { + wallet::get_max_height_hash(self.conn.borrow()).map_err(SqliteClientError::from) + } + fn block_max_scanned(&self) -> Result, Self::Error> { wallet::block_max_scanned(self.conn.borrow(), &self.params) } @@ -368,69 +425,16 @@ impl, P: consensus::Parameters> WalletRead for W wallet::get_min_unspent_height(self.conn.borrow()).map_err(SqliteClientError::from) } - fn get_block_hash(&self, block_height: BlockHeight) -> Result, Self::Error> { - wallet::get_block_hash(self.conn.borrow(), block_height).map_err(SqliteClientError::from) - } - - fn get_max_height_hash(&self) -> Result, Self::Error> { - wallet::get_max_height_hash(self.conn.borrow()).map_err(SqliteClientError::from) - } - fn get_tx_height(&self, txid: TxId) -> Result, Self::Error> { 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, - ) -> Result, Self::Error> { - wallet::get_current_address(self.conn.borrow(), &self.params, account) - .map(|res| res.map(|(addr, _)| addr)) - } - fn get_unified_full_viewing_keys( &self, ) -> Result, Self::Error> { wallet::get_unified_full_viewing_keys(self.conn.borrow(), &self.params) } - fn get_account_for_ufvk( - &self, - ufvk: &UnifiedFullViewingKey, - ) -> Result, Self::Error> { - wallet::get_account_for_ufvk(self.conn.borrow(), &self.params, ufvk) - } - - fn get_seed_account( - &self, - seed: &HdSeedFingerprint, - account_id: zip32::AccountId, - ) -> Result, Self::Error> { - wallet::get_seed_account(self.conn.borrow(), &self.params, seed, account_id) - } - - fn get_wallet_summary( - &self, - min_confirmations: u32, - ) -> Result>, Self::Error> { - // This will return a runtime error if we call `get_wallet_summary` from two - // threads at the same time, as transactions cannot nest. - wallet::get_wallet_summary( - &self.conn.borrow().unchecked_transaction()?, - &self.params, - min_confirmations, - &SubtreeScanProgress, - ) - } - fn get_memo(&self, note_id: NoteId) -> Result, Self::Error> { let sent_memo = wallet::get_sent_memo(self.conn.borrow(), note_id)?; if sent_memo.is_some() { @@ -475,10 +479,6 @@ impl, P: consensus::Parameters> WalletRead for W ) -> Result, Self::Error> { wallet::get_transparent_balances(self.conn.borrow(), &self.params, account, max_height) } - - fn get_account_ids(&self) -> Result, Self::Error> { - wallet::get_account_ids(self.conn.borrow()) - } } impl WalletWrite for WalletDb { @@ -544,6 +544,13 @@ impl WalletWrite for WalletDb ) } + fn update_chain_tip(&mut self, tip_height: BlockHeight) -> Result<(), Self::Error> { + let tx = self.conn.transaction()?; + wallet::scanning::update_chain_tip(&tx, &self.params, tip_height)?; + tx.commit()?; + Ok(()) + } + #[tracing::instrument(skip_all, fields(height = blocks.first().map(|b| u32::from(b.height()))))] #[allow(clippy::type_complexity)] fn put_blocks( @@ -900,11 +907,17 @@ impl WalletWrite for WalletDb }) } - fn update_chain_tip(&mut self, tip_height: BlockHeight) -> Result<(), Self::Error> { - let tx = self.conn.transaction()?; - wallet::scanning::update_chain_tip(&tx, &self.params, tip_height)?; - tx.commit()?; - Ok(()) + fn put_received_transparent_utxo( + &mut self, + _output: &WalletTransparentOutput, + ) -> Result { + #[cfg(feature = "transparent-inputs")] + return wallet::put_received_transparent_utxo(&self.conn, &self.params, _output); + + #[cfg(not(feature = "transparent-inputs"))] + panic!( + "The wallet must be compiled with the transparent-inputs feature to use this method." + ); } fn store_decrypted_tx( @@ -1162,19 +1175,6 @@ impl WalletWrite for WalletDb wallet::truncate_to_height(wdb.conn.0, &wdb.params, block_height) }) } - - fn put_received_transparent_utxo( - &mut self, - _output: &WalletTransparentOutput, - ) -> Result { - #[cfg(feature = "transparent-inputs")] - return wallet::put_received_transparent_utxo(&self.conn, &self.params, _output); - - #[cfg(not(feature = "transparent-inputs"))] - panic!( - "The wallet must be compiled with the transparent-inputs feature to use this method." - ); - } } impl WalletCommitmentTrees for WalletDb {