-
Notifications
You must be signed in to change notification settings - Fork 261
/
Copy pathzip317.rs
196 lines (175 loc) · 6.56 KB
/
zip317.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
//! Types related to implementing a [`FeeRule`] provides [ZIP 317] fee calculation.
//!
//! [`FeeRule`]: crate::transaction::fees::FeeRule
//! [ZIP 317]: https//zips.z.cash/zip-0317
use alloc::vec::Vec;
use core::cmp::max;
use ::transparent::bundle::OutPoint;
use zcash_protocol::{
consensus::{self, BlockHeight},
value::{BalanceError, Zatoshis},
};
use crate::transaction::fees::transparent;
/// The standard [ZIP 317] marginal fee.
///
/// [ZIP 317]: https//zips.z.cash/zip-0317
pub const MARGINAL_FEE: Zatoshis = Zatoshis::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: Zatoshis = Zatoshis::const_from_u64(10_000);
/// A [`FeeRule`] implementation that implements the [ZIP 317] fee rule.
///
/// This fee rule supports Orchard, Sapling, and (P2PKH only) transparent inputs.
/// Returns an error if a coin containing a non-P2PKH script is provided as an input.
///
/// This fee rule may slightly overestimate fees in case where the user is attempting
/// to spend a large number of transparent inputs. This is intentional and is relied
/// on for the correctness of transaction construction algorithms in the
/// `zcash_client_backend` crate.
///
/// [`FeeRule`]: crate::transaction::fees::FeeRule
/// [ZIP 317]: https//zips.z.cash/zip-0317
#[derive(Clone, Debug)]
pub struct FeeRule {
marginal_fee: Zatoshis,
grace_actions: usize,
p2pkh_standard_input_size: usize,
p2pkh_standard_output_size: usize,
}
impl FeeRule {
/// Construct a new FeeRule using the standard [ZIP 317] constants.
///
/// [ZIP 317]: https//zips.z.cash/zip-0317
pub fn standard() -> Self {
Self {
marginal_fee: MARGINAL_FEE,
grace_actions: GRACE_ACTIONS,
p2pkh_standard_input_size: P2PKH_STANDARD_INPUT_SIZE,
p2pkh_standard_output_size: P2PKH_STANDARD_OUTPUT_SIZE,
}
}
/// Construct a new FeeRule instance with the specified parameter values.
///
/// Using this fee rule with
/// ```compile_fail
/// marginal_fee < 5000 || grace_actions < 2
/// || p2pkh_standard_input_size > P2PKH_STANDARD_INPUT_SIZE
/// || p2pkh_standard_output_size > P2PKH_STANDARD_OUTPUT_SIZE
/// ```
/// violates ZIP 317, and might cause transactions built with it to fail.
///
/// Returns `None` if either `p2pkh_standard_input_size` or `p2pkh_standard_output_size` are
/// zero.
#[cfg(feature = "non-standard-fees")]
pub fn non_standard(
marginal_fee: Zatoshis,
grace_actions: usize,
p2pkh_standard_input_size: usize,
p2pkh_standard_output_size: usize,
) -> Option<Self> {
if p2pkh_standard_input_size == 0 || p2pkh_standard_output_size == 0 {
None
} else {
Some(Self {
marginal_fee,
grace_actions,
p2pkh_standard_input_size,
p2pkh_standard_output_size,
})
}
}
/// Returns the ZIP 317 marginal fee.
pub fn marginal_fee(&self) -> Zatoshis {
self.marginal_fee
}
/// Returns the ZIP 317 number of grace actions
pub fn grace_actions(&self) -> usize {
self.grace_actions
}
/// Returns the ZIP 317 standard P2PKH input size
pub fn p2pkh_standard_input_size(&self) -> usize {
self.p2pkh_standard_input_size
}
/// Returns the ZIP 317 standard P2PKH output size
pub fn p2pkh_standard_output_size(&self) -> usize {
self.p2pkh_standard_output_size
}
}
/// Errors that can occur in ZIP 317 fee computation
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FeeError {
/// An overflow or underflow of amount computation occurred.
Balance(BalanceError),
/// Transparent inputs provided to the fee calculation included coins that do not pay to
/// standard P2pkh scripts.
NonP2pkhInputs(Vec<OutPoint>),
}
impl From<BalanceError> for FeeError {
fn from(err: BalanceError) -> Self {
FeeError::Balance(err)
}
}
impl core::fmt::Display for FeeError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match &self {
FeeError::Balance(e) => write!(
f,
"A balance calculation violated amount validity bounds: {}.",
e
),
FeeError::NonP2pkhInputs(_) => write!(f, "Only P2PKH inputs are supported."),
}
}
}
impl super::FeeRule for FeeRule {
type Error = FeeError;
fn fee_required<P: consensus::Parameters>(
&self,
_params: &P,
_target_height: BlockHeight,
transparent_input_sizes: impl IntoIterator<Item = transparent::InputSize>,
transparent_output_sizes: impl IntoIterator<Item = usize>,
sapling_input_count: usize,
sapling_output_count: usize,
orchard_action_count: usize,
) -> Result<Zatoshis, Self::Error> {
let mut t_in_total_size: usize = 0;
let mut non_p2pkh_outpoints = vec![];
for sz in transparent_input_sizes.into_iter() {
match sz {
transparent::InputSize::Known(s) => {
t_in_total_size += s;
}
transparent::InputSize::Unknown(outpoint) => {
non_p2pkh_outpoints.push(outpoint.clone());
}
}
}
if !non_p2pkh_outpoints.is_empty() {
return Err(FeeError::NonP2pkhInputs(non_p2pkh_outpoints));
}
let t_out_total_size = transparent_output_sizes.into_iter().sum();
let ceildiv = |num: usize, den: usize| num.div_ceil(den);
let logical_actions = max(
ceildiv(t_in_total_size, self.p2pkh_standard_input_size),
ceildiv(t_out_total_size, self.p2pkh_standard_output_size),
) + max(sapling_input_count, sapling_output_count)
+ orchard_action_count;
(self.marginal_fee * max(self.grace_actions, logical_actions))
.ok_or_else(|| BalanceError::Overflow.into())
}
}