diff --git a/rpc-client-api/src/config.rs b/rpc-client-api/src/config.rs index 04c4dac743..50176d309c 100644 --- a/rpc-client-api/src/config.rs +++ b/rpc-client-api/src/config.rs @@ -354,3 +354,9 @@ pub struct RpcContextConfig { pub commitment: Option, pub min_context_slot: Option, } + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RpcRecentPrioritizationFeesConfig { + pub percentile: Option, +} diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 453779e57f..dbf69149ea 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -2225,10 +2225,18 @@ impl JsonRpcRequestProcessor { fn get_recent_prioritization_fees( &self, pubkeys: Vec, + percentile: Option, ) -> Result> { - Ok(self - .prioritization_fee_cache - .get_prioritization_fees(&pubkeys) + let cache = match percentile { + Some(percentile) => self + .prioritization_fee_cache + .get_prioritization_fees2(&pubkeys, percentile), + None => self + .prioritization_fee_cache + .get_prioritization_fees(&pubkeys), + }; + + Ok(cache .into_iter() .map(|(slot, prioritization_fee)| RpcPrioritizationFee { slot, @@ -3451,6 +3459,7 @@ pub mod rpc_full { &self, meta: Self::Metadata, pubkey_strs: Option>, + config: Option, ) -> Result>; } @@ -4151,6 +4160,7 @@ pub mod rpc_full { &self, meta: Self::Metadata, pubkey_strs: Option>, + config: Option, ) -> Result> { let pubkey_strs = pubkey_strs.unwrap_or_default(); debug!( @@ -4166,7 +4176,17 @@ pub mod rpc_full { .into_iter() .map(|pubkey_str| verify_pubkey(&pubkey_str)) .collect::>>()?; - meta.get_recent_prioritization_fees(pubkeys) + + let RpcRecentPrioritizationFeesConfig { percentile } = config.unwrap_or_default(); + if let Some(percentile) = percentile { + if percentile > 10_000 { + return Err(Error::invalid_params( + "Percentile is too big; max value is 10000".to_owned(), + )); + } + } + + meta.get_recent_prioritization_fees(pubkeys, percentile) } } } diff --git a/runtime/src/prioritization_fee.rs b/runtime/src/prioritization_fee.rs index 45425059f9..8d3e9abc95 100644 --- a/runtime/src/prioritization_fee.rs +++ b/runtime/src/prioritization_fee.rs @@ -136,13 +136,13 @@ pub enum PrioritizationFeeError { /// block; and the minimum fee for each writable account in all transactions in this block. The only relevant /// write account minimum fees are those greater than the block minimum transaction fee, because the minimum fee needed to land /// a transaction is determined by Max( min_transaction_fee, min_writable_account_fees(key), ...) -#[derive(Debug)] +#[derive(Debug, Default)] pub struct PrioritizationFee { - // The minimum prioritization fee of transactions that landed in this block. - min_transaction_fee: u64, + // Prioritization fee of transactions that landed in this block. + transaction_fees: Vec, - // The minimum prioritization fee of each writable account in transactions in this block. - min_writable_account_fees: HashMap, + // Prioritization fee of each writable account in transactions in this block. + writable_account_fees: HashMap>, // Default to `false`, set to `true` when a block is completed, therefore the minimum fees recorded // are finalized, and can be made available for use (e.g., RPC query) @@ -152,34 +152,19 @@ pub struct PrioritizationFee { metrics: PrioritizationFeeMetrics, } -impl Default for PrioritizationFee { - fn default() -> Self { - PrioritizationFee { - min_transaction_fee: u64::MAX, - min_writable_account_fees: HashMap::new(), - is_finalized: false, - metrics: PrioritizationFeeMetrics::default(), - } - } -} - impl PrioritizationFee { /// Update self for minimum transaction fee in the block and minimum fee for each writable account. pub fn update(&mut self, transaction_fee: u64, writable_accounts: Vec) { let (_, update_time) = measure!( { if !self.is_finalized { - if transaction_fee < self.min_transaction_fee { - self.min_transaction_fee = transaction_fee; - } + self.transaction_fees.push(transaction_fee); for write_account in writable_accounts { - self.min_writable_account_fees + self.writable_account_fees .entry(write_account) - .and_modify(|write_lock_fee| { - *write_lock_fee = std::cmp::min(*write_lock_fee, transaction_fee) - }) - .or_insert(transaction_fee); + .or_default() + .push(transaction_fee); } self.metrics @@ -197,38 +182,54 @@ impl PrioritizationFee { .accumulate_total_update_elapsed_us(update_time.as_us()); } - /// Accounts that have minimum fees lesser or equal to the minimum fee in the block are redundant, they are - /// removed to reduce memory footprint when mark_block_completed() is called. - fn prune_irrelevant_writable_accounts(&mut self) { - self.metrics.total_writable_accounts_count = self.get_writable_accounts_count() as u64; - self.min_writable_account_fees - .retain(|_, account_fee| account_fee > &mut self.min_transaction_fee); - self.metrics.relevant_writable_accounts_count = self.get_writable_accounts_count() as u64; - } - pub fn mark_block_completed(&mut self) -> Result<(), PrioritizationFeeError> { if self.is_finalized { return Err(PrioritizationFeeError::BlockIsAlreadyFinalized); } - self.prune_irrelevant_writable_accounts(); self.is_finalized = true; + + self.transaction_fees.sort(); + for fees in self.writable_account_fees.values_mut() { + fees.sort() + } + + self.metrics.total_writable_accounts_count = self.get_writable_accounts_count() as u64; + self.metrics.relevant_writable_accounts_count = self.get_writable_accounts_count() as u64; + Ok(()) } pub fn get_min_transaction_fee(&self) -> Option { - (self.min_transaction_fee != u64::MAX).then_some(self.min_transaction_fee) + self.transaction_fees.first().copied() + } + + fn get_percentile(fees: &[u64], percentile: u16) -> Option { + let index = (percentile as usize).min(9_999) * fees.len() / 10_000; + fees.get(index).copied() + } + + pub fn get_transaction_fee(&self, percentile: u16) -> Option { + Self::get_percentile(&self.transaction_fees, percentile) + } + + pub fn get_min_writable_account_fee(&self, key: &Pubkey) -> Option { + self.writable_account_fees + .get(key) + .and_then(|fees| fees.first().copied()) } - pub fn get_writable_account_fee(&self, key: &Pubkey) -> Option { - self.min_writable_account_fees.get(key).copied() + pub fn get_writable_account_fee(&self, key: &Pubkey, percentile: u16) -> Option { + self.writable_account_fees + .get(key) + .and_then(|fees| Self::get_percentile(fees, percentile)) } - pub fn get_writable_account_fees(&self) -> impl Iterator { - self.min_writable_account_fees.iter() + pub fn get_writable_account_fees(&self) -> impl Iterator)> { + self.writable_account_fees.iter() } pub fn get_writable_accounts_count(&self) -> usize { - self.min_writable_account_fees.len() + self.writable_account_fees.len() } pub fn is_finalized(&self) -> bool { @@ -240,20 +241,28 @@ impl PrioritizationFee { // report this slot's min_transaction_fee and top 10 min_writable_account_fees let min_transaction_fee = self.get_min_transaction_fee().unwrap_or(0); - let mut accounts_fees: Vec<_> = self.get_writable_account_fees().collect(); - accounts_fees.sort_by(|lh, rh| rh.1.cmp(lh.1)); datapoint_info!( "block_min_prioritization_fee", ("slot", slot as i64, i64), ("entity", "block", String), ("min_prioritization_fee", min_transaction_fee as i64, i64), ); - for (account_key, fee) in accounts_fees.iter().take(10) { + + let mut accounts_fees: Vec<(&Pubkey, u64)> = self + .get_writable_account_fees() + .filter_map(|(account, fees)| { + fees.first() + .copied() + .map(|min_account_fee| (account, min_transaction_fee.min(min_account_fee))) + }) + .collect(); + accounts_fees.sort_by(|lh, rh| rh.1.cmp(&lh.1)); + for (account_key, fee) in accounts_fees.into_iter().take(10) { datapoint_trace!( "block_min_prioritization_fee", ("slot", slot as i64, i64), ("entity", account_key.to_string(), String), - ("min_prioritization_fee", **fee as i64, i64), + ("min_prioritization_fee", fee as i64, i64), ); } } @@ -279,22 +288,26 @@ mod tests { // [5, a, b ] --> [5, 5, 5, nil ] { prioritization_fee.update(5, vec![write_account_a, write_account_b]); + assert!(prioritization_fee.mark_block_completed().is_ok()); + assert_eq!(5, prioritization_fee.get_min_transaction_fee().unwrap()); assert_eq!( 5, prioritization_fee - .get_writable_account_fee(&write_account_a) + .get_min_writable_account_fee(&write_account_a) .unwrap() ); assert_eq!( 5, prioritization_fee - .get_writable_account_fee(&write_account_b) + .get_min_writable_account_fee(&write_account_b) .unwrap() ); assert!(prioritization_fee - .get_writable_account_fee(&write_account_c) + .get_min_writable_account_fee(&write_account_c) .is_none()); + + prioritization_fee.is_finalized = false; } // Assert for second transaction: @@ -303,25 +316,29 @@ mod tests { // [9, b, c ] --> [5, 5, 5, 9 ] { prioritization_fee.update(9, vec![write_account_b, write_account_c]); + assert!(prioritization_fee.mark_block_completed().is_ok()); + assert_eq!(5, prioritization_fee.get_min_transaction_fee().unwrap()); assert_eq!( 5, prioritization_fee - .get_writable_account_fee(&write_account_a) + .get_min_writable_account_fee(&write_account_a) .unwrap() ); assert_eq!( 5, prioritization_fee - .get_writable_account_fee(&write_account_b) + .get_min_writable_account_fee(&write_account_b) .unwrap() ); assert_eq!( 9, prioritization_fee - .get_writable_account_fee(&write_account_c) + .get_min_writable_account_fee(&write_account_c) .unwrap() ); + + prioritization_fee.is_finalized = false; } // Assert for third transaction: @@ -330,44 +347,56 @@ mod tests { // [2, a, c ] --> [2, 2, 5, 2 ] { prioritization_fee.update(2, vec![write_account_a, write_account_c]); + assert!(prioritization_fee.mark_block_completed().is_ok()); + assert_eq!(2, prioritization_fee.get_min_transaction_fee().unwrap()); assert_eq!( 2, prioritization_fee - .get_writable_account_fee(&write_account_a) + .get_min_writable_account_fee(&write_account_a) .unwrap() ); assert_eq!( 5, prioritization_fee - .get_writable_account_fee(&write_account_b) + .get_min_writable_account_fee(&write_account_b) .unwrap() ); assert_eq!( 2, prioritization_fee - .get_writable_account_fee(&write_account_c) + .get_min_writable_account_fee(&write_account_c) .unwrap() ); + + prioritization_fee.is_finalized = false; } - // assert after prune, account a and c should be removed from cache to save space + // assert after sort { - prioritization_fee.prune_irrelevant_writable_accounts(); - assert_eq!(1, prioritization_fee.min_writable_account_fees.len()); + prioritization_fee.update(2, vec![write_account_a, write_account_c]); + assert!(prioritization_fee.mark_block_completed().is_ok()); + assert_eq!(2, prioritization_fee.get_min_transaction_fee().unwrap()); - assert!(prioritization_fee - .get_writable_account_fee(&write_account_a) - .is_none()); + assert_eq!(3, prioritization_fee.writable_account_fees.len()); + assert_eq!( + 2, + prioritization_fee + .get_min_writable_account_fee(&write_account_a) + .unwrap() + ); assert_eq!( 5, prioritization_fee - .get_writable_account_fee(&write_account_b) + .get_min_writable_account_fee(&write_account_b) + .unwrap() + ); + assert_eq!( + 2, + prioritization_fee + .get_min_writable_account_fee(&write_account_c) .unwrap() ); - assert!(prioritization_fee - .get_writable_account_fee(&write_account_c) - .is_none()); } } diff --git a/runtime/src/prioritization_fee_cache.rs b/runtime/src/prioritization_fee_cache.rs index 796fafbb41..909f5535ba 100644 --- a/runtime/src/prioritization_fee_cache.rs +++ b/runtime/src/prioritization_fee_cache.rs @@ -413,7 +413,32 @@ impl PrioritizationFeeCache { .unwrap_or_default(); for account_key in account_keys { if let Some(account_fee) = - slot_prioritization_fee.get_writable_account_fee(account_key) + slot_prioritization_fee.get_min_writable_account_fee(account_key) + { + fee = std::cmp::max(fee, account_fee); + } + } + (*slot, fee) + }) + .collect() + } + + pub fn get_prioritization_fees2( + &self, + account_keys: &[Pubkey], + percentile: u16, + ) -> Vec<(Slot, u64)> { + self.cache + .read() + .unwrap() + .iter() + .map(|(slot, slot_prioritization_fee)| { + let mut fee = slot_prioritization_fee + .get_transaction_fee(percentile) + .unwrap_or_default(); + for account_key in account_keys { + if let Some(account_fee) = + slot_prioritization_fee.get_writable_account_fee(account_key, percentile) { fee = std::cmp::max(fee, account_fee); } @@ -546,9 +571,18 @@ mod tests { let lock = prioritization_fee_cache.cache.read().unwrap(); let fee = lock.get(&slot).unwrap(); assert_eq!(2, fee.get_min_transaction_fee().unwrap()); - assert!(fee.get_writable_account_fee(&write_account_a).is_none()); - assert_eq!(5, fee.get_writable_account_fee(&write_account_b).unwrap()); - assert!(fee.get_writable_account_fee(&write_account_c).is_none()); + assert_eq!( + 2, + fee.get_min_writable_account_fee(&write_account_a).unwrap() + ); + assert_eq!( + 5, + fee.get_min_writable_account_fee(&write_account_b).unwrap() + ); + assert_eq!( + 2, + fee.get_min_writable_account_fee(&write_account_c).unwrap() + ); } }