zcash_client_backend: Add Orchard support to change strategies.
This modifies the `compute_balance` method to operate in a bundle-oriented fashion, which simplifies the API and makes it easier to elide Orchard functionality in the case that the `orchard` feature is not enabled.
This commit is contained in:
parent
0548b3dd9b
commit
56f2ac573c
|
@ -27,7 +27,8 @@ and this library adheres to Rust's notion of
|
||||||
- `wallet::input_selection::ShieldingSelector` has been
|
- `wallet::input_selection::ShieldingSelector` has been
|
||||||
factored out from the `InputSelector` trait to separate out transparent
|
factored out from the `InputSelector` trait to separate out transparent
|
||||||
functionality and move it behind the `transparent-inputs` feature flag.
|
functionality and move it behind the `transparent-inputs` feature flag.
|
||||||
- `zcash_client_backend::fees::{standard, sapling}`
|
- `zcash_client_backend::fees::{standard, orchard, sapling}`
|
||||||
|
- `zcash_client_backend::fees::ChangeValue::{new, orchard}`
|
||||||
- `zcash_client_backend::wallet`:
|
- `zcash_client_backend::wallet`:
|
||||||
- `Note`
|
- `Note`
|
||||||
- `ReceivedNote`
|
- `ReceivedNote`
|
||||||
|
@ -141,9 +142,10 @@ and this library adheres to Rust's notion of
|
||||||
for its `InputSource` associated type.
|
for its `InputSource` associated type.
|
||||||
|
|
||||||
- `zcash_client_backend::fees`:
|
- `zcash_client_backend::fees`:
|
||||||
- `ChangeValue::Sapling` is now a structured variant. In addition to the
|
- `ChangeStrategy::compute_balance` arguments have changed.
|
||||||
existing change value, it now also carries an optional memo to be associated
|
- `ChangeValue` is now a struct. In addition to the existing change value, it
|
||||||
with the change output.
|
now also provides the output pool to which change should be sent and an
|
||||||
|
optional memo to be associated with the change output.
|
||||||
- `fixed::SingleOutputChangeStrategy::new` and
|
- `fixed::SingleOutputChangeStrategy::new` and
|
||||||
`zip317::SingleOutputChangeStrategy::new` each now accept an additional
|
`zip317::SingleOutputChangeStrategy::new` each now accept an additional
|
||||||
`change_memo` argument.
|
`change_memo` argument.
|
||||||
|
@ -157,7 +159,6 @@ and this library adheres to Rust's notion of
|
||||||
- `error::Error::InsufficientFunds.{available, required}`
|
- `error::Error::InsufficientFunds.{available, required}`
|
||||||
- `wallet::input_selection::InputSelectorError::InsufficientFunds.{available, required}`
|
- `wallet::input_selection::InputSelectorError::InsufficientFunds.{available, required}`
|
||||||
- `zcash_client_backend::fees`:
|
- `zcash_client_backend::fees`:
|
||||||
- `ChangeValue::Sapling.value`
|
|
||||||
- `ChangeError::InsufficientFunds.{available, required}`
|
- `ChangeError::InsufficientFunds.{available, required}`
|
||||||
- `zcash_client_backend::zip321::Payment.amount`
|
- `zcash_client_backend::zip321::Payment.amount`
|
||||||
- The following methods now take `NonNegativeAmount` instead of `Amount`:
|
- The following methods now take `NonNegativeAmount` instead of `Amount`:
|
||||||
|
|
|
@ -24,7 +24,7 @@ use crate::{
|
||||||
SentTransactionOutput, WalletCommitmentTrees, WalletRead, WalletWrite,
|
SentTransactionOutput, WalletCommitmentTrees, WalletRead, WalletWrite,
|
||||||
},
|
},
|
||||||
decrypt_transaction,
|
decrypt_transaction,
|
||||||
fees::{self, ChangeValue, DustOutputPolicy},
|
fees::{self, DustOutputPolicy},
|
||||||
keys::UnifiedSpendingKey,
|
keys::UnifiedSpendingKey,
|
||||||
wallet::{Note, OvkPolicy, Recipient},
|
wallet::{Note, OvkPolicy, Recipient},
|
||||||
zip321::{self, Payment},
|
zip321::{self, Payment},
|
||||||
|
@ -718,13 +718,15 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
for change_value in proposal.balance().proposed_change() {
|
for change_value in proposal.balance().proposed_change() {
|
||||||
match change_value {
|
let memo = change_value
|
||||||
ChangeValue::Sapling { value, memo } => {
|
.memo()
|
||||||
let memo = memo.as_ref().map_or_else(MemoBytes::empty, |m| m.clone());
|
.map_or_else(MemoBytes::empty, |m| m.clone());
|
||||||
|
match change_value.output_pool() {
|
||||||
|
ShieldedProtocol::Sapling => {
|
||||||
builder.add_sapling_output(
|
builder.add_sapling_output(
|
||||||
internal_ovk(),
|
internal_ovk(),
|
||||||
dfvk.change_address().1,
|
dfvk.change_address().1,
|
||||||
*value,
|
change_value.value(),
|
||||||
memo.clone(),
|
memo.clone(),
|
||||||
)?;
|
)?;
|
||||||
sapling_output_meta.push((
|
sapling_output_meta.push((
|
||||||
|
@ -732,10 +734,19 @@ where
|
||||||
account,
|
account,
|
||||||
PoolType::Shielded(ShieldedProtocol::Sapling),
|
PoolType::Shielded(ShieldedProtocol::Sapling),
|
||||||
),
|
),
|
||||||
*value,
|
change_value.value(),
|
||||||
Some(memo),
|
Some(memo),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
ShieldedProtocol::Orchard => {
|
||||||
|
#[cfg(not(feature = "orchard"))]
|
||||||
|
return Err(Error::UnsupportedPoolType(PoolType::Shielded(
|
||||||
|
ShieldedProtocol::Orchard,
|
||||||
|
)));
|
||||||
|
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
unimplemented!("FIXME: implement Orchard change output creation.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,10 +27,10 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
use {
|
use {std::collections::BTreeSet, zcash_primitives::transaction::components::OutPoint};
|
||||||
std::collections::BTreeSet, std::convert::Infallible,
|
|
||||||
zcash_primitives::transaction::components::OutPoint,
|
#[cfg(feature = "orchard")]
|
||||||
};
|
use {crate::fees::orchard as orchard_fees, std::convert::Infallible};
|
||||||
|
|
||||||
/// The type of errors that may be produced in input selection.
|
/// The type of errors that may be produced in input selection.
|
||||||
pub enum InputSelectorError<DbErrT, SelectorErrT> {
|
pub enum InputSelectorError<DbErrT, SelectorErrT> {
|
||||||
|
@ -447,6 +447,23 @@ impl sapling::OutputView for SaplingPayment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An [`InputSelector`] implementation that uses a greedy strategy to select between available
|
/// An [`InputSelector`] implementation that uses a greedy strategy to select between available
|
||||||
/// notes.
|
/// notes.
|
||||||
///
|
///
|
||||||
|
@ -499,6 +516,7 @@ where
|
||||||
{
|
{
|
||||||
let mut transparent_outputs = vec![];
|
let mut transparent_outputs = vec![];
|
||||||
let mut sapling_outputs = vec![];
|
let mut sapling_outputs = vec![];
|
||||||
|
let mut orchard_outputs = vec![];
|
||||||
for payment in transaction_request.payments() {
|
for payment in transaction_request.payments() {
|
||||||
let mut push_transparent = |taddr: TransparentAddress| {
|
let mut push_transparent = |taddr: TransparentAddress| {
|
||||||
transparent_outputs.push(TxOut {
|
transparent_outputs.push(TxOut {
|
||||||
|
@ -509,6 +527,9 @@ where
|
||||||
let mut push_sapling = || {
|
let mut push_sapling = || {
|
||||||
sapling_outputs.push(SaplingPayment(payment.amount));
|
sapling_outputs.push(SaplingPayment(payment.amount));
|
||||||
};
|
};
|
||||||
|
let mut push_orchard = || {
|
||||||
|
orchard_outputs.push(OrchardPayment(payment.amount));
|
||||||
|
};
|
||||||
|
|
||||||
match &payment.recipient_address {
|
match &payment.recipient_address {
|
||||||
Address::Transparent(addr) => {
|
Address::Transparent(addr) => {
|
||||||
|
@ -518,7 +539,14 @@ where
|
||||||
push_sapling();
|
push_sapling();
|
||||||
}
|
}
|
||||||
Address::Unified(addr) => {
|
Address::Unified(addr) => {
|
||||||
if addr.sapling().is_some() {
|
#[cfg(feature = "orchard")]
|
||||||
|
let has_orchard = addr.orchard().is_some();
|
||||||
|
#[cfg(not(feature = "orchard"))]
|
||||||
|
let has_orchard = false;
|
||||||
|
|
||||||
|
if has_orchard {
|
||||||
|
push_orchard();
|
||||||
|
} else if addr.sapling().is_some() {
|
||||||
push_sapling();
|
push_sapling();
|
||||||
} else if let Some(addr) = addr.transparent() {
|
} else if let Some(addr) = addr.transparent() {
|
||||||
push_transparent(*addr);
|
push_transparent(*addr);
|
||||||
|
@ -544,8 +572,17 @@ where
|
||||||
target_height,
|
target_height,
|
||||||
&Vec::<WalletTransparentOutput>::new(),
|
&Vec::<WalletTransparentOutput>::new(),
|
||||||
&transparent_outputs,
|
&transparent_outputs,
|
||||||
&sapling_inputs,
|
&(
|
||||||
&sapling_outputs,
|
::sapling::builder::BundleType::DEFAULT,
|
||||||
|
&sapling_inputs[..],
|
||||||
|
&sapling_outputs[..],
|
||||||
|
),
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
&(
|
||||||
|
::orchard::builder::BundleType::DEFAULT,
|
||||||
|
&Vec::<Infallible>::new()[..],
|
||||||
|
&orchard_outputs[..],
|
||||||
|
),
|
||||||
&self.dust_output_policy,
|
&self.dust_output_policy,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -652,8 +689,17 @@ where
|
||||||
target_height,
|
target_height,
|
||||||
&transparent_inputs,
|
&transparent_inputs,
|
||||||
&Vec::<TxOut>::new(),
|
&Vec::<TxOut>::new(),
|
||||||
&Vec::<ReceivedNote<Infallible>>::new(),
|
&(
|
||||||
&Vec::<SaplingPayment>::new(),
|
::sapling::builder::BundleType::DEFAULT,
|
||||||
|
&Vec::<Infallible>::new()[..],
|
||||||
|
&Vec::<Infallible>::new()[..],
|
||||||
|
),
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
&(
|
||||||
|
orchard::builder::BundleType::DEFAULT,
|
||||||
|
&Vec::<Infallible>::new()[..],
|
||||||
|
&Vec::<Infallible>::new()[..],
|
||||||
|
),
|
||||||
&self.dust_output_policy,
|
&self.dust_output_policy,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -668,8 +714,17 @@ where
|
||||||
target_height,
|
target_height,
|
||||||
&transparent_inputs,
|
&transparent_inputs,
|
||||||
&Vec::<TxOut>::new(),
|
&Vec::<TxOut>::new(),
|
||||||
&Vec::<ReceivedNote<Infallible>>::new(),
|
&(
|
||||||
&Vec::<SaplingPayment>::new(),
|
::sapling::builder::BundleType::DEFAULT,
|
||||||
|
&Vec::<Infallible>::new()[..],
|
||||||
|
&Vec::<Infallible>::new()[..],
|
||||||
|
),
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
&(
|
||||||
|
orchard::builder::BundleType::DEFAULT,
|
||||||
|
&Vec::<Infallible>::new()[..],
|
||||||
|
&Vec::<Infallible>::new()[..],
|
||||||
|
),
|
||||||
&self.dust_output_policy,
|
&self.dust_output_policy,
|
||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,38 +12,69 @@ use zcash_primitives::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::ShieldedProtocol;
|
||||||
|
|
||||||
pub(crate) mod common;
|
pub(crate) mod common;
|
||||||
pub mod fixed;
|
pub mod fixed;
|
||||||
|
pub mod orchard;
|
||||||
pub mod sapling;
|
pub mod sapling;
|
||||||
pub mod standard;
|
pub mod standard;
|
||||||
pub mod zip317;
|
pub mod zip317;
|
||||||
|
|
||||||
/// A proposed change amount and output pool.
|
/// A proposed change amount and output pool.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum ChangeValue {
|
pub struct ChangeValue {
|
||||||
Sapling {
|
output_pool: ShieldedProtocol,
|
||||||
value: NonNegativeAmount,
|
value: NonNegativeAmount,
|
||||||
memo: Option<MemoBytes>,
|
memo: Option<MemoBytes>,
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChangeValue {
|
impl ChangeValue {
|
||||||
|
/// Constructs a new change value from its constituent parts.
|
||||||
|
pub fn new(
|
||||||
|
output_pool: ShieldedProtocol,
|
||||||
|
value: NonNegativeAmount,
|
||||||
|
memo: Option<MemoBytes>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
output_pool,
|
||||||
|
value,
|
||||||
|
memo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs a new change value that will be created as a Sapling output.
|
||||||
pub fn sapling(value: NonNegativeAmount, memo: Option<MemoBytes>) -> Self {
|
pub fn sapling(value: NonNegativeAmount, memo: Option<MemoBytes>) -> Self {
|
||||||
Self::Sapling { value, memo }
|
Self {
|
||||||
|
output_pool: ShieldedProtocol::Sapling,
|
||||||
|
value,
|
||||||
|
memo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs a new change value that will be created as an Orchard output.
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
pub fn orchard(value: NonNegativeAmount, memo: Option<MemoBytes>) -> Self {
|
||||||
|
Self {
|
||||||
|
output_pool: ShieldedProtocol::Orchard,
|
||||||
|
value,
|
||||||
|
memo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the pool to which the change output should be sent.
|
||||||
|
pub fn output_pool(&self) -> ShieldedProtocol {
|
||||||
|
self.output_pool
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the value of the change output to be created, in zatoshis.
|
/// Returns the value of the change output to be created, in zatoshis.
|
||||||
pub fn value(&self) -> NonNegativeAmount {
|
pub fn value(&self) -> NonNegativeAmount {
|
||||||
match self {
|
self.value
|
||||||
ChangeValue::Sapling { value, .. } => *value,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the memo to be associated with the change output.
|
/// Returns the memo to be associated with the change output.
|
||||||
pub fn memo(&self) -> Option<&MemoBytes> {
|
pub fn memo(&self) -> Option<&MemoBytes> {
|
||||||
match self {
|
self.memo.as_ref()
|
||||||
ChangeValue::Sapling { memo, .. } => memo.as_ref(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,6 +151,8 @@ pub enum ChangeError<E, NoteRefT> {
|
||||||
},
|
},
|
||||||
/// An error occurred that was specific to the change selection strategy in use.
|
/// An error occurred that was specific to the change selection strategy in use.
|
||||||
StrategyError(E),
|
StrategyError(E),
|
||||||
|
/// The proposed bundle structure would violate bundle type construction rules.
|
||||||
|
BundleError(&'static str),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E, NoteRefT> ChangeError<E, NoteRefT> {
|
impl<E, NoteRefT> ChangeError<E, NoteRefT> {
|
||||||
|
@ -140,6 +173,7 @@ impl<E, NoteRefT> ChangeError<E, NoteRefT> {
|
||||||
sapling,
|
sapling,
|
||||||
},
|
},
|
||||||
ChangeError::StrategyError(e) => ChangeError::StrategyError(f(e)),
|
ChangeError::StrategyError(e) => ChangeError::StrategyError(f(e)),
|
||||||
|
ChangeError::BundleError(e) => ChangeError::BundleError(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -167,6 +201,13 @@ impl<CE: fmt::Display, N: fmt::Display> fmt::Display for ChangeError<CE, N> {
|
||||||
ChangeError::StrategyError(err) => {
|
ChangeError::StrategyError(err) => {
|
||||||
write!(f, "{}", err)
|
write!(f, "{}", err)
|
||||||
}
|
}
|
||||||
|
ChangeError::BundleError(err) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"The proposed transaction structure violates bundle type constrints: {}",
|
||||||
|
err
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -252,8 +293,8 @@ pub trait ChangeStrategy {
|
||||||
target_height: BlockHeight,
|
target_height: BlockHeight,
|
||||||
transparent_inputs: &[impl transparent::InputView],
|
transparent_inputs: &[impl transparent::InputView],
|
||||||
transparent_outputs: &[impl transparent::OutputView],
|
transparent_outputs: &[impl transparent::OutputView],
|
||||||
sapling_inputs: &[impl sapling::InputView<NoteRefT>],
|
sapling: &impl sapling::BundleView<NoteRefT>,
|
||||||
sapling_outputs: &[impl sapling::OutputView],
|
#[cfg(feature = "orchard")] orchard: &impl orchard::BundleView<NoteRefT>,
|
||||||
dust_output_policy: &DustOutputPolicy,
|
dust_output_policy: &DustOutputPolicy,
|
||||||
) -> Result<TransactionBalance, ChangeError<Self::Error, NoteRefT>>;
|
) -> Result<TransactionBalance, ChangeError<Self::Error, NoteRefT>>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,15 @@ use zcash_primitives::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{sapling, ChangeError, ChangeValue, DustAction, DustOutputPolicy, TransactionBalance};
|
use crate::ShieldedProtocol;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
sapling as sapling_fees, ChangeError, ChangeValue, DustAction, DustOutputPolicy,
|
||||||
|
TransactionBalance,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
use super::orchard as orchard_fees;
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(crate) fn single_change_output_balance<
|
pub(crate) fn single_change_output_balance<
|
||||||
|
@ -21,8 +29,8 @@ pub(crate) fn single_change_output_balance<
|
||||||
target_height: BlockHeight,
|
target_height: BlockHeight,
|
||||||
transparent_inputs: &[impl transparent::InputView],
|
transparent_inputs: &[impl transparent::InputView],
|
||||||
transparent_outputs: &[impl transparent::OutputView],
|
transparent_outputs: &[impl transparent::OutputView],
|
||||||
sapling_inputs: &[impl sapling::InputView<NoteRefT>],
|
sapling: &impl sapling_fees::BundleView<NoteRefT>,
|
||||||
sapling_outputs: &[impl sapling::OutputView],
|
#[cfg(feature = "orchard")] orchard: &impl orchard_fees::BundleView<NoteRefT>,
|
||||||
dust_output_policy: &DustOutputPolicy,
|
dust_output_policy: &DustOutputPolicy,
|
||||||
default_dust_threshold: NonNegativeAmount,
|
default_dust_threshold: NonNegativeAmount,
|
||||||
change_memo: Option<MemoBytes>,
|
change_memo: Option<MemoBytes>,
|
||||||
|
@ -43,40 +51,88 @@ where
|
||||||
.map(|t_out| t_out.value())
|
.map(|t_out| t_out.value())
|
||||||
.sum::<Option<_>>()
|
.sum::<Option<_>>()
|
||||||
.ok_or_else(overflow)?;
|
.ok_or_else(overflow)?;
|
||||||
let sapling_in = sapling_inputs
|
let sapling_in = sapling
|
||||||
|
.inputs()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s_in| s_in.value())
|
.map(sapling_fees::InputView::<NoteRefT>::value)
|
||||||
.sum::<Option<_>>()
|
.sum::<Option<_>>()
|
||||||
.ok_or_else(overflow)?;
|
.ok_or_else(overflow)?;
|
||||||
let sapling_out = sapling_outputs
|
let sapling_out = sapling
|
||||||
|
.outputs()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s_out| s_out.value())
|
.map(sapling_fees::OutputView::value)
|
||||||
.sum::<Option<_>>()
|
.sum::<Option<_>>()
|
||||||
.ok_or_else(overflow)?;
|
.ok_or_else(overflow)?;
|
||||||
|
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
let orchard_in = orchard
|
||||||
|
.inputs()
|
||||||
|
.iter()
|
||||||
|
.map(orchard_fees::InputView::<NoteRefT>::value)
|
||||||
|
.sum::<Option<_>>()
|
||||||
|
.ok_or_else(overflow)?;
|
||||||
|
#[cfg(not(feature = "orchard"))]
|
||||||
|
let orchard_in = NonNegativeAmount::ZERO;
|
||||||
|
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
let orchard_out = orchard
|
||||||
|
.outputs()
|
||||||
|
.iter()
|
||||||
|
.map(orchard_fees::OutputView::value)
|
||||||
|
.sum::<Option<_>>()
|
||||||
|
.ok_or_else(overflow)?;
|
||||||
|
#[cfg(not(feature = "orchard"))]
|
||||||
|
let orchard_out = NonNegativeAmount::ZERO;
|
||||||
|
|
||||||
|
// TODO: implement a less naive strategy for selecting the pool to which change will be sent.
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
let (change_pool, sapling_change, orchard_change) =
|
||||||
|
if orchard_in > NonNegativeAmount::ZERO || orchard_out > NonNegativeAmount::ZERO {
|
||||||
|
// Send change to Orchard if we're spending any Orchard inputs or creating any Orchard outputs
|
||||||
|
(ShieldedProtocol::Orchard, 0, 1)
|
||||||
|
} else if sapling_in > NonNegativeAmount::ZERO || sapling_out > NonNegativeAmount::ZERO {
|
||||||
|
// Otherwise, send change to Sapling if we're spending any Sapling inputs or creating any
|
||||||
|
// Sapling outputs, so that we avoid pool-crossing.
|
||||||
|
(ShieldedProtocol::Sapling, 1, 0)
|
||||||
|
} else {
|
||||||
|
// For all other transactions, send change to Sapling.
|
||||||
|
// FIXME: Change this to Orchard once Orchard outputs are enabled.
|
||||||
|
(ShieldedProtocol::Sapling, 1, 0)
|
||||||
|
};
|
||||||
|
#[cfg(not(feature = "orchard"))]
|
||||||
|
let (change_pool, sapling_change) = (ShieldedProtocol::Sapling, 1);
|
||||||
|
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
let orchard_num_actions = orchard
|
||||||
|
.bundle_type()
|
||||||
|
.num_actions(
|
||||||
|
orchard.inputs().len(),
|
||||||
|
orchard.outputs().len() + orchard_change,
|
||||||
|
)
|
||||||
|
.map_err(ChangeError::BundleError)?;
|
||||||
|
#[cfg(not(feature = "orchard"))]
|
||||||
|
let orchard_num_actions = 0;
|
||||||
|
|
||||||
let fee_amount = fee_rule
|
let fee_amount = fee_rule
|
||||||
.fee_required(
|
.fee_required(
|
||||||
params,
|
params,
|
||||||
target_height,
|
target_height,
|
||||||
transparent_inputs,
|
transparent_inputs,
|
||||||
transparent_outputs,
|
transparent_outputs,
|
||||||
sapling_inputs.len(),
|
sapling.inputs().len(),
|
||||||
if sapling_inputs.is_empty() {
|
sapling
|
||||||
sapling_outputs.len() + 1
|
.bundle_type()
|
||||||
} else {
|
.num_outputs(
|
||||||
std::cmp::max(sapling_outputs.len() + 1, 2)
|
sapling.inputs().len(),
|
||||||
},
|
sapling.outputs().len() + sapling_change,
|
||||||
//Orchard is not yet supported in zcash_client_backend
|
)
|
||||||
0,
|
.map_err(ChangeError::BundleError)?,
|
||||||
|
orchard_num_actions,
|
||||||
)
|
)
|
||||||
.map_err(|fee_error| ChangeError::StrategyError(E::from(fee_error)))?;
|
.map_err(|fee_error| ChangeError::StrategyError(E::from(fee_error)))?;
|
||||||
|
|
||||||
let total_in = (t_in + sapling_in).ok_or_else(overflow)?;
|
let total_in = (t_in + sapling_in + orchard_in).ok_or_else(overflow)?;
|
||||||
|
let total_out = (t_out + sapling_out + orchard_out + fee_amount).ok_or_else(overflow)?;
|
||||||
let total_out = [t_out, sapling_out, fee_amount]
|
|
||||||
.iter()
|
|
||||||
.sum::<Option<NonNegativeAmount>>()
|
|
||||||
.ok_or_else(overflow)?;
|
|
||||||
|
|
||||||
let proposed_change = (total_in - total_out).ok_or(ChangeError::InsufficientFunds {
|
let proposed_change = (total_in - total_out).ok_or(ChangeError::InsufficientFunds {
|
||||||
available: total_in,
|
available: total_in,
|
||||||
|
@ -101,7 +157,7 @@ where
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
DustAction::AllowDustChange => TransactionBalance::new(
|
DustAction::AllowDustChange => TransactionBalance::new(
|
||||||
vec![ChangeValue::sapling(proposed_change, change_memo)],
|
vec![ChangeValue::new(change_pool, proposed_change, change_memo)],
|
||||||
fee_amount,
|
fee_amount,
|
||||||
)
|
)
|
||||||
.map_err(|_| overflow()),
|
.map_err(|_| overflow()),
|
||||||
|
@ -113,7 +169,7 @@ where
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
TransactionBalance::new(
|
TransactionBalance::new(
|
||||||
vec![ChangeValue::sapling(proposed_change, change_memo)],
|
vec![ChangeValue::new(change_pool, proposed_change, change_memo)],
|
||||||
fee_amount,
|
fee_amount,
|
||||||
)
|
)
|
||||||
.map_err(|_| overflow())
|
.map_err(|_| overflow())
|
||||||
|
|
|
@ -10,10 +10,13 @@ use zcash_primitives::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
common::single_change_output_balance, sapling, ChangeError, ChangeStrategy, DustOutputPolicy,
|
common::single_change_output_balance, sapling as sapling_fees, ChangeError, ChangeStrategy,
|
||||||
TransactionBalance,
|
DustOutputPolicy, TransactionBalance,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
use super::orchard as orchard_fees;
|
||||||
|
|
||||||
/// A change strategy that and proposes change as a single output to the most current supported
|
/// A change strategy that and proposes change as a single output to the most current supported
|
||||||
/// shielded pool and delegates fee calculation to the provided fee rule.
|
/// shielded pool and delegates fee calculation to the provided fee rule.
|
||||||
pub struct SingleOutputChangeStrategy {
|
pub struct SingleOutputChangeStrategy {
|
||||||
|
@ -46,8 +49,8 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
target_height: BlockHeight,
|
target_height: BlockHeight,
|
||||||
transparent_inputs: &[impl transparent::InputView],
|
transparent_inputs: &[impl transparent::InputView],
|
||||||
transparent_outputs: &[impl transparent::OutputView],
|
transparent_outputs: &[impl transparent::OutputView],
|
||||||
sapling_inputs: &[impl sapling::InputView<NoteRefT>],
|
sapling: &impl sapling_fees::BundleView<NoteRefT>,
|
||||||
sapling_outputs: &[impl sapling::OutputView],
|
#[cfg(feature = "orchard")] orchard: &impl orchard_fees::BundleView<NoteRefT>,
|
||||||
dust_output_policy: &DustOutputPolicy,
|
dust_output_policy: &DustOutputPolicy,
|
||||||
) -> Result<TransactionBalance, ChangeError<Self::Error, NoteRefT>> {
|
) -> Result<TransactionBalance, ChangeError<Self::Error, NoteRefT>> {
|
||||||
single_change_output_balance(
|
single_change_output_balance(
|
||||||
|
@ -56,8 +59,9 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
target_height,
|
target_height,
|
||||||
transparent_inputs,
|
transparent_inputs,
|
||||||
transparent_outputs,
|
transparent_outputs,
|
||||||
sapling_inputs,
|
sapling,
|
||||||
sapling_outputs,
|
#[cfg(feature = "orchard")]
|
||||||
|
orchard,
|
||||||
dust_output_policy,
|
dust_output_policy,
|
||||||
self.fee_rule().fixed_fee(),
|
self.fee_rule().fixed_fee(),
|
||||||
self.change_memo.clone(),
|
self.change_memo.clone(),
|
||||||
|
@ -67,6 +71,9 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
use std::convert::Infallible;
|
||||||
|
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
consensus::{Network, NetworkUpgrade, Parameters},
|
consensus::{Network, NetworkUpgrade, Parameters},
|
||||||
transaction::{
|
transaction::{
|
||||||
|
@ -98,13 +105,22 @@ mod tests {
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
&Vec::<TestTransparentInput>::new(),
|
&Vec::<TestTransparentInput>::new(),
|
||||||
&Vec::<TxOut>::new(),
|
&Vec::<TxOut>::new(),
|
||||||
|
&(
|
||||||
|
sapling::builder::BundleType::DEFAULT,
|
||||||
&[TestSaplingInput {
|
&[TestSaplingInput {
|
||||||
note_id: 0,
|
note_id: 0,
|
||||||
value: NonNegativeAmount::const_from_u64(60000),
|
value: NonNegativeAmount::const_from_u64(60000),
|
||||||
}],
|
}][..],
|
||||||
&[SaplingPayment::new(NonNegativeAmount::const_from_u64(
|
&[SaplingPayment::new(NonNegativeAmount::const_from_u64(
|
||||||
40000,
|
40000,
|
||||||
))],
|
))][..],
|
||||||
|
),
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
&(
|
||||||
|
orchard::builder::BundleType::DEFAULT,
|
||||||
|
&[] as &[Infallible],
|
||||||
|
&[] as &[Infallible],
|
||||||
|
),
|
||||||
&DustOutputPolicy::default(),
|
&DustOutputPolicy::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -130,6 +146,8 @@ mod tests {
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
&Vec::<TestTransparentInput>::new(),
|
&Vec::<TestTransparentInput>::new(),
|
||||||
&Vec::<TxOut>::new(),
|
&Vec::<TxOut>::new(),
|
||||||
|
&(
|
||||||
|
sapling::builder::BundleType::DEFAULT,
|
||||||
&[
|
&[
|
||||||
TestSaplingInput {
|
TestSaplingInput {
|
||||||
note_id: 0,
|
note_id: 0,
|
||||||
|
@ -140,10 +158,17 @@ mod tests {
|
||||||
note_id: 0,
|
note_id: 0,
|
||||||
value: NonNegativeAmount::const_from_u64(10100),
|
value: NonNegativeAmount::const_from_u64(10100),
|
||||||
},
|
},
|
||||||
],
|
][..],
|
||||||
&[SaplingPayment::new(NonNegativeAmount::const_from_u64(
|
&[SaplingPayment::new(NonNegativeAmount::const_from_u64(
|
||||||
40000,
|
40000,
|
||||||
))],
|
))][..],
|
||||||
|
),
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
&(
|
||||||
|
orchard::builder::BundleType::DEFAULT,
|
||||||
|
&[] as &[Infallible],
|
||||||
|
&[] as &[Infallible],
|
||||||
|
),
|
||||||
&DustOutputPolicy::default(),
|
&DustOutputPolicy::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
//! Types related to computation of fees and change related to the Orchard components
|
||||||
|
//! of a transaction.
|
||||||
|
|
||||||
|
use std::convert::Infallible;
|
||||||
|
use zcash_primitives::transaction::components::amount::NonNegativeAmount;
|
||||||
|
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
use orchard::builder::BundleType;
|
||||||
|
|
||||||
|
/// A trait that provides a minimized view of Orchard bundle configuration
|
||||||
|
/// suitable for use in fee and change calculation.
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
pub trait BundleView<NoteRef> {
|
||||||
|
/// The type of inputs to the bundle.
|
||||||
|
type In: InputView<NoteRef>;
|
||||||
|
/// The type of inputs of the bundle.
|
||||||
|
type Out: OutputView;
|
||||||
|
|
||||||
|
/// Returns the type of the bundle
|
||||||
|
fn bundle_type(&self) -> BundleType;
|
||||||
|
/// Returns the inputs to the bundle.
|
||||||
|
fn inputs(&self) -> &[Self::In];
|
||||||
|
/// Returns the outputs of the bundle.
|
||||||
|
fn outputs(&self) -> &[Self::Out];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
impl<'a, NoteRef, In: InputView<NoteRef>, Out: OutputView> BundleView<NoteRef>
|
||||||
|
for (BundleType, &'a [In], &'a [Out])
|
||||||
|
{
|
||||||
|
type In = In;
|
||||||
|
type Out = Out;
|
||||||
|
|
||||||
|
fn bundle_type(&self) -> BundleType {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inputs(&self) -> &[In] {
|
||||||
|
self.1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn outputs(&self) -> &[Out] {
|
||||||
|
self.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait that provides a minimized view of an Orchard input suitable for use in fee and change
|
||||||
|
/// calculation.
|
||||||
|
pub trait InputView<NoteRef> {
|
||||||
|
/// An identifier for the input being spent.
|
||||||
|
fn note_id(&self) -> &NoteRef;
|
||||||
|
/// The value of the input being spent.
|
||||||
|
fn value(&self) -> NonNegativeAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N> InputView<N> for Infallible {
|
||||||
|
fn note_id(&self) -> &N {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
fn value(&self) -> NonNegativeAmount {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait that provides a minimized view of a Orchard output suitable for use in fee and change
|
||||||
|
/// calculation.
|
||||||
|
pub trait OutputView {
|
||||||
|
/// The value of the output being produced.
|
||||||
|
fn value(&self) -> NonNegativeAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutputView for Infallible {
|
||||||
|
fn value(&self) -> NonNegativeAmount {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,9 +3,44 @@
|
||||||
|
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
|
|
||||||
use sapling::builder::{OutputInfo, SpendInfo};
|
use sapling::builder::{BundleType, OutputInfo, SpendInfo};
|
||||||
use zcash_primitives::transaction::components::amount::NonNegativeAmount;
|
use zcash_primitives::transaction::components::amount::NonNegativeAmount;
|
||||||
|
|
||||||
|
/// A trait that provides a minimized view of Sapling bundle configuration
|
||||||
|
/// suitable for use in fee and change calculation.
|
||||||
|
pub trait BundleView<NoteRef> {
|
||||||
|
/// The type of inputs to the bundle.
|
||||||
|
type In: InputView<NoteRef>;
|
||||||
|
/// The type of inputs of the bundle.
|
||||||
|
type Out: OutputView;
|
||||||
|
|
||||||
|
/// Returns the type of the bundle
|
||||||
|
fn bundle_type(&self) -> BundleType;
|
||||||
|
/// Returns the inputs to the bundle.
|
||||||
|
fn inputs(&self) -> &[Self::In];
|
||||||
|
/// Returns the outputs of the bundle.
|
||||||
|
fn outputs(&self) -> &[Self::Out];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, NoteRef, In: InputView<NoteRef>, Out: OutputView> BundleView<NoteRef>
|
||||||
|
for (BundleType, &'a [In], &'a [Out])
|
||||||
|
{
|
||||||
|
type In = In;
|
||||||
|
type Out = Out;
|
||||||
|
|
||||||
|
fn bundle_type(&self) -> BundleType {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inputs(&self) -> &[In] {
|
||||||
|
self.1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn outputs(&self) -> &[Out] {
|
||||||
|
self.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A trait that provides a minimized view of a Sapling input suitable for use in
|
/// A trait that provides a minimized view of a Sapling input suitable for use in
|
||||||
/// fee and change calculation.
|
/// fee and change calculation.
|
||||||
pub trait InputView<NoteRef> {
|
pub trait InputView<NoteRef> {
|
||||||
|
|
|
@ -15,9 +15,13 @@ use zcash_primitives::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
fixed, sapling, zip317, ChangeError, ChangeStrategy, DustOutputPolicy, TransactionBalance,
|
fixed, sapling as sapling_fees, zip317, ChangeError, ChangeStrategy, DustOutputPolicy,
|
||||||
|
TransactionBalance,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
use super::orchard as orchard_fees;
|
||||||
|
|
||||||
/// A change strategy that proposes change as a single output to the most current supported
|
/// A change strategy that proposes change as a single output to the most current supported
|
||||||
/// shielded pool and delegates fee calculation to the provided fee rule.
|
/// shielded pool and delegates fee calculation to the provided fee rule.
|
||||||
pub struct SingleOutputChangeStrategy {
|
pub struct SingleOutputChangeStrategy {
|
||||||
|
@ -50,8 +54,8 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
target_height: BlockHeight,
|
target_height: BlockHeight,
|
||||||
transparent_inputs: &[impl transparent::InputView],
|
transparent_inputs: &[impl transparent::InputView],
|
||||||
transparent_outputs: &[impl transparent::OutputView],
|
transparent_outputs: &[impl transparent::OutputView],
|
||||||
sapling_inputs: &[impl sapling::InputView<NoteRefT>],
|
sapling: &impl sapling_fees::BundleView<NoteRefT>,
|
||||||
sapling_outputs: &[impl sapling::OutputView],
|
#[cfg(feature = "orchard")] orchard: &impl orchard_fees::BundleView<NoteRefT>,
|
||||||
dust_output_policy: &DustOutputPolicy,
|
dust_output_policy: &DustOutputPolicy,
|
||||||
) -> Result<TransactionBalance, ChangeError<Self::Error, NoteRefT>> {
|
) -> Result<TransactionBalance, ChangeError<Self::Error, NoteRefT>> {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
|
@ -65,8 +69,9 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
target_height,
|
target_height,
|
||||||
transparent_inputs,
|
transparent_inputs,
|
||||||
transparent_outputs,
|
transparent_outputs,
|
||||||
sapling_inputs,
|
sapling,
|
||||||
sapling_outputs,
|
#[cfg(feature = "orchard")]
|
||||||
|
orchard,
|
||||||
dust_output_policy,
|
dust_output_policy,
|
||||||
)
|
)
|
||||||
.map_err(|e| e.map(Zip317FeeError::Balance)),
|
.map_err(|e| e.map(Zip317FeeError::Balance)),
|
||||||
|
@ -79,8 +84,9 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
target_height,
|
target_height,
|
||||||
transparent_inputs,
|
transparent_inputs,
|
||||||
transparent_outputs,
|
transparent_outputs,
|
||||||
sapling_inputs,
|
sapling,
|
||||||
sapling_outputs,
|
#[cfg(feature = "orchard")]
|
||||||
|
orchard,
|
||||||
dust_output_policy,
|
dust_output_policy,
|
||||||
)
|
)
|
||||||
.map_err(|e| e.map(Zip317FeeError::Balance)),
|
.map_err(|e| e.map(Zip317FeeError::Balance)),
|
||||||
|
@ -93,8 +99,9 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
target_height,
|
target_height,
|
||||||
transparent_inputs,
|
transparent_inputs,
|
||||||
transparent_outputs,
|
transparent_outputs,
|
||||||
sapling_inputs,
|
sapling,
|
||||||
sapling_outputs,
|
#[cfg(feature = "orchard")]
|
||||||
|
orchard,
|
||||||
dust_output_policy,
|
dust_output_policy,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,13 @@ use zcash_primitives::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
common::single_change_output_balance, sapling, ChangeError, ChangeStrategy, DustOutputPolicy,
|
common::single_change_output_balance, sapling as sapling_fees, ChangeError, ChangeStrategy,
|
||||||
TransactionBalance,
|
DustOutputPolicy, TransactionBalance,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
use super::orchard as orchard_fees;
|
||||||
|
|
||||||
/// A change strategy that and proposes change as a single output to the most current supported
|
/// A change strategy that and proposes change as a single output to the most current supported
|
||||||
/// shielded pool and delegates fee calculation to the provided fee rule.
|
/// shielded pool and delegates fee calculation to the provided fee rule.
|
||||||
pub struct SingleOutputChangeStrategy {
|
pub struct SingleOutputChangeStrategy {
|
||||||
|
@ -50,8 +53,8 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
target_height: BlockHeight,
|
target_height: BlockHeight,
|
||||||
transparent_inputs: &[impl transparent::InputView],
|
transparent_inputs: &[impl transparent::InputView],
|
||||||
transparent_outputs: &[impl transparent::OutputView],
|
transparent_outputs: &[impl transparent::OutputView],
|
||||||
sapling_inputs: &[impl sapling::InputView<NoteRefT>],
|
sapling: &impl sapling_fees::BundleView<NoteRefT>,
|
||||||
sapling_outputs: &[impl sapling::OutputView],
|
#[cfg(feature = "orchard")] orchard: &impl orchard_fees::BundleView<NoteRefT>,
|
||||||
dust_output_policy: &DustOutputPolicy,
|
dust_output_policy: &DustOutputPolicy,
|
||||||
) -> Result<TransactionBalance, ChangeError<Self::Error, NoteRefT>> {
|
) -> Result<TransactionBalance, ChangeError<Self::Error, NoteRefT>> {
|
||||||
let mut transparent_dust: Vec<_> = transparent_inputs
|
let mut transparent_dust: Vec<_> = transparent_inputs
|
||||||
|
@ -67,11 +70,12 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut sapling_dust: Vec<_> = sapling_inputs
|
let mut sapling_dust: Vec<_> = sapling
|
||||||
|
.inputs()
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|i| {
|
.filter_map(|i| {
|
||||||
if i.value() < self.fee_rule.marginal_fee() {
|
if sapling_fees::InputView::<NoteRefT>::value(i) < self.fee_rule.marginal_fee() {
|
||||||
Some(i.note_id().clone())
|
Some(sapling_fees::InputView::<NoteRefT>::note_id(i).clone())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -88,8 +92,8 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
// We add one to the sapling outputs for the (single) change output Note that this
|
// We add one to the sapling outputs for the (single) change output Note that this
|
||||||
// means that wallet-internal shielding transactions are an opportunity to spend a dust
|
// means that wallet-internal shielding transactions are an opportunity to spend a dust
|
||||||
// note.
|
// note.
|
||||||
let s_non_dust = sapling_inputs.len() - sapling_dust.len();
|
let s_non_dust = sapling.inputs().len() - sapling_dust.len();
|
||||||
let s_allowed_dust = (sapling_outputs.len() + 1).saturating_sub(s_non_dust);
|
let s_allowed_dust = (sapling.outputs().len() + 1).saturating_sub(s_non_dust);
|
||||||
|
|
||||||
let available_grace_inputs = self
|
let available_grace_inputs = self
|
||||||
.fee_rule
|
.fee_rule
|
||||||
|
@ -135,8 +139,9 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
target_height,
|
target_height,
|
||||||
transparent_inputs,
|
transparent_inputs,
|
||||||
transparent_outputs,
|
transparent_outputs,
|
||||||
sapling_inputs,
|
sapling,
|
||||||
sapling_outputs,
|
#[cfg(feature = "orchard")]
|
||||||
|
orchard,
|
||||||
dust_output_policy,
|
dust_output_policy,
|
||||||
self.fee_rule.marginal_fee(),
|
self.fee_rule.marginal_fee(),
|
||||||
self.change_memo.clone(),
|
self.change_memo.clone(),
|
||||||
|
@ -147,6 +152,8 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
|
use std::convert::Infallible;
|
||||||
|
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
consensus::{Network, NetworkUpgrade, Parameters},
|
consensus::{Network, NetworkUpgrade, Parameters},
|
||||||
legacy::Script,
|
legacy::Script,
|
||||||
|
@ -177,13 +184,22 @@ mod tests {
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
&Vec::<TestTransparentInput>::new(),
|
&Vec::<TestTransparentInput>::new(),
|
||||||
&Vec::<TxOut>::new(),
|
&Vec::<TxOut>::new(),
|
||||||
|
&(
|
||||||
|
sapling::builder::BundleType::DEFAULT,
|
||||||
&[TestSaplingInput {
|
&[TestSaplingInput {
|
||||||
note_id: 0,
|
note_id: 0,
|
||||||
value: NonNegativeAmount::const_from_u64(55000),
|
value: NonNegativeAmount::const_from_u64(55000),
|
||||||
}],
|
}][..],
|
||||||
&[SaplingPayment::new(NonNegativeAmount::const_from_u64(
|
&[SaplingPayment::new(NonNegativeAmount::const_from_u64(
|
||||||
40000,
|
40000,
|
||||||
))],
|
))][..],
|
||||||
|
),
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
&(
|
||||||
|
orchard::builder::BundleType::DEFAULT,
|
||||||
|
&Vec::<Infallible>::new()[..],
|
||||||
|
&Vec::<Infallible>::new()[..],
|
||||||
|
),
|
||||||
&DustOutputPolicy::default(),
|
&DustOutputPolicy::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -210,11 +226,20 @@ mod tests {
|
||||||
value: NonNegativeAmount::const_from_u64(40000),
|
value: NonNegativeAmount::const_from_u64(40000),
|
||||||
script_pubkey: Script(vec![]),
|
script_pubkey: Script(vec![]),
|
||||||
}],
|
}],
|
||||||
|
&(
|
||||||
|
sapling::builder::BundleType::DEFAULT,
|
||||||
&[TestSaplingInput {
|
&[TestSaplingInput {
|
||||||
note_id: 0,
|
note_id: 0,
|
||||||
value: NonNegativeAmount::const_from_u64(55000),
|
value: NonNegativeAmount::const_from_u64(55000),
|
||||||
}],
|
}][..],
|
||||||
&Vec::<SaplingPayment>::new(),
|
&Vec::<Infallible>::new()[..],
|
||||||
|
),
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
&(
|
||||||
|
orchard::builder::BundleType::DEFAULT,
|
||||||
|
&Vec::<Infallible>::new()[..],
|
||||||
|
&Vec::<Infallible>::new()[..],
|
||||||
|
),
|
||||||
&DustOutputPolicy::default(),
|
&DustOutputPolicy::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -237,6 +262,8 @@ mod tests {
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
&Vec::<TestTransparentInput>::new(),
|
&Vec::<TestTransparentInput>::new(),
|
||||||
&Vec::<TxOut>::new(),
|
&Vec::<TxOut>::new(),
|
||||||
|
&(
|
||||||
|
sapling::builder::BundleType::DEFAULT,
|
||||||
&[
|
&[
|
||||||
TestSaplingInput {
|
TestSaplingInput {
|
||||||
note_id: 0,
|
note_id: 0,
|
||||||
|
@ -246,10 +273,17 @@ mod tests {
|
||||||
note_id: 1,
|
note_id: 1,
|
||||||
value: NonNegativeAmount::const_from_u64(1000),
|
value: NonNegativeAmount::const_from_u64(1000),
|
||||||
},
|
},
|
||||||
],
|
][..],
|
||||||
&[SaplingPayment::new(NonNegativeAmount::const_from_u64(
|
&[SaplingPayment::new(NonNegativeAmount::const_from_u64(
|
||||||
40000,
|
40000,
|
||||||
))],
|
))][..],
|
||||||
|
),
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
&(
|
||||||
|
orchard::builder::BundleType::DEFAULT,
|
||||||
|
&Vec::<Infallible>::new()[..],
|
||||||
|
&Vec::<Infallible>::new()[..],
|
||||||
|
),
|
||||||
&DustOutputPolicy::default(),
|
&DustOutputPolicy::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -272,6 +306,8 @@ mod tests {
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
&Vec::<TestTransparentInput>::new(),
|
&Vec::<TestTransparentInput>::new(),
|
||||||
&Vec::<TxOut>::new(),
|
&Vec::<TxOut>::new(),
|
||||||
|
&(
|
||||||
|
sapling::builder::BundleType::DEFAULT,
|
||||||
&[
|
&[
|
||||||
TestSaplingInput {
|
TestSaplingInput {
|
||||||
note_id: 0,
|
note_id: 0,
|
||||||
|
@ -285,10 +321,17 @@ mod tests {
|
||||||
note_id: 2,
|
note_id: 2,
|
||||||
value: NonNegativeAmount::const_from_u64(1000),
|
value: NonNegativeAmount::const_from_u64(1000),
|
||||||
},
|
},
|
||||||
],
|
][..],
|
||||||
&[SaplingPayment::new(NonNegativeAmount::const_from_u64(
|
&[SaplingPayment::new(NonNegativeAmount::const_from_u64(
|
||||||
40000,
|
40000,
|
||||||
))],
|
))][..],
|
||||||
|
),
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
&(
|
||||||
|
orchard::builder::BundleType::DEFAULT,
|
||||||
|
&Vec::<Infallible>::new()[..],
|
||||||
|
&Vec::<Infallible>::new()[..],
|
||||||
|
),
|
||||||
&DustOutputPolicy::default(),
|
&DustOutputPolicy::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -341,17 +341,20 @@ impl proposal::Proposal {
|
||||||
.balance()
|
.balance()
|
||||||
.proposed_change()
|
.proposed_change()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|change| match change {
|
.map(|change| match change.output_pool() {
|
||||||
ChangeValue::Sapling { value, memo } => proposal::ChangeValue {
|
ShieldedProtocol::Sapling => proposal::ChangeValue {
|
||||||
value: Some(proposal::change_value::Value::SaplingValue(
|
value: Some(proposal::change_value::Value::SaplingValue(
|
||||||
proposal::SaplingChange {
|
proposal::SaplingChange {
|
||||||
amount: (*value).into(),
|
amount: change.value().into(),
|
||||||
memo: memo.as_ref().map(|memo_bytes| proposal::MemoBytes {
|
memo: change.memo().map(|memo_bytes| proposal::MemoBytes {
|
||||||
value: memo_bytes.as_slice().to_vec(),
|
value: memo_bytes.as_slice().to_vec(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
|
ShieldedProtocol::Orchard => {
|
||||||
|
unimplemented!("FIXME: implement Orchard change outputs!")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
fee_required: value.balance().fee_required().into(),
|
fee_required: value.balance().fee_required().into(),
|
||||||
|
|
|
@ -44,9 +44,11 @@ and this library adheres to Rust's notion of
|
||||||
- `impl From<&NonNegativeAmount> for Amount`
|
- `impl From<&NonNegativeAmount> for Amount`
|
||||||
- `impl From<NonNegativeAmount> for u64`
|
- `impl From<NonNegativeAmount> for u64`
|
||||||
- `impl From<NonNegativeAmount> for zcash_primitives::sapling::value::NoteValue`
|
- `impl From<NonNegativeAmount> for zcash_primitives::sapling::value::NoteValue`
|
||||||
|
- `impl From<NonNegativeAmount> for orchard::::NoteValue`
|
||||||
- `impl Sum<NonNegativeAmount> for Option<NonNegativeAmount>`
|
- `impl Sum<NonNegativeAmount> for Option<NonNegativeAmount>`
|
||||||
- `impl<'a> Sum<&'a NonNegativeAmount> for Option<NonNegativeAmount>`
|
- `impl<'a> Sum<&'a NonNegativeAmount> for Option<NonNegativeAmount>`
|
||||||
- `impl TryFrom<sapling::value::NoteValue> for NonNegativeAmount`
|
- `impl TryFrom<sapling::value::NoteValue> for NonNegativeAmount`
|
||||||
|
- `impl TryFrom<orchard::NoteValue> for NonNegativeAmount`
|
||||||
- `impl {Clone, PartialEq, Eq} for zcash_primitives::memo::Error`
|
- `impl {Clone, PartialEq, Eq} for zcash_primitives::memo::Error`
|
||||||
- `impl {PartialEq, Eq} for zcash_primitives::sapling::note::Rseed`
|
- `impl {PartialEq, Eq} for zcash_primitives::sapling::note::Rseed`
|
||||||
- `impl From<TxId> for [u8; 32]`
|
- `impl From<TxId> for [u8; 32]`
|
||||||
|
|
|
@ -331,6 +331,20 @@ impl TryFrom<sapling::value::NoteValue> for NonNegativeAmount {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<NonNegativeAmount> for orchard::NoteValue {
|
||||||
|
fn from(n: NonNegativeAmount) -> Self {
|
||||||
|
orchard::NoteValue::from_raw(n.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<orchard::NoteValue> for NonNegativeAmount {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: orchard::NoteValue) -> Result<Self, Self::Error> {
|
||||||
|
Self::from_u64(value.inner())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<Amount> for NonNegativeAmount {
|
impl TryFrom<Amount> for NonNegativeAmount {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue