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_prioritization_fee: Option<u64>,
31
32 max_prioritization_fee: 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_prioritization_fee(&mut self, fee: u64) {
53 if fee == 0 {
54 self.non_prioritized_transactions_count += 1;
55 return;
56 }
57
58 self.prioritized_transactions_count += 1;
60
61 self.max_prioritization_fee = self.max_prioritization_fee.max(fee);
62
63 self.min_prioritization_fee = Some(
64 self.min_prioritization_fee
65 .map_or(fee, |min_fee| min_fee.min(fee)),
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_prioritization_fee,
79 max_prioritization_fee,
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_prioritization_fee",
117 min_prioritization_fee.unwrap_or(0) as i64,
118 i64
119 ),
120 ("max_prioritization_fee", max_prioritization_fee 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_transaction_fee: 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_transaction_fee: 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(&mut self, transaction_fee: u64, writable_accounts: Vec<Pubkey>) {
178 let (_, update_us) = measure_us!({
179 if !self.is_finalized {
180 if transaction_fee < self.min_transaction_fee {
181 self.min_transaction_fee = transaction_fee;
182 }
183
184 for write_account in writable_accounts {
185 self.min_writable_account_fees
186 .entry(write_account)
187 .and_modify(|write_lock_fee| {
188 *write_lock_fee = std::cmp::min(*write_lock_fee, transaction_fee)
189 })
190 .or_insert(transaction_fee);
191 }
192
193 self.metrics
194 .accumulate_total_prioritization_fee(transaction_fee);
195 self.metrics.update_prioritization_fee(transaction_fee);
196 } else {
197 self.metrics
198 .increment_attempted_update_on_finalized_fee_count(1);
199 }
200 });
201
202 self.metrics.accumulate_total_update_elapsed_us(update_us);
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::*, solana_pubkey::Pubkey};
270
271 #[test]
272 fn test_update_prioritization_fee() {
273 solana_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 prioritization_fee.update(5, vec![write_account_a, write_account_b]);
287 assert_eq!(5, prioritization_fee.get_min_transaction_fee().unwrap());
288 assert_eq!(
289 5,
290 prioritization_fee
291 .get_writable_account_fee(&write_account_a)
292 .unwrap()
293 );
294 assert_eq!(
295 5,
296 prioritization_fee
297 .get_writable_account_fee(&write_account_b)
298 .unwrap()
299 );
300 assert!(prioritization_fee
301 .get_writable_account_fee(&write_account_c)
302 .is_none());
303 }
304
305 {
310 prioritization_fee.update(9, vec![write_account_b, write_account_c]);
311 assert_eq!(5, prioritization_fee.get_min_transaction_fee().unwrap());
312 assert_eq!(
313 5,
314 prioritization_fee
315 .get_writable_account_fee(&write_account_a)
316 .unwrap()
317 );
318 assert_eq!(
319 5,
320 prioritization_fee
321 .get_writable_account_fee(&write_account_b)
322 .unwrap()
323 );
324 assert_eq!(
325 9,
326 prioritization_fee
327 .get_writable_account_fee(&write_account_c)
328 .unwrap()
329 );
330 }
331
332 {
337 prioritization_fee.update(2, vec![write_account_a, write_account_c]);
338 assert_eq!(2, prioritization_fee.get_min_transaction_fee().unwrap());
339 assert_eq!(
340 2,
341 prioritization_fee
342 .get_writable_account_fee(&write_account_a)
343 .unwrap()
344 );
345 assert_eq!(
346 5,
347 prioritization_fee
348 .get_writable_account_fee(&write_account_b)
349 .unwrap()
350 );
351 assert_eq!(
352 2,
353 prioritization_fee
354 .get_writable_account_fee(&write_account_c)
355 .unwrap()
356 );
357 }
358
359 {
361 prioritization_fee.prune_irrelevant_writable_accounts();
362 assert_eq!(1, prioritization_fee.min_writable_account_fees.len());
363 assert_eq!(2, prioritization_fee.get_min_transaction_fee().unwrap());
364 assert!(prioritization_fee
365 .get_writable_account_fee(&write_account_a)
366 .is_none());
367 assert_eq!(
368 5,
369 prioritization_fee
370 .get_writable_account_fee(&write_account_b)
371 .unwrap()
372 );
373 assert!(prioritization_fee
374 .get_writable_account_fee(&write_account_c)
375 .is_none());
376 }
377 }
378
379 #[test]
380 fn test_mark_block_completed() {
381 let mut prioritization_fee = PrioritizationFee::default();
382
383 assert!(prioritization_fee.mark_block_completed().is_ok());
384 assert!(prioritization_fee.mark_block_completed().is_err());
385 }
386}