Skip to content

Commit

Permalink
zcash_client_sqlite: Use account source metadata for note selection.
Browse files Browse the repository at this point in the history
  • Loading branch information
nuttycom committed Mar 9, 2024
1 parent 1fdd0dc commit 9683813
Show file tree
Hide file tree
Showing 12 changed files with 278 additions and 121 deletions.
13 changes: 10 additions & 3 deletions zcash_client_backend/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this library adheres to Rust's notion of
- `Account`
- `AccountBalance::with_orchard_balance_mut`
- `AccountBirthday::orchard_frontier`
- `AccountSources`
- `BlockMetadata::orchard_tree_size`
- `DecryptedTransaction::{new, tx(), orchard_outputs()}`
- `ScannedBlock::orchard`
Expand Down Expand Up @@ -54,9 +55,11 @@ and this library adheres to Rust's notion of
- `get_account_for_ufvk` now returns an `Self::Account` instead of a bare
`AccountId`
- 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
`NonNegativeAmount`s instead of `Amount`s.
- `select_spendable_notes` has changed:
- It now takes its `target_value` argument as a `NonNegativeAmount`.
- Instead of an `AccountId`, it takes an `AccountSources` argument. The
separate `sources` argument has been removed.
- The values of the returned map are `NonNegativeAmount`s instead of `Amount`s.
- Fields of `DecryptedTransaction` are now private. Use `DecryptedTransaction::new`
and the newly provided accessors instead.
- Fields of `SentTransaction` are now private. Use `SentTransaction::new`
Expand All @@ -68,6 +71,10 @@ and this library adheres to Rust's notion of
- `fn put_orchard_subtree_roots`
- Added method `WalletRead::validate_seed`
- Removed `Error::AccountNotFound` variant.
- `wallet::input_selection::InputSelector::propose_transaction` now takes an
`AccountSources` rather than a bare `AccountId`.
- `wallet::{propose_transfer, propose_standard_transfer_to_address}` now
each take an `AccountSources` instead of a bare `AccountId`.
- `zcash_client_backend::decrypt`:
- Fields of `DecryptedOutput` are now private. Use `DecryptedOutput::new`
and the newly provided accessors instead.
Expand Down
97 changes: 90 additions & 7 deletions zcash_client_backend/src/data_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,28 @@ pub trait Account<AccountId: Copy> {

/// Returns the UFVK that the wallet backend has stored for the account, if any.
fn ufvk(&self) -> Option<&UnifiedFullViewingKey>;

/// Returns the default sources of funds that are available to spending by the account.
///
/// This corresponds to the set of shielded pools for which the account's UFVK can
/// maintain balance.
fn default_sources(&self) -> Option<AccountSources<AccountId>> {
self.ufvk().map(|ufvk| {
#[cfg(not(feature = "orchard"))]
let use_orchard = false;
#[cfg(feature = "orchard")]
let use_orchard = ufvk.orchard().is_some();

let use_sapling = ufvk.sapling().is_some();

AccountSources {
account_id: self.id(),
use_orchard,
use_sapling,
use_transparent: false,
}
})
}
}

impl<A: Copy> Account<A> for (A, UnifiedFullViewingKey) {
Expand All @@ -337,6 +359,68 @@ impl<A: Copy> Account<A> for (A, Option<UnifiedFullViewingKey>) {
}
}

/// A type that describes what FVK components are known to the wallet for an account.
#[derive(Clone, Copy, Debug)]
pub struct AccountSources<AccountId> {
account_id: AccountId,
use_orchard: bool,
use_sapling: bool,
use_transparent: bool,
}

