use log::{debug, error, info, trace, warn}; use solana_lite_rpc_core::solana_utils::SerializableTransaction; use solana_lite_rpc_core::stores::data_cache::DataCache; use solana_lite_rpc_core::structures::identity_stakes::IdentityStakesData; use solana_lite_rpc_core::structures::transaction_sent_info::SentTransactionInfo; use solana_lite_rpc_services::quic_connection_utils::QuicConnectionParameters; use solana_lite_rpc_services::tpu_utils::tpu_connection_manager::TpuConnectionManager; use solana_sdk::instruction::Instruction; use solana_sdk::message::Message; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::{Keypair, Signature, Signer}; use solana_sdk::hash::Hash; use solana_sdk::transaction::{Transaction, VersionedTransaction}; use solana_streamer::nonblocking::quic::ConnectionPeerType; use solana_streamer::tls_certificates::new_self_signed_tls_certificate; use std::collections::{HashMap, HashSet}; use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket}; use std::str::FromStr; use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::task::{yield_now, JoinHandle}; use tokio::time::sleep; use solana_lite_rpc_services::tpu_utils::quic_proxy_connection_manager::QuicProxyConnectionManager; pub const MAXIMUM_TRANSACTIONS_IN_QUEUE: usize = 16_384; pub const MAX_QUIC_CONNECTIONS_PER_PEER: usize = 8; // like solana repo pub const QUIC_CONNECTION_PARAMS: QuicConnectionParameters = QuicConnectionParameters { connection_timeout: Duration::from_secs(2), connection_retry_count: 10, finalize_timeout: Duration::from_secs(2), max_number_of_connections: 8, unistream_timeout: Duration::from_secs(2), write_timeout: Duration::from_secs(2), number_of_transactions_per_unistream: 10, unistreams_to_create_new_connection_in_percentage: 10, prioritization_heap_size: None, }; #[derive(Copy, Clone, Debug)] pub struct TestCaseParams { pub sample_tx_count: u32, pub stake_connection: bool, pub proxy_mode: bool, } pub async fn start_literpc_client_proxy_mode( test_case_params: TestCaseParams, streamer_listen_addrs: SocketAddr, validator_identity: Arc, forward_proxy_address: SocketAddr, ) -> anyhow::Result<()> { info!( "Start lite-rpc test client using quic proxy at {} ...", forward_proxy_address ); // (String, Vec) (signature, transaction) let (sender, _) = tokio::sync::broadcast::channel(MAXIMUM_TRANSACTIONS_IN_QUEUE); let broadcast_sender = Arc::new(sender); let (certificate, key) = new_self_signed_tls_certificate( validator_identity.as_ref(), IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), ) .expect("Failed to initialize QUIC connection certificates"); let quic_proxy_connection_manager = QuicProxyConnectionManager::new(certificate, key, forward_proxy_address).await; // this effectively controls how many connections we will have let mut connections_to_keep: HashMap = HashMap::new(); let addr1 = UdpSocket::bind("127.0.0.1:0") .unwrap() .local_addr() .unwrap(); connections_to_keep.insert( Pubkey::from_str("1111111jepwNWbYG87sgwnBbUJnQHrPiUJzMpqJXZ")?, addr1, ); let addr2 = UdpSocket::bind("127.0.0.1:0") .unwrap() .local_addr() .unwrap(); connections_to_keep.insert( Pubkey::from_str("1111111k4AYMctpyJakWNvGcte6tR8BLyZw54R8qu")?, addr2, ); // this is the real streamer connections_to_keep.insert(validator_identity.pubkey(), streamer_listen_addrs); // get information about the optional validator identity stake // populated from get_stakes_for_identity() let _identity_stakes = IdentityStakesData { peer_type: ConnectionPeerType::Staked, stakes: if test_case_params.stake_connection { 30 } else { 0 }, // stake of lite-rpc min_stakes: 0, max_stakes: 40, total_stakes: 100, }; let transaction_receiver = broadcast_sender.subscribe(); quic_proxy_connection_manager .update_connection( transaction_receiver, connections_to_keep, QUIC_CONNECTION_PARAMS, ) .await; for i in 0..test_case_params.sample_tx_count { let raw_sample_tx = build_raw_sample_tx(i); trace!( "broadcast transaction {} to {} receivers: {}", raw_sample_tx.signature, broadcast_sender.receiver_count(), format!("hi {}", i) ); broadcast_sender.send(raw_sample_tx)?; if (i + 1) % 1000 == 0 { yield_now().await; } } while !broadcast_sender.is_empty() { sleep(Duration::from_millis(1000)).await; warn!("broadcast channel is not empty - wait before shutdown test client thread"); } assert!( broadcast_sender.is_empty(), "broadcast channel must be empty" ); quic_proxy_connection_manager.signal_shutdown(); sleep(Duration::from_secs(3)).await; Ok(()) } // no quic proxy pub async fn start_literpc_client_direct_mode( test_case_params: TestCaseParams, streamer_listen_addrs: SocketAddr, literpc_validator_identity: Arc, ) -> anyhow::Result<()> { info!("Start lite-rpc test client in direct-mode..."); let fanout_slots = 4; // (String, Vec) (signature, transaction) let (sender, _) = tokio::sync::broadcast::channel(MAXIMUM_TRANSACTIONS_IN_QUEUE); let broadcast_sender = Arc::new(sender); let (certificate, key) = new_self_signed_tls_certificate( literpc_validator_identity.as_ref(), IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), ) .expect("Failed to initialize QUIC connection certificates"); let tpu_connection_manager = TpuConnectionManager::new(certificate, key, fanout_slots as usize).await; // this effectively controls how many connections we will have let mut connections_to_keep: HashMap = HashMap::new(); let addr1 = UdpSocket::bind("127.0.0.1:0") .unwrap() .local_addr() .unwrap(); connections_to_keep.insert( Pubkey::from_str("1111111jepwNWbYG87sgwnBbUJnQHrPiUJzMpqJXZ")?, addr1, ); let addr2 = UdpSocket::bind("127.0.0.1:0") .unwrap() .local_addr() .unwrap(); connections_to_keep.insert( Pubkey::from_str("1111111k4AYMctpyJakWNvGcte6tR8BLyZw54R8qu")?, addr2, ); // this is the real streamer connections_to_keep.insert(literpc_validator_identity.pubkey(), streamer_listen_addrs); // get information about the optional validator identity stake // populated from get_stakes_for_identity() let identity_stakes = IdentityStakesData { peer_type: ConnectionPeerType::Staked, stakes: if test_case_params.stake_connection { 30 } else { 0 }, // stake of lite-rpc min_stakes: 0, max_stakes: 40, total_stakes: 100, }; // solana_streamer::nonblocking::quic: Peer type: Staked, stake 30, total stake 0, max streams 128 receive_window Ok(12320) from peer 127.0.0.1:8000 tpu_connection_manager .update_connections( broadcast_sender.clone(), connections_to_keep, identity_stakes, // note: tx_store is useless in this scenario as it is never changed; it's only used to check for duplicates DataCache::new_for_tests(), QUIC_CONNECTION_PARAMS, ) .await; for i in 0..test_case_params.sample_tx_count { let raw_sample_tx = build_raw_sample_tx(i); trace!( "broadcast transaction {} to {} receivers: {}", raw_sample_tx.signature, broadcast_sender.receiver_count(), format!("hi {}", i) ); broadcast_sender.send(raw_sample_tx)?; } while !broadcast_sender.is_empty() { sleep(Duration::from_millis(1000)).await; warn!("broadcast channel is not empty - wait before shutdown test client thread"); } assert!( broadcast_sender.is_empty(), "broadcast channel must be empty" ); sleep(Duration::from_secs(3)).await; Ok(()) } const MEMO_PROGRAM_ID: &str = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"; pub fn build_raw_sample_tx(i: u32) -> SentTransactionInfo { let payer_keypair = Keypair::from_base58_string( "rKiJ7H5UUp3JR18kNyTF1XPuwPKHEM7gMLWHZPWP5djrW1vSjfwjhvJrevxF9MPmUmN9gJMLHZdLMgc9ao78eKr", ); let tx = build_sample_tx(&payer_keypair, i); let transaction = Arc::new(bincode::serialize::(&tx).expect("failed to serialize tx")); SentTransactionInfo { signature: *tx.get_signature(), slot: 1, transaction, last_valid_block_height: 300, prioritization_fee: 0, } } fn build_sample_tx(payer_keypair: &Keypair, i: u32) -> VersionedTransaction { let blockhash = Hash::default(); create_memo_tx(format!("hi {}", i).as_bytes(), payer_keypair, blockhash).into() } fn create_memo_tx(msg: &[u8], payer: &Keypair, blockhash: Hash) -> Transaction { let memo = Pubkey::from_str(MEMO_PROGRAM_ID).unwrap(); let instruction = Instruction::new_with_bytes(memo, msg, vec![]); let message = Message::new(&[instruction], Some(&payer.pubkey())); Transaction::new(&[payer], message, blockhash) }