main.rs 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259
  1. //! DoS tool
  2. //!
  3. //! Sends requests to cluster in a loop to measure
  4. //! the effect of handling these requests on the performance of the cluster.
  5. //!
  6. //! * `mode` argument defines interface to use (e.g. rpc, tvu, tpu)
  7. //! * `data-type` argument specifies the type of the request.
  8. //!
  9. //! Some request types might be used only with particular `mode` value.
  10. //! For example, `get-account-info` is valid only with `mode=rpc`.
  11. //!
  12. //! Most options are provided for `data-type = transaction`.
  13. //! These options allow to compose transaction which fails at
  14. //! a particular stage of the processing pipeline.
  15. //!
  16. //! To limit the number of possible options and simplify the usage of the tool,
  17. //! The following configurations are suggested:
  18. //! Let `COMMON="--mode tpu --data-type transaction --unique-transactions"`
  19. //! 1. Without blockhash or payer:
  20. //! 1.1 With invalid signatures
  21. //! ```bash
  22. //! solana-dos $COMMON --num-signatures 8
  23. //! ```
  24. //! 1.2 With valid signatures
  25. //! ```bash
  26. //! solana-dos $COMMON --valid-signatures --num-signatures 8
  27. //! ```
  28. //! 2. With blockhash and payer:
  29. //! 2.1 Single-instruction transaction
  30. //! ```bash
  31. //! solana-dos $COMMON --valid-blockhash --transaction-type transfer --num-instructions 1
  32. //! ```
  33. //! 2.2 Multi-instruction transaction
  34. //! ```bash
  35. //! solana-dos $COMMON --valid-blockhash --transaction-type transfer --num-instructions 8
  36. //! ```
  37. //! 2.3 Account-creation transaction
  38. //! ```bash
  39. //! solana-dos $COMMON --valid-blockhash --transaction-type account-creation
  40. //! ```
  41. //!
  42. #![allow(clippy::arithmetic_side_effects)]
  43. #![allow(deprecated)]
  44. use {
  45. crossbeam_channel::{select, tick, unbounded, Receiver, Sender},
  46. itertools::Itertools,
  47. log::*,
  48. rand::{thread_rng, Rng},
  49. solana_bench_tps::bench::generate_and_fund_keypairs,
  50. solana_client::{connection_cache::ConnectionCache, tpu_client::TpuClientWrapper},
  51. solana_connection_cache::client_connection::ClientConnection as TpuConnection,
  52. solana_core::repair::serve_repair::{RepairProtocol, RepairRequestHeader, ServeRepair},
  53. solana_dos::cli::*,
  54. solana_gossip::{
  55. contact_info::{ContactInfo, Protocol},
  56. gossip_service::{discover, get_client},
  57. },
  58. solana_hash::Hash,
  59. solana_keypair::Keypair,
  60. solana_measure::measure::Measure,
  61. solana_message::{compiled_instruction::CompiledInstruction, Message},
  62. solana_net_utils::{bind_to_unspecified, SocketAddrSpace},
  63. solana_pubkey::Pubkey,
  64. solana_rpc_client::rpc_client::RpcClient,
  65. solana_signature::Signature,
  66. solana_signer::Signer,
  67. solana_stake_interface as stake,
  68. solana_system_interface::{
  69. instruction::{self as system_instruction, SystemInstruction},
  70. program as system_program,
  71. },
  72. solana_time_utils::timestamp,
  73. solana_tps_client::TpsClient,
  74. solana_tpu_client::tpu_client::DEFAULT_TPU_CONNECTION_POOL_SIZE,
  75. solana_transaction::Transaction,
  76. std::{
  77. net::SocketAddr,
  78. process::exit,
  79. sync::Arc,
  80. thread,
  81. time::{Duration, Instant},
  82. },
  83. };
  84. const PROGRESS_TIMEOUT_S: u64 = 120;
  85. const SAMPLE_PERIOD_MS: u64 = 10_000;
  86. fn compute_rate_per_second(count: usize) -> usize {
  87. (count * 1000) / (SAMPLE_PERIOD_MS as usize)
  88. }
  89. /// Provide functionality to generate several types of transactions:
  90. ///
  91. /// 1. Without blockhash
  92. /// 1.1 With valid signatures (number of signatures is configurable)
  93. /// 1.2 With invalid signatures (number of signatures is configurable)
  94. ///
  95. /// 2. With blockhash (but still deliberately invalid):
  96. /// 2.1 Transfer from 1 payer to multiple destinations (many instructions per transaction)
  97. /// 2.2 Create an account
  98. ///
  99. #[derive(Clone)]
  100. struct TransactionGenerator {
  101. blockhash: Hash,
  102. last_generated: Instant,
  103. transaction_params: TransactionParams,
  104. }
  105. impl TransactionGenerator {
  106. fn new(transaction_params: TransactionParams) -> Self {
  107. TransactionGenerator {
  108. blockhash: Hash::default(),
  109. last_generated: Instant::now()
  110. .checked_sub(Duration::from_secs(100))
  111. .unwrap(), //to force generation when generate is called
  112. transaction_params,
  113. }
  114. }
  115. /// Generate transaction
  116. ///
  117. /// `payer` - the account responsible for paying the cost of executing transaction, used as
  118. /// a source for transfer instructions and as funding account for create-account instructions.
  119. /// `destinations` - depending on the transaction type, might be destination accounts receiving transfers,
  120. /// new accounts, signing accounts. It is `None` only if `valid_signatures==false`.
  121. /// `client` - structure responsible for providing blockhash.
  122. ///
  123. fn generate<T: 'static + TpsClient + Send + Sync>(
  124. &mut self,
  125. payer: Option<&Keypair>,
  126. destinations: Option<Vec<&Keypair>>,
  127. client: Option<&Arc<T>>,
  128. ) -> Transaction {
  129. if self.transaction_params.valid_blockhash {
  130. let client = client.as_ref().unwrap();
  131. let destinations = destinations.unwrap();
  132. let payer = payer.as_ref().unwrap();
  133. self.generate_with_blockhash(payer, destinations, client)
  134. } else {
  135. self.generate_without_blockhash(destinations)
  136. }
  137. }
  138. fn generate_with_blockhash<T: 'static + TpsClient + Send + Sync>(
  139. &mut self,
  140. payer: &Keypair,
  141. destinations: Vec<&Keypair>,
  142. client: &Arc<T>,
  143. ) -> Transaction {
  144. // generate a new blockhash every 1sec
  145. if self.transaction_params.valid_blockhash
  146. && self.last_generated.elapsed().as_millis() > 1000
  147. {
  148. self.blockhash = client.get_latest_blockhash().unwrap();
  149. self.last_generated = Instant::now();
  150. }
  151. // transaction_type is known to be present because it is required by blockhash option in cli
  152. let transaction_type = self.transaction_params.transaction_type.as_ref().unwrap();
  153. match transaction_type {
  154. TransactionType::Transfer => {
  155. self.create_multi_transfer_transaction(payer, &destinations)
  156. }
  157. TransactionType::AccountCreation => {
  158. self.create_account_transaction(payer, destinations[0])
  159. }
  160. }
  161. }
  162. /// Create a transaction which transfers some lamports from payer to several destinations
  163. fn create_multi_transfer_transaction(&self, payer: &Keypair, to: &[&Keypair]) -> Transaction {
  164. let to_transfer: u64 = 500_000_000; // specify amount which will cause error
  165. let to: Vec<(Pubkey, u64)> = to.iter().map(|to| (to.pubkey(), to_transfer)).collect();
  166. let instructions = system_instruction::transfer_many(&payer.pubkey(), to.as_slice());
  167. let message = Message::new(&instructions, Some(&payer.pubkey()));
  168. let mut tx = Transaction::new_unsigned(message);
  169. tx.sign(&[payer], self.blockhash);
  170. tx
  171. }
  172. /// Create a transaction which opens account
  173. fn create_account_transaction(&self, payer: &Keypair, to: &Keypair) -> Transaction {
  174. let program_id = system_program::id(); // some valid program id
  175. let balance = 500_000_000;
  176. let space = 1024;
  177. let instructions = vec![system_instruction::create_account(
  178. &payer.pubkey(),
  179. &to.pubkey(),
  180. balance,
  181. space,
  182. &program_id,
  183. )];
  184. let message = Message::new(&instructions, Some(&payer.pubkey()));
  185. let signers: Vec<&Keypair> = vec![payer, to];
  186. Transaction::new(&signers, message, self.blockhash)
  187. }
  188. fn generate_without_blockhash(
  189. &mut self,
  190. destinations: Option<Vec<&Keypair>>, // provided for valid signatures
  191. ) -> Transaction {
  192. // create an arbitrary valid instruction
  193. let lamports = 5;
  194. let transfer_instruction = SystemInstruction::Transfer { lamports };
  195. let program_ids = vec![system_program::id(), stake::program::id()];
  196. let instructions = vec![CompiledInstruction::new(
  197. 0,
  198. &transfer_instruction,
  199. vec![0, 1],
  200. )];
  201. if self.transaction_params.valid_signatures {
  202. // Since we don't provide a payer, this transaction will end up
  203. // filtered at legacy.rs sanitize method (banking_stage) with error "a program cannot be payer"
  204. let destinations = destinations.unwrap();
  205. Transaction::new_with_compiled_instructions(
  206. &destinations,
  207. &[],
  208. self.blockhash,
  209. program_ids,
  210. instructions,
  211. )
  212. } else {
  213. // Since we provided invalid signatures
  214. // this transaction will end up filtered at legacy.rs (banking_stage) because
  215. // num_required_signatures == 0
  216. let mut tx = Transaction::new_with_compiled_instructions(
  217. &[] as &[&Keypair; 0],
  218. &[],
  219. self.blockhash,
  220. program_ids,
  221. instructions,
  222. );
  223. let num_signatures = self.transaction_params.num_signatures.unwrap();
  224. tx.signatures = vec![Signature::new_unique(); num_signatures];
  225. tx
  226. }
  227. }
  228. }
  229. // Multithreading-related functions
  230. //
  231. // The most computationally expensive work is signing new transactions.
  232. // Here we generate them in `num_gen_threads` threads.
  233. //
  234. struct TransactionBatchMsg {
  235. batch: Vec<Vec<u8>>,
  236. gen_time: u64,
  237. }
  238. /// Creates thread which receives batches of transactions from tx_receiver
  239. /// and sends them to the target.
  240. /// If `iterations` is 0, it works indefenetely.
  241. /// Otherwise, it sends at least `iterations` number of transactions
  242. fn create_sender_thread(
  243. tx_receiver: Receiver<TransactionBatchMsg>,
  244. iterations: usize,
  245. target: &SocketAddr,
  246. tpu_use_quic: bool,
  247. ) -> thread::JoinHandle<()> {
  248. // ConnectionCache is used instead of client because it gives ~6% higher pps
  249. let connection_cache = if tpu_use_quic {
  250. ConnectionCache::new_quic(
  251. "connection_cache_dos_quic",
  252. DEFAULT_TPU_CONNECTION_POOL_SIZE,
  253. )
  254. } else {
  255. ConnectionCache::with_udp("connection_cache_dos_udp", DEFAULT_TPU_CONNECTION_POOL_SIZE)
  256. };
  257. let connection = connection_cache.get_connection(target);
  258. let stats_timer_receiver = tick(Duration::from_millis(SAMPLE_PERIOD_MS));
  259. let progress_timer_receiver = tick(Duration::from_secs(PROGRESS_TIMEOUT_S));
  260. let mut time_send_ns = 0;
  261. let mut time_generate_ns = 0;
  262. // Sender signals to stop Generators by dropping receiver.
  263. // It happens in 2 cases:
  264. // * Sender has sent at least `iterations` number of transactions
  265. // * Sender observes that there is no progress. Since there is no way to use recv_timeout with select,
  266. // a timer is used.
  267. thread::Builder::new().name("Sender".to_string()).spawn(move || {
  268. let mut total_count: usize = 0;
  269. let mut prev_total_count = 0; // to track progress
  270. let mut stats_count: usize = 0;
  271. let mut stats_error_count: usize = 0;
  272. loop {
  273. select! {
  274. recv(tx_receiver) -> msg => {
  275. match msg {
  276. Ok(tx_batch) => {
  277. let len = tx_batch.batch.len();
  278. let mut measure_send_txs = Measure::start("measure_send_txs");
  279. let res = connection.send_data_batch_async(tx_batch.batch);
  280. measure_send_txs.stop();
  281. time_send_ns += measure_send_txs.as_ns();
  282. time_generate_ns += tx_batch.gen_time;
  283. if res.is_err() {
  284. stats_error_count += len;
  285. }
  286. stats_count += len;
  287. total_count += len;
  288. if iterations != 0 && total_count >= iterations {
  289. info!("All transactions have been sent");
  290. // dropping receiver to signal generator threads to stop
  291. drop(tx_receiver);
  292. break;
  293. }
  294. }
  295. _ => panic!("Sender panics"),
  296. }
  297. },
  298. recv(stats_timer_receiver) -> _ => {
  299. info!("tx_receiver queue len: {}", tx_receiver.len());
  300. info!("Count: {}, error count: {}, send mean time: {}, generate mean time: {}, rps: {}",
  301. stats_count,
  302. stats_error_count,
  303. time_send_ns.checked_div(stats_count as u64).unwrap_or(0),
  304. time_generate_ns.checked_div(stats_count as u64).unwrap_or(0),
  305. compute_rate_per_second(stats_count),
  306. );
  307. stats_count = 0;
  308. stats_error_count = 0;
  309. time_send_ns = 0;
  310. time_generate_ns = 0;
  311. },
  312. recv(progress_timer_receiver) -> _ => {
  313. if prev_total_count - total_count == 0 {
  314. info!("No progress, stop execution");
  315. // dropping receiver to signal generator threads to stop
  316. drop(tx_receiver);
  317. break;
  318. }
  319. prev_total_count = total_count;
  320. }
  321. }
  322. }
  323. }).unwrap()
  324. }
  325. fn create_generator_thread<T: 'static + TpsClient + Send + Sync>(
  326. tx_sender: &Sender<TransactionBatchMsg>,
  327. send_batch_size: usize,
  328. transaction_generator: &TransactionGenerator,
  329. client: Option<Arc<T>>,
  330. payer: Option<Keypair>,
  331. ) -> thread::JoinHandle<()> {
  332. let tx_sender = tx_sender.clone();
  333. let mut transaction_generator = transaction_generator.clone();
  334. let transaction_params: &TransactionParams = &transaction_generator.transaction_params;
  335. // Generate n=1000 unique keypairs
  336. // The number of chunks is described by binomial coefficient
  337. // and hence this choice of n provides large enough number of permutations
  338. let mut keypairs_flat: Vec<Keypair> = Vec::new();
  339. // 1000 is arbitrary number. In case of permutation_size > 1,
  340. // this guarantees large enough set of unique permutations
  341. let permutation_size = get_permutation_size(
  342. transaction_params.num_signatures.as_ref(),
  343. transaction_params.num_instructions.as_ref(),
  344. );
  345. let num_keypairs = 1000 * permutation_size;
  346. let generate_keypairs =
  347. transaction_params.valid_signatures || transaction_params.valid_blockhash;
  348. if generate_keypairs {
  349. keypairs_flat = (0..num_keypairs).map(|_| Keypair::new()).collect();
  350. }
  351. thread::Builder::new()
  352. .name("Generator".to_string())
  353. .spawn(move || {
  354. let indexes: Vec<usize> = (0..keypairs_flat.len()).collect();
  355. let mut it = indexes.iter().permutations(permutation_size);
  356. loop {
  357. let mut data = Vec::<Vec<u8>>::with_capacity(send_batch_size);
  358. let mut measure_generate_txs = Measure::start("measure_generate_txs");
  359. for _ in 0..send_batch_size {
  360. let chunk_keypairs = if generate_keypairs {
  361. let mut permutation = it.next();
  362. if permutation.is_none() {
  363. // if ran out of permutations, regenerate keys
  364. keypairs_flat.iter_mut().for_each(|v| *v = Keypair::new());
  365. info!("Regenerate keypairs");
  366. permutation = it.next();
  367. }
  368. let permutation = permutation.unwrap();
  369. Some(apply_permutation(permutation, &keypairs_flat))
  370. } else {
  371. None
  372. };
  373. let tx = transaction_generator.generate(
  374. payer.as_ref(),
  375. chunk_keypairs,
  376. client.as_ref(),
  377. );
  378. data.push(bincode::serialize(&tx).unwrap());
  379. }
  380. measure_generate_txs.stop();
  381. let result = tx_sender.send(TransactionBatchMsg {
  382. batch: data,
  383. gen_time: measure_generate_txs.as_ns(),
  384. });
  385. if result.is_err() {
  386. // means that receiver has been dropped by sender thread
  387. info!("Exit generator thread");
  388. break;
  389. }
  390. }
  391. })
  392. .unwrap()
  393. }
  394. fn get_target(
  395. nodes: &[ContactInfo],
  396. mode: Mode,
  397. entrypoint_addr: SocketAddr,
  398. tpu_use_quic: bool,
  399. ) -> Option<(Pubkey, SocketAddr)> {
  400. let protocol = if tpu_use_quic {
  401. Protocol::QUIC
  402. } else {
  403. Protocol::UDP
  404. };
  405. let mut target = None;
  406. if nodes.is_empty() {
  407. // skip-gossip case
  408. target = Some((solana_pubkey::new_rand(), entrypoint_addr));
  409. } else {
  410. info!("************ NODE ***********");
  411. for node in nodes {
  412. info!("{node:?}");
  413. }
  414. info!("ADDR = {entrypoint_addr}");
  415. for node in nodes {
  416. if node.gossip() == Some(entrypoint_addr) {
  417. info!("{:?}", node.gossip());
  418. target = match mode {
  419. Mode::Gossip => Some((*node.pubkey(), node.gossip().unwrap())),
  420. Mode::Tvu => Some((*node.pubkey(), node.tvu(Protocol::UDP).unwrap())),
  421. Mode::Tpu => Some((*node.pubkey(), node.tpu(protocol).unwrap())),
  422. Mode::TpuForwards => {
  423. Some((*node.pubkey(), node.tpu_forwards(protocol).unwrap()))
  424. }
  425. Mode::Repair => todo!("repair socket is not gossiped anymore!"),
  426. Mode::ServeRepair => {
  427. Some((*node.pubkey(), node.serve_repair(Protocol::UDP).unwrap()))
  428. }
  429. Mode::Rpc => None,
  430. };
  431. break;
  432. }
  433. }
  434. }
  435. target
  436. }
  437. fn get_rpc_client(
  438. nodes: &[ContactInfo],
  439. entrypoint_addr: SocketAddr,
  440. ) -> Result<RpcClient, &'static str> {
  441. if nodes.is_empty() {
  442. // skip-gossip case
  443. return Ok(RpcClient::new_socket(entrypoint_addr));
  444. }
  445. // find target node
  446. for node in nodes {
  447. if node.gossip() == Some(entrypoint_addr) {
  448. info!("{:?}", node.gossip());
  449. return Ok(RpcClient::new_socket(node.rpc().unwrap()));
  450. }
  451. }
  452. Err("Node with entrypoint_addr was not found")
  453. }
  454. fn run_dos_rpc_mode_helper<F: Fn() -> bool>(iterations: usize, rpc_client_call: F) {
  455. let mut last_log = Instant::now();
  456. let mut total_count: usize = 0;
  457. let mut count = 0;
  458. let mut error_count = 0;
  459. loop {
  460. if !rpc_client_call() {
  461. error_count += 1;
  462. }
  463. count += 1;
  464. total_count += 1;
  465. if last_log.elapsed().as_millis() > SAMPLE_PERIOD_MS as u128 {
  466. info!(
  467. "count: {}, errors: {}, rps: {}",
  468. count,
  469. error_count,
  470. compute_rate_per_second(count)
  471. );
  472. last_log = Instant::now();
  473. count = 0;
  474. }
  475. if iterations != 0 && total_count >= iterations {
  476. break;
  477. }
  478. }
  479. }
  480. fn run_dos_rpc_mode(
  481. rpc_client: RpcClient,
  482. iterations: usize,
  483. data_type: DataType,
  484. data_input: &Pubkey,
  485. ) {
  486. match data_type {
  487. DataType::GetAccountInfo => {
  488. run_dos_rpc_mode_helper(iterations, || -> bool {
  489. rpc_client.get_account(data_input).is_ok()
  490. });
  491. }
  492. DataType::GetProgramAccounts => {
  493. run_dos_rpc_mode_helper(iterations, || -> bool {
  494. rpc_client.get_program_accounts(data_input).is_ok()
  495. });
  496. }
  497. _ => {
  498. panic!("unsupported data type");
  499. }
  500. }
  501. }
  502. /// Apply given permutation to the vector of items
  503. fn apply_permutation<'a, T>(permutation: Vec<&usize>, items: &'a [T]) -> Vec<&'a T> {
  504. let mut res = Vec::with_capacity(permutation.len());
  505. for i in permutation {
  506. res.push(&items[*i]);
  507. }
  508. res
  509. }
  510. fn create_payers<T: 'static + TpsClient + Send + Sync>(
  511. valid_blockhash: bool,
  512. size: usize,
  513. client: Option<&Arc<T>>,
  514. ) -> Vec<Option<Keypair>> {
  515. // Assume that if we use valid blockhash, we also have a payer
  516. if valid_blockhash {
  517. // each payer is used to fund transaction
  518. // transactions are built to be invalid so the amount here is arbitrary
  519. let funding_key = Keypair::new();
  520. let funding_key = Arc::new(funding_key);
  521. let res = generate_and_fund_keypairs(
  522. client.unwrap().clone(),
  523. &funding_key,
  524. size,
  525. 1_000_000,
  526. false,
  527. false,
  528. )
  529. .unwrap_or_else(|e| {
  530. eprintln!("Error could not fund keys: {e:?}");
  531. exit(1);
  532. });
  533. res.into_iter().map(Some).collect()
  534. } else {
  535. std::iter::repeat_with(|| None).take(size).collect()
  536. }
  537. }
  538. fn get_permutation_size(num_signatures: Option<&usize>, num_instructions: Option<&usize>) -> usize {
  539. if let Some(num_signatures) = num_signatures {
  540. *num_signatures
  541. } else if let Some(num_instructions) = num_instructions {
  542. *num_instructions
  543. } else {
  544. 1 // for the case AccountCreation
  545. }
  546. }
  547. fn run_dos_transactions<T: 'static + TpsClient + Send + Sync>(
  548. target: SocketAddr,
  549. iterations: usize,
  550. client: Option<Arc<T>>,
  551. transaction_params: TransactionParams,
  552. tpu_use_quic: bool,
  553. num_gen_threads: usize,
  554. send_batch_size: usize,
  555. ) {
  556. // Number of payers is the number of generating threads
  557. // Later, we will create a new payer for each thread since Keypair is not clonable
  558. let payers: Vec<Option<Keypair>> = create_payers(
  559. transaction_params.valid_blockhash,
  560. num_gen_threads,
  561. client.as_ref(),
  562. );
  563. let transaction_generator = TransactionGenerator::new(transaction_params);
  564. let (tx_sender, tx_receiver) = unbounded();
  565. let sender_thread = create_sender_thread(tx_receiver, iterations, &target, tpu_use_quic);
  566. let tx_generator_threads: Vec<_> = payers
  567. .into_iter()
  568. .map(|payer| {
  569. create_generator_thread(
  570. &tx_sender,
  571. send_batch_size,
  572. &transaction_generator,
  573. client.clone(),
  574. payer,
  575. )
  576. })
  577. .collect();
  578. if let Err(err) = sender_thread.join() {
  579. println!("join() failed with: {err:?}");
  580. }
  581. for t_generator in tx_generator_threads {
  582. if let Err(err) = t_generator.join() {
  583. println!("join() failed with: {err:?}");
  584. }
  585. }
  586. }
  587. fn run_dos<T: 'static + TpsClient + Send + Sync>(
  588. nodes: &[ContactInfo],
  589. iterations: usize,
  590. client: Option<Arc<T>>,
  591. params: DosClientParameters,
  592. ) {
  593. let target = get_target(
  594. nodes,
  595. params.mode,
  596. params.entrypoint_addr,
  597. params.tpu_use_quic,
  598. );
  599. if params.mode == Mode::Rpc {
  600. // creating rpc_client because get_account, get_program_accounts are not implemented for TpsClient
  601. let rpc_client =
  602. get_rpc_client(nodes, params.entrypoint_addr).expect("Failed to get rpc client");
  603. // existence of data_input is checked at cli level
  604. run_dos_rpc_mode(
  605. rpc_client,
  606. iterations,
  607. params.data_type,
  608. &params.data_input.unwrap(),
  609. );
  610. } else if params.data_type == DataType::Transaction
  611. && params.transaction_params.unique_transactions
  612. {
  613. let (_, target_addr) = target.expect("should have target");
  614. info!("Targeting {target_addr}");
  615. run_dos_transactions(
  616. target_addr,
  617. iterations,
  618. client,
  619. params.transaction_params,
  620. params.tpu_use_quic,
  621. params.num_gen_threads,
  622. params.send_batch_size,
  623. );
  624. } else {
  625. let (target_id, target_addr) = target.expect("should have target");
  626. info!("Targeting {target_addr}");
  627. let mut data = match params.data_type {
  628. DataType::RepairHighest => {
  629. let slot = 100;
  630. let keypair = Keypair::new();
  631. let header = RepairRequestHeader::new(keypair.pubkey(), target_id, timestamp(), 0);
  632. let req = RepairProtocol::WindowIndex {
  633. header,
  634. slot,
  635. shred_index: 0,
  636. };
  637. ServeRepair::repair_proto_to_bytes(&req, &keypair).unwrap()
  638. }
  639. DataType::RepairShred => {
  640. let slot = 100;
  641. let keypair = Keypair::new();
  642. let header = RepairRequestHeader::new(keypair.pubkey(), target_id, timestamp(), 0);
  643. let req = RepairProtocol::HighestWindowIndex {
  644. header,
  645. slot,
  646. shred_index: 0,
  647. };
  648. ServeRepair::repair_proto_to_bytes(&req, &keypair).unwrap()
  649. }
  650. DataType::RepairOrphan => {
  651. let slot = 100;
  652. let keypair = Keypair::new();
  653. let header = RepairRequestHeader::new(keypair.pubkey(), target_id, timestamp(), 0);
  654. let req = RepairProtocol::Orphan { header, slot };
  655. ServeRepair::repair_proto_to_bytes(&req, &keypair).unwrap()
  656. }
  657. DataType::Random => {
  658. vec![0; params.data_size]
  659. }
  660. DataType::Transaction => {
  661. let tp = params.transaction_params;
  662. info!("{tp:?}");
  663. let valid_blockhash = tp.valid_blockhash;
  664. let payers: Vec<Option<Keypair>> =
  665. create_payers(valid_blockhash, 1, client.as_ref());
  666. let payer = payers[0].as_ref();
  667. let permutation_size =
  668. get_permutation_size(tp.num_signatures.as_ref(), tp.num_instructions.as_ref());
  669. let keypairs: Vec<Keypair> =
  670. (0..permutation_size).map(|_| Keypair::new()).collect();
  671. let keypairs_chunk: Option<Vec<&Keypair>> =
  672. if tp.valid_signatures || tp.valid_blockhash {
  673. Some(keypairs.iter().collect())
  674. } else {
  675. None
  676. };
  677. let mut transaction_generator = TransactionGenerator::new(tp);
  678. let tx = transaction_generator.generate(payer, keypairs_chunk, client.as_ref());
  679. info!("{tx:?}");
  680. bincode::serialize(&tx).unwrap()
  681. }
  682. _ => panic!("Unsupported data_type detected"),
  683. };
  684. let socket = bind_to_unspecified().unwrap();
  685. let mut last_log = Instant::now();
  686. let mut total_count: usize = 0;
  687. let mut count: usize = 0;
  688. let mut error_count = 0;
  689. loop {
  690. if params.data_type == DataType::Random {
  691. thread_rng().fill(&mut data[..]);
  692. }
  693. let res = socket.send_to(&data, target_addr);
  694. if res.is_err() {
  695. error_count += 1;
  696. }
  697. count += 1;
  698. total_count += 1;
  699. if last_log.elapsed().as_millis() > SAMPLE_PERIOD_MS as u128 {
  700. info!(
  701. "count: {}, errors: {}, rps: {}",
  702. count,
  703. error_count,
  704. compute_rate_per_second(count)
  705. );
  706. last_log = Instant::now();
  707. count = 0;
  708. }
  709. if iterations != 0 && total_count >= iterations {
  710. break;
  711. }
  712. }
  713. }
  714. }
  715. fn main() {
  716. agave_logger::setup_with_default_filter();
  717. let mut cmd_params = build_cli_parameters();
  718. if !cmd_params.skip_gossip && cmd_params.shred_version.is_none() {
  719. // Try to get shred version from the entrypoint
  720. cmd_params.shred_version = Some(
  721. solana_net_utils::get_cluster_shred_version(&cmd_params.entrypoint_addr)
  722. .unwrap_or_else(|err| {
  723. eprintln!("Failed to get shred version: {err}");
  724. exit(1);
  725. }),
  726. );
  727. }
  728. let (nodes, client) = if !cmd_params.skip_gossip {
  729. info!("Finding cluster entry: {:?}", cmd_params.entrypoint_addr);
  730. let socket_addr_space = SocketAddrSpace::new(cmd_params.allow_private_addr);
  731. let (gossip_nodes, validators) = discover(
  732. None, // keypair
  733. Some(&cmd_params.entrypoint_addr),
  734. None, // num_nodes
  735. Duration::from_secs(60), // timeout
  736. None, // find_nodes_by_pubkey
  737. Some(&cmd_params.entrypoint_addr), // find_node_by_gossip_addr
  738. None, // my_gossip_addr
  739. cmd_params.shred_version.unwrap(), // my_shred_version
  740. socket_addr_space,
  741. )
  742. .unwrap_or_else(|err| {
  743. eprintln!(
  744. "Failed to discover {} node: {:?}",
  745. cmd_params.entrypoint_addr, err
  746. );
  747. exit(1);
  748. });
  749. let connection_cache = match cmd_params.tpu_use_quic {
  750. true => ConnectionCache::new_quic(
  751. "connection_cache_dos_quic",
  752. DEFAULT_TPU_CONNECTION_POOL_SIZE,
  753. ),
  754. false => ConnectionCache::with_udp(
  755. "connection_cache_dos_udp",
  756. DEFAULT_TPU_CONNECTION_POOL_SIZE,
  757. ),
  758. };
  759. let client = get_client(&validators, Arc::new(connection_cache));
  760. (gossip_nodes, Some(client))
  761. } else {
  762. (vec![], None)
  763. };
  764. info!("done found {} nodes", nodes.len());
  765. if let Some(tpu_client) = client {
  766. match tpu_client {
  767. TpuClientWrapper::Quic(quic_client) => {
  768. run_dos(&nodes, 0, Some(Arc::new(quic_client)), cmd_params);
  769. }
  770. TpuClientWrapper::Udp(udp_client) => {
  771. run_dos(&nodes, 0, Some(Arc::new(udp_client)), cmd_params);
  772. }
  773. };
  774. }
  775. }
  776. #[cfg(test)]
  777. pub mod test {
  778. use {
  779. super::*,
  780. solana_core::validator::ValidatorConfig,
  781. solana_faucet::faucet::run_local_faucet_with_unique_port_for_tests,
  782. solana_local_cluster::{
  783. cluster::Cluster,
  784. local_cluster::{ClusterConfig, LocalCluster},
  785. validator_configs::make_identical_validator_configs,
  786. },
  787. solana_quic_client::{QuicConfig, QuicConnectionManager, QuicPool},
  788. solana_rpc::rpc::JsonRpcConfig,
  789. solana_time_utils::timestamp,
  790. solana_tpu_client::tpu_client::TpuClient,
  791. };
  792. const TEST_SEND_BATCH_SIZE: usize = 1;
  793. // thin wrapper for the run_dos function
  794. // to avoid specifying everywhere generic parameters
  795. fn run_dos_no_client(nodes: &[ContactInfo], iterations: usize, params: DosClientParameters) {
  796. run_dos::<TpuClient<QuicPool, QuicConnectionManager, QuicConfig>>(
  797. nodes, iterations, None, params,
  798. );
  799. }
  800. #[test]
  801. fn test_dos() {
  802. let nodes = [ContactInfo::new_localhost(
  803. &solana_pubkey::new_rand(),
  804. timestamp(),
  805. )];
  806. let entrypoint_addr = nodes[0].gossip().unwrap();
  807. run_dos_no_client(
  808. &nodes,
  809. 1,
  810. DosClientParameters {
  811. entrypoint_addr,
  812. mode: Mode::Tvu,
  813. data_size: 10,
  814. data_type: DataType::Random,
  815. data_input: None,
  816. skip_gossip: false,
  817. shred_version: Some(42),
  818. allow_private_addr: false,
  819. num_gen_threads: 1,
  820. transaction_params: TransactionParams::default(),
  821. tpu_use_quic: false,
  822. send_batch_size: TEST_SEND_BATCH_SIZE,
  823. },
  824. );
  825. // TODO: Figure out how to DOS repair. Repair socket is no longer
  826. // gossiped and cannot be obtained from a node's contact-info.
  827. #[cfg(not(test))]
  828. run_dos_no_client(
  829. &nodes,
  830. 1,
  831. DosClientParameters {
  832. entrypoint_addr,
  833. mode: Mode::Repair,
  834. data_size: 10,
  835. data_type: DataType::RepairHighest,
  836. data_input: None,
  837. skip_gossip: false,
  838. shred_version: Some(42),
  839. allow_private_addr: false,
  840. num_gen_threads: 1,
  841. transaction_params: TransactionParams::default(),
  842. tpu_use_quic: false,
  843. send_batch_size: TEST_SEND_BATCH_SIZE,
  844. },
  845. );
  846. run_dos_no_client(
  847. &nodes,
  848. 1,
  849. DosClientParameters {
  850. entrypoint_addr,
  851. mode: Mode::ServeRepair,
  852. data_size: 10,
  853. data_type: DataType::RepairShred,
  854. data_input: None,
  855. skip_gossip: false,
  856. shred_version: Some(42),
  857. allow_private_addr: false,
  858. num_gen_threads: 1,
  859. transaction_params: TransactionParams::default(),
  860. tpu_use_quic: false,
  861. send_batch_size: TEST_SEND_BATCH_SIZE,
  862. },
  863. );
  864. run_dos_no_client(
  865. &nodes,
  866. 1,
  867. DosClientParameters {
  868. entrypoint_addr,
  869. mode: Mode::Rpc,
  870. data_size: 0,
  871. data_type: DataType::GetAccountInfo,
  872. data_input: Some(Pubkey::default()),
  873. skip_gossip: false,
  874. shred_version: Some(42),
  875. allow_private_addr: false,
  876. num_gen_threads: 1,
  877. transaction_params: TransactionParams::default(),
  878. tpu_use_quic: false,
  879. send_batch_size: TEST_SEND_BATCH_SIZE,
  880. },
  881. );
  882. }
  883. #[test]
  884. fn test_dos_random() {
  885. agave_logger::setup();
  886. let num_nodes = 1;
  887. let cluster =
  888. LocalCluster::new_with_equal_stakes(num_nodes, 100, 3, SocketAddrSpace::Unspecified);
  889. assert_eq!(cluster.validators.len(), num_nodes);
  890. let nodes = cluster.get_node_pubkeys();
  891. let node = cluster.get_contact_info(&nodes[0]).unwrap().clone();
  892. let nodes_slice = [node];
  893. // send random transactions to TPU
  894. // will be discarded on sigverify stage
  895. run_dos_no_client(
  896. &nodes_slice,
  897. 10,
  898. DosClientParameters {
  899. entrypoint_addr: cluster.entry_point_info.gossip().unwrap(),
  900. mode: Mode::Tpu,
  901. data_size: 1024,
  902. data_type: DataType::Random,
  903. data_input: None,
  904. skip_gossip: false,
  905. shred_version: Some(42),
  906. allow_private_addr: false,
  907. num_gen_threads: 1,
  908. transaction_params: TransactionParams::default(),
  909. tpu_use_quic: false,
  910. send_batch_size: TEST_SEND_BATCH_SIZE,
  911. },
  912. );
  913. }
  914. #[test]
  915. fn test_dos_without_blockhash() {
  916. agave_logger::setup();
  917. let num_nodes = 1;
  918. let cluster =
  919. LocalCluster::new_with_equal_stakes(num_nodes, 100, 3, SocketAddrSpace::Unspecified);
  920. assert_eq!(cluster.validators.len(), num_nodes);
  921. let nodes = cluster.get_node_pubkeys();
  922. let node = cluster.get_contact_info(&nodes[0]).unwrap().clone();
  923. let nodes_slice = [node];
  924. let client = Arc::new(
  925. cluster
  926. .build_validator_tpu_quic_client(cluster.entry_point_info.pubkey())
  927. .unwrap_or_else(|err| {
  928. panic!("Could not create TpuClient with Quic Cache {err:?}");
  929. }),
  930. );
  931. // creates one transaction with 8 valid signatures and sends it 10 times
  932. run_dos(
  933. &nodes_slice,
  934. 10,
  935. Some(client.clone()),
  936. DosClientParameters {
  937. entrypoint_addr: cluster.entry_point_info.gossip().unwrap(),
  938. mode: Mode::Tpu,
  939. data_size: 0, // irrelevant
  940. data_type: DataType::Transaction,
  941. data_input: None,
  942. skip_gossip: false,
  943. shred_version: Some(42),
  944. allow_private_addr: false,
  945. num_gen_threads: 1,
  946. transaction_params: TransactionParams {
  947. num_signatures: Some(8),
  948. valid_blockhash: false,
  949. valid_signatures: true,
  950. unique_transactions: false,
  951. transaction_type: None,
  952. num_instructions: None,
  953. },
  954. tpu_use_quic: false,
  955. send_batch_size: TEST_SEND_BATCH_SIZE,
  956. },
  957. );
  958. // creates and sends unique transactions which have invalid signatures
  959. run_dos(
  960. &nodes_slice,
  961. 10,
  962. Some(client.clone()),
  963. DosClientParameters {
  964. entrypoint_addr: cluster.entry_point_info.gossip().unwrap(),
  965. mode: Mode::Tpu,
  966. data_size: 0, // irrelevant
  967. data_type: DataType::Transaction,
  968. data_input: None,
  969. skip_gossip: false,
  970. shred_version: Some(42),
  971. allow_private_addr: false,
  972. num_gen_threads: 1,
  973. transaction_params: TransactionParams {
  974. num_signatures: Some(8),
  975. valid_blockhash: false,
  976. valid_signatures: false,
  977. unique_transactions: true,
  978. transaction_type: None,
  979. num_instructions: None,
  980. },
  981. tpu_use_quic: false,
  982. send_batch_size: TEST_SEND_BATCH_SIZE,
  983. },
  984. );
  985. // creates and sends unique transactions which have valid signatures
  986. run_dos(
  987. &nodes_slice,
  988. 10,
  989. Some(client),
  990. DosClientParameters {
  991. entrypoint_addr: cluster.entry_point_info.gossip().unwrap(),
  992. mode: Mode::Tpu,
  993. data_size: 0, // irrelevant
  994. data_type: DataType::Transaction,
  995. data_input: None,
  996. skip_gossip: false,
  997. shred_version: Some(42),
  998. allow_private_addr: false,
  999. num_gen_threads: 1,
  1000. transaction_params: TransactionParams {
  1001. num_signatures: Some(8),
  1002. valid_blockhash: false,
  1003. valid_signatures: true,
  1004. unique_transactions: true,
  1005. transaction_type: None,
  1006. num_instructions: None,
  1007. },
  1008. tpu_use_quic: false,
  1009. send_batch_size: TEST_SEND_BATCH_SIZE,
  1010. },
  1011. );
  1012. }
  1013. fn run_dos_with_blockhash_and_payer(tpu_use_quic: bool) {
  1014. agave_logger::setup();
  1015. // 1. Create faucet thread
  1016. let faucet_keypair = Keypair::new();
  1017. let faucet_pubkey = faucet_keypair.pubkey();
  1018. let faucet_addr = run_local_faucet_with_unique_port_for_tests(faucet_keypair);
  1019. let mut validator_config = ValidatorConfig::default_for_test();
  1020. validator_config.rpc_config = JsonRpcConfig {
  1021. faucet_addr: Some(faucet_addr),
  1022. ..JsonRpcConfig::default_for_test()
  1023. };
  1024. // 2. Create a local cluster which is aware of faucet
  1025. let num_nodes = 1;
  1026. let cluster = LocalCluster::new(
  1027. &mut ClusterConfig {
  1028. node_stakes: vec![999_990; num_nodes],
  1029. mint_lamports: 200_000_000,
  1030. validator_configs: make_identical_validator_configs(
  1031. &ValidatorConfig {
  1032. rpc_config: JsonRpcConfig {
  1033. faucet_addr: Some(faucet_addr),
  1034. ..JsonRpcConfig::default_for_test()
  1035. },
  1036. ..ValidatorConfig::default_for_test()
  1037. },
  1038. num_nodes,
  1039. ),
  1040. ..ClusterConfig::default()
  1041. },
  1042. SocketAddrSpace::Unspecified,
  1043. );
  1044. assert_eq!(cluster.validators.len(), num_nodes);
  1045. // 3. Transfer funds to faucet account
  1046. cluster.transfer(&cluster.funding_keypair, &faucet_pubkey, 100_000_000);
  1047. let nodes = cluster.get_node_pubkeys();
  1048. let node = cluster.get_contact_info(&nodes[0]).unwrap().clone();
  1049. let nodes_slice = [node];
  1050. let client = Arc::new(
  1051. cluster
  1052. .build_validator_tpu_quic_client(cluster.entry_point_info.pubkey())
  1053. .unwrap_or_else(|err| {
  1054. panic!("Could not create TpuClient with Quic Cache {err:?}");
  1055. }),
  1056. );
  1057. // creates one transaction and sends it 10 times
  1058. // this is done in single thread
  1059. run_dos(
  1060. &nodes_slice,
  1061. 10,
  1062. Some(client.clone()),
  1063. DosClientParameters {
  1064. entrypoint_addr: cluster.entry_point_info.gossip().unwrap(),
  1065. mode: Mode::Tpu,
  1066. data_size: 0, // irrelevant if not random
  1067. data_type: DataType::Transaction,
  1068. data_input: None,
  1069. skip_gossip: false,
  1070. shred_version: Some(42),
  1071. allow_private_addr: false,
  1072. num_gen_threads: 1,
  1073. transaction_params: TransactionParams {
  1074. num_signatures: None,
  1075. valid_blockhash: true,
  1076. valid_signatures: true,
  1077. unique_transactions: false,
  1078. transaction_type: Some(TransactionType::Transfer),
  1079. num_instructions: Some(1),
  1080. },
  1081. tpu_use_quic,
  1082. send_batch_size: TEST_SEND_BATCH_SIZE,
  1083. },
  1084. );
  1085. // creates and sends unique transactions of Transfer
  1086. // which tries to send too much lamports from payer to one recipient
  1087. // it uses several threads
  1088. run_dos(
  1089. &nodes_slice,
  1090. 10,
  1091. Some(client.clone()),
  1092. DosClientParameters {
  1093. entrypoint_addr: cluster.entry_point_info.gossip().unwrap(),
  1094. mode: Mode::Tpu,
  1095. data_size: 0, // irrelevant if not random
  1096. data_type: DataType::Transaction,
  1097. data_input: None,
  1098. skip_gossip: false,
  1099. shred_version: Some(42),
  1100. allow_private_addr: false,
  1101. num_gen_threads: 1,
  1102. transaction_params: TransactionParams {
  1103. num_signatures: None,
  1104. valid_blockhash: true,
  1105. valid_signatures: true,
  1106. unique_transactions: true,
  1107. transaction_type: Some(TransactionType::Transfer),
  1108. num_instructions: Some(1),
  1109. },
  1110. tpu_use_quic,
  1111. send_batch_size: TEST_SEND_BATCH_SIZE,
  1112. },
  1113. );
  1114. // creates and sends unique transactions of type Transfer
  1115. // which tries to send too much lamports from payer to several recipients
  1116. // it uses several threads
  1117. run_dos(
  1118. &nodes_slice,
  1119. 10,
  1120. Some(client.clone()),
  1121. DosClientParameters {
  1122. entrypoint_addr: cluster.entry_point_info.gossip().unwrap(),
  1123. mode: Mode::Tpu,
  1124. data_size: 0, // irrelevant if not random
  1125. data_type: DataType::Transaction,
  1126. data_input: None,
  1127. skip_gossip: false,
  1128. shred_version: Some(42),
  1129. allow_private_addr: false,
  1130. num_gen_threads: 1,
  1131. transaction_params: TransactionParams {
  1132. num_signatures: None,
  1133. valid_blockhash: true,
  1134. valid_signatures: true,
  1135. unique_transactions: true,
  1136. transaction_type: Some(TransactionType::Transfer),
  1137. num_instructions: Some(8),
  1138. },
  1139. tpu_use_quic,
  1140. send_batch_size: TEST_SEND_BATCH_SIZE,
  1141. },
  1142. );
  1143. // creates and sends unique transactions of type CreateAccount
  1144. // which tries to create account with too large balance
  1145. // it uses several threads
  1146. run_dos(
  1147. &nodes_slice,
  1148. 10,
  1149. Some(client),
  1150. DosClientParameters {
  1151. entrypoint_addr: cluster.entry_point_info.gossip().unwrap(),
  1152. mode: Mode::Tpu,
  1153. data_size: 0, // irrelevant if not random
  1154. data_type: DataType::Transaction,
  1155. data_input: None,
  1156. skip_gossip: false,
  1157. shred_version: Some(42),
  1158. allow_private_addr: false,
  1159. num_gen_threads: 1,
  1160. transaction_params: TransactionParams {
  1161. num_signatures: None,
  1162. valid_blockhash: true,
  1163. valid_signatures: true,
  1164. unique_transactions: true,
  1165. transaction_type: Some(TransactionType::AccountCreation),
  1166. num_instructions: None,
  1167. },
  1168. tpu_use_quic,
  1169. send_batch_size: TEST_SEND_BATCH_SIZE,
  1170. },
  1171. );
  1172. }
  1173. #[test]
  1174. fn test_dos_with_blockhash_and_payer() {
  1175. run_dos_with_blockhash_and_payer(/*tpu_use_quic*/ false)
  1176. }
  1177. #[test]
  1178. fn test_dos_with_blockhash_and_payer_and_quic() {
  1179. run_dos_with_blockhash_and_payer(/*tpu_use_quic*/ true)
  1180. }
  1181. }