diff --git a/core/src/banking_stage/consume_worker.rs b/core/src/banking_stage/consume_worker.rs index efce866dab6289..c055bc27a9c2c5 100644 --- a/core/src/banking_stage/consume_worker.rs +++ b/core/src/banking_stage/consume_worker.rs @@ -1,6 +1,9 @@ use { super::{ - consumer::{Consumer, ExecuteAndCommitTransactionsOutput, ProcessTransactionBatchOutput}, + consumer::{ + Consumer, ExecuteAndCommitTransactionsOutput, ProcessTransactionBatchOutput, + RetryableIndexKind, + }, leader_slot_timing_metrics::LeaderExecuteAndCommitTimings, scheduler_messages::{ConsumeWork, FinishedConsumeWork}, }, @@ -122,6 +125,7 @@ impl ConsumeWorker { self.metrics.has_data.store(true, Ordering::Relaxed); self.consumed_sender.send(FinishedConsumeWork { + slot: Some(bank.slot()), work, retryable_indexes: output .execute_and_commit_transactions_output @@ -152,7 +156,9 @@ impl ConsumeWorker { /// Send transactions back to scheduler as retryable. fn retry(&self, work: ConsumeWork) -> Result<(), ConsumeWorkerError> { - let retryable_indexes: Vec<_> = (0..work.transactions.len()).collect(); + let retryable_indexes: Vec<_> = (0..work.transactions.len()) + .map(RetryableIndexKind::InvalidBank) + .collect(); let num_retryable = retryable_indexes.len(); self.metrics .count_metrics @@ -164,6 +170,7 @@ impl ConsumeWorker { .fetch_add(num_retryable, Ordering::Relaxed); self.metrics.has_data.store(true, Ordering::Relaxed); self.consumed_sender.send(FinishedConsumeWork { + slot: None, work, retryable_indexes, })?; @@ -907,7 +914,10 @@ mod tests { assert_eq!(consumed.work.batch_id, bid); assert_eq!(consumed.work.ids, vec![id]); assert_eq!(consumed.work.max_ages, vec![max_age]); - assert_eq!(consumed.retryable_indexes, vec![0]); + assert_eq!( + consumed.retryable_indexes, + vec![RetryableIndexKind::InvalidBank(0)] + ); drop(test_frame); let _ = worker_thread.join().unwrap(); @@ -956,7 +966,7 @@ mod tests { assert_eq!(consumed.work.batch_id, bid); assert_eq!(consumed.work.ids, vec![id]); assert_eq!(consumed.work.max_ages, vec![max_age]); - assert_eq!(consumed.retryable_indexes, Vec::::new()); + assert_eq!(consumed.retryable_indexes, Vec::new()); drop(test_frame); let _ = worker_thread.join().unwrap(); @@ -1008,7 +1018,10 @@ mod tests { assert_eq!(consumed.work.batch_id, bid); assert_eq!(consumed.work.ids, vec![id1, id2]); assert_eq!(consumed.work.max_ages, vec![max_age, max_age]); - assert_eq!(consumed.retryable_indexes, vec![1]); // id2 is retryable since lock conflict + assert_eq!( + consumed.retryable_indexes, + vec![RetryableIndexKind::AccountInUse(1)] + ); // id2 is retryable since lock conflict drop(test_frame); let _ = worker_thread.join().unwrap(); @@ -1077,13 +1090,13 @@ mod tests { assert_eq!(consumed.work.batch_id, bid1); assert_eq!(consumed.work.ids, vec![id1]); assert_eq!(consumed.work.max_ages, vec![max_age]); - assert_eq!(consumed.retryable_indexes, Vec::::new()); + assert_eq!(consumed.retryable_indexes, Vec::new()); let consumed = consumed_receiver.recv().unwrap(); assert_eq!(consumed.work.batch_id, bid2); assert_eq!(consumed.work.ids, vec![id2]); assert_eq!(consumed.work.max_ages, vec![max_age]); - assert_eq!(consumed.retryable_indexes, Vec::::new()); + assert_eq!(consumed.retryable_indexes, Vec::new()); drop(test_frame); let _ = worker_thread.join().unwrap(); @@ -1213,7 +1226,7 @@ mod tests { .unwrap(); let consumed = consumed_receiver.recv().unwrap(); - assert_eq!(consumed.retryable_indexes, Vec::::new()); + assert_eq!(consumed.retryable_indexes, Vec::new()); // all but one succeed. 6 for initial funding assert_eq!(bank.transaction_count(), 6 + 5); diff --git a/core/src/banking_stage/consumer.rs b/core/src/banking_stage/consumer.rs index 761b957cc0de70..670f4b036d7af3 100644 --- a/core/src/banking_stage/consumer.rs +++ b/core/src/banking_stage/consumer.rs @@ -34,6 +34,16 @@ use { /// Consumer will create chunks of transactions from buffer with up to this size. pub const TARGET_NUM_TRANSACTIONS_PER_BATCH: usize = 64; +#[cfg_attr(test, derive(Debug, PartialEq, Eq, PartialOrd, Ord))] +pub enum RetryableIndexKind { + /// Retryable index due to account lock failures (Jito) + AccountInUse(usize), + /// Retryable index due to block-limits. + BlockLimits(usize), + /// Retryable index due to recording failure or missing bank i.e. block ended during execution. + InvalidBank(usize), +} + pub struct ProcessTransactionBatchOutput { // The number of transactions filtered out by the cost model pub(crate) cost_model_throttled_transactions_count: u64, @@ -48,7 +58,7 @@ pub struct ExecuteAndCommitTransactionsOutput { pub(crate) transaction_counts: LeaderProcessedTransactionCounts, // Transactions that either were not executed, or were executed and failed to be committed due // to the block ending. - pub(crate) retryable_transaction_indexes: Vec, + pub(crate) retryable_transaction_indexes: Vec, // A result that indicates whether transactions were successfully // committed into the Poh stream. pub commit_transactions_result: Result, PohRecorderError>, @@ -274,23 +284,23 @@ impl Consumer { // following are retryable errors Err(TransactionError::AccountInUse) => { error_counters.account_in_use += 1; - Some(index) + Some(RetryableIndexKind::AccountInUse(index)) } Err(TransactionError::WouldExceedMaxBlockCostLimit) => { error_counters.would_exceed_max_block_cost_limit += 1; - Some(index) + Some(RetryableIndexKind::BlockLimits(index)) } Err(TransactionError::WouldExceedMaxVoteCostLimit) => { error_counters.would_exceed_max_vote_cost_limit += 1; - Some(index) + Some(RetryableIndexKind::BlockLimits(index)) } Err(TransactionError::WouldExceedMaxAccountCostLimit) => { error_counters.would_exceed_max_account_cost_limit += 1; - Some(index) + Some(RetryableIndexKind::BlockLimits(index)) } Err(TransactionError::WouldExceedAccountDataBlockLimit) => { error_counters.would_exceed_account_data_block_limit += 1; - Some(index) + Some(RetryableIndexKind::BlockLimits(index)) } // following are non-retryable errors Err(TransactionError::TooManyAccountLocks) => { @@ -367,7 +377,11 @@ impl Consumer { if let Err(recorder_err) = record_transactions_result { retryable_transaction_indexes.extend(processing_results.iter().enumerate().filter_map( - |(index, processing_result)| processing_result.was_processed().then_some(index), + |(index, processing_result)| { + processing_result + .was_processed() + .then_some(RetryableIndexKind::InvalidBank(index)) + }, )); return ExecuteAndCommitTransactionsOutput { @@ -756,7 +770,10 @@ mod tests { processed_with_successful_result_count: 1, } ); - assert_eq!(retryable_transaction_indexes, vec![0]); + assert_eq!( + retryable_transaction_indexes, + vec![RetryableIndexKind::InvalidBank(0)] + ); assert_matches!( commit_transactions_result, Err(PohRecorderError::MaxHeightReached) @@ -1131,7 +1148,10 @@ mod tests { commit_transactions_result.get(1), Some(CommitTransactionDetails::NotCommitted) ); - assert_eq!(retryable_transaction_indexes, vec![1]); + assert_eq!( + retryable_transaction_indexes, + vec![RetryableIndexKind::AccountInUse(1)] + ); let expected_block_cost = { let (actual_programs_execution_cost, actual_loaded_accounts_data_size_cost) = @@ -1246,7 +1266,10 @@ mod tests { processed_with_successful_result_count: 1, } ); - assert_eq!(retryable_transaction_indexes, vec![1]); + assert_eq!( + retryable_transaction_indexes, + vec![RetryableIndexKind::AccountInUse(1)] + ); assert!(commit_transactions_result.is_ok()); } @@ -1302,7 +1325,9 @@ mod tests { assert_eq!( execute_and_commit_transactions_output.retryable_transaction_indexes, - (1..transactions_len - 1).collect::>() + (1..transactions_len - 1) + .map(RetryableIndexKind::InvalidBank) + .collect::>() ); } @@ -1360,7 +1385,9 @@ mod tests { // Everything except first of the transactions failed and are retryable assert_eq!( execute_and_commit_transactions_output.retryable_transaction_indexes, - (1..transactions_len).collect::>() + (1..transactions_len) + .map(RetryableIndexKind::AccountInUse) + .collect::>() ); } @@ -1447,7 +1474,9 @@ mod tests { execute_and_commit_transactions_output .retryable_transaction_indexes .sort_unstable(); - let expected: Vec = (0..transactions.len()).collect(); + let expected = (0..transactions.len()) + .map(RetryableIndexKind::InvalidBank) + .collect::>(); assert_eq!( execute_and_commit_transactions_output.retryable_transaction_indexes, expected diff --git a/core/src/banking_stage/scheduler_messages.rs b/core/src/banking_stage/scheduler_messages.rs index 1c7cf31592b791..892a08e69d1f61 100644 --- a/core/src/banking_stage/scheduler_messages.rs +++ b/core/src/banking_stage/scheduler_messages.rs @@ -1,4 +1,5 @@ use { + super::consumer::RetryableIndexKind, solana_sdk::clock::{Epoch, Slot}, std::fmt::Display, }; @@ -46,6 +47,8 @@ pub struct ConsumeWork { /// Message: [Worker -> Scheduler] /// Processed transactions. pub struct FinishedConsumeWork { + /// Slot that work was attempted on. `None` if sent back without attempt. + pub slot: Option, pub work: ConsumeWork, - pub retryable_indexes: Vec, + pub retryable_indexes: Vec, } diff --git a/core/src/banking_stage/transaction_scheduler/greedy_scheduler.rs b/core/src/banking_stage/transaction_scheduler/greedy_scheduler.rs index b7de3e35778a2b..3f057b362b37a3 100644 --- a/core/src/banking_stage/transaction_scheduler/greedy_scheduler.rs +++ b/core/src/banking_stage/transaction_scheduler/greedy_scheduler.rs @@ -98,6 +98,7 @@ impl Scheduler for GreedyScheduler { let mut num_sent: usize = 0; let mut num_unschedulable_conflicts: usize = 0; let mut num_unschedulable_threads: usize = 0; + let mut num_skipped_retry: usize = 0; let mut batches = Batches::new(num_threads, self.config.target_transactions_per_batch); while num_scanned < self.config.max_scanned_transactions_per_scheduling_pass @@ -152,6 +153,10 @@ impl Scheduler for GreedyScheduler { num_unschedulable_threads += 1; self.unschedulables.push(id); } + Err(TransactionSchedulingError::Skipped) => { + num_skipped_retry += 1; + self.unschedulables.push(id); + } Ok(TransactionSchedulingInfo { thread_id, transaction, @@ -209,6 +214,7 @@ impl Scheduler for GreedyScheduler { num_scheduled, num_unschedulable_conflicts, num_unschedulable_threads, + num_skipped_retry, num_filtered_out: 0, filter_time_us: 0, }) @@ -228,6 +234,7 @@ fn try_schedule_transaction( ) -> Result, TransactionSchedulingError> { match pre_lock_filter(transaction_state) { PreLockFilterAction::AttemptToSchedule => {} + PreLockFilterAction::SkipAndRetain => return Err(TransactionSchedulingError::Skipped), } // Schedule the transaction if it can be. 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 b74b94a642de92..debbb93a380212 100644 --- a/core/src/banking_stage/transaction_scheduler/prio_graph_scheduler.rs +++ b/core/src/banking_stage/transaction_scheduler/prio_graph_scheduler.rs @@ -197,6 +197,7 @@ impl Scheduler for PrioGraphScheduler { let mut num_sent: usize = 0; let mut num_unschedulable_conflicts: usize = 0; let mut num_unschedulable_threads: usize = 0; + let mut num_skipped_retry: usize = 0; while num_scanned < self.config.max_scanned_transactions_per_scheduling_pass { // If nothing is in the main-queue of the `PrioGraph` then there's nothing left to schedule. if self.prio_graph.is_empty() { @@ -239,6 +240,10 @@ impl Scheduler for PrioGraphScheduler { num_unschedulable_threads += 1; unschedulable_ids.push(id); } + Err(TransactionSchedulingError::Skipped) => { + num_skipped_retry += 1; + unschedulable_ids.push(id); + } Ok(TransactionSchedulingInfo { thread_id, transaction, @@ -331,6 +336,7 @@ impl Scheduler for PrioGraphScheduler { num_scheduled, num_unschedulable_conflicts, num_unschedulable_threads, + num_skipped_retry, num_filtered_out, filter_time_us: total_filter_time_us, }) @@ -370,6 +376,7 @@ fn try_schedule_transaction( ) -> Result, TransactionSchedulingError> { match pre_lock_filter(transaction_state) { PreLockFilterAction::AttemptToSchedule => {} + PreLockFilterAction::SkipAndRetain => return Err(TransactionSchedulingError::Skipped), } // Check if this transaction conflicts with any blocked transactions @@ -694,6 +701,7 @@ mod tests { // Complete batch on thread 0. Remaining txs can be scheduled onto thread 1 finished_work_sender .send(FinishedConsumeWork { + slot: None, work: thread_0_work.into_iter().next().unwrap(), retryable_indexes: vec![], }) diff --git a/core/src/banking_stage/transaction_scheduler/scheduler.rs b/core/src/banking_stage/transaction_scheduler/scheduler.rs index 9d4849684857df..ef3d7aa1e0fb35 100644 --- a/core/src/banking_stage/transaction_scheduler/scheduler.rs +++ b/core/src/banking_stage/transaction_scheduler/scheduler.rs @@ -50,6 +50,8 @@ pub(crate) trait Scheduler { pub(crate) enum PreLockFilterAction { /// Attempt to schedule the transaction. AttemptToSchedule, + /// Skip the transaction but do not drop it. + SkipAndRetain, } /// Metrics from scheduling transactions. @@ -67,6 +69,8 @@ pub(crate) struct SchedulingSummary { pub num_unschedulable_conflicts: usize, /// Number of transactions that were skipped due to thread capacity. pub num_unschedulable_threads: usize, + /// Number of transactions that were skipped due too recently attempted. + pub num_skipped_retry: usize, /// Number of transactions that were dropped due to filter. pub num_filtered_out: usize, /// Time spent filtering transactions diff --git a/core/src/banking_stage/transaction_scheduler/scheduler_common.rs b/core/src/banking_stage/transaction_scheduler/scheduler_common.rs index df6acc233629d2..4b2f3f96fc5958 100644 --- a/core/src/banking_stage/transaction_scheduler/scheduler_common.rs +++ b/core/src/banking_stage/transaction_scheduler/scheduler_common.rs @@ -6,6 +6,7 @@ use { transaction_state_container::StateContainer, }, crate::banking_stage::{ + consumer::RetryableIndexKind, scheduler_messages::{ ConsumeWork, FinishedConsumeWork, MaxAge, TransactionBatchId, TransactionId, }, @@ -74,6 +75,8 @@ pub enum TransactionSchedulingError { UnschedulableConflicts, /// Thread is not allowed to be scheduled on at this time. UnschedulableThread, + /// Transaction was skipped by pre-filter logic. + Skipped, } /// Given the schedulable `thread_set`, select the thread with the least amount @@ -198,6 +201,7 @@ impl SchedulingCommon { max_ages: _, }, retryable_indexes, + slot, }) => { let num_transactions = ids.len(); let num_retryable = retryable_indexes.len(); @@ -209,10 +213,27 @@ impl SchedulingCommon { let mut retryable_iter = retryable_indexes.into_iter().peekable(); for (index, (id, transaction)) in izip!(ids, transactions).enumerate() { if let Some(retryable_index) = retryable_iter.peek() { - if *retryable_index == index { - container.retry_transaction(id, transaction); - retryable_iter.next(); - continue; + match retryable_index { + RetryableIndexKind::AccountInUse(retryable_index) + | RetryableIndexKind::InvalidBank(retryable_index) + if *retryable_index == index => + { + // Immediately retry on: + // - account-locking failures (jito). + // - recording failures (slot ended). + container.retry_transaction(None, id, transaction); + retryable_iter.next(); + continue; + } + RetryableIndexKind::BlockLimits(retryable_index) + if *retryable_index == index => + { + // Do not immediately retry on block-limits failures - want to wait for next slot. + container.retry_transaction(slot, id, transaction); + retryable_iter.next(); + continue; + } + _ => {} // not the current index } } container.remove_by_id(id); diff --git a/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs b/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs index c8c730a879dbce..8818d9d5c7c8e1 100644 --- a/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs +++ b/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs @@ -147,7 +147,13 @@ where MAX_PROCESSING_AGE, ) }, - |_| PreLockFilterAction::AttemptToSchedule // no pre-lock filter for now + |state| { + if state.last_tried_slot() == Some(bank_start.working_bank.slot()) { + PreLockFilterAction::SkipAndRetain + } else { + PreLockFilterAction::AttemptToSchedule + } + } )?); self.count_metrics.update(|count_metrics| { @@ -247,7 +253,7 @@ where while transaction_ids.len() < MAX_TRANSACTION_CHECKS { let Some(id) = self.container.pop() else { - break + break; }; transaction_ids.push(id); } @@ -339,7 +345,7 @@ mod tests { use { super::*, crate::banking_stage::{ - consumer::TARGET_NUM_TRANSACTIONS_PER_BATCH, + consumer::{RetryableIndexKind, TARGET_NUM_TRANSACTIONS_PER_BATCH}, packet_deserializer::PacketDeserializer, scheduler_messages::{ConsumeWork, FinishedConsumeWork, TransactionBatchId}, tests::create_slow_genesis_config, @@ -542,6 +548,7 @@ mod tests { finished_consume_work_sender .send(FinishedConsumeWork { + slot: None, work: ConsumeWork { batch_id: TransactionBatchId::new(0), ids: vec![], @@ -880,8 +887,9 @@ mod tests { // Complete the batch - marking the second transaction as retryable finished_consume_work_sender .send(FinishedConsumeWork { + slot: None, work: consume_work, - retryable_indexes: vec![1], + retryable_indexes: vec![RetryableIndexKind::InvalidBank(1)], }) .unwrap(); @@ -897,4 +905,85 @@ mod tests { .collect_vec(); assert_eq!(message_hashes, vec![&tx1_hash]); } + + #[test_case(test_create_sanitized_transaction_receive_and_buffer, true; "test-case::sdk_immediate_retry")] + #[test_case(test_create_transaction_view_receive_and_buffer, true; "test-case::view_immediate_retry")] + #[test_case(test_create_sanitized_transaction_receive_and_buffer, false; "test-case::sdk_delayed_retry")] + #[test_case(test_create_transaction_view_receive_and_buffer, false; "test-case::view_delayed_retry")] + fn test_schedule_consume_slot_gated_retry( + create_receive_and_buffer: impl FnOnce(BankingPacketReceiver, Arc>) -> R, + immediate_retry: bool, + ) { + let (test_frame, mut scheduler_controller) = + create_test_frame(1, create_receive_and_buffer); + let TestFrame { + bank, + mint_keypair, + poh_recorder, + banking_packet_sender, + consume_work_receivers, + finished_consume_work_sender, + .. + } = &test_frame; + + poh_recorder + .write() + .unwrap() + .set_bank_for_test(bank.clone()); + + // Send packet batch to the scheduler - should do nothing until we become the leader. + let tx1 = create_and_fund_prioritized_transfer( + bank, + mint_keypair, + &Keypair::new(), + &Pubkey::new_unique(), + 1, + 1000, + bank.last_blockhash(), + ); + let tx2 = create_and_fund_prioritized_transfer( + bank, + mint_keypair, + &Keypair::new(), + &Pubkey::new_unique(), + 1, + 2000, + bank.last_blockhash(), + ); + let tx1_hash = tx1.message().hash(); + let tx2_hash = tx2.message().hash(); + + let txs = vec![tx1, tx2]; + banking_packet_sender + .send(to_banking_packet_batch(&txs)) + .unwrap(); + + test_receive_then_schedule(&mut scheduler_controller); + let consume_work = consume_work_receivers[0].try_recv().unwrap(); + assert_eq!(consume_work.ids.len(), 2); + assert_eq!(consume_work.transactions.len(), 2); + let message_hashes = consume_work + .transactions + .iter() + .map(|tx| tx.message_hash()) + .collect_vec(); + assert_eq!(message_hashes, vec![&tx2_hash, &tx1_hash]); + + // Complete the batch - marking the second transaction as retryable + finished_consume_work_sender + .send(FinishedConsumeWork { + slot: Some(bank.slot()), + work: consume_work, + retryable_indexes: vec![if immediate_retry { + RetryableIndexKind::AccountInUse(1) + } else { + RetryableIndexKind::BlockLimits(1) + }], + }) + .unwrap(); + + // Transaction should NOT be rescheduled + test_receive_then_schedule(&mut scheduler_controller); + assert_eq!(consume_work_receivers[0].is_empty(), !immediate_retry); + } } diff --git a/core/src/banking_stage/transaction_scheduler/scheduler_metrics.rs b/core/src/banking_stage/transaction_scheduler/scheduler_metrics.rs index b09ef262a0095d..00adbd83822cc0 100644 --- a/core/src/banking_stage/transaction_scheduler/scheduler_metrics.rs +++ b/core/src/banking_stage/transaction_scheduler/scheduler_metrics.rs @@ -419,6 +419,7 @@ pub struct SchedulingDetails { pub sum_num_scheduled: usize, pub sum_unschedulable_conflicts: usize, pub sum_unschedulable_threads: usize, + pub sum_skipped_retry: usize, } impl Default for SchedulingDetails { @@ -435,6 +436,7 @@ impl Default for SchedulingDetails { sum_num_scheduled: 0, sum_unschedulable_conflicts: 0, sum_unschedulable_threads: 0, + sum_skipped_retry: 0, } } } @@ -458,6 +460,7 @@ impl SchedulingDetails { .max_starting_buffer_size .max(scheduling_summary.starting_buffer_size); self.sum_starting_buffer_size += scheduling_summary.starting_buffer_size; + self.sum_skipped_retry += scheduling_summary.num_skipped_retry; self.sum_num_scheduled += scheduling_summary.num_scheduled; self.sum_unschedulable_conflicts += scheduling_summary.num_unschedulable_conflicts; @@ -502,6 +505,7 @@ impl SchedulingDetails { self.sum_unschedulable_threads, i64 ), + ("num_skipped_retry", self.sum_skipped_retry, i64), ); *self = Self { last_report: now, diff --git a/core/src/banking_stage/transaction_scheduler/transaction_state.rs b/core/src/banking_stage/transaction_scheduler/transaction_state.rs index 7a30fcab28cf10..92267ecf7712cc 100644 --- a/core/src/banking_stage/transaction_scheduler/transaction_state.rs +++ b/core/src/banking_stage/transaction_scheduler/transaction_state.rs @@ -1,6 +1,6 @@ -use crate::banking_stage::scheduler_messages::MaxAge; #[cfg(feature = "dev-context-only-utils")] use qualifier_attr::qualifiers; +use {crate::banking_stage::scheduler_messages::MaxAge, solana_sdk::clock::Slot}; /// TransactionState is used to track the state of a transaction in the transaction scheduler /// and banking stage as a whole. @@ -23,6 +23,8 @@ pub(crate) struct TransactionState { priority: u64, /// Estimated cost of the transaction. cost: u64, + /// Last slot transaction attempted execution on. + last_tried_slot: Option, } impl TransactionState { @@ -33,6 +35,7 @@ impl TransactionState { max_age, priority, cost, + last_tried_slot: None, } } @@ -61,6 +64,16 @@ impl TransactionState { (tx, self.max_age) } + /// Return the last tried slot. + pub(crate) fn last_tried_slot(&self) -> Option { + self.last_tried_slot + } + + /// Set a new last tried slot. + pub(crate) fn set_last_tried_slot(&mut self, slot: Option) { + self.last_tried_slot = slot; + } + /// Intended to be called when a transaction is retried. This method will /// return ownership of the transaction to the state. /// 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 22d6e4b9a2b715..3ddf7ebe93f80d 100644 --- a/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs +++ b/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs @@ -10,7 +10,7 @@ use { solana_runtime_transaction::{ runtime_transaction::RuntimeTransaction, transaction_with_meta::TransactionWithMeta, }, - solana_sdk::packet::PACKET_DATA_SIZE, + solana_sdk::{clock::Slot, packet::PACKET_DATA_SIZE}, std::sync::Arc, }; @@ -71,10 +71,16 @@ pub(crate) trait StateContainer { /// Retries a transaction - inserts transaction back into map. /// This transitions the transaction to `Unprocessed` state. - fn retry_transaction(&mut self, transaction_id: TransactionId, transaction: Tx) { + fn retry_transaction( + &mut self, + tried_slot: Option, + transaction_id: TransactionId, + transaction: Tx, + ) { let transaction_state = self .get_mut_transaction_state(transaction_id) .expect("transaction must exist"); + transaction_state.set_last_tried_slot(tried_slot); let priority_id = TransactionPriorityId::new(transaction_state.priority(), transaction_id); transaction_state.retry_transaction(transaction); self.push_ids_into_queue(std::iter::once(priority_id)); diff --git a/core/src/banking_stage/vote_worker.rs b/core/src/banking_stage/vote_worker.rs index 45c21ded55c5a8..0e78883134e566 100644 --- a/core/src/banking_stage/vote_worker.rs +++ b/core/src/banking_stage/vote_worker.rs @@ -1,6 +1,6 @@ use { super::{ - consumer::Consumer, + consumer::{Consumer, RetryableIndexKind}, decision_maker::{BufferedPacketsDecision, DecisionMaker}, immutable_deserialized_packet::ImmutableDeserializedPacket, leader_slot_metrics::{ @@ -412,7 +412,15 @@ impl VoteWorker { ProcessTransactionsSummary { reached_max_poh_height, transaction_counts: total_transaction_counts, - retryable_transaction_indexes, + // vote thread does not care about reason for retryable transactions + retryable_transaction_indexes: retryable_transaction_indexes + .into_iter() + .map(|index_kind| match index_kind { + RetryableIndexKind::AccountInUse(index) + | RetryableIndexKind::BlockLimits(index) + | RetryableIndexKind::InvalidBank(index) => index, + }) + .collect(), cost_model_throttled_transactions_count, cost_model_us, execute_and_commit_timings,