impl<AccountId: Copy> AccountSources<AccountId> {
/// Constructs AccountSources from its constituent parts
pub fn new(
account_id: AccountId,
use_orchard: bool,
use_sapling: bool,
use_transparent: bool,
) -> Self {
Self {
account_id,
use_orchard,
use_sapling,
use_transparent,
}
}

/// Returns the id for the account to which this metadata applies.
pub fn account_id(&self) -> AccountId {
self.account_id
}

/// Returns whether the account has an Orchard balance and spendability determination
/// capability.
pub fn use_orchard(&self) -> bool {
self.use_orchard

Check warning on line 395 in zcash_client_backend/src/data_api.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api.rs#L394-L395

Added lines #L394 - L395 were not covered by tests
}

/// Returns whether the account has an Sapling balance and spendability determination
/// capability.
pub fn use_sapling(&self) -> bool {
self.use_sapling
}

/// Returns whether the account has a Transparent balance determination capability.
pub fn use_transparent(&self) -> bool {
self.use_transparent

Check warning on line 406 in zcash_client_backend/src/data_api.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api.rs#L405-L406

Added lines #L405 - L406 were not covered by tests
}

/// Restricts the sources to be used to those for which the given [`UnifiedSpendingKey`]
/// provides a spending capability.
pub fn filter_with_usk(&mut self, usk: &UnifiedSpendingKey) {
self.use_orchard &= usk.has_orchard();
self.use_sapling &= usk.has_sapling();
self.use_transparent &= usk.has_transparent();

Check warning on line 414 in zcash_client_backend/src/data_api.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api.rs#L411-L414

Added lines #L411 - L414 were not covered by tests
}

/// Returns the [`UnifiedAddressRequest`] that will produce a [`UnifiedAddress`] having
/// receivers corresponding to the spending capabilities described by this value.
pub fn to_unified_address_request(&self) -> Option<UnifiedAddressRequest> {
UnifiedAddressRequest::new(self.use_orchard, self.use_sapling, self.use_transparent)
}
}

