2022-10-17 10:35:14 -07:00
|
|
|
//! Types related to the process of selecting inputs to be spent given a transaction request.
|
|
|
|
|
|
|
|
use core::marker::PhantomData;
|
2024-02-12 11:13:17 -08:00
|
|
|
use std::{
|
|
|
|
collections::BTreeMap,
|
2024-02-12 11:46:34 -08:00
|
|
|
error,
|
2024-02-12 11:13:17 -08:00
|
|
|
fmt::{self, Debug, Display},
|
|
|
|
};
|
2022-10-17 10:35:14 -07:00
|
|
|
|
2023-10-02 17:02:06 -07:00
|
|
|
use nonempty::NonEmpty;
|
2022-10-17 10:35:14 -07:00
|
|
|
use zcash_primitives::{
|
|
|
|
consensus::{self, BlockHeight},
|
|
|
|
legacy::TransparentAddress,
|
|
|
|
transaction::{
|
|
|
|
components::{
|
2023-10-09 17:05:48 -07:00
|
|
|
amount::{BalanceError, NonNegativeAmount},
|
2023-10-02 17:02:06 -07:00
|
|
|
TxOut,
|
2022-10-17 10:35:14 -07:00
|
|
|
},
|
|
|
|
fees::FeeRule,
|
|
|
|
},
|
|
|
|
zip32::AccountId,
|
|
|
|
};
|
|
|
|
|
|
|
|
use crate::{
|
2023-12-07 14:26:48 -08:00
|
|
|
address::{Address, UnifiedAddress},
|
2023-12-04 13:45:22 -08:00
|
|
|
data_api::InputSource,
|
2024-02-12 11:46:34 -08:00
|
|
|
fees::{sapling, ChangeError, ChangeStrategy, DustOutputPolicy},
|
|
|
|
proposal::{Proposal, ProposalError, ShieldedInputs},
|
2023-12-04 13:45:22 -08:00
|
|
|
wallet::{Note, ReceivedNote, WalletTransparentOutput},
|
2022-10-17 10:35:14 -07:00
|
|
|
zip321::TransactionRequest,
|
2024-02-12 11:13:17 -08:00
|
|
|
PoolType, ShieldedProtocol,
|
2022-10-17 10:35:14 -07:00
|
|
|
};
|
|
|
|
|
2024-01-26 16:08:38 -08:00
|
|
|
#[cfg(any(feature = "transparent-inputs"))]
|
2023-12-04 13:45:22 -08:00
|
|
|
use std::convert::Infallible;
|
|
|
|
|
2023-10-02 17:02:06 -07:00
|
|
|
#[cfg(feature = "transparent-inputs")]
|
2023-12-04 13:45:22 -08:00
|
|
|
use {std::collections::BTreeSet, zcash_primitives::transaction::components::OutPoint};
|
|
|
|
|
|
|
|
#[cfg(feature = "orchard")]
|
2023-12-04 13:45:22 -08:00
|
|
|
use crate::fees::orchard as orchard_fees;
|
2023-10-02 17:02:06 -07:00
|
|
|
|
2022-10-17 10:35:14 -07:00
|
|
|
/// The type of errors that may be produced in input selection.
|
2024-02-12 11:46:34 -08:00
|
|
|
#[derive(Debug)]
|
2022-10-17 10:35:14 -07:00
|
|
|
pub enum InputSelectorError<DbErrT, SelectorErrT> {
|
|
|
|
/// An error occurred accessing the underlying data store.
|
|
|
|
DataSource(DbErrT),
|
|
|
|
/// An error occurred specific to the provided input selector's selection rules.
|
|
|
|
Selection(SelectorErrT),
|
2024-02-12 11:46:34 -08:00
|
|
|
/// Input selection attempted to generate an invalid transaction proposal.
|
|
|
|
Proposal(ProposalError),
|
2022-10-17 10:35:14 -07:00
|
|
|
/// Insufficient funds were available to satisfy the payment request that inputs were being
|
|
|
|
/// selected to attempt to satisfy.
|
2023-10-09 17:05:48 -07:00
|
|
|
InsufficientFunds {
|
|
|
|
available: NonNegativeAmount,
|
|
|
|
required: NonNegativeAmount,
|
|
|
|
},
|
2023-04-03 12:53:43 -07:00
|
|
|
/// The data source does not have enough information to choose an expiry height
|
|
|
|
/// for the transaction.
|
|
|
|
SyncRequired,
|
2022-10-17 10:35:14 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<DE: fmt::Display, SE: fmt::Display> fmt::Display for InputSelectorError<DE, SE> {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
match &self {
|
|
|
|
InputSelectorError::DataSource(e) => {
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"The underlying datasource produced the following error: {}",
|
|
|
|
e
|
|
|
|
)
|
|
|
|
}
|
|
|
|
InputSelectorError::Selection(e) => {
|
|
|
|
write!(f, "Note selection encountered the following error: {}", e)
|
|
|
|
}
|
2024-02-12 11:46:34 -08:00
|
|
|
InputSelectorError::Proposal(e) => {
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"Input selection attempted to generate an invalid proposal: {}",
|
|
|
|
e
|
|
|
|
)
|
|
|
|
}
|
2022-10-17 10:35:14 -07:00
|
|
|
InputSelectorError::InsufficientFunds {
|
|
|
|
available,
|
|
|
|
required,
|
|
|
|
} => write!(
|
|
|
|
f,
|
|
|
|
"Insufficient balance (have {}, need {} including fee)",
|
2023-10-09 17:05:48 -07:00
|
|
|
u64::from(*available),
|
|
|
|
u64::from(*required)
|
2022-10-17 10:35:14 -07:00
|
|
|
),
|
2023-06-29 15:26:22 -07:00
|
|
|
InputSelectorError::SyncRequired => {
|
|
|
|
write!(f, "Insufficient chain data is available, sync required.")
|
|
|
|
}
|
2022-10-17 10:35:14 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-12 11:46:34 -08:00
|
|
|
impl<DE, SE> error::Error for InputSelectorError<DE, SE>
|
|
|
|
where
|
|
|
|
DE: Debug + Display + error::Error + 'static,
|
|
|
|
SE: Debug + Display + error::Error + 'static,
|
|
|
|
{
|
|
|
|
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
|
|
|
match &self {
|
|
|
|
Self::DataSource(e) => Some(e),
|
|
|
|
Self::Selection(e) => Some(e),
|
|
|
|
Self::Proposal(e) => Some(e),
|
|
|
|
_ => None,
|
2023-10-12 12:48:20 -07:00
|
|
|
}
|
|
|
|
}
|
2023-10-02 17:02:06 -07:00
|
|
|
}
|
|
|
|
|
2022-10-17 10:35:14 -07:00
|
|
|
/// A strategy for selecting transaction inputs and proposing transaction outputs.
|
|
|
|
///
|
|
|
|
/// Proposals should include only economically useful inputs, as determined by `Self::FeeRule`;
|
|
|
|
/// that is, do not return inputs that cause fees to increase by an amount greater than the value
|
|
|
|
/// of the input.
|
|
|
|
pub trait InputSelector {
|
|
|
|
/// The type of errors that may be generated in input selection
|
|
|
|
type Error;
|
2023-10-02 17:02:06 -07:00
|
|
|
/// The type of data source that the input selector expects to access to obtain input Sapling
|
|
|
|
/// notes. This associated type permits input selectors that may use specialized knowledge of
|
|
|
|
/// the internals of a particular backing data store, if the generic API of
|
2023-12-04 13:45:22 -08:00
|
|
|
/// `InputSource` does not provide sufficiently fine-grained operations for a particular
|
2023-10-02 17:02:06 -07:00
|
|
|
/// backing store to optimally perform input selection.
|
2023-12-04 13:45:22 -08:00
|
|
|
type InputSource: InputSource;
|
2022-10-17 10:35:14 -07:00
|
|
|
/// The type of the fee rule that this input selector uses when computing fees.
|
|
|
|
type FeeRule: FeeRule;
|
|
|
|
|
|
|
|
/// Performs input selection and returns a proposal for transaction construction including
|
|
|
|
/// change and fee outputs.
|
|
|
|
///
|
|
|
|
/// Implementations of this method should return inputs sufficient to satisfy the given
|
|
|
|
/// transaction request using a best-effort strategy to preserve user privacy, as follows:
|
|
|
|
/// * If it is possible to satisfy the specified transaction request by creating
|
|
|
|
/// a fully-shielded transaction without requiring value to cross pool boundaries,
|
|
|
|
/// return the inputs necessary to construct such a transaction; otherwise
|
|
|
|
/// * If it is possible to satisfy the transaction request by creating a fully-shielded
|
|
|
|
/// transaction with some amounts crossing between shielded pools, return the inputs
|
|
|
|
/// necessary.
|
|
|
|
///
|
|
|
|
/// If insufficient funds are available to satisfy the required outputs for the shielding
|
|
|
|
/// request, this operation must fail and return [`InputSelectorError::InsufficientFunds`].
|
|
|
|
#[allow(clippy::type_complexity)]
|
|
|
|
fn propose_transaction<ParamsT>(
|
|
|
|
&self,
|
|
|
|
params: &ParamsT,
|
2023-10-02 17:02:06 -07:00
|
|
|
wallet_db: &Self::InputSource,
|
|
|
|
target_height: BlockHeight,
|
|
|
|
anchor_height: BlockHeight,
|
2022-10-17 10:35:14 -07:00
|
|
|
account: AccountId,
|
|
|
|
transaction_request: TransactionRequest,
|
|
|
|
) -> Result<
|
2023-12-04 13:45:22 -08:00
|
|
|
Proposal<Self::FeeRule, <Self::InputSource as InputSource>::NoteRef>,
|
|
|
|
InputSelectorError<<Self::InputSource as InputSource>::Error, Self::Error>,
|
2022-10-17 10:35:14 -07:00
|
|
|
>
|
|
|
|
where
|
|
|
|
ParamsT: consensus::Parameters;
|
2023-10-02 17:02:06 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// A strategy for selecting transaction inputs and proposing transaction outputs
|
|
|
|
/// for shielding-only transactions (transactions which spend transparent UTXOs and
|
|
|
|
/// send all transaction outputs to the wallet's shielded internal address(es)).
|
|
|
|
#[cfg(feature = "transparent-inputs")]
|
|
|
|
pub trait ShieldingSelector {
|
|
|
|
/// The type of errors that may be generated in input selection
|
|
|
|
type Error;
|
|
|
|
/// The type of data source that the input selector expects to access to obtain input
|
|
|
|
/// transparent UTXOs. This associated type permits input selectors that may use specialized
|
|
|
|
/// knowledge of the internals of a particular backing data store, if the generic API of
|
2023-12-04 13:45:22 -08:00
|
|
|
/// [`InputSource`] does not provide sufficiently fine-grained operations for a
|
2023-10-02 17:02:06 -07:00
|
|
|
/// particular backing store to optimally perform input selection.
|
2023-12-04 13:45:22 -08:00
|
|
|
type InputSource: InputSource;
|
2023-10-02 17:02:06 -07:00
|
|
|
/// The type of the fee rule that this input selector uses when computing fees.
|
|
|
|
type FeeRule: FeeRule;
|
2022-10-17 10:35:14 -07:00
|
|
|
|
|
|
|
/// Performs input selection and returns a proposal for the construction of a shielding
|
|
|
|
/// transaction.
|
|
|
|
///
|
|
|
|
/// Implementations should return the maximum possible number of economically useful inputs
|
|
|
|
/// required to supply at least the requested value, choosing only inputs received at the
|
|
|
|
/// specified source addresses. If insufficient funds are available to satisfy the required
|
|
|
|
/// outputs for the shielding request, this operation must fail and return
|
|
|
|
/// [`InputSelectorError::InsufficientFunds`].
|
|
|
|
#[allow(clippy::type_complexity)]
|
|
|
|
fn propose_shielding<ParamsT>(
|
|
|
|
&self,
|
|
|
|
params: &ParamsT,
|
2023-10-02 17:02:06 -07:00
|
|
|
wallet_db: &Self::InputSource,
|
2022-10-17 10:35:14 -07:00
|
|
|
shielding_threshold: NonNegativeAmount,
|
|
|
|
source_addrs: &[TransparentAddress],
|
2023-10-02 17:02:06 -07:00
|
|
|
target_height: BlockHeight,
|
|
|
|
min_confirmations: u32,
|
2022-10-17 10:35:14 -07:00
|
|
|
) -> Result<
|
2023-10-02 17:02:06 -07:00
|
|
|
Proposal<Self::FeeRule, Infallible>,
|
2023-12-04 13:45:22 -08:00
|
|
|
InputSelectorError<<Self::InputSource as InputSource>::Error, Self::Error>,
|
2022-10-17 10:35:14 -07:00
|
|
|
>
|
|
|
|
where
|
|
|
|
ParamsT: consensus::Parameters;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Errors that can occur as a consequence of greedy input selection.
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
2022-11-09 07:13:34 -08:00
|
|
|
pub enum GreedyInputSelectorError<ChangeStrategyErrT, NoteRefT> {
|
2022-10-17 10:35:14 -07:00
|
|
|
/// An intermediate value overflowed or underflowed the valid monetary range.
|
|
|
|
Balance(BalanceError),
|
|
|
|
/// A unified address did not contain a supported receiver.
|
|
|
|
UnsupportedAddress(Box<UnifiedAddress>),
|
|
|
|
/// An error was encountered in change selection.
|
2022-11-09 07:13:34 -08:00
|
|
|
Change(ChangeError<ChangeStrategyErrT, NoteRefT>),
|
2022-10-17 10:35:14 -07:00
|
|
|
}
|
|
|
|
|
2022-11-10 19:29:50 -08:00
|
|
|
impl<CE: fmt::Display, N: fmt::Display> fmt::Display for GreedyInputSelectorError<CE, N> {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
match &self {
|
|
|
|
GreedyInputSelectorError::Balance(e) => write!(
|
|
|
|
f,
|
|
|
|
"A balance calculation violated amount validity bounds: {:?}.",
|
|
|
|
e
|
|
|
|
),
|
|
|
|
GreedyInputSelectorError::UnsupportedAddress(_) => {
|
|
|
|
// we can't encode the UA to its string representation because we
|
|
|
|
// don't have network parameters here
|
|
|
|
write!(f, "Unified address contains no supported receivers.")
|
|
|
|
}
|
|
|
|
GreedyInputSelectorError::Change(err) => {
|
|
|
|
write!(f, "An error occurred computing change and fees: {}", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-09 07:13:34 -08:00
|
|
|
impl<DbErrT, ChangeStrategyErrT, NoteRefT>
|
|
|
|
From<GreedyInputSelectorError<ChangeStrategyErrT, NoteRefT>>
|
|
|
|
for InputSelectorError<DbErrT, GreedyInputSelectorError<ChangeStrategyErrT, NoteRefT>>
|
2022-10-17 10:35:14 -07:00
|
|
|
{
|
2022-11-09 07:13:34 -08:00
|
|
|
fn from(err: GreedyInputSelectorError<ChangeStrategyErrT, NoteRefT>) -> Self {
|
2022-10-17 10:35:14 -07:00
|
|
|
InputSelectorError::Selection(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-09 07:13:34 -08:00
|
|
|
impl<DbErrT, ChangeStrategyErrT, NoteRefT> From<ChangeError<ChangeStrategyErrT, NoteRefT>>
|
|
|
|
for InputSelectorError<DbErrT, GreedyInputSelectorError<ChangeStrategyErrT, NoteRefT>>
|
2022-10-17 10:35:14 -07:00
|
|
|
{
|
2022-11-09 07:13:34 -08:00
|
|
|
fn from(err: ChangeError<ChangeStrategyErrT, NoteRefT>) -> Self {
|
2022-10-17 10:35:14 -07:00
|
|
|
InputSelectorError::Selection(GreedyInputSelectorError::Change(err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-09 07:13:34 -08:00
|
|
|
impl<DbErrT, ChangeStrategyErrT, NoteRefT> From<BalanceError>
|
|
|
|
for InputSelectorError<DbErrT, GreedyInputSelectorError<ChangeStrategyErrT, NoteRefT>>
|
2022-10-17 10:35:14 -07:00
|
|
|
{
|
|
|
|
fn from(err: BalanceError) -> Self {
|
|
|
|
InputSelectorError::Selection(GreedyInputSelectorError::Balance(err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-09 17:05:48 -07:00
|
|
|
pub(crate) struct SaplingPayment(NonNegativeAmount);
|
2022-11-09 07:13:34 -08:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
impl SaplingPayment {
|
2023-10-09 17:05:48 -07:00
|
|
|
pub(crate) fn new(amount: NonNegativeAmount) -> Self {
|
2022-11-09 07:13:34 -08:00
|
|
|
SaplingPayment(amount)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-17 10:35:14 -07:00
|
|
|
impl sapling::OutputView for SaplingPayment {
|
2023-10-09 17:05:48 -07:00
|
|
|
fn value(&self) -> NonNegativeAmount {
|
2022-10-17 10:35:14 -07:00
|
|
|
self.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-24 19:19:14 -08:00
|
|
|
#[cfg(feature = "orchard")]
|
2023-12-04 13:45:22 -08:00
|
|
|
pub(crate) struct OrchardPayment(NonNegativeAmount);
|
|
|
|
|
|
|
|
// TODO: introduce this method when it is needed for testing.
|
|
|
|
// #[cfg(test)]
|
|
|
|
// impl OrchardPayment {
|
|
|
|
// pub(crate) fn new(amount: NonNegativeAmount) -> Self {
|
|
|
|
// OrchardPayment(amount)
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
#[cfg(feature = "orchard")]
|
|
|
|
impl orchard_fees::OutputView for OrchardPayment {
|
|
|
|
fn value(&self) -> NonNegativeAmount {
|
|
|
|
self.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-17 10:35:14 -07:00
|
|
|
/// An [`InputSelector`] implementation that uses a greedy strategy to select between available
|
|
|
|
/// notes.
|
|
|
|
///
|
2023-10-02 17:02:06 -07:00
|
|
|
/// This implementation performs input selection using methods available via the
|
2023-12-04 13:45:22 -08:00
|
|
|
/// [`InputSource`] interface.
|
2022-10-17 10:35:14 -07:00
|
|
|
pub struct GreedyInputSelector<DbT, ChangeT> {
|
|
|
|
change_strategy: ChangeT,
|
2022-11-09 07:13:34 -08:00
|
|
|
dust_output_policy: DustOutputPolicy,
|
2022-10-17 10:35:14 -07:00
|
|
|
_ds_type: PhantomData<DbT>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<DbT, ChangeT: ChangeStrategy> GreedyInputSelector<DbT, ChangeT> {
|
|
|
|
/// Constructs a new greedy input selector that uses the provided change strategy to determine
|
|
|
|
/// change values and fee amounts.
|
2022-11-09 07:13:34 -08:00
|
|
|
pub fn new(change_strategy: ChangeT, dust_output_policy: DustOutputPolicy) -> Self {
|
2022-10-17 10:35:14 -07:00
|
|
|
GreedyInputSelector {
|
|
|
|
change_strategy,
|
2022-11-09 07:13:34 -08:00
|
|
|
dust_output_policy,
|
2022-10-17 10:35:14 -07:00
|
|
|
_ds_type: PhantomData,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<DbT, ChangeT> InputSelector for GreedyInputSelector<DbT, ChangeT>
|
|
|
|
where
|
2023-12-04 13:45:22 -08:00
|
|
|
DbT: InputSource,
|
2022-10-17 10:35:14 -07:00
|
|
|
ChangeT: ChangeStrategy,
|
|
|
|
ChangeT::FeeRule: Clone,
|
|
|
|
{
|
2022-11-09 07:13:34 -08:00
|
|
|
type Error = GreedyInputSelectorError<ChangeT::Error, DbT::NoteRef>;
|
2023-10-02 17:02:06 -07:00
|
|
|
type InputSource = DbT;
|
2022-10-17 10:35:14 -07:00
|
|
|
type FeeRule = ChangeT::FeeRule;
|
|
|
|
|
|
|
|
#[allow(clippy::type_complexity)]
|
|
|
|
fn propose_transaction<ParamsT>(
|
|
|
|
&self,
|
|
|
|
params: &ParamsT,
|
2023-10-02 17:02:06 -07:00
|
|
|
wallet_db: &Self::InputSource,
|
|
|
|
target_height: BlockHeight,
|
|
|
|
anchor_height: BlockHeight,
|
2022-10-17 10:35:14 -07:00
|
|
|
account: AccountId,
|
|
|
|
transaction_request: TransactionRequest,
|
2023-10-02 17:02:06 -07:00
|
|
|
) -> Result<
|
|
|
|
Proposal<Self::FeeRule, DbT::NoteRef>,
|
2023-12-04 13:45:22 -08:00
|
|
|
InputSelectorError<<DbT as InputSource>::Error, Self::Error>,
|
2023-10-02 17:02:06 -07:00
|
|
|
>
|
2022-10-17 10:35:14 -07:00
|
|
|
where
|
|
|
|
ParamsT: consensus::Parameters,
|
2023-12-04 13:45:22 -08:00
|
|
|
Self::InputSource: InputSource,
|
2022-10-17 10:35:14 -07:00
|
|
|
{
|
|
|
|
let mut transparent_outputs = vec![];
|
|
|
|
let mut sapling_outputs = vec![];
|
2024-01-24 19:19:14 -08:00
|
|
|
#[cfg(feature = "orchard")]
|
2023-12-04 13:45:22 -08:00
|
|
|
let mut orchard_outputs = vec![];
|
2024-02-12 11:13:17 -08:00
|
|
|
let mut payment_pools = BTreeMap::new();
|
|
|
|
for (idx, payment) in transaction_request.payments() {
|
2022-10-17 10:35:14 -07:00
|
|
|
match &payment.recipient_address {
|
2023-12-07 14:26:48 -08:00
|
|
|
Address::Transparent(addr) => {
|
2024-02-12 11:13:17 -08:00
|
|
|
payment_pools.insert(*idx, PoolType::Transparent);
|
|
|
|
transparent_outputs.push(TxOut {
|
|
|
|
value: payment.amount,
|
|
|
|
script_pubkey: addr.script(),
|
|
|
|
});
|
2022-10-17 10:35:14 -07:00
|
|
|
}
|
2023-12-07 14:26:48 -08:00
|
|
|
Address::Sapling(_) => {
|
2024-02-12 11:13:17 -08:00
|
|
|
payment_pools.insert(*idx, PoolType::Shielded(ShieldedProtocol::Sapling));
|
|
|
|
sapling_outputs.push(SaplingPayment(payment.amount));
|
2022-10-17 10:35:14 -07:00
|
|
|
}
|
2023-12-07 14:26:48 -08:00
|
|
|
Address::Unified(addr) => {
|
2023-12-04 13:45:22 -08:00
|
|
|
#[cfg(feature = "orchard")]
|
2024-02-12 11:13:17 -08:00
|
|
|
if addr.orchard().is_some() {
|
|
|
|
payment_pools.insert(*idx, PoolType::Shielded(ShieldedProtocol::Orchard));
|
|
|
|
orchard_outputs.push(OrchardPayment(payment.amount));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if addr.sapling().is_some() {
|
|
|
|
payment_pools.insert(*idx, PoolType::Shielded(ShieldedProtocol::Sapling));
|
|
|
|
sapling_outputs.push(SaplingPayment(payment.amount));
|
|
|
|
continue;
|
2022-10-17 10:35:14 -07:00
|
|
|
}
|
2024-02-12 11:13:17 -08:00
|
|
|
|
|
|
|
if let Some(addr) = addr.transparent() {
|
|
|
|
payment_pools.insert(*idx, PoolType::Transparent);
|
|
|
|
transparent_outputs.push(TxOut {
|
|
|
|
value: payment.amount,
|
|
|
|
script_pubkey: addr.script(),
|
|
|
|
});
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Err(InputSelectorError::Selection(
|
|
|
|
GreedyInputSelectorError::UnsupportedAddress(Box::new(addr.clone())),
|
|
|
|
));
|
2022-10-17 10:35:14 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-04 13:45:22 -08:00
|
|
|
let mut shielded_inputs: Vec<ReceivedNote<DbT::NoteRef, Note>> = vec![];
|
2023-10-09 17:05:48 -07:00
|
|
|
let mut prior_available = NonNegativeAmount::ZERO;
|
|
|
|
let mut amount_required = NonNegativeAmount::ZERO;
|
2022-11-09 07:13:34 -08:00
|
|
|
let mut exclude: Vec<DbT::NoteRef> = vec![];
|
2022-10-17 10:35:14 -07:00
|
|
|
// This loop is guaranteed to terminate because on each iteration we check that the amount
|
|
|
|
// of funds selected is strictly increasing. The loop will either return a successful
|
|
|
|
// result or the wallet will eventually run out of funds to select.
|
|
|
|
loop {
|
|
|
|
let balance = self.change_strategy.compute_balance(
|
|
|
|
params,
|
|
|
|
target_height,
|
|
|
|
&Vec::<WalletTransparentOutput>::new(),
|
|
|
|
&transparent_outputs,
|
2023-12-04 13:45:22 -08:00
|
|
|
&(
|
|
|
|
::sapling::builder::BundleType::DEFAULT,
|
2023-12-04 13:45:22 -08:00
|
|
|
&shielded_inputs
|
|
|
|
.iter()
|
|
|
|
.filter_map(|i| {
|
|
|
|
i.clone().traverse_opt(|wn| match wn {
|
|
|
|
Note::Sapling(n) => Some(n),
|
|
|
|
#[cfg(feature = "orchard")]
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>()[..],
|
2023-12-04 13:45:22 -08:00
|
|
|
&sapling_outputs[..],
|
|
|
|
),
|
|
|
|
#[cfg(feature = "orchard")]
|
|
|
|
&(
|
|
|
|
::orchard::builder::BundleType::DEFAULT,
|
2023-12-04 13:45:22 -08:00
|
|
|
&shielded_inputs
|
|
|
|
.iter()
|
|
|
|
.filter_map(|i| {
|
|
|
|
i.clone().traverse_opt(|wn| match wn {
|
|
|
|
Note::Orchard(n) => Some(n),
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>()[..],
|
2023-12-04 13:45:22 -08:00
|
|
|
&orchard_outputs[..],
|
|
|
|
),
|
2022-11-09 07:13:34 -08:00
|
|
|
&self.dust_output_policy,
|
2022-10-17 10:35:14 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
match balance {
|
|
|
|
Ok(balance) => {
|
2024-02-12 11:46:34 -08:00
|
|
|
return Proposal::from_parts(
|
2022-10-17 10:35:14 -07:00
|
|
|
transaction_request,
|
2024-02-12 11:13:17 -08:00
|
|
|
payment_pools,
|
2024-02-12 11:46:34 -08:00
|
|
|
vec![],
|
|
|
|
NonEmpty::from_vec(shielded_inputs)
|
|
|
|
.map(|notes| ShieldedInputs::from_parts(anchor_height, notes)),
|
2022-10-17 10:35:14 -07:00
|
|
|
balance,
|
2024-02-12 11:46:34 -08:00
|
|
|
(*self.change_strategy.fee_rule()).clone(),
|
|
|
|
target_height,
|
|
|
|
false,
|
|
|
|
)
|
|
|
|
.map_err(InputSelectorError::Proposal);
|
2022-10-17 10:35:14 -07:00
|
|
|
}
|
2022-11-09 07:13:34 -08:00
|
|
|
Err(ChangeError::DustInputs { mut sapling, .. }) => {
|
|
|
|
exclude.append(&mut sapling);
|
|
|
|
}
|
2022-10-17 10:35:14 -07:00
|
|
|
Err(ChangeError::InsufficientFunds { required, .. }) => {
|
2022-11-09 07:13:34 -08:00
|
|
|
amount_required = required;
|
2022-10-17 10:35:14 -07:00
|
|
|
}
|
|
|
|
Err(other) => return Err(other.into()),
|
|
|
|
}
|
2022-11-09 07:13:34 -08:00
|
|
|
|
2024-01-23 16:12:51 -08:00
|
|
|
#[cfg(not(zcash_unstable = "orchard"))]
|
|
|
|
let selectable_pools = &[ShieldedProtocol::Sapling];
|
|
|
|
#[cfg(zcash_unstable = "orchard")]
|
|
|
|
let selectable_pools = &[ShieldedProtocol::Sapling, ShieldedProtocol::Orchard];
|
|
|
|
|
2023-12-04 13:45:22 -08:00
|
|
|
shielded_inputs = wallet_db
|
2023-12-04 13:45:22 -08:00
|
|
|
.select_spendable_notes(
|
2023-10-09 17:05:48 -07:00
|
|
|
account,
|
|
|
|
amount_required.into(),
|
2024-01-23 16:12:51 -08:00
|
|
|
selectable_pools,
|
2023-10-09 17:05:48 -07:00
|
|
|
anchor_height,
|
|
|
|
&exclude,
|
|
|
|
)
|
2022-11-09 07:13:34 -08:00
|
|
|
.map_err(InputSelectorError::DataSource)?;
|
|
|
|
|
2023-12-04 13:45:22 -08:00
|
|
|
let new_available = shielded_inputs
|
2022-11-09 07:13:34 -08:00
|
|
|
.iter()
|
2023-12-04 13:45:22 -08:00
|
|
|
.map(|n| n.note().value())
|
2023-10-09 17:05:48 -07:00
|
|
|
.sum::<Option<NonNegativeAmount>>()
|
2022-11-09 07:13:34 -08:00
|
|
|
.ok_or(BalanceError::Overflow)?;
|
|
|
|
|
|
|
|
if new_available <= prior_available {
|
|
|
|
return Err(InputSelectorError::InsufficientFunds {
|
|
|
|
required: amount_required,
|
|
|
|
available: new_available,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// If the set of selected inputs has changed after selection, we will loop again
|
|
|
|
// and see whether we now have enough funds.
|
|
|
|
prior_available = new_available;
|
|
|
|
}
|
2022-10-17 10:35:14 -07:00
|
|
|
}
|
|
|
|
}
|
2023-10-02 17:02:06 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "transparent-inputs")]
|
|
|
|
impl<DbT, ChangeT> ShieldingSelector for GreedyInputSelector<DbT, ChangeT>
|
|
|
|
where
|
2023-12-04 13:45:22 -08:00
|
|
|
DbT: InputSource,
|
2023-10-02 17:02:06 -07:00
|
|
|
ChangeT: ChangeStrategy,
|
|
|
|
ChangeT::FeeRule: Clone,
|
|
|
|
{
|
|
|
|
type Error = GreedyInputSelectorError<ChangeT::Error, Infallible>;
|
|
|
|
type InputSource = DbT;
|
|
|
|
type FeeRule = ChangeT::FeeRule;
|
2022-10-17 10:35:14 -07:00
|
|
|
|
|
|
|
#[allow(clippy::type_complexity)]
|
|
|
|
fn propose_shielding<ParamsT>(
|
|
|
|
&self,
|
|
|
|
params: &ParamsT,
|
2023-10-02 17:02:06 -07:00
|
|
|
wallet_db: &Self::InputSource,
|
2022-10-17 10:35:14 -07:00
|
|
|
shielding_threshold: NonNegativeAmount,
|
|
|
|
source_addrs: &[TransparentAddress],
|
2023-10-02 17:02:06 -07:00
|
|
|
target_height: BlockHeight,
|
|
|
|
min_confirmations: u32,
|
|
|
|
) -> Result<
|
|
|
|
Proposal<Self::FeeRule, Infallible>,
|
2023-12-04 13:45:22 -08:00
|
|
|
InputSelectorError<<DbT as InputSource>::Error, Self::Error>,
|
2023-10-02 17:02:06 -07:00
|
|
|
>
|
2022-10-17 10:35:14 -07:00
|
|
|
where
|
|
|
|
ParamsT: consensus::Parameters,
|
|
|
|
{
|
2022-11-09 07:13:34 -08:00
|
|
|
let mut transparent_inputs: Vec<WalletTransparentOutput> = source_addrs
|
2022-10-17 10:35:14 -07:00
|
|
|
.iter()
|
2023-10-02 17:02:06 -07:00
|
|
|
.map(|taddr| {
|
|
|
|
wallet_db.get_unspent_transparent_outputs(
|
|
|
|
taddr,
|
|
|
|
target_height - min_confirmations,
|
|
|
|
&[],
|
|
|
|
)
|
|
|
|
})
|
2022-10-17 10:35:14 -07:00
|
|
|
.collect::<Result<Vec<Vec<_>>, _>>()
|
|
|
|
.map_err(InputSelectorError::DataSource)?
|
|
|
|
.into_iter()
|
|
|
|
.flat_map(|v| v.into_iter())
|
|
|
|
.collect();
|
|
|
|
|
2022-11-09 07:13:34 -08:00
|
|
|
let trial_balance = self.change_strategy.compute_balance(
|
2022-10-17 10:35:14 -07:00
|
|
|
params,
|
|
|
|
target_height,
|
|
|
|
&transparent_inputs,
|
|
|
|
&Vec::<TxOut>::new(),
|
2023-12-04 13:45:22 -08:00
|
|
|
&(
|
|
|
|
::sapling::builder::BundleType::DEFAULT,
|
|
|
|
&Vec::<Infallible>::new()[..],
|
|
|
|
&Vec::<Infallible>::new()[..],
|
|
|
|
),
|
|
|
|
#[cfg(feature = "orchard")]
|
|
|
|
&(
|
|
|
|
orchard::builder::BundleType::DEFAULT,
|
|
|
|
&Vec::<Infallible>::new()[..],
|
|
|
|
&Vec::<Infallible>::new()[..],
|
|
|
|
),
|
2022-11-09 07:13:34 -08:00
|
|
|
&self.dust_output_policy,
|
|
|
|
);
|
|
|
|
|
|
|
|
let balance = match trial_balance {
|
|
|
|
Ok(balance) => balance,
|
|
|
|
Err(ChangeError::DustInputs { transparent, .. }) => {
|
|
|
|
let exclusions: BTreeSet<OutPoint> = transparent.into_iter().collect();
|
2023-05-16 08:50:31 -07:00
|
|
|
transparent_inputs.retain(|i| !exclusions.contains(i.outpoint()));
|
2022-11-09 07:13:34 -08:00
|
|
|
|
|
|
|
self.change_strategy.compute_balance(
|
|
|
|
params,
|
|
|
|
target_height,
|
|
|
|
&transparent_inputs,
|
|
|
|
&Vec::<TxOut>::new(),
|
2023-12-04 13:45:22 -08:00
|
|
|
&(
|
|
|
|
::sapling::builder::BundleType::DEFAULT,
|
|
|
|
&Vec::<Infallible>::new()[..],
|
|
|
|
&Vec::<Infallible>::new()[..],
|
|
|
|
),
|
|
|
|
#[cfg(feature = "orchard")]
|
|
|
|
&(
|
|
|
|
orchard::builder::BundleType::DEFAULT,
|
|
|
|
&Vec::<Infallible>::new()[..],
|
|
|
|
&Vec::<Infallible>::new()[..],
|
|
|
|
),
|
2022-11-09 07:13:34 -08:00
|
|
|
&self.dust_output_policy,
|
|
|
|
)?
|
|
|
|
}
|
|
|
|
Err(other) => {
|
|
|
|
return Err(other.into());
|
|
|
|
}
|
|
|
|
};
|
2022-10-17 10:35:14 -07:00
|
|
|
|
2023-08-28 18:09:14 -07:00
|
|
|
if balance.total() >= shielding_threshold {
|
2024-02-12 11:46:34 -08:00
|
|
|
Proposal::from_parts(
|
|
|
|
TransactionRequest::empty(),
|
|
|
|
BTreeMap::new(),
|
2022-10-17 10:35:14 -07:00
|
|
|
transparent_inputs,
|
2024-02-12 11:46:34 -08:00
|
|
|
None,
|
2022-10-17 10:35:14 -07:00
|
|
|
balance,
|
2024-02-12 11:46:34 -08:00
|
|
|
(*self.change_strategy.fee_rule()).clone(),
|
|
|
|
target_height,
|
|
|
|
true,
|
|
|
|
)
|
|
|
|
.map_err(InputSelectorError::Proposal)
|
2022-10-17 10:35:14 -07:00
|
|
|
} else {
|
|
|
|
Err(InputSelectorError::InsufficientFunds {
|
2023-10-09 17:05:48 -07:00
|
|
|
available: balance.total(),
|
|
|
|
required: shielding_threshold,
|
2022-10-17 10:35:14 -07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|