Skip to content

Commit

Permalink
zcash_client_backend: Add fees::zip317::MultiOutputChangeStrategy.
Browse files Browse the repository at this point in the history
  • Loading branch information
nuttycom committed Oct 17, 2024
1 parent 1447aa6 commit e426b68
Show file tree
Hide file tree
Showing 8 changed files with 408 additions and 68 deletions.
4 changes: 4 additions & 0 deletions components/zcash_protocol/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this library adheres to Rust's notion of

## [Unreleased]

### Added
- `zcash_protocol::value::DivRem`
- `zcash_protocol::value::Zatoshis::div_with_remainder`

## [0.4.0] - 2024-10-02
### Added
- `impl Sub<BlockHeight> for BlockHeight` unlike the implementation that was
Expand Down
28 changes: 28 additions & 0 deletions components/zcash_protocol/src/value.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::convert::{Infallible, TryFrom};
use std::error;
use std::iter::Sum;
use std::num::NonZeroUsize;
use std::ops::{Add, Mul, Neg, Sub};

use memuse::DynamicUsage;
Expand Down Expand Up @@ -229,6 +230,24 @@ impl Mul<usize> for ZatBalance {
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)]
pub struct Zatoshis(u64);

/// A struct that provides both the dividend and remainder of a division operation.
pub struct DivRem<A> {
dividend: A,
remainder: A,
}

impl<A> DivRem<A> {
/// Returns the dividend portion of the value.
pub fn dividend(&self) -> &A {
&self.dividend
}

/// Returns the remainder portion of the value.
pub fn remainder(&self) -> &A {
&self.remainder
}
}

impl Zatoshis {
/// Returns the identity `Zatoshis`
pub const ZERO: Self = Zatoshis(0);
Expand Down Expand Up @@ -298,6 +317,15 @@ impl Zatoshis {
pub fn is_positive(&self) -> bool {
self > &Zatoshis::ZERO
}

/// Divides this `Zatoshis` value by the given divisor and returns the dividend and remainder.
pub fn div_with_remainder(&self, divisor: NonZeroUsize) -> DivRem<Zatoshis> {
let divisor = u64::try_from(usize::from(divisor)).expect("divisor fits into a u64");
DivRem {
dividend: Zatoshis(self.0 / divisor),
remainder: Zatoshis(self.0 % divisor),
}
}
}

impl From<Zatoshis> for ZatBalance {
Expand Down
2 changes: 2 additions & 0 deletions zcash_client_backend/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this library adheres to Rust's notion of
- `zcash_client_backend::data_api`:
- `WalletMeta`
- `impl Default for wallet::input_selection::GreedyInputSelector`
- `zcash_client_backend::fees::SplitPolicy`
- `zcash_client_backend::fees::zip317::MultiOutputChangeStrategy`

### Changed
- `zcash_client_backend::data_api`:
Expand Down
2 changes: 1 addition & 1 deletion zcash_client_backend/src/data_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,7 @@ impl<NoteRef> SpendableNotes<NoteRef> {
}
}

/// Metadata about the structure of the wallet for a particular account.
/// Metadata about the structure of the wallet for a particular account.
///
/// At present this just contains counts of unspent outputs in each pool, but it may be extended in
/// the future to contain note values or other more detailed information about wallet structure.
Expand Down
66 changes: 65 additions & 1 deletion zcash_client_backend/src/fees.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::fmt::{self, Debug, Display};
use std::{
fmt::{self, Debug, Display},
num::NonZeroUsize,
};

use zcash_primitives::{
consensus::{self, BlockHeight},
Expand Down Expand Up @@ -336,6 +339,67 @@ impl Default for DustOutputPolicy {
}
}

/// A policy that describes how change output should be split into multiple notes for the purpose
/// of note management.
#[derive(Clone, Copy, Debug)]
pub struct SplitPolicy {
target_output_count: NonZeroUsize,
min_split_output_size: NonNegativeAmount,
}

impl SplitPolicy {
/// Constructs a new [`SplitPolicy`] from its constituent parts.
pub fn new(
target_output_count: NonZeroUsize,
min_split_output_size: NonNegativeAmount,
) -> Self {
Self {
target_output_count,
min_split_output_size,
}
}

/// Constructs a [`SplitPolicy`] that prescribes a single output (no splitting).
pub fn single_output() -> Self {
Self {
target_output_count: NonZeroUsize::MIN,
min_split_output_size: NonNegativeAmount::ZERO,
}
}

/// Returns the minimum value for a note resulting from splitting of change.
///
/// If splitting change would result in notes of value less than the minimum split output size,
/// a smaller number of splits should be chosen.
pub fn min_split_output_size(&self) -> NonNegativeAmount {
self.min_split_output_size
}

/// Returns the number of output notes to produce from the given total change value, given the
/// number of existing unspent notes in the account and this policy.
pub fn split_count(
&self,
existing_notes: usize,
total_change: NonNegativeAmount,
) -> NonZeroUsize {
let mut split_count =
NonZeroUsize::new(usize::from(self.target_output_count).saturating_sub(existing_notes))
.unwrap_or(NonZeroUsize::MIN);

loop {
let per_output_change = total_change.div_with_remainder(split_count);
if split_count > NonZeroUsize::MIN
&& *per_output_change.dividend() < self.min_split_output_size
{
// safety: `split_count` has just been verified to be > 1
split_count = unsafe { NonZeroUsize::new_unchecked(usize::from(split_count) - 1) };
} else {
return split_count;
}
}
}
}

/// `EphemeralBalance` describes the ephemeral input or output value for a transaction. It is used
/// in fee computation for series of transactions that use an ephemeral transparent output in an
/// intermediate step, such as when sending from a shielded pool to a [ZIP 320] "TEX" address.
Expand Down
Loading

0 comments on commit e426b68

Please sign in to comment.