diff --git a/Cargo.lock b/Cargo.lock index ce428b22a3283b..623e90fe3917a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5432,6 +5432,7 @@ dependencies = [ "solana-nohash-hasher", "solana-program-runtime", "solana-rayon-threadlimit", + "solana-runtime-transaction", "solana-sdk", "solana-stake-program", "solana-svm", @@ -5536,6 +5537,7 @@ dependencies = [ "solana-banks-interface", "solana-client", "solana-runtime", + "solana-runtime-transaction", "solana-sdk", "solana-send-transaction-service", "solana-svm", @@ -6005,12 +6007,14 @@ dependencies = [ "solana-rpc", "solana-rpc-client-api", "solana-runtime", + "solana-runtime-transaction", "solana-sdk", "solana-send-transaction-service", "solana-stake-program", "solana-streamer", "solana-svm", "solana-tpu-client", + "solana-transaction-metrics-tracker", "solana-transaction-status", "solana-turbine", "solana-unified-scheduler-pool", @@ -6128,6 +6132,7 @@ dependencies = [ "solana-metrics", "solana-perf", "solana-rayon-threadlimit", + "solana-runtime-transaction", "solana-sdk", ] @@ -6364,6 +6369,7 @@ dependencies = [ "solana-program-runtime", "solana-rayon-threadlimit", "solana-runtime", + "solana-runtime-transaction", "solana-sdk", "solana-stake-program", "solana-storage-bigtable", @@ -6859,6 +6865,7 @@ dependencies = [ "solana-rayon-threadlimit", "solana-rpc-client-api", "solana-runtime", + "solana-runtime-transaction", "solana-sdk", "solana-send-transaction-service", "solana-stake-program", @@ -6993,6 +7000,7 @@ dependencies = [ "ed25519-dalek", "flate2", "fnv", + "histogram", "im", "index_list", "itertools", @@ -7036,6 +7044,7 @@ dependencies = [ "solana-program-runtime", "solana-rayon-threadlimit", "solana-runtime", + "solana-runtime-transaction", "solana-sdk", "solana-stake-program", "solana-svm", @@ -7276,9 +7285,11 @@ dependencies = [ "rand 0.8.5", "rustls", "solana-logger", + "solana-measure", "solana-metrics", "solana-perf", "solana-sdk", + "solana-transaction-metrics-tracker", "thiserror", "tokio", "x509-parser", @@ -7289,6 +7300,7 @@ name = "solana-svm" version = "2.0.0" dependencies = [ "bincode", + "histogram", "itertools", "log", "percentage", @@ -7301,6 +7313,7 @@ dependencies = [ "solana-measure", "solana-metrics", "solana-program-runtime", + "solana-runtime-transaction", "solana-sdk", "solana-system-program", ] @@ -7445,6 +7458,20 @@ dependencies = [ "solana-version", ] +[[package]] +name = "solana-transaction-metrics-tracker" +version = "2.0.0" +dependencies = [ + "Inflector", + "base64 0.21.7", + "bincode", + "lazy_static", + "log", + "rand 0.8.5", + "solana-perf", + "solana-sdk", +] + [[package]] name = "solana-transaction-status" version = "2.0.0" @@ -7522,6 +7549,7 @@ dependencies = [ name = "solana-unified-scheduler-logic" version = "2.0.0" dependencies = [ + "solana-runtime-transaction", "solana-sdk", ] @@ -7537,6 +7565,7 @@ dependencies = [ "solana-logger", "solana-program-runtime", "solana-runtime", + "solana-runtime-transaction", "solana-sdk", "solana-unified-scheduler-logic", "solana-vote", diff --git a/Cargo.toml b/Cargo.toml index 6cbd56762fbfe8..f50a7142821044 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,6 +106,7 @@ members = [ "tokens", "tpu-client", "transaction-dos", + "transaction-metrics-tracker", "transaction-status", "turbine", "udp-client", @@ -378,6 +379,7 @@ solana-test-validator = { path = "test-validator", version = "=2.0.0" } solana-thin-client = { path = "thin-client", version = "=2.0.0" } solana-tpu-client = { path = "tpu-client", version = "=2.0.0", default-features = false } solana-transaction-status = { path = "transaction-status", version = "=2.0.0" } +solana-transaction-metrics-tracker = { path = "transaction-metrics-tracker", version = "=2.0.0" } solana-turbine = { path = "turbine", version = "=2.0.0" } solana-udp-client = { path = "udp-client", version = "=2.0.0" } solana-version = { path = "version", version = "=2.0.0" } diff --git a/accounts-db/Cargo.toml b/accounts-db/Cargo.toml index 0fc5a381fbda5e..b575e47db07299 100644 --- a/accounts-db/Cargo.toml +++ b/accounts-db/Cargo.toml @@ -52,6 +52,7 @@ solana-metrics = { workspace = true } solana-nohash-hasher = { workspace = true } solana-program-runtime = { workspace = true } solana-rayon-threadlimit = { workspace = true } +solana-runtime-transaction = { workspace = true } solana-sdk = { workspace = true } solana-stake-program = { workspace = true } solana-svm = { workspace = true } diff --git a/accounts-db/src/accounts.rs b/accounts-db/src/accounts.rs index 33a57d56461c78..3da2dbe1bea59a 100644 --- a/accounts-db/src/accounts.rs +++ b/accounts-db/src/accounts.rs @@ -10,6 +10,7 @@ use { }, dashmap::DashMap, log::*, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, solana_sdk::{ account::{AccountSharedData, ReadableAccount}, account_utils::StateMut, @@ -23,7 +24,7 @@ use { nonce_info::{NonceFull, NonceInfo}, pubkey::Pubkey, slot_hashes::SlotHashes, - transaction::{Result, SanitizedTransaction, TransactionAccountLocks, TransactionError}, + transaction::{Result, TransactionAccountLocks, TransactionError}, transaction_context::TransactionAccount, }, solana_svm::{ @@ -31,10 +32,7 @@ use { }, std::{ cmp::Reverse, - collections::{ - hash_map::{self}, - BinaryHeap, HashMap, HashSet, - }, + collections::{hash_map, BinaryHeap, HashMap, HashSet}, ops::RangeBounds, sync::{ atomic::{AtomicUsize, Ordering}, @@ -577,7 +575,7 @@ impl Accounts { #[allow(clippy::needless_collect)] pub fn lock_accounts<'a>( &self, - txs: impl Iterator, + txs: impl Iterator, tx_account_lock_limit: usize, ) -> Vec> { let tx_account_locks_results: Vec> = txs @@ -590,7 +588,7 @@ impl Accounts { #[allow(clippy::needless_collect)] pub fn lock_accounts_with_results<'a>( &self, - txs: impl Iterator, + txs: impl Iterator, results: impl Iterator>, tx_account_lock_limit: usize, ) -> Vec> { @@ -627,7 +625,7 @@ impl Accounts { #[allow(clippy::needless_collect)] pub fn unlock_accounts<'a>( &self, - txs_and_results: impl Iterator)>, + txs_and_results: impl Iterator)>, ) { let keys: Vec<_> = txs_and_results .filter(|(_, res)| res.is_ok()) @@ -650,7 +648,7 @@ impl Accounts { pub fn store_cached( &self, slot: Slot, - txs: &[SanitizedTransaction], + txs: &[ExtendedSanitizedTransaction], res: &[TransactionExecutionResult], loaded: &mut [TransactionLoadResult], durable_nonce: &DurableNonce, @@ -677,14 +675,14 @@ impl Accounts { #[allow(clippy::too_many_arguments)] fn collect_accounts_to_store<'a>( &self, - txs: &'a [SanitizedTransaction], + txs: &'a [ExtendedSanitizedTransaction], execution_results: &'a [TransactionExecutionResult], load_results: &'a mut [TransactionLoadResult], durable_nonce: &DurableNonce, lamports_per_signature: u64, ) -> ( Vec<(&'a Pubkey, &'a AccountSharedData)>, - Vec>, + Vec>, ) { let mut accounts = Vec::with_capacity(load_results.len()); let mut transactions = Vec::with_capacity(load_results.len()); @@ -815,7 +813,7 @@ mod tests { rent_debits::RentDebits, signature::{keypair_from_seed, signers::Signers, Keypair, Signer}, system_instruction, system_program, - transaction::{Transaction, MAX_TX_ACCOUNT_LOCKS}, + transaction::{SanitizedTransaction, Transaction, MAX_TX_ACCOUNT_LOCKS}, }, solana_svm::{ account_loader::LoadedTransaction, @@ -1082,7 +1080,7 @@ mod tests { }; let tx = new_sanitized_tx(&[&keypair], message, Hash::default()); - let results = accounts.lock_accounts([tx].iter(), MAX_TX_ACCOUNT_LOCKS); + let results = accounts.lock_accounts([tx.into()].iter(), MAX_TX_ACCOUNT_LOCKS); assert_eq!(results[0], Err(TransactionError::AccountLoadedTwice)); } @@ -1109,7 +1107,7 @@ mod tests { ..Message::default() }; - let txs = vec![new_sanitized_tx(&[&keypair], message, Hash::default())]; + let txs = vec![new_sanitized_tx(&[&keypair], message, Hash::default()).into()]; let results = accounts.lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS); assert_eq!(results, vec![Ok(())]); accounts.unlock_accounts(txs.iter().zip(&results)); @@ -1131,7 +1129,7 @@ mod tests { ..Message::default() }; - let txs = vec![new_sanitized_tx(&[&keypair], message, Hash::default())]; + let txs = vec![new_sanitized_tx(&[&keypair], message, Hash::default()).into()]; let results = accounts.lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS); assert_eq!(results[0], Err(TransactionError::TooManyAccountLocks)); } @@ -1166,7 +1164,7 @@ mod tests { instructions, ); let tx = new_sanitized_tx(&[&keypair0], message, Hash::default()); - let results0 = accounts.lock_accounts([tx.clone()].iter(), MAX_TX_ACCOUNT_LOCKS); + let results0 = accounts.lock_accounts([tx.clone().into()].iter(), MAX_TX_ACCOUNT_LOCKS); assert_eq!(results0, vec![Ok(())]); assert_eq!( @@ -1200,7 +1198,7 @@ mod tests { instructions, ); let tx1 = new_sanitized_tx(&[&keypair1], message, Hash::default()); - let txs = vec![tx0, tx1]; + let txs = vec![tx0.into(), tx1.into()]; let results1 = accounts.lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS); assert_eq!( results1, @@ -1220,7 +1218,7 @@ mod tests { 2 ); - accounts.unlock_accounts(iter::once(&tx).zip(&results0)); + accounts.unlock_accounts(iter::once(&tx.into()).zip(&results0)); accounts.unlock_accounts(txs.iter().zip(&results1)); let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])]; let message = Message::new_with_compiled_instructions( @@ -1232,7 +1230,7 @@ mod tests { instructions, ); let tx = new_sanitized_tx(&[&keypair1], message, Hash::default()); - let results2 = accounts.lock_accounts([tx].iter(), MAX_TX_ACCOUNT_LOCKS); + let results2 = accounts.lock_accounts([tx.into()].iter(), MAX_TX_ACCOUNT_LOCKS); assert_eq!( results2, vec![Ok(())] // Now keypair1 account can be locked as writable @@ -1295,7 +1293,7 @@ mod tests { let accounts_clone = accounts_arc.clone(); let exit_clone = exit.clone(); thread::spawn(move || loop { - let txs = vec![writable_tx.clone()]; + let txs = vec![writable_tx.clone().into()]; let results = accounts_clone .clone() .lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS); @@ -1311,7 +1309,7 @@ mod tests { }); let counter_clone = counter; for _ in 0..5 { - let txs = vec![readonly_tx.clone()]; + let txs = vec![readonly_tx.clone().into()]; let results = accounts_arc .clone() .lock_accounts(txs.iter(), MAX_TX_ACCOUNT_LOCKS); @@ -1355,7 +1353,7 @@ mod tests { instructions, ); let tx = new_sanitized_tx(&[&keypair0], message, Hash::default()); - let results0 = accounts.lock_accounts([tx].iter(), MAX_TX_ACCOUNT_LOCKS); + let results0 = accounts.lock_accounts([tx.into()].iter(), MAX_TX_ACCOUNT_LOCKS); assert!(results0[0].is_ok()); // Instruction program-id account demoted to readonly @@ -1447,7 +1445,7 @@ mod tests { instructions, ); let tx2 = new_sanitized_tx(&[&keypair3], message, Hash::default()); - let txs = vec![tx0, tx1, tx2]; + let txs = vec![tx0.into(), tx1.into(), tx2.into()]; let qos_results = vec![ Ok(()), @@ -1577,7 +1575,7 @@ mod tests { .unwrap() .insert_new_readonly(&pubkey); } - let txs = vec![tx0.clone(), tx1.clone()]; + let txs = vec![tx0.clone().into(), tx1.clone().into()]; let execution_results = vec![new_execution_result(Ok(()), None); 2]; let (collected_accounts, transactions) = accounts.collect_accounts_to_store( &txs, @@ -1595,8 +1593,12 @@ mod tests { .any(|(pubkey, _account)| *pubkey == &keypair1.pubkey())); assert_eq!(transactions.len(), 2); - assert!(transactions.iter().any(|txn| txn.unwrap().eq(&tx0))); - assert!(transactions.iter().any(|txn| txn.unwrap().eq(&tx1))); + assert!(transactions + .iter() + .any(|txn| txn.unwrap().eq(&tx0.clone().into()))); + assert!(transactions + .iter() + .any(|txn| txn.unwrap().eq(&tx1.clone().into()))); // Ensure readonly_lock reflects lock assert_eq!( @@ -1949,7 +1951,7 @@ mod tests { let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique()); let accounts_db = AccountsDb::new_single_for_tests(); let accounts = Accounts::new(Arc::new(accounts_db)); - let txs = vec![tx]; + let txs = vec![tx.into()]; let execution_results = vec![new_execution_result( Err(TransactionError::InstructionError( 1, @@ -2055,7 +2057,7 @@ mod tests { let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique()); let accounts_db = AccountsDb::new_single_for_tests(); let accounts = Accounts::new(Arc::new(accounts_db)); - let txs = vec![tx]; + let txs = vec![tx.into()]; let execution_results = vec![new_execution_result( Err(TransactionError::InstructionError( 1, diff --git a/accounts-db/src/accounts_db.rs b/accounts-db/src/accounts_db.rs index eab5ca33af417c..628bbb1432dd2e 100644 --- a/accounts-db/src/accounts_db.rs +++ b/accounts-db/src/accounts_db.rs @@ -83,6 +83,7 @@ use { solana_measure::{measure::Measure, measure_us}, solana_nohash_hasher::{IntMap, IntSet}, solana_rayon_threadlimit::get_thread_count, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, solana_sdk::{ account::{Account, AccountSharedData, ReadableAccount, WritableAccount}, clock::{BankId, Epoch, Slot}, @@ -93,7 +94,6 @@ use { rent_collector::RentCollector, saturating_add_assign, timing::AtomicInterval, - transaction::SanitizedTransaction, }, std::{ borrow::{Borrow, Cow}, @@ -6373,7 +6373,7 @@ impl AccountsDb { &self, slot: Slot, accounts_and_meta_to_store: &impl StorableAccounts<'b, T>, - txn_iter: Box> + 'a>, + txn_iter: Box> + 'a>, mut write_version_producer: P, ) -> Vec where @@ -6391,7 +6391,7 @@ impl AccountsDb { self.notify_account_at_accounts_update( slot, &account, - txn, + &txn.map(|txn| txn.transaction()), accounts_and_meta_to_store.pubkey(i), &mut write_version_producer, ); @@ -6423,7 +6423,7 @@ impl AccountsDb { hashes: Option>>, mut write_version_producer: P, store_to: &StoreTo, - transactions: Option<&[Option<&'a SanitizedTransaction>]>, + transactions: Option<&[Option<&'a ExtendedSanitizedTransaction>]>, ) -> Vec { let mut calc_stored_meta_time = Measure::start("calc_stored_meta"); let slot = accounts.target_slot(); @@ -6438,14 +6438,15 @@ impl AccountsDb { match store_to { StoreTo::Cache => { - let txn_iter: Box>> = - match transactions { - Some(transactions) => { - assert_eq!(transactions.len(), accounts.len()); - Box::new(transactions.iter()) - } - None => Box::new(std::iter::repeat(&None).take(accounts.len())), - }; + let txn_iter: Box< + dyn std::iter::Iterator>, + > = match transactions { + Some(transactions) => { + assert_eq!(transactions.len(), accounts.len()); + Box::new(transactions.iter()) + } + None => Box::new(std::iter::repeat(&None).take(accounts.len())), + }; self.write_accounts_to_cache(slot, accounts, txn_iter, write_version_producer) } @@ -8125,7 +8126,7 @@ impl AccountsDb { pub fn store_cached<'a, T: ReadableAccount + Sync + ZeroLamport + 'a>( &self, accounts: impl StorableAccounts<'a, T>, - transactions: Option<&'a [Option<&'a SanitizedTransaction>]>, + transactions: Option<&'a [Option<&'a ExtendedSanitizedTransaction>]>, ) { self.store( accounts, @@ -8142,7 +8143,7 @@ impl AccountsDb { >( &self, accounts: impl StorableAccounts<'a, T>, - transactions: Option<&'a [Option<&'a SanitizedTransaction>]>, + transactions: Option<&'a [Option<&'a ExtendedSanitizedTransaction>]>, ) { self.store( accounts, @@ -8170,7 +8171,7 @@ impl AccountsDb { &self, accounts: impl StorableAccounts<'a, T>, store_to: &StoreTo, - transactions: Option<&'a [Option<&'a SanitizedTransaction>]>, + transactions: Option<&'a [Option<&'a ExtendedSanitizedTransaction>]>, reclaim: StoreReclaims, update_index_thread_selection: UpdateIndexThreadSelection, ) { @@ -8345,7 +8346,7 @@ impl AccountsDb { accounts: impl StorableAccounts<'a, T>, hashes: Option>>, store_to: &StoreTo, - transactions: Option<&'a [Option<&'a SanitizedTransaction>]>, + transactions: Option<&'a [Option<&'a ExtendedSanitizedTransaction>]>, reclaim: StoreReclaims, update_index_thread_selection: UpdateIndexThreadSelection, ) { @@ -8400,7 +8401,7 @@ impl AccountsDb { write_version_producer: Option>>, store_to: &StoreTo, reset_accounts: bool, - transactions: Option<&[Option<&SanitizedTransaction>]>, + transactions: Option<&[Option<&ExtendedSanitizedTransaction>]>, reclaim: StoreReclaims, update_index_thread_selection: UpdateIndexThreadSelection, ) -> StoreAccountsTiming { diff --git a/banks-server/Cargo.toml b/banks-server/Cargo.toml index 6cf5f77f92548b..357a07bddd44b3 100644 --- a/banks-server/Cargo.toml +++ b/banks-server/Cargo.toml @@ -16,6 +16,7 @@ futures = { workspace = true } solana-banks-interface = { workspace = true } solana-client = { workspace = true } solana-runtime = { workspace = true } +solana-runtime-transaction = { workspace = true } solana-sdk = { workspace = true } solana-send-transaction-service = { workspace = true } solana-svm = { workspace = true } diff --git a/banks-server/src/banks_server.rs b/banks-server/src/banks_server.rs index b3028c0132ed48..8cca21009edc79 100644 --- a/banks-server/src/banks_server.rs +++ b/banks-server/src/banks_server.rs @@ -13,6 +13,7 @@ use { bank_forks::BankForks, commitment::BlockCommitmentCache, }, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, solana_sdk::{ account::Account, clock::Slot, @@ -194,7 +195,10 @@ fn simulate_transaction( units_consumed, return_data, inner_instructions, - } = bank.simulate_transaction_unchecked(&sanitized_transaction, false); + } = bank.simulate_transaction_unchecked( + &ExtendedSanitizedTransaction::from(sanitized_transaction), + false, + ); let simulation_details = TransactionSimulationDetails { logs, diff --git a/core/Cargo.toml b/core/Cargo.toml index e2a936cdabc4c1..4308dc3fba3a43 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -62,11 +62,13 @@ solana-rayon-threadlimit = { workspace = true } solana-rpc = { workspace = true } solana-rpc-client-api = { workspace = true } solana-runtime = { workspace = true } +solana-runtime-transaction = { workspace = true } solana-sdk = { workspace = true } solana-send-transaction-service = { workspace = true } solana-streamer = { workspace = true } solana-svm = { workspace = true } solana-tpu-client = { workspace = true } +solana-transaction-metrics-tracker = { workspace = true } solana-transaction-status = { workspace = true } solana-turbine = { workspace = true } solana-unified-scheduler-pool = { workspace = true } diff --git a/core/benches/consumer.rs b/core/benches/consumer.rs index f056fdd0d49a34..10027558397920 100644 --- a/core/benches/consumer.rs +++ b/core/benches/consumer.rs @@ -20,6 +20,7 @@ use { poh_service::PohService, }, solana_runtime::bank::Bank, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, solana_sdk::{ account::Account, feature_set::apply_cost_tracker_during_replay, signature::Keypair, signer::Signer, stake_history::Epoch, system_program, system_transaction, @@ -62,7 +63,7 @@ fn create_funded_accounts(bank: &Bank, num: usize) -> Vec { accounts } -fn create_transactions(bank: &Bank, num: usize) -> Vec { +fn create_transactions(bank: &Bank, num: usize) -> Vec { let funded_accounts = create_funded_accounts(bank, 2 * num); funded_accounts .into_par_iter() @@ -72,7 +73,7 @@ fn create_transactions(bank: &Bank, num: usize) -> Vec { let to = &chunk[1]; system_transaction::transfer(from, &to.pubkey(), 1, bank.last_blockhash()) }) - .map(SanitizedTransaction::from_transaction_for_tests) + .map(|t| SanitizedTransaction::from_transaction_for_tests(t).into()) .collect() } @@ -167,6 +168,7 @@ fn bench_process_and_record_transactions( &bank, transaction_iter.next().unwrap(), 0, + &mut None, ); assert!(summary .execute_and_commit_transactions_output diff --git a/core/src/banking_stage.rs b/core/src/banking_stage.rs index 603ff55f0003b4..9d30eb5a0ccaa1 100644 --- a/core/src/banking_stage.rs +++ b/core/src/banking_stage.rs @@ -804,6 +804,7 @@ mod tests { poh_service::PohService, }, solana_runtime::{bank::Bank, genesis_utils::bootstrap_validator_stake_lamports}, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, solana_sdk::{ hash::Hash, poh_config::PohConfig, @@ -830,9 +831,11 @@ mod tests { (node, cluster_info) } - pub(crate) fn sanitize_transactions(txs: Vec) -> Vec { + pub(crate) fn sanitize_transactions( + txs: Vec, + ) -> Vec { txs.into_iter() - .map(SanitizedTransaction::from_transaction_for_tests) + .map(|t| SanitizedTransaction::from_transaction_for_tests(t).into()) .collect() } diff --git a/core/src/banking_stage/consume_worker.rs b/core/src/banking_stage/consume_worker.rs index 92fb07ddfab18c..1b0dad208fefc7 100644 --- a/core/src/banking_stage/consume_worker.rs +++ b/core/src/banking_stage/consume_worker.rs @@ -12,7 +12,7 @@ use { std::{ sync::{ atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering}, - Arc, + Arc, Mutex, }, time::Duration, }, @@ -90,6 +90,7 @@ impl ConsumeWorker { bank, &work.transactions, &work.max_age_slots, + Some(&mut self.metrics.perf_track_metrics.lock().unwrap()), ); self.metrics.update_for_consume(&output); @@ -158,6 +159,7 @@ pub(crate) struct ConsumeWorkerMetrics { count_metrics: ConsumeWorkerCountMetrics, error_metrics: ConsumeWorkerTransactionErrorMetrics, timing_metrics: ConsumeWorkerTimingMetrics, + perf_track_metrics: Arc>, } impl ConsumeWorkerMetrics { @@ -170,9 +172,50 @@ impl ConsumeWorkerMetrics { self.count_metrics.report_and_reset(self.id); self.timing_metrics.report_and_reset(self.id); self.error_metrics.report_and_reset(self.id); + self.report_and_reset_perf_track_metrics(self.id); } } + fn report_and_reset_perf_track_metrics(&self, id: u32) { + let process_sampled_packets_us_hist = { + let mut metrics = self.perf_track_metrics.lock().unwrap(); + let local_metrics = metrics.clone(); + metrics.clear(); + local_metrics + }; + datapoint_info!( + "banking_stage_tracked_packet_metrics", + ("id", id, i64), + ( + "process_sampled_packets_us_90pct", + process_sampled_packets_us_hist + .percentile(90.0) + .unwrap_or(0), + i64 + ), + ( + "process_sampled_packets_us_min", + process_sampled_packets_us_hist.minimum().unwrap_or(0), + i64 + ), + ( + "process_sampled_packets_us_max", + process_sampled_packets_us_hist.maximum().unwrap_or(0), + i64 + ), + ( + "process_sampled_packets_us_mean", + process_sampled_packets_us_hist.mean().unwrap_or(0), + i64 + ), + ( + "process_sampled_packets_count", + process_sampled_packets_us_hist.entries(), + i64 + ), + ); + } + fn new(id: u32) -> Self { Self { id, @@ -181,6 +224,7 @@ impl ConsumeWorkerMetrics { count_metrics: ConsumeWorkerCountMetrics::default(), error_metrics: ConsumeWorkerTransactionErrorMetrics::default(), timing_metrics: ConsumeWorkerTimingMetrics::default(), + perf_track_metrics: Arc::new(Mutex::new(histogram::Histogram::default())), } } diff --git a/core/src/banking_stage/consumer.rs b/core/src/banking_stage/consumer.rs index c5ed22a34278ce..cf7b2c936a42f0 100644 --- a/core/src/banking_stage/consumer.rs +++ b/core/src/banking_stage/consumer.rs @@ -23,13 +23,14 @@ use { compute_budget_details::GetComputeBudgetDetails, transaction_batch::TransactionBatch, }, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, solana_sdk::{ clock::{Slot, FORWARD_TRANSACTIONS_TO_LEADER_AT_SLOT_OFFSET, MAX_PROCESSING_AGE}, feature_set, message::SanitizedMessage, saturating_add_assign, timing::timestamp, - transaction::{self, AddressLoader, SanitizedTransaction, TransactionError}, + transaction::{self, AddressLoader, TransactionError}, }, solana_svm::{ account_loader::{validate_fee_payer, TransactionCheckResult}, @@ -208,7 +209,6 @@ impl Consumer { payload .slot_metrics_tracker .increment_retryable_packets_count(retryable_transaction_indexes.len() as u64); - Some(retryable_transaction_indexes) } @@ -216,13 +216,17 @@ impl Consumer { &self, bank: &Arc, bank_creation_time: &Instant, - sanitized_transactions: &[SanitizedTransaction], + sanitized_transactions: &[ExtendedSanitizedTransaction], banking_stage_stats: &BankingStageStats, slot_metrics_tracker: &mut LeaderSlotMetricsTracker, ) -> ProcessTransactionsSummary { - let (mut process_transactions_summary, process_transactions_us) = measure_us!( - self.process_transactions(bank, bank_creation_time, sanitized_transactions) - ); + let (mut process_transactions_summary, process_transactions_us) = measure_us!(self + .process_transactions( + bank, + bank_creation_time, + sanitized_transactions, + &mut slot_metrics_tracker.get_transaction_perf_track_metrics_ref() + )); slot_metrics_tracker.increment_process_transactions_us(process_transactions_us); banking_stage_stats .transaction_processing_elapsed @@ -272,7 +276,8 @@ impl Consumer { &self, bank: &Arc, bank_creation_time: &Instant, - transactions: &[SanitizedTransaction], + transactions: &[ExtendedSanitizedTransaction], + perf_track_metrics: &mut Option<&mut histogram::Histogram>, ) -> ProcessTransactionsSummary { let mut chunk_start = 0; let mut all_retryable_tx_indexes = vec![]; @@ -302,6 +307,7 @@ impl Consumer { bank, &transactions[chunk_start..chunk_end], chunk_start, + perf_track_metrics, ); let ProcessTransactionBatchOutput { @@ -406,8 +412,9 @@ impl Consumer { pub fn process_and_record_transactions( &self, bank: &Arc, - txs: &[SanitizedTransaction], + txs: &[ExtendedSanitizedTransaction], chunk_offset: usize, + perf_track_metrics: &mut Option<&mut histogram::Histogram>, ) -> ProcessTransactionBatchOutput { let mut error_counters = TransactionErrorMetrics::default(); let pre_results = vec![Ok(()); txs.len()]; @@ -421,6 +428,7 @@ impl Consumer { txs, chunk_offset, check_results, + perf_track_metrics, ); // Accumulate error counters from the initial checks into final results @@ -434,8 +442,9 @@ impl Consumer { pub fn process_and_record_aged_transactions( &self, bank: &Arc, - txs: &[SanitizedTransaction], + txs: &[ExtendedSanitizedTransaction], max_slot_ages: &[Slot], + mut perf_track_metrics: Option<&mut histogram::Histogram>, ) -> ProcessTransactionBatchOutput { // Need to filter out transactions since they were sanitized earlier. // This means that the transaction may cross and epoch boundary (not allowed), @@ -461,15 +470,22 @@ impl Consumer { } Ok(()) }); - self.process_and_record_transactions_with_pre_results(bank, txs, 0, pre_results) + self.process_and_record_transactions_with_pre_results( + bank, + txs, + 0, + pre_results, + &mut perf_track_metrics, + ) } fn process_and_record_transactions_with_pre_results( &self, bank: &Arc, - txs: &[SanitizedTransaction], + txs: &[ExtendedSanitizedTransaction], chunk_offset: usize, pre_results: impl Iterator>, + perf_track_metrics: &mut Option<&mut histogram::Histogram>, ) -> ProcessTransactionBatchOutput { let ( (transaction_qos_cost_results, cost_model_throttled_transactions_count), @@ -495,7 +511,7 @@ impl Consumer { // WouldExceedMaxAccountCostLimit, WouldExceedMaxVoteCostLimit // and WouldExceedMaxAccountDataCostLimit let mut execute_and_commit_transactions_output = - self.execute_and_commit_transactions_locked(bank, &batch); + self.execute_and_commit_transactions_locked(bank, &batch, perf_track_metrics); // Once the accounts are new transactions can enter the pipeline to process them let (_, unlock_us) = measure_us!(drop(batch)); @@ -561,6 +577,7 @@ impl Consumer { &self, bank: &Arc, batch: &TransactionBatch, + perf_track_metrics: &mut Option<&mut histogram::Histogram>, ) -> ExecuteAndCommitTransactionsOutput { let transaction_status_sender_enabled = self.committer.transaction_status_sender_enabled(); let mut execute_and_commit_timings = LeaderExecuteAndCommitTimings::default(); @@ -599,6 +616,7 @@ impl Consumer { None, // account_overrides self.log_messages_bytes_limit, true, + perf_track_metrics, )); execute_and_commit_timings.load_execute_us = load_execute_us; @@ -785,7 +803,7 @@ impl Consumer { /// * `pending_indexes` - identifies which indexes in the `transactions` list are still pending fn filter_pending_packets_from_pending_txs( bank: &Bank, - transactions: &[SanitizedTransaction], + transactions: &[ExtendedSanitizedTransaction], pending_indexes: &[usize], ) -> Vec { let filter = @@ -869,7 +887,7 @@ mod tests { signature::Keypair, signer::Signer, system_instruction, system_program, system_transaction, - transaction::{MessageHash, Transaction, VersionedTransaction}, + transaction::{MessageHash, SanitizedTransaction, Transaction, VersionedTransaction}, }, solana_transaction_status::{TransactionStatusMeta, VersionedTransactionWithStatusMeta}, std::{ @@ -922,7 +940,7 @@ mod tests { ); let consumer = Consumer::new(committer, recorder, QosService::new(1), None); let process_transactions_summary = - consumer.process_transactions(&bank, &Instant::now(), &transactions); + consumer.process_transactions(&bank, &Instant::now(), &transactions, &mut None); poh_recorder .read() @@ -1098,7 +1116,7 @@ mod tests { let consumer = Consumer::new(committer, recorder, QosService::new(1), None); let process_transactions_batch_output = - consumer.process_and_record_transactions(&bank, &transactions, 0); + consumer.process_and_record_transactions(&bank, &transactions, 0, &mut None); let ExecuteAndCommitTransactionsOutput { transactions_attempted_execution_count, @@ -1143,7 +1161,7 @@ mod tests { )]); let process_transactions_batch_output = - consumer.process_and_record_transactions(&bank, &transactions, 0); + consumer.process_and_record_transactions(&bank, &transactions, 0, &mut None); let ExecuteAndCommitTransactionsOutput { transactions_attempted_execution_count, @@ -1283,7 +1301,7 @@ mod tests { let consumer = Consumer::new(committer, recorder, QosService::new(1), None); let process_transactions_batch_output = - consumer.process_and_record_transactions(&bank, &transactions, 0); + consumer.process_and_record_transactions(&bank, &transactions, 0, &mut None); let ExecuteAndCommitTransactionsOutput { transactions_attempted_execution_count, executed_transactions_count, @@ -1385,7 +1403,7 @@ mod tests { let consumer = Consumer::new(committer, recorder, QosService::new(1), None); let process_transactions_batch_output = - consumer.process_and_record_transactions(&bank, &transactions, 0); + consumer.process_and_record_transactions(&bank, &transactions, 0, &mut None); let ExecuteAndCommitTransactionsOutput { transactions_attempted_execution_count, @@ -1491,7 +1509,7 @@ mod tests { )]); let process_transactions_batch_output = - consumer.process_and_record_transactions(&bank, &transactions, 0); + consumer.process_and_record_transactions(&bank, &transactions, 0, &mut None); let ExecuteAndCommitTransactionsOutput { executed_with_successful_result_count, @@ -1522,7 +1540,7 @@ mod tests { ]); let process_transactions_batch_output = - consumer.process_and_record_transactions(&bank, &transactions, 0); + consumer.process_and_record_transactions(&bank, &transactions, 0, &mut None); let ExecuteAndCommitTransactionsOutput { executed_with_successful_result_count, @@ -1556,14 +1574,17 @@ mod tests { } }; - let mut cost = CostModel::calculate_cost(&transactions[0], &bank.feature_set); + let mut cost = + CostModel::calculate_cost(transactions[0].transaction(), &bank.feature_set); if let TransactionCost::Transaction(ref mut usage_cost) = cost { usage_cost.programs_execution_cost = actual_programs_execution_cost; } block_cost + cost.sum() } else { - block_cost + CostModel::calculate_cost(&transactions[0], &bank.feature_set).sum() + block_cost + + CostModel::calculate_cost(transactions[0].transaction(), &bank.feature_set) + .sum() }; assert_eq!(get_block_cost(), expected_block_cost); @@ -1631,7 +1652,7 @@ mod tests { let consumer = Consumer::new(committer, recorder, QosService::new(1), None); let process_transactions_batch_output = - consumer.process_and_record_transactions(&bank, &transactions, 0); + consumer.process_and_record_transactions(&bank, &transactions, 0, &mut None); poh_recorder .read() @@ -1828,7 +1849,7 @@ mod tests { let consumer = Consumer::new(committer, recorder.clone(), QosService::new(1), None); let process_transactions_summary = - consumer.process_transactions(&bank, &Instant::now(), &transactions); + consumer.process_transactions(&bank, &Instant::now(), &transactions, &mut None); let ProcessTransactionsSummary { reached_max_poh_height, @@ -1955,7 +1976,7 @@ mod tests { ); let consumer = Consumer::new(committer, recorder, QosService::new(1), None); - let _ = consumer.process_and_record_transactions(&bank, &transactions, 0); + let _ = consumer.process_and_record_transactions(&bank, &transactions, 0, &mut None); drop(consumer); // drop/disconnect transaction_status_sender transaction_status_service.join().unwrap(); @@ -2100,7 +2121,12 @@ mod tests { ); let consumer = Consumer::new(committer, recorder, QosService::new(1), None); - let _ = consumer.process_and_record_transactions(&bank, &[sanitized_tx.clone()], 0); + let _ = consumer.process_and_record_transactions( + &bank, + &[sanitized_tx.clone().into()], + 0, + &mut None, + ); drop(consumer); // drop/disconnect transaction_status_sender transaction_status_service.join().unwrap(); @@ -2309,7 +2335,8 @@ mod tests { &lock_account, 1, bank.last_blockhash(), - )); + )) + .into(); let _ = bank_start.working_bank.accounts().lock_accounts( std::iter::once(&manual_lock_tx), bank_start.working_bank.get_transaction_account_lock_limit(), diff --git a/core/src/banking_stage/forward_packet_batches_by_accounts.rs b/core/src/banking_stage/forward_packet_batches_by_accounts.rs index 54efb27ce56129..5f1f39291b59c5 100644 --- a/core/src/banking_stage/forward_packet_batches_by_accounts.rs +++ b/core/src/banking_stage/forward_packet_batches_by_accounts.rs @@ -6,7 +6,8 @@ use { cost_tracker::{CostTracker, CostTrackerError}, }, solana_perf::packet::Packet, - solana_sdk::{feature_set::FeatureSet, transaction::SanitizedTransaction}, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, + solana_sdk::feature_set::FeatureSet, std::sync::Arc, }; @@ -58,11 +59,11 @@ impl ForwardBatch { fn try_add( &mut self, - sanitized_transaction: &SanitizedTransaction, + sanitized_transaction: &ExtendedSanitizedTransaction, immutable_packet: Arc, feature_set: &FeatureSet, ) -> Result { - let tx_cost = CostModel::calculate_cost(sanitized_transaction, feature_set); + let tx_cost = CostModel::calculate_cost(sanitized_transaction.transaction(), feature_set); let res = self.cost_tracker.try_add(&tx_cost); if res.is_ok() { self.forwardable_packets.push(immutable_packet); @@ -112,7 +113,7 @@ impl ForwardPacketBatchesByAccounts { /// packets are filled into first available 'batch' that have space to fit it. pub fn try_add_packet( &mut self, - sanitized_transaction: &SanitizedTransaction, + sanitized_transaction: &ExtendedSanitizedTransaction, immutable_packet: Arc, feature_set: &FeatureSet, ) -> bool { @@ -138,8 +139,12 @@ mod tests { super::*, crate::banking_stage::unprocessed_packet_batches::DeserializedPacket, solana_sdk::{ - compute_budget::ComputeBudgetInstruction, feature_set::FeatureSet, message::Message, - pubkey::Pubkey, system_instruction, transaction::Transaction, + compute_budget::ComputeBudgetInstruction, + feature_set::FeatureSet, + message::Message, + pubkey::Pubkey, + system_instruction, + transaction::{SanitizedTransaction, Transaction}, }, }; @@ -148,7 +153,7 @@ mod tests { fn build_test_transaction_and_packet( priority: u64, write_to_account: &Pubkey, - ) -> (SanitizedTransaction, DeserializedPacket, u32) { + ) -> (ExtendedSanitizedTransaction, DeserializedPacket, u32) { let from_account = solana_sdk::pubkey::new_rand(); let transaction = Transaction::new_unsigned(Message::new( @@ -168,7 +173,11 @@ mod tests { // set limit ratio so each batch can only have one test transaction let limit_ratio: u32 = ((block_cost_limits::MAX_WRITABLE_ACCOUNT_UNITS - cost + 1) / cost) as u32; - (sanitized_transaction, deserialized_packet, limit_ratio) + ( + sanitized_transaction.into(), + deserialized_packet, + limit_ratio, + ) } #[test] diff --git a/core/src/banking_stage/immutable_deserialized_packet.rs b/core/src/banking_stage/immutable_deserialized_packet.rs index 26ede7045d3480..66fc39d11fe711 100644 --- a/core/src/banking_stage/immutable_deserialized_packet.rs +++ b/core/src/banking_stage/immutable_deserialized_packet.rs @@ -1,6 +1,7 @@ use { solana_perf::packet::Packet, solana_runtime::compute_budget_details::{ComputeBudgetDetails, GetComputeBudgetDetails}, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, solana_sdk::{ feature_set, hash::Hash, @@ -13,7 +14,7 @@ use { VersionedTransaction, }, }, - std::{cmp::Ordering, mem::size_of, sync::Arc}, + std::{cmp::Ordering, mem::size_of, sync::Arc, time::Instant}, thiserror::Error, }; @@ -41,10 +42,16 @@ pub struct ImmutableDeserializedPacket { message_hash: Hash, is_simple_vote: bool, compute_budget_details: ComputeBudgetDetails, + banking_stage_start_time: Option, } impl ImmutableDeserializedPacket { pub fn new(packet: Packet) -> Result { + let banking_stage_start_time = packet + .meta() + .is_perf_track_packet() + .then_some(Instant::now()); + let versioned_transaction: VersionedTransaction = packet.deserialize_slice(..)?; let sanitized_transaction = SanitizedVersionedTransaction::try_from(versioned_transaction)?; let message_bytes = packet_message(&packet)?; @@ -67,6 +74,7 @@ impl ImmutableDeserializedPacket { message_hash, is_simple_vote, compute_budget_details, + banking_stage_start_time, }) } @@ -98,6 +106,10 @@ impl ImmutableDeserializedPacket { self.compute_budget_details.clone() } + pub fn start_time(&self) -> &Option { + &self.banking_stage_start_time + } + // This function deserializes packets into transactions, computes the blake3 hash of transaction // messages, and verifies secp256k1 instructions. pub fn build_sanitized_transaction( @@ -105,7 +117,7 @@ impl ImmutableDeserializedPacket { feature_set: &Arc, votes_only: bool, address_loader: impl AddressLoader, - ) -> Option { + ) -> Option { if votes_only && !self.is_simple_vote() { return None; } @@ -117,7 +129,7 @@ impl ImmutableDeserializedPacket { ) .ok()?; tx.verify_precompiles(feature_set).ok()?; - Some(tx) + Some(ExtendedSanitizedTransaction::new(tx, *self.start_time())) } } diff --git a/core/src/banking_stage/leader_slot_metrics.rs b/core/src/banking_stage/leader_slot_metrics.rs index 88ea6b5ee340cf..301df48a4f2582 100644 --- a/core/src/banking_stage/leader_slot_metrics.rs +++ b/core/src/banking_stage/leader_slot_metrics.rs @@ -936,6 +936,19 @@ impl LeaderSlotMetricsTracker { ); } } + + pub(crate) fn get_transaction_perf_track_metrics_ref( + &mut self, + ) -> Option<&mut histogram::Histogram> { + self.leader_slot_metrics + .as_mut() + .map(|leader_slot_metrics| { + &mut leader_slot_metrics + .timing_metrics + .process_packets_timings + .process_sampled_packets_us_hist + }) + } } #[cfg(test)] diff --git a/core/src/banking_stage/leader_slot_timing_metrics.rs b/core/src/banking_stage/leader_slot_timing_metrics.rs index 7727b6cf6c6563..a2cdfa33bd8d43 100644 --- a/core/src/banking_stage/leader_slot_timing_metrics.rs +++ b/core/src/banking_stage/leader_slot_timing_metrics.rs @@ -244,6 +244,9 @@ pub(crate) struct ProcessPacketsTimings { // Time spent running the cost model in processing transactions before executing // transactions pub cost_model_us: u64, + + // banking stage processing time histogram for sampled packets + pub process_sampled_packets_us_hist: histogram::Histogram, } impl ProcessPacketsTimings { @@ -264,6 +267,33 @@ impl ProcessPacketsTimings { i64 ), ("cost_model_us", self.cost_model_us, i64), + ( + "process_sampled_packets_us_90pct", + self.process_sampled_packets_us_hist + .percentile(90.0) + .unwrap_or(0), + i64 + ), + ( + "process_sampled_packets_us_min", + self.process_sampled_packets_us_hist.minimum().unwrap_or(0), + i64 + ), + ( + "process_sampled_packets_us_max", + self.process_sampled_packets_us_hist.maximum().unwrap_or(0), + i64 + ), + ( + "process_sampled_packets_us_mean", + self.process_sampled_packets_us_hist.mean().unwrap_or(0), + i64 + ), + ( + "process_sampled_packets_count", + self.process_sampled_packets_us_hist.entries(), + i64 + ), ); } } diff --git a/core/src/banking_stage/qos_service.rs b/core/src/banking_stage/qos_service.rs index 8c1507ae3fb91c..08dc7a460788b1 100644 --- a/core/src/banking_stage/qos_service.rs +++ b/core/src/banking_stage/qos_service.rs @@ -8,11 +8,12 @@ use { solana_cost_model::{cost_model::CostModel, transaction_cost::TransactionCost}, solana_measure::measure::Measure, solana_runtime::bank::Bank, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, solana_sdk::{ clock::Slot, feature_set::FeatureSet, saturating_add_assign, - transaction::{self, SanitizedTransaction, TransactionError}, + transaction::{self, TransactionError}, }, std::sync::atomic::{AtomicU64, Ordering}, }; @@ -40,7 +41,7 @@ impl QosService { pub fn select_and_accumulate_transaction_costs( &self, bank: &Bank, - transactions: &[SanitizedTransaction], + transactions: &[ExtendedSanitizedTransaction], pre_results: impl Iterator>, ) -> (Vec>, usize) { let transaction_costs = @@ -67,13 +68,15 @@ impl QosService { fn compute_transaction_costs<'a>( &self, feature_set: &FeatureSet, - transactions: impl Iterator, + transactions: impl Iterator, pre_results: impl Iterator>, ) -> Vec> { let mut compute_cost_time = Measure::start("compute_cost_time"); let txs_costs: Vec<_> = transactions .zip(pre_results) - .map(|(tx, pre_result)| pre_result.map(|()| CostModel::calculate_cost(tx, feature_set))) + .map(|(tx, pre_result)| { + pre_result.map(|()| CostModel::calculate_cost(tx.transaction(), feature_set)) + }) .collect(); compute_cost_time.stop(); self.metrics @@ -92,7 +95,7 @@ impl QosService { /// and a count of the number of transactions that would fit in the block fn select_transactions_per_cost<'a>( &self, - transactions: impl Iterator, + transactions: impl Iterator, transactions_costs: impl Iterator>, bank: &Bank, ) -> (Vec>, usize) { @@ -579,6 +582,7 @@ mod tests { hash::Hash, signature::{Keypair, Signer}, system_transaction, + transaction::SanitizedTransaction, }, solana_vote_program::vote_transaction, std::sync::Arc, @@ -590,20 +594,27 @@ mod tests { // make a vec of txs let keypair = Keypair::new(); - let transfer_tx = SanitizedTransaction::from_transaction_for_tests( - system_transaction::transfer(&keypair, &keypair.pubkey(), 1, Hash::default()), - ); - let vote_tx = SanitizedTransaction::from_transaction_for_tests( - vote_transaction::new_vote_transaction( - vec![42], - Hash::default(), - Hash::default(), - &keypair, - &keypair, + let transfer_tx: ExtendedSanitizedTransaction = + SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( &keypair, - None, - ), - ); + &keypair.pubkey(), + 1, + Hash::default(), + )) + .into(); + let vote_tx: ExtendedSanitizedTransaction = + SanitizedTransaction::from_transaction_for_tests( + vote_transaction::new_vote_transaction( + vec![42], + Hash::default(), + Hash::default(), + &keypair, + &keypair, + &keypair, + None, + ), + ) + .into(); let txs = vec![transfer_tx.clone(), vote_tx.clone(), vote_tx, transfer_tx]; let qos_service = QosService::new(1); @@ -621,7 +632,8 @@ mod tests { .map(|(index, cost)| { assert_eq!( cost.as_ref().unwrap().sum(), - CostModel::calculate_cost(&txs[index], &FeatureSet::all_enabled()).sum() + CostModel::calculate_cost(txs[index].transaction(), &FeatureSet::all_enabled()) + .sum() ); }) .collect_vec(); @@ -653,7 +665,12 @@ mod tests { let vote_tx_cost = CostModel::calculate_cost(&vote_tx, &FeatureSet::all_enabled()).sum(); // make a vec of txs - let txs = vec![transfer_tx.clone(), vote_tx.clone(), transfer_tx, vote_tx]; + let txs = vec![ + transfer_tx.clone().into(), + vote_tx.clone().into(), + transfer_tx.into(), + vote_tx.into(), + ]; let qos_service = QosService::new(1); let txs_costs = qos_service.compute_transaction_costs( @@ -692,8 +709,8 @@ mod tests { let transfer_tx = SanitizedTransaction::from_transaction_for_tests( system_transaction::transfer(&keypair, &keypair.pubkey(), 1, Hash::default()), ); - let txs: Vec = (0..transaction_count) - .map(|_| transfer_tx.clone()) + let txs: Vec = (0..transaction_count) + .map(|_| transfer_tx.clone().into()) .collect(); let execute_units_adjustment = 10u64; @@ -758,10 +775,15 @@ mod tests { // calculate their costs, apply to cost_tracker let transaction_count = 5; let keypair = Keypair::new(); - let transfer_tx = SanitizedTransaction::from_transaction_for_tests( - system_transaction::transfer(&keypair, &keypair.pubkey(), 1, Hash::default()), - ); - let txs: Vec = (0..transaction_count) + let transfer_tx: ExtendedSanitizedTransaction = + SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( + &keypair, + &keypair.pubkey(), + 1, + Hash::default(), + )) + .into(); + let txs: Vec = (0..transaction_count) .map(|_| transfer_tx.clone()) .collect(); @@ -811,10 +833,15 @@ mod tests { // calculate their costs, apply to cost_tracker let transaction_count = 5; let keypair = Keypair::new(); - let transfer_tx = SanitizedTransaction::from_transaction_for_tests( - system_transaction::transfer(&keypair, &keypair.pubkey(), 1, Hash::default()), - ); - let txs: Vec = (0..transaction_count) + let transfer_tx: ExtendedSanitizedTransaction = + SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( + &keypair, + &keypair.pubkey(), + 1, + Hash::default(), + )) + .into(); + let txs: Vec = (0..transaction_count) .map(|_| transfer_tx.clone()) .collect(); let execute_units_adjustment = 10u64; diff --git a/core/src/banking_stage/scheduler_messages.rs b/core/src/banking_stage/scheduler_messages.rs index 172087e2cf8e82..b1836b4db54f22 100644 --- a/core/src/banking_stage/scheduler_messages.rs +++ b/core/src/banking_stage/scheduler_messages.rs @@ -1,6 +1,7 @@ use { super::immutable_deserialized_packet::ImmutableDeserializedPacket, - solana_sdk::{clock::Slot, transaction::SanitizedTransaction}, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, + solana_sdk::clock::Slot, std::{fmt::Display, sync::Arc}, }; @@ -41,7 +42,7 @@ impl Display for TransactionId { pub struct ConsumeWork { pub batch_id: TransactionBatchId, pub ids: Vec, - pub transactions: Vec, + pub transactions: Vec, pub max_age_slots: Vec, } diff --git a/core/src/banking_stage/transaction_scheduler/prio_graph_scheduler.rs b/core/src/banking_stage/transaction_scheduler/prio_graph_scheduler.rs index d983fcf4d163c3..5a585dd79fd9b0 100644 --- a/core/src/banking_stage/transaction_scheduler/prio_graph_scheduler.rs +++ b/core/src/banking_stage/transaction_scheduler/prio_graph_scheduler.rs @@ -16,10 +16,8 @@ use { itertools::izip, prio_graph::{AccessKind, PrioGraph}, solana_measure::measure_us, - solana_sdk::{ - pubkey::Pubkey, saturating_add_assign, slot_history::Slot, - transaction::SanitizedTransaction, - }, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, + solana_sdk::{pubkey::Pubkey, saturating_add_assign, slot_history::Slot}, }; pub(crate) struct PrioGraphScheduler { @@ -64,8 +62,8 @@ impl PrioGraphScheduler { pub(crate) fn schedule( &mut self, container: &mut TransactionStateContainer, - pre_graph_filter: impl Fn(&[&SanitizedTransaction], &mut [bool]), - pre_lock_filter: impl Fn(&SanitizedTransaction) -> bool, + pre_graph_filter: impl Fn(&[&ExtendedSanitizedTransaction], &mut [bool]), + pre_lock_filter: impl Fn(&ExtendedSanitizedTransaction) -> bool, ) -> Result { let num_threads = self.consume_work_senders.len(); let mut batches = Batches::new(num_threads); @@ -329,7 +327,7 @@ impl PrioGraphScheduler { fn complete_batch( &mut self, batch_id: TransactionBatchId, - transactions: &[SanitizedTransaction], + transactions: &[ExtendedSanitizedTransaction], ) { let thread_id = self.in_flight_tracker.complete_batch(batch_id); for transaction in transactions { @@ -392,7 +390,7 @@ impl PrioGraphScheduler { /// on `ThreadAwareAccountLocks::try_lock_accounts`. fn select_thread( thread_set: ThreadSet, - batches_per_thread: &[Vec], + batches_per_thread: &[Vec], in_flight_per_thread: &[usize], ) -> ThreadId { thread_set @@ -442,7 +440,7 @@ pub(crate) struct SchedulingSummary { struct Batches { ids: Vec>, - transactions: Vec>, + transactions: Vec>, max_age_slots: Vec>, total_cus: Vec, } @@ -462,7 +460,7 @@ impl Batches { thread_id: ThreadId, ) -> ( Vec, - Vec, + Vec, Vec, u64, ) { @@ -492,8 +490,14 @@ mod tests { crossbeam_channel::{unbounded, Receiver}, itertools::Itertools, solana_sdk::{ - compute_budget::ComputeBudgetInstruction, hash::Hash, message::Message, pubkey::Pubkey, - signature::Keypair, signer::Signer, system_instruction, transaction::Transaction, + compute_budget::ComputeBudgetInstruction, + hash::Hash, + message::Message, + pubkey::Pubkey, + signature::Keypair, + signer::Signer, + system_instruction, + transaction::{SanitizedTransaction, Transaction}, }, std::borrow::Borrow, }; @@ -569,7 +573,8 @@ mod tests { to_pubkeys, lamports, compute_unit_price, - ); + ) + .into(); let transaction_ttl = SanitizedTransactionTTL { transaction, max_age_slot: Slot::MAX, @@ -598,11 +603,11 @@ mod tests { .unzip() } - fn test_pre_graph_filter(_txs: &[&SanitizedTransaction], results: &mut [bool]) { + fn test_pre_graph_filter(_txs: &[&ExtendedSanitizedTransaction], results: &mut [bool]) { results.fill(true); } - fn test_pre_lock_filter(_tx: &SanitizedTransaction) -> bool { + fn test_pre_lock_filter(_tx: &ExtendedSanitizedTransaction) -> bool { true } @@ -778,7 +783,7 @@ mod tests { // 2nd transaction should be filtered out and dropped before locking. let pre_lock_filter = - |tx: &SanitizedTransaction| tx.message().fee_payer() != &keypair.pubkey(); + |tx: &ExtendedSanitizedTransaction| tx.message().fee_payer() != &keypair.pubkey(); let scheduling_summary = scheduler .schedule(&mut container, test_pre_graph_filter, pre_lock_filter) .unwrap(); diff --git a/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs b/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs index 12e8f7bf8bf0bf..39435d45de7c12 100644 --- a/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs +++ b/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs @@ -23,6 +23,7 @@ use { solana_measure::measure_us, solana_program_runtime::compute_budget_processor::process_compute_budget_instructions, solana_runtime::{bank::Bank, bank_forks::BankForks}, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, solana_sdk::{ clock::MAX_PROCESSING_AGE, feature_set::{ @@ -189,7 +190,11 @@ impl SchedulerController { Ok(()) } - fn pre_graph_filter(transactions: &[&SanitizedTransaction], results: &mut [bool], bank: &Bank) { + fn pre_graph_filter( + transactions: &[&ExtendedSanitizedTransaction], + results: &mut [bool], + bank: &Bank, + ) { let lock_results = vec![Ok(()); transactions.len()]; let mut error_counters = TransactionErrorMetrics::default(); let check_results = bank.check_transactions( @@ -478,11 +483,11 @@ impl SchedulerController { /// Any difference in the prioritization is negligible for /// the current transaction costs. fn calculate_priority_and_cost( - transaction: &SanitizedTransaction, + transaction: &ExtendedSanitizedTransaction, fee_budget_limits: &FeeBudgetLimits, bank: &Bank, ) -> (u64, u64) { - let cost = CostModel::calculate_cost(transaction, &bank.feature_set).sum(); + let cost = CostModel::calculate_cost(transaction.transaction(), &bank.feature_set).sum(); let fee = bank.fee_structure.calculate_fee( transaction.message(), 5_000, // this just needs to be non-zero diff --git a/core/src/banking_stage/transaction_scheduler/transaction_state.rs b/core/src/banking_stage/transaction_scheduler/transaction_state.rs index 727140545ab656..642bed2a7639bd 100644 --- a/core/src/banking_stage/transaction_scheduler/transaction_state.rs +++ b/core/src/banking_stage/transaction_scheduler/transaction_state.rs @@ -1,8 +1,11 @@ -use solana_sdk::{clock::Slot, transaction::SanitizedTransaction}; +use { + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, + solana_sdk::clock::Slot, +}; /// Simple wrapper type to tie a sanitized transaction to max age slot. pub(crate) struct SanitizedTransactionTTL { - pub(crate) transaction: SanitizedTransaction, + pub(crate) transaction: ExtendedSanitizedTransaction, pub(crate) max_age_slot: Slot, } @@ -138,8 +141,13 @@ mod tests { use { super::*, solana_sdk::{ - compute_budget::ComputeBudgetInstruction, hash::Hash, message::Message, - signature::Keypair, signer::Signer, system_instruction, transaction::Transaction, + compute_budget::ComputeBudgetInstruction, + hash::Hash, + message::Message, + signature::Keypair, + signer::Signer, + system_instruction, + transaction::{SanitizedTransaction, Transaction}, }, }; @@ -157,7 +165,7 @@ mod tests { let tx = Transaction::new(&[&from_keypair], message, Hash::default()); let transaction_ttl = SanitizedTransactionTTL { - transaction: SanitizedTransaction::from_transaction_for_tests(tx), + transaction: SanitizedTransaction::from_transaction_for_tests(tx).into(), max_age_slot: Slot::MAX, }; const TEST_TRANSACTION_COST: u64 = 5000; diff --git a/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs b/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs index a627375a03e6ba..23873da4c44167 100644 --- a/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs +++ b/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs @@ -187,7 +187,8 @@ mod tests { &[&from_keypair], message, Hash::default(), - )); + )) + .into(); let transaction_ttl = SanitizedTransactionTTL { transaction: tx, max_age_slot: Slot::MAX, diff --git a/core/src/banking_stage/unprocessed_transaction_storage.rs b/core/src/banking_stage/unprocessed_transaction_storage.rs index fcc68050b72d4c..e606c52a63beca 100644 --- a/core/src/banking_stage/unprocessed_transaction_storage.rs +++ b/core/src/banking_stage/unprocessed_transaction_storage.rs @@ -19,6 +19,7 @@ use { min_max_heap::MinMaxHeap, solana_measure::{measure, measure_us}, solana_runtime::bank::Bank, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, solana_sdk::{ clock::FORWARD_TRANSACTIONS_TO_LEADER_AT_SLOT_OFFSET, feature_set::FeatureSet, hash::Hash, saturating_add_assign, transaction::SanitizedTransaction, @@ -135,7 +136,7 @@ fn filter_processed_packets<'a, F>( pub struct ConsumeScannerPayload<'a> { pub reached_end_of_slot: bool, pub account_locks: ReadWriteAccountSet, - pub sanitized_transactions: Vec, + pub sanitized_transactions: Vec, pub slot_metrics_tracker: &'a mut LeaderSlotMetricsTracker, pub message_hash_to_transaction: &'a mut HashMap, pub error_counters: TransactionErrorMetrics, @@ -635,7 +636,7 @@ impl ThreadLocalUnprocessedPackets { (sanitized_transactions, transaction_to_packet_indexes), packet_conversion_time, ): ( - (Vec, Vec), + (Vec, Vec), _, ) = measure!( self.sanitize_unforwarded_packets( @@ -762,18 +763,20 @@ impl ThreadLocalUnprocessedPackets { packets_to_process: &[Arc], bank: &Bank, total_dropped_packets: &mut usize, - ) -> (Vec, Vec) { + ) -> (Vec, Vec) { // Get ref of ImmutableDeserializedPacket let deserialized_packets = packets_to_process.iter().map(|p| &**p); - let (transactions, transaction_to_packet_indexes): (Vec, Vec) = - deserialized_packets - .enumerate() - .filter_map(|(packet_index, deserialized_packet)| { - deserialized_packet - .build_sanitized_transaction(&bank.feature_set, bank.vote_only_bank(), bank) - .map(|transaction| (transaction, packet_index)) - }) - .unzip(); + let (transactions, transaction_to_packet_indexes): ( + Vec, + Vec, + ) = deserialized_packets + .enumerate() + .filter_map(|(packet_index, deserialized_packet)| { + deserialized_packet + .build_sanitized_transaction(&bank.feature_set, bank.vote_only_bank(), bank) + .map(|transaction| (transaction, packet_index)) + }) + .unzip(); let filtered_count = packets_to_process.len().saturating_sub(transactions.len()); saturating_add_assign!(*total_dropped_packets, filtered_count); @@ -783,7 +786,7 @@ impl ThreadLocalUnprocessedPackets { /// Checks sanitized transactions against bank, returns valid transaction indexes fn filter_invalid_transactions( - transactions: &[SanitizedTransaction], + transactions: &[ExtendedSanitizedTransaction], bank: &Bank, total_dropped_packets: &mut usize, ) -> Vec { @@ -821,7 +824,7 @@ impl ThreadLocalUnprocessedPackets { fn add_filtered_packets_to_forward_buffer( forward_buffer: &mut ForwardPacketBatchesByAccounts, packets_to_process: &[Arc], - transactions: &[SanitizedTransaction], + transactions: &[ExtendedSanitizedTransaction], transaction_to_packet_indexes: &[usize], forwardable_transaction_indexes: &[usize], total_dropped_packets: &mut usize, @@ -924,6 +927,7 @@ impl ThreadLocalUnprocessedPackets { .iter() .map(|p| (*p).clone()) .collect_vec(); + let retryable_packets = if let Some(retryable_transaction_indexes) = processing_function(&packets_to_process, payload) { diff --git a/core/src/sigverify_stage.rs b/core/src/sigverify_stage.rs index cde1735611c0d0..4da3e6c874e767 100644 --- a/core/src/sigverify_stage.rs +++ b/core/src/sigverify_stage.rs @@ -18,8 +18,9 @@ use { count_discarded_packets, count_packets_in_batches, count_valid_packets, shrink_batches, }, }, - solana_sdk::timing, + solana_sdk::{signature::Signature, timing}, solana_streamer::streamer::{self, StreamerError}, + solana_transaction_metrics_tracker::get_signature_from_packet, std::{ thread::{self, Builder, JoinHandle}, time::Instant, @@ -78,8 +79,9 @@ struct SigVerifierStats { verify_batches_pp_us_hist: histogram::Histogram, // per-packet time to call verify_batch discard_packets_pp_us_hist: histogram::Histogram, // per-packet time to call verify_batch dedup_packets_pp_us_hist: histogram::Histogram, // per-packet time to call verify_batch - batches_hist: histogram::Histogram, // number of packet batches per verify call - packets_hist: histogram::Histogram, // number of packets per verify call + process_sampled_packets_us_hist: histogram::Histogram, // per-packet time do do overall verify for sampled packets + batches_hist: histogram::Histogram, // number of packet batches per verify call + packets_hist: histogram::Histogram, // number of packets per verify call num_deduper_saturations: usize, total_batches: usize, total_packets: usize, @@ -93,6 +95,7 @@ struct SigVerifierStats { total_discard_random_time_us: usize, total_verify_time_us: usize, total_shrink_time_us: usize, + perf_track_overhead_us: usize, } impl SigVerifierStats { @@ -181,6 +184,33 @@ impl SigVerifierStats { self.dedup_packets_pp_us_hist.mean().unwrap_or(0), i64 ), + ( + "process_sampled_packets_us_90pct", + self.process_sampled_packets_us_hist + .percentile(90.0) + .unwrap_or(0), + i64 + ), + ( + "process_sampled_packets_us_min", + self.process_sampled_packets_us_hist.minimum().unwrap_or(0), + i64 + ), + ( + "process_sampled_packets_us_max", + self.process_sampled_packets_us_hist.maximum().unwrap_or(0), + i64 + ), + ( + "process_sampled_packets_us_mean", + self.process_sampled_packets_us_hist.mean().unwrap_or(0), + i64 + ), + ( + "process_sampled_packets_count", + self.process_sampled_packets_us_hist.entries(), + i64 + ), ( "batches_90pct", self.batches_hist.percentile(90.0).unwrap_or(0), @@ -214,6 +244,7 @@ impl SigVerifierStats { ), ("total_verify_time_us", self.total_verify_time_us, i64), ("total_shrink_time_us", self.total_shrink_time_us, i64), + ("perf_track_overhead_us", self.perf_track_overhead_us, i64), ); } } @@ -298,8 +329,26 @@ impl SigVerifyStage { verifier: &mut T, stats: &mut SigVerifierStats, ) -> Result<(), T::SendType> { + let mut packet_perf_measure: Vec<([u8; 64], std::time::Instant)> = Vec::default(); + let (mut batches, num_packets, recv_duration) = streamer::recv_packet_batches(recvr)?; + let mut start_perf_track_measure = Measure::start("start_perf_track"); + // track sigverify start time for interested packets + for batch in &batches { + for packet in batch.iter() { + if packet.meta().is_perf_track_packet() { + let signature = get_signature_from_packet(packet); + if let Ok(signature) = signature { + packet_perf_measure.push((*signature, Instant::now())); + } + } + } + } + start_perf_track_measure.stop(); + + stats.perf_track_overhead_us = start_perf_track_measure.as_us() as usize; + let batches_len = batches.len(); debug!( "@{:?} verifier: verifying: {}", @@ -372,6 +421,22 @@ impl SigVerifyStage { (num_packets as f32 / verify_time.as_s()) ); + let mut perf_track_end_measure = Measure::start("perf_track_end"); + for (signature, start_time) in packet_perf_measure.iter() { + let duration = Instant::now().duration_since(*start_time); + debug!( + "Sigverify took {duration:?} for transaction {:?}", + Signature::from(*signature) + ); + stats + .process_sampled_packets_us_hist + .increment(duration.as_micros() as u64) + .unwrap(); + } + + perf_track_end_measure.stop(); + stats.perf_track_overhead_us += perf_track_end_measure.as_us() as usize; + stats .recv_batches_us_hist .increment(recv_duration.as_micros() as u64) diff --git a/entry/Cargo.toml b/entry/Cargo.toml index a9bde85d833e7c..984c08a2a93e18 100644 --- a/entry/Cargo.toml +++ b/entry/Cargo.toml @@ -23,6 +23,7 @@ solana-merkle-tree = { workspace = true } solana-metrics = { workspace = true } solana-perf = { workspace = true } solana-rayon-threadlimit = { workspace = true } +solana-runtime-transaction = { workspace = true } solana-sdk = { workspace = true } [dev-dependencies] diff --git a/entry/benches/entry_sigverify.rs b/entry/benches/entry_sigverify.rs index b3a1b7b5cdb3e6..4d9f89d949efd0 100644 --- a/entry/benches/entry_sigverify.rs +++ b/entry/benches/entry_sigverify.rs @@ -3,6 +3,7 @@ extern crate test; use { solana_entry::entry::{self, VerifyRecyclers}, solana_perf::test_tx::test_tx, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, solana_sdk::{ hash::Hash, transaction::{ @@ -26,7 +27,7 @@ fn bench_gpusigverify(bencher: &mut Bencher) { let verify_transaction = { move |versioned_tx: VersionedTransaction, verification_mode: TransactionVerificationMode| - -> Result { + -> Result { let sanitized_tx = { let message_hash = if verification_mode == TransactionVerificationMode::FullVerification { @@ -41,6 +42,7 @@ fn bench_gpusigverify(bencher: &mut Bencher) { None, SimpleAddressLoader::Disabled, ) + .map(|t| t.into()) }?; Ok(sanitized_tx) @@ -73,7 +75,7 @@ fn bench_cpusigverify(bencher: &mut Bencher) { .collect::>(); let verify_transaction = { - move |versioned_tx: VersionedTransaction| -> Result { + move |versioned_tx: VersionedTransaction| -> Result { let sanitized_tx = { let message_hash = versioned_tx.verify_and_hash_message()?; SanitizedTransaction::try_create( @@ -82,7 +84,8 @@ fn bench_cpusigverify(bencher: &mut Bencher) { None, SimpleAddressLoader::Disabled, ) - }?; + }? + .into(); Ok(sanitized_tx) } diff --git a/entry/src/entry.rs b/entry/src/entry.rs index af3fdca9518e83..4f6b36e767256d 100644 --- a/entry/src/entry.rs +++ b/entry/src/entry.rs @@ -22,13 +22,14 @@ use { sigverify, }, solana_rayon_threadlimit::get_max_thread_count, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, solana_sdk::{ hash::Hash, packet::Meta, timing, transaction::{ - Result, SanitizedTransaction, Transaction, TransactionError, - TransactionVerificationMode, VersionedTransaction, + Result, Transaction, TransactionError, TransactionVerificationMode, + VersionedTransaction, }, }, std::{ @@ -163,7 +164,7 @@ impl From<&Entry> for EntrySummary { /// Typed entry to distinguish between transaction and tick entries pub enum EntryType { - Transactions(Vec), + Transactions(Vec), Tick(Hash), } @@ -405,7 +406,7 @@ impl EntryVerificationState { pub fn verify_transactions( entries: Vec, - verify: Arc Result + Send + Sync>, + verify: Arc Result + Send + Sync>, ) -> Result> { PAR_THREAD_POOL.install(|| { entries @@ -432,7 +433,10 @@ pub fn start_verify_transactions( skip_verification: bool, verify_recyclers: VerifyRecyclers, verify: Arc< - dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result + dyn Fn( + VersionedTransaction, + TransactionVerificationMode, + ) -> Result + Send + Sync, >, @@ -469,7 +473,10 @@ fn start_verify_transactions_cpu( entries: Vec, skip_verification: bool, verify: Arc< - dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result + dyn Fn( + VersionedTransaction, + TransactionVerificationMode, + ) -> Result + Send + Sync, >, @@ -498,13 +505,16 @@ fn start_verify_transactions_gpu( entries: Vec, verify_recyclers: VerifyRecyclers, verify: Arc< - dyn Fn(VersionedTransaction, TransactionVerificationMode) -> Result + dyn Fn( + VersionedTransaction, + TransactionVerificationMode, + ) -> Result + Send + Sync, >, ) -> Result { let verify_func = { - move |versioned_tx: VersionedTransaction| -> Result { + move |versioned_tx: VersionedTransaction| -> Result { verify( versioned_tx, TransactionVerificationMode::HashAndVerifyPrecompiles, @@ -514,7 +524,7 @@ fn start_verify_transactions_gpu( let entries = verify_transactions(entries, Arc::new(verify_func))?; - let entry_txs: Vec<&SanitizedTransaction> = entries + let entry_txs: Vec<&ExtendedSanitizedTransaction> = entries .iter() .filter_map(|entry_type| match entry_type { EntryType::Tick(_) => None, @@ -972,7 +982,7 @@ mod tests { dyn Fn( VersionedTransaction, TransactionVerificationMode, - ) -> Result + ) -> Result + Send + Sync, >, @@ -984,7 +994,7 @@ mod tests { } else { TransactionVerificationMode::FullVerification }; - move |versioned_tx: VersionedTransaction| -> Result { + move |versioned_tx: VersionedTransaction| -> Result { verify(versioned_tx, verification_mode) } }; @@ -1025,7 +1035,7 @@ mod tests { let verify_transaction = { move |versioned_tx: VersionedTransaction, verification_mode: TransactionVerificationMode| - -> Result { + -> Result { let sanitized_tx = { let message_hash = if verification_mode == TransactionVerificationMode::FullVerification { @@ -1040,7 +1050,8 @@ mod tests { None, SimpleAddressLoader::Disabled, ) - }?; + }? + .into(); Ok(sanitized_tx) } diff --git a/ledger/Cargo.toml b/ledger/Cargo.toml index 7665428981ed82..9ebe4743bc6a7b 100644 --- a/ledger/Cargo.toml +++ b/ledger/Cargo.toml @@ -50,6 +50,7 @@ solana-perf = { workspace = true } solana-program-runtime = { workspace = true } solana-rayon-threadlimit = { workspace = true } solana-runtime = { workspace = true } +solana-runtime-transaction = { workspace = true } solana-sdk = { workspace = true } solana-stake-program = { workspace = true } solana-storage-bigtable = { workspace = true } diff --git a/ledger/benches/blockstore_processor.rs b/ledger/benches/blockstore_processor.rs index f1c335596140b0..3772ef7cf1306c 100644 --- a/ledger/benches/blockstore_processor.rs +++ b/ledger/benches/blockstore_processor.rs @@ -15,6 +15,7 @@ use { bank::Bank, prioritization_fee_cache::PrioritizationFeeCache, transaction_batch::TransactionBatch, }, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, solana_sdk::{ account::Account, feature_set::apply_cost_tracker_during_replay, signature::Keypair, signer::Signer, stake_history::Epoch, system_program, system_transaction, @@ -53,7 +54,7 @@ fn create_funded_accounts(bank: &Bank, num: usize) -> Vec { accounts } -fn create_transactions(bank: &Bank, num: usize) -> Vec { +fn create_transactions(bank: &Bank, num: usize) -> Vec { let funded_accounts = create_funded_accounts(bank, 2 * num); funded_accounts .into_par_iter() @@ -63,7 +64,7 @@ fn create_transactions(bank: &Bank, num: usize) -> Vec { let to = &chunk[1]; system_transaction::transfer(from, &to.pubkey(), 1, bank.last_blockhash()) }) - .map(SanitizedTransaction::from_transaction_for_tests) + .map(|t| SanitizedTransaction::from_transaction_for_tests(t).into()) .collect() } diff --git a/ledger/src/blockstore_processor.rs b/ledger/src/blockstore_processor.rs index e4ae5f368b2afd..32001944ec10b4 100644 --- a/ledger/src/blockstore_processor.rs +++ b/ledger/src/blockstore_processor.rs @@ -42,6 +42,7 @@ use { prioritization_fee_cache::PrioritizationFeeCache, transaction_batch::TransactionBatch, }, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, solana_sdk::{ clock::{Slot, MAX_PROCESSING_AGE}, feature_set, @@ -53,8 +54,7 @@ use { signature::{Keypair, Signature}, timing, transaction::{ - Result, SanitizedTransaction, TransactionError, TransactionVerificationMode, - VersionedTransaction, + Result, TransactionError, TransactionVerificationMode, VersionedTransaction, }, }, solana_svm::{ @@ -169,6 +169,7 @@ pub fn execute_batch( ExecutionRecordingConfig::new_single_setting(transaction_status_sender.is_some()), timings, log_messages_bytes_limit, + None, ); bank_utils::find_and_send_votes( @@ -379,7 +380,7 @@ fn schedule_batches_for_execution( fn rebatch_transactions<'a>( lock_results: &'a [Result<()>], bank: &'a Arc, - sanitized_txs: &'a [SanitizedTransaction], + sanitized_txs: &'a [ExtendedSanitizedTransaction], start: usize, end: usize, transaction_indexes: &'a [usize], @@ -427,7 +428,7 @@ fn rebatch_and_execute_batches( let tx_costs = sanitized_txs .iter() .map(|tx| { - let tx_cost = CostModel::calculate_cost(tx, &bank.feature_set); + let tx_cost = CostModel::calculate_cost(tx.transaction(), &bank.feature_set); let cost = tx_cost.sum(); minimal_tx_cost = std::cmp::min(minimal_tx_cost, cost); total_cost = total_cost.saturating_add(cost); @@ -508,7 +509,7 @@ pub fn process_entries_for_tests( ) -> Result<()> { let verify_transaction = { let bank = bank.clone_with_scheduler(); - move |versioned_tx: VersionedTransaction| -> Result { + move |versioned_tx: VersionedTransaction| -> Result { bank.verify_transaction(versioned_tx, TransactionVerificationMode::FullVerification) } }; @@ -1287,7 +1288,7 @@ fn confirm_slot_entries( let bank = bank.clone_with_scheduler(); move |versioned_tx: VersionedTransaction, verification_mode: TransactionVerificationMode| - -> Result { + -> Result { bank.verify_transaction(versioned_tx, verification_mode) } }; @@ -1834,7 +1835,7 @@ pub enum TransactionStatusMessage { pub struct TransactionStatusBatch { pub bank: Arc, - pub transactions: Vec, + pub transactions: Vec, pub execution_results: Vec>, pub balances: TransactionBalancesSet, pub token_balances: TransactionTokenBalancesSet, @@ -1851,7 +1852,7 @@ impl TransactionStatusSender { pub fn send_transaction_status_batch( &self, bank: Arc, - transactions: Vec, + transactions: Vec, execution_results: Vec, balances: TransactionBalancesSet, token_balances: TransactionTokenBalancesSet, @@ -1971,7 +1972,7 @@ pub mod tests { signature::{Keypair, Signer}, system_instruction::SystemError, system_transaction, - transaction::{Transaction, TransactionError}, + transaction::{SanitizedTransaction, Transaction, TransactionError}, }, solana_svm::transaction_processor::ExecutionRecordingConfig, solana_vote::vote_account::VoteAccount, @@ -3967,6 +3968,7 @@ pub mod tests { ExecutionRecordingConfig::new_single_setting(false), &mut ExecuteTimings::default(), None, + None, ); let (err, signature) = get_first_error(&batch, fee_collection_results).unwrap(); assert_eq!(err.unwrap_err(), TransactionError::AccountNotFound); @@ -4360,7 +4362,7 @@ pub mod tests { fn create_test_transactions( mint_keypair: &Keypair, genesis_hash: &Hash, - ) -> Vec { + ) -> Vec { let pubkey = solana_sdk::pubkey::new_rand(); let keypair2 = Keypair::new(); let pubkey2 = solana_sdk::pubkey::new_rand(); @@ -4373,19 +4375,22 @@ pub mod tests { &pubkey, 1, *genesis_hash, - )), + )) + .into(), SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( &keypair2, &pubkey2, 1, *genesis_hash, - )), + )) + .into(), SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( &keypair3, &pubkey3, 1, *genesis_hash, - )), + )) + .into(), ] } diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index 1043b74c67c619..966dbad946b633 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -4666,6 +4666,7 @@ dependencies = [ "solana-nohash-hasher", "solana-program-runtime", "solana-rayon-threadlimit", + "solana-runtime-transaction", "solana-sdk", "solana-stake-program", "solana-svm", @@ -4732,6 +4733,7 @@ dependencies = [ "solana-banks-interface", "solana-client", "solana-runtime", + "solana-runtime-transaction", "solana-sdk", "solana-send-transaction-service", "solana-svm", @@ -4977,11 +4979,13 @@ dependencies = [ "solana-rpc", "solana-rpc-client-api", "solana-runtime", + "solana-runtime-transaction", "solana-sdk", "solana-send-transaction-service", "solana-streamer", "solana-svm", "solana-tpu-client", + "solana-transaction-metrics-tracker", "solana-transaction-status", "solana-turbine", "solana-unified-scheduler-pool", @@ -5050,6 +5054,7 @@ dependencies = [ "solana-metrics", "solana-perf", "solana-rayon-threadlimit", + "solana-runtime-transaction", "solana-sdk", ] @@ -5237,6 +5242,7 @@ dependencies = [ "solana-program-runtime", "solana-rayon-threadlimit", "solana-runtime", + "solana-runtime-transaction", "solana-sdk", "solana-stake-program", "solana-storage-bigtable", @@ -5592,6 +5598,7 @@ dependencies = [ "solana-rayon-threadlimit", "solana-rpc-client-api", "solana-runtime", + "solana-runtime-transaction", "solana-sdk", "solana-send-transaction-service", "solana-stake-program", @@ -5684,6 +5691,7 @@ dependencies = [ "dir-diff", "flate2", "fnv", + "histogram", "im", "index_list", "itertools", @@ -5722,6 +5730,7 @@ dependencies = [ "solana-perf", "solana-program-runtime", "solana-rayon-threadlimit", + "solana-runtime-transaction", "solana-sdk", "solana-stake-program", "solana-svm", @@ -5741,6 +5750,17 @@ dependencies = [ "zstd", ] +[[package]] +name = "solana-runtime-transaction" +version = "2.0.0" +dependencies = [ + "log", + "rustc_version", + "solana-program-runtime", + "solana-sdk", + "thiserror", +] + [[package]] name = "solana-sbf-programs" version = "2.0.0" @@ -6328,9 +6348,11 @@ dependencies = [ "quinn-proto", "rand 0.8.5", "rustls", + "solana-measure", "solana-metrics", "solana-perf", "solana-sdk", + "solana-transaction-metrics-tracker", "thiserror", "tokio", "x509-parser", @@ -6340,6 +6362,7 @@ dependencies = [ name = "solana-svm" version = "2.0.0" dependencies = [ + "histogram", "itertools", "log", "percentage", @@ -6351,6 +6374,7 @@ dependencies = [ "solana-measure", "solana-metrics", "solana-program-runtime", + "solana-runtime-transaction", "solana-sdk", "solana-system-program", ] @@ -6432,6 +6456,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "solana-transaction-metrics-tracker" +version = "2.0.0" +dependencies = [ + "Inflector", + "base64 0.21.7", + "bincode", + "lazy_static", + "log", + "rand 0.8.5", + "solana-perf", + "solana-sdk", +] + [[package]] name = "solana-transaction-status" version = "2.0.0" @@ -6506,6 +6544,7 @@ dependencies = [ name = "solana-unified-scheduler-logic" version = "2.0.0" dependencies = [ + "solana-runtime-transaction", "solana-sdk", ] @@ -6520,6 +6559,7 @@ dependencies = [ "solana-ledger", "solana-program-runtime", "solana-runtime", + "solana-runtime-transaction", "solana-sdk", "solana-unified-scheduler-logic", "solana-vote", diff --git a/programs/sbf/tests/programs.rs b/programs/sbf/tests/programs.rs index 22969bc482a28e..9e8eef6be01b2f 100644 --- a/programs/sbf/tests/programs.rs +++ b/programs/sbf/tests/programs.rs @@ -112,6 +112,7 @@ fn process_transaction_and_record_inner( }, &mut ExecuteTimings::default(), None, + None, ) .0; let result = results @@ -158,6 +159,7 @@ fn execute_transactions( ExecutionRecordingConfig::new_single_setting(true), &mut timings, None, + None, ); let tx_post_token_balances = collect_token_balances(&bank, &batch, &mut mint_decimals); @@ -676,7 +678,7 @@ fn test_return_data_and_log_data_syscall() { let blockhash = bank.last_blockhash(); let message = Message::new(&[instruction], Some(&mint_keypair.pubkey())); let transaction = Transaction::new(&[&mint_keypair], message, blockhash); - let sanitized_tx = SanitizedTransaction::from_transaction_for_tests(transaction); + let sanitized_tx = SanitizedTransaction::from_transaction_for_tests(transaction).into(); let result = bank.simulate_transaction(&sanitized_tx, false); diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index d4f2648b6b1078..8a5c7f57c5b1ea 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -43,6 +43,7 @@ solana-poh = { workspace = true } solana-rayon-threadlimit = { workspace = true } solana-rpc-client-api = { workspace = true } solana-runtime = { workspace = true } +solana-runtime-transaction = { workspace = true } solana-sdk = { workspace = true } solana-send-transaction-service = { workspace = true } solana-stake-program = { workspace = true } diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 41b26e5fa1e2c2..6b921ae990fdfb 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -3252,6 +3252,7 @@ pub mod rpc_accounts_scan { pub mod rpc_full { use { super::*, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, solana_sdk::message::{SanitizedVersionedMessage, VersionedMessage}, solana_transaction_status::UiInnerInstructions, }; @@ -3665,7 +3666,8 @@ pub mod rpc_full { units_consumed, return_data, inner_instructions: _, // Always `None` due to `enable_cpi_recording = false` - } = preflight_bank.simulate_transaction(&transaction, false) + } = preflight_bank + .simulate_transaction(&ExtendedSanitizedTransaction::from(transaction), false) { match err { TransactionError::BlockhashNotFound => { @@ -3741,8 +3743,9 @@ pub mod rpc_full { } let transaction = sanitize_transaction(unsanitized_tx, bank)?; + let transaction = ExtendedSanitizedTransaction::from(transaction); if sig_verify { - verify_transaction(&transaction, &bank.feature_set)?; + verify_transaction(transaction.transaction(), &bank.feature_set)?; } let TransactionSimulationResult { @@ -5078,7 +5081,7 @@ pub mod tests { let prioritization_fee_cache = &self.meta.prioritization_fee_cache; let transactions: Vec<_> = transactions .into_iter() - .map(SanitizedTransaction::from_transaction_for_tests) + .map(|t| SanitizedTransaction::from_transaction_for_tests(t).into()) .collect(); prioritization_fee_cache.update(&bank, transactions.iter()); } diff --git a/rpc/src/transaction_status_service.rs b/rpc/src/transaction_status_service.rs index 8730fb2ed0f3d8..8fae3cbbee5688 100644 --- a/rpc/src/transaction_status_service.rs +++ b/rpc/src/transaction_status_service.rs @@ -160,7 +160,7 @@ impl TransactionStatusService { transaction_index, transaction.signature(), &transaction_status_meta, - &transaction, + transaction.transaction(), ); } @@ -408,7 +408,7 @@ pub(crate) mod tests { let transaction_index: usize = bank.transaction_count().try_into().unwrap(); let transaction_status_batch = TransactionStatusBatch { bank, - transactions: vec![transaction], + transactions: vec![transaction.into()], execution_results: vec![transaction_result], balances, token_balances, diff --git a/runtime-transaction/src/extended_transaction.rs b/runtime-transaction/src/extended_transaction.rs new file mode 100644 index 00000000000000..ea85920d984630 --- /dev/null +++ b/runtime-transaction/src/extended_transaction.rs @@ -0,0 +1,108 @@ +use { + solana_sdk::{ + hash::Hash, + message::{v0::LoadedAddresses, SanitizedMessage}, + signature::Signature, + transaction::{ + Result, SanitizedTransaction, TransactionAccountLocks, VersionedTransaction, + }, + }, + std::time::Instant, +}; + +/// Sanitized transaction with optional start_time +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct ExtendedSanitizedTransaction { + transaction: SanitizedTransaction, + start_time: Option, +} + +impl From for ExtendedSanitizedTransaction { + fn from(value: SanitizedTransaction) -> Self { + Self { + transaction: value, + start_time: None, + } + } +} + +impl ExtendedSanitizedTransaction { + pub fn new(transaction: SanitizedTransaction, start_time: Option) -> Self { + Self { + transaction, + start_time, + } + } + + pub fn transaction(&self) -> &SanitizedTransaction { + &self.transaction + } + + pub fn start_time(&self) -> &Option { + &self.start_time + } + + /// Return the first signature for this transaction. + /// + /// Notes: + /// + /// Sanitized transactions must have at least one signature because the + /// number of signatures must be greater than or equal to the message header + /// value `num_required_signatures` which must be greater than 0 itself. + #[inline(always)] + pub fn signature(&self) -> &Signature { + self.transaction.signature() + } + + /// Return the list of signatures for this transaction + #[inline(always)] + pub fn signatures(&self) -> &[Signature] { + self.transaction.signatures() + } + + /// Return the signed message + #[inline(always)] + pub fn message(&self) -> &SanitizedMessage { + self.transaction.message() + } + + /// Return the hash of the signed message + #[inline(always)] + pub fn message_hash(&self) -> &Hash { + self.transaction.message_hash() + } + + /// Returns true if this transaction is a simple vote + #[inline(always)] + pub fn is_simple_vote_transaction(&self) -> bool { + self.transaction.is_simple_vote_transaction() + } + + /// Convert this sanitized transaction into a versioned transaction for + /// recording in the ledger. + #[inline(always)] + pub fn to_versioned_transaction(&self) -> VersionedTransaction { + self.transaction.to_versioned_transaction() + } + + /// Validate and return the account keys locked by this transaction + #[inline(always)] + pub fn get_account_locks( + &self, + tx_account_lock_limit: usize, + ) -> Result { + self.transaction.get_account_locks(tx_account_lock_limit) + } + + /// Return the list of accounts that must be locked during processing this transaction. + #[inline(always)] + pub fn get_account_locks_unchecked(&self) -> TransactionAccountLocks { + self.transaction.get_account_locks_unchecked() + } + + /// Return the list of addresses loaded from on-chain address lookup tables + #[inline(always)] + pub fn get_loaded_addresses(&self) -> LoadedAddresses { + self.transaction.get_loaded_addresses() + } +} diff --git a/runtime-transaction/src/lib.rs b/runtime-transaction/src/lib.rs index 0fdeb7c5b6bd65..4868e1f77275fc 100644 --- a/runtime-transaction/src/lib.rs +++ b/runtime-transaction/src/lib.rs @@ -1,5 +1,6 @@ #![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(min_specialization))] #![allow(clippy::arithmetic_side_effects)] +pub mod extended_transaction; pub mod runtime_transaction; pub mod transaction_meta; diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 02553d4215909d..e439f92ef008c0 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -24,6 +24,7 @@ dashmap = { workspace = true, features = ["rayon", "raw-api"] } dir-diff = { workspace = true } flate2 = { workspace = true } fnv = { workspace = true } +histogram = { workspace = true } im = { workspace = true, features = ["rayon", "serde"] } index_list = { workspace = true } itertools = { workspace = true } @@ -61,6 +62,7 @@ solana-metrics = { workspace = true } solana-perf = { workspace = true } solana-program-runtime = { workspace = true } solana-rayon-threadlimit = { workspace = true } +solana-runtime-transaction = { workspace = true } solana-sdk = { workspace = true } solana-stake-program = { workspace = true } solana-svm = { workspace = true } diff --git a/runtime/benches/prioritization_fee_cache.rs b/runtime/benches/prioritization_fee_cache.rs index 8c6bf1fe0a7d68..5913a76a14581c 100644 --- a/runtime/benches/prioritization_fee_cache.rs +++ b/runtime/benches/prioritization_fee_cache.rs @@ -55,6 +55,7 @@ fn bench_process_transactions_single_slot(bencher: &mut Bencher) { &Pubkey::new_unique(), &Pubkey::new_unique(), ) + .into() }) .collect(); @@ -82,6 +83,7 @@ fn process_transactions_multiple_slots(banks: &[Arc], num_slots: usize, nu &Pubkey::new_unique(), &Pubkey::new_unique(), ) + .into() }) .collect(); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index d1a1805d0d3a20..14a8ec6137a12a 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -102,6 +102,7 @@ use { runtime_config::RuntimeConfig, timings::{ExecuteTimingType, ExecuteTimings}, }, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, solana_sdk::{ account::{ create_account_shared_data_with_fields as create_account, create_executable_meta, @@ -4081,7 +4082,7 @@ impl Bank { fn update_transaction_statuses( &self, - sanitized_txs: &[SanitizedTransaction], + sanitized_txs: &[ExtendedSanitizedTransaction], execution_results: &[TransactionExecutionResult], ) { let mut status_cache = self.status_cache.write().unwrap(); @@ -4202,7 +4203,10 @@ impl Bank { pub fn prepare_entry_batch(&self, txs: Vec) -> Result { let sanitized_txs = txs .into_iter() - .map(|tx| SanitizedTransaction::try_create(tx, MessageHash::Compute, None, self)) + .map(|tx| { + SanitizedTransaction::try_create(tx, MessageHash::Compute, None, self) + .map(|txn| txn.into()) + }) .collect::>>()?; let tx_account_lock_limit = self.get_transaction_account_lock_limit(); let lock_results = self @@ -4219,7 +4223,7 @@ impl Bank { /// Prepare a locked transaction batch from a list of sanitized transactions. pub fn prepare_sanitized_batch<'a, 'b>( &'a self, - txs: &'b [SanitizedTransaction], + txs: &'b [ExtendedSanitizedTransaction], ) -> TransactionBatch<'a, 'b> { let tx_account_lock_limit = self.get_transaction_account_lock_limit(); let lock_results = self @@ -4233,7 +4237,7 @@ impl Bank { /// limited packing status pub fn prepare_sanitized_batch_with_results<'a, 'b>( &'a self, - transactions: &'b [SanitizedTransaction], + transactions: &'b [ExtendedSanitizedTransaction], transaction_results: impl Iterator>, ) -> TransactionBatch<'a, 'b> { // this lock_results could be: Ok, AccountInUse, WouldExceedBlockMaxLimit or WouldExceedAccountMaxLimit @@ -4249,7 +4253,7 @@ impl Bank { /// Prepare a transaction batch from a single transaction without locking accounts pub fn prepare_unlocked_batch_from_single_tx<'a>( &'a self, - transaction: &'a SanitizedTransaction, + transaction: &'a ExtendedSanitizedTransaction, ) -> TransactionBatch<'_, '_> { let tx_account_lock_limit = self.get_transaction_account_lock_limit(); let lock_result = transaction @@ -4267,7 +4271,7 @@ impl Bank { /// Run transactions against a frozen bank without committing the results pub fn simulate_transaction( &self, - transaction: &SanitizedTransaction, + transaction: &ExtendedSanitizedTransaction, enable_cpi_recording: bool, ) -> TransactionSimulationResult { assert!(self.is_frozen(), "simulation bank must be frozen"); @@ -4279,7 +4283,7 @@ impl Bank { /// is frozen, enabling use in single-Bank test frameworks pub fn simulate_transaction_unchecked( &self, - transaction: &SanitizedTransaction, + transaction: &ExtendedSanitizedTransaction, enable_cpi_recording: bool, ) -> TransactionSimulationResult { let account_keys = transaction.message().account_keys(); @@ -4307,6 +4311,7 @@ impl Bank { Some(&account_overrides), None, true, + &mut None, ); let post_simulation_accounts = loaded_transactions @@ -4384,7 +4389,7 @@ impl Bank { pub fn unlock_accounts<'a>( &self, - txs_and_results: impl Iterator)>, + txs_and_results: impl Iterator)>, ) { self.rc.accounts.unlock_accounts(txs_and_results) } @@ -4395,7 +4400,7 @@ impl Bank { fn check_age( &self, - sanitized_txs: &[impl core::borrow::Borrow], + sanitized_txs: &[impl core::borrow::Borrow], lock_results: &[Result<()>], max_age: usize, error_counters: &mut TransactionErrorMetrics, @@ -4409,7 +4414,7 @@ impl Bank { .zip(lock_results) .map(|(tx, lock_res)| match lock_res { Ok(()) => self.check_transaction_age( - tx.borrow(), + tx.borrow().transaction(), max_age, &next_durable_nonce, &hash_queue, @@ -4461,7 +4466,7 @@ impl Bank { fn check_status_cache( &self, - sanitized_txs: &[impl core::borrow::Borrow], + sanitized_txs: &[impl core::borrow::Borrow], lock_results: Vec, error_counters: &mut TransactionErrorMetrics, ) -> Vec { @@ -4472,7 +4477,7 @@ impl Bank { .map(|(sanitized_tx, (lock_result, nonce, lamports))| { let sanitized_tx = sanitized_tx.borrow(); if lock_result.is_ok() - && self.is_transaction_already_processed(sanitized_tx, &rcache) + && self.is_transaction_already_processed(sanitized_tx.transaction(), &rcache) { error_counters.already_processed += 1; return (Err(TransactionError::AlreadyProcessed), None, None); @@ -4525,7 +4530,7 @@ impl Bank { pub fn check_transactions( &self, - sanitized_txs: &[impl core::borrow::Borrow], + sanitized_txs: &[impl core::borrow::Borrow], lock_results: &[Result<()>], max_age: usize, error_counters: &mut TransactionErrorMetrics, @@ -4556,6 +4561,7 @@ impl Bank { account_overrides: Option<&AccountOverrides>, log_messages_bytes_limit: Option, limit_to_load_programs: bool, + perf_track_metrics: &mut Option<&mut histogram::Histogram>, ) -> LoadAndExecuteTransactionsOutput { let sanitized_txs = batch.sanitized_transactions(); debug!("processing transactions: {}", sanitized_txs.len()); @@ -4616,6 +4622,7 @@ impl Bank { &mut check_results, &mut error_counters, recording_config, + perf_track_metrics, timings, account_overrides, self.builtin_programs.iter(), @@ -4817,7 +4824,7 @@ impl Bank { fn filter_program_errors_and_collect_fee( &self, - txs: &[SanitizedTransaction], + txs: &[ExtendedSanitizedTransaction], execution_results: &[TransactionExecutionResult], ) -> Vec> { let hash_queue = self.blockhash_queue.read().unwrap(); @@ -4877,7 +4884,7 @@ impl Bank { /// a failure result. pub fn commit_transactions( &self, - sanitized_txs: &[SanitizedTransaction], + sanitized_txs: &[ExtendedSanitizedTransaction], loaded_txs: &mut [TransactionLoadResult], execution_results: Vec, last_blockhash: Hash, @@ -5636,6 +5643,7 @@ impl Bank { /// Process a batch of transactions. #[must_use] + #[allow(clippy::too_many_arguments)] pub fn load_execute_and_commit_transactions( &self, batch: &TransactionBatch, @@ -5644,6 +5652,7 @@ impl Bank { recording_config: ExecutionRecordingConfig, timings: &mut ExecuteTimings, log_messages_bytes_limit: Option, + mut perf_track_metrics: Option<&mut histogram::Histogram>, ) -> (TransactionResults, TransactionBalancesSet) { let pre_balances = if collect_balances { self.collect_balances(batch) @@ -5667,6 +5676,7 @@ impl Bank { None, log_messages_bytes_limit, false, + &mut perf_track_metrics, ); let (last_blockhash, lamports_per_signature) = @@ -5737,6 +5747,7 @@ impl Bank { }, &mut ExecuteTimings::default(), Some(1000 * 1000), + None, ); execution_results.remove(0) @@ -5773,6 +5784,7 @@ impl Bank { ExecutionRecordingConfig::new_single_setting(false), &mut ExecuteTimings::default(), None, + None, ) .0 .fee_collection_results @@ -6542,7 +6554,7 @@ impl Bank { &self, tx: VersionedTransaction, verification_mode: TransactionVerificationMode, - ) -> Result { + ) -> Result { let sanitized_tx = { let size = bincode::serialized_size(&tx).map_err(|_| TransactionError::SanitizeFailure)?; @@ -6565,13 +6577,13 @@ impl Bank { sanitized_tx.verify_precompiles(&self.feature_set)?; } - Ok(sanitized_tx) + Ok(ExtendedSanitizedTransaction::from(sanitized_tx)) } pub fn fully_verify_transaction( &self, tx: VersionedTransaction, - ) -> Result { + ) -> Result { self.verify_transaction(tx, TransactionVerificationMode::FullVerification) } @@ -6913,7 +6925,7 @@ impl Bank { /// a bank-level cache of vote accounts and stake delegation info fn update_stakes_cache( &self, - txs: &[SanitizedTransaction], + txs: &[ExtendedSanitizedTransaction], execution_results: &[TransactionExecutionResult], loaded_txs: &[TransactionLoadResult], ) { @@ -7446,7 +7458,7 @@ impl Bank { /// Checks a batch of sanitized transactions again bank for age and status pub fn check_transactions_with_forwarding_delay( &self, - transactions: &[SanitizedTransaction], + transactions: &[ExtendedSanitizedTransaction], filter: &[transaction::Result<()>], forward_transactions_to_leader_at_slot_offset: u64, ) -> Vec { @@ -7669,7 +7681,7 @@ impl Bank { let transaction_account_lock_limit = self.get_transaction_account_lock_limit(); let sanitized_txs = txs .into_iter() - .map(SanitizedTransaction::from_transaction_for_tests) + .map(|txn| SanitizedTransaction::from_transaction_for_tests(txn).into()) .collect::>(); let lock_results = self .rc diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index 29dbdc2e5aeacd..c7e3c68e74f793 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -2903,7 +2903,7 @@ fn test_filter_program_errors_and_collect_fee() { ]; let initial_balance = bank.get_balance(&leader); - let results = bank.filter_program_errors_and_collect_fee(&[tx1, tx2], &results); + let results = bank.filter_program_errors_and_collect_fee(&[tx1.into(), tx2.into()], &results); bank.freeze(); assert_eq!( bank.get_balance(&leader), @@ -2954,7 +2954,7 @@ fn test_filter_program_errors_and_collect_compute_unit_fee() { ]; let initial_balance = bank.get_balance(&leader); - let results = bank.filter_program_errors_and_collect_fee(&[tx1, tx2], &results); + let results = bank.filter_program_errors_and_collect_fee(&[tx1.into(), tx2.into()], &results); bank.freeze(); assert_eq!( bank.get_balance(&leader), @@ -3105,6 +3105,7 @@ fn test_interleaving_locks() { ExecutionRecordingConfig::new_single_setting(false), &mut ExecuteTimings::default(), None, + None, ) .0 .fee_collection_results; @@ -5929,6 +5930,7 @@ fn test_pre_post_transaction_balances() { ExecutionRecordingConfig::new_single_setting(false), &mut ExecuteTimings::default(), None, + None, ); assert_eq!(transaction_balances_set.pre_balances.len(), 3); @@ -9213,6 +9215,7 @@ fn test_tx_log_order() { }, &mut ExecuteTimings::default(), None, + None, ) .0 .execution_results; @@ -9323,6 +9326,7 @@ fn test_tx_return_data() { }, &mut ExecuteTimings::default(), None, + None, ) .0 .execution_results[0] @@ -13790,7 +13794,7 @@ fn test_failed_simulation_compute_units() { let transaction = Transaction::new(&[&mint_keypair], message, bank.last_blockhash()); bank.freeze(); - let sanitized = SanitizedTransaction::from_transaction_for_tests(transaction); + let sanitized = SanitizedTransaction::from_transaction_for_tests(transaction).into(); let simulation = bank.simulate_transaction(&sanitized, false); assert_eq!(expected_consumed_units, simulation.units_consumed); } diff --git a/runtime/src/bank_utils.rs b/runtime/src/bank_utils.rs index 10835afb82dc49..80b4ee21b0f9fd 100644 --- a/runtime/src/bank_utils.rs +++ b/runtime/src/bank_utils.rs @@ -7,7 +7,7 @@ use { solana_sdk::{pubkey::Pubkey, signature::Signer}, }; use { - solana_sdk::transaction::SanitizedTransaction, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, solana_svm::transaction_results::TransactionResults, solana_vote::{vote_parser, vote_sender_types::ReplayVoteSender}, }; @@ -37,7 +37,7 @@ pub fn setup_bank_and_vote_pubkeys_for_tests( } pub fn find_and_send_votes( - sanitized_txs: &[SanitizedTransaction], + sanitized_txs: &[ExtendedSanitizedTransaction], tx_results: &TransactionResults, vote_sender: Option<&ReplayVoteSender>, ) { @@ -50,7 +50,9 @@ pub fn find_and_send_votes( .zip(execution_results.iter()) .for_each(|(tx, result)| { if tx.is_simple_vote_transaction() && result.was_executed_successfully() { - if let Some(parsed_vote) = vote_parser::parse_sanitized_vote_transaction(tx) { + if let Some(parsed_vote) = + vote_parser::parse_sanitized_vote_transaction(tx.transaction()) + { if parsed_vote.1.last_voted_slot().is_some() { let _ = vote_sender.send(parsed_vote); } diff --git a/runtime/src/compute_budget_details.rs b/runtime/src/compute_budget_details.rs index 72b10a11b33bcc..fb403720f0c8f7 100644 --- a/runtime/src/compute_budget_details.rs +++ b/runtime/src/compute_budget_details.rs @@ -1,5 +1,6 @@ use { solana_program_runtime::compute_budget_processor::process_compute_budget_instructions, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, solana_sdk::{ instruction::CompiledInstruction, pubkey::Pubkey, @@ -55,6 +56,16 @@ impl GetComputeBudgetDetails for SanitizedTransaction { } } +impl GetComputeBudgetDetails for ExtendedSanitizedTransaction { + fn get_compute_budget_details( + &self, + round_compute_unit_price_enabled: bool, + ) -> Option { + self.transaction() + .get_compute_budget_details(round_compute_unit_price_enabled) + } +} + #[cfg(test)] mod tests { use { diff --git a/runtime/src/installed_scheduler_pool.rs b/runtime/src/installed_scheduler_pool.rs index d39a18d567232a..df77af1718d934 100644 --- a/runtime/src/installed_scheduler_pool.rs +++ b/runtime/src/installed_scheduler_pool.rs @@ -24,11 +24,8 @@ use { crate::bank::Bank, log::*, solana_program_runtime::timings::ExecuteTimings, - solana_sdk::{ - hash::Hash, - slot_history::Slot, - transaction::{Result, SanitizedTransaction}, - }, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, + solana_sdk::{hash::Hash, slot_history::Slot, transaction::Result}, std::{ fmt::Debug, ops::Deref, @@ -104,7 +101,7 @@ pub trait InstalledScheduler: Send + Sync + Debug + 'static { // Calling this is illegal as soon as wait_for_termination is called. fn schedule_execution<'a>( &'a self, - transaction_with_index: &'a (&'a SanitizedTransaction, usize), + transaction_with_index: &'a (&'a ExtendedSanitizedTransaction, usize), ); /// Wait for a scheduler to terminate after processing. @@ -289,7 +286,9 @@ impl BankWithScheduler { // 'a is needed; anonymous_lifetime_in_impl_trait isn't stabilized yet... pub fn schedule_transaction_executions<'a>( &self, - transactions_with_indexes: impl ExactSizeIterator, + transactions_with_indexes: impl ExactSizeIterator< + Item = (&'a ExtendedSanitizedTransaction, &'a usize), + >, ) { trace!( "schedule_transaction_executions(): {} txs", @@ -428,7 +427,7 @@ mod tests { }, assert_matches::assert_matches, mockall::Sequence, - solana_sdk::system_transaction, + solana_sdk::{system_transaction, transaction::SanitizedTransaction}, std::sync::Mutex, }; @@ -575,6 +574,6 @@ mod tests { ); let bank = BankWithScheduler::new(bank, Some(mocked_scheduler)); - bank.schedule_transaction_executions([(&tx0, &0)].into_iter()); + bank.schedule_transaction_executions([(&tx0.into(), &0)].into_iter()); } } diff --git a/runtime/src/prioritization_fee_cache.rs b/runtime/src/prioritization_fee_cache.rs index 0490f594451b9c..f2022399a55da5 100644 --- a/runtime/src/prioritization_fee_cache.rs +++ b/runtime/src/prioritization_fee_cache.rs @@ -5,10 +5,10 @@ use { log::*, lru::LruCache, solana_measure::measure, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, solana_sdk::{ clock::{BankId, Slot}, pubkey::Pubkey, - transaction::SanitizedTransaction, }, std::{ collections::HashMap, @@ -208,7 +208,11 @@ impl PrioritizationFeeCache { /// Update with a list of non-vote transactions' compute_budget_details and account_locks; Only /// transactions have both valid compute_budget_details and account_locks will be used to update /// fee_cache asynchronously. - pub fn update<'a>(&self, bank: &Bank, txs: impl Iterator) { + pub fn update<'a>( + &self, + bank: &Bank, + txs: impl Iterator, + ) { let (_, send_updates_time) = measure!( { for sanitized_transaction in txs { @@ -466,7 +470,7 @@ mod tests { fn sync_update<'a>( prioritization_fee_cache: &PrioritizationFeeCache, bank: Arc, - txs: impl Iterator + ExactSizeIterator, + txs: impl Iterator + ExactSizeIterator, ) { let expected_update_count = prioritization_fee_cache .metrics @@ -525,9 +529,9 @@ mod tests { // [2, a, c ] --> [2, 2, 5, 2 ] // let txs = vec![ - build_sanitized_transaction_for_test(5, &write_account_a, &write_account_b), - build_sanitized_transaction_for_test(9, &write_account_b, &write_account_c), - build_sanitized_transaction_for_test(2, &write_account_a, &write_account_c), + build_sanitized_transaction_for_test(5, &write_account_a, &write_account_b).into(), + build_sanitized_transaction_for_test(9, &write_account_b, &write_account_c).into(), + build_sanitized_transaction_for_test(2, &write_account_a, &write_account_c).into(), ]; let bank = Arc::new(Bank::default_for_tests()); @@ -642,12 +646,13 @@ mod tests { // Assert after add one transaction for slot 1 { let txs = vec![ - build_sanitized_transaction_for_test(2, &write_account_a, &write_account_b), + build_sanitized_transaction_for_test(2, &write_account_a, &write_account_b).into(), build_sanitized_transaction_for_test( 1, &Pubkey::new_unique(), &Pubkey::new_unique(), - ), + ) + .into(), ]; sync_update(&prioritization_fee_cache, bank1.clone(), txs.iter()); // before block is marked as completed @@ -705,12 +710,13 @@ mod tests { // Assert after add one transaction for slot 2 { let txs = vec![ - build_sanitized_transaction_for_test(4, &write_account_b, &write_account_c), + build_sanitized_transaction_for_test(4, &write_account_b, &write_account_c).into(), build_sanitized_transaction_for_test( 3, &Pubkey::new_unique(), &Pubkey::new_unique(), - ), + ) + .into(), ]; sync_update(&prioritization_fee_cache, bank2.clone(), txs.iter()); // before block is marked as completed @@ -779,12 +785,13 @@ mod tests { // Assert after add one transaction for slot 3 { let txs = vec![ - build_sanitized_transaction_for_test(6, &write_account_a, &write_account_c), + build_sanitized_transaction_for_test(6, &write_account_a, &write_account_c).into(), build_sanitized_transaction_for_test( 5, &Pubkey::new_unique(), &Pubkey::new_unique(), - ), + ) + .into(), ]; sync_update(&prioritization_fee_cache, bank3.clone(), txs.iter()); // before block is marked as completed @@ -874,12 +881,13 @@ mod tests { // Assert after add transactions for bank1 of slot 1 { let txs = vec![ - build_sanitized_transaction_for_test(2, &write_account_a, &write_account_b), + build_sanitized_transaction_for_test(2, &write_account_a, &write_account_b).into(), build_sanitized_transaction_for_test( 1, &Pubkey::new_unique(), &Pubkey::new_unique(), - ), + ) + .into(), ]; sync_update(&prioritization_fee_cache, bank1.clone(), txs.iter()); @@ -894,12 +902,13 @@ mod tests { // Assert after add transactions for bank2 of slot 1 { let txs = vec![ - build_sanitized_transaction_for_test(4, &write_account_b, &write_account_c), + build_sanitized_transaction_for_test(4, &write_account_b, &write_account_c).into(), build_sanitized_transaction_for_test( 3, &Pubkey::new_unique(), &Pubkey::new_unique(), - ), + ) + .into(), ]; sync_update(&prioritization_fee_cache, bank2.clone(), txs.iter()); diff --git a/runtime/src/transaction_batch.rs b/runtime/src/transaction_batch.rs index ecec27e02e93aa..5cbfbe5818ac33 100644 --- a/runtime/src/transaction_batch.rs +++ b/runtime/src/transaction_batch.rs @@ -1,14 +1,14 @@ use { crate::bank::Bank, - solana_sdk::transaction::{Result, SanitizedTransaction}, - std::borrow::Cow, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, + solana_sdk::transaction::Result, std::borrow::Cow, }; // Represents the results of trying to lock a set of accounts pub struct TransactionBatch<'a, 'b> { lock_results: Vec>, bank: &'a Bank, - sanitized_txs: Cow<'b, [SanitizedTransaction]>, + sanitized_txs: Cow<'b, [ExtendedSanitizedTransaction]>, needs_unlock: bool, } @@ -16,7 +16,7 @@ impl<'a, 'b> TransactionBatch<'a, 'b> { pub fn new( lock_results: Vec>, bank: &'a Bank, - sanitized_txs: Cow<'b, [SanitizedTransaction]>, + sanitized_txs: Cow<'b, [ExtendedSanitizedTransaction]>, ) -> Self { assert_eq!(lock_results.len(), sanitized_txs.len()); Self { @@ -31,7 +31,7 @@ impl<'a, 'b> TransactionBatch<'a, 'b> { &self.lock_results } - pub fn sanitized_transactions(&self) -> &[SanitizedTransaction] { + pub fn sanitized_transactions(&self) -> &[ExtendedSanitizedTransaction] { &self.sanitized_txs } @@ -100,7 +100,11 @@ mod tests { use { super::*, crate::genesis_utils::{create_genesis_config_with_leader, GenesisConfigInfo}, - solana_sdk::{signature::Keypair, system_transaction, transaction::TransactionError}, + solana_sdk::{ + signature::Keypair, + system_transaction, + transaction::{SanitizedTransaction, TransactionError}, + }, }; #[test] @@ -172,7 +176,7 @@ mod tests { ); } - fn setup(insert_conflicting_tx: bool) -> (Bank, Vec) { + fn setup(insert_conflicting_tx: bool) -> (Bank, Vec) { let dummy_leader_pubkey = solana_sdk::pubkey::new_rand(); let GenesisConfigInfo { genesis_config, @@ -185,18 +189,36 @@ mod tests { let keypair2 = Keypair::new(); let pubkey2 = solana_sdk::pubkey::new_rand(); - let mut txs = vec![SanitizedTransaction::from_transaction_for_tests( - system_transaction::transfer(&mint_keypair, &pubkey, 1, genesis_config.hash()), - )]; + let mut txs = + vec![ + SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( + &mint_keypair, + &pubkey, + 1, + genesis_config.hash(), + )) + .into(), + ]; if insert_conflicting_tx { - txs.push(SanitizedTransaction::from_transaction_for_tests( - system_transaction::transfer(&mint_keypair, &pubkey2, 1, genesis_config.hash()), - )); + txs.push( + SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( + &mint_keypair, + &pubkey2, + 1, + genesis_config.hash(), + )) + .into(), + ); } - txs.push(SanitizedTransaction::from_transaction_for_tests( - system_transaction::transfer(&keypair2, &pubkey2, 1, genesis_config.hash()), - )); - + txs.push( + SanitizedTransaction::from_transaction_for_tests(system_transaction::transfer( + &keypair2, + &pubkey2, + 1, + genesis_config.hash(), + )) + .into(), + ); (bank, txs) } } diff --git a/sdk/src/packet.rs b/sdk/src/packet.rs index faea9ab4753c67..8300b57218c696 100644 --- a/sdk/src/packet.rs +++ b/sdk/src/packet.rs @@ -33,6 +33,8 @@ bitflags! { /// the packet is built. /// This field can be removed when the above feature gate is adopted by mainnet-beta. const ROUND_COMPUTE_UNIT_PRICE = 0b0010_0000; + /// For tracking performance + const PERF_TRACK_PACKET = 0b0100_0000; } } @@ -228,6 +230,12 @@ impl Meta { self.flags.set(PacketFlags::TRACER_PACKET, is_tracer); } + #[inline] + pub fn set_track_performance(&mut self, is_performance_track: bool) { + self.flags + .set(PacketFlags::PERF_TRACK_PACKET, is_performance_track); + } + #[inline] pub fn set_simple_vote(&mut self, is_simple_vote: bool) { self.flags.set(PacketFlags::SIMPLE_VOTE_TX, is_simple_vote); @@ -261,6 +269,11 @@ impl Meta { self.flags.contains(PacketFlags::TRACER_PACKET) } + #[inline] + pub fn is_perf_track_packet(&self) -> bool { + self.flags.contains(PacketFlags::PERF_TRACK_PACKET) + } + #[inline] pub fn round_compute_unit_price(&self) -> bool { self.flags.contains(PacketFlags::ROUND_COMPUTE_UNIT_PRICE) diff --git a/sdk/src/transaction/versioned/sanitized.rs b/sdk/src/transaction/versioned/sanitized.rs index 61ecdfea56bb2a..b6311d5886b0e3 100644 --- a/sdk/src/transaction/versioned/sanitized.rs +++ b/sdk/src/transaction/versioned/sanitized.rs @@ -33,6 +33,10 @@ impl SanitizedVersionedTransaction { &self.message } + pub fn get_signatures(&self) -> &Vec { + &self.signatures + } + /// Consumes the SanitizedVersionedTransaction, returning the fields individually. pub fn destruct(self) -> (Vec, SanitizedVersionedMessage) { (self.signatures, self.message) diff --git a/streamer/Cargo.toml b/streamer/Cargo.toml index 8e1eb12dff1d42..55d0030e734607 100644 --- a/streamer/Cargo.toml +++ b/streamer/Cargo.toml @@ -26,9 +26,11 @@ quinn = { workspace = true } quinn-proto = { workspace = true } rand = { workspace = true } rustls = { workspace = true, features = ["dangerous_configuration"] } +solana-measure = { workspace = true } solana-metrics = { workspace = true } solana-perf = { workspace = true } solana-sdk = { workspace = true } +solana-transaction-metrics-tracker = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } x509-parser = { workspace = true } diff --git a/streamer/src/nonblocking/quic.rs b/streamer/src/nonblocking/quic.rs index 225412dd08b315..3485e4fe585d06 100644 --- a/streamer/src/nonblocking/quic.rs +++ b/streamer/src/nonblocking/quic.rs @@ -17,6 +17,7 @@ use { quinn::{Connecting, Connection, Endpoint, EndpointConfig, TokioRuntime, VarInt}, quinn_proto::VarIntBoundsExceeded, rand::{thread_rng, Rng}, + solana_measure::measure::Measure, solana_perf::packet::{PacketBatch, PACKETS_PER_BATCH}, solana_sdk::{ packet::{Meta, PACKET_DATA_SIZE}, @@ -27,9 +28,10 @@ use { QUIC_MIN_STAKED_CONCURRENT_STREAMS, QUIC_MIN_STAKED_RECEIVE_WINDOW_RATIO, QUIC_TOTAL_STAKED_CONCURRENT_STREAMS, QUIC_UNSTAKED_RECEIVE_WINDOW_RATIO, }, - signature::Keypair, + signature::{Keypair, Signature}, timing, }, + solana_transaction_metrics_tracker::signature_if_should_track_packet, std::{ iter::repeat_with, net::{IpAddr, SocketAddr, UdpSocket}, @@ -81,6 +83,7 @@ struct PacketChunk { struct PacketAccumulator { pub meta: Meta, pub chunks: Vec, + pub start_time: Instant, } #[derive(Copy, Clone, Debug)] @@ -628,6 +631,7 @@ async fn packet_batch_sender( trace!("enter packet_batch_sender"); let mut batch_start_time = Instant::now(); loop { + let mut packet_perf_measure: Vec<([u8; 64], std::time::Instant)> = Vec::default(); let mut packet_batch = PacketBatch::with_capacity(PACKETS_PER_BATCH); let mut total_bytes: usize = 0; @@ -647,6 +651,8 @@ async fn packet_batch_sender( || (!packet_batch.is_empty() && elapsed >= coalesce) { let len = packet_batch.len(); + track_streamer_fetch_packet_performance(&mut packet_perf_measure, &stats); + if let Err(e) = packet_sender.send(packet_batch) { stats .total_packet_batch_send_err @@ -692,6 +698,14 @@ async fn packet_batch_sender( total_bytes += packet_batch[i].meta().size; + if let Some(signature) = signature_if_should_track_packet(&packet_batch[i]) + .ok() + .flatten() + { + packet_perf_measure.push((*signature, packet_accumulator.start_time)); + // we set the PERF_TRACK_PACKET on + packet_batch[i].meta_mut().set_track_performance(true); + } stats .total_chunks_processed_by_batcher .fetch_add(num_chunks, Ordering::Relaxed); @@ -700,6 +714,32 @@ async fn packet_batch_sender( } } +fn track_streamer_fetch_packet_performance( + packet_perf_measure: &mut [([u8; 64], Instant)], + stats: &Arc, +) { + if packet_perf_measure.is_empty() { + return; + } + let mut measure = Measure::start("track_perf"); + let mut process_sampled_packets_us_hist = stats.process_sampled_packets_us_hist.lock().unwrap(); + + for (signature, start_time) in packet_perf_measure.iter() { + let duration = Instant::now().duration_since(*start_time); + debug!( + "QUIC streamer fetch stage took {duration:?} for transaction {:?}", + Signature::from(*signature) + ); + process_sampled_packets_us_hist + .increment(duration.as_micros() as u64) + .unwrap(); + } + measure.stop(); + stats + .perf_track_overhead_us + .fetch_add(measure.as_us(), Ordering::Relaxed); +} + async fn handle_connection( connection: Connection, remote_addr: SocketAddr, @@ -854,6 +894,7 @@ async fn handle_chunk( *packet_accum = Some(PacketAccumulator { meta, chunks: Vec::new(), + start_time: Instant::now(), }); } @@ -1453,6 +1494,7 @@ pub mod test { offset, end_of_chunk: size, }], + start_time: Instant::now(), }; ptk_sender.send(packet_accum).await.unwrap(); } diff --git a/streamer/src/quic.rs b/streamer/src/quic.rs index a7a08c73f4833b..3b1b6b21adf468 100644 --- a/streamer/src/quic.rs +++ b/streamer/src/quic.rs @@ -16,8 +16,8 @@ use { std::{ net::UdpSocket, sync::{ - atomic::{AtomicBool, AtomicUsize, Ordering}, - Arc, RwLock, + atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering}, + Arc, Mutex, RwLock, }, thread, time::{Duration, SystemTime}, @@ -175,10 +175,19 @@ pub struct StreamStats { pub(crate) stream_load_ema: AtomicUsize, pub(crate) stream_load_ema_overflow: AtomicUsize, pub(crate) stream_load_capacity_overflow: AtomicUsize, + pub(crate) process_sampled_packets_us_hist: Mutex, + pub(crate) perf_track_overhead_us: AtomicU64, } impl StreamStats { pub fn report(&self, name: &'static str) { + let process_sampled_packets_us_hist = { + let mut metrics = self.process_sampled_packets_us_hist.lock().unwrap(); + let process_sampled_packets_us_hist = metrics.clone(); + metrics.clear(); + process_sampled_packets_us_hist + }; + datapoint_info!( name, ( @@ -425,6 +434,38 @@ impl StreamStats { self.stream_load_capacity_overflow.load(Ordering::Relaxed), i64 ), + ( + "process_sampled_packets_us_90pct", + process_sampled_packets_us_hist + .percentile(90.0) + .unwrap_or(0), + i64 + ), + ( + "process_sampled_packets_us_min", + process_sampled_packets_us_hist.minimum().unwrap_or(0), + i64 + ), + ( + "process_sampled_packets_us_max", + process_sampled_packets_us_hist.maximum().unwrap_or(0), + i64 + ), + ( + "process_sampled_packets_us_mean", + process_sampled_packets_us_hist.mean().unwrap_or(0), + i64 + ), + ( + "process_sampled_packets_count", + process_sampled_packets_us_hist.entries(), + i64 + ), + ( + "perf_track_overhead_us", + self.perf_track_overhead_us.swap(0, Ordering::Relaxed), + i64 + ), ); } } diff --git a/svm/Cargo.toml b/svm/Cargo.toml index 21da2f7105bd73..4d5bb9f17781b0 100644 --- a/svm/Cargo.toml +++ b/svm/Cargo.toml @@ -10,6 +10,7 @@ license = { workspace = true } edition = { workspace = true } [dependencies] +histogram = { workspace = true } itertools = { workspace = true } log = { workspace = true } percentage = { workspace = true } @@ -20,6 +21,7 @@ solana-loader-v4-program = { workspace = true } solana-measure = { workspace = true } solana-metrics = { workspace = true } solana-program-runtime = { workspace = true } +solana-runtime-transaction = { workspace = true } solana-sdk = { workspace = true } solana-system-program = { workspace = true } diff --git a/svm/src/account_loader.rs b/svm/src/account_loader.rs index bf9b5b9c40bfee..ab7d27f25c0a36 100644 --- a/svm/src/account_loader.rs +++ b/svm/src/account_loader.rs @@ -10,6 +10,7 @@ use { compute_budget_processor::process_compute_budget_instructions, loaded_programs::LoadedProgramsForTxBatch, }, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, solana_sdk::{ account::{ create_executable_meta, is_builtin, is_executable, Account, AccountSharedData, @@ -30,7 +31,7 @@ use { rent_debits::RentDebits, saturating_add_assign, sysvar::{self, instructions::construct_instructions_data}, - transaction::{self, Result, SanitizedTransaction, TransactionError}, + transaction::{self, Result, TransactionError}, transaction_context::{IndexOfAccount, TransactionAccount}, }, solana_system_program::{get_system_account_kind, SystemAccountKind}, @@ -112,7 +113,7 @@ pub fn validate_fee_payer( /// second element. pub(crate) fn load_accounts( callbacks: &CB, - txs: &[SanitizedTransaction], + txs: &[ExtendedSanitizedTransaction], lock_results: &[TransactionCheckResult], error_counters: &mut TransactionErrorMetrics, fee_structure: &FeeStructure, @@ -549,7 +550,7 @@ mod tests { fee_structure: &FeeStructure, ) -> Vec { feature_set.deactivate(&feature_set::disable_rent_fees_collection::id()); - let sanitized_tx = SanitizedTransaction::from_transaction_for_tests(tx); + let sanitized_tx = SanitizedTransaction::from_transaction_for_tests(tx).into(); let mut accounts_map = HashMap::new(); for (pubkey, account) in ka { accounts_map.insert(*pubkey, account.clone()); @@ -1025,7 +1026,7 @@ mod tests { tx: Transaction, account_overrides: Option<&AccountOverrides>, ) -> Vec { - let tx = SanitizedTransaction::from_transaction_for_tests(tx); + let tx = SanitizedTransaction::from_transaction_for_tests(tx).into(); let mut error_counters = TransactionErrorMetrics::default(); let mut accounts_map = HashMap::new(); @@ -2068,7 +2069,7 @@ mod tests { let mut error_counters = TransactionErrorMetrics::default(); let loaded_txs = load_accounts( &bank, - &[sanitized_tx.clone()], + &[sanitized_tx.clone().into()], &[(Ok(()), None, Some(0))], &mut error_counters, &FeeStructure::default(), @@ -2153,7 +2154,7 @@ mod tests { let results = load_accounts( &mock_bank, - &[sanitized_transaction], + &[sanitized_transaction.into()], &[lock_results], &mut error_counter, &FeeStructure::default(), @@ -2228,7 +2229,7 @@ mod tests { let result = load_accounts( &mock_bank, - &[sanitized_transaction.clone()], + &[sanitized_transaction.clone().into()], &[lock_results], &mut TransactionErrorMetrics::default(), &fee_structure, @@ -2247,7 +2248,7 @@ mod tests { let result = load_accounts( &mock_bank, - &[sanitized_transaction.clone()], + &[sanitized_transaction.clone().into()], &[lock_results.clone()], &mut TransactionErrorMetrics::default(), &fee_structure, @@ -2266,7 +2267,7 @@ mod tests { let result = load_accounts( &mock_bank, - &[sanitized_transaction.clone()], + &[sanitized_transaction.clone().into()], &[lock_results], &mut TransactionErrorMetrics::default(), &fee_structure, diff --git a/svm/src/transaction_processor.rs b/svm/src/transaction_processor.rs index 5801b3b8316fdc..9f457361e64041 100644 --- a/svm/src/transaction_processor.rs +++ b/svm/src/transaction_processor.rs @@ -26,6 +26,7 @@ use { sysvar_cache::SysvarCache, timings::{ExecuteDetailsTimings, ExecuteTimingType, ExecuteTimings}, }, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, solana_sdk::{ account::{AccountSharedData, ReadableAccount, PROGRAM_OWNERS}, account_utils::StateMut, @@ -43,6 +44,7 @@ use { pubkey::Pubkey, rent_collector::RentCollector, saturating_add_assign, + timing::duration_as_us, transaction::{self, SanitizedTransaction, TransactionError}, transaction_context::{ExecutionRecord, TransactionContext}, }, @@ -52,6 +54,7 @@ use { fmt::{Debug, Formatter}, rc::Rc, sync::{atomic::Ordering, Arc, RwLock}, + time::Instant, }, }; @@ -200,10 +203,11 @@ impl TransactionBatchProcessor { pub fn load_and_execute_sanitized_transactions<'a, CB: TransactionProcessingCallback>( &self, callbacks: &CB, - sanitized_txs: &[SanitizedTransaction], + sanitized_txs: &[ExtendedSanitizedTransaction], check_results: &mut [TransactionCheckResult], error_counters: &mut TransactionErrorMetrics, recording_config: ExecutionRecordingConfig, + perf_track_metrics: &mut Option<&mut histogram::Histogram>, timings: &mut ExecuteTimings, account_overrides: Option<&AccountOverrides>, builtin_programs: impl Iterator, @@ -279,7 +283,7 @@ impl TransactionBatchProcessor { let result = self.execute_loaded_transaction( callbacks, - tx, + tx.transaction(), loaded_transaction, compute_budget, nonce.as_ref().map(DurableNonceFee::from), @@ -295,6 +299,15 @@ impl TransactionBatchProcessor { programs_modified_by_tx, } = &result { + if let Some(perf_track_metrics) = perf_track_metrics.as_mut() { + if let Some(start_time) = tx.start_time() { + // measure the time from start of banking stage to the execution of the transaction + let duration = Instant::now().duration_since(*start_time); + perf_track_metrics + .increment(duration_as_us(&duration)) + .unwrap(); + } + } // Update batch specific cache of the loaded programs with the modifications // made by the transaction, if it executed successfully. if details.status.is_ok() { @@ -341,7 +354,7 @@ impl TransactionBatchProcessor { /// blockhash or nonce. pub fn filter_executable_program_accounts<'a, CB: TransactionProcessingCallback>( callbacks: &CB, - txs: &[SanitizedTransaction], + txs: &[ExtendedSanitizedTransaction], lock_results: &mut [TransactionCheckResult], program_owners: &'a [Pubkey], ) -> HashMap { @@ -1921,10 +1934,10 @@ mod tests { ); let transactions = vec![ - sanitized_transaction_1.clone(), - sanitized_transaction_2.clone(), - sanitized_transaction_2, - sanitized_transaction_1, + sanitized_transaction_1.clone().into(), + sanitized_transaction_2.clone().into(), + sanitized_transaction_2.into(), + sanitized_transaction_1.into(), ]; let mut lock_results = vec![ (Ok(()), None, Some(25)), diff --git a/svm/tests/transaction_processor.rs b/svm/tests/transaction_processor.rs index 1704054246748d..ff8e543c4d5c5e 100644 --- a/svm/tests/transaction_processor.rs +++ b/svm/tests/transaction_processor.rs @@ -95,7 +95,7 @@ fn test_filter_executable_program_accounts() { let owners = &[program1_pubkey, program2_pubkey]; let programs = TransactionBatchProcessor::::filter_executable_program_accounts( &bank, - &[sanitized_tx1, sanitized_tx2], + &[sanitized_tx1.into(), sanitized_tx2.into()], &mut [(Ok(()), None, Some(0)), (Ok(()), None, Some(0))], owners, ); @@ -189,7 +189,7 @@ fn test_filter_executable_program_accounts_invalid_blockhash() { let mut lock_results = vec![(Ok(()), None, Some(0)), (Ok(()), None, None)]; let programs = TransactionBatchProcessor::::filter_executable_program_accounts( &bank, - &[sanitized_tx1, sanitized_tx2], + &[sanitized_tx1.into(), sanitized_tx2.into()], &mut lock_results, owners, ); diff --git a/transaction-metrics-tracker/Cargo.toml b/transaction-metrics-tracker/Cargo.toml new file mode 100644 index 00000000000000..9bd82702a3ebb4 --- /dev/null +++ b/transaction-metrics-tracker/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "solana-transaction-metrics-tracker" +description = "Solana transaction metrics tracker" +documentation = "https://docs.rs/solana-transaction-metrics-tracker" +version = { workspace = true } +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +publish = false + +[dependencies] +Inflector = { workspace = true } +base64 = { workspace = true } +bincode = { workspace = true } +# Update this borsh dependency to the workspace version once +lazy_static = { workspace = true } +log = { workspace = true } +rand = { workspace = true } +solana-perf = { workspace = true } +solana-sdk = { workspace = true } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/transaction-metrics-tracker/src/lib.rs b/transaction-metrics-tracker/src/lib.rs new file mode 100644 index 00000000000000..2baec195de9b84 --- /dev/null +++ b/transaction-metrics-tracker/src/lib.rs @@ -0,0 +1,157 @@ +use { + lazy_static::lazy_static, + log::*, + rand::Rng, + solana_perf::sigverify::PacketError, + solana_sdk::{packet::Packet, short_vec::decode_shortu16_len, signature::SIGNATURE_BYTES}, +}; + +// The mask is 12 bits long (1<<12 = 4096), it means the probability of matching +// the transaction is 1/4096 assuming the portion being matched is random. +lazy_static! { + static ref TXN_MASK: u16 = rand::thread_rng().gen_range(0..4096); +} + +/// Check if a transaction given its signature matches the randomly selected mask. +/// The signaure should be from the reference of Signature +pub fn should_track_transaction(signature: &[u8; SIGNATURE_BYTES]) -> bool { + // We do not use the highest signature byte as it is not really random + let match_portion: u16 = u16::from_le_bytes([signature[61], signature[62]]) >> 4; + trace!("Matching txn: {match_portion:016b} {:016b}", *TXN_MASK); + *TXN_MASK == match_portion +} + +/// Check if a transaction packet's signature matches the mask. +/// This does a rudimentry verification to make sure the packet at least +/// contains the signature data and it returns the reference to the signature. +pub fn signature_if_should_track_packet( + packet: &Packet, +) -> Result, PacketError> { + let signature = get_signature_from_packet(packet)?; + Ok(should_track_transaction(signature).then_some(signature)) +} + +/// Get the signature of the transaction packet +/// This does a rudimentry verification to make sure the packet at least +/// contains the signature data and it returns the reference to the signature. +pub fn get_signature_from_packet(packet: &Packet) -> Result<&[u8; SIGNATURE_BYTES], PacketError> { + let (sig_len_untrusted, sig_start) = packet + .data(..) + .and_then(|bytes| decode_shortu16_len(bytes).ok()) + .ok_or(PacketError::InvalidShortVec)?; + + if sig_len_untrusted < 1 { + return Err(PacketError::InvalidSignatureLen); + } + + let signature = packet + .data(sig_start..sig_start.saturating_add(SIGNATURE_BYTES)) + .ok_or(PacketError::InvalidSignatureLen)?; + let signature = signature + .try_into() + .map_err(|_| PacketError::InvalidSignatureLen)?; + Ok(signature) +} + +#[cfg(test)] +mod tests { + use { + super::*, + solana_sdk::{ + hash::Hash, + signature::{Keypair, Signature}, + system_transaction, + }, + }; + + #[test] + fn test_get_signature_from_packet() { + // Default invalid txn packet + let packet = Packet::default(); + let sig = get_signature_from_packet(&packet); + assert_eq!(sig, Err(PacketError::InvalidShortVec)); + + // Use a valid transaction, it should succeed + let tx = system_transaction::transfer( + &Keypair::new(), + &solana_sdk::pubkey::new_rand(), + 1, + Hash::new_unique(), + ); + let mut packet = Packet::from_data(None, tx).unwrap(); + + let sig = get_signature_from_packet(&packet); + assert!(sig.is_ok()); + + // Invalid signature length + packet.buffer_mut()[0] = 0x0; + let sig = get_signature_from_packet(&packet); + assert_eq!(sig, Err(PacketError::InvalidSignatureLen)); + } + + #[test] + fn test_should_track_transaction() { + let mut sig = [0x0; SIGNATURE_BYTES]; + let track = should_track_transaction(&sig); + assert!(!track); + + // Intentionally matching the randomly generated mask + // The lower four bits are ignored as only 12 highest bits from + // signature's 61 and 62 u8 are used for matching. + // We generate a random one + let mut rng = rand::thread_rng(); + let random_number: u8 = rng.gen_range(0..=15); + sig[61] = ((*TXN_MASK & 0xf_u16) << 4) as u8 | random_number; + sig[62] = (*TXN_MASK >> 4) as u8; + + let track = should_track_transaction(&sig); + assert!(track); + } + + #[test] + fn test_signature_if_should_track_packet() { + // Default invalid txn packet + let packet = Packet::default(); + let sig = signature_if_should_track_packet(&packet); + assert_eq!(sig, Err(PacketError::InvalidShortVec)); + + // Use a valid transaction which is not matched + let tx = system_transaction::transfer( + &Keypair::new(), + &solana_sdk::pubkey::new_rand(), + 1, + Hash::new_unique(), + ); + let packet = Packet::from_data(None, tx).unwrap(); + let sig = signature_if_should_track_packet(&packet); + assert_eq!(Ok(None), sig); + + // Now simulate a txn matching the signature mask + let mut tx = system_transaction::transfer( + &Keypair::new(), + &solana_sdk::pubkey::new_rand(), + 1, + Hash::new_unique(), + ); + let mut sig = [0x0; SIGNATURE_BYTES]; + sig[61] = ((*TXN_MASK & 0xf_u16) << 4) as u8; + sig[62] = (*TXN_MASK >> 4) as u8; + + let sig = Signature::from(sig); + tx.signatures[0] = sig; + let mut packet = Packet::from_data(None, tx).unwrap(); + let sig2 = signature_if_should_track_packet(&packet); + + match sig2 { + Ok(sig) => { + assert!(sig.is_some()); + } + Err(_) => panic!("Expected to get a matching signature!"), + } + + // Invalid signature length + packet.buffer_mut()[0] = 0x0; + let sig = signature_if_should_track_packet(&packet); + assert_eq!(sig, Err(PacketError::InvalidSignatureLen)); + } +} diff --git a/unified-scheduler-logic/Cargo.toml b/unified-scheduler-logic/Cargo.toml index b2e80c79c7a08f..60d4a74dd1db74 100644 --- a/unified-scheduler-logic/Cargo.toml +++ b/unified-scheduler-logic/Cargo.toml @@ -10,4 +10,5 @@ license = { workspace = true } edition = { workspace = true } [dependencies] +solana-runtime-transaction = { workspace = true } solana-sdk = { workspace = true } diff --git a/unified-scheduler-logic/src/lib.rs b/unified-scheduler-logic/src/lib.rs index 997c6c1745a7c9..e1c9dc64205a84 100644 --- a/unified-scheduler-logic/src/lib.rs +++ b/unified-scheduler-logic/src/lib.rs @@ -1,12 +1,12 @@ -use solana_sdk::transaction::SanitizedTransaction; +use solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction; pub struct Task { - transaction: SanitizedTransaction, + transaction: ExtendedSanitizedTransaction, index: usize, } impl Task { - pub fn create_task(transaction: SanitizedTransaction, index: usize) -> Self { + pub fn create_task(transaction: ExtendedSanitizedTransaction, index: usize) -> Self { Task { transaction, index } } @@ -14,7 +14,7 @@ impl Task { self.index } - pub fn transaction(&self) -> &SanitizedTransaction { + pub fn transaction(&self) -> &ExtendedSanitizedTransaction { &self.transaction } } diff --git a/unified-scheduler-pool/Cargo.toml b/unified-scheduler-pool/Cargo.toml index 7626215b1e1126..b5a6920bb93753 100644 --- a/unified-scheduler-pool/Cargo.toml +++ b/unified-scheduler-pool/Cargo.toml @@ -17,6 +17,7 @@ log = { workspace = true } solana-ledger = { workspace = true } solana-program-runtime = { workspace = true } solana-runtime = { workspace = true } +solana-runtime-transaction = { workspace = true } solana-sdk = { workspace = true } solana-unified-scheduler-logic = { workspace = true } solana-vote = { workspace = true } diff --git a/unified-scheduler-pool/src/lib.rs b/unified-scheduler-pool/src/lib.rs index 09ded82ee88e7d..8821c28257d0f3 100644 --- a/unified-scheduler-pool/src/lib.rs +++ b/unified-scheduler-pool/src/lib.rs @@ -26,7 +26,8 @@ use { }, prioritization_fee_cache::PrioritizationFeeCache, }, - solana_sdk::transaction::{Result, SanitizedTransaction}, + solana_runtime_transaction::extended_transaction::ExtendedSanitizedTransaction, + solana_sdk::transaction::Result, solana_unified_scheduler_logic::Task, solana_vote::vote_sender_types::ReplayVoteSender, std::{ @@ -203,7 +204,7 @@ pub trait TaskHandler: Send + Sync + Debug + Sized + 'static { result: &mut Result<()>, timings: &mut ExecuteTimings, bank: &Arc, - transaction: &SanitizedTransaction, + transaction: &ExtendedSanitizedTransaction, index: usize, handler_context: &HandlerContext, ); @@ -217,7 +218,7 @@ impl TaskHandler for DefaultTaskHandler { result: &mut Result<()>, timings: &mut ExecuteTimings, bank: &Arc, - transaction: &SanitizedTransaction, + transaction: &ExtendedSanitizedTransaction, index: usize, handler_context: &HandlerContext, ) { @@ -740,7 +741,7 @@ impl InstalledScheduler for PooledScheduler { &self.context } - fn schedule_execution(&self, &(transaction, index): &(&SanitizedTransaction, usize)) { + fn schedule_execution(&self, &(transaction, index): &(&ExtendedSanitizedTransaction, usize)) { let task = Task::create_task(transaction.clone(), index); self.inner.thread_manager.send_task(task); } @@ -962,7 +963,8 @@ mod tests { &solana_sdk::pubkey::new_rand(), 2, genesis_config.hash(), - )); + )) + .into(); let bank = Bank::new_for_tests(&genesis_config); let bank = setup_dummy_fork_graph(bank); let ignored_prioritization_fee_cache = Arc::new(PrioritizationFeeCache::new(0u64)); @@ -1003,7 +1005,8 @@ mod tests { &solana_sdk::pubkey::new_rand(), 2, genesis_config.hash(), - )); + )) + .into(); assert_eq!(bank.transaction_count(), 0); scheduler.schedule_execution(&(bad_tx, 0)); // simulate the task-sending thread is stalled for some reason. @@ -1016,7 +1019,8 @@ mod tests { &solana_sdk::pubkey::new_rand(), 3, genesis_config.hash(), - )); + )) + .into(); // make sure this tx is really a good one to execute. assert_matches!( bank.simulate_transaction_unchecked(good_tx_after_bad_tx, false) @@ -1079,7 +1083,10 @@ mod tests { &self.2 } - fn schedule_execution(&self, &(transaction, index): &(&SanitizedTransaction, usize)) { + fn schedule_execution( + &self, + &(transaction, index): &(&ExtendedSanitizedTransaction, usize), + ) { let transaction_and_index = (transaction.clone(), index); let context = self.context().clone(); let pool = self.3.clone(); @@ -1207,7 +1214,7 @@ mod tests { assert_eq!(bank.transaction_count(), 0); // schedule but not immediately execute transaction - bank.schedule_transaction_executions([(&very_old_valid_tx, &0)].into_iter()); + bank.schedule_transaction_executions([(&very_old_valid_tx.into(), &0)].into_iter()); // this calls register_recent_blockhash internally bank.fill_bank_with_ticks_for_tests();