monero_interface/provides_fee_rates.rs
1use core::future::Future;
2use alloc::vec::Vec;
3use std_shims::io;
4
5use zeroize::Zeroize;
6
7use monero_oxide::io::read_u64;
8
9use crate::InterfaceError;
10
11/// The priority for the fee.
12///
13/// Higher-priority transactions will be included in blocks earlier.
14#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
15pub enum FeePriority {
16 /// The `Unimportant` priority, as defined by Monero.
17 Unimportant,
18 /// The `Normal` priority, as defined by Monero.
19 Normal,
20 /// The `Elevated` priority, as defined by Monero.
21 Elevated,
22 /// The `Priority` priority, as defined by Monero.
23 Priority,
24 /// A custom priority.
25 Custom {
26 /// The numeric representation of the priority, as used within the RPC.
27 priority: u32,
28 },
29}
30
31/// https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/
32/// src/simplewallet/simplewallet.cpp#L161
33impl FeePriority {
34 /// The `u32` representation of this fee.
35 pub fn to_u32(&self) -> u32 {
36 match self {
37 FeePriority::Unimportant => 1,
38 FeePriority::Normal => 2,
39 FeePriority::Elevated => 3,
40 FeePriority::Priority => 4,
41 FeePriority::Custom { priority, .. } => *priority,
42 }
43 }
44}
45
46/// A struct containing a fee rate.
47///
48/// The fee rate is defined as a per-weight cost, along with a mask for rounding purposes.
49#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
50pub struct FeeRate {
51 /// The fee per-weight of the transaction.
52 per_weight: u64,
53 /// The mask to round with.
54 mask: u64,
55}
56
57impl FeeRate {
58 /// Construct a new fee rate.
59 ///
60 /// Returns `None` if the fee rate is invalid.
61 pub fn new(per_weight: u64, mask: u64) -> Option<FeeRate> {
62 if (per_weight == 0) || (mask == 0) {
63 None?;
64 }
65 Some(FeeRate { per_weight, mask })
66 }
67
68 /// Write the FeeRate.
69 ///
70 /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
71 /// defined serialization. This may run in time variable to its value.
72 pub fn write(&self, w: &mut impl io::Write) -> io::Result<()> {
73 w.write_all(&self.per_weight.to_le_bytes())?;
74 w.write_all(&self.mask.to_le_bytes())
75 }
76
77 /// Serialize the FeeRate to a `Vec<u8>`.
78 ///
79 /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
80 /// defined serialization. This may run in time variable to its value.
81 pub fn serialize(&self) -> Vec<u8> {
82 let mut res = Vec::with_capacity(16);
83 self.write(&mut res).expect("write failed but <Vec as io::Write> doesn't fail");
84 res
85 }
86
87 /// The fee to pay per weight.
88 pub fn per_weight(&self) -> u64 {
89 self.per_weight
90 }
91
92 /// Read a FeeRate.
93 ///
94 /// This is not a Monero protocol defined struct, and this is accordingly not a Monero protocol
95 /// defined serialization. This may run in time variable to its value.
96 pub fn read(r: &mut impl io::Read) -> io::Result<FeeRate> {
97 let per_weight = read_u64(r)?;
98 let mask = read_u64(r)?;
99 FeeRate::new(per_weight, mask).ok_or_else(|| io::Error::other("fee rate was invalid"))
100 }
101
102 /// Calculate the fee to use from the weight.
103 pub fn calculate_fee_from_weight(&self, weight: u64) -> u128 {
104 /*
105 The following is infallible. For the first line, at most, we have `(u64::MAX - 1)^2`, which
106 expands to a maximum value of `u64::MAX^2 - 2 * u64::MAX + 1`. For the second line, we have
107 a round up to the next multiple of the mask. For a rounding by the mask (a `u64`), the most
108 we will add to round up is `u64::MAX - 1`. This means the maximum value returned by this
109 function is `(u64::MAX^2 - 2 * u64::MAX + 1) + (u64::MAX - 1) = u64::MAX^2 - u64::MAX`, which
110 fits within a `u128` (as we return).
111 */
112 let fee = u128::from(self.per_weight) * u128::from(weight);
113 fee.div_ceil(u128::from(self.mask)) * u128::from(self.mask)
114 }
115
116 /// Calculate the weight from the fee.
117 ///
118 /// Returns `None` if the weight would not fit within a `usize`.
119 pub fn calculate_weight_from_fee(&self, fee: u64) -> Option<usize> {
120 usize::try_from(fee / self.per_weight).ok()
121 }
122}
123
124/// An error from the interface.
125#[derive(Clone, PartialEq, Eq, Debug, thiserror::Error)]
126pub enum FeeError {
127 /// Error with the interface.
128 #[error("interface error ({0})")]
129 InterfaceError(InterfaceError),
130 /// The fee was invalid.
131 #[error("invalid fee")]
132 InvalidFee,
133 /// The fee priority was invalid.
134 #[error("invalid fee priority")]
135 InvalidFeePriority,
136}
137
138impl From<InterfaceError> for FeeError {
139 fn from(err: InterfaceError) -> Self {
140 Self::InterfaceError(err)
141 }
142}
143
144/// An interface which provides unvalidated fee rates.
145pub trait ProvidesUnvalidatedFeeRates: Sync {
146 /// Get the recommended fee rate.
147 ///
148 /// This may be manipulated to unsafe levels and MUST be sanity checked.
149 ///
150 /// This MUST NOT be expected to be deterministic in any way.
151 fn fee_rate(
152 &self,
153 priority: FeePriority,
154 ) -> impl Send + Future<Output = Result<FeeRate, FeeError>>;
155}
156
157/// An interface which provides fee rates.
158pub trait ProvidesFeeRates: Sync {
159 /// Get the recommended fee rate.
160 ///
161 /// This MUST NOT be expected to be deterministic in any way.
162 fn fee_rate(
163 &self,
164 priority: FeePriority,
165 max_per_weight: u64,
166 ) -> impl Send + Future<Output = Result<FeeRate, FeeError>>;
167}
168
169impl<P: ProvidesUnvalidatedFeeRates> ProvidesFeeRates for P {
170 fn fee_rate(
171 &self,
172 priority: FeePriority,
173 max_per_weight: u64,
174 ) -> impl Send + Future<Output = Result<FeeRate, FeeError>> {
175 async move {
176 let fee_rate = <P as ProvidesUnvalidatedFeeRates>::fee_rate(self, priority).await?;
177 if fee_rate.per_weight > max_per_weight {
178 Err(FeeError::InvalidFee)?;
179 }
180 Ok(fee_rate)
181 }
182 }
183}