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:
parent
6e0d9a9420
commit
1db3109cb4
|
@ -47,7 +47,6 @@ and this library adheres to Rust's notion of
|
||||||
}`
|
}`
|
||||||
- `WalletSummary::next_sapling_subtree_index`
|
- `WalletSummary::next_sapling_subtree_index`
|
||||||
- `wallet::propose_standard_transfer_to_address`
|
- `wallet::propose_standard_transfer_to_address`
|
||||||
- `wallet::input_selection::Proposal::{from_parts, shielded_inputs, payment_pools}`
|
|
||||||
- `wallet::input_selection::ShieldedInputs`
|
- `wallet::input_selection::ShieldedInputs`
|
||||||
- `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
|
||||||
|
@ -59,6 +58,10 @@ and this library adheres to Rust's notion of
|
||||||
- `ReceivedNote`
|
- `ReceivedNote`
|
||||||
- `WalletSaplingOutput::recipient_key_scope`
|
- `WalletSaplingOutput::recipient_key_scope`
|
||||||
- `wallet::TransparentAddressMetadata` (which replaces `zcash_keys::address::AddressMetadata`).
|
- `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::`
|
- `zcash_client_backend::proto::`
|
||||||
- `PROPOSAL_SER_V1`
|
- `PROPOSAL_SER_V1`
|
||||||
- `ProposalDecodingError`
|
- `ProposalDecodingError`
|
||||||
|
@ -66,12 +69,12 @@ and this library adheres to Rust's notion of
|
||||||
- `impl Clone for zcash_client_backend::{
|
- `impl Clone for zcash_client_backend::{
|
||||||
zip321::{Payment, TransactionRequest, Zip321Error, parse::Param, parse::IndexedParam},
|
zip321::{Payment, TransactionRequest, Zip321Error, parse::Param, parse::IndexedParam},
|
||||||
wallet::{ReceivedSaplingNote, WalletTransparentOutput},
|
wallet::{ReceivedSaplingNote, WalletTransparentOutput},
|
||||||
wallet::input_selection::{Proposal, SaplingInputs},
|
proposal::{Proposal, SaplingInputs},
|
||||||
}`
|
}`
|
||||||
- `impl {PartialEq, Eq} for zcash_client_backend::{
|
- `impl {PartialEq, Eq} for zcash_client_backend::{
|
||||||
zip321::{Zip321Error, parse::Param, parse::IndexedParam},
|
zip321::{Zip321Error, parse::Param, parse::IndexedParam},
|
||||||
wallet::{ReceivedSaplingNote, WalletTransparentOutput},
|
wallet::{ReceivedSaplingNote, WalletTransparentOutput},
|
||||||
wallet::input_selection::{Proposal, SaplingInputs},
|
proposal::{Proposal, SaplingInputs},
|
||||||
}`
|
}`
|
||||||
- `zcash_client_backend::zip321
|
- `zcash_client_backend::zip321
|
||||||
` `TransactionRequest::{total, from_indexed}`
|
` `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}`
|
- `ScannedBlock::{sapling_tree_size, sapling_nullifier_map, sapling_commitments}`
|
||||||
have been moved to `ScannedBlockSapling` and in that context are now
|
have been moved to `ScannedBlockSapling` and in that context are now
|
||||||
named `{tree_size, nullifier_map, commitments}` respectively.
|
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
|
### Changed
|
||||||
- `zcash_client_backend::data_api`:
|
- `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 `UnsupportedPoolType` has been added.
|
||||||
- A new variant `NoSupportedReceivers` has been added.
|
- A new variant `NoSupportedReceivers` has been added.
|
||||||
- A new variant `NoSpendingKey` has been added.
|
- A new variant `NoSpendingKey` has been added.
|
||||||
|
- A new variant `Proposal` has been added.
|
||||||
- Variant `ChildIndexOutOfRange` has been removed.
|
- Variant `ChildIndexOutOfRange` has been removed.
|
||||||
- `wallet::shield_transparent_funds` no longer takes a `memo` argument;
|
- `wallet::shield_transparent_funds` no longer takes a `memo` argument;
|
||||||
instead, memos to be associated with the shielded outputs should be
|
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`.
|
`min_confirmations` argument as `u32` instead of `NonZeroU32`.
|
||||||
- The `wallet::input_selection::InputSelector::DataSource`
|
- The `wallet::input_selection::InputSelector::DataSource`
|
||||||
associated type has been renamed to `InputSource`.
|
associated type has been renamed to `InputSource`.
|
||||||
|
- `wallet::input_selection::InputSelectorError` has added variant `Proposal`
|
||||||
- The signature of `wallet:input_selection::InputSelector::propose_transaction`
|
- The signature of `wallet:input_selection::InputSelector::propose_transaction`
|
||||||
has been altered such that it longer takes `min_confirmations` as an
|
has been altered such that it longer takes `min_confirmations` as an
|
||||||
argument, instead taking explicit `target_height` and `anchor_height`
|
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
|
- `wallet::create_proposed_transaction` now forces implementations to ignore
|
||||||
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.
|
||||||
- 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
|
- `wallet::input_selection::GreedyInputSelector` now has relaxed requirements
|
||||||
for its `InputSource` associated type.
|
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`:
|
- `zcash_client_backend::fees`:
|
||||||
- `ChangeStrategy::compute_balance` arguments have changed.
|
- `ChangeStrategy::compute_balance` arguments have changed.
|
||||||
- `ChangeValue` is now a struct. In addition to the existing change value, it
|
- `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
|
### Removed
|
||||||
- `zcash_client_backend::wallet::ReceivedSaplingNote` has been replaced by
|
- `zcash_client_backend::wallet::ReceivedSaplingNote` has been replaced by
|
||||||
`zcash_client_backend::ReceivedNote`.
|
`zcash_client_backend::ReceivedNote`.
|
||||||
- `zcash_client_backend::wallet::input_selection::Proposal::sapling_inputs` has
|
- `zcash_client_backend::wallet::input_selection::Proposal`
|
||||||
been replaced by `Proposal::shielded_inputs`
|
|
||||||
- `zcash_client_backend::data_api`
|
- `zcash_client_backend::data_api`
|
||||||
- `zcash_client_backend::data_api::ScannedBlock::from_parts` has been made crate-private.
|
- `zcash_client_backend::data_api::ScannedBlock::from_parts` has been made crate-private.
|
||||||
- `zcash_client_backend::data_api::ScannedBlock::into_sapling_commitments` has been
|
- `zcash_client_backend::data_api::ScannedBlock::into_sapling_commitments` has been
|
||||||
|
|
|
@ -14,6 +14,7 @@ use zcash_primitives::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::data_api::wallet::input_selection::InputSelectorError;
|
use crate::data_api::wallet::input_selection::InputSelectorError;
|
||||||
|
use crate::proposal::ProposalError;
|
||||||
use crate::PoolType;
|
use crate::PoolType;
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
@ -33,6 +34,9 @@ pub enum Error<DataSourceError, CommitmentTreeError, SelectionError, FeeError> {
|
||||||
/// An error in note selection
|
/// An error in note selection
|
||||||
NoteSelection(SelectionError),
|
NoteSelection(SelectionError),
|
||||||
|
|
||||||
|
/// An error in transaction proposal construction
|
||||||
|
Proposal(ProposalError),
|
||||||
|
|
||||||
/// No account could be found corresponding to a provided spending key.
|
/// No account could be found corresponding to a provided spending key.
|
||||||
KeyNotRecognized,
|
KeyNotRecognized,
|
||||||
|
|
||||||
|
@ -100,6 +104,9 @@ where
|
||||||
Error::NoteSelection(e) => {
|
Error::NoteSelection(e) => {
|
||||||
write!(f, "Note selection encountered the following error: {}", 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 => {
|
Error::KeyNotRecognized => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
|
@ -148,6 +155,7 @@ where
|
||||||
Error::DataSource(e) => Some(e),
|
Error::DataSource(e) => Some(e),
|
||||||
Error::CommitmentTree(e) => Some(e),
|
Error::CommitmentTree(e) => Some(e),
|
||||||
Error::NoteSelection(e) => Some(e),
|
Error::NoteSelection(e) => Some(e),
|
||||||
|
Error::Proposal(e) => Some(e),
|
||||||
Error::Builder(e) => Some(e),
|
Error::Builder(e) => Some(e),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
@ -171,6 +179,7 @@ impl<DE, CE, SE, FE> From<InputSelectorError<DE, SE>> for Error<DE, CE, SE, FE>
|
||||||
match e {
|
match e {
|
||||||
InputSelectorError::DataSource(e) => Error::DataSource(e),
|
InputSelectorError::DataSource(e) => Error::DataSource(e),
|
||||||
InputSelectorError::Selection(e) => Error::NoteSelection(e),
|
InputSelectorError::Selection(e) => Error::NoteSelection(e),
|
||||||
|
InputSelectorError::Proposal(e) => Error::Proposal(e),
|
||||||
InputSelectorError::InsufficientFunds {
|
InputSelectorError::InsufficientFunds {
|
||||||
available,
|
available,
|
||||||
required,
|
required,
|
||||||
|
|
|
@ -20,12 +20,13 @@ use zcash_primitives::{
|
||||||
use crate::{
|
use crate::{
|
||||||
address::Address,
|
address::Address,
|
||||||
data_api::{
|
data_api::{
|
||||||
error::Error, wallet::input_selection::Proposal, DecryptedTransaction, SentTransaction,
|
error::Error, DecryptedTransaction, SentTransaction, SentTransactionOutput,
|
||||||
SentTransactionOutput, WalletCommitmentTrees, WalletRead, WalletWrite,
|
WalletCommitmentTrees, WalletRead, WalletWrite,
|
||||||
},
|
},
|
||||||
decrypt_transaction,
|
decrypt_transaction,
|
||||||
fees::{self, DustOutputPolicy},
|
fees::{self, DustOutputPolicy},
|
||||||
keys::UnifiedSpendingKey,
|
keys::UnifiedSpendingKey,
|
||||||
|
proposal::Proposal,
|
||||||
wallet::{Note, OvkPolicy, Recipient},
|
wallet::{Note, OvkPolicy, Recipient},
|
||||||
zip321::{self, Payment},
|
zip321::{self, Payment},
|
||||||
PoolType, ShieldedProtocol,
|
PoolType, ShieldedProtocol,
|
||||||
|
@ -710,7 +711,7 @@ where
|
||||||
} else {
|
} else {
|
||||||
builder.add_transparent_output(to, payment.amount)?;
|
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(
|
SentTransactionOutput::from_parts(
|
||||||
output_index,
|
output_index,
|
||||||
Recipient::Transparent(addr),
|
Recipient::Transparent(*addr),
|
||||||
value,
|
value,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
use std::{
|
use std::{
|
||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
|
error,
|
||||||
fmt::{self, Debug, Display},
|
fmt::{self, Debug, Display},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -23,7 +24,8 @@ use zcash_primitives::{
|
||||||
use crate::{
|
use crate::{
|
||||||
address::{Address, UnifiedAddress},
|
address::{Address, UnifiedAddress},
|
||||||
data_api::InputSource,
|
data_api::InputSource,
|
||||||
fees::{sapling, ChangeError, ChangeStrategy, DustOutputPolicy, TransactionBalance},
|
fees::{sapling, ChangeError, ChangeStrategy, DustOutputPolicy},
|
||||||
|
proposal::{Proposal, ProposalError, ShieldedInputs},
|
||||||
wallet::{Note, ReceivedNote, WalletTransparentOutput},
|
wallet::{Note, ReceivedNote, WalletTransparentOutput},
|
||||||
zip321::TransactionRequest,
|
zip321::TransactionRequest,
|
||||||
PoolType, ShieldedProtocol,
|
PoolType, ShieldedProtocol,
|
||||||
|
@ -39,11 +41,14 @@ use {std::collections::BTreeSet, zcash_primitives::transaction::components::OutP
|
||||||
use crate::fees::orchard as orchard_fees;
|
use crate::fees::orchard as orchard_fees;
|
||||||
|
|
||||||
/// The type of errors that may be produced in input selection.
|
/// The type of errors that may be produced in input selection.
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum InputSelectorError<DbErrT, SelectorErrT> {
|
pub enum InputSelectorError<DbErrT, SelectorErrT> {
|
||||||
/// An error occurred accessing the underlying data store.
|
/// An error occurred accessing the underlying data store.
|
||||||
DataSource(DbErrT),
|
DataSource(DbErrT),
|
||||||
/// An error occurred specific to the provided input selector's selection rules.
|
/// An error occurred specific to the provided input selector's selection rules.
|
||||||
Selection(SelectorErrT),
|
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
|
/// Insufficient funds were available to satisfy the payment request that inputs were being
|
||||||
/// selected to attempt to satisfy.
|
/// selected to attempt to satisfy.
|
||||||
InsufficientFunds {
|
InsufficientFunds {
|
||||||
|
@ -68,6 +73,13 @@ impl<DE: fmt::Display, SE: fmt::Display> fmt::Display for InputSelectorError<DE,
|
||||||
InputSelectorError::Selection(e) => {
|
InputSelectorError::Selection(e) => {
|
||||||
write!(f, "Note selection encountered the following error: {}", 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 {
|
InputSelectorError::InsufficientFunds {
|
||||||
available,
|
available,
|
||||||
required,
|
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.
|
impl<DE, SE> error::Error for InputSelectorError<DE, SE>
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
where
|
||||||
pub struct Proposal<FeeRuleT, NoteRef> {
|
DE: Debug + Display + error::Error + 'static,
|
||||||
transaction_request: TransactionRequest,
|
SE: Debug + Display + error::Error + 'static,
|
||||||
payment_pools: BTreeMap<usize, PoolType>,
|
{
|
||||||
transparent_inputs: Vec<WalletTransparentOutput>,
|
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||||
shielded_inputs: Option<ShieldedInputs<NoteRef>>,
|
match &self {
|
||||||
balance: TransactionBalance,
|
Self::DataSource(e) => Some(e),
|
||||||
fee_rule: FeeRuleT,
|
Self::Selection(e) => Some(e),
|
||||||
min_target_height: BlockHeight,
|
Self::Proposal(e) => Some(e),
|
||||||
is_shielding: bool,
|
_ => None,
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 {}
|
|
||||||
|
|
||||||
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.
|
/// A strategy for selecting transaction inputs and proposing transaction outputs.
|
||||||
///
|
///
|
||||||
/// Proposals should include only economically useful inputs, as determined by `Self::FeeRule`;
|
/// Proposals should include only economically useful inputs, as determined by `Self::FeeRule`;
|
||||||
|
@ -638,21 +433,18 @@ where
|
||||||
|
|
||||||
match balance {
|
match balance {
|
||||||
Ok(balance) => {
|
Ok(balance) => {
|
||||||
return Ok(Proposal {
|
return Proposal::from_parts(
|
||||||
transaction_request,
|
transaction_request,
|
||||||
payment_pools,
|
payment_pools,
|
||||||
transparent_inputs: vec![],
|
vec![],
|
||||||
shielded_inputs: NonEmpty::from_vec(shielded_inputs).map(|notes| {
|
NonEmpty::from_vec(shielded_inputs)
|
||||||
ShieldedInputs {
|
.map(|notes| ShieldedInputs::from_parts(anchor_height, notes)),
|
||||||
anchor_height,
|
|
||||||
notes,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
balance,
|
balance,
|
||||||
fee_rule: (*self.change_strategy.fee_rule()).clone(),
|
(*self.change_strategy.fee_rule()).clone(),
|
||||||
min_target_height: target_height,
|
target_height,
|
||||||
is_shielding: false,
|
false,
|
||||||
});
|
)
|
||||||
|
.map_err(InputSelectorError::Proposal);
|
||||||
}
|
}
|
||||||
Err(ChangeError::DustInputs { mut sapling, .. }) => {
|
Err(ChangeError::DustInputs { mut sapling, .. }) => {
|
||||||
exclude.append(&mut sapling);
|
exclude.append(&mut sapling);
|
||||||
|
@ -790,16 +582,17 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
if balance.total() >= shielding_threshold {
|
if balance.total() >= shielding_threshold {
|
||||||
Ok(Proposal {
|
Proposal::from_parts(
|
||||||
transaction_request: TransactionRequest::empty(),
|
TransactionRequest::empty(),
|
||||||
payment_pools: BTreeMap::new(),
|
BTreeMap::new(),
|
||||||
transparent_inputs,
|
transparent_inputs,
|
||||||
shielded_inputs: None,
|
None,
|
||||||
balance,
|
balance,
|
||||||
fee_rule: (*self.change_strategy.fee_rule()).clone(),
|
(*self.change_strategy.fee_rule()).clone(),
|
||||||
min_target_height: target_height,
|
target_height,
|
||||||
is_shielding: true,
|
true,
|
||||||
})
|
)
|
||||||
|
.map_err(InputSelectorError::Proposal)
|
||||||
} else {
|
} else {
|
||||||
Err(InputSelectorError::InsufficientFunds {
|
Err(InputSelectorError::InsufficientFunds {
|
||||||
available: balance.total(),
|
available: balance.total(),
|
||||||
|
|
|
@ -67,6 +67,7 @@ mod decrypt;
|
||||||
pub use zcash_keys::encoding;
|
pub use zcash_keys::encoding;
|
||||||
pub mod fees;
|
pub mod fees;
|
||||||
pub use zcash_keys::keys;
|
pub use zcash_keys::keys;
|
||||||
|
pub mod proposal;
|
||||||
pub mod proto;
|
pub mod proto;
|
||||||
pub mod scan;
|
pub mod scan;
|
||||||
pub mod scanning;
|
pub mod scanning;
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,11 +22,9 @@ use zcash_primitives::{
|
||||||
use zcash_note_encryption::{EphemeralKeyBytes, COMPACT_NOTE_SIZE};
|
use zcash_note_encryption::{EphemeralKeyBytes, COMPACT_NOTE_SIZE};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
data_api::{
|
data_api::InputSource,
|
||||||
wallet::input_selection::{Proposal, ProposalError, ShieldedInputs},
|
|
||||||
InputSource,
|
|
||||||
},
|
|
||||||
fees::{ChangeValue, TransactionBalance},
|
fees::{ChangeValue, TransactionBalance},
|
||||||
|
proposal::{Proposal, ProposalError, ShieldedInputs},
|
||||||
zip321::{TransactionRequest, Zip321Error},
|
zip321::{TransactionRequest, Zip321Error},
|
||||||
PoolType, ShieldedProtocol,
|
PoolType, ShieldedProtocol,
|
||||||
};
|
};
|
||||||
|
|
|
@ -30,14 +30,13 @@ use zcash_client_backend::{
|
||||||
chain::{scan_cached_blocks, BlockSource, ScanSummary},
|
chain::{scan_cached_blocks, BlockSource, ScanSummary},
|
||||||
wallet::{
|
wallet::{
|
||||||
create_proposed_transaction, create_spend_to_address,
|
create_proposed_transaction, create_spend_to_address,
|
||||||
input_selection::{
|
input_selection::{GreedyInputSelector, GreedyInputSelectorError, InputSelector},
|
||||||
GreedyInputSelector, GreedyInputSelectorError, InputSelector, Proposal,
|
|
||||||
},
|
|
||||||
propose_standard_transfer_to_address, propose_transfer, spend,
|
propose_standard_transfer_to_address, propose_transfer, spend,
|
||||||
},
|
},
|
||||||
AccountBalance, AccountBirthday, WalletRead, WalletSummary, WalletWrite,
|
AccountBalance, AccountBirthday, WalletRead, WalletSummary, WalletWrite,
|
||||||
},
|
},
|
||||||
keys::UnifiedSpendingKey,
|
keys::UnifiedSpendingKey,
|
||||||
|
proposal::Proposal,
|
||||||
proto::compact_formats::{
|
proto::compact_formats::{
|
||||||
self as compact, CompactBlock, CompactSaplingOutput, CompactSaplingSpend, CompactTx,
|
self as compact, CompactBlock, CompactSaplingOutput, CompactSaplingSpend, CompactTx,
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue