Add DOS test that sends large transactions (#20624)
Problem We need a test that stress-tests the network using large transactions, including sending multiple large transactions that reference independent sets of accounts, so they can be executed in parallel. Summary of Changes Adds such a test. Also adds a version of the tuner program that runs for a configurable number of iterations.
This commit is contained in:
26
Cargo.lock
generated
26
Cargo.lock
generated
@ -5862,6 +5862,32 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "solana-transaction-dos"
|
||||||
|
version = "1.9.0"
|
||||||
|
dependencies = [
|
||||||
|
"bincode",
|
||||||
|
"clap 2.33.3",
|
||||||
|
"log 0.4.14",
|
||||||
|
"rand 0.7.3",
|
||||||
|
"rayon",
|
||||||
|
"solana-clap-utils",
|
||||||
|
"solana-cli",
|
||||||
|
"solana-client",
|
||||||
|
"solana-core",
|
||||||
|
"solana-faucet",
|
||||||
|
"solana-gossip",
|
||||||
|
"solana-local-cluster",
|
||||||
|
"solana-logger 1.9.0",
|
||||||
|
"solana-measure",
|
||||||
|
"solana-net-utils",
|
||||||
|
"solana-runtime",
|
||||||
|
"solana-sdk",
|
||||||
|
"solana-streamer",
|
||||||
|
"solana-transaction-status",
|
||||||
|
"solana-version",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "solana-transaction-status"
|
name = "solana-transaction-status"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
|
@ -63,6 +63,7 @@ members = [
|
|||||||
"stake-accounts",
|
"stake-accounts",
|
||||||
"sys-tuner",
|
"sys-tuner",
|
||||||
"tokens",
|
"tokens",
|
||||||
|
"transaction-dos",
|
||||||
"transaction-status",
|
"transaction-status",
|
||||||
"account-decoder",
|
"account-decoder",
|
||||||
"upload-perf",
|
"upload-perf",
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* @brief Compute budget tuner program. Spins in a loop for the specified number of iterations
|
||||||
|
* (or for UINT64_MAX iterations if 0 is specified for the number of iterations), in order to consume
|
||||||
|
* a configurable amount of the budget.
|
||||||
|
*
|
||||||
|
* Care should be taken because the compiler might optimize out the mechanism
|
||||||
|
* you are trying to tune.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <solana_sdk.h>
|
||||||
|
|
||||||
|
#define NUM_KA 1
|
||||||
|
|
||||||
|
extern uint64_t entrypoint(const uint8_t *input) {
|
||||||
|
SolAccountInfo ka[NUM_KA];
|
||||||
|
SolParameters params = (SolParameters){.ka = ka};
|
||||||
|
if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(ka))) {
|
||||||
|
return ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t current = 1;
|
||||||
|
|
||||||
|
uint64_t iterations = params.data[0] * 18;
|
||||||
|
iterations = iterations == 0 ? UINT64_MAX : iterations;
|
||||||
|
size_t rand = params.data[1];
|
||||||
|
size_t account_index = rand % params.ka_num;
|
||||||
|
uint8_t *val = (uint8_t *)ka[account_index].data;
|
||||||
|
uint64_t memory_size = ka[account_index].data_len;
|
||||||
|
|
||||||
|
for (uint64_t i = 0; i < iterations; i++) {
|
||||||
|
{
|
||||||
|
*val ^= val[current % memory_size];
|
||||||
|
current = current * 76510171 + 47123;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return *val;
|
||||||
|
}
|
2
transaction-dos/.gitignore
vendored
Normal file
2
transaction-dos/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/target/
|
||||||
|
/farf/
|
36
transaction-dos/Cargo.toml
Normal file
36
transaction-dos/Cargo.toml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
[package]
|
||||||
|
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||||
|
edition = "2018"
|
||||||
|
name = "solana-transaction-dos"
|
||||||
|
version = "1.9.0"
|
||||||
|
repository = "https://github.com/solana-labs/solana"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
homepage = "https://solana.com/"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bincode = "1.3.3"
|
||||||
|
clap = "2.33.1"
|
||||||
|
log = "0.4.14"
|
||||||
|
rand = "0.7.0"
|
||||||
|
rayon = "1.5.1"
|
||||||
|
solana-cli = { path = "../cli", version = "=1.9.0" }
|
||||||
|
solana-clap-utils = { path = "../clap-utils", version = "=1.9.0" }
|
||||||
|
solana-client = { path = "../client", version = "=1.9.0" }
|
||||||
|
solana-core = { path = "../core", version = "=1.9.0" }
|
||||||
|
solana-faucet = { path = "../faucet", version = "=1.9.0" }
|
||||||
|
solana-gossip = { path = "../gossip", version = "=1.9.0" }
|
||||||
|
solana-logger = { path = "../logger", version = "=1.9.0" }
|
||||||
|
solana-measure = { path = "../measure", version = "=1.9.0" }
|
||||||
|
solana-net-utils = { path = "../net-utils", version = "=1.9.0" }
|
||||||
|
solana-runtime = { path = "../runtime", version = "=1.9.0" }
|
||||||
|
solana-sdk = { path = "../sdk", version = "=1.9.0" }
|
||||||
|
solana-streamer = { path = "../streamer", version = "=1.9.0" }
|
||||||
|
solana-transaction-status = { path = "../transaction-status", version = "=1.9.0" }
|
||||||
|
solana-version = { path = "../version", version = "=1.9.0" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
solana-local-cluster = { path = "../local-cluster", version = "=1.9.0" }
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
targets = ["x86_64-unknown-linux-gnu"]
|
722
transaction-dos/src/main.rs
Normal file
722
transaction-dos/src/main.rs
Normal file
@ -0,0 +1,722 @@
|
|||||||
|
#![allow(clippy::integer_arithmetic)]
|
||||||
|
use {
|
||||||
|
clap::{crate_description, crate_name, value_t, values_t_or_exit, App, Arg},
|
||||||
|
log::*,
|
||||||
|
rand::{thread_rng, Rng},
|
||||||
|
rayon::prelude::*,
|
||||||
|
solana_clap_utils::input_parsers::pubkey_of,
|
||||||
|
solana_cli::{cli::CliConfig, program::process_deploy},
|
||||||
|
solana_client::{rpc_client::RpcClient, transaction_executor::TransactionExecutor},
|
||||||
|
solana_faucet::faucet::{request_airdrop_transaction, FAUCET_PORT},
|
||||||
|
solana_gossip::gossip_service::discover,
|
||||||
|
solana_sdk::{
|
||||||
|
commitment_config::CommitmentConfig,
|
||||||
|
instruction::{AccountMeta, Instruction},
|
||||||
|
message::Message,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
rpc_port::DEFAULT_RPC_PORT,
|
||||||
|
signature::{read_keypair_file, Keypair, Signer},
|
||||||
|
system_instruction,
|
||||||
|
transaction::Transaction,
|
||||||
|
},
|
||||||
|
solana_streamer::socket::SocketAddrSpace,
|
||||||
|
std::{
|
||||||
|
net::SocketAddr,
|
||||||
|
process::exit,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
thread::sleep,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn airdrop_lamports(
|
||||||
|
client: &RpcClient,
|
||||||
|
faucet_addr: &SocketAddr,
|
||||||
|
id: &Keypair,
|
||||||
|
desired_balance: u64,
|
||||||
|
) -> bool {
|
||||||
|
let starting_balance = client.get_balance(&id.pubkey()).unwrap_or(0);
|
||||||
|
info!("starting balance {}", starting_balance);
|
||||||
|
|
||||||
|
if starting_balance < desired_balance {
|
||||||
|
let airdrop_amount = desired_balance - starting_balance;
|
||||||
|
info!(
|
||||||
|
"Airdropping {:?} lamports from {} for {}",
|
||||||
|
airdrop_amount,
|
||||||
|
faucet_addr,
|
||||||
|
id.pubkey(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let blockhash = client.get_latest_blockhash().unwrap();
|
||||||
|
match request_airdrop_transaction(faucet_addr, &id.pubkey(), airdrop_amount, blockhash) {
|
||||||
|
Ok(transaction) => {
|
||||||
|
let mut tries = 0;
|
||||||
|
loop {
|
||||||
|
tries += 1;
|
||||||
|
let result = client.send_and_confirm_transaction(&transaction);
|
||||||
|
|
||||||
|
if result.is_ok() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if tries >= 5 {
|
||||||
|
panic!(
|
||||||
|
"Error requesting airdrop: to addr: {:?} amount: {} {:?}",
|
||||||
|
faucet_addr, airdrop_amount, result
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
panic!(
|
||||||
|
"Error requesting airdrop: {:?} to addr: {:?} amount: {}",
|
||||||
|
err, faucet_addr, airdrop_amount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let current_balance = client.get_balance(&id.pubkey()).unwrap_or_else(|e| {
|
||||||
|
panic!("airdrop error {}", e);
|
||||||
|
});
|
||||||
|
info!("current balance {}...", current_balance);
|
||||||
|
|
||||||
|
if current_balance - starting_balance != airdrop_amount {
|
||||||
|
info!(
|
||||||
|
"Airdrop failed? {} {} {} {}",
|
||||||
|
id.pubkey(),
|
||||||
|
current_balance,
|
||||||
|
starting_balance,
|
||||||
|
airdrop_amount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_create_message(
|
||||||
|
keypair: &Keypair,
|
||||||
|
base_keypair: &Keypair,
|
||||||
|
balance: u64,
|
||||||
|
space: u64,
|
||||||
|
program_id: Pubkey,
|
||||||
|
) -> Message {
|
||||||
|
let instructions = vec![system_instruction::create_account(
|
||||||
|
&keypair.pubkey(),
|
||||||
|
&base_keypair.pubkey(),
|
||||||
|
balance,
|
||||||
|
space,
|
||||||
|
&program_id,
|
||||||
|
)];
|
||||||
|
|
||||||
|
Message::new(&instructions, Some(&keypair.pubkey()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_dos_message(
|
||||||
|
keypair: &Keypair,
|
||||||
|
num_instructions: usize,
|
||||||
|
program_id: Pubkey,
|
||||||
|
num_program_iterations: u8,
|
||||||
|
account_metas: &[AccountMeta],
|
||||||
|
) -> Message {
|
||||||
|
let instructions: Vec<_> = (0..num_instructions)
|
||||||
|
.into_iter()
|
||||||
|
.map(|_| {
|
||||||
|
let data = [num_program_iterations, thread_rng().gen_range(0, 255)];
|
||||||
|
Instruction::new_with_bytes(program_id, &data, account_metas.to_vec())
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Message::new(&instructions, Some(&keypair.pubkey()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn run_transactions_dos(
|
||||||
|
entrypoint_addr: SocketAddr,
|
||||||
|
faucet_addr: SocketAddr,
|
||||||
|
payer_keypairs: &[&Keypair],
|
||||||
|
iterations: usize,
|
||||||
|
maybe_space: Option<u64>,
|
||||||
|
batch_size: usize,
|
||||||
|
maybe_lamports: Option<u64>,
|
||||||
|
num_instructions: usize,
|
||||||
|
num_program_iterations: usize,
|
||||||
|
program_id: Pubkey,
|
||||||
|
program_options: Option<(Keypair, String)>,
|
||||||
|
account_keypairs: &[&Keypair],
|
||||||
|
maybe_account_groups: Option<usize>,
|
||||||
|
just_calculate_fees: bool,
|
||||||
|
batch_sleep_ms: u64,
|
||||||
|
) {
|
||||||
|
assert!(num_instructions > 0);
|
||||||
|
let client = Arc::new(RpcClient::new_socket_with_commitment(
|
||||||
|
entrypoint_addr,
|
||||||
|
CommitmentConfig::confirmed(),
|
||||||
|
));
|
||||||
|
|
||||||
|
info!("Targeting {}", entrypoint_addr);
|
||||||
|
|
||||||
|
let space = maybe_space.unwrap_or(1000);
|
||||||
|
|
||||||
|
let min_balance = maybe_lamports.unwrap_or_else(|| {
|
||||||
|
client
|
||||||
|
.get_minimum_balance_for_rent_exemption(space as usize)
|
||||||
|
.expect("min balance")
|
||||||
|
});
|
||||||
|
assert!(min_balance > 0);
|
||||||
|
|
||||||
|
let account_groups = maybe_account_groups.unwrap_or(1);
|
||||||
|
|
||||||
|
assert!(account_keypairs.len() % account_groups == 0);
|
||||||
|
|
||||||
|
let account_group_size = account_keypairs.len() / account_groups;
|
||||||
|
|
||||||
|
let program_account = client.get_account(&program_id);
|
||||||
|
|
||||||
|
let message = Message::new(
|
||||||
|
&[
|
||||||
|
Instruction::new_with_bytes(
|
||||||
|
Pubkey::new_unique(),
|
||||||
|
&[],
|
||||||
|
vec![AccountMeta::new(Pubkey::new_unique(), true)],
|
||||||
|
),
|
||||||
|
Instruction::new_with_bytes(
|
||||||
|
Pubkey::new_unique(),
|
||||||
|
&[],
|
||||||
|
vec![AccountMeta::new(Pubkey::new_unique(), true)],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut latest_blockhash = Instant::now();
|
||||||
|
let mut last_log = Instant::now();
|
||||||
|
let mut count = 0;
|
||||||
|
let mut blockhash = client.get_latest_blockhash().expect("blockhash");
|
||||||
|
|
||||||
|
if just_calculate_fees {
|
||||||
|
let fee = client
|
||||||
|
.get_fee_for_message(&message)
|
||||||
|
.expect("get_fee_for_message");
|
||||||
|
|
||||||
|
let account_space_fees = min_balance * account_keypairs.len() as u64;
|
||||||
|
let program_fees = if program_account.is_ok() {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
// todo, dynamic real size
|
||||||
|
client.get_minimum_balance_for_rent_exemption(2400).unwrap()
|
||||||
|
};
|
||||||
|
let transaction_fees =
|
||||||
|
account_keypairs.len() as u64 * fee + iterations as u64 * batch_size as u64 * fee;
|
||||||
|
info!(
|
||||||
|
"Accounts fees: {} program_account fees: {} transaction fees: {} total: {}",
|
||||||
|
account_space_fees,
|
||||||
|
program_fees,
|
||||||
|
transaction_fees,
|
||||||
|
account_space_fees + program_fees + transaction_fees,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if program_account.is_err() {
|
||||||
|
let mut config = CliConfig::default();
|
||||||
|
let (program_keypair, program_location) = program_options
|
||||||
|
.as_ref()
|
||||||
|
.expect("If the program doesn't exist, need to provide program keypair to deploy");
|
||||||
|
info!(
|
||||||
|
"processing deploy: {:?} key: {}",
|
||||||
|
program_account,
|
||||||
|
program_keypair.pubkey()
|
||||||
|
);
|
||||||
|
config.signers = vec![payer_keypairs[0], program_keypair];
|
||||||
|
process_deploy(
|
||||||
|
client.clone(),
|
||||||
|
&config,
|
||||||
|
program_location,
|
||||||
|
Some(1),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.expect("deploy didn't pass");
|
||||||
|
} else {
|
||||||
|
info!("Found program account. Skipping deploy..");
|
||||||
|
assert!(program_account.unwrap().executable);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut tx_sent_count = 0;
|
||||||
|
let mut total_dos_messages_sent = 0;
|
||||||
|
let mut balances: Vec<_> = payer_keypairs
|
||||||
|
.iter()
|
||||||
|
.map(|keypair| client.get_balance(&keypair.pubkey()).unwrap_or(0))
|
||||||
|
.collect();
|
||||||
|
let mut last_balance = Instant::now();
|
||||||
|
|
||||||
|
info!("Starting balance(s): {:?}", balances);
|
||||||
|
|
||||||
|
let executor = TransactionExecutor::new(entrypoint_addr);
|
||||||
|
|
||||||
|
let mut accounts_created = false;
|
||||||
|
let tested_size = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
|
let account_metas: Vec<_> = account_keypairs
|
||||||
|
.iter()
|
||||||
|
.map(|kp| AccountMeta::new(kp.pubkey(), false))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if latest_blockhash.elapsed().as_secs() > 10 {
|
||||||
|
blockhash = client.get_latest_blockhash().expect("blockhash");
|
||||||
|
latest_blockhash = Instant::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
let fee = client
|
||||||
|
.get_fee_for_message(&message)
|
||||||
|
.expect("get_fee_for_message");
|
||||||
|
let lamports = min_balance + fee;
|
||||||
|
|
||||||
|
for (i, balance) in balances.iter_mut().enumerate() {
|
||||||
|
if *balance < lamports || last_balance.elapsed().as_secs() > 2 {
|
||||||
|
if let Ok(b) = client.get_balance(&payer_keypairs[i].pubkey()) {
|
||||||
|
*balance = b;
|
||||||
|
}
|
||||||
|
last_balance = Instant::now();
|
||||||
|
if *balance < lamports * 2 {
|
||||||
|
info!(
|
||||||
|
"Balance {} is less than needed: {}, doing aidrop...",
|
||||||
|
balance, lamports
|
||||||
|
);
|
||||||
|
if !airdrop_lamports(
|
||||||
|
&client,
|
||||||
|
&faucet_addr,
|
||||||
|
payer_keypairs[i],
|
||||||
|
lamports * 100_000,
|
||||||
|
) {
|
||||||
|
warn!("failed airdrop, exiting");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !accounts_created {
|
||||||
|
let mut accounts_to_create = vec![];
|
||||||
|
for kp in account_keypairs {
|
||||||
|
if let Ok(account) = client.get_account(&kp.pubkey()) {
|
||||||
|
if account.data.len() as u64 != space {
|
||||||
|
info!(
|
||||||
|
"account {} doesn't have space specified. Has {} requested: {}",
|
||||||
|
kp.pubkey(),
|
||||||
|
account.data.len(),
|
||||||
|
space,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
accounts_to_create.push(kp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !accounts_to_create.is_empty() {
|
||||||
|
info!("creating accounts {}", accounts_to_create.len());
|
||||||
|
let txs: Vec<_> = accounts_to_create
|
||||||
|
.par_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, keypair)| {
|
||||||
|
let message = make_create_message(
|
||||||
|
payer_keypairs[i % payer_keypairs.len()],
|
||||||
|
keypair,
|
||||||
|
min_balance,
|
||||||
|
space,
|
||||||
|
program_id,
|
||||||
|
);
|
||||||
|
let signers: Vec<&Keypair> =
|
||||||
|
vec![payer_keypairs[i % payer_keypairs.len()], keypair];
|
||||||
|
Transaction::new(&signers, message, blockhash)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let mut new_ids = executor.push_transactions(txs);
|
||||||
|
warn!("sent account creation {}", new_ids.len());
|
||||||
|
let start = Instant::now();
|
||||||
|
loop {
|
||||||
|
let cleared = executor.drain_cleared();
|
||||||
|
new_ids.retain(|x| !cleared.contains(x));
|
||||||
|
if new_ids.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if start.elapsed().as_secs() > 60 {
|
||||||
|
info!("Some creation failed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sleep(Duration::from_millis(500));
|
||||||
|
}
|
||||||
|
for kp in account_keypairs {
|
||||||
|
let account = client.get_account(&kp.pubkey()).unwrap();
|
||||||
|
info!("{} => {:?}", kp.pubkey(), account);
|
||||||
|
assert!(account.data.len() as u64 == space);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info!("All accounts created.");
|
||||||
|
}
|
||||||
|
accounts_created = true;
|
||||||
|
} else {
|
||||||
|
// Create dos transactions
|
||||||
|
info!("creating new batch of size: {}", batch_size);
|
||||||
|
let chunk_size = batch_size / payer_keypairs.len();
|
||||||
|
for (i, keypair) in payer_keypairs.iter().enumerate() {
|
||||||
|
let txs: Vec<_> = (0..chunk_size)
|
||||||
|
.into_par_iter()
|
||||||
|
.map(|x| {
|
||||||
|
let message = make_dos_message(
|
||||||
|
keypair,
|
||||||
|
num_instructions,
|
||||||
|
program_id,
|
||||||
|
num_program_iterations as u8,
|
||||||
|
&account_metas[(x % account_groups) * account_group_size
|
||||||
|
..(x % account_groups) * account_group_size + account_group_size],
|
||||||
|
);
|
||||||
|
let signers: Vec<&Keypair> = vec![keypair];
|
||||||
|
let tx = Transaction::new(&signers, message, blockhash);
|
||||||
|
if !tested_size.load(Ordering::Relaxed) {
|
||||||
|
let ser_size = bincode::serialized_size(&tx).unwrap();
|
||||||
|
assert!(ser_size < 1200, "{}", ser_size);
|
||||||
|
tested_size.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
tx
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
balances[i] = balances[i].saturating_sub(fee * txs.len() as u64);
|
||||||
|
info!("txs: {}", txs.len());
|
||||||
|
let new_ids = executor.push_transactions(txs);
|
||||||
|
info!("ids: {}", new_ids.len());
|
||||||
|
tx_sent_count += new_ids.len();
|
||||||
|
total_dos_messages_sent += num_instructions * new_ids.len();
|
||||||
|
}
|
||||||
|
let _ = executor.drain_cleared();
|
||||||
|
}
|
||||||
|
|
||||||
|
count += 1;
|
||||||
|
if last_log.elapsed().as_secs() > 3 {
|
||||||
|
info!(
|
||||||
|
"total_dos_messages_sent: {} tx_sent_count: {} loop_count: {} balance(s): {:?}",
|
||||||
|
total_dos_messages_sent, tx_sent_count, count, balances
|
||||||
|
);
|
||||||
|
last_log = Instant::now();
|
||||||
|
}
|
||||||
|
if iterations != 0 && count >= iterations {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if executor.num_outstanding() >= batch_size {
|
||||||
|
sleep(Duration::from_millis(batch_sleep_ms));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
executor.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
solana_logger::setup_with_default("solana=info");
|
||||||
|
let matches = App::new(crate_name!())
|
||||||
|
.about(crate_description!())
|
||||||
|
.version(solana_version::version!())
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("entrypoint")
|
||||||
|
.long("entrypoint")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("HOST:PORT")
|
||||||
|
.help("RPC entrypoint address. Usually <ip>:8899"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("faucet_addr")
|
||||||
|
.long("faucet")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("HOST:PORT")
|
||||||
|
.help("Faucet entrypoint address. Usually <ip>:9900"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("space")
|
||||||
|
.long("space")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("BYTES")
|
||||||
|
.help("Size of accounts to create"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("lamports")
|
||||||
|
.long("lamports")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("LAMPORTS")
|
||||||
|
.help("How many lamports to fund each account"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("payer")
|
||||||
|
.long("payer")
|
||||||
|
.takes_value(true)
|
||||||
|
.multiple(true)
|
||||||
|
.value_name("FILE")
|
||||||
|
.help("One or more payer keypairs to fund account creation."),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("account")
|
||||||
|
.long("account")
|
||||||
|
.takes_value(true)
|
||||||
|
.multiple(true)
|
||||||
|
.value_name("FILE")
|
||||||
|
.help("One or more keypairs to create accounts owned by the program and which the program will write to."),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("account_groups")
|
||||||
|
.long("account_groups")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("NUM")
|
||||||
|
.help("Number of groups of accounts to split the accounts into")
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("batch_size")
|
||||||
|
.long("batch-size")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("NUM")
|
||||||
|
.help("Number of transactions to send per batch"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("num_instructions")
|
||||||
|
.long("num-instructions")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("NUM")
|
||||||
|
.help("Number of accounts to create on each transaction"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("num_program_iterations")
|
||||||
|
.long("num-program-iterations")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("NUM")
|
||||||
|
.help("Number of iterations in the smart contract"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("iterations")
|
||||||
|
.long("iterations")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("NUM")
|
||||||
|
.help("Number of iterations to make"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("batch_sleep_ms")
|
||||||
|
.long("batch-sleep-ms")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("NUM")
|
||||||
|
.help("Sleep for this long the num outstanding transctions is greater than the batch size."),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("check_gossip")
|
||||||
|
.long("check-gossip")
|
||||||
|
.help("Just use entrypoint address directly"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("just_calculate_fees")
|
||||||
|
.long("just-calculate-fees")
|
||||||
|
.help("Just print the necessary fees and exit"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("program_id")
|
||||||
|
.long("program-id")
|
||||||
|
.takes_value(true)
|
||||||
|
.required(true)
|
||||||
|
.help("program_id address to initialize account"),
|
||||||
|
)
|
||||||
|
.get_matches();
|
||||||
|
|
||||||
|
let skip_gossip = !matches.is_present("check_gossip");
|
||||||
|
let just_calculate_fees = matches.is_present("just_calculate_fees");
|
||||||
|
|
||||||
|
let port = if skip_gossip { DEFAULT_RPC_PORT } else { 8001 };
|
||||||
|
let mut entrypoint_addr = SocketAddr::from(([127, 0, 0, 1], port));
|
||||||
|
if let Some(addr) = matches.value_of("entrypoint") {
|
||||||
|
entrypoint_addr = solana_net_utils::parse_host_port(addr).unwrap_or_else(|e| {
|
||||||
|
eprintln!("failed to parse entrypoint address: {}", e);
|
||||||
|
exit(1)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let mut faucet_addr = SocketAddr::from(([127, 0, 0, 1], FAUCET_PORT));
|
||||||
|
if let Some(addr) = matches.value_of("faucet_addr") {
|
||||||
|
faucet_addr = solana_net_utils::parse_host_port(addr).unwrap_or_else(|e| {
|
||||||
|
eprintln!("failed to parse entrypoint address: {}", e);
|
||||||
|
exit(1)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let space = value_t!(matches, "space", u64).ok();
|
||||||
|
let lamports = value_t!(matches, "lamports", u64).ok();
|
||||||
|
let batch_size = value_t!(matches, "batch_size", usize).unwrap_or(4);
|
||||||
|
let iterations = value_t!(matches, "iterations", usize).unwrap_or(10);
|
||||||
|
let num_program_iterations = value_t!(matches, "num_program_iterations", usize).unwrap_or(10);
|
||||||
|
let num_instructions = value_t!(matches, "num_instructions", usize).unwrap_or(1);
|
||||||
|
if num_instructions == 0 || num_instructions > 500 {
|
||||||
|
eprintln!("bad num_instructions: {}", num_instructions);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
let batch_sleep_ms = value_t!(matches, "batch_sleep_ms", u64).unwrap_or(500);
|
||||||
|
|
||||||
|
let program_id = pubkey_of(&matches, "program_id").unwrap();
|
||||||
|
|
||||||
|
let payer_keypairs: Vec<_> = values_t_or_exit!(matches, "payer", String)
|
||||||
|
.iter()
|
||||||
|
.map(|keypair_string| {
|
||||||
|
read_keypair_file(keypair_string)
|
||||||
|
.unwrap_or_else(|_| panic!("bad keypair {:?}", keypair_string))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let account_keypairs: Vec<_> = values_t_or_exit!(matches, "account", String)
|
||||||
|
.iter()
|
||||||
|
.map(|keypair_string| {
|
||||||
|
read_keypair_file(keypair_string)
|
||||||
|
.unwrap_or_else(|_| panic!("bad keypair {:?}", keypair_string))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let account_groups = value_t!(matches, "account_groups", usize).ok();
|
||||||
|
let payer_keypair_refs: Vec<&Keypair> = payer_keypairs.iter().collect();
|
||||||
|
let account_keypair_refs: Vec<&Keypair> = account_keypairs.iter().collect();
|
||||||
|
|
||||||
|
let rpc_addr = if !skip_gossip {
|
||||||
|
info!("Finding cluster entry: {:?}", entrypoint_addr);
|
||||||
|
let (gossip_nodes, _validators) = discover(
|
||||||
|
None, // keypair
|
||||||
|
Some(&entrypoint_addr),
|
||||||
|
None, // num_nodes
|
||||||
|
Duration::from_secs(60), // timeout
|
||||||
|
None, // find_node_by_pubkey
|
||||||
|
Some(&entrypoint_addr), // find_node_by_gossip_addr
|
||||||
|
None, // my_gossip_addr
|
||||||
|
0, // my_shred_version
|
||||||
|
SocketAddrSpace::Unspecified,
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
eprintln!("Failed to discover {} node: {:?}", entrypoint_addr, err);
|
||||||
|
exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
info!("done found {} nodes", gossip_nodes.len());
|
||||||
|
gossip_nodes[0].rpc
|
||||||
|
} else {
|
||||||
|
info!("Using {:?} as the RPC address", entrypoint_addr);
|
||||||
|
entrypoint_addr
|
||||||
|
};
|
||||||
|
|
||||||
|
run_transactions_dos(
|
||||||
|
rpc_addr,
|
||||||
|
faucet_addr,
|
||||||
|
&payer_keypair_refs,
|
||||||
|
iterations,
|
||||||
|
space,
|
||||||
|
batch_size,
|
||||||
|
lamports,
|
||||||
|
num_instructions,
|
||||||
|
num_program_iterations,
|
||||||
|
program_id,
|
||||||
|
None,
|
||||||
|
&account_keypair_refs,
|
||||||
|
account_groups,
|
||||||
|
just_calculate_fees,
|
||||||
|
batch_sleep_ms,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod test {
|
||||||
|
use super::*;
|
||||||
|
use solana_core::validator::ValidatorConfig;
|
||||||
|
use solana_local_cluster::{
|
||||||
|
local_cluster::{ClusterConfig, LocalCluster},
|
||||||
|
validator_configs::make_identical_validator_configs,
|
||||||
|
};
|
||||||
|
use solana_measure::measure::Measure;
|
||||||
|
use solana_sdk::poh_config::PohConfig;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tx_size() {
|
||||||
|
solana_logger::setup();
|
||||||
|
let keypair = Keypair::new();
|
||||||
|
let num_instructions = 20;
|
||||||
|
let program_id = Pubkey::new_unique();
|
||||||
|
let num_accounts = 17;
|
||||||
|
|
||||||
|
let account_metas: Vec<_> = (0..num_accounts)
|
||||||
|
.into_iter()
|
||||||
|
.map(|_| AccountMeta::new(Pubkey::new_unique(), false))
|
||||||
|
.collect();
|
||||||
|
let num_program_iterations = 10;
|
||||||
|
let message = make_dos_message(
|
||||||
|
&keypair,
|
||||||
|
num_instructions,
|
||||||
|
program_id,
|
||||||
|
num_program_iterations,
|
||||||
|
&account_metas,
|
||||||
|
);
|
||||||
|
let signers: Vec<&Keypair> = vec![&keypair];
|
||||||
|
let blockhash = solana_sdk::hash::Hash::default();
|
||||||
|
let tx = Transaction::new(&signers, message, blockhash);
|
||||||
|
let size = bincode::serialized_size(&tx).unwrap();
|
||||||
|
info!("size:{}", size);
|
||||||
|
assert!(size < 1200);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn test_transaction_dos() {
|
||||||
|
solana_logger::setup();
|
||||||
|
|
||||||
|
let validator_config = ValidatorConfig::default();
|
||||||
|
let num_nodes = 1;
|
||||||
|
let mut config = ClusterConfig {
|
||||||
|
cluster_lamports: 10_000_000,
|
||||||
|
poh_config: PohConfig::new_sleep(Duration::from_millis(50)),
|
||||||
|
node_stakes: vec![100; num_nodes],
|
||||||
|
validator_configs: make_identical_validator_configs(&validator_config, num_nodes),
|
||||||
|
..ClusterConfig::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let faucet_addr = SocketAddr::from(([127, 0, 0, 1], 9900));
|
||||||
|
let cluster = LocalCluster::new(&mut config, SocketAddrSpace::Unspecified);
|
||||||
|
|
||||||
|
let program_keypair = Keypair::new();
|
||||||
|
|
||||||
|
let iterations = 1000;
|
||||||
|
let maybe_space = Some(10_000_000);
|
||||||
|
let batch_size = 1;
|
||||||
|
let maybe_lamports = Some(10);
|
||||||
|
let maybe_account_groups = Some(1);
|
||||||
|
// 85 inst, 142 iterations, 5 accounts
|
||||||
|
// 20 inst, 30 * 20 iterations, 1 account
|
||||||
|
//
|
||||||
|
// 100 inst, 7 * 20 iterations, 1 account
|
||||||
|
let num_instructions = 70;
|
||||||
|
let num_program_iterations = 10;
|
||||||
|
let num_accounts = 7;
|
||||||
|
let account_keypairs: Vec<_> = (0..num_accounts)
|
||||||
|
.into_iter()
|
||||||
|
.map(|_| Keypair::new())
|
||||||
|
.collect();
|
||||||
|
let account_keypair_refs: Vec<_> = account_keypairs.iter().collect();
|
||||||
|
let mut start = Measure::start("total accounts run");
|
||||||
|
run_transactions_dos(
|
||||||
|
cluster.entry_point_info.rpc,
|
||||||
|
faucet_addr,
|
||||||
|
&[&cluster.funding_keypair],
|
||||||
|
iterations,
|
||||||
|
maybe_space,
|
||||||
|
batch_size,
|
||||||
|
maybe_lamports,
|
||||||
|
num_instructions,
|
||||||
|
num_program_iterations,
|
||||||
|
program_keypair.pubkey(),
|
||||||
|
Some((
|
||||||
|
program_keypair,
|
||||||
|
String::from("../programs/bpf/c/out/tuner.so"),
|
||||||
|
)),
|
||||||
|
&account_keypair_refs,
|
||||||
|
maybe_account_groups,
|
||||||
|
false,
|
||||||
|
100,
|
||||||
|
);
|
||||||
|
start.stop();
|
||||||
|
info!("{}", start);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user