1use {
2 solana_clock::Slot,
3 solana_measure::measure_us,
4 solana_pubkey::Pubkey,
5 std::{collections::HashMap, num::Saturating},
6};
7
8#[derive(Debug, Default)]
9struct PrioritizationFeeMetrics {
10 total_writable_accounts_count: u64,
12
13 relevant_writable_accounts_count: u64,
16
17 prioritized_transactions_count: Saturating<u64>,
19
20 non_prioritized_transactions_count: Saturating<u64>,
22
23 attempted_update_on_finalized_fee_count: Saturating<u64>,
25
26 total_prioritization_fee: Saturating<u64>,
28
29 min_compute_unit_price: Option<u64>,
31
32 max_compute_unit_price: u64,
34
35 total_update_elapsed_us: Saturating<u64>,
37}
38
39impl PrioritizationFeeMetrics {
40 fn accumulate_total_prioritization_fee(&mut self, val: u64) {
41 self.total_prioritization_fee += val;
42 }
43
44 fn accumulate_total_update_elapsed_us(&mut self, val: u64) {
45 self.total_update_elapsed_us += val;
46 }
47
48 fn increment_attempted_update_on_finalized_fee_count(&mut self, val: u64) {
49 self.attempted_update_on_finalized_fee_count += val;
50 }
51
52 fn update_compute_unit_price(&mut self, cu_price: u64) {
53 if cu_price == 0 {
54 self.non_prioritized_transactions_count += 1;
55 return;
56 }
57
58 self.prioritized_transactions_count += 1;
60
61 self.max_compute_unit_price = self.max_compute_unit_price.max(cu_price);
62
63 self.min_compute_unit_price = Some(
64 self.min_compute_unit_price
65 .map_or(cu_price, |min_cu_price| min_cu_price.min(cu_price)),
66 );
67 }
68
69 fn report(&self, slot: Slot) {
70 let &PrioritizationFeeMetrics {
71 total_writable_accounts_count,
72 relevant_writable_accounts_count,
73 prioritized_transactions_count: Saturating(prioritized_transactions_count),
74 non_prioritized_transactions_count: Saturating(non_prioritized_transactions_count),
75 attempted_update_on_finalized_fee_count:
76 Saturating(attempted_update_on_finalized_fee_count),
77 total_prioritization_fee: Saturating(total_prioritization_fee),
78 min_compute_unit_price,
79 max_compute_unit_price,
80 total_update_elapsed_us: Saturating(total_update_elapsed_us),
81 } = self;
82 datapoint_info!(
83 "block_prioritization_fee",
84 ("slot", slot as i64, i64),
85 (
86 "total_writable_accounts_count",
87 total_writable_accounts_count as i64,
88 i64
89 ),
90 (
91 "relevant_writable_accounts_count",
92 relevant_writable_accounts_count as i64,
93 i64
94 ),
95 (
96 "prioritized_transactions_count",
97 prioritized_transactions_count as i64,
98 i64
99 ),
100 (
101 "non_prioritized_transactions_count",
102 non_prioritized_transactions_count as i64,
103 i64
104 ),
105 (
106 "attempted_update_on_finalized_fee_count",
107 attempted_update_on_finalized_fee_count as i64,
108 i64
109 ),
110 (
111 "total_prioritization_fee",
112 total_prioritization_fee as i64,
113 i64
114 ),
115 (
116 "min_compute_unit_price",
117 min_compute_unit_price.unwrap_or(0) as i64,
118 i64
119 ),
120 ("max_compute_unit_price", max_compute_unit_price as i64, i64),
121 (
122 "total_update_elapsed_us",
123 total_update_elapsed_us as i64,
124 i64
125 ),
126 );
127 }
128}
129
130#[derive(Debug)]
131pub enum PrioritizationFeeError {
132 FailGetTransactionAccountLocks,
135
136 FailGetComputeBudgetDetails,
139
140 BlockIsAlreadyFinalized,
142}
143
144#[derive(Debug)]
149pub struct PrioritizationFee {
150 min_compute_unit_price: u64,
152
153 min_writable_account_fees: HashMap<Pubkey, u64>,
155
156 is_finalized: bool,
159
160 metrics: PrioritizationFeeMetrics,
162}
163
164impl Default for PrioritizationFee {
165 fn default() -> Self {
166 PrioritizationFee {
167 min_compute_unit_price: u64::MAX,
168 min_writable_account_fees: HashMap::new(),
169 is_finalized: false,
170 metrics: PrioritizationFeeMetrics::default(),
171 }
172 }
173}
174
175impl PrioritizationFee {
176 pub fn update(
178 &mut self,
179 compute_unit_price: u64,
180 prioritization_fee: u64,
181 writable_accounts: Vec<Pubkey>,
182 ) {
183 let (_, update_us) = measure_us!({
184 if !self.is_finalized {
185 if compute_unit_price < self.min_compute_unit_price {
186 self.min_compute_unit_price = compute_unit_price;
187 }
188
189 for write_account in writable_accounts {
190 self.min_writable_account_fees
191 .entry(write_account)
192 .and_modify(|write_lock_fee| {
193 *write_lock_fee = std::cmp::min(*write_lock_fee, compute_unit_price)
194 })
195 .or_insert(compute_unit_price);
196 }
197
198 self.metrics
199 .accumulate_total_prioritization_fee(prioritization_fee);
200 self.metrics.update_compute_unit_price(compute_unit_price);
201 } else {
202 self.metrics
203 .increment_attempted_update_on_finalized_fee_count(1);
204 }
205 });
206
207 self.metrics.accumulate_total_update_elapsed_us(update_us);
208 }
209
210 fn prune_irrelevant_writable_accounts(&mut self) {
213 self.metrics.total_writable_accounts_count = self.get_writable_accounts_count() as u64;
214 self.min_writable_account_fees
215 .retain(|_, account_fee| account_fee > &mut self.min_compute_unit_price);
216 self.metrics.relevant_writable_accounts_count = self.get_writable_accounts_count() as u64;
217 }
218
219 pub fn mark_block_completed(&mut self) -> Result<(), PrioritizationFeeError> {
220 if self.is_finalized {
221 return Err(PrioritizationFeeError::BlockIsAlreadyFinalized);
222 }
223 self.prune_irrelevant_writable_accounts();
224 self.is_finalized = true;
225 Ok(())
226 }
227
228 pub fn get_min_compute_unit_price(&self) -> Option<u64> {
229 (self.min_compute_unit_price != u64::MAX).then_some(self.min_compute_unit_price)
230 }
231
232 pub fn get_writable_account_fee(&self, key: &Pubkey) -> Option<u64> {
233 self.min_writable_account_fees.get(key).copied()
234 }
235
236 pub fn get_writable_account_fees(&self) -> impl Iterator<Item = (&Pubkey, &u64)> {
237 self.min_writable_account_fees.iter()
238 }
239
240 pub fn get_writable_accounts_count(&self) -> usize {
241 self.min_writable_account_fees.len()
242 }
243
244 pub fn is_finalized(&self) -> bool {
245 self.is_finalized
246 }
247
248 pub fn report_metrics(&self, slot: Slot) {
249 self.metrics.report(slot);
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use {super::*, solana_pubkey::Pubkey};
256
257 #[test]
258 fn test_update_compute_unit_price() {
259 solana_logger::setup();
260 let write_account_a = Pubkey::new_unique();
261 let write_account_b = Pubkey::new_unique();
262 let write_account_c = Pubkey::new_unique();
263 let tx_fee = 10;
264
265 let mut prioritization_fee = PrioritizationFee::default();
266 assert!(prioritization_fee.get_min_compute_unit_price().is_none());
267
268 {
273 prioritization_fee.update(5, tx_fee, vec![write_account_a, write_account_b]);
274 assert_eq!(5, prioritization_fee.get_min_compute_unit_price().unwrap());
275 assert_eq!(
276 5,
277 prioritization_fee
278 .get_writable_account_fee(&write_account_a)
279 .unwrap()
280 );
281 assert_eq!(
282 5,
283 prioritization_fee
284 .get_writable_account_fee(&write_account_b)
285 .unwrap()
286 );
287 assert!(prioritization_fee
288 .get_writable_account_fee(&write_account_c)
289 .is_none());
290 }
291
292 {
297 prioritization_fee.update(9, tx_fee, vec![write_account_b, write_account_c]);
298 assert_eq!(5, prioritization_fee.get_min_compute_unit_price().unwrap());
299 assert_eq!(
300 5,
301 prioritization_fee
302 .get_writable_account_fee(&write_account_a)
303 .unwrap()
304 );
305 assert_eq!(
306 5,
307 prioritization_fee
308 .get_writable_account_fee(&write_account_b)
309 .unwrap()
310 );
311 assert_eq!(
312 9,
313 prioritization_fee
314 .get_writable_account_fee(&write_account_c)
315 .unwrap()
316 );
317 }
318
319 {
324 prioritization_fee.update(2, tx_fee, vec![write_account_a, write_account_c]);
325 assert_eq!(2, prioritization_fee.get_min_compute_unit_price().unwrap());
326 assert_eq!(
327 2,
328 prioritization_fee
329 .get_writable_account_fee(&write_account_a)
330 .unwrap()
331 );
332 assert_eq!(
333 5,
334 prioritization_fee
335 .get_writable_account_fee(&write_account_b)
336 .unwrap()
337 );
338 assert_eq!(
339 2,
340 prioritization_fee
341 .get_writable_account_fee(&write_account_c)
342 .unwrap()
343 );
344 }
345
346 {
348 prioritization_fee.prune_irrelevant_writable_accounts();
349 assert_eq!(1, prioritization_fee.min_writable_account_fees.len());
350 assert_eq!(2, prioritization_fee.get_min_compute_unit_price().unwrap());
351 assert!(prioritization_fee
352 .get_writable_account_fee(&write_account_a)
353 .is_none());
354 assert_eq!(
355 5,
356 prioritization_fee
357 .get_writable_account_fee(&write_account_b)
358 .unwrap()
359 );
360 assert!(prioritization_fee
361 .get_writable_account_fee(&write_account_c)
362 .is_none());
363 }
364 }
365
366 #[test]
367 fn test_total_prioritization_fee() {
368 let mut prioritization_fee = PrioritizationFee::default();
369 prioritization_fee.update(0, 10, vec![]);
370 assert_eq!(10, prioritization_fee.metrics.total_prioritization_fee.0);
371
372 prioritization_fee.update(10, u64::MAX, vec![]);
373 assert_eq!(
374 u64::MAX,
375 prioritization_fee.metrics.total_prioritization_fee.0
376 );
377
378 prioritization_fee.update(10, 100, vec![]);
379 assert_eq!(
380 u64::MAX,
381 prioritization_fee.metrics.total_prioritization_fee.0
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}