From 6c58acf73ec87e9c8895332e5b5e8d0821edb98b Mon Sep 17 00:00:00 2001 From: Tao Zhu <82401714+taozhu-chicago@users.noreply.github.com> Date: Thu, 11 Aug 2022 20:59:17 -0500 Subject: [PATCH] add subcommand to set randomized compute-unit-price to transactions. (#26891) * add subcommand to set randomized compute-unit-price to transactions. * add compute-unit-limit to limit additional cost from prioritization. * increase funding if use_randomized_compute_unit_price is enabled. --- Cargo.lock | 1 + bench-tps/Cargo.toml | 1 + bench-tps/src/bench.rs | 94 +++++++++++++++++++++++++++++++++++++----- bench-tps/src/cli.rs | 12 ++++++ bench-tps/src/main.rs | 10 +++-- 5 files changed, 105 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 250918eb95..42a60f3168 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4721,6 +4721,7 @@ dependencies = [ "clap 2.33.3", "crossbeam-channel", "log", + "rand 0.7.3", "rayon", "serde_json", "serde_yaml", diff --git a/bench-tps/Cargo.toml b/bench-tps/Cargo.toml index ed340d8507..6810dc2384 100644 --- a/bench-tps/Cargo.toml +++ b/bench-tps/Cargo.toml @@ -12,6 +12,7 @@ publish = false clap = "2.33.1" crossbeam-channel = "0.5" log = "0.4.17" +rand = "0.7.0" rayon = "1.5.3" serde_json = "1.0.83" serde_yaml = "0.8.26" diff --git a/bench-tps/src/bench.rs b/bench-tps/src/bench.rs index 53a82c143d..df34e619a6 100644 --- a/bench-tps/src/bench.rs +++ b/bench-tps/src/bench.rs @@ -6,18 +6,20 @@ use { send_batch::*, }, log::*, + rand::distributions::{Distribution, Uniform}, rayon::prelude::*, solana_client::nonce_utils, solana_metrics::{self, datapoint_info}, solana_sdk::{ clock::{DEFAULT_MS_PER_SLOT, DEFAULT_S_PER_SLOT, MAX_PROCESSING_AGE}, + compute_budget::ComputeBudgetInstruction, hash::Hash, instruction::{AccountMeta, Instruction}, message::Message, native_token::Sol, pubkey::Pubkey, signature::{Keypair, Signer}, - system_transaction, + system_instruction, system_transaction, timing::{duration_as_ms, duration_as_s, duration_as_us, timestamp}, transaction::Transaction, }, @@ -36,6 +38,28 @@ use { // The point at which transactions become "too old", in seconds. const MAX_TX_QUEUE_AGE: u64 = (MAX_PROCESSING_AGE as f64 * DEFAULT_S_PER_SLOT) as u64; +// Add prioritization fee to transfer transactions, when `--use-randomized-compute-unit-price` +// is used, compute-unit-price is randomly generated in range of (0..MAX_COMPUTE_UNIT_PRICE). +// It also sets transaction's compute-unit to TRANSFER_TRANSACTION_COMPUTE_UNIT. Therefore the +// max additional cost is `TRANSFER_TRANSACTION_COMPUTE_UNIT * MAX_COMPUTE_UNIT_PRICE / 1_000_000` +const MAX_COMPUTE_UNIT_PRICE: u64 = 50; +const TRANSFER_TRANSACTION_COMPUTE_UNIT: u32 = 200; +/// calculate maximum possible prioritizatino fee, if `use-randomized-compute-unit-price` is +/// enabled, round to nearest lamports. +pub fn max_lamporots_for_prioritization(use_randomized_compute_unit_price: bool) -> u64 { + if use_randomized_compute_unit_price { + const MICRO_LAMPORTS_PER_LAMPORT: u64 = 1_000_000; + let micro_lamport_fee: u128 = (MAX_COMPUTE_UNIT_PRICE as u128) + .saturating_mul(TRANSFER_TRANSACTION_COMPUTE_UNIT as u128); + let fee = micro_lamport_fee + .saturating_add(MICRO_LAMPORTS_PER_LAMPORT.saturating_sub(1) as u128) + .saturating_div(MICRO_LAMPORTS_PER_LAMPORT as u128); + u64::try_from(fee).unwrap_or(u64::MAX) + } else { + 0u64 + } +} + pub type TimestampedTransaction = (Transaction, Option); pub type SharedTransactions = Arc>>>; @@ -68,6 +92,7 @@ struct TransactionChunkGenerator<'a, 'b, T: ?Sized> { nonce_chunks: Option>, chunk_index: usize, reclaim_lamports_back_to_source_account: bool, + use_randomized_compute_unit_price: bool, } impl<'a, 'b, T> TransactionChunkGenerator<'a, 'b, T> @@ -79,6 +104,7 @@ where gen_keypairs: &'a [Keypair], nonce_keypairs: Option<&'b Vec>, chunk_size: usize, + use_randomized_compute_unit_price: bool, ) -> Self { let account_chunks = KeypairChunks::new(gen_keypairs, chunk_size); let nonce_chunks = @@ -90,6 +116,7 @@ where nonce_chunks, chunk_index: 0, reclaim_lamports_back_to_source_account: false, + use_randomized_compute_unit_price, } } @@ -123,6 +150,7 @@ where dest_chunk, self.reclaim_lamports_back_to_source_account, blockhash.unwrap(), + self.use_randomized_compute_unit_price, ) }; @@ -302,6 +330,7 @@ where tx_count, sustained, target_slots_per_epoch, + use_randomized_compute_unit_price, .. } = config; @@ -311,6 +340,7 @@ where &gen_keypairs, None, // TODO(klykov): to be added in the follow up PR tx_count, + use_randomized_compute_unit_price, ); let first_tx_count = loop { @@ -423,6 +453,7 @@ fn generate_system_txs( dest: &VecDeque<&Keypair>, reclaim: bool, blockhash: &Hash, + use_randomized_compute_unit_price: bool, ) -> Vec { let pairs: Vec<_> = if !reclaim { source.iter().zip(dest.iter()).collect() @@ -430,15 +461,58 @@ fn generate_system_txs( dest.iter().zip(source.iter()).collect() }; - pairs - .par_iter() - .map(|(from, to)| { - ( - system_transaction::transfer(from, &to.pubkey(), 1, *blockhash), - Some(timestamp()), - ) - }) - .collect() + if use_randomized_compute_unit_price { + let mut rng = rand::thread_rng(); + let range = Uniform::from(0..MAX_COMPUTE_UNIT_PRICE); + let compute_unit_prices: Vec<_> = (0..pairs.len()) + .map(|_| range.sample(&mut rng) as u64) + .collect(); + let pairs_with_compute_unit_prices: Vec<_> = + pairs.iter().zip(compute_unit_prices.iter()).collect(); + + pairs_with_compute_unit_prices + .par_iter() + .map(|((from, to), compute_unit_price)| { + ( + transfer_with_compute_unit_price( + from, + &to.pubkey(), + 1, + *blockhash, + **compute_unit_price, + ), + Some(timestamp()), + ) + }) + .collect() + } else { + pairs + .par_iter() + .map(|(from, to)| { + ( + system_transaction::transfer(from, &to.pubkey(), 1, *blockhash), + Some(timestamp()), + ) + }) + .collect() + } +} + +fn transfer_with_compute_unit_price( + from_keypair: &Keypair, + to: &Pubkey, + lamports: u64, + recent_blockhash: Hash, + compute_unit_price: 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 message = Message::new(&instructions, Some(&from_pubkey)); + Transaction::new(&[from_keypair], message, recent_blockhash) } fn get_nonce_blockhash( diff --git a/bench-tps/src/cli.rs b/bench-tps/src/cli.rs index e9b4faa848..0fad3a37b3 100644 --- a/bench-tps/src/cli.rs +++ b/bench-tps/src/cli.rs @@ -54,6 +54,7 @@ pub struct Config { pub external_client_type: ExternalClientType, pub use_quic: bool, pub tpu_connection_pool_size: usize, + pub use_randomized_compute_unit_price: bool, } impl Default for Config { @@ -81,6 +82,7 @@ impl Default for Config { external_client_type: ExternalClientType::default(), use_quic: DEFAULT_TPU_USE_QUIC, tpu_connection_pool_size: DEFAULT_TPU_CONNECTION_POOL_SIZE, + use_randomized_compute_unit_price: false, } } } @@ -303,6 +305,12 @@ pub fn build_args<'a, 'b>(version: &'b str) -> App<'a, 'b> { .help("Controls the connection pool size per remote address; only affects ThinClient (default) \ or TpuClient sends"), ) + .arg( + Arg::with_name("use_randomized_compute_unit_price") + .long("use-randomized-compute-unit-price") + .takes_value(false) + .help("Sets random compute-unit-price in range [0..100] to transfer transactions"), + ) } /// Parses a clap `ArgMatches` structure into a `Config` @@ -433,5 +441,9 @@ pub fn extract_args(matches: &ArgMatches) -> Config { .expect("can't parse target slots per epoch"); } + if matches.is_present("use_randomized_compute_unit_price") { + args.use_randomized_compute_unit_price = true; + } + args } diff --git a/bench-tps/src/main.rs b/bench-tps/src/main.rs index 8d8fde7fda..2894731a52 100644 --- a/bench-tps/src/main.rs +++ b/bench-tps/src/main.rs @@ -4,7 +4,7 @@ use { clap::value_t, log::*, solana_bench_tps::{ - bench::do_bench_tps, + bench::{do_bench_tps, max_lamporots_for_prioritization}, bench_tps_client::BenchTpsClient, cli::{self, ExternalClientType}, keypairs::get_keypairs, @@ -153,6 +153,7 @@ fn main() { external_client_type, use_quic, tpu_connection_pool_size, + use_randomized_compute_unit_price, .. } = &cli_config; @@ -161,8 +162,11 @@ fn main() { info!("Generating {} keypairs", keypair_count); let (keypairs, _) = generate_keypairs(id, keypair_count as u64); let num_accounts = keypairs.len() as u64; - let max_fee = - FeeRateGovernor::new(*target_lamports_per_signature, 0).max_lamports_per_signature; + let max_fee = FeeRateGovernor::new(*target_lamports_per_signature, 0) + .max_lamports_per_signature + .saturating_add(max_lamporots_for_prioritization( + *use_randomized_compute_unit_price, + )); let num_lamports_per_account = (num_accounts - 1 + NUM_SIGNATURES_FOR_TXS * max_fee) / num_accounts + num_lamports_per_account;