Merge pull request #1266 from zcash/apis-for-mobile-sdks

APIs for mobile SDKs
This commit is contained in:
str4d 2024-03-13 20:57:07 +00:00 committed by GitHub
commit 05259eff75
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 215 additions and 199 deletions

View File

@ -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`:

View File

@ -543,6 +543,17 @@ pub trait WalletRead {
/// The concrete account type used by this wallet backend.
type Account: Account<Self::AccountId>;
/// Returns a vector with the IDs of all accounts known to this wallet.
fn get_account_ids(&self) -> Result<Vec<Self::AccountId>, 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<Option<Self::Account>, 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<u8>,
) -> Result<bool, Self::Error>;
/// Returns the account corresponding to a given [`UnifiedFullViewingKey`], if any.
fn get_account_for_ufvk(
&self,
ufvk: &UnifiedFullViewingKey,
) -> Result<Option<Self::Account>, 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<Option<UnifiedAddress>, 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<BlockHeight, 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<Option<BlockHeight>, 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<Option<WalletSummary<Self::AccountId>>, 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<Option<BlockHeight>, 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<Option<BlockHash>, Self::Error>;
/// Returns the available block metadata for the block at the specified height, if any.
fn block_metadata(&self, height: BlockHeight) -> Result<Option<BlockMetadata>, Self::Error>;
@ -576,6 +625,11 @@ pub trait WalletRead {
/// block.
fn block_fully_scanned(&self) -> Result<Option<BlockMetadata>, 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<Option<(BlockHeight, BlockHash)>, 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<Option<BlockHeight>, 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<Option<BlockHash>, 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<Option<(BlockHeight, BlockHash)>, 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<Option<BlockHeight>, 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<Option<BlockHeight>, 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<BlockHeight, 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<Option<UnifiedAddress>, Self::Error>;
/// Returns all unified full viewing keys known to this wallet.
fn get_unified_full_viewing_keys(
&self,
) -> Result<HashMap<Self::AccountId, UnifiedFullViewingKey>, Self::Error>;
/// Returns the account corresponding to a given [`UnifiedFullViewingKey`], if any.
fn get_account_for_ufvk(
&self,
ufvk: &UnifiedFullViewingKey,
) -> Result<Option<Self::Account>, 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<Option<Self::Account>, 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<Option<WalletSummary<Self::AccountId>>, 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<HashMap<TransparentAddress, NonNegativeAmount>, Self::Error> {
Ok(HashMap::new())
}
/// Returns a vector with the IDs of all accounts known to this wallet.
fn get_account_ids(&self) -> Result<Vec<Self::AccountId>, 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<Option<UnifiedAddress>, 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<ScannedBlock<Self::AccountId>>,
) -> 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<Self::UtxoRef, Self::Error>;
/// 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<Self::UtxoRef, Self::Error>;
}
/// 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<Vec<Self::AccountId>, Self::Error> {
Ok(Vec::new())
}
fn get_seed_account(
&self,
_seed: &HdSeedFingerprint,
_account_id: zip32::AccountId,
) -> Result<Option<Self::Account>, 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<Option<Self::Account>, Self::Error> {
Ok(None)
}
fn get_current_address(
&self,
_account: Self::AccountId,
) -> Result<Option<UnifiedAddress>, Self::Error> {
Ok(None)
}
fn get_account_birthday(
&self,
_account: Self::AccountId,
) -> Result<BlockHeight, Self::Error> {
Err(())
}
fn get_wallet_birthday(&self) -> Result<Option<BlockHeight>, Self::Error> {
Ok(None)
}
fn get_wallet_summary(
&self,
_min_confirmations: u32,
) -> Result<Option<WalletSummary<Self::AccountId>>, Self::Error> {
Ok(None)
}
fn chain_height(&self) -> Result<Option<BlockHeight>, Self::Error> {
Ok(None)
}
fn get_block_hash(
&self,
_block_height: BlockHeight,
) -> Result<Option<BlockHash>, 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<Option<(BlockHeight, BlockHash)>, Self::Error> {
Ok(None)
}
fn block_max_scanned(&self) -> Result<Option<BlockMetadata>, Self::Error> {
Ok(None)
}
@ -1563,67 +1618,16 @@ pub mod testing {
Ok(None)
}
fn get_block_hash(
&self,
_block_height: BlockHeight,
) -> Result<Option<BlockHash>, Self::Error> {
Ok(None)
}
fn get_max_height_hash(&self) -> Result<Option<(BlockHeight, BlockHash)>, Self::Error> {
Ok(None)
}
fn get_tx_height(&self, _txid: TxId) -> Result<Option<BlockHeight>, Self::Error> {
Ok(None)
}
fn get_wallet_birthday(&self) -> Result<Option<BlockHeight>, Self::Error> {
Ok(None)
}
fn get_account_birthday(
&self,
_account: Self::AccountId,
) -> Result<BlockHeight, Self::Error> {
Err(())
}
fn get_current_address(
&self,
_account: Self::AccountId,
) -> Result<Option<UnifiedAddress>, Self::Error> {
Ok(None)
}
fn get_unified_full_viewing_keys(
&self,
) -> Result<HashMap<Self::AccountId, UnifiedFullViewingKey>, Self::Error> {
Ok(HashMap::new())
}
fn get_account_for_ufvk(
&self,
_ufvk: &UnifiedFullViewingKey,
) -> Result<Option<Self::Account>, Self::Error> {
Ok(None)
}
fn get_seed_account(
&self,
_seed: &HdSeedFingerprint,
_account_id: zip32::AccountId,
) -> Result<Option<Self::Account>, Self::Error> {
Ok(None)
}
fn get_wallet_summary(
&self,
_min_confirmations: u32,
) -> Result<Option<WalletSummary<Self::AccountId>>, Self::Error> {
Ok(None)
}
fn get_memo(&self, _id_note: NoteId) -> Result<Option<Memo>, Self::Error> {
Ok(None)
}
@ -1664,10 +1668,6 @@ pub mod testing {
) -> Result<HashMap<TransparentAddress, NonNegativeAmount>, Self::Error> {
Ok(HashMap::new())
}
fn get_account_ids(&self) -> Result<Vec<Self::AccountId>, Self::Error> {
Ok(Vec::new())
}
}
impl WalletWrite for MockWalletDb {

View File

@ -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<ChainState> {
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.

View File

@ -289,6 +289,18 @@ impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> WalletRead for W
type AccountId = AccountId;
type Account = (AccountId, Option<UnifiedFullViewingKey>);
fn get_account_ids(&self) -> Result<Vec<AccountId>, Self::Error> {
wallet::get_account_ids(self.conn.borrow())
}
fn get_seed_account(
&self,
seed: &HdSeedFingerprint,
account_id: zip32::AccountId,
) -> Result<Option<Self::Account>, 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<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> WalletRead for W
}
}
fn get_account_for_ufvk(
&self,
ufvk: &UnifiedFullViewingKey,
) -> Result<Option<Self::Account>, Self::Error> {
wallet::get_account_for_ufvk(self.conn.borrow(), &self.params, ufvk)
}
fn get_current_address(
&self,
account: AccountId,
) -> Result<Option<UnifiedAddress>, 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<BlockHeight, Self::Error> {
wallet::account_birthday(self.conn.borrow(), account).map_err(SqliteClientError::from)
}
fn get_wallet_birthday(&self) -> Result<Option<BlockHeight>, Self::Error> {
wallet::wallet_birthday(self.conn.borrow()).map_err(SqliteClientError::from)
}
fn get_wallet_summary(
&self,
min_confirmations: u32,
) -> Result<Option<WalletSummary<Self::AccountId>>, 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<Option<BlockHeight>, 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<Option<BlockHash>, Self::Error> {
wallet::get_block_hash(self.conn.borrow(), block_height).map_err(SqliteClientError::from)
}
fn block_metadata(&self, height: BlockHeight) -> Result<Option<BlockMetadata>, Self::Error> {
wallet::block_metadata(self.conn.borrow(), &self.params, height)
}
@ -347,6 +400,10 @@ impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> WalletRead for W
wallet::block_fully_scanned(self.conn.borrow(), &self.params)
}
fn get_max_height_hash(&self) -> Result<Option<(BlockHeight, BlockHash)>, Self::Error> {
wallet::get_max_height_hash(self.conn.borrow()).map_err(SqliteClientError::from)
}
fn block_max_scanned(&self) -> Result<Option<BlockMetadata>, Self::Error> {
wallet::block_max_scanned(self.conn.borrow(), &self.params)
}
@ -368,69 +425,16 @@ impl<C: Borrow<rusqlite::Connection>, 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<Option<BlockHash>, Self::Error> {
wallet::get_block_hash(self.conn.borrow(), block_height).map_err(SqliteClientError::from)
}
fn get_max_height_hash(&self) -> Result<Option<(BlockHeight, BlockHash)>, Self::Error> {
wallet::get_max_height_hash(self.conn.borrow()).map_err(SqliteClientError::from)
}
fn get_tx_height(&self, txid: TxId) -> Result<Option<BlockHeight>, Self::Error> {
wallet::get_tx_height(self.conn.borrow(), txid).map_err(SqliteClientError::from)
}
fn get_wallet_birthday(&self) -> Result<Option<BlockHeight>, Self::Error> {
wallet::wallet_birthday(self.conn.borrow()).map_err(SqliteClientError::from)
}
fn get_account_birthday(&self, account: AccountId) -> Result<BlockHeight, Self::Error> {
wallet::account_birthday(self.conn.borrow(), account).map_err(SqliteClientError::from)
}
fn get_current_address(
&self,
account: AccountId,
) -> Result<Option<UnifiedAddress>, 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<HashMap<AccountId, UnifiedFullViewingKey>, Self::Error> {
wallet::get_unified_full_viewing_keys(self.conn.borrow(), &self.params)
}
fn get_account_for_ufvk(
&self,
ufvk: &UnifiedFullViewingKey,
) -> Result<Option<Self::Account>, 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<Option<Self::Account>, Self::Error> {
wallet::get_seed_account(self.conn.borrow(), &self.params, seed, account_id)
}
fn get_wallet_summary(
&self,
min_confirmations: u32,
) -> Result<Option<WalletSummary<Self::AccountId>>, 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<Option<Memo>, Self::Error> {
let sent_memo = wallet::get_sent_memo(self.conn.borrow(), note_id)?;
if sent_memo.is_some() {
@ -475,10 +479,6 @@ impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> WalletRead for W
) -> Result<HashMap<TransparentAddress, NonNegativeAmount>, Self::Error> {
wallet::get_transparent_balances(self.conn.borrow(), &self.params, account, max_height)
}
fn get_account_ids(&self) -> Result<Vec<AccountId>, Self::Error> {
wallet::get_account_ids(self.conn.borrow())
}
}
impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P> {
@ -544,6 +544,13 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
)
}
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<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
})
}
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<Self::UtxoRef, Self::Error> {
#[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<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
wallet::truncate_to_height(wdb.conn.0, &wdb.params, block_height)
})
}
fn put_received_transparent_utxo(
&mut self,
_output: &WalletTransparentOutput,
) -> Result<Self::UtxoRef, Self::Error> {
#[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<P: consensus::Parameters> WalletCommitmentTrees for WalletDb<rusqlite::Connection, P> {