use {
super::quic::{ALPN_TPU_PROTOCOL_ID, SpawnNonBlockingServerResult},
crate::{
nonblocking::{
quic::spawn_server,
swqos::{SwQos, SwQosConfig},
},
quic::{QUIC_MAX_TIMEOUT, QuicServerError, QuicStreamerConfig, StreamerStats},
quic_socket::QuicSocket,
streamer::StakedNodes,
},
crossbeam_channel::{Receiver, Sender, unbounded},
quinn::{
ClientConfig, Connection, EndpointConfig, IdleTimeout, TokioRuntime, TransportConfig,
crypto::rustls::QuicClientConfig,
},
solana_keypair::Keypair,
solana_net_utils::sockets::{
SocketConfiguration as SocketConfig, bind_to_with_config, localhost_port_range_for_tests,
multi_bind_in_range_with_config, unique_port_range_for_tests,
},
solana_perf::packet::PacketBatch,
solana_tls_utils::{new_dummy_x509_certificate, tls_client_config_builder},
std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
sync::{Arc, RwLock},
time::{Duration, Instant},
},
tokio::{task::JoinHandle, time::sleep},
tokio_util::sync::CancellationToken,
};
const QUIC_KEEP_ALIVE_FOR_TESTS: Duration = Duration::from_secs(5);
pub fn spawn_stake_weighted_qos_server(
name: &'static str,
sockets: impl IntoIterator<Item = QuicSocket>,
keypair: &Keypair,
packet_sender: Sender<PacketBatch>,
staked_nodes: Arc<RwLock<StakedNodes>>,
quic_server_params: QuicStreamerConfig,
qos_config: SwQosConfig,
cancel: CancellationToken,
) -> Result<SpawnNonBlockingServerResult, QuicServerError>
where
{
let stats = Arc::<StreamerStats>::default();
let swqos = SwQos::new(qos_config, stats.clone(), staked_nodes, cancel.clone());
spawn_server(
name,
stats,
sockets,
keypair,
packet_sender,
quic_server_params,
swqos,
cancel,
)
}
pub fn get_client_config(keypair: &Keypair) -> ClientConfig {
let (cert, key) = new_dummy_x509_certificate(keypair);
let mut crypto = tls_client_config_builder()
.with_client_auth_cert(vec![cert], key)
.expect("Failed to use client certificate");
crypto.enable_early_data = true;
crypto.alpn_protocols = vec![ALPN_TPU_PROTOCOL_ID.to_vec()];
let mut config = ClientConfig::new(Arc::new(QuicClientConfig::try_from(crypto).unwrap()));
let mut transport_config = TransportConfig::default();
let timeout = IdleTimeout::try_from(QUIC_MAX_TIMEOUT).unwrap();
transport_config.max_idle_timeout(Some(timeout));
transport_config.keep_alive_interval(Some(QUIC_KEEP_ALIVE_FOR_TESTS));
transport_config.send_fairness(false);
config.transport_config(Arc::new(transport_config));
config
}
pub struct SpawnTestServerResult {
pub join_handle: JoinHandle<()>,
pub receiver: crossbeam_channel::Receiver<PacketBatch>,
pub server_address: SocketAddr,
pub stats: Arc<StreamerStats>,
pub cancel: CancellationToken,
}
pub fn create_quic_server_sockets() -> Vec<QuicSocket> {
let num = if cfg!(not(target_os = "windows")) {
10
} else {
1
};
let port_range = localhost_port_range_for_tests();
multi_bind_in_range_with_config(
IpAddr::V4(Ipv4Addr::LOCALHOST),
port_range,
SocketConfig::default(),
num,
)
.expect("bind operation for quic server sockets should succeed")
.1
.into_iter()
.map(QuicSocket::from)
.collect()
}
pub fn setup_quic_server(
option_staked_nodes: Option<StakedNodes>,
quic_server_params: QuicStreamerConfig,
qos_config: SwQosConfig,
) -> SpawnTestServerResult {
let sockets = create_quic_server_sockets();
let (sender, receiver) = unbounded();
let keypair = Keypair::new();
let server_address = sockets[0].local_addr().unwrap();
let staked_nodes = Arc::new(RwLock::new(option_staked_nodes.unwrap_or_default()));
let cancel = CancellationToken::new();
let SpawnNonBlockingServerResult {
endpoints: _,
stats,
thread: handle,
max_concurrent_connections: _,
} = spawn_stake_weighted_qos_server(
"quic_streamer_test",
sockets,
&keypair,
sender,
staked_nodes,
quic_server_params,
qos_config,
cancel.clone(),
)
.unwrap();
SpawnTestServerResult {
join_handle: handle,
receiver,
server_address,
stats,
cancel,
}
}
pub async fn make_client_endpoint(
addr: &SocketAddr,
client_keypair: Option<&Keypair>,
) -> Connection {
make_client_endpoint_with_local_addr(
addr,
SocketAddr::new(
IpAddr::V4(Ipv4Addr::LOCALHOST),
unique_port_range_for_tests(1).start,
),
client_keypair,
)
.await
.expect("Test server should be already listening on '{addr}'")
}
pub async fn make_client_endpoint_with_bind_ip(
addr: &SocketAddr,
bind_ip: IpAddr,
client_keypair: Option<&Keypair>,
) -> Result<Connection, quinn::ConnectionError> {
make_client_endpoint_with_local_addr(
addr,
SocketAddr::new(bind_ip, unique_port_range_for_tests(1).start),
client_keypair,
)
.await
}
pub async fn make_client_endpoint_with_local_addr(
addr: &SocketAddr,
local_addr: SocketAddr,
client_keypair: Option<&Keypair>,
) -> Result<Connection, quinn::ConnectionError> {
let client_socket = bind_to_with_config(
local_addr.ip(),
local_addr.port(),
SocketConfig::default().set_non_blocking(true),
)
.expect("should bind client socket with local addr");
let mut endpoint = quinn::Endpoint::new(
EndpointConfig::default(),
None,
client_socket,
Arc::new(TokioRuntime),
)
.unwrap();
let default_keypair = Keypair::new();
endpoint.set_default_client_config(get_client_config(
client_keypair.unwrap_or(&default_keypair),
));
endpoint
.connect(*addr, "localhost")
.expect("Endpoint configuration should be correct")
.await
}
pub async fn check_multiple_streams(
receiver: Receiver<PacketBatch>,
server_address: SocketAddr,
client_keypair: Option<&Keypair>,
) {
let conn1 = Arc::new(make_client_endpoint(&server_address, client_keypair).await);
let conn2 = Arc::new(make_client_endpoint(&server_address, client_keypair).await);
let mut num_expected_packets = 0;
for i in 0..10 {
info!("sending: {i}");
let c1 = conn1.clone();
let c2 = conn2.clone();
let mut s1 = c1.open_uni().await.unwrap();
let mut s2 = c2.open_uni().await.unwrap();
s1.write_all(&[0u8]).await.unwrap();
s1.finish().unwrap();
s2.write_all(&[0u8]).await.unwrap();
s2.finish().unwrap();
num_expected_packets += 2;
sleep(Duration::from_millis(200)).await;
}
let mut all_packets = vec![];
let now = Instant::now();
let mut total_packets = 0;
while now.elapsed().as_secs() < 10 {
if let Ok(packets) = receiver.try_recv() {
total_packets += packets.len();
all_packets.push(packets)
} else {
sleep(Duration::from_secs(1)).await;
}
if total_packets == num_expected_packets {
break;
}
}
for batch in all_packets {
for p in batch.iter() {
assert_eq!(p.meta().size, 1);
}
}
assert_eq!(total_packets, num_expected_packets);
}