Fix merge conflicts
This commit is contained in:
commit
a788cc4c4d
|
@ -8,15 +8,31 @@ and this library adheres to Rust's notion of
|
|||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- `zcash_client_backend::data_api::ShieldedProtocol` has a new variant for `Orchard`,
|
||||
allowing for better reporting to callers trying to perform actions using `Orchard`
|
||||
before it is fully supported.
|
||||
- `zcash_client_backend::data_api::error::Error` has new error variant:
|
||||
- `Error::UnsupportedPoolType(zcash_client_backend::data_api::PoolType)`
|
||||
- Added methods to `zcash_client_backend::wallet::ReceivedSaplingNote`:
|
||||
`{from_parts, txid, output_index, diversifier, rseed, note_commitment_tree_position}`.
|
||||
|
||||
### Changed
|
||||
- `zcash_client_backend::data_api::chain::scan_cached_blocks` now returns
|
||||
a `ScanSummary` containing metadata about the scanned blocks on success.
|
||||
- The fields of `zcash_client_backend::wallet::ReceivedSaplingNote` are now
|
||||
private. Use `ReceivedSaplingNote::from_parts` for construction instead.
|
||||
Accessor methods are provided for each previously-public field.
|
||||
- `zcash_client_backend::data_api` changes:
|
||||
- The `NoteMismatch` variant of `data_api::error::Error` now wraps a
|
||||
`data_api::NoteId` instead of a backend-specific note identifier. The
|
||||
related `NoteRef` type parameter has been removed from `data_api::error::Error`.
|
||||
- `wallet::create_spend_to_address` now takes a `NonNegativeAmount` rather than
|
||||
an `Amount`.
|
||||
- All uses of `Amount` in `data_api::wallet::input_selection` have been replaced
|
||||
with `NonNegativeAmount`.
|
||||
- All uses of `Amount` in `zcash_client_backend::fees` have been replaced
|
||||
with `NonNegativeAmount`.
|
||||
|
||||
## [0.10.0] - 2023-09-25
|
||||
|
||||
|
|
|
@ -651,6 +651,7 @@ impl SentTransactionOutput {
|
|||
sapling_change_to,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the index within the transaction that contains the recipient output.
|
||||
///
|
||||
/// - If `recipient_address` is a Sapling address, this is an index into the Sapling
|
||||
|
|
|
@ -21,9 +21,11 @@ use crate::data_api::PoolType;
|
|||
#[cfg(feature = "transparent-inputs")]
|
||||
use zcash_primitives::{legacy::TransparentAddress, zip32::DiversifierIndex};
|
||||
|
||||
use super::NoteId;
|
||||
|
||||
/// Errors that can occur as a consequence of wallet operations.
|
||||
#[derive(Debug)]
|
||||
pub enum Error<DataSourceError, CommitmentTreeError, SelectionError, FeeError, NoteRef> {
|
||||
pub enum Error<DataSourceError, CommitmentTreeError, SelectionError, FeeError> {
|
||||
/// An error occurred retrieving data from the underlying data source
|
||||
DataSource(DataSourceError),
|
||||
|
||||
|
@ -60,7 +62,7 @@ pub enum Error<DataSourceError, CommitmentTreeError, SelectionError, FeeError, N
|
|||
|
||||
/// A note being spent does not correspond to either the internal or external
|
||||
/// full viewing key for an account.
|
||||
NoteMismatch(NoteRef),
|
||||
NoteMismatch(NoteId),
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
AddressNotRecognized(TransparentAddress),
|
||||
|
@ -69,13 +71,12 @@ pub enum Error<DataSourceError, CommitmentTreeError, SelectionError, FeeError, N
|
|||
ChildIndexOutOfRange(DiversifierIndex),
|
||||
}
|
||||
|
||||
impl<DE, CE, SE, FE, N> fmt::Display for Error<DE, CE, SE, FE, N>
|
||||
impl<DE, CE, SE, FE> fmt::Display for Error<DE, CE, SE, FE>
|
||||
where
|
||||
DE: fmt::Display,
|
||||
CE: fmt::Display,
|
||||
SE: fmt::Display,
|
||||
FE: fmt::Display,
|
||||
N: fmt::Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match &self {
|
||||
|
@ -116,7 +117,7 @@ where
|
|||
Error::Builder(e) => write!(f, "An error occurred building the transaction: {}", e),
|
||||
Error::MemoForbidden => write!(f, "It is not possible to send a memo to a transparent address."),
|
||||
Error::UnsupportedPoolType(t) => write!(f, "Attempted to create spend to an unsupported pool type: {}", t),
|
||||
Error::NoteMismatch(n) => write!(f, "A note being spent ({}) does not correspond to either the internal or external full viewing key for the provided spending key.", n),
|
||||
Error::NoteMismatch(n) => write!(f, "A note being spent ({:?}) does not correspond to either the internal or external full viewing key for the provided spending key.", n),
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
Error::AddressNotRecognized(_) => {
|
||||
|
@ -134,13 +135,12 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<DE, CE, SE, FE, N> error::Error for Error<DE, CE, SE, FE, N>
|
||||
impl<DE, CE, SE, FE> error::Error for Error<DE, CE, SE, FE>
|
||||
where
|
||||
DE: Debug + Display + error::Error + 'static,
|
||||
CE: Debug + Display + error::Error + 'static,
|
||||
SE: Debug + Display + error::Error + 'static,
|
||||
FE: Debug + Display + 'static,
|
||||
N: Debug + Display,
|
||||
{
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
match &self {
|
||||
|
@ -153,19 +153,19 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<DE, CE, SE, FE, N> From<builder::Error<FE>> for Error<DE, CE, SE, FE, N> {
|
||||
impl<DE, CE, SE, FE> From<builder::Error<FE>> for Error<DE, CE, SE, FE> {
|
||||
fn from(e: builder::Error<FE>) -> Self {
|
||||
Error::Builder(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl<DE, CE, SE, FE, N> From<BalanceError> for Error<DE, CE, SE, FE, N> {
|
||||
impl<DE, CE, SE, FE> From<BalanceError> for Error<DE, CE, SE, FE> {
|
||||
fn from(e: BalanceError) -> Self {
|
||||
Error::BalanceError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl<DE, CE, SE, FE, N> From<InputSelectorError<DE, SE>> for Error<DE, CE, SE, FE, N> {
|
||||
impl<DE, CE, SE, FE> From<InputSelectorError<DE, SE>> for Error<DE, CE, SE, FE> {
|
||||
fn from(e: InputSelectorError<DE, SE>) -> Self {
|
||||
match e {
|
||||
InputSelectorError::DataSource(e) => Error::DataSource(e),
|
||||
|
@ -182,19 +182,19 @@ impl<DE, CE, SE, FE, N> From<InputSelectorError<DE, SE>> for Error<DE, CE, SE, F
|
|||
}
|
||||
}
|
||||
|
||||
impl<DE, CE, SE, FE, N> From<sapling::builder::Error> for Error<DE, CE, SE, FE, N> {
|
||||
impl<DE, CE, SE, FE> From<sapling::builder::Error> for Error<DE, CE, SE, FE> {
|
||||
fn from(e: sapling::builder::Error) -> Self {
|
||||
Error::Builder(builder::Error::SaplingBuild(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl<DE, CE, SE, FE, N> From<transparent::builder::Error> for Error<DE, CE, SE, FE, N> {
|
||||
impl<DE, CE, SE, FE> From<transparent::builder::Error> for Error<DE, CE, SE, FE> {
|
||||
fn from(e: transparent::builder::Error) -> Self {
|
||||
Error::Builder(builder::Error::TransparentBuild(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl<DE, CE, SE, FE, N> From<ShardTreeError<CE>> for Error<DE, CE, SE, FE, N> {
|
||||
impl<DE, CE, SE, FE> From<ShardTreeError<CE>> for Error<DE, CE, SE, FE> {
|
||||
fn from(e: ShardTreeError<CE>) -> Self {
|
||||
Error::CommitmentTree(e)
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ use zcash_primitives::{
|
|||
},
|
||||
transaction::{
|
||||
builder::Builder,
|
||||
components::amount::{Amount, BalanceError},
|
||||
components::amount::{Amount, BalanceError, NonNegativeAmount},
|
||||
fees::{fixed, FeeRule},
|
||||
Transaction,
|
||||
},
|
||||
|
@ -37,15 +37,12 @@ use crate::{
|
|||
pub mod input_selection;
|
||||
use input_selection::{GreedyInputSelector, GreedyInputSelectorError, InputSelector};
|
||||
|
||||
use super::ShieldedProtocol;
|
||||
use super::{NoteId, ShieldedProtocol};
|
||||
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
use {
|
||||
crate::wallet::WalletTransparentOutput,
|
||||
zcash_primitives::{
|
||||
legacy::TransparentAddress, sapling::keys::OutgoingViewingKey,
|
||||
transaction::components::amount::NonNegativeAmount,
|
||||
},
|
||||
zcash_primitives::{legacy::TransparentAddress, sapling::keys::OutgoingViewingKey},
|
||||
};
|
||||
|
||||
/// Scans a [`Transaction`] for any information that can be decrypted by the accounts in
|
||||
|
@ -195,7 +192,7 @@ pub fn create_spend_to_address<DbT, ParamsT>(
|
|||
prover: impl SaplingProver,
|
||||
usk: &UnifiedSpendingKey,
|
||||
to: &RecipientAddress,
|
||||
amount: Amount,
|
||||
amount: NonNegativeAmount,
|
||||
memo: Option<MemoBytes>,
|
||||
ovk_policy: OvkPolicy,
|
||||
min_confirmations: NonZeroU32,
|
||||
|
@ -206,7 +203,6 @@ pub fn create_spend_to_address<DbT, ParamsT>(
|
|||
<DbT as WalletCommitmentTrees>::Error,
|
||||
GreedyInputSelectorError<BalanceError, DbT::NoteRef>,
|
||||
Infallible,
|
||||
DbT::NoteRef,
|
||||
>,
|
||||
>
|
||||
where
|
||||
|
@ -216,7 +212,7 @@ where
|
|||
{
|
||||
let req = zip321::TransactionRequest::new(vec![Payment {
|
||||
recipient_address: to.clone(),
|
||||
amount,
|
||||
amount: amount.into(),
|
||||
memo,
|
||||
label: None,
|
||||
message: None,
|
||||
|
@ -309,7 +305,6 @@ pub fn spend<DbT, ParamsT, InputsT>(
|
|||
<DbT as WalletCommitmentTrees>::Error,
|
||||
InputsT::Error,
|
||||
<InputsT::FeeRule as FeeRule>::Error,
|
||||
DbT::NoteRef,
|
||||
>,
|
||||
>
|
||||
where
|
||||
|
@ -358,13 +353,7 @@ pub fn propose_transfer<DbT, ParamsT, InputsT, CommitmentTreeErrT>(
|
|||
min_confirmations: NonZeroU32,
|
||||
) -> Result<
|
||||
Proposal<InputsT::FeeRule, DbT::NoteRef>,
|
||||
Error<
|
||||
DbT::Error,
|
||||
CommitmentTreeErrT,
|
||||
InputsT::Error,
|
||||
<InputsT::FeeRule as FeeRule>::Error,
|
||||
DbT::NoteRef,
|
||||
>,
|
||||
Error<DbT::Error, CommitmentTreeErrT, InputsT::Error, <InputsT::FeeRule as FeeRule>::Error>,
|
||||
>
|
||||
where
|
||||
DbT: WalletWrite,
|
||||
|
@ -395,13 +384,7 @@ pub fn propose_shielding<DbT, ParamsT, InputsT, CommitmentTreeErrT>(
|
|||
min_confirmations: NonZeroU32,
|
||||
) -> Result<
|
||||
Proposal<InputsT::FeeRule, DbT::NoteRef>,
|
||||
Error<
|
||||
DbT::Error,
|
||||
CommitmentTreeErrT,
|
||||
InputsT::Error,
|
||||
<InputsT::FeeRule as FeeRule>::Error,
|
||||
DbT::NoteRef,
|
||||
>,
|
||||
Error<DbT::Error, CommitmentTreeErrT, InputsT::Error, <InputsT::FeeRule as FeeRule>::Error>,
|
||||
>
|
||||
where
|
||||
ParamsT: consensus::Parameters,
|
||||
|
@ -446,7 +429,6 @@ pub fn create_proposed_transaction<DbT, ParamsT, InputsErrT, FeeRuleT>(
|
|||
<DbT as WalletCommitmentTrees>::Error,
|
||||
InputsErrT,
|
||||
FeeRuleT::Error,
|
||||
DbT::NoteRef,
|
||||
>,
|
||||
>
|
||||
where
|
||||
|
@ -491,8 +473,7 @@ where
|
|||
let mut builder = Builder::new(params.clone(), proposal.min_target_height(), None);
|
||||
|
||||
let checkpoint_depth = wallet_db.get_checkpoint_depth(min_confirmations)?;
|
||||
|
||||
wallet_db.with_sapling_tree_mut::<_, _, Error<_, _, _, _, _>>(|sapling_tree| {
|
||||
wallet_db.with_sapling_tree_mut::<_, _, Error<_, _, _, _>>(|sapling_tree| {
|
||||
for selected in proposal.sapling_inputs() {
|
||||
let (note, key, merkle_path) = select_key_for_note(
|
||||
sapling_tree,
|
||||
|
@ -501,9 +482,15 @@ where
|
|||
&dfvk,
|
||||
checkpoint_depth,
|
||||
)?
|
||||
.ok_or(Error::NoteMismatch(selected.note_id))?;
|
||||
.ok_or_else(|| {
|
||||
Error::NoteMismatch(NoteId {
|
||||
txid: *selected.txid(),
|
||||
protocol: ShieldedProtocol::Sapling,
|
||||
output_index: selected.output_index(),
|
||||
})
|
||||
})?;
|
||||
|
||||
builder.add_sapling_spend(key, selected.diversifier, note, merkle_path)?;
|
||||
builder.add_sapling_spend(key, selected.diversifier(), note, merkle_path)?;
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
|
@ -605,7 +592,7 @@ where
|
|||
builder.add_sapling_output(
|
||||
internal_ovk(),
|
||||
dfvk.change_address().1,
|
||||
*amount,
|
||||
amount.into(),
|
||||
memo.clone(),
|
||||
)?;
|
||||
sapling_output_meta.push((
|
||||
|
@ -613,7 +600,7 @@ where
|
|||
account,
|
||||
PoolType::Shielded(ShieldedProtocol::Sapling),
|
||||
),
|
||||
*amount,
|
||||
amount.into(),
|
||||
Some(memo),
|
||||
))
|
||||
}
|
||||
|
@ -682,7 +669,7 @@ where
|
|||
created: time::OffsetDateTime::now_utc(),
|
||||
account,
|
||||
outputs: sapling_outputs.chain(transparent_outputs).collect(),
|
||||
fee_amount: proposal.balance().fee_required(),
|
||||
fee_amount: Amount::from(proposal.balance().fee_required()),
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
utxos_spent: utxos.iter().map(|utxo| utxo.outpoint().clone()).collect(),
|
||||
})
|
||||
|
@ -744,7 +731,6 @@ pub fn shield_transparent_funds<DbT, ParamsT, InputsT>(
|
|||
<DbT as WalletCommitmentTrees>::Error,
|
||||
InputsT::Error,
|
||||
<InputsT::FeeRule as FeeRule>::Error,
|
||||
DbT::NoteRef,
|
||||
>,
|
||||
>
|
||||
where
|
||||
|
@ -793,15 +779,15 @@ fn select_key_for_note<N, S: ShardStore<H = Node, CheckpointId = BlockHeight>>(
|
|||
// corresponding to the unified spending key, checking against the witness we are using
|
||||
// to spend the note that we've used the correct key.
|
||||
let external_note = dfvk
|
||||
.diversified_address(selected.diversifier)
|
||||
.map(|addr| addr.create_note(selected.note_value.into(), selected.rseed));
|
||||
.diversified_address(selected.diversifier())
|
||||
.map(|addr| addr.create_note(selected.value().try_into().unwrap(), selected.rseed()));
|
||||
let internal_note = dfvk
|
||||
.diversified_change_address(selected.diversifier)
|
||||
.map(|addr| addr.create_note(selected.note_value.into(), selected.rseed));
|
||||
.diversified_change_address(selected.diversifier())
|
||||
.map(|addr| addr.create_note(selected.value().try_into().unwrap(), selected.rseed()));
|
||||
|
||||
let expected_root = commitment_tree.root_at_checkpoint(checkpoint_depth)?;
|
||||
let merkle_path = commitment_tree
|
||||
.witness_caching(selected.note_commitment_tree_position, checkpoint_depth)?;
|
||||
.witness_caching(selected.note_commitment_tree_position(), checkpoint_depth)?;
|
||||
|
||||
Ok(external_note
|
||||
.filter(|n| expected_root == merkle_path.root(Node::from_cmu(&n.cmu())))
|
||||
|
|
|
@ -419,7 +419,7 @@ where
|
|||
|
||||
let new_available = sapling_inputs
|
||||
.iter()
|
||||
.map(|n| n.note_value)
|
||||
.map(|n| n.value())
|
||||
.sum::<Option<Amount>>()
|
||||
.ok_or(BalanceError::Overflow)?;
|
||||
|
||||
|
@ -493,7 +493,7 @@ where
|
|||
}
|
||||
};
|
||||
|
||||
if balance.total() >= shielding_threshold.into() {
|
||||
if balance.total() >= shielding_threshold {
|
||||
Ok(Proposal {
|
||||
transaction_request: TransactionRequest::empty(),
|
||||
transparent_inputs,
|
||||
|
@ -506,7 +506,7 @@ where
|
|||
})
|
||||
} else {
|
||||
Err(InputSelectorError::InsufficientFunds {
|
||||
available: balance.total(),
|
||||
available: balance.total().into(),
|
||||
required: shielding_threshold.into(),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use zcash_primitives::{
|
|||
consensus::{self, BlockHeight},
|
||||
transaction::{
|
||||
components::{
|
||||
amount::{Amount, BalanceError},
|
||||
amount::{Amount, BalanceError, NonNegativeAmount},
|
||||
sapling::fees as sapling,
|
||||
transparent::fees as transparent,
|
||||
OutPoint,
|
||||
|
@ -19,11 +19,11 @@ pub mod zip317;
|
|||
/// A proposed change amount and output pool.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ChangeValue {
|
||||
Sapling(Amount),
|
||||
Sapling(NonNegativeAmount),
|
||||
}
|
||||
|
||||
impl ChangeValue {
|
||||
pub fn value(&self) -> Amount {
|
||||
pub fn value(&self) -> NonNegativeAmount {
|
||||
match self {
|
||||
ChangeValue::Sapling(value) => *value,
|
||||
}
|
||||
|
@ -36,23 +36,28 @@ impl ChangeValue {
|
|||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TransactionBalance {
|
||||
proposed_change: Vec<ChangeValue>,
|
||||
fee_required: Amount,
|
||||
total: Amount,
|
||||
fee_required: NonNegativeAmount,
|
||||
total: NonNegativeAmount,
|
||||
}
|
||||
|
||||
impl TransactionBalance {
|
||||
/// Constructs a new balance from its constituent parts.
|
||||
pub fn new(proposed_change: Vec<ChangeValue>, fee_required: Amount) -> Option<Self> {
|
||||
proposed_change
|
||||
pub fn new(
|
||||
proposed_change: Vec<ChangeValue>,
|
||||
fee_required: NonNegativeAmount,
|
||||
) -> Result<Self, ()> {
|
||||
let total = proposed_change
|
||||
.iter()
|
||||
.map(|v| v.value())
|
||||
.chain(Some(fee_required))
|
||||
.sum::<Option<Amount>>()
|
||||
.map(|total| TransactionBalance {
|
||||
proposed_change,
|
||||
fee_required,
|
||||
total,
|
||||
})
|
||||
.map(|c| c.value())
|
||||
.chain(Some(fee_required).into_iter())
|
||||
.sum::<Option<NonNegativeAmount>>()
|
||||
.ok_or(())?;
|
||||
|
||||
Ok(Self {
|
||||
proposed_change,
|
||||
fee_required,
|
||||
total,
|
||||
})
|
||||
}
|
||||
|
||||
/// The change values proposed by the [`ChangeStrategy`] that computed this balance.
|
||||
|
@ -62,12 +67,12 @@ impl TransactionBalance {
|
|||
|
||||
/// Returns the fee computed for the transaction, assuming that the suggested
|
||||
/// change outputs are added to the transaction.
|
||||
pub fn fee_required(&self) -> Amount {
|
||||
pub fn fee_required(&self) -> NonNegativeAmount {
|
||||
self.fee_required
|
||||
}
|
||||
|
||||
/// Returns the sum of the proposed change outputs and the required fee.
|
||||
pub fn total(&self) -> Amount {
|
||||
pub fn total(&self) -> NonNegativeAmount {
|
||||
self.total
|
||||
}
|
||||
}
|
||||
|
@ -147,7 +152,7 @@ pub enum DustAction {
|
|||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct DustOutputPolicy {
|
||||
action: DustAction,
|
||||
dust_threshold: Option<Amount>,
|
||||
dust_threshold: Option<NonNegativeAmount>,
|
||||
}
|
||||
|
||||
impl DustOutputPolicy {
|
||||
|
@ -157,7 +162,7 @@ impl DustOutputPolicy {
|
|||
/// of the dust threshold to the change strategy that is evaluating the strategy; this
|
||||
/// recommended, but an explicit value (including zero) may be provided to explicitly
|
||||
/// override the determination of the change strategy.
|
||||
pub fn new(action: DustAction, dust_threshold: Option<Amount>) -> Self {
|
||||
pub fn new(action: DustAction, dust_threshold: Option<NonNegativeAmount>) -> Self {
|
||||
Self {
|
||||
action,
|
||||
dust_threshold,
|
||||
|
@ -170,7 +175,7 @@ impl DustOutputPolicy {
|
|||
}
|
||||
/// Returns a value that will be used to override the dust determination logic of the
|
||||
/// change policy, if any.
|
||||
pub fn dust_threshold(&self) -> Option<Amount> {
|
||||
pub fn dust_threshold(&self) -> Option<NonNegativeAmount> {
|
||||
self.dust_threshold
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use zcash_primitives::{
|
|||
consensus::{self, BlockHeight},
|
||||
transaction::{
|
||||
components::{
|
||||
amount::{Amount, BalanceError},
|
||||
amount::{Amount, BalanceError, NonNegativeAmount},
|
||||
sapling::fees as sapling,
|
||||
transparent::fees as transparent,
|
||||
},
|
||||
|
@ -83,7 +83,9 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
|||
)
|
||||
.unwrap(); // fixed::FeeRule::fee_required is infallible.
|
||||
|
||||
let total_in = (t_in + sapling_in).ok_or(BalanceError::Overflow)?;
|
||||
let total_in = (t_in + sapling_in)
|
||||
.and_then(|v| NonNegativeAmount::try_from(v).ok())
|
||||
.ok_or(BalanceError::Overflow)?;
|
||||
|
||||
if (!transparent_inputs.is_empty() || !sapling_inputs.is_empty()) && fee_amount > total_in {
|
||||
// For the fixed-fee selection rule, the only time we consider inputs dust is when the fee
|
||||
|
@ -101,20 +103,22 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
|||
.collect(),
|
||||
})
|
||||
} else {
|
||||
let total_out = [t_out, sapling_out, fee_amount]
|
||||
let total_out = [t_out, sapling_out, fee_amount.into()]
|
||||
.iter()
|
||||
.sum::<Option<Amount>>()
|
||||
.ok_or(BalanceError::Overflow)?;
|
||||
|
||||
let proposed_change = (total_in - total_out).ok_or(BalanceError::Underflow)?;
|
||||
let overflow = |_| ChangeError::StrategyError(BalanceError::Overflow);
|
||||
let proposed_change =
|
||||
(Amount::from(total_in) - total_out).ok_or(BalanceError::Underflow)?;
|
||||
match proposed_change.cmp(&Amount::zero()) {
|
||||
Ordering::Less => Err(ChangeError::InsufficientFunds {
|
||||
available: total_in,
|
||||
available: total_in.into(),
|
||||
required: total_out,
|
||||
}),
|
||||
Ordering::Equal => TransactionBalance::new(vec![], fee_amount)
|
||||
.ok_or_else(|| BalanceError::Overflow.into()),
|
||||
Ordering::Equal => TransactionBalance::new(vec![], fee_amount).map_err(overflow),
|
||||
Ordering::Greater => {
|
||||
let proposed_change = NonNegativeAmount::try_from(proposed_change).unwrap();
|
||||
let dust_threshold = dust_output_policy
|
||||
.dust_threshold()
|
||||
.unwrap_or_else(|| self.fee_rule.fixed_fee());
|
||||
|
@ -125,28 +129,29 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
|||
let shortfall = (dust_threshold - proposed_change)
|
||||
.ok_or(BalanceError::Underflow)?;
|
||||
Err(ChangeError::InsufficientFunds {
|
||||
available: total_in,
|
||||
available: total_in.into(),
|
||||
required: (total_in + shortfall)
|
||||
.ok_or(BalanceError::Overflow)?,
|
||||
.ok_or(BalanceError::Overflow)?
|
||||
.into(),
|
||||
})
|
||||
}
|
||||
DustAction::AllowDustChange => TransactionBalance::new(
|
||||
vec![ChangeValue::Sapling(proposed_change)],
|
||||
fee_amount,
|
||||
)
|
||||
.ok_or_else(|| BalanceError::Overflow.into()),
|
||||
.map_err(overflow),
|
||||
DustAction::AddDustToFee => TransactionBalance::new(
|
||||
vec![],
|
||||
(fee_amount + proposed_change).unwrap(),
|
||||
(fee_amount + proposed_change).ok_or(BalanceError::Overflow)?,
|
||||
)
|
||||
.ok_or_else(|| BalanceError::Overflow.into()),
|
||||
.map_err(overflow),
|
||||
}
|
||||
} else {
|
||||
TransactionBalance::new(
|
||||
vec![ChangeValue::Sapling(proposed_change)],
|
||||
fee_amount,
|
||||
)
|
||||
.ok_or_else(|| BalanceError::Overflow.into())
|
||||
.map_err(overflow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -159,7 +164,10 @@ mod tests {
|
|||
use zcash_primitives::{
|
||||
consensus::{Network, NetworkUpgrade, Parameters},
|
||||
transaction::{
|
||||
components::{amount::Amount, transparent::TxOut},
|
||||
components::{
|
||||
amount::{Amount, NonNegativeAmount},
|
||||
transparent::TxOut,
|
||||
},
|
||||
fees::fixed::FeeRule as FixedFeeRule,
|
||||
},
|
||||
};
|
||||
|
@ -197,8 +205,8 @@ mod tests {
|
|||
|
||||
assert_matches!(
|
||||
result,
|
||||
Ok(balance) if balance.proposed_change() == [ChangeValue::Sapling(Amount::from_u64(10000).unwrap())]
|
||||
&& balance.fee_required() == Amount::from_u64(10000).unwrap()
|
||||
Ok(balance) if balance.proposed_change() == [ChangeValue::Sapling(NonNegativeAmount::const_from_u64(10000))]
|
||||
&& balance.fee_required() == NonNegativeAmount::const_from_u64(10000)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ use zcash_primitives::{
|
|||
consensus::{self, BlockHeight},
|
||||
transaction::{
|
||||
components::{
|
||||
amount::{Amount, BalanceError},
|
||||
amount::{Amount, BalanceError, NonNegativeAmount},
|
||||
sapling::fees as sapling,
|
||||
transparent::fees as transparent,
|
||||
},
|
||||
|
@ -61,7 +61,7 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
|||
.filter_map(|i| {
|
||||
// for now, we're just assuming p2pkh inputs, so we don't check the size of the input
|
||||
// script
|
||||
if i.coin().value < self.fee_rule.marginal_fee() {
|
||||
if i.coin().value < self.fee_rule.marginal_fee().into() {
|
||||
Some(i.outpoint().clone())
|
||||
} else {
|
||||
None
|
||||
|
@ -72,7 +72,7 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
|||
let mut sapling_dust: Vec<_> = sapling_inputs
|
||||
.iter()
|
||||
.filter_map(|i| {
|
||||
if i.value() < self.fee_rule.marginal_fee() {
|
||||
if i.value() < self.fee_rule.marginal_fee().into() {
|
||||
Some(i.note_id().clone())
|
||||
} else {
|
||||
None
|
||||
|
@ -174,7 +174,7 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
|||
|
||||
let total_in = (t_in + sapling_in).ok_or_else(overflow)?;
|
||||
|
||||
let total_out = [t_out, sapling_out, fee_amount]
|
||||
let total_out = [t_out, sapling_out, fee_amount.into()]
|
||||
.iter()
|
||||
.sum::<Option<Amount>>()
|
||||
.ok_or_else(overflow)?;
|
||||
|
@ -185,8 +185,9 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
|||
available: total_in,
|
||||
required: total_out,
|
||||
}),
|
||||
Ordering::Equal => TransactionBalance::new(vec![], fee_amount).ok_or_else(overflow),
|
||||
Ordering::Equal => TransactionBalance::new(vec![], fee_amount).map_err(|_| overflow()),
|
||||
Ordering::Greater => {
|
||||
let proposed_change = NonNegativeAmount::try_from(proposed_change).unwrap();
|
||||
let dust_threshold = dust_output_policy
|
||||
.dust_threshold()
|
||||
.unwrap_or_else(|| self.fee_rule.marginal_fee());
|
||||
|
@ -199,22 +200,23 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
|||
|
||||
Err(ChangeError::InsufficientFunds {
|
||||
available: total_in,
|
||||
required: (total_in + shortfall).ok_or_else(overflow)?,
|
||||
required: (total_in + shortfall.into()).ok_or_else(overflow)?,
|
||||
})
|
||||
}
|
||||
DustAction::AllowDustChange => TransactionBalance::new(
|
||||
vec![ChangeValue::Sapling(proposed_change)],
|
||||
fee_amount,
|
||||
)
|
||||
.ok_or_else(overflow),
|
||||
DustAction::AddDustToFee => {
|
||||
TransactionBalance::new(vec![], (fee_amount + proposed_change).unwrap())
|
||||
.ok_or_else(overflow)
|
||||
}
|
||||
.map_err(|_| overflow()),
|
||||
DustAction::AddDustToFee => TransactionBalance::new(
|
||||
vec![],
|
||||
(fee_amount + proposed_change).ok_or_else(overflow)?,
|
||||
)
|
||||
.map_err(|_| overflow()),
|
||||
}
|
||||
} else {
|
||||
TransactionBalance::new(vec![ChangeValue::Sapling(proposed_change)], fee_amount)
|
||||
.ok_or_else(overflow)
|
||||
.map_err(|_| overflow())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -228,7 +230,10 @@ mod tests {
|
|||
consensus::{Network, NetworkUpgrade, Parameters},
|
||||
legacy::Script,
|
||||
transaction::{
|
||||
components::{amount::Amount, transparent::TxOut},
|
||||
components::{
|
||||
amount::{Amount, NonNegativeAmount},
|
||||
transparent::TxOut,
|
||||
},
|
||||
fees::zip317::FeeRule as Zip317FeeRule,
|
||||
},
|
||||
};
|
||||
|
@ -264,8 +269,8 @@ mod tests {
|
|||
|
||||
assert_matches!(
|
||||
result,
|
||||
Ok(balance) if balance.proposed_change() == [ChangeValue::Sapling(Amount::from_u64(5000).unwrap())]
|
||||
&& balance.fee_required() == Amount::from_u64(10000).unwrap()
|
||||
Ok(balance) if balance.proposed_change() == [ChangeValue::Sapling(NonNegativeAmount::const_from_u64(5000))]
|
||||
&& balance.fee_required() == NonNegativeAmount::const_from_u64(10000)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -295,7 +300,7 @@ mod tests {
|
|||
assert_matches!(
|
||||
result,
|
||||
Ok(balance) if balance.proposed_change().is_empty()
|
||||
&& balance.fee_required() == Amount::from_u64(15000).unwrap()
|
||||
&& balance.fee_required() == NonNegativeAmount::const_from_u64(15000)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -328,7 +333,7 @@ mod tests {
|
|||
assert_matches!(
|
||||
result,
|
||||
Ok(balance) if balance.proposed_change().is_empty()
|
||||
&& balance.fee_required() == Amount::from_u64(10000).unwrap()
|
||||
&& balance.fee_required() == NonNegativeAmount::const_from_u64(10000)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -580,7 +580,7 @@ mod tests {
|
|||
value::NoteValue,
|
||||
Nullifier, SaplingIvk,
|
||||
},
|
||||
transaction::components::Amount,
|
||||
transaction::components::amount::NonNegativeAmount,
|
||||
zip32::{AccountId, DiversifiableFullViewingKey, ExtendedSpendingKey},
|
||||
};
|
||||
|
||||
|
@ -637,7 +637,7 @@ mod tests {
|
|||
prev_hash: BlockHash,
|
||||
nf: Nullifier,
|
||||
dfvk: &DiversifiableFullViewingKey,
|
||||
value: Amount,
|
||||
value: NonNegativeAmount,
|
||||
tx_after: bool,
|
||||
initial_sapling_tree_size: Option<u32>,
|
||||
) -> CompactBlock {
|
||||
|
@ -646,7 +646,7 @@ mod tests {
|
|||
// Create a fake Note for the account
|
||||
let mut rng = OsRng;
|
||||
let rseed = generate_random_rseed(&Network::TestNetwork, height, &mut rng);
|
||||
let note = sapling::Note::from_parts(to, NoteValue::from_raw(value.into()), rseed);
|
||||
let note = sapling::Note::from_parts(to, NoteValue::from(value), rseed);
|
||||
let encryptor = sapling_note_encryption::<_, Network>(
|
||||
Some(dfvk.fvk().ovk),
|
||||
note.clone(),
|
||||
|
@ -724,7 +724,7 @@ mod tests {
|
|||
BlockHash([0; 32]),
|
||||
Nullifier([0; 32]),
|
||||
&dfvk,
|
||||
Amount::from_u64(5).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(5),
|
||||
false,
|
||||
None,
|
||||
);
|
||||
|
@ -808,7 +808,7 @@ mod tests {
|
|||
BlockHash([0; 32]),
|
||||
Nullifier([0; 32]),
|
||||
&dfvk,
|
||||
Amount::from_u64(5).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(5),
|
||||
true,
|
||||
Some(0),
|
||||
);
|
||||
|
@ -884,7 +884,7 @@ mod tests {
|
|||
BlockHash([0; 32]),
|
||||
nf,
|
||||
&dfvk,
|
||||
Amount::from_u64(5).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(5),
|
||||
false,
|
||||
Some(0),
|
||||
);
|
||||
|
|
|
@ -177,11 +177,58 @@ impl<N> WalletSaplingOutput<N> {
|
|||
/// with sufficient information for use in note selection.
|
||||
#[derive(Debug)]
|
||||
pub struct ReceivedSaplingNote<NoteRef> {
|
||||
pub note_id: NoteRef,
|
||||
pub diversifier: sapling::Diversifier,
|
||||
pub note_value: Amount,
|
||||
pub rseed: sapling::Rseed,
|
||||
pub note_commitment_tree_position: Position,
|
||||
note_id: NoteRef,
|
||||
txid: TxId,
|
||||
output_index: u16,
|
||||
diversifier: sapling::Diversifier,
|
||||
note_value: Amount,
|
||||
rseed: sapling::Rseed,
|
||||
note_commitment_tree_position: Position,
|
||||
}
|
||||
|
||||
impl<NoteRef> ReceivedSaplingNote<NoteRef> {
|
||||
pub fn from_parts(
|
||||
note_id: NoteRef,
|
||||
txid: TxId,
|
||||
output_index: u16,
|
||||
diversifier: sapling::Diversifier,
|
||||
note_value: Amount,
|
||||
rseed: sapling::Rseed,
|
||||
note_commitment_tree_position: Position,
|
||||
) -> Self {
|
||||
ReceivedSaplingNote {
|
||||
note_id,
|
||||
txid,
|
||||
output_index,
|
||||
diversifier,
|
||||
note_value,
|
||||
rseed,
|
||||
note_commitment_tree_position,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn internal_note_id(&self) -> &NoteRef {
|
||||
&self.note_id
|
||||
}
|
||||
|
||||
pub fn txid(&self) -> &TxId {
|
||||
&self.txid
|
||||
}
|
||||
pub fn output_index(&self) -> u16 {
|
||||
self.output_index
|
||||
}
|
||||
pub fn diversifier(&self) -> sapling::Diversifier {
|
||||
self.diversifier
|
||||
}
|
||||
pub fn value(&self) -> Amount {
|
||||
self.note_value
|
||||
}
|
||||
pub fn rseed(&self) -> sapling::Rseed {
|
||||
self.rseed
|
||||
}
|
||||
pub fn note_commitment_tree_position(&self) -> Position {
|
||||
self.note_commitment_tree_position
|
||||
}
|
||||
}
|
||||
|
||||
impl<NoteRef> sapling_fees::InputView<NoteRef> for ReceivedSaplingNote<NoteRef> {
|
||||
|
|
|
@ -367,7 +367,7 @@ mod tests {
|
|||
let (h1, _, _) = st.generate_next_block(
|
||||
&dfvk,
|
||||
AddressType::DefaultExternal,
|
||||
Amount::from_u64(5).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(5),
|
||||
);
|
||||
|
||||
// Scan the cache
|
||||
|
@ -377,7 +377,7 @@ mod tests {
|
|||
let (h2, _, _) = st.generate_next_block(
|
||||
&dfvk,
|
||||
AddressType::DefaultExternal,
|
||||
Amount::from_u64(7).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(7),
|
||||
);
|
||||
|
||||
// Scanning should detect no inconsistencies
|
||||
|
@ -397,12 +397,12 @@ mod tests {
|
|||
let (h, _, _) = st.generate_next_block(
|
||||
&dfvk,
|
||||
AddressType::DefaultExternal,
|
||||
Amount::from_u64(5).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(5),
|
||||
);
|
||||
let (last_contiguous_height, _, _) = st.generate_next_block(
|
||||
&dfvk,
|
||||
AddressType::DefaultExternal,
|
||||
Amount::from_u64(7).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(7),
|
||||
);
|
||||
|
||||
// Scanning the cache should find no inconsistencies
|
||||
|
@ -415,13 +415,13 @@ mod tests {
|
|||
BlockHash([1; 32]),
|
||||
&dfvk,
|
||||
AddressType::DefaultExternal,
|
||||
Amount::from_u64(8).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(8),
|
||||
2,
|
||||
);
|
||||
st.generate_next_block(
|
||||
&dfvk,
|
||||
AddressType::DefaultExternal,
|
||||
Amount::from_u64(3).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(3),
|
||||
);
|
||||
|
||||
// Data+cache chain should be invalid at the data/cache boundary
|
||||
|
@ -448,10 +448,10 @@ mod tests {
|
|||
assert_eq!(st.get_wallet_summary(0), None);
|
||||
|
||||
// Create fake CompactBlocks sending value to the address
|
||||
let value = NonNegativeAmount::from_u64(5).unwrap();
|
||||
let value2 = NonNegativeAmount::from_u64(7).unwrap();
|
||||
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value.into());
|
||||
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value2.into());
|
||||
let value = NonNegativeAmount::const_from_u64(5);
|
||||
let value2 = NonNegativeAmount::const_from_u64(7);
|
||||
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value2);
|
||||
|
||||
// Scan the cache
|
||||
st.scan_cached_blocks(h, 2);
|
||||
|
@ -502,14 +502,14 @@ mod tests {
|
|||
let dfvk = st.test_account_sapling().unwrap();
|
||||
|
||||
// Create a block with height SAPLING_ACTIVATION_HEIGHT
|
||||
let value = NonNegativeAmount::from_u64(50000).unwrap();
|
||||
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value.into());
|
||||
let value = NonNegativeAmount::const_from_u64(50000);
|
||||
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||
st.scan_cached_blocks(h1, 1);
|
||||
assert_eq!(st.get_total_balance(AccountId::from(0)), value);
|
||||
|
||||
// Create blocks to reach SAPLING_ACTIVATION_HEIGHT + 2
|
||||
let (h2, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value.into());
|
||||
let (h3, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value.into());
|
||||
let (h2, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||
let (h3, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||
|
||||
// Scan the later block first
|
||||
st.scan_cached_blocks(h3, 1);
|
||||
|
@ -560,8 +560,8 @@ mod tests {
|
|||
assert_eq!(st.get_wallet_summary(0), None);
|
||||
|
||||
// Create a fake CompactBlock sending value to the address
|
||||
let value = NonNegativeAmount::from_u64(5).unwrap();
|
||||
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value.into());
|
||||
let value = NonNegativeAmount::const_from_u64(5);
|
||||
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||
|
||||
// Scan the cache
|
||||
let summary = st.scan_cached_blocks(h1, 1);
|
||||
|
@ -573,8 +573,8 @@ mod tests {
|
|||
assert_eq!(st.get_total_balance(AccountId::from(0)), value);
|
||||
|
||||
// Create a second fake CompactBlock sending more value to the address
|
||||
let value2 = NonNegativeAmount::from_u64(7).unwrap();
|
||||
let (h2, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value2.into());
|
||||
let value2 = NonNegativeAmount::const_from_u64(7);
|
||||
let (h2, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value2);
|
||||
|
||||
// Scan the cache again
|
||||
let summary = st.scan_cached_blocks(h2, 1);
|
||||
|
@ -601,9 +601,9 @@ mod tests {
|
|||
assert_eq!(st.get_wallet_summary(0), None);
|
||||
|
||||
// Create a fake CompactBlock sending value to the address
|
||||
let value = NonNegativeAmount::from_u64(5).unwrap();
|
||||
let value = NonNegativeAmount::const_from_u64(5);
|
||||
let (received_height, _, nf) =
|
||||
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value.into());
|
||||
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||
|
||||
// Scan the cache
|
||||
st.scan_cached_blocks(received_height, 1);
|
||||
|
@ -614,9 +614,8 @@ mod tests {
|
|||
// Create a second fake CompactBlock spending value from the address
|
||||
let extsk2 = ExtendedSpendingKey::master(&[0]);
|
||||
let to2 = extsk2.default_address().1;
|
||||
let value2 = NonNegativeAmount::from_u64(2).unwrap();
|
||||
let (spent_height, _) =
|
||||
st.generate_next_block_spending(&dfvk, (nf, value.into()), to2, value2.into());
|
||||
let value2 = NonNegativeAmount::const_from_u64(2);
|
||||
let (spent_height, _) = st.generate_next_block_spending(&dfvk, (nf, value), to2, value2);
|
||||
|
||||
// Scan the cache again
|
||||
st.scan_cached_blocks(spent_height, 1);
|
||||
|
@ -641,16 +640,15 @@ mod tests {
|
|||
assert_eq!(st.get_wallet_summary(0), None);
|
||||
|
||||
// Create a fake CompactBlock sending value to the address
|
||||
let value = NonNegativeAmount::from_u64(5).unwrap();
|
||||
let value = NonNegativeAmount::const_from_u64(5);
|
||||
let (received_height, _, nf) =
|
||||
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value.into());
|
||||
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||
|
||||
// Create a second fake CompactBlock spending value from the address
|
||||
let extsk2 = ExtendedSpendingKey::master(&[0]);
|
||||
let to2 = extsk2.default_address().1;
|
||||
let value2 = NonNegativeAmount::from_u64(2).unwrap();
|
||||
let (spent_height, _) =
|
||||
st.generate_next_block_spending(&dfvk, (nf, value.into()), to2, value2.into());
|
||||
let value2 = NonNegativeAmount::const_from_u64(2);
|
||||
let (spent_height, _) = st.generate_next_block_spending(&dfvk, (nf, value), to2, value2);
|
||||
|
||||
// Scan the spending block first.
|
||||
st.scan_cached_blocks(spent_height, 1);
|
||||
|
|
|
@ -1135,7 +1135,9 @@ mod tests {
|
|||
use {
|
||||
crate::testing::AddressType,
|
||||
zcash_client_backend::keys::sapling,
|
||||
zcash_primitives::{consensus::Parameters, transaction::components::Amount},
|
||||
zcash_primitives::{
|
||||
consensus::Parameters, transaction::components::amount::NonNegativeAmount,
|
||||
},
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
@ -1194,12 +1196,12 @@ mod tests {
|
|||
let (h1, meta1, _) = st.generate_next_block(
|
||||
&dfvk,
|
||||
AddressType::DefaultExternal,
|
||||
Amount::from_u64(5).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(5),
|
||||
);
|
||||
let (h2, meta2, _) = st.generate_next_block(
|
||||
&dfvk,
|
||||
AddressType::DefaultExternal,
|
||||
Amount::from_u64(10).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(10),
|
||||
);
|
||||
|
||||
// The BlockMeta DB is not updated until we do so explicitly.
|
||||
|
|
|
@ -48,10 +48,7 @@ use zcash_primitives::{
|
|||
Note, Nullifier, PaymentAddress,
|
||||
},
|
||||
transaction::{
|
||||
components::{
|
||||
amount::{BalanceError, NonNegativeAmount},
|
||||
Amount,
|
||||
},
|
||||
components::amount::{BalanceError, NonNegativeAmount},
|
||||
fees::FeeRule,
|
||||
Transaction, TxId,
|
||||
},
|
||||
|
@ -181,7 +178,7 @@ where
|
|||
&mut self,
|
||||
dfvk: &DiversifiableFullViewingKey,
|
||||
req: AddressType,
|
||||
value: Amount,
|
||||
value: NonNegativeAmount,
|
||||
) -> (BlockHeight, Cache::InsertResult, Nullifier) {
|
||||
let (height, prev_hash, initial_sapling_tree_size) = self
|
||||
.latest_cached_block
|
||||
|
@ -211,7 +208,7 @@ where
|
|||
prev_hash: BlockHash,
|
||||
dfvk: &DiversifiableFullViewingKey,
|
||||
req: AddressType,
|
||||
value: Amount,
|
||||
value: NonNegativeAmount,
|
||||
initial_sapling_tree_size: u32,
|
||||
) -> (Cache::InsertResult, Nullifier) {
|
||||
let (cb, nf) = fake_compact_block(
|
||||
|
@ -240,9 +237,9 @@ where
|
|||
pub(crate) fn generate_next_block_spending(
|
||||
&mut self,
|
||||
dfvk: &DiversifiableFullViewingKey,
|
||||
note: (Nullifier, Amount),
|
||||
note: (Nullifier, NonNegativeAmount),
|
||||
to: PaymentAddress,
|
||||
value: Amount,
|
||||
value: NonNegativeAmount,
|
||||
) -> (BlockHeight, Cache::InsertResult) {
|
||||
let (height, prev_hash, initial_sapling_tree_size) = self
|
||||
.latest_cached_block
|
||||
|
@ -434,7 +431,7 @@ impl<Cache> TestState<Cache> {
|
|||
&mut self,
|
||||
usk: &UnifiedSpendingKey,
|
||||
to: &RecipientAddress,
|
||||
amount: Amount,
|
||||
amount: NonNegativeAmount,
|
||||
memo: Option<MemoBytes>,
|
||||
ovk_policy: OvkPolicy,
|
||||
min_confirmations: NonZeroU32,
|
||||
|
@ -445,7 +442,6 @@ impl<Cache> TestState<Cache> {
|
|||
commitment_tree::Error,
|
||||
GreedyInputSelectorError<BalanceError, ReceivedNoteId>,
|
||||
Infallible,
|
||||
ReceivedNoteId,
|
||||
>,
|
||||
> {
|
||||
let params = self.network();
|
||||
|
@ -478,7 +474,6 @@ impl<Cache> TestState<Cache> {
|
|||
commitment_tree::Error,
|
||||
InputsT::Error,
|
||||
<InputsT::FeeRule as FeeRule>::Error,
|
||||
ReceivedNoteId,
|
||||
>,
|
||||
>
|
||||
where
|
||||
|
@ -512,7 +507,6 @@ impl<Cache> TestState<Cache> {
|
|||
Infallible,
|
||||
InputsT::Error,
|
||||
<InputsT::FeeRule as FeeRule>::Error,
|
||||
ReceivedNoteId,
|
||||
>,
|
||||
>
|
||||
where
|
||||
|
@ -546,7 +540,6 @@ impl<Cache> TestState<Cache> {
|
|||
Infallible,
|
||||
InputsT::Error,
|
||||
<InputsT::FeeRule as FeeRule>::Error,
|
||||
ReceivedNoteId,
|
||||
>,
|
||||
>
|
||||
where
|
||||
|
@ -578,7 +571,6 @@ impl<Cache> TestState<Cache> {
|
|||
commitment_tree::Error,
|
||||
Infallible,
|
||||
FeeRuleT::Error,
|
||||
ReceivedNoteId,
|
||||
>,
|
||||
>
|
||||
where
|
||||
|
@ -615,7 +607,6 @@ impl<Cache> TestState<Cache> {
|
|||
commitment_tree::Error,
|
||||
InputsT::Error,
|
||||
<InputsT::FeeRule as FeeRule>::Error,
|
||||
ReceivedNoteId,
|
||||
>,
|
||||
>
|
||||
where
|
||||
|
@ -706,7 +697,7 @@ pub(crate) fn fake_compact_block<P: consensus::Parameters>(
|
|||
prev_hash: BlockHash,
|
||||
dfvk: &DiversifiableFullViewingKey,
|
||||
req: AddressType,
|
||||
value: Amount,
|
||||
value: NonNegativeAmount,
|
||||
initial_sapling_tree_size: u32,
|
||||
) -> (CompactBlock, Nullifier) {
|
||||
let to = match req {
|
||||
|
@ -718,7 +709,7 @@ pub(crate) fn fake_compact_block<P: consensus::Parameters>(
|
|||
// Create a fake Note for the account
|
||||
let mut rng = OsRng;
|
||||
let rseed = generate_random_rseed(params, height, &mut rng);
|
||||
let note = Note::from_parts(to, NoteValue::from_raw(value.into()), rseed);
|
||||
let note = Note::from_parts(to, NoteValue::from(value), rseed);
|
||||
let encryptor = sapling_note_encryption::<_, Network>(
|
||||
Some(dfvk.fvk().ovk),
|
||||
note.clone(),
|
||||
|
@ -807,10 +798,10 @@ pub(crate) fn fake_compact_block_spending<P: consensus::Parameters>(
|
|||
params: &P,
|
||||
height: BlockHeight,
|
||||
prev_hash: BlockHash,
|
||||
(nf, in_value): (Nullifier, Amount),
|
||||
(nf, in_value): (Nullifier, NonNegativeAmount),
|
||||
dfvk: &DiversifiableFullViewingKey,
|
||||
to: PaymentAddress,
|
||||
value: Amount,
|
||||
value: NonNegativeAmount,
|
||||
initial_sapling_tree_size: u32,
|
||||
) -> CompactBlock {
|
||||
let mut rng = OsRng;
|
||||
|
@ -826,7 +817,7 @@ pub(crate) fn fake_compact_block_spending<P: consensus::Parameters>(
|
|||
|
||||
// Create a fake Note for the payment
|
||||
ctx.outputs.push({
|
||||
let note = Note::from_parts(to, NoteValue::from_raw(value.into()), rseed);
|
||||
let note = Note::from_parts(to, NoteValue::from(value), rseed);
|
||||
let encryptor = sapling_note_encryption::<_, Network>(
|
||||
Some(dfvk.fvk().ovk),
|
||||
note.clone(),
|
||||
|
@ -852,7 +843,7 @@ pub(crate) fn fake_compact_block_spending<P: consensus::Parameters>(
|
|||
let rseed = generate_random_rseed(params, height, &mut rng);
|
||||
let note = Note::from_parts(
|
||||
change_addr,
|
||||
NoteValue::from_raw((in_value - value).unwrap().into()),
|
||||
NoteValue::from((in_value - value).unwrap()),
|
||||
rseed,
|
||||
);
|
||||
let encryptor = sapling_note_encryption::<_, Network>(
|
||||
|
|
|
@ -2100,7 +2100,7 @@ mod tests {
|
|||
|
||||
// Initialize the wallet with chain data that has no shielded notes for us.
|
||||
let not_our_key = ExtendedSpendingKey::master(&[]).to_diversifiable_full_viewing_key();
|
||||
let not_our_value = Amount::const_from_i64(10000);
|
||||
let not_our_value = NonNegativeAmount::const_from_u64(10000);
|
||||
let (start_height, _, _) =
|
||||
st.generate_next_block(¬_our_key, AddressType::DefaultExternal, not_our_value);
|
||||
for _ in 1..10 {
|
||||
|
@ -2166,7 +2166,9 @@ mod tests {
|
|||
|
||||
// Shield the output.
|
||||
let input_selector = GreedyInputSelector::new(
|
||||
fixed::SingleOutputChangeStrategy::new(FixedFeeRule::non_standard(Amount::zero())),
|
||||
fixed::SingleOutputChangeStrategy::new(FixedFeeRule::non_standard(
|
||||
NonNegativeAmount::ZERO,
|
||||
)),
|
||||
DustOutputPolicy::default(),
|
||||
);
|
||||
let txid = st
|
||||
|
|
|
@ -9,7 +9,7 @@ use zcash_primitives::{
|
|||
consensus::BlockHeight,
|
||||
memo::MemoBytes,
|
||||
sapling::{self, Diversifier, Note, Nullifier, Rseed},
|
||||
transaction::components::Amount,
|
||||
transaction::{components::Amount, TxId},
|
||||
zip32::AccountId,
|
||||
};
|
||||
|
||||
|
@ -83,8 +83,10 @@ impl ReceivedSaplingOutput for DecryptedOutput<Note> {
|
|||
|
||||
fn to_spendable_note(row: &Row) -> Result<ReceivedSaplingNote<ReceivedNoteId>, SqliteClientError> {
|
||||
let note_id = ReceivedNoteId(row.get(0)?);
|
||||
let txid = row.get::<_, [u8; 32]>(1).map(TxId::from_bytes)?;
|
||||
let output_index = row.get(2)?;
|
||||
let diversifier = {
|
||||
let d: Vec<_> = row.get(1)?;
|
||||
let d: Vec<_> = row.get(3)?;
|
||||
if d.len() != 11 {
|
||||
return Err(SqliteClientError::CorruptedData(
|
||||
"Invalid diversifier length".to_string(),
|
||||
|
@ -95,10 +97,10 @@ fn to_spendable_note(row: &Row) -> Result<ReceivedSaplingNote<ReceivedNoteId>, S
|
|||
Diversifier(tmp)
|
||||
};
|
||||
|
||||
let note_value = Amount::from_i64(row.get(2)?).unwrap();
|
||||
let note_value = Amount::from_i64(row.get(4)?).unwrap();
|
||||
|
||||
let rseed = {
|
||||
let rcm_bytes: Vec<_> = row.get(3)?;
|
||||
let rcm_bytes: Vec<_> = row.get(5)?;
|
||||
|
||||
// We store rcm directly in the data DB, regardless of whether the note
|
||||
// used a v1 or v2 note plaintext, so for the purposes of spending let's
|
||||
|
@ -113,17 +115,19 @@ fn to_spendable_note(row: &Row) -> Result<ReceivedSaplingNote<ReceivedNoteId>, S
|
|||
};
|
||||
|
||||
let note_commitment_tree_position =
|
||||
Position::from(u64::try_from(row.get::<_, i64>(4)?).map_err(|_| {
|
||||
Position::from(u64::try_from(row.get::<_, i64>(6)?).map_err(|_| {
|
||||
SqliteClientError::CorruptedData("Note commitment tree position invalid.".to_string())
|
||||
})?);
|
||||
|
||||
Ok(ReceivedSaplingNote {
|
||||
Ok(ReceivedSaplingNote::from_parts(
|
||||
note_id,
|
||||
txid,
|
||||
output_index,
|
||||
diversifier,
|
||||
note_value,
|
||||
rseed,
|
||||
note_commitment_tree_position,
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
/// Utility method for determining whether we have any spendable notes
|
||||
|
@ -170,7 +174,7 @@ pub(crate) fn get_spendable_sapling_notes(
|
|||
}
|
||||
|
||||
let mut stmt_select_notes = conn.prepare_cached(
|
||||
"SELECT id_note, diversifier, value, rcm, commitment_tree_position
|
||||
"SELECT id_note, txid, output_index, diversifier, value, rcm, commitment_tree_position
|
||||
FROM sapling_received_notes
|
||||
INNER JOIN transactions ON transactions.id_tx = sapling_received_notes.tx
|
||||
WHERE account = :account
|
||||
|
@ -244,7 +248,7 @@ pub(crate) fn select_spendable_sapling_notes(
|
|||
// 4) Match the selected notes against the witnesses at the desired height.
|
||||
let mut stmt_select_notes = conn.prepare_cached(
|
||||
"WITH eligible AS (
|
||||
SELECT id_note, diversifier, value, rcm, commitment_tree_position,
|
||||
SELECT id_note, txid, output_index, diversifier, value, rcm, commitment_tree_position,
|
||||
SUM(value)
|
||||
OVER (PARTITION BY account, spent ORDER BY id_note) AS so_far
|
||||
FROM sapling_received_notes
|
||||
|
@ -266,10 +270,10 @@ pub(crate) fn select_spendable_sapling_notes(
|
|||
AND unscanned.block_range_end > :wallet_birthday
|
||||
)
|
||||
)
|
||||
SELECT id_note, diversifier, value, rcm, commitment_tree_position
|
||||
SELECT id_note, txid, output_index, diversifier, value, rcm, commitment_tree_position
|
||||
FROM eligible WHERE so_far < :target_value
|
||||
UNION
|
||||
SELECT id_note, diversifier, value, rcm, commitment_tree_position
|
||||
SELECT id_note, txid, output_index, diversifier, value, rcm, commitment_tree_position
|
||||
FROM (SELECT * from eligible WHERE so_far >= :target_value LIMIT 1)",
|
||||
)?;
|
||||
|
||||
|
@ -510,8 +514,8 @@ pub(crate) mod tests {
|
|||
let dfvk = st.test_account_sapling().unwrap();
|
||||
|
||||
// Add funds to the wallet in a single note
|
||||
let value = NonNegativeAmount::from_u64(60000).unwrap();
|
||||
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value.into());
|
||||
let value = NonNegativeAmount::const_from_u64(60000);
|
||||
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||
st.scan_cached_blocks(h, 1);
|
||||
|
||||
// Spendable balance matches total balance
|
||||
|
@ -530,7 +534,7 @@ pub(crate) mod tests {
|
|||
let to: RecipientAddress = to_extsk.default_address().1.into();
|
||||
let request = zip321::TransactionRequest::new(vec![Payment {
|
||||
recipient_address: to,
|
||||
amount: Amount::from_u64(10000).unwrap(),
|
||||
amount: Amount::const_from_i64(10000),
|
||||
memo: None, // this should result in the creation of an empty memo
|
||||
label: None,
|
||||
message: None,
|
||||
|
@ -662,7 +666,7 @@ pub(crate) mod tests {
|
|||
st.create_spend_to_address(
|
||||
&usk1,
|
||||
&to,
|
||||
Amount::from_u64(1).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(1),
|
||||
None,
|
||||
OvkPolicy::Sender,
|
||||
NonZeroU32::new(1).unwrap(),
|
||||
|
@ -689,7 +693,7 @@ pub(crate) mod tests {
|
|||
st.create_spend_to_address(
|
||||
&usk,
|
||||
&to,
|
||||
Amount::from_u64(1).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(1),
|
||||
None,
|
||||
OvkPolicy::Sender,
|
||||
NonZeroU32::new(1).unwrap(),
|
||||
|
@ -709,8 +713,8 @@ pub(crate) mod tests {
|
|||
let dfvk = st.test_account_sapling().unwrap();
|
||||
|
||||
// Add funds to the wallet in a single note
|
||||
let value = NonNegativeAmount::from_u64(50000).unwrap();
|
||||
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value.into());
|
||||
let value = NonNegativeAmount::const_from_u64(50000);
|
||||
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||
st.scan_cached_blocks(h1, 1);
|
||||
|
||||
// Spendable balance matches total balance at 1 confirmation.
|
||||
|
@ -732,7 +736,7 @@ pub(crate) mod tests {
|
|||
);
|
||||
|
||||
// Add more funds to the wallet in a second note
|
||||
let (h2, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value.into());
|
||||
let (h2, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||
st.scan_cached_blocks(h2, 1);
|
||||
|
||||
// Verified balance does not include the second note
|
||||
|
@ -755,7 +759,7 @@ pub(crate) mod tests {
|
|||
st.create_spend_to_address(
|
||||
&usk,
|
||||
&to,
|
||||
Amount::from_u64(70000).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(70000),
|
||||
None,
|
||||
OvkPolicy::Sender,
|
||||
NonZeroU32::new(10).unwrap(),
|
||||
|
@ -764,14 +768,14 @@ pub(crate) mod tests {
|
|||
available,
|
||||
required
|
||||
})
|
||||
if available == Amount::from_u64(50000).unwrap()
|
||||
&& required == Amount::from_u64(80000).unwrap()
|
||||
if available == Amount::const_from_i64(50000)
|
||||
&& required == Amount::const_from_i64(80000)
|
||||
);
|
||||
|
||||
// Mine blocks SAPLING_ACTIVATION_HEIGHT + 2 to 9 until just before the second
|
||||
// note is verified
|
||||
for _ in 2..10 {
|
||||
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value.into());
|
||||
st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||
}
|
||||
st.scan_cached_blocks(h2 + 1, 8);
|
||||
|
||||
|
@ -783,7 +787,7 @@ pub(crate) mod tests {
|
|||
st.create_spend_to_address(
|
||||
&usk,
|
||||
&to,
|
||||
Amount::from_u64(70000).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(70000),
|
||||
None,
|
||||
OvkPolicy::Sender,
|
||||
NonZeroU32::new(10).unwrap(),
|
||||
|
@ -792,12 +796,12 @@ pub(crate) mod tests {
|
|||
available,
|
||||
required
|
||||
})
|
||||
if available == Amount::from_u64(50000).unwrap()
|
||||
&& required == Amount::from_u64(80000).unwrap()
|
||||
if available == Amount::const_from_i64(50000)
|
||||
&& required == Amount::const_from_i64(80000)
|
||||
);
|
||||
|
||||
// Mine block 11 so that the second note becomes verified
|
||||
let (h11, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value.into());
|
||||
let (h11, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||
st.scan_cached_blocks(h11, 1);
|
||||
|
||||
// Total balance is value * number of blocks scanned (11).
|
||||
|
@ -815,7 +819,7 @@ pub(crate) mod tests {
|
|||
.create_spend_to_address(
|
||||
&usk,
|
||||
&to,
|
||||
amount_sent.into(),
|
||||
amount_sent,
|
||||
None,
|
||||
OvkPolicy::Sender,
|
||||
NonZeroU32::new(10).unwrap(),
|
||||
|
@ -845,8 +849,8 @@ pub(crate) mod tests {
|
|||
let dfvk = st.test_account_sapling().unwrap();
|
||||
|
||||
// Add funds to the wallet in a single note
|
||||
let value = NonNegativeAmount::from_u64(50000).unwrap();
|
||||
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value.into());
|
||||
let value = NonNegativeAmount::const_from_u64(50000);
|
||||
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||
st.scan_cached_blocks(h1, 1);
|
||||
|
||||
// Spendable balance matches total balance at 1 confirmation.
|
||||
|
@ -860,7 +864,7 @@ pub(crate) mod tests {
|
|||
st.create_spend_to_address(
|
||||
&usk,
|
||||
&to,
|
||||
Amount::from_u64(15000).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(15000),
|
||||
None,
|
||||
OvkPolicy::Sender,
|
||||
NonZeroU32::new(1).unwrap(),
|
||||
|
@ -873,7 +877,7 @@ pub(crate) mod tests {
|
|||
st.create_spend_to_address(
|
||||
&usk,
|
||||
&to,
|
||||
Amount::from_u64(2000).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(2000),
|
||||
None,
|
||||
OvkPolicy::Sender,
|
||||
NonZeroU32::new(1).unwrap(),
|
||||
|
@ -882,7 +886,7 @@ pub(crate) mod tests {
|
|||
available,
|
||||
required
|
||||
})
|
||||
if available == Amount::zero() && required == Amount::from_u64(12000).unwrap()
|
||||
if available == Amount::zero() && required == Amount::const_from_i64(12000)
|
||||
);
|
||||
|
||||
// Mine blocks SAPLING_ACTIVATION_HEIGHT + 1 to 41 (that don't send us funds)
|
||||
|
@ -891,7 +895,7 @@ pub(crate) mod tests {
|
|||
st.generate_next_block(
|
||||
&ExtendedSpendingKey::master(&[i as u8]).to_diversifiable_full_viewing_key(),
|
||||
AddressType::DefaultExternal,
|
||||
value.into(),
|
||||
value,
|
||||
);
|
||||
}
|
||||
st.scan_cached_blocks(h1 + 1, 41);
|
||||
|
@ -901,7 +905,7 @@ pub(crate) mod tests {
|
|||
st.create_spend_to_address(
|
||||
&usk,
|
||||
&to,
|
||||
Amount::from_u64(2000).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(2000),
|
||||
None,
|
||||
OvkPolicy::Sender,
|
||||
NonZeroU32::new(1).unwrap(),
|
||||
|
@ -910,14 +914,14 @@ pub(crate) mod tests {
|
|||
available,
|
||||
required
|
||||
})
|
||||
if available == Amount::zero() && required == Amount::from_u64(12000).unwrap()
|
||||
if available == Amount::zero() && required == Amount::const_from_i64(12000)
|
||||
);
|
||||
|
||||
// Mine block SAPLING_ACTIVATION_HEIGHT + 42 so that the first transaction expires
|
||||
let (h43, _, _) = st.generate_next_block(
|
||||
&ExtendedSpendingKey::master(&[42]).to_diversifiable_full_viewing_key(),
|
||||
AddressType::DefaultExternal,
|
||||
value.into(),
|
||||
value,
|
||||
);
|
||||
st.scan_cached_blocks(h43, 1);
|
||||
|
||||
|
@ -931,7 +935,7 @@ pub(crate) mod tests {
|
|||
.create_spend_to_address(
|
||||
&usk,
|
||||
&to,
|
||||
amount_sent2.into(),
|
||||
amount_sent2,
|
||||
None,
|
||||
OvkPolicy::Sender,
|
||||
NonZeroU32::new(1).unwrap(),
|
||||
|
@ -960,8 +964,8 @@ pub(crate) mod tests {
|
|||
let dfvk = st.test_account_sapling().unwrap();
|
||||
|
||||
// Add funds to the wallet in a single note
|
||||
let value = NonNegativeAmount::from_u64(50000).unwrap();
|
||||
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value.into());
|
||||
let value = NonNegativeAmount::const_from_u64(50000);
|
||||
let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||
st.scan_cached_blocks(h1, 1);
|
||||
|
||||
// Spendable balance matches total balance at 1 confirmation.
|
||||
|
@ -982,13 +986,12 @@ pub(crate) mod tests {
|
|||
commitment_tree::Error,
|
||||
GreedyInputSelectorError<BalanceError, ReceivedNoteId>,
|
||||
Infallible,
|
||||
ReceivedNoteId,
|
||||
>,
|
||||
> {
|
||||
let txid = st.create_spend_to_address(
|
||||
&usk,
|
||||
&to,
|
||||
Amount::from_u64(15000).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(15000),
|
||||
None,
|
||||
ovk_policy,
|
||||
NonZeroU32::new(1).unwrap(),
|
||||
|
@ -1037,7 +1040,7 @@ pub(crate) mod tests {
|
|||
st.generate_next_block(
|
||||
&ExtendedSpendingKey::master(&[i as u8]).to_diversifiable_full_viewing_key(),
|
||||
AddressType::DefaultExternal,
|
||||
value.into(),
|
||||
value,
|
||||
);
|
||||
}
|
||||
st.scan_cached_blocks(h1 + 1, 42);
|
||||
|
@ -1061,8 +1064,8 @@ pub(crate) mod tests {
|
|||
let dfvk = st.test_account_sapling().unwrap();
|
||||
|
||||
// Add funds to the wallet in a single note
|
||||
let value = NonNegativeAmount::from_u64(60000).unwrap();
|
||||
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value.into());
|
||||
let value = NonNegativeAmount::const_from_u64(60000);
|
||||
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||
st.scan_cached_blocks(h, 1);
|
||||
|
||||
// Spendable balance matches total balance at 1 confirmation.
|
||||
|
@ -1075,7 +1078,7 @@ pub(crate) mod tests {
|
|||
st.create_spend_to_address(
|
||||
&usk,
|
||||
&to,
|
||||
Amount::from_u64(50000).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(50000),
|
||||
None,
|
||||
OvkPolicy::Sender,
|
||||
NonZeroU32::new(1).unwrap(),
|
||||
|
@ -1095,8 +1098,8 @@ pub(crate) mod tests {
|
|||
let dfvk = st.test_account_sapling().unwrap();
|
||||
|
||||
// Add funds to the wallet in a single note owned by the internal spending key
|
||||
let value = NonNegativeAmount::from_u64(60000).unwrap();
|
||||
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::Internal, value.into());
|
||||
let value = NonNegativeAmount::const_from_u64(60000);
|
||||
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::Internal, value);
|
||||
st.scan_cached_blocks(h, 1);
|
||||
|
||||
// Spendable balance matches total balance at 1 confirmation.
|
||||
|
@ -1116,7 +1119,7 @@ pub(crate) mod tests {
|
|||
st.create_spend_to_address(
|
||||
&usk,
|
||||
&to,
|
||||
Amount::from_u64(50000).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(50000),
|
||||
None,
|
||||
OvkPolicy::Sender,
|
||||
NonZeroU32::new(1).unwrap(),
|
||||
|
@ -1146,7 +1149,7 @@ pub(crate) mod tests {
|
|||
|
||||
// Add funds to the wallet in a single note
|
||||
let value = NonNegativeAmount::from_u64(100000).unwrap();
|
||||
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value.into());
|
||||
let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value);
|
||||
st.scan_cached_blocks(h, 1);
|
||||
|
||||
// Spendable balance matches total balance
|
||||
|
@ -1199,8 +1202,7 @@ pub(crate) mod tests {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let amount_left =
|
||||
(value - (amount_sent + fee_rule.fixed_fee().try_into().unwrap()).unwrap()).unwrap();
|
||||
let amount_left = (value - (amount_sent + fee_rule.fixed_fee()).unwrap()).unwrap();
|
||||
let pending_change = (amount_left - amount_legacy_change).unwrap();
|
||||
|
||||
// The "legacy change" is not counted by get_pending_change().
|
||||
|
@ -1258,7 +1260,7 @@ pub(crate) mod tests {
|
|||
let (h1, _, _) = st.generate_next_block(
|
||||
&dfvk,
|
||||
AddressType::Internal,
|
||||
Amount::from_u64(50000).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(50000),
|
||||
);
|
||||
|
||||
// Add 10 dust notes to the wallet
|
||||
|
@ -1266,14 +1268,14 @@ pub(crate) mod tests {
|
|||
st.generate_next_block(
|
||||
&dfvk,
|
||||
AddressType::DefaultExternal,
|
||||
Amount::from_u64(1000).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(1000),
|
||||
);
|
||||
}
|
||||
|
||||
st.scan_cached_blocks(h1, 11);
|
||||
|
||||
// Spendable balance matches total balance
|
||||
let total = NonNegativeAmount::from_u64(60000).unwrap();
|
||||
let total = NonNegativeAmount::const_from_u64(60000);
|
||||
assert_eq!(st.get_total_balance(account), total);
|
||||
assert_eq!(st.get_spendable_balance(account, 1), total);
|
||||
|
||||
|
@ -1285,7 +1287,7 @@ pub(crate) mod tests {
|
|||
// This first request will fail due to insufficient non-dust funds
|
||||
let req = TransactionRequest::new(vec![Payment {
|
||||
recipient_address: RecipientAddress::Shielded(dfvk.default_address().1),
|
||||
amount: Amount::from_u64(50000).unwrap(),
|
||||
amount: Amount::const_from_i64(50000),
|
||||
memo: None,
|
||||
label: None,
|
||||
message: None,
|
||||
|
@ -1302,15 +1304,15 @@ pub(crate) mod tests {
|
|||
NonZeroU32::new(1).unwrap(),
|
||||
),
|
||||
Err(Error::InsufficientFunds { available, required })
|
||||
if available == Amount::from_u64(51000).unwrap()
|
||||
&& required == Amount::from_u64(60000).unwrap()
|
||||
if available == Amount::const_from_i64(51000)
|
||||
&& required == Amount::const_from_i64(60000)
|
||||
);
|
||||
|
||||
// This request will succeed, spending a single dust input to pay the 10000
|
||||
// ZAT fee in addition to the 41000 ZAT output to the recipient
|
||||
let req = TransactionRequest::new(vec![Payment {
|
||||
recipient_address: RecipientAddress::Shielded(dfvk.default_address().1),
|
||||
amount: Amount::from_u64(41000).unwrap(),
|
||||
amount: Amount::const_from_i64(41000),
|
||||
memo: None,
|
||||
label: None,
|
||||
message: None,
|
||||
|
@ -1362,14 +1364,14 @@ pub(crate) mod tests {
|
|||
let (h, _, _) = st.generate_next_block(
|
||||
&dfvk,
|
||||
AddressType::Internal,
|
||||
Amount::from_u64(50000).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(50000),
|
||||
);
|
||||
st.scan_cached_blocks(h, 1);
|
||||
|
||||
let utxo = WalletTransparentOutput::from_parts(
|
||||
OutPoint::new([1u8; 32], 1),
|
||||
TxOut {
|
||||
value: Amount::from_u64(10000).unwrap(),
|
||||
value: Amount::const_from_i64(10000),
|
||||
script_pubkey: taddr.script(),
|
||||
},
|
||||
h,
|
||||
|
@ -1428,7 +1430,7 @@ pub(crate) mod tests {
|
|||
|
||||
// Generate 9 blocks that have no value for us, starting at the birthday height.
|
||||
let not_our_key = ExtendedSpendingKey::master(&[]).to_diversifiable_full_viewing_key();
|
||||
let not_our_value = Amount::const_from_i64(10000);
|
||||
let not_our_value = NonNegativeAmount::const_from_u64(10000);
|
||||
st.generate_block_at(
|
||||
birthday.height(),
|
||||
BlockHash([0; 32]),
|
||||
|
@ -1445,7 +1447,7 @@ pub(crate) mod tests {
|
|||
st.generate_next_block(
|
||||
&dfvk,
|
||||
AddressType::DefaultExternal,
|
||||
Amount::const_from_i64(500000),
|
||||
NonNegativeAmount::const_from_u64(500000),
|
||||
);
|
||||
|
||||
// Generate some more blocks to get above our anchor height
|
||||
|
@ -1499,14 +1501,14 @@ pub(crate) mod tests {
|
|||
st.generate_next_block(
|
||||
&dfvk,
|
||||
AddressType::DefaultExternal,
|
||||
Amount::const_from_i64(500000),
|
||||
NonNegativeAmount::const_from_u64(500000),
|
||||
);
|
||||
st.scan_cached_blocks(birthday.height(), 1);
|
||||
|
||||
// Create a gap of 10 blocks having no shielded outputs, then add a block that doesn't
|
||||
// belong to us so that we can get a checkpoint in the tree.
|
||||
let not_our_key = ExtendedSpendingKey::master(&[]).to_diversifiable_full_viewing_key();
|
||||
let not_our_value = Amount::const_from_i64(10000);
|
||||
let not_our_value = NonNegativeAmount::const_from_u64(10000);
|
||||
st.generate_block_at(
|
||||
birthday.height() + 10,
|
||||
BlockHash([0; 32]),
|
||||
|
@ -1542,7 +1544,7 @@ pub(crate) mod tests {
|
|||
st.create_spend_to_address(
|
||||
&usk,
|
||||
&to,
|
||||
Amount::from_u64(10000).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(10000),
|
||||
None,
|
||||
OvkPolicy::Sender,
|
||||
NonZeroU32::new(5).unwrap(),
|
||||
|
|
|
@ -512,7 +512,7 @@ pub(crate) mod tests {
|
|||
block::BlockHash,
|
||||
consensus::{BlockHeight, NetworkUpgrade, Parameters},
|
||||
sapling::Node,
|
||||
transaction::components::Amount,
|
||||
transaction::components::amount::NonNegativeAmount,
|
||||
zip32::DiversifiableFullViewingKey,
|
||||
};
|
||||
|
||||
|
@ -564,7 +564,7 @@ pub(crate) mod tests {
|
|||
let initial_sapling_tree_size = (0x1 << 16) * 3 + 5;
|
||||
let initial_height = sapling_activation_height + 310;
|
||||
|
||||
let value = Amount::from_u64(50000).unwrap();
|
||||
let value = NonNegativeAmount::const_from_u64(50000);
|
||||
st.generate_block_at(
|
||||
initial_height,
|
||||
BlockHash([0; 32]),
|
||||
|
@ -578,7 +578,7 @@ pub(crate) mod tests {
|
|||
st.generate_next_block(
|
||||
&dfvk,
|
||||
AddressType::DefaultExternal,
|
||||
Amount::from_u64(10000).unwrap(),
|
||||
NonNegativeAmount::const_from_u64(10000),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -845,8 +845,8 @@ pub(crate) mod tests {
|
|||
BlockHash([0u8; 32]),
|
||||
&dfvk,
|
||||
AddressType::DefaultExternal,
|
||||
Amount::const_from_i64(10000),
|
||||
// 1235 notes into into the second shard
|
||||
NonNegativeAmount::const_from_u64(10000),
|
||||
u64::from(birthday.sapling_frontier().value().unwrap().position() + 1)
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
|
@ -961,7 +961,7 @@ pub(crate) mod tests {
|
|||
BlockHash([0u8; 32]),
|
||||
&dfvk,
|
||||
AddressType::DefaultExternal,
|
||||
Amount::const_from_i64(10000),
|
||||
NonNegativeAmount::const_from_u64(10000),
|
||||
u64::from(birthday.sapling_frontier().value().unwrap().position() + 1)
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
|
|
|
@ -847,7 +847,7 @@ mod tests {
|
|||
|
||||
let mut builder_b = demo_builder(tx_height + 1);
|
||||
let prevout_a = (OutPoint::new(tx_a.txid(), 0), tze_a.vout[0].clone());
|
||||
let value_xfr = (value - fee_rule.fixed_fee()).unwrap();
|
||||
let value_xfr = (value - fee_rule.fixed_fee().into()).unwrap();
|
||||
builder_b
|
||||
.demo_transfer_to_close(prevout_a, value_xfr, preimage_1, h2)
|
||||
.map_err(|e| format!("transfer failure: {:?}", e))
|
||||
|
@ -873,7 +873,7 @@ mod tests {
|
|||
builder_c
|
||||
.add_transparent_output(
|
||||
&TransparentAddress::PublicKey([0; 20]),
|
||||
(value_xfr - fee_rule.fixed_fee()).unwrap(),
|
||||
(value_xfr - fee_rule.fixed_fee().into()).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
|
|
@ -21,14 +21,41 @@ and this library adheres to Rust's notion of
|
|||
set of closures.
|
||||
- Test helpers, behind the `test-dependencies` feature flag:
|
||||
- `zcash_primitives::prover::mock::{MockSpendProver, MockOutputProver}`
|
||||
- Constants in `zcash_primitives::transaction::fees::zip317`:
|
||||
- `MARGINAL_FEE`
|
||||
- `GRACE_ACTIONS`
|
||||
- `P2PKH_STANDARD_INPUT_SIZE`
|
||||
- `P2PKH_STANDARD_OUTPUT_SIZE`
|
||||
- `zcash_primitives::transaction::builder::get_fee`
|
||||
- Additions related to `zcash_primitive::components::transaction::Amount`
|
||||
and `zcash_primitive::components::transaction::NonNegativeAmount`
|
||||
- `impl TryFrom<Amount> for u64`
|
||||
- `Amount::const_from_u64`
|
||||
- `NonNegativeAmount::const_from_u64`
|
||||
- `NonNegativeAmount::from_nonnegative_i64_le_bytes`
|
||||
- `NonNegativeAmount::to_i64_le_bytes`
|
||||
- `impl From<&NonNegativeAmount> for Amount`
|
||||
- `impl From<NonNegativeAmount> for u64`
|
||||
- `impl From<NonNegativeAmount> for zcash_primitives::sapling::value::NoteValue`
|
||||
- `impl Sum<NonNegativeAmount> for Option<NonNegativeAmount>`
|
||||
- `impl<'a> Sum<&'a NonNegativeAmount> for Option<NonNegativeAmount>`
|
||||
|
||||
### Changed
|
||||
- `zcash_primitives::transaction::fees`:
|
||||
- `FeeRule::fee_required` now returns the required fee as a `NonNegativeAmount` instead
|
||||
of as an `Amount`.
|
||||
- When using the `zfuture` feature flag, `FutureFeeRule::fee_required_zfuture` now returns
|
||||
the required fee as a `NonNegativeAmount` instead of as an `Amount`.
|
||||
- `fees::fixed::FeeRule::fixed_fee` now wraps a `NonNegativeAmount` instead of an `Amount`
|
||||
- `fees::zip317::FeeRule::marginal_fee` is now represented and exposed as a
|
||||
`NonNegativeAmount` instead of an `Amount`
|
||||
- `zcash_primitives::transaction::components::sapling`:
|
||||
- `MapAuth` trait methods now take `&mut self` instead of `&self`.
|
||||
|
||||
### Removed
|
||||
- `zcash_primitives::constants`:
|
||||
- All `const` values (moved to `zcash_primitives::sapling::constants`).
|
||||
- `impl From<zcash_primitive::components::transaction::Amount> for u64`
|
||||
|
||||
## [0.13.0] - 2023-09-25
|
||||
### Added
|
||||
|
@ -53,7 +80,7 @@ and this library adheres to Rust's notion of
|
|||
### Changed
|
||||
- Migrated to `incrementalmerkletree 0.5`, `orchard 0.6`.
|
||||
- `zcash_primitives::transaction`:
|
||||
- `builder::Builder::{new, new_with_rng}` now take an optional `orchard_anchor`
|
||||
- `builder::Builder::{new, new_with_rng}` now take an optional `orchard_anchor`
|
||||
argument which must be provided in order to enable Orchard spends and recipients.
|
||||
- All `builder::Builder` methods now require the bound `R: CryptoRng` on
|
||||
`Builder<'a, P, R>`. A non-`CryptoRng` randomness source is still accepted
|
||||
|
@ -62,11 +89,11 @@ and this library adheres to Rust's notion of
|
|||
- `builder::Error` has several additional variants for Orchard-related errors.
|
||||
- `fees::FeeRule::fee_required` now takes an additional argument,
|
||||
`orchard_action_count`
|
||||
- `Unauthorized`'s associated type `OrchardAuth` is now
|
||||
- `Unauthorized`'s associated type `OrchardAuth` is now
|
||||
`orchard::builder::InProgress<orchard::builder::Unproven, orchard::builder::Unauthorized>`
|
||||
instead of `zcash_primitives::transaction::components::orchard::Unauthorized`
|
||||
- `zcash_primitives::consensus::NetworkUpgrade` now implements `PartialEq`, `Eq`
|
||||
- `zcash_primitives::legacy::Script` now has a custom `Debug` implementation that
|
||||
- `zcash_primitives::legacy::Script` now has a custom `Debug` implementation that
|
||||
renders script details in a much more legible fashion.
|
||||
- `zcash_primitives::sapling::redjubjub::Signature` now has a custom `Debug`
|
||||
implementation that renders details in a much more legible fashion.
|
||||
|
@ -74,7 +101,7 @@ and this library adheres to Rust's notion of
|
|||
implementation that renders details in a much more legible fashion.
|
||||
|
||||
### Removed
|
||||
- `impl {PartialEq, Eq} for transaction::builder::Error`
|
||||
- `impl {PartialEq, Eq} for transaction::builder::Error`
|
||||
(use `assert_matches!` where error comparisons are required)
|
||||
- `zcash_primitives::transaction::components::orchard::Unauthorized`
|
||||
- `zcash_primitives::transaction::components::amount::DEFAULT_FEE` was
|
||||
|
|
|
@ -27,7 +27,7 @@ use crate::{
|
|||
Diversifier, Note, PaymentAddress, Rseed,
|
||||
},
|
||||
transaction::components::{
|
||||
amount::Amount,
|
||||
amount::NonNegativeAmount,
|
||||
sapling::{self, OutputDescription},
|
||||
},
|
||||
};
|
||||
|
@ -82,9 +82,20 @@ where
|
|||
}
|
||||
|
||||
// The unwraps below are guaranteed to succeed by the assertion above
|
||||
let diversifier = Diversifier(plaintext[1..12].try_into().unwrap());
|
||||
let value = Amount::from_u64_le_bytes(plaintext[12..20].try_into().unwrap()).ok()?;
|
||||
let r: [u8; 32] = plaintext[20..COMPACT_NOTE_SIZE].try_into().unwrap();
|
||||
let diversifier = Diversifier(
|
||||
plaintext[1..12]
|
||||
.try_into()
|
||||
.expect("Note plaintext is checked to have length >= COMPACT_NOTE_SIZE."),
|
||||
);
|
||||
let value = NonNegativeAmount::from_u64_le_bytes(
|
||||
plaintext[12..20]
|
||||
.try_into()
|
||||
.expect("Note plaintext is checked to have length >= COMPACT_NOTE_SIZE."),
|
||||
)
|
||||
.ok()?;
|
||||
let r: [u8; 32] = plaintext[20..COMPACT_NOTE_SIZE]
|
||||
.try_into()
|
||||
.expect("Note plaintext is checked to have length >= COMPACT_NOTE_SIZE.");
|
||||
|
||||
let rseed = if plaintext[0] == 0x01 {
|
||||
let rcm = Option::from(jubjub::Fr::from_repr(r))?;
|
||||
|
@ -97,7 +108,7 @@ where
|
|||
|
||||
// `diversifier` was checked by `get_pk_d`.
|
||||
let to = PaymentAddress::from_parts_unchecked(diversifier, pk_d)?;
|
||||
let note = to.create_note(value.into(), rseed);
|
||||
let note = to.create_note(value.try_into().unwrap(), rseed);
|
||||
Some((note, to))
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,8 @@ use crate::{
|
|||
},
|
||||
};
|
||||
|
||||
use super::components::amount::NonNegativeAmount;
|
||||
|
||||
/// Since Blossom activation, the default transaction expiry delta should be 40 blocks.
|
||||
/// <https://zips.z.cash/zip-0203#changes-for-blossom>
|
||||
const DEFAULT_TX_EXPIRY_DELTA: u32 = 40;
|
||||
|
@ -345,7 +347,11 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
|||
&mut self.rng,
|
||||
ovk,
|
||||
to,
|
||||
NoteValue::from_raw(value.into()),
|
||||
NoteValue::from_raw(
|
||||
value
|
||||
.try_into()
|
||||
.expect("Cannot create Sapling outputs with negative note values."),
|
||||
),
|
||||
memo,
|
||||
)
|
||||
}
|
||||
|
@ -407,28 +413,26 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
|||
///
|
||||
/// This fee is a function of the spends and outputs that have been added to the builder,
|
||||
/// pursuant to the specified [`FeeRule`].
|
||||
pub fn get_fee<FR: FeeRule>(&self, fee_rule: &FR) -> Result<Amount, Error<FR::Error>> {
|
||||
fee_rule
|
||||
.fee_required(
|
||||
&self.params,
|
||||
self.target_height,
|
||||
self.transparent_builder.inputs(),
|
||||
self.transparent_builder.outputs(),
|
||||
self.sapling_builder.inputs().len(),
|
||||
self.sapling_builder.bundle_output_count(),
|
||||
match std::cmp::max(
|
||||
self.orchard_builder
|
||||
.as_ref()
|
||||
.map_or(0, |builder| builder.outputs().len()),
|
||||
self.orchard_builder
|
||||
.as_ref()
|
||||
.map_or(0, |builder| builder.spends().len()),
|
||||
) {
|
||||
1 => 2,
|
||||
n => n,
|
||||
},
|
||||
)
|
||||
.map_err(Error::Fee)
|
||||
pub fn get_fee<FR: FeeRule>(&self, fee_rule: &FR) -> Result<NonNegativeAmount, FR::Error> {
|
||||
fee_rule.fee_required(
|
||||
&self.params,
|
||||
self.target_height,
|
||||
self.transparent_builder.inputs(),
|
||||
self.transparent_builder.outputs(),
|
||||
self.sapling_builder.inputs().len(),
|
||||
self.sapling_builder.bundle_output_count(),
|
||||
match std::cmp::max(
|
||||
self.orchard_builder
|
||||
.as_ref()
|
||||
.map_or(0, |builder| builder.outputs().len()),
|
||||
self.orchard_builder
|
||||
.as_ref()
|
||||
.map_or(0, |builder| builder.spends().len()),
|
||||
) {
|
||||
1 => 2,
|
||||
n => n,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Builds a transaction from the configured spends and outputs.
|
||||
|
@ -440,8 +444,8 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
|||
prover: &impl TxProver,
|
||||
fee_rule: &FR,
|
||||
) -> Result<(Transaction, SaplingMetadata), Error<FR::Error>> {
|
||||
let fee = self.get_fee(fee_rule)?;
|
||||
self.build_internal(prover, fee)
|
||||
let fee = self.get_fee(fee_rule).map_err(Error::Fee)?;
|
||||
self.build_internal(prover, fee.into())
|
||||
}
|
||||
|
||||
/// Builds a transaction from the configured spends and outputs.
|
||||
|
@ -467,7 +471,7 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
|||
)
|
||||
.map_err(Error::Fee)?;
|
||||
|
||||
self.build_internal(prover, fee)
|
||||
self.build_internal(prover, fee.into())
|
||||
}
|
||||
|
||||
fn build_internal<FE>(
|
||||
|
@ -714,7 +718,7 @@ mod tests {
|
|||
memo::MemoBytes,
|
||||
sapling::{Node, Rseed},
|
||||
transaction::components::{
|
||||
amount::Amount,
|
||||
amount::{Amount, NonNegativeAmount},
|
||||
sapling::builder::{self as sapling_builder},
|
||||
transparent::builder::{self as transparent_builder},
|
||||
},
|
||||
|
@ -893,7 +897,7 @@ mod tests {
|
|||
let builder = Builder::new(TEST_NETWORK, tx_height, None);
|
||||
assert_matches!(
|
||||
builder.mock_build(),
|
||||
Err(Error::InsufficientFunds(MINIMUM_FEE))
|
||||
Err(Error::InsufficientFunds(expected)) if expected == MINIMUM_FEE.into()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -916,7 +920,7 @@ mod tests {
|
|||
assert_matches!(
|
||||
builder.mock_build(),
|
||||
Err(Error::InsufficientFunds(expected)) if
|
||||
expected == (Amount::from_i64(50000).unwrap() + MINIMUM_FEE).unwrap()
|
||||
expected == (NonNegativeAmount::const_from_u64(50000) + MINIMUM_FEE).unwrap().into()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -933,7 +937,7 @@ mod tests {
|
|||
assert_matches!(
|
||||
builder.mock_build(),
|
||||
Err(Error::InsufficientFunds(expected)) if expected ==
|
||||
(Amount::from_i64(50000).unwrap() + MINIMUM_FEE).unwrap()
|
||||
(NonNegativeAmount::const_from_u64(50000) + MINIMUM_FEE).unwrap().into()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -971,7 +975,7 @@ mod tests {
|
|||
.unwrap();
|
||||
assert_matches!(
|
||||
builder.mock_build(),
|
||||
Err(Error::InsufficientFunds(expected)) if expected == Amount::from_i64(1).unwrap()
|
||||
Err(Error::InsufficientFunds(expected)) if expected == Amount::const_from_i64(1)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,10 +5,12 @@ use std::ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign};
|
|||
use memuse::DynamicUsage;
|
||||
use orchard::value as orchard;
|
||||
|
||||
use crate::sapling::value::NoteValue;
|
||||
|
||||
pub const COIN: i64 = 1_0000_0000;
|
||||
pub const MAX_MONEY: i64 = 21_000_000 * COIN;
|
||||
|
||||
/// A type-safe representation of some quantity of Zcash.
|
||||
/// A type-safe representation of a Zcash value delta, in zatoshis.
|
||||
///
|
||||
/// An Amount can only be constructed from an integer that is within the valid monetary
|
||||
/// range of `{-MAX_MONEY..MAX_MONEY}` (where `MAX_MONEY` = 21,000,000 × 10⁸ zatoshis).
|
||||
|
@ -38,6 +40,14 @@ impl Amount {
|
|||
Amount(amount)
|
||||
}
|
||||
|
||||
/// Creates a constant Amount from a u64.
|
||||
///
|
||||
/// Panics: if the amount is outside the range `{0..MAX_MONEY}`.
|
||||
const fn const_from_u64(amount: u64) -> Self {
|
||||
assert!(amount <= (MAX_MONEY as u64)); // contains is not const
|
||||
Amount(amount as i64)
|
||||
}
|
||||
|
||||
/// Creates an Amount from an i64.
|
||||
///
|
||||
/// Returns an error if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`.
|
||||
|
@ -141,9 +151,11 @@ impl From<&Amount> for i64 {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Amount> for u64 {
|
||||
fn from(amount: Amount) -> u64 {
|
||||
amount.0 as u64
|
||||
impl TryFrom<Amount> for u64 {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: Amount) -> Result<Self, Self::Error> {
|
||||
value.0.try_into().map_err(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -248,12 +260,27 @@ impl NonNegativeAmount {
|
|||
Amount::from_u64(amount).map(NonNegativeAmount)
|
||||
}
|
||||
|
||||
/// Creates a constant NonNegativeAmount from a u64.
|
||||
///
|
||||
/// Panics: if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`.
|
||||
pub const fn const_from_u64(amount: u64) -> Self {
|
||||
NonNegativeAmount(Amount::const_from_u64(amount))
|
||||
}
|
||||
|
||||
/// Creates a NonNegativeAmount from an i64.
|
||||
///
|
||||
/// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
|
||||
pub fn from_nonnegative_i64(amount: i64) -> Result<Self, ()> {
|
||||
Amount::from_nonnegative_i64(amount).map(NonNegativeAmount)
|
||||
}
|
||||
|
||||
/// Reads an NonNegativeAmount from an unsigned 64-bit little-endian integer.
|
||||
///
|
||||
/// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
|
||||
pub fn from_u64_le_bytes(bytes: [u8; 8]) -> Result<Self, ()> {
|
||||
let amount = u64::from_le_bytes(bytes);
|
||||
Self::from_u64(amount)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NonNegativeAmount> for Amount {
|
||||
|
@ -262,6 +289,24 @@ impl From<NonNegativeAmount> for Amount {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&NonNegativeAmount> for Amount {
|
||||
fn from(n: &NonNegativeAmount) -> Self {
|
||||
n.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NonNegativeAmount> for u64 {
|
||||
fn from(n: NonNegativeAmount) -> Self {
|
||||
n.0.try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NonNegativeAmount> for NoteValue {
|
||||
fn from(n: NonNegativeAmount) -> Self {
|
||||
NoteValue::from_raw(n.0.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Amount> for NonNegativeAmount {
|
||||
type Error = ();
|
||||
|
||||
|
@ -310,7 +355,19 @@ impl Mul<usize> for NonNegativeAmount {
|
|||
type Output = Option<Self>;
|
||||
|
||||
fn mul(self, rhs: usize) -> Option<NonNegativeAmount> {
|
||||
(self.0 * rhs).map(NonNegativeAmount)
|
||||
(self.0 * rhs).and_then(|v| NonNegativeAmount::try_from(v).ok())
|
||||
}
|
||||
}
|
||||
|
||||
impl Sum<NonNegativeAmount> for Option<NonNegativeAmount> {
|
||||
fn sum<I: Iterator<Item = NonNegativeAmount>>(iter: I) -> Self {
|
||||
iter.fold(Some(NonNegativeAmount::ZERO), |acc, a| acc? + a)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Sum<&'a NonNegativeAmount> for Option<NonNegativeAmount> {
|
||||
fn sum<I: Iterator<Item = &'a NonNegativeAmount>>(iter: I) -> Self {
|
||||
iter.fold(Some(NonNegativeAmount::ZERO), |acc, a| acc? + *a)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use crate::{
|
||||
consensus::{self, BlockHeight},
|
||||
transaction::components::{amount::Amount, transparent::fees as transparent},
|
||||
transaction::components::{amount::NonNegativeAmount, transparent::fees as transparent},
|
||||
};
|
||||
|
||||
#[cfg(feature = "zfuture")]
|
||||
|
@ -31,7 +31,7 @@ pub trait FeeRule {
|
|||
sapling_input_count: usize,
|
||||
sapling_output_count: usize,
|
||||
orchard_action_count: usize,
|
||||
) -> Result<Amount, Self::Error>;
|
||||
) -> Result<NonNegativeAmount, Self::Error>;
|
||||
}
|
||||
|
||||
/// A trait that represents the ability to compute the fees that must be paid by a transaction
|
||||
|
@ -54,5 +54,5 @@ pub trait FutureFeeRule: FeeRule {
|
|||
sapling_output_count: usize,
|
||||
tze_inputs: &[impl tze::InputView],
|
||||
tze_outputs: &[impl tze::OutputView],
|
||||
) -> Result<Amount, Self::Error>;
|
||||
) -> Result<NonNegativeAmount, Self::Error>;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
consensus::{self, BlockHeight},
|
||||
transaction::components::{amount::Amount, transparent::fees as transparent},
|
||||
transaction::components::{amount::NonNegativeAmount, transparent::fees as transparent},
|
||||
transaction::fees::zip317,
|
||||
};
|
||||
|
||||
|
@ -11,12 +11,12 @@ use crate::transaction::components::tze::fees as tze;
|
|||
/// the transaction being constructed.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FeeRule {
|
||||
fixed_fee: Amount,
|
||||
fixed_fee: NonNegativeAmount,
|
||||
}
|
||||
|
||||
impl FeeRule {
|
||||
/// Creates a new nonstandard fixed fee rule with the specified fixed fee.
|
||||
pub fn non_standard(fixed_fee: Amount) -> Self {
|
||||
pub fn non_standard(fixed_fee: NonNegativeAmount) -> Self {
|
||||
Self { fixed_fee }
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ impl FeeRule {
|
|||
}
|
||||
|
||||
/// Returns the fixed fee amount which which this rule was configured.
|
||||
pub fn fixed_fee(&self) -> Amount {
|
||||
pub fn fixed_fee(&self) -> NonNegativeAmount {
|
||||
self.fixed_fee
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ impl super::FeeRule for FeeRule {
|
|||
_sapling_input_count: usize,
|
||||
_sapling_output_count: usize,
|
||||
_orchard_action_count: usize,
|
||||
) -> Result<Amount, Self::Error> {
|
||||
) -> Result<NonNegativeAmount, Self::Error> {
|
||||
Ok(self.fixed_fee)
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ impl super::FutureFeeRule for FeeRule {
|
|||
_sapling_output_count: usize,
|
||||
_tze_inputs: &[impl tze::InputView],
|
||||
_tze_outputs: &[impl tze::OutputView],
|
||||
) -> Result<Amount, Self::Error> {
|
||||
) -> Result<NonNegativeAmount, Self::Error> {
|
||||
Ok(self.fixed_fee)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,17 +8,36 @@ use crate::{
|
|||
consensus::{self, BlockHeight},
|
||||
legacy::TransparentAddress,
|
||||
transaction::components::{
|
||||
amount::{Amount, BalanceError},
|
||||
amount::{BalanceError, NonNegativeAmount},
|
||||
transparent::{fees as transparent, OutPoint},
|
||||
},
|
||||
};
|
||||
|
||||
/// The minimum conventional fee using the standard [ZIP 317] constants. Equivalent to
|
||||
/// `(FeeRule::standard().marginal_fee() * FeeRule::standard().grace_actions()).unwrap()`,
|
||||
/// but as a constant.
|
||||
/// The standard [ZIP 317] marginal fee.
|
||||
///
|
||||
/// [ZIP 317]: https//zips.z.cash/zip-0317
|
||||
pub const MINIMUM_FEE: Amount = Amount::const_from_i64(10_000);
|
||||
pub const MARGINAL_FEE: NonNegativeAmount = NonNegativeAmount::const_from_u64(5_000);
|
||||
|
||||
/// The minimum number of logical actions that must be paid according to [ZIP 317].
|
||||
///
|
||||
/// [ZIP 317]: https//zips.z.cash/zip-0317
|
||||
pub const GRACE_ACTIONS: usize = 2;
|
||||
|
||||
/// The standard size of a P2PKH input, in bytes, according to [ZIP 317].
|
||||
///
|
||||
/// [ZIP 317]: https//zips.z.cash/zip-0317
|
||||
pub const P2PKH_STANDARD_INPUT_SIZE: usize = 150;
|
||||
|
||||
/// The standard size of a P2PKH output, in bytes, according to [ZIP 317].
|
||||
///
|
||||
/// [ZIP 317]: https//zips.z.cash/zip-0317
|
||||
pub const P2PKH_STANDARD_OUTPUT_SIZE: usize = 34;
|
||||
|
||||
/// The minimum conventional fee computed from the standard [ZIP 317] constants. Equivalent to
|
||||
/// `MARGINAL_FEE * GRACE_ACTIONS`.
|
||||
///
|
||||
/// [ZIP 317]: https//zips.z.cash/zip-0317
|
||||
pub const MINIMUM_FEE: NonNegativeAmount = NonNegativeAmount::const_from_u64(10_000);
|
||||
|
||||
/// A [`FeeRule`] implementation that implements the [ZIP 317] fee rule.
|
||||
///
|
||||
|
@ -30,7 +49,7 @@ pub const MINIMUM_FEE: Amount = Amount::const_from_i64(10_000);
|
|||
/// [ZIP 317]: https//zips.z.cash/zip-0317
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FeeRule {
|
||||
marginal_fee: Amount,
|
||||
marginal_fee: NonNegativeAmount,
|
||||
grace_actions: usize,
|
||||
p2pkh_standard_input_size: usize,
|
||||
p2pkh_standard_output_size: usize,
|
||||
|
@ -42,10 +61,10 @@ impl FeeRule {
|
|||
/// [ZIP 317]: https//zips.z.cash/zip-0317
|
||||
pub fn standard() -> Self {
|
||||
Self {
|
||||
marginal_fee: Amount::from_u64(5000).unwrap(),
|
||||
grace_actions: 2,
|
||||
p2pkh_standard_input_size: 150,
|
||||
p2pkh_standard_output_size: 34,
|
||||
marginal_fee: MARGINAL_FEE,
|
||||
grace_actions: GRACE_ACTIONS,
|
||||
p2pkh_standard_input_size: P2PKH_STANDARD_INPUT_SIZE,
|
||||
p2pkh_standard_output_size: P2PKH_STANDARD_OUTPUT_SIZE,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,7 +73,7 @@ impl FeeRule {
|
|||
/// Returns `None` if either `p2pkh_standard_input_size` or `p2pkh_standard_output_size` are
|
||||
/// zero.
|
||||
pub fn non_standard(
|
||||
marginal_fee: Amount,
|
||||
marginal_fee: NonNegativeAmount,
|
||||
grace_actions: usize,
|
||||
p2pkh_standard_input_size: usize,
|
||||
p2pkh_standard_output_size: usize,
|
||||
|
@ -72,7 +91,7 @@ impl FeeRule {
|
|||
}
|
||||
|
||||
/// Returns the ZIP 317 marginal fee.
|
||||
pub fn marginal_fee(&self) -> Amount {
|
||||
pub fn marginal_fee(&self) -> NonNegativeAmount {
|
||||
self.marginal_fee
|
||||
}
|
||||
/// Returns the ZIP 317 number of grace actions
|
||||
|
@ -130,7 +149,7 @@ impl super::FeeRule for FeeRule {
|
|||
sapling_input_count: usize,
|
||||
sapling_output_count: usize,
|
||||
orchard_action_count: usize,
|
||||
) -> Result<Amount, Self::Error> {
|
||||
) -> Result<NonNegativeAmount, Self::Error> {
|
||||
let non_p2pkh_inputs: Vec<_> = transparent_inputs
|
||||
.iter()
|
||||
.filter_map(|t_in| match t_in.coin().script_pubkey.address() {
|
||||
|
|
|
@ -92,6 +92,12 @@ impl AsRef<[u8; 32]> for TxId {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<TxId> for [u8; 32] {
|
||||
fn from(value: TxId) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TxId {
|
||||
pub fn from_bytes(bytes: [u8; 32]) -> Self {
|
||||
TxId(bytes)
|
||||
|
|
Loading…
Reference in New Issue