Merge remote-tracking branch 'upstream/main' into uivk
This commit is contained in:
commit
1770c2ec5f
|
@ -16,6 +16,7 @@ and this library adheres to Rust's notion of
|
|||
- `Account`
|
||||
- `AccountBalance::with_orchard_balance_mut`
|
||||
- `AccountBirthday::orchard_frontier`
|
||||
- `AccountKind`
|
||||
- `BlockMetadata::orchard_tree_size`
|
||||
- `DecryptedTransaction::{new, tx(), orchard_outputs()}`
|
||||
- `ScannedBlock::orchard`
|
||||
|
@ -24,12 +25,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`:
|
||||
|
@ -51,10 +54,11 @@ and this library adheres to Rust's notion of
|
|||
- Arguments to `ScannedBlock::from_parts` have changed.
|
||||
- Changes to the `WalletRead` trait:
|
||||
- Added `Account` associated type.
|
||||
- Added `get_account` method.
|
||||
- Added `get_derived_account` method.
|
||||
- `get_account_for_ufvk` now returns `Self::Account` instead of a bare
|
||||
`AccountId`.
|
||||
- Added `get_orchard_nullifiers` method.
|
||||
- `get_account_for_ufvk` now returns an `Self::Account` instead of a bare
|
||||
`AccountId`
|
||||
- Added `get_seed_account` method.
|
||||
- Changes to the `InputSource` trait:
|
||||
- `select_spendable_notes` now takes its `target_value` argument as a
|
||||
`NonNegativeAmount`. Also, the values of the returned map are also
|
||||
|
|
|
@ -315,12 +315,32 @@ impl AccountBalance {
|
|||
}
|
||||
}
|
||||
|
||||
/// The kinds of accounts supported by `zcash_client_backend`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum AccountKind {
|
||||
/// An account derived from a known seed.
|
||||
Derived {
|
||||
seed_fingerprint: HdSeedFingerprint,
|
||||
account_index: zip32::AccountId,
|
||||
},
|
||||
|
||||
/// An account imported from a viewing key.
|
||||
Imported,
|
||||
}
|
||||
|
||||
/// A set of capabilities that a client account must provide.
|
||||
pub trait Account<AccountId: Copy> {
|
||||
/// Returns the unique identifier for the account.
|
||||
fn id(&self) -> AccountId;
|
||||
|
||||
/// Returns whether this account is derived or imported, and the derivation parameters
|
||||
/// if applicable.
|
||||
fn kind(&self) -> AccountKind;
|
||||
|
||||
/// Returns the UFVK that the wallet backend has stored for the account, if any.
|
||||
///
|
||||
/// Accounts for which this returns `None` cannot be used in wallet contexts, because
|
||||
/// they are unable to maintain an accurate balance.
|
||||
fn ufvk(&self) -> Option<&UnifiedFullViewingKey>;
|
||||
}
|
||||
|
||||
|
@ -329,6 +349,10 @@ impl<A: Copy> Account<A> for (A, UnifiedFullViewingKey) {
|
|||
self.0
|
||||
}
|
||||
|
||||
fn kind(&self) -> AccountKind {
|
||||
AccountKind::Imported
|
||||
}
|
||||
|
||||
fn ufvk(&self) -> Option<&UnifiedFullViewingKey> {
|
||||
Some(&self.1)
|
||||
}
|
||||
|
@ -339,6 +363,10 @@ impl<A: Copy> Account<A> for (A, Option<UnifiedFullViewingKey>) {
|
|||
self.0
|
||||
}
|
||||
|
||||
fn kind(&self) -> AccountKind {
|
||||
AccountKind::Imported
|
||||
}
|
||||
|
||||
fn ufvk(&self) -> Option<&UnifiedFullViewingKey> {
|
||||
self.1.as_ref()
|
||||
}
|
||||
|
@ -543,6 +571,23 @@ 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 the given ID, if any.
|
||||
fn get_account(
|
||||
&self,
|
||||
account_id: Self::AccountId,
|
||||
) -> Result<Option<Self::Account>, Self::Error>;
|
||||
|
||||
/// Returns the account corresponding to a given [`HdSeedFingerprint`] and
|
||||
/// [`zip32::AccountId`], if any.
|
||||
fn get_derived_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 +603,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 +659,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 +702,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 +761,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 +1333,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 +1356,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 +1389,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 +1555,25 @@ 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_account(
|
||||
&self,
|
||||
_account_id: Self::AccountId,
|
||||
) -> Result<Option<Self::Account>, Self::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_derived_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 +1582,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 +1636,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 +1659,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 +1709,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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -11,6 +11,7 @@ and this library adheres to Rust's notion of
|
|||
- A new `orchard` feature flag has been added to make it possible to
|
||||
build client code without `orchard` dependendencies.
|
||||
- `zcash_client_sqlite::AccountId`
|
||||
- `zcash_client_sqlite::wallet::Account`
|
||||
- `impl From<zcash_keys::keys::AddressGenerationError> for SqliteClientError`
|
||||
|
||||
### Changed
|
||||
|
|
|
@ -60,9 +60,9 @@ use zcash_client_backend::{
|
|||
self,
|
||||
chain::{BlockSource, ChainState, CommitmentTreeRoot},
|
||||
scanning::{ScanPriority, ScanRange},
|
||||
AccountBirthday, BlockMetadata, DecryptedTransaction, InputSource, NullifierQuery,
|
||||
ScannedBlock, SentTransaction, WalletCommitmentTrees, WalletRead, WalletSummary,
|
||||
WalletWrite, SAPLING_SHARD_HEIGHT,
|
||||
Account, AccountBirthday, AccountKind, BlockMetadata, DecryptedTransaction, InputSource,
|
||||
NullifierQuery, ScannedBlock, SentTransaction, WalletCommitmentTrees, WalletRead,
|
||||
WalletSummary, WalletWrite, SAPLING_SHARD_HEIGHT,
|
||||
},
|
||||
keys::{
|
||||
AddressGenerationError, UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey,
|
||||
|
@ -100,7 +100,7 @@ pub mod error;
|
|||
pub mod wallet;
|
||||
use wallet::{
|
||||
commitment_tree::{self, put_shard_roots},
|
||||
Account, HdSeedAccount, SubtreeScanProgress,
|
||||
SubtreeScanProgress,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -287,24 +287,46 @@ impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> InputSource for
|
|||
impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> WalletRead for WalletDb<C, P> {
|
||||
type Error = SqliteClientError;
|
||||
type AccountId = AccountId;
|
||||
type Account = (AccountId, Option<UnifiedFullViewingKey>);
|
||||
type Account = wallet::Account;
|
||||
|
||||
fn get_account_ids(&self) -> Result<Vec<AccountId>, Self::Error> {
|
||||
wallet::get_account_ids(self.conn.borrow())
|
||||
}
|
||||
|
||||
fn get_account(
|
||||
&self,
|
||||
account_id: Self::AccountId,
|
||||
) -> Result<Option<Self::Account>, Self::Error> {
|
||||
wallet::get_account(self.conn.borrow(), &self.params, account_id)
|
||||
}
|
||||
|
||||
fn get_derived_account(
|
||||
&self,
|
||||
seed: &HdSeedFingerprint,
|
||||
account_id: zip32::AccountId,
|
||||
) -> Result<Option<Self::Account>, Self::Error> {
|
||||
wallet::get_derived_account(self.conn.borrow(), &self.params, seed, account_id)
|
||||
}
|
||||
|
||||
fn validate_seed(
|
||||
&self,
|
||||
account_id: Self::AccountId,
|
||||
seed: &SecretVec<u8>,
|
||||
) -> Result<bool, Self::Error> {
|
||||
if let Some(account) = wallet::get_account(self, account_id)? {
|
||||
if let Account::Zip32(hdaccount) = account {
|
||||
let seed_fingerprint_match =
|
||||
HdSeedFingerprint::from_seed(seed) == *hdaccount.hd_seed_fingerprint();
|
||||
if let Some(account) = self.get_account(account_id)? {
|
||||
if let AccountKind::Derived {
|
||||
seed_fingerprint,
|
||||
account_index,
|
||||
} = account.kind()
|
||||
{
|
||||
let seed_fingerprint_match = HdSeedFingerprint::from_seed(seed) == seed_fingerprint;
|
||||
|
||||
let usk = UnifiedSpendingKey::from_seed(
|
||||
&self.params,
|
||||
&seed.expose_secret()[..],
|
||||
hdaccount.account_index(),
|
||||
account_index,
|
||||
)
|
||||
.map_err(|_| SqliteClientError::KeyDerivationError(hdaccount.account_index()))?;
|
||||
.map_err(|_| SqliteClientError::KeyDerivationError(account_index))?;
|
||||
|
||||
// Keys are not comparable with `Eq`, but addresses are, so we derive what should
|
||||
// be equivalent addresses for each key and use those to check for key equality.
|
||||
|
@ -314,7 +336,7 @@ impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> WalletRead for W
|
|||
Ok(usk
|
||||
.to_unified_full_viewing_key()
|
||||
.default_address(ua_request)?
|
||||
== hdaccount.ufvk().default_address(ua_request)?)
|
||||
== account.default_address(ua_request)?)
|
||||
},
|
||||
)?;
|
||||
|
||||
|
@ -333,12 +355,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 +410,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 +435,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 +489,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> {
|
||||
|
@ -490,8 +500,8 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
|||
birthday: AccountBirthday,
|
||||
) -> Result<(AccountId, UnifiedSpendingKey), Self::Error> {
|
||||
self.transactionally(|wdb| {
|
||||
let seed_id = HdSeedFingerprint::from_seed(seed);
|
||||
let account_index = wallet::max_zip32_account_index(wdb.conn.0, &seed_id)?
|
||||
let seed_fingerprint = HdSeedFingerprint::from_seed(seed);
|
||||
let account_index = wallet::max_zip32_account_index(wdb.conn.0, &seed_fingerprint)?
|
||||
.map(|a| a.next().ok_or(SqliteClientError::AccountIdOutOfRange))
|
||||
.transpose()?
|
||||
.unwrap_or(zip32::AccountId::ZERO);
|
||||
|
@ -501,8 +511,16 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
|||
.map_err(|_| SqliteClientError::KeyDerivationError(account_index))?;
|
||||
let ufvk = usk.to_unified_full_viewing_key();
|
||||
|
||||
let account = Account::Zip32(HdSeedAccount::new(seed_id, account_index, ufvk));
|
||||
let account_id = wallet::add_account(wdb.conn.0, &wdb.params, account, birthday)?;
|
||||
let account_id = wallet::add_account(
|
||||
wdb.conn.0,
|
||||
&wdb.params,
|
||||
AccountKind::Derived {
|
||||
seed_fingerprint,
|
||||
account_index,
|
||||
},
|
||||
wallet::ViewingKey::Full(Box::new(ufvk)),
|
||||
birthday,
|
||||
)?;
|
||||
|
||||
Ok((account_id, usk))
|
||||
})
|
||||
|
@ -544,6 +562,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 +925,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 +1193,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> {
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
use incrementalmerkletree::Retention;
|
||||
use rusqlite::{self, named_params, params, OptionalExtension};
|
||||
use shardtree::{error::ShardTreeError, store::ShardStore, ShardTree};
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::io::{self, Cursor};
|
||||
|
@ -83,7 +83,7 @@ use zcash_client_backend::{
|
|||
address::{Address, UnifiedAddress},
|
||||
data_api::{
|
||||
scanning::{ScanPriority, ScanRange},
|
||||
AccountBalance, AccountBirthday, BlockMetadata, Ratio, SentTransactionOutput,
|
||||
AccountBalance, AccountBirthday, AccountKind, BlockMetadata, Ratio, SentTransactionOutput,
|
||||
WalletSummary, SAPLING_SHARD_HEIGHT,
|
||||
},
|
||||
encoding::AddressCodec,
|
||||
|
@ -140,84 +140,59 @@ pub(crate) mod scanning;
|
|||
|
||||
pub(crate) const BLOCK_SAPLING_FRONTIER_ABSENT: &[u8] = &[0x0];
|
||||
|
||||
/// This tracks the allowed values of the `account_type` column of the `accounts` table
|
||||
/// and should not be made public.
|
||||
enum AccountType {
|
||||
Zip32,
|
||||
Imported,
|
||||
fn parse_account_kind(
|
||||
account_kind: u32,
|
||||
hd_seed_fingerprint: Option<[u8; 32]>,
|
||||
hd_account_index: Option<u32>,
|
||||
) -> Result<AccountKind, SqliteClientError> {
|
||||
match (account_kind, hd_seed_fingerprint, hd_account_index) {
|
||||
(0, Some(seed_fp), Some(account_index)) => Ok(AccountKind::Derived {
|
||||
seed_fingerprint: HdSeedFingerprint::from_bytes(seed_fp),
|
||||
account_index: zip32::AccountId::try_from(account_index).map_err(|_| {
|
||||
SqliteClientError::CorruptedData(
|
||||
"ZIP-32 account ID from wallet DB is out of range.".to_string(),
|
||||
)
|
||||
})?,
|
||||
}),
|
||||
(1, None, None) => Ok(AccountKind::Imported),
|
||||
(0, None, None) | (1, Some(_), Some(_)) => Err(SqliteClientError::CorruptedData(
|
||||
"Wallet DB account_kind constraint violated".to_string(),
|
||||
)),
|
||||
(_, _, _) => Err(SqliteClientError::CorruptedData(
|
||||
"Unrecognized account_kind".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for AccountType {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
fn account_kind_code(value: AccountKind) -> u32 {
|
||||
match value {
|
||||
0 => Ok(AccountType::Zip32),
|
||||
1 => Ok(AccountType::Imported),
|
||||
_ => Err(()),
|
||||
}
|
||||
AccountKind::Derived { .. } => 0,
|
||||
AccountKind::Imported => 1,
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AccountType> for u32 {
|
||||
fn from(value: AccountType) -> Self {
|
||||
match value {
|
||||
AccountType::Zip32 => 0,
|
||||
AccountType::Imported => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes the key inputs and UFVK for an account that was derived from a ZIP-32 HD seed and account index.
|
||||
/// The viewing key that an [`Account`] has available to it.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct HdSeedAccount(
|
||||
HdSeedFingerprint,
|
||||
zip32::AccountId,
|
||||
Box<UnifiedFullViewingKey>,
|
||||
);
|
||||
|
||||
impl HdSeedAccount {
|
||||
pub fn new(
|
||||
hd_seed_fingerprint: HdSeedFingerprint,
|
||||
account_index: zip32::AccountId,
|
||||
ufvk: UnifiedFullViewingKey,
|
||||
) -> Self {
|
||||
Self(hd_seed_fingerprint, account_index, Box::new(ufvk))
|
||||
}
|
||||
|
||||
/// Returns the HD seed fingerprint for this account.
|
||||
pub fn hd_seed_fingerprint(&self) -> &HdSeedFingerprint {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Returns the ZIP-32 account index for this account.
|
||||
pub fn account_index(&self) -> zip32::AccountId {
|
||||
self.1
|
||||
}
|
||||
|
||||
/// Returns the Unified Full Viewing Key for this account.
|
||||
pub fn ufvk(&self) -> &UnifiedFullViewingKey {
|
||||
&self.2
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an arbitrary account for which the seed and ZIP-32 account ID are not known
|
||||
/// and may not have been involved in creating this account.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum ImportedAccount {
|
||||
/// An account that was imported via its full viewing key.
|
||||
pub(crate) enum ViewingKey {
|
||||
/// A full viewing key.
|
||||
///
|
||||
/// This is available to derived accounts, as well as accounts directly imported as
|
||||
/// full viewing keys.
|
||||
Full(Box<UnifiedFullViewingKey>),
|
||||
/// An account that was imported via its incoming viewing key.
|
||||
|
||||
/// An incoming viewing key.
|
||||
///
|
||||
/// Accounts that have this kind of viewing key cannot be used in wallet contexts,
|
||||
/// because they are unable to maintain an accurate balance.
|
||||
Incoming(Box<UnifiedIncomingViewingKey>),
|
||||
}
|
||||
|
||||
/// Describes an account in terms of its UVK or ZIP-32 origins.
|
||||
/// An account stored in a `zcash_client_sqlite` database.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum Account {
|
||||
/// Inputs for a ZIP-32 HD account.
|
||||
Zip32(HdSeedAccount),
|
||||
/// Inputs for an imported account.
|
||||
Imported(ImportedAccount),
|
||||
pub struct Account {
|
||||
account_id: AccountId,
|
||||
kind: AccountKind,
|
||||
viewing_key: ViewingKey,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
|
@ -226,14 +201,45 @@ impl Account {
|
|||
///
|
||||
/// The diversifier index may be non-zero if the Unified Address includes a Sapling
|
||||
/// receiver, and there was no valid Sapling receiver at diversifier index zero.
|
||||
pub fn default_address(
|
||||
pub(crate) fn default_address(
|
||||
&self,
|
||||
request: UnifiedAddressRequest,
|
||||
) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> {
|
||||
match &self.viewing_key {
|
||||
ViewingKey::Full(ufvk) => ufvk.default_address(request),
|
||||
ViewingKey::Incoming(_uivk) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl zcash_client_backend::data_api::Account<AccountId> for Account {
|
||||
fn id(&self) -> AccountId {
|
||||
self.account_id
|
||||
}
|
||||
|
||||
fn kind(&self) -> AccountKind {
|
||||
self.kind
|
||||
}
|
||||
|
||||
fn ufvk(&self) -> Option<&UnifiedFullViewingKey> {
|
||||
self.viewing_key.ufvk()
|
||||
}
|
||||
}
|
||||
|
||||
impl ViewingKey {
|
||||
fn ufvk(&self) -> Option<&UnifiedFullViewingKey> {
|
||||
match self {
|
||||
Account::Zip32(HdSeedAccount(_, _, ufvk)) => ufvk.default_address(request),
|
||||
Account::Imported(ImportedAccount::Full(ufvk)) => ufvk.default_address(request),
|
||||
Account::Imported(ImportedAccount::Incoming(uivk)) => uivk.default_address(request),
|
||||
ViewingKey::Full(ufvk) => Some(ufvk),
|
||||
ViewingKey::Incoming(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn uivk(&self) -> Result<UnifiedIncomingViewingKey, SqliteClientError> {
|
||||
match self {
|
||||
ViewingKey::Full(ufvk) => Ok(ufvk
|
||||
.to_unified_incoming_viewing_key()
|
||||
.map_err(|e| SqliteClientError::CorruptedData(e.to_string()))?),
|
||||
ViewingKey::Incoming(uivk) => Ok((**uivk).to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -293,65 +299,30 @@ pub(crate) fn max_zip32_account_index(
|
|||
)
|
||||
}
|
||||
|
||||
struct AccountSqlValues<'a> {
|
||||
account_type: u32,
|
||||
hd_seed_fingerprint: Option<&'a [u8]>,
|
||||
hd_account_index: Option<u32>,
|
||||
ufvk: Option<&'a UnifiedFullViewingKey>,
|
||||
uivk: UnifiedIncomingViewingKey,
|
||||
}
|
||||
|
||||
/// Returns (account_type, hd_seed_fingerprint, hd_account_index, ufvk, uivk) for a given account.
|
||||
fn get_sql_values_for_account_parameters(
|
||||
account: &Account,
|
||||
) -> Result<AccountSqlValues, SqliteClientError> {
|
||||
Ok(match account {
|
||||
Account::Zip32(hdaccount) => AccountSqlValues {
|
||||
account_type: AccountType::Zip32.into(),
|
||||
hd_seed_fingerprint: Some(hdaccount.hd_seed_fingerprint().as_bytes()),
|
||||
hd_account_index: Some(hdaccount.account_index().into()),
|
||||
ufvk: Some(hdaccount.ufvk()),
|
||||
uivk: hdaccount
|
||||
.ufvk()
|
||||
.to_unified_incoming_viewing_key()
|
||||
.map_err(|e| SqliteClientError::CorruptedData(e.to_string()))?,
|
||||
},
|
||||
Account::Imported(ImportedAccount::Full(ufvk)) => AccountSqlValues {
|
||||
account_type: AccountType::Imported.into(),
|
||||
hd_seed_fingerprint: None,
|
||||
hd_account_index: None,
|
||||
ufvk: Some(ufvk),
|
||||
uivk: ufvk
|
||||
.to_unified_incoming_viewing_key()
|
||||
.map_err(|e| SqliteClientError::CorruptedData(e.to_string()))?,
|
||||
},
|
||||
Account::Imported(ImportedAccount::Incoming(uivk)) => AccountSqlValues {
|
||||
account_type: AccountType::Imported.into(),
|
||||
hd_seed_fingerprint: None,
|
||||
hd_account_index: None,
|
||||
ufvk: None,
|
||||
uivk: (**uivk).clone(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn add_account<P: consensus::Parameters>(
|
||||
conn: &rusqlite::Transaction,
|
||||
params: &P,
|
||||
account: Account,
|
||||
kind: AccountKind,
|
||||
viewing_key: ViewingKey,
|
||||
birthday: AccountBirthday,
|
||||
) -> Result<AccountId, SqliteClientError> {
|
||||
let args = get_sql_values_for_account_parameters(&account)?;
|
||||
let (hd_seed_fingerprint, hd_account_index) = match kind {
|
||||
AccountKind::Derived {
|
||||
seed_fingerprint,
|
||||
account_index,
|
||||
} => (Some(seed_fingerprint), Some(account_index)),
|
||||
AccountKind::Imported => (None, None),
|
||||
};
|
||||
|
||||
let orchard_item = args
|
||||
.ufvk
|
||||
let orchard_item = viewing_key
|
||||
.ufvk()
|
||||
.and_then(|ufvk| ufvk.orchard().map(|k| k.to_bytes()));
|
||||
let sapling_item = args
|
||||
.ufvk
|
||||
let sapling_item = viewing_key
|
||||
.ufvk()
|
||||
.and_then(|ufvk| ufvk.sapling().map(|k| k.to_bytes()));
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
let transparent_item = args
|
||||
.ufvk
|
||||
let transparent_item = viewing_key
|
||||
.ufvk()
|
||||
.and_then(|ufvk| ufvk.transparent().map(|k| k.serialize()));
|
||||
#[cfg(not(feature = "transparent-inputs"))]
|
||||
let transparent_item: Option<Vec<u8>> = None;
|
||||
|
@ -359,13 +330,13 @@ pub(crate) fn add_account<P: consensus::Parameters>(
|
|||
let account_id: AccountId = conn.query_row(
|
||||
r#"
|
||||
INSERT INTO accounts (
|
||||
account_type, hd_seed_fingerprint, hd_account_index,
|
||||
account_kind, hd_seed_fingerprint, hd_account_index,
|
||||
ufvk, uivk,
|
||||
orchard_fvk_item_cache, sapling_fvk_item_cache, p2pkh_fvk_item_cache,
|
||||
birthday_height, recover_until_height
|
||||
)
|
||||
VALUES (
|
||||
:account_type, :hd_seed_fingerprint, :hd_account_index,
|
||||
:account_kind, :hd_seed_fingerprint, :hd_account_index,
|
||||
:ufvk, :uivk,
|
||||
:orchard_fvk_item_cache, :sapling_fvk_item_cache, :p2pkh_fvk_item_cache,
|
||||
:birthday_height, :recover_until_height
|
||||
|
@ -373,11 +344,11 @@ pub(crate) fn add_account<P: consensus::Parameters>(
|
|||
RETURNING id;
|
||||
"#,
|
||||
named_params![
|
||||
":account_type": args.account_type,
|
||||
":hd_seed_fingerprint": args.hd_seed_fingerprint,
|
||||
":hd_account_index": args.hd_account_index,
|
||||
":ufvk": args.ufvk.map(|ufvk| ufvk.encode(params)),
|
||||
":uivk": args.uivk.to_uivk().encode(¶ms.network_type()),
|
||||
":account_kind": account_kind_code(kind),
|
||||
":hd_seed_fingerprint": hd_seed_fingerprint.as_ref().map(|fp| fp.as_bytes()),
|
||||
":hd_account_index": hd_account_index.map(u32::from),
|
||||
":ufvk": viewing_key.ufvk().map(|ufvk| ufvk.encode(params)),
|
||||
":uivk": viewing_key.uivk()?.to_uivk().encode(¶ms.network_type()),
|
||||
":orchard_fvk_item_cache": orchard_item,
|
||||
":sapling_fvk_item_cache": sapling_item,
|
||||
":p2pkh_fvk_item_cache": transparent_item,
|
||||
|
@ -387,6 +358,12 @@ pub(crate) fn add_account<P: consensus::Parameters>(
|
|||
|row| Ok(AccountId(row.get(0)?)),
|
||||
)?;
|
||||
|
||||
let account = Account {
|
||||
account_id,
|
||||
kind,
|
||||
viewing_key,
|
||||
};
|
||||
|
||||
// If a birthday frontier is available, insert it into the note commitment tree. If the
|
||||
// birthday frontier is the empty frontier, we don't need to do anything.
|
||||
if let Some(frontier) = birthday.sapling_frontier().value() {
|
||||
|
@ -712,14 +689,14 @@ pub(crate) fn get_account_for_ufvk<P: consensus::Parameters>(
|
|||
conn: &rusqlite::Connection,
|
||||
params: &P,
|
||||
ufvk: &UnifiedFullViewingKey,
|
||||
) -> Result<Option<(AccountId, Option<UnifiedFullViewingKey>)>, SqliteClientError> {
|
||||
) -> Result<Option<Account>, SqliteClientError> {
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
let transparent_item = ufvk.transparent().map(|k| k.serialize());
|
||||
#[cfg(not(feature = "transparent-inputs"))]
|
||||
let transparent_item: Option<Vec<u8>> = None;
|
||||
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT id, ufvk
|
||||
"SELECT id, account_kind, hd_seed_fingerprint, hd_account_index, ufvk
|
||||
FROM accounts
|
||||
WHERE orchard_fvk_item_cache = :orchard_fvk_item_cache
|
||||
OR sapling_fvk_item_cache = :sapling_fvk_item_cache
|
||||
|
@ -735,18 +712,25 @@ pub(crate) fn get_account_for_ufvk<P: consensus::Parameters>(
|
|||
],
|
||||
|row| {
|
||||
let account_id = row.get::<_, u32>(0).map(AccountId)?;
|
||||
Ok((
|
||||
account_id,
|
||||
row.get::<_, Option<String>>(1)?
|
||||
.map(|ufvk_str| UnifiedFullViewingKey::decode(params, &ufvk_str))
|
||||
.transpose()
|
||||
.map_err(|e| {
|
||||
let kind = parse_account_kind(row.get(1)?, row.get(2)?, row.get(3)?)?;
|
||||
|
||||
// We looked up the account by FVK components, so the UFVK column must be
|
||||
// non-null.
|
||||
let ufvk_str: String = row.get(4)?;
|
||||
let viewing_key = ViewingKey::Full(Box::new(
|
||||
UnifiedFullViewingKey::decode(params, &ufvk_str).map_err(|e| {
|
||||
SqliteClientError::CorruptedData(format!(
|
||||
"Could not decode unified full viewing key for account {:?}: {}",
|
||||
account_id, e
|
||||
))
|
||||
})?,
|
||||
))
|
||||
));
|
||||
|
||||
Ok(Account {
|
||||
account_id,
|
||||
kind,
|
||||
viewing_key,
|
||||
})
|
||||
},
|
||||
)?
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
@ -762,12 +746,12 @@ pub(crate) fn get_account_for_ufvk<P: consensus::Parameters>(
|
|||
|
||||
/// Returns the account id corresponding to a given [`HdSeedFingerprint`]
|
||||
/// and [`zip32::AccountId`], if any.
|
||||
pub(crate) fn get_seed_account<P: consensus::Parameters>(
|
||||
pub(crate) fn get_derived_account<P: consensus::Parameters>(
|
||||
conn: &rusqlite::Connection,
|
||||
params: &P,
|
||||
seed: &HdSeedFingerprint,
|
||||
account_id: zip32::AccountId,
|
||||
) -> Result<Option<(AccountId, Option<UnifiedFullViewingKey>)>, SqliteClientError> {
|
||||
account_index: zip32::AccountId,
|
||||
) -> Result<Option<Account>, SqliteClientError> {
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT id, ufvk
|
||||
FROM accounts
|
||||
|
@ -778,22 +762,30 @@ pub(crate) fn get_seed_account<P: consensus::Parameters>(
|
|||
let mut accounts = stmt.query_and_then::<_, SqliteClientError, _, _>(
|
||||
named_params![
|
||||
":hd_seed_fingerprint": seed.as_bytes(),
|
||||
":hd_account_index": u32::from(account_id),
|
||||
":hd_account_index": u32::from(account_index),
|
||||
],
|
||||
|row| {
|
||||
let account_id = row.get::<_, u32>(0).map(AccountId)?;
|
||||
Ok((
|
||||
let ufvk = match row.get::<_, Option<String>>(1)? {
|
||||
None => Err(SqliteClientError::CorruptedData(format!(
|
||||
"Missing unified full viewing key for derived account {:?}",
|
||||
account_id,
|
||||
row.get::<_, Option<String>>(1)?
|
||||
.map(|ufvk_str| UnifiedFullViewingKey::decode(params, &ufvk_str))
|
||||
.transpose()
|
||||
.map_err(|e| {
|
||||
))),
|
||||
Some(ufvk_str) => UnifiedFullViewingKey::decode(params, &ufvk_str).map_err(|e| {
|
||||
SqliteClientError::CorruptedData(format!(
|
||||
"Could not decode unified full viewing key for account {:?}: {}",
|
||||
account_id, e
|
||||
))
|
||||
})?,
|
||||
))
|
||||
}),
|
||||
}?;
|
||||
Ok(Account {
|
||||
account_id,
|
||||
kind: AccountKind::Derived {
|
||||
seed_fingerprint: *seed,
|
||||
account_index,
|
||||
},
|
||||
viewing_key: ViewingKey::Full(Box::new(ufvk)),
|
||||
})
|
||||
},
|
||||
)?;
|
||||
|
||||
|
@ -1481,13 +1473,14 @@ pub(crate) fn block_height_extrema(
|
|||
})
|
||||
}
|
||||
|
||||
pub(crate) fn get_account<C: Borrow<rusqlite::Connection>, P: Parameters>(
|
||||
db: &WalletDb<C, P>,
|
||||
pub(crate) fn get_account<P: Parameters>(
|
||||
conn: &rusqlite::Connection,
|
||||
params: &P,
|
||||
account_id: AccountId,
|
||||
) -> Result<Option<Account>, SqliteClientError> {
|
||||
let mut sql = db.conn.borrow().prepare_cached(
|
||||
let mut sql = conn.prepare_cached(
|
||||
r#"
|
||||
SELECT account_type, ufvk, uivk, hd_seed_fingerprint, hd_account_index
|
||||
SELECT account_kind, hd_seed_fingerprint, hd_account_index, ufvk, uivk
|
||||
FROM accounts
|
||||
WHERE id = :account_id
|
||||
"#,
|
||||
|
@ -1497,53 +1490,40 @@ pub(crate) fn get_account<C: Borrow<rusqlite::Connection>, P: Parameters>(
|
|||
let row = result.next()?;
|
||||
match row {
|
||||
Some(row) => {
|
||||
let account_type: AccountType =
|
||||
row.get::<_, u32>("account_type")?.try_into().map_err(|_| {
|
||||
SqliteClientError::CorruptedData("Unrecognized account_type".to_string())
|
||||
})?;
|
||||
let kind = parse_account_kind(
|
||||
row.get("account_kind")?,
|
||||
row.get("hd_seed_fingerprint")?,
|
||||
row.get("hd_account_index")?,
|
||||
)?;
|
||||
|
||||
let ufvk_str: Option<String> = row.get("ufvk")?;
|
||||
let ufvk = if let Some(ufvk_str) = ufvk_str {
|
||||
Some(
|
||||
UnifiedFullViewingKey::decode(&db.params, &ufvk_str[..])
|
||||
let viewing_key = if let Some(ufvk_str) = ufvk_str {
|
||||
ViewingKey::Full(Box::new(
|
||||
UnifiedFullViewingKey::decode(params, &ufvk_str[..])
|
||||
.map_err(SqliteClientError::BadAccountData)?,
|
||||
)
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let uivk_str: String = row.get("uivk")?;
|
||||
let (network, uivk) = Uivk::decode(&uivk_str).map_err(|e| {
|
||||
SqliteClientError::CorruptedData(format!("Failure to decode UIVK: {e}"))
|
||||
})?;
|
||||
if network != db.params.network_type() {
|
||||
if network != params.network_type() {
|
||||
return Err(SqliteClientError::CorruptedData(
|
||||
"UIVK network type does not match wallet network type".to_string(),
|
||||
));
|
||||
}
|
||||
let uivk = UnifiedIncomingViewingKey::from_uivk(&uivk)
|
||||
.map_err(|e| SqliteClientError::CorruptedData(e.to_string()))?;
|
||||
|
||||
match account_type {
|
||||
AccountType::Zip32 => Ok(Some(Account::Zip32(HdSeedAccount::new(
|
||||
HdSeedFingerprint::from_bytes(row.get("hd_seed_fingerprint")?),
|
||||
zip32::AccountId::try_from(row.get::<_, u32>("hd_account_index")?).map_err(
|
||||
|_| {
|
||||
SqliteClientError::CorruptedData(
|
||||
"ZIP-32 account ID from db is out of range.".to_string(),
|
||||
)
|
||||
},
|
||||
)?,
|
||||
ufvk.ok_or_else(|| {
|
||||
SqliteClientError::CorruptedData(
|
||||
"ZIP-32 account is missing a full viewing key".to_string(),
|
||||
)
|
||||
ViewingKey::Incoming(Box::new(
|
||||
UnifiedIncomingViewingKey::from_uivk(&uivk).map_err(|e| {
|
||||
SqliteClientError::CorruptedData(format!("Failure to decode UIVK: {e}"))
|
||||
})?,
|
||||
)))),
|
||||
AccountType::Imported => Ok(Some(Account::Imported(if let Some(ufvk) = ufvk {
|
||||
ImportedAccount::Full(Box::new(ufvk))
|
||||
} else {
|
||||
ImportedAccount::Incoming(Box::new(uivk))
|
||||
}))),
|
||||
}
|
||||
))
|
||||
};
|
||||
|
||||
Ok(Some(Account {
|
||||
account_id,
|
||||
kind,
|
||||
viewing_key,
|
||||
}))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
|
@ -2703,12 +2683,11 @@ mod tests {
|
|||
use std::num::NonZeroU32;
|
||||
|
||||
use sapling::zip32::ExtendedSpendingKey;
|
||||
use zcash_client_backend::data_api::{AccountBirthday, WalletRead};
|
||||
use zcash_client_backend::data_api::{AccountBirthday, AccountKind, WalletRead};
|
||||
use zcash_primitives::{block::BlockHash, transaction::components::amount::NonNegativeAmount};
|
||||
|
||||
use crate::{
|
||||
testing::{AddressType, BlockCache, TestBuilder, TestState},
|
||||
wallet::{get_account, Account},
|
||||
AccountId,
|
||||
};
|
||||
|
||||
|
@ -2856,12 +2835,12 @@ mod tests {
|
|||
.with_test_account(AccountBirthday::from_sapling_activation)
|
||||
.build();
|
||||
let account_id = st.test_account().unwrap().0;
|
||||
let account_parameters = get_account(st.wallet(), account_id).unwrap().unwrap();
|
||||
let account_parameters = st.wallet().get_account(account_id).unwrap().unwrap();
|
||||
|
||||
let expected_account_index = zip32::AccountId::try_from(0).unwrap();
|
||||
assert_matches!(
|
||||
account_parameters,
|
||||
Account::Zip32(hdaccount) if hdaccount.account_index() == expected_account_index
|
||||
account_parameters.kind,
|
||||
AccountKind::Derived{account_index, ..} if account_index == expected_account_index
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -224,7 +224,7 @@ mod tests {
|
|||
let expected_tables = vec![
|
||||
r#"CREATE TABLE "accounts" (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
account_type INTEGER NOT NULL DEFAULT 0,
|
||||
account_kind INTEGER NOT NULL DEFAULT 0,
|
||||
hd_seed_fingerprint BLOB,
|
||||
hd_account_index INTEGER,
|
||||
ufvk TEXT,
|
||||
|
@ -234,7 +234,7 @@ mod tests {
|
|||
p2pkh_fvk_item_cache BLOB,
|
||||
birthday_height INTEGER NOT NULL,
|
||||
recover_until_height INTEGER,
|
||||
CHECK ( (account_type = 0 AND hd_seed_fingerprint IS NOT NULL AND hd_account_index IS NOT NULL AND ufvk IS NOT NULL) OR (account_type = 1 AND hd_seed_fingerprint IS NULL AND hd_account_index IS NULL) )
|
||||
CHECK ( (account_kind = 0 AND hd_seed_fingerprint IS NOT NULL AND hd_account_index IS NOT NULL AND ufvk IS NOT NULL) OR (account_kind = 1 AND hd_seed_fingerprint IS NULL AND hd_account_index IS NULL) )
|
||||
)"#,
|
||||
r#"CREATE TABLE "addresses" (
|
||||
account_id INTEGER NOT NULL,
|
||||
|
@ -1282,9 +1282,7 @@ mod tests {
|
|||
#[test]
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
fn account_produces_expected_ua_sequence() {
|
||||
use zcash_client_backend::data_api::AccountBirthday;
|
||||
|
||||
use crate::wallet::{get_account, Account};
|
||||
use zcash_client_backend::data_api::{AccountBirthday, AccountKind, WalletRead};
|
||||
|
||||
let network = Network::MainNetwork;
|
||||
let data_file = NamedTempFile::new().unwrap();
|
||||
|
@ -1300,8 +1298,11 @@ mod tests {
|
|||
.create_account(&Secret::new(seed.to_vec()), birthday)
|
||||
.unwrap();
|
||||
assert_matches!(
|
||||
get_account(&db_data, account_id),
|
||||
Ok(Some(Account::Zip32(hdaccount))) if hdaccount.account_index() == zip32::AccountId::ZERO
|
||||
db_data.get_account(account_id),
|
||||
Ok(Some(account)) if matches!(
|
||||
account.kind,
|
||||
AccountKind::Derived{account_index, ..} if account_index == zip32::AccountId::ZERO,
|
||||
)
|
||||
);
|
||||
|
||||
for tv in &test_vectors::UNIFIED[..3] {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use std::{collections::HashSet, rc::Rc};
|
||||
|
||||
use crate::wallet::{init::WalletMigrationError, AccountType};
|
||||
use crate::wallet::{account_kind_code, init::WalletMigrationError};
|
||||
use rusqlite::{named_params, Transaction};
|
||||
use schemer_rusqlite::RusqliteMigration;
|
||||
use secrecy::{ExposeSecret, SecretVec};
|
||||
use uuid::Uuid;
|
||||
use zcash_address::unified::Encoding;
|
||||
use zcash_client_backend::keys::UnifiedSpendingKey;
|
||||
use zcash_client_backend::{data_api::AccountKind, keys::UnifiedSpendingKey};
|
||||
use zcash_keys::keys::{HdSeedFingerprint, UnifiedFullViewingKey};
|
||||
use zcash_primitives::consensus;
|
||||
|
||||
|
@ -45,8 +45,11 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
|||
type Error = WalletMigrationError;
|
||||
|
||||
fn up(&self, transaction: &Transaction) -> Result<(), WalletMigrationError> {
|
||||
let account_type_zip32 = u32::from(AccountType::Zip32);
|
||||
let account_type_imported = u32::from(AccountType::Imported);
|
||||
let account_kind_derived = account_kind_code(AccountKind::Derived {
|
||||
seed_fingerprint: HdSeedFingerprint::from_bytes([0; 32]),
|
||||
account_index: zip32::AccountId::ZERO,
|
||||
});
|
||||
let account_kind_imported = account_kind_code(AccountKind::Imported);
|
||||
transaction.execute_batch(
|
||||
&format!(r#"
|
||||
PRAGMA foreign_keys = OFF;
|
||||
|
@ -54,7 +57,7 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
|||
|
||||
CREATE TABLE accounts_new (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
account_type INTEGER NOT NULL DEFAULT {account_type_zip32},
|
||||
account_kind INTEGER NOT NULL DEFAULT {account_kind_derived},
|
||||
hd_seed_fingerprint BLOB,
|
||||
hd_account_index INTEGER,
|
||||
ufvk TEXT,
|
||||
|
@ -65,9 +68,9 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
|||
birthday_height INTEGER NOT NULL,
|
||||
recover_until_height INTEGER,
|
||||
CHECK (
|
||||
(account_type = {account_type_zip32} AND hd_seed_fingerprint IS NOT NULL AND hd_account_index IS NOT NULL AND ufvk IS NOT NULL)
|
||||
(account_kind = {account_kind_derived} AND hd_seed_fingerprint IS NOT NULL AND hd_account_index IS NOT NULL AND ufvk IS NOT NULL)
|
||||
OR
|
||||
(account_type = {account_type_imported} AND hd_seed_fingerprint IS NULL AND hd_account_index IS NULL)
|
||||
(account_kind = {account_kind_imported} AND hd_seed_fingerprint IS NULL AND hd_account_index IS NULL)
|
||||
)
|
||||
);
|
||||
CREATE UNIQUE INDEX hd_account ON accounts_new (hd_seed_fingerprint, hd_account_index);
|
||||
|
@ -86,7 +89,6 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
|||
let mut rows = q.query([])?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let account_index: u32 = row.get("account")?;
|
||||
let account_type = u32::from(AccountType::Zip32);
|
||||
let birthday_height: u32 = row.get("birthday_height")?;
|
||||
let recover_until_height: Option<u32> = row.get("recover_until_height")?;
|
||||
|
||||
|
@ -132,13 +134,13 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
|||
transaction.execute(
|
||||
r#"
|
||||
INSERT INTO accounts_new (
|
||||
id, account_type, hd_seed_fingerprint, hd_account_index,
|
||||
id, account_kind, hd_seed_fingerprint, hd_account_index,
|
||||
ufvk, uivk,
|
||||
orchard_fvk_item_cache, sapling_fvk_item_cache, p2pkh_fvk_item_cache,
|
||||
birthday_height, recover_until_height
|
||||
)
|
||||
VALUES (
|
||||
:account_id, :account_type, :seed_id, :account_index,
|
||||
:account_id, :account_kind, :seed_id, :account_index,
|
||||
:ufvk, :uivk,
|
||||
:orchard_fvk_item_cache, :sapling_fvk_item_cache, :p2pkh_fvk_item_cache,
|
||||
:birthday_height, :recover_until_height
|
||||
|
@ -146,7 +148,7 @@ impl<P: consensus::Parameters> RusqliteMigration for Migration<P> {
|
|||
"#,
|
||||
named_params![
|
||||
":account_id": account_id,
|
||||
":account_type": account_type,
|
||||
":account_kind": account_kind_derived,
|
||||
":seed_id": seed_id.as_bytes(),
|
||||
":account_index": account_index,
|
||||
":ufvk": ufvk,
|
||||
|
|
|
@ -514,7 +514,6 @@ pub enum AddressGenerationError {
|
|||
/// A Unified address cannot be generated without at least one shielded receiver being
|
||||
/// included.
|
||||
ShieldedReceiverRequired,
|
||||
|
||||
// An error occurred while deriving a key or address from an HD wallet.
|
||||
Derivation(DerivationError),
|
||||
}
|
||||
|
@ -739,7 +738,7 @@ impl UnifiedFullViewingKey {
|
|||
})
|
||||
.transpose(),
|
||||
#[cfg(not(feature = "orchard"))]
|
||||
unified::Fvk::Orchard(data) => Some(Ok::<_, UnifiedError>((
|
||||
unified::Fvk::Orchard(data) => Some(Ok::<_, DerivationError>((
|
||||
u32::from(unified::Typecode::Orchard),
|
||||
data.to_vec(),
|
||||
))),
|
||||
|
@ -756,7 +755,7 @@ impl UnifiedFullViewingKey {
|
|||
.transpose()
|
||||
}
|
||||
#[cfg(not(feature = "sapling"))]
|
||||
unified::Fvk::Sapling(data) => Some(Ok::<_, UnifiedError>((
|
||||
unified::Fvk::Sapling(data) => Some(Ok::<_, DerivationError>((
|
||||
u32::from(unified::Typecode::Sapling),
|
||||
data.to_vec(),
|
||||
))),
|
||||
|
@ -769,7 +768,7 @@ impl UnifiedFullViewingKey {
|
|||
})
|
||||
.transpose(),
|
||||
#[cfg(not(feature = "transparent-inputs"))]
|
||||
unified::Fvk::P2pkh(data) => Some(Ok::<_, UnifiedError>((
|
||||
unified::Fvk::P2pkh(data) => Some(Ok::<_, DerivationError>((
|
||||
u32::from(unified::Typecode::P2pkh),
|
||||
data.to_vec(),
|
||||
))),
|
||||
|
|
Loading…
Reference in New Issue