BankingStageErrorsTrackingS.../src/block_info.rs

503 lines
18 KiB
Rust

use crate::alt_store::ALTStore;
use itertools::Itertools;
use serde::Serialize;
use solana_sdk::{
borsh0_10::try_from_slice_unchecked,
compute_budget::{self, ComputeBudgetInstruction},
instruction::CompiledInstruction,
message::{
v0::{self, MessageAddressTableLookup},
MessageHeader, VersionedMessage,
},
pubkey::Pubkey,
signature::Signature,
slot_history::Slot,
};
use std::{
collections::{HashMap, HashSet},
sync::Arc,
};
use std::rc::Rc;
#[derive(Serialize, Debug, Clone)]
pub struct PrioFeeData {
pub max: Option<u64>,
pub min: Option<u64>,
pub p75: Option<u64>,
pub p90: Option<u64>,
pub p95: Option<u64>,
pub med: Option<u64>,
}
impl PrioFeeData {
pub fn new(pf_vec: &Vec<u64>) -> Self {
let mut vec = pf_vec.clone();
vec.sort();
Self {
max: vec.last().cloned(),
min: vec.first().cloned(),
p75: (pf_vec.len() > 1).then(|| pf_vec[pf_vec.len() * 75 / 100]),
p90: (pf_vec.len() > 1).then(|| pf_vec[pf_vec.len() * 90 / 100]),
p95: (pf_vec.len() > 1).then(|| pf_vec[pf_vec.len() * 95 / 100]),
med: (pf_vec.len() > 1).then(|| pf_vec[pf_vec.len() / 2]),
}
}
}
#[derive(Serialize, Debug, Clone)]
pub struct AccountUsage {
pub key: String,
pub is_write_locked: bool,
pub cu_requested: i64,
pub cu_consumed: i64,
pub prioritization_fee_data: PrioFeeData,
}
pub struct AccountData {
pub key: String,
pub cu_requested: u64,
pub cu_consumed: u64,
pub vec_pf: Vec<u64>,
}
impl From<(&AccountData, bool)> for AccountUsage {
fn from(value: (&AccountData, bool)) -> Self {
let (account_data, is_write_locked) = value;
AccountUsage {
key: account_data.key.clone(),
cu_requested: account_data.cu_requested as i64,
cu_consumed: account_data.cu_consumed as i64,
is_write_locked,
prioritization_fee_data: PrioFeeData::new(&account_data.vec_pf),
}
}
}
#[derive(Serialize, Debug)]
pub struct PrioritizationFeesInfo {
pub p_min: u64,
pub p_median: u64,
pub p_75: u64,
pub p_90: u64,
pub p_max: u64,
pub med_cu: Option<u64>,
pub p75_cu: Option<u64>,
pub p90_cu: Option<u64>,
pub p95_cu: Option<u64>,
}
#[derive(Clone)]
pub struct TransactionAccount {
pub key: Pubkey,
pub is_writable: bool,
pub is_signer: bool,
pub is_alt: bool,
}
pub struct BlockTransactionInfo {
pub signature: String,
pub processed_slot: i64,
pub is_successful: bool,
pub cu_requested: i64,
pub cu_consumed: i64,
pub prioritization_fees: i64,
pub supp_infos: String,
pub accounts: Vec<TransactionAccount>,
}
pub struct BlockInfo {
pub block_hash: String,
pub slot: i64,
pub leader_identity: Option<String>,
pub successful_transactions: i64,
pub processed_transactions: i64,
pub total_cu_used: i64,
pub total_cu_requested: i64,
pub heavily_locked_accounts: Vec<AccountUsage>,
pub sup_info: Option<PrioritizationFeesInfo>,
pub transactions: Vec<BlockTransactionInfo>,
}
impl BlockInfo {
pub async fn process_versioned_message(
atl_store: &Arc<ALTStore>,
signature: String,
slot: Slot,
message: &VersionedMessage,
prio_fees_in_block: &mut Vec<(u64, u64)>,
writelocked_accounts: &mut HashMap<Pubkey, AccountData>,
readlocked_accounts: &mut HashMap<Pubkey, AccountData>,
cu_consumed: u64,
total_cu_requested: &mut u64,
is_vote: bool,
is_successful: bool,
) -> Option<BlockTransactionInfo> {
let (cu_requested, prio_fees, nb_ix_except_cb) = {
let mut cu_request: Option<u64> = None;
let mut prio_fees: Option<u64> = None;
let mut nb_ix_except_cb: u64 = 0;
for ix in message.instructions() {
if ix
.program_id(message.static_account_keys())
.eq(&compute_budget::id())
{
let ix_which =
try_from_slice_unchecked::<ComputeBudgetInstruction>(ix.data.as_slice());
if let Ok(ix_which) = ix_which {
match ix_which {
ComputeBudgetInstruction::RequestUnitsDeprecated {
units,
additional_fee,
} => {
cu_request = Some(units as u64);
if additional_fee > 0 {
prio_fees = Some(
(units as u64)
.saturating_mul(1000)
.saturating_div(additional_fee as u64),
);
}
}
ComputeBudgetInstruction::SetComputeUnitLimit(units) => {
cu_request = Some(units as u64)
}
ComputeBudgetInstruction::SetComputeUnitPrice(price) => {
prio_fees = Some(price)
}
_ => {}
}
}
} else {
nb_ix_except_cb += 1;
}
}
(cu_request, prio_fees, nb_ix_except_cb)
};
let prioritization_fees = prio_fees.unwrap_or_default();
prio_fees_in_block.push((prioritization_fees, cu_consumed));
let cu_requested =
std::cmp::min(1_400_000, cu_requested.unwrap_or(200000 * nb_ix_except_cb));
*total_cu_requested += cu_requested;
if !is_vote {
let mut accounts = message
.static_account_keys()
.iter().cloned()
.enumerate()
.map(|(index, account_pk)| TransactionAccount {
key: account_pk,
is_writable: message.is_maybe_writable(index),
is_signer: message.is_signer(index),
is_alt: false,
})
.collect_vec();
if let Some(atl_messages) = message.address_table_lookups() {
for atl_message in atl_messages {
let atl_acc = atl_message.account_key;
let mut atl_accs = atl_store
.get_accounts(
&atl_acc,
&atl_message.writable_indexes,
&atl_message.readonly_indexes,
)
.await;
accounts.append(&mut atl_accs);
}
}
for writable_account in accounts
.iter()
.filter(|x| x.is_writable)
.map(|x| x.key)
{
match writelocked_accounts.get_mut(&writable_account) {
Some(x) => {
x.cu_requested += cu_requested;
x.cu_consumed += cu_consumed;
x.vec_pf.push(prioritization_fees);
}
None => {
writelocked_accounts.insert(
writable_account,
AccountData {
key: fd_bs58::encode_32(writable_account),
cu_consumed,
cu_requested,
vec_pf: vec![prioritization_fees],
},
);
}
}
}
for readable_account in accounts
.iter()
.filter(|x| !x.is_writable)
.map(|x| x.key)
{
match readlocked_accounts.get_mut(&readable_account) {
Some(x) => {
x.cu_requested += cu_requested;
x.cu_consumed += cu_consumed;
x.vec_pf.push(prioritization_fees);
}
None => {
readlocked_accounts.insert(
readable_account,
AccountData {
key: fd_bs58::encode_32(readable_account),
cu_consumed,
cu_requested,
vec_pf: vec![prioritization_fees],
},
);
}
}
}
Some(BlockTransactionInfo {
signature,
processed_slot: slot as i64,
is_successful,
cu_requested: cu_requested as i64,
cu_consumed: cu_consumed as i64,
prioritization_fees: prioritization_fees as i64,
supp_infos: String::new(),
accounts,
})
} else {
None
}
}
pub fn calculate_account_usage(
writelocked_accounts: &HashMap<Pubkey, AccountData>,
readlocked_accounts: &HashMap<Pubkey, AccountData>,
) -> Vec<AccountUsage> {
let mut accounts = writelocked_accounts
.iter()
.map(|(_, data)| AccountUsage::from((data, true)))
.collect_vec();
let mut heavily_readlocked_accounts = readlocked_accounts
.iter()
.map(|(_, data)| AccountUsage::from((data, false)))
.collect_vec();
accounts.append(&mut heavily_readlocked_accounts);
accounts
}
pub fn calculate_supp_info(
prio_fees_in_block: &mut Vec<(u64, u64)>,
) -> Option<PrioritizationFeesInfo> {
if !prio_fees_in_block.is_empty() {
// get stats by transaction
prio_fees_in_block.sort_by(|a, b| a.0.cmp(&b.0));
let median_index = prio_fees_in_block.len() / 2;
let p75_index = prio_fees_in_block.len() * 75 / 100;
let p90_index = prio_fees_in_block.len() * 90 / 100;
let p_min = prio_fees_in_block[0].0;
let p_median = prio_fees_in_block[median_index].0;
let p_75 = prio_fees_in_block[p75_index].0;
let p_90 = prio_fees_in_block[p90_index].0;
let p_max = prio_fees_in_block.last().map(|x| x.0).unwrap_or_default();
let mut med_cu = None;
let mut p75_cu = None;
let mut p90_cu = None;
let mut p95_cu = None;
// get stats by CU
let cu_sum: u64 = prio_fees_in_block.iter().map(|x| x.1).sum();
let mut agg: u64 = 0;
for (prio, cu) in prio_fees_in_block {
agg = agg + *cu;
if med_cu.is_none() && agg > (cu_sum as f64 * 0.5) as u64 {
med_cu = Some(*prio);
}
if p75_cu.is_none() && agg > (cu_sum as f64 * 0.75) as u64 {
p75_cu = Some(*prio)
}
if p90_cu.is_none() && agg > (cu_sum as f64 * 0.9) as u64 {
p90_cu = Some(*prio);
}
if p95_cu.is_none() && agg > (cu_sum as f64 * 0.95) as u64 {
p95_cu = Some(*prio)
}
}
Some(PrioritizationFeesInfo {
p_min,
p_median,
p_75,
p_90,
p_max,
med_cu,
p75_cu,
p90_cu,
p95_cu,
})
} else {
None
}
}
pub async fn new(
atl_store: Arc<ALTStore>,
block: &yellowstone_grpc_proto_original::prelude::SubscribeUpdateBlock,
) -> BlockInfo {
let block_hash = block.blockhash.clone();
let _span = tracing::debug_span!("map_block_info", block_hash = block_hash);
let slot = block.slot;
let leader_identity = block
.rewards
.as_ref()
.map(|rewards| {
rewards
.rewards
.iter()
.find(|x| x.reward_type == 1)
.map(|x| x.pubkey.clone())
})
.unwrap_or(None);
let successful_transactions = block
.transactions
.iter()
.filter(|x| x.meta.as_ref().map(|x| x.err.is_none()).unwrap_or(false))
.count() as u64;
let processed_transactions = block.transactions.len() as u64;
let total_cu_used = block
.transactions
.iter()
.map(|x| {
x.meta
.as_ref()
.map(|x| x.compute_units_consumed.unwrap_or(0))
.unwrap_or(0)
})
.sum::<u64>() as i64;
let mut writelocked_accounts: HashMap<Pubkey, AccountData> = HashMap::new();
let mut readlocked_accounts: HashMap<Pubkey, AccountData> = HashMap::new();
let mut total_cu_requested: u64 = 0;
let mut prio_fees_in_block = vec![];
let mut lookup_tables = HashSet::new();
let sigs_and_messages = block
.transactions
.iter()
.filter_map(|transaction| {
let Some(tx) = &transaction.transaction else {
return None;
};
let Some(message) = &tx.message else {
return None;
};
let Some(header) = &message.header else {
return None;
};
let Some(meta) = &transaction.meta else {
return None;
};
let signature = fd_bs58::encode_64(&tx.signatures[0]);
let message = VersionedMessage::V0(v0::Message {
header: MessageHeader {
num_required_signatures: header.num_required_signatures as u8,
num_readonly_signed_accounts: header.num_readonly_signed_accounts as u8,
num_readonly_unsigned_accounts: header.num_readonly_unsigned_accounts as u8,
},
account_keys: message
.account_keys
.clone()
.into_iter()
.map(|key| {
let bytes: [u8; 32] =
key.try_into().unwrap_or(Pubkey::default().to_bytes());
Pubkey::new_from_array(bytes)
})
.collect(),
recent_blockhash: solana_sdk::hash::Hash::new(&message.recent_blockhash),
instructions: message
.instructions
.clone()
.into_iter()
.map(|ix| CompiledInstruction {
program_id_index: ix.program_id_index as u8,
accounts: ix.accounts,
data: ix.data,
})
.collect(),
address_table_lookups: message
.address_table_lookups
.clone()
.into_iter()
.map(|table| {
let bytes: [u8; 32] = table
.account_key
.try_into()
.unwrap_or(Pubkey::default().to_bytes());
let account_key = Pubkey::new_from_array(bytes);
lookup_tables.insert(account_key);
MessageAddressTableLookup {
account_key,
writable_indexes: table.writable_indexes,
readonly_indexes: table.readonly_indexes,
}
})
.collect(),
});
Some((signature, message, meta, transaction.is_vote))
})
.collect_vec();
atl_store
.start_loading_missing_alts(&lookup_tables.iter().collect_vec())
.await;
let mut block_transactions = vec![];
for (signature, message, meta, is_vote) in sigs_and_messages {
let tx = Self::process_versioned_message(
&atl_store,
signature,
slot,
&message,
&mut prio_fees_in_block,
&mut writelocked_accounts,
&mut readlocked_accounts,
meta.compute_units_consumed.unwrap_or(0),
&mut total_cu_requested,
is_vote,
meta.err.is_none(),
)
.await;
if let Some(tx) = tx {
block_transactions.push(tx);
}
}
let heavily_locked_accounts =
Self::calculate_account_usage(&writelocked_accounts, &readlocked_accounts);
let sup_info = Self::calculate_supp_info(&mut prio_fees_in_block);
BlockInfo {
block_hash,
slot: slot as i64,
leader_identity,
successful_transactions: successful_transactions as i64,
processed_transactions: processed_transactions as i64,
total_cu_used,
total_cu_requested: total_cu_requested as i64,
heavily_locked_accounts,
sup_info,
transactions: block_transactions,
}
}
}