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  ///
104  /// This function may panic upon overflow.
105  pub fn calculate_fee_from_weight(&self, weight: usize) -> u64 {
106    let fee =
107      self.per_weight * u64::try_from(weight).expect("couldn't convert weight (usize) to u64");
108    fee.div_ceil(self.mask) * self.mask
109  }
110
111  /// Calculate the weight from the fee.
112  ///
113  /// Returns `None` if the weight would not fit within a `usize`.
114  pub fn calculate_weight_from_fee(&self, fee: u64) -> Option<usize> {
115    usize::try_from(fee / self.per_weight).ok()
116  }
117}
118
119/// An error from the interface.
120#[derive(Clone, PartialEq, Eq, Debug, thiserror::Error)]
121pub enum FeeError {
122  /// Error with the interface.
123  #[error("interface error ({0})")]
124  InterfaceError(InterfaceError),
125  /// The fee was invalid.
126  #[error("invalid fee")]
127  InvalidFee,
128  /// The fee priority was invalid.
129  #[error("invalid fee priority")]
130  InvalidFeePriority,
131}
132
133impl From<InterfaceError> for FeeError {
134  fn from(err: InterfaceError) -> Self {
135    Self::InterfaceError(err)
136  }
137}
138
139/// An interface which provides unvalidated fee rates.
140pub trait ProvidesUnvalidatedFeeRates: Sync {
141  /// Get the recommended fee rate.
142  ///
143  /// This may be manipulated to unsafe levels and MUST be sanity checked.
144  ///
145  /// This MUST NOT be expected to be deterministic in any way.
146  fn fee_rate(
147    &self,
148    priority: FeePriority,
149  ) -> impl Send + Future<Output = Result<FeeRate, FeeError>>;
150}
151
152/// An interface which provides fee rates.
153pub trait ProvidesFeeRates: Sync {
154  /// Get the recommended fee rate.
155  ///
156  /// This MUST NOT be expected to be deterministic in any way.
157  fn fee_rate(
158    &self,
159    priority: FeePriority,
160    max_per_weight: u64,
161  ) -> impl Send + Future<Output = Result<FeeRate, FeeError>>;
162}
163
164impl<P: ProvidesUnvalidatedFeeRates> ProvidesFeeRates for P {
165  fn fee_rate(
166    &self,
167    priority: FeePriority,
168    max_per_weight: u64,
169  ) -> impl Send + Future<Output = Result<FeeRate, FeeError>> {
170    async move {
171      let fee_rate = <P as ProvidesUnvalidatedFeeRates>::fee_rate(self, priority).await?;
172      if fee_rate.per_weight > max_per_weight {
173        Err(FeeError::InvalidFee)?;
174      }
175      Ok(fee_rate)
176    }
177  }
178}