diff --git a/src/priority_fee.rs b/src/priority_fee.rs index 8680422..5812d2a 100644 --- a/src/priority_fee.rs +++ b/src/priority_fee.rs @@ -1,21 +1,26 @@ -use std::sync::Arc; -use std::time::{Duration, Instant}; +use crate::grpc_consumer::GrpcConsumer; +use crate::rpc_server::get_recommended_fee; +use crate::slot_cache::SlotCache; use cadence_macros::statsd_count; use cadence_macros::statsd_gauge; use dashmap::DashMap; use serde::Deserialize; use serde::Serialize; +use solana::storage::confirmed_block::Message; use solana_program_runtime::compute_budget::ComputeBudget; +use solana_program_runtime::prioritization_fee::PrioritizationFeeDetails; use solana_sdk::instruction::CompiledInstruction; +use solana_sdk::transaction::TransactionError; use solana_sdk::{pubkey::Pubkey, slot_history::Slot}; +use std::collections::HashMap; +use std::sync::Arc; +use std::time::{Duration, Instant}; use tokio::sync::mpsc::{channel, Receiver, Sender}; use tracing::{debug, error}; use yellowstone_grpc_proto::geyser::subscribe_update::UpdateOneof; -use yellowstone_grpc_proto::geyser::SubscribeUpdate; - -use crate::grpc_consumer::GrpcConsumer; -use crate::rpc_server::get_recommended_fee; -use crate::slot_cache::SlotCache; +use yellowstone_grpc_proto::geyser::{SubscribeUpdate, SubscribeUpdateTransactionInfo}; +use yellowstone_grpc_proto::prelude::{MessageHeader, Transaction, TransactionStatusMeta}; +use yellowstone_grpc_proto::solana; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub enum PriorityLevel { @@ -96,7 +101,142 @@ pub struct PriorityFeeTracker { priority_fees: Arc, compute_budget: ComputeBudget, slot_cache: SlotCache, - sampling_sender: Sender<(Vec, bool, Option)> + sampling_sender: Sender<(Vec, bool, Option)>, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +enum TransactionValidationError { + TransactionFailed, + TransactionMissing, + MessageMissing, + InvalidAccount, +} + +impl Into<&str> for TransactionValidationError { + fn into(self) -> &'static str { + match self { + TransactionValidationError::TransactionFailed => "txn_failed", + TransactionValidationError::TransactionMissing => "txn_missing", + TransactionValidationError::MessageMissing => "message_missing", + TransactionValidationError::InvalidAccount => "invalid_pubkey", + } + } +} + +fn extract_from_meta( + transaction_meta: Option, +) -> Result, TransactionValidationError> { + match transaction_meta { + None => Ok(Vec::with_capacity(0)), + Some(meta) => { + if meta.err.is_some() { + // statsd_count!("txn_failed", 1); + return Result::Err(TransactionValidationError::TransactionFailed); + } + let writable = meta + .loaded_writable_addresses + .into_iter() + .map(|v| Pubkey::try_from(v)) + .filter(|p| p.is_ok()) + .map(|p| p.unwrap()) + .collect::>(); + + Ok(writable) + } + } +} +fn extract_from_transaction( + mut transaction: SubscribeUpdateTransactionInfo, +) -> Result<(Message, Vec, bool), TransactionValidationError> { + let writable_accounts = extract_from_meta(transaction.meta.take())?; + let tran: &mut Transaction = transaction + .transaction + .as_mut() + .ok_or(TransactionValidationError::TransactionMissing)?; + let message: Message = tran + .message + .take() + .ok_or(TransactionValidationError::MessageMissing)?; + let is_vote = transaction.is_vote; + + Result::Ok((message, writable_accounts, is_vote)) +} + +fn extract_from_message( + message: Message, +) -> Result< + (Vec, Vec, Option), + TransactionValidationError, +> { + let account_keys = message.account_keys; + let compiled_instructions = message.instructions; + + let accounts: Result, _> = account_keys.into_iter().map(Pubkey::try_from).collect(); + if let Err(_) = accounts { + return Err(TransactionValidationError::InvalidAccount); + } + let accounts = accounts.unwrap(); + + let compiled_instructions: Vec = compiled_instructions + .iter() + .map(|ix| { + CompiledInstruction::new_from_raw_parts( + ix.program_id_index as u8, + ix.data.clone(), + ix.accounts.clone(), + ) + }) + .collect(); + + Ok((accounts, compiled_instructions, message.header)) +} + +fn calculate_priority_fee_details( + accounts: &Vec, + instructions: &Vec, + budget: &mut ComputeBudget, +) -> Result { + let instructions_for_processing: HashMap<&Pubkey, &CompiledInstruction> = instructions + .iter() + .filter_map(|ix: &CompiledInstruction| { + let account = accounts.get(ix.program_id_index as usize); + if account.is_none() { + statsd_count!("program_id_index_not_found", 1); + return None; + } + Some((account.unwrap(), ix)) + }) + .collect(); + + budget.process_instructions(instructions_for_processing.into_iter(), true, true) +} + +pub(crate) fn construct_writable_accounts( + message_accounts: Vec, + header: &Option, +) -> Vec { + if header.is_none() { + return message_accounts; + } + + let header = header.as_ref().unwrap(); + let min_pos_non_sig_write_accts = header.num_required_signatures as usize; + let max_pos_write_sig = + min_pos_non_sig_write_accts.saturating_sub(header.num_readonly_signed_accounts as usize); + let max_non_sig_write_accts = message_accounts + .len() + .checked_sub(header.num_readonly_unsigned_accounts as usize) + .unwrap_or(message_accounts.len()); + + message_accounts + .into_iter() + .enumerate() + .filter(|data: &(usize, T)| { + data.0 < max_pos_write_sig + || (data.0 >= min_pos_non_sig_write_accts && data.0 < max_non_sig_write_accts) + }) + .map(|data: (usize, T)| data.1) + .collect() } impl GrpcConsumer for PriorityFeeTracker { @@ -107,77 +247,42 @@ impl GrpcConsumer for PriorityFeeTracker { statsd_count!("txns_received", block.transactions.len() as i64); let slot = block.slot; for txn in block.transactions { - // skip failed txs - if txn.meta.map_or(false, |meta| meta.err.is_some()) { - statsd_count!("txn_failed", 1); + let res = extract_from_transaction(txn); + if let Err(error) = res { + statsd_count!(error.into(), 1); continue; } - if txn.transaction.is_none() { - statsd_count!("txn_missing", 1); - continue; - } - let transaction = txn.transaction.unwrap(); - let message = transaction.message; - if message.is_none() { - statsd_count!("message_missing", 1); + let (message, writable_accounts, is_vote) = res.unwrap(); + + let res = extract_from_message(message); + if let Err(error) = res { + statsd_count!(error.into(), 1); continue; } - let message = message.unwrap(); - let mut account_keys = message.account_keys; - for lookup in message.address_table_lookups { - account_keys.push(lookup.account_key); - } - let accounts: Vec = account_keys - .into_iter() - .filter_map(|p| { - let pubkey: Option<[u8; 32]> = p.try_into().ok(); - if pubkey.is_none() { - statsd_count!("invalid_pubkey", 1); - } - pubkey - }) - .map(|p| Pubkey::new_from_array(p)) - .collect(); - - let compiled_instructions: Vec = message - .instructions - .iter() - .map(|ix| { - CompiledInstruction::new_from_raw_parts( - ix.program_id_index as u8, - ix.data.clone(), - ix.accounts.clone(), - ) - }) - .collect(); - let instructions_for_processing: Vec<(&Pubkey, &CompiledInstruction)> = - compiled_instructions - .iter() - .filter_map(|ix| { - let account = accounts.get(ix.program_id_index as usize); - if account.is_none() { - statsd_count!("program_id_index_not_found", 1); - return None; - } - Some((account.unwrap(), ix)) - }) - .collect(); + let (accounts, instructions, header) = res.unwrap(); + let mut compute_budget = self.compute_budget; + let priority_fee_details = calculate_priority_fee_details( + &accounts, + &instructions, + &mut compute_budget, + ); + + let writable_accounts = vec!( + construct_writable_accounts(accounts, &header), + writable_accounts + ).concat(); + statsd_count!( "priority_fee_tracker.accounts_processed", - accounts.len() as i64 - ); - let priority_fee_details = self.compute_budget.clone().process_instructions( - instructions_for_processing.into_iter(), - true, - true, + writable_accounts.len() as i64 ); statsd_count!("txns_processed", 1); match priority_fee_details { Ok(priority_fee_details) => self.push_priority_fee_for_txn( slot, - accounts, + writable_accounts, priority_fee_details.get_priority(), - txn.is_vote, + is_vote, ), Err(e) => { error!("error processing priority fee details: {:?}", e); @@ -222,13 +327,12 @@ impl PriorityFeeTracker { // task to poll the queue and run comparison to see what is the diff between algos tokio::spawn(async move { loop { - match sampling_rxn.recv().await - { - Some((accounts, include_vote, lookback_period)) => - priority_fee_tracker.record_specific_fees(accounts, include_vote, lookback_period), - _ => {}, + match sampling_rxn.recv().await { + Some((accounts, include_vote, lookback_period)) => priority_fee_tracker + .record_specific_fees(accounts, include_vote, lookback_period), + _ => {} } - }; + } }); } } @@ -268,8 +372,12 @@ impl PriorityFeeTracker { ); } - fn record_specific_fees(&self, accounts: Vec, include_vote: bool, lookback_period: Option) - { + fn record_specific_fees( + &self, + accounts: Vec, + include_vote: bool, + lookback_period: Option, + ) { let old_fee = self.calculation1(&accounts, include_vote, &lookback_period); let new_fee = self.calculation2(&accounts, include_vote, &lookback_period); let new_fee_last = self.calculation2(&accounts, include_vote, &Some(1)); @@ -415,8 +523,6 @@ impl PriorityFeeTracker { } } - - // TODO: DKH - both algos should probably be in some enum (like algo1, algo2) and be passed to // this method instead of sending a bool flag. I'll refactor this in next iteration. already too many changes pub fn get_priority_fee_estimates( @@ -424,14 +530,12 @@ impl PriorityFeeTracker { accounts: Vec, include_vote: bool, lookback_period: Option, - calculation1: bool + calculation1: bool, ) -> MicroLamportPriorityFeeEstimates { let start = Instant::now(); - let micro_lamport_priority_fee_estimates = if calculation1 - { + let micro_lamport_priority_fee_estimates = if calculation1 { self.calculation1(&accounts, include_vote, &lookback_period) - } - else { + } else { self.calculation2(&accounts, include_vote, &lookback_period) }; @@ -439,9 +543,11 @@ impl PriorityFeeTracker { "get_priority_fee_estimates_time", start.elapsed().as_nanos() as u64 ); - if let Err(e) = self.sampling_sender - .try_send((accounts.to_owned(), include_vote, lookback_period.to_owned())) - { + if let Err(e) = self.sampling_sender.try_send(( + accounts.to_owned(), + include_vote, + lookback_period.to_owned(), + )) { debug!("Did not add sample for calculation, {:?}", e); } @@ -455,7 +561,12 @@ impl PriorityFeeTracker { 3. will calculate the percentile distributions for each of two groups 4. will choose the highest value from each percentile between two groups */ - fn calculation1(&self, accounts: &Vec, include_vote: bool, lookback_period: &Option) -> MicroLamportPriorityFeeEstimates { + fn calculation1( + &self, + accounts: &Vec, + include_vote: bool, + lookback_period: &Option, + ) -> MicroLamportPriorityFeeEstimates { let mut account_fees = vec![]; let mut transaction_fees = vec![]; for (i, slot_priority_fees) in self.priority_fees.iter().enumerate() { @@ -490,13 +601,17 @@ impl PriorityFeeTracker { } /* - Algo2: given the list of accounts the algorithm will: - 1. collect all the transactions fees over n slots - 2. for each specified account collect the fees and calculate the percentiles - 4. choose maximum values for each percentile between all transactions and each account - */ - fn calculation2(&self, accounts: &Vec, include_vote: bool, lookback_period: &Option) -> MicroLamportPriorityFeeEstimates { - + Algo2: given the list of accounts the algorithm will: + 1. collect all the transactions fees over n slots + 2. for each specified account collect the fees and calculate the percentiles + 4. choose maximum values for each percentile between all transactions and each account + */ + fn calculation2( + &self, + accounts: &Vec, + include_vote: bool, + lookback_period: &Option, + ) -> MicroLamportPriorityFeeEstimates { let mut slots_vec = Vec::with_capacity(self.slot_cache.len()); self.slot_cache.copy_slots(&mut slots_vec); slots_vec.sort(); @@ -508,24 +623,21 @@ impl PriorityFeeTracker { let mut micro_lamport_priority_fee_estimates = MicroLamportPriorityFeeEstimates::default(); for slot in &slots_vec[..lookback] { - if let Some(slot_priority_fees) = self.priority_fees.get(slot) - { + if let Some(slot_priority_fees) = self.priority_fees.get(slot) { if include_vote { fees.extend_from_slice(&slot_priority_fees.fees.vote_fees); } fees.extend_from_slice(&slot_priority_fees.fees.non_vote_fees); } } - micro_lamport_priority_fee_estimates = estimate_max_values(&mut fees, - micro_lamport_priority_fee_estimates); + micro_lamport_priority_fee_estimates = + estimate_max_values(&mut fees, micro_lamport_priority_fee_estimates); for account in accounts { fees.clear(); for slot in &slots_vec[..lookback] { - - if let Some(slot_priority_fees) = self.priority_fees.get(slot) - { + if let Some(slot_priority_fees) = self.priority_fees.get(slot) { let account_priority_fees = slot_priority_fees.account_fees.get(account); if let Some(account_priority_fees) = account_priority_fees { if include_vote { @@ -535,8 +647,8 @@ impl PriorityFeeTracker { } } } - micro_lamport_priority_fee_estimates = estimate_max_values(&mut fees, - micro_lamport_priority_fee_estimates); + micro_lamport_priority_fee_estimates = + estimate_max_values(&mut fees, micro_lamport_priority_fee_estimates); } micro_lamport_priority_fee_estimates } @@ -622,8 +734,7 @@ fn percentile(values: &mut Vec, percentile: Percentile) -> f64 { let n = values.len() as f64; let r = (percentile as f64 / 100.0) * (n - 1.0) + 1.0; - let val = - if r.fract() == 0.0 { + let val = if r.fract() == 0.0 { values[r as usize - 1] } else { let ri = r.trunc() as usize - 1; @@ -635,6 +746,7 @@ fn percentile(values: &mut Vec, percentile: Percentile) -> f64 { #[cfg(test)] mod tests { + use std::collections::HashSet; use cadence::{NopMetricSink, StatsdClient}; use cadence_macros::set_global_default; @@ -668,8 +780,7 @@ mod tests { } // Now test the fee estimates for a known priority level, let's say medium (50th percentile) - let estimates = - tracker.calculation1(&vec![accounts.get(0).unwrap().clone()], false, &None); + let estimates = tracker.calculation1(&vec![accounts.get(0).unwrap().clone()], false, &None); // Since the fixed fees are evenly distributed, the 50th percentile should be the middle value let expected_min_fee = 0.0; let expected_low_fee = 25.0; @@ -684,10 +795,8 @@ mod tests { assert_eq!(estimates.very_high, expected_very_high_fee); assert_eq!(estimates.unsafe_max, expected_max_fee); - // Now test the fee estimates for a known priority level, let's say medium (50th percentile) - let estimates = - tracker.calculation2(&vec![accounts.get(0).unwrap().clone()], false, &None); + let estimates = tracker.calculation2(&vec![accounts.get(0).unwrap().clone()], false, &None); // Since the fixed fees are evenly distributed, the 50th percentile should be the middle value let expected_min_fee = 0.0; let expected_low_fee = 25.0; @@ -723,12 +832,16 @@ mod tests { // Simulate adding the fixed fees as both account-specific and transaction fees for (i, fee) in fees.clone().into_iter().enumerate() { - tracker.push_priority_fee_for_txn(i as Slot, accounts.clone(), fee as u64, false); + tracker.push_priority_fee_for_txn( + i as Slot, + accounts.clone(), + fee as u64, + false, + ); } // Now test the fee estimates for a known priority level, let's say medium (50th percentile) - let estimates = - tracker.calculation1(&vec![accounts.get(0).unwrap().clone()], false, &None); + let estimates = tracker.calculation1(&vec![accounts.get(0).unwrap().clone()], false, &None); let expected_min_fee = 1.0; let expected_low_fee = 25.0; let expected_medium_fee = 50.0; @@ -742,10 +855,8 @@ mod tests { assert_eq!(estimates.very_high, expected_very_high_fee); assert_eq!(estimates.unsafe_max, expected_max_fee); - // Now test the fee estimates for a known priority level, let's say medium (50th percentile) - let estimates = - tracker.calculation2(&vec![accounts.get(0).unwrap().clone()], false, &None); + let estimates = tracker.calculation2(&vec![accounts.get(0).unwrap().clone()], false, &None); let expected_min_fee = 1.0; let expected_low_fee = 25.0; let expected_medium_fee = 50.0; @@ -781,12 +892,16 @@ mod tests { // Simulate adding the fixed fees as both account-specific and transaction fees for (i, fee) in fees.clone().into_iter().enumerate() { - tracker.push_priority_fee_for_txn(i as Slot, accounts.clone(), fee as u64, false); + tracker.push_priority_fee_for_txn( + i as Slot, + accounts.clone(), + fee as u64, + false, + ); } // Now test the fee estimates for a known priority level, let's say medium (50th percentile) - let estimates = - tracker.calculation1(&vec![accounts.get(0).unwrap().clone()], false, &None); + let estimates = tracker.calculation1(&vec![accounts.get(0).unwrap().clone()], false, &None); let expected_low_fee = 25.0; let expected_medium_fee = 50.0; let expected_high_fee = 75.0; @@ -796,10 +911,8 @@ mod tests { assert_ne!(estimates.high, expected_high_fee); assert_ne!(estimates.very_high, expected_very_high_fee); - // Now test the fee estimates for a known priority level, let's say medium (50th percentile) - let estimates = - tracker.calculation2(&vec![accounts.get(0).unwrap().clone()], false, &None); + let estimates = tracker.calculation2(&vec![accounts.get(0).unwrap().clone()], false, &None); let expected_low_fee = 25.0; let expected_medium_fee = 50.0; let expected_high_fee = 75.0; @@ -854,8 +967,7 @@ mod tests { } // Now test the fee estimates for a known priority level, let's say medium (50th percentile) - let estimates = - tracker.calculation1(&vec![account_1, account_4], false, &None); + let estimates = tracker.calculation1(&vec![account_1, account_4], false, &None); let expected_min_fee = 0.0; let expected_low_fee = 24.75; let expected_medium_fee = 49.5; @@ -869,11 +981,8 @@ mod tests { assert_eq!(estimates.very_high, expected_very_high_fee); assert_eq!(estimates.unsafe_max, expected_max_fee); - - // Now test the fee estimates for a known priority level, let's say medium (50th percentile) - let estimates = - tracker.calculation2(&vec![account_1, account_4], false, &None); + let estimates = tracker.calculation2(&vec![account_1, account_4], false, &None); let expected_min_fee = 75.0; let expected_low_fee = 81.0; let expected_medium_fee = 87.0; @@ -933,10 +1042,8 @@ mod tests { } } - // Now test the fee estimates for a known priority level, let's say medium (50th percentile) - let estimates = - tracker.calculation1(&vec![account_1, account_4], false, &None); + let estimates = tracker.calculation1(&vec![account_1, account_4], false, &None); let expected_min_fee = 0.0; let expected_low_fee = 24.75; let expected_medium_fee = 49.5; @@ -950,11 +1057,8 @@ mod tests { assert_eq!(estimates.very_high, expected_very_high_fee); assert_eq!(estimates.unsafe_max, expected_max_fee); - - // Now test the fee estimates for a known priority level, let's say medium (50th percentile) - let estimates = - tracker.calculation2(&vec![account_1, account_4], false, &None); + let estimates = tracker.calculation2(&vec![account_1, account_4], false, &None); let expected_min_fee = 3.0; let expected_low_fee = 27.0; let expected_medium_fee = 51.0; @@ -993,8 +1097,7 @@ mod tests { } // Now test the fee estimates for a known priority level, let's say medium (50th percentile) - let estimates = - tracker.calculation1(&vec![accounts.get(0).unwrap().clone()], false, &None); + let estimates = tracker.calculation1(&vec![accounts.get(0).unwrap().clone()], false, &None); // Since the fixed fees are evenly distributed, the 50th percentile should be the middle value let expected_min_fee = 0.0; let expected_low_fee = 25.0; @@ -1009,10 +1112,8 @@ mod tests { assert_eq!(estimates.very_high, expected_very_high_fee); assert_eq!(estimates.unsafe_max, expected_max_fee); - // Now test the fee estimates for a known priority level, let's say medium (50th percentile) - let estimates = - tracker.calculation2(&vec![accounts.get(0).unwrap().clone()], false, &None); + let estimates = tracker.calculation2(&vec![accounts.get(0).unwrap().clone()], false, &None); // Since the fixed fees are evenly distributed, the 50th percentile should be the middle value let expected_min_fee = 0.0; let expected_low_fee = 25.0; @@ -1027,4 +1128,147 @@ mod tests { assert_eq!(estimates.very_high, expected_very_high_fee); assert_eq!(estimates.unsafe_max, expected_max_fee); } + + #[test] + fn test_constructing_accounts() { + for (test_id, data) in generate_data().iter().enumerate() { + let (message_accounts, header, expectation) = data; + let result = construct_writable_accounts(message_accounts.clone(), header); + assert_eq!(result.len(), expectation.len()); + let expectation = &expectation.clone().into_iter().collect::>(); + let result = &result.clone().into_iter().collect::>(); + let diff1 = expectation - result; + let diff2 = result - expectation; + + assert!( + diff1.is_empty(), + "Error ${test_id}: {:?}, {:?} not equal to {:?}", + message_accounts, + header, + expectation + ); + assert!( + diff2.is_empty(), + "Error ${test_id}: {:?}, {:?} not equal to {:?}", + message_accounts, + header, + expectation + ); + } + } + + fn generate_data() -> Vec<(Vec, Option, Vec)> { + let p1 = Pubkey::new_unique(); + let p2 = Pubkey::new_unique(); + let p3 = Pubkey::new_unique(); + let p4 = Pubkey::new_unique(); + let p5 = Pubkey::new_unique(); + + vec![ + (Vec::with_capacity(0), None, Vec::with_capacity(0)), + (vec![p1], None, vec![p1]), + (vec![p1, p2], None, vec![p1, p2]), + // one unsigned account + ( + vec![p2, p3, p4], + Some(MessageHeader { + num_required_signatures: 1, + num_readonly_signed_accounts: 0, + num_readonly_unsigned_accounts: 1, + }), + vec![p2, p3], + ), + // 2 unsigned accounts + ( + vec![p2, p3, p4], + Some(MessageHeader { + num_required_signatures: 1, + num_readonly_signed_accounts: 0, + num_readonly_unsigned_accounts: 2, + }), + vec![p2], + ), + // all unsigned accounts + ( + vec![p2, p3, p4], + Some(MessageHeader { + num_required_signatures: 0, + num_readonly_signed_accounts: 1, + num_readonly_unsigned_accounts: 2, + }), + vec![p2], + ), + // should not happen but just in case we should check that we can handle bad data + // too many signatures + ( + vec![p2, p3, p4], + Some(MessageHeader { + num_required_signatures: 5, + num_readonly_signed_accounts: 0, + num_readonly_unsigned_accounts: 0, + }), + vec![p2, p3, p4], + ), + // too many read only signed + ( + vec![p2, p3, p4], + Some(MessageHeader { + num_required_signatures: 0, + num_readonly_signed_accounts: 5, + num_readonly_unsigned_accounts: 0, + }), + vec![p2, p3, p4], + ), + // too many read only signed + ( + vec![p2, p3, p4], + Some(MessageHeader { + num_required_signatures: 0, + num_readonly_signed_accounts: 0, + num_readonly_unsigned_accounts: 5, + }), + vec![p2, p3, p4], + ), + // too many read only signed + ( + vec![p3, p4, p5], + Some(MessageHeader { + num_required_signatures: 0, + num_readonly_signed_accounts: 0, + num_readonly_unsigned_accounts: 5, + }), + vec![p3, p4, p5], + ), + // Specific cases for signed read accounts + ( + vec![p2, p3, p4, p5], + Some(MessageHeader { + num_required_signatures: 2, + num_readonly_signed_accounts: 1, + num_readonly_unsigned_accounts: 1, + }), + vec![p2, p4], + ), + // Specific cases for signed read accounts + ( + vec![p2, p3, p4, p5], + Some(MessageHeader { + num_required_signatures: 2, + num_readonly_signed_accounts: 1, + num_readonly_unsigned_accounts: 2, + }), + vec![p2], + ), + // Specific cases for signed read accounts + ( + vec![p2, p3, p4, p5], + Some(MessageHeader { + num_required_signatures: 1, + num_readonly_signed_accounts: 1, + num_readonly_unsigned_accounts: 2, + }), + vec![p3], + ), + ] + } } diff --git a/src/rpc_server.rs b/src/rpc_server.rs index dd1e160..d59d9e1 100644 --- a/src/rpc_server.rs +++ b/src/rpc_server.rs @@ -23,8 +23,10 @@ use solana_account_decoder::parse_address_lookup_table::{ }; use solana_client::rpc_client::RpcClient; use solana_sdk::{pubkey::Pubkey, transaction::VersionedTransaction}; +use solana_sdk::message::MessageHeader; use solana_transaction_status::UiTransactionEncoding; use tracing::info; +use crate::priority_fee::construct_writable_accounts; pub struct AtlasPriorityFeeEstimator { pub priority_fee_tracker: Arc, @@ -232,12 +234,26 @@ fn validate_get_priority_fee_estimate_request( /// returns account keys from transaction fn get_from_account_keys(transaction: &VersionedTransaction) -> Vec { - transaction + let keys: Vec = transaction .message .static_account_keys() .iter() .map(|key| key.to_string()) - .collect() + .collect(); + + let MessageHeader { + num_required_signatures: _num_required_signatures, + num_readonly_signed_accounts: _num_readonly_signed_accounts, + num_readonly_unsigned_accounts: _num_readonly_unsigned_accounts, + .. + } = transaction.message.header(); + let rpc_header = yellowstone_grpc_proto::solana::storage::confirmed_block::MessageHeader { + num_required_signatures: *(_num_required_signatures) as u32, + num_readonly_signed_accounts: *(_num_readonly_signed_accounts) as u32, + num_readonly_unsigned_accounts: *(_num_readonly_unsigned_accounts) as u32, + }; + + construct_writable_accounts(keys, &Some(rpc_header)) } /// gets address lookup tables and then fetches them from an RPC. Returns