use {
crate::{
nonblocking::{
qos::{ConnectionContext, OpaqueStreamerCounter, QosController},
quic::{
CONNECTION_CLOSE_CODE_DISALLOWED, CONNECTION_CLOSE_REASON_DISALLOWED,
ClientConnectionTracker, ConnectionHandlerError, ConnectionPeerType,
ConnectionTable, ConnectionTableKey, ConnectionTableType, MAX_RTT, MIN_RTT,
get_connection_stake, update_open_connections_stat,
},
},
quic::{
DEFAULT_MAX_QUIC_CONNECTIONS_PER_STAKED_PEER, DEFAULT_MAX_STAKED_CONNECTIONS,
DEFAULT_MAX_STREAMS_PER_MS, StreamerStats,
},
streamer::StakedNodes,
},
quinn::{Connection, VarInt},
solana_net_utils::{banlist::Banlist, token_bucket::TokenBucket},
solana_pubkey::Pubkey,
solana_time_utils as timing,
std::{
future::Future,
sync::{
Arc, RwLock,
atomic::{AtomicU64, Ordering},
},
time::Duration,
},
tokio::{
sync::{
Mutex, MutexGuard,
mpsc::{Receiver, Sender, channel, error::TrySendError},
},
time::{MissedTickBehavior, interval, sleep},
},
tokio_util::sync::CancellationToken,
};
const STREAMS_IN_FLIGHT_MARGIN: u32 = 2;
const BANLIST_PRUNE_INTERVAL: Duration = Duration::from_hours(1);
const MAX_IN_FLIGHT_EVICTIONS: usize = 2_000;
pub struct SimpleQosBanlist {
banlist: Arc<Banlist<Pubkey>>,
eviction_sender: Sender<Pubkey>,
}
impl SimpleQosBanlist {
pub fn new() -> (Self, Receiver<Pubkey>) {
let (eviction_sender, eviction_receiver) = channel(MAX_IN_FLIGHT_EVICTIONS);
(
Self {
banlist: Arc::new(Banlist::default()),
eviction_sender,
},
eviction_receiver,
)
}
pub fn ban(&self, pubkey: Pubkey, timeout: Duration) -> bool {
let ret = self.banlist.ban(pubkey, timeout);
match self.eviction_sender.try_send(pubkey) {
Ok(()) => {}
Err(TrySendError::Full(pubkey)) => {
error!(
"Simple QoS banlist eviction queue full, dropping eviction request for \
{pubkey}"
);
}
Err(TrySendError::Closed(pubkey)) => {
info!(
"Simple QoS banlist eviction queue closed, dropping eviction request for \
{pubkey}"
);
}
}
ret
}
pub fn is_banned(&self, pubkey: &Pubkey) -> bool {
self.banlist.is_banned(pubkey)
}
fn spawn_connection_evictor(
&self,
mut eviction_receiver: Receiver<Pubkey>,
staked_connection_table: Arc<Mutex<ConnectionTable<TokenBucket>>>,
stats: Arc<StreamerStats>,
) {
let banlist = self.banlist.clone();
let _eviction_task = tokio::spawn(async move {
let mut prune_interval = interval(BANLIST_PRUNE_INTERVAL);
prune_interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
prune_interval.tick().await;
loop {
tokio::select! {
maybe_pubkey = eviction_receiver.recv() => {
let Some(pubkey) = maybe_pubkey else {
break;
};
let mut connection_table = staked_connection_table.lock().await;
let removed_connection_count = connection_table
.remove_connections_by_key(ConnectionTableKey::Pubkey(pubkey));
if removed_connection_count > 0 {
update_open_connections_stat(&stats, &connection_table);
stats
.connection_removed
.fetch_add(removed_connection_count, Ordering::Relaxed);
stats
.connection_removed_banned
.fetch_add(removed_connection_count, Ordering::Relaxed);
}
}
_ = prune_interval.tick() => {
banlist.prune();
}
}
}
});
}
}
#[derive(Clone)]
pub struct SimpleQosConfig {
pub max_streams_per_second: u64,
pub max_staked_connections: usize,
pub max_connections_per_peer: usize,
}
impl Default for SimpleQosConfig {
fn default() -> Self {
SimpleQosConfig {
max_streams_per_second: DEFAULT_MAX_STREAMS_PER_MS * 1000,
max_staked_connections: DEFAULT_MAX_STAKED_CONNECTIONS,
max_connections_per_peer: DEFAULT_MAX_QUIC_CONNECTIONS_PER_STAKED_PEER,
}
}
}
impl OpaqueStreamerCounter for TokenBucket {}
pub struct SimpleQos {
config: SimpleQosConfig,
stats: Arc<StreamerStats>,
staked_connection_table: Arc<Mutex<ConnectionTable<TokenBucket>>>,
staked_nodes: Arc<RwLock<StakedNodes>>,
pub(crate) banlist: Arc<SimpleQosBanlist>,
banlist_eviction_receiver: Option<Receiver<Pubkey>>,
}
impl SimpleQos {
pub fn new(
config: SimpleQosConfig,
stats: Arc<StreamerStats>,
staked_nodes: Arc<RwLock<StakedNodes>>,
cancel: CancellationToken,
) -> Self {
let (banlist, banlist_eviction_receiver) = SimpleQosBanlist::new();
let banlist = Arc::new(banlist);
Self {
config,
stats,
staked_nodes,
banlist,
banlist_eviction_receiver: Some(banlist_eviction_receiver),
staked_connection_table: Arc::new(Mutex::new(ConnectionTable::new(
ConnectionTableType::Staked,
cancel,
))),
}
}
fn cache_new_connection(
&self,
client_connection_tracker: ClientConnectionTracker,
connection: &Connection,
mut connection_table_l: MutexGuard<ConnectionTable<TokenBucket>>,
conn_context: &SimpleQosConnectionContext,
) -> Result<(Arc<AtomicU64>, CancellationToken, Arc<TokenBucket>), ConnectionHandlerError> {
let remote_addr = conn_context.remote_address;
let rtt = connection.rtt().clamp(MIN_RTT, MAX_RTT).as_millis() as u32;
let max_streams_in_flight = (self.config.max_streams_per_second as u32).saturating_mul(rtt)
/ 1000
* STREAMS_IN_FLIGHT_MARGIN;
let max_streams_in_flight = max_streams_in_flight.max(STREAMS_IN_FLIGHT_MARGIN);
connection.set_max_concurrent_uni_streams(VarInt::from_u32(max_streams_in_flight));
debug!(
"Peer type {:?}, from peer {}, max_streams {max_streams_in_flight}",
conn_context.peer_type(),
remote_addr,
);
let key = ConnectionTableKey::new(remote_addr.ip(), conn_context.remote_pubkey);
if let Some((last_update, cancel_connection, stream_counter)) = connection_table_l
.try_add_connection(
key,
remote_addr.port(),
client_connection_tracker,
Some(connection.clone()),
conn_context.peer_type(),
conn_context.last_update.clone(),
self.config.max_connections_per_peer,
|| {
Arc::new(TokenBucket::new(
self.config.max_streams_per_second,
self.config.max_streams_per_second,
self.config.max_streams_per_second as f64,
))
},
)
{
update_open_connections_stat(&self.stats, &connection_table_l);
drop(connection_table_l);
Ok((last_update, cancel_connection, stream_counter))
} else {
self.stats
.connection_add_failed
.fetch_add(1, Ordering::Relaxed);
Err(ConnectionHandlerError::ConnectionAddError)
}
}
}
#[derive(Clone)]
pub struct SimpleQosConnectionContext {
peer_type: ConnectionPeerType,
remote_pubkey: Option<Pubkey>,
remote_address: std::net::SocketAddr,
last_update: Arc<AtomicU64>,
stream_counter: Option<Arc<TokenBucket>>,
}
impl ConnectionContext for SimpleQosConnectionContext {
fn peer_type(&self) -> ConnectionPeerType {
self.peer_type
}
fn remote_pubkey(&self) -> Option<Pubkey> {
self.remote_pubkey
}
}
impl QosController<SimpleQosConnectionContext> for SimpleQos {
fn build_connection_context(&self, connection: &Connection) -> SimpleQosConnectionContext {
let (peer_type, remote_pubkey, _total_stake) =
get_connection_stake(connection, &self.staked_nodes).map_or(
(ConnectionPeerType::Unstaked, None, 0),
|(pubkey, stake, total_stake)| {
(ConnectionPeerType::Staked(stake), Some(pubkey), total_stake)
},
);
SimpleQosConnectionContext {
peer_type,
remote_pubkey,
remote_address: connection.remote_address(),
last_update: Arc::new(AtomicU64::new(timing::timestamp())),
stream_counter: None,
}
}
fn spawn_background_tasks(&mut self) {
let eviction_receiver = self
.banlist_eviction_receiver
.take()
.expect("Simple QoS banlist eviction task already spawned");
self.banlist.spawn_connection_evictor(
eviction_receiver,
self.staked_connection_table.clone(),
self.stats.clone(),
);
}
#[allow(clippy::manual_async_fn)]
fn try_add_connection(
&self,
client_connection_tracker: ClientConnectionTracker,
connection: &quinn::Connection,
conn_context: &mut SimpleQosConnectionContext,
) -> impl Future<Output = Option<CancellationToken>> + Send {
async move {
const PRUNE_RANDOM_SAMPLE_SIZE: usize = 2;
let remote_pubkey = conn_context.remote_pubkey()?;
if self.banlist.is_banned(&remote_pubkey) {
let remote_address = conn_context.remote_address;
info!("Rejecting banned pubkey {remote_pubkey} from {remote_address:?}");
self.stats
.connection_add_failed_banned
.fetch_add(1, Ordering::Relaxed);
connection.close(
CONNECTION_CLOSE_CODE_DISALLOWED.into(),
CONNECTION_CLOSE_REASON_DISALLOWED,
);
return None;
}
match conn_context.peer_type() {
ConnectionPeerType::Staked(stake) => {
let mut connection_table_l = self.staked_connection_table.lock().await;
if connection_table_l.total_size >= self.config.max_staked_connections {
let num_pruned =
connection_table_l.prune_random(PRUNE_RANDOM_SAMPLE_SIZE, stake);
debug!(
"Pruned {} staked connections to make room for new staked connection \
from {}",
num_pruned, conn_context.remote_address,
);
self.stats
.num_evictions_staked
.fetch_add(num_pruned, Ordering::Relaxed);
update_open_connections_stat(&self.stats, &connection_table_l);
}
if connection_table_l.total_size < self.config.max_staked_connections {
if let Ok((last_update, cancel_connection, stream_counter)) = self
.cache_new_connection(
client_connection_tracker,
connection,
connection_table_l,
conn_context,
)
{
self.stats
.connection_added_from_staked_peer
.fetch_add(1, Ordering::Relaxed);
conn_context.last_update = last_update;
conn_context.stream_counter = Some(stream_counter);
return Some(cancel_connection);
}
}
None
}
ConnectionPeerType::Unstaked => None,
}
}
}
fn on_stream_accepted(&self, _conn_context: &SimpleQosConnectionContext) {}
fn on_stream_error(&self, _conn_context: &SimpleQosConnectionContext) {}
fn on_stream_closed(&self, _conn_context: &SimpleQosConnectionContext) {}
#[allow(clippy::manual_async_fn)]
fn remove_connection(
&self,
conn_context: &SimpleQosConnectionContext,
connection: Connection,
) -> impl Future<Output = usize> + Send {
async move {
let stable_id = connection.stable_id();
let remote_addr = conn_context.remote_address;
let mut connection_table = self.staked_connection_table.lock().await;
let removed_connection_count = connection_table.remove_connection(
ConnectionTableKey::new(remote_addr.ip(), conn_context.remote_pubkey()),
remote_addr.port(),
stable_id,
);
update_open_connections_stat(&self.stats, &connection_table);
removed_connection_count
}
}
fn on_stream_finished(&self, context: &SimpleQosConnectionContext) {
context
.last_update
.store(timing::timestamp(), Ordering::Relaxed);
}
#[allow(clippy::manual_async_fn)]
fn on_new_stream(
&self,
context: &SimpleQosConnectionContext,
) -> impl Future<Output = ()> + Send {
async move {
let peer_type = context.peer_type();
let remote_addr = context.remote_address;
let stream_counter = context
.stream_counter
.as_ref()
.expect("This will always be populated before streams are opened");
while stream_counter.consume_tokens(1).is_err() {
debug!("Throttling stream from {remote_addr:?}");
self.stats.throttled_streams.fetch_add(1, Ordering::Relaxed);
match peer_type {
ConnectionPeerType::Unstaked => {
self.stats
.throttled_unstaked_streams
.fetch_add(1, Ordering::Relaxed);
}
ConnectionPeerType::Staked(_) => {
self.stats
.throttled_staked_streams
.fetch_add(1, Ordering::Relaxed);
}
}
let min_sleep = stream_counter.us_to_have_tokens(1).expect(
"Valid QoS configurations guarantee enough token bucket fits at least one \
token",
);
sleep(Duration::from_micros(min_sleep)).await;
}
}
}
fn max_concurrent_connections(&self) -> usize {
self.config.max_staked_connections * 5 / 4
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{
nonblocking::{
quic::{ConnectionTable, ConnectionTableType},
testing_utilities::get_client_config,
},
quic::{QuicStreamerConfig, StreamerStats, configure_server},
streamer::StakedNodes,
},
quinn::Endpoint,
solana_keypair::{Keypair, Signer},
solana_net_utils::sockets::bind_to_localhost_unique,
std::{
collections::HashMap,
sync::{
Arc, RwLock,
atomic::{AtomicU64, Ordering},
},
},
tokio_util::sync::CancellationToken,
};
async fn create_connection_with_keypairs(
server_keypair: &Keypair,
client_keypair: &Keypair,
) -> (Connection, Endpoint, Endpoint) {
let (server_config, _) =
configure_server(server_keypair, &QuicStreamerConfig::default()).unwrap();
let server_socket = bind_to_localhost_unique().expect("should bind - server");
let server_addr = server_socket.local_addr().unwrap();
let server_endpoint = Endpoint::new(
quinn::EndpointConfig::default(),
Some(server_config),
server_socket,
Arc::new(quinn::TokioRuntime),
)
.unwrap();
let client_socket = bind_to_localhost_unique().expect("should bind - client");
let mut client_endpoint = Endpoint::new(
quinn::EndpointConfig::default(),
None,
client_socket,
Arc::new(quinn::TokioRuntime),
)
.unwrap();
let client_config = get_client_config(client_keypair);
client_endpoint.set_default_client_config(client_config);
let server_connection_future = async {
let incoming = server_endpoint.accept().await.unwrap();
incoming.await.unwrap()
};
let client_connect_future = client_endpoint.connect(server_addr, "localhost").unwrap();
let (server_connection, client_connection) =
tokio::join!(server_connection_future, client_connect_future);
let _client_connection = client_connection.unwrap();
(server_connection, client_endpoint, server_endpoint)
}
async fn create_server_side_connection() -> (Connection, Endpoint, Endpoint) {
let server_keypair = Keypair::new();
let client_keypair = Keypair::new();
create_connection_with_keypairs(&server_keypair, &client_keypair).await
}
fn create_staked_nodes_with_keypairs(
server_keypair: &Keypair,
client_keypair: &Keypair,
stake_amount: u64,
) -> Arc<RwLock<StakedNodes>> {
let mut stakes = HashMap::new();
stakes.insert(server_keypair.pubkey(), stake_amount);
stakes.insert(client_keypair.pubkey(), stake_amount);
let overrides: HashMap<Pubkey, u64> = HashMap::new();
Arc::new(RwLock::new(StakedNodes::new(Arc::new(stakes), overrides)))
}
#[tokio::test]
async fn test_cache_new_connection_success() {
let cancel = CancellationToken::new();
let stats = Arc::new(StreamerStats::default());
let staked_nodes = Arc::new(RwLock::new(StakedNodes::default()));
let simple_qos = SimpleQos::new(
SimpleQosConfig::default(),
stats.clone(),
staked_nodes,
cancel.clone(),
);
let connection_table = ConnectionTable::new(ConnectionTableType::Staked, cancel);
let connection_table_guard = tokio::sync::Mutex::new(connection_table);
let connection_table_l = connection_table_guard.lock().await;
let client_tracker = ClientConnectionTracker {
stats: stats.clone(),
};
let (server_connection, _client_endpoint, _server_endpoint) =
create_server_side_connection().await;
let remote_addr = server_connection.remote_address();
let conn_context = SimpleQosConnectionContext {
peer_type: ConnectionPeerType::Staked(1000),
remote_pubkey: Some(Pubkey::new_unique()),
remote_address: remote_addr,
last_update: Arc::new(AtomicU64::new(0)),
stream_counter: None,
};
let result = simple_qos.cache_new_connection(
client_tracker,
&server_connection, connection_table_l,
&conn_context,
);
assert!(result.is_ok());
let (_last_update, cancel_token, _stream_counter) = result.unwrap();
assert!(!cancel_token.is_cancelled());
}
#[tokio::test]
async fn test_cache_new_connection_max_connections_reached() {
let cancel = CancellationToken::new();
let stats = Arc::new(StreamerStats::default());
let staked_nodes = Arc::new(RwLock::new(StakedNodes::default()));
let simple_qos = SimpleQos::new(
SimpleQosConfig {
max_connections_per_peer: 1,
..Default::default()
},
stats.clone(),
staked_nodes,
cancel.clone(),
);
let mut connection_table =
ConnectionTable::new(ConnectionTableType::Staked, cancel.clone());
let (connection1, _client_endpoint1, _server_endpoint1) =
create_server_side_connection().await;
let remote_addr = connection1.remote_address();
let key = ConnectionTableKey::new(remote_addr.ip(), None);
let client_tracker1 = ClientConnectionTracker {
stats: stats.clone(),
};
let _ = connection_table.try_add_connection(
key,
remote_addr.port(),
client_tracker1,
Some(connection1),
ConnectionPeerType::Staked(1000),
Arc::new(AtomicU64::new(0)),
1, || Arc::new(TokenBucket::new(1, 1, 1.0)),
);
let connection_table_guard = tokio::sync::Mutex::new(connection_table);
let connection_table_l = connection_table_guard.lock().await;
let (connection2, _client_endpoint2, _server_endpoint2) =
create_server_side_connection().await;
let client_tracker2 = ClientConnectionTracker {
stats: stats.clone(),
};
let conn_context = SimpleQosConnectionContext {
peer_type: ConnectionPeerType::Staked(1000),
remote_pubkey: None,
remote_address: remote_addr,
last_update: Arc::new(AtomicU64::new(0)),
stream_counter: None,
};
let result = simple_qos.cache_new_connection(
client_tracker2,
&connection2, connection_table_l,
&conn_context,
);
assert!(result.is_err());
assert_eq!(stats.connection_add_failed.load(Ordering::Relaxed), 1);
}
#[tokio::test]
async fn test_cache_new_connection_updates_stats() {
let cancel = CancellationToken::new();
let stats = Arc::new(StreamerStats::default());
let staked_nodes = Arc::new(RwLock::new(StakedNodes::default()));
let simple_qos = SimpleQos::new(
SimpleQosConfig::default(),
stats.clone(),
staked_nodes,
cancel.clone(),
);
let connection_table = ConnectionTable::new(ConnectionTableType::Staked, cancel);
let connection_table_guard = tokio::sync::Mutex::new(connection_table);
let connection_table_l = connection_table_guard.lock().await;
let client_tracker = ClientConnectionTracker {
stats: stats.clone(),
};
let (server_connection, _client_endpoint, _server_endpoint) =
create_server_side_connection().await;
let remote_addr = server_connection.remote_address();
let conn_context = SimpleQosConnectionContext {
peer_type: ConnectionPeerType::Staked(1000),
remote_pubkey: Some(Pubkey::new_unique()),
remote_address: remote_addr,
last_update: Arc::new(AtomicU64::new(0)),
stream_counter: None,
};
let initial_open_connections = stats.open_staked_connections.load(Ordering::Relaxed);
let result = simple_qos.cache_new_connection(
client_tracker,
&server_connection,
connection_table_l,
&conn_context,
);
if result.is_ok() {
assert!(
stats.open_staked_connections.load(Ordering::Relaxed) > initial_open_connections
);
}
}
#[tokio::test]
async fn test_build_connection_context_unstaked_peer() {
let cancel = CancellationToken::new();
let stats = Arc::new(StreamerStats::default());
let staked_nodes = Arc::new(RwLock::new(StakedNodes::default()));
let simple_qos = SimpleQos::new(
SimpleQosConfig::default(),
stats.clone(),
staked_nodes,
cancel.clone(),
);
let (server_connection, _client_endpoint, _server_endpoint) =
create_server_side_connection().await;
let context = simple_qos.build_connection_context(&server_connection);
assert!(matches!(context.peer_type(), ConnectionPeerType::Unstaked));
assert_eq!(context.remote_pubkey(), None);
assert_eq!(context.remote_address, server_connection.remote_address());
assert!(context.last_update.load(Ordering::Relaxed) > 0); assert!(context.stream_counter.is_none()); }
#[tokio::test]
async fn test_build_connection_context_staked_peer() {
let cancel = CancellationToken::new();
let stats = Arc::new(StreamerStats::default());
let server_keypair = Keypair::new();
let client_keypair = Keypair::new();
let stake_amount = 50_000_000;
let staked_nodes =
create_staked_nodes_with_keypairs(&server_keypair, &client_keypair, stake_amount);
let simple_qos = SimpleQos::new(
SimpleQosConfig::default(),
stats.clone(),
staked_nodes,
cancel.clone(),
);
let (server_connection, _client_endpoint, _server_endpoint) =
create_connection_with_keypairs(&server_keypair, &client_keypair).await;
let context = simple_qos.build_connection_context(&server_connection);
assert!(matches!(context.peer_type(), ConnectionPeerType::Staked(_)));
if let ConnectionPeerType::Staked(stake) = context.peer_type() {
assert_eq!(stake, stake_amount);
}
assert_eq!(context.remote_pubkey(), Some(client_keypair.pubkey()));
assert_eq!(context.remote_address, server_connection.remote_address());
assert!(context.last_update.load(Ordering::Relaxed) > 0); assert!(context.stream_counter.is_none()); }
#[tokio::test]
async fn test_try_add_connection_staked_peer_success() {
let cancel = CancellationToken::new();
let stats = Arc::new(StreamerStats::default());
let server_keypair = Keypair::new();
let client_keypair = Keypair::new();
let stake_amount = 50_000_000;
let staked_nodes =
create_staked_nodes_with_keypairs(&server_keypair, &client_keypair, stake_amount);
let simple_qos = SimpleQos::new(
SimpleQosConfig::default(),
stats.clone(),
staked_nodes,
cancel.clone(),
);
let client_tracker = ClientConnectionTracker {
stats: stats.clone(),
};
let (server_connection, _client_endpoint, _server_endpoint) =
create_connection_with_keypairs(&server_keypair, &client_keypair).await;
let mut conn_context = simple_qos.build_connection_context(&server_connection);
let result = simple_qos
.try_add_connection(client_tracker, &server_connection, &mut conn_context)
.await;
assert!(result.is_some());
let cancel_token = result.unwrap();
assert!(!cancel_token.is_cancelled());
assert!(conn_context.stream_counter.is_some());
assert_eq!(
stats
.connection_added_from_staked_peer
.load(Ordering::Relaxed),
1
);
}
#[tokio::test]
async fn test_try_add_connection_unstaked_peer_rejected() {
let cancel = CancellationToken::new();
let stats = Arc::new(StreamerStats::default());
let staked_nodes = Arc::new(RwLock::new(StakedNodes::default()));
let simple_qos = SimpleQos::new(
SimpleQosConfig::default(),
stats.clone(),
staked_nodes,
cancel.clone(),
);
let client_tracker = ClientConnectionTracker {
stats: stats.clone(),
};
let (server_connection, _client_endpoint, _server_endpoint) =
create_server_side_connection().await;
let mut conn_context = simple_qos.build_connection_context(&server_connection);
let result = simple_qos
.try_add_connection(client_tracker, &server_connection, &mut conn_context)
.await;
assert!(result.is_none());
assert!(conn_context.stream_counter.is_none());
assert_eq!(
stats
.connection_added_from_staked_peer
.load(Ordering::Relaxed),
0
);
}
#[tokio::test]
async fn test_try_add_connection_banned_pubkey_rejected() {
let cancel = CancellationToken::new();
let stats = Arc::new(StreamerStats::default());
let server_keypair = Keypair::new();
let client_keypair = Keypair::new();
let staked_nodes =
create_staked_nodes_with_keypairs(&server_keypair, &client_keypair, 50_000_000);
let simple_qos = SimpleQos::new(
SimpleQosConfig::default(),
stats.clone(),
staked_nodes,
cancel,
);
simple_qos
.banlist
.ban(client_keypair.pubkey(), Duration::from_secs(30));
let client_tracker = ClientConnectionTracker {
stats: stats.clone(),
};
let (server_connection, _client_endpoint, _server_endpoint) =
create_connection_with_keypairs(&server_keypair, &client_keypair).await;
let mut conn_context = simple_qos.build_connection_context(&server_connection);
let result = simple_qos
.try_add_connection(client_tracker, &server_connection, &mut conn_context)
.await;
assert!(result.is_none());
assert_eq!(
stats.connection_add_failed_banned.load(Ordering::Relaxed),
1
);
}
#[tokio::test]
async fn test_try_add_connection_max_staked_connections_with_pruning() {
let cancel = CancellationToken::new();
let stats = Arc::new(StreamerStats::default());
let server_keypair1 = Keypair::new();
let client_keypair1 = Keypair::new();
let server_keypair2 = Keypair::new();
let client_keypair2 = Keypair::new();
let stake_amount = 50_000_000;
let mut stakes = HashMap::new();
stakes.insert(server_keypair1.pubkey(), stake_amount);
stakes.insert(client_keypair1.pubkey(), stake_amount);
stakes.insert(server_keypair2.pubkey(), stake_amount);
stakes.insert(client_keypair2.pubkey(), stake_amount * 2);
let overrides: HashMap<Pubkey, u64> = HashMap::new();
let staked_nodes = Arc::new(RwLock::new(StakedNodes::new(Arc::new(stakes), overrides)));
let simple_qos = SimpleQos::new(
SimpleQosConfig {
max_staked_connections: 1,
..Default::default()
},
stats.clone(),
staked_nodes,
cancel.clone(),
);
let client_tracker1 = ClientConnectionTracker {
stats: stats.clone(),
};
let (server_connection1, _client_endpoint1, _server_endpoint1) =
create_connection_with_keypairs(&server_keypair1, &client_keypair1).await;
let mut conn_context1 = simple_qos.build_connection_context(&server_connection1);
let result1 = simple_qos
.try_add_connection(client_tracker1, &server_connection1, &mut conn_context1)
.await;
assert!(result1.is_some());
let client_tracker2 = ClientConnectionTracker {
stats: stats.clone(),
};
let (server_connection2, _client_endpoint2, _server_endpoint2) =
create_connection_with_keypairs(&server_keypair2, &client_keypair2).await;
let mut conn_context2 = simple_qos.build_connection_context(&server_connection2);
let result2 = simple_qos
.try_add_connection(client_tracker2, &server_connection2, &mut conn_context2)
.await;
assert!(result2.is_some());
assert!(stats.num_evictions_staked.load(Ordering::Relaxed) > 0);
}
#[tokio::test]
async fn test_try_add_connection_max_staked_connections_no_pruning_possible() {
let cancel = CancellationToken::new();
let stats = Arc::new(StreamerStats::default());
let server_keypair1 = Keypair::new();
let client_keypair1 = Keypair::new();
let server_keypair2 = Keypair::new();
let client_keypair2 = Keypair::new();
let high_stake = 100_000_000; let low_stake = 10_000_000;
let mut stakes = HashMap::new();
stakes.insert(server_keypair1.pubkey(), high_stake);
stakes.insert(client_keypair1.pubkey(), high_stake);
stakes.insert(server_keypair2.pubkey(), low_stake);
stakes.insert(client_keypair2.pubkey(), low_stake);
let overrides: HashMap<Pubkey, u64> = HashMap::new();
let staked_nodes = Arc::new(RwLock::new(StakedNodes::new(Arc::new(stakes), overrides)));
let simple_qos = SimpleQos::new(
SimpleQosConfig {
max_staked_connections: 1,
..Default::default()
},
stats.clone(),
staked_nodes,
cancel.clone(),
);
let client_tracker1 = ClientConnectionTracker {
stats: stats.clone(),
};
let (server_connection1, _client_endpoint1, _server_endpoint1) =
create_connection_with_keypairs(&server_keypair1, &client_keypair1).await;
let mut conn_context1 = simple_qos.build_connection_context(&server_connection1);
let result1 = simple_qos
.try_add_connection(client_tracker1, &server_connection1, &mut conn_context1)
.await;
assert!(result1.is_some());
let client_tracker2 = ClientConnectionTracker {
stats: stats.clone(),
};
let (server_connection2, _client_endpoint2, _server_endpoint2) =
create_connection_with_keypairs(&server_keypair2, &client_keypair2).await;
let mut conn_context2 = simple_qos.build_connection_context(&server_connection2);
let result2 = simple_qos
.try_add_connection(client_tracker2, &server_connection2, &mut conn_context2)
.await;
assert!(result2.is_none());
assert!(conn_context2.stream_counter.is_none());
}
#[tokio::test]
async fn test_try_add_connection_context_updates() {
let cancel = CancellationToken::new();
let stats = Arc::new(StreamerStats::default());
let server_keypair = Keypair::new();
let client_keypair = Keypair::new();
let stake_amount = 50_000_000;
let staked_nodes =
create_staked_nodes_with_keypairs(&server_keypair, &client_keypair, stake_amount);
let simple_qos = SimpleQos::new(
SimpleQosConfig::default(),
stats.clone(),
staked_nodes,
cancel.clone(),
);
let client_tracker = ClientConnectionTracker {
stats: stats.clone(),
};
let (server_connection, _client_endpoint, _server_endpoint) =
create_connection_with_keypairs(&server_keypair, &client_keypair).await;
let mut conn_context = simple_qos.build_connection_context(&server_connection);
let initial_last_update = conn_context.last_update.load(Ordering::Relaxed);
assert!(conn_context.stream_counter.is_none());
let result = simple_qos
.try_add_connection(client_tracker, &server_connection, &mut conn_context)
.await;
assert!(result.is_some());
assert!(conn_context.stream_counter.is_some());
let updated_last_update = conn_context.last_update.load(Ordering::Relaxed);
assert!(updated_last_update >= initial_last_update);
}
#[tokio::test]
async fn test_on_stream_accepted_increments_counter() {
let cancel = CancellationToken::new();
let stats = Arc::new(StreamerStats::default());
let server_keypair = Keypair::new();
let client_keypair = Keypair::new();
let stake_amount = 50_000_000;
let staked_nodes =
create_staked_nodes_with_keypairs(&server_keypair, &client_keypair, stake_amount);
let simple_qos = SimpleQos::new(
SimpleQosConfig::default(),
stats.clone(),
staked_nodes,
cancel.clone(),
);
let client_tracker = ClientConnectionTracker {
stats: stats.clone(),
};
let (server_connection, _client_endpoint, _server_endpoint) =
create_connection_with_keypairs(&server_keypair, &client_keypair).await;
let mut conn_context = simple_qos.build_connection_context(&server_connection);
let result = simple_qos
.try_add_connection(client_tracker, &server_connection, &mut conn_context)
.await;
assert!(result.is_some()); assert!(conn_context.stream_counter.is_some()); }
#[tokio::test]
async fn test_remove_connection_success() {
let cancel = CancellationToken::new();
let stats = Arc::new(StreamerStats::default());
let server_keypair = Keypair::new();
let client_keypair = Keypair::new();
let stake_amount = 50_000_000;
let staked_nodes =
create_staked_nodes_with_keypairs(&server_keypair, &client_keypair, stake_amount);
let simple_qos = SimpleQos::new(
SimpleQosConfig::default(),
stats.clone(),
staked_nodes,
cancel.clone(),
);
let client_tracker = ClientConnectionTracker {
stats: stats.clone(),
};
let (server_connection, _client_endpoint, _server_endpoint) =
create_connection_with_keypairs(&server_keypair, &client_keypair).await;
let mut conn_context = simple_qos.build_connection_context(&server_connection);
let add_result = simple_qos
.try_add_connection(client_tracker, &server_connection, &mut conn_context)
.await;
assert!(add_result.is_some());
let initial_open_connections = stats.open_staked_connections.load(Ordering::Relaxed);
assert!(initial_open_connections > 0);
let removed_count = simple_qos
.remove_connection(&conn_context, server_connection.clone())
.await;
assert_eq!(removed_count, 1);
let final_open_connections = stats.open_staked_connections.load(Ordering::Relaxed);
assert!(final_open_connections < initial_open_connections);
assert_eq!(final_open_connections, initial_open_connections - 1);
}
#[tokio::test]
async fn test_on_new_stream_throttles_correctly() {
let cancel = CancellationToken::new();
let stats = Arc::new(StreamerStats::default());
let server_keypair = Keypair::new();
let client_keypair = Keypair::new();
let stake_amount = 50_000_000;
let staked_nodes =
create_staked_nodes_with_keypairs(&server_keypair, &client_keypair, stake_amount);
let max_streams_per_second = 10;
let qos_config = SimpleQosConfig {
max_streams_per_second,
max_staked_connections: 100,
max_connections_per_peer: 10,
};
let simple_qos = SimpleQos::new(qos_config, stats.clone(), staked_nodes, cancel.clone());
let client_tracker = ClientConnectionTracker {
stats: stats.clone(),
};
let (server_connection, _client_endpoint, _server_endpoint) =
create_connection_with_keypairs(&server_keypair, &client_keypair).await;
let mut conn_context = simple_qos.build_connection_context(&server_connection);
let result = simple_qos
.try_add_connection(client_tracker, &server_connection, &mut conn_context)
.await;
assert!(result.is_some()); assert!(conn_context.stream_counter.is_some());
let start_time = std::time::Instant::now();
for _ in 0..max_streams_per_second * 2 {
simple_qos.on_new_stream(&conn_context).await;
}
let elapsed = start_time.elapsed();
assert!(elapsed > std::time::Duration::from_millis(950)); assert!(elapsed < std::time::Duration::from_millis(1200)); }
}