Optimize SWQOS dual fee lane submission

This commit is contained in:
0xfnzero
2026-05-19 06:21:37 +08:00
parent 05ac079d6d
commit 6dab47f8f9
9 changed files with 581 additions and 140 deletions
+179 -10
View File
@@ -1,6 +1,6 @@
use crate::swqos::{SwqosType, TradeType};
use arc_swap::ArcSwap;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -14,6 +14,15 @@ impl GasFeeStrategyType {
pub fn values() -> Vec<Self> {
vec![Self::Normal, Self::LowTipHighCuPrice, Self::HighTipLowCuPrice]
}
#[inline]
pub fn as_str(&self) -> &'static str {
match self {
Self::Normal => "Normal",
Self::LowTipHighCuPrice => "LowTipHighCuPrice",
Self::HighTipLowCuPrice => "HighTipLowCuPrice",
}
}
}
#[derive(Debug, Clone, Copy)]
@@ -84,6 +93,33 @@ impl GasFeeStrategy {
);
}
/// 设置 Default/RPC 的优先费-only 策略。Default 没有 relay tip account
/// 但仍应携带 ComputeBudget 优先费。
pub fn set_default_rpc_fee_strategy(
&self,
buy_cu_limit: u32,
sell_cu_limit: u32,
buy_cu_price: u64,
sell_cu_price: u64,
) {
self.set(
SwqosType::Default,
TradeType::Buy,
GasFeeStrategyType::Normal,
buy_cu_limit,
buy_cu_price,
0.0,
);
self.set(
SwqosType::Default,
TradeType::Sell,
GasFeeStrategyType::Normal,
sell_cu_limit,
sell_cu_price,
0.0,
);
}
/// 为多个服务类型添加高低费率策略,会移除(SwqosType,TradeType)的默认策略。
/// Add high-low fee strategies for multiple service types, Will remove the default strategy of (SwqosType,TradeType)
pub fn set_high_low_fee_strategies(
@@ -271,7 +307,7 @@ impl GasFeeStrategy {
) -> Vec<(SwqosType, GasFeeStrategyType, GasFeeStrategyValue)> {
let strategies = self.strategies.load();
let mut result = Vec::new();
let mut swqos_types = std::collections::HashSet::new();
let mut swqos_types = HashSet::new();
for (swqos_type, t_type, _) in strategies.keys() {
if *t_type == trade_type {
swqos_types.insert(*swqos_type);
@@ -298,10 +334,17 @@ impl GasFeeStrategy {
/// 动态更新买入小费(保持其他参数不变)
/// Dynamically update buy tip (keep other parameters unchanged)
pub fn update_buy_tip(&self, buy_tip: f64) {
self.update_buy_tip_for_strategy(GasFeeStrategyType::Normal, buy_tip);
}
/// 动态更新指定买入策略的小费(保持其他参数不变)。
/// Dynamic updates should generally target Normal only; updating all strategies would
/// collapse the low-tip/high-tip dual-lane spread into the same tip.
pub fn update_buy_tip_for_strategy(&self, strategy_type: GasFeeStrategyType, buy_tip: f64) {
self.strategies.rcu(|current_map| {
let mut new_map = (**current_map).clone();
for ((_swqos_type, trade_type, _strategy_type), value) in new_map.iter_mut() {
if *trade_type == TradeType::Buy {
for ((_swqos_type, trade_type, s_type), value) in new_map.iter_mut() {
if *trade_type == TradeType::Buy && *s_type == strategy_type {
value.tip = buy_tip;
}
}
@@ -312,10 +355,15 @@ impl GasFeeStrategy {
/// 动态更新卖出小费(保持其他参数不变)
/// Dynamically update sell tip (keep other parameters unchanged)
pub fn update_sell_tip(&self, sell_tip: f64) {
self.update_sell_tip_for_strategy(GasFeeStrategyType::Normal, sell_tip);
}
/// 动态更新指定卖出策略的小费(保持其他参数不变)。
pub fn update_sell_tip_for_strategy(&self, strategy_type: GasFeeStrategyType, sell_tip: f64) {
self.strategies.rcu(|current_map| {
let mut new_map = (**current_map).clone();
for ((_swqos_type, trade_type, _strategy_type), value) in new_map.iter_mut() {
if *trade_type == TradeType::Sell {
for ((_swqos_type, trade_type, s_type), value) in new_map.iter_mut() {
if *trade_type == TradeType::Sell && *s_type == strategy_type {
value.tip = sell_tip;
}
}
@@ -326,10 +374,19 @@ impl GasFeeStrategy {
/// 动态更新买入优先费(保持其他参数不变)
/// Dynamically update buy compute unit price (keep other parameters unchanged)
pub fn update_buy_cu_price(&self, buy_cu_price: u64) {
self.update_buy_cu_price_for_strategy(GasFeeStrategyType::Normal, buy_cu_price);
}
/// 动态更新指定买入策略的优先费(保持其他参数不变)。
pub fn update_buy_cu_price_for_strategy(
&self,
strategy_type: GasFeeStrategyType,
buy_cu_price: u64,
) {
self.strategies.rcu(|current_map| {
let mut new_map = (**current_map).clone();
for ((_swqos_type, trade_type, _strategy_type), value) in new_map.iter_mut() {
if *trade_type == TradeType::Buy {
for ((_swqos_type, trade_type, s_type), value) in new_map.iter_mut() {
if *trade_type == TradeType::Buy && *s_type == strategy_type {
value.cu_price = buy_cu_price;
}
}
@@ -340,10 +397,19 @@ impl GasFeeStrategy {
/// 动态更新卖出优先费(保持其他参数不变)
/// Dynamically update sell compute unit price (keep other parameters unchanged)
pub fn update_sell_cu_price(&self, sell_cu_price: u64) {
self.update_sell_cu_price_for_strategy(GasFeeStrategyType::Normal, sell_cu_price);
}
/// 动态更新指定卖出策略的优先费(保持其他参数不变)。
pub fn update_sell_cu_price_for_strategy(
&self,
strategy_type: GasFeeStrategyType,
sell_cu_price: u64,
) {
self.strategies.rcu(|current_map| {
let mut new_map = (**current_map).clone();
for ((_swqos_type, trade_type, _strategy_type), value) in new_map.iter_mut() {
if *trade_type == TradeType::Sell {
for ((_swqos_type, trade_type, s_type), value) in new_map.iter_mut() {
if *trade_type == TradeType::Sell && *s_type == strategy_type {
value.cu_price = sell_cu_price;
}
}
@@ -365,3 +431,106 @@ impl GasFeeStrategy {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn find_strategy(
strategies: &[(SwqosType, GasFeeStrategyType, GasFeeStrategyValue)],
swqos_type: SwqosType,
strategy_type: GasFeeStrategyType,
) -> GasFeeStrategyValue {
strategies
.iter()
.find(|(s, t, _)| *s == swqos_type && *t == strategy_type)
.map(|(_, _, v)| *v)
.expect("strategy exists")
}
#[test]
fn high_low_fee_strategy_expands_two_lanes_per_swqos() {
let strategy = GasFeeStrategy::new();
strategy.set_high_low_fee_strategies(
&[SwqosType::Jito, SwqosType::Helius],
TradeType::Buy,
100_000,
180_000,
400_000,
0.002,
0.005,
);
let strategies = strategy.get_strategies(TradeType::Buy);
assert_eq!(strategies.len(), 4);
for swqos_type in [SwqosType::Jito, SwqosType::Helius] {
let low_tip_high_cu =
find_strategy(&strategies, swqos_type, GasFeeStrategyType::LowTipHighCuPrice);
assert_eq!(low_tip_high_cu.cu_limit, 100_000);
assert_eq!(low_tip_high_cu.cu_price, 400_000);
assert_eq!(low_tip_high_cu.tip, 0.002);
let high_tip_low_cu =
find_strategy(&strategies, swqos_type, GasFeeStrategyType::HighTipLowCuPrice);
assert_eq!(high_tip_low_cu.cu_limit, 100_000);
assert_eq!(high_tip_low_cu.cu_price, 180_000);
assert_eq!(high_tip_low_cu.tip, 0.005);
}
}
#[test]
fn dynamic_updates_do_not_collapse_dual_lane_fees() {
let strategy = GasFeeStrategy::new();
strategy.set_high_low_fee_strategy(
SwqosType::Jito,
TradeType::Buy,
100_000,
180_000,
400_000,
0.002,
0.005,
);
strategy.update_buy_tip(0.009);
strategy.update_buy_cu_price(999_999);
let strategies = strategy.get_strategies(TradeType::Buy);
let low_tip_high_cu =
find_strategy(&strategies, SwqosType::Jito, GasFeeStrategyType::LowTipHighCuPrice);
let high_tip_low_cu =
find_strategy(&strategies, SwqosType::Jito, GasFeeStrategyType::HighTipLowCuPrice);
assert_eq!(low_tip_high_cu.cu_price, 400_000);
assert_eq!(low_tip_high_cu.tip, 0.002);
assert_eq!(high_tip_low_cu.cu_price, 180_000);
assert_eq!(high_tip_low_cu.tip, 0.005);
}
#[test]
fn default_rpc_strategy_uses_priority_fee_without_tip() {
let strategy = GasFeeStrategy::new();
strategy.set_default_rpc_fee_strategy(100_000, 90_000, 700_000, 800_000);
let buy = find_strategy(
&strategy.get_strategies(TradeType::Buy),
SwqosType::Default,
GasFeeStrategyType::Normal,
);
assert_eq!(buy.cu_limit, 100_000);
assert_eq!(buy.cu_price, 700_000);
assert_eq!(buy.tip, 0.0);
let sell = find_strategy(
&strategy.get_strategies(TradeType::Sell),
SwqosType::Default,
GasFeeStrategyType::Normal,
);
assert_eq!(sell.cu_limit, 90_000);
assert_eq!(sell.cu_price, 800_000);
assert_eq!(sell.tip, 0.0);
}
}
+12 -10
View File
@@ -84,7 +84,7 @@ pub fn print_sdk_timing_block(
start_us: Option<i64>,
build_end_us: Option<i64>,
before_submit_us: Option<i64>,
submit_timings: &[(crate::swqos::SwqosType, i64)],
submit_timings: &[crate::common::SwqosSubmitTiming],
confirm_us: Option<i64>,
) {
println!();
@@ -112,13 +112,14 @@ pub fn print_sdk_timing_block(
}
if let Some(confirm_done_us) = confirm_us {
let total_ms = (confirm_done_us - start_us) as f64 / 1000.0;
for (swqos_type, submit_done_us) in submit_timings {
let submit_ms = (*submit_done_us - start_us).max(0) as f64 / 1000.0;
let confirmed_ms = (confirm_done_us - *submit_done_us).max(0) as f64 / 1000.0;
for timing in submit_timings {
let submit_ms = (timing.submit_done_us - start_us).max(0) as f64 / 1000.0;
let confirmed_ms = (confirm_done_us - timing.submit_done_us).max(0) as f64 / 1000.0;
println!(
" [SDK][{:width$}] {} submit_done: {:.4} ms, confirmed: {:.4} ms, total: {:.4} ms",
swqos_type.as_str(),
" [SDK][{:width$}] {} {} submit_done: {:.4} ms, confirmed: {:.4} ms, total: {:.4} ms",
timing.swqos_type.as_str(),
dir,
timing.strategy_type.as_str(),
submit_ms,
confirmed_ms,
total_ms,
@@ -126,12 +127,13 @@ pub fn print_sdk_timing_block(
);
}
} else {
for (swqos_type, submit_done_us) in submit_timings {
let submit_ms = (*submit_done_us - start_us).max(0) as f64 / 1000.0;
for timing in submit_timings {
let submit_ms = (timing.submit_done_us - start_us).max(0) as f64 / 1000.0;
println!(
" [SDK][{:width$}] {} submit_done: {:.4} ms, confirmed: -, total: {:.4} ms",
swqos_type.as_str(),
" [SDK][{:width$}] {} {} submit_done: {:.4} ms, confirmed: -, total: {:.4} ms",
timing.swqos_type.as_str(),
dir,
timing.strategy_type.as_str(),
submit_ms,
submit_ms,
width = SWQOS_LABEL_WIDTH
+9 -1
View File
@@ -1,4 +1,5 @@
use crate::swqos::SwqosConfig;
use crate::common::GasFeeStrategyType;
use crate::swqos::{SwqosConfig, SwqosType};
use solana_commitment_config::CommitmentConfig;
use std::hash::{Hash, Hasher};
@@ -75,6 +76,13 @@ impl PartialEq for InfrastructureConfig {
impl Eq for InfrastructureConfig {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SwqosSubmitTiming {
pub swqos_type: SwqosType,
pub strategy_type: GasFeeStrategyType,
pub submit_done_us: i64,
}
#[derive(Debug, Clone)]
pub struct TradeConfig {
pub rpc_url: String,