zcash_client_backend: Move the `Proposal` types to a `proposal` module.

This separation is in preparation for modifying the `Proposal` type
to wrap a vector of proposal steps.
This commit is contained in:
Kris Nuttycombe 2024-02-12 12:46:34 -07:00
parent 6e0d9a9420
commit 1db3109cb4
8 changed files with 326 additions and 268 deletions

View File

@ -47,7 +47,6 @@ and this library adheres to Rust's notion of
}`
- `WalletSummary::next_sapling_subtree_index`
- `wallet::propose_standard_transfer_to_address`
- `wallet::input_selection::Proposal::{from_parts, shielded_inputs, payment_pools}`
- `wallet::input_selection::ShieldedInputs`
- `wallet::input_selection::ShieldingSelector` has been
factored out from the `InputSelector` trait to separate out transparent
@ -59,6 +58,10 @@ and this library adheres to Rust's notion of
- `ReceivedNote`
- `WalletSaplingOutput::recipient_key_scope`
- `wallet::TransparentAddressMetadata` (which replaces `zcash_keys::address::AddressMetadata`).
- `zcash_client_backend::zip321::TransactionRequest::total`
- `zcash_client_backend::zip321::parse::Param::name`
- `zcash_client_backend::proposal`
- `Proposal::{from_parts, shielded_inputs, payment_pools}`
- `zcash_client_backend::proto::`
- `PROPOSAL_SER_V1`
- `ProposalDecodingError`
@ -66,12 +69,12 @@ and this library adheres to Rust's notion of
- `impl Clone for zcash_client_backend::{
zip321::{Payment, TransactionRequest, Zip321Error, parse::Param, parse::IndexedParam},
wallet::{ReceivedSaplingNote, WalletTransparentOutput},
wallet::input_selection::{Proposal, SaplingInputs},
proposal::{Proposal, SaplingInputs},
}`
- `impl {PartialEq, Eq} for zcash_client_backend::{
zip321::{Zip321Error, parse::Param, parse::IndexedParam},
wallet::{ReceivedSaplingNote, WalletTransparentOutput},
wallet::input_selection::{Proposal, SaplingInputs},
proposal::{Proposal, SaplingInputs},
}`
- `zcash_client_backend::zip321
` `TransactionRequest::{total, from_indexed}`
@ -85,6 +88,8 @@ and this library adheres to Rust's notion of
- `ScannedBlock::{sapling_tree_size, sapling_nullifier_map, sapling_commitments}`
have been moved to `ScannedBlockSapling` and in that context are now
named `{tree_size, nullifier_map, commitments}` respectively.
- `zcash_client_backend::::wallet::input_selection::{Proposal, ShieldedInputs, ProposalError}`
have been moved to `zcash_client_backend::proposal`.
### Changed
- `zcash_client_backend::data_api`:
@ -115,6 +120,7 @@ and this library adheres to Rust's notion of
- A new variant `UnsupportedPoolType` has been added.
- A new variant `NoSupportedReceivers` has been added.
- A new variant `NoSpendingKey` has been added.
- A new variant `Proposal` has been added.
- Variant `ChildIndexOutOfRange` has been removed.
- `wallet::shield_transparent_funds` no longer takes a `memo` argument;
instead, memos to be associated with the shielded outputs should be
@ -148,6 +154,7 @@ and this library adheres to Rust's notion of
`min_confirmations` argument as `u32` instead of `NonZeroU32`.
- The `wallet::input_selection::InputSelector::DataSource`
associated type has been renamed to `InputSource`.
- `wallet::input_selection::InputSelectorError` has added variant `Proposal`
- The signature of `wallet:input_selection::InputSelector::propose_transaction`
has been altered such that it longer takes `min_confirmations` as an
argument, instead taking explicit `target_height` and `anchor_height`
@ -175,12 +182,15 @@ and this library adheres to Rust's notion of
- `wallet::create_proposed_transaction` now forces implementations to ignore
the database identifiers for its contained notes by universally quantifying
the `NoteRef` type parameter.
- Arguments to `wallet::input_selection::Proposal::from_parts` have changed.
- `wallet::input_selection::Proposal::min_anchor_height` has been removed in
favor of storing this value in `SaplingInputs`.
- `wallet::input_selection::GreedyInputSelector` now has relaxed requirements
for its `InputSource` associated type.
- `zcash_client_backend::proposal`:
- Arguments to `Proposal::from_parts` have changed.
- `Proposal::min_anchor_height` has been removed in favor of storing this
value in `SaplingInputs`.
- `Proposal::sapling_inputs` has been replaced by `Proposal::shielded_inputs`
- `zcash_client_backend::fees`:
- `ChangeStrategy::compute_balance` arguments have changed.
- `ChangeValue` is now a struct. In addition to the existing change value, it
@ -230,8 +240,7 @@ and this library adheres to Rust's notion of
### Removed
- `zcash_client_backend::wallet::ReceivedSaplingNote` has been replaced by
`zcash_client_backend::ReceivedNote`.
- `zcash_client_backend::wallet::input_selection::Proposal::sapling_inputs` has
been replaced by `Proposal::shielded_inputs`
- `zcash_client_backend::wallet::input_selection::Proposal`
- `zcash_client_backend::data_api`
- `zcash_client_backend::data_api::ScannedBlock::from_parts` has been made crate-private.
- `zcash_client_backend::data_api::ScannedBlock::into_sapling_commitments` has been

View File

@ -14,6 +14,7 @@ use zcash_primitives::{
};
use crate::data_api::wallet::input_selection::InputSelectorError;
use crate::proposal::ProposalError;
use crate::PoolType;
#[cfg(feature = "transparent-inputs")]
@ -33,6 +34,9 @@ pub enum Error<DataSourceError, CommitmentTreeError, SelectionError, FeeError> {
/// An error in note selection
NoteSelection(SelectionError),
/// An error in transaction proposal construction
Proposal(ProposalError),
/// No account could be found corresponding to a provided spending key.
KeyNotRecognized,
@ -100,6 +104,9 @@ where
Error::NoteSelection(e) => {
write!(f, "Note selection encountered the following error: {}", e)
}
Error::Proposal(e) => {
write!(f, "Input selection attempted to construct an invalid proposal: {}", e)
}
Error::KeyNotRecognized => {
write!(
f,
@ -148,6 +155,7 @@ where
Error::DataSource(e) => Some(e),
Error::CommitmentTree(e) => Some(e),
Error::NoteSelection(e) => Some(e),
Error::Proposal(e) => Some(e),
Error::Builder(e) => Some(e),
_ => None,
}
@ -171,6 +179,7 @@ impl<DE, CE, SE, FE> From<InputSelectorError<DE, SE>> for Error<DE, CE, SE, FE>
match e {
InputSelectorError::DataSource(e) => Error::DataSource(e),
InputSelectorError::Selection(e) => Error::NoteSelection(e),
InputSelectorError::Proposal(e) => Error::Proposal(e),
InputSelectorError::InsufficientFunds {
available,
required,

View File

@ -20,12 +20,13 @@ use zcash_primitives::{
use crate::{
address::Address,
data_api::{
error::Error, wallet::input_selection::Proposal, DecryptedTransaction, SentTransaction,
SentTransactionOutput, WalletCommitmentTrees, WalletRead, WalletWrite,
error::Error, DecryptedTransaction, SentTransaction, SentTransactionOutput,
WalletCommitmentTrees, WalletRead, WalletWrite,
},
decrypt_transaction,
fees::{self, DustOutputPolicy},
keys::UnifiedSpendingKey,
proposal::Proposal,
wallet::{Note, OvkPolicy, Recipient},
zip321::{self, Payment},
PoolType, ShieldedProtocol,
@ -710,7 +711,7 @@ where
} else {
builder.add_transparent_output(to, payment.amount)?;
}
transparent_output_meta.push((*to, payment.amount));
transparent_output_meta.push((to, payment.amount));
}
}
}
@ -805,7 +806,7 @@ where
SentTransactionOutput::from_parts(
output_index,
Recipient::Transparent(addr),
Recipient::Transparent(*addr),
value,
None,
None,

View File

@ -3,6 +3,7 @@
use core::marker::PhantomData;
use std::{
collections::BTreeMap,
error,
fmt::{self, Debug, Display},
};
@ -23,7 +24,8 @@ use zcash_primitives::{
use crate::{
address::{Address, UnifiedAddress},
data_api::InputSource,
fees::{sapling, ChangeError, ChangeStrategy, DustOutputPolicy, TransactionBalance},
fees::{sapling, ChangeError, ChangeStrategy, DustOutputPolicy},
proposal::{Proposal, ProposalError, ShieldedInputs},
wallet::{Note, ReceivedNote, WalletTransparentOutput},
zip321::TransactionRequest,
PoolType, ShieldedProtocol,
@ -39,11 +41,14 @@ use {std::collections::BTreeSet, zcash_primitives::transaction::components::OutP
use crate::fees::orchard as orchard_fees;
/// The type of errors that may be produced in input selection.
#[derive(Debug)]
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),
/// Input selection attempted to generate an invalid transaction proposal.
Proposal(ProposalError),
/// Insufficient funds were available to satisfy the payment request that inputs were being
/// selected to attempt to satisfy.
InsufficientFunds {
@ -68,6 +73,13 @@ impl<DE: fmt::Display, SE: fmt::Display> fmt::Display for InputSelectorError<DE,
InputSelectorError::Selection(e) => {
write!(f, "Note selection encountered the following error: {}", e)
}
InputSelectorError::Proposal(e) => {
write!(
f,
"Input selection attempted to generate an invalid proposal: {}",
e
)
}
InputSelectorError::InsufficientFunds {
available,
required,
@ -84,238 +96,21 @@ impl<DE: fmt::Display, SE: fmt::Display> fmt::Display for InputSelectorError<DE,
}
}
/// The inputs to be consumed and outputs to be produced in a proposed transaction.
#[derive(Clone, PartialEq, Eq)]
pub struct Proposal<FeeRuleT, NoteRef> {
transaction_request: TransactionRequest,
payment_pools: BTreeMap<usize, PoolType>,
transparent_inputs: Vec<WalletTransparentOutput>,
shielded_inputs: Option<ShieldedInputs<NoteRef>>,
balance: TransactionBalance,
fee_rule: FeeRuleT,
min_target_height: BlockHeight,
is_shielding: bool,
}
/// Errors that can occur in construction of a [`Proposal`].
#[derive(Debug, Clone)]
pub enum ProposalError {
/// 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.
Overflow,
/// 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.
BalanceError {
input_total: NonNegativeAmount,
output_total: NonNegativeAmount,
},
/// 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,
}
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<DE, SE> error::Error for InputSelectorError<DE, SE>
where
DE: Debug + Display + error::Error + 'static,
SE: Debug + Display + error::Error + 'static,
{
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match &self {
Self::DataSource(e) => Some(e),
Self::Selection(e) => Some(e),
Self::Proposal(e) => Some(e),
_ => None,
}
}
}
impl std::error::Error for ProposalError {}
impl<FeeRuleT, NoteRef> Proposal<FeeRuleT, NoteRef> {
/// Constructs a validated [`Proposal`] from its constituent parts.
///
/// This operation validates the proposal for balance consistency and agreement between
/// the `is_shielding` flag and the structure of the proposal.
///
/// Parameters:
/// * `transaction_request`: The ZIP 321 transaction request describing the payments
/// to be made.
/// * `payment_pools`: A map from payment index to pool type.
/// * `transparent_inputs`: The set of previous transparent outputs to be spent.
/// * `shielded_inputs`: The sets of previous shielded outputs to be spent.
/// * `balance`: The change outputs to be added the transaction and the fee to be paid.
/// * `fee_rule`: The fee rule observed by the proposed transaction.
/// * `min_target_height`: The minimum block height at which the transaction may be created.
/// * `is_shielding`: A flag that identifies whether this is a wallet-internal shielding
/// transaction.
#[allow(clippy::too_many_arguments)]
pub fn from_parts(
transaction_request: TransactionRequest,
payment_pools: BTreeMap<usize, PoolType>,
transparent_inputs: Vec<WalletTransparentOutput>,
shielded_inputs: Option<ShieldedInputs<NoteRef>>,
balance: TransactionBalance,
fee_rule: FeeRuleT,
min_target_height: BlockHeight,
is_shielding: bool,
) -> Result<Self, ProposalError> {
let transparent_input_total = transparent_inputs
.iter()
.map(|out| out.txout().value)
.fold(Ok(NonNegativeAmount::ZERO), |acc, a| {
(acc? + a).ok_or(ProposalError::Overflow)
})?;
let shielded_input_total = shielded_inputs
.iter()
.flat_map(|s_in| s_in.notes().iter())
.map(|out| out.note().value())
.fold(Some(NonNegativeAmount::ZERO), |acc, a| (acc? + a))
.ok_or(ProposalError::Overflow)?;
let input_total =
(transparent_input_total + shielded_input_total).ok_or(ProposalError::Overflow)?;
let request_total = transaction_request
.total()
.map_err(|_| ProposalError::RequestTotalInvalid)?;
let output_total = (request_total + balance.total()).ok_or(ProposalError::Overflow)?;
if is_shielding
&& (transparent_input_total == NonNegativeAmount::ZERO
|| shielded_input_total > NonNegativeAmount::ZERO
|| request_total > NonNegativeAmount::ZERO)
{
return Err(ProposalError::ShieldingInvalid);
}
if input_total == output_total {
Ok(Self {
transaction_request,
payment_pools,
transparent_inputs,
shielded_inputs,
balance,
fee_rule,
min_target_height,
is_shielding,
})
} else {
Err(ProposalError::BalanceError {
input_total,
output_total,
})
}
}
/// Returns the transaction request that describes the payments to be made.
pub fn transaction_request(&self) -> &TransactionRequest {
&self.transaction_request
}
/// Returns the map from payment index to the pool that has been selected
/// for the output that will fulfill that payment.
pub fn payment_pools(&self) -> &BTreeMap<usize, PoolType> {
&self.payment_pools
}
/// Returns the transparent inputs that have been selected to fund the transaction.
pub fn transparent_inputs(&self) -> &[WalletTransparentOutput] {
&self.transparent_inputs
}
/// Returns the Sapling inputs that have been selected to fund the transaction.
pub fn shielded_inputs(&self) -> Option<&ShieldedInputs<NoteRef>> {
self.shielded_inputs.as_ref()
}
/// 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
}
/// Returns the target height for which the proposal was prepared.
///
/// 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
}
/// 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
}
}
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)
.field(
"shielded_inputs",
&self.shielded_inputs().map(|i| i.notes.len()),
)
.field(
"anchor_height",
&self.shielded_inputs().map(|i| i.anchor_height),
)
.field("balance", &self.balance)
//.field("fee_rule", &self.fee_rule)
.field("min_target_height", &self.min_target_height)
.field("is_shielding", &self.is_shielding)
.finish_non_exhaustive()
}
}
/// The Sapling inputs to a proposed transaction.
#[derive(Clone, PartialEq, Eq)]
pub struct ShieldedInputs<NoteRef> {
anchor_height: BlockHeight,
notes: NonEmpty<ReceivedNote<NoteRef, Note>>,
}
impl<NoteRef> ShieldedInputs<NoteRef> {
/// Constructs a [`ShieldedInputs`] from its constituent parts.
pub fn from_parts(
anchor_height: BlockHeight,
notes: NonEmpty<ReceivedNote<NoteRef, Note>>,
) -> Self {
Self {
anchor_height,
notes,
}
}
/// 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.
pub fn notes(&self) -> &NonEmpty<ReceivedNote<NoteRef, Note>> {
&self.notes
}
}
/// A strategy for selecting transaction inputs and proposing transaction outputs.
///
/// Proposals should include only economically useful inputs, as determined by `Self::FeeRule`;
@ -638,21 +433,18 @@ where
match balance {
Ok(balance) => {
return Ok(Proposal {
return Proposal::from_parts(
transaction_request,
payment_pools,
transparent_inputs: vec![],
shielded_inputs: NonEmpty::from_vec(shielded_inputs).map(|notes| {
ShieldedInputs {
anchor_height,
notes,
}
}),
vec![],
NonEmpty::from_vec(shielded_inputs)
.map(|notes| ShieldedInputs::from_parts(anchor_height, notes)),
balance,
fee_rule: (*self.change_strategy.fee_rule()).clone(),
min_target_height: target_height,
is_shielding: false,
});
(*self.change_strategy.fee_rule()).clone(),
target_height,
false,
)
.map_err(InputSelectorError::Proposal);
}
Err(ChangeError::DustInputs { mut sapling, .. }) => {
exclude.append(&mut sapling);
@ -790,16 +582,17 @@ where
};
if balance.total() >= shielding_threshold {
Ok(Proposal {
transaction_request: TransactionRequest::empty(),
payment_pools: BTreeMap::new(),
Proposal::from_parts(
TransactionRequest::empty(),
BTreeMap::new(),
transparent_inputs,
shielded_inputs: None,
None,
balance,
fee_rule: (*self.change_strategy.fee_rule()).clone(),
min_target_height: target_height,
is_shielding: true,
})
(*self.change_strategy.fee_rule()).clone(),
target_height,
true,
)
.map_err(InputSelectorError::Proposal)
} else {
Err(InputSelectorError::InsufficientFunds {
available: balance.total(),

View File

@ -67,6 +67,7 @@ mod decrypt;
pub use zcash_keys::encoding;
pub mod fees;
pub use zcash_keys::keys;
pub mod proposal;
pub mod proto;
pub mod scan;
pub mod scanning;

View File

@ -0,0 +1,248 @@
use std::{
collections::BTreeMap,
fmt::{self, Debug, Display},
};
use nonempty::NonEmpty;
use zcash_primitives::{
consensus::BlockHeight, transaction::components::amount::NonNegativeAmount,
};
use crate::{
fees::TransactionBalance,
wallet::{Note, ReceivedNote, WalletTransparentOutput},
zip321::TransactionRequest,
PoolType,
};
/// Errors that can occur in construction of a [`Proposal`].
#[derive(Debug, Clone)]
pub enum ProposalError {
/// 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.
Overflow,
/// 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.
BalanceError {
input_total: NonNegativeAmount,
output_total: NonNegativeAmount,
},
/// 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,
}
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 {}
/// The Sapling inputs to a proposed transaction.
#[derive(Clone, PartialEq, Eq)]
pub struct ShieldedInputs<NoteRef> {
anchor_height: BlockHeight,
notes: NonEmpty<ReceivedNote<NoteRef, Note>>,
}
impl<NoteRef> ShieldedInputs<NoteRef> {
/// Constructs a [`ShieldedInputs`] from its constituent parts.
pub fn from_parts(
anchor_height: BlockHeight,
notes: NonEmpty<ReceivedNote<NoteRef, Note>>,
) -> Self {
Self {
anchor_height,
notes,
}
}
/// 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.
pub fn notes(&self) -> &NonEmpty<ReceivedNote<NoteRef, Note>> {
&self.notes
}
}
/// The inputs to be consumed and outputs to be produced in a proposed transaction.
#[derive(Clone, PartialEq, Eq)]
pub struct Proposal<FeeRuleT, NoteRef> {
transaction_request: TransactionRequest,
payment_pools: BTreeMap<usize, PoolType>,
transparent_inputs: Vec<WalletTransparentOutput>,
shielded_inputs: Option<ShieldedInputs<NoteRef>>,
balance: TransactionBalance,
fee_rule: FeeRuleT,
min_target_height: BlockHeight,
is_shielding: bool,
}
impl<FeeRuleT, NoteRef> Proposal<FeeRuleT, NoteRef> {
/// Constructs a validated [`Proposal`] from its constituent parts.
///
/// This operation validates the proposal for balance consistency and agreement between
/// the `is_shielding` flag and the structure of the proposal.
///
/// Parameters:
/// * `transaction_request`: The ZIP 321 transaction request describing the payments
/// to be made.
/// * `payment_pools`: A map from payment index to pool type.
/// * `transparent_inputs`: The set of previous transparent outputs to be spent.
/// * `shielded_inputs`: The sets of previous shielded outputs to be spent.
/// * `balance`: The change outputs to be added the transaction and the fee to be paid.
/// * `fee_rule`: The fee rule observed by the proposed transaction.
/// * `min_target_height`: The minimum block height at which the transaction may be created.
/// * `is_shielding`: A flag that identifies whether this is a wallet-internal shielding
/// transaction.
#[allow(clippy::too_many_arguments)]
pub fn from_parts(
transaction_request: TransactionRequest,
payment_pools: BTreeMap<usize, PoolType>,
transparent_inputs: Vec<WalletTransparentOutput>,
shielded_inputs: Option<ShieldedInputs<NoteRef>>,
balance: TransactionBalance,
fee_rule: FeeRuleT,
min_target_height: BlockHeight,
is_shielding: bool,
) -> Result<Self, ProposalError> {
let transparent_input_total = transparent_inputs
.iter()
.map(|out| out.txout().value)
.fold(Ok(NonNegativeAmount::ZERO), |acc, a| {
(acc? + a).ok_or(ProposalError::Overflow)
})?;
let shielded_input_total = shielded_inputs
.iter()
.flat_map(|s_in| s_in.notes().iter())
.map(|out| out.note().value())
.fold(Some(NonNegativeAmount::ZERO), |acc, a| (acc? + a))
.ok_or(ProposalError::Overflow)?;
let input_total =
(transparent_input_total + shielded_input_total).ok_or(ProposalError::Overflow)?;
let request_total = transaction_request
.total()
.map_err(|_| ProposalError::RequestTotalInvalid)?;
let output_total = (request_total + balance.total()).ok_or(ProposalError::Overflow)?;
if is_shielding
&& (transparent_input_total == NonNegativeAmount::ZERO
|| shielded_input_total > NonNegativeAmount::ZERO
|| request_total > NonNegativeAmount::ZERO)
{
return Err(ProposalError::ShieldingInvalid);
}
if input_total == output_total {
Ok(Self {
transaction_request,
payment_pools,
transparent_inputs,
shielded_inputs,
balance,
fee_rule,
min_target_height,
is_shielding,
})
} else {
Err(ProposalError::BalanceError {
input_total,
output_total,
})
}
}
/// Returns the transaction request that describes the payments to be made.
pub fn transaction_request(&self) -> &TransactionRequest {
&self.transaction_request
}
/// Returns the map from payment index to the pool that has been selected
/// for the output that will fulfill that payment.
pub fn payment_pools(&self) -> &BTreeMap<usize, PoolType> {
&self.payment_pools
}
/// Returns the transparent inputs that have been selected to fund the transaction.
pub fn transparent_inputs(&self) -> &[WalletTransparentOutput] {
&self.transparent_inputs
}
/// Returns the Sapling inputs that have been selected to fund the transaction.
pub fn shielded_inputs(&self) -> Option<&ShieldedInputs<NoteRef>> {
self.shielded_inputs.as_ref()
}
/// 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
}
/// Returns the target height for which the proposal was prepared.
///
/// 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
}
/// 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
}
}
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)
.field(
"shielded_inputs",
&self.shielded_inputs().map(|i| i.notes.len()),
)
.field(
"anchor_height",
&self.shielded_inputs().map(|i| i.anchor_height),
)
.field("balance", &self.balance)
//.field("fee_rule", &self.fee_rule)
.field("min_target_height", &self.min_target_height)
.field("is_shielding", &self.is_shielding)
.finish_non_exhaustive()
}
}

View File

@ -22,11 +22,9 @@ use zcash_primitives::{
use zcash_note_encryption::{EphemeralKeyBytes, COMPACT_NOTE_SIZE};
use crate::{
data_api::{
wallet::input_selection::{Proposal, ProposalError, ShieldedInputs},
InputSource,
},
data_api::InputSource,
fees::{ChangeValue, TransactionBalance},
proposal::{Proposal, ProposalError, ShieldedInputs},
zip321::{TransactionRequest, Zip321Error},
PoolType, ShieldedProtocol,
};

View File

@ -30,14 +30,13 @@ use zcash_client_backend::{
chain::{scan_cached_blocks, BlockSource, ScanSummary},
wallet::{
create_proposed_transaction, create_spend_to_address,
input_selection::{
GreedyInputSelector, GreedyInputSelectorError, InputSelector, Proposal,
},
input_selection::{GreedyInputSelector, GreedyInputSelectorError, InputSelector},
propose_standard_transfer_to_address, propose_transfer, spend,
},
AccountBalance, AccountBirthday, WalletRead, WalletSummary, WalletWrite,
},
keys::UnifiedSpendingKey,
proposal::Proposal,
proto::compact_formats::{
self as compact, CompactBlock, CompactSaplingOutput, CompactSaplingSpend, CompactTx,
},