1use {
2 safecoin_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 total_prioritization_fee: u64,
18
19 total_update_elapsed_us: u64,
21}
22
23impl PrioritizationFeeMetrics {
24 fn accumulate_total_prioritization_fee(&mut self, val: u64) {
25 saturating_add_assign!(self.total_prioritization_fee, val);
26 }
27
28 fn accumulate_total_update_elapsed_us(&mut self, val: u64) {
29 saturating_add_assign!(self.total_update_elapsed_us, val);
30 }
31
32 fn report(&self, slot: Slot) {
33 datapoint_info!(
34 "block_prioritization_fee",
35 ("slot", slot as i64, i64),
36 (
37 "total_writable_accounts_count",
38 self.total_writable_accounts_count as i64,
39 i64
40 ),
41 (
42 "relevant_writable_accounts_count",
43 self.relevant_writable_accounts_count as i64,
44 i64
45 ),
46 (
47 "total_prioritization_fee",
48 self.total_prioritization_fee as i64,
49 i64
50 ),
51 (
52 "total_update_elapsed_us",
53 self.total_update_elapsed_us as i64,
54 i64
55 ),
56 );
57 }
58}
59
60pub enum PrioritizationFeeError {
61 FailGetTransactionAccountLocks,
64
65 FailGetTransactionPriorityDetails,
68
69 BlockIsAlreadyFinalized,
71}
72
73#[derive(Debug)]
78pub struct PrioritizationFee {
79 min_transaction_fee: u64,
81
82 min_writable_account_fees: HashMap<Pubkey, u64>,
84
85 is_finalized: bool,
88
89 metrics: PrioritizationFeeMetrics,
91}
92
93impl Default for PrioritizationFee {
94 fn default() -> Self {
95 PrioritizationFee {
96 min_transaction_fee: u64::MAX,
97 min_writable_account_fees: HashMap::new(),
98 is_finalized: false,
99 metrics: PrioritizationFeeMetrics::default(),
100 }
101 }
102}
103
104impl PrioritizationFee {
105 pub fn update(
107 &mut self,
108 transaction_fee: u64,
109 writable_accounts: &[Pubkey],
110 ) -> Result<(), PrioritizationFeeError> {
111 let (_, update_time) = measure!(
112 {
113 if transaction_fee < self.min_transaction_fee {
114 self.min_transaction_fee = transaction_fee;
115 }
116
117 for write_account in writable_accounts.iter() {
118 self.min_writable_account_fees
119 .entry(*write_account)
120 .and_modify(|write_lock_fee| {
121 *write_lock_fee = std::cmp::min(*write_lock_fee, transaction_fee)
122 })
123 .or_insert(transaction_fee);
124 }
125
126 self.metrics
127 .accumulate_total_prioritization_fee(transaction_fee);
128 },
129 "update_time",
130 );
131
132 self.metrics
133 .accumulate_total_update_elapsed_us(update_time.as_us());
134 Ok(())
135 }
136
137 fn prune_irrelevant_writable_accounts(&mut self) {
140 self.metrics.total_writable_accounts_count = self.get_writable_accounts_count() as u64;
141 self.min_writable_account_fees
142 .retain(|_, account_fee| account_fee > &mut self.min_transaction_fee);
143 self.metrics.relevant_writable_accounts_count = self.get_writable_accounts_count() as u64;
144 }
145
146 pub fn mark_block_completed(&mut self) -> Result<(), PrioritizationFeeError> {
147 if self.is_finalized {
148 return Err(PrioritizationFeeError::BlockIsAlreadyFinalized);
149 }
150 self.prune_irrelevant_writable_accounts();
151 self.is_finalized = true;
152 Ok(())
153 }
154
155 pub fn get_min_transaction_fee(&self) -> Option<u64> {
156 if self.min_transaction_fee != u64::MAX {
157 Some(self.min_transaction_fee)
158 } else {
159 None
160 }
161 }
162
163 pub fn get_writable_account_fee(&self, key: &Pubkey) -> Option<u64> {
164 self.min_writable_account_fees.get(key).copied()
165 }
166
167 pub fn get_writable_account_fees(&self) -> impl Iterator<Item = (&Pubkey, &u64)> {
168 self.min_writable_account_fees.iter()
169 }
170
171 pub fn get_writable_accounts_count(&self) -> usize {
172 self.min_writable_account_fees.len()
173 }
174
175 pub fn is_finalized(&self) -> bool {
176 self.is_finalized
177 }
178
179 pub fn report_metrics(&self, slot: Slot) {
180 self.metrics.report(slot);
181
182 let min_transaction_fee = self.get_min_transaction_fee().unwrap_or(0);
184 let mut accounts_fees: Vec<_> = self.get_writable_account_fees().collect();
185 accounts_fees.sort_by(|lh, rh| rh.1.cmp(lh.1));
186 datapoint_info!(
187 "block_min_prioritization_fee",
188 ("slot", slot as i64, i64),
189 ("entity", "block", String),
190 ("min_prioritization_fee", min_transaction_fee as i64, i64),
191 );
192 for (account_key, fee) in accounts_fees.iter().take(10) {
193 datapoint_trace!(
194 "block_min_prioritization_fee",
195 ("slot", slot as i64, i64),
196 ("entity", account_key.to_string(), String),
197 ("min_prioritization_fee", **fee as i64, i64),
198 );
199 }
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use {super::*, solana_sdk::pubkey::Pubkey};
206
207 #[test]
208 fn test_update_prioritization_fee() {
209 solana_logger::setup();
210 let write_account_a = Pubkey::new_unique();
211 let write_account_b = Pubkey::new_unique();
212 let write_account_c = Pubkey::new_unique();
213
214 let mut prioritization_fee = PrioritizationFee::default();
215 assert!(prioritization_fee.get_min_transaction_fee().is_none());
216
217 {
222 assert!(prioritization_fee
223 .update(5, &[write_account_a, write_account_b])
224 .is_ok());
225 assert_eq!(5, prioritization_fee.get_min_transaction_fee().unwrap());
226 assert_eq!(
227 5,
228 prioritization_fee
229 .get_writable_account_fee(&write_account_a)
230 .unwrap()
231 );
232 assert_eq!(
233 5,
234 prioritization_fee
235 .get_writable_account_fee(&write_account_b)
236 .unwrap()
237 );
238 assert!(prioritization_fee
239 .get_writable_account_fee(&write_account_c)
240 .is_none());
241 }
242
243 {
248 assert!(prioritization_fee
249 .update(9, &[write_account_b, write_account_c])
250 .is_ok());
251 assert_eq!(5, prioritization_fee.get_min_transaction_fee().unwrap());
252 assert_eq!(
253 5,
254 prioritization_fee
255 .get_writable_account_fee(&write_account_a)
256 .unwrap()
257 );
258 assert_eq!(
259 5,
260 prioritization_fee
261 .get_writable_account_fee(&write_account_b)
262 .unwrap()
263 );
264 assert_eq!(
265 9,
266 prioritization_fee
267 .get_writable_account_fee(&write_account_c)
268 .unwrap()
269 );
270 }
271
272 {
277 assert!(prioritization_fee
278 .update(2, &[write_account_a, write_account_c])
279 .is_ok());
280 assert_eq!(2, prioritization_fee.get_min_transaction_fee().unwrap());
281 assert_eq!(
282 2,
283 prioritization_fee
284 .get_writable_account_fee(&write_account_a)
285 .unwrap()
286 );
287 assert_eq!(
288 5,
289 prioritization_fee
290 .get_writable_account_fee(&write_account_b)
291 .unwrap()
292 );
293 assert_eq!(
294 2,
295 prioritization_fee
296 .get_writable_account_fee(&write_account_c)
297 .unwrap()
298 );
299 }
300
301 {
303 prioritization_fee.prune_irrelevant_writable_accounts();
304 assert_eq!(1, prioritization_fee.min_writable_account_fees.len());
305 assert_eq!(2, prioritization_fee.get_min_transaction_fee().unwrap());
306 assert!(prioritization_fee
307 .get_writable_account_fee(&write_account_a)
308 .is_none());
309 assert_eq!(
310 5,
311 prioritization_fee
312 .get_writable_account_fee(&write_account_b)
313 .unwrap()
314 );
315 assert!(prioritization_fee
316 .get_writable_account_fee(&write_account_c)
317 .is_none());
318 }
319 }
320
321 #[test]
322 fn test_mark_block_completed() {
323 let mut prioritization_fee = PrioritizationFee::default();
324
325 assert!(prioritization_fee.mark_block_completed().is_ok());
326 assert!(prioritization_fee.mark_block_completed().is_err());
327 }
328}