cli.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. use {
  2. clap::{crate_description, crate_name, crate_version, ArgEnum, Args, Parser},
  3. serde::{Deserialize, Serialize},
  4. solana_pubkey::Pubkey,
  5. std::{net::SocketAddr, process::exit, str::FromStr},
  6. };
  7. #[derive(Parser, Debug, PartialEq, Eq)]
  8. #[clap(name = crate_name!(),
  9. version = crate_version!(),
  10. about = crate_description!(),
  11. rename_all = "kebab-case"
  12. )]
  13. pub struct DosClientParameters {
  14. #[clap(long, arg_enum, help = "Interface to DoS")]
  15. pub mode: Mode,
  16. #[clap(long, arg_enum, help = "Type of data to send")]
  17. pub data_type: DataType,
  18. #[clap(
  19. long = "entrypoint",
  20. parse(try_from_str = addr_parser),
  21. default_value = "127.0.0.1:8001",
  22. help = "Gossip entrypoint address. Usually <ip>:8001"
  23. )]
  24. pub entrypoint_addr: SocketAddr,
  25. #[clap(
  26. long,
  27. default_value = "128",
  28. required_if_eq("data-type", "random"),
  29. help = "Size of packet to DoS with, relevant only for data-type=random"
  30. )]
  31. pub data_size: usize,
  32. #[clap(
  33. long,
  34. parse(try_from_str = pubkey_parser),
  35. required_if_eq("mode", "rpc"),
  36. help = "Pubkey for rpc-mode calls"
  37. )]
  38. pub data_input: Option<Pubkey>,
  39. #[clap(long, help = "Just use entrypoint address directly")]
  40. pub skip_gossip: bool,
  41. #[clap(
  42. long,
  43. conflicts_with("skip-gossip"),
  44. help = "The shred version to use for gossip discovery. If not provided, will be \
  45. discovered from the network"
  46. )]
  47. pub shred_version: Option<u16>,
  48. #[clap(long, help = "Allow contacting private ip addresses")]
  49. pub allow_private_addr: bool,
  50. #[clap(
  51. long,
  52. default_value = "1",
  53. help = "Number of threads generating transactions"
  54. )]
  55. pub num_gen_threads: usize,
  56. #[clap(flatten)]
  57. pub transaction_params: TransactionParams,
  58. #[clap(
  59. long,
  60. conflicts_with("skip-gossip"),
  61. help = "Submit transactions via QUIC"
  62. )]
  63. pub tpu_use_quic: bool,
  64. #[clap(long, default_value = "16384", help = "Size of the transactions batch")]
  65. pub send_batch_size: usize,
  66. }
  67. #[derive(Args, Clone, Serialize, Deserialize, Debug, Default, PartialEq, Eq)]
  68. #[clap(rename_all = "kebab-case")]
  69. pub struct TransactionParams {
  70. #[clap(
  71. long,
  72. conflicts_with("valid-blockhash"),
  73. help = "Number of signatures in transaction"
  74. )]
  75. pub num_signatures: Option<usize>,
  76. #[clap(
  77. long,
  78. requires("transaction-type"),
  79. conflicts_with("skip-gossip"),
  80. help = "Generate a valid blockhash for transaction"
  81. )]
  82. pub valid_blockhash: bool,
  83. #[clap(
  84. long,
  85. requires("num-signatures"),
  86. help = "Generate valid signature(s) for transaction"
  87. )]
  88. pub valid_signatures: bool,
  89. #[clap(long, help = "Generate unique transactions")]
  90. pub unique_transactions: bool,
  91. #[clap(
  92. long,
  93. arg_enum,
  94. requires("valid-blockhash"),
  95. help = "Type of transaction to be sent"
  96. )]
  97. pub transaction_type: Option<TransactionType>,
  98. #[clap(
  99. long,
  100. required_if_eq("transaction-type", "transfer"),
  101. help = "Number of instructions in transfer transaction"
  102. )]
  103. pub num_instructions: Option<usize>,
  104. }
  105. #[derive(ArgEnum, Clone, Copy, Debug, Eq, PartialEq)]
  106. pub enum Mode {
  107. Gossip,
  108. Tvu,
  109. Tpu,
  110. TpuForwards,
  111. Repair,
  112. ServeRepair,
  113. Rpc,
  114. }
  115. #[derive(ArgEnum, Clone, Copy, Debug, Eq, PartialEq)]
  116. pub enum DataType {
  117. RepairHighest,
  118. RepairShred,
  119. RepairOrphan,
  120. Random,
  121. GetAccountInfo,
  122. GetProgramAccounts,
  123. Transaction,
  124. }
  125. #[derive(ArgEnum, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
  126. pub enum TransactionType {
  127. Transfer,
  128. AccountCreation,
  129. }
  130. fn addr_parser(addr: &str) -> Result<SocketAddr, &'static str> {
  131. match solana_net_utils::parse_host_port(addr) {
  132. Ok(v) => Ok(v),
  133. Err(_) => Err("failed to parse address"),
  134. }
  135. }
  136. fn pubkey_parser(pubkey: &str) -> Result<Pubkey, &'static str> {
  137. match Pubkey::from_str(pubkey) {
  138. Ok(v) => Ok(v),
  139. Err(_) => Err("failed to parse pubkey"),
  140. }
  141. }
  142. /// input checks which are not covered by Clap
  143. fn validate_input(params: &DosClientParameters) {
  144. if params.mode == Mode::Rpc
  145. && (params.data_type != DataType::GetAccountInfo
  146. && params.data_type != DataType::GetProgramAccounts)
  147. {
  148. eprintln!("unsupported data type");
  149. exit(1);
  150. }
  151. if params.data_type != DataType::Transaction {
  152. let tp = &params.transaction_params;
  153. if tp.valid_blockhash || tp.valid_signatures || tp.unique_transactions {
  154. eprintln!(
  155. "Arguments valid-blockhash, valid-sign, unique-transactions are ignored if \
  156. data-type != transaction"
  157. );
  158. exit(1);
  159. }
  160. }
  161. }
  162. pub fn build_cli_parameters() -> DosClientParameters {
  163. let cmd_params = DosClientParameters::parse();
  164. validate_input(&cmd_params);
  165. cmd_params
  166. }
  167. #[cfg(test)]
  168. mod tests {
  169. use {super::*, clap::Parser, solana_pubkey::Pubkey};
  170. #[test]
  171. fn test_cli_parse_rpc_no_data_input() {
  172. let result = DosClientParameters::try_parse_from(vec![
  173. "solana-dos",
  174. "--mode",
  175. "rpc",
  176. "--data-type",
  177. "get-account-info",
  178. //--data-input is required for `--mode rpc` but it is not specified
  179. ]);
  180. assert!(result.is_err());
  181. assert_eq!(
  182. result.unwrap_err().kind(),
  183. clap::error::ErrorKind::MissingRequiredArgument
  184. );
  185. }
  186. #[test]
  187. fn test_cli_parse_rpc_data_input() {
  188. let entrypoint_addr: SocketAddr = "127.0.0.1:8001".parse().unwrap();
  189. let pubkey = Pubkey::default();
  190. let pubkey_str: String = pubkey.to_string();
  191. let params = DosClientParameters::try_parse_from(vec![
  192. "solana-dos",
  193. "--mode",
  194. "rpc",
  195. "--data-type",
  196. "get-account-info",
  197. "--data-input",
  198. &pubkey_str,
  199. "--shred-version",
  200. "42",
  201. ])
  202. .unwrap();
  203. assert_eq!(
  204. params,
  205. DosClientParameters {
  206. entrypoint_addr,
  207. mode: Mode::Rpc,
  208. data_size: 128, // default value
  209. data_type: DataType::GetAccountInfo,
  210. data_input: Some(pubkey),
  211. skip_gossip: false,
  212. shred_version: Some(42),
  213. allow_private_addr: false,
  214. transaction_params: TransactionParams::default(),
  215. tpu_use_quic: false,
  216. num_gen_threads: 1,
  217. send_batch_size: 16384,
  218. },
  219. );
  220. }
  221. #[test]
  222. fn test_cli_parse_dos_valid_signatures() {
  223. let entrypoint_addr: SocketAddr = "127.0.0.1:8001".parse().unwrap();
  224. let params = DosClientParameters::try_parse_from(vec![
  225. "solana-dos",
  226. "--mode",
  227. "tpu",
  228. "--data-type",
  229. "transaction",
  230. "--unique-transactions",
  231. "--valid-signatures",
  232. "--num-signatures",
  233. "8",
  234. "--tpu-use-quic",
  235. "--send-batch-size",
  236. "1",
  237. "--shred-version",
  238. "42",
  239. ])
  240. .unwrap();
  241. assert_eq!(
  242. params,
  243. DosClientParameters {
  244. entrypoint_addr,
  245. mode: Mode::Tpu,
  246. data_size: 128,
  247. data_type: DataType::Transaction,
  248. data_input: None,
  249. skip_gossip: false,
  250. shred_version: Some(42),
  251. allow_private_addr: false,
  252. num_gen_threads: 1,
  253. transaction_params: TransactionParams {
  254. num_signatures: Some(8),
  255. valid_blockhash: false,
  256. valid_signatures: true,
  257. unique_transactions: true,
  258. transaction_type: None,
  259. num_instructions: None,
  260. },
  261. tpu_use_quic: true,
  262. send_batch_size: 1,
  263. },
  264. );
  265. }
  266. #[test]
  267. fn test_cli_parse_dos_transfer() {
  268. let entrypoint_addr: SocketAddr = "127.0.0.1:8001".parse().unwrap();
  269. let params = DosClientParameters::try_parse_from(vec![
  270. "solana-dos",
  271. "--mode",
  272. "tpu",
  273. "--data-type",
  274. "transaction",
  275. "--unique-transactions",
  276. "--valid-blockhash",
  277. "--transaction-type",
  278. "transfer",
  279. "--num-instructions",
  280. "1",
  281. "--send-batch-size",
  282. "1",
  283. "--shred-version",
  284. "42",
  285. ])
  286. .unwrap();
  287. assert_eq!(
  288. params,
  289. DosClientParameters {
  290. entrypoint_addr,
  291. mode: Mode::Tpu,
  292. data_size: 128, // irrelevant if not random
  293. data_type: DataType::Transaction,
  294. data_input: None,
  295. skip_gossip: false,
  296. shred_version: Some(42),
  297. allow_private_addr: false,
  298. num_gen_threads: 1,
  299. transaction_params: TransactionParams {
  300. num_signatures: None,
  301. valid_blockhash: true,
  302. valid_signatures: false,
  303. unique_transactions: true,
  304. transaction_type: Some(TransactionType::Transfer),
  305. num_instructions: Some(1),
  306. },
  307. tpu_use_quic: false,
  308. send_batch_size: 1,
  309. },
  310. );
  311. let result = DosClientParameters::try_parse_from(vec![
  312. "solana-dos",
  313. "--mode",
  314. "tpu",
  315. "--data-type",
  316. "transaction",
  317. "--unique-transactions",
  318. "--transaction-type",
  319. "transfer",
  320. "--num-instructions",
  321. "8",
  322. "--shred-version",
  323. "42",
  324. ]);
  325. assert!(result.is_err());
  326. assert_eq!(
  327. result.unwrap_err().kind(),
  328. clap::error::ErrorKind::MissingRequiredArgument
  329. );
  330. let entrypoint_addr: SocketAddr = "127.0.0.1:8001".parse().unwrap();
  331. let params = DosClientParameters::try_parse_from(vec![
  332. "solana-dos",
  333. "--mode",
  334. "tpu",
  335. "--data-type",
  336. "transaction",
  337. "--unique-transactions",
  338. "--valid-blockhash",
  339. "--transaction-type",
  340. "transfer",
  341. "--num-instructions",
  342. "8",
  343. "--send-batch-size",
  344. "1",
  345. "--shred-version",
  346. "42",
  347. ])
  348. .unwrap();
  349. assert_eq!(
  350. params,
  351. DosClientParameters {
  352. entrypoint_addr,
  353. mode: Mode::Tpu,
  354. data_size: 128, // irrelevant if not random
  355. data_type: DataType::Transaction,
  356. data_input: None,
  357. skip_gossip: false,
  358. shred_version: Some(42),
  359. allow_private_addr: false,
  360. num_gen_threads: 1,
  361. transaction_params: TransactionParams {
  362. num_signatures: None,
  363. valid_blockhash: true,
  364. valid_signatures: false,
  365. unique_transactions: true,
  366. transaction_type: Some(TransactionType::Transfer),
  367. num_instructions: Some(8),
  368. },
  369. tpu_use_quic: false,
  370. send_batch_size: 1,
  371. },
  372. );
  373. }
  374. #[test]
  375. fn test_cli_parse_dos_create_account() {
  376. let entrypoint_addr: SocketAddr = "127.0.0.1:8001".parse().unwrap();
  377. let params = DosClientParameters::try_parse_from(vec![
  378. "solana-dos",
  379. "--mode",
  380. "tpu",
  381. "--data-type",
  382. "transaction",
  383. "--unique-transactions",
  384. "--valid-blockhash",
  385. "--transaction-type",
  386. "account-creation",
  387. "--send-batch-size",
  388. "1",
  389. "--shred-version",
  390. "42",
  391. ])
  392. .unwrap();
  393. assert_eq!(
  394. params,
  395. DosClientParameters {
  396. entrypoint_addr,
  397. mode: Mode::Tpu,
  398. data_size: 128, // irrelevant if not random
  399. data_type: DataType::Transaction,
  400. data_input: None,
  401. skip_gossip: false,
  402. shred_version: Some(42),
  403. allow_private_addr: false,
  404. num_gen_threads: 1,
  405. transaction_params: TransactionParams {
  406. num_signatures: None,
  407. valid_blockhash: true,
  408. valid_signatures: false,
  409. unique_transactions: true,
  410. transaction_type: Some(TransactionType::AccountCreation),
  411. num_instructions: None,
  412. },
  413. tpu_use_quic: false,
  414. send_batch_size: 1,
  415. },
  416. );
  417. }
  418. #[test]
  419. #[should_panic]
  420. fn test_cli_parse_dos_conflicting_sign_instruction() {
  421. // check conflicting args num-signatures and num-instructions
  422. let result = DosClientParameters::try_parse_from(vec![
  423. "solana-dos",
  424. "--mode",
  425. "tpu",
  426. "--data-type",
  427. "transaction",
  428. "--unique-transactions",
  429. "--valid-signatures",
  430. "--num-signatures",
  431. "8",
  432. "--num-instructions",
  433. "1",
  434. "--shred-version",
  435. "42",
  436. ]);
  437. assert!(result.is_err());
  438. }
  439. }