From 9d7e999c1d9e3eb748909efc02888f892f1dbdeb Mon Sep 17 00:00:00 2001 From: Jon Cinque Date: Wed, 26 Oct 2022 11:16:57 -0400 Subject: [PATCH] bench-tps: Transition to using spl-instruction-padding (#28496) * bench-tps: Transition to using spl-instruction-padding * Use macros for conversions --- Cargo.lock | 72 +++++++++------- bench-tps/Cargo.toml | 1 + bench-tps/src/bench.rs | 33 ++++---- bench-tps/src/cli.rs | 9 +- .../src/inline_instruction_padding_program.rs | 78 ------------------ bench-tps/src/lib.rs | 2 +- bench-tps/src/spl_convert.rs | 62 ++++++++++++++ bench-tps/tests/bench_tps.rs | 12 +-- .../tests/fixtures/spl_instruction_padding.so | Bin 47184 -> 26432 bytes 9 files changed, 138 insertions(+), 131 deletions(-) delete mode 100644 bench-tps/src/inline_instruction_padding_program.rs create mode 100644 bench-tps/src/spl_convert.rs diff --git a/Cargo.lock b/Cargo.lock index 3d1df0532..1a70c087b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4716,6 +4716,7 @@ dependencies = [ "solana-thin-client", "solana-tpu-client", "solana-version", + "spl-instruction-padding", "thiserror", ] @@ -5184,9 +5185,9 @@ dependencies = [ [[package]] name = "solana-frozen-abi" -version = "1.11.5" +version = "1.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28e4e35bc58c465f161bde764ebce41fdfcb503583cf3a77e0211274cc12b22d" +checksum = "3f07a997db3dac7b9da06b007d4a8df6dbd8281182e6ebbbd8a56f935f540b0a" dependencies = [ "ahash", "blake3", @@ -5211,7 +5212,7 @@ dependencies = [ "serde_derive", "serde_json", "sha2 0.10.5", - "solana-frozen-abi-macro 1.11.5", + "solana-frozen-abi-macro 1.14.5", "subtle", "thiserror", ] @@ -5251,9 +5252,9 @@ dependencies = [ [[package]] name = "solana-frozen-abi-macro" -version = "1.11.5" +version = "1.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708f837d748e574b1e53b250ab1f4a69ba330bbc10d041d02381165f0f36291a" +checksum = "dcd7d529da0fa5b3b5ca71645122fc94c2aaf867744497969c109e1d4b8ad02d" dependencies = [ "proc-macro2 1.0.41", "quote 1.0.18", @@ -5577,9 +5578,9 @@ dependencies = [ [[package]] name = "solana-logger" -version = "1.11.5" +version = "1.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ea6fc68d63d33d862d919d4c8ad7f613ec243ccf6762d595c660020b289b57" +checksum = "c690a6ae623bdd2d71229880a9f668ff714b5c6a9bc180a1abef4887da8b6f27" dependencies = [ "env_logger", "lazy_static", @@ -5753,9 +5754,9 @@ dependencies = [ [[package]] name = "solana-program" -version = "1.11.5" +version = "1.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdd314d85b171bb20ccdcaf07346a9d52a012b10d84f4706f0628813d002fef8" +checksum = "f835be7a20e172209538241cdf46451c08b38eaaca65cf16e65658700c447b17" dependencies = [ "base64 0.13.0", "bincode", @@ -5791,9 +5792,9 @@ dependencies = [ "serde_json", "sha2 0.10.5", "sha3 0.10.4", - "solana-frozen-abi 1.11.5", - "solana-frozen-abi-macro 1.11.5", - "solana-sdk-macro 1.11.5", + "solana-frozen-abi 1.14.5", + "solana-frozen-abi-macro 1.14.5", + "solana-sdk-macro 1.14.5", "thiserror", "tiny-bip39", "wasm-bindgen", @@ -6179,9 +6180,9 @@ dependencies = [ [[package]] name = "solana-sdk" -version = "1.11.5" +version = "1.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad7d954df63b267857e26670e3aacfd8e2943ca703653b0418e5afc85046c2f3" +checksum = "f74e8d699c3a441a5b0cd94c718e75b25c1a4295c2180a714b12fb1bcf66a51e" dependencies = [ "assert_matches", "base64 0.13.0", @@ -6218,11 +6219,11 @@ dependencies = [ "serde_json", "sha2 0.10.5", "sha3 0.10.4", - "solana-frozen-abi 1.11.5", - "solana-frozen-abi-macro 1.11.5", - "solana-logger 1.11.5", - "solana-program 1.11.5", - "solana-sdk-macro 1.11.5", + "solana-frozen-abi 1.14.5", + "solana-frozen-abi-macro 1.14.5", + "solana-logger 1.14.5", + "solana-program 1.14.5", + "solana-sdk-macro 1.14.5", "thiserror", "uriparse", "wasm-bindgen", @@ -6285,9 +6286,9 @@ dependencies = [ [[package]] name = "solana-sdk-macro" -version = "1.11.5" +version = "1.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d9e81bc46edcc517b2df504856d57a5101c7586ec63f3143ae11fbe2eba613" +checksum = "92ffde9b5b7313629780baca10eaffec7421d53be725c76031ca409a5298705c" dependencies = [ "bs58", "proc-macro2 1.0.41", @@ -6759,9 +6760,9 @@ dependencies = [ [[package]] name = "solana-zk-token-sdk" -version = "1.11.5" +version = "1.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62415c05a9ebfffaf8befaa61b24492ebf88269cf84cbeba714bac4125ec4ea3" +checksum = "0a23a2c443027e8cc2981131a38928cb37e554970c497b5735e888049cc85d3f" dependencies = [ "aes-gcm-siv", "arrayref", @@ -6772,6 +6773,7 @@ dependencies = [ "cipher 0.4.3", "curve25519-dalek", "getrandom 0.1.16", + "itertools", "lazy_static", "merlin", "num-derive", @@ -6780,8 +6782,8 @@ dependencies = [ "serde", "serde_json", "sha3 0.9.1", - "solana-program 1.11.5", - "solana-sdk 1.11.5", + "solana-program 1.14.5", + "solana-sdk 1.14.5", "subtle", "thiserror", "zeroize", @@ -6866,19 +6868,29 @@ dependencies = [ "borsh", "num-derive", "num-traits", - "solana-program 1.11.5", + "solana-program 1.14.5", "spl-token", "spl-token-2022", "thiserror", ] +[[package]] +name = "spl-instruction-padding" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c5557ec281a34f7f9053feb6e0d795162ba0c6a52898b21c3d1e899481191d5" +dependencies = [ + "num_enum", + "solana-program 1.14.5", +] + [[package]] name = "spl-memo" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0dc6f70db6bacea7ff25870b016a65ba1d1b6013536f08e4fd79a8f9005325" dependencies = [ - "solana-program 1.11.5", + "solana-program 1.14.5", ] [[package]] @@ -6892,7 +6904,7 @@ dependencies = [ "num-derive", "num-traits", "num_enum", - "solana-program 1.11.5", + "solana-program 1.14.5", "thiserror", ] @@ -6907,8 +6919,8 @@ dependencies = [ "num-derive", "num-traits", "num_enum", - "solana-program 1.11.5", - "solana-zk-token-sdk 1.11.5", + "solana-program 1.14.5", + "solana-zk-token-sdk 1.14.5", "spl-memo", "spl-token", "thiserror", diff --git a/bench-tps/Cargo.toml b/bench-tps/Cargo.toml index e7bf4c53b..afe2d91d6 100644 --- a/bench-tps/Cargo.toml +++ b/bench-tps/Cargo.toml @@ -37,6 +37,7 @@ solana-streamer = { path = "../streamer", version = "=1.15.0" } solana-thin-client = { path = "../thin-client", version = "=1.15.0" } solana-tpu-client = { path = "../tpu-client", version = "=1.15.0", default-features = false } solana-version = { path = "../version", version = "=1.15.0" } +spl-instruction-padding = "0.1" thiserror = "1.0" [dev-dependencies] diff --git a/bench-tps/src/bench.rs b/bench-tps/src/bench.rs index 3d5a25a71..02cd1ddb4 100644 --- a/bench-tps/src/bench.rs +++ b/bench-tps/src/bench.rs @@ -1,10 +1,10 @@ use { crate::{ bench_tps_client::*, - cli::Config, - inline_instruction_padding_program::{create_padded_instruction, InstructionPaddingConfig}, + cli::{Config, InstructionPaddingConfig}, perf_utils::{sample_txs, SampleStats}, send_batch::*, + spl_convert::FromOtherSolana, }, log::*, rand::distributions::{Distribution, Uniform}, @@ -25,6 +25,7 @@ use { timing::{duration_as_ms, duration_as_s, duration_as_us, timestamp}, transaction::Transaction, }, + spl_instruction_padding::instruction::wrap_instruction, std::{ collections::{HashSet, VecDeque}, process::exit, @@ -552,13 +553,15 @@ fn transfer_with_compute_unit_price_and_padding( 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, + FromOtherSolana::from( + wrap_instruction( + FromOtherSolana::from(instruction_padding_config.program_id), + FromOtherSolana::from(transfer_instruction), + vec![], + instruction_padding_config.data_size, + ) + .expect("Could not create padded instruction"), ) - .expect("Could not create padded instruction") } else { transfer_instruction }; @@ -645,13 +648,15 @@ fn nonced_transfer_with_padding( 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, + FromOtherSolana::from( + wrap_instruction( + FromOtherSolana::from(instruction_padding_config.program_id), + FromOtherSolana::from(transfer_instruction), + vec![], + instruction_padding_config.data_size, + ) + .expect("Could not create padded instruction"), ) - .expect("Could not create padded instruction") } else { transfer_instruction }; diff --git a/bench-tps/src/cli.rs b/bench-tps/src/cli.rs index 4518711f7..8f30a6f52 100644 --- a/bench-tps/src/cli.rs +++ b/bench-tps/src/cli.rs @@ -1,5 +1,5 @@ use { - crate::inline_instruction_padding_program::{self, InstructionPaddingConfig}, + crate::spl_convert::FromOtherSolana, 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}, @@ -31,6 +31,11 @@ impl Default for ExternalClientType { } } +pub struct InstructionPaddingConfig { + pub program_id: Pubkey, + pub data_size: u32, +} + /// Holds the configuration for a single run of the benchmark pub struct Config { pub entrypoint_addr: SocketAddr, @@ -477,7 +482,7 @@ pub fn extract_args(matches: &ArgMatches) -> Config { let program_id = matches .value_of("instruction_padding_program_id") .map(|target_str| target_str.parse().unwrap()) - .unwrap_or(inline_instruction_padding_program::ID); + .unwrap_or_else(|| FromOtherSolana::from(spl_instruction_padding::ID)); args.instruction_padding_config = Some(InstructionPaddingConfig { program_id, data_size: data_size diff --git a/bench-tps/src/inline_instruction_padding_program.rs b/bench-tps/src/inline_instruction_padding_program.rs deleted file mode 100644 index bf7f75929..000000000 --- a/bench-tps/src/inline_instruction_padding_program.rs +++ /dev/null @@ -1,78 +0,0 @@ -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 ac0f9aba5..3003e149d 100644 --- a/bench-tps/src/lib.rs +++ b/bench-tps/src/lib.rs @@ -2,7 +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; +pub mod spl_convert; diff --git a/bench-tps/src/spl_convert.rs b/bench-tps/src/spl_convert.rs new file mode 100644 index 000000000..ec5195b6d --- /dev/null +++ b/bench-tps/src/spl_convert.rs @@ -0,0 +1,62 @@ +use { + solana_sdk::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + }, + spl_instruction_padding::solana_program::{ + instruction::{AccountMeta as SplAccountMeta, Instruction as SplInstruction}, + pubkey::Pubkey as SplPubkey, + }, +}; + +pub trait FromOtherSolana { + fn from(_: T) -> Self; +} + +macro_rules! impl_pubkey_conversion { + ($S:ty, $L:ty) => { + impl FromOtherSolana<$S> for $L { + fn from(pubkey: $S) -> Self { + Self::new_from_array(pubkey.to_bytes()) + } + } + }; +} +impl_pubkey_conversion!(SplPubkey, Pubkey); +impl_pubkey_conversion!(Pubkey, SplPubkey); + +macro_rules! impl_account_meta_conversion { + ($S:ty, $L:ty) => { + impl FromOtherSolana<$S> for $L { + fn from(meta: $S) -> Self { + Self { + pubkey: FromOtherSolana::from(meta.pubkey), + is_signer: meta.is_signer, + is_writable: meta.is_writable, + } + } + } + }; +} +impl_account_meta_conversion!(SplAccountMeta, AccountMeta); +impl_account_meta_conversion!(AccountMeta, SplAccountMeta); + +macro_rules! impl_instruction_conversion { + ($S: ty, $L:ty) => { + impl FromOtherSolana<$S> for $L { + fn from(instruction: $S) -> Self { + Self { + program_id: FromOtherSolana::from(instruction.program_id), + accounts: instruction + .accounts + .into_iter() + .map(|meta| FromOtherSolana::from(meta)) + .collect(), + data: instruction.data, + } + } + } + }; +} +impl_instruction_conversion!(SplInstruction, Instruction); +impl_instruction_conversion!(Instruction, SplInstruction); diff --git a/bench-tps/tests/bench_tps.rs b/bench-tps/tests/bench_tps.rs index a16fbc364..f66c6137a 100644 --- a/bench-tps/tests/bench_tps.rs +++ b/bench-tps/tests/bench_tps.rs @@ -4,9 +4,9 @@ use { serial_test::serial, solana_bench_tps::{ bench::{do_bench_tps, generate_and_fund_keypairs}, - cli::Config, - inline_instruction_padding_program::{self, InstructionPaddingConfig}, + cli::{Config, InstructionPaddingConfig}, send_batch::generate_durable_nonce_accounts, + spl_convert::FromOtherSolana, }, solana_core::validator::ValidatorConfig, solana_faucet::faucet::run_local_faucet, @@ -46,7 +46,7 @@ fn program_account(program_data: &[u8]) -> AccountSharedData { fn test_bench_tps_local_cluster(config: Config) { let native_instruction_processors = vec![]; let additional_accounts = vec![( - inline_instruction_padding_program::id(), + FromOtherSolana::from(spl_instruction_padding::ID), program_account(include_bytes!("fixtures/spl_instruction_padding.so")), )]; @@ -121,7 +121,7 @@ fn test_bench_tps_test_validator(config: Config) { .faucet_addr(Some(faucet_addr)) .add_program( "spl_instruction_padding", - inline_instruction_padding_program::id(), + FromOtherSolana::from(spl_instruction_padding::ID), ) .start_with_mint_address(mint_pubkey, SocketAddrSpace::Unspecified) .expect("validator start failed"); @@ -203,7 +203,7 @@ fn test_bench_tps_local_cluster_with_padding() { tx_count: 100, duration: Duration::from_secs(10), instruction_padding_config: Some(InstructionPaddingConfig { - program_id: inline_instruction_padding_program::id(), + program_id: FromOtherSolana::from(spl_instruction_padding::ID), data_size: 0, }), ..Config::default() @@ -217,7 +217,7 @@ fn test_bench_tps_tpu_client_with_padding() { tx_count: 100, duration: Duration::from_secs(10), instruction_padding_config: Some(InstructionPaddingConfig { - program_id: inline_instruction_padding_program::id(), + program_id: FromOtherSolana::from(spl_instruction_padding::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 index 89c9fca5540de01d1d3eb6958899db37497b11bd..8f7dc6406b41f7abbb87ba44832afa83e973ab8c 100755 GIT binary patch delta 1357 zcmZvcOH30{6o&8V1E`>gj}#wR0+JSiDR0U`ipGSx0!kWMs>0AV&}bi-mIxak3u0Gl zbJA#HOkiQeg~4RfLQF7{V4@2REC{$T)+IYx7+FB@lunyr;z{P7`Tp~tnVY$H?&@#V z$8Ye|q-Q>sk8WmR8rIEptSD}E>0+8}B^IC6msQ@#LO#d87|X)$teDRtfEpksYE`{z zSQH)FL4XkS!`9esGZ72U~U{Y)-A3H)RQB?@ABfhL&(7+rzOjfXA!ej)e zxMb>3!4Cep)B)?*P^T|54drLb%C(GzMMs@MUGlzUUC0z_s@uKzfNKR0Cb=GXhkM)@ z_4~}PahDsXcT6;v0=;9pX%H&$N7GM;;ZpNFY+|5APs()5Gbk4;S|6QKS={)c`2w!k zoHUP$>vs!a0u#L*G=A;fpz&4TFpb*&C)Adp@gCOL2QY1~*L>gG-^Z~18WdyFZcw%D zipK`_)VKY?NK}~cME#*)Lzwq^{lPJFSO|>?e83zLJZ4#)6CyJDubVnOzDOh_kT=uq z3r2;haL6Bwk{|{L&#F2(=~H!4&fRwoGFC6?7bzR0d%50@%dT@redH{Ys<&{<<*r?k zDVf3?@p;LiRWU}(aS%lq7}8&9bTU>cDVfwqd_u;R#PS#=2AMF5pN7nQNTG&_)iRgz zDG{pERPvFjAI}qGmtBli$#@tY?mDpGL${UIaoKI4&H2k+s~1UWIO4OB&z>ZYRyU+%msw< literal 47184 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