2021-02-16 13:48:20 -08:00
|
|
|
#![allow(clippy::integer_arithmetic)]
|
2021-12-03 09:00:31 -08:00
|
|
|
use {
|
2022-03-04 01:54:46 -08:00
|
|
|
clap::{crate_description, crate_name, crate_version, ArgEnum, Args, Parser},
|
2021-12-03 09:00:31 -08:00
|
|
|
log::*,
|
|
|
|
rand::{thread_rng, Rng},
|
2022-02-28 07:15:38 -08:00
|
|
|
serde::{Deserialize, Serialize},
|
2021-12-03 09:00:31 -08:00
|
|
|
solana_client::rpc_client::RpcClient,
|
|
|
|
solana_core::serve_repair::RepairProtocol,
|
|
|
|
solana_gossip::{contact_info::ContactInfo, gossip_service::discover},
|
2022-02-11 01:36:15 -08:00
|
|
|
solana_sdk::{
|
2022-02-16 07:05:38 -08:00
|
|
|
hash::Hash,
|
2022-02-28 07:15:38 -08:00
|
|
|
instruction::{AccountMeta, CompiledInstruction, Instruction},
|
|
|
|
pubkey::Pubkey,
|
2022-03-09 07:49:15 -08:00
|
|
|
signature::{read_keypair_file, Keypair, Signature, Signer},
|
2022-02-16 07:05:38 -08:00
|
|
|
stake,
|
|
|
|
system_instruction::SystemInstruction,
|
|
|
|
system_program,
|
|
|
|
transaction::Transaction,
|
2022-02-11 01:36:15 -08:00
|
|
|
},
|
2021-12-03 09:00:31 -08:00
|
|
|
solana_streamer::socket::SocketAddrSpace,
|
|
|
|
std::{
|
|
|
|
net::{SocketAddr, UdpSocket},
|
|
|
|
process::exit,
|
|
|
|
str::FromStr,
|
|
|
|
time::{Duration, Instant},
|
|
|
|
},
|
|
|
|
};
|
2020-03-20 12:55:38 -07:00
|
|
|
|
2021-10-06 09:49:53 -07:00
|
|
|
fn get_repair_contact(nodes: &[ContactInfo]) -> ContactInfo {
|
|
|
|
let source = thread_rng().gen_range(0, nodes.len());
|
|
|
|
let mut contact = nodes[source].clone();
|
|
|
|
contact.id = solana_sdk::pubkey::new_rand();
|
|
|
|
contact
|
|
|
|
}
|
|
|
|
|
2022-02-16 07:05:38 -08:00
|
|
|
struct TransactionGenerator {
|
|
|
|
blockhash: Hash,
|
|
|
|
last_generated: Instant,
|
|
|
|
transaction_params: TransactionParams,
|
|
|
|
cached_transaction: Option<Transaction>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TransactionGenerator {
|
|
|
|
fn new(transaction_params: TransactionParams) -> Self {
|
|
|
|
TransactionGenerator {
|
|
|
|
blockhash: Hash::default(),
|
|
|
|
last_generated: (Instant::now() - Duration::from_secs(100)),
|
|
|
|
transaction_params,
|
|
|
|
cached_transaction: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-04 01:54:46 -08:00
|
|
|
fn generate(&mut self, payer: Option<&Keypair>, rpc_client: &Option<RpcClient>) -> Transaction {
|
|
|
|
if !self.transaction_params.unique_transactions && self.cached_transaction.is_some() {
|
2022-02-16 07:05:38 -08:00
|
|
|
return self.cached_transaction.as_ref().unwrap().clone();
|
|
|
|
}
|
|
|
|
|
|
|
|
// generate a new blockhash every 1sec
|
2022-03-04 01:54:46 -08:00
|
|
|
if self.transaction_params.valid_blockhash
|
2022-02-16 07:05:38 -08:00
|
|
|
&& self.last_generated.elapsed().as_millis() > 1000
|
|
|
|
{
|
|
|
|
self.blockhash = rpc_client.as_ref().unwrap().get_latest_blockhash().unwrap();
|
|
|
|
self.last_generated = Instant::now();
|
|
|
|
}
|
|
|
|
|
2022-03-15 08:22:54 -07:00
|
|
|
// in order to evaluate the performance implications of the different transactions
|
|
|
|
// we create here transactions which are filetered out on different stages of processing pipeline
|
|
|
|
|
|
|
|
// create an arbitrary valid instruction
|
2022-02-16 07:05:38 -08:00
|
|
|
let lamports = 5;
|
|
|
|
let transfer_instruction = SystemInstruction::Transfer { lamports };
|
|
|
|
let program_ids = vec![system_program::id(), stake::program::id()];
|
|
|
|
|
2022-03-15 08:22:54 -07:00
|
|
|
// transaction with payer, in this case signatures are valid and num_signatures is irrelevant
|
2022-03-04 01:54:46 -08:00
|
|
|
// random payer will cause error "attempt to debit an account but found no record of a prior credit"
|
2022-02-16 07:05:38 -08:00
|
|
|
// if payer is correct, it will trigger error with not enough signatures
|
2022-03-04 01:54:46 -08:00
|
|
|
let transaction = if let Some(payer) = payer {
|
2022-02-16 07:05:38 -08:00
|
|
|
let instruction = Instruction::new_with_bincode(
|
|
|
|
program_ids[0],
|
|
|
|
&transfer_instruction,
|
|
|
|
vec![
|
|
|
|
AccountMeta::new(program_ids[0], false),
|
|
|
|
AccountMeta::new(program_ids[1], false),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
Transaction::new_signed_with_payer(
|
|
|
|
&[instruction],
|
|
|
|
Some(&payer.pubkey()),
|
|
|
|
&[payer],
|
|
|
|
self.blockhash,
|
|
|
|
)
|
|
|
|
} else if self.transaction_params.valid_signatures {
|
2022-03-04 01:54:46 -08:00
|
|
|
// Since we don't provide a payer, this transaction will
|
2022-03-09 07:49:15 -08:00
|
|
|
// end up filtered at legacy.rs sanitize method (banking_stage) with error "a program cannot be payer"
|
2022-03-15 08:22:54 -07:00
|
|
|
let kpvals: Vec<Keypair> = (0..self.transaction_params.num_signatures)
|
2022-02-16 07:05:38 -08:00
|
|
|
.map(|_| Keypair::new())
|
|
|
|
.collect();
|
|
|
|
let keypairs: Vec<&Keypair> = kpvals.iter().collect();
|
|
|
|
|
|
|
|
let instructions = vec![CompiledInstruction::new(
|
|
|
|
0,
|
|
|
|
&transfer_instruction,
|
|
|
|
vec![0, 1],
|
|
|
|
)];
|
|
|
|
|
|
|
|
Transaction::new_with_compiled_instructions(
|
|
|
|
&keypairs,
|
|
|
|
&[],
|
|
|
|
self.blockhash,
|
|
|
|
program_ids,
|
|
|
|
instructions,
|
2022-02-11 07:19:10 -08:00
|
|
|
)
|
|
|
|
} else {
|
2022-03-09 07:49:15 -08:00
|
|
|
// Since we provided invalid signatures
|
|
|
|
// this transaction will end up filtered at legacy.rs (banking_stage) because
|
|
|
|
// num_required_signatures == 0
|
2022-02-16 07:05:38 -08:00
|
|
|
let instructions = vec![CompiledInstruction::new(
|
|
|
|
0,
|
|
|
|
&transfer_instruction,
|
|
|
|
vec![0, 1],
|
|
|
|
)];
|
2022-02-11 01:36:15 -08:00
|
|
|
|
2022-02-16 07:05:38 -08:00
|
|
|
let mut tx = Transaction::new_with_compiled_instructions(
|
|
|
|
&[] as &[&Keypair; 0],
|
|
|
|
&[],
|
|
|
|
self.blockhash,
|
|
|
|
program_ids,
|
|
|
|
instructions,
|
|
|
|
);
|
2022-03-15 08:22:54 -07:00
|
|
|
tx.signatures = vec![Signature::new_unique(); self.transaction_params.num_signatures];
|
2022-02-16 07:05:38 -08:00
|
|
|
tx
|
|
|
|
};
|
2022-02-08 03:11:58 -08:00
|
|
|
|
2022-03-04 01:54:46 -08:00
|
|
|
// if we need to generate only one transaction, we cache it to reuse later
|
2022-02-16 07:05:38 -08:00
|
|
|
if !self.transaction_params.unique_transactions {
|
|
|
|
self.cached_transaction = Some(transaction.clone());
|
|
|
|
}
|
2022-02-08 03:11:58 -08:00
|
|
|
|
2022-02-16 07:05:38 -08:00
|
|
|
transaction
|
2022-02-11 01:36:15 -08:00
|
|
|
}
|
2022-02-08 03:11:58 -08:00
|
|
|
}
|
|
|
|
|
2020-03-29 14:44:25 -07:00
|
|
|
fn run_dos(
|
|
|
|
nodes: &[ContactInfo],
|
|
|
|
iterations: usize,
|
2022-03-04 01:54:46 -08:00
|
|
|
payer: Option<&Keypair>,
|
|
|
|
params: DosClientParameters,
|
2020-03-29 14:44:25 -07:00
|
|
|
) {
|
|
|
|
let mut target = None;
|
2020-09-09 08:21:48 -07:00
|
|
|
let mut rpc_client = None;
|
|
|
|
if nodes.is_empty() {
|
2022-03-04 01:54:46 -08:00
|
|
|
if params.mode == Mode::Rpc {
|
|
|
|
rpc_client = Some(RpcClient::new_socket(params.entrypoint_addr));
|
2020-09-09 08:21:48 -07:00
|
|
|
}
|
2022-03-04 01:54:46 -08:00
|
|
|
target = Some(params.entrypoint_addr);
|
2020-09-09 08:21:48 -07:00
|
|
|
} else {
|
2022-02-28 07:15:38 -08:00
|
|
|
info!("************ NODE ***********");
|
2020-09-09 08:21:48 -07:00
|
|
|
for node in nodes {
|
2022-02-28 07:15:38 -08:00
|
|
|
info!("{:?}", node);
|
|
|
|
}
|
2022-03-04 01:54:46 -08:00
|
|
|
info!("ADDR = {}", params.entrypoint_addr);
|
2022-02-28 07:15:38 -08:00
|
|
|
|
|
|
|
for node in nodes {
|
2022-03-04 01:54:46 -08:00
|
|
|
if node.gossip == params.entrypoint_addr {
|
2022-02-28 07:15:38 -08:00
|
|
|
info!("{}", node.gossip);
|
2022-03-04 01:54:46 -08:00
|
|
|
target = match params.mode {
|
|
|
|
Mode::Gossip => Some(node.gossip),
|
|
|
|
Mode::Tvu => Some(node.tvu),
|
|
|
|
Mode::TvuForwards => Some(node.tvu_forwards),
|
|
|
|
Mode::Tpu => {
|
2022-02-09 00:25:29 -08:00
|
|
|
rpc_client = Some(RpcClient::new_socket(node.rpc));
|
|
|
|
Some(node.tpu)
|
2022-02-11 01:36:15 -08:00
|
|
|
}
|
2022-03-04 01:54:46 -08:00
|
|
|
Mode::TpuForwards => Some(node.tpu_forwards),
|
|
|
|
Mode::Repair => Some(node.repair),
|
|
|
|
Mode::ServeRepair => Some(node.serve_repair),
|
|
|
|
Mode::Rpc => {
|
2020-09-09 08:21:48 -07:00
|
|
|
rpc_client = Some(RpcClient::new_socket(node.rpc));
|
|
|
|
None
|
|
|
|
}
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
}
|
2020-03-29 14:44:25 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
let target = target.expect("should have target");
|
|
|
|
|
2022-02-16 07:05:38 -08:00
|
|
|
info!("Targeting {}", target);
|
2020-03-29 14:44:25 -07:00
|
|
|
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
|
|
|
|
|
|
|
|
let mut data = Vec::new();
|
2022-03-04 01:54:46 -08:00
|
|
|
let mut transaction_generator = None;
|
2020-03-29 14:44:25 -07:00
|
|
|
|
2022-03-04 01:54:46 -08:00
|
|
|
match params.data_type {
|
|
|
|
DataType::RepairHighest => {
|
2021-10-06 09:49:53 -07:00
|
|
|
let slot = 100;
|
|
|
|
let req = RepairProtocol::WindowIndexWithNonce(get_repair_contact(nodes), slot, 0, 0);
|
|
|
|
data = bincode::serialize(&req).unwrap();
|
|
|
|
}
|
2022-03-04 01:54:46 -08:00
|
|
|
DataType::RepairShred => {
|
2021-10-06 09:49:53 -07:00
|
|
|
let slot = 100;
|
|
|
|
let req =
|
|
|
|
RepairProtocol::HighestWindowIndexWithNonce(get_repair_contact(nodes), slot, 0, 0);
|
|
|
|
data = bincode::serialize(&req).unwrap();
|
|
|
|
}
|
2022-03-04 01:54:46 -08:00
|
|
|
DataType::RepairOrphan => {
|
2021-10-06 09:49:53 -07:00
|
|
|
let slot = 100;
|
|
|
|
let req = RepairProtocol::OrphanWithNonce(get_repair_contact(nodes), slot, 0);
|
|
|
|
data = bincode::serialize(&req).unwrap();
|
|
|
|
}
|
2022-03-04 01:54:46 -08:00
|
|
|
DataType::Random => {
|
|
|
|
data.resize(params.data_size, 0);
|
2021-10-06 09:49:53 -07:00
|
|
|
}
|
2022-03-04 01:54:46 -08:00
|
|
|
DataType::Transaction => {
|
|
|
|
let tp = params.transaction_params;
|
2022-02-28 07:15:38 -08:00
|
|
|
info!("{:?}", tp);
|
|
|
|
|
2022-03-04 01:54:46 -08:00
|
|
|
transaction_generator = Some(TransactionGenerator::new(tp));
|
|
|
|
let tx = transaction_generator
|
|
|
|
.as_mut()
|
|
|
|
.unwrap()
|
|
|
|
.generate(payer, &rpc_client);
|
2022-02-16 07:05:38 -08:00
|
|
|
info!("{:?}", tx);
|
|
|
|
data = bincode::serialize(&tx).unwrap();
|
2021-10-06 09:49:53 -07:00
|
|
|
}
|
2022-03-04 01:54:46 -08:00
|
|
|
DataType::GetAccountInfo => {}
|
|
|
|
DataType::GetProgramAccounts => {}
|
2020-03-29 14:44:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
let mut last_log = Instant::now();
|
|
|
|
let mut count = 0;
|
|
|
|
let mut error_count = 0;
|
|
|
|
loop {
|
2022-03-04 01:54:46 -08:00
|
|
|
if params.mode == Mode::Rpc {
|
|
|
|
match params.data_type {
|
|
|
|
DataType::GetAccountInfo => {
|
|
|
|
let res = rpc_client.as_ref().unwrap().get_account(
|
|
|
|
&Pubkey::from_str(params.data_input.as_ref().unwrap()).unwrap(),
|
|
|
|
);
|
2020-09-09 08:21:48 -07:00
|
|
|
if res.is_err() {
|
|
|
|
error_count += 1;
|
|
|
|
}
|
|
|
|
}
|
2022-03-04 01:54:46 -08:00
|
|
|
DataType::GetProgramAccounts => {
|
2020-09-09 08:21:48 -07:00
|
|
|
let res = rpc_client.as_ref().unwrap().get_program_accounts(
|
2022-03-04 01:54:46 -08:00
|
|
|
&Pubkey::from_str(params.data_input.as_ref().unwrap()).unwrap(),
|
2020-09-09 08:21:48 -07:00
|
|
|
);
|
|
|
|
if res.is_err() {
|
|
|
|
error_count += 1;
|
|
|
|
}
|
|
|
|
}
|
2022-03-04 01:54:46 -08:00
|
|
|
_ => {
|
2020-09-09 08:21:48 -07:00
|
|
|
panic!("unsupported data type");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2022-03-04 01:54:46 -08:00
|
|
|
if params.data_type == DataType::Random {
|
2020-09-09 08:21:48 -07:00
|
|
|
thread_rng().fill(&mut data[..]);
|
|
|
|
}
|
2022-03-04 01:54:46 -08:00
|
|
|
if let Some(tg) = transaction_generator.as_mut() {
|
2022-02-16 07:05:38 -08:00
|
|
|
let tx = tg.generate(payer, &rpc_client);
|
|
|
|
info!("{:?}", tx);
|
|
|
|
data = bincode::serialize(&tx).unwrap();
|
2022-02-11 01:36:15 -08:00
|
|
|
}
|
2020-09-09 08:21:48 -07:00
|
|
|
let res = socket.send_to(&data, target);
|
|
|
|
if res.is_err() {
|
|
|
|
error_count += 1;
|
|
|
|
}
|
2020-03-29 14:44:25 -07:00
|
|
|
}
|
|
|
|
count += 1;
|
2022-02-01 06:26:55 -08:00
|
|
|
if last_log.elapsed().as_millis() > 10_000 {
|
2020-03-29 14:44:25 -07:00
|
|
|
info!("count: {} errors: {}", count, error_count);
|
|
|
|
last_log = Instant::now();
|
|
|
|
count = 0;
|
|
|
|
}
|
|
|
|
if iterations != 0 && count >= iterations {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-04 01:54:46 -08:00
|
|
|
// command line parsing
|
|
|
|
#[derive(Parser)]
|
2022-03-15 08:22:54 -07:00
|
|
|
#[clap(name = crate_name!(),
|
|
|
|
version = crate_version!(),
|
|
|
|
about = crate_description!(),
|
|
|
|
rename_all = "kebab-case"
|
|
|
|
)]
|
2022-03-04 01:54:46 -08:00
|
|
|
struct DosClientParameters {
|
2022-03-15 08:22:54 -07:00
|
|
|
#[clap(long, arg_enum, help = "Interface to DoS")]
|
|
|
|
mode: Mode,
|
|
|
|
|
|
|
|
#[clap(long, arg_enum, help = "Type of data to send")]
|
|
|
|
data_type: DataType,
|
|
|
|
|
2022-03-04 01:54:46 -08:00
|
|
|
#[clap(
|
|
|
|
long = "entrypoint",
|
|
|
|
parse(try_from_str = addr_parser),
|
|
|
|
default_value = "127.0.0.1:8001",
|
|
|
|
help = "Gossip entrypoint address. Usually <ip>:8001"
|
|
|
|
)]
|
|
|
|
entrypoint_addr: SocketAddr,
|
|
|
|
|
|
|
|
#[clap(
|
2022-03-15 08:22:54 -07:00
|
|
|
long,
|
2022-03-04 01:54:46 -08:00
|
|
|
default_value = "128",
|
|
|
|
required_if_eq("data-type", "random"),
|
|
|
|
help = "Size of packet to DoS with, relevant only for data-type=random"
|
|
|
|
)]
|
|
|
|
data_size: usize,
|
|
|
|
|
2022-03-15 08:22:54 -07:00
|
|
|
#[clap(long, help = "Data to send [Optional]")]
|
2022-03-04 01:54:46 -08:00
|
|
|
data_input: Option<String>,
|
|
|
|
|
2022-03-15 08:22:54 -07:00
|
|
|
#[clap(long, help = "Just use entrypoint address directly")]
|
2022-03-04 01:54:46 -08:00
|
|
|
skip_gossip: bool,
|
|
|
|
|
2022-03-15 08:22:54 -07:00
|
|
|
#[clap(long, help = "Allow contacting private ip addresses")]
|
2022-03-04 01:54:46 -08:00
|
|
|
allow_private_addr: bool,
|
|
|
|
|
|
|
|
#[clap(flatten)]
|
|
|
|
transaction_params: TransactionParams,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Args, Serialize, Deserialize, Debug, Default)]
|
2022-03-15 08:22:54 -07:00
|
|
|
#[clap(rename_all = "kebab-case")]
|
2022-03-04 01:54:46 -08:00
|
|
|
struct TransactionParams {
|
|
|
|
#[clap(
|
2022-03-15 08:22:54 -07:00
|
|
|
long,
|
2022-03-04 01:54:46 -08:00
|
|
|
default_value = "2",
|
|
|
|
help = "Number of signatures in transaction"
|
|
|
|
)]
|
2022-03-15 08:22:54 -07:00
|
|
|
num_signatures: usize,
|
2022-03-04 01:54:46 -08:00
|
|
|
|
2022-03-15 08:22:54 -07:00
|
|
|
#[clap(long, help = "Generate a valid blockhash for transaction")]
|
2022-03-04 01:54:46 -08:00
|
|
|
valid_blockhash: bool,
|
|
|
|
|
2022-03-15 08:22:54 -07:00
|
|
|
#[clap(long, help = "Generate valid signature(s) for transaction")]
|
2022-03-04 01:54:46 -08:00
|
|
|
valid_signatures: bool,
|
|
|
|
|
2022-03-15 08:22:54 -07:00
|
|
|
#[clap(long, help = "Generate unique transactions")]
|
2022-03-04 01:54:46 -08:00
|
|
|
unique_transactions: bool,
|
|
|
|
|
|
|
|
#[clap(
|
|
|
|
long = "payer",
|
|
|
|
help = "Payer's keypair file to fund transactions [Optional]"
|
|
|
|
)]
|
|
|
|
payer_filename: Option<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(ArgEnum, Clone, Eq, PartialEq)]
|
|
|
|
enum Mode {
|
|
|
|
Gossip,
|
|
|
|
Tvu,
|
|
|
|
TvuForwards,
|
|
|
|
Tpu,
|
|
|
|
TpuForwards,
|
|
|
|
Repair,
|
|
|
|
ServeRepair,
|
|
|
|
Rpc,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(ArgEnum, Clone, Eq, PartialEq)]
|
|
|
|
enum DataType {
|
|
|
|
RepairHighest,
|
|
|
|
RepairShred,
|
|
|
|
RepairOrphan,
|
|
|
|
Random,
|
|
|
|
GetAccountInfo,
|
|
|
|
GetProgramAccounts,
|
|
|
|
Transaction,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn addr_parser(addr: &str) -> Result<SocketAddr, &'static str> {
|
|
|
|
match solana_net_utils::parse_host_port(addr) {
|
|
|
|
Ok(v) => Ok(v),
|
|
|
|
Err(_) => Err("failed to parse entrypoint address"),
|
2020-03-20 12:55:38 -07:00
|
|
|
}
|
2022-03-04 01:54:46 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// input checks which are not covered by Clap
|
|
|
|
fn validate_input(params: &DosClientParameters) {
|
|
|
|
if params.mode == Mode::Rpc
|
|
|
|
&& (params.data_type != DataType::GetAccountInfo
|
|
|
|
&& params.data_type != DataType::GetProgramAccounts)
|
|
|
|
{
|
|
|
|
panic!("unsupported data type");
|
|
|
|
}
|
|
|
|
|
|
|
|
if params.data_type != DataType::Transaction {
|
|
|
|
let tp = ¶ms.transaction_params;
|
|
|
|
if tp.valid_blockhash
|
|
|
|
|| tp.valid_signatures
|
|
|
|
|| tp.unique_transactions
|
|
|
|
|| tp.payer_filename.is_some()
|
|
|
|
{
|
|
|
|
println!("Arguments valid-blockhash, valid-sign, unique-trans, payer are ignored if data-type != transaction");
|
|
|
|
}
|
|
|
|
}
|
2022-03-09 07:49:15 -08:00
|
|
|
|
|
|
|
if params.transaction_params.payer_filename.is_some()
|
|
|
|
&& params.transaction_params.valid_signatures
|
|
|
|
{
|
|
|
|
println!("Arguments valid-signatures is ignored if payer is provided");
|
|
|
|
}
|
2022-03-04 01:54:46 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
solana_logger::setup_with_default("solana=info");
|
|
|
|
let cmd_params = DosClientParameters::parse();
|
|
|
|
validate_input(&cmd_params);
|
2020-03-20 12:55:38 -07:00
|
|
|
|
2020-09-09 08:21:48 -07:00
|
|
|
let mut nodes = vec![];
|
2022-03-04 01:54:46 -08:00
|
|
|
if !cmd_params.skip_gossip {
|
|
|
|
info!("Finding cluster entry: {:?}", cmd_params.entrypoint_addr);
|
|
|
|
let socket_addr_space = SocketAddrSpace::new(cmd_params.allow_private_addr);
|
2020-09-09 08:21:48 -07:00
|
|
|
let (gossip_nodes, _validators) = discover(
|
2021-05-22 09:02:13 -07:00
|
|
|
None, // keypair
|
2022-03-04 01:54:46 -08:00
|
|
|
Some(&cmd_params.entrypoint_addr),
|
|
|
|
None, // num_nodes
|
|
|
|
Duration::from_secs(60), // timeout
|
|
|
|
None, // find_node_by_pubkey
|
|
|
|
Some(&cmd_params.entrypoint_addr), // find_node_by_gossip_addr
|
|
|
|
None, // my_gossip_addr
|
|
|
|
0, // my_shred_version
|
2021-07-23 08:25:03 -07:00
|
|
|
socket_addr_space,
|
2020-09-09 08:21:48 -07:00
|
|
|
)
|
|
|
|
.unwrap_or_else(|err| {
|
2022-03-04 01:54:46 -08:00
|
|
|
eprintln!(
|
|
|
|
"Failed to discover {} node: {:?}",
|
|
|
|
cmd_params.entrypoint_addr, err
|
|
|
|
);
|
2020-09-09 08:21:48 -07:00
|
|
|
exit(1);
|
|
|
|
});
|
|
|
|
nodes = gossip_nodes;
|
|
|
|
}
|
2020-03-20 12:55:38 -07:00
|
|
|
|
2022-02-16 07:05:38 -08:00
|
|
|
info!("done found {} nodes", nodes.len());
|
2022-03-04 01:54:46 -08:00
|
|
|
let payer = cmd_params
|
|
|
|
.transaction_params
|
|
|
|
.payer_filename
|
|
|
|
.as_ref()
|
|
|
|
.map(|keypair_file_name| {
|
|
|
|
read_keypair_file(&keypair_file_name)
|
|
|
|
.unwrap_or_else(|_| panic!("bad keypair {:?}", keypair_file_name))
|
|
|
|
});
|
|
|
|
|
|
|
|
run_dos(&nodes, 0, payer.as_ref(), cmd_params);
|
2020-03-29 14:44:25 -07:00
|
|
|
}
|
2020-03-20 12:55:38 -07:00
|
|
|
|
2020-03-29 14:44:25 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
pub mod test {
|
2022-02-18 21:32:29 -08:00
|
|
|
use {
|
|
|
|
super::*,
|
|
|
|
solana_local_cluster::{cluster::Cluster, local_cluster::LocalCluster},
|
|
|
|
solana_sdk::timing::timestamp,
|
|
|
|
};
|
2020-03-29 14:44:25 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_dos() {
|
2020-10-19 12:23:14 -07:00
|
|
|
let nodes = [ContactInfo::new_localhost(
|
|
|
|
&solana_sdk::pubkey::new_rand(),
|
|
|
|
timestamp(),
|
|
|
|
)];
|
2020-05-15 09:35:43 -07:00
|
|
|
let entrypoint_addr = nodes[0].gossip;
|
2022-02-16 07:05:38 -08:00
|
|
|
|
2020-03-29 14:44:25 -07:00
|
|
|
run_dos(
|
|
|
|
&nodes,
|
|
|
|
1,
|
2022-02-11 01:36:15 -08:00
|
|
|
None,
|
2022-03-04 01:54:46 -08:00
|
|
|
DosClientParameters {
|
|
|
|
entrypoint_addr,
|
|
|
|
mode: Mode::Tvu,
|
|
|
|
data_size: 10,
|
|
|
|
data_type: DataType::Random,
|
|
|
|
data_input: None,
|
|
|
|
skip_gossip: false,
|
|
|
|
allow_private_addr: false,
|
|
|
|
transaction_params: TransactionParams::default(),
|
|
|
|
},
|
2020-03-29 14:44:25 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
run_dos(
|
|
|
|
&nodes,
|
|
|
|
1,
|
2022-02-11 01:36:15 -08:00
|
|
|
None,
|
2022-03-04 01:54:46 -08:00
|
|
|
DosClientParameters {
|
|
|
|
entrypoint_addr,
|
|
|
|
mode: Mode::Repair,
|
|
|
|
data_size: 10,
|
|
|
|
data_type: DataType::RepairHighest,
|
|
|
|
data_input: None,
|
|
|
|
skip_gossip: false,
|
|
|
|
allow_private_addr: false,
|
|
|
|
transaction_params: TransactionParams::default(),
|
|
|
|
},
|
2020-03-29 14:44:25 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
run_dos(
|
|
|
|
&nodes,
|
|
|
|
1,
|
2022-02-11 01:36:15 -08:00
|
|
|
None,
|
2022-03-04 01:54:46 -08:00
|
|
|
DosClientParameters {
|
|
|
|
entrypoint_addr,
|
|
|
|
mode: Mode::ServeRepair,
|
|
|
|
data_size: 10,
|
|
|
|
data_type: DataType::RepairShred,
|
|
|
|
data_input: None,
|
|
|
|
skip_gossip: false,
|
|
|
|
allow_private_addr: false,
|
|
|
|
transaction_params: TransactionParams::default(),
|
|
|
|
},
|
2020-03-29 14:44:25 -07:00
|
|
|
);
|
2020-03-20 12:55:38 -07:00
|
|
|
}
|
2022-02-01 06:26:55 -08:00
|
|
|
|
2022-03-09 07:49:15 -08:00
|
|
|
#[test]
|
|
|
|
fn test_dos_local_cluster_transactions() {
|
|
|
|
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(
|
|
|
|
&nodes_slice,
|
|
|
|
1,
|
|
|
|
None,
|
|
|
|
DosClientParameters {
|
|
|
|
entrypoint_addr: cluster.entry_point_info.gossip,
|
|
|
|
mode: Mode::Tpu,
|
|
|
|
data_size: 1024,
|
|
|
|
data_type: DataType::Random,
|
|
|
|
data_input: None,
|
|
|
|
skip_gossip: false,
|
|
|
|
allow_private_addr: false,
|
|
|
|
transaction_params: TransactionParams::default(),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2022-03-15 08:22:54 -07:00
|
|
|
// send transactions to TPU with 2 random signatures
|
2022-03-09 07:49:15 -08:00
|
|
|
// will be filtered on dedup (because transactions are not unique)
|
|
|
|
run_dos(
|
|
|
|
&nodes_slice,
|
|
|
|
1,
|
|
|
|
None,
|
|
|
|
DosClientParameters {
|
|
|
|
entrypoint_addr: cluster.entry_point_info.gossip,
|
|
|
|
mode: Mode::Tpu,
|
|
|
|
data_size: 0, // irrelevant if not random
|
|
|
|
data_type: DataType::Transaction,
|
|
|
|
data_input: None,
|
|
|
|
skip_gossip: false,
|
|
|
|
allow_private_addr: false,
|
|
|
|
transaction_params: TransactionParams {
|
2022-03-15 08:22:54 -07:00
|
|
|
num_signatures: 2,
|
2022-03-09 07:49:15 -08:00
|
|
|
valid_blockhash: false,
|
|
|
|
valid_signatures: false,
|
|
|
|
unique_transactions: false,
|
|
|
|
payer_filename: None,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2022-03-15 08:22:54 -07:00
|
|
|
// send *unique* transactions to TPU with 2 random signatures
|
2022-03-09 07:49:15 -08:00
|
|
|
// will be discarded on banking stage in legacy.rs
|
|
|
|
// ("there should be at least 1 RW fee-payer account")
|
|
|
|
run_dos(
|
|
|
|
&nodes_slice,
|
|
|
|
1,
|
|
|
|
None,
|
|
|
|
DosClientParameters {
|
|
|
|
entrypoint_addr: cluster.entry_point_info.gossip,
|
|
|
|
mode: Mode::Tpu,
|
|
|
|
data_size: 0, // irrelevant if not random
|
|
|
|
data_type: DataType::Transaction,
|
|
|
|
data_input: None,
|
|
|
|
skip_gossip: false,
|
|
|
|
allow_private_addr: false,
|
|
|
|
transaction_params: TransactionParams {
|
2022-03-15 08:22:54 -07:00
|
|
|
num_signatures: 4,
|
2022-03-09 07:49:15 -08:00
|
|
|
valid_blockhash: false,
|
|
|
|
valid_signatures: false,
|
|
|
|
unique_transactions: true,
|
|
|
|
payer_filename: None,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2022-03-15 08:22:54 -07:00
|
|
|
// send unique transactions to TPU with 2 random signatures
|
2022-03-09 07:49:15 -08:00
|
|
|
// will be discarded on banking stage in legacy.rs (A program cannot be a payer)
|
|
|
|
// because we haven't provided a valid payer
|
|
|
|
run_dos(
|
|
|
|
&nodes_slice,
|
|
|
|
1,
|
|
|
|
None,
|
|
|
|
DosClientParameters {
|
|
|
|
entrypoint_addr: cluster.entry_point_info.gossip,
|
|
|
|
mode: Mode::Tpu,
|
|
|
|
data_size: 0, // irrelevant if not random
|
|
|
|
data_type: DataType::Transaction,
|
|
|
|
data_input: None,
|
|
|
|
skip_gossip: false,
|
|
|
|
allow_private_addr: false,
|
|
|
|
transaction_params: TransactionParams {
|
2022-03-15 08:22:54 -07:00
|
|
|
num_signatures: 2,
|
2022-03-09 07:49:15 -08:00
|
|
|
valid_blockhash: false, // irrelevant without valid payer, because
|
|
|
|
// it will be filtered before blockhash validity checks
|
|
|
|
valid_signatures: true,
|
|
|
|
unique_transactions: true,
|
|
|
|
payer_filename: None,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
// send unique transaction to TPU with valid blockhash
|
|
|
|
// will be discarded due to invalid hash
|
|
|
|
run_dos(
|
|
|
|
&nodes_slice,
|
|
|
|
1,
|
|
|
|
Some(&cluster.funding_keypair),
|
|
|
|
DosClientParameters {
|
|
|
|
entrypoint_addr: cluster.entry_point_info.gossip,
|
|
|
|
mode: Mode::Tpu,
|
|
|
|
data_size: 0, // irrelevant if not random
|
|
|
|
data_type: DataType::Transaction,
|
|
|
|
data_input: None,
|
|
|
|
skip_gossip: false,
|
|
|
|
allow_private_addr: false,
|
|
|
|
transaction_params: TransactionParams {
|
2022-03-15 08:22:54 -07:00
|
|
|
num_signatures: 2,
|
2022-03-09 07:49:15 -08:00
|
|
|
valid_blockhash: false,
|
|
|
|
valid_signatures: true,
|
|
|
|
unique_transactions: true,
|
|
|
|
payer_filename: None,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
// send unique transaction to TPU with valid blockhash
|
|
|
|
// will fail with error processing Instruction 0: missing required signature for instruction
|
|
|
|
run_dos(
|
|
|
|
&nodes_slice,
|
|
|
|
1,
|
|
|
|
Some(&cluster.funding_keypair),
|
|
|
|
DosClientParameters {
|
|
|
|
entrypoint_addr: cluster.entry_point_info.gossip,
|
|
|
|
mode: Mode::Tpu,
|
|
|
|
data_size: 0, // irrelevant if not random
|
|
|
|
data_type: DataType::Transaction,
|
|
|
|
data_input: None,
|
|
|
|
skip_gossip: false,
|
|
|
|
allow_private_addr: false,
|
|
|
|
transaction_params: TransactionParams {
|
2022-03-15 08:22:54 -07:00
|
|
|
num_signatures: 2,
|
2022-03-09 07:49:15 -08:00
|
|
|
valid_blockhash: true,
|
|
|
|
valid_signatures: true,
|
|
|
|
unique_transactions: true,
|
|
|
|
payer_filename: None,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-02-01 06:26:55 -08:00
|
|
|
#[test]
|
|
|
|
#[ignore]
|
|
|
|
fn test_dos_local_cluster() {
|
|
|
|
solana_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();
|
|
|
|
|
|
|
|
run_dos(
|
|
|
|
&[node],
|
2022-02-16 07:05:38 -08:00
|
|
|
10_000_000,
|
2022-03-04 01:54:46 -08:00
|
|
|
Some(&cluster.funding_keypair),
|
|
|
|
DosClientParameters {
|
|
|
|
entrypoint_addr: cluster.entry_point_info.gossip,
|
|
|
|
mode: Mode::Tpu,
|
|
|
|
data_size: 0, // irrelevant if not random
|
|
|
|
data_type: DataType::Transaction,
|
|
|
|
data_input: None,
|
|
|
|
skip_gossip: false,
|
|
|
|
allow_private_addr: false,
|
|
|
|
transaction_params: TransactionParams {
|
2022-03-15 08:22:54 -07:00
|
|
|
num_signatures: 2,
|
2022-03-04 01:54:46 -08:00
|
|
|
valid_blockhash: true,
|
|
|
|
valid_signatures: true,
|
|
|
|
unique_transactions: true,
|
|
|
|
payer_filename: None,
|
|
|
|
},
|
|
|
|
},
|
2022-02-01 06:26:55 -08:00
|
|
|
);
|
|
|
|
}
|
2020-03-20 12:55:38 -07:00
|
|
|
}
|