| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259 |
- //! DoS tool
- //!
- //! Sends requests to cluster in a loop to measure
- //! the effect of handling these requests on the performance of the cluster.
- //!
- //! * `mode` argument defines interface to use (e.g. rpc, tvu, tpu)
- //! * `data-type` argument specifies the type of the request.
- //!
- //! Some request types might be used only with particular `mode` value.
- //! For example, `get-account-info` is valid only with `mode=rpc`.
- //!
- //! Most options are provided for `data-type = transaction`.
- //! These options allow to compose transaction which fails at
- //! a particular stage of the processing pipeline.
- //!
- //! To limit the number of possible options and simplify the usage of the tool,
- //! The following configurations are suggested:
- //! Let `COMMON="--mode tpu --data-type transaction --unique-transactions"`
- //! 1. Without blockhash or payer:
- //! 1.1 With invalid signatures
- //! ```bash
- //! solana-dos $COMMON --num-signatures 8
- //! ```
- //! 1.2 With valid signatures
- //! ```bash
- //! solana-dos $COMMON --valid-signatures --num-signatures 8
- //! ```
- //! 2. With blockhash and payer:
- //! 2.1 Single-instruction transaction
- //! ```bash
- //! solana-dos $COMMON --valid-blockhash --transaction-type transfer --num-instructions 1
- //! ```
- //! 2.2 Multi-instruction transaction
- //! ```bash
- //! solana-dos $COMMON --valid-blockhash --transaction-type transfer --num-instructions 8
- //! ```
- //! 2.3 Account-creation transaction
- //! ```bash
- //! solana-dos $COMMON --valid-blockhash --transaction-type account-creation
- //! ```
- //!
- #![allow(clippy::arithmetic_side_effects)]
- #![allow(deprecated)]
- use {
- crossbeam_channel::{select, tick, unbounded, Receiver, Sender},
- itertools::Itertools,
- log::*,
- rand::{thread_rng, Rng},
- solana_bench_tps::bench::generate_and_fund_keypairs,
- solana_client::{connection_cache::ConnectionCache, tpu_client::TpuClientWrapper},
- solana_connection_cache::client_connection::ClientConnection as TpuConnection,
- solana_core::repair::serve_repair::{RepairProtocol, RepairRequestHeader, ServeRepair},
- solana_dos::cli::*,
- solana_gossip::{
- contact_info::{ContactInfo, Protocol},
- gossip_service::{discover, get_client},
- },
- solana_hash::Hash,
- solana_keypair::Keypair,
- solana_measure::measure::Measure,
- solana_message::{compiled_instruction::CompiledInstruction, Message},
- solana_net_utils::{bind_to_unspecified, SocketAddrSpace},
- solana_pubkey::Pubkey,
- solana_rpc_client::rpc_client::RpcClient,
- solana_signature::Signature,
- solana_signer::Signer,
- solana_stake_interface as stake,
- solana_system_interface::{
- instruction::{self as system_instruction, SystemInstruction},
- program as system_program,
- },
- solana_time_utils::timestamp,
- solana_tps_client::TpsClient,
- solana_tpu_client::tpu_client::DEFAULT_TPU_CONNECTION_POOL_SIZE,
- solana_transaction::Transaction,
- std::{
- net::SocketAddr,
- process::exit,
- sync::Arc,
- thread,
- time::{Duration, Instant},
- },
- };
- const PROGRESS_TIMEOUT_S: u64 = 120;
- const SAMPLE_PERIOD_MS: u64 = 10_000;
- fn compute_rate_per_second(count: usize) -> usize {
- (count * 1000) / (SAMPLE_PERIOD_MS as usize)
- }
- /// Provide functionality to generate several types of transactions:
- ///
- /// 1. Without blockhash
- /// 1.1 With valid signatures (number of signatures is configurable)
- /// 1.2 With invalid signatures (number of signatures is configurable)
- ///
- /// 2. With blockhash (but still deliberately invalid):
- /// 2.1 Transfer from 1 payer to multiple destinations (many instructions per transaction)
- /// 2.2 Create an account
- ///
- #[derive(Clone)]
- struct TransactionGenerator {
- blockhash: Hash,
- last_generated: Instant,
- transaction_params: TransactionParams,
- }
- impl TransactionGenerator {
- fn new(transaction_params: TransactionParams) -> Self {
- TransactionGenerator {
- blockhash: Hash::default(),
- last_generated: Instant::now()
- .checked_sub(Duration::from_secs(100))
- .unwrap(), //to force generation when generate is called
- transaction_params,
- }
- }
- /// Generate transaction
- ///
- /// `payer` - the account responsible for paying the cost of executing transaction, used as
- /// a source for transfer instructions and as funding account for create-account instructions.
- /// `destinations` - depending on the transaction type, might be destination accounts receiving transfers,
- /// new accounts, signing accounts. It is `None` only if `valid_signatures==false`.
- /// `client` - structure responsible for providing blockhash.
- ///
- fn generate<T: 'static + TpsClient + Send + Sync>(
- &mut self,
- payer: Option<&Keypair>,
- destinations: Option<Vec<&Keypair>>,
- client: Option<&Arc<T>>,
- ) -> Transaction {
- if self.transaction_params.valid_blockhash {
- let client = client.as_ref().unwrap();
- let destinations = destinations.unwrap();
- let payer = payer.as_ref().unwrap();
- self.generate_with_blockhash(payer, destinations, client)
- } else {
- self.generate_without_blockhash(destinations)
- }
- }
- fn generate_with_blockhash<T: 'static + TpsClient + Send + Sync>(
- &mut self,
- payer: &Keypair,
- destinations: Vec<&Keypair>,
- client: &Arc<T>,
- ) -> Transaction {
- // generate a new blockhash every 1sec
- if self.transaction_params.valid_blockhash
- && self.last_generated.elapsed().as_millis() > 1000
- {
- self.blockhash = client.get_latest_blockhash().unwrap();
- self.last_generated = Instant::now();
- }
- // transaction_type is known to be present because it is required by blockhash option in cli
- let transaction_type = self.transaction_params.transaction_type.as_ref().unwrap();
- match transaction_type {
- TransactionType::Transfer => {
- self.create_multi_transfer_transaction(payer, &destinations)
- }
- TransactionType::AccountCreation => {
- self.create_account_transaction(payer, destinations[0])
- }
- }
- }
- /// Create a transaction which transfers some lamports from payer to several destinations
- fn create_multi_transfer_transaction(&self, payer: &Keypair, to: &[&Keypair]) -> Transaction {
- let to_transfer: u64 = 500_000_000; // specify amount which will cause error
- let to: Vec<(Pubkey, u64)> = to.iter().map(|to| (to.pubkey(), to_transfer)).collect();
- let instructions = system_instruction::transfer_many(&payer.pubkey(), to.as_slice());
- let message = Message::new(&instructions, Some(&payer.pubkey()));
- let mut tx = Transaction::new_unsigned(message);
- tx.sign(&[payer], self.blockhash);
- tx
- }
- /// Create a transaction which opens account
- fn create_account_transaction(&self, payer: &Keypair, to: &Keypair) -> Transaction {
- let program_id = system_program::id(); // some valid program id
- let balance = 500_000_000;
- let space = 1024;
- let instructions = vec![system_instruction::create_account(
- &payer.pubkey(),
- &to.pubkey(),
- balance,
- space,
- &program_id,
- )];
- let message = Message::new(&instructions, Some(&payer.pubkey()));
- let signers: Vec<&Keypair> = vec![payer, to];
- Transaction::new(&signers, message, self.blockhash)
- }
- fn generate_without_blockhash(
- &mut self,
- destinations: Option<Vec<&Keypair>>, // provided for valid signatures
- ) -> Transaction {
- // create an arbitrary valid instruction
- let lamports = 5;
- let transfer_instruction = SystemInstruction::Transfer { lamports };
- let program_ids = vec![system_program::id(), stake::program::id()];
- let instructions = vec![CompiledInstruction::new(
- 0,
- &transfer_instruction,
- vec![0, 1],
- )];
- if self.transaction_params.valid_signatures {
- // Since we don't provide a payer, this transaction will end up
- // filtered at legacy.rs sanitize method (banking_stage) with error "a program cannot be payer"
- let destinations = destinations.unwrap();
- Transaction::new_with_compiled_instructions(
- &destinations,
- &[],
- self.blockhash,
- program_ids,
- instructions,
- )
- } else {
- // Since we provided invalid signatures
- // this transaction will end up filtered at legacy.rs (banking_stage) because
- // num_required_signatures == 0
- let mut tx = Transaction::new_with_compiled_instructions(
- &[] as &[&Keypair; 0],
- &[],
- self.blockhash,
- program_ids,
- instructions,
- );
- let num_signatures = self.transaction_params.num_signatures.unwrap();
- tx.signatures = vec![Signature::new_unique(); num_signatures];
- tx
- }
- }
- }
- // Multithreading-related functions
- //
- // The most computationally expensive work is signing new transactions.
- // Here we generate them in `num_gen_threads` threads.
- //
- struct TransactionBatchMsg {
- batch: Vec<Vec<u8>>,
- gen_time: u64,
- }
- /// Creates thread which receives batches of transactions from tx_receiver
- /// and sends them to the target.
- /// If `iterations` is 0, it works indefenetely.
- /// Otherwise, it sends at least `iterations` number of transactions
- fn create_sender_thread(
- tx_receiver: Receiver<TransactionBatchMsg>,
- iterations: usize,
- target: &SocketAddr,
- tpu_use_quic: bool,
- ) -> thread::JoinHandle<()> {
- // ConnectionCache is used instead of client because it gives ~6% higher pps
- let connection_cache = if tpu_use_quic {
- ConnectionCache::new_quic(
- "connection_cache_dos_quic",
- DEFAULT_TPU_CONNECTION_POOL_SIZE,
- )
- } else {
- ConnectionCache::with_udp("connection_cache_dos_udp", DEFAULT_TPU_CONNECTION_POOL_SIZE)
- };
- let connection = connection_cache.get_connection(target);
- let stats_timer_receiver = tick(Duration::from_millis(SAMPLE_PERIOD_MS));
- let progress_timer_receiver = tick(Duration::from_secs(PROGRESS_TIMEOUT_S));
- let mut time_send_ns = 0;
- let mut time_generate_ns = 0;
- // Sender signals to stop Generators by dropping receiver.
- // It happens in 2 cases:
- // * Sender has sent at least `iterations` number of transactions
- // * Sender observes that there is no progress. Since there is no way to use recv_timeout with select,
- // a timer is used.
- thread::Builder::new().name("Sender".to_string()).spawn(move || {
- let mut total_count: usize = 0;
- let mut prev_total_count = 0; // to track progress
- let mut stats_count: usize = 0;
- let mut stats_error_count: usize = 0;
- loop {
- select! {
- recv(tx_receiver) -> msg => {
- match msg {
- Ok(tx_batch) => {
- let len = tx_batch.batch.len();
- let mut measure_send_txs = Measure::start("measure_send_txs");
- let res = connection.send_data_batch_async(tx_batch.batch);
- measure_send_txs.stop();
- time_send_ns += measure_send_txs.as_ns();
- time_generate_ns += tx_batch.gen_time;
- if res.is_err() {
- stats_error_count += len;
- }
- stats_count += len;
- total_count += len;
- if iterations != 0 && total_count >= iterations {
- info!("All transactions have been sent");
- // dropping receiver to signal generator threads to stop
- drop(tx_receiver);
- break;
- }
- }
- _ => panic!("Sender panics"),
- }
- },
- recv(stats_timer_receiver) -> _ => {
- info!("tx_receiver queue len: {}", tx_receiver.len());
- info!("Count: {}, error count: {}, send mean time: {}, generate mean time: {}, rps: {}",
- stats_count,
- stats_error_count,
- time_send_ns.checked_div(stats_count as u64).unwrap_or(0),
- time_generate_ns.checked_div(stats_count as u64).unwrap_or(0),
- compute_rate_per_second(stats_count),
- );
- stats_count = 0;
- stats_error_count = 0;
- time_send_ns = 0;
- time_generate_ns = 0;
- },
- recv(progress_timer_receiver) -> _ => {
- if prev_total_count - total_count == 0 {
- info!("No progress, stop execution");
- // dropping receiver to signal generator threads to stop
- drop(tx_receiver);
- break;
- }
- prev_total_count = total_count;
- }
- }
- }
- }).unwrap()
- }
- fn create_generator_thread<T: 'static + TpsClient + Send + Sync>(
- tx_sender: &Sender<TransactionBatchMsg>,
- send_batch_size: usize,
- transaction_generator: &TransactionGenerator,
- client: Option<Arc<T>>,
- payer: Option<Keypair>,
- ) -> thread::JoinHandle<()> {
- let tx_sender = tx_sender.clone();
- let mut transaction_generator = transaction_generator.clone();
- let transaction_params: &TransactionParams = &transaction_generator.transaction_params;
- // Generate n=1000 unique keypairs
- // The number of chunks is described by binomial coefficient
- // and hence this choice of n provides large enough number of permutations
- let mut keypairs_flat: Vec<Keypair> = Vec::new();
- // 1000 is arbitrary number. In case of permutation_size > 1,
- // this guarantees large enough set of unique permutations
- let permutation_size = get_permutation_size(
- transaction_params.num_signatures.as_ref(),
- transaction_params.num_instructions.as_ref(),
- );
- let num_keypairs = 1000 * permutation_size;
- let generate_keypairs =
- transaction_params.valid_signatures || transaction_params.valid_blockhash;
- if generate_keypairs {
- keypairs_flat = (0..num_keypairs).map(|_| Keypair::new()).collect();
- }
- thread::Builder::new()
- .name("Generator".to_string())
- .spawn(move || {
- let indexes: Vec<usize> = (0..keypairs_flat.len()).collect();
- let mut it = indexes.iter().permutations(permutation_size);
- loop {
- let mut data = Vec::<Vec<u8>>::with_capacity(send_batch_size);
- let mut measure_generate_txs = Measure::start("measure_generate_txs");
- for _ in 0..send_batch_size {
- let chunk_keypairs = if generate_keypairs {
- let mut permutation = it.next();
- if permutation.is_none() {
- // if ran out of permutations, regenerate keys
- keypairs_flat.iter_mut().for_each(|v| *v = Keypair::new());
- info!("Regenerate keypairs");
- permutation = it.next();
- }
- let permutation = permutation.unwrap();
- Some(apply_permutation(permutation, &keypairs_flat))
- } else {
- None
- };
- let tx = transaction_generator.generate(
- payer.as_ref(),
- chunk_keypairs,
- client.as_ref(),
- );
- data.push(bincode::serialize(&tx).unwrap());
- }
- measure_generate_txs.stop();
- let result = tx_sender.send(TransactionBatchMsg {
- batch: data,
- gen_time: measure_generate_txs.as_ns(),
- });
- if result.is_err() {
- // means that receiver has been dropped by sender thread
- info!("Exit generator thread");
- break;
- }
- }
- })
- .unwrap()
- }
- fn get_target(
- nodes: &[ContactInfo],
- mode: Mode,
- entrypoint_addr: SocketAddr,
- tpu_use_quic: bool,
- ) -> Option<(Pubkey, SocketAddr)> {
- let protocol = if tpu_use_quic {
- Protocol::QUIC
- } else {
- Protocol::UDP
- };
- let mut target = None;
- if nodes.is_empty() {
- // skip-gossip case
- target = Some((solana_pubkey::new_rand(), entrypoint_addr));
- } else {
- info!("************ NODE ***********");
- for node in nodes {
- info!("{node:?}");
- }
- info!("ADDR = {entrypoint_addr}");
- for node in nodes {
- if node.gossip() == Some(entrypoint_addr) {
- info!("{:?}", node.gossip());
- target = match mode {
- Mode::Gossip => Some((*node.pubkey(), node.gossip().unwrap())),
- Mode::Tvu => Some((*node.pubkey(), node.tvu(Protocol::UDP).unwrap())),
- Mode::Tpu => Some((*node.pubkey(), node.tpu(protocol).unwrap())),
- Mode::TpuForwards => {
- Some((*node.pubkey(), node.tpu_forwards(protocol).unwrap()))
- }
- Mode::Repair => todo!("repair socket is not gossiped anymore!"),
- Mode::ServeRepair => {
- Some((*node.pubkey(), node.serve_repair(Protocol::UDP).unwrap()))
- }
- Mode::Rpc => None,
- };
- break;
- }
- }
- }
- target
- }
- fn get_rpc_client(
- nodes: &[ContactInfo],
- entrypoint_addr: SocketAddr,
- ) -> Result<RpcClient, &'static str> {
- if nodes.is_empty() {
- // skip-gossip case
- return Ok(RpcClient::new_socket(entrypoint_addr));
- }
- // find target node
- for node in nodes {
- if node.gossip() == Some(entrypoint_addr) {
- info!("{:?}", node.gossip());
- return Ok(RpcClient::new_socket(node.rpc().unwrap()));
- }
- }
- Err("Node with entrypoint_addr was not found")
- }
- fn run_dos_rpc_mode_helper<F: Fn() -> bool>(iterations: usize, rpc_client_call: F) {
- let mut last_log = Instant::now();
- let mut total_count: usize = 0;
- let mut count = 0;
- let mut error_count = 0;
- loop {
- if !rpc_client_call() {
- error_count += 1;
- }
- count += 1;
- total_count += 1;
- if last_log.elapsed().as_millis() > SAMPLE_PERIOD_MS as u128 {
- info!(
- "count: {}, errors: {}, rps: {}",
- count,
- error_count,
- compute_rate_per_second(count)
- );
- last_log = Instant::now();
- count = 0;
- }
- if iterations != 0 && total_count >= iterations {
- break;
- }
- }
- }
- fn run_dos_rpc_mode(
- rpc_client: RpcClient,
- iterations: usize,
- data_type: DataType,
- data_input: &Pubkey,
- ) {
- match data_type {
- DataType::GetAccountInfo => {
- run_dos_rpc_mode_helper(iterations, || -> bool {
- rpc_client.get_account(data_input).is_ok()
- });
- }
- DataType::GetProgramAccounts => {
- run_dos_rpc_mode_helper(iterations, || -> bool {
- rpc_client.get_program_accounts(data_input).is_ok()
- });
- }
- _ => {
- panic!("unsupported data type");
- }
- }
- }
- /// Apply given permutation to the vector of items
- fn apply_permutation<'a, T>(permutation: Vec<&usize>, items: &'a [T]) -> Vec<&'a T> {
- let mut res = Vec::with_capacity(permutation.len());
- for i in permutation {
- res.push(&items[*i]);
- }
- res
- }
- fn create_payers<T: 'static + TpsClient + Send + Sync>(
- valid_blockhash: bool,
- size: usize,
- client: Option<&Arc<T>>,
- ) -> Vec<Option<Keypair>> {
- // Assume that if we use valid blockhash, we also have a payer
- if valid_blockhash {
- // each payer is used to fund transaction
- // transactions are built to be invalid so the amount here is arbitrary
- let funding_key = Keypair::new();
- let funding_key = Arc::new(funding_key);
- let res = generate_and_fund_keypairs(
- client.unwrap().clone(),
- &funding_key,
- size,
- 1_000_000,
- false,
- false,
- )
- .unwrap_or_else(|e| {
- eprintln!("Error could not fund keys: {e:?}");
- exit(1);
- });
- res.into_iter().map(Some).collect()
- } else {
- std::iter::repeat_with(|| None).take(size).collect()
- }
- }
- fn get_permutation_size(num_signatures: Option<&usize>, num_instructions: Option<&usize>) -> usize {
- if let Some(num_signatures) = num_signatures {
- *num_signatures
- } else if let Some(num_instructions) = num_instructions {
- *num_instructions
- } else {
- 1 // for the case AccountCreation
- }
- }
- fn run_dos_transactions<T: 'static + TpsClient + Send + Sync>(
- target: SocketAddr,
- iterations: usize,
- client: Option<Arc<T>>,
- transaction_params: TransactionParams,
- tpu_use_quic: bool,
- num_gen_threads: usize,
- send_batch_size: usize,
- ) {
- // Number of payers is the number of generating threads
- // Later, we will create a new payer for each thread since Keypair is not clonable
- let payers: Vec<Option<Keypair>> = create_payers(
- transaction_params.valid_blockhash,
- num_gen_threads,
- client.as_ref(),
- );
- let transaction_generator = TransactionGenerator::new(transaction_params);
- let (tx_sender, tx_receiver) = unbounded();
- let sender_thread = create_sender_thread(tx_receiver, iterations, &target, tpu_use_quic);
- let tx_generator_threads: Vec<_> = payers
- .into_iter()
- .map(|payer| {
- create_generator_thread(
- &tx_sender,
- send_batch_size,
- &transaction_generator,
- client.clone(),
- payer,
- )
- })
- .collect();
- if let Err(err) = sender_thread.join() {
- println!("join() failed with: {err:?}");
- }
- for t_generator in tx_generator_threads {
- if let Err(err) = t_generator.join() {
- println!("join() failed with: {err:?}");
- }
- }
- }
- fn run_dos<T: 'static + TpsClient + Send + Sync>(
- nodes: &[ContactInfo],
- iterations: usize,
- client: Option<Arc<T>>,
- params: DosClientParameters,
- ) {
- let target = get_target(
- nodes,
- params.mode,
- params.entrypoint_addr,
- params.tpu_use_quic,
- );
- if params.mode == Mode::Rpc {
- // creating rpc_client because get_account, get_program_accounts are not implemented for TpsClient
- let rpc_client =
- get_rpc_client(nodes, params.entrypoint_addr).expect("Failed to get rpc client");
- // existence of data_input is checked at cli level
- run_dos_rpc_mode(
- rpc_client,
- iterations,
- params.data_type,
- ¶ms.data_input.unwrap(),
- );
- } else if params.data_type == DataType::Transaction
- && params.transaction_params.unique_transactions
- {
- let (_, target_addr) = target.expect("should have target");
- info!("Targeting {target_addr}");
- run_dos_transactions(
- target_addr,
- iterations,
- client,
- params.transaction_params,
- params.tpu_use_quic,
- params.num_gen_threads,
- params.send_batch_size,
- );
- } else {
- let (target_id, target_addr) = target.expect("should have target");
- info!("Targeting {target_addr}");
- let mut data = match params.data_type {
- DataType::RepairHighest => {
- let slot = 100;
- let keypair = Keypair::new();
- let header = RepairRequestHeader::new(keypair.pubkey(), target_id, timestamp(), 0);
- let req = RepairProtocol::WindowIndex {
- header,
- slot,
- shred_index: 0,
- };
- ServeRepair::repair_proto_to_bytes(&req, &keypair).unwrap()
- }
- DataType::RepairShred => {
- let slot = 100;
- let keypair = Keypair::new();
- let header = RepairRequestHeader::new(keypair.pubkey(), target_id, timestamp(), 0);
- let req = RepairProtocol::HighestWindowIndex {
- header,
- slot,
- shred_index: 0,
- };
- ServeRepair::repair_proto_to_bytes(&req, &keypair).unwrap()
- }
- DataType::RepairOrphan => {
- let slot = 100;
- let keypair = Keypair::new();
- let header = RepairRequestHeader::new(keypair.pubkey(), target_id, timestamp(), 0);
- let req = RepairProtocol::Orphan { header, slot };
- ServeRepair::repair_proto_to_bytes(&req, &keypair).unwrap()
- }
- DataType::Random => {
- vec![0; params.data_size]
- }
- DataType::Transaction => {
- let tp = params.transaction_params;
- info!("{tp:?}");
- let valid_blockhash = tp.valid_blockhash;
- let payers: Vec<Option<Keypair>> =
- create_payers(valid_blockhash, 1, client.as_ref());
- let payer = payers[0].as_ref();
- let permutation_size =
- get_permutation_size(tp.num_signatures.as_ref(), tp.num_instructions.as_ref());
- let keypairs: Vec<Keypair> =
- (0..permutation_size).map(|_| Keypair::new()).collect();
- let keypairs_chunk: Option<Vec<&Keypair>> =
- if tp.valid_signatures || tp.valid_blockhash {
- Some(keypairs.iter().collect())
- } else {
- None
- };
- let mut transaction_generator = TransactionGenerator::new(tp);
- let tx = transaction_generator.generate(payer, keypairs_chunk, client.as_ref());
- info!("{tx:?}");
- bincode::serialize(&tx).unwrap()
- }
- _ => panic!("Unsupported data_type detected"),
- };
- let socket = bind_to_unspecified().unwrap();
- let mut last_log = Instant::now();
- let mut total_count: usize = 0;
- let mut count: usize = 0;
- let mut error_count = 0;
- loop {
- if params.data_type == DataType::Random {
- thread_rng().fill(&mut data[..]);
- }
- let res = socket.send_to(&data, target_addr);
- if res.is_err() {
- error_count += 1;
- }
- count += 1;
- total_count += 1;
- if last_log.elapsed().as_millis() > SAMPLE_PERIOD_MS as u128 {
- info!(
- "count: {}, errors: {}, rps: {}",
- count,
- error_count,
- compute_rate_per_second(count)
- );
- last_log = Instant::now();
- count = 0;
- }
- if iterations != 0 && total_count >= iterations {
- break;
- }
- }
- }
- }
- fn main() {
- agave_logger::setup_with_default_filter();
- let mut cmd_params = build_cli_parameters();
- if !cmd_params.skip_gossip && cmd_params.shred_version.is_none() {
- // Try to get shred version from the entrypoint
- cmd_params.shred_version = Some(
- solana_net_utils::get_cluster_shred_version(&cmd_params.entrypoint_addr)
- .unwrap_or_else(|err| {
- eprintln!("Failed to get shred version: {err}");
- exit(1);
- }),
- );
- }
- let (nodes, client) = if !cmd_params.skip_gossip {
- info!("Finding cluster entry: {:?}", cmd_params.entrypoint_addr);
- let socket_addr_space = SocketAddrSpace::new(cmd_params.allow_private_addr);
- let (gossip_nodes, validators) = discover(
- None, // keypair
- Some(&cmd_params.entrypoint_addr),
- None, // num_nodes
- Duration::from_secs(60), // timeout
- None, // find_nodes_by_pubkey
- Some(&cmd_params.entrypoint_addr), // find_node_by_gossip_addr
- None, // my_gossip_addr
- cmd_params.shred_version.unwrap(), // my_shred_version
- socket_addr_space,
- )
- .unwrap_or_else(|err| {
- eprintln!(
- "Failed to discover {} node: {:?}",
- cmd_params.entrypoint_addr, err
- );
- exit(1);
- });
- let connection_cache = match cmd_params.tpu_use_quic {
- true => ConnectionCache::new_quic(
- "connection_cache_dos_quic",
- DEFAULT_TPU_CONNECTION_POOL_SIZE,
- ),
- false => ConnectionCache::with_udp(
- "connection_cache_dos_udp",
- DEFAULT_TPU_CONNECTION_POOL_SIZE,
- ),
- };
- let client = get_client(&validators, Arc::new(connection_cache));
- (gossip_nodes, Some(client))
- } else {
- (vec![], None)
- };
- info!("done found {} nodes", nodes.len());
- if let Some(tpu_client) = client {
- match tpu_client {
- TpuClientWrapper::Quic(quic_client) => {
- run_dos(&nodes, 0, Some(Arc::new(quic_client)), cmd_params);
- }
- TpuClientWrapper::Udp(udp_client) => {
- run_dos(&nodes, 0, Some(Arc::new(udp_client)), cmd_params);
- }
- };
- }
- }
- #[cfg(test)]
- pub mod test {
- use {
- super::*,
- solana_core::validator::ValidatorConfig,
- solana_faucet::faucet::run_local_faucet_with_unique_port_for_tests,
- solana_local_cluster::{
- cluster::Cluster,
- local_cluster::{ClusterConfig, LocalCluster},
- validator_configs::make_identical_validator_configs,
- },
- solana_quic_client::{QuicConfig, QuicConnectionManager, QuicPool},
- solana_rpc::rpc::JsonRpcConfig,
- solana_time_utils::timestamp,
- solana_tpu_client::tpu_client::TpuClient,
- };
- const TEST_SEND_BATCH_SIZE: usize = 1;
- // thin wrapper for the run_dos function
- // to avoid specifying everywhere generic parameters
- fn run_dos_no_client(nodes: &[ContactInfo], iterations: usize, params: DosClientParameters) {
- run_dos::<TpuClient<QuicPool, QuicConnectionManager, QuicConfig>>(
- nodes, iterations, None, params,
- );
- }
- #[test]
- fn test_dos() {
- let nodes = [ContactInfo::new_localhost(
- &solana_pubkey::new_rand(),
- timestamp(),
- )];
- let entrypoint_addr = nodes[0].gossip().unwrap();
- run_dos_no_client(
- &nodes,
- 1,
- DosClientParameters {
- entrypoint_addr,
- mode: Mode::Tvu,
- data_size: 10,
- data_type: DataType::Random,
- data_input: None,
- skip_gossip: false,
- shred_version: Some(42),
- allow_private_addr: false,
- num_gen_threads: 1,
- transaction_params: TransactionParams::default(),
- tpu_use_quic: false,
- send_batch_size: TEST_SEND_BATCH_SIZE,
- },
- );
- // TODO: Figure out how to DOS repair. Repair socket is no longer
- // gossiped and cannot be obtained from a node's contact-info.
- #[cfg(not(test))]
- run_dos_no_client(
- &nodes,
- 1,
- DosClientParameters {
- entrypoint_addr,
- mode: Mode::Repair,
- data_size: 10,
- data_type: DataType::RepairHighest,
- data_input: None,
- skip_gossip: false,
- shred_version: Some(42),
- allow_private_addr: false,
- num_gen_threads: 1,
- transaction_params: TransactionParams::default(),
- tpu_use_quic: false,
- send_batch_size: TEST_SEND_BATCH_SIZE,
- },
- );
- run_dos_no_client(
- &nodes,
- 1,
- DosClientParameters {
- entrypoint_addr,
- mode: Mode::ServeRepair,
- data_size: 10,
- data_type: DataType::RepairShred,
- data_input: None,
- skip_gossip: false,
- shred_version: Some(42),
- allow_private_addr: false,
- num_gen_threads: 1,
- transaction_params: TransactionParams::default(),
- tpu_use_quic: false,
- send_batch_size: TEST_SEND_BATCH_SIZE,
- },
- );
- run_dos_no_client(
- &nodes,
- 1,
- DosClientParameters {
- entrypoint_addr,
- mode: Mode::Rpc,
- data_size: 0,
- data_type: DataType::GetAccountInfo,
- data_input: Some(Pubkey::default()),
- skip_gossip: false,
- shred_version: Some(42),
- allow_private_addr: false,
- num_gen_threads: 1,
- transaction_params: TransactionParams::default(),
- tpu_use_quic: false,
- send_batch_size: TEST_SEND_BATCH_SIZE,
- },
- );
- }
- #[test]
- fn test_dos_random() {
- agave_logger::setup();
- let num_nodes = 1;
- let cluster =
- LocalCluster::new_with_equal_stakes(num_nodes, 100, 3, SocketAddrSpace::Unspecified);
- assert_eq!(cluster.validators.len(), num_nodes);
- let nodes = cluster.get_node_pubkeys();
- let node = cluster.get_contact_info(&nodes[0]).unwrap().clone();
- let nodes_slice = [node];
- // send random transactions to TPU
- // will be discarded on sigverify stage
- run_dos_no_client(
- &nodes_slice,
- 10,
- DosClientParameters {
- entrypoint_addr: cluster.entry_point_info.gossip().unwrap(),
- mode: Mode::Tpu,
- data_size: 1024,
- data_type: DataType::Random,
- data_input: None,
- skip_gossip: false,
- shred_version: Some(42),
- allow_private_addr: false,
- num_gen_threads: 1,
- transaction_params: TransactionParams::default(),
- tpu_use_quic: false,
- send_batch_size: TEST_SEND_BATCH_SIZE,
- },
- );
- }
- #[test]
- fn test_dos_without_blockhash() {
- agave_logger::setup();
- let num_nodes = 1;
- let cluster =
- LocalCluster::new_with_equal_stakes(num_nodes, 100, 3, SocketAddrSpace::Unspecified);
- assert_eq!(cluster.validators.len(), num_nodes);
- let nodes = cluster.get_node_pubkeys();
- let node = cluster.get_contact_info(&nodes[0]).unwrap().clone();
- let nodes_slice = [node];
- let client = Arc::new(
- cluster
- .build_validator_tpu_quic_client(cluster.entry_point_info.pubkey())
- .unwrap_or_else(|err| {
- panic!("Could not create TpuClient with Quic Cache {err:?}");
- }),
- );
- // creates one transaction with 8 valid signatures and sends it 10 times
- run_dos(
- &nodes_slice,
- 10,
- Some(client.clone()),
- DosClientParameters {
- entrypoint_addr: cluster.entry_point_info.gossip().unwrap(),
- mode: Mode::Tpu,
- data_size: 0, // irrelevant
- data_type: DataType::Transaction,
- data_input: None,
- skip_gossip: false,
- shred_version: Some(42),
- allow_private_addr: false,
- num_gen_threads: 1,
- transaction_params: TransactionParams {
- num_signatures: Some(8),
- valid_blockhash: false,
- valid_signatures: true,
- unique_transactions: false,
- transaction_type: None,
- num_instructions: None,
- },
- tpu_use_quic: false,
- send_batch_size: TEST_SEND_BATCH_SIZE,
- },
- );
- // creates and sends unique transactions which have invalid signatures
- run_dos(
- &nodes_slice,
- 10,
- Some(client.clone()),
- DosClientParameters {
- entrypoint_addr: cluster.entry_point_info.gossip().unwrap(),
- mode: Mode::Tpu,
- data_size: 0, // irrelevant
- data_type: DataType::Transaction,
- data_input: None,
- skip_gossip: false,
- shred_version: Some(42),
- allow_private_addr: false,
- num_gen_threads: 1,
- transaction_params: TransactionParams {
- num_signatures: Some(8),
- valid_blockhash: false,
- valid_signatures: false,
- unique_transactions: true,
- transaction_type: None,
- num_instructions: None,
- },
- tpu_use_quic: false,
- send_batch_size: TEST_SEND_BATCH_SIZE,
- },
- );
- // creates and sends unique transactions which have valid signatures
- run_dos(
- &nodes_slice,
- 10,
- Some(client),
- DosClientParameters {
- entrypoint_addr: cluster.entry_point_info.gossip().unwrap(),
- mode: Mode::Tpu,
- data_size: 0, // irrelevant
- data_type: DataType::Transaction,
- data_input: None,
- skip_gossip: false,
- shred_version: Some(42),
- allow_private_addr: false,
- num_gen_threads: 1,
- transaction_params: TransactionParams {
- num_signatures: Some(8),
- valid_blockhash: false,
- valid_signatures: true,
- unique_transactions: true,
- transaction_type: None,
- num_instructions: None,
- },
- tpu_use_quic: false,
- send_batch_size: TEST_SEND_BATCH_SIZE,
- },
- );
- }
- fn run_dos_with_blockhash_and_payer(tpu_use_quic: bool) {
- agave_logger::setup();
- // 1. Create faucet thread
- let faucet_keypair = Keypair::new();
- let faucet_pubkey = faucet_keypair.pubkey();
- let faucet_addr = run_local_faucet_with_unique_port_for_tests(faucet_keypair);
- let mut validator_config = ValidatorConfig::default_for_test();
- validator_config.rpc_config = JsonRpcConfig {
- faucet_addr: Some(faucet_addr),
- ..JsonRpcConfig::default_for_test()
- };
- // 2. Create a local cluster which is aware of faucet
- let num_nodes = 1;
- let cluster = LocalCluster::new(
- &mut ClusterConfig {
- node_stakes: vec![999_990; num_nodes],
- mint_lamports: 200_000_000,
- validator_configs: make_identical_validator_configs(
- &ValidatorConfig {
- rpc_config: JsonRpcConfig {
- faucet_addr: Some(faucet_addr),
- ..JsonRpcConfig::default_for_test()
- },
- ..ValidatorConfig::default_for_test()
- },
- num_nodes,
- ),
- ..ClusterConfig::default()
- },
- SocketAddrSpace::Unspecified,
- );
- assert_eq!(cluster.validators.len(), num_nodes);
- // 3. Transfer funds to faucet account
- cluster.transfer(&cluster.funding_keypair, &faucet_pubkey, 100_000_000);
- let nodes = cluster.get_node_pubkeys();
- let node = cluster.get_contact_info(&nodes[0]).unwrap().clone();
- let nodes_slice = [node];
- let client = Arc::new(
- cluster
- .build_validator_tpu_quic_client(cluster.entry_point_info.pubkey())
- .unwrap_or_else(|err| {
- panic!("Could not create TpuClient with Quic Cache {err:?}");
- }),
- );
- // creates one transaction and sends it 10 times
- // this is done in single thread
- run_dos(
- &nodes_slice,
- 10,
- Some(client.clone()),
- DosClientParameters {
- entrypoint_addr: cluster.entry_point_info.gossip().unwrap(),
- mode: Mode::Tpu,
- data_size: 0, // irrelevant if not random
- data_type: DataType::Transaction,
- data_input: None,
- skip_gossip: false,
- shred_version: Some(42),
- allow_private_addr: false,
- num_gen_threads: 1,
- transaction_params: TransactionParams {
- num_signatures: None,
- valid_blockhash: true,
- valid_signatures: true,
- unique_transactions: false,
- transaction_type: Some(TransactionType::Transfer),
- num_instructions: Some(1),
- },
- tpu_use_quic,
- send_batch_size: TEST_SEND_BATCH_SIZE,
- },
- );
- // creates and sends unique transactions of Transfer
- // which tries to send too much lamports from payer to one recipient
- // it uses several threads
- run_dos(
- &nodes_slice,
- 10,
- Some(client.clone()),
- DosClientParameters {
- entrypoint_addr: cluster.entry_point_info.gossip().unwrap(),
- mode: Mode::Tpu,
- data_size: 0, // irrelevant if not random
- data_type: DataType::Transaction,
- data_input: None,
- skip_gossip: false,
- shred_version: Some(42),
- allow_private_addr: false,
- num_gen_threads: 1,
- transaction_params: TransactionParams {
- num_signatures: None,
- valid_blockhash: true,
- valid_signatures: true,
- unique_transactions: true,
- transaction_type: Some(TransactionType::Transfer),
- num_instructions: Some(1),
- },
- tpu_use_quic,
- send_batch_size: TEST_SEND_BATCH_SIZE,
- },
- );
- // creates and sends unique transactions of type Transfer
- // which tries to send too much lamports from payer to several recipients
- // it uses several threads
- run_dos(
- &nodes_slice,
- 10,
- Some(client.clone()),
- DosClientParameters {
- entrypoint_addr: cluster.entry_point_info.gossip().unwrap(),
- mode: Mode::Tpu,
- data_size: 0, // irrelevant if not random
- data_type: DataType::Transaction,
- data_input: None,
- skip_gossip: false,
- shred_version: Some(42),
- allow_private_addr: false,
- num_gen_threads: 1,
- transaction_params: TransactionParams {
- num_signatures: None,
- valid_blockhash: true,
- valid_signatures: true,
- unique_transactions: true,
- transaction_type: Some(TransactionType::Transfer),
- num_instructions: Some(8),
- },
- tpu_use_quic,
- send_batch_size: TEST_SEND_BATCH_SIZE,
- },
- );
- // creates and sends unique transactions of type CreateAccount
- // which tries to create account with too large balance
- // it uses several threads
- run_dos(
- &nodes_slice,
- 10,
- Some(client),
- DosClientParameters {
- entrypoint_addr: cluster.entry_point_info.gossip().unwrap(),
- mode: Mode::Tpu,
- data_size: 0, // irrelevant if not random
- data_type: DataType::Transaction,
- data_input: None,
- skip_gossip: false,
- shred_version: Some(42),
- allow_private_addr: false,
- num_gen_threads: 1,
- transaction_params: TransactionParams {
- num_signatures: None,
- valid_blockhash: true,
- valid_signatures: true,
- unique_transactions: true,
- transaction_type: Some(TransactionType::AccountCreation),
- num_instructions: None,
- },
- tpu_use_quic,
- send_batch_size: TEST_SEND_BATCH_SIZE,
- },
- );
- }
- #[test]
- fn test_dos_with_blockhash_and_payer() {
- run_dos_with_blockhash_and_payer(/*tpu_use_quic*/ false)
- }
- #[test]
- fn test_dos_with_blockhash_and_payer_and_quic() {
- run_dos_with_blockhash_and_payer(/*tpu_use_quic*/ true)
- }
- }
|