Skip to content

Commit

Permalink
zcash_primitives: add StandardFeeRule
Browse files Browse the repository at this point in the history
`StandardFeeRule` is an enumeration of the standard fees that have
existed in the history of Zcash zips. It is provided to simplify
transition to new fee strategies; legacy elements of this enumeration
are introduced already-deprecated.
  • Loading branch information
nuttycom committed Oct 25, 2023
1 parent b3e0a19 commit cc0cc2d
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 30 deletions.
1 change: 1 addition & 0 deletions zcash_client_backend/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this library adheres to Rust's notion of
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 module `zcash_client_backend::fees::standard`
- Added methods to `zcash_client_backend::wallet::ReceivedSaplingNote`:
`{from_parts, txid, output_index, diversifier, rseed, note_commitment_tree_position}`.

Expand Down
17 changes: 8 additions & 9 deletions zcash_client_backend/src/data_api/wallet.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::{convert::Infallible, num::NonZeroU32};
use std::num::NonZeroU32;

use shardtree::{error::ShardTreeError, store::ShardStore, ShardTree};
use zcash_primitives::transaction::TxId;
use zcash_primitives::{
consensus::{self, BlockHeight, NetworkUpgrade},
memo::MemoBytes,
Expand All @@ -13,9 +12,9 @@ use zcash_primitives::{
},
transaction::{
builder::Builder,
components::amount::{Amount, BalanceError, NonNegativeAmount},
fees::{fixed, FeeRule},
Transaction,
components::amount::{Amount, NonNegativeAmount},
fees::{zip317::FeeError as Zip317FeeError, FeeRule, StandardFeeRule},
Transaction, TxId,
},
zip32::{sapling::DiversifiableFullViewingKey, sapling::ExtendedSpendingKey, AccountId, Scope},
};
Expand Down Expand Up @@ -204,8 +203,8 @@ pub fn create_spend_to_address<DbT, ParamsT>(
Error<
<DbT as WalletRead>::Error,
<DbT as WalletCommitmentTrees>::Error,
GreedyInputSelectorError<BalanceError, DbT::NoteRef>,
Infallible,
GreedyInputSelectorError<Zip317FeeError, DbT::NoteRef>,
Zip317FeeError,
>,
>
where
Expand All @@ -226,8 +225,8 @@ where
);

