zcash_client_backend: Allow proposer to specify fallback change pool.
In the event that the pool to which change should be sent cannot automatically be determined based upon the inputs and outputs of a transaction, it is up to the caller to specify where change should be sent.
This commit is contained in:
parent
daf88a12e5
commit
4e3d99f1d0
|
@ -154,8 +154,9 @@ and this library adheres to Rust's notion of
|
||||||
the database identifiers for its contained notes by universally quantifying
|
the database identifiers for its contained notes by universally quantifying
|
||||||
the `NoteRef` type parameter.
|
the `NoteRef` type parameter.
|
||||||
- It returns a `NonEmpty<TxId>` instead of a single `TxId` value.
|
- It returns a `NonEmpty<TxId>` instead of a single `TxId` value.
|
||||||
- `wallet::create_spend_to_address` now takes an additional `change_memo`
|
- `wallet::create_spend_to_address` now takes additional `change_memo` and
|
||||||
argument. It also returns its result as a `NonEmpty<TxId>` instead of a
|
`fallback_change_pool` arguments. It also returns its result as a
|
||||||
|
`NonEmpty<TxId>` instead of a
|
||||||
single `TxId`.
|
single `TxId`.
|
||||||
- `wallet::spend` returns its result as a `NonEmpty<TxId>` instead of a
|
- `wallet::spend` returns its result as a `NonEmpty<TxId>` instead of a
|
||||||
single `TxId`.
|
single `TxId`.
|
||||||
|
@ -226,9 +227,10 @@ and this library adheres to Rust's notion of
|
||||||
now also provides the output pool to which change should be sent and an
|
now also provides the output pool to which change should be sent and an
|
||||||
optional memo to be associated with the change output.
|
optional memo to be associated with the change output.
|
||||||
- `ChangeError` has a new `BundleError` variant.
|
- `ChangeError` has a new `BundleError` variant.
|
||||||
- `fixed::SingleOutputChangeStrategy::new` and
|
- `fixed::SingleOutputChangeStrategy::new`,
|
||||||
`zip317::SingleOutputChangeStrategy::new` each now accept an additional
|
`zip317::SingleOutputChangeStrategy::new`, and
|
||||||
`change_memo` argument.
|
`standard::SingleOutputChangeStrategy::new` each now accept additional
|
||||||
|
`change_memo` and `fallback_change_pool` arguments.
|
||||||
- `zcash_client_backend::wallet`:
|
- `zcash_client_backend::wallet`:
|
||||||
- The fields of `ReceivedSaplingNote` are now private. Use
|
- The fields of `ReceivedSaplingNote` are now private. Use
|
||||||
`ReceivedSaplingNote::from_parts` for construction instead. Accessor methods
|
`ReceivedSaplingNote::from_parts` for construction instead. Accessor methods
|
||||||
|
|
|
@ -27,8 +27,7 @@ use crate::{
|
||||||
decrypt_transaction,
|
decrypt_transaction,
|
||||||
fees::{self, DustOutputPolicy},
|
fees::{self, DustOutputPolicy},
|
||||||
keys::UnifiedSpendingKey,
|
keys::UnifiedSpendingKey,
|
||||||
proposal::ProposalError,
|
proposal::{self, Proposal, ProposalError},
|
||||||
proposal::{self, Proposal},
|
|
||||||
wallet::{Note, OvkPolicy, Recipient},
|
wallet::{Note, OvkPolicy, Recipient},
|
||||||
zip321::{self, Payment},
|
zip321::{self, Payment},
|
||||||
PoolType, ShieldedProtocol,
|
PoolType, ShieldedProtocol,
|
||||||
|
@ -210,6 +209,7 @@ pub fn create_spend_to_address<DbT, ParamsT>(
|
||||||
ovk_policy: OvkPolicy,
|
ovk_policy: OvkPolicy,
|
||||||
min_confirmations: NonZeroU32,
|
min_confirmations: NonZeroU32,
|
||||||
change_memo: Option<MemoBytes>,
|
change_memo: Option<MemoBytes>,
|
||||||
|
fallback_change_pool: ShieldedProtocol,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
NonEmpty<TxId>,
|
NonEmpty<TxId>,
|
||||||
Error<
|
Error<
|
||||||
|
@ -240,6 +240,7 @@ where
|
||||||
amount,
|
amount,
|
||||||
memo,
|
memo,
|
||||||
change_memo,
|
change_memo,
|
||||||
|
fallback_change_pool,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
create_proposed_transactions(
|
create_proposed_transactions(
|
||||||
|
@ -423,6 +424,8 @@ where
|
||||||
/// * `amount`: The amount to send.
|
/// * `amount`: The amount to send.
|
||||||
/// * `memo`: A memo to be included in the output to the recipient.
|
/// * `memo`: A memo to be included in the output to the recipient.
|
||||||
/// * `change_memo`: A memo to be included in any change output that is created.
|
/// * `change_memo`: A memo to be included in any change output that is created.
|
||||||
|
/// * `fallback_change_pool`: The shielded pool to which change should be sent if
|
||||||
|
/// automatic change pool determination fails.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
pub fn propose_standard_transfer_to_address<DbT, ParamsT, CommitmentTreeErrT>(
|
pub fn propose_standard_transfer_to_address<DbT, ParamsT, CommitmentTreeErrT>(
|
||||||
|
@ -435,6 +438,7 @@ pub fn propose_standard_transfer_to_address<DbT, ParamsT, CommitmentTreeErrT>(
|
||||||
amount: NonNegativeAmount,
|
amount: NonNegativeAmount,
|
||||||
memo: Option<MemoBytes>,
|
memo: Option<MemoBytes>,
|
||||||
change_memo: Option<MemoBytes>,
|
change_memo: Option<MemoBytes>,
|
||||||
|
fallback_change_pool: ShieldedProtocol,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
Proposal<StandardFeeRule, DbT::NoteRef>,
|
Proposal<StandardFeeRule, DbT::NoteRef>,
|
||||||
Error<
|
Error<
|
||||||
|
@ -461,7 +465,11 @@ where
|
||||||
"It should not be possible for this to violate ZIP 321 request construction invariants.",
|
"It should not be possible for this to violate ZIP 321 request construction invariants.",
|
||||||
);
|
);
|
||||||
|
|
||||||
let change_strategy = fees::standard::SingleOutputChangeStrategy::new(fee_rule, change_memo);
|
let change_strategy = fees::standard::SingleOutputChangeStrategy::new(
|
||||||
|
fee_rule,
|
||||||
|
change_memo,
|
||||||
|
fallback_change_pool,
|
||||||
|
);
|
||||||
let input_selector =
|
let input_selector =
|
||||||
GreedyInputSelector::<DbT, _>::new(change_strategy, DustOutputPolicy::default());
|
GreedyInputSelector::<DbT, _>::new(change_strategy, DustOutputPolicy::default());
|
||||||
|
|
||||||
|
@ -644,8 +652,10 @@ where
|
||||||
.map_err(Error::DataSource)?
|
.map_err(Error::DataSource)?
|
||||||
.ok_or(Error::KeyNotRecognized)?;
|
.ok_or(Error::KeyNotRecognized)?;
|
||||||
|
|
||||||
let (sapling_anchor, sapling_inputs) = proposal_step.shielded_inputs().map_or_else(
|
let (sapling_anchor, sapling_inputs) =
|
||||||
|| Ok((sapling::Anchor::empty_tree(), vec![])),
|
if proposal_step.involves(PoolType::Shielded(ShieldedProtocol::Sapling)) {
|
||||||
|
proposal_step.shielded_inputs().map_or_else(
|
||||||
|
|| Ok((Some(sapling::Anchor::empty_tree()), vec![])),
|
||||||
|inputs| {
|
|inputs| {
|
||||||
wallet_db.with_sapling_tree_mut::<_, _, Error<_, _, _, _>>(|sapling_tree| {
|
wallet_db.with_sapling_tree_mut::<_, _, Error<_, _, _, _>>(|sapling_tree| {
|
||||||
let anchor = sapling_tree
|
let anchor = sapling_tree
|
||||||
|
@ -676,13 +686,18 @@ where
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, Error<_, _, _, _>>>()?;
|
.collect::<Result<Vec<_>, Error<_, _, _, _>>>()?;
|
||||||
|
|
||||||
Ok((anchor, sapling_inputs))
|
Ok((Some(anchor), sapling_inputs))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
)?;
|
)?
|
||||||
|
} else {
|
||||||
|
(None, vec![])
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
let (orchard_anchor, orchard_inputs) = proposal_step.shielded_inputs().map_or_else(
|
let (orchard_anchor, orchard_inputs) =
|
||||||
|
if proposal_step.involves(PoolType::Shielded(ShieldedProtocol::Orchard)) {
|
||||||
|
proposal_step.shielded_inputs().map_or_else(
|
||||||
|| Ok((Some(orchard::Anchor::empty_tree()), vec![])),
|
|| Ok((Some(orchard::Anchor::empty_tree()), vec![])),
|
||||||
|inputs| {
|
|inputs| {
|
||||||
wallet_db.with_orchard_tree_mut::<_, _, Error<_, _, _, _>>(|orchard_tree| {
|
wallet_db.with_orchard_tree_mut::<_, _, Error<_, _, _, _>>(|orchard_tree| {
|
||||||
|
@ -710,7 +725,10 @@ where
|
||||||
Ok((Some(anchor), orchard_inputs))
|
Ok((Some(anchor), orchard_inputs))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
)?;
|
)?
|
||||||
|
} else {
|
||||||
|
(None, vec![])
|
||||||
|
};
|
||||||
#[cfg(not(feature = "orchard"))]
|
#[cfg(not(feature = "orchard"))]
|
||||||
let orchard_anchor = None;
|
let orchard_anchor = None;
|
||||||
|
|
||||||
|
@ -720,7 +738,7 @@ where
|
||||||
params.clone(),
|
params.clone(),
|
||||||
min_target_height,
|
min_target_height,
|
||||||
BuildConfig::Standard {
|
BuildConfig::Standard {
|
||||||
sapling_anchor: Some(sapling_anchor),
|
sapling_anchor,
|
||||||
orchard_anchor,
|
orchard_anchor,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -984,7 +1002,6 @@ where
|
||||||
Some(memo),
|
Some(memo),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
#[cfg(zcash_unstable = "orchard")]
|
|
||||||
ShieldedProtocol::Orchard => {
|
ShieldedProtocol::Orchard => {
|
||||||
#[cfg(not(feature = "orchard"))]
|
#[cfg(not(feature = "orchard"))]
|
||||||
return Err(Error::UnsupportedChangeType(PoolType::Shielded(
|
return Err(Error::UnsupportedChangeType(PoolType::Shielded(
|
||||||
|
|
|
@ -455,9 +455,9 @@ where
|
||||||
Err(other) => return Err(other.into()),
|
Err(other) => return Err(other.into()),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(zcash_unstable = "orchard"))]
|
#[cfg(not(feature = "orchard"))]
|
||||||
let selectable_pools = &[ShieldedProtocol::Sapling];
|
let selectable_pools = &[ShieldedProtocol::Sapling];
|
||||||
#[cfg(zcash_unstable = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
let selectable_pools = &[ShieldedProtocol::Sapling, ShieldedProtocol::Orchard];
|
let selectable_pools = &[ShieldedProtocol::Sapling, ShieldedProtocol::Orchard];
|
||||||
|
|
||||||
shielded_inputs = wallet_db
|
shielded_inputs = wallet_db
|
||||||
|
|
|
@ -34,6 +34,7 @@ pub(crate) fn single_change_output_balance<
|
||||||
dust_output_policy: &DustOutputPolicy,
|
dust_output_policy: &DustOutputPolicy,
|
||||||
default_dust_threshold: NonNegativeAmount,
|
default_dust_threshold: NonNegativeAmount,
|
||||||
change_memo: Option<MemoBytes>,
|
change_memo: Option<MemoBytes>,
|
||||||
|
_fallback_change_pool: ShieldedProtocol,
|
||||||
) -> Result<TransactionBalance, ChangeError<E, NoteRefT>>
|
) -> Result<TransactionBalance, ChangeError<E, NoteRefT>>
|
||||||
where
|
where
|
||||||
E: From<F::Error> + From<BalanceError>,
|
E: From<F::Error> + From<BalanceError>,
|
||||||
|
@ -86,7 +87,6 @@ where
|
||||||
|
|
||||||
// TODO: implement a less naive strategy for selecting the pool to which change will be sent.
|
// TODO: implement a less naive strategy for selecting the pool to which change will be sent.
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
#[allow(clippy::if_same_then_else)]
|
|
||||||
let (change_pool, sapling_change, orchard_change) =
|
let (change_pool, sapling_change, orchard_change) =
|
||||||
if orchard_in.is_positive() || orchard_out.is_positive() {
|
if orchard_in.is_positive() || orchard_out.is_positive() {
|
||||||
// Send change to Orchard if we're spending any Orchard inputs or creating any Orchard outputs
|
// Send change to Orchard if we're spending any Orchard inputs or creating any Orchard outputs
|
||||||
|
@ -96,8 +96,12 @@ where
|
||||||
// Sapling outputs, so that we avoid pool-crossing.
|
// Sapling outputs, so that we avoid pool-crossing.
|
||||||
(ShieldedProtocol::Sapling, 1, 0)
|
(ShieldedProtocol::Sapling, 1, 0)
|
||||||
} else {
|
} else {
|
||||||
// For all other transactions, send change to Orchard.
|
// This is a fully-transparent transaction, so the caller gets to decide
|
||||||
(ShieldedProtocol::Orchard, 0, 1)
|
// where to shield change.
|
||||||
|
match _fallback_change_pool {
|
||||||
|
ShieldedProtocol::Orchard => (_fallback_change_pool, 0, 1),
|
||||||
|
ShieldedProtocol::Sapling => (_fallback_change_pool, 1, 0),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
#[cfg(not(feature = "orchard"))]
|
#[cfg(not(feature = "orchard"))]
|
||||||
let (change_pool, sapling_change) = (ShieldedProtocol::Sapling, 1);
|
let (change_pool, sapling_change) = (ShieldedProtocol::Sapling, 1);
|
||||||
|
|
|
@ -9,6 +9,8 @@ use zcash_primitives::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::ShieldedProtocol;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
common::single_change_output_balance, sapling as sapling_fees, ChangeError, ChangeStrategy,
|
common::single_change_output_balance, sapling as sapling_fees, ChangeError, ChangeStrategy,
|
||||||
DustOutputPolicy, TransactionBalance,
|
DustOutputPolicy, TransactionBalance,
|
||||||
|
@ -22,15 +24,21 @@ use super::orchard as orchard_fees;
|
||||||
pub struct SingleOutputChangeStrategy {
|
pub struct SingleOutputChangeStrategy {
|
||||||
fee_rule: FixedFeeRule,
|
fee_rule: FixedFeeRule,
|
||||||
change_memo: Option<MemoBytes>,
|
change_memo: Option<MemoBytes>,
|
||||||
|
fallback_change_pool: ShieldedProtocol,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SingleOutputChangeStrategy {
|
impl SingleOutputChangeStrategy {
|
||||||
/// Constructs a new [`SingleOutputChangeStrategy`] with the specified fee rule
|
/// Constructs a new [`SingleOutputChangeStrategy`] with the specified fee rule
|
||||||
/// and change memo.
|
/// and change memo.
|
||||||
pub fn new(fee_rule: FixedFeeRule, change_memo: Option<MemoBytes>) -> Self {
|
pub fn new(
|
||||||
|
fee_rule: FixedFeeRule,
|
||||||
|
change_memo: Option<MemoBytes>,
|
||||||
|
fallback_change_pool: ShieldedProtocol,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
fee_rule,
|
fee_rule,
|
||||||
change_memo,
|
change_memo,
|
||||||
|
fallback_change_pool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +73,7 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
dust_output_policy,
|
dust_output_policy,
|
||||||
self.fee_rule().fixed_fee(),
|
self.fee_rule().fixed_fee(),
|
||||||
self.change_memo.clone(),
|
self.change_memo.clone(),
|
||||||
|
self.fallback_change_pool,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,13 +98,15 @@ mod tests {
|
||||||
tests::{TestSaplingInput, TestTransparentInput},
|
tests::{TestSaplingInput, TestTransparentInput},
|
||||||
ChangeError, ChangeStrategy, ChangeValue, DustOutputPolicy,
|
ChangeError, ChangeStrategy, ChangeValue, DustOutputPolicy,
|
||||||
},
|
},
|
||||||
|
ShieldedProtocol,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn change_without_dust() {
|
fn change_without_dust() {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
let fee_rule = FixedFeeRule::standard();
|
let fee_rule = FixedFeeRule::standard();
|
||||||
let change_strategy = SingleOutputChangeStrategy::new(fee_rule, None);
|
let change_strategy =
|
||||||
|
SingleOutputChangeStrategy::new(fee_rule, None, ShieldedProtocol::Sapling);
|
||||||
|
|
||||||
// spend a single Sapling note that is sufficient to pay the fee
|
// spend a single Sapling note that is sufficient to pay the fee
|
||||||
let result = change_strategy.compute_balance(
|
let result = change_strategy.compute_balance(
|
||||||
|
@ -136,7 +147,8 @@ mod tests {
|
||||||
fn dust_change() {
|
fn dust_change() {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
let fee_rule = FixedFeeRule::standard();
|
let fee_rule = FixedFeeRule::standard();
|
||||||
let change_strategy = SingleOutputChangeStrategy::new(fee_rule, None);
|
let change_strategy =
|
||||||
|
SingleOutputChangeStrategy::new(fee_rule, None, ShieldedProtocol::Sapling);
|
||||||
|
|
||||||
// spend a single Sapling note that is sufficient to pay the fee
|
// spend a single Sapling note that is sufficient to pay the fee
|
||||||
let result = change_strategy.compute_balance(
|
let result = change_strategy.compute_balance(
|
||||||
|
|
|
@ -14,6 +14,8 @@ use zcash_primitives::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::ShieldedProtocol;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
fixed, sapling as sapling_fees, zip317, ChangeError, ChangeStrategy, DustOutputPolicy,
|
fixed, sapling as sapling_fees, zip317, ChangeError, ChangeStrategy, DustOutputPolicy,
|
||||||
TransactionBalance,
|
TransactionBalance,
|
||||||
|
@ -27,15 +29,21 @@ use super::orchard as orchard_fees;
|
||||||
pub struct SingleOutputChangeStrategy {
|
pub struct SingleOutputChangeStrategy {
|
||||||
fee_rule: StandardFeeRule,
|
fee_rule: StandardFeeRule,
|
||||||
change_memo: Option<MemoBytes>,
|
change_memo: Option<MemoBytes>,
|
||||||
|
fallback_change_pool: ShieldedProtocol,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SingleOutputChangeStrategy {
|
impl SingleOutputChangeStrategy {
|
||||||
/// Constructs a new [`SingleOutputChangeStrategy`] with the specified ZIP 317
|
/// Constructs a new [`SingleOutputChangeStrategy`] with the specified ZIP 317
|
||||||
/// fee parameters.
|
/// fee parameters.
|
||||||
pub fn new(fee_rule: StandardFeeRule, change_memo: Option<MemoBytes>) -> Self {
|
pub fn new(
|
||||||
|
fee_rule: StandardFeeRule,
|
||||||
|
change_memo: Option<MemoBytes>,
|
||||||
|
fallback_change_pool: ShieldedProtocol,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
fee_rule,
|
fee_rule,
|
||||||
change_memo,
|
change_memo,
|
||||||
|
fallback_change_pool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,6 +71,7 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
StandardFeeRule::PreZip313 => fixed::SingleOutputChangeStrategy::new(
|
StandardFeeRule::PreZip313 => fixed::SingleOutputChangeStrategy::new(
|
||||||
FixedFeeRule::non_standard(NonNegativeAmount::const_from_u64(10000)),
|
FixedFeeRule::non_standard(NonNegativeAmount::const_from_u64(10000)),
|
||||||
self.change_memo.clone(),
|
self.change_memo.clone(),
|
||||||
|
self.fallback_change_pool,
|
||||||
)
|
)
|
||||||
.compute_balance(
|
.compute_balance(
|
||||||
params,
|
params,
|
||||||
|
@ -78,6 +87,7 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
StandardFeeRule::Zip313 => fixed::SingleOutputChangeStrategy::new(
|
StandardFeeRule::Zip313 => fixed::SingleOutputChangeStrategy::new(
|
||||||
FixedFeeRule::non_standard(NonNegativeAmount::const_from_u64(1000)),
|
FixedFeeRule::non_standard(NonNegativeAmount::const_from_u64(1000)),
|
||||||
self.change_memo.clone(),
|
self.change_memo.clone(),
|
||||||
|
self.fallback_change_pool,
|
||||||
)
|
)
|
||||||
.compute_balance(
|
.compute_balance(
|
||||||
params,
|
params,
|
||||||
|
@ -93,6 +103,7 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
StandardFeeRule::Zip317 => zip317::SingleOutputChangeStrategy::new(
|
StandardFeeRule::Zip317 => zip317::SingleOutputChangeStrategy::new(
|
||||||
Zip317FeeRule::standard(),
|
Zip317FeeRule::standard(),
|
||||||
self.change_memo.clone(),
|
self.change_memo.clone(),
|
||||||
|
self.fallback_change_pool,
|
||||||
)
|
)
|
||||||
.compute_balance(
|
.compute_balance(
|
||||||
params,
|
params,
|
||||||
|
|
|
@ -13,6 +13,8 @@ use zcash_primitives::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::ShieldedProtocol;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
common::single_change_output_balance, sapling as sapling_fees, ChangeError, ChangeStrategy,
|
common::single_change_output_balance, sapling as sapling_fees, ChangeError, ChangeStrategy,
|
||||||
DustOutputPolicy, TransactionBalance,
|
DustOutputPolicy, TransactionBalance,
|
||||||
|
@ -26,15 +28,21 @@ use super::orchard as orchard_fees;
|
||||||
pub struct SingleOutputChangeStrategy {
|
pub struct SingleOutputChangeStrategy {
|
||||||
fee_rule: Zip317FeeRule,
|
fee_rule: Zip317FeeRule,
|
||||||
change_memo: Option<MemoBytes>,
|
change_memo: Option<MemoBytes>,
|
||||||
|
fallback_change_pool: ShieldedProtocol,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SingleOutputChangeStrategy {
|
impl SingleOutputChangeStrategy {
|
||||||
/// Constructs a new [`SingleOutputChangeStrategy`] with the specified ZIP 317
|
/// Constructs a new [`SingleOutputChangeStrategy`] with the specified ZIP 317
|
||||||
/// fee parameters and change memo.
|
/// fee parameters and change memo.
|
||||||
pub fn new(fee_rule: Zip317FeeRule, change_memo: Option<MemoBytes>) -> Self {
|
pub fn new(
|
||||||
|
fee_rule: Zip317FeeRule,
|
||||||
|
change_memo: Option<MemoBytes>,
|
||||||
|
fallback_change_pool: ShieldedProtocol,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
fee_rule,
|
fee_rule,
|
||||||
change_memo,
|
change_memo,
|
||||||
|
fallback_change_pool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,6 +153,7 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
dust_output_policy,
|
dust_output_policy,
|
||||||
self.fee_rule.marginal_fee(),
|
self.fee_rule.marginal_fee(),
|
||||||
self.change_memo.clone(),
|
self.change_memo.clone(),
|
||||||
|
self.fallback_change_pool,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,11 +179,16 @@ mod tests {
|
||||||
tests::{TestSaplingInput, TestTransparentInput},
|
tests::{TestSaplingInput, TestTransparentInput},
|
||||||
ChangeError, ChangeStrategy, ChangeValue, DustOutputPolicy,
|
ChangeError, ChangeStrategy, ChangeValue, DustOutputPolicy,
|
||||||
},
|
},
|
||||||
|
ShieldedProtocol,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn change_without_dust() {
|
fn change_without_dust() {
|
||||||
let change_strategy = SingleOutputChangeStrategy::new(Zip317FeeRule::standard(), None);
|
let change_strategy = SingleOutputChangeStrategy::new(
|
||||||
|
Zip317FeeRule::standard(),
|
||||||
|
None,
|
||||||
|
ShieldedProtocol::Sapling,
|
||||||
|
);
|
||||||
|
|
||||||
// spend a single Sapling note that is sufficient to pay the fee
|
// spend a single Sapling note that is sufficient to pay the fee
|
||||||
let result = change_strategy.compute_balance(
|
let result = change_strategy.compute_balance(
|
||||||
|
@ -213,7 +227,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn change_with_transparent_payments() {
|
fn change_with_transparent_payments() {
|
||||||
let change_strategy = SingleOutputChangeStrategy::new(Zip317FeeRule::standard(), None);
|
let change_strategy = SingleOutputChangeStrategy::new(
|
||||||
|
Zip317FeeRule::standard(),
|
||||||
|
None,
|
||||||
|
ShieldedProtocol::Sapling,
|
||||||
|
);
|
||||||
|
|
||||||
// spend a single Sapling note that is sufficient to pay the fee
|
// spend a single Sapling note that is sufficient to pay the fee
|
||||||
let result = change_strategy.compute_balance(
|
let result = change_strategy.compute_balance(
|
||||||
|
@ -252,7 +270,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn change_with_allowable_dust() {
|
fn change_with_allowable_dust() {
|
||||||
let change_strategy = SingleOutputChangeStrategy::new(Zip317FeeRule::standard(), None);
|
let change_strategy = SingleOutputChangeStrategy::new(
|
||||||
|
Zip317FeeRule::standard(),
|
||||||
|
None,
|
||||||
|
ShieldedProtocol::Sapling,
|
||||||
|
);
|
||||||
|
|
||||||
// spend a single Sapling note that is sufficient to pay the fee
|
// spend a single Sapling note that is sufficient to pay the fee
|
||||||
let result = change_strategy.compute_balance(
|
let result = change_strategy.compute_balance(
|
||||||
|
@ -296,7 +318,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn change_with_disallowed_dust() {
|
fn change_with_disallowed_dust() {
|
||||||
let change_strategy = SingleOutputChangeStrategy::new(Zip317FeeRule::standard(), None);
|
let change_strategy = SingleOutputChangeStrategy::new(
|
||||||
|
Zip317FeeRule::standard(),
|
||||||
|
None,
|
||||||
|
ShieldedProtocol::Sapling,
|
||||||
|
);
|
||||||
|
|
||||||
// spend a single Sapling note that is sufficient to pay the fee
|
// spend a single Sapling note that is sufficient to pay the fee
|
||||||
let result = change_strategy.compute_balance(
|
let result = change_strategy.compute_balance(
|
||||||
|
|
|
@ -97,7 +97,6 @@ pub enum ShieldedProtocol {
|
||||||
/// The Sapling protocol
|
/// The Sapling protocol
|
||||||
Sapling,
|
Sapling,
|
||||||
/// The Orchard protocol
|
/// The Orchard protocol
|
||||||
#[cfg(zcash_unstable = "orchard")]
|
|
||||||
Orchard,
|
Orchard,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +117,6 @@ impl PoolType {
|
||||||
Address::Unified(ua) => match self {
|
Address::Unified(ua) => match self {
|
||||||
PoolType::Transparent => ua.transparent().is_some(),
|
PoolType::Transparent => ua.transparent().is_some(),
|
||||||
PoolType::Shielded(ShieldedProtocol::Sapling) => ua.sapling().is_some(),
|
PoolType::Shielded(ShieldedProtocol::Sapling) => ua.sapling().is_some(),
|
||||||
#[cfg(zcash_unstable = "orchard")]
|
|
||||||
PoolType::Shielded(ShieldedProtocol::Orchard) => {
|
PoolType::Shielded(ShieldedProtocol::Orchard) => {
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
return ua.orchard().is_some();
|
return ua.orchard().is_some();
|
||||||
|
@ -136,7 +134,6 @@ impl fmt::Display for PoolType {
|
||||||
match self {
|
match self {
|
||||||
PoolType::Transparent => f.write_str("Transparent"),
|
PoolType::Transparent => f.write_str("Transparent"),
|
||||||
PoolType::Shielded(ShieldedProtocol::Sapling) => f.write_str("Sapling"),
|
PoolType::Shielded(ShieldedProtocol::Sapling) => f.write_str("Sapling"),
|
||||||
#[cfg(zcash_unstable = "orchard")]
|
|
||||||
PoolType::Shielded(ShieldedProtocol::Orchard) => f.write_str("Orchard"),
|
PoolType::Shielded(ShieldedProtocol::Orchard) => f.write_str("Orchard"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -490,6 +490,59 @@ impl<NoteRef> Step<NoteRef> {
|
||||||
pub fn is_shielding(&self) -> bool {
|
pub fn is_shielding(&self) -> bool {
|
||||||
self.is_shielding
|
self.is_shielding
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether or not this proposal requires interaction with the specified pool
|
||||||
|
pub fn involves(&self, pool_type: PoolType) -> bool {
|
||||||
|
match pool_type {
|
||||||
|
PoolType::Transparent => {
|
||||||
|
self.is_shielding
|
||||||
|
|| !self.transparent_inputs.is_empty()
|
||||||
|
|| self
|
||||||
|
.payment_pools()
|
||||||
|
.values()
|
||||||
|
.any(|pool| matches!(pool, PoolType::Transparent))
|
||||||
|
}
|
||||||
|
PoolType::Shielded(ShieldedProtocol::Sapling) => {
|
||||||
|
let sapling_in = self.shielded_inputs.iter().any(|s_in| {
|
||||||
|
s_in.notes()
|
||||||
|
.iter()
|
||||||
|
.any(|note| matches!(note.note(), Note::Sapling(_)))
|
||||||
|
});
|
||||||
|
let sapling_out = self
|
||||||
|
.payment_pools()
|
||||||
|
.values()
|
||||||
|
.any(|pool| matches!(pool, PoolType::Shielded(ShieldedProtocol::Sapling)));
|
||||||
|
let sapling_change = self
|
||||||
|
.balance
|
||||||
|
.proposed_change()
|
||||||
|
.iter()
|
||||||
|
.any(|c| c.output_pool() == ShieldedProtocol::Sapling);
|
||||||
|
|
||||||
|
sapling_in || sapling_out || sapling_change
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "orchard"))]
|
||||||
|
PoolType::Shielded(ShieldedProtocol::Orchard) => false,
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
PoolType::Shielded(ShieldedProtocol::Orchard) => {
|
||||||
|
let orchard_in = self.shielded_inputs.iter().any(|s_in| {
|
||||||
|
s_in.notes()
|
||||||
|
.iter()
|
||||||
|
.any(|note| matches!(note.note(), Note::Orchard(_)))
|
||||||
|
});
|
||||||
|
let orchard_out = self
|
||||||
|
.payment_pools()
|
||||||
|
.values()
|
||||||
|
.any(|pool| matches!(pool, PoolType::Shielded(ShieldedProtocol::Orchard)));
|
||||||
|
let orchard_change = self
|
||||||
|
.balance
|
||||||
|
.proposed_change()
|
||||||
|
.iter()
|
||||||
|
.any(|c| c.output_pool() == ShieldedProtocol::Orchard);
|
||||||
|
|
||||||
|
orchard_in || orchard_out || orchard_change
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<NoteRef> Debug for Step<NoteRef> {
|
impl<NoteRef> Debug for Step<NoteRef> {
|
||||||
|
@ -501,6 +554,7 @@ impl<NoteRef> Debug for Step<NoteRef> {
|
||||||
"shielded_inputs",
|
"shielded_inputs",
|
||||||
&self.shielded_inputs().map(|i| i.notes.len()),
|
&self.shielded_inputs().map(|i| i.notes.len()),
|
||||||
)
|
)
|
||||||
|
.field("prior_step_inputs", &self.prior_step_inputs)
|
||||||
.field(
|
.field(
|
||||||
"anchor_height",
|
"anchor_height",
|
||||||
&self.shielded_inputs().map(|i| i.anchor_height),
|
&self.shielded_inputs().map(|i| i.anchor_height),
|
||||||
|
|
|
@ -319,7 +319,6 @@ fn pool_type<T>(pool_id: i32) -> Result<PoolType, ProposalDecodingError<T>> {
|
||||||
match proposal::ValuePool::try_from(pool_id) {
|
match proposal::ValuePool::try_from(pool_id) {
|
||||||
Ok(proposal::ValuePool::Transparent) => Ok(PoolType::Transparent),
|
Ok(proposal::ValuePool::Transparent) => Ok(PoolType::Transparent),
|
||||||
Ok(proposal::ValuePool::Sapling) => Ok(PoolType::Shielded(ShieldedProtocol::Sapling)),
|
Ok(proposal::ValuePool::Sapling) => Ok(PoolType::Shielded(ShieldedProtocol::Sapling)),
|
||||||
#[cfg(zcash_unstable = "orchard")]
|
|
||||||
Ok(proposal::ValuePool::Orchard) => Ok(PoolType::Shielded(ShieldedProtocol::Orchard)),
|
Ok(proposal::ValuePool::Orchard) => Ok(PoolType::Shielded(ShieldedProtocol::Orchard)),
|
||||||
_ => Err(ProposalDecodingError::ValuePoolNotSupported(pool_id)),
|
_ => Err(ProposalDecodingError::ValuePoolNotSupported(pool_id)),
|
||||||
}
|
}
|
||||||
|
@ -354,7 +353,6 @@ impl From<ShieldedProtocol> for proposal::ValuePool {
|
||||||
fn from(value: ShieldedProtocol) -> Self {
|
fn from(value: ShieldedProtocol) -> Self {
|
||||||
match value {
|
match value {
|
||||||
ShieldedProtocol::Sapling => proposal::ValuePool::Sapling,
|
ShieldedProtocol::Sapling => proposal::ValuePool::Sapling,
|
||||||
#[cfg(zcash_unstable = "orchard")]
|
|
||||||
ShieldedProtocol::Orchard => proposal::ValuePool::Orchard,
|
ShieldedProtocol::Orchard => proposal::ValuePool::Orchard,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -340,6 +340,7 @@ mod tests {
|
||||||
scanning::ScanError,
|
scanning::ScanError,
|
||||||
wallet::OvkPolicy,
|
wallet::OvkPolicy,
|
||||||
zip321::{Payment, TransactionRequest},
|
zip321::{Payment, TransactionRequest},
|
||||||
|
ShieldedProtocol,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -529,7 +530,7 @@ mod tests {
|
||||||
}])
|
}])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let input_selector = GreedyInputSelector::new(
|
let input_selector = GreedyInputSelector::new(
|
||||||
SingleOutputChangeStrategy::new(FeeRule::standard(), None),
|
SingleOutputChangeStrategy::new(FeeRule::standard(), None, ShieldedProtocol::Sapling),
|
||||||
DustOutputPolicy::default(),
|
DustOutputPolicy::default(),
|
||||||
);
|
);
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
|
|
|
@ -22,7 +22,6 @@ use sapling::{
|
||||||
zip32::DiversifiableFullViewingKey,
|
zip32::DiversifiableFullViewingKey,
|
||||||
Note, Nullifier, PaymentAddress,
|
Note, Nullifier, PaymentAddress,
|
||||||
};
|
};
|
||||||
use zcash_client_backend::fees::{standard, DustOutputPolicy};
|
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
use zcash_client_backend::{
|
use zcash_client_backend::{
|
||||||
address::Address,
|
address::Address,
|
||||||
|
@ -45,6 +44,10 @@ use zcash_client_backend::{
|
||||||
wallet::OvkPolicy,
|
wallet::OvkPolicy,
|
||||||
zip321,
|
zip321,
|
||||||
};
|
};
|
||||||
|
use zcash_client_backend::{
|
||||||
|
fees::{standard, DustOutputPolicy},
|
||||||
|
ShieldedProtocol,
|
||||||
|
};
|
||||||
use zcash_note_encryption::Domain;
|
use zcash_note_encryption::Domain;
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
block::BlockHash,
|
block::BlockHash,
|
||||||
|
@ -465,6 +468,7 @@ impl<Cache> TestState<Cache> {
|
||||||
ovk_policy,
|
ovk_policy,
|
||||||
min_confirmations,
|
min_confirmations,
|
||||||
change_memo,
|
change_memo,
|
||||||
|
ShieldedProtocol::Sapling,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -567,6 +571,7 @@ impl<Cache> TestState<Cache> {
|
||||||
amount,
|
amount,
|
||||||
memo,
|
memo,
|
||||||
change_memo,
|
change_memo,
|
||||||
|
ShieldedProtocol::Sapling,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Ok(proposal) = &result {
|
if let Ok(proposal) = &result {
|
||||||
|
@ -1058,7 +1063,8 @@ pub(crate) fn input_selector(
|
||||||
standard::SingleOutputChangeStrategy,
|
standard::SingleOutputChangeStrategy,
|
||||||
> {
|
> {
|
||||||
let change_memo = change_memo.map(|m| MemoBytes::from(m.parse::<Memo>().unwrap()));
|
let change_memo = change_memo.map(|m| MemoBytes::from(m.parse::<Memo>().unwrap()));
|
||||||
let change_strategy = standard::SingleOutputChangeStrategy::new(fee_rule, change_memo);
|
let change_strategy =
|
||||||
|
standard::SingleOutputChangeStrategy::new(fee_rule, change_memo, ShieldedProtocol::Sapling);
|
||||||
GreedyInputSelector::new(change_strategy, DustOutputPolicy::default())
|
GreedyInputSelector::new(change_strategy, DustOutputPolicy::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -137,7 +137,6 @@ pub(crate) fn pool_code(pool_type: PoolType) -> i64 {
|
||||||
match pool_type {
|
match pool_type {
|
||||||
PoolType::Transparent => 0i64,
|
PoolType::Transparent => 0i64,
|
||||||
PoolType::Shielded(ShieldedProtocol::Sapling) => 2i64,
|
PoolType::Shielded(ShieldedProtocol::Sapling) => 2i64,
|
||||||
#[cfg(zcash_unstable = "orchard")]
|
|
||||||
PoolType::Shielded(ShieldedProtocol::Orchard) => 3i64,
|
PoolType::Shielded(ShieldedProtocol::Orchard) => 3i64,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -818,7 +817,6 @@ pub(crate) fn get_received_memo(
|
||||||
)
|
)
|
||||||
.optional()?
|
.optional()?
|
||||||
.flatten(),
|
.flatten(),
|
||||||
#[cfg(zcash_unstable = "orchard")]
|
|
||||||
_ => {
|
_ => {
|
||||||
return Err(SqliteClientError::UnsupportedPoolType(PoolType::Shielded(
|
return Err(SqliteClientError::UnsupportedPoolType(PoolType::Shielded(
|
||||||
note_id.protocol(),
|
note_id.protocol(),
|
||||||
|
@ -2232,6 +2230,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
fn transparent_balance_across_shielding() {
|
fn transparent_balance_across_shielding() {
|
||||||
|
use zcash_client_backend::ShieldedProtocol;
|
||||||
|
|
||||||
let mut st = TestBuilder::new()
|
let mut st = TestBuilder::new()
|
||||||
.with_block_cache()
|
.with_block_cache()
|
||||||
.with_test_account(AccountBirthday::from_sapling_activation)
|
.with_test_account(AccountBirthday::from_sapling_activation)
|
||||||
|
@ -2316,6 +2316,7 @@ mod tests {
|
||||||
fixed::SingleOutputChangeStrategy::new(
|
fixed::SingleOutputChangeStrategy::new(
|
||||||
FixedFeeRule::non_standard(NonNegativeAmount::ZERO),
|
FixedFeeRule::non_standard(NonNegativeAmount::ZERO),
|
||||||
None,
|
None,
|
||||||
|
ShieldedProtocol::Sapling,
|
||||||
),
|
),
|
||||||
DustOutputPolicy::default(),
|
DustOutputPolicy::default(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -579,8 +579,11 @@ pub(crate) mod tests {
|
||||||
let fee_rule = StandardFeeRule::PreZip313;
|
let fee_rule = StandardFeeRule::PreZip313;
|
||||||
|
|
||||||
let change_memo = "Test change memo".parse::<Memo>().unwrap();
|
let change_memo = "Test change memo".parse::<Memo>().unwrap();
|
||||||
let change_strategy =
|
let change_strategy = standard::SingleOutputChangeStrategy::new(
|
||||||
standard::SingleOutputChangeStrategy::new(fee_rule, Some(change_memo.clone().into()));
|
fee_rule,
|
||||||
|
Some(change_memo.clone().into()),
|
||||||
|
ShieldedProtocol::Sapling,
|
||||||
|
);
|
||||||
let input_selector =
|
let input_selector =
|
||||||
&GreedyInputSelector::new(change_strategy, DustOutputPolicy::default());
|
&GreedyInputSelector::new(change_strategy, DustOutputPolicy::default());
|
||||||
|
|
||||||
|
@ -731,7 +734,7 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
let fee_rule = StandardFeeRule::Zip317;
|
let fee_rule = StandardFeeRule::Zip317;
|
||||||
let input_selector = GreedyInputSelector::new(
|
let input_selector = GreedyInputSelector::new(
|
||||||
standard::SingleOutputChangeStrategy::new(fee_rule, None),
|
standard::SingleOutputChangeStrategy::new(fee_rule, None, ShieldedProtocol::Sapling),
|
||||||
DustOutputPolicy::default(),
|
DustOutputPolicy::default(),
|
||||||
);
|
);
|
||||||
let proposal0 = st
|
let proposal0 = st
|
||||||
|
@ -1455,7 +1458,7 @@ pub(crate) mod tests {
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
let fee_rule = FixedFeeRule::standard();
|
let fee_rule = FixedFeeRule::standard();
|
||||||
let input_selector = GreedyInputSelector::new(
|
let input_selector = GreedyInputSelector::new(
|
||||||
fixed::SingleOutputChangeStrategy::new(fee_rule, None),
|
fixed::SingleOutputChangeStrategy::new(fee_rule, None, ShieldedProtocol::Sapling),
|
||||||
DustOutputPolicy::default(),
|
DustOutputPolicy::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1657,7 +1660,7 @@ pub(crate) mod tests {
|
||||||
let fee_rule = StandardFeeRule::PreZip313;
|
let fee_rule = StandardFeeRule::PreZip313;
|
||||||
|
|
||||||
let input_selector = GreedyInputSelector::new(
|
let input_selector = GreedyInputSelector::new(
|
||||||
standard::SingleOutputChangeStrategy::new(fee_rule, None),
|
standard::SingleOutputChangeStrategy::new(fee_rule, None, ShieldedProtocol::Sapling),
|
||||||
DustOutputPolicy::default(),
|
DustOutputPolicy::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ The entries below are relative to the `zcash_client_backend` crate as of
|
||||||
- `address`
|
- `address`
|
||||||
- `encoding`
|
- `encoding`
|
||||||
- `keys`
|
- `keys`
|
||||||
- `zcash_keys::address::UnifiedAddress::unknown`:
|
- `zcash_keys::address::UnifiedAddress::{unknown, has_orchard, has_sapling, has_transparent}`:
|
||||||
- `zcash_keys::keys`:
|
- `zcash_keys::keys`:
|
||||||
- `AddressGenerationError`
|
- `AddressGenerationError`
|
||||||
- `UnifiedAddressRequest`
|
- `UnifiedAddressRequest`
|
||||||
|
|
|
@ -108,17 +108,37 @@ impl UnifiedAddress {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether this address has an Orchard receiver.
|
||||||
|
///
|
||||||
|
/// This method is available irrespective of whether the `orchard` feature flag is enabled.
|
||||||
|
pub fn has_orchard(&self) -> bool {
|
||||||
|
#[cfg(not(feature = "orchard"))]
|
||||||
|
return false;
|
||||||
|
#[cfg(feature = "orchard")]
|
||||||
|
return self.orchard.is_some();
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the Orchard receiver within this Unified Address, if any.
|
/// Returns the Orchard receiver within this Unified Address, if any.
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
pub fn orchard(&self) -> Option<&orchard::Address> {
|
pub fn orchard(&self) -> Option<&orchard::Address> {
|
||||||
self.orchard.as_ref()
|
self.orchard.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether this address has a Sapling receiver.
|
||||||
|
pub fn has_sapling(&self) -> bool {
|
||||||
|
self.sapling.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the Sapling receiver within this Unified Address, if any.
|
/// Returns the Sapling receiver within this Unified Address, if any.
|
||||||
pub fn sapling(&self) -> Option<&PaymentAddress> {
|
pub fn sapling(&self) -> Option<&PaymentAddress> {
|
||||||
self.sapling.as_ref()
|
self.sapling.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether this address has a Transparent receiver.
|
||||||
|
pub fn has_transparent(&self) -> bool {
|
||||||
|
self.transparent.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the transparent receiver within this Unified Address, if any.
|
/// Returns the transparent receiver within this Unified Address, if any.
|
||||||
pub fn transparent(&self) -> Option<&TransparentAddress> {
|
pub fn transparent(&self) -> Option<&TransparentAddress> {
|
||||||
self.transparent.as_ref()
|
self.transparent.as_ref()
|
||||||
|
|
Loading…
Reference in New Issue