/// A polymorphic ratio type, usually used for rational numbers.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Ratio<T> {
Expand Down Expand Up @@ -473,9 +557,8 @@ pub trait InputSource {
/// be included.
fn select_spendable_notes(
&self,
account: Self::AccountId,
inputs_from: AccountSources<Self::AccountId>,
target_value: NonNegativeAmount,
sources: &[ShieldedProtocol],
anchor_height: BlockHeight,
exclude: &[Self::NoteRef],
) -> Result<Vec<ReceivedNote<Self::NoteRef, Note>>, Self::Error>;
Expand Down Expand Up @@ -1404,9 +1487,10 @@ pub mod testing {
};

use super::{
chain::CommitmentTreeRoot, scanning::ScanRange, AccountBirthday, BlockMetadata,
DecryptedTransaction, InputSource, NullifierQuery, ScannedBlock, SentTransaction,
WalletCommitmentTrees, WalletRead, WalletSummary, WalletWrite, SAPLING_SHARD_HEIGHT,
chain::CommitmentTreeRoot, scanning::ScanRange, AccountBirthday, AccountSources,
BlockMetadata, DecryptedTransaction, InputSource, NullifierQuery, ScannedBlock,
SentTransaction, WalletCommitmentTrees, WalletRead, WalletSummary, WalletWrite,
SAPLING_SHARD_HEIGHT,
};

#[cfg(feature = "transparent-inputs")]
Expand Down Expand Up @@ -1457,9 +1541,8 @@ pub mod testing {

fn select_spendable_notes(
&self,
_account: Self::AccountId,
_inputs_from: AccountSources<Self::AccountId>,
_target_value: NonNegativeAmount,
_sources: &[ShieldedProtocol],
_anchor_height: BlockHeight,
_exclude: &[Self::NoteRef],
) -> Result<Vec<ReceivedNote<Self::NoteRef, Note>>, Self::Error> {
Expand Down
9 changes: 9 additions & 0 deletions zcash_client_backend/src/data_api/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ pub enum Error<DataSourceError, CommitmentTreeError, SelectionError, FeeError> {
/// No account could be found corresponding to a provided spending key.
KeyNotRecognized,

/// No shielded source of funds was available for an account.
NoShieldedSources,

/// Zcash amount computation encountered an overflow or underflow.
BalanceError(BalanceError),

Expand Down Expand Up @@ -122,6 +125,12 @@ where
"Wallet does not contain an account corresponding to the provided spending key"
)
}
Error::NoShieldedSources => {
write!(
f,

Check warning on line 130 in zcash_client_backend/src/data_api/error.rs

View check run for this annotation

Codecov / codecov/patch

zcash_client_backend/src/data_api/error.rs#L128-L130

Added lines #L128 - L130 were not covered by tests
"A wallet account contains no shielded sources of funds."
)
}
Error::BalanceError(e) => write!(
f,
"The value lies outside the valid range of Zcash amounts: {:?}.",
Expand Down
21 changes: 11 additions & 10 deletions zcash_client_backend/src/data_api/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use sapling::{
};
use std::num::NonZeroU32;

use super::InputSource;
use super::{AccountSources, InputSource};
use crate::{
address::Address,
data_api::{
Expand Down Expand Up @@ -269,7 +269,7 @@ where
wallet_db,
params,
StandardFeeRule::PreZip313,
account.id(),
account.default_sources().ok_or(Error::NoShieldedSources)?,
min_confirmations,
to,
amount,
Expand Down Expand Up @@ -380,7 +380,7 @@ where
let proposal = propose_transfer(
wallet_db,
params,
account.id(),
account.default_sources().ok_or(Error::NoShieldedSources)?,
input_selector,
request,
min_confirmations,
Expand All @@ -405,7 +405,7 @@ where
pub fn propose_transfer<DbT, ParamsT, InputsT, CommitmentTreeErrT>(
wallet_db: &mut DbT,
params: &ParamsT,
spend_from_account: <DbT as InputSource>::AccountId,
sources: AccountSources<<DbT as InputSource>::AccountId>,
input_selector: &InputsT,
request: zip321::TransactionRequest,
min_confirmations: NonZeroU32,
Expand Down Expand Up @@ -435,7 +435,7 @@ where
wallet_db,
target_height,
anchor_height,
spend_from_account,
sources,
request,
)
.map_err(Error::from)
Expand All @@ -453,9 +453,10 @@ where
/// * `wallet_db`: A read/write reference to the wallet database.
/// * `params`: Consensus parameters.
/// * `fee_rule`: The fee rule to use in creating the transaction.
/// * `spend_from_account`: The unified account that controls the funds that will be spent
/// in the resulting transaction. This procedure will return an error if the
/// account ID does not correspond to an account known to the wallet.
/// * `sources`: Metadata that describes the unified account and the pools from which
/// funds may be spent in the resulting transaction. This procedure will return an
/// error if the contained account ID does not correspond to an account known to
/// the wallet.
/// * `min_confirmations`: The minimum number of confirmations that a previously
/// received note must have in the blockchain in order to be considered for being
/// spent. A value of 10 confirmations is recommended and 0-conf transactions are
Expand All @@ -472,7 +473,7 @@ pub fn propose_standard_transfer_to_address<DbT, ParamsT, CommitmentTreeErrT>(
wallet_db: &mut DbT,
params: &ParamsT,
fee_rule: StandardFeeRule,
spend_from_account: <DbT as InputSource>::AccountId,
sources: AccountSources<<DbT as InputSource>::AccountId>,
min_confirmations: NonZeroU32,
to: &Address,
amount: NonNegativeAmount,
Expand Down Expand Up @@ -520,7 +521,7 @@ where
propose_transfer(
wallet_db,
params,
spend_from_account,
sources,
&input_selector,
request,
min_confirmations,
Expand Down
19 changes: 4 additions & 15 deletions zcash_client_backend/src/data_api/wallet/input_selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use zcash_primitives::{

use crate::{
address::{Address, UnifiedAddress},
data_api::InputSource,
data_api::{AccountSources, InputSource},
fees::{sapling, ChangeError, ChangeStrategy, DustOutputPolicy},
proposal::{Proposal, ProposalError, ShieldedInputs},
wallet::{Note, ReceivedNote, WalletTransparentOutput},
Expand Down Expand Up @@ -148,7 +148,7 @@ pub trait InputSelector {
wallet_db: &Self::InputSource,
target_height: BlockHeight,
anchor_height: BlockHeight,
account: <Self::InputSource as InputSource>::AccountId,
sources: AccountSources<<Self::InputSource as InputSource>::AccountId>,
transaction_request: TransactionRequest,
) -> Result<
Proposal<Self::FeeRule, <Self::InputSource as InputSource>::NoteRef>,
Expand Down Expand Up @@ -328,7 +328,7 @@ where
wallet_db: &Self::InputSource,
target_height: BlockHeight,
anchor_height: BlockHeight,
account: <DbT as InputSource>::AccountId,
sources: AccountSources<<DbT as InputSource>::AccountId>,
transaction_request: TransactionRequest,
) -> Result<
Proposal<Self::FeeRule, DbT::NoteRef>,
Expand Down Expand Up @@ -454,19 +454,8 @@ where
Err(other) => return Err(other.into()),
}

#[cfg(not(feature = "orchard"))]
let selectable_pools = &[ShieldedProtocol::Sapling];
#[cfg(feature = "orchard")]
let selectable_pools = &[ShieldedProtocol::Sapling, ShieldedProtocol::Orchard];

shielded_inputs = wallet_db
.select_spendable_notes(
account,
amount_required,
selectable_pools,
anchor_height,
&exclude,
)
.select_spendable_notes(sources, amount_required, anchor_height, &exclude)
.map_err(InputSelectorError::DataSource)?;

let new_available = shielded_inputs
Expand Down
Loading

0 comments on commit 9683813

Please sign in to comment.