Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 66 additions & 41 deletions runtime/src/prioritization_fee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ struct PrioritizationFeeMetrics {
// Count of attempted update on finalized PrioritizationFee
attempted_update_on_finalized_fee_count: Saturating<u64>,

// Total prioritization fees included in this slot.
// Total transaction fees of non-vote transactions included in this slot.
total_prioritization_fee: Saturating<u64>,

// The minimum prioritization fee of prioritized transactions in this slot.
min_prioritization_fee: Option<u64>,
// The minimum compute unit price of prioritized transactions in this slot.
min_compute_unit_price: Option<u64>,
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the min|max_compute_unit_prices are reported as sanitized raw value, in micro-lamports; vs above total_non_vote_transaction_fee is reported in lamports


// The maximum prioritization fee of prioritized transactions in this slot.
max_prioritization_fee: u64,
// The maximum compute unit price of prioritized transactions in this slot.
max_compute_unit_price: u64,

// Accumulated time spent on tracking prioritization fee for each slot.
total_update_elapsed_us: Saturating<u64>,
Expand All @@ -49,20 +49,20 @@ impl PrioritizationFeeMetrics {
self.attempted_update_on_finalized_fee_count += val;
}

fn update_prioritization_fee(&mut self, fee: u64) {
if fee == 0 {
fn update_compute_unit_price(&mut self, cu_price: u64) {
if cu_price == 0 {
self.non_prioritized_transactions_count += 1;
return;
}

// update prioritized transaction fee metrics.
self.prioritized_transactions_count += 1;

self.max_prioritization_fee = self.max_prioritization_fee.max(fee);
self.max_compute_unit_price = self.max_compute_unit_price.max(cu_price);

self.min_prioritization_fee = Some(
self.min_prioritization_fee
.map_or(fee, |min_fee| min_fee.min(fee)),
self.min_compute_unit_price = Some(
self.min_compute_unit_price
.map_or(cu_price, |min_cu_price| min_cu_price.min(cu_price)),
);
}

Expand All @@ -75,8 +75,8 @@ impl PrioritizationFeeMetrics {
attempted_update_on_finalized_fee_count:
Saturating(attempted_update_on_finalized_fee_count),
total_prioritization_fee: Saturating(total_prioritization_fee),
min_prioritization_fee,
max_prioritization_fee,
min_compute_unit_price,
max_compute_unit_price,
total_update_elapsed_us: Saturating(total_update_elapsed_us),
} = self;
datapoint_info!(
Expand Down Expand Up @@ -113,11 +113,11 @@ impl PrioritizationFeeMetrics {
i64
),
(
"min_prioritization_fee",
min_prioritization_fee.unwrap_or(0) as i64,
"min_compute_unit_price",
min_compute_unit_price.unwrap_or(0) as i64,
i64
),
("max_prioritization_fee", max_prioritization_fee as i64, i64),
("max_compute_unit_price", max_compute_unit_price as i64, i64),
(
"total_update_elapsed_us",
total_update_elapsed_us as i64,
Expand All @@ -144,11 +144,11 @@ pub enum PrioritizationFeeError {
/// Block minimum prioritization fee stats, includes the minimum prioritization fee for a transaction in this
/// 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), ...)
/// a transaction is determined by Max( min_compute_unit_price, min_writable_account_fees(key), ...)
#[derive(Debug)]
pub struct PrioritizationFee {
// The minimum prioritization fee of transactions that landed in this block.
min_transaction_fee: u64,
min_compute_unit_price: u64,

// The minimum prioritization fee of each writable account in transactions in this block.
min_writable_account_fees: HashMap<Pubkey, u64>,
Expand All @@ -164,7 +164,7 @@ pub struct PrioritizationFee {
impl Default for PrioritizationFee {
fn default() -> Self {
PrioritizationFee {
min_transaction_fee: u64::MAX,
min_compute_unit_price: u64::MAX,
min_writable_account_fees: HashMap::new(),
is_finalized: false,
metrics: PrioritizationFeeMetrics::default(),
Expand All @@ -174,25 +174,30 @@ impl Default for PrioritizationFee {

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<Pubkey>) {
pub fn update(
&mut self,
compute_unit_price: u64,
prioritization_fee: u64,
writable_accounts: Vec<Pubkey>,
) {
let (_, update_us) = measure_us!({
if !self.is_finalized {
if transaction_fee < self.min_transaction_fee {
self.min_transaction_fee = transaction_fee;
if compute_unit_price < self.min_compute_unit_price {
self.min_compute_unit_price = compute_unit_price;
}

for write_account in writable_accounts {
self.min_writable_account_fees
.entry(write_account)
.and_modify(|write_lock_fee| {
*write_lock_fee = std::cmp::min(*write_lock_fee, transaction_fee)
*write_lock_fee = std::cmp::min(*write_lock_fee, compute_unit_price)
})
.or_insert(transaction_fee);
.or_insert(compute_unit_price);
}

self.metrics
.accumulate_total_prioritization_fee(transaction_fee);
self.metrics.update_prioritization_fee(transaction_fee);
.accumulate_total_prioritization_fee(prioritization_fee);
self.metrics.update_compute_unit_price(compute_unit_price);
} else {
self.metrics
.increment_attempted_update_on_finalized_fee_count(1);
Expand All @@ -207,7 +212,7 @@ impl PrioritizationFee {
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);
.retain(|_, account_fee| account_fee > &mut self.min_compute_unit_price);
self.metrics.relevant_writable_accounts_count = self.get_writable_accounts_count() as u64;
}

Expand All @@ -220,8 +225,8 @@ impl PrioritizationFee {
Ok(())
}

pub fn get_min_transaction_fee(&self) -> Option<u64> {
(self.min_transaction_fee != u64::MAX).then_some(self.min_transaction_fee)
pub fn get_min_compute_unit_price(&self) -> Option<u64> {
(self.min_compute_unit_price != u64::MAX).then_some(self.min_compute_unit_price)
}

pub fn get_writable_account_fee(&self, key: &Pubkey) -> Option<u64> {
Expand Down Expand Up @@ -250,22 +255,23 @@ mod tests {
use {super::*, solana_pubkey::Pubkey};

#[test]
fn test_update_prioritization_fee() {
fn test_update_compute_unit_price() {
solana_logger::setup();
let write_account_a = Pubkey::new_unique();
let write_account_b = Pubkey::new_unique();
let write_account_c = Pubkey::new_unique();
let tx_fee = 10;

let mut prioritization_fee = PrioritizationFee::default();
assert!(prioritization_fee.get_min_transaction_fee().is_none());
assert!(prioritization_fee.get_min_compute_unit_price().is_none());

// Assert for 1st transaction
// [fee, write_accounts...] --> [block, account_a, account_b, account_c]
// [cu_px, write_accounts...] --> [block, account_a, account_b, account_c]
// -----------------------------------------------------------------------
// [5, a, b ] --> [5, 5, 5, nil ]
{
prioritization_fee.update(5, vec![write_account_a, write_account_b]);
assert_eq!(5, prioritization_fee.get_min_transaction_fee().unwrap());
prioritization_fee.update(5, tx_fee, vec![write_account_a, write_account_b]);
assert_eq!(5, prioritization_fee.get_min_compute_unit_price().unwrap());
assert_eq!(
5,
prioritization_fee
Expand All @@ -284,12 +290,12 @@ mod tests {
}

// Assert for second transaction:
// [fee, write_accounts...] --> [block, account_a, account_b, account_c]
// [cu_px, write_accounts...] --> [block, account_a, account_b, account_c]
// -----------------------------------------------------------------------
// [9, b, c ] --> [5, 5, 5, 9 ]
{
prioritization_fee.update(9, vec![write_account_b, write_account_c]);
assert_eq!(5, prioritization_fee.get_min_transaction_fee().unwrap());
prioritization_fee.update(9, tx_fee, vec![write_account_b, write_account_c]);
assert_eq!(5, prioritization_fee.get_min_compute_unit_price().unwrap());
assert_eq!(
5,
prioritization_fee
Expand All @@ -311,12 +317,12 @@ mod tests {
}

// Assert for third transaction:
// [fee, write_accounts...] --> [block, account_a, account_b, account_c]
// [cu_px, write_accounts...] --> [block, account_a, account_b, account_c]
// -----------------------------------------------------------------------
// [2, a, c ] --> [2, 2, 5, 2 ]
{
prioritization_fee.update(2, vec![write_account_a, write_account_c]);
assert_eq!(2, prioritization_fee.get_min_transaction_fee().unwrap());
prioritization_fee.update(2, tx_fee, vec![write_account_a, write_account_c]);
assert_eq!(2, prioritization_fee.get_min_compute_unit_price().unwrap());
assert_eq!(
2,
prioritization_fee
Expand All @@ -341,7 +347,7 @@ mod tests {
{
prioritization_fee.prune_irrelevant_writable_accounts();
assert_eq!(1, prioritization_fee.min_writable_account_fees.len());
assert_eq!(2, prioritization_fee.get_min_transaction_fee().unwrap());
assert_eq!(2, prioritization_fee.get_min_compute_unit_price().unwrap());
assert!(prioritization_fee
.get_writable_account_fee(&write_account_a)
.is_none());
Expand All @@ -357,6 +363,25 @@ mod tests {
}
}

#[test]
fn test_total_prioritization_fee() {
let mut prioritization_fee = PrioritizationFee::default();
prioritization_fee.update(0, 10, vec![]);
assert_eq!(10, prioritization_fee.metrics.total_prioritization_fee.0);

prioritization_fee.update(10, u64::MAX, vec![]);
assert_eq!(
u64::MAX,
prioritization_fee.metrics.total_prioritization_fee.0
);

prioritization_fee.update(10, 100, vec![]);
assert_eq!(
u64::MAX,
prioritization_fee.metrics.total_prioritization_fee.0
);
}

#[test]
fn test_mark_block_completed() {
let mut prioritization_fee = PrioritizationFee::default();
Expand Down
46 changes: 37 additions & 9 deletions runtime/src/prioritization_fee_cache.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use {
crate::{bank::Bank, prioritization_fee::*},
crate::{bank::Bank, prioritization_fee::PrioritizationFee},
crossbeam_channel::{unbounded, Receiver, Sender, TryRecvError},
log::*,
solana_accounts_db::account_locks::validate_account_locks,
Expand Down Expand Up @@ -47,6 +47,9 @@ struct PrioritizationFeeCacheMetrics {

// Accumulated time spent on finalizing block prioritization fees.
total_block_finalize_elapsed_us: AtomicU64,

// Accumulated time spent on calculate transaction fees.
total_calculate_prioritization_fee_elapsed_us: AtomicU64,
}

impl PrioritizationFeeCacheMetrics {
Expand Down Expand Up @@ -80,6 +83,11 @@ impl PrioritizationFeeCacheMetrics {
.fetch_add(val, Ordering::Relaxed);
}

fn accumulate_total_calculate_prioritization_fee_elapsed_us(&self, val: u64) {
self.total_calculate_prioritization_fee_elapsed_us
.fetch_add(val, Ordering::Relaxed);
}

fn report(&self, slot: Slot) {
datapoint_info!(
"block_prioritization_fee_counters",
Expand Down Expand Up @@ -117,6 +125,12 @@ impl PrioritizationFeeCacheMetrics {
.swap(0, Ordering::Relaxed) as i64,
i64
),
(
"total_calculate_prioritization_fee_elapsed_us",
self.total_calculate_prioritization_fee_elapsed_us
.swap(0, Ordering::Relaxed) as i64,
i64
),
);
}
}
Expand All @@ -126,7 +140,8 @@ enum CacheServiceUpdate {
TransactionUpdate {
slot: Slot,
bank_id: BankId,
transaction_fee: u64,
compute_unit_price: u64,
prioritization_fee: u64,
writable_accounts: Vec<Pubkey>,
},
BankFinalized {
Expand Down Expand Up @@ -233,11 +248,21 @@ impl PrioritizationFeeCache {
.map(|(_, key)| *key)
.collect();

let (prioritization_fee, calculate_prioritization_fee_us) = measure_us!({
solana_fee_structure::FeeBudgetLimits::from(compute_budget_limits)
.prioritization_fee
});
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making additional fee calculation in hot path might be an issue, reporting its us, testing in testnet

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

metrics reported from devbox indicates near 0 us

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I don't like the additional work here.

It's also worth noting that I've had a WIP PR to make PrioritizationFeeCache rpc-only - we don't need to do any of this on a block-producing node; but then we'd lose the metrics.

See my other comment on reporting all fees.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I felt the same. Was focusing on Trent's particular use case, when one is curious about how cu-price is bidding up, it helps to put total fee collected as ref point. But yea, I don't love to have to calculate fee again here just for reporting.

One benefit of reporting here vs from cost-tracking is, here only report when block is opportunistically confirmed. Wondering if adding fee to transaction meta is a good idea - I chatted about it before, don't remember why we didn't do it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but we won't report this on building nodes if we make prio-fee-cache optional (we should).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not separate infra, but the cost tracking is currently where we report other objective block facts around the cost. I feel it makes sense to just lop it near there for now so we have semething without reinventing a metric system.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i mostly want the separate infra 'cause 5000 identical datapoints is kinda useless load on the metrics db

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed, but i feel we should have this metric asap even if repeated. then can figure out how we want to report going forward separately, but don't lose data time

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. Do we have objection of reporting total_priority_fee at cost of one multiplying (eg FeeBudgetLimits::from(compute_budget_limits))?

Can add total transaction fee reporting at Cost Tracker separately.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's fine

self.metrics
.accumulate_total_calculate_prioritization_fee_elapsed_us(
calculate_prioritization_fee_us,
);

self.sender
.send(CacheServiceUpdate::TransactionUpdate {
slot: bank.slot(),
bank_id: bank.bank_id(),
transaction_fee: compute_budget_limits.compute_unit_price,
compute_unit_price: compute_budget_limits.compute_unit_price,
prioritization_fee,
writable_accounts,
})
.unwrap_or_else(|err| {
Expand Down Expand Up @@ -271,7 +296,8 @@ impl PrioritizationFeeCache {
unfinalized: &mut UnfinalizedPrioritizationFees,
slot: Slot,
bank_id: BankId,
transaction_fee: u64,
compute_unit_price: u64,
prioritization_fee: u64,
writable_accounts: Vec<Pubkey>,
metrics: &PrioritizationFeeCacheMetrics,
) {
Expand All @@ -280,7 +306,7 @@ impl PrioritizationFeeCache {
.or_default()
.entry(bank_id)
.or_default()
.update(transaction_fee, writable_accounts));
.update(compute_unit_price, prioritization_fee, writable_accounts));
metrics.accumulate_total_entry_update_elapsed_us(entry_update_us);
metrics.accumulate_successful_transaction_update_count(1);
}
Expand Down Expand Up @@ -374,13 +400,15 @@ impl PrioritizationFeeCache {
CacheServiceUpdate::TransactionUpdate {
slot,
bank_id,
transaction_fee,
compute_unit_price,
prioritization_fee,
writable_accounts,
} => Self::update_cache(
&mut unfinalized,
slot,
bank_id,
transaction_fee,
compute_unit_price,
prioritization_fee,
writable_accounts,
&metrics,
),
Expand Down Expand Up @@ -414,7 +442,7 @@ impl PrioritizationFeeCache {
.iter()
.map(|(slot, slot_prioritization_fee)| {
let mut fee = slot_prioritization_fee
.get_min_transaction_fee()
.get_min_compute_unit_price()
.unwrap_or_default();
for account_key in account_keys {
if let Some(account_fee) =
Expand Down Expand Up @@ -549,7 +577,7 @@ mod tests {
sync_finalize_priority_fee_for_test(&prioritization_fee_cache, slot, bank.bank_id());
let lock = prioritization_fee_cache.cache.read().unwrap();
let fee = lock.get(&slot).unwrap();
assert_eq!(2, fee.get_min_transaction_fee().unwrap());
assert_eq!(2, fee.get_min_compute_unit_price().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());
Expand Down
Loading