1use {
2 miraland_measure::measure,
3 miraland_sdk::{clock::Slot, pubkey::Pubkey, saturating_add_assign},
4 std::collections::HashMap,
5};
6
7#[derive(Debug, Default)]
8struct PrioritizationFeeMetrics {
9 total_writable_accounts_count: u64,
11
12 relevant_writable_accounts_count: u64,
15
16 prioritized_transactions_count: u64,
18
19 non_prioritized_transactions_count: u64,
21
22 attempted_update_on_finalized_fee_count: u64,
24
25 total_prioritization_fee: u64,
27
28 min_prioritization_fee: Option<u64>,
30
31 max_prioritization_fee: u64,
33
34 total_update_elapsed_us: u64,
36}
37
38impl PrioritizationFeeMetrics {
39 fn accumulate_total_prioritization_fee(&mut self, val: u64) {
40 saturating_add_assign!(self.total_prioritization_fee, val);
41 }
42
43 fn accumulate_total_update_elapsed_us(&mut self, val: u64) {
44 saturating_add_assign!(self.total_update_elapsed_us, val);
45 }
46
47 fn increment_attempted_update_on_finalized_fee_count(&mut self, val: u64) {
48 saturating_add_assign!(self.attempted_update_on_finalized_fee_count, val);
49 }
50
51 fn update_prioritization_fee(&mut self, fee: u64) {
52 if fee == 0 {
53 saturating_add_assign!(self.non_prioritized_transactions_count, 1);
54 return;
55 }
56
57 saturating_add_assign!(self.prioritized_transactions_count, 1);
59
60 self.max_prioritization_fee = self.max_prioritization_fee.max(fee);
61
62 self.min_prioritization_fee = Some(
63 self.min_prioritization_fee
64 .map_or(fee, |min_fee| min_fee.min(fee)),
65 );
66 }
67
68 fn report(&self, slot: Slot) {
69 datapoint_info!(
70 "block_prioritization_fee",
71 ("slot", slot as i64, i64),
72 (
73 "total_writable_accounts_count",
74 self.total_writable_accounts_count as i64,
75 i64
76 ),
77 (
78 "relevant_writable_accounts_count",
79 self.relevant_writable_accounts_count as i64,
80 i64
81 ),
82 (
83 "prioritized_transactions_count",
84 self.prioritized_transactions_count as i64,
85 i64
86 ),
87 (
88 "non_prioritized_transactions_count",
89 self.non_prioritized_transactions_count as i64,
90 i64
91 ),
92 (
93 "attempted_update_on_finalized_fee_count",
94 self.attempted_update_on_finalized_fee_count as i64,
95 i64
96 ),
97 (
98 "total_prioritization_fee",
99 self.total_prioritization_fee as i64,
100 i64
101 ),
102 (
103 "min_prioritization_fee",
104 self.min_prioritization_fee.unwrap_or(0) as i64,
105 i64
106 ),
107 (
108 "max_prioritization_fee",
109 self.max_prioritization_fee as i64,
110 i64
111 ),
112 (
113 "total_update_elapsed_us",
114 self.total_update_elapsed_us as i64,
115 i64
116 ),
117 );
118 }
119}
120
121#[derive(Debug)]
122pub enum PrioritizationFeeError {
123 FailGetTransactionAccountLocks,
126
127 FailGetComputeBudgetDetails,
130
131 BlockIsAlreadyFinalized,
133}
134
135#[derive(Debug)]
140pub struct PrioritizationFee {
141 min_transaction_fee: u64,
143
144 min_writable_account_fees: HashMap<Pubkey, u64>,
146
147 is_finalized: bool,
150
151 metrics: PrioritizationFeeMetrics,
153}
154
155impl Default for PrioritizationFee {
156 fn default() -> Self {
157 PrioritizationFee {
158 min_transaction_fee: u64::MAX,
159 min_writable_account_fees: HashMap::new(),
160 is_finalized: false,
161 metrics: PrioritizationFeeMetrics::default(),
162 }
163 }
164}
165
166impl PrioritizationFee {
167 pub fn update(
169 &mut self,
170 transaction_fee: u64,
171 writable_accounts: &[Pubkey],
172 ) -> Result<(), PrioritizationFeeError> {
173 let (_, update_time) = measure!(
174 {
175 if !self.is_finalized {
176 if transaction_fee < self.min_transaction_fee {
177 self.min_transaction_fee = transaction_fee;
178 }
179
180 for write_account in writable_accounts.iter() {
181 self.min_writable_account_fees
182 .entry(*write_account)
183 .and_modify(|write_lock_fee| {
184 *write_lock_fee = std::cmp::min(*write_lock_fee, transaction_fee)
185 })
186 .or_insert(transaction_fee);
187 }
188
189 self.metrics
190 .accumulate_total_prioritization_fee(transaction_fee);
191 self.metrics.update_prioritization_fee(transaction_fee);
192 } else {
193 self.metrics
194 .increment_attempted_update_on_finalized_fee_count(1);
195 }
196 },
197 "update_time",
198 );
199
200 self.metrics
201 .accumulate_total_update_elapsed_us(update_time.as_us());
202 Ok(())
203 }
204
205 fn prune_irrelevant_writable_accounts(&mut self) {
208 self.metrics.total_writable_accounts_count = self.get_writable_accounts_count() as u64;
209 self.min_writable_account_fees
210 .retain(|_, account_fee| account_fee > &mut self.min_transaction_fee);
211 self.metrics.relevant_writable_accounts_count = self.get_writable_accounts_count() as u64;
212 }
213
214 pub fn mark_block_completed(&mut self) -> Result<(), PrioritizationFeeError> {
215 if self.is_finalized {
216 return Err(PrioritizationFeeError::BlockIsAlreadyFinalized);
217 }
218 self.prune_irrelevant_writable_accounts();
219 self.is_finalized = true;
220 Ok(())
221 }
222
223 pub fn get_min_transaction_fee(&self) -> Option<u64> {
224 (self.min_transaction_fee != u64::MAX).then_some(self.min_transaction_fee)
225 }
226
227 pub fn get_writable_account_fee(&self, key: &Pubkey) -> Option<u64> {
228 self.min_writable_account_fees.get(key).copied()
229 }
230
231 pub fn get_writable_account_fees(&self) -> impl Iterator<Item = (&Pubkey, &u64)> {
232 self.min_writable_account_fees.iter()
233 }
234
235 pub fn get_writable_accounts_count(&self) -> usize {
236 self.min_writable_account_fees.len()
237 }
238
239 pub fn is_finalized(&self) -> bool {
240 self.is_finalized
241 }
242
243 pub fn report_metrics(&self, slot: Slot) {
244 self.metrics.report(slot);
245
246 let min_transaction_fee = self.get_min_transaction_fee().unwrap_or(0);
248 let mut accounts_fees: Vec<_> = self.get_writable_account_fees().collect();
249 accounts_fees.sort_by(|lh, rh| rh.1.cmp(lh.1));
250 datapoint_info!(
251 "block_min_prioritization_fee",
252 ("slot", slot as i64, i64),
253 ("entity", "block", String),
254 ("min_prioritization_fee", min_transaction_fee as i64, i64),
255 );
256 for (account_key, fee) in accounts_fees.iter().take(10) {
257 datapoint_trace!(
258 "block_min_prioritization_fee",
259 ("slot", slot as i64, i64),
260 ("entity", account_key.to_string(), String),
261 ("min_prioritization_fee", **fee as i64, i64),
262 );
263 }
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use {super::*, miraland_sdk::pubkey::Pubkey};
270
271 #[test]
272 fn test_update_prioritization_fee() {
273 miraland_logger::setup();
274 let write_account_a = Pubkey::new_unique();
275 let write_account_b = Pubkey::new_unique();
276 let write_account_c = Pubkey::new_unique();
277
278 let mut prioritization_fee = PrioritizationFee::default();
279 assert!(prioritization_fee.get_min_transaction_fee().is_none());
280
281 {
286 assert!(prioritization_fee
287 .update(5, &[write_account_a, write_account_b])
288 .is_ok());
289 assert_eq!(5, prioritization_fee.get_min_transaction_fee().unwrap());
290 assert_eq!(
291 5,
292 prioritization_fee
293 .get_writable_account_fee(&write_account_a)
294 .unwrap()
295 );
296 assert_eq!(
297 5,
298 prioritization_fee
299 .get_writable_account_fee(&write_account_b)
300 .unwrap()
301 );
302 assert!(prioritization_fee
303 .get_writable_account_fee(&write_account_c)
304 .is_none());
305 }
306
307 {
312 assert!(prioritization_fee
313 .update(9, &[write_account_b, write_account_c])
314 .is_ok());
315 assert_eq!(5, prioritization_fee.get_min_transaction_fee().unwrap());
316 assert_eq!(
317 5,
318 prioritization_fee
319 .get_writable_account_fee(&write_account_a)
320 .unwrap()
321 );
322 assert_eq!(
323 5,
324 prioritization_fee
325 .get_writable_account_fee(&write_account_b)
326 .unwrap()
327 );
328 assert_eq!(
329 9,
330 prioritization_fee
331 .get_writable_account_fee(&write_account_c)
332 .unwrap()
333 );
334 }
335
336 {
341 assert!(prioritization_fee
342 .update(2, &[write_account_a, write_account_c])
343 .is_ok());
344 assert_eq!(2, prioritization_fee.get_min_transaction_fee().unwrap());
345 assert_eq!(
346 2,
347 prioritization_fee
348 .get_writable_account_fee(&write_account_a)
349 .unwrap()
350 );
351 assert_eq!(
352 5,
353 prioritization_fee
354 .get_writable_account_fee(&write_account_b)
355 .unwrap()
356 );
357 assert_eq!(
358 2,
359 prioritization_fee
360 .get_writable_account_fee(&write_account_c)
361 .unwrap()
362 );
363 }
364
365 {
367 prioritization_fee.prune_irrelevant_writable_accounts();
368 assert_eq!(1, prioritization_fee.min_writable_account_fees.len());
369 assert_eq!(2, prioritization_fee.get_min_transaction_fee().unwrap());
370 assert!(prioritization_fee
371 .get_writable_account_fee(&write_account_a)
372 .is_none());
373 assert_eq!(
374 5,
375 prioritization_fee
376 .get_writable_account_fee(&write_account_b)
377 .unwrap()
378 );
379 assert!(prioritization_fee
380 .get_writable_account_fee(&write_account_c)
381 .is_none());
382 }
383 }
384
385 #[test]
386 fn test_mark_block_completed() {
387 let mut prioritization_fee = PrioritizationFee::default();
388
389 assert!(prioritization_fee.mark_block_completed().is_ok());
390 assert!(prioritization_fee.mark_block_completed().is_err());
391 }
392}