From 740ac37ea1ef03ceed5bd46d72b4aee183f15dd0 Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Tue, 4 Jun 2024 10:08:19 -0700 Subject: [PATCH] Improve priority fee selection --- Cargo.lock | 72 ++++++++++++++++++++++++++++ Cargo.toml | 1 + src/bin/sys-lend.rs | 36 ++++++++++---- src/main.rs | 4 +- src/priority_fee.rs | 113 ++++++++++++++++++++++++++------------------ 5 files changed, 167 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 06f5949..80025f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -782,6 +782,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cast" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a" +dependencies = [ + "rustc_version", +] + [[package]] name = "cc" version = "1.0.90" @@ -1041,6 +1050,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion-stats" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387df94cb74ada1b33e10ce034bb0d9360cc73edb5063e7d7d4120a40ee1c9d2" +dependencies = [ + "cast", + "num-traits", + "num_cpus", + "rand 0.4.6", + "thread-scoped", +] + [[package]] name = "crossbeam-channel" version = "0.5.12" @@ -1632,6 +1654,12 @@ dependencies = [ "tokio-tungstenite 0.14.0", ] +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "futures" version = "0.3.30" @@ -3100,6 +3128,19 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + [[package]] name = "rand" version = "0.7.3" @@ -3144,6 +3185,21 @@ dependencies = [ "rand_core 0.6.3", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.5.1" @@ -3212,6 +3268,15 @@ dependencies = [ "yasna", ] +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "redox_syscall" version = "0.2.11" @@ -5000,6 +5065,7 @@ dependencies = [ "clap 2.33.3", "coinbase-rs", "console 0.14.1", + "criterion-stats", "fd-lock", "fixed", "fixed-macro", @@ -5132,6 +5198,12 @@ dependencies = [ "syn 2.0.52", ] +[[package]] +name = "thread-scoped" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcbb6aa301e5d3b0b5ef639c9a9c7e2f1c944f177b460c04dc24c69b1fa2bd99" + [[package]] name = "time" version = "0.1.44" diff --git a/Cargo.toml b/Cargo.toml index 9937666..e91167d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,4 +70,5 @@ thiserror = "1.0" tokio = { version = "1", features = ["macros", "time"] } #tulipv2-sdk-common = "0.9.5" uint = "0.9.5" +criterion-stats = "0.3.0" diff --git a/src/bin/sys-lend.rs b/src/bin/sys-lend.rs index 00e4a0b..ff1a352 100644 --- a/src/bin/sys-lend.rs +++ b/src/bin/sys-lend.rs @@ -195,6 +195,15 @@ async fn main() -> Result<(), Box> { .default_value(default_json_rpc_url) .help("JSON RPC URL for the cluster"), ) + .arg( + Arg::with_name("send_json_rpc_url") + .long("send-url") + .value_name("URL") + .takes_value(true) + .validator(is_url_or_moniker) + .help("Optional addition JSON RPC URL for the cluster to be used only \ + for submitting transactions [default: same as --url]"), + ) .arg( Arg::with_name("priority_fee_exact") .long("priority-fee-exact") @@ -439,19 +448,24 @@ async fn main() -> Result<(), Box> { ); let app_matches = app.get_matches(); - let rpc_client = RpcClient::new_with_commitment( - normalize_to_url_if_moniker(value_t_or_exit!(app_matches, "json_rpc_url", String)), - CommitmentConfig::confirmed(), - ); + + let json_rpc_url = + normalize_to_url_if_moniker(value_t_or_exit!(app_matches, "json_rpc_url", String)); + let rpc_client = RpcClient::new_with_commitment(&json_rpc_url, CommitmentConfig::confirmed()); + let send_json_rpc_url = match value_t!(app_matches, "send_json_rpc_url", String).ok() { + Some(send_json_rpc_url) => normalize_to_url_if_moniker(send_json_rpc_url), + None => json_rpc_url, + }; + let send_rpc_client = + RpcClient::new_with_commitment(send_json_rpc_url, CommitmentConfig::confirmed()); + let priority_fee = if let Ok(ui_priority_fee) = value_t!(app_matches, "priority_fee_exact", f64) { PriorityFee::Exact { lamports: sol_to_lamports(ui_priority_fee), } } else if let Ok(ui_priority_fee) = value_t!(app_matches, "priority_fee_auto", f64) { - PriorityFee::Auto { - max_lamports: sol_to_lamports(ui_priority_fee), - } + PriorityFee::default_auto_percentile(sol_to_lamports(ui_priority_fee)) } else { PriorityFee::default_auto() }; @@ -988,7 +1002,7 @@ async fn main() -> Result<(), Box> { &vec![signer], )? }; - let simulation_result = rpc_client.simulate_transaction(&transaction)?.value; + let simulation_result = send_rpc_client.simulate_transaction(&transaction)?.value; if simulation_result.err.is_some() { return Err(format!("Simulation failure: {simulation_result:?}").into()); } @@ -1026,7 +1040,11 @@ async fn main() -> Result<(), Box> { ), }; - if !send_transaction_until_expired(&rpc_client, &transaction, last_valid_block_height) { + if !send_transaction_until_expired( + &send_rpc_client, + &transaction, + last_valid_block_height, + ) { let msg = format!("Transaction failed: {signature}"); notifier.send(&msg).await; return Err(msg.into()); diff --git a/src/main.rs b/src/main.rs index ec255d7..91b50e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5926,9 +5926,7 @@ async fn main() -> Result<(), Box> { lamports: sol_to_lamports(ui_priority_fee), } } else if let Ok(ui_priority_fee) = value_t!(app_matches, "priority_fee_auto", f64) { - PriorityFee::Auto { - max_lamports: sol_to_lamports(ui_priority_fee), - } + PriorityFee::default_auto_percentile(sol_to_lamports(ui_priority_fee)) } else { PriorityFee::default_auto() }; diff --git a/src/priority_fee.rs b/src/priority_fee.rs index a5df4b0..43612b8 100644 --- a/src/priority_fee.rs +++ b/src/priority_fee.rs @@ -11,14 +11,23 @@ use { #[derive(Debug, Clone, Copy)] pub enum PriorityFee { - Auto { max_lamports: u64 }, - Exact { lamports: u64 }, + Auto { + max_lamports: u64, + fee_percentile: u8, + }, + Exact { + lamports: u64, + }, } impl PriorityFee { pub fn default_auto() -> Self { + Self::default_auto_percentile(sol_to_lamports(0.005)) // Same max as the Jupiter V6 Swap API + } + pub fn default_auto_percentile(max_lamports: u64) -> Self { Self::Auto { - max_lamports: sol_to_lamports(0.005), // Same max as the Jupiter V6 Swap API + max_lamports, + fee_percentile: 90, // Pay at this percentile of recent fees } } } @@ -26,7 +35,7 @@ impl PriorityFee { impl PriorityFee { pub fn max_lamports(&self) -> u64 { match self { - Self::Auto { max_lamports } => *max_lamports, + Self::Auto { max_lamports, .. } => *max_lamports, Self::Exact { lamports } => *lamports, } } @@ -98,52 +107,62 @@ pub fn apply_priority_fee( compute_unit_limit: u32, priority_fee: PriorityFee, ) -> Result<(), Box> { - let compute_budget = if let Some(exact_lamports) = priority_fee.exact_lamports() { - ComputeBudget::new(compute_unit_limit, exact_lamports) - } else { - let recent_compute_unit_prices = - get_recent_priority_fees_for_instructions(rpc_client, instructions)?; - - let mean_compute_unit_price_micro_lamports = - recent_compute_unit_prices.iter().copied().sum::() - / recent_compute_unit_prices.len() as u64; - - /* - let max_compute_unit_price_micro_lamports = recent_compute_unit_prices - .iter() - .max() - .copied() - .unwrap_or_default(); - - println!("{recent_compute_unit_prices:?}: mean {mean_compute_unit_price_micro_lamports}, max {max_compute_unit_price_micro_lamports}"); - */ - - let compute_unit_price_micro_lamports = mean_compute_unit_price_micro_lamports; - - if let Ok(priority_fee_estimate) = helius_rpc::get_priority_fee_estimate_for_instructions( - rpc_client, - helius_rpc::HeliusPriorityLevel::High, - instructions, - ) { + let compute_budget = match priority_fee { + PriorityFee::Exact { lamports } => ComputeBudget::new(compute_unit_limit, lamports), + PriorityFee::Auto { + 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 dist = + criterion_stats::Distribution::from(recent_compute_unit_prices.into_boxed_slice()); + print!("Recent CU prices: mean={:.0}", dist.mean()); + let percentiles = dist.percentiles(); + for i in [50., 75., 85., 90., 95., 100.] { + print!(", {i}th={:.0}", percentiles.at(i)); + } + + let compute_unit_price_micro_lamports = percentiles.at(fee_percentile as f64) as u64; println!( - "Note: helius compute unit price (high) estimate is {priority_fee_estimate}. \ - `sys` computed {compute_unit_price_micro_lamports}" + "\nUsing the {fee_percentile}th percentile recent CU price of {:.0}", + compute_unit_price_micro_lamports ); - } - let compute_budget = ComputeBudget { - compute_unit_price_micro_lamports, - compute_unit_limit, - }; - - if compute_budget.priority_fee_lamports() > priority_fee.max_lamports() { - println!( - "Note: Computed priority fee of {} exceeds the maximum priority fee", - Sol(compute_budget.priority_fee_lamports()) - ); - ComputeBudget::new(compute_unit_limit, priority_fee.max_lamports()) - } else { - compute_budget + todo!(); + + if let Ok(priority_fee_estimate) = + helius_rpc::get_priority_fee_estimate_for_instructions( + rpc_client, + helius_rpc::HeliusPriorityLevel::High, + instructions, + ) + { + println!( + "Note: helius compute unit price (high) estimate is {priority_fee_estimate}. \ + `sys` computed {compute_unit_price_micro_lamports}" + ); + } + + let compute_budget = ComputeBudget { + compute_unit_price_micro_lamports, + compute_unit_limit, + }; + + if compute_budget.priority_fee_lamports() > max_lamports { + println!( + "Note: Computed priority fee of {} exceeds the maximum priority fee", + Sol(compute_budget.priority_fee_lamports()) + ); + ComputeBudget::new(compute_unit_limit, max_lamports) + } else { + compute_budget + } } };