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;
|
2023-11-14 11:36:25 -08:00
|
|
|
use std::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,
|
2023-11-30 15:56:44 -08:00
|
|
|
fees::{sapling, ChangeError, ChangeStrategy, DustOutputPolicy, TransactionBalance},
|
2023-12-04 13:45:22 -08:00
|
|
|
wallet::{Note, ReceivedNote, WalletTransparentOutput},
|
2022-10-17 10:35:14 -07:00
|
|
|
zip321::TransactionRequest,
|
2023-12-04 13:45:22 -08:00
|
|
|
ShieldedProtocol,
|
2022-10-17 10:35:14 -07:00
|
|
|
};
|
|
|
|
|
2023-12-04 13:45:22 -08:00
|
|
|
#[cfg(any(feature = "transparent-inputs", feature = "orchard"))]
|
|
|
|
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.
|
|
|
|
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),
|
|
|
|
/// 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)
|
|
|
|
}
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-02 17:02:06 -07:00
|
|
|
/// The inputs to be consumed and outputs to be produced in a proposed transaction.
|
2023-10-12 12:48:20 -07:00
|
|
|
#[derive(Clone, PartialEq, Eq)]
|
2023-01-19 15:32:09 -08:00
|
|
|
pub struct Proposal<FeeRuleT, NoteRef> {
|
2022-10-17 10:35:14 -07:00
|
|
|
transaction_request: TransactionRequest,
|
2023-01-19 15:32:09 -08:00
|
|
|
transparent_inputs: Vec<WalletTransparentOutput>,
|
2023-12-04 13:45:22 -08:00
|
|
|
shielded_inputs: Option<ShieldedInputs<NoteRef>>,
|
2022-10-17 10:35:14 -07:00
|
|
|
balance: TransactionBalance,
|
|
|
|
fee_rule: FeeRuleT,
|
2023-04-03 12:53:43 -07:00
|
|
|
min_target_height: BlockHeight,
|
2023-01-19 15:32:09 -08:00
|
|
|
is_shielding: bool,
|
2022-10-17 10:35:14 -07:00
|
|
|
}
|
|
|
|
|
2023-11-14 11:36:25 -08:00
|
|
|
/// Errors that can occur in construction of a [`Proposal`].
|
2023-11-14 09:44:21 -08:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub enum ProposalError {
|
2023-11-14 11:36:25 -08:00
|
|
|
/// The total output value of the transaction request is not a valid Zcash amount.
|
|
|
|
RequestTotalInvalid,
|
|
|
|
/// The total of transaction inputs overflows the valid range of Zcash values.
|
2023-11-14 09:44:21 -08:00
|
|
|
Overflow,
|
2023-11-14 11:36:25 -08:00
|
|
|
/// The input total and output total of the payment request are not equal to one another. The
|
|
|
|
/// sum of transaction outputs, change, and fees is required to be exactly equal to the value
|
|
|
|
/// of provided inputs.
|
2023-11-14 09:44:21 -08:00
|
|
|
BalanceError {
|
|
|
|
input_total: NonNegativeAmount,
|
|
|
|
output_total: NonNegativeAmount,
|
|
|
|
},
|
2023-11-14 11:36:25 -08:00
|
|
|
/// The `is_shielding` flag may only be set to `true` under the following conditions:
|
|
|
|
/// * The total of transparent inputs is nonzero
|
|
|
|
/// * There exist no Sapling inputs
|
|
|
|
/// * There provided transaction request is empty; i.e. the only output values specified
|
|
|
|
/// are change and fee amounts.
|
|
|
|
ShieldingInvalid,
|
2023-11-14 09:44:21 -08:00
|
|
|
}
|
|
|
|
|
2023-11-14 11:36:25 -08:00
|
|
|
impl Display for ProposalError {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
match self {
|
|
|
|
ProposalError::RequestTotalInvalid => write!(
|
|
|
|
f,
|
|
|
|
"The total requested output value is not a valid Zcash amount."
|
|
|
|
),
|
|
|
|
ProposalError::Overflow => write!(
|
|
|
|
f,
|
|
|
|
"The total of transaction inputs overflows the valid range of Zcash values."
|
|
|
|
),
|
|
|
|
ProposalError::BalanceError {
|
|
|
|
input_total,
|
|
|
|
output_total,
|
|
|
|
} => write!(
|
|
|
|
f,
|
|
|
|
"Balance error: the output total {} was not equal to the input total {}",
|
|
|
|
u64::from(*output_total),
|
|
|
|
u64::from(*input_total)
|
|
|
|
),
|
|
|
|
ProposalError::ShieldingInvalid => write!(
|
|
|
|
f,
|
|
|
|
"The proposal violates the rules for a shielding transaction."
|
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::error::Error for ProposalError {}
|
|
|
|
|
2023-01-19 15:32:09 -08:00
|
|
|
impl<FeeRuleT, NoteRef> Proposal<FeeRuleT, NoteRef> {
|
2023-11-14 09:44:21 -08:00
|
|
|
/// Constructs a validated [`Proposal`] from its constituent parts.
|
|
|
|
///
|
2023-11-15 08:26:02 -08:00
|
|
|
/// This operation validates the proposal for balance consistency and agreement between
|
2023-11-14 09:44:21 -08:00
|
|
|
/// the `is_shielding` flag and the structure of the proposal.
|
2023-10-12 12:48:20 -07:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2023-11-14 09:44:21 -08:00
|
|
|
pub fn from_parts(
|
2023-10-12 12:48:20 -07:00
|
|
|
transaction_request: TransactionRequest,
|
|
|
|
transparent_inputs: Vec<WalletTransparentOutput>,
|
2023-12-04 13:45:22 -08:00
|
|
|
shielded_inputs: Option<ShieldedInputs<NoteRef>>,
|
2023-10-12 12:48:20 -07:00
|
|
|
balance: TransactionBalance,
|
|
|
|
fee_rule: FeeRuleT,
|
|
|
|
min_target_height: BlockHeight,
|
|
|
|
is_shielding: bool,
|
2023-11-14 09:44:21 -08:00
|
|
|
) -> Result<Self, ProposalError> {
|
|
|
|
let transparent_input_total = transparent_inputs
|
2023-10-12 12:48:20 -07:00
|
|
|
.iter()
|
|
|
|
.map(|out| out.txout().value)
|
2023-11-14 09:44:21 -08:00
|
|
|
.fold(Ok(NonNegativeAmount::ZERO), |acc, a| {
|
|
|
|
(acc? + a).ok_or(ProposalError::Overflow)
|
|
|
|
})?;
|
2023-12-04 13:45:22 -08:00
|
|
|
let shielded_input_total = shielded_inputs
|
2023-10-12 12:48:20 -07:00
|
|
|
.iter()
|
|
|
|
.flat_map(|s_in| s_in.notes().iter())
|
2023-12-04 13:45:22 -08:00
|
|
|
.map(|out| out.note().value())
|
2023-11-15 08:26:02 -08:00
|
|
|
.fold(Some(NonNegativeAmount::ZERO), |acc, a| (acc? + a))
|
|
|
|
.ok_or(ProposalError::Overflow)?;
|
2023-11-14 09:44:21 -08:00
|
|
|
let input_total =
|
2023-12-04 13:45:22 -08:00
|
|
|
(transparent_input_total + shielded_input_total).ok_or(ProposalError::Overflow)?;
|
2023-10-12 12:48:20 -07:00
|
|
|
|
2023-11-14 09:44:21 -08:00
|
|
|
let request_total = transaction_request
|
|
|
|
.total()
|
2023-11-14 11:36:25 -08:00
|
|
|
.map_err(|_| ProposalError::RequestTotalInvalid)?;
|
2023-11-14 09:44:21 -08:00
|
|
|
let output_total = (request_total + balance.total()).ok_or(ProposalError::Overflow)?;
|
|
|
|
|
|
|
|
if is_shielding
|
|
|
|
&& (transparent_input_total == NonNegativeAmount::ZERO
|
2023-12-04 13:45:22 -08:00
|
|
|
|| shielded_input_total > NonNegativeAmount::ZERO
|
2023-11-14 09:44:21 -08:00
|
|
|
|| request_total > NonNegativeAmount::ZERO)
|
|
|
|
{
|
2023-11-14 11:36:25 -08:00
|
|
|
return Err(ProposalError::ShieldingInvalid);
|
2023-11-14 09:44:21 -08:00
|
|
|
}
|
2023-10-12 12:48:20 -07:00
|
|
|
|
|
|
|
if input_total == output_total {
|
|
|
|
Ok(Self {
|
|
|
|
transaction_request,
|
|
|
|
transparent_inputs,
|
2023-12-04 13:45:22 -08:00
|
|
|
shielded_inputs,
|
2023-10-12 12:48:20 -07:00
|
|
|
balance,
|
|
|
|
fee_rule,
|
|
|
|
min_target_height,
|
|
|
|
is_shielding,
|
|
|
|
})
|
|
|
|
} else {
|
2023-11-14 09:44:21 -08:00
|
|
|
Err(ProposalError::BalanceError {
|
|
|
|
input_total,
|
|
|
|
output_total,
|
|
|
|
})
|
2023-10-12 12:48:20 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-17 10:35:14 -07:00
|
|
|
/// Returns the transaction request that describes the payments to be made.
|
|
|
|
pub fn transaction_request(&self) -> &TransactionRequest {
|
|
|
|
&self.transaction_request
|
|
|
|
}
|
|
|
|
/// Returns the transparent inputs that have been selected to fund the transaction.
|
2023-01-19 15:32:09 -08:00
|
|
|
pub fn transparent_inputs(&self) -> &[WalletTransparentOutput] {
|
2022-10-17 10:35:14 -07:00
|
|
|
&self.transparent_inputs
|
|
|
|
}
|
|
|
|
/// Returns the Sapling inputs that have been selected to fund the transaction.
|
2023-12-04 13:45:22 -08:00
|
|
|
pub fn shielded_inputs(&self) -> Option<&ShieldedInputs<NoteRef>> {
|
|
|
|
self.shielded_inputs.as_ref()
|
2022-10-17 10:35:14 -07:00
|
|
|
}
|
|
|
|
/// Returns the change outputs to be added to the transaction and the fee to be paid.
|
|
|
|
pub fn balance(&self) -> &TransactionBalance {
|
|
|
|
&self.balance
|
|
|
|
}
|
|
|
|
/// Returns the fee rule to be used by the transaction builder.
|
|
|
|
pub fn fee_rule(&self) -> &FeeRuleT {
|
|
|
|
&self.fee_rule
|
|
|
|
}
|
2023-01-19 15:32:09 -08:00
|
|
|
/// Returns the target height for which the proposal was prepared.
|
2023-04-03 12:53:43 -07:00
|
|
|
///
|
|
|
|
/// The chain must contain at least this many blocks in order for the proposal to
|
|
|
|
/// be executed.
|
|
|
|
pub fn min_target_height(&self) -> BlockHeight {
|
|
|
|
self.min_target_height
|
|
|
|
}
|
2023-01-19 15:32:09 -08:00
|
|
|
/// Returns a flag indicating whether or not the proposed transaction
|
|
|
|
/// is exclusively wallet-internal (if it does not involve any external
|
|
|
|
/// recipients).
|
|
|
|
pub fn is_shielding(&self) -> bool {
|
|
|
|
self.is_shielding
|
|
|
|
}
|
2022-10-17 10:35:14 -07:00
|
|
|
}
|
|
|
|
|
2023-06-14 14:34:28 -07:00
|
|
|
impl<FeeRuleT, NoteRef> Debug for Proposal<FeeRuleT, NoteRef> {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
f.debug_struct("Proposal")
|
|
|
|
.field("transaction_request", &self.transaction_request)
|
|
|
|
.field("transparent_inputs", &self.transparent_inputs)
|
2023-10-02 17:02:06 -07:00
|
|
|
.field(
|
2023-12-04 13:45:22 -08:00
|
|
|
"shielded_inputs",
|
|
|
|
&self.shielded_inputs().map(|i| i.notes.len()),
|
2023-10-02 17:02:06 -07:00
|
|
|
)
|
|
|
|
.field(
|
2023-12-04 13:45:22 -08:00
|
|
|
"anchor_height",
|
|
|
|
&self.shielded_inputs().map(|i| i.anchor_height),
|
2023-10-02 17:02:06 -07:00
|
|
|
)
|
2023-06-14 14:34:28 -07:00
|
|
|
.field("balance", &self.balance)
|
|
|
|
//.field("fee_rule", &self.fee_rule)
|
|
|
|
.field("min_target_height", &self.min_target_height)
|
|
|
|
.field("is_shielding", &self.is_shielding)
|
2023-06-29 15:26:22 -07:00
|
|
|
.finish_non_exhaustive()
|
2023-06-14 14:34:28 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-02 17:02:06 -07:00
|
|
|
/// The Sapling inputs to a proposed transaction.
|
2023-10-12 12:48:20 -07:00
|
|
|
#[derive(Clone, PartialEq, Eq)]
|
2023-12-04 13:45:22 -08:00
|
|
|
pub struct ShieldedInputs<NoteRef> {
|
2023-10-02 17:02:06 -07:00
|
|
|
anchor_height: BlockHeight,
|
2023-12-04 13:45:22 -08:00
|
|
|
notes: NonEmpty<ReceivedNote<NoteRef, Note>>,
|
2023-10-02 17:02:06 -07:00
|
|
|
}
|
|
|
|
|
2023-12-04 13:45:22 -08:00
|
|
|
impl<NoteRef> ShieldedInputs<NoteRef> {
|
|
|
|
/// Constructs a [`ShieldedInputs`] from its constituent parts.
|
|
|
|
pub fn from_parts(
|
|
|
|
anchor_height: BlockHeight,
|
|
|
|
notes: NonEmpty<ReceivedNote<NoteRef, Note>>,
|
|
|
|
) -> Self {
|
2023-10-12 12:48:20 -07:00
|
|
|
Self {
|
|
|
|
anchor_height,
|
|
|
|
notes,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-02 17:02:06 -07:00
|
|
|
/// Returns the anchor height for Sapling inputs that should be used when constructing the
|
|
|
|
/// proposed transaction.
|
|
|
|
pub fn anchor_height(&self) -> BlockHeight {
|
|
|
|
self.anchor_height
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the list of Sapling notes to be used as inputs to the proposed transaction.
|
2023-12-04 13:45:22 -08:00
|
|
|
pub fn notes(&self) -> &NonEmpty<ReceivedNote<NoteRef, Note>> {
|
2023-10-02 17:02:06 -07:00
|
|
|
&self.notes
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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![];
|
2022-10-17 10:35:14 -07:00
|
|
|
for payment in transaction_request.payments() {
|
|
|
|
let mut push_transparent = |taddr: TransparentAddress| {
|
|
|
|
transparent_outputs.push(TxOut {
|
|
|
|
value: payment.amount,
|
|
|
|
script_pubkey: taddr.script(),
|
|
|
|
});
|
|
|
|
};
|
|
|
|
let mut push_sapling = || {
|
|
|
|
sapling_outputs.push(SaplingPayment(payment.amount));
|
|
|
|
};
|
2024-01-24 19:19:14 -08:00
|
|
|
#[cfg(feature = "orchard")]
|
2023-12-04 13:45:22 -08:00
|
|
|
let mut push_orchard = || {
|
|
|
|
orchard_outputs.push(OrchardPayment(payment.amount));
|
|
|
|
};
|
2022-10-17 10:35:14 -07:00
|
|
|
|
|
|
|
match &payment.recipient_address {
|
2023-12-07 14:26:48 -08:00
|
|
|
Address::Transparent(addr) => {
|
2022-10-17 10:35:14 -07:00
|
|
|
push_transparent(*addr);
|
|
|
|
}
|
2023-12-07 14:26:48 -08:00
|
|
|
Address::Sapling(_) => {
|
2022-10-17 10:35:14 -07:00
|
|
|
push_sapling();
|
|
|
|
}
|
2023-12-07 14:26:48 -08:00
|
|
|
Address::Unified(addr) => {
|
2023-12-04 13:45:22 -08:00
|
|
|
#[cfg(feature = "orchard")]
|
|
|
|
let has_orchard = addr.orchard().is_some();
|
|
|
|
#[cfg(not(feature = "orchard"))]
|
|
|
|
let has_orchard = false;
|
|
|
|
|
|
|
|
if has_orchard {
|
2024-01-24 19:19:14 -08:00
|
|
|
#[cfg(feature = "orchard")]
|
2023-12-04 13:45:22 -08:00
|
|
|
push_orchard();
|
|
|
|
} else if addr.sapling().is_some() {
|
2022-10-17 10:35:14 -07:00
|
|
|
push_sapling();
|
|
|
|
} else if let Some(addr) = addr.transparent() {
|
|
|
|
push_transparent(*addr);
|
|
|
|
} else {
|
|
|
|
return Err(InputSelectorError::Selection(
|
|
|
|
GreedyInputSelectorError::UnsupportedAddress(Box::new(addr.clone())),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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) => {
|
|
|
|
return Ok(Proposal {
|
|
|
|
transaction_request,
|
|
|
|
transparent_inputs: vec![],
|
2023-12-04 13:45:22 -08:00
|
|
|
shielded_inputs: NonEmpty::from_vec(shielded_inputs).map(|notes| {
|
|
|
|
ShieldedInputs {
|
2023-10-02 17:02:06 -07:00
|
|
|
anchor_height,
|
|
|
|
notes,
|
|
|
|
}
|
|
|
|
}),
|
2022-10-17 10:35:14 -07:00
|
|
|
balance,
|
|
|
|
fee_rule: (*self.change_strategy.fee_rule()).clone(),
|
2023-04-03 12:53:43 -07:00
|
|
|
min_target_height: target_height,
|
2023-01-19 15:32:09 -08:00
|
|
|
is_shielding: false,
|
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 {
|
2022-10-17 10:35:14 -07:00
|
|
|
Ok(Proposal {
|
|
|
|
transaction_request: TransactionRequest::empty(),
|
|
|
|
transparent_inputs,
|
2023-12-04 13:45:22 -08:00
|
|
|
shielded_inputs: None,
|
2022-10-17 10:35:14 -07:00
|
|
|
balance,
|
|
|
|
fee_rule: (*self.change_strategy.fee_rule()).clone(),
|
2023-04-03 12:53:43 -07:00
|
|
|
min_target_height: target_height,
|
2023-01-19 15:32:09 -08:00
|
|
|
is_shielding: true,
|
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
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|