bench-tps: Add instruction padding program support (#27813)

* bench-tps: Add instruction padding program support

* Add ability to customize program id

* Improve names and comments
This commit is contained in:
Jon Cinque 2022-09-22 21:37:40 +02:00 committed by GitHub
parent 64f64dedea
commit f3fcbdba29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 261 additions and 16 deletions

View File

@ -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<InstructionPaddingConfig>,
}
impl<'a, 'b, T> TransactionChunkGenerator<'a, 'b, T>
@ -106,6 +108,7 @@ where
nonce_keypairs: Option<&'b Vec<Keypair>>,
chunk_size: usize,
use_randomized_compute_unit_price: bool,
instruction_padding_config: Option<InstructionPaddingConfig>,
) -> 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<InstructionPaddingConfig>,
use_randomized_compute_unit_price: bool,
) -> Vec<TimestampedTransaction> {
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<InstructionPaddingConfig>,
compute_unit_price: Option<u64>,
) -> 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<T: 'static + BenchTpsClient + Send + Sync + ?Sized>(
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<InstructionPaddingConfig>,
) -> 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<T: 'static + BenchTpsClient + Send + Sync + ?Sized>(
client: Arc<T>,
source: &[&Keypair],
@ -608,6 +671,7 @@ fn generate_nonced_system_txs<T: 'static + BenchTpsClient + Send + Sync + ?Sized
source_nonce: &[&Keypair],
dest_nonce: &VecDeque<&Keypair>,
reclaim: bool,
instruction_padding_config: &Option<InstructionPaddingConfig>,
) -> Vec<TimestampedTransaction> {
let length = source.len();
let mut transactions: Vec<TimestampedTransaction> = Vec::with_capacity(length);
@ -620,13 +684,14 @@ fn generate_nonced_system_txs<T: 'static + BenchTpsClient + Send + Sync + ?Sized
let blockhashes: Vec<Hash> = 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<T: 'static + BenchTpsClient + Send + Sync + ?Sized
for i in 0..length {
transactions.push((
system_transaction::nonced_transfer(
nonced_transfer_with_padding(
dest[i],
&source[i].pubkey(),
1,
&dest_nonce[i].pubkey(),
dest[i],
blockhashes[i],
instruction_padding_config,
),
None,
));

View File

@ -1,4 +1,5 @@
use {
crate::inline_instruction_padding_program::{self, InstructionPaddingConfig},
clap::{crate_description, crate_name, App, Arg, ArgMatches},
solana_clap_utils::input_validators::{is_url, is_url_or_moniker},
solana_cli_config::{ConfigInput, CONFIG_FILE},
@ -56,6 +57,7 @@ pub struct Config {
pub tpu_connection_pool_size: usize,
pub use_randomized_compute_unit_price: bool,
pub use_durable_nonce: bool,
pub instruction_padding_config: Option<InstructionPaddingConfig>,
}
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
}

View File

@ -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<AccountMeta>,
padding_data: u32,
) -> Result<Instruction, ProgramError> {
// 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::<u8>()
+ size_of::<u32>()
+ size_of::<u32>()
+ 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,
})
}

View File

@ -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;

View File

@ -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,

View File

@ -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()
});
}

Binary file not shown.