From dba1d8da83c21b6c7da75b4034cdfe2b6ebb9ef5 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Tue, 26 Nov 2024 15:13:13 -0600 Subject: [PATCH 01/30] impl TransactionData for Bytes --- Cargo.lock | 1 + programs/sbf/Cargo.lock | 1 + svm/examples/Cargo.lock | 1 + transaction-view/Cargo.toml | 1 + transaction-view/src/transaction_data.rs | 7 +++++++ 5 files changed, 11 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index de03e70a2cd0f4..cae6042d80e6be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,6 +259,7 @@ version = "2.2.0" dependencies = [ "agave-transaction-view", "bincode", + "bytes", "criterion", "solana-hash", "solana-instruction", diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index 4f6eb8f1c6fd5f..8ab20194f26ab2 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -87,6 +87,7 @@ dependencies = [ name = "agave-transaction-view" version = "2.2.0" dependencies = [ + "bytes", "solana-hash", "solana-message", "solana-packet", diff --git a/svm/examples/Cargo.lock b/svm/examples/Cargo.lock index adf74f49a027c3..144284b73b81c3 100644 --- a/svm/examples/Cargo.lock +++ b/svm/examples/Cargo.lock @@ -87,6 +87,7 @@ dependencies = [ name = "agave-transaction-view" version = "2.2.0" dependencies = [ + "bytes", "solana-hash", "solana-message", "solana-packet", diff --git a/transaction-view/Cargo.toml b/transaction-view/Cargo.toml index 575e2022139d50..a95dc5bd44a6cb 100644 --- a/transaction-view/Cargo.toml +++ b/transaction-view/Cargo.toml @@ -10,6 +10,7 @@ license = { workspace = true } edition = { workspace = true } [dependencies] +bytes = { workspace = true } solana-hash = { workspace = true } solana-message = { workspace = true } solana-packet = { workspace = true } diff --git a/transaction-view/src/transaction_data.rs b/transaction-view/src/transaction_data.rs index 2bfe0c85ce0e55..d6152c8069d3e8 100644 --- a/transaction-view/src/transaction_data.rs +++ b/transaction-view/src/transaction_data.rs @@ -10,3 +10,10 @@ impl TransactionData for &[u8] { self } } + +impl TransactionData for bytes::Bytes { + #[inline] + fn data(&self) -> &[u8] { + self.as_ref() + } +} From 2df54afef5e02151e361b9962a88113f1a6d8e6d Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Tue, 26 Nov 2024 15:36:21 -0600 Subject: [PATCH 02/30] Optional packet --- .../prio_graph_scheduler.rs | 2 +- .../receive_and_buffer.rs | 2 +- .../scheduler_controller.rs | 4 ++-- .../transaction_state.rs | 23 +++++++++++-------- .../transaction_state_container.rs | 6 ++--- 5 files changed, 21 insertions(+), 16 deletions(-) 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 8edebc1f80c200..5e401f15942fb6 100644 --- a/core/src/banking_stage/transaction_scheduler/prio_graph_scheduler.rs +++ b/core/src/banking_stage/transaction_scheduler/prio_graph_scheduler.rs @@ -721,7 +721,7 @@ mod tests { const TEST_TRANSACTION_COST: u64 = 5000; container.insert_new_transaction( transaction_ttl, - packet, + Some(packet), compute_unit_price, TEST_TRANSACTION_COST, ); diff --git a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs index 21afe448d151d3..8925611dd2961f 100644 --- a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs +++ b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs @@ -242,7 +242,7 @@ impl SanitizedTransactionReceiveAndBuffer { max_age, }; - if container.insert_new_transaction(transaction_ttl, packet, priority, cost) { + if container.insert_new_transaction(transaction_ttl, Some(packet), priority, cost) { saturating_add_assign!(num_dropped_on_capacity, 1); } saturating_add_assign!(num_buffered, 1); diff --git a/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs b/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs index 8cd38b81ab3e67..5061bf843f42f5 100644 --- a/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs +++ b/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs @@ -282,13 +282,13 @@ impl SchedulerController { ids_to_add_back.push(*id); // add back to the queue at end let state = self.container.get_mut_transaction_state(id.id).unwrap(); let sanitized_transaction = &state.transaction_ttl().transaction; - let immutable_packet = state.packet().clone(); + let immutable_packet = state.packet().expect("forwarding requires packet"); // If not already forwarded and can be forwarded, add to forwardable packets. if state.should_forward() && forwarder.try_add_packet( sanitized_transaction, - immutable_packet, + immutable_packet.clone(), feature_set, ) { diff --git a/core/src/banking_stage/transaction_scheduler/transaction_state.rs b/core/src/banking_stage/transaction_scheduler/transaction_state.rs index ecea0a71847670..1511463ecb213f 100644 --- a/core/src/banking_stage/transaction_scheduler/transaction_state.rs +++ b/core/src/banking_stage/transaction_scheduler/transaction_state.rs @@ -35,14 +35,14 @@ pub(crate) enum TransactionState { /// The transaction is available for scheduling. Unprocessed { transaction_ttl: SanitizedTransactionTTL, - packet: Arc, + packet: Option>, priority: u64, cost: u64, should_forward: bool, }, /// The transaction is currently scheduled or being processed. Pending { - packet: Arc, + packet: Option>, priority: u64, cost: u64, should_forward: bool, @@ -55,12 +55,17 @@ impl TransactionState { /// Creates a new `TransactionState` in the `Unprocessed` state. pub(crate) fn new( transaction_ttl: SanitizedTransactionTTL, - packet: Arc, + packet: Option>, priority: u64, cost: u64, ) -> Self { - let should_forward = !packet.original_packet().meta().forwarded() - && packet.original_packet().meta().is_from_staked_node(); + let should_forward = !packet + .as_ref() + .map(|packet| { + packet.original_packet().meta().forwarded() + && packet.original_packet().meta().is_from_staked_node() + }) + .unwrap_or_default(); Self::Unprocessed { transaction_ttl, packet, @@ -116,10 +121,10 @@ impl TransactionState { } /// Return the packet of the transaction. - pub(crate) fn packet(&self) -> &Arc { + pub(crate) fn packet(&self) -> Option<&Arc> { match self { - Self::Unprocessed { packet, .. } => packet, - Self::Pending { packet, .. } => packet, + Self::Unprocessed { packet, .. } => packet.as_ref(), + Self::Pending { packet, .. } => packet.as_ref(), Self::Transitioning => unreachable!(), } } @@ -244,7 +249,7 @@ mod tests { const TEST_TRANSACTION_COST: u64 = 5000; TransactionState::new( transaction_ttl, - packet, + Some(packet), compute_unit_price, TEST_TRANSACTION_COST, ) 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 ada23d24219e09..970e9fac5f6de3 100644 --- a/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs +++ b/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs @@ -70,7 +70,7 @@ pub(crate) trait StateContainer { fn insert_new_transaction( &mut self, transaction_ttl: SanitizedTransactionTTL, - packet: Arc, + packet: Option>, priority: u64, cost: u64, ) -> bool; @@ -135,7 +135,7 @@ impl StateContainer for TransactionStateContainer, - packet: Arc, + packet: Option>, priority: u64, cost: u64, ) -> bool { @@ -264,7 +264,7 @@ mod tests { ) { for priority in 0..num as u64 { let (transaction_ttl, packet, priority, cost) = test_transaction(priority); - container.insert_new_transaction(transaction_ttl, packet, priority, cost); + container.insert_new_transaction(transaction_ttl, Some(packet), priority, cost); } } From 6043f4dea6a65e2083b0a7acb3c23bb04ae3aa5d Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Wed, 27 Nov 2024 08:28:16 -0600 Subject: [PATCH 03/30] TransactionViewStateContainer --- Cargo.lock | 1 + core/Cargo.toml | 1 + .../transaction_state_container.rs | 232 +++++++++++++++--- programs/sbf/Cargo.lock | 1 + svm/examples/Cargo.lock | 1 + svm/examples/Cargo.toml | 6 +- 6 files changed, 199 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cae6042d80e6be..16cf780ce17255 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6840,6 +6840,7 @@ name = "solana-core" version = "2.2.0" dependencies = [ "agave-banking-stage-ingress-types", + "agave-transaction-view", "ahash 0.8.11", "anyhow", "arrayvec", diff --git a/core/Cargo.toml b/core/Cargo.toml index 3425e63fd8fe1b..0c23cfa9108ed4 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -15,6 +15,7 @@ codecov = { repository = "solana-labs/solana", branch = "master", service = "git [dependencies] agave-banking-stage-ingress-types = { workspace = true } +agave-transaction-view = { workspace = true } ahash = { workspace = true } anyhow = { workspace = true } arrayvec = { workspace = true } 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 970e9fac5f6de3..ce014d32c01907 100644 --- a/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs +++ b/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs @@ -7,10 +7,15 @@ use { immutable_deserialized_packet::ImmutableDeserializedPacket, scheduler_messages::TransactionId, }, + agave_transaction_view::resolved_transaction_view::ResolvedTransactionView, + bytes::{Bytes, BytesMut}, itertools::MinMaxResult, min_max_heap::MinMaxHeap, slab::Slab, - solana_runtime_transaction::transaction_with_meta::TransactionWithMeta, + solana_runtime_transaction::{ + runtime_transaction::RuntimeTransaction, transaction_with_meta::TransactionWithMeta, + }, + solana_sdk::packet::PACKET_DATA_SIZE, std::sync::Arc, }; @@ -65,23 +70,20 @@ pub(crate) trait StateContainer { /// Panics if the transaction does not exist. fn get_transaction_ttl(&self, id: TransactionId) -> Option<&SanitizedTransactionTTL>; - /// Insert a new transaction into the container's queues and maps. - /// Returns `true` if a packet was dropped due to capacity limits. - fn insert_new_transaction( - &mut self, - transaction_ttl: SanitizedTransactionTTL, - packet: Option>, - priority: u64, - cost: u64, - ) -> bool; - /// Retries a transaction - inserts transaction back into map (but not packet). /// This transitions the transaction to `Unprocessed` state. fn retry_transaction( &mut self, transaction_id: TransactionId, transaction_ttl: SanitizedTransactionTTL, - ); + ) { + let transaction_state = self + .get_mut_transaction_state(transaction_id) + .expect("transaction must exist"); + let priority_id = TransactionPriorityId::new(transaction_state.priority(), transaction_id); + transaction_state.transition_to_unprocessed(transaction_ttl); + self.push_id_into_queue(priority_id); + } /// Pushes a transaction id into the priority queue. If the queue is full, the lowest priority /// transaction will be dropped (removed from the queue and map). @@ -132,7 +134,29 @@ impl StateContainer for TransactionStateContainer bool { + self.push_id_into_queue_with_remaining_capacity(priority_id, self.remaining_capacity()) + } + + fn remove_by_id(&mut self, id: TransactionId) { + self.id_to_transaction_state.remove(id); + } + + fn get_min_max_priority(&self) -> MinMaxResult { + match self.priority_queue.peek_min() { + Some(min) => match self.priority_queue.peek_max() { + Some(max) => MinMaxResult::MinMax(min.priority, max.priority), + None => MinMaxResult::OneElement(min.priority), + }, + None => MinMaxResult::NoElements, + } + } +} + +impl TransactionStateContainer { + /// Insert a new transaction into the container's queues and maps. + /// Returns `true` if a packet was dropped due to capacity limits. + pub(crate) fn insert_new_transaction( &mut self, transaction_ttl: SanitizedTransactionTTL, packet: Option>, @@ -157,55 +181,187 @@ impl StateContainer for TransactionStateContainer, - ) { - let transaction_state = self - .get_mut_transaction_state(transaction_id) - .expect("transaction must exist"); - let priority_id = TransactionPriorityId::new(transaction_state.priority(), transaction_id); - transaction_state.transition_to_unprocessed(transaction_ttl); - self.push_id_into_queue(priority_id); + priority_id: TransactionPriorityId, + remaining_capacity: usize, + ) -> bool { + if remaining_capacity == 0 { + let popped_id = self.priority_queue.push_pop_min(priority_id); + self.remove_by_id(popped_id.id); + true + } else { + self.priority_queue.push(priority_id); + false + } + } +} + +/// A wrapper around `TransactionStateContainer` that allows re-uses +/// pre-allocated `Bytes` to copy packet data into and use for serialization. +/// This is used to avoid allocations in parsing transactions. +pub struct TransactionViewStateContainer { + inner: TransactionStateContainer>>, + bytes_buffer: Box<[MaybeBytes]>, +} + +enum MaybeBytes { + None, + Bytes(Bytes), + BytesMut(BytesMut), +} + +impl MaybeBytes { + fn reserve_space(&mut self) -> BytesMut { + match core::mem::replace(self, MaybeBytes::None) { + MaybeBytes::BytesMut(bytes) => bytes, + _ => unreachable!("invalid state"), + } } - fn push_id_into_queue(&mut self, priority_id: TransactionPriorityId) -> bool { - self.push_id_into_queue_with_remaining_capacity(priority_id, self.remaining_capacity()) + fn freeze(&mut self, bytes: Bytes) { + debug_assert!(matches!(self, MaybeBytes::None)); + *self = MaybeBytes::Bytes(bytes); } - fn remove_by_id(&mut self, id: TransactionId) { - self.id_to_transaction_state.remove(id); + fn free_space(&mut self, mut bytes: BytesMut) { + debug_assert!(matches!(self, MaybeBytes::None)); + bytes.clear(); + *self = MaybeBytes::BytesMut(bytes); } +} - fn get_min_max_priority(&self) -> MinMaxResult { - match self.priority_queue.peek_min() { - Some(min) => match self.priority_queue.peek_max() { - Some(max) => MinMaxResult::MinMax(min.priority, max.priority), - None => MinMaxResult::OneElement(min.priority), - }, - None => MinMaxResult::NoElements, +struct SuccessfulInsert { + state: TransactionState>>, + bytes: Bytes, +} + +impl TransactionViewStateContainer { + fn try_insert_with(&mut self, f: impl FnOnce(BytesMut) -> Result) { + if self.inner.id_to_transaction_state.len() == self.inner.id_to_transaction_state.capacity() + { + return; + } + + // Get a vacant entry in the slab. + let vacant_entry = self.inner.id_to_transaction_state.vacant_entry(); + let transaction_id = vacant_entry.key(); + + // Get the vacant space in the bytes buffer. + let bytes_entry = &mut self.bytes_buffer[transaction_id]; + let bytes = bytes_entry.reserve_space(); + + // Attempt to insert the transaction, storing the frozen bytes back into bytes buffer. + match f(bytes) { + Ok(SuccessfulInsert { state, bytes }) => { + vacant_entry.insert(state); + bytes_entry.freeze(bytes); + } + Err(bytes) => bytes_entry.free_space(bytes), } } -} -impl TransactionStateContainer { + // This is re-implemented since we need it to call `remove_by_id` on this + // struct rather than `inner`. This is important because we need to return + // the `Bytes` to the pool. fn push_id_into_queue_with_remaining_capacity( &mut self, priority_id: TransactionPriorityId, remaining_capacity: usize, ) -> bool { if remaining_capacity == 0 { - let popped_id = self.priority_queue.push_pop_min(priority_id); + let popped_id = self.inner.priority_queue.push_pop_min(priority_id); self.remove_by_id(popped_id.id); true } else { - self.priority_queue.push(priority_id); + self.inner.priority_queue.push(priority_id); false } } } +impl StateContainer>> + for TransactionViewStateContainer +{ + fn with_capacity(capacity: usize) -> Self { + let inner = TransactionStateContainer::with_capacity(capacity); + let bytes_buffer = (0..inner.id_to_transaction_state.capacity()) + .map(|_| { + MaybeBytes::BytesMut({ + let mut bytes = BytesMut::zeroed(PACKET_DATA_SIZE); + bytes.clear(); + bytes + }) + }) + .collect::>() + .into_boxed_slice(); + Self { + inner, + bytes_buffer, + } + } + + #[inline] + fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + #[inline] + fn remaining_capacity(&self) -> usize { + self.inner.remaining_capacity() + } + + #[inline] + fn pop(&mut self) -> Option { + self.inner.pop() + } + + #[inline] + fn get_mut_transaction_state( + &mut self, + id: TransactionId, + ) -> Option<&mut TransactionState>>> { + self.inner.get_mut_transaction_state(id) + } + + #[inline] + fn get_transaction_ttl( + &self, + id: TransactionId, + ) -> Option<&SanitizedTransactionTTL>>> { + self.inner.get_transaction_ttl(id) + } + + #[inline] + fn push_id_into_queue(&mut self, priority_id: TransactionPriorityId) -> bool { + self.push_id_into_queue_with_remaining_capacity(priority_id, self.remaining_capacity()) + } + + fn remove_by_id(&mut self, id: TransactionId) { + // Remove the entry from the map: + // 1. If it was unprocessed, this will drop the `Bytes` held. + // 2. If it was scheduled, this just marks the entry as removed. + let _ = self.inner.id_to_transaction_state.remove(id); + + // Clear the bytes buffer. + let bytes_entry = &mut self.bytes_buffer[id]; + let MaybeBytes::Bytes(bytes) = core::mem::replace(bytes_entry, MaybeBytes::None) else { + unreachable!("invalid state"); + }; + + // Return the `Bytes` to the pool. + let bytes = bytes + .try_into_mut() + .expect("all `Bytes` instances must be dropped"); + bytes_entry.free_space(bytes); + } + + #[inline] + fn get_min_max_priority(&self) -> MinMaxResult { + self.inner.get_min_max_priority() + } +} + #[cfg(test)] mod tests { use { diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index 8ab20194f26ab2..66b293cc058cb8 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -5487,6 +5487,7 @@ name = "solana-core" version = "2.2.0" dependencies = [ "agave-banking-stage-ingress-types", + "agave-transaction-view", "ahash 0.8.11", "anyhow", "arrayvec", diff --git a/svm/examples/Cargo.lock b/svm/examples/Cargo.lock index 144284b73b81c3..a8f62e764e303e 100644 --- a/svm/examples/Cargo.lock +++ b/svm/examples/Cargo.lock @@ -5338,6 +5338,7 @@ name = "solana-core" version = "2.2.0" dependencies = [ "agave-banking-stage-ingress-types", + "agave-transaction-view", "ahash 0.8.11", "anyhow", "arrayvec", diff --git a/svm/examples/Cargo.toml b/svm/examples/Cargo.toml index a5df7288e95576..5015d183ba2d13 100644 --- a/svm/examples/Cargo.toml +++ b/svm/examples/Cargo.toml @@ -1,9 +1,5 @@ [workspace] -members = [ - "json-rpc/client", - "json-rpc/server", - "paytube", -] +members = ["json-rpc/client", "json-rpc/server", "paytube"] resolver = "2" From 5ae70195daddb421a9d4b9387c1365904f0f0951 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Wed, 27 Nov 2024 09:20:13 -0600 Subject: [PATCH 04/30] TransactionViewReceiveAndBuffer --- core/src/banking_stage/packet_filter.rs | 3 +- .../receive_and_buffer.rs | 263 +++++++++++++++++- .../transaction_state_container.rs | 26 +- runtime/src/bank/fee_distribution.rs | 4 +- 4 files changed, 276 insertions(+), 20 deletions(-) diff --git a/core/src/banking_stage/packet_filter.rs b/core/src/banking_stage/packet_filter.rs index b9176c9b8ac91d..d4e68f590d8045 100644 --- a/core/src/banking_stage/packet_filter.rs +++ b/core/src/banking_stage/packet_filter.rs @@ -9,6 +9,8 @@ use { thiserror::Error, }; +pub const MAX_ALLOWED_PRECOMPILE_SIGNATURES: u64 = 8; + lazy_static! { // To calculate the static_builtin_cost_sum conservatively, an all-enabled dummy feature_set // is used. It lowers required minimal compute_unit_limit, aligns with future versions. @@ -58,7 +60,6 @@ impl ImmutableDeserializedPacket { } } - const MAX_ALLOWED_PRECOMPILE_SIGNATURES: u64 = 8; if num_precompile_signatures <= MAX_ALLOWED_PRECOMPILE_SIGNATURES { Ok(()) } else { diff --git a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs index 8925611dd2961f..1b6c672de705d8 100644 --- a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs +++ b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs @@ -1,18 +1,30 @@ use { super::{ scheduler_metrics::{SchedulerCountMetrics, SchedulerTimingMetrics}, - transaction_state_container::StateContainer, + transaction_state::TransactionState, + transaction_state_container::{ + StateContainer, SuccessfulInsert, TransactionViewStateContainer, + }, }, - crate::banking_stage::{ - decision_maker::BufferedPacketsDecision, - immutable_deserialized_packet::ImmutableDeserializedPacket, - packet_deserializer::PacketDeserializer, scheduler_messages::MaxAge, - transaction_scheduler::transaction_state::SanitizedTransactionTTL, - TransactionStateContainer, + crate::{ + banking_stage::{ + decision_maker::BufferedPacketsDecision, + immutable_deserialized_packet::ImmutableDeserializedPacket, + packet_deserializer::PacketDeserializer, + packet_filter::MAX_ALLOWED_PRECOMPILE_SIGNATURES, scheduler_messages::MaxAge, + transaction_scheduler::transaction_state::SanitizedTransactionTTL, + TransactionStateContainer, + }, + banking_trace::{BankingPacketBatch, BankingPacketReceiver}, + }, + agave_transaction_view::{ + resolved_transaction_view::ResolvedTransactionView, + transaction_version::TransactionVersion, transaction_view::SanitizedTransactionView, }, arrayvec::ArrayVec, + bytes::Bytes, core::time::Duration, - crossbeam_channel::RecvTimeoutError, + crossbeam_channel::{RecvTimeoutError, TryRecvError}, solana_accounts_db::account_locks::validate_account_locks, solana_cost_model::cost_model::CostModel, solana_measure::measure_us, @@ -26,10 +38,14 @@ use { clock::{Epoch, Slot, MAX_PROCESSING_AGE}, fee::FeeBudgetLimits, saturating_add_assign, - transaction::SanitizedTransaction, + transaction::{MessageHash, SanitizedTransaction}, }, solana_svm::transaction_error_metrics::TransactionErrorMetrics, - std::sync::{Arc, RwLock}, + solana_svm_transaction::svm_message::SVMMessage, + std::{ + sync::{Arc, RwLock}, + time::Instant, + }, }; pub(crate) trait ReceiveAndBuffer { @@ -278,6 +294,231 @@ impl SanitizedTransactionReceiveAndBuffer { } } +pub(crate) struct TransactionViewRecieveAndBuffer { + pub receiver: BankingPacketReceiver, + pub bank_forks: Arc>, +} + +impl ReceiveAndBuffer for TransactionViewRecieveAndBuffer { + type Transaction = RuntimeTransaction>; + type Container = TransactionViewStateContainer; + + fn receive_and_buffer_packets( + &mut self, + container: &mut Self::Container, + timing_metrics: &mut SchedulerTimingMetrics, + count_metrics: &mut SchedulerCountMetrics, + decision: &BufferedPacketsDecision, + ) -> bool { + // Receive packet batches. + const TIMEOUT: Duration = Duration::from_millis(10); + let start = Instant::now(); + + // If not leader, do a blocking-receive initially. This lets the thread + // sleep when there is not work to do. + // TODO: Is it better to manually sleep instead, avoiding the locking + // overhead for wakers? But then risk not waking up when message + // received - as long as sleep is somewhat short, this should be + // fine. + if matches!( + decision, + BufferedPacketsDecision::Forward + | BufferedPacketsDecision::ForwardAndHold + | BufferedPacketsDecision::Hold + ) { + match self.receiver.recv_timeout(TIMEOUT) { + Ok(packet_batch_message) => { + self.handle_packet_batch_message( + container, + timing_metrics, + count_metrics, + decision, + packet_batch_message, + ); + } + Err(RecvTimeoutError::Timeout) => return true, + Err(RecvTimeoutError::Disconnected) => return false, + } + } + + while start.elapsed() < TIMEOUT { + match self.receiver.try_recv() { + Ok(packet_batch_message) => { + self.handle_packet_batch_message( + container, + timing_metrics, + count_metrics, + decision, + packet_batch_message, + ); + } + Err(TryRecvError::Empty) => return true, + Err(TryRecvError::Disconnected) => return false, + } + } + + true + } +} + +impl TransactionViewRecieveAndBuffer { + fn handle_packet_batch_message( + &mut self, + container: &mut TransactionViewStateContainer, + timing_metrics: &mut SchedulerTimingMetrics, + count_metrics: &mut SchedulerCountMetrics, + decision: &BufferedPacketsDecision, + packet_batch_message: BankingPacketBatch, + ) { + // Do not support forwarding - only add support for this if we really need it. + if matches!(decision, BufferedPacketsDecision::Forward) { + return; + } + + let start = Instant::now(); + // Sanitize packets, generate IDs, and insert into the container. + let (root_bank, working_bank) = { + let bank_forks = self.bank_forks.read().unwrap(); + let root_bank = bank_forks.root_bank(); + let working_bank = bank_forks.working_bank(); + (root_bank, working_bank) + }; + let alt_resolved_slot = root_bank.slot(); + let sanitized_epoch = root_bank.epoch(); + let transaction_account_lock_limit = working_bank.get_transaction_account_lock_limit(); + + let mut num_received = 0usize; + let mut num_buffered = 0usize; + let mut num_dropped_on_capacity = 0usize; + let mut num_dropped_on_receive = 0usize; + for packet_batch in packet_batch_message.0.iter() { + for packet in packet_batch.iter() { + let Some(packet_data) = packet.data(..) else { + continue; + }; + + num_received += 1; + + // Reserve free-space to copy packet into, run sanitization checks, and insert. + if container.try_insert_with(|mut bytes| { + // Copy packet data into the buffer, and freeze. + bytes.extend_from_slice(packet_data); + let bytes = bytes.freeze(); + + match Self::try_handle_packet( + bytes.clone(), + &root_bank, + &working_bank, + alt_resolved_slot, + sanitized_epoch, + transaction_account_lock_limit, + ) { + Ok(state) => { + num_buffered += 1; + Ok(SuccessfulInsert { state, bytes }) + } + Err(()) => { + num_dropped_on_receive += 1; + Err(bytes.try_into_mut().expect("no leaks")) + } + } + }) { + num_dropped_on_capacity += 1; + }; + } + } + + let buffer_time_us = start.elapsed().as_micros() as u64; + timing_metrics.update(|timing_metrics| { + saturating_add_assign!(timing_metrics.buffer_time_us, buffer_time_us); + }); + count_metrics.update(|count_metrics| { + saturating_add_assign!(count_metrics.num_received, num_received); + saturating_add_assign!(count_metrics.num_buffered, num_buffered); + saturating_add_assign!( + count_metrics.num_dropped_on_capacity, + num_dropped_on_capacity + ); + saturating_add_assign!(count_metrics.num_dropped_on_receive, num_dropped_on_receive); + }); + } + + fn try_handle_packet( + bytes: Bytes, + root_bank: &Bank, + working_bank: &Bank, + alt_resolved_slot: Slot, + sanitized_epoch: Epoch, + transaction_account_lock_limit: usize, + ) -> Result>>, ()> { + // Parsing and basic sanitization checks + let Ok(view) = SanitizedTransactionView::try_new_sanitized(bytes.clone()) else { + return Err(()); + }; + + let Ok(view) = RuntimeTransaction::>::try_from( + view, + MessageHash::Compute, + None, + ) else { + return Err(()); + }; + + // Check excessive pre-compiles. + let signature_detials = view.signature_details(); + let num_precompiles = signature_detials.num_ed25519_instruction_signatures() + + signature_detials.num_secp256k1_instruction_signatures(); + if num_precompiles > MAX_ALLOWED_PRECOMPILE_SIGNATURES { + return Err(()); + } + + // Load addresses for transaction. + let load_addresses_result = match view.version() { + TransactionVersion::Legacy => Ok((None, u64::MAX)), + TransactionVersion::V0 => root_bank + .load_addresses_from_ref(view.address_table_lookup_iter()) + .map(|(loaded_addresses, deactivation_slot)| { + (Some(loaded_addresses), deactivation_slot) + }), + }; + let Ok((loaded_addresses, deactivation_slot)) = load_addresses_result else { + return Err(()); + }; + + let Ok(view) = RuntimeTransaction::>::try_from( + view, + loaded_addresses, + root_bank.get_reserved_account_keys(), + ) else { + return Err(()); + }; + + if validate_account_locks(view.account_keys(), transaction_account_lock_limit).is_err() { + return Err(()); + } + + let Ok(compute_budget_limits) = view.compute_budget_limits(&working_bank.feature_set) + else { + return Err(()); + }; + + let max_age = calculate_max_age(sanitized_epoch, deactivation_slot, alt_resolved_slot); + let fee_budget_limits = FeeBudgetLimits::from(compute_budget_limits); + let (priority, cost) = + calculate_priority_and_cost(&view, &fee_budget_limits, &working_bank); + + Ok(TransactionState::new( + SanitizedTransactionTTL { + transaction: view, + max_age, + }, + None, + priority, + cost, + )) + } +} + /// Calculate priority and cost for a transaction: /// /// Cost is calculated through the `CostModel`, @@ -297,7 +538,7 @@ impl SanitizedTransactionReceiveAndBuffer { /// Any difference in the prioritization is negligible for /// the current transaction costs. fn calculate_priority_and_cost( - transaction: &RuntimeTransaction, + transaction: &impl TransactionWithMeta, fee_budget_limits: &FeeBudgetLimits, bank: &Bank, ) -> (u64, u64) { 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 ce014d32c01907..2d2113b22fbd05 100644 --- a/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs +++ b/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs @@ -231,17 +231,23 @@ impl MaybeBytes { } } -struct SuccessfulInsert { - state: TransactionState>>, - bytes: Bytes, +pub(crate) struct SuccessfulInsert { + pub state: TransactionState>>, + pub bytes: Bytes, } impl TransactionViewStateContainer { - fn try_insert_with(&mut self, f: impl FnOnce(BytesMut) -> Result) { + /// Returns true if packet was dropped due to capacity limits. + pub(crate) fn try_insert_with( + &mut self, + f: impl FnOnce(BytesMut) -> Result, + ) -> bool { if self.inner.id_to_transaction_state.len() == self.inner.id_to_transaction_state.capacity() { - return; + return true; } + // Get remaining capacity before inserting. + let remaining_capacity = self.remaining_capacity(); // Get a vacant entry in the slab. let vacant_entry = self.inner.id_to_transaction_state.vacant_entry(); @@ -254,16 +260,24 @@ impl TransactionViewStateContainer { // Attempt to insert the transaction, storing the frozen bytes back into bytes buffer. match f(bytes) { Ok(SuccessfulInsert { state, bytes }) => { + let priority_id = TransactionPriorityId::new(state.priority(), transaction_id); vacant_entry.insert(state); bytes_entry.freeze(bytes); + + // Push the transaction into the queue. + self.push_id_into_queue_with_remaining_capacity(priority_id, remaining_capacity) + } + Err(bytes) => { + bytes_entry.free_space(bytes); + false } - Err(bytes) => bytes_entry.free_space(bytes), } } // This is re-implemented since we need it to call `remove_by_id` on this // struct rather than `inner`. This is important because we need to return // the `Bytes` to the pool. + /// Returns true if packet was dropped due to capacity limits. fn push_id_into_queue_with_remaining_capacity( &mut self, priority_id: TransactionPriorityId, diff --git a/runtime/src/bank/fee_distribution.rs b/runtime/src/bank/fee_distribution.rs index e0be18d5e609fc..e90444053df51e 100644 --- a/runtime/src/bank/fee_distribution.rs +++ b/runtime/src/bank/fee_distribution.rs @@ -3,6 +3,7 @@ use { crate::bank::CollectorFeeDetails, log::{debug, warn}, solana_feature_set::{remove_rounding_in_fee_calculation, reward_full_priority_fee}, + solana_runtime_transaction::transaction_with_meta::TransactionWithMeta, solana_sdk::{ account::{ReadableAccount, WritableAccount}, fee::FeeBudgetLimits, @@ -10,7 +11,6 @@ use { reward_info::RewardInfo, reward_type::RewardType, system_program, - transaction::SanitizedTransaction, }, solana_svm_rent_collector::svm_rent_collector::SVMRentCollector, solana_vote::vote_account::VoteAccountsHashMap, @@ -73,7 +73,7 @@ impl Bank { pub fn calculate_reward_for_transaction( &self, - transaction: &SanitizedTransaction, + transaction: &impl TransactionWithMeta, fee_budget_limits: &FeeBudgetLimits, ) -> u64 { let fee_details = solana_fee::calculate_fee_details( From ea485dfebc44574611a33ef479dc70aa74e6ff04 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Wed, 27 Nov 2024 09:31:37 -0600 Subject: [PATCH 05/30] spawn_scheduler_and_workers --- core/src/banking_stage.rs | 132 ++++++++++++++---- .../receive_and_buffer.rs | 37 +++-- core/src/validator.rs | 25 ++++ 3 files changed, 144 insertions(+), 50 deletions(-) diff --git a/core/src/banking_stage.rs b/core/src/banking_stage.rs index 845e2981d2fe6d..22cbe68de26e74 100644 --- a/core/src/banking_stage.rs +++ b/core/src/banking_stage.rs @@ -25,7 +25,7 @@ use { scheduler_controller::SchedulerController, scheduler_error::SchedulerError, }, }, - validator::BlockProductionMethod, + validator::{BlockProductionMethod, TransactionStructure}, }, agave_banking_stage_ingress_types::BankingPacketReceiver, crossbeam_channel::{unbounded, Receiver, RecvTimeoutError, Sender}, @@ -53,7 +53,9 @@ use { }, transaction_scheduler::{ prio_graph_scheduler::PrioGraphSchedulerConfig, - receive_and_buffer::SanitizedTransactionReceiveAndBuffer, + receive_and_buffer::{ + ReceiveAndBuffer, SanitizedTransactionReceiveAndBuffer, TransactionViewReceiveAndBuffer, + }, transaction_state_container::TransactionStateContainer, }, }; @@ -401,6 +403,7 @@ impl BankingStage { ) -> Self { match block_production_method { BlockProductionMethod::CentralScheduler => Self::new_central_scheduler( + TransactionStructure::Sdk, cluster_info, poh_recorder, non_vote_receiver, @@ -420,6 +423,7 @@ impl BankingStage { #[allow(clippy::too_many_arguments)] pub fn new_central_scheduler( + transaction_struct: TransactionStructure, cluster_info: &impl LikeClusterInfo, poh_recorder: &Arc>, non_vote_receiver: BankingPacketReceiver, @@ -482,6 +486,77 @@ impl BankingStage { )); } + let transaction_struct = if enable_forwarding { + warn!( + "Forwarding only supported for `Sdk` transaction struct. Overriding to use `Sdk`." + ); + TransactionStructure::Sdk + } else { + transaction_struct + }; + + match transaction_struct { + TransactionStructure::Sdk => { + let receive_and_buffer = SanitizedTransactionReceiveAndBuffer::new( + PacketDeserializer::new(non_vote_receiver), + bank_forks.clone(), + enable_forwarding, + ); + Self::spawn_scheduler_and_workers( + &mut bank_thread_hdls, + receive_and_buffer, + decision_maker, + committer, + cluster_info, + poh_recorder, + num_threads, + log_messages_bytes_limit, + connection_cache, + bank_forks, + enable_forwarding, + data_budget, + ); + } + TransactionStructure::View => { + let receive_and_buffer = TransactionViewReceiveAndBuffer { + receiver: non_vote_receiver, + bank_forks: bank_forks.clone(), + }; + Self::spawn_scheduler_and_workers( + &mut bank_thread_hdls, + receive_and_buffer, + decision_maker, + committer, + cluster_info, + poh_recorder, + num_threads, + log_messages_bytes_limit, + connection_cache, + bank_forks, + enable_forwarding, + data_budget, + ); + } + } + + Self { bank_thread_hdls } + } + + #[allow(clippy::too_many_arguments)] + fn spawn_scheduler_and_workers( + bank_thread_hdls: &mut Vec>, + receive_and_buffer: R, + decision_maker: DecisionMaker, + committer: Committer, + cluster_info: &impl LikeClusterInfo, + poh_recorder: &Arc>, + num_threads: u32, + log_messages_bytes_limit: Option, + connection_cache: Arc, + bank_forks: Arc>, + enable_forwarding: bool, + data_budget: Arc, + ) { // Create channels for communication between scheduler and workers let num_workers = (num_threads).saturating_sub(NUM_VOTE_PROCESSING_THREADS); let (work_senders, work_receivers): (Vec>, Vec>) = @@ -527,39 +602,34 @@ impl BankingStage { }); // Spawn the central scheduler thread - bank_thread_hdls.push({ - let packet_deserializer = PacketDeserializer::new(non_vote_receiver); - let receive_and_buffer = SanitizedTransactionReceiveAndBuffer::new( - packet_deserializer, - bank_forks.clone(), - forwarder.is_some(), - ); - let scheduler = PrioGraphScheduler::new( - work_senders, - finished_work_receiver, - PrioGraphSchedulerConfig::default(), - ); - let scheduler_controller = SchedulerController::new( - decision_maker.clone(), - receive_and_buffer, - bank_forks, - scheduler, - worker_metrics, - forwarder, - ); + bank_thread_hdls.push( Builder::new() .name("solBnkTxSched".to_string()) - .spawn(move || match scheduler_controller.run() { - Ok(_) => {} - Err(SchedulerError::DisconnectedRecvChannel(_)) => {} - Err(SchedulerError::DisconnectedSendChannel(_)) => { - warn!("Unexpected worker disconnect from scheduler") + .spawn(move || { + let scheduler = PrioGraphScheduler::new( + work_senders, + finished_work_receiver, + PrioGraphSchedulerConfig::default(), + ); + let scheduler_controller = SchedulerController::new( + decision_maker.clone(), + receive_and_buffer, + bank_forks, + scheduler, + worker_metrics, + forwarder, + ); + + match scheduler_controller.run() { + Ok(_) => {} + Err(SchedulerError::DisconnectedRecvChannel(_)) => {} + Err(SchedulerError::DisconnectedSendChannel(_)) => { + warn!("Unexpected worker disconnect from scheduler") + } } }) - .unwrap() - }); - - Self { bank_thread_hdls } + .unwrap(), + ); } fn spawn_thread_local_multi_iterator_thread( diff --git a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs index 1b6c672de705d8..e56d084197a280 100644 --- a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs +++ b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs @@ -6,17 +6,15 @@ use { StateContainer, SuccessfulInsert, TransactionViewStateContainer, }, }, - crate::{ - banking_stage::{ - decision_maker::BufferedPacketsDecision, - immutable_deserialized_packet::ImmutableDeserializedPacket, - packet_deserializer::PacketDeserializer, - packet_filter::MAX_ALLOWED_PRECOMPILE_SIGNATURES, scheduler_messages::MaxAge, - transaction_scheduler::transaction_state::SanitizedTransactionTTL, - TransactionStateContainer, - }, - banking_trace::{BankingPacketBatch, BankingPacketReceiver}, + crate::banking_stage::{ + decision_maker::BufferedPacketsDecision, + immutable_deserialized_packet::ImmutableDeserializedPacket, + packet_deserializer::PacketDeserializer, packet_filter::MAX_ALLOWED_PRECOMPILE_SIGNATURES, + scheduler_messages::MaxAge, + transaction_scheduler::transaction_state::SanitizedTransactionTTL, + TransactionStateContainer, }, + agave_banking_stage_ingress_types::{BankingPacketBatch, BankingPacketReceiver}, agave_transaction_view::{ resolved_transaction_view::ResolvedTransactionView, transaction_version::TransactionVersion, transaction_view::SanitizedTransactionView, @@ -49,8 +47,8 @@ use { }; pub(crate) trait ReceiveAndBuffer { - type Transaction: TransactionWithMeta; - type Container: StateContainer; + type Transaction: TransactionWithMeta + Send + Sync; + type Container: StateContainer + Send + Sync; /// Returns whether the packet receiver is still connected. fn receive_and_buffer_packets( @@ -294,12 +292,12 @@ impl SanitizedTransactionReceiveAndBuffer { } } -pub(crate) struct TransactionViewRecieveAndBuffer { +pub(crate) struct TransactionViewReceiveAndBuffer { pub receiver: BankingPacketReceiver, pub bank_forks: Arc>, } -impl ReceiveAndBuffer for TransactionViewRecieveAndBuffer { +impl ReceiveAndBuffer for TransactionViewReceiveAndBuffer { type Transaction = RuntimeTransaction>; type Container = TransactionViewStateContainer; @@ -361,7 +359,7 @@ impl ReceiveAndBuffer for TransactionViewRecieveAndBuffer { } } -impl TransactionViewRecieveAndBuffer { +impl TransactionViewReceiveAndBuffer { fn handle_packet_batch_message( &mut self, container: &mut TransactionViewStateContainer, @@ -391,7 +389,7 @@ impl TransactionViewRecieveAndBuffer { let mut num_buffered = 0usize; let mut num_dropped_on_capacity = 0usize; let mut num_dropped_on_receive = 0usize; - for packet_batch in packet_batch_message.0.iter() { + for packet_batch in packet_batch_message.iter() { for packet in packet_batch.iter() { let Some(packet_data) = packet.data(..) else { continue; @@ -497,15 +495,16 @@ impl TransactionViewRecieveAndBuffer { return Err(()); } - let Ok(compute_budget_limits) = view.compute_budget_limits(&working_bank.feature_set) + let Ok(compute_budget_limits) = view + .compute_budget_instruction_details() + .sanitize_and_convert_to_compute_budget_limits(&working_bank.feature_set) else { return Err(()); }; let max_age = calculate_max_age(sanitized_epoch, deactivation_slot, alt_resolved_slot); let fee_budget_limits = FeeBudgetLimits::from(compute_budget_limits); - let (priority, cost) = - calculate_priority_and_cost(&view, &fee_budget_limits, &working_bank); + let (priority, cost) = calculate_priority_and_cost(&view, &fee_budget_limits, working_bank); Ok(TransactionState::new( SanitizedTransactionTTL { diff --git a/core/src/validator.rs b/core/src/validator.rs index 10f23b4dde15c3..ee9e84aa70d188 100644 --- a/core/src/validator.rs +++ b/core/src/validator.rs @@ -207,6 +207,31 @@ impl BlockProductionMethod { } } +#[derive(Clone, EnumString, EnumVariantNames, Default, IntoStaticStr, Display)] +#[strum(serialize_all = "kebab-case")] +pub enum TransactionStructure { + #[default] + Sdk, + View, +} + +impl TransactionStructure { + pub const fn cli_names() -> &'static [&'static str] { + Self::VARIANTS + } + + pub fn cli_message() -> &'static str { + lazy_static! { + static ref MESSAGE: String = format!( + "Switch internal transaction structure/representation [default: {}]", + TransactionStructure::default() + ); + }; + + &MESSAGE + } +} + /// Configuration for the block generator invalidator for replay. #[derive(Clone, Debug)] pub struct GeneratorConfig { From e7a4eee0896555d39791332bd9d6cd7f476778f6 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Wed, 27 Nov 2024 09:43:41 -0600 Subject: [PATCH 06/30] convert benches/tests to be switchable --- banking-bench/src/main.rs | 14 +++++- core/benches/banking_stage.rs | 83 +++++++++++++++++++++++++++++++--- core/src/banking_simulation.rs | 6 ++- core/src/banking_stage.rs | 46 +++++++++++++------ core/src/tpu.rs | 3 +- ledger-tool/src/main.rs | 8 +++- 6 files changed, 135 insertions(+), 25 deletions(-) diff --git a/banking-bench/src/main.rs b/banking-bench/src/main.rs index 6196f713666e19..c0edc0bee1f73d 100644 --- a/banking-bench/src/main.rs +++ b/banking-bench/src/main.rs @@ -11,7 +11,7 @@ use { solana_core::{ banking_stage::{update_bank_forks_and_poh_recorder_for_new_tpu_bank, BankingStage}, banking_trace::{BankingTracer, Channels, BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT}, - validator::BlockProductionMethod, + validator::{BlockProductionMethod, TransactionStructure}, }, solana_gossip::cluster_info::{ClusterInfo, Node}, solana_ledger::{ @@ -290,6 +290,14 @@ fn main() { .possible_values(BlockProductionMethod::cli_names()) .help(BlockProductionMethod::cli_message()), ) + .arg( + Arg::with_name("transaction_struct") + .long("transaction-structure") + .value_name("STRUCT") + .takes_value(true) + .possible_values(TransactionStructure::cli_names()) + .help(TransactionStructure::cli_message()), + ) .arg( Arg::new("num_banking_threads") .long("num-banking-threads") @@ -320,6 +328,9 @@ fn main() { let block_production_method = matches .value_of_t::("block_production_method") .unwrap_or_default(); + let transaction_struct = matches + .value_of_t::("transaction_struct") + .unwrap_or_default(); let num_banking_threads = matches .value_of_t::("num_banking_threads") .unwrap_or_else(|_| BankingStage::num_threads()); @@ -470,6 +481,7 @@ fn main() { }; let banking_stage = BankingStage::new_num_threads( block_production_method, + transaction_struct, &cluster_info, &poh_recorder, non_vote_receiver, diff --git a/core/benches/banking_stage.rs b/core/benches/banking_stage.rs index dfad8bc8c227cf..10e121db4210b9 100644 --- a/core/benches/banking_stage.rs +++ b/core/benches/banking_stage.rs @@ -3,7 +3,10 @@ use { agave_banking_stage_ingress_types::BankingPacketBatch, - solana_core::{banking_trace::Channels, validator::BlockProductionMethod}, + solana_core::{ + banking_trace::Channels, + validator::{BlockProductionMethod, TransactionStructure}, + }, solana_vote_program::{vote_state::TowerSync, vote_transaction::new_tower_sync_transaction}, }; @@ -193,7 +196,12 @@ enum TransactionType { ProgramsAndVotes, } -fn bench_banking(bencher: &mut Bencher, tx_type: TransactionType) { +fn bench_banking( + bencher: &mut Bencher, + tx_type: TransactionType, + block_production_method: BlockProductionMethod, + transaction_struct: TransactionStructure, +) { solana_logger::setup(); let num_threads = BankingStage::num_threads() as usize; // a multiple of packet chunk duplicates to avoid races @@ -297,7 +305,8 @@ fn bench_banking(bencher: &mut Bencher, tx_type: TransactionType) { let cluster_info = Arc::new(cluster_info); let (s, _r) = unbounded(); let _banking_stage = BankingStage::new( - BlockProductionMethod::CentralScheduler, + block_production_method, + transaction_struct, &cluster_info, &poh_recorder, non_vote_receiver, @@ -372,22 +381,82 @@ fn bench_banking(bencher: &mut Bencher, tx_type: TransactionType) { #[bench] fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) { - bench_banking(bencher, TransactionType::Accounts); + bench_banking( + bencher, + TransactionType::Accounts, + BlockProductionMethod::CentralScheduler, + TransactionStructure::Sdk, + ); } #[bench] fn bench_banking_stage_multi_programs(bencher: &mut Bencher) { - bench_banking(bencher, TransactionType::Programs); + bench_banking( + bencher, + TransactionType::Programs, + BlockProductionMethod::CentralScheduler, + TransactionStructure::Sdk, + ); } #[bench] fn bench_banking_stage_multi_accounts_with_voting(bencher: &mut Bencher) { - bench_banking(bencher, TransactionType::AccountsAndVotes); + bench_banking( + bencher, + TransactionType::AccountsAndVotes, + BlockProductionMethod::CentralScheduler, + TransactionStructure::Sdk, + ); } #[bench] fn bench_banking_stage_multi_programs_with_voting(bencher: &mut Bencher) { - bench_banking(bencher, TransactionType::ProgramsAndVotes); + bench_banking( + bencher, + TransactionType::ProgramsAndVotes, + BlockProductionMethod::CentralScheduler, + TransactionStructure::Sdk, + ); +} + +#[bench] +fn bench_banking_stage_multi_accounts_view(bencher: &mut Bencher) { + bench_banking( + bencher, + TransactionType::Accounts, + BlockProductionMethod::CentralScheduler, + TransactionStructure::View, + ); +} + +#[bench] +fn bench_banking_stage_multi_programs_view(bencher: &mut Bencher) { + bench_banking( + bencher, + TransactionType::Programs, + BlockProductionMethod::CentralScheduler, + TransactionStructure::View, + ); +} + +#[bench] +fn bench_banking_stage_multi_accounts_with_voting_view(bencher: &mut Bencher) { + bench_banking( + bencher, + TransactionType::AccountsAndVotes, + BlockProductionMethod::CentralScheduler, + TransactionStructure::View, + ); +} + +#[bench] +fn bench_banking_stage_multi_programs_with_voting_view(bencher: &mut Bencher) { + bench_banking( + bencher, + TransactionType::ProgramsAndVotes, + BlockProductionMethod::CentralScheduler, + TransactionStructure::View, + ); } fn simulate_process_entries( diff --git a/core/src/banking_simulation.rs b/core/src/banking_simulation.rs index e811c9c6df9bd8..69c7112d86c45a 100644 --- a/core/src/banking_simulation.rs +++ b/core/src/banking_simulation.rs @@ -8,7 +8,7 @@ use { BankingTracer, ChannelLabel, Channels, TimedTracedEvent, TracedEvent, TracedSender, TracerThread, BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT, BASENAME, }, - validator::BlockProductionMethod, + validator::{BlockProductionMethod, TransactionStructure}, }, agave_banking_stage_ingress_types::BankingPacketBatch, assert_matches::assert_matches, @@ -679,6 +679,7 @@ impl BankingSimulator { bank_forks: Arc>, blockstore: Arc, block_production_method: BlockProductionMethod, + transaction_struct: TransactionStructure, ) -> (SenderLoop, SimulatorLoop, SimulatorThreads) { let parent_slot = self.parent_slot().unwrap(); let mut packet_batches_by_time = self.banking_trace_events.packet_batches_by_time; @@ -810,6 +811,7 @@ impl BankingSimulator { let prioritization_fee_cache = &Arc::new(PrioritizationFeeCache::new(0u64)); let banking_stage = BankingStage::new_num_threads( block_production_method.clone(), + transaction_struct.clone(), &cluster_info, &poh_recorder, non_vote_receiver, @@ -896,12 +898,14 @@ impl BankingSimulator { bank_forks: Arc>, blockstore: Arc, block_production_method: BlockProductionMethod, + transaction_struct: TransactionStructure, ) -> Result<(), SimulateError> { let (sender_loop, simulator_loop, simulator_threads) = self.prepare_simulation( genesis_config, bank_forks, blockstore, block_production_method, + transaction_struct, ); sender_loop.log_starting(); diff --git a/core/src/banking_stage.rs b/core/src/banking_stage.rs index 22cbe68de26e74..0d20d354a26401 100644 --- a/core/src/banking_stage.rs +++ b/core/src/banking_stage.rs @@ -353,6 +353,7 @@ impl BankingStage { #[allow(clippy::too_many_arguments)] pub fn new( block_production_method: BlockProductionMethod, + transaction_struct: TransactionStructure, cluster_info: &impl LikeClusterInfo, poh_recorder: &Arc>, non_vote_receiver: BankingPacketReceiver, @@ -368,6 +369,7 @@ impl BankingStage { ) -> Self { Self::new_num_threads( block_production_method, + transaction_struct, cluster_info, poh_recorder, non_vote_receiver, @@ -387,6 +389,7 @@ impl BankingStage { #[allow(clippy::too_many_arguments)] pub fn new_num_threads( block_production_method: BlockProductionMethod, + transaction_struct: TransactionStructure, cluster_info: &impl LikeClusterInfo, poh_recorder: &Arc>, non_vote_receiver: BankingPacketReceiver, @@ -403,7 +406,7 @@ impl BankingStage { ) -> Self { match block_production_method { BlockProductionMethod::CentralScheduler => Self::new_central_scheduler( - TransactionStructure::Sdk, + transaction_struct, cluster_info, poh_recorder, non_vote_receiver, @@ -845,6 +848,7 @@ mod tests { sync::atomic::{AtomicBool, Ordering}, thread::sleep, }, + test_case::test_case, }; pub(crate) fn new_test_cluster_info(keypair: Option>) -> (Node, ClusterInfo) { @@ -863,8 +867,9 @@ mod tests { .collect() } - #[test] - fn test_banking_stage_shutdown1() { + #[test_case(TransactionStructure::Sdk)] + #[test_case(TransactionStructure::View)] + fn test_banking_stage_shutdown1(transaction_struct: TransactionStructure) { let genesis_config = create_genesis_config(2).genesis_config; let (bank, bank_forks) = Bank::new_no_wallclock_throttle_for_tests(&genesis_config); let banking_tracer = BankingTracer::new_disabled(); @@ -890,6 +895,7 @@ mod tests { let banking_stage = BankingStage::new( BlockProductionMethod::CentralScheduler, + transaction_struct, &cluster_info, &poh_recorder, non_vote_receiver, @@ -913,8 +919,9 @@ mod tests { Blockstore::destroy(ledger_path.path()).unwrap(); } - #[test] - fn test_banking_stage_tick() { + #[test_case(TransactionStructure::Sdk)] + #[test_case(TransactionStructure::View)] + fn test_banking_stage_tick(transaction_struct: TransactionStructure) { solana_logger::setup(); let GenesisConfigInfo { mut genesis_config, .. @@ -950,6 +957,7 @@ mod tests { let banking_stage = BankingStage::new( BlockProductionMethod::CentralScheduler, + transaction_struct, &cluster_info, &poh_recorder, non_vote_receiver, @@ -996,7 +1004,10 @@ mod tests { with_vers.into_iter().map(|(b, _)| b).collect() } - fn test_banking_stage_entries_only(block_production_method: BlockProductionMethod) { + fn test_banking_stage_entries_only( + block_production_method: BlockProductionMethod, + transaction_struct: TransactionStructure, + ) { solana_logger::setup(); let GenesisConfigInfo { genesis_config, @@ -1034,6 +1045,7 @@ mod tests { let banking_stage = BankingStage::new( block_production_method, + transaction_struct, &cluster_info, &poh_recorder, non_vote_receiver, @@ -1127,13 +1139,18 @@ mod tests { Blockstore::destroy(ledger_path.path()).unwrap(); } - #[test] - fn test_banking_stage_entries_only_central_scheduler() { - test_banking_stage_entries_only(BlockProductionMethod::CentralScheduler); + #[test_case(TransactionStructure::Sdk)] + #[test_case(TransactionStructure::View)] + fn test_banking_stage_entries_only_central_scheduler(transaction_struct: TransactionStructure) { + test_banking_stage_entries_only( + BlockProductionMethod::CentralScheduler, + transaction_struct, + ); } - #[test] - fn test_banking_stage_entryfication() { + #[test_case(TransactionStructure::Sdk)] + #[test_case(TransactionStructure::View)] + fn test_banking_stage_entryfication(transaction_struct: TransactionStructure) { solana_logger::setup(); // In this attack we'll demonstrate that a verifier can interpret the ledger // differently if either the server doesn't signal the ledger to add an @@ -1204,6 +1221,7 @@ mod tests { let cluster_info = Arc::new(cluster_info); let _banking_stage = BankingStage::new( BlockProductionMethod::CentralScheduler, + transaction_struct, &cluster_info, &poh_recorder, non_vote_receiver, @@ -1361,8 +1379,9 @@ mod tests { tick_producer.unwrap() } - #[test] - fn test_unprocessed_transaction_storage_full_send() { + #[test_case(TransactionStructure::Sdk)] + #[test_case(TransactionStructure::View)] + fn test_unprocessed_transaction_storage_full_send(transaction_struct: TransactionStructure) { solana_logger::setup(); let GenesisConfigInfo { genesis_config, @@ -1400,6 +1419,7 @@ mod tests { let banking_stage = BankingStage::new( BlockProductionMethod::CentralScheduler, + transaction_struct, &cluster_info, &poh_recorder, non_vote_receiver, diff --git a/core/src/tpu.rs b/core/src/tpu.rs index d715bb5c7b0534..9f2a483a62b2a8 100644 --- a/core/src/tpu.rs +++ b/core/src/tpu.rs @@ -15,7 +15,7 @@ use { sigverify_stage::SigVerifyStage, staked_nodes_updater_service::StakedNodesUpdaterService, tpu_entry_notifier::TpuEntryNotifier, - validator::{BlockProductionMethod, GeneratorConfig}, + validator::{BlockProductionMethod, GeneratorConfig, TransactionStructure}, }, bytes::Bytes, crossbeam_channel::{unbounded, Receiver}, @@ -269,6 +269,7 @@ impl Tpu { let banking_stage = BankingStage::new( block_production_method, + TransactionStructure::Sdk, // TODO: add cli cluster_info, poh_recorder, non_vote_receiver, diff --git a/ledger-tool/src/main.rs b/ledger-tool/src/main.rs index 8e0cdc5a019895..b6be917334dd82 100644 --- a/ledger-tool/src/main.rs +++ b/ledger-tool/src/main.rs @@ -33,7 +33,7 @@ use { solana_core::{ banking_simulation::{BankingSimulator, BankingTraceEvents}, system_monitor_service::{SystemMonitorService, SystemMonitorStatsReportConfig}, - validator::{BlockProductionMethod, BlockVerificationMethod}, + validator::{BlockProductionMethod, BlockVerificationMethod, TransactionStructure}, }, solana_cost_model::{cost_model::CostModel, cost_tracker::CostTracker}, solana_feature_set::{self as feature_set, FeatureSet}, @@ -2492,14 +2492,18 @@ fn main() { BlockProductionMethod ) .unwrap_or_default(); + let transaction_struct = + value_t!(arg_matches, "transaction_struct", TransactionStructure) + .unwrap_or_default(); - info!("Using: block-production-method: {block_production_method}"); + info!("Using: block-production-method: {block_production_method} transaction-structure: {transaction_struct}"); match simulator.start( genesis_config, bank_forks, blockstore, block_production_method, + transaction_struct, ) { Ok(()) => println!("Ok"), Err(error) => { From 513ff6a1b8b3e6c104ef2686e89a85611651b558 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Wed, 27 Nov 2024 09:46:01 -0600 Subject: [PATCH 07/30] add cli hookup for validator --- core/src/tpu.rs | 3 ++- core/src/validator.rs | 7 +++++-- local-cluster/src/validator_configs.rs | 1 + multinode-demo/bootstrap-validator.sh | 3 +++ multinode-demo/validator.sh | 3 +++ validator/src/cli.rs | 10 +++++++++- validator/src/main.rs | 11 +++++++++-- 7 files changed, 32 insertions(+), 6 deletions(-) diff --git a/core/src/tpu.rs b/core/src/tpu.rs index 9f2a483a62b2a8..b5a73415160260 100644 --- a/core/src/tpu.rs +++ b/core/src/tpu.rs @@ -118,6 +118,7 @@ impl Tpu { tpu_max_connections_per_ipaddr_per_minute: u64, prioritization_fee_cache: &Arc, block_production_method: BlockProductionMethod, + transaction_struct: TransactionStructure, enable_block_production_forwarding: bool, _generator_config: Option, /* vestigial code for replay invalidator */ ) -> (Self, Vec>) { @@ -269,7 +270,7 @@ impl Tpu { let banking_stage = BankingStage::new( block_production_method, - TransactionStructure::Sdk, // TODO: add cli + transaction_struct, cluster_info, poh_recorder, non_vote_receiver, diff --git a/core/src/validator.rs b/core/src/validator.rs index ee9e84aa70d188..a540cc9c564642 100644 --- a/core/src/validator.rs +++ b/core/src/validator.rs @@ -298,6 +298,7 @@ pub struct ValidatorConfig { pub banking_trace_dir_byte_limit: banking_trace::DirByteLimit, pub block_verification_method: BlockVerificationMethod, pub block_production_method: BlockProductionMethod, + pub transaction_struct: TransactionStructure, pub enable_block_production_forwarding: bool, pub generator_config: Option, pub use_snapshot_archives_at_startup: UseSnapshotArchivesAtStartup, @@ -370,6 +371,7 @@ impl Default for ValidatorConfig { banking_trace_dir_byte_limit: 0, block_verification_method: BlockVerificationMethod::default(), block_production_method: BlockProductionMethod::default(), + transaction_struct: TransactionStructure::default(), enable_block_production_forwarding: false, generator_config: None, use_snapshot_archives_at_startup: UseSnapshotArchivesAtStartup::default(), @@ -894,8 +896,8 @@ impl Validator { config.accounts_db_test_hash_calculation, ); info!( - "Using: block-verification-method: {}, block-production-method: {}", - config.block_verification_method, config.block_production_method + "Using: block-verification-method: {}, block-production-method: {}, transaction-structure: {}", + config.block_verification_method, config.block_production_method, config.transaction_struct ); let (replay_vote_sender, replay_vote_receiver) = unbounded(); @@ -1549,6 +1551,7 @@ impl Validator { tpu_max_connections_per_ipaddr_per_minute, &prioritization_fee_cache, config.block_production_method.clone(), + config.transaction_struct.clone(), config.enable_block_production_forwarding, config.generator_config.clone(), ); diff --git a/local-cluster/src/validator_configs.rs b/local-cluster/src/validator_configs.rs index e90475aad2a06f..50199be9e7172c 100644 --- a/local-cluster/src/validator_configs.rs +++ b/local-cluster/src/validator_configs.rs @@ -61,6 +61,7 @@ pub fn safe_clone_config(config: &ValidatorConfig) -> ValidatorConfig { banking_trace_dir_byte_limit: config.banking_trace_dir_byte_limit, block_verification_method: config.block_verification_method.clone(), block_production_method: config.block_production_method.clone(), + transaction_struct: config.transaction_struct.clone(), enable_block_production_forwarding: config.enable_block_production_forwarding, generator_config: config.generator_config.clone(), use_snapshot_archives_at_startup: config.use_snapshot_archives_at_startup, diff --git a/multinode-demo/bootstrap-validator.sh b/multinode-demo/bootstrap-validator.sh index d21ee1aaa8b73f..4285be6cac5dc1 100755 --- a/multinode-demo/bootstrap-validator.sh +++ b/multinode-demo/bootstrap-validator.sh @@ -112,6 +112,9 @@ while [[ -n $1 ]]; do elif [[ $1 == --block-production-method ]]; then args+=("$1" "$2") shift 2 + elif [[ $1 == --transaction-structure ]]; then + args+=("$1" "$2") + shift 2 elif [[ $1 == --wen-restart ]]; then args+=("$1" "$2") shift 2 diff --git a/multinode-demo/validator.sh b/multinode-demo/validator.sh index c97812c6cbb910..800b4ce9d136d2 100755 --- a/multinode-demo/validator.sh +++ b/multinode-demo/validator.sh @@ -182,6 +182,9 @@ while [[ -n $1 ]]; do elif [[ $1 == --block-production-method ]]; then args+=("$1" "$2") shift 2 + elif [[ $1 == --transaction-structure ]]; then + args+=("$1" "$2") + shift 2 elif [[ $1 == --wen-restart ]]; then args+=("$1" "$2") shift 2 diff --git a/validator/src/cli.rs b/validator/src/cli.rs index fa8d439b095e3b..a54d8c7735245d 100644 --- a/validator/src/cli.rs +++ b/validator/src/cli.rs @@ -21,7 +21,7 @@ use { }, solana_core::{ banking_trace::{DirByteLimit, BANKING_TRACE_DIR_DEFAULT_BYTE_LIMIT}, - validator::{BlockProductionMethod, BlockVerificationMethod}, + validator::{BlockProductionMethod, BlockVerificationMethod, TransactionStructure}, }, solana_faucet::faucet::{self, FAUCET_PORT}, solana_ledger::use_snapshot_archives_at_startup, @@ -1599,6 +1599,14 @@ pub fn app<'a>(version: &'a str, default_args: &'a DefaultArgs) -> App<'a, 'a> { .possible_values(BlockProductionMethod::cli_names()) .help(BlockProductionMethod::cli_message()), ) + .arg( + Arg::with_name("transaction_struct") + .long("transaction-structure") + .value_name("STRUCT") + .takes_value(true) + .possible_values(TransactionStructure::cli_names()) + .help(TransactionStructure::cli_message()), + ) .arg( Arg::with_name("unified_scheduler_handler_threads") .long("unified-scheduler-handler-threads") diff --git a/validator/src/main.rs b/validator/src/main.rs index 4af305a39da5c8..2b67cb2f2619bd 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -35,8 +35,9 @@ use { system_monitor_service::SystemMonitorService, tpu::DEFAULT_TPU_COALESCE, validator::{ - is_snapshot_config_valid, BlockProductionMethod, BlockVerificationMethod, Validator, - ValidatorConfig, ValidatorError, ValidatorStartProgress, ValidatorTpuConfig, + is_snapshot_config_valid, BlockProductionMethod, BlockVerificationMethod, + TransactionStructure, Validator, ValidatorConfig, ValidatorError, + ValidatorStartProgress, ValidatorTpuConfig, }, }, solana_gossip::{ @@ -1848,6 +1849,12 @@ pub fn main() { BlockProductionMethod ) .unwrap_or_default(); + validator_config.transaction_struct = value_t!( + matches, // comment to align formatting... + "transaction_struct", + TransactionStructure + ) + .unwrap_or_default(); validator_config.enable_block_production_forwarding = staked_nodes_overrides_path.is_some(); validator_config.unified_scheduler_handler_threads = value_t!(matches, "unified_scheduler_handler_threads", usize).ok(); From 0ba8bbe5cfa81cb9e79118c4f1c1f5c2d9466b15 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Wed, 27 Nov 2024 10:18:30 -0600 Subject: [PATCH 08/30] test both ReceiveAndBuffer in scheduler_controller --- .../scheduler_controller.rs | 128 +++++++++++------- 1 file changed, 79 insertions(+), 49 deletions(-) diff --git a/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs b/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs index 5061bf843f42f5..0d391824d0e9f8 100644 --- a/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs +++ b/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs @@ -444,8 +444,9 @@ mod tests { prio_graph_scheduler::PrioGraphSchedulerConfig, receive_and_buffer::SanitizedTransactionReceiveAndBuffer, }, + TransactionViewReceiveAndBuffer, }, - agave_banking_stage_ingress_types::BankingPacketBatch, + agave_banking_stage_ingress_types::{BankingPacketBatch, BankingPacketReceiver}, crossbeam_channel::{unbounded, Receiver, Sender}, itertools::Itertools, solana_gossip::cluster_info::ClusterInfo, @@ -456,21 +457,15 @@ mod tests { solana_perf::packet::{to_packet_batches, PacketBatch, NUM_PACKETS}, solana_poh::poh_recorder::{PohRecorder, Record, WorkingBankEntry}, solana_runtime::bank::Bank, - solana_runtime_transaction::runtime_transaction::RuntimeTransaction, + solana_runtime_transaction::transaction_meta::StaticMeta, solana_sdk::{ - compute_budget::ComputeBudgetInstruction, - fee_calculator::FeeRateGovernor, - hash::Hash, - message::Message, - poh_config::PohConfig, - pubkey::Pubkey, - signature::Keypair, - signer::Signer, - system_instruction, system_transaction, - transaction::{SanitizedTransaction, Transaction}, + compute_budget::ComputeBudgetInstruction, fee_calculator::FeeRateGovernor, hash::Hash, + message::Message, poh_config::PohConfig, pubkey::Pubkey, signature::Keypair, + signer::Signer, system_instruction, system_transaction, transaction::Transaction, }, std::sync::{atomic::AtomicBool, Arc, RwLock}, tempfile::TempDir, + test_case::test_case, }; fn create_channels(num: usize) -> (Vec>, Vec>) { @@ -479,7 +474,7 @@ mod tests { // Helper struct to create tests that hold channels, files, etc. // such that our tests can be more easily set up and run. - struct TestFrame { + struct TestFrame { bank: Arc, mint_keypair: Keypair, _ledger_path: TempDir, @@ -488,18 +483,38 @@ mod tests { poh_recorder: Arc>, banking_packet_sender: Sender>>, - consume_work_receivers: - Vec>>>, - finished_consume_work_sender: - Sender>>, + consume_work_receivers: Vec>>, + finished_consume_work_sender: Sender>, + } + + fn test_create_sanitized_transaction_receive_and_buffer( + receiver: BankingPacketReceiver, + bank_forks: Arc>, + ) -> SanitizedTransactionReceiveAndBuffer { + SanitizedTransactionReceiveAndBuffer::new( + PacketDeserializer::new(receiver), + bank_forks, + false, + ) + } + + fn test_create_transaction_view_receive_and_buffer( + receiver: BankingPacketReceiver, + bank_forks: Arc>, + ) -> TransactionViewReceiveAndBuffer { + TransactionViewReceiveAndBuffer { + receiver, + bank_forks, + } } #[allow(clippy::type_complexity)] - fn create_test_frame( + fn create_test_frame( num_threads: usize, + create_receive_and_buffer: impl FnOnce(BankingPacketReceiver, Arc>) -> R, ) -> ( - TestFrame, - SchedulerController, SanitizedTransactionReceiveAndBuffer>, + TestFrame, + SchedulerController, R>, ) { let GenesisConfigInfo { mut genesis_config, @@ -527,7 +542,8 @@ mod tests { let decision_maker = DecisionMaker::new(Pubkey::new_unique(), poh_recorder.clone()); let (banking_packet_sender, banking_packet_receiver) = unbounded(); - let packet_deserializer = PacketDeserializer::new(banking_packet_receiver); + let receive_and_buffer = + create_receive_and_buffer(banking_packet_receiver, bank_forks.clone()); let (consume_work_senders, consume_work_receivers) = create_channels(num_threads); let (finished_consume_work_sender, finished_consume_work_receiver) = unbounded(); @@ -544,12 +560,6 @@ mod tests { finished_consume_work_sender, }; - let receive_and_buffer = SanitizedTransactionReceiveAndBuffer::new( - packet_deserializer, - bank_forks.clone(), - false, - ); - let scheduler = PrioGraphScheduler::new( consume_work_senders, finished_consume_work_receiver, @@ -605,10 +615,7 @@ mod tests { // In the tests, the decision will not become stale, so it is more convenient // to receive first and then schedule. fn test_receive_then_schedule( - scheduler_controller: &mut SchedulerController< - Arc, - SanitizedTransactionReceiveAndBuffer, - >, + scheduler_controller: &mut SchedulerController, impl ReceiveAndBuffer>, ) { let decision = scheduler_controller .decision_maker @@ -619,10 +626,13 @@ mod tests { assert!(scheduler_controller.process_transactions(&decision).is_ok()); } - #[test] + #[test_case(test_create_sanitized_transaction_receive_and_buffer; "Sdk")] + #[test_case(test_create_transaction_view_receive_and_buffer; "View")] #[should_panic(expected = "batch id 0 is not being tracked")] - fn test_unexpected_batch_id() { - let (test_frame, scheduler_controller) = create_test_frame(1); + fn test_unexpected_batch_id( + create_receive_and_buffer: impl FnOnce(BankingPacketReceiver, Arc>) -> R, + ) { + let (test_frame, scheduler_controller) = create_test_frame(1, create_receive_and_buffer); let TestFrame { finished_consume_work_sender, .. @@ -643,9 +653,13 @@ mod tests { scheduler_controller.run().unwrap(); } - #[test] - fn test_schedule_consume_single_threaded_no_conflicts() { - let (test_frame, mut scheduler_controller) = create_test_frame(1); + #[test_case(test_create_sanitized_transaction_receive_and_buffer; "Sdk")] + #[test_case(test_create_transaction_view_receive_and_buffer; "View")] + fn test_schedule_consume_single_threaded_no_conflicts( + create_receive_and_buffer: impl FnOnce(BankingPacketReceiver, Arc>) -> R, + ) { + let (test_frame, mut scheduler_controller) = + create_test_frame(1, create_receive_and_buffer); let TestFrame { bank, mint_keypair, @@ -699,9 +713,13 @@ mod tests { assert_eq!(message_hashes, vec![&tx2_hash, &tx1_hash]); } - #[test] - fn test_schedule_consume_single_threaded_conflict() { - let (test_frame, mut scheduler_controller) = create_test_frame(1); + #[test_case(test_create_sanitized_transaction_receive_and_buffer; "Sdk")] + #[test_case(test_create_transaction_view_receive_and_buffer; "View")] + fn test_schedule_consume_single_threaded_conflict( + create_receive_and_buffer: impl FnOnce(BankingPacketReceiver, Arc>) -> R, + ) { + let (test_frame, mut scheduler_controller) = + create_test_frame(1, create_receive_and_buffer); let TestFrame { bank, mint_keypair, @@ -758,9 +776,13 @@ mod tests { assert_eq!(message_hashes, vec![&tx2_hash, &tx1_hash]); } - #[test] - fn test_schedule_consume_single_threaded_multi_batch() { - let (test_frame, mut scheduler_controller) = create_test_frame(1); + #[test_case(test_create_sanitized_transaction_receive_and_buffer; "Sdk")] + #[test_case(test_create_transaction_view_receive_and_buffer; "View")] + fn test_schedule_consume_single_threaded_multi_batch( + create_receive_and_buffer: impl FnOnce(BankingPacketReceiver, Arc>) -> R, + ) { + let (test_frame, mut scheduler_controller) = + create_test_frame(1, create_receive_and_buffer); let TestFrame { bank, mint_keypair, @@ -822,9 +844,13 @@ mod tests { ); } - #[test] - fn test_schedule_consume_simple_thread_selection() { - let (test_frame, mut scheduler_controller) = create_test_frame(2); + #[test_case(test_create_sanitized_transaction_receive_and_buffer; "Sdk")] + #[test_case(test_create_transaction_view_receive_and_buffer; "View")] + fn test_schedule_consume_simple_thread_selection( + create_receive_and_buffer: impl FnOnce(BankingPacketReceiver, Arc>) -> R, + ) { + let (test_frame, mut scheduler_controller) = + create_test_frame(2, create_receive_and_buffer); let TestFrame { bank, mint_keypair, @@ -889,9 +915,13 @@ mod tests { assert_eq!(t1_actual, t1_expected); } - #[test] - fn test_schedule_consume_retryable() { - let (test_frame, mut scheduler_controller) = create_test_frame(1); + #[test_case(test_create_sanitized_transaction_receive_and_buffer; "Sdk")] + #[test_case(test_create_transaction_view_receive_and_buffer; "View")] + fn test_schedule_consume_retryable( + create_receive_and_buffer: impl FnOnce(BankingPacketReceiver, Arc>) -> R, + ) { + let (test_frame, mut scheduler_controller) = + create_test_frame(1, create_receive_and_buffer); let TestFrame { bank, mint_keypair, From 87c406400d3320509398cc0ac9b8bf7cc08bd8a0 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Wed, 27 Nov 2024 10:33:51 -0600 Subject: [PATCH 09/30] Fix bytes leak on retry --- .../transaction_scheduler/prio_graph_scheduler.rs | 5 +++++ 1 file changed, 5 insertions(+) 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 5e401f15942fb6..7b016d41fc43c7 100644 --- a/core/src/banking_stage/transaction_scheduler/prio_graph_scheduler.rs +++ b/core/src/banking_stage/transaction_scheduler/prio_graph_scheduler.rs @@ -378,6 +378,11 @@ impl PrioGraphScheduler { continue; } } + // Transaction must be dropped before removing, since we + // currently have ownership of the transaction, and + // therefore may have a reference to the backing-memory + // that the container expects to be free. + drop(transaction); container.remove_by_id(id); } From 34acab8a9453ce26b85c0cc15f6b397f14802701 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Wed, 27 Nov 2024 10:48:46 -0600 Subject: [PATCH 10/30] receive_and_buffer exit condition fix --- .../transaction_scheduler/receive_and_buffer.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs index e56d084197a280..39f96d6b88a3ab 100644 --- a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs +++ b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs @@ -50,7 +50,8 @@ pub(crate) trait ReceiveAndBuffer { type Transaction: TransactionWithMeta + Send + Sync; type Container: StateContainer + Send + Sync; - /// Returns whether the packet receiver is still connected. + /// Returns false only if no packets were received + /// AND the receiver is disconnected. fn receive_and_buffer_packets( &mut self, container: &mut Self::Container, @@ -311,6 +312,7 @@ impl ReceiveAndBuffer for TransactionViewReceiveAndBuffer { // Receive packet batches. const TIMEOUT: Duration = Duration::from_millis(10); let start = Instant::now(); + let mut received_message = false; // If not leader, do a blocking-receive initially. This lets the thread // sleep when there is not work to do. @@ -326,6 +328,7 @@ impl ReceiveAndBuffer for TransactionViewReceiveAndBuffer { ) { match self.receiver.recv_timeout(TIMEOUT) { Ok(packet_batch_message) => { + received_message = true; self.handle_packet_batch_message( container, timing_metrics, @@ -335,13 +338,16 @@ impl ReceiveAndBuffer for TransactionViewReceiveAndBuffer { ); } Err(RecvTimeoutError::Timeout) => return true, - Err(RecvTimeoutError::Disconnected) => return false, + Err(RecvTimeoutError::Disconnected) => { + return received_message; + } } } while start.elapsed() < TIMEOUT { match self.receiver.try_recv() { Ok(packet_batch_message) => { + received_message = true; self.handle_packet_batch_message( container, timing_metrics, @@ -351,7 +357,9 @@ impl ReceiveAndBuffer for TransactionViewReceiveAndBuffer { ); } Err(TryRecvError::Empty) => return true, - Err(TryRecvError::Disconnected) => return false, + Err(TryRecvError::Disconnected) => { + return received_message; + } } } From 9dbcf523819ca25dcd636621b130967500b70bc8 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Wed, 27 Nov 2024 11:42:23 -0600 Subject: [PATCH 11/30] lock bank_forks less often --- .../receive_and_buffer.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs index 39f96d6b88a3ab..9c63ced9a25d56 100644 --- a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs +++ b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs @@ -309,6 +309,13 @@ impl ReceiveAndBuffer for TransactionViewReceiveAndBuffer { count_metrics: &mut SchedulerCountMetrics, decision: &BufferedPacketsDecision, ) -> bool { + let (root_bank, working_bank) = { + let bank_forks = self.bank_forks.read().unwrap(); + let root_bank = bank_forks.root_bank(); + let working_bank = bank_forks.working_bank(); + (root_bank, working_bank) + }; + // Receive packet batches. const TIMEOUT: Duration = Duration::from_millis(10); let start = Instant::now(); @@ -334,6 +341,8 @@ impl ReceiveAndBuffer for TransactionViewReceiveAndBuffer { timing_metrics, count_metrics, decision, + &root_bank, + &working_bank, packet_batch_message, ); } @@ -353,6 +362,8 @@ impl ReceiveAndBuffer for TransactionViewReceiveAndBuffer { timing_metrics, count_metrics, decision, + &root_bank, + &working_bank, packet_batch_message, ); } @@ -374,6 +385,8 @@ impl TransactionViewReceiveAndBuffer { timing_metrics: &mut SchedulerTimingMetrics, count_metrics: &mut SchedulerCountMetrics, decision: &BufferedPacketsDecision, + root_bank: &Bank, + working_bank: &Bank, packet_batch_message: BankingPacketBatch, ) { // Do not support forwarding - only add support for this if we really need it. @@ -383,12 +396,6 @@ impl TransactionViewReceiveAndBuffer { let start = Instant::now(); // Sanitize packets, generate IDs, and insert into the container. - let (root_bank, working_bank) = { - let bank_forks = self.bank_forks.read().unwrap(); - let root_bank = bank_forks.root_bank(); - let working_bank = bank_forks.working_bank(); - (root_bank, working_bank) - }; let alt_resolved_slot = root_bank.slot(); let sanitized_epoch = root_bank.epoch(); let transaction_account_lock_limit = working_bank.get_transaction_account_lock_limit(); From b702d5cfb06298eb23cc0a658a25320728c68736 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Mon, 2 Dec 2024 09:03:25 -0600 Subject: [PATCH 12/30] remove references --- .../banking_stage/transaction_scheduler/receive_and_buffer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs index 9c63ced9a25d56..63740301dc0f89 100644 --- a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs +++ b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs @@ -420,8 +420,8 @@ impl TransactionViewReceiveAndBuffer { match Self::try_handle_packet( bytes.clone(), - &root_bank, - &working_bank, + root_bank, + working_bank, alt_resolved_slot, sanitized_epoch, transaction_account_lock_limit, From e4d5b9492fd1fb564aee3e5a4e1b44cc4be53494 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Tue, 3 Dec 2024 10:03:11 -0600 Subject: [PATCH 13/30] minor sleeping efficiency improvement --- .../receive_and_buffer.rs | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs index 63740301dc0f89..f3a76febf3adec 100644 --- a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs +++ b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs @@ -321,18 +321,19 @@ impl ReceiveAndBuffer for TransactionViewReceiveAndBuffer { let start = Instant::now(); let mut received_message = false; - // If not leader, do a blocking-receive initially. This lets the thread - // sleep when there is not work to do. - // TODO: Is it better to manually sleep instead, avoiding the locking - // overhead for wakers? But then risk not waking up when message - // received - as long as sleep is somewhat short, this should be - // fine. - if matches!( - decision, - BufferedPacketsDecision::Forward - | BufferedPacketsDecision::ForwardAndHold - | BufferedPacketsDecision::Hold - ) { + // If not leader/unknown, do a blocking-receive initially. This lets + // the thread sleep until a message is received, or until the timeout. + // Additionally, only sleep if the container is empty. + if container.is_empty() + && matches!( + decision, + BufferedPacketsDecision::Forward | BufferedPacketsDecision::ForwardAndHold + ) + { + // TODO: Is it better to manually sleep instead, avoiding the locking + // overhead for wakers? But then risk not waking up when message + // received - as long as sleep is somewhat short, this should be + // fine. match self.receiver.recv_timeout(TIMEOUT) { Ok(packet_batch_message) => { received_message = true; From 81d1826973db63053eb1aa878ab911dc869430b8 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Thu, 5 Dec 2024 16:36:36 -0600 Subject: [PATCH 14/30] impl TransactionData for Arc> --- transaction-view/src/transaction_data.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/transaction-view/src/transaction_data.rs b/transaction-view/src/transaction_data.rs index d6152c8069d3e8..df20f96b65a39c 100644 --- a/transaction-view/src/transaction_data.rs +++ b/transaction-view/src/transaction_data.rs @@ -17,3 +17,10 @@ impl TransactionData for bytes::Bytes { self.as_ref() } } + +impl TransactionData for std::sync::Arc> { + #[inline] + fn data(&self) -> &[u8] { + self.as_ref() + } +} From db9ade68b30f4b48a2fa94ee82e09f564e1f6a2e Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Thu, 5 Dec 2024 17:00:53 -0600 Subject: [PATCH 15/30] SharedBytes = Arc> --- .../receive_and_buffer.rs | 31 +++-- .../transaction_state_container.rs | 119 +++++------------- 2 files changed, 45 insertions(+), 105 deletions(-) diff --git a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs index f3a76febf3adec..161e4e854b7242 100644 --- a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs +++ b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs @@ -3,7 +3,7 @@ use { scheduler_metrics::{SchedulerCountMetrics, SchedulerTimingMetrics}, transaction_state::TransactionState, transaction_state_container::{ - StateContainer, SuccessfulInsert, TransactionViewStateContainer, + SharedBytes, StateContainer, SuccessfulInsert, TransactionViewStateContainer, }, }, crate::banking_stage::{ @@ -20,7 +20,6 @@ use { transaction_version::TransactionVersion, transaction_view::SanitizedTransactionView, }, arrayvec::ArrayVec, - bytes::Bytes, core::time::Duration, crossbeam_channel::{RecvTimeoutError, TryRecvError}, solana_accounts_db::account_locks::validate_account_locks, @@ -299,7 +298,7 @@ pub(crate) struct TransactionViewReceiveAndBuffer { } impl ReceiveAndBuffer for TransactionViewReceiveAndBuffer { - type Transaction = RuntimeTransaction>; + type Transaction = RuntimeTransaction>; type Container = TransactionViewStateContainer; fn receive_and_buffer_packets( @@ -414,13 +413,10 @@ impl TransactionViewReceiveAndBuffer { num_received += 1; // Reserve free-space to copy packet into, run sanitization checks, and insert. - if container.try_insert_with(|mut bytes| { - // Copy packet data into the buffer, and freeze. - bytes.extend_from_slice(packet_data); - let bytes = bytes.freeze(); - - match Self::try_handle_packet( - bytes.clone(), + if container.try_insert_with_data( + packet_data, + |bytes| match Self::try_handle_packet( + bytes, root_bank, working_bank, alt_resolved_slot, @@ -429,14 +425,14 @@ impl TransactionViewReceiveAndBuffer { ) { Ok(state) => { num_buffered += 1; - Ok(SuccessfulInsert { state, bytes }) + Ok(SuccessfulInsert { state }) } Err(()) => { num_dropped_on_receive += 1; - Err(bytes.try_into_mut().expect("no leaks")) + Err(()) } - } - }) { + }, + ) { num_dropped_on_capacity += 1; }; } @@ -458,15 +454,16 @@ impl TransactionViewReceiveAndBuffer { } fn try_handle_packet( - bytes: Bytes, + bytes: SharedBytes, root_bank: &Bank, working_bank: &Bank, alt_resolved_slot: Slot, sanitized_epoch: Epoch, transaction_account_lock_limit: usize, - ) -> Result>>, ()> { + ) -> Result>>, ()> + { // Parsing and basic sanitization checks - let Ok(view) = SanitizedTransactionView::try_new_sanitized(bytes.clone()) else { + let Ok(view) = SanitizedTransactionView::try_new_sanitized(bytes) else { return Err(()); }; 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 2d2113b22fbd05..654a89bf78e6cb 100644 --- a/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs +++ b/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs @@ -8,7 +8,6 @@ use { scheduler_messages::TransactionId, }, agave_transaction_view::resolved_transaction_view::ResolvedTransactionView, - bytes::{Bytes, BytesMut}, itertools::MinMaxResult, min_max_heap::MinMaxHeap, slab::Slab, @@ -197,50 +196,26 @@ impl TransactionStateContainer { } } +pub type SharedBytes = Arc>; + /// A wrapper around `TransactionStateContainer` that allows re-uses /// pre-allocated `Bytes` to copy packet data into and use for serialization. /// This is used to avoid allocations in parsing transactions. pub struct TransactionViewStateContainer { - inner: TransactionStateContainer>>, - bytes_buffer: Box<[MaybeBytes]>, -} - -enum MaybeBytes { - None, - Bytes(Bytes), - BytesMut(BytesMut), -} - -impl MaybeBytes { - fn reserve_space(&mut self) -> BytesMut { - match core::mem::replace(self, MaybeBytes::None) { - MaybeBytes::BytesMut(bytes) => bytes, - _ => unreachable!("invalid state"), - } - } - - fn freeze(&mut self, bytes: Bytes) { - debug_assert!(matches!(self, MaybeBytes::None)); - *self = MaybeBytes::Bytes(bytes); - } - - fn free_space(&mut self, mut bytes: BytesMut) { - debug_assert!(matches!(self, MaybeBytes::None)); - bytes.clear(); - *self = MaybeBytes::BytesMut(bytes); - } + inner: TransactionStateContainer>>, + bytes_buffer: Box<[SharedBytes]>, } pub(crate) struct SuccessfulInsert { - pub state: TransactionState>>, - pub bytes: Bytes, + pub state: TransactionState>>, } impl TransactionViewStateContainer { /// Returns true if packet was dropped due to capacity limits. - pub(crate) fn try_insert_with( + pub(crate) fn try_insert_with_data( &mut self, - f: impl FnOnce(BytesMut) -> Result, + data: &[u8], + f: impl FnOnce(SharedBytes) -> Result, ) -> bool { if self.inner.id_to_transaction_state.len() == self.inner.id_to_transaction_state.capacity() { @@ -255,58 +230,38 @@ impl TransactionViewStateContainer { // Get the vacant space in the bytes buffer. let bytes_entry = &mut self.bytes_buffer[transaction_id]; - let bytes = bytes_entry.reserve_space(); + // Assert the entry is unique, then copy the packet data. + { + assert_eq!(Arc::strong_count(bytes_entry), 1, "entry must be unique"); + let bytes = Arc::make_mut(bytes_entry); + + // Clear and copy the packet data into the bytes buffer. + bytes.clear(); + bytes.extend_from_slice(data); + } // Attempt to insert the transaction, storing the frozen bytes back into bytes buffer. - match f(bytes) { - Ok(SuccessfulInsert { state, bytes }) => { + match f(Arc::clone(bytes_entry)) { + Ok(SuccessfulInsert { state }) => { let priority_id = TransactionPriorityId::new(state.priority(), transaction_id); vacant_entry.insert(state); - bytes_entry.freeze(bytes); // Push the transaction into the queue. - self.push_id_into_queue_with_remaining_capacity(priority_id, remaining_capacity) - } - Err(bytes) => { - bytes_entry.free_space(bytes); - false + self.inner + .push_id_into_queue_with_remaining_capacity(priority_id, remaining_capacity) } - } - } - - // This is re-implemented since we need it to call `remove_by_id` on this - // struct rather than `inner`. This is important because we need to return - // the `Bytes` to the pool. - /// Returns true if packet was dropped due to capacity limits. - fn push_id_into_queue_with_remaining_capacity( - &mut self, - priority_id: TransactionPriorityId, - remaining_capacity: usize, - ) -> bool { - if remaining_capacity == 0 { - let popped_id = self.inner.priority_queue.push_pop_min(priority_id); - self.remove_by_id(popped_id.id); - true - } else { - self.inner.priority_queue.push(priority_id); - false + Err(_) => false, } } } -impl StateContainer>> +impl StateContainer>> for TransactionViewStateContainer { fn with_capacity(capacity: usize) -> Self { let inner = TransactionStateContainer::with_capacity(capacity); let bytes_buffer = (0..inner.id_to_transaction_state.capacity()) - .map(|_| { - MaybeBytes::BytesMut({ - let mut bytes = BytesMut::zeroed(PACKET_DATA_SIZE); - bytes.clear(); - bytes - }) - }) + .map(|_| Arc::new(Vec::with_capacity(PACKET_DATA_SIZE))) .collect::>() .into_boxed_slice(); Self { @@ -334,7 +289,8 @@ impl StateContainer>> fn get_mut_transaction_state( &mut self, id: TransactionId, - ) -> Option<&mut TransactionState>>> { + ) -> Option<&mut TransactionState>>> + { self.inner.get_mut_transaction_state(id) } @@ -342,32 +298,19 @@ impl StateContainer>> fn get_transaction_ttl( &self, id: TransactionId, - ) -> Option<&SanitizedTransactionTTL>>> { + ) -> Option<&SanitizedTransactionTTL>>> + { self.inner.get_transaction_ttl(id) } #[inline] fn push_id_into_queue(&mut self, priority_id: TransactionPriorityId) -> bool { - self.push_id_into_queue_with_remaining_capacity(priority_id, self.remaining_capacity()) + self.inner.push_id_into_queue(priority_id) } + #[inline] fn remove_by_id(&mut self, id: TransactionId) { - // Remove the entry from the map: - // 1. If it was unprocessed, this will drop the `Bytes` held. - // 2. If it was scheduled, this just marks the entry as removed. - let _ = self.inner.id_to_transaction_state.remove(id); - - // Clear the bytes buffer. - let bytes_entry = &mut self.bytes_buffer[id]; - let MaybeBytes::Bytes(bytes) = core::mem::replace(bytes_entry, MaybeBytes::None) else { - unreachable!("invalid state"); - }; - - // Return the `Bytes` to the pool. - let bytes = bytes - .try_into_mut() - .expect("all `Bytes` instances must be dropped"); - bytes_entry.free_space(bytes); + self.inner.remove_by_id(id); } #[inline] From 1a059162997387921bc01fe2464cee24d2b738c0 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Mon, 9 Dec 2024 10:27:48 -0600 Subject: [PATCH 16/30] only warn about forwarding if not already using Sdk --- core/src/banking_stage.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/core/src/banking_stage.rs b/core/src/banking_stage.rs index 0d20d354a26401..fb77f899a92caf 100644 --- a/core/src/banking_stage.rs +++ b/core/src/banking_stage.rs @@ -489,14 +489,15 @@ impl BankingStage { )); } - let transaction_struct = if enable_forwarding { - warn!( + let transaction_struct = + if enable_forwarding && !matches!(transaction_struct, TransactionStructure::Sdk) { + warn!( "Forwarding only supported for `Sdk` transaction struct. Overriding to use `Sdk`." ); - TransactionStructure::Sdk - } else { - transaction_struct - }; + TransactionStructure::Sdk + } else { + transaction_struct + }; match transaction_struct { TransactionStructure::Sdk => { From 183a4fdb697549e736dfc6858c8124acbdb7e3a0 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Mon, 9 Dec 2024 14:18:05 -0600 Subject: [PATCH 17/30] fix bug on should_forward --- .../banking_stage/transaction_scheduler/transaction_state.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/banking_stage/transaction_scheduler/transaction_state.rs b/core/src/banking_stage/transaction_scheduler/transaction_state.rs index 1511463ecb213f..80ee0eff88818d 100644 --- a/core/src/banking_stage/transaction_scheduler/transaction_state.rs +++ b/core/src/banking_stage/transaction_scheduler/transaction_state.rs @@ -59,10 +59,10 @@ impl TransactionState { priority: u64, cost: u64, ) -> Self { - let should_forward = !packet + let should_forward = packet .as_ref() .map(|packet| { - packet.original_packet().meta().forwarded() + !packet.original_packet().meta().forwarded() && packet.original_packet().meta().is_from_staked_node() }) .unwrap_or_default(); From da2420deb3d8a3d27c3ac3d7564cd1eb94742ce3 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Mon, 9 Dec 2024 14:26:15 -0600 Subject: [PATCH 18/30] test_initialize_should_forward --- .../transaction_state.rs | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/core/src/banking_stage/transaction_scheduler/transaction_state.rs b/core/src/banking_stage/transaction_scheduler/transaction_state.rs index 80ee0eff88818d..e9ab55e4004ece 100644 --- a/core/src/banking_stage/transaction_scheduler/transaction_state.rs +++ b/core/src/banking_stage/transaction_scheduler/transaction_state.rs @@ -2,6 +2,7 @@ use { crate::banking_stage::{ immutable_deserialized_packet::ImmutableDeserializedPacket, scheduler_messages::MaxAge, }, + solana_sdk::packet::{self}, std::sync::Arc, }; @@ -61,10 +62,7 @@ impl TransactionState { ) -> Self { let should_forward = packet .as_ref() - .map(|packet| { - !packet.original_packet().meta().forwarded() - && packet.original_packet().meta().is_from_staked_node() - }) + .map(|packet| initialize_should_forward(packet.original_packet().meta())) .unwrap_or_default(); Self::Unprocessed { transaction_ttl, @@ -211,10 +209,15 @@ impl TransactionState { } } +fn initialize_should_forward(meta: &packet::Meta) -> bool { + !meta.forwarded() && meta.is_from_staked_node() +} + #[cfg(test)] mod tests { use { super::*, + packet::PacketFlags, solana_runtime_transaction::runtime_transaction::RuntimeTransaction, solana_sdk::{ compute_budget::ComputeBudgetInstruction, @@ -370,4 +373,23 @@ mod tests { )); assert_eq!(transaction_ttl.max_age, MaxAge::MAX); } + + #[test] + fn test_initialize_should_forward() { + let meta = packet::Meta::default(); + assert!(!initialize_should_forward(&meta)); + + let mut meta = packet::Meta::default(); + meta.flags.set(PacketFlags::FORWARDED, true); + assert!(!initialize_should_forward(&meta)); + + let mut meta = packet::Meta::default(); + meta.set_from_staked_node(true); + assert!(initialize_should_forward(&meta)); + + let mut meta = packet::Meta::default(); + meta.flags.set(PacketFlags::FORWARDED, true); + meta.set_from_staked_node(true); + assert!(!initialize_should_forward(&meta)); + } } From 393a8f8bb5657af6689d20c8e048239226c4d6ce Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Mon, 9 Dec 2024 14:31:16 -0600 Subject: [PATCH 19/30] remove option --- .../transaction_scheduler/prio_graph_scheduler.rs | 2 +- .../transaction_scheduler/receive_and_buffer.rs | 2 +- .../transaction_scheduler/transaction_state_container.rs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) 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 7b016d41fc43c7..726d21284ea23e 100644 --- a/core/src/banking_stage/transaction_scheduler/prio_graph_scheduler.rs +++ b/core/src/banking_stage/transaction_scheduler/prio_graph_scheduler.rs @@ -726,7 +726,7 @@ mod tests { const TEST_TRANSACTION_COST: u64 = 5000; container.insert_new_transaction( transaction_ttl, - Some(packet), + packet, compute_unit_price, TEST_TRANSACTION_COST, ); diff --git a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs index 161e4e854b7242..903c1a1ef94e16 100644 --- a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs +++ b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs @@ -256,7 +256,7 @@ impl SanitizedTransactionReceiveAndBuffer { max_age, }; - if container.insert_new_transaction(transaction_ttl, Some(packet), priority, cost) { + if container.insert_new_transaction(transaction_ttl, packet, priority, cost) { saturating_add_assign!(num_dropped_on_capacity, 1); } saturating_add_assign!(num_buffered, 1); 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 654a89bf78e6cb..732633a8df26c3 100644 --- a/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs +++ b/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs @@ -158,7 +158,7 @@ impl TransactionStateContainer { pub(crate) fn insert_new_transaction( &mut self, transaction_ttl: SanitizedTransactionTTL, - packet: Option>, + packet: Arc, priority: u64, cost: u64, ) -> bool { @@ -170,7 +170,7 @@ impl TransactionStateContainer { let transaction_id = entry.key(); entry.insert(TransactionState::new( transaction_ttl, - packet, + Some(packet), priority, cost, )); @@ -377,7 +377,7 @@ mod tests { ) { for priority in 0..num as u64 { let (transaction_ttl, packet, priority, cost) = test_transaction(priority); - container.insert_new_transaction(transaction_ttl, Some(packet), priority, cost); + container.insert_new_transaction(transaction_ttl, packet, priority, cost); } } From 9c41a65b9521309098057cc8f1ddcf02641ea3b6 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Mon, 9 Dec 2024 14:36:34 -0600 Subject: [PATCH 20/30] remove impl TransactionData for Bytes --- Cargo.lock | 1 - programs/sbf/Cargo.lock | 1 - svm/examples/Cargo.lock | 1 - transaction-view/Cargo.toml | 1 - transaction-view/src/transaction_data.rs | 7 ------- 5 files changed, 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 16cf780ce17255..a5807893be877d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,7 +259,6 @@ version = "2.2.0" dependencies = [ "agave-transaction-view", "bincode", - "bytes", "criterion", "solana-hash", "solana-instruction", diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index 66b293cc058cb8..9c0621fffde1bc 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -87,7 +87,6 @@ dependencies = [ name = "agave-transaction-view" version = "2.2.0" dependencies = [ - "bytes", "solana-hash", "solana-message", "solana-packet", diff --git a/svm/examples/Cargo.lock b/svm/examples/Cargo.lock index a8f62e764e303e..a5b494f5b4af1b 100644 --- a/svm/examples/Cargo.lock +++ b/svm/examples/Cargo.lock @@ -87,7 +87,6 @@ dependencies = [ name = "agave-transaction-view" version = "2.2.0" dependencies = [ - "bytes", "solana-hash", "solana-message", "solana-packet", diff --git a/transaction-view/Cargo.toml b/transaction-view/Cargo.toml index a95dc5bd44a6cb..575e2022139d50 100644 --- a/transaction-view/Cargo.toml +++ b/transaction-view/Cargo.toml @@ -10,7 +10,6 @@ license = { workspace = true } edition = { workspace = true } [dependencies] -bytes = { workspace = true } solana-hash = { workspace = true } solana-message = { workspace = true } solana-packet = { workspace = true } diff --git a/transaction-view/src/transaction_data.rs b/transaction-view/src/transaction_data.rs index df20f96b65a39c..323c085660fa85 100644 --- a/transaction-view/src/transaction_data.rs +++ b/transaction-view/src/transaction_data.rs @@ -11,13 +11,6 @@ impl TransactionData for &[u8] { } } -impl TransactionData for bytes::Bytes { - #[inline] - fn data(&self) -> &[u8] { - self.as_ref() - } -} - impl TransactionData for std::sync::Arc> { #[inline] fn data(&self) -> &[u8] { From 4e353598d19746f80dcf5f7c359879cb3155bcdc Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Mon, 9 Dec 2024 15:21:56 -0600 Subject: [PATCH 21/30] get_vacant_map_entry --- .../transaction_state_container.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) 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 732633a8df26c3..267a22d96f95f2 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 { agave_transaction_view::resolved_transaction_view::ResolvedTransactionView, itertools::MinMaxResult, min_max_heap::MinMaxHeap, - slab::Slab, + slab::{Slab, VacantEntry}, solana_runtime_transaction::{ runtime_transaction::RuntimeTransaction, transaction_with_meta::TransactionWithMeta, }, @@ -166,7 +166,7 @@ impl TransactionStateContainer { // the next vacant entry. i.e. get the size before we insert. let remaining_capacity = self.remaining_capacity(); let priority_id = { - let entry = self.id_to_transaction_state.vacant_entry(); + let entry = self.get_vacant_map_entry(); let transaction_id = entry.key(); entry.insert(TransactionState::new( transaction_ttl, @@ -194,6 +194,11 @@ impl TransactionStateContainer { false } } + + fn get_vacant_map_entry(&mut self) -> VacantEntry> { + assert!(self.id_to_transaction_state.len() < self.id_to_transaction_state.capacity()); + self.id_to_transaction_state.vacant_entry() + } } pub type SharedBytes = Arc>; @@ -217,15 +222,11 @@ impl TransactionViewStateContainer { data: &[u8], f: impl FnOnce(SharedBytes) -> Result, ) -> bool { - if self.inner.id_to_transaction_state.len() == self.inner.id_to_transaction_state.capacity() - { - return true; - } // Get remaining capacity before inserting. let remaining_capacity = self.remaining_capacity(); // Get a vacant entry in the slab. - let vacant_entry = self.inner.id_to_transaction_state.vacant_entry(); + let vacant_entry = self.inner.get_vacant_map_entry(); let transaction_id = vacant_entry.key(); // Get the vacant space in the bytes buffer. @@ -240,7 +241,7 @@ impl TransactionViewStateContainer { bytes.extend_from_slice(data); } - // Attempt to insert the transaction, storing the frozen bytes back into bytes buffer. + // Attempt to insert the transaction. match f(Arc::clone(bytes_entry)) { Ok(SuccessfulInsert { state }) => { let priority_id = TransactionPriorityId::new(state.priority(), transaction_id); From 58e3aae1d301675dc62f8a6fa419e0f567292977 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Mon, 9 Dec 2024 15:29:58 -0600 Subject: [PATCH 22/30] Safety comment --- .../transaction_scheduler/transaction_state_container.rs | 8 ++++++++ 1 file changed, 8 insertions(+) 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 267a22d96f95f2..bd2da4a3a72462 100644 --- a/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs +++ b/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs @@ -233,6 +233,14 @@ impl TransactionViewStateContainer { let bytes_entry = &mut self.bytes_buffer[transaction_id]; // Assert the entry is unique, then copy the packet data. { + // The strong count must be 1 here. These are only cloned into the + // inner container below, wrapped by a `ResolveTransactionView`, + // which does not expose the backing memory (the `Arc`), or + // implement `Clone`. + // This could only fail if there is a bug in the container that the + // entry in the slab was not cleared. However, since we share + // indexing between the slab and our `bytes_buffer`, we know that + // `vacant_entry` is not occupied. assert_eq!(Arc::strong_count(bytes_entry), 1, "entry must be unique"); let bytes = Arc::make_mut(bytes_entry); From aa2bff1fc832715bdd88ed40798a4837d315b54e Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Mon, 9 Dec 2024 15:39:15 -0600 Subject: [PATCH 23/30] remove SuccessfulInsert type --- .../transaction_scheduler/receive_and_buffer.rs | 6 ++---- .../transaction_state_container.rs | 13 +++++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs index 903c1a1ef94e16..986a992af0cdf8 100644 --- a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs +++ b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs @@ -2,9 +2,7 @@ use { super::{ scheduler_metrics::{SchedulerCountMetrics, SchedulerTimingMetrics}, transaction_state::TransactionState, - transaction_state_container::{ - SharedBytes, StateContainer, SuccessfulInsert, TransactionViewStateContainer, - }, + transaction_state_container::{SharedBytes, StateContainer, TransactionViewStateContainer}, }, crate::banking_stage::{ decision_maker::BufferedPacketsDecision, @@ -425,7 +423,7 @@ impl TransactionViewReceiveAndBuffer { ) { Ok(state) => { num_buffered += 1; - Ok(SuccessfulInsert { state }) + Ok(state) } Err(()) => { num_dropped_on_receive += 1; 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 bd2da4a3a72462..2b3b937bec53a3 100644 --- a/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs +++ b/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs @@ -211,16 +211,17 @@ pub struct TransactionViewStateContainer { bytes_buffer: Box<[SharedBytes]>, } -pub(crate) struct SuccessfulInsert { - pub state: TransactionState>>, -} - impl TransactionViewStateContainer { /// Returns true if packet was dropped due to capacity limits. pub(crate) fn try_insert_with_data( &mut self, data: &[u8], - f: impl FnOnce(SharedBytes) -> Result, + f: impl FnOnce( + SharedBytes, + ) -> Result< + TransactionState>>, + (), + >, ) -> bool { // Get remaining capacity before inserting. let remaining_capacity = self.remaining_capacity(); @@ -251,7 +252,7 @@ impl TransactionViewStateContainer { // Attempt to insert the transaction. match f(Arc::clone(bytes_entry)) { - Ok(SuccessfulInsert { state }) => { + Ok(state) => { let priority_id = TransactionPriorityId::new(state.priority(), transaction_id); vacant_entry.insert(state); From c215f863c450c4c38a317d9283b229fe8fca3770 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Mon, 9 Dec 2024 15:42:19 -0600 Subject: [PATCH 24/30] type aliases --- .../receive_and_buffer.rs | 7 ++++--- .../transaction_state_container.rs | 21 +++++++------------ 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs index 986a992af0cdf8..25e67232160ef9 100644 --- a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs +++ b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs @@ -2,7 +2,9 @@ use { super::{ scheduler_metrics::{SchedulerCountMetrics, SchedulerTimingMetrics}, transaction_state::TransactionState, - transaction_state_container::{SharedBytes, StateContainer, TransactionViewStateContainer}, + transaction_state_container::{ + SharedBytes, StateContainer, TransactionViewState, TransactionViewStateContainer, + }, }, crate::banking_stage::{ decision_maker::BufferedPacketsDecision, @@ -458,8 +460,7 @@ impl TransactionViewReceiveAndBuffer { alt_resolved_slot: Slot, sanitized_epoch: Epoch, transaction_account_lock_limit: usize, - ) -> Result>>, ()> - { + ) -> Result { // Parsing and basic sanitization checks let Ok(view) = SanitizedTransactionView::try_new_sanitized(bytes) else { return Err(()); 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 2b3b937bec53a3..c9c8ddbde751e5 100644 --- a/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs +++ b/core/src/banking_stage/transaction_scheduler/transaction_state_container.rs @@ -202,12 +202,14 @@ impl TransactionStateContainer { } pub type SharedBytes = Arc>; +pub(crate) type RuntimeTransactionView = RuntimeTransaction>; +pub(crate) type TransactionViewState = TransactionState; /// A wrapper around `TransactionStateContainer` that allows re-uses /// pre-allocated `Bytes` to copy packet data into and use for serialization. /// This is used to avoid allocations in parsing transactions. pub struct TransactionViewStateContainer { - inner: TransactionStateContainer>>, + inner: TransactionStateContainer, bytes_buffer: Box<[SharedBytes]>, } @@ -216,12 +218,7 @@ impl TransactionViewStateContainer { pub(crate) fn try_insert_with_data( &mut self, data: &[u8], - f: impl FnOnce( - SharedBytes, - ) -> Result< - TransactionState>>, - (), - >, + f: impl FnOnce(SharedBytes) -> Result, ()>, ) -> bool { // Get remaining capacity before inserting. let remaining_capacity = self.remaining_capacity(); @@ -265,9 +262,7 @@ impl TransactionViewStateContainer { } } -impl StateContainer>> - for TransactionViewStateContainer -{ +impl StateContainer for TransactionViewStateContainer { fn with_capacity(capacity: usize) -> Self { let inner = TransactionStateContainer::with_capacity(capacity); let bytes_buffer = (0..inner.id_to_transaction_state.capacity()) @@ -299,8 +294,7 @@ impl StateContainer>> fn get_mut_transaction_state( &mut self, id: TransactionId, - ) -> Option<&mut TransactionState>>> - { + ) -> Option<&mut TransactionViewState> { self.inner.get_mut_transaction_state(id) } @@ -308,8 +302,7 @@ impl StateContainer>> fn get_transaction_ttl( &self, id: TransactionId, - ) -> Option<&SanitizedTransactionTTL>>> - { + ) -> Option<&SanitizedTransactionTTL> { self.inner.get_transaction_ttl(id) } From fcccad182980c2457eafec3c5f266bb26ca32746 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Mon, 9 Dec 2024 15:45:44 -0600 Subject: [PATCH 25/30] remove explicit drop --- .../transaction_scheduler/prio_graph_scheduler.rs | 5 ----- 1 file changed, 5 deletions(-) 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 726d21284ea23e..8edebc1f80c200 100644 --- a/core/src/banking_stage/transaction_scheduler/prio_graph_scheduler.rs +++ b/core/src/banking_stage/transaction_scheduler/prio_graph_scheduler.rs @@ -378,11 +378,6 @@ impl PrioGraphScheduler { continue; } } - // Transaction must be dropped before removing, since we - // currently have ownership of the transaction, and - // therefore may have a reference to the backing-memory - // that the container expects to be free. - drop(transaction); container.remove_by_id(id); } From 6e394f4925a21706e6422684928318ea3f3ba60e Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Tue, 10 Dec 2024 09:08:46 -0600 Subject: [PATCH 26/30] Remove timeout from testing --- .../receive_and_buffer.rs | 43 +++++++++++-------- .../scheduler_controller.rs | 18 ++++++-- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs index 25e67232160ef9..cee611a7163e69 100644 --- a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs +++ b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs @@ -49,15 +49,15 @@ pub(crate) trait ReceiveAndBuffer { type Transaction: TransactionWithMeta + Send + Sync; type Container: StateContainer + Send + Sync; - /// Returns false only if no packets were received - /// AND the receiver is disconnected. + /// Return Err if the receiver is disconnected AND no packets were + /// received. Otherwise return Ok(num_received). fn receive_and_buffer_packets( &mut self, container: &mut Self::Container, timing_metrics: &mut SchedulerTimingMetrics, count_metrics: &mut SchedulerCountMetrics, decision: &BufferedPacketsDecision, - ) -> bool; + ) -> Result; } pub(crate) struct SanitizedTransactionReceiveAndBuffer { @@ -79,7 +79,7 @@ impl ReceiveAndBuffer for SanitizedTransactionReceiveAndBuffer { timing_metrics: &mut SchedulerTimingMetrics, count_metrics: &mut SchedulerCountMetrics, decision: &BufferedPacketsDecision, - ) -> bool { + ) -> Result { let remaining_queue_capacity = container.remaining_capacity(); const MAX_PACKET_RECEIVE_TIME: Duration = Duration::from_millis(10); @@ -109,7 +109,7 @@ impl ReceiveAndBuffer for SanitizedTransactionReceiveAndBuffer { saturating_add_assign!(timing_metrics.receive_time_us, receive_time_us); }); - match received_packet_results { + let num_received = match received_packet_results { Ok(receive_packet_results) => { let num_received_packets = receive_packet_results.deserialized_packets.len(); @@ -135,12 +135,13 @@ impl ReceiveAndBuffer for SanitizedTransactionReceiveAndBuffer { ); }); } + num_received_packets } - Err(RecvTimeoutError::Timeout) => {} - Err(RecvTimeoutError::Disconnected) => return false, - } + Err(RecvTimeoutError::Timeout) => 0, + Err(RecvTimeoutError::Disconnected) => return Err(()), + }; - true + Ok(num_received) } } @@ -307,7 +308,7 @@ impl ReceiveAndBuffer for TransactionViewReceiveAndBuffer { timing_metrics: &mut SchedulerTimingMetrics, count_metrics: &mut SchedulerCountMetrics, decision: &BufferedPacketsDecision, - ) -> bool { + ) -> Result { let (root_bank, working_bank) = { let bank_forks = self.bank_forks.read().unwrap(); let root_bank = bank_forks.root_bank(); @@ -318,6 +319,7 @@ impl ReceiveAndBuffer for TransactionViewReceiveAndBuffer { // Receive packet batches. const TIMEOUT: Duration = Duration::from_millis(10); let start = Instant::now(); + let mut num_received = 0; let mut received_message = false; // If not leader/unknown, do a blocking-receive initially. This lets @@ -336,7 +338,7 @@ impl ReceiveAndBuffer for TransactionViewReceiveAndBuffer { match self.receiver.recv_timeout(TIMEOUT) { Ok(packet_batch_message) => { received_message = true; - self.handle_packet_batch_message( + num_received += self.handle_packet_batch_message( container, timing_metrics, count_metrics, @@ -346,9 +348,9 @@ impl ReceiveAndBuffer for TransactionViewReceiveAndBuffer { packet_batch_message, ); } - Err(RecvTimeoutError::Timeout) => return true, + Err(RecvTimeoutError::Timeout) => return Ok(num_received), Err(RecvTimeoutError::Disconnected) => { - return received_message; + return received_message.then_some(num_received).ok_or(()); } } } @@ -357,7 +359,7 @@ impl ReceiveAndBuffer for TransactionViewReceiveAndBuffer { match self.receiver.try_recv() { Ok(packet_batch_message) => { received_message = true; - self.handle_packet_batch_message( + num_received += self.handle_packet_batch_message( container, timing_metrics, count_metrics, @@ -367,18 +369,19 @@ impl ReceiveAndBuffer for TransactionViewReceiveAndBuffer { packet_batch_message, ); } - Err(TryRecvError::Empty) => return true, + Err(TryRecvError::Empty) => return Ok(num_received), Err(TryRecvError::Disconnected) => { - return received_message; + return received_message.then_some(num_received).ok_or(()); } } } - true + Ok(num_received) } } impl TransactionViewReceiveAndBuffer { + /// Return number of received packets. fn handle_packet_batch_message( &mut self, container: &mut TransactionViewStateContainer, @@ -388,10 +391,10 @@ impl TransactionViewReceiveAndBuffer { root_bank: &Bank, working_bank: &Bank, packet_batch_message: BankingPacketBatch, - ) { + ) -> usize { // Do not support forwarding - only add support for this if we really need it. if matches!(decision, BufferedPacketsDecision::Forward) { - return; + return 0; } let start = Instant::now(); @@ -451,6 +454,8 @@ impl TransactionViewReceiveAndBuffer { ); saturating_add_assign!(count_metrics.num_dropped_on_receive, num_dropped_on_receive); }); + + num_received } fn try_handle_packet( diff --git a/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs b/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs index 0d391824d0e9f8..e4e4388cff9c0d 100644 --- a/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs +++ b/core/src/banking_stage/transaction_scheduler/scheduler_controller.rs @@ -107,7 +107,7 @@ impl SchedulerController { self.process_transactions(&decision)?; self.receive_completed()?; - if !self.receive_and_buffer_packets(&decision) { + if self.receive_and_buffer_packets(&decision).is_err() { break; } // Report metrics only if there is data. @@ -421,7 +421,10 @@ impl SchedulerController { } /// Returns whether the packet receiver is still connected. - fn receive_and_buffer_packets(&mut self, decision: &BufferedPacketsDecision) -> bool { + fn receive_and_buffer_packets( + &mut self, + decision: &BufferedPacketsDecision, + ) -> Result { self.receive_and_buffer.receive_and_buffer_packets( &mut self.container, &mut self.timing_metrics, @@ -622,7 +625,16 @@ mod tests { .make_consume_or_forward_decision(); assert!(matches!(decision, BufferedPacketsDecision::Consume(_))); assert!(scheduler_controller.receive_completed().is_ok()); - assert!(scheduler_controller.receive_and_buffer_packets(&decision)); + + // Time is not a reliable way for deterministic testing. + // Loop here until no more packets are received, this avoids parallel + // tests from inconsistently timing out and not receiving + // from the channel. + while scheduler_controller + .receive_and_buffer_packets(&decision) + .map(|n| n > 0) + .unwrap_or_default() + {} assert!(scheduler_controller.process_transactions(&decision).is_ok()); } From 113b9ca4feb32aa5c25b6080ded210cd8f1b295c Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Fri, 3 Jan 2025 09:17:52 -0600 Subject: [PATCH 27/30] should_forward_from_meta --- .../transaction_scheduler/transaction_state.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/banking_stage/transaction_scheduler/transaction_state.rs b/core/src/banking_stage/transaction_scheduler/transaction_state.rs index e9ab55e4004ece..d684a2994c1900 100644 --- a/core/src/banking_stage/transaction_scheduler/transaction_state.rs +++ b/core/src/banking_stage/transaction_scheduler/transaction_state.rs @@ -62,7 +62,7 @@ impl TransactionState { ) -> Self { let should_forward = packet .as_ref() - .map(|packet| initialize_should_forward(packet.original_packet().meta())) + .map(|packet| should_forward_from_meta(packet.original_packet().meta())) .unwrap_or_default(); Self::Unprocessed { transaction_ttl, @@ -209,7 +209,7 @@ impl TransactionState { } } -fn initialize_should_forward(meta: &packet::Meta) -> bool { +fn should_forward_from_meta(meta: &packet::Meta) -> bool { !meta.forwarded() && meta.is_from_staked_node() } @@ -377,19 +377,19 @@ mod tests { #[test] fn test_initialize_should_forward() { let meta = packet::Meta::default(); - assert!(!initialize_should_forward(&meta)); + assert!(!should_forward_from_meta(&meta)); let mut meta = packet::Meta::default(); meta.flags.set(PacketFlags::FORWARDED, true); - assert!(!initialize_should_forward(&meta)); + assert!(!should_forward_from_meta(&meta)); let mut meta = packet::Meta::default(); meta.set_from_staked_node(true); - assert!(initialize_should_forward(&meta)); + assert!(should_forward_from_meta(&meta)); let mut meta = packet::Meta::default(); meta.flags.set(PacketFlags::FORWARDED, true); meta.set_from_staked_node(true); - assert!(!initialize_should_forward(&meta)); + assert!(!should_forward_from_meta(&meta)); } } From ecd7fb622aebfa3cdc090850cc112a9fac250a3e Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Fri, 3 Jan 2025 09:19:00 -0600 Subject: [PATCH 28/30] typo --- .../transaction_scheduler/receive_and_buffer.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs index cee611a7163e69..8b7c0385aebcc5 100644 --- a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs +++ b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs @@ -480,9 +480,9 @@ impl TransactionViewReceiveAndBuffer { }; // Check excessive pre-compiles. - let signature_detials = view.signature_details(); - let num_precompiles = signature_detials.num_ed25519_instruction_signatures() - + signature_detials.num_secp256k1_instruction_signatures(); + let signature_details = view.signature_details(); + let num_precompiles = signature_details.num_ed25519_instruction_signatures() + + signature_details.num_secp256k1_instruction_signatures(); if num_precompiles > MAX_ALLOWED_PRECOMPILE_SIGNATURES { return Err(()); } From 63bef6230452c2b468e29c9547dff8f22f021ad2 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Mon, 6 Jan 2025 16:38:54 -0600 Subject: [PATCH 29/30] secp256r1 --- .../banking_stage/transaction_scheduler/receive_and_buffer.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs index 8b7c0385aebcc5..70bf9c7b58075a 100644 --- a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs +++ b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs @@ -482,7 +482,8 @@ impl TransactionViewReceiveAndBuffer { // Check excessive pre-compiles. let signature_details = view.signature_details(); let num_precompiles = signature_details.num_ed25519_instruction_signatures() - + signature_details.num_secp256k1_instruction_signatures(); + + signature_details.num_secp256k1_instruction_signatures() + + signature_details.num_secp256r1_instruction_signatures(); if num_precompiles > MAX_ALLOWED_PRECOMPILE_SIGNATURES { return Err(()); } From 7fe132fb1c678c36330549eecb110b116162a13f Mon Sep 17 00:00:00 2001 From: Andrew Fitzgerald Date: Mon, 6 Jan 2025 16:40:16 -0600 Subject: [PATCH 30/30] vote-only discarding --- .../transaction_scheduler/receive_and_buffer.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs index 70bf9c7b58075a..9284c0f9ed5028 100644 --- a/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs +++ b/core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs @@ -479,6 +479,11 @@ impl TransactionViewReceiveAndBuffer { return Err(()); }; + // Discard non-vote packets if in vote-only mode. + if root_bank.vote_only_bank() && !view.is_simple_vote_transaction() { + return Err(()); + } + // Check excessive pre-compiles. let signature_details = view.signature_details(); let num_precompiles = signature_details.num_ed25519_instruction_signatures()