diff --git a/bench-tps/src/bench.rs b/bench-tps/src/bench.rs index c6b333d192..0ee82b8377 100644 --- a/bench-tps/src/bench.rs +++ b/bench-tps/src/bench.rs @@ -2,6 +2,7 @@ use { crate::{ bench_tps_client::*, cli::Config, + inline_instruction_padding_program::{create_padded_instruction, InstructionPaddingConfig}, perf_utils::{sample_txs, SampleStats}, send_batch::*, }, @@ -20,7 +21,7 @@ use { native_token::Sol, pubkey::Pubkey, signature::{Keypair, Signer}, - system_instruction, system_transaction, + system_instruction, timing::{duration_as_ms, duration_as_s, duration_as_us, timestamp}, transaction::Transaction, }, @@ -94,6 +95,7 @@ struct TransactionChunkGenerator<'a, 'b, T: ?Sized> { chunk_index: usize, reclaim_lamports_back_to_source_account: bool, use_randomized_compute_unit_price: bool, + instruction_padding_config: Option, } impl<'a, 'b, T> TransactionChunkGenerator<'a, 'b, T> @@ -106,6 +108,7 @@ where nonce_keypairs: Option<&'b Vec>, chunk_size: usize, use_randomized_compute_unit_price: bool, + instruction_padding_config: Option, ) -> Self { let account_chunks = KeypairChunks::new(gen_keypairs, chunk_size); let nonce_chunks = @@ -118,6 +121,7 @@ where chunk_index: 0, reclaim_lamports_back_to_source_account: false, use_randomized_compute_unit_price, + instruction_padding_config, } } @@ -143,6 +147,7 @@ where source_nonce_chunk, dest_nonce_chunk, self.reclaim_lamports_back_to_source_account, + &self.instruction_padding_config, ) } else { assert!(blockhash.is_some()); @@ -151,6 +156,7 @@ where dest_chunk, self.reclaim_lamports_back_to_source_account, blockhash.unwrap(), + &self.instruction_padding_config, self.use_randomized_compute_unit_price, ) }; @@ -345,6 +351,7 @@ where target_slots_per_epoch, use_randomized_compute_unit_price, use_durable_nonce, + instruction_padding_config, .. } = config; @@ -355,6 +362,7 @@ where nonce_keypairs.as_ref(), tx_count, use_randomized_compute_unit_price, + instruction_padding_config, ); let first_tx_count = loop { @@ -479,6 +487,7 @@ fn generate_system_txs( dest: &VecDeque<&Keypair>, reclaim: bool, blockhash: &Hash, + instruction_padding_config: &Option, use_randomized_compute_unit_price: bool, ) -> Vec { let pairs: Vec<_> = if !reclaim { @@ -500,12 +509,13 @@ fn generate_system_txs( .par_iter() .map(|((from, to), compute_unit_price)| { ( - transfer_with_compute_unit_price( + transfer_with_compute_unit_price_and_padding( from, &to.pubkey(), 1, *blockhash, - **compute_unit_price, + instruction_padding_config, + Some(**compute_unit_price), ), Some(timestamp()), ) @@ -516,7 +526,14 @@ fn generate_system_txs( .par_iter() .map(|(from, to)| { ( - system_transaction::transfer(from, &to.pubkey(), 1, *blockhash), + transfer_with_compute_unit_price_and_padding( + from, + &to.pubkey(), + 1, + *blockhash, + instruction_padding_config, + None, + ), Some(timestamp()), ) }) @@ -524,19 +541,34 @@ fn generate_system_txs( } } -fn transfer_with_compute_unit_price( +fn transfer_with_compute_unit_price_and_padding( from_keypair: &Keypair, to: &Pubkey, lamports: u64, recent_blockhash: Hash, - compute_unit_price: u64, + instruction_padding_config: &Option, + compute_unit_price: Option, ) -> Transaction { let from_pubkey = from_keypair.pubkey(); - let instructions = vec![ - system_instruction::transfer(&from_pubkey, to, lamports), - ComputeBudgetInstruction::set_compute_unit_limit(TRANSFER_TRANSACTION_COMPUTE_UNIT), - ComputeBudgetInstruction::set_compute_unit_price(compute_unit_price), - ]; + let transfer_instruction = system_instruction::transfer(&from_pubkey, to, lamports); + let instruction = if let Some(instruction_padding_config) = instruction_padding_config { + create_padded_instruction( + instruction_padding_config.program_id, + transfer_instruction, + vec![], + instruction_padding_config.data_size, + ) + .expect("Could not create padded instruction") + } else { + transfer_instruction + }; + let mut instructions = vec![instruction]; + if let Some(compute_unit_price) = compute_unit_price { + instructions.extend_from_slice(&[ + ComputeBudgetInstruction::set_compute_unit_limit(TRANSFER_TRANSACTION_COMPUTE_UNIT), + ComputeBudgetInstruction::set_compute_unit_price(compute_unit_price), + ]) + } let message = Message::new(&instructions, Some(&from_pubkey)); Transaction::new(&[from_keypair], message, recent_blockhash) } @@ -601,6 +633,37 @@ fn get_nonce_blockhashes( blockhashes } +fn nonced_transfer_with_padding( + from_keypair: &Keypair, + to: &Pubkey, + lamports: u64, + nonce_account: &Pubkey, + nonce_authority: &Keypair, + nonce_hash: Hash, + instruction_padding_config: &Option, +) -> Transaction { + let from_pubkey = from_keypair.pubkey(); + let transfer_instruction = system_instruction::transfer(&from_pubkey, to, lamports); + let instruction = if let Some(instruction_padding_config) = instruction_padding_config { + create_padded_instruction( + instruction_padding_config.program_id, + transfer_instruction, + vec![], + instruction_padding_config.data_size, + ) + .expect("Could not create padded instruction") + } else { + transfer_instruction + }; + let message = Message::new_with_nonce( + vec![instruction], + Some(&from_pubkey), + nonce_account, + &nonce_authority.pubkey(), + ); + Transaction::new(&[from_keypair, nonce_authority], message, nonce_hash) +} + fn generate_nonced_system_txs( client: Arc, source: &[&Keypair], @@ -608,6 +671,7 @@ fn generate_nonced_system_txs, reclaim: bool, + instruction_padding_config: &Option, ) -> Vec { let length = source.len(); let mut transactions: Vec = Vec::with_capacity(length); @@ -620,13 +684,14 @@ fn generate_nonced_system_txs = get_nonce_blockhashes(&client, &pubkeys); for i in 0..length { transactions.push(( - system_transaction::nonced_transfer( + nonced_transfer_with_padding( source[i], &dest[i].pubkey(), 1, &source_nonce[i].pubkey(), source[i], blockhashes[i], + instruction_padding_config, ), None, )); @@ -637,13 +702,14 @@ fn generate_nonced_system_txs, } impl Default for Config { @@ -85,6 +87,7 @@ impl Default for Config { tpu_connection_pool_size: DEFAULT_TPU_CONNECTION_POOL_SIZE, use_randomized_compute_unit_price: false, use_durable_nonce: false, + instruction_padding_config: None, } } } @@ -318,6 +321,20 @@ pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> { .long("use-durable-nonce") .help("Use durable transaction nonce instead of recent blockhash"), ) + .arg( + Arg::with_name("instruction_padding_program_id") + .long("instruction-padding-program-id") + .requires("instruction_padding_data_size") + .takes_value(true) + .value_name("PUBKEY") + .help("If instruction data is padded, optionally specify the padding program id to target"), + ) + .arg( + Arg::with_name("instruction_padding_data_size") + .long("instruction-padding-data-size") + .takes_value(true) + .help("If set, wraps all instructions in the instruction padding program, with the given amount of padding bytes in instruction data."), + ) } /// Parses a clap `ArgMatches` structure into a `Config` @@ -456,5 +473,19 @@ pub fn extract_args(matches: &ArgMatches) -> Config { args.use_durable_nonce = true; } + if let Some(data_size) = matches.value_of("instruction_padding_data_size") { + let program_id = matches + .value_of("instruction_padding_program_id") + .map(|target_str| target_str.parse().unwrap()) + .unwrap_or(inline_instruction_padding_program::ID); + args.instruction_padding_config = Some(InstructionPaddingConfig { + program_id, + data_size: data_size + .to_string() + .parse() + .expect("Can't parse padded instruction data size"), + }); + } + args } diff --git a/bench-tps/src/inline_instruction_padding_program.rs b/bench-tps/src/inline_instruction_padding_program.rs new file mode 100644 index 0000000000..bf7f75929a --- /dev/null +++ b/bench-tps/src/inline_instruction_padding_program.rs @@ -0,0 +1,78 @@ +use { + solana_sdk::{ + declare_id, + instruction::{AccountMeta, Instruction}, + program_error::ProgramError, + pubkey::Pubkey, + syscalls::{MAX_CPI_ACCOUNT_INFOS, MAX_CPI_INSTRUCTION_DATA_LEN}, + }, + std::{convert::TryInto, mem::size_of}, +}; + +pub struct InstructionPaddingConfig { + pub program_id: Pubkey, + pub data_size: u32, +} + +declare_id!("iXpADd6AW1k5FaaXum5qHbSqyd7TtoN6AD7suVa83MF"); + +pub fn create_padded_instruction( + program_id: Pubkey, + instruction: Instruction, + padding_accounts: Vec, + padding_data: u32, +) -> Result { + // The format for instruction data goes: + // * 1 byte for the instruction type + // * 4 bytes for the number of accounts required + // * 4 bytes for the size of the data required + // * the actual instruction data + // * additional bytes are all padding + let data_size = size_of::() + + size_of::() + + size_of::() + + instruction.data.len() + + padding_data as usize; + // crude, but can find a potential issue right away + if instruction.data.len() > MAX_CPI_INSTRUCTION_DATA_LEN as usize { + return Err(ProgramError::InvalidInstructionData); + } + let mut data = Vec::with_capacity(data_size); + data.push(1); + let num_accounts: u32 = instruction + .accounts + .len() + .try_into() + .map_err(|_| ProgramError::InvalidInstructionData)?; + data.extend(num_accounts.to_le_bytes().into_iter()); + + let data_size: u32 = instruction + .data + .len() + .try_into() + .map_err(|_| ProgramError::InvalidInstructionData)?; + data.extend(data_size.to_le_bytes().into_iter()); + data.extend(instruction.data.into_iter()); + for i in 0..padding_data { + data.push((i % u8::MAX as u32) as u8); + } + + // The format for account data goes: + // * accounts required for the CPI + // * program account to call into + // * additional accounts may be included as padding or to test loading / locks + let num_accounts = instruction.accounts.len() + 1 + padding_accounts.len(); + if num_accounts > MAX_CPI_ACCOUNT_INFOS { + return Err(ProgramError::InvalidAccountData); + } + let mut accounts = Vec::with_capacity(num_accounts); + accounts.extend(instruction.accounts.into_iter()); + accounts.push(AccountMeta::new_readonly(instruction.program_id, false)); + accounts.extend(padding_accounts.into_iter()); + + Ok(Instruction { + program_id, + accounts, + data, + }) +} diff --git a/bench-tps/src/lib.rs b/bench-tps/src/lib.rs index 5226b4e56f..ac0f9aba51 100644 --- a/bench-tps/src/lib.rs +++ b/bench-tps/src/lib.rs @@ -2,6 +2,7 @@ pub mod bench; pub mod bench_tps_client; pub mod cli; +pub mod inline_instruction_padding_program; pub mod keypairs; mod perf_utils; pub mod send_batch; diff --git a/bench-tps/src/main.rs b/bench-tps/src/main.rs index a51f49700a..94422c485f 100644 --- a/bench-tps/src/main.rs +++ b/bench-tps/src/main.rs @@ -155,6 +155,7 @@ fn main() { tpu_connection_pool_size, use_randomized_compute_unit_price, use_durable_nonce, + instruction_padding_config, .. } = &cli_config; @@ -223,6 +224,15 @@ fn main() { *num_nodes, *target_node, ); + if let Some(instruction_padding_config) = instruction_padding_config { + info!( + "Checking for existence of instruction padding program: {}", + instruction_padding_config.program_id + ); + client + .get_account(&instruction_padding_config.program_id) + .expect("Instruction padding program must be deployed to this cluster. Deploy the program using `solana program deploy ./bench-tps/tests/fixtures/spl_instruction_padding.so` and pass the resulting program id with `--instruction-padding-program-id`"); + } let keypairs = get_keypairs( client.clone(), id, diff --git a/bench-tps/tests/bench_tps.rs b/bench-tps/tests/bench_tps.rs index b3b5af83f7..a16fbc3645 100644 --- a/bench-tps/tests/bench_tps.rs +++ b/bench-tps/tests/bench_tps.rs @@ -5,6 +5,7 @@ use { solana_bench_tps::{ bench::{do_bench_tps, generate_and_fund_keypairs}, cli::Config, + inline_instruction_padding_program::{self, InstructionPaddingConfig}, send_batch::generate_durable_nonce_accounts, }, solana_core::validator::ValidatorConfig, @@ -16,11 +17,14 @@ use { solana_rpc::rpc::JsonRpcConfig, solana_rpc_client::rpc_client::RpcClient, solana_sdk::{ + account::{Account, AccountSharedData}, commitment_config::CommitmentConfig, + fee_calculator::FeeRateGovernor, + rent::Rent, signature::{Keypair, Signer}, }, solana_streamer::socket::SocketAddrSpace, - solana_test_validator::TestValidator, + solana_test_validator::TestValidatorGenesis, solana_thin_client::thin_client::ThinClient, solana_tpu_client::{ connection_cache::ConnectionCache, @@ -29,8 +33,22 @@ use { std::{sync::Arc, time::Duration}, }; +fn program_account(program_data: &[u8]) -> AccountSharedData { + AccountSharedData::from(Account { + lamports: Rent::default().minimum_balance(program_data.len()).min(1), + data: program_data.to_vec(), + owner: solana_sdk::bpf_loader::id(), + executable: true, + rent_epoch: 0, + }) +} + fn test_bench_tps_local_cluster(config: Config) { let native_instruction_processors = vec![]; + let additional_accounts = vec![( + inline_instruction_padding_program::id(), + program_account(include_bytes!("fixtures/spl_instruction_padding.so")), + )]; solana_logger::setup(); @@ -54,6 +72,7 @@ fn test_bench_tps_local_cluster(config: Config) { NUM_NODES, ), native_instruction_processors, + additional_accounts, ..ClusterConfig::default() }, SocketAddrSpace::Unspecified, @@ -92,8 +111,20 @@ fn test_bench_tps_test_validator(config: Config) { let faucet_addr = run_local_faucet(mint_keypair, None); - let test_validator = - TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified); + let test_validator = TestValidatorGenesis::default() + .fee_rate_governor(FeeRateGovernor::new(0, 0)) + .rent(Rent { + lamports_per_byte_year: 1, + exemption_threshold: 1.0, + ..Rent::default() + }) + .faucet_addr(Some(faucet_addr)) + .add_program( + "spl_instruction_padding", + inline_instruction_padding_program::id(), + ) + .start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified) + .expect("validator start failed"); let rpc_client = Arc::new(RpcClient::new_with_commitment( test_validator.rpc_url(), @@ -164,3 +195,31 @@ fn test_bench_tps_tpu_client_nonce() { ..Config::default() }); } + +#[test] +#[serial] +fn test_bench_tps_local_cluster_with_padding() { + test_bench_tps_local_cluster(Config { + tx_count: 100, + duration: Duration::from_secs(10), + instruction_padding_config: Some(InstructionPaddingConfig { + program_id: inline_instruction_padding_program::id(), + data_size: 0, + }), + ..Config::default() + }); +} + +#[test] +#[serial] +fn test_bench_tps_tpu_client_with_padding() { + test_bench_tps_test_validator(Config { + tx_count: 100, + duration: Duration::from_secs(10), + instruction_padding_config: Some(InstructionPaddingConfig { + program_id: inline_instruction_padding_program::id(), + data_size: 0, + }), + ..Config::default() + }); +} diff --git a/bench-tps/tests/fixtures/spl_instruction_padding.so b/bench-tps/tests/fixtures/spl_instruction_padding.so new file mode 100755 index 0000000000..89c9fca554 Binary files /dev/null and b/bench-tps/tests/fixtures/spl_instruction_padding.so differ