#[allow(deprecated)]
let fee_rule = fixed::FeeRule::standard();
let change_strategy = fees::fixed::SingleOutputChangeStrategy::new(fee_rule, change_memo);
let fee_rule = StandardFeeRule::PreZip313;
let change_strategy = fees::standard::SingleOutputChangeStrategy::new(fee_rule, change_memo);
spend(
wallet_db,
params,
Expand Down
23 changes: 23 additions & 0 deletions zcash_client_backend/src/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use zcash_primitives::{
};

pub mod fixed;
pub mod standard;
pub mod zip317;

/// A proposed change amount and output pool.
Expand Down Expand Up @@ -118,6 +119,28 @@ pub enum ChangeError<E, NoteRefT> {
StrategyError(E),
}

impl<E, NoteRefT> ChangeError<E, NoteRefT> {
pub fn map<E0, F: FnOnce(E) -> E0>(self, f: F) -> ChangeError<E0, NoteRefT> {
match self {
ChangeError::InsufficientFunds {
available,
required,
} => ChangeError::InsufficientFunds {
available,
required,
},
ChangeError::DustInputs {
transparent,
sapling,
} => ChangeError::DustInputs {
transparent,
sapling,
},
ChangeError::StrategyError(e) => ChangeError::StrategyError(f(e)),
}
}
}

impl<CE: fmt::Display, N: fmt::Display> fmt::Display for ChangeError<CE, N> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self {
Expand Down
101 changes: 101 additions & 0 deletions zcash_client_backend/src/fees/standard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//! Change strategies designed for use with a standard fee.
use zcash_primitives::{
consensus::{self, BlockHeight},
memo::MemoBytes,
transaction::{
components::{
amount::NonNegativeAmount, sapling::fees as sapling, transparent::fees as transparent,
},
fees::{
fixed::FeeRule as FixedFeeRule,
zip317::{FeeError as Zip317FeeError, FeeRule as Zip317FeeRule},
StandardFeeRule,
},
},
};

use super::{fixed, zip317, ChangeError, ChangeStrategy, DustOutputPolicy, TransactionBalance};

/// A change strategy that and proposes change as a single output to the most current supported
/// shielded pool and delegates fee calculation to the provided fee rule.
pub struct SingleOutputChangeStrategy {
fee_rule: StandardFeeRule,
change_memo: Option<MemoBytes>,
}

impl SingleOutputChangeStrategy {
/// Constructs a new [`SingleOutputChangeStrategy`] with the specified ZIP 317
/// fee parameters.
pub fn new(fee_rule: StandardFeeRule, change_memo: Option<MemoBytes>) -> Self {
Self {
fee_rule,
change_memo,
}
}
}

impl ChangeStrategy for SingleOutputChangeStrategy {
type FeeRule = StandardFeeRule;
type Error = Zip317FeeError;

fn fee_rule(&self) -> &Self::FeeRule {
&self.fee_rule
}

fn compute_balance<P: consensus::Parameters, NoteRefT: Clone>(
&self,
params: &P,
target_height: BlockHeight,
transparent_inputs: &[impl transparent::InputView],
transparent_outputs: &[impl transparent::OutputView],
sapling_inputs: &[impl sapling::InputView<NoteRefT>],
sapling_outputs: &[impl sapling::OutputView],
dust_output_policy: &DustOutputPolicy,
) -> Result<TransactionBalance, ChangeError<Self::Error, NoteRefT>> {
#[allow(deprecated)]
match self.fee_rule() {
StandardFeeRule::PreZip313 => fixed::SingleOutputChangeStrategy::new(
FixedFeeRule::non_standard(NonNegativeAmount::const_from_u64(10000)),
self.change_memo.clone(),
)
.compute_balance(
params,
target_height,
transparent_inputs,
transparent_outputs,
sapling_inputs,
sapling_outputs,
dust_output_policy,
)
.map_err(|e| e.map(Zip317FeeError::Balance)),
StandardFeeRule::Zip313 => fixed::SingleOutputChangeStrategy::new(
FixedFeeRule::non_standard(NonNegativeAmount::const_from_u64(1000)),
self.change_memo.clone(),
)
.compute_balance(
params,
target_height,
transparent_inputs,
transparent_outputs,
sapling_inputs,
sapling_outputs,
dust_output_policy,
)
.map_err(|e| e.map(Zip317FeeError::Balance)),
StandardFeeRule::Zip317 => zip317::SingleOutputChangeStrategy::new(
Zip317FeeRule::standard(),
self.change_memo.clone(),
)
.compute_balance(
params,
target_height,
transparent_inputs,
transparent_outputs,
sapling_inputs,
sapling_outputs,
dust_output_policy,
),
}
}
}
20 changes: 9 additions & 11 deletions zcash_client_sqlite/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,18 @@ use tempfile::NamedTempFile;
#[cfg(feature = "unstable")]
use tempfile::TempDir;

use zcash_client_backend::data_api::chain::ScanSummary;
use zcash_client_backend::data_api::{AccountBalance, WalletRead};
#[allow(deprecated)]
use zcash_client_backend::{
address::RecipientAddress,
data_api::{
self,
chain::{scan_cached_blocks, BlockSource},
chain::{scan_cached_blocks, BlockSource, ScanSummary},
wallet::{
create_proposed_transaction, create_spend_to_address,
input_selection::{GreedyInputSelectorError, InputSelector, Proposal},
propose_transfer, spend,
},
AccountBirthday, WalletSummary, WalletWrite,
AccountBalance, AccountBirthday, WalletRead, WalletSummary, WalletWrite,
},
keys::UnifiedSpendingKey,
proto::compact_formats::{
Expand All @@ -48,8 +46,8 @@ use zcash_primitives::{
Note, Nullifier, PaymentAddress,
},
transaction::{
components::amount::{BalanceError, NonNegativeAmount},
fees::FeeRule,
components::amount::NonNegativeAmount,
fees::{zip317::FeeError as Zip317FeeError, FeeRule},
Transaction, TxId,
},
zip32::{sapling::DiversifiableFullViewingKey, DiversifierIndex},
Expand Down Expand Up @@ -442,8 +440,8 @@ impl<Cache> TestState<Cache> {
data_api::error::Error<
SqliteClientError,
commitment_tree::Error,
GreedyInputSelectorError<BalanceError, ReceivedNoteId>,
Infallible,
GreedyInputSelectorError<Zip317FeeError, ReceivedNoteId>,
Zip317FeeError,
>,
> {
let params = self.network();
Expand Down Expand Up @@ -560,7 +558,7 @@ impl<Cache> TestState<Cache> {
}

/// Invokes [`create_proposed_transaction`] with the given arguments.
pub(crate) fn create_proposed_transaction<FeeRuleT>(
pub(crate) fn create_proposed_transaction<InputsErrT, FeeRuleT>(
&mut self,
usk: &UnifiedSpendingKey,
ovk_policy: OvkPolicy,
Expand All @@ -571,15 +569,15 @@ impl<Cache> TestState<Cache> {
data_api::error::Error<
SqliteClientError,
commitment_tree::Error,
Infallible,
InputsErrT,
FeeRuleT::Error,
>,
>
where
FeeRuleT: FeeRule,
{
let params = self.network();
create_proposed_transaction::<_, _, Infallible, _>(
create_proposed_transaction(
&mut self.db_data,
&params,
test_prover(),
Expand Down
22 changes: 12 additions & 10 deletions zcash_client_sqlite/src/wallet/sapling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -456,11 +456,12 @@ pub(crate) mod tests {
PaymentAddress,
},
transaction::{
components::{
amount::{BalanceError, NonNegativeAmount},
Amount,
components::{amount::NonNegativeAmount, Amount},
fees::{
fixed::FeeRule as FixedFeeRule,
zip317::{FeeError as Zip317FeeError, FeeRule as Zip317FeeRule},
StandardFeeRule,
},
fees::{fixed::FeeRule as FixedFeeRule, zip317::FeeRule as Zip317FeeRule},
Transaction,
},
zip32::{sapling::ExtendedSpendingKey, Scope},
Expand All @@ -477,7 +478,7 @@ pub(crate) mod tests {
WalletWrite,
},
decrypt_transaction,
fees::{fixed, zip317, DustOutputPolicy},
fees::{fixed, standard, zip317, DustOutputPolicy},
keys::UnifiedSpendingKey,
wallet::OvkPolicy,
zip321::{self, Payment, TransactionRequest},
Expand Down Expand Up @@ -547,10 +548,10 @@ pub(crate) mod tests {
}])
.unwrap();

let fee_rule = FixedFeeRule::standard();
let fee_rule = StandardFeeRule::PreZip313;
let change_memo = "Test change memo".parse::<Memo>().unwrap();
let change_strategy =
fixed::SingleOutputChangeStrategy::new(fee_rule, Some(change_memo.clone().into()));
standard::SingleOutputChangeStrategy::new(fee_rule, Some(change_memo.clone().into()));
let input_selector =
&GreedyInputSelector::new(change_strategy, DustOutputPolicy::default());
let proposal_result = st.propose_transfer(
Expand All @@ -561,7 +562,7 @@ pub(crate) mod tests {
);
assert_matches!(proposal_result, Ok(_));

let create_proposed_result = st.create_proposed_transaction(
let create_proposed_result = st.create_proposed_transaction::<Infallible, _>(
&usk,
OvkPolicy::Sender,
proposal_result.unwrap(),
Expand Down Expand Up @@ -655,6 +656,7 @@ pub(crate) mod tests {
}

#[test]
#[allow(deprecated)]
fn create_to_address_fails_on_incorrect_usk() {
let mut st = TestBuilder::new()
.with_test_account(AccountBirthday::from_sapling_activation)
Expand Down Expand Up @@ -998,8 +1000,8 @@ pub(crate) mod tests {
Error<
SqliteClientError,
commitment_tree::Error,
GreedyInputSelectorError<BalanceError, ReceivedNoteId>,
Infallible,
GreedyInputSelectorError<Zip317FeeError, ReceivedNoteId>,
Zip317FeeError,
>,
> {
let txid = st.create_spend_to_address(
Expand Down
2 changes: 2 additions & 0 deletions zcash_primitives/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ and this library adheres to Rust's notion of
- All `const` values (moved to `zcash_primitives::sapling::constants`).
- `impl From<zcash_primitive::components::transaction::Amount> for u64`

### Added
- `transaction::fees::StandardFeeRule`
## [0.13.0] - 2023-09-25
### Added
- `zcash_primitives::consensus::BlockHeight::saturating_sub`
Expand Down
40 changes: 40 additions & 0 deletions zcash_primitives/src/transaction/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,43 @@ pub trait FutureFeeRule: FeeRule {
tze_outputs: &[impl tze::OutputView],
) -> Result<NonNegativeAmount, Self::Error>;
}

/// An enumeration of the standard fee rules supported by the wallet.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum StandardFeeRule {
#[deprecated(note = "It is recommended to use `StandardFeeRule::Zip317` instead.")]
PreZip313,
#[deprecated(note = "It is recommended to use `StandardFeeRule::Zip317` instead.")]
Zip313,
Zip317,
}

impl FeeRule for StandardFeeRule {
type Error = zip317::FeeError;

fn fee_required<P: consensus::Parameters>(
&self,
params: &P,
target_height: BlockHeight,
transparent_inputs: &[impl transparent::InputView],
transparent_outputs: &[impl transparent::OutputView],
sapling_input_count: usize,
sapling_output_count: usize,
orchard_action_count: usize,
) -> Result<NonNegativeAmount, Self::Error> {
#[allow(deprecated)]
match self {
Self::PreZip313 => Ok(zip317::MINIMUM_FEE),
Self::Zip313 => Ok(NonNegativeAmount::const_from_u64(1000)),
Self::Zip317 => zip317::FeeRule::standard().fee_required(
params,
target_height,
transparent_inputs,
transparent_outputs,
sapling_input_count,
sapling_output_count,
orchard_action_count,
),
}
}
}

0 comments on commit cc0cc2d

Please sign in to comment.