1use {
2 solana_measure::measure_us,
3 solana_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(&mut self, transaction_fee: u64, writable_accounts: Vec<Pubkey>) {
169 let (_, update_us) = measure_us!({
170 if !self.is_finalized {
171 if transaction_fee < self.min_transaction_fee {
172 self.min_transaction_fee = transaction_fee;
173 }
174
175 for write_account in writable_accounts {
176 self.min_writable_account_fees
177 .entry(write_account)
178 .and_modify(|write_lock_fee| {
179 *write_lock_fee = std::cmp::min(*write_lock_fee, transaction_fee)
180 })
181 .or_insert(transaction_fee);
182 }
183
184 self.metrics
185 .accumulate_total_prioritization_fee(transaction_fee);
186 self.metrics.update_prioritization_fee(transaction_fee);
187 } else {
188 self.metrics
189 .increment_attempted_update_on_finalized_fee_count(1);
190 }
191 });
192
193 self.metrics.accumulate_total_update_elapsed_us(update_us);
194 }
195
196 fn prune_irrelevant_writable_accounts(&mut self) {
199 self.metrics.total_writable_accounts_count = self.get_writable_accounts_count() as u64;
200 self.min_writable_account_fees
201 .retain(|_, account_fee| account_fee > &mut self.min_transaction_fee);
202 self.metrics.relevant_writable_accounts_count = self.get_writable_accounts_count() as u64;
203 }
204
205 pub fn mark_block_completed(&mut self) -> Result<(), PrioritizationFeeError> {
206 if self.is_finalized {
207 return Err(PrioritizationFeeError::BlockIsAlreadyFinalized);
208 }
209 self.prune_irrelevant_writable_accounts();
210 self.is_finalized = true;
211 Ok(())
212 }
213
214 pub fn get_min_transaction_fee(&self) -> Option<u64> {
215 (self.min_transaction_fee != u64::MAX).then_some(self.min_transaction_fee)
216 }
217
218 pub fn get_writable_account_fee(&self, key: &Pubkey) -> Option<u64> {
219 self.min_writable_account_fees.get(key).copied()
220 }
221
222 pub fn get_writable_account_fees(&self) -> impl Iterator<Item = (&Pubkey, &u64)> {
223 self.min_writable_account_fees.iter()
224 }
225
226 pub fn get_writable_accounts_count(&self) -> usize {
227 self.min_writable_account_fees.len()
228 }
229
230 pub fn is_finalized(&self) -> bool {
231 self.is_finalized
232 }
233
234 pub fn report_metrics(&self, slot: Slot) {
235 self.metrics.report(slot);
236
237 let min_transaction_fee = self.get_min_transaction_fee().unwrap_or(0);
239 let mut accounts_fees: Vec<_> = self.get_writable_account_fees().collect();
240 accounts_fees.sort_by(|lh, rh| rh.1.cmp(lh.1));
241 datapoint_info!(
242 "block_min_prioritization_fee",
243 ("slot", slot as i64, i64),
244 ("entity", "block", String),
245 ("min_prioritization_fee", min_transaction_fee as i64, i64),
246 );
247 for (account_key, fee) in accounts_fees.iter().take(10) {
248 datapoint_trace!(
249 "block_min_prioritization_fee",
250 ("slot", slot as i64, i64),
251 ("entity", account_key.to_string(), String),
252 ("min_prioritization_fee", **fee as i64, i64),
253 );
254 }
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use {super::*, solana_pubkey::Pubkey};
261
262 #[test]
263 fn test_update_prioritization_fee() {
264 solana_logger::setup();
265 let write_account_a = Pubkey::new_unique();
266 let write_account_b = Pubkey::new_unique();
267 let write_account_c = Pubkey::new_unique();
268
269 let mut prioritization_fee = PrioritizationFee::default();
270 assert!(prioritization_fee.get_min_transaction_fee().is_none());
271
272 {
277 prioritization_fee.update(5, vec![write_account_a, write_account_b]);
278 assert_eq!(5, prioritization_fee.get_min_transaction_fee().unwrap());
279 assert_eq!(
280 5,
281 prioritization_fee
282 .get_writable_account_fee(&write_account_a)
283 .unwrap()
284 );
285 assert_eq!(
286 5,
287 prioritization_fee
288 .get_writable_account_fee(&write_account_b)
289 .unwrap()
290 );
291 assert!(prioritization_fee
292 .get_writable_account_fee(&write_account_c)
293 .is_none());
294 }
295
296 {
301 prioritization_fee.update(9, vec![write_account_b, write_account_c]);
302 assert_eq!(5, prioritization_fee.get_min_transaction_fee().unwrap());
303 assert_eq!(
304 5,
305 prioritization_fee
306 .get_writable_account_fee(&write_account_a)
307 .unwrap()
308 );
309 assert_eq!(
310 5,
311 prioritization_fee
312 .get_writable_account_fee(&write_account_b)
313 .unwrap()
314 );
315 assert_eq!(
316 9,
317 prioritization_fee
318 .get_writable_account_fee(&write_account_c)
319 .unwrap()
320 );
321 }
322
323 {
328 prioritization_fee.update(2, vec![write_account_a, write_account_c]);
329 assert_eq!(2, prioritization_fee.get_min_transaction_fee().unwrap());
330 assert_eq!(
331 2,
332 prioritization_fee
333 .get_writable_account_fee(&write_account_a)
334 .unwrap()
335 );
336 assert_eq!(
337 5,
338 prioritization_fee
339 .get_writable_account_fee(&write_account_b)
340 .unwrap()
341 );
342 assert_eq!(
343 2,
344 prioritization_fee
345 .get_writable_account_fee(&write_account_c)
346 .unwrap()
347 );
348 }
349
350 {
352 prioritization_fee.prune_irrelevant_writable_accounts();
353 assert_eq!(1, prioritization_fee.min_writable_account_fees.len());
354 assert_eq!(2, prioritization_fee.get_min_transaction_fee().unwrap());
355 assert!(prioritization_fee
356 .get_writable_account_fee(&write_account_a)
357 .is_none());
358 assert_eq!(
359 5,
360 prioritization_fee
361 .get_writable_account_fee(&write_account_b)
362 .unwrap()
363 );
364 assert!(prioritization_fee
365 .get_writable_account_fee(&write_account_c)
366 .is_none());
367 }
368 }
369
370 #[test]
371 fn test_mark_block_completed() {
372 let mut prioritization_fee = PrioritizationFee::default();
373
374 assert!(prioritization_fee.mark_block_completed().is_ok());
375 assert!(prioritization_fee.mark_block_completed().is_err());
376 }
377}