From 27c89c7d7b211c890efd9c85abef4d5dd5142389 Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Tue, 11 Jun 2024 17:32:58 -0700 Subject: [PATCH] Add optional support for the proprietary (vomit) helius RPC priority fee RPC endpoint --- src/bin/sys-lend.rs | 12 ++++- src/helius_rpc.rs | 8 +-- src/lib.rs | 16 +++++- src/main.rs | 22 ++++++--- src/priority_fee.rs | 115 +++++++++++++++++++++++++++++--------------- 5 files changed, 121 insertions(+), 52 deletions(-) diff --git a/src/bin/sys-lend.rs b/src/bin/sys-lend.rs index 1a335ce..54f37ce 100644 --- a/src/bin/sys-lend.rs +++ b/src/bin/sys-lend.rs @@ -349,6 +349,15 @@ async fn main() -> Result<(), Box> { submit transactions with in addition to --url"), ) + .arg( + Arg::with_name("helius_json_rpc_url") + .long("helius-url") + .value_name("URL") + .takes_value(true) + .global(true) + .validator(is_url) + .help("Helium JSON RPC URL to use only for the proprietary getPriorityFeeEstimate RPC method"), + ) .arg( Arg::with_name("priority_fee_exact") .long("priority-fee-exact") @@ -650,6 +659,7 @@ async fn main() -> Result<(), Box> { let rpc_clients = RpcClients::new( value_t_or_exit!(app_matches, "json_rpc_url", String), value_t!(app_matches, "send_json_rpc_urls", String).ok(), + value_t!(app_matches, "helius_json_rpc_url", String).ok(), ); let rpc_client = rpc_clients.default(); let mut account_data_cache = AccountDataCache::new(rpc_client); @@ -1454,7 +1464,7 @@ async fn main() -> Result<(), Box> { let mut instructions = instructions; let priority_fee = apply_priority_fee( - rpc_client, + &rpc_clients, &mut instructions, required_compute_units, priority_fee, diff --git a/src/helius_rpc.rs b/src/helius_rpc.rs index 12ff2c3..273bf02 100644 --- a/src/helius_rpc.rs +++ b/src/helius_rpc.rs @@ -1,7 +1,4 @@ -use { - solana_client::rpc_client::RpcClient, - solana_sdk::{instruction::Instruction}, -}; +use {solana_client::rpc_client::RpcClient, solana_sdk::instruction::Instruction}; #[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)] #[serde(rename_all = "camelCase")] @@ -53,7 +50,6 @@ pub fn get_priority_fee_estimate_for_instructions( priority_level: HeliusPriorityLevel, instructions: &[Instruction], ) -> Result { - let mut account_keys: Vec<_> = instructions .iter() .flat_map(|instruction| { @@ -85,7 +81,7 @@ pub fn get_priority_fee_estimate_for_instructions( request, ) .map(|response| { - response + response .priority_fee_estimate .expect("priority_fee_estimate") as u64 }) diff --git a/src/lib.rs b/src/lib.rs index 7bf276f..16ccaac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,10 +48,15 @@ where pub struct RpcClients { clients: Vec<(String, RpcClient)>, + helius: Option, } impl RpcClients { - pub fn new(json_rpc_url: String, send_json_rpc_urls: Option) -> Self { + pub fn new( + json_rpc_url: String, + send_json_rpc_urls: Option, + helius: Option, + ) -> Self { let mut json_rpc_urls = vec![json_rpc_url]; if let Some(send_json_rpc_urls) = send_json_rpc_urls { for send_json_rpc_url in send_json_rpc_urls.split(',') { @@ -70,12 +75,21 @@ impl RpcClients { ) }) .collect(), + helius: helius.map(|helius_json_rpc_url| { + RpcClient::new_with_commitment(helius_json_rpc_url, CommitmentConfig::confirmed()) + }), } } pub fn default(&self) -> &RpcClient { &self.clients[0].1 } + + pub fn helius_or_default(&self) -> &RpcClient { + self.helius + .as_ref() + .map_or_else(|| self.default(), |helius| helius) + } } // Assumes `transaction` has already been signed and simulated... diff --git a/src/main.rs b/src/main.rs index 359db91..7847f8d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -614,7 +614,7 @@ async fn process_exchange_deposit( (instructions, amount, compute_units) } }; - apply_priority_fee(rpc_client, &mut instructions, compute_units, priority_fee)?; + apply_priority_fee(rpc_clients, &mut instructions, compute_units, priority_fee)?; if amount == 0 { return Err("Nothing to deposit".into()); @@ -3027,7 +3027,7 @@ async fn process_account_merge( ) .into()); }; - apply_priority_fee(rpc_client, &mut instructions, 10_000, priority_fee)?; + apply_priority_fee(rpc_clients, &mut instructions, 10_000, priority_fee)?; println!("Merging {from_address} into {into_address}"); if from_address != authority_address { @@ -3363,7 +3363,7 @@ async fn process_account_sweep( let (signature, maybe_transaction) = match existing_signature { None => { - apply_priority_fee(rpc_client, &mut instructions, 7_000, priority_fee)?; + apply_priority_fee(rpc_clients, &mut instructions, 7_000, priority_fee)?; let mut message = Message::new(&instructions, Some(&from_authority_address)); message.recent_blockhash = recent_blockhash; @@ -3494,7 +3494,7 @@ async fn process_account_split( .get_minimum_balance_for_rent_exemption(solana_sdk::stake::state::StakeStateV2::size_of())?; let mut instructions = vec![]; - apply_priority_fee(rpc_client, &mut instructions, 10_000, priority_fee)?; + apply_priority_fee(rpc_clients, &mut instructions, 10_000, priority_fee)?; instructions.push(system_instruction::transfer( &authority_address, @@ -3980,7 +3980,7 @@ async fn process_account_wrap( spl_token::instruction::sync_native(&spl_token::id(), &wsol_address).unwrap(), ]); - apply_priority_fee(rpc_client, &mut instructions, 30_000, priority_fee)?; + apply_priority_fee(rpc_clients, &mut instructions, 30_000, priority_fee)?; let message = Message::new(&instructions, Some(&authority_address)); let mut transaction = Transaction::new_unsigned(message); @@ -4079,7 +4079,7 @@ async fn process_account_unwrap( ) .unwrap(), ]; - apply_priority_fee(rpc_client, &mut instructions, 30_000, priority_fee)?; + apply_priority_fee(rpc_clients, &mut instructions, 30_000, priority_fee)?; let message = Message::new(&instructions, Some(&authority_address)); @@ -4408,6 +4408,15 @@ async fn main() -> Result<(), Box> { .help("Optional additional JSON RPC URLs, separated by commas, to \ submit transactions with in addition to --url"), ) + .arg( + Arg::with_name("helius_json_rpc_url") + .long("helius-url") + .value_name("URL") + .takes_value(true) + .global(true) + .validator(is_url) + .help("Helium JSON RPC URL to use only for the proprietary getPriorityFeeEstimate RPC method"), + ) .arg( Arg::with_name("verbose") .short("v") @@ -5974,6 +5983,7 @@ async fn main() -> Result<(), Box> { let rpc_clients = RpcClients::new( value_t_or_exit!(app_matches, "json_rpc_url", String), value_t!(app_matches, "send_json_rpc_urls", String).ok(), + value_t!(app_matches, "helius_json_rpc_url", String).ok(), ); let rpc_client = rpc_clients.default(); diff --git a/src/priority_fee.rs b/src/priority_fee.rs index 5ca4b65..852603b 100644 --- a/src/priority_fee.rs +++ b/src/priority_fee.rs @@ -1,4 +1,5 @@ use { + crate::{helius_rpc, RpcClients}, solana_client::rpc_client::RpcClient, solana_sdk::{ compute_budget, @@ -99,7 +100,7 @@ fn get_recent_priority_fees_for_instructions( } pub fn apply_priority_fee( - rpc_client: &RpcClient, + rpc_clients: &RpcClients, instructions: &mut Vec, compute_unit_limit: u32, priority_fee: PriorityFee, @@ -110,48 +111,86 @@ pub fn apply_priority_fee( max_lamports, fee_percentile, } => { - let recent_compute_unit_prices = - get_recent_priority_fees_for_instructions(rpc_client, instructions)? - .into_iter() - // .skip_while(|fee| *fee == 0) // Skip 0 fee payers - .map(|f| f as f64) - .collect::>(); - - let ui_fee_for = |compute_unit_price_micro_lamports: f64| { - Sol(ComputeBudget { - compute_unit_price_micro_lamports: compute_unit_price_micro_lamports as u64, + let helius_compute_budget = if let Ok(helius_priority_fee_estimate) = + helius_rpc::get_priority_fee_estimate_for_instructions( + rpc_clients.helius_or_default(), + helius_rpc::HeliusPriorityLevel::High, + instructions, + ) { + let helius_compute_budget = ComputeBudget { + compute_unit_price_micro_lamports: helius_priority_fee_estimate, compute_unit_limit, - } - .priority_fee_lamports()) + }; + + println!( + "Helius priority fee (high): {}", + Sol(helius_compute_budget.priority_fee_lamports()) + ); + + helius_compute_budget + } else { + ComputeBudget::default() }; - let dist = - criterion_stats::Distribution::from(recent_compute_unit_prices.into_boxed_slice()); - print!("Recent priority fees: mean={}", ui_fee_for(dist.mean())); - let percentiles = dist.percentiles(); - for i in [50., 75., 85., 90., 95., 100.] { - print!(", {i}th={}", ui_fee_for(percentiles.at(i))); - } + let sys_compute_budget = { + let recent_compute_unit_prices = + get_recent_priority_fees_for_instructions(rpc_clients.default(), instructions)? + .into_iter() + // .skip_while(|fee| *fee == 0) // Skip 0 fee payers + .map(|f| f as f64) + .collect::>(); + + let ui_fee_for = |compute_unit_price_micro_lamports: f64| { + Sol(ComputeBudget { + compute_unit_price_micro_lamports: compute_unit_price_micro_lamports as u64, + compute_unit_limit, + } + .priority_fee_lamports()) + }; - let fee_percentile_compute_unit_price_micro_lamports = - percentiles.at(fee_percentile as f64) as u64; - let mean_compute_unit_price_micro_lamports = dist.mean() as u64; - - // Use the greater of the `fee_percentile`th percentile fee or the mean fee - let compute_unit_price_micro_lamports = - if fee_percentile_compute_unit_price_micro_lamports - > mean_compute_unit_price_micro_lamports - { - println!("\nusing {fee_percentile}th percentile fee"); - fee_percentile_compute_unit_price_micro_lamports - } else { - println!("\nusing mean fee"); - mean_compute_unit_price_micro_lamports + let dist = criterion_stats::Distribution::from( + recent_compute_unit_prices.into_boxed_slice(), + ); + let mut verbose_msg = format!("mean={}", ui_fee_for(dist.mean())); + let percentiles = dist.percentiles(); + for i in [50., 75., 90., 95., 100.] { + verbose_msg += &format!(", {i}th={}", ui_fee_for(percentiles.at(i))); + } + + let fee_percentile_compute_unit_price_micro_lamports = + percentiles.at(fee_percentile as f64) as u64; + let mean_compute_unit_price_micro_lamports = dist.mean() as u64; + + // Use the greater of the `fee_percentile`th percentile fee or the mean fee + let compute_unit_price_micro_lamports = + if fee_percentile_compute_unit_price_micro_lamports + > mean_compute_unit_price_micro_lamports + { + verbose_msg += &format!(". Selected {fee_percentile}th percentile"); + fee_percentile_compute_unit_price_micro_lamports + } else { + verbose_msg += ". Selected mean)"; + mean_compute_unit_price_micro_lamports + }; + + let sys_compute_budget = ComputeBudget { + compute_unit_price_micro_lamports, + compute_unit_limit, }; - let compute_budget = ComputeBudget { - compute_unit_price_micro_lamports, - compute_unit_limit, + println!( + "Observed priority fee: {}\n ({verbose_msg})", + Sol(sys_compute_budget.priority_fee_lamports()) + ); + sys_compute_budget + }; + + let compute_budget = if sys_compute_budget.compute_unit_price_micro_lamports + > helius_compute_budget.compute_unit_price_micro_lamports + { + sys_compute_budget + } else { + helius_compute_budget }; if compute_budget.priority_fee_lamports() > max_lamports { @@ -167,7 +206,7 @@ pub fn apply_priority_fee( }; println!( - "Priority fee: {}", + "Selected priority fee: {}", Sol(compute_budget.priority_fee_lamports()) ); assert!(