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