From 2abcf3839e7968ccc3fe86bef278cc92b96b459e Mon Sep 17 00:00:00 2001 From: hookenful Date: Sat, 14 Mar 2026 18:16:55 +0200 Subject: [PATCH] style: run rustfmt --- examples/address_lookup/src/main.rs | 13 +- examples/bonk_copy_trading/src/main.rs | 14 +- examples/bonk_sniper_trading/src/main.rs | 14 +- examples/cli_trading/src/main.rs | 25 +- .../src/main.rs | 25 +- examples/middleware_system/src/main.rs | 6 +- examples/nonce_cache/src/main.rs | 9 +- examples/pumpfun_copy_trading/src/main.rs | 21 +- examples/pumpfun_sniper_trading/src/main.rs | 17 +- examples/pumpswap_direct_trading/src/main.rs | 19 +- examples/pumpswap_trading/src/main.rs | 14 +- examples/raydium_amm_v4_trading/src/main.rs | 26 +- examples/raydium_cpmm_trading/src/main.rs | 20 +- examples/seed_trading/src/main.rs | 5 +- examples/trading_client/src/main.rs | 16 +- examples/wsol_wrapper/src/main.rs | 5 +- src/common/fast_fn.rs | 12 +- src/common/fast_timing.rs | 15 +- src/common/mod.rs | 2 +- src/common/seed.rs | 14 +- src/common/spl_token.rs | 6 +- src/common/types.rs | 16 +- src/constants/accounts.rs | 3 +- src/constants/swqos.rs | 31 +- src/constants/trade.rs | 2 +- src/instruction/meteora_damm_v2.rs | 18 +- src/instruction/mod.rs | 8 +- src/instruction/pumpfun.rs | 48 ++- src/instruction/pumpswap.rs | 14 +- src/instruction/raydium_amm_v4.rs | 14 +- src/instruction/raydium_cpmm.rs | 62 +++- src/instruction/utils/mod.rs | 4 +- src/instruction/utils/pumpfun.rs | 6 +- src/instruction/utils/pumpswap.rs | 22 +- src/instruction/utils/raydium_cpmm.rs | 6 +- src/lib.rs | 84 ++--- src/perf/compiler_optimization.rs | 165 +++++----- src/perf/hardware_optimizations.rs | 175 +++++----- src/perf/mod.rs | 16 +- src/perf/simd.rs | 2 +- src/perf/syscall_bypass.rs | 308 +++++++++--------- src/perf/zero_copy_io.rs | 259 ++++++++------- src/swqos/astralane.rs | 93 ++++-- src/swqos/astralane_quic.rs | 44 +-- src/swqos/blockrazor.rs | 100 ++++-- src/swqos/bloxroute.rs | 61 +++- src/swqos/common.rs | 34 +- src/swqos/flashblock.rs | 57 +++- src/swqos/helius.rs | 36 +- src/swqos/jito.rs | 55 +++- src/swqos/lightspeed.rs | 56 +++- src/swqos/mod.rs | 265 ++++++--------- src/swqos/nextblock.rs | 58 +++- src/swqos/node1.rs | 95 ++++-- src/swqos/node1_quic.rs | 66 ++-- src/swqos/speedlanding.rs | 23 +- src/swqos/stellium.rs | 65 +++- src/swqos/temporal.rs | 110 ++++--- src/swqos/zeroslot.rs | 53 ++- src/trading/common/compute_budget_manager.rs | 2 +- src/trading/common/mod.rs | 6 +- src/trading/common/nonce_manager.rs | 5 +- src/trading/common/transaction_builder.rs | 5 +- src/trading/common/utils.rs | 9 +- src/trading/common/wsol_manager.rs | 32 +- src/trading/core/async_executor.rs | 39 +-- src/trading/core/execution.rs | 13 +- src/trading/core/executor.rs | 95 ++++-- src/trading/core/mod.rs | 6 +- src/trading/core/traits.rs | 5 +- src/trading/core/transaction_pool.rs | 22 +- src/trading/middleware/mod.rs | 2 +- src/trading/middleware/traits.rs | 7 +- src/utils/calc/mod.rs | 8 +- src/utils/mod.rs | 25 +- src/utils/price/mod.rs | 2 +- 76 files changed, 1763 insertions(+), 1352 deletions(-) diff --git a/examples/address_lookup/src/main.rs b/examples/address_lookup/src/main.rs index 3e5f70f..30074c7 100644 --- a/examples/address_lookup/src/main.rs +++ b/examples/address_lookup/src/main.rs @@ -16,7 +16,10 @@ use sol_trade_sdk::common::{GasFeeStrategy, TradeConfig}; use sol_trade_sdk::{ common::AnyResult, swqos::SwqosConfig, - trading::{core::params::{PumpFunParams, DexParamEnum}, factory::DexType}, + trading::{ + core::params::{DexParamEnum, PumpFunParams}, + factory::DexType, + }, SolanaTrade, }; use solana_commitment_config::CommitmentConfig; @@ -63,7 +66,9 @@ async fn main() -> Result<(), Box> { loop { if let Some(event) = queue.pop() { let run = match &event { - DexEvent::PumpFunBuy(e) | DexEvent::PumpFunSell(e) | DexEvent::PumpFunBuyExactSolIn(e) => { + DexEvent::PumpFunBuy(e) + | DexEvent::PumpFunSell(e) + | DexEvent::PumpFunBuyExactSolIn(e) => { if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) { Some(e.clone()) } else { @@ -123,7 +128,9 @@ async fn pumpfun_copy_trade_with_grpc( let lookup_table_key = Pubkey::from_str("use_your_lookup_table_key_here").unwrap(); let address_lookup_table_account = - fetch_address_lookup_table_account(&client.infrastructure.rpc, &lookup_table_key).await.ok(); + fetch_address_lookup_table_account(&client.infrastructure.rpc, &lookup_table_key) + .await + .ok(); let gas_fee_strategy = GasFeeStrategy::new(); gas_fee_strategy.set_global_fee_strategy(150000, 150000, 500000, 500000, 0.001, 0.001); diff --git a/examples/bonk_copy_trading/src/main.rs b/examples/bonk_copy_trading/src/main.rs index 20cbf0a..84d5b9e 100644 --- a/examples/bonk_copy_trading/src/main.rs +++ b/examples/bonk_copy_trading/src/main.rs @@ -10,7 +10,10 @@ use sol_trade_sdk::common::{ use sol_trade_sdk::{ common::AnyResult, swqos::SwqosConfig, - trading::{core::params::{BonkParams, DexParamEnum}, factory::DexType}, + trading::{ + core::params::{BonkParams, DexParamEnum}, + factory::DexType, + }, SolanaTrade, }; use solana_commitment_config::CommitmentConfig; @@ -127,14 +130,7 @@ async fn bonk_copy_trade_with_grpc(trade_info: BonkTradeEvent) -> AnyResult<()> let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?; let gas_fee_strategy = GasFeeStrategy::new(); - gas_fee_strategy.set_global_fee_strategy( - 150000, - 150000, - 500000, - 500000, - 0.001, - 0.001, - ); + gas_fee_strategy.set_global_fee_strategy(150000, 150000, 500000, 500000, 0.001, 0.001); // Buy tokens println!("Buying tokens from Bonk..."); diff --git a/examples/bonk_sniper_trading/src/main.rs b/examples/bonk_sniper_trading/src/main.rs index dac5411..5c3c600 100644 --- a/examples/bonk_sniper_trading/src/main.rs +++ b/examples/bonk_sniper_trading/src/main.rs @@ -3,7 +3,10 @@ use sol_trade_sdk::common::TradeConfig; use sol_trade_sdk::{ common::AnyResult, swqos::SwqosConfig, - trading::{core::params::{BonkParams, DexParamEnum}, factory::DexType}, + trading::{ + core::params::{BonkParams, DexParamEnum}, + factory::DexType, + }, SolanaTrade, }; use solana_commitment_config::CommitmentConfig; @@ -95,14 +98,7 @@ async fn bonk_sniper_trade_with_shreds(trade_info: BonkTradeEvent) -> AnyResult< let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?; let gas_fee_strategy = sol_trade_sdk::common::GasFeeStrategy::new(); - gas_fee_strategy.set_global_fee_strategy( - 150000, - 150000, - 500000, - 500000, - 0.001, - 0.001, - ); + gas_fee_strategy.set_global_fee_strategy(150000, 150000, 500000, 500000, 0.001, 0.001); let token_type = if trade_info.quote_token_mint == sol_trade_sdk::constants::USD1_TOKEN_ACCOUNT { diff --git a/examples/cli_trading/src/main.rs b/examples/cli_trading/src/main.rs index 2690523..42d190c 100644 --- a/examples/cli_trading/src/main.rs +++ b/examples/cli_trading/src/main.rs @@ -12,7 +12,8 @@ use sol_trade_sdk::{ swqos::SwqosConfig, trading::{ core::params::{ - BonkParams, PumpFunParams, PumpSwapParams, RaydiumAmmV4Params, RaydiumCpmmParams, DexParamEnum, + BonkParams, DexParamEnum, PumpFunParams, PumpSwapParams, RaydiumAmmV4Params, + RaydiumCpmmParams, }, factory::DexType, }, @@ -721,7 +722,8 @@ async fn handle_buy_bonk( println!(" Slippage: {}%", slippage.unwrap()); } let mint_pubkey = Pubkey::from_str(mint)?; - let param = BonkParams::from_mint_by_rpc(&client.infrastructure.rpc, &mint_pubkey, false).await?; + let param = + BonkParams::from_mint_by_rpc(&client.infrastructure.rpc, &mint_pubkey, false).await?; let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?; let sol_lamports = sol_str_to_lamports(sol_amount.to_string().as_str()).unwrap(); @@ -780,7 +782,8 @@ async fn handle_buy_raydium_v4( let mint_pubkey = Pubkey::from_str(mint)?; let amm_pubkey = Pubkey::from_str(amm)?; - let param = RaydiumAmmV4Params::from_amm_address_by_rpc(&client.infrastructure.rpc, amm_pubkey).await?; + let param = + RaydiumAmmV4Params::from_amm_address_by_rpc(&client.infrastructure.rpc, amm_pubkey).await?; let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?; let sol_lamports = sol_str_to_lamports(sol_amount.to_string().as_str()).unwrap(); @@ -839,7 +842,9 @@ async fn handle_buy_raydium_cpmm( let mint_pubkey = Pubkey::from_str(mint)?; let pool_pubkey = Pubkey::from_str(pool_address)?; - let param = RaydiumCpmmParams::from_pool_address_by_rpc(&client.infrastructure.rpc, &pool_pubkey).await?; + let param = + RaydiumCpmmParams::from_pool_address_by_rpc(&client.infrastructure.rpc, &pool_pubkey) + .await?; let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?; let sol_lamports = sol_str_to_lamports(sol_amount.to_string().as_str()).unwrap(); @@ -1097,7 +1102,7 @@ async fn handle_sell_pumpswap( match client.sell(sell_params).await { Ok((_, signature, _)) => { println!(" ✅ Successfully sold tokens from PumpSwap!"); - println!(" ✅ Transaction Signature: {:?}", signature); + println!(" ✅ Transaction Signature: {:?}", signature); } Err(e) => { println!(" ❌ Failed to sell tokens from PumpSwap: {}", e); @@ -1126,7 +1131,8 @@ async fn handle_sell_bonk( } let client = initialize_real_client().await?; let mint_pubkey = Pubkey::from_str(mint)?; - let param = BonkParams::from_mint_by_rpc(&client.infrastructure.rpc, &mint_pubkey, false).await?; + let param = + BonkParams::from_mint_by_rpc(&client.infrastructure.rpc, &mint_pubkey, false).await?; let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?; let gas_fee_strategy = sol_trade_sdk::common::GasFeeStrategy::new(); @@ -1187,7 +1193,8 @@ async fn handle_sell_raydium_v4( let client = initialize_real_client().await?; let amm_pubkey = Pubkey::from_str(amm)?; let mint_pubkey = Pubkey::from_str(mint)?; - let param = RaydiumAmmV4Params::from_amm_address_by_rpc(&client.infrastructure.rpc, amm_pubkey).await?; + let param = + RaydiumAmmV4Params::from_amm_address_by_rpc(&client.infrastructure.rpc, amm_pubkey).await?; let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?; let gas_fee_strategy = sol_trade_sdk::common::GasFeeStrategy::new(); @@ -1248,7 +1255,9 @@ async fn handle_sell_raydium_cpmm( let client = initialize_real_client().await?; let pool_pubkey = Pubkey::from_str(pool_address)?; let mint_pubkey = Pubkey::from_str(mint)?; - let param = RaydiumCpmmParams::from_pool_address_by_rpc(&client.infrastructure.rpc, &pool_pubkey).await?; + let param = + RaydiumCpmmParams::from_pool_address_by_rpc(&client.infrastructure.rpc, &pool_pubkey) + .await?; let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?; let gas_fee_strategy = sol_trade_sdk::common::GasFeeStrategy::new(); diff --git a/examples/meteora_damm_v2_direct_trading/src/main.rs b/examples/meteora_damm_v2_direct_trading/src/main.rs index 6af152b..4737a66 100644 --- a/examples/meteora_damm_v2_direct_trading/src/main.rs +++ b/examples/meteora_damm_v2_direct_trading/src/main.rs @@ -1,7 +1,13 @@ use sol_trade_sdk::{ - SolanaTrade, TradeTokenType, common::{ - AnyResult, TradeConfig, fast_fn::get_associated_token_address_with_program_id_fast_use_seed - }, swqos::SwqosConfig, trading::{core::params::{MeteoraDammV2Params, DexParamEnum}, factory::DexType} + common::{ + fast_fn::get_associated_token_address_with_program_id_fast_use_seed, AnyResult, TradeConfig, + }, + swqos::SwqosConfig, + trading::{ + core::params::{DexParamEnum, MeteoraDammV2Params}, + factory::DexType, + }, + SolanaTrade, TradeTokenType, }; use solana_commitment_config::CommitmentConfig; use solana_sdk::signature::Keypair; @@ -32,7 +38,8 @@ async fn main() -> Result<(), Box> { slippage_basis_points: slippage_basis_points, recent_blockhash: Some(recent_blockhash), extension_params: DexParamEnum::MeteoraDammV2( - MeteoraDammV2Params::from_pool_address_by_rpc(&client.infrastructure.rpc, &pool).await?, + MeteoraDammV2Params::from_pool_address_by_rpc(&client.infrastructure.rpc, &pool) + .await?, ), address_lookup_table_account: None, wait_transaction_confirmed: true, @@ -54,7 +61,12 @@ async fn main() -> Result<(), Box> { let rpc = client.infrastructure.rpc.clone(); let payer = client.payer.pubkey(); let program_id = sol_trade_sdk::constants::TOKEN_PROGRAM; - let account = get_associated_token_address_with_program_id_fast_use_seed(&payer, &mint_pubkey, &program_id, client.use_seed_optimize); + let account = get_associated_token_address_with_program_id_fast_use_seed( + &payer, + &mint_pubkey, + &program_id, + client.use_seed_optimize, + ); let balance = rpc.get_token_account_balance(&account).await?; let amount_token = balance.amount.parse::().unwrap(); println!("Token balance: {}", amount_token); @@ -67,7 +79,8 @@ async fn main() -> Result<(), Box> { recent_blockhash: Some(recent_blockhash), with_tip: false, extension_params: DexParamEnum::MeteoraDammV2( - MeteoraDammV2Params::from_pool_address_by_rpc(&client.infrastructure.rpc, &pool).await?, + MeteoraDammV2Params::from_pool_address_by_rpc(&client.infrastructure.rpc, &pool) + .await?, ), address_lookup_table_account: None, wait_transaction_confirmed: true, diff --git a/examples/middleware_system/src/main.rs b/examples/middleware_system/src/main.rs index 46e15da..e66a4c3 100644 --- a/examples/middleware_system/src/main.rs +++ b/examples/middleware_system/src/main.rs @@ -3,7 +3,8 @@ use sol_trade_sdk::{ common::{AnyResult, TradeConfig}, swqos::SwqosConfig, trading::{ - core::params::{PumpSwapParams, DexParamEnum}, factory::DexType, + core::params::{DexParamEnum, PumpSwapParams}, + factory::DexType, InstructionMiddleware, MiddlewareManager, }, SolanaTrade, TradeTokenType, @@ -91,7 +92,8 @@ async fn test_middleware() -> AnyResult<()> { slippage_basis_points: slippage_basis_points, recent_blockhash: Some(recent_blockhash), extension_params: DexParamEnum::PumpSwap( - PumpSwapParams::from_pool_address_by_rpc(&client.infrastructure.rpc, &pool_address).await?, + PumpSwapParams::from_pool_address_by_rpc(&client.infrastructure.rpc, &pool_address) + .await?, ), address_lookup_table_account: None, wait_transaction_confirmed: true, diff --git a/examples/nonce_cache/src/main.rs b/examples/nonce_cache/src/main.rs index b49952f..80a340b 100644 --- a/examples/nonce_cache/src/main.rs +++ b/examples/nonce_cache/src/main.rs @@ -16,7 +16,10 @@ use sol_trade_sdk::TradeTokenType; use sol_trade_sdk::{ common::AnyResult, swqos::SwqosConfig, - trading::{core::params::{PumpFunParams, DexParamEnum}, factory::DexType}, + trading::{ + core::params::{DexParamEnum, PumpFunParams}, + factory::DexType, + }, SolanaTrade, }; use solana_commitment_config::CommitmentConfig; @@ -62,7 +65,9 @@ async fn main() -> Result<(), Box> { loop { if let Some(event) = queue.pop() { let run = match &event { - DexEvent::PumpFunBuy(e) | DexEvent::PumpFunSell(e) | DexEvent::PumpFunBuyExactSolIn(e) => { + DexEvent::PumpFunBuy(e) + | DexEvent::PumpFunSell(e) + | DexEvent::PumpFunBuyExactSolIn(e) => { if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) { Some(e.clone()) } else { diff --git a/examples/pumpfun_copy_trading/src/main.rs b/examples/pumpfun_copy_trading/src/main.rs index eb873d8..aa72b22 100644 --- a/examples/pumpfun_copy_trading/src/main.rs +++ b/examples/pumpfun_copy_trading/src/main.rs @@ -2,7 +2,10 @@ //! //! 收到 PumpFun 买卖事件后,用事件中的参数(含 is_cashback_coin)构造交易并执行一次买+卖。 -use std::sync::{atomic::{AtomicBool, Ordering}, Arc}; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; use sol_parser_sdk::grpc::{ AccountFilter, ClientConfig, EventType, EventTypeFilter, OrderMode, Protocol, @@ -16,7 +19,10 @@ use sol_trade_sdk::TradeTokenType; use sol_trade_sdk::{ common::AnyResult, swqos::SwqosConfig, - trading::{core::params::{PumpFunParams, DexParamEnum}, factory::DexType}, + trading::{ + core::params::{DexParamEnum, PumpFunParams}, + factory::DexType, + }, SolanaTrade, }; use solana_commitment_config::CommitmentConfig; @@ -65,7 +71,9 @@ async fn main() -> Result<(), Box> { loop { if let Some(event) = queue.pop() { let run = match &event { - DexEvent::PumpFunBuy(e) | DexEvent::PumpFunSell(e) | DexEvent::PumpFunBuyExactSolIn(e) => { + DexEvent::PumpFunBuy(e) + | DexEvent::PumpFunSell(e) + | DexEvent::PumpFunBuyExactSolIn(e) => { if !ALREADY_EXECUTED.swap(true, Ordering::SeqCst) { Some(e.clone()) } else { @@ -102,16 +110,15 @@ async fn main() -> Result<(), Box> { async fn create_solana_trade_client() -> AnyResult { let payer = Keypair::from_base58_string("use_your_payer_keypair_here"); - let rpc_url = std::env::var("RPC_URL").unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string()); + let rpc_url = std::env::var("RPC_URL") + .unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string()); let commitment = CommitmentConfig::confirmed(); let swqos_configs: Vec = vec![SwqosConfig::Default(rpc_url.clone())]; let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); Ok(SolanaTrade::new(Arc::new(payer), trade_config).await) } -async fn pumpfun_copy_trade( - e: sol_parser_sdk::core::events::PumpFunTradeEvent, -) -> AnyResult<()> { +async fn pumpfun_copy_trade(e: sol_parser_sdk::core::events::PumpFunTradeEvent) -> AnyResult<()> { let client = create_solana_trade_client().await?; let mint_pubkey = e.mint; let slippage_basis_points = Some(100u64); diff --git a/examples/pumpfun_sniper_trading/src/main.rs b/examples/pumpfun_sniper_trading/src/main.rs index f53e76a..0189597 100644 --- a/examples/pumpfun_sniper_trading/src/main.rs +++ b/examples/pumpfun_sniper_trading/src/main.rs @@ -3,7 +3,10 @@ //! 监听创建者首次买入(Create 后同笔/首笔 Buy,is_created_buy == true), //! 用事件参数(含 is_cashback_coin)构造 from_dev_trade 并执行一次买+卖。 -use std::sync::{atomic::{AtomicBool, Ordering}, Arc}; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; use sol_parser_sdk::grpc::{ AccountFilter, ClientConfig, EventType, EventTypeFilter, OrderMode, Protocol, @@ -16,7 +19,10 @@ use sol_trade_sdk::TradeTokenType; use sol_trade_sdk::{ common::AnyResult, swqos::SwqosConfig, - trading::{core::params::{PumpFunParams, DexParamEnum}, factory::DexType}, + trading::{ + core::params::{DexParamEnum, PumpFunParams}, + factory::DexType, + }, SolanaTrade, }; use solana_commitment_config::CommitmentConfig; @@ -94,16 +100,15 @@ async fn main() -> Result<(), Box> { async fn create_solana_trade_client() -> AnyResult { let payer = Keypair::from_base58_string("use_your_payer_keypair_here"); - let rpc_url = std::env::var("RPC_URL").unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string()); + let rpc_url = std::env::var("RPC_URL") + .unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string()); let commitment = CommitmentConfig::confirmed(); let swqos_configs: Vec = vec![SwqosConfig::Default(rpc_url.clone())]; let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment); Ok(SolanaTrade::new(Arc::new(payer), trade_config).await) } -async fn pumpfun_sniper_trade( - e: sol_parser_sdk::core::events::PumpFunTradeEvent, -) -> AnyResult<()> { +async fn pumpfun_sniper_trade(e: sol_parser_sdk::core::events::PumpFunTradeEvent) -> AnyResult<()> { let client = create_solana_trade_client().await?; let mint_pubkey = e.mint; let slippage_basis_points = Some(300u64); diff --git a/examples/pumpswap_direct_trading/src/main.rs b/examples/pumpswap_direct_trading/src/main.rs index e432f7c..468b897 100644 --- a/examples/pumpswap_direct_trading/src/main.rs +++ b/examples/pumpswap_direct_trading/src/main.rs @@ -1,7 +1,13 @@ use sol_trade_sdk::{ - SolanaTrade, TradeTokenType, common::{ - AnyResult, TradeConfig, fast_fn::get_associated_token_address_with_program_id_fast_use_seed - }, swqos::SwqosConfig, trading::{core::params::{PumpSwapParams, DexParamEnum}, factory::DexType} + common::{ + fast_fn::get_associated_token_address_with_program_id_fast_use_seed, AnyResult, TradeConfig, + }, + swqos::SwqosConfig, + trading::{ + core::params::{DexParamEnum, PumpSwapParams}, + factory::DexType, + }, + SolanaTrade, TradeTokenType, }; use solana_commitment_config::CommitmentConfig; use solana_sdk::signature::Keypair; @@ -54,7 +60,12 @@ async fn main() -> Result<(), Box> { let rpc = client.infrastructure.rpc.clone(); let payer = client.payer.pubkey(); let program_id = sol_trade_sdk::constants::TOKEN_PROGRAM_2022; - let account = get_associated_token_address_with_program_id_fast_use_seed(&payer, &mint_pubkey, &program_id, client.use_seed_optimize); + let account = get_associated_token_address_with_program_id_fast_use_seed( + &payer, + &mint_pubkey, + &program_id, + client.use_seed_optimize, + ); let balance = rpc.get_token_account_balance(&account).await?; let amount_token = balance.amount.parse::().unwrap(); let sell_params = sol_trade_sdk::TradeSellParams { diff --git a/examples/pumpswap_trading/src/main.rs b/examples/pumpswap_trading/src/main.rs index 82c6419..a8a058e 100644 --- a/examples/pumpswap_trading/src/main.rs +++ b/examples/pumpswap_trading/src/main.rs @@ -1,11 +1,14 @@ use sol_trade_sdk::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed; use sol_trade_sdk::common::TradeConfig; -use sol_trade_sdk::TradeTokenType; use sol_trade_sdk::instruction::utils::pumpswap::fetch_pool; +use sol_trade_sdk::TradeTokenType; use sol_trade_sdk::{ common::AnyResult, swqos::SwqosConfig, - trading::{core::params::{PumpSwapParams, DexParamEnum}, factory::DexType}, + trading::{ + core::params::{DexParamEnum, PumpSwapParams}, + factory::DexType, + }, SolanaTrade, }; use solana_commitment_config::CommitmentConfig; @@ -244,7 +247,12 @@ async fn pumpswap_trade_with_grpc( } else { params.quote_token_program }; - let account = get_associated_token_address_with_program_id_fast_use_seed(&payer, &mint_pubkey, &program_id, client.use_seed_optimize); + let account = get_associated_token_address_with_program_id_fast_use_seed( + &payer, + &mint_pubkey, + &program_id, + client.use_seed_optimize, + ); let balance = rpc.get_token_account_balance(&account).await?; let amount_token = balance.amount.parse::().unwrap(); let sell_params = sol_trade_sdk::TradeSellParams { diff --git a/examples/raydium_amm_v4_trading/src/main.rs b/examples/raydium_amm_v4_trading/src/main.rs index 1010dba..18d50ec 100644 --- a/examples/raydium_amm_v4_trading/src/main.rs +++ b/examples/raydium_amm_v4_trading/src/main.rs @@ -2,7 +2,10 @@ use sol_trade_sdk::common::fast_fn::get_associated_token_address_with_program_id use sol_trade_sdk::{ common::AnyResult, swqos::SwqosConfig, - trading::{core::params::{RaydiumAmmV4Params, DexParamEnum}, factory::DexType}, + trading::{ + core::params::{DexParamEnum, RaydiumAmmV4Params}, + factory::DexType, + }, SolanaTrade, }; use sol_trade_sdk::{ @@ -122,8 +125,12 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent) let recent_blockhash = client.infrastructure.rpc.get_latest_blockhash().await?; let amm_info = fetch_amm_info(&client.infrastructure.rpc, trade_info.amm).await?; - let (coin_reserve, pc_reserve) = - get_multi_token_balances(&client.infrastructure.rpc, &amm_info.token_coin, &amm_info.token_pc).await?; + let (coin_reserve, pc_reserve) = get_multi_token_balances( + &client.infrastructure.rpc, + &amm_info.token_coin, + &amm_info.token_pc, + ) + .await?; let mint_pubkey = if amm_info.pc_mint == sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT || amm_info.pc_mint == sol_trade_sdk::constants::USDC_TOKEN_ACCOUNT { @@ -142,14 +149,7 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent) ); let gas_fee_strategy = sol_trade_sdk::common::GasFeeStrategy::new(); - gas_fee_strategy.set_global_fee_strategy( - 150000, - 150000, - 500000, - 500000, - 0.001, - 0.001, - ); + gas_fee_strategy.set_global_fee_strategy(150000, 150000, 500000, 500000, 0.001, 0.001); // Buy tokens println!("Buying tokens from Raydium_amm_v4..."); @@ -194,7 +194,9 @@ async fn raydium_amm_v4_copy_trade_with_grpc(trade_info: RaydiumAmmV4SwapEvent) let amount_token = balance.amount.parse::().unwrap(); println!("Selling {} tokens", amount_token); - let params = RaydiumAmmV4Params::from_amm_address_by_rpc(&client.infrastructure.rpc, trade_info.amm).await?; + let params = + RaydiumAmmV4Params::from_amm_address_by_rpc(&client.infrastructure.rpc, trade_info.amm) + .await?; let sell_params = sol_trade_sdk::TradeSellParams { dex_type: DexType::RaydiumAmmV4, output_token_type: if is_wsol { TradeTokenType::WSOL } else { TradeTokenType::USDC }, diff --git a/examples/raydium_cpmm_trading/src/main.rs b/examples/raydium_cpmm_trading/src/main.rs index 2b36f4c..bbe1bd5 100644 --- a/examples/raydium_cpmm_trading/src/main.rs +++ b/examples/raydium_cpmm_trading/src/main.rs @@ -1,7 +1,7 @@ use sol_trade_sdk::common::spl_associated_token_account::get_associated_token_address; use sol_trade_sdk::common::TradeConfig; -use sol_trade_sdk::constants::{WSOL_TOKEN_ACCOUNT, USDC_TOKEN_ACCOUNT}; -use sol_trade_sdk::trading::core::params::{RaydiumCpmmParams, DexParamEnum}; +use sol_trade_sdk::constants::{USDC_TOKEN_ACCOUNT, WSOL_TOKEN_ACCOUNT}; +use sol_trade_sdk::trading::core::params::{DexParamEnum, RaydiumCpmmParams}; use sol_trade_sdk::trading::factory::DexType; use sol_trade_sdk::TradeTokenType; use sol_trade_sdk::{common::AnyResult, swqos::SwqosConfig, SolanaTrade}; @@ -132,9 +132,12 @@ async fn raydium_cpmm_copy_trade_with_grpc(trade_info: RaydiumCpmmSwapEvent) -> let gas_fee_strategy = sol_trade_sdk::common::GasFeeStrategy::new(); gas_fee_strategy.set_global_fee_strategy(150000, 150000, 500000, 500000, 0.001, 0.001); - let buy_params = - RaydiumCpmmParams::from_pool_address_by_rpc(&client.infrastructure.rpc, &trade_info.pool_state).await?; - + let buy_params = RaydiumCpmmParams::from_pool_address_by_rpc( + &client.infrastructure.rpc, + &trade_info.pool_state, + ) + .await?; + let is_wsol = trade_info.input_token_mint == sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT || trade_info.output_token_mint == sol_trade_sdk::constants::WSOL_TOKEN_ACCOUNT; @@ -173,8 +176,11 @@ async fn raydium_cpmm_copy_trade_with_grpc(trade_info: RaydiumCpmmSwapEvent) -> println!("Balance: {:?}", balance); let amount_token = balance.amount.parse::().unwrap(); - let sell_params = - RaydiumCpmmParams::from_pool_address_by_rpc(&client.infrastructure.rpc, &trade_info.pool_state).await?; + let sell_params = RaydiumCpmmParams::from_pool_address_by_rpc( + &client.infrastructure.rpc, + &trade_info.pool_state, + ) + .await?; println!("Selling {} tokens", amount_token); let sell_params = sol_trade_sdk::TradeSellParams { diff --git a/examples/seed_trading/src/main.rs b/examples/seed_trading/src/main.rs index 6da5c5a..8caa956 100644 --- a/examples/seed_trading/src/main.rs +++ b/examples/seed_trading/src/main.rs @@ -3,7 +3,10 @@ use sol_trade_sdk::{ fast_fn::get_associated_token_address_with_program_id_fast_use_seed, AnyResult, TradeConfig, }, swqos::SwqosConfig, - trading::{core::params::{PumpSwapParams, DexParamEnum}, factory::DexType}, + trading::{ + core::params::{DexParamEnum, PumpSwapParams}, + factory::DexType, + }, SolanaTrade, TradeTokenType, }; use solana_commitment_config::CommitmentConfig; diff --git a/examples/trading_client/src/main.rs b/examples/trading_client/src/main.rs index c840562..de85803 100644 --- a/examples/trading_client/src/main.rs +++ b/examples/trading_client/src/main.rs @@ -48,17 +48,21 @@ async fn create_trading_client_simple() -> AnyResult { SwqosConfig::FlashBlock("your_api_token".to_string(), SwqosRegion::Frankfurt, None), SwqosConfig::Node1("your_api_token".to_string(), SwqosRegion::Frankfurt, None, None), SwqosConfig::BlockRazor("your_api_token".to_string(), SwqosRegion::Frankfurt, None), - SwqosConfig::Astralane("your_api_token".to_string(), SwqosRegion::Frankfurt, None, Some(SwqosTransport::Quic)), // QUIC; use None for HTTP + SwqosConfig::Astralane( + "your_api_token".to_string(), + SwqosRegion::Frankfurt, + None, + Some(SwqosTransport::Quic), + ), // QUIC; use None for HTTP // Helius Sender: 4th param swqos_only Some(true) => min tip 0.000005 SOL; None => 0.0002 SOL SwqosConfig::Helius("".to_string(), SwqosRegion::Default, None, Some(true)), ]; // Optional: Customize WSOL ATA and Seed optimization settings - let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment) - .with_wsol_ata_config( - true, // create_wsol_ata_on_startup: Check and create WSOL ATA on startup - true, // use_seed_optimize: Enable seed optimization for all ATA operations - ); + let trade_config = TradeConfig::new(rpc_url, swqos_configs, commitment).with_wsol_ata_config( + true, // create_wsol_ata_on_startup: Check and create WSOL ATA on startup + true, // use_seed_optimize: Enable seed optimization for all ATA operations + ); // Creates new infrastructure internally let client = TradingClient::new(Arc::new(payer), trade_config).await; diff --git a/examples/wsol_wrapper/src/main.rs b/examples/wsol_wrapper/src/main.rs index 2e4c789..dfbc8b8 100644 --- a/examples/wsol_wrapper/src/main.rs +++ b/examples/wsol_wrapper/src/main.rs @@ -38,7 +38,10 @@ async fn main() -> Result<(), Box> { // Example 2: Unwrap half of the WSOL back to SOL using seed account println!("\n🔄 Example 2: Unwrapping half of WSOL back to SOL using seed account"); let unwrap_amount = wrap_amount / 2; // Half of the wrapped amount - println!("Unwrapping {} lamports (0.0005 SOL) back to SOL using seed account...", unwrap_amount); + println!( + "Unwrapping {} lamports (0.0005 SOL) back to SOL using seed account...", + unwrap_amount + ); match solana_trade.wrap_wsol_to_sol(unwrap_amount).await { Ok(signature) => { diff --git a/src/common/fast_fn.rs b/src/common/fast_fn.rs index f7e0a1b..78e806a 100644 --- a/src/common/fast_fn.rs +++ b/src/common/fast_fn.rs @@ -46,7 +46,10 @@ static INSTRUCTION_CACHE: Lazy /// Get cached instruction, compute and cache if not exists (lock-free) /// 🚀 返回 Arc 避免每次调用克隆整个 Vec #[inline] -pub fn get_cached_instructions(cache_key: InstructionCacheKey, compute_fn: F) -> Arc> +pub fn get_cached_instructions( + cache_key: InstructionCacheKey, + compute_fn: F, +) -> Arc> where F: FnOnce() -> Vec, { @@ -63,10 +66,7 @@ where }; // Lock-free cache lookup with entry API - INSTRUCTION_CACHE - .entry(cache_key) - .or_insert_with(|| Arc::new(compute_fn())) - .clone() + INSTRUCTION_CACHE.entry(cache_key).or_insert_with(|| Arc::new(compute_fn())).clone() } // --------------------- Associated Token Account --------------------- @@ -141,7 +141,7 @@ pub fn _create_associated_token_account_idempotent_fast( }] }) }; - + // 🚀 性能优化:尝试零开销解包 Arc,如果引用计数=1则直接移出,否则克隆 Arc::try_unwrap(arc_instructions).unwrap_or_else(|arc| (*arc).clone()) } diff --git a/src/common/fast_timing.rs b/src/common/fast_timing.rs index 9b9cf3e..92b9b61 100644 --- a/src/common/fast_timing.rs +++ b/src/common/fast_timing.rs @@ -2,9 +2,9 @@ //! //! 使用 syscall_bypass 提供的快速时间戳避免频繁的系统调用 -use std::time::{Duration, Instant}; -use once_cell::sync::Lazy; use crate::perf::syscall_bypass::SystemCallBypassManager; +use once_cell::sync::Lazy; +use std::time::{Duration, Instant}; /// 全局快速时间提供器 static FAST_TIMER: Lazy = Lazy::new(|| FastTimer::new()); @@ -26,11 +26,7 @@ impl FastTimer { let base_instant = Instant::now(); let base_nanos = bypass_manager.fast_timestamp_nanos(); - Self { - bypass_manager, - _base_instant: base_instant, - _base_nanos: base_nanos, - } + Self { bypass_manager, _base_instant: base_instant, _base_nanos: base_nanos } } /// 🚀 获取当前时间戳(纳秒) - 使用快速系统调用绕过 @@ -107,10 +103,7 @@ impl FastStopwatch { /// 创建并启动计时器 #[inline(always)] pub fn start(label: &'static str) -> Self { - Self { - start_nanos: fast_now_nanos(), - label, - } + Self { start_nanos: fast_now_nanos(), label } } /// 获取已耗时(纳秒) diff --git a/src/common/mod.rs b/src/common/mod.rs index 24b25d9..c46a2e6 100755 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -2,11 +2,11 @@ pub mod address_lookup; pub mod bonding_curve; pub mod clock; pub mod fast_fn; -pub mod sdk_log; pub mod fast_timing; pub mod gas_fee_strategy; pub mod global; pub mod nonce_cache; +pub mod sdk_log; pub mod seed; pub mod spl_associated_token_account; pub mod spl_token; diff --git a/src/common/seed.rs b/src/common/seed.rs index cecc8d7..e308347 100644 --- a/src/common/seed.rs +++ b/src/common/seed.rs @@ -1,13 +1,13 @@ use crate::common::SolanaRpcClient; use anyhow::anyhow; use fnv::FnvHasher; +use once_cell::sync::Lazy; use solana_sdk::{instruction::Instruction, pubkey::Pubkey}; use solana_system_interface::instruction::create_account_with_seed; use std::hash::Hasher; -use std::sync::Arc; use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Arc; use tokio::time::{sleep, Duration}; -use once_cell::sync::Lazy; // 🚀 优化:使用 AtomicU64 替代 RwLock,性能提升 5-10x // u64::MAX 表示未初始化状态 @@ -17,7 +17,7 @@ static SPL_TOKEN_2022_RENT: Lazy = Lazy::new(|| AtomicU64::new(u64::M /// 更新租金缓存(后台任务调用) pub async fn update_rents(client: &SolanaRpcClient) -> Result<(), anyhow::Error> { let rent = fetch_rent_for_token_account(client, false).await?; - SPL_TOKEN_RENT.store(rent, Ordering::Release); // Release 确保其他线程可见 + SPL_TOKEN_RENT.store(rent, Ordering::Release); // Release 确保其他线程可见 let rent = fetch_rent_for_token_account(client, true).await?; SPL_TOKEN_2022_RENT.store(rent, Ordering::Release); @@ -62,11 +62,15 @@ pub fn create_associated_token_account_use_seed( // Relaxed: 租金值不变,无需同步;Release/Acquire 在 update_rents 保证初始化可见性 let rent = if is_2022_token { let v = SPL_TOKEN_2022_RENT.load(Ordering::Relaxed); - if v == u64::MAX { return Err(anyhow!("Rent not initialized")); } + if v == u64::MAX { + return Err(anyhow!("Rent not initialized")); + } v } else { let v = SPL_TOKEN_RENT.load(Ordering::Relaxed); - if v == u64::MAX { return Err(anyhow!("Rent not initialized")); } + if v == u64::MAX { + return Err(anyhow!("Rent not initialized")); + } v }; diff --git a/src/common/spl_token.rs b/src/common/spl_token.rs index c094394..6e6f4fc 100644 --- a/src/common/spl_token.rs +++ b/src/common/spl_token.rs @@ -52,11 +52,7 @@ pub fn transfer( accounts.push(AccountMeta::new_readonly(**signer, true)); } - Ok(Instruction { - program_id: *token_program_id, - accounts, - data, - }) + Ok(Instruction { program_id: *token_program_id, accounts, data }) } pub fn initialize_account3( diff --git a/src/common/types.rs b/src/common/types.rs index baa9373..f50fd99 100755 --- a/src/common/types.rs +++ b/src/common/types.rs @@ -17,11 +17,7 @@ impl InfrastructureConfig { swqos_configs: Vec, commitment: CommitmentConfig, ) -> Self { - Self { - rpc_url, - swqos_configs, - commitment, - } + Self { rpc_url, swqos_configs, commitment } } /// Create from TradeConfig (extract infrastructure-only settings) @@ -94,11 +90,11 @@ impl TradeConfig { rpc_url, swqos_configs, commitment, - create_wsol_ata_on_startup: true, // default: check and create on startup - use_seed_optimize: true, // default: use seed optimization - use_core_affinity: true, // default: pin parallel submit tasks to cores - log_enabled: true, // default: enable all SDK logs - check_min_tip: false, // default: skip min tip check to reduce latency + create_wsol_ata_on_startup: true, // default: check and create on startup + use_seed_optimize: true, // default: use seed optimization + use_core_affinity: true, // default: pin parallel submit tasks to cores + log_enabled: true, // default: enable all SDK logs + check_min_tip: false, // default: skip min tip check to reduce latency } } diff --git a/src/constants/accounts.rs b/src/constants/accounts.rs index 06db02c..0f42882 100644 --- a/src/constants/accounts.rs +++ b/src/constants/accounts.rs @@ -43,8 +43,7 @@ pub const USD1_TOKEN_ACCOUNT_META: solana_sdk::instruction::AccountMeta = }; // USDC (mainnet) mint and meta -pub const USDC_TOKEN_ACCOUNT: Pubkey = - pubkey!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); +pub const USDC_TOKEN_ACCOUNT: Pubkey = pubkey!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); pub const USDC_TOKEN_ACCOUNT_META: solana_sdk::instruction::AccountMeta = solana_sdk::instruction::AccountMeta { pubkey: USDC_TOKEN_ACCOUNT, diff --git a/src/constants/swqos.rs b/src/constants/swqos.rs index 40d08ee..eccfb6a 100755 --- a/src/constants/swqos.rs +++ b/src/constants/swqos.rs @@ -1,7 +1,6 @@ use solana_program::pubkey; use solana_sdk::pubkey::Pubkey; - pub const JITO_TIP_ACCOUNTS: &[Pubkey] = &[ pubkey!("96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5"), pubkey!("HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe"), @@ -165,13 +164,13 @@ pub const SPEEDLANDING_TIP_ACCOUNTS: &[Pubkey] = &[ // Default, pub const SWQOS_ENDPOINTS_JITO: [&str; 8] = [ - "https://ny.mainnet.block-engine.jito.wtf", + "https://ny.mainnet.block-engine.jito.wtf", "https://frankfurt.mainnet.block-engine.jito.wtf", "https://amsterdam.mainnet.block-engine.jito.wtf", "https://slc.mainnet.block-engine.jito.wtf", "https://tokyo.mainnet.block-engine.jito.wtf", "https://london.mainnet.block-engine.jito.wtf", - "https://ny.mainnet.block-engine.jito.wtf", + "https://ny.mainnet.block-engine.jito.wtf", "https://mainnet.block-engine.jito.wtf", ]; @@ -180,8 +179,8 @@ pub const SWQOS_ENDPOINTS_NEXTBLOCK: [&str; 8] = [ "http://frankfurt.nextblock.io", "http://amsterdam.nextblock.io", "http://slc.nextblock.io", - "http://tokyo.nextblock.io", - "http://london.nextblock.io", + "http://tokyo.nextblock.io", + "http://london.nextblock.io", "http://singapore.nextblock.io", "http://frankfurt.nextblock.io", ]; @@ -236,11 +235,11 @@ pub const SWQOS_ENDPOINTS_NODE1_QUIC: [&str; 8] = [ "ny.node1.me:16666", "fra.node1.me:16666", "ams.node1.me:16666", - "ny.node1.me:16666", // SLC → ny + "ny.node1.me:16666", // SLC → ny "tk.node1.me:16666", "lon.node1.me:16666", - "ny.node1.me:16666", // LA → ny - "ny.node1.me:16666", // Default → ny + "ny.node1.me:16666", // LA → ny + "ny.node1.me:16666", // Default → ny ]; pub const SWQOS_ENDPOINTS_FLASHBLOCK: [&str; 8] = [ @@ -284,14 +283,14 @@ pub const SWQOS_ENDPOINTS_ASTRALANE: [&str; 8] = [ /// Astralane QUIC endpoints (port 7000). Region order: NewYork, Frankfurt, Amsterdam, SLC, Tokyo, London, LosAngeles, Default. /// See: https://github.com/Astralane/astralane-quic-client. We use fr, ams, la, ny, lim, sg only (avoid ams2/fr2 for lower latency). pub const SWQOS_ENDPOINTS_ASTRALANE_QUIC: [&str; 8] = [ - "ny.gateway.astralane.io:7000", // NewYork - "fr.gateway.astralane.io:7000", // Frankfurt - "ams.gateway.astralane.io:7000", // Amsterdam - "lim.gateway.astralane.io:7000", // SLC (no slc, use lim) - "sg.gateway.astralane.io:7000", // Tokyo (Asia) - "ams.gateway.astralane.io:7000", // London (Europe, avoid ams2) - "la.gateway.astralane.io:7000", // LosAngeles - "lim.gateway.astralane.io:7000", // Default + "ny.gateway.astralane.io:7000", // NewYork + "fr.gateway.astralane.io:7000", // Frankfurt + "ams.gateway.astralane.io:7000", // Amsterdam + "lim.gateway.astralane.io:7000", // SLC (no slc, use lim) + "sg.gateway.astralane.io:7000", // Tokyo (Asia) + "ams.gateway.astralane.io:7000", // London (Europe, avoid ams2) + "la.gateway.astralane.io:7000", // LosAngeles + "lim.gateway.astralane.io:7000", // Default ]; pub const SWQOS_ENDPOINTS_STELLIUM: [&str; 8] = [ diff --git a/src/constants/trade.rs b/src/constants/trade.rs index 39936eb..eab0c5f 100644 --- a/src/constants/trade.rs +++ b/src/constants/trade.rs @@ -6,4 +6,4 @@ pub mod trade { pub const DEFAULT_SELL_TIP_FEE: f64 = 0.0001; pub const DEFAULT_RPC_UNIT_LIMIT: u32 = 150000; pub const DEFAULT_RPC_UNIT_PRICE: u64 = 500000; -} \ No newline at end of file +} diff --git a/src/instruction/meteora_damm_v2.rs b/src/instruction/meteora_damm_v2.rs index 35d6017..70c3641 100644 --- a/src/instruction/meteora_damm_v2.rs +++ b/src/instruction/meteora_damm_v2.rs @@ -29,8 +29,10 @@ impl InstructionBuilder for MeteoraDammV2InstructionBuilder { .downcast_ref::() .ok_or_else(|| anyhow!("Invalid protocol params for MeteoraDammV2"))?; - let is_wsol = protocol_params.token_a_mint == crate::constants::WSOL_TOKEN_ACCOUNT || protocol_params.token_b_mint == crate::constants::WSOL_TOKEN_ACCOUNT; - let is_usdc = protocol_params.token_a_mint == crate::constants::USDC_TOKEN_ACCOUNT || protocol_params.token_b_mint == crate::constants::USDC_TOKEN_ACCOUNT; + let is_wsol = protocol_params.token_a_mint == crate::constants::WSOL_TOKEN_ACCOUNT + || protocol_params.token_b_mint == crate::constants::WSOL_TOKEN_ACCOUNT; + let is_usdc = protocol_params.token_a_mint == crate::constants::USDC_TOKEN_ACCOUNT + || protocol_params.token_b_mint == crate::constants::USDC_TOKEN_ACCOUNT; if !is_wsol && !is_usdc { return Err(anyhow!("Pool must contain WSOL or USDC")); } @@ -38,7 +40,8 @@ impl InstructionBuilder for MeteoraDammV2InstructionBuilder { // ======================================== // Trade calculation and account address preparation // ======================================== - let is_a_in = protocol_params.token_a_mint == crate::constants::WSOL_TOKEN_ACCOUNT || protocol_params.token_a_mint == crate::constants::USDC_TOKEN_ACCOUNT; + let is_a_in = protocol_params.token_a_mint == crate::constants::WSOL_TOKEN_ACCOUNT + || protocol_params.token_a_mint == crate::constants::USDC_TOKEN_ACCOUNT; let amount_in: u64 = params.input_amount.unwrap_or(0); let minimum_amount_out: u64 = match params.fixed_output_amount { Some(fixed) => fixed, @@ -141,8 +144,10 @@ impl InstructionBuilder for MeteoraDammV2InstructionBuilder { return Err(anyhow!("Token amount is not set")); } - let is_wsol = protocol_params.token_b_mint == crate::constants::WSOL_TOKEN_ACCOUNT || protocol_params.token_a_mint == crate::constants::WSOL_TOKEN_ACCOUNT; - let is_usdc = protocol_params.token_b_mint == crate::constants::USDC_TOKEN_ACCOUNT || protocol_params.token_a_mint == crate::constants::USDC_TOKEN_ACCOUNT; + let is_wsol = protocol_params.token_b_mint == crate::constants::WSOL_TOKEN_ACCOUNT + || protocol_params.token_a_mint == crate::constants::WSOL_TOKEN_ACCOUNT; + let is_usdc = protocol_params.token_b_mint == crate::constants::USDC_TOKEN_ACCOUNT + || protocol_params.token_a_mint == crate::constants::USDC_TOKEN_ACCOUNT; if !is_wsol && !is_usdc { return Err(anyhow!("Pool must contain WSOL or USDC")); } @@ -150,7 +155,8 @@ impl InstructionBuilder for MeteoraDammV2InstructionBuilder { // ======================================== // Trade calculation and account address preparation // ======================================== - let is_a_in = protocol_params.token_b_mint == crate::constants::WSOL_TOKEN_ACCOUNT || protocol_params.token_b_mint == crate::constants::USDC_TOKEN_ACCOUNT; + let is_a_in = protocol_params.token_b_mint == crate::constants::WSOL_TOKEN_ACCOUNT + || protocol_params.token_b_mint == crate::constants::USDC_TOKEN_ACCOUNT; let minimum_amount_out: u64 = match params.fixed_output_amount { Some(fixed) => fixed, None => return Err(anyhow!("fixed_output_amount must be set for MeteoraDammV2 swap")), diff --git a/src/instruction/mod.rs b/src/instruction/mod.rs index 5afd618..f9264b9 100755 --- a/src/instruction/mod.rs +++ b/src/instruction/mod.rs @@ -1,7 +1,7 @@ +pub mod bonk; +pub mod meteora_damm_v2; pub mod pumpfun; pub mod pumpswap; -pub mod bonk; -pub mod raydium_cpmm; pub mod raydium_amm_v4; -pub mod meteora_damm_v2; -pub mod utils; \ No newline at end of file +pub mod raydium_cpmm; +pub mod utils; diff --git a/src/instruction/pumpfun.rs b/src/instruction/pumpfun.rs index e14405f..c223de5 100755 --- a/src/instruction/pumpfun.rs +++ b/src/instruction/pumpfun.rs @@ -10,7 +10,8 @@ use crate::{ instruction::utils::pumpfun::{ accounts, get_bonding_curve_pda, get_bonding_curve_v2_pda, get_creator, get_mayhem_fee_recipient_meta_random, get_user_volume_accumulator_pda, - global_constants::{self}, BUY_DISCRIMINATOR, BUY_EXACT_SOL_IN_DISCRIMINATOR, SELL_DISCRIMINATOR, + global_constants::{self}, + BUY_DISCRIMINATOR, BUY_EXACT_SOL_IN_DISCRIMINATOR, SELL_DISCRIMINATOR, }, utils::calc::{ common::{calculate_with_slippage_buy, calculate_with_slippage_sell}, @@ -64,8 +65,9 @@ impl InstructionBuilder for PumpFunInstructionBuilder { ); let bonding_curve_addr = if bonding_curve.account == Pubkey::default() { - get_bonding_curve_pda(¶ms.output_mint) - .ok_or_else(|| anyhow!("bonding_curve PDA derivation failed for mint {}", params.output_mint))? + get_bonding_curve_pda(¶ms.output_mint).ok_or_else(|| { + anyhow!("bonding_curve PDA derivation failed for mint {}", params.output_mint) + })? } else { bonding_curve.account }; @@ -147,8 +149,9 @@ impl InstructionBuilder for PumpFunInstructionBuilder { global_constants::FEE_RECIPIENT_META }; - let bonding_curve_v2 = get_bonding_curve_v2_pda(¶ms.output_mint) - .ok_or_else(|| anyhow!("bonding_curve_v2 PDA derivation failed for mint {}", params.output_mint))?; + let bonding_curve_v2 = get_bonding_curve_v2_pda(¶ms.output_mint).ok_or_else(|| { + anyhow!("bonding_curve_v2 PDA derivation failed for mint {}", params.output_mint) + })?; let mut accounts: Vec = vec![ global_constants::GLOBAL_ACCOUNT_META, fee_recipient_meta, @@ -169,11 +172,7 @@ impl InstructionBuilder for PumpFunInstructionBuilder { ]; accounts.push(AccountMeta::new_readonly(bonding_curve_v2, false)); // remainingAccounts: @pump-fun/pump-sdk 要求末尾传 bondingCurveV2Pda(mint),勿删 - instructions.push(Instruction::new_with_bytes( - accounts::PUMPFUN, - &buy_data, - accounts, - )); + instructions.push(Instruction::new_with_bytes(accounts::PUMPFUN, &buy_data, accounts)); Ok(instructions) } @@ -220,8 +219,9 @@ impl InstructionBuilder for PumpFunInstructionBuilder { }; let bonding_curve_addr = if bonding_curve.account == Pubkey::default() { - get_bonding_curve_pda(¶ms.input_mint) - .ok_or_else(|| anyhow!("bonding_curve PDA derivation failed for mint {}", params.input_mint))? + get_bonding_curve_pda(¶ms.input_mint).ok_or_else(|| { + anyhow!("bonding_curve PDA derivation failed for mint {}", params.input_mint) + })? } else { bonding_curve.account }; @@ -290,20 +290,18 @@ impl InstructionBuilder for PumpFunInstructionBuilder { // Cashback: Bonding Curve Sell expects UserVolumeAccumulator PDA at 0th remaining account (writable) if bonding_curve.is_cashback_coin { - let user_volume_accumulator = get_user_volume_accumulator_pda(¶ms.payer.pubkey()) - .ok_or_else(|| anyhow!("user_volume_accumulator PDA derivation failed"))?; + let user_volume_accumulator = + get_user_volume_accumulator_pda(¶ms.payer.pubkey()) + .ok_or_else(|| anyhow!("user_volume_accumulator PDA derivation failed"))?; accounts.push(AccountMeta::new(user_volume_accumulator, false)); } // remainingAccounts: @pump-fun/pump-sdk sell 要求末尾传 bondingCurveV2Pda(mint)(cashback 时在 user_volume_accumulator 之后),勿删 - let bonding_curve_v2 = get_bonding_curve_v2_pda(¶ms.input_mint) - .ok_or_else(|| anyhow!("bonding_curve_v2 PDA derivation failed for mint {}", params.input_mint))?; + let bonding_curve_v2 = get_bonding_curve_v2_pda(¶ms.input_mint).ok_or_else(|| { + anyhow!("bonding_curve_v2 PDA derivation failed for mint {}", params.input_mint) + })?; accounts.push(AccountMeta::new_readonly(bonding_curve_v2, false)); - instructions.push(Instruction::new_with_bytes( - accounts::PUMPFUN, - &sell_data, - accounts, - )); + instructions.push(Instruction::new_with_bytes(accounts::PUMPFUN, &sell_data, accounts)); // Optional: Close token account if protocol_params.close_token_account_when_sell.unwrap_or(false) @@ -327,15 +325,11 @@ pub fn claim_cashback_pumpfun_instruction(payer: &Pubkey) -> Option const CLAIM_CASHBACK_DISCRIMINATOR: [u8; 8] = [37, 58, 35, 126, 190, 53, 228, 197]; let user_volume_accumulator = get_user_volume_accumulator_pda(payer)?; let accounts = vec![ - AccountMeta::new(*payer, true), // user (signer, writable) + AccountMeta::new(*payer, true), // user (signer, writable) AccountMeta::new(user_volume_accumulator, false), // user_volume_accumulator (writable, not signer) crate::constants::SYSTEM_PROGRAM_META, accounts::EVENT_AUTHORITY_META, accounts::PUMPFUN_META, ]; - Some(Instruction::new_with_bytes( - accounts::PUMPFUN, - &CLAIM_CASHBACK_DISCRIMINATOR, - accounts, - )) + Some(Instruction::new_with_bytes(accounts::PUMPFUN, &CLAIM_CASHBACK_DISCRIMINATOR, accounts)) } diff --git a/src/instruction/pumpswap.rs b/src/instruction/pumpswap.rs index 45f5a87..a5fb596 100755 --- a/src/instruction/pumpswap.rs +++ b/src/instruction/pumpswap.rs @@ -225,11 +225,7 @@ impl InstructionBuilder for PumpSwapInstructionBuilder { buf.to_vec() }; - instructions.push(Instruction { - program_id: accounts::AMM_PROGRAM, - accounts, - data, - }); + instructions.push(Instruction { program_id: accounts::AMM_PROGRAM, accounts, data }); if close_wsol_ata { // Close wSOL ATA account, reclaim rent instructions.extend(crate::trading::common::close_wsol(¶ms.payer.pubkey())); @@ -462,12 +458,12 @@ pub fn claim_cashback_pumpswap_instruction( // IDL order: user, user_volume_accumulator, quote_mint, quote_token_program, // user_volume_accumulator_wsol_token_account, user_wsol_token_account, system_program, event_authority, program let accounts = vec![ - AccountMeta::new(*payer, true), // user (signer, writable) - AccountMeta::new(user_volume_accumulator, false), // user_volume_accumulator (writable) + AccountMeta::new(*payer, true), // user (signer, writable) + AccountMeta::new(user_volume_accumulator, false), // user_volume_accumulator (writable) AccountMeta::new_readonly(quote_mint, false), AccountMeta::new_readonly(quote_token_program, false), - AccountMeta::new(user_volume_accumulator_wsol_ata, false), // writable - AccountMeta::new(user_wsol_ata, false), // writable + AccountMeta::new(user_volume_accumulator_wsol_ata, false), // writable + AccountMeta::new(user_wsol_ata, false), // writable crate::constants::SYSTEM_PROGRAM_META, accounts::EVENT_AUTHORITY_META, accounts::AMM_PROGRAM_META, diff --git a/src/instruction/raydium_amm_v4.rs b/src/instruction/raydium_amm_v4.rs index 822e7bf..a5deeab 100755 --- a/src/instruction/raydium_amm_v4.rs +++ b/src/instruction/raydium_amm_v4.rs @@ -44,7 +44,7 @@ impl InstructionBuilder for RaydiumAmmV4InstructionBuilder { // ======================================== // Trade calculation and account address preparation // ======================================== - let is_base_in = protocol_params.coin_mint == crate::constants::WSOL_TOKEN_ACCOUNT + let is_base_in = protocol_params.coin_mint == crate::constants::WSOL_TOKEN_ACCOUNT || protocol_params.coin_mint == crate::constants::USDC_TOKEN_ACCOUNT; let amount_in: u64 = params.input_amount.unwrap_or(0); let swap_result = compute_swap_amount( @@ -62,7 +62,11 @@ impl InstructionBuilder for RaydiumAmmV4InstructionBuilder { let user_source_token_account = crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed( ¶ms.payer.pubkey(), - if is_wsol { &crate::constants::WSOL_TOKEN_ACCOUNT } else { &crate::constants::USDC_TOKEN_ACCOUNT }, + if is_wsol { + &crate::constants::WSOL_TOKEN_ACCOUNT + } else { + &crate::constants::USDC_TOKEN_ACCOUNT + }, &crate::constants::TOKEN_PROGRAM, params.open_seed_optimize, ); @@ -187,7 +191,11 @@ impl InstructionBuilder for RaydiumAmmV4InstructionBuilder { let user_destination_token_account = crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed( ¶ms.payer.pubkey(), - if is_wsol { &crate::constants::WSOL_TOKEN_ACCOUNT } else { &crate::constants::USDC_TOKEN_ACCOUNT }, + if is_wsol { + &crate::constants::WSOL_TOKEN_ACCOUNT + } else { + &crate::constants::USDC_TOKEN_ACCOUNT + }, &crate::constants::TOKEN_PROGRAM, params.open_seed_optimize, ); diff --git a/src/instruction/raydium_cpmm.rs b/src/instruction/raydium_cpmm.rs index ddab752..1fc780f 100755 --- a/src/instruction/raydium_cpmm.rs +++ b/src/instruction/raydium_cpmm.rs @@ -61,7 +61,7 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { // ======================================== // Trade calculation and account address preparation // ======================================== - let is_base_in = protocol_params.base_mint == crate::constants::WSOL_TOKEN_ACCOUNT + let is_base_in = protocol_params.base_mint == crate::constants::WSOL_TOKEN_ACCOUNT || protocol_params.base_mint == crate::constants::USDC_TOKEN_ACCOUNT; let mint_token_program = if is_base_in { protocol_params.quote_token_program @@ -84,7 +84,11 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { let input_token_account = get_associated_token_address_with_program_id_fast_use_seed( ¶ms.payer.pubkey(), - if is_wsol { &crate::constants::WSOL_TOKEN_ACCOUNT } else { &crate::constants::USDC_TOKEN_ACCOUNT }, + if is_wsol { + &crate::constants::WSOL_TOKEN_ACCOUNT + } else { + &crate::constants::USDC_TOKEN_ACCOUNT + }, &crate::constants::TOKEN_PROGRAM, params.open_seed_optimize, ); @@ -97,10 +101,15 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { let input_vault_account = get_vault_account( &pool_state, - if is_wsol { &crate::constants::WSOL_TOKEN_ACCOUNT } else { &crate::constants::USDC_TOKEN_ACCOUNT }, + if is_wsol { + &crate::constants::WSOL_TOKEN_ACCOUNT + } else { + &crate::constants::USDC_TOKEN_ACCOUNT + }, protocol_params, ); - let output_vault_account = get_vault_account(&pool_state, ¶ms.output_mint, protocol_params); + let output_vault_account = + get_vault_account(&pool_state, ¶ms.output_mint, protocol_params); let observation_state_account = if protocol_params.observation_state == Pubkey::default() { get_observation_state_pda(&pool_state).unwrap() @@ -136,13 +145,17 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { accounts::AUTHORITY_META, // Authority (readonly) AccountMeta::new(protocol_params.amm_config, false), // Amm Config (readonly) AccountMeta::new(pool_state, false), // Pool State - AccountMeta::new(input_token_account, false), // Input Token Account - AccountMeta::new(output_token_account, false), // Output Token Account - AccountMeta::new(input_vault_account, false), // Input Vault Account - AccountMeta::new(output_vault_account, false), // Output Vault Account + AccountMeta::new(input_token_account, false), // Input Token Account + AccountMeta::new(output_token_account, false), // Output Token Account + AccountMeta::new(input_vault_account, false), // Input Vault Account + AccountMeta::new(output_vault_account, false), // Output Vault Account crate::constants::TOKEN_PROGRAM_META, // Input Token Program (readonly) AccountMeta::new_readonly(mint_token_program, false), // Output Token Program (readonly) - if is_wsol { crate::constants::WSOL_TOKEN_ACCOUNT_META } else { crate::constants::USDC_TOKEN_ACCOUNT_META }, // Input token mint (readonly) + if is_wsol { + crate::constants::WSOL_TOKEN_ACCOUNT_META + } else { + crate::constants::USDC_TOKEN_ACCOUNT_META + }, // Input token mint (readonly) AccountMeta::new_readonly(params.output_mint, false), // Output token mint (readonly) AccountMeta::new(observation_state_account, false), // Observation State Account ]; @@ -196,7 +209,7 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { let is_usdc = protocol_params.base_mint == crate::constants::USDC_TOKEN_ACCOUNT || protocol_params.quote_mint == crate::constants::USDC_TOKEN_ACCOUNT; - + if !is_wsol && !is_usdc { return Err(anyhow!("Pool must contain WSOL or USDC")); } @@ -228,7 +241,11 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { let output_token_account = get_associated_token_address_with_program_id_fast_use_seed( ¶ms.payer.pubkey(), - if is_wsol { &crate::constants::WSOL_TOKEN_ACCOUNT } else { &crate::constants::USDC_TOKEN_ACCOUNT }, + if is_wsol { + &crate::constants::WSOL_TOKEN_ACCOUNT + } else { + &crate::constants::USDC_TOKEN_ACCOUNT + }, &crate::constants::TOKEN_PROGRAM, params.open_seed_optimize, ); @@ -241,10 +258,15 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { let output_vault_account = get_vault_account( &pool_state, - if is_wsol { &crate::constants::WSOL_TOKEN_ACCOUNT } else { &crate::constants::USDC_TOKEN_ACCOUNT }, + if is_wsol { + &crate::constants::WSOL_TOKEN_ACCOUNT + } else { + &crate::constants::USDC_TOKEN_ACCOUNT + }, protocol_params, ); - let input_vault_account = get_vault_account(&pool_state, ¶ms.input_mint, protocol_params); + let input_vault_account = + get_vault_account(&pool_state, ¶ms.input_mint, protocol_params); let observation_state_account = if protocol_params.observation_state == Pubkey::default() { get_observation_state_pda(&pool_state).unwrap() @@ -267,14 +289,18 @@ impl InstructionBuilder for RaydiumCpmmInstructionBuilder { accounts::AUTHORITY_META, // Authority (readonly) AccountMeta::new(protocol_params.amm_config, false), // Amm Config (readonly) AccountMeta::new(pool_state, false), // Pool State - AccountMeta::new(input_token_account, false), // Input Token Account - AccountMeta::new(output_token_account, false), // Output Token Account - AccountMeta::new(input_vault_account, false), // Input Vault Account - AccountMeta::new(output_vault_account, false), // Output Vault Account + AccountMeta::new(input_token_account, false), // Input Token Account + AccountMeta::new(output_token_account, false), // Output Token Account + AccountMeta::new(input_vault_account, false), // Input Vault Account + AccountMeta::new(output_vault_account, false), // Output Vault Account AccountMeta::new_readonly(mint_token_program, false), // Input Token Program (readonly) crate::constants::TOKEN_PROGRAM_META, // Output Token Program (readonly) AccountMeta::new_readonly(params.input_mint, false), // Input token mint (readonly) - if is_wsol { crate::constants::WSOL_TOKEN_ACCOUNT_META } else { crate::constants::USDC_TOKEN_ACCOUNT_META }, // Output token mint (readonly) + if is_wsol { + crate::constants::WSOL_TOKEN_ACCOUNT_META + } else { + crate::constants::USDC_TOKEN_ACCOUNT_META + }, // Output token mint (readonly) AccountMeta::new(observation_state_account, false), // Observation State Account ]; // Create instruction data diff --git a/src/instruction/utils/mod.rs b/src/instruction/utils/mod.rs index aac39c5..b7b1d1a 100644 --- a/src/instruction/utils/mod.rs +++ b/src/instruction/utils/mod.rs @@ -1,13 +1,13 @@ pub mod bonk; +pub mod meteora_damm_v2; pub mod pumpfun; pub mod pumpswap; pub mod raydium_amm_v4; pub mod raydium_cpmm; -pub mod meteora_damm_v2; // types pub mod bonk_types; +pub mod meteora_damm_v2_types; pub mod pumpswap_types; pub mod raydium_amm_v4_types; pub mod raydium_cpmm_types; -pub mod meteora_damm_v2_types; \ No newline at end of file diff --git a/src/instruction/utils/pumpfun.rs b/src/instruction/utils/pumpfun.rs index b23e8b6..bdd3f9a 100644 --- a/src/instruction/utils/pumpfun.rs +++ b/src/instruction/utils/pumpfun.rs @@ -178,11 +178,7 @@ pub fn get_mayhem_fee_recipient_meta_random() -> AccountMeta { let recipient = *global_constants::MAYHEM_FEE_RECIPIENTS .choose(&mut rand::rng()) .unwrap_or(&global_constants::MAYHEM_FEE_RECIPIENTS[0]); - AccountMeta { - pubkey: recipient, - is_signer: false, - is_writable: true, - } + AccountMeta { pubkey: recipient, is_signer: false, is_writable: true } } pub struct Symbol; diff --git a/src/instruction/utils/pumpswap.rs b/src/instruction/utils/pumpswap.rs index 1e880c7..6ec0c41 100644 --- a/src/instruction/utils/pumpswap.rs +++ b/src/instruction/utils/pumpswap.rs @@ -169,11 +169,7 @@ pub fn get_mayhem_fee_recipient_random() -> (Pubkey, AccountMeta) { let recipient = *accounts::MAYHEM_FEE_RECIPIENTS .choose(&mut rand::rng()) .unwrap_or(&accounts::MAYHEM_FEE_RECIPIENTS[0]); - let meta = AccountMeta { - pubkey: recipient, - is_signer: false, - is_writable: false, - }; + let meta = AccountMeta { pubkey: recipient, is_signer: false, is_writable: false }; (recipient, meta) } @@ -334,7 +330,7 @@ pub async fn find_by_base_mint( if accounts.is_empty() { return Err(anyhow!("No pool found for mint {}", base_mint)); } - let accounts_count = accounts.len(); // 🔧 保存长度,因为 into_iter() 会消耗 accounts + let accounts_count = accounts.len(); // 🔧 保存长度,因为 into_iter() 会消耗 accounts let mut pools: Vec<_> = accounts .into_iter() .filter_map(|(addr, acc)| { @@ -349,7 +345,11 @@ pub async fn find_by_base_mint( // 🔧 修复:检查过滤后的 pools 是否为空(accounts 可能不为空但解码全部失败) if pools.is_empty() { - return Err(anyhow!("No valid pool decoded for mint {} (found {} accounts but all decode failed)", base_mint, accounts_count)); + return Err(anyhow!( + "No valid pool decoded for mint {} (found {} accounts but all decode failed)", + base_mint, + accounts_count + )); } pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply)); @@ -386,7 +386,7 @@ pub async fn find_by_quote_mint( if accounts.is_empty() { return Err(anyhow!("No pool found for mint {}", quote_mint)); } - let accounts_count = accounts.len(); // 🔧 保存长度,因为 into_iter() 会消耗 accounts + let accounts_count = accounts.len(); // 🔧 保存长度,因为 into_iter() 会消耗 accounts let mut pools: Vec<_> = accounts .into_iter() .filter_map(|(addr, acc)| { @@ -401,7 +401,11 @@ pub async fn find_by_quote_mint( // 🔧 修复:检查过滤后的 pools 是否为空(accounts 可能不为空但解码全部失败) if pools.is_empty() { - return Err(anyhow!("No valid pool decoded for quote_mint {} (found {} accounts but all decode failed)", quote_mint, accounts_count)); + return Err(anyhow!( + "No valid pool decoded for quote_mint {} (found {} accounts but all decode failed)", + quote_mint, + accounts_count + )); } pools.sort_by(|a, b| b.1.lp_supply.cmp(&a.1.lp_supply)); diff --git a/src/instruction/utils/raydium_cpmm.rs b/src/instruction/utils/raydium_cpmm.rs index 7103d20..5c30ff0 100644 --- a/src/instruction/utils/raydium_cpmm.rs +++ b/src/instruction/utils/raydium_cpmm.rs @@ -135,9 +135,11 @@ pub fn get_vault_account( ) -> Pubkey { if protocol_params.base_mint == *token_mint && protocol_params.base_vault != Pubkey::default() { protocol_params.base_vault - } else if protocol_params.quote_mint == *token_mint && protocol_params.quote_vault != Pubkey::default() { + } else if protocol_params.quote_mint == *token_mint + && protocol_params.quote_vault != Pubkey::default() + { protocol_params.quote_vault } else { get_vault_pda(pool_state, token_mint).unwrap() } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 8ffd1aa..90ad258 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ pub mod utils; use crate::common::nonce_cache::DurableNonceInfo; use crate::common::sdk_log; use crate::common::GasFeeStrategy; -use crate::common::{TradeConfig, InfrastructureConfig}; +use crate::common::{InfrastructureConfig, TradeConfig}; #[cfg(feature = "perf-trace")] use crate::constants::trade::trade::DEFAULT_SLIPPAGE; use crate::constants::SOL_TOKEN_ACCOUNT; @@ -22,12 +22,12 @@ use crate::swqos::TradeType; // Re-export for SWQOS HTTP/QUIC choice in SwqosConfig (e.g. Astralane) pub use crate::swqos::SwqosTransport; use crate::trading::core::params::BonkParams; +use crate::trading::core::params::DexParamEnum; use crate::trading::core::params::MeteoraDammV2Params; use crate::trading::core::params::PumpFunParams; use crate::trading::core::params::PumpSwapParams; use crate::trading::core::params::RaydiumAmmV4Params; use crate::trading::core::params::RaydiumCpmmParams; -use crate::trading::core::params::DexParamEnum; use crate::trading::factory::DexType; use crate::trading::MiddlewareManager; use crate::trading::SwapParams; @@ -65,13 +65,8 @@ pub async fn find_pool_by_mint( dex_type: DexType, ) -> Result { match dex_type { - DexType::PumpSwap => { - crate::instruction::utils::pumpswap::find_pool(rpc, mint).await - } - _ => Err(anyhow::anyhow!( - "find_pool_by_mint not implemented for {:?}", - dex_type - )), + DexType::PumpSwap => crate::instruction::utils::pumpswap::find_pool(rpc, mint).await, + _ => Err(anyhow::anyhow!("find_pool_by_mint not implemented for {:?}", dex_type)), } } @@ -180,11 +175,7 @@ impl TradingInfrastructure { } } - Self { - rpc, - swqos_clients, - config, - } + Self { rpc, swqos_clients, config } } } @@ -408,7 +399,9 @@ impl TradingClient { timeout_secs: u64, ) -> Result<(), String> { use solana_sdk::transaction::Transaction; - let recent_blockhash = rpc.get_latest_blockhash().await + let recent_blockhash = rpc + .get_latest_blockhash() + .await .map_err(|e| format!("Failed to get blockhash: {}", e))?; let tx = Transaction::new_signed_with_payer( create_ata_ixs, @@ -419,7 +412,8 @@ impl TradingClient { let send_result = tokio::time::timeout( tokio::time::Duration::from_secs(timeout_secs), rpc.send_and_confirm_transaction(&tx), - ).await; + ) + .await; match send_result { Ok(Ok(_signature)) => Ok(()), Ok(Err(e)) => { @@ -437,12 +431,11 @@ impl TradingClient { const MAX_RETRIES: usize = 3; const TIMEOUT_SECS: u64 = 10; - let wsol_ata = - crate::common::fast_fn::get_associated_token_address_with_program_id_fast( - &payer.pubkey(), - &WSOL_TOKEN_ACCOUNT, - &crate::constants::TOKEN_PROGRAM, - ); + let wsol_ata = crate::common::fast_fn::get_associated_token_address_with_program_id_fast( + &payer.pubkey(), + &WSOL_TOKEN_ACCOUNT, + &crate::constants::TOKEN_PROGRAM, + ); if rpc.get_account(&wsol_ata).await.is_ok() { if sdk_log::sdk_log_enabled() { @@ -451,8 +444,7 @@ impl TradingClient { return; } - let create_ata_ixs = - crate::trading::common::wsol_manager::create_wsol_ata(&payer.pubkey()); + let create_ata_ixs = crate::trading::common::wsol_manager::create_wsol_ata(&payer.pubkey()); if create_ata_ixs.is_empty() { if sdk_log::sdk_log_enabled() { info!(target: "sol_trade_sdk", "ℹ️ WSOL ATA already exists (no need to create)"); @@ -471,7 +463,15 @@ impl TradingClient { } tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; } - match Self::try_create_wsol_ata_once(rpc.as_ref(), payer, &wsol_ata, &create_ata_ixs, TIMEOUT_SECS).await { + match Self::try_create_wsol_ata_once( + rpc.as_ref(), + payer, + &wsol_ata, + &create_ata_ixs, + TIMEOUT_SECS, + ) + .await + { Ok(()) => { if sdk_log::sdk_log_enabled() { info!(target: "sol_trade_sdk", "✅ WSOL ATA created or already exists"); @@ -981,8 +981,10 @@ impl TradingClient { /// - 交易执行或确认失败 /// - 网络或 RPC 错误 pub async fn wrap_wsol_to_sol(&self, amount: u64) -> Result { - use crate::trading::common::wsol_manager::{wrap_wsol_to_sol as wrap_wsol_to_sol_internal, wrap_wsol_to_sol_without_create}; use crate::common::seed::get_associated_token_address_with_program_id_use_seed; + use crate::trading::common::wsol_manager::{ + wrap_wsol_to_sol as wrap_wsol_to_sol_internal, wrap_wsol_to_sol_without_create, + }; use solana_sdk::transaction::Transaction; // 检查临时seed账户是否已存在 @@ -1003,7 +1005,8 @@ impl TradingClient { }; let recent_blockhash = self.infrastructure.rpc.get_latest_blockhash().await?; - let mut transaction = Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey())); + let mut transaction = + Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey())); transaction.sign(&[&*self.payer], recent_blockhash); let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?; Ok(signature.to_string()) @@ -1019,8 +1022,10 @@ impl TradingClient { /// * `Err(anyhow::Error)` - Build or send failure (e.g. invalid PDA) pub async fn claim_cashback_pumpfun(&self) -> Result { use solana_sdk::transaction::Transaction; - let ix = crate::instruction::pumpfun::claim_cashback_pumpfun_instruction(&self.payer.pubkey()) - .ok_or_else(|| anyhow::anyhow!("Failed to build PumpFun claim_cashback instruction"))?; + let ix = crate::instruction::pumpfun::claim_cashback_pumpfun_instruction( + &self.payer.pubkey(), + ) + .ok_or_else(|| anyhow::anyhow!("Failed to build PumpFun claim_cashback instruction"))?; let recent_blockhash = self.infrastructure.rpc.get_latest_blockhash().await?; let mut transaction = Transaction::new_with_payer(&[ix], Some(&self.payer.pubkey())); transaction.sign(&[&*self.payer], recent_blockhash); @@ -1038,21 +1043,24 @@ impl TradingClient { /// * `Err(anyhow::Error)` - Build or send failure pub async fn claim_cashback_pumpswap(&self) -> Result { use solana_sdk::transaction::Transaction; - let mut instructions = crate::common::fast_fn::create_associated_token_account_idempotent_fast_use_seed( - &self.payer.pubkey(), - &self.payer.pubkey(), - &WSOL_TOKEN_ACCOUNT, - &crate::constants::TOKEN_PROGRAM, - self.use_seed_optimize, - ); + let mut instructions = + crate::common::fast_fn::create_associated_token_account_idempotent_fast_use_seed( + &self.payer.pubkey(), + &self.payer.pubkey(), + &WSOL_TOKEN_ACCOUNT, + &crate::constants::TOKEN_PROGRAM, + self.use_seed_optimize, + ); let ix = crate::instruction::pumpswap::claim_cashback_pumpswap_instruction( &self.payer.pubkey(), WSOL_TOKEN_ACCOUNT, crate::constants::TOKEN_PROGRAM, - ).ok_or_else(|| anyhow::anyhow!("Failed to build PumpSwap claim_cashback instruction"))?; + ) + .ok_or_else(|| anyhow::anyhow!("Failed to build PumpSwap claim_cashback instruction"))?; instructions.push(ix); let recent_blockhash = self.infrastructure.rpc.get_latest_blockhash().await?; - let mut transaction = Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey())); + let mut transaction = + Transaction::new_with_payer(&instructions, Some(&self.payer.pubkey())); transaction.sign(&[&*self.payer], recent_blockhash); let signature = self.infrastructure.rpc.send_and_confirm_transaction(&transaction).await?; Ok(signature.to_string()) diff --git a/src/perf/compiler_optimization.rs b/src/perf/compiler_optimization.rs index 7c709d6..37b3fd4 100644 --- a/src/perf/compiler_optimization.rs +++ b/src/perf/compiler_optimization.rs @@ -1,5 +1,5 @@ //! 🚀 编译器级性能优化 - 极致编译时优化 -//! +//! //! 实现编译时的极致性能优化,包括: //! - 编译器标志优化配置 //! - 编译时代码生成 @@ -137,100 +137,110 @@ impl CompilerOptimizer { stats: CompilerOptimizationStats::default(), } } - + /// 🚀 生成超高性能编译配置 pub fn generate_ultra_performance_config(&self) -> Result { tracing::info!(target: "sol_trade_sdk","🚀 Generating ultra-performance compiler configuration..."); - + let mut rustflags = Vec::new(); - + // 基础优化标志 rustflags.push("-C".to_string()); rustflags.push("opt-level=3".to_string()); // 最高优化级别 - + // 链接时优化 if self.optimization_flags.enable_lto { rustflags.push("-C".to_string()); rustflags.push("lto=fat".to_string()); // 胖LTO获得最佳优化 } - + // 目标CPU优化 if !self.optimization_flags.target_cpu.is_empty() { rustflags.push("-C".to_string()); rustflags.push(format!("target-cpu={}", self.optimization_flags.target_cpu)); } - + // 目标特性 if !self.optimization_flags.target_features.is_empty() { rustflags.push("-C".to_string()); - rustflags.push(format!("target-feature={}", self.optimization_flags.target_features.join(","))); + rustflags.push(format!( + "target-feature={}", + self.optimization_flags.target_features.join(",") + )); } - + // 代码模型 rustflags.push("-C".to_string()); - rustflags.push(format!("code-model={:?}", self.optimization_flags.code_model).to_lowercase()); - + rustflags + .push(format!("code-model={:?}", self.optimization_flags.code_model).to_lowercase()); + // 恐慌处理 if self.codegen_config.panic_abort { rustflags.push("-C".to_string()); rustflags.push("panic=abort".to_string()); } - + // 溢出检查 if !self.codegen_config.overflow_checks { rustflags.push("-C".to_string()); rustflags.push("overflow-checks=no".to_string()); } - + // 代码生成单元 if let Some(units) = self.optimization_flags.codegen_units { rustflags.push("-C".to_string()); rustflags.push(format!("codegen-units={}", units)); } - + // 内联阈值 rustflags.push("-C".to_string()); rustflags.push(format!("inline-threshold={}", self.inline_strategy.inline_threshold)); - + // 额外的性能优化标志 rustflags.extend([ - "-C".to_string(), "embed-bitcode=no".to_string(), // 不嵌入位码以减少体积 - "-C".to_string(), "debuginfo=0".to_string(), // 禁用调试信息 - "-C".to_string(), "rpath=no".to_string(), // 禁用rpath - "-C".to_string(), "force-frame-pointers=no".to_string(), // 禁用帧指针 + "-C".to_string(), + "embed-bitcode=no".to_string(), // 不嵌入位码以减少体积 + "-C".to_string(), + "debuginfo=0".to_string(), // 禁用调试信息 + "-C".to_string(), + "rpath=no".to_string(), // 禁用rpath + "-C".to_string(), + "force-frame-pointers=no".to_string(), // 禁用帧指针 ]); - + let config = CompilerConfig { rustflags, env_vars: self.generate_env_vars(), cargo_config: self.generate_cargo_config(), }; - + tracing::info!(target: "sol_trade_sdk","✅ Ultra-performance compiler configuration generated"); Ok(config) } - + /// 生成环境变量配置 fn generate_env_vars(&self) -> HashMap { let mut env_vars = HashMap::new(); - + // CPU特定优化 - env_vars.insert("CARGO_CFG_TARGET_FEATURE".to_string(), - self.optimization_flags.target_features.join(",")); - + env_vars.insert( + "CARGO_CFG_TARGET_FEATURE".to_string(), + self.optimization_flags.target_features.join(","), + ); + // 启用不稳定特性 env_vars.insert("RUSTC_BOOTSTRAP".to_string(), "1".to_string()); - + // 编译缓存设置 if self.optimization_flags.incremental { env_vars.insert("CARGO_INCREMENTAL".to_string(), "1".to_string()); } else { env_vars.insert("CARGO_INCREMENTAL".to_string(), "0".to_string()); } - + env_vars } - + /// 生成Cargo配置 fn generate_cargo_config(&self) -> CargoConfig { CargoConfig { @@ -244,17 +254,21 @@ impl CompilerOptimizer { debug_assertions: false, rpath: false, strip: true, // 去除符号表 - } + }, } } - + /// 获取统计信息 pub fn get_stats(&self) -> CompilerOptimizationStats { CompilerOptimizationStats { inlined_functions: AtomicU64::new(self.stats.inlined_functions.load(Ordering::Relaxed)), constant_folding: AtomicU64::new(self.stats.constant_folding.load(Ordering::Relaxed)), - dead_code_elimination: AtomicU64::new(self.stats.dead_code_elimination.load(Ordering::Relaxed)), - loop_optimizations: AtomicU64::new(self.stats.loop_optimizations.load(Ordering::Relaxed)), + dead_code_elimination: AtomicU64::new( + self.stats.dead_code_elimination.load(Ordering::Relaxed), + ), + loop_optimizations: AtomicU64::new( + self.stats.loop_optimizations.load(Ordering::Relaxed), + ), } } } @@ -279,12 +293,12 @@ impl OptimizationFlags { Self { opt_level: OptLevel::Aggressive, enable_lto: true, - enable_pgo: false, // PGO需要多阶段构建 + enable_pgo: false, // PGO需要多阶段构建 target_cpu: "native".to_string(), // 使用本机CPU特性 target_features, code_model: CodeModel::Small, debug_info: false, - incremental: false, // 发布版本禁用增量编译 + incremental: false, // 发布版本禁用增量编译 codegen_units: Some(1), // 单个代码生成单元获得最佳优化 } } @@ -294,7 +308,7 @@ impl CodegenConfig { /// 超高性能配置 pub fn ultra_performance() -> Self { Self { - panic_abort: true, // 恐慌即中止,避免展开开销 + panic_abort: true, // 恐慌即中止,避免展开开销 overflow_checks: false, // 生产环境禁用溢出检查 fat_lto: true, enable_simd: true, @@ -353,14 +367,14 @@ macro_rules! compile_time_optimize { (const $expr:expr) => { const { $expr } }; - + // 强制内联热路径 (inline_hot $fn_name:ident) => { #[inline(always)] #[hot] $fn_name }; - + // 标记冷路径 (cold $fn_name:ident) => { #[inline(never)] @@ -372,10 +386,10 @@ macro_rules! compile_time_optimize { /// 🚀 零成本抽象特征 pub trait ZeroCostAbstraction { type Output; - + /// 编译时计算 fn compute_at_compile_time(&self) -> Self::Output; - + /// 内联操作 #[inline(always)] fn inline_operation(&self) -> Self::Output { @@ -399,35 +413,35 @@ impl CompileTimeOptimizedEventProcessor { route_table: Self::precompute_route_table(), } } - + /// 编译时预计算哈希表 const fn precompute_hash_table() -> [u64; 256] { let mut table = [0u64; 256]; let mut i = 0; - + while i < 256 { // 使用编译时常量计算哈希值 table[i] = Self::const_hash(i as u8); i += 1; } - + table } - + /// 编译时预计算路由表 const fn precompute_route_table() -> [u32; 1024] { let mut table = [0u32; 1024]; let mut i = 0; - + while i < 1024 { // 预计算路由信息 table[i] = (i as u32) % 16; // 16个工作线程 i += 1; } - + table } - + /// 编译时常量哈希函数 const fn const_hash(input: u8) -> u64 { // 使用简单的编译时常量哈希 @@ -437,16 +451,14 @@ impl CompileTimeOptimizedEventProcessor { hash ^= hash << 17; hash } - + /// 🚀 零开销事件路由 #[inline(always)] pub fn route_event_zero_cost(&self, event_id: u8) -> u32 { // 编译时优化:直接数组访问,无边界检查 - unsafe { - *self.route_table.get_unchecked((event_id as usize) & 1023) - } + unsafe { *self.route_table.get_unchecked((event_id as usize) & 1023) } } - + /// 🚀 编译时优化的哈希查找 #[inline(always)] pub fn hash_lookup_optimized(&self, key: u8) -> u64 { @@ -464,28 +476,28 @@ impl SIMDCompileTimeOptimizer { #[target_feature(enable = "avx2")] pub unsafe fn vectorized_sum_compile_time(data: &[u64]) -> u64 { use std::arch::x86_64::*; - + if data.len() < 4 { return data.iter().sum(); } - + let chunks = data.len() / 4; let mut sum_vec = _mm256_setzero_si256(); - + for i in 0..chunks { let ptr = data.as_ptr().add(i * 4) as *const __m256i; let vec = _mm256_loadu_si256(ptr); sum_vec = _mm256_add_epi64(sum_vec, vec); } - + // 水平求和 let mut result = [0u64; 4]; _mm256_storeu_si256(result.as_mut_ptr() as *mut __m256i, sum_vec); let partial_sum: u64 = result.iter().sum(); - + // 处理剩余元素 let remaining: u64 = data[chunks * 4..].iter().sum(); - + partial_sum + remaining } @@ -523,7 +535,8 @@ fn main() { println!("cargo:rustc-link-arg=-fprofile-use"); } } -"#.to_string() +"# + .to_string() } /// 🚀 生成.cargo/config.toml @@ -576,56 +589,58 @@ rustflags = [ rustflags = [ "-C", "target-feature=+sse4.2,+avx,+avx2,+fma,+bmi1,+bmi2,+lzcnt,+popcnt", ] -"#.to_string() +"# + .to_string() } #[cfg(test)] mod tests { use super::*; - + #[test] fn test_compiler_optimizer_creation() { let optimizer = CompilerOptimizer::new(); assert!(optimizer.optimization_flags.enable_lto); assert_eq!(optimizer.optimization_flags.opt_level as u8, OptLevel::Aggressive as u8); } - + #[test] fn test_compile_time_processor() { - const PROCESSOR: CompileTimeOptimizedEventProcessor = CompileTimeOptimizedEventProcessor::new(); - + const PROCESSOR: CompileTimeOptimizedEventProcessor = + CompileTimeOptimizedEventProcessor::new(); + let route = PROCESSOR.route_event_zero_cost(42); assert!(route < 16); // 应该路由到16个工作线程之一 - + let hash = PROCESSOR.hash_lookup_optimized(100); assert!(hash > 0); // 哈希值应该非零 } - + #[test] fn test_ultra_performance_config() { let flags = OptimizationFlags::ultra_performance(); assert!(flags.enable_lto); assert_eq!(flags.target_cpu, "native"); assert!(!flags.target_features.is_empty()); - + let codegen = CodegenConfig::ultra_performance(); assert!(codegen.panic_abort); assert!(!codegen.overflow_checks); assert!(codegen.enable_simd); } - - #[test] + + #[test] fn test_compiler_config_generation() { let optimizer = CompilerOptimizer::new(); let config = optimizer.generate_ultra_performance_config().unwrap(); - + assert!(!config.rustflags.is_empty()); assert!(config.rustflags.contains(&"-C".to_string())); assert!(config.rustflags.contains(&"opt-level=3".to_string())); - + assert!(config.env_vars.contains_key("CARGO_INCREMENTAL")); } - + #[test] fn test_simd_compile_time_optimization() { #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] @@ -642,7 +657,7 @@ mod tests { assert_eq!(sum, 36); // 1+2+3+4+5+6+7+8 = 36 } } - + #[test] fn test_build_script_generation() { let build_script = generate_build_script(); @@ -650,7 +665,7 @@ mod tests { assert!(build_script.contains("TARGET_FEATURE")); assert!(build_script.contains("lld")); } - + #[test] fn test_cargo_config_generation() { let config = generate_cargo_config_toml(); @@ -659,4 +674,4 @@ mod tests { assert!(config.contains("target-cpu=native")); assert!(config.contains("panic = \"abort\"")); } -} \ No newline at end of file +} diff --git a/src/perf/hardware_optimizations.rs b/src/perf/hardware_optimizations.rs index 4966cfb..368c24b 100644 --- a/src/perf/hardware_optimizations.rs +++ b/src/perf/hardware_optimizations.rs @@ -1,11 +1,11 @@ //! Hardware-oriented optimizations: cache-line alignment, prefetch, SIMD, branch hints, memory barriers. //! 硬件级优化:缓存行对齐与预取、SIMD、分支提示、内存屏障。 -use std::sync::atomic::{AtomicU64, Ordering}; +use anyhow::Result; +use crossbeam_utils::CachePadded; use std::mem::size_of; use std::ptr; -use crossbeam_utils::CachePadded; -use anyhow::Result; +use std::sync::atomic::{AtomicU64, Ordering}; /// Typical CPU cache line size in bytes. 典型 CPU 缓存行大小(字节)。 pub const CACHE_LINE_SIZE: usize = 64; @@ -32,7 +32,7 @@ impl SIMDMemoryOps { _ => Self::memcpy_avx512_or_fallback(dst, src, len), } } - + /// Copy 1–8 bytes (scalar / small word). 小数据拷贝(1–8 字节)。 #[inline(always)] unsafe fn memcpy_small(dst: *mut u8, src: *const u8, len: usize) { @@ -53,52 +53,52 @@ impl SIMDMemoryOps { _ => unreachable!(), } } - + /// Copy 9–16 bytes using SSE (128-bit). SSE 拷贝(9–16 字节)。 #[inline(always)] unsafe fn memcpy_sse(dst: *mut u8, src: *const u8, len: usize) { #[cfg(target_arch = "x86_64")] { use std::arch::x86_64::{__m128i, _mm_loadu_si128, _mm_storeu_si128}; - + if len <= 16 { let chunk = _mm_loadu_si128(src as *const __m128i); _mm_storeu_si128(dst as *mut __m128i, chunk); } } - + #[cfg(not(target_arch = "x86_64"))] { ptr::copy_nonoverlapping(src, dst, len); } } - + /// Copy 17–32 bytes using AVX (256-bit). AVX 拷贝(17–32 字节)。 #[inline(always)] unsafe fn memcpy_avx(dst: *mut u8, src: *const u8, len: usize) { #[cfg(target_arch = "x86_64")] { use std::arch::x86_64::{__m256i, _mm256_loadu_si256, _mm256_storeu_si256}; - + if len <= 32 { let chunk = _mm256_loadu_si256(src as *const __m256i); _mm256_storeu_si256(dst as *mut __m256i, chunk); } } - + #[cfg(not(target_arch = "x86_64"))] { ptr::copy_nonoverlapping(src, dst, len); } } - + /// Copy 33–64 bytes using AVX2 (256-bit, two chunks). AVX2 拷贝(33–64 字节,两段)。 #[inline(always)] unsafe fn memcpy_avx2(dst: *mut u8, src: *const u8, len: usize) { #[cfg(target_arch = "x86_64")] { use std::arch::x86_64::{__m256i, _mm256_loadu_si256, _mm256_storeu_si256}; - + let chunk1 = _mm256_loadu_si256(src as *const __m256i); _mm256_storeu_si256(dst as *mut __m256i, chunk1); if len > 32 { @@ -109,52 +109,52 @@ impl SIMDMemoryOps { } } } - + #[cfg(not(target_arch = "x86_64"))] { ptr::copy_nonoverlapping(src, dst, len); } } - + /// Copy >64 bytes: AVX-512 64-byte chunks when available, else AVX2 32-byte chunks. >64 字节:有 AVX512 用 64 字节块,否则 AVX2 32 字节块。 #[inline(always)] unsafe fn memcpy_avx512_or_fallback(dst: *mut u8, src: *const u8, len: usize) { #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))] { use std::arch::x86_64::{__m512i, _mm512_loadu_si512, _mm512_storeu_si512}; - + let chunks = len / 64; let mut offset = 0; - + for _ in 0..chunks { let chunk = _mm512_loadu_si512(src.add(offset) as *const __m512i); _mm512_storeu_si512(dst.add(offset) as *mut __m512i, chunk); offset += 64; } - + let remaining = len % 64; if remaining > 0 { Self::memcpy_avx2(dst.add(offset), src.add(offset), remaining); } } - + #[cfg(not(all(target_arch = "x86_64", target_feature = "avx512f")))] { let chunks = len / 32; let mut offset = 0; - + for _ in 0..chunks { Self::memcpy_avx2(dst.add(offset), src.add(offset), 32); offset += 32; } - + let remaining = len % 32; if remaining > 0 { Self::memcpy_avx(dst.add(offset), src.add(offset), remaining); } } } - + /// SIMD-optimized byte equality; dispatches by length (small / SSE / AVX2 / large). SIMD 加速的内存比较,按长度分派。 #[inline(always)] pub unsafe fn memcmp_simd_optimized(a: *const u8, b: *const u8, len: usize) -> bool { @@ -166,109 +166,108 @@ impl SIMDMemoryOps { _ => Self::memcmp_large(a, b, len), } } - + /// Compare 1–8 bytes (scalar). 小数据比较(1–8 字节)。 #[inline(always)] unsafe fn memcmp_small(a: *const u8, b: *const u8, len: usize) -> bool { match len { 1 => *a == *b, 2 => *(a as *const u16) == *(b as *const u16), - 3 => { - *(a as *const u16) == *(b as *const u16) && - *a.add(2) == *b.add(2) - } + 3 => *(a as *const u16) == *(b as *const u16) && *a.add(2) == *b.add(2), 4 => *(a as *const u32) == *(b as *const u32), 5..=8 => *(a as *const u64) == *(b as *const u64), _ => unreachable!(), } } - + /// Compare 9–16 bytes using SSE. SSE 比较(9–16 字节)。 #[inline(always)] unsafe fn memcmp_sse(a: *const u8, b: *const u8, len: usize) -> bool { #[cfg(target_arch = "x86_64")] { - use std::arch::x86_64::{__m128i, _mm_loadu_si128, _mm_cmpeq_epi8, _mm_movemask_epi8}; - + use std::arch::x86_64::{__m128i, _mm_cmpeq_epi8, _mm_loadu_si128, _mm_movemask_epi8}; + let chunk_a = _mm_loadu_si128(a as *const __m128i); let chunk_b = _mm_loadu_si128(b as *const __m128i); let cmp_result = _mm_cmpeq_epi8(chunk_a, chunk_b); let mask = _mm_movemask_epi8(cmp_result) as u32; - + let valid_mask = if len >= 16 { 0xFFFF } else { (1u32 << len) - 1 }; (mask & valid_mask) == valid_mask } - + #[cfg(not(target_arch = "x86_64"))] { (0..len).all(|i| *a.add(i) == *b.add(i)) } } - + /// Compare 17–32 bytes using AVX2. AVX2 比较(17–32 字节)。 #[inline(always)] unsafe fn memcmp_avx2(a: *const u8, b: *const u8, len: usize) -> bool { #[cfg(target_arch = "x86_64")] { - use std::arch::x86_64::{__m256i, _mm256_loadu_si256, _mm256_cmpeq_epi8, _mm256_movemask_epi8}; - + use std::arch::x86_64::{ + __m256i, _mm256_cmpeq_epi8, _mm256_loadu_si256, _mm256_movemask_epi8, + }; + let chunk_a = _mm256_loadu_si256(a as *const __m256i); let chunk_b = _mm256_loadu_si256(b as *const __m256i); let cmp_result = _mm256_cmpeq_epi8(chunk_a, chunk_b); let mask = _mm256_movemask_epi8(cmp_result) as u32; - + let valid_mask = if len >= 32 { 0xFFFFFFFF } else { (1u32 << len) - 1 }; (mask & valid_mask) == valid_mask } - + #[cfg(not(target_arch = "x86_64"))] { (0..len).all(|i| *a.add(i) == *b.add(i)) } } - + /// Compare >32 bytes in 32-byte AVX2 chunks. 大数据比较(32 字节 AVX2 分块)。 #[inline(always)] unsafe fn memcmp_large(a: *const u8, b: *const u8, len: usize) -> bool { let chunks = len / 32; - + for i in 0..chunks { let offset = i * 32; if !Self::memcmp_avx2(a.add(offset), b.add(offset), 32) { return false; } } - + let remaining = len % 32; if remaining > 0 { return Self::memcmp_avx2(a.add(chunks * 32), b.add(chunks * 32), remaining); } - + true } - + /// SIMD-optimized zero memory. SIMD 加速的内存清零。 #[inline(always)] pub unsafe fn memzero_simd_optimized(ptr: *mut u8, len: usize) { #[cfg(target_arch = "x86_64")] { use std::arch::x86_64::{__m256i, _mm256_setzero_si256, _mm256_storeu_si256}; - + let zero = _mm256_setzero_si256(); let chunks = len / 32; let mut offset = 0; - + for _ in 0..chunks { _mm256_storeu_si256(ptr.add(offset) as *mut __m256i, zero); offset += 32; } - + let remaining = len % 32; for i in 0..remaining { *ptr.add(offset + i) = 0; } } - + #[cfg(not(target_arch = "x86_64"))] { ptr::write_bytes(ptr, 0, len); @@ -291,17 +290,17 @@ impl CacheAlignedCounter { _padding: [0; CACHE_LINE_SIZE - size_of::()], } } - + #[inline(always)] pub fn increment(&self) -> u64 { self.value.fetch_add(1, Ordering::Relaxed) } - + #[inline(always)] pub fn load(&self) -> u64 { self.value.load(Ordering::Relaxed) } - + #[inline(always)] pub fn store(&self, val: u64) { self.value.store(val, Ordering::Relaxed) @@ -312,7 +311,7 @@ impl CacheLineAligned for CacheAlignedCounter { fn ensure_cache_aligned(&self) -> bool { (self as *const Self as usize) % CACHE_LINE_SIZE == 0 } - + fn prefetch_data(&self) { #[cfg(target_arch = "x86_64")] unsafe { @@ -339,10 +338,10 @@ impl CacheOptimizedRingBuffer { if !capacity.is_power_of_two() { return Err(anyhow::anyhow!("Capacity must be a power of 2")); } - + let mut buffer = Vec::with_capacity(capacity); buffer.resize_with(capacity, Default::default); - + Ok(Self { buffer, producer_head: CachePadded::new(AtomicU64::new(0)), @@ -351,7 +350,7 @@ impl CacheOptimizedRingBuffer { mask: capacity - 1, }) } - + /// Lock-free push; returns false if full. 无锁写入,满则返回 false。 #[inline(always)] pub fn try_push(&self, item: T) -> bool { @@ -368,7 +367,7 @@ impl CacheOptimizedRingBuffer { self.producer_head.store(current_head + 1, Ordering::Release); true } - + /// Lock-free pop; returns None if empty. 无锁读取,空则返回 None。 #[inline(always)] pub fn try_pop(&self) -> Option { @@ -385,7 +384,7 @@ impl CacheOptimizedRingBuffer { self.consumer_tail.store(current_tail + 1, Ordering::Release); Some(item) } - + /// Current number of elements. 当前元素个数。 #[inline(always)] pub fn len(&self) -> usize { @@ -393,12 +392,11 @@ impl CacheOptimizedRingBuffer { let tail = self.consumer_tail.load(Ordering::Relaxed); ((head + self.capacity as u64 - tail) & self.mask as u64) as usize } - + /// True if no elements. 是否为空。 #[inline(always)] pub fn is_empty(&self) -> bool { - self.producer_head.load(Ordering::Relaxed) == - self.consumer_tail.load(Ordering::Relaxed) + self.producer_head.load(Ordering::Relaxed) == self.consumer_tail.load(Ordering::Relaxed) } } @@ -406,7 +404,7 @@ impl CacheLineAligned for CacheOptimizedRingBuffer { fn ensure_cache_aligned(&self) -> bool { (self as *const Self as usize) % CACHE_LINE_SIZE == 0 } - + fn prefetch_data(&self) { #[cfg(target_arch = "x86_64")] unsafe { @@ -428,25 +426,25 @@ impl BranchOptimizer { pub fn likely(condition: bool) -> bool { #[cold] fn cold() {} - + if !condition { cold(); } condition } - + /// Hint: condition is usually false. 提示编译器条件大概率为假。 #[inline(always)] pub fn unlikely(condition: bool) -> bool { #[cold] fn cold() {} - + if condition { cold(); } condition } - + /// Prefetch: load cache line at ptr into L1. Caller must ensure ptr is valid, read-only, no concurrent write. 预取:将 ptr 所在缓存行加载到 L1;调用方需保证有效、只读、无并发写。 #[inline(always)] pub unsafe fn prefetch_read_data(ptr: *const T) { @@ -457,7 +455,7 @@ impl BranchOptimizer { _mm_prefetch(ptr as *const i8, _MM_HINT_T0); } } - + /// Prefetch for write (T1 hint). 写预取(T1 提示)。 #[inline(always)] pub unsafe fn prefetch_write_data(ptr: *const T) { @@ -479,25 +477,25 @@ impl MemoryBarriers { pub fn compiler_barrier() { std::sync::atomic::compiler_fence(Ordering::SeqCst); } - + /// Light barrier (Acquire). 轻量级屏障(Acquire)。 #[inline(always)] pub fn memory_barrier_light() { std::sync::atomic::fence(Ordering::Acquire); } - + /// Full sequential consistency barrier. 全序一致性屏障。 #[inline(always)] pub fn memory_barrier_heavy() { std::sync::atomic::fence(Ordering::SeqCst); } - + /// Store/release barrier. 存储屏障,保证写入可见性。 #[inline(always)] pub fn store_barrier() { std::sync::atomic::fence(Ordering::Release); } - + /// Load/acquire barrier. 加载屏障,保证读取顺序。 #[inline(always)] pub fn load_barrier() { @@ -508,63 +506,54 @@ impl MemoryBarriers { #[cfg(test)] mod tests { use super::*; - + #[test] fn test_cache_aligned_counter() { let counter = CacheAlignedCounter::new(0); assert!(counter.ensure_cache_aligned()); - + assert_eq!(counter.load(), 0); counter.increment(); assert_eq!(counter.load(), 1); } - + #[test] fn test_simd_memcpy() { let src = [1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10]; let mut dst = [0u8; 10]; - + unsafe { - SIMDMemoryOps::memcpy_simd_optimized( - dst.as_mut_ptr(), - src.as_ptr(), - src.len() - ); + SIMDMemoryOps::memcpy_simd_optimized(dst.as_mut_ptr(), src.as_ptr(), src.len()); } - + assert_eq!(src, dst); } - + #[test] fn test_cache_optimized_ring_buffer() { - let buffer: CacheOptimizedRingBuffer = - CacheOptimizedRingBuffer::new(16).unwrap(); - + let buffer: CacheOptimizedRingBuffer = CacheOptimizedRingBuffer::new(16).unwrap(); + assert!(buffer.is_empty()); - + // 测试推入 assert!(buffer.try_push(42)); assert_eq!(buffer.len(), 1); - + // 测试弹出 assert_eq!(buffer.try_pop(), Some(42)); assert!(buffer.is_empty()); } - + #[test] fn test_simd_memcmp() { let a = [1u8, 2, 3, 4, 5]; let b = [1u8, 2, 3, 4, 5]; let c = [1u8, 2, 3, 4, 6]; - + unsafe { - assert!(SIMDMemoryOps::memcmp_simd_optimized( - a.as_ptr(), b.as_ptr(), a.len() - )); - - assert!(!SIMDMemoryOps::memcmp_simd_optimized( - a.as_ptr(), c.as_ptr(), a.len() - )); + assert!(SIMDMemoryOps::memcmp_simd_optimized(a.as_ptr(), b.as_ptr(), a.len())); + + assert!(!SIMDMemoryOps::memcmp_simd_optimized(a.as_ptr(), c.as_ptr(), a.len())); } } -} \ No newline at end of file +} diff --git a/src/perf/mod.rs b/src/perf/mod.rs index ca44fbd..64ce464 100644 --- a/src/perf/mod.rs +++ b/src/perf/mod.rs @@ -1,14 +1,14 @@ //! Performance: SIMD, cache prefetch, branch hints, zero-copy I/O, syscall bypass, compiler hints. //! 性能优化:SIMD、缓存预取、分支提示、零拷贝 I/O、系统调用绕过、编译器提示。 -pub mod simd; -pub mod hardware_optimizations; -pub mod zero_copy_io; -pub mod syscall_bypass; pub mod compiler_optimization; +pub mod hardware_optimizations; +pub mod simd; +pub mod syscall_bypass; +pub mod zero_copy_io; -pub use simd::*; -pub use hardware_optimizations::*; -pub use zero_copy_io::*; -pub use syscall_bypass::*; pub use compiler_optimization::*; +pub use hardware_optimizations::*; +pub use simd::*; +pub use syscall_bypass::*; +pub use zero_copy_io::*; diff --git a/src/perf/simd.rs b/src/perf/simd.rs index 2af8d42..83eba86 100644 --- a/src/perf/simd.rs +++ b/src/perf/simd.rs @@ -235,7 +235,7 @@ impl SIMDHash { /// 批量计算 SHA256 哈希 #[inline(always)] pub fn hash_batch_sha256(data: &[&[u8]]) -> Vec<[u8; 32]> { - use sha2::{Sha256, Digest}; + use sha2::{Digest, Sha256}; data.iter() .map(|item| { diff --git a/src/perf/syscall_bypass.rs b/src/perf/syscall_bypass.rs index 0dda284..ea044a3 100644 --- a/src/perf/syscall_bypass.rs +++ b/src/perf/syscall_bypass.rs @@ -1,11 +1,11 @@ //! Syscall bypass: batching, vDSO fast time, io_uring, mmap, userspace impl. //! 系统调用绕过:批处理、vDSO 快速时间、io_uring、mmap、用户态实现。 -use std::sync::atomic::{AtomicU64, Ordering}; -use std::sync::Arc; -use std::time::{SystemTime, UNIX_EPOCH, Duration, Instant}; #[allow(unused_imports)] use std::fs::OpenOptions; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Arc; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use anyhow::Result; use crossbeam_utils::CachePadded; @@ -55,14 +55,30 @@ pub struct SyscallBatchProcessor { #[derive(Debug, Clone)] pub enum SyscallRequest { - Write { fd: i32, data: Vec }, - Read { fd: i32, size: usize }, - Send { socket: i32, data: Vec }, - Recv { socket: i32, size: usize }, + Write { + fd: i32, + data: Vec, + }, + Read { + fd: i32, + size: usize, + }, + Send { + socket: i32, + data: Vec, + }, + Recv { + socket: i32, + size: usize, + }, GetTime, - MemAlloc { size: usize }, + MemAlloc { + size: usize, + }, /// 内存释放 - MemFree { ptr: usize }, + MemFree { + ptr: usize, + }, } /// 🚀 快速时间提供器 - 绕过系统调用获取时间 @@ -86,24 +102,22 @@ impl FastTimeProvider { pub fn new(enable_vdso: bool) -> Result { let now = SystemTime::now(); let instant_now = Instant::now(); - + let provider = Self { _base_time: now, monotonic_start: instant_now, time_cache: CachePadded::new(AtomicU64::new( - now.duration_since(UNIX_EPOCH)?.as_nanos() as u64 + now.duration_since(UNIX_EPOCH)?.as_nanos() as u64, )), cache_update_interval_ns: 1_000_000, // 1ms - last_update: CachePadded::new(AtomicU64::new( - instant_now.elapsed().as_nanos() as u64 - )), + last_update: CachePadded::new(AtomicU64::new(instant_now.elapsed().as_nanos() as u64)), vdso_enabled: enable_vdso, }; - + tracing::info!(target: "sol_trade_sdk","🚀 Fast time provider initialized with vDSO: {}", enable_vdso); Ok(provider) } - + /// 🚀 超快速获取当前时间 - 绕过系统调用 #[inline(always)] pub fn fast_now_nanos(&self) -> u64 { @@ -111,19 +125,19 @@ impl FastTimeProvider { // 使用vDSO快速获取时间 return self.vdso_time_nanos(); } - + // 使用缓存的时间 let now_mono = self.monotonic_start.elapsed().as_nanos() as u64; let last_update = self.last_update.load(Ordering::Relaxed); - + if now_mono.saturating_sub(last_update) > self.cache_update_interval_ns { // 需要更新缓存 self.update_time_cache(); } - + self.time_cache.load(Ordering::Relaxed) } - + /// vDSO时间获取 #[inline(always)] fn vdso_time_nanos(&self) -> u64 { @@ -132,36 +146,34 @@ impl FastTimeProvider { // 在Linux上使用vDSO获取时间,避免系统调用 unsafe { let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 }; - + // CLOCK_MONOTONIC_RAW不受NTP调整影响,更适合性能测量 if libc::clock_gettime(libc::CLOCK_MONOTONIC_RAW, &mut ts) == 0 { return (ts.tv_sec as u64) * 1_000_000_000 + (ts.tv_nsec as u64); } } } - + // 回退到缓存时间 self.time_cache.load(Ordering::Relaxed) } - + /// 更新时间缓存 fn update_time_cache(&self) { if let Ok(now) = SystemTime::now().duration_since(UNIX_EPOCH) { let nanos = now.as_nanos() as u64; self.time_cache.store(nanos, Ordering::Relaxed); - self.last_update.store( - self.monotonic_start.elapsed().as_nanos() as u64, - Ordering::Relaxed - ); + self.last_update + .store(self.monotonic_start.elapsed().as_nanos() as u64, Ordering::Relaxed); } } - + /// 🚀 快速获取微秒时间戳 #[inline(always)] pub fn fast_now_micros(&self) -> u64 { self.fast_now_nanos() / 1000 } - + /// 🚀 快速获取毫秒时间戳 #[inline(always)] pub fn fast_now_millis(&self) -> u64 { @@ -200,16 +212,16 @@ impl IOOptimizer { /// 创建I/O优化器 pub fn new(_config: &SyscallBypassConfig) -> Result { let io_uring_available = Self::check_io_uring_support(); - + tracing::info!(target: "sol_trade_sdk","🚀 I/O Optimizer initialized - io_uring: {}", io_uring_available); - + Ok(Self { io_uring_available, async_io_stats: Arc::new(AsyncIOStats::default()), mmap_regions: Vec::new(), }) } - + /// 检查io_uring支持 fn check_io_uring_support() -> bool { #[cfg(target_os = "linux")] @@ -218,7 +230,7 @@ impl IOOptimizer { if let Ok(uname) = std::process::Command::new("uname").arg("-r").output() { let kernel_version = String::from_utf8_lossy(&uname.stdout); tracing::info!(target: "sol_trade_sdk","Kernel version: {}", kernel_version.trim()); - + // 简单检查:内核版本 >= 5.1 支持io_uring if let Some(version_str) = kernel_version.split('.').next() { if let Ok(major_version) = version_str.parse::() { @@ -227,60 +239,62 @@ impl IOOptimizer { } } } - + false } - + /// 🚀 批量异步写入 - 绕过多次系统调用 #[inline(always)] pub async fn batch_async_write(&self, requests: &[(i32, &[u8])]) -> Result> { if self.io_uring_available && requests.len() > 1 { return self.io_uring_batch_write(requests).await; } - + // 回退到标准批量写入 self.standard_batch_write(requests).await } - + /// 使用io_uring进行批量写入 async fn io_uring_batch_write(&self, requests: &[(i32, &[u8])]) -> Result> { // 这里是伪代码 - 实际实现需要io_uring库 tracing::trace!(target: "sol_trade_sdk","Using io_uring for {} write operations", requests.len()); - + let mut results = Vec::with_capacity(requests.len()); - + // 模拟批量提交到io_uring for (_fd, data) in requests { self.async_io_stats.operations_queued.fetch_add(1, Ordering::Relaxed); - + // 实际的io_uring实现会在这里提交所有操作 // 然后等待完成,避免多次系统调用 - + results.push(data.len()); // 模拟写入成功 self.async_io_stats.bytes_transferred.fetch_add(data.len() as u64, Ordering::Relaxed); self.async_io_stats.operations_completed.fetch_add(1, Ordering::Relaxed); } - + // 这是一个系统调用而不是N个 - self.async_io_stats.syscalls_avoided.fetch_add(requests.len() as u64 - 1, Ordering::Relaxed); - + self.async_io_stats + .syscalls_avoided + .fetch_add(requests.len() as u64 - 1, Ordering::Relaxed); + Ok(results) } - + /// 标准批量写入 async fn standard_batch_write(&self, requests: &[(i32, &[u8])]) -> Result> { let mut results = Vec::with_capacity(requests.len()); - + // 将所有写入打包成一个写操作 for (_fd, data) in requests { // 模拟写入操作 results.push(data.len()); self.async_io_stats.bytes_transferred.fetch_add(data.len() as u64, Ordering::Relaxed); } - + Ok(results) } - + /// 🚀 内存映射文件I/O - 避免read/write系统调用 pub fn create_memory_mapped_io(&mut self, file_path: &str, size: usize) -> Result { #[cfg(unix)] @@ -298,16 +312,12 @@ impl IOOptimizer { .custom_flags(libc::O_DIRECT) // 直接I/O,绕过页面缓存 .open(file_path)? }; - + #[cfg(not(target_os = "linux"))] - let file = OpenOptions::new() - .read(true) - .write(true) - .create(true) - .open(file_path)?; - + let file = OpenOptions::new().read(true).write(true).create(true).open(file_path)?; + let fd = file.as_raw_fd(); - + unsafe { let addr = libc::mmap( std::ptr::null_mut(), @@ -317,37 +327,42 @@ impl IOOptimizer { fd, 0, ); - + if addr == libc::MAP_FAILED { return Err(anyhow::anyhow!("Memory mapping failed")); } - - let region = MemoryMappedRegion { - address: addr as usize, - size, - file_descriptor: fd, - }; - + + let region = + MemoryMappedRegion { address: addr as usize, size, file_descriptor: fd }; + self.mmap_regions.push(region); - + tracing::info!(target: "sol_trade_sdk","✅ Memory mapped I/O created: {} bytes at {:p}", size, addr); Ok(addr as usize) } } - + #[cfg(not(unix))] { Err(anyhow::anyhow!("Memory mapped I/O not supported on this platform")) } } - + /// 获取I/O统计 pub fn get_stats(&self) -> AsyncIOStats { AsyncIOStats { - operations_queued: AtomicU64::new(self.async_io_stats.operations_queued.load(Ordering::Relaxed)), - operations_completed: AtomicU64::new(self.async_io_stats.operations_completed.load(Ordering::Relaxed)), - bytes_transferred: AtomicU64::new(self.async_io_stats.bytes_transferred.load(Ordering::Relaxed)), - syscalls_avoided: AtomicU64::new(self.async_io_stats.syscalls_avoided.load(Ordering::Relaxed)), + operations_queued: AtomicU64::new( + self.async_io_stats.operations_queued.load(Ordering::Relaxed), + ), + operations_completed: AtomicU64::new( + self.async_io_stats.operations_completed.load(Ordering::Relaxed), + ), + bytes_transferred: AtomicU64::new( + self.async_io_stats.bytes_transferred.load(Ordering::Relaxed), + ), + syscalls_avoided: AtomicU64::new( + self.async_io_stats.syscalls_avoided.load(Ordering::Relaxed), + ), } } } @@ -357,47 +372,46 @@ impl SyscallBatchProcessor { pub fn new(batch_size: usize) -> Result { let pending_calls = crossbeam_queue::ArrayQueue::new(batch_size * 10); let executor = tokio::runtime::Handle::current(); - + tracing::info!(target: "sol_trade_sdk","🚀 Syscall batch processor created with batch size: {}", batch_size); - + Ok(Self { pending_calls, _executor: executor, batch_stats: CachePadded::new(AtomicU64::new(0)), }) } - + /// 🚀 提交系统调用请求到批处理队列 #[inline(always)] pub fn submit_request(&self, request: SyscallRequest) -> Result<()> { - self.pending_calls.push(request) - .map_err(|_| anyhow::anyhow!("Batch queue full"))?; - + self.pending_calls.push(request).map_err(|_| anyhow::anyhow!("Batch queue full"))?; + Ok(()) } - + /// 🚀 执行批量系统调用 pub async fn execute_batch(&self) -> Result { let mut batch = Vec::new(); - + // 收集批量请求 while batch.len() < 100 && !self.pending_calls.is_empty() { if let Some(request) = self.pending_calls.pop() { batch.push(request); } } - + if batch.is_empty() { return Ok(0); } - + let batch_size = batch.len(); - + // 按类型分组批量执行 let mut write_requests = Vec::new(); let mut read_requests = Vec::new(); let mut network_requests = Vec::new(); - + for request in batch { match request { SyscallRequest::Write { fd, data } => { @@ -414,28 +428,28 @@ impl SyscallBatchProcessor { } } } - + // 批量执行写入 if !write_requests.is_empty() { self.batch_write_operations(write_requests).await?; } - + // 批量执行读取 if !read_requests.is_empty() { self.batch_read_operations(read_requests).await?; } - + // 批量执行网络操作 if !network_requests.is_empty() { self.batch_network_operations(network_requests).await?; } - + self.batch_stats.fetch_add(1, Ordering::Relaxed); - + tracing::trace!(target: "sol_trade_sdk","Executed batch of {} syscalls", batch_size); Ok(batch_size) } - + /// 批量写入操作 async fn batch_write_operations(&self, requests: Vec<(i32, Vec)>) -> Result<()> { // 使用writev系统调用进行批量写入 @@ -445,7 +459,7 @@ impl SyscallBatchProcessor { } Ok(()) } - + /// 批量读取操作 async fn batch_read_operations(&self, requests: Vec<(i32, usize)>) -> Result<()> { // 使用readv系统调用进行批量读取 @@ -454,7 +468,7 @@ impl SyscallBatchProcessor { } Ok(()) } - + /// 批量网络操作 async fn batch_network_operations(&self, requests: Vec<(i32, Vec)>) -> Result<()> { // 使用sendmsg/recvmsg进行批量网络操作 @@ -482,22 +496,16 @@ impl SystemCallBypassManager { let fast_time_provider = Arc::new(FastTimeProvider::new(config.enable_vdso)?); let io_optimizer = Arc::new(IOOptimizer::new(&config)?); let stats = Arc::new(SyscallBypassStats::default()); - + tracing::info!(target: "sol_trade_sdk","🚀 System Call Bypass Manager initialized"); tracing::info!(target: "sol_trade_sdk"," 📦 Batch Processing: {}", config.enable_batch_processing); tracing::info!(target: "sol_trade_sdk"," ⏰ Fast Time: {}", config.enable_fast_time); tracing::info!(target: "sol_trade_sdk"," 🚀 vDSO: {}", config.enable_vdso); tracing::info!(target: "sol_trade_sdk"," 📁 io_uring: {}", config.enable_io_uring); - - Ok(Self { - config, - batch_processor, - fast_time_provider, - _io_optimizer: io_optimizer, - stats, - }) + + Ok(Self { config, batch_processor, fast_time_provider, _io_optimizer: io_optimizer, stats }) } - + /// 🚀 快速获取当前时间戳 - 绕过系统调用 #[inline(always)] pub fn fast_timestamp_nanos(&self) -> u64 { @@ -505,28 +513,25 @@ impl SystemCallBypassManager { self.stats.time_calls_cached.fetch_add(1, Ordering::Relaxed); return self.fast_time_provider.fast_now_nanos(); } - + // 回退到标准时间获取 - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_nanos() as u64 + SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_nanos() as u64 } - + /// 🚀 提交批量I/O操作 pub async fn submit_batch_io(&self, operations: Vec) -> Result<()> { if !self.config.enable_batch_processing { return Err(anyhow::anyhow!("Batch processing disabled")); } - + for op in operations { self.batch_processor.submit_request(op)?; } - + self.stats.syscalls_batched.fetch_add(1, Ordering::Relaxed); Ok(()) } - + /// 🚀 执行优化的内存分配 - 绕过malloc系统调用 #[inline(always)] pub fn fast_allocate(&self, size: usize) -> Result<*mut u8> { @@ -534,34 +539,30 @@ impl SystemCallBypassManager { self.stats.memory_operations_avoided.fetch_add(1, Ordering::Relaxed); return self.userspace_allocate(size); } - + // 回退到标准分配 let layout = std::alloc::Layout::from_size_align(size, 8)?; let ptr = unsafe { std::alloc::alloc(layout) }; - + if ptr.is_null() { Err(anyhow::anyhow!("Allocation failed")) } else { Ok(ptr) } } - + /// 用户空间内存分配 fn userspace_allocate(&self, size: usize) -> Result<*mut u8> { - use std::sync::Mutex; use once_cell::sync::Lazy; + use std::sync::Mutex; struct MemoryPool { pool: Box<[u8; 1024 * 1024]>, offset: usize, } - static MEMORY_POOL: Lazy> = Lazy::new(|| { - Mutex::new(MemoryPool { - pool: Box::new([0; 1024 * 1024]), - offset: 0, - }) - }); + static MEMORY_POOL: Lazy> = + Lazy::new(|| Mutex::new(MemoryPool { pool: Box::new([0; 1024 * 1024]), offset: 0 })); let mut pool = MEMORY_POOL.lock().unwrap(); @@ -574,18 +575,18 @@ impl SystemCallBypassManager { Ok(ptr) } - + /// 启动批处理工作线程 pub async fn start_batch_processing(&self) -> Result<()> { let processor = Arc::clone(&self.batch_processor); let stats = Arc::clone(&self.stats); - + tokio::spawn(async move { let mut interval = tokio::time::interval(Duration::from_micros(100)); // 100μs间隔 - + loop { interval.tick().await; - + if let Ok(processed) = processor.execute_batch().await { if processed > 0 { stats.syscalls_bypassed.fetch_add(processed as u64, Ordering::Relaxed); @@ -593,11 +594,11 @@ impl SystemCallBypassManager { } } }); - + tracing::info!(target: "sol_trade_sdk","✅ Batch processing worker started"); Ok(()) } - + /// 获取绕过统计 pub fn get_bypass_stats(&self) -> SyscallBypassStatsSnapshot { SyscallBypassStatsSnapshot { @@ -608,7 +609,7 @@ impl SystemCallBypassManager { memory_operations_avoided: self.stats.memory_operations_avoided.load(Ordering::Relaxed), } } - + /// 🚀 极致优化配置 pub fn extreme_bypass_config() -> SyscallBypassConfig { SyscallBypassConfig { @@ -643,9 +644,11 @@ impl SyscallBypassStatsSnapshot { tracing::info!(target: "sol_trade_sdk"," ⏰ Time Calls Cached: {}", self.time_calls_cached); tracing::info!(target: "sol_trade_sdk"," 📁 I/O Operations Optimized: {}", self.io_operations_optimized); tracing::info!(target: "sol_trade_sdk"," 💾 Memory Operations Avoided: {}", self.memory_operations_avoided); - - let total_optimizations = self.syscalls_bypassed + self.time_calls_cached + - self.io_operations_optimized + self.memory_operations_avoided; + + let total_optimizations = self.syscalls_bypassed + + self.time_calls_cached + + self.io_operations_optimized + + self.memory_operations_avoided; tracing::info!(target: "sol_trade_sdk"," 🏆 Total Optimizations: {}", total_optimizations); } } @@ -657,7 +660,7 @@ macro_rules! bypass_syscall { // 使用快速时间而不是系统调用 crate::performance::syscall_bypass::GLOBAL_TIME_PROVIDER.fast_now_nanos() }; - + (batch_io $ops:expr) => { // 批量提交I/O操作 crate::performance::syscall_bypass::GLOBAL_BYPASS_MANAGER.submit_batch_io($ops).await @@ -667,60 +670,57 @@ macro_rules! bypass_syscall { #[cfg(test)] mod tests { use super::*; - + #[tokio::test] async fn test_fast_time_provider() { let provider = FastTimeProvider::new(false).unwrap(); - + let time1 = provider.fast_now_nanos(); tokio::time::sleep(Duration::from_millis(1)).await; let time2 = provider.fast_now_nanos(); - + assert!(time2 > time1); assert!(time2 - time1 >= 1_000_000); // 至少1ms差异 } - - #[tokio::test] + + #[tokio::test] async fn test_syscall_batch_processor() { let processor = SyscallBatchProcessor::new(10).unwrap(); - - let request = SyscallRequest::Write { - fd: 1, - data: vec![1, 2, 3, 4, 5], - }; - + + let request = SyscallRequest::Write { fd: 1, data: vec![1, 2, 3, 4, 5] }; + processor.submit_request(request).unwrap(); - + let processed = processor.execute_batch().await.unwrap(); assert_eq!(processed, 1); } - + #[tokio::test] async fn test_io_optimizer() { let config = SyscallBypassConfig::default(); let optimizer = IOOptimizer::new(&config).unwrap(); - + let requests = vec![(1, b"test data".as_ref())]; let results = optimizer.batch_async_write(&requests).await.unwrap(); - + assert_eq!(results.len(), 1); assert_eq!(results[0], 9); // "test data".len() } - + #[tokio::test] async fn test_system_call_bypass_manager() { let config = SyscallBypassConfig::default(); let manager = SystemCallBypassManager::new(config).unwrap(); - + // 测试快速时间戳 let timestamp = manager.fast_timestamp_nanos(); assert!(timestamp > 0); - + // 测试统计 let stats = manager.get_bypass_stats(); assert_eq!(stats.time_calls_cached, 1); } - + #[test] fn test_extreme_bypass_config() { let config = SystemCallBypassManager::extreme_bypass_config(); @@ -731,16 +731,16 @@ mod tests { assert_eq!(config.batch_size, 1000); assert_eq!(config.syscall_cache_size, 10000); } - + #[test] fn test_userspace_allocation() { let config = SyscallBypassConfig::default(); let manager = SystemCallBypassManager::new(config).unwrap(); - + let ptr = manager.fast_allocate(64).unwrap(); assert!(!ptr.is_null()); - + let stats = manager.get_bypass_stats(); assert_eq!(stats.memory_operations_avoided, 1); } -} \ No newline at end of file +} diff --git a/src/perf/zero_copy_io.rs b/src/perf/zero_copy_io.rs index 4030f80..fc41165 100644 --- a/src/perf/zero_copy_io.rs +++ b/src/perf/zero_copy_io.rs @@ -1,5 +1,5 @@ //! 🚀 零拷贝内存映射IO - 完全消除数据拷贝开销 -//! +//! //! 实现极致的零拷贝策略,包括: //! - 内存映射文件IO //! - 共享内存环形缓冲区 @@ -10,11 +10,11 @@ use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; use std::sync::Arc; // use std::mem::{size_of, MaybeUninit}; +use anyhow::{Context, Result}; +use crossbeam_utils::CachePadded; +use memmap2::{MmapMut, MmapOptions}; use std::ptr::NonNull; use std::slice; -use memmap2::{MmapMut, MmapOptions}; -use anyhow::{Result, Context}; -use crossbeam_utils::CachePadded; /// 🚀 零拷贝内存管理器 pub struct ZeroCopyMemoryManager { @@ -50,17 +50,17 @@ impl SharedMemoryPool { // 确保块大小是64字节对齐(缓存行对齐) let aligned_block_size = (block_size + 63) & !63; let total_blocks = total_size / aligned_block_size; - + // 创建内存映射文件 let memory_region = MmapOptions::new() .len(total_blocks * aligned_block_size) .map_anon() .context("Failed to create memory mapped region")?; - + // 初始化空闲块位图 (每个u64可以管理64个块) let bitmap_size = (total_blocks + 63) / 64; let mut free_blocks = Vec::with_capacity(bitmap_size); - + // 将所有块标记为空闲(全1) for i in 0..bitmap_size { let bits = if i == bitmap_size - 1 && total_blocks % 64 != 0 { @@ -72,10 +72,10 @@ impl SharedMemoryPool { }; free_blocks.push(AtomicU64::new(bits)); } - + tracing::info!(target: "sol_trade_sdk","🚀 Created shared memory pool {} with {} blocks of {} bytes each", pool_id, total_blocks, aligned_block_size); - + Ok(Self { memory_region, free_blocks, @@ -85,31 +85,31 @@ impl SharedMemoryPool { pool_id, }) } - + /// 🚀 零拷贝分配内存块 #[inline(always)] pub fn allocate_block(&self) -> Option { // 快速路径:尝试从预期位置分配 let start_index = self.allocator_head.load(Ordering::Relaxed) / 64; - + // 遍历所有位图寻找空闲块 for attempt in 0..self.free_blocks.len() { let bitmap_index = (start_index + attempt) % self.free_blocks.len(); let bitmap = &self.free_blocks[bitmap_index]; - + let mut current = bitmap.load(Ordering::Acquire); - + while current != 0 { // 找到最低位的1(最小的空闲块) let bit_pos = current.trailing_zeros() as usize; let mask = 1u64 << bit_pos; - + // 尝试原子地清除这一位(标记为已分配) match bitmap.compare_exchange_weak( - current, + current, current & !mask, Ordering::AcqRel, - Ordering::Relaxed + Ordering::Relaxed, ) { Ok(_) => { // 成功分配 @@ -119,20 +119,17 @@ impl SharedMemoryPool { bitmap.fetch_or(mask, Ordering::Relaxed); break; } - + let offset = block_index * self.block_size; let ptr = unsafe { NonNull::new_unchecked( self.memory_region.as_ptr().add(offset) as *mut u8 ) }; - + // 更新分配器头指针 - self.allocator_head.store( - (block_index + 1) * 64, - Ordering::Relaxed - ); - + self.allocator_head.store((block_index + 1) * 64, Ordering::Relaxed); + return Some(ZeroCopyBlock { ptr, size: self.block_size, @@ -147,10 +144,10 @@ impl SharedMemoryPool { } } } - + None // 没有可用块 } - + /// 🚀 零拷贝释放内存块 #[inline(always)] pub fn deallocate_block(&self, block: ZeroCopyBlock) { @@ -158,20 +155,21 @@ impl SharedMemoryPool { tracing::error!(target: "sol_trade_sdk", "Attempting to deallocate block from wrong pool"); return; } - + let bitmap_index = block.block_index / 64; let bit_pos = block.block_index % 64; let mask = 1u64 << bit_pos; - + if bitmap_index < self.free_blocks.len() { // 原子地设置位为1(标记为空闲) self.free_blocks[bitmap_index].fetch_or(mask, Ordering::Release); } } - + /// 获取可用块数量 pub fn available_blocks(&self) -> usize { - self.free_blocks.iter() + self.free_blocks + .iter() .map(|bitmap| bitmap.load(Ordering::Relaxed).count_ones() as usize) .sum() } @@ -195,49 +193,49 @@ impl ZeroCopyBlock { pub fn as_ptr(&self) -> *mut u8 { self.ptr.as_ptr() } - + /// 获取只读切片 #[inline(always)] pub unsafe fn as_slice(&self) -> &[u8] { slice::from_raw_parts(self.ptr.as_ptr(), self.size) } - + /// 获取可变切片 #[inline(always)] pub unsafe fn as_mut_slice(&mut self) -> &mut [u8] { slice::from_raw_parts_mut(self.ptr.as_ptr(), self.size) } - + /// 获取块大小 #[inline(always)] pub fn size(&self) -> usize { self.size } - + /// 零拷贝写入数据 #[inline(always)] pub unsafe fn write_bytes(&mut self, data: &[u8]) -> Result<()> { if data.len() > self.size { return Err(anyhow::anyhow!("Data too large for block")); } - + // 使用硬件优化的内存拷贝 super::hardware_optimizations::SIMDMemoryOps::memcpy_simd_optimized( self.ptr.as_ptr(), data.as_ptr(), - data.len() + data.len(), ); - + Ok(()) } - + /// 零拷贝读取数据 #[inline(always)] pub unsafe fn read_bytes(&self, len: usize) -> Result<&[u8]> { if len > self.size { return Err(anyhow::anyhow!("Read length exceeds block size")); } - + Ok(slice::from_raw_parts(self.ptr.as_ptr(), len)) } } @@ -266,9 +264,9 @@ impl MemoryMappedBuffer { .len(size) .map_anon() .context("Failed to create memory mapped buffer")?; - + tracing::info!(target: "sol_trade_sdk","🚀 Created memory mapped buffer {} with size {} bytes", buffer_id, size); - + Ok(Self { mmap, read_pos: CachePadded::new(AtomicUsize::new(0)), @@ -277,128 +275,136 @@ impl MemoryMappedBuffer { _buffer_id: buffer_id, }) } - + /// 🚀 零拷贝写入数据 #[inline(always)] pub fn write_data(&self, data: &[u8]) -> Result { let data_len = data.len(); let current_write = self.write_pos.load(Ordering::Relaxed); let current_read = self.read_pos.load(Ordering::Acquire); - + // 计算可用空间 let available_space = if current_write >= current_read { self.size - (current_write - current_read) - 1 } else { current_read - current_write - 1 }; - + if data_len > available_space { return Err(anyhow::anyhow!("Insufficient buffer space")); } - + // 零拷贝写入 unsafe { let write_ptr = self.mmap.as_ptr().add(current_write) as *mut u8; - + if current_write + data_len <= self.size { // 数据不跨越缓冲区边界 super::hardware_optimizations::SIMDMemoryOps::memcpy_simd_optimized( - write_ptr, data.as_ptr(), data_len + write_ptr, + data.as_ptr(), + data_len, ); } else { // 数据跨越缓冲区边界,分两段写入 let first_part = self.size - current_write; let second_part = data_len - first_part; - + // 写入第一部分 super::hardware_optimizations::SIMDMemoryOps::memcpy_simd_optimized( - write_ptr, data.as_ptr(), first_part + write_ptr, + data.as_ptr(), + first_part, ); - + // 写入第二部分(从缓冲区开头) super::hardware_optimizations::SIMDMemoryOps::memcpy_simd_optimized( - self.mmap.as_ptr() as *mut u8, - data.as_ptr().add(first_part), - second_part + self.mmap.as_ptr() as *mut u8, + data.as_ptr().add(first_part), + second_part, ); } } - + // 更新写指针 let new_write_pos = (current_write + data_len) % self.size; self.write_pos.store(new_write_pos, Ordering::Release); - + Ok(data_len) } - + /// 🚀 零拷贝读取数据 #[inline(always)] pub fn read_data(&self, buffer: &mut [u8]) -> Result { let buffer_len = buffer.len(); let current_read = self.read_pos.load(Ordering::Relaxed); let current_write = self.write_pos.load(Ordering::Acquire); - + // 计算可读数据量 let available_data = if current_write >= current_read { current_write - current_read } else { self.size - (current_read - current_write) }; - + if available_data == 0 { return Ok(0); // 无数据可读 } - + let read_len = buffer_len.min(available_data); - + // 零拷贝读取 unsafe { let read_ptr = self.mmap.as_ptr().add(current_read); - + if current_read + read_len <= self.size { // 数据不跨越缓冲区边界 super::hardware_optimizations::SIMDMemoryOps::memcpy_simd_optimized( - buffer.as_mut_ptr(), read_ptr, read_len + buffer.as_mut_ptr(), + read_ptr, + read_len, ); } else { // 数据跨越缓冲区边界,分两段读取 let first_part = self.size - current_read; let second_part = read_len - first_part; - + // 读取第一部分 super::hardware_optimizations::SIMDMemoryOps::memcpy_simd_optimized( - buffer.as_mut_ptr(), read_ptr, first_part + buffer.as_mut_ptr(), + read_ptr, + first_part, ); - + // 读取第二部分(从缓冲区开头) super::hardware_optimizations::SIMDMemoryOps::memcpy_simd_optimized( buffer.as_mut_ptr().add(first_part), - self.mmap.as_ptr(), - second_part + self.mmap.as_ptr(), + second_part, ); } } - + // 更新读指针 let new_read_pos = (current_read + read_len) % self.size; self.read_pos.store(new_read_pos, Ordering::Release); - + Ok(read_len) } - + /// 获取可读数据量 #[inline(always)] pub fn available_data(&self) -> usize { let current_read = self.read_pos.load(Ordering::Relaxed); let current_write = self.write_pos.load(Ordering::Relaxed); - + if current_write >= current_read { current_write - current_read } else { self.size - (current_read - current_write) } } - + /// 获取可用空间 #[inline(always)] pub fn available_space(&self) -> usize { @@ -420,38 +426,39 @@ impl DirectMemoryAccessManager { /// 创建DMA管理器 pub fn new(num_channels: usize) -> Result { let mut dma_channels = Vec::with_capacity(num_channels); - + for i in 0..num_channels { dma_channels.push(Arc::new(DMAChannel::new(i)?)); } - + tracing::info!(target: "sol_trade_sdk","🚀 Created DMA manager with {} channels", num_channels); - + Ok(Self { dma_channels, channel_allocator: AtomicUsize::new(0), dma_stats: Arc::new(DMAStats::new()), }) } - + /// 🚀 执行零拷贝DMA传输 #[inline(always)] pub async fn dma_transfer(&self, src: &[u8], dst: &mut [u8]) -> Result { if src.len() != dst.len() { return Err(anyhow::anyhow!("Source and destination sizes don't match")); } - + // 选择DMA通道(轮询分配) - let channel_index = self.channel_allocator.fetch_add(1, Ordering::Relaxed) % self.dma_channels.len(); + let channel_index = + self.channel_allocator.fetch_add(1, Ordering::Relaxed) % self.dma_channels.len(); let channel = &self.dma_channels[channel_index]; - + // 执行DMA传输 let transferred = channel.transfer(src, dst).await?; - + // 更新统计 self.dma_stats.bytes_transferred.fetch_add(transferred as u64, Ordering::Relaxed); self.dma_stats.transfers_completed.fetch_add(1, Ordering::Relaxed); - + Ok(transferred) } } @@ -475,21 +482,21 @@ impl DMAChannel { _status: AtomicU64::new(0), }) } - + /// 🚀 执行零拷贝传输 #[inline(always)] pub async fn transfer(&self, src: &[u8], dst: &mut [u8]) -> Result { let transfer_size = src.len(); - + // 使用硬件优化的SIMD内存拷贝 unsafe { super::hardware_optimizations::SIMDMemoryOps::memcpy_simd_optimized( dst.as_mut_ptr(), src.as_ptr(), - transfer_size + transfer_size, ); } - + Ok(transfer_size) } } @@ -541,14 +548,14 @@ impl ZeroCopyStats { mmap_buffer_usage: AtomicU64::new(0), } } - + /// 打印统计信息 pub fn print_stats(&self) { let allocated = self.blocks_allocated.load(Ordering::Relaxed); let freed = self.blocks_freed.load(Ordering::Relaxed); let bytes = self.bytes_transferred.load(Ordering::Relaxed); let mmap_usage = self.mmap_buffer_usage.load(Ordering::Relaxed); - + tracing::info!(target: "sol_trade_sdk","🚀 Zero-Copy Stats:"); tracing::info!(target: "sol_trade_sdk"," 📦 Blocks: Allocated={}, Freed={}, Active={}", allocated, freed, allocated.saturating_sub(freed)); @@ -564,36 +571,36 @@ impl ZeroCopyMemoryManager { pub fn new() -> Result { let mut shared_pools = Vec::new(); let mut mmap_buffers = Vec::new(); - + // 创建不同大小的内存池 // 小块池: 64KB blocks, 1GB total shared_pools.push(Arc::new(SharedMemoryPool::new(0, 1024 * 1024 * 1024, 64 * 1024)?)); - // 中块池: 1MB blocks, 4GB total + // 中块池: 1MB blocks, 4GB total shared_pools.push(Arc::new(SharedMemoryPool::new(1, 4 * 1024 * 1024 * 1024, 1024 * 1024)?)); // 大块池: 16MB blocks, 8GB total - shared_pools.push(Arc::new(SharedMemoryPool::new(2, 8 * 1024 * 1024 * 1024, 16 * 1024 * 1024)?)); - + shared_pools.push(Arc::new(SharedMemoryPool::new( + 2, + 8 * 1024 * 1024 * 1024, + 16 * 1024 * 1024, + )?)); + // 创建内存映射缓冲区 for i in 0..8 { - mmap_buffers.push(Arc::new(MemoryMappedBuffer::new(i, 256 * 1024 * 1024)?)); // 256MB each + mmap_buffers.push(Arc::new(MemoryMappedBuffer::new(i, 256 * 1024 * 1024)?)); + // 256MB each } - + let dma_manager = Arc::new(DirectMemoryAccessManager::new(16)?); // 16 DMA channels let stats = Arc::new(ZeroCopyStats::new()); - + tracing::info!(target: "sol_trade_sdk","🚀 Zero-Copy Memory Manager initialized"); tracing::info!(target: "sol_trade_sdk"," 📦 Memory Pools: {}", shared_pools.len()); tracing::info!(target: "sol_trade_sdk"," 💾 Mapped Buffers: {}", mmap_buffers.len()); tracing::info!(target: "sol_trade_sdk"," 🔄 DMA Channels: 16"); - - Ok(Self { - shared_pools, - mmap_buffers, - dma_manager, - stats, - }) + + Ok(Self { shared_pools, mmap_buffers, dma_manager, stats }) } - + /// 🚀 分配零拷贝内存块 #[inline(always)] pub fn allocate(&self, size: usize) -> Option { @@ -605,7 +612,7 @@ impl ZeroCopyMemoryManager { } else { &self.shared_pools[2] // 大块池 }; - + if let Some(block) = pool.allocate_block() { self.stats.blocks_allocated.fetch_add(1, Ordering::Relaxed); Some(block) @@ -613,7 +620,7 @@ impl ZeroCopyMemoryManager { None } } - + /// 🚀 释放零拷贝内存块 #[inline(always)] pub fn deallocate(&self, block: ZeroCopyBlock) { @@ -623,19 +630,19 @@ impl ZeroCopyMemoryManager { self.stats.blocks_freed.fetch_add(1, Ordering::Relaxed); } } - + /// 获取内存映射缓冲区 #[inline(always)] pub fn get_mmap_buffer(&self, buffer_id: usize) -> Option> { self.mmap_buffers.get(buffer_id).cloned() } - + /// 获取DMA管理器 #[inline(always)] pub fn get_dma_manager(&self) -> Arc { self.dma_manager.clone() } - + /// 获取统计信息 pub fn get_stats(&self) -> Arc { self.stats.clone() @@ -645,73 +652,73 @@ impl ZeroCopyMemoryManager { #[cfg(test)] mod tests { use super::*; - + #[tokio::test] async fn test_shared_memory_pool() -> Result<()> { let pool = SharedMemoryPool::new(0, 1024 * 1024, 4096)?; - + // 测试分配 let block1 = pool.allocate_block().expect("Should allocate block"); assert_eq!(block1.size(), 4096); - + let block2 = pool.allocate_block().expect("Should allocate another block"); assert_eq!(block2.size(), 4096); - + // 测试释放 pool.deallocate_block(block1); pool.deallocate_block(block2); - + Ok(()) } - + #[tokio::test] async fn test_memory_mapped_buffer() -> Result<()> { let buffer = MemoryMappedBuffer::new(0, 1024 * 1024)?; - + let test_data = b"Hello, Zero-Copy World!"; - + // 测试写入 let written = buffer.write_data(test_data)?; assert_eq!(written, test_data.len()); - + // 测试读取 let mut read_buffer = vec![0u8; test_data.len()]; let read = buffer.read_data(&mut read_buffer)?; assert_eq!(read, test_data.len()); assert_eq!(&read_buffer, test_data); - + Ok(()) } - + #[tokio::test] async fn test_dma_transfer() -> Result<()> { let dma_manager = DirectMemoryAccessManager::new(4)?; - + let src = vec![1u8, 2, 3, 4, 5, 6, 7, 8]; let mut dst = vec![0u8; 8]; - + let transferred = dma_manager.dma_transfer(&src, &mut dst).await?; assert_eq!(transferred, 8); assert_eq!(src, dst); - + Ok(()) } - + #[tokio::test] async fn test_zero_copy_manager() -> Result<()> { let manager = ZeroCopyMemoryManager::new()?; - + // 测试小块分配 let small_block = manager.allocate(1024).expect("Should allocate small block"); assert_eq!(small_block.size(), 65536); // 小块池的块大小 - + // 测试大块分配 let large_block = manager.allocate(5 * 1024 * 1024).expect("Should allocate large block"); assert_eq!(large_block.size(), 16 * 1024 * 1024); // 大块池的块大小 - + manager.deallocate(small_block); manager.deallocate(large_block); - + Ok(()) } -} \ No newline at end of file +} diff --git a/src/swqos/astralane.rs b/src/swqos/astralane.rs index d25df37..b0032b5 100644 --- a/src/swqos/astralane.rs +++ b/src/swqos/astralane.rs @@ -4,18 +4,18 @@ use reqwest::Client; use std::{sync::Arc, time::Instant}; use tracing::{error, info, warn}; -use std::time::Duration; +use crate::swqos::SwqosClientTrait; +use crate::swqos::{SwqosType, TradeType}; use anyhow::Result; use bincode::serialize as bincode_serialize; use solana_client::rpc_client::SerializableTransaction; use solana_sdk::transaction::VersionedTransaction; -use crate::swqos::{SwqosType, TradeType}; -use crate::swqos::SwqosClientTrait; +use std::time::Duration; use crate::{common::SolanaRpcClient, constants::swqos::ASTRALANE_TIP_ACCOUNTS}; -use tokio::task::JoinHandle; use std::sync::atomic::{AtomicBool, Ordering}; +use tokio::task::JoinHandle; /// Empty body for getHealth POST; avoid per-request allocation. static PING_BODY: &[u8] = &[]; @@ -42,11 +42,21 @@ pub struct AstralaneClient { #[async_trait::async_trait] impl SwqosClientTrait for AstralaneClient { - async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { self.send_transaction_impl(trade_type, transaction, wait_confirmation).await } - async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, wait_confirmation: bool) -> Result<()> { + async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + wait_confirmation: bool, + ) -> Result<()> { for transaction in transactions { self.send_transaction_impl(trade_type, transaction, wait_confirmation).await?; } @@ -54,7 +64,10 @@ impl SwqosClientTrait for AstralaneClient { } fn get_tip_account(&self) -> Result { - let tip_account = *ASTRALANE_TIP_ACCOUNTS.choose(&mut rand::rng()).or_else(|| ASTRALANE_TIP_ACCOUNTS.first()).unwrap(); + let tip_account = *ASTRALANE_TIP_ACCOUNTS + .choose(&mut rand::rng()) + .or_else(|| ASTRALANE_TIP_ACCOUNTS.first()) + .unwrap(); Ok(tip_account.to_string()) } @@ -100,36 +113,48 @@ impl AstralaneClient { async fn start_ping_task(&self) { match &self.backend { - AstralaneBackend::Http { endpoint, auth_token, http_client, ping_handle, stop_ping } => { - let endpoint = endpoint.clone(); - let auth_token = auth_token.clone(); - let http_client = http_client.clone(); - let ping_handle = ping_handle.clone(); - let stop_ping = stop_ping.clone(); - let handle = tokio::spawn(async move { - let mut interval = tokio::time::interval(Duration::from_secs(30)); - loop { - interval.tick().await; - if stop_ping.load(Ordering::Relaxed) { - break; - } - if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await { - warn!(target: "sol_trade_sdk", "Astralane ping request failed: {}", e); + AstralaneBackend::Http { + endpoint, + auth_token, + http_client, + ping_handle, + stop_ping, + } => { + let endpoint = endpoint.clone(); + let auth_token = auth_token.clone(); + let http_client = http_client.clone(); + let ping_handle = ping_handle.clone(); + let stop_ping = stop_ping.clone(); + let handle = tokio::spawn(async move { + let mut interval = tokio::time::interval(Duration::from_secs(30)); + loop { + interval.tick().await; + if stop_ping.load(Ordering::Relaxed) { + break; + } + if let Err(e) = + Self::send_ping_request(&http_client, &endpoint, &auth_token).await + { + warn!(target: "sol_trade_sdk", "Astralane ping request failed: {}", e); + } } + }); + let mut guard = ping_handle.lock().await; + if let Some(old) = guard.as_ref() { + old.abort(); } - }); - let mut guard = ping_handle.lock().await; - if let Some(old) = guard.as_ref() { - old.abort(); - } - *guard = Some(handle); + *guard = Some(handle); } AstralaneBackend::Quic(_) => {} } } /// Send ping request: POST endpoint?api-key=...&method=getHealth - async fn send_ping_request(http_client: &Client, endpoint: &str, auth_token: &str) -> Result<()> { + async fn send_ping_request( + http_client: &Client, + endpoint: &str, + auth_token: &str, + ) -> Result<()> { let response = http_client .post(endpoint) .query(&[("api-key", auth_token), ("method", "getHealth")]) @@ -145,10 +170,16 @@ impl AstralaneClient { Ok(()) } - async fn send_transaction_impl(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + async fn send_transaction_impl( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { let start_time = Instant::now(); let signature = transaction.get_signature(); - let body_bytes = bincode_serialize(transaction).map_err(|e| anyhow::anyhow!("Astralane binary serialize failed: {}", e))?; + let body_bytes = bincode_serialize(transaction) + .map_err(|e| anyhow::anyhow!("Astralane binary serialize failed: {}", e))?; match &self.backend { AstralaneBackend::Http { endpoint, auth_token, http_client, .. } => { diff --git a/src/swqos/astralane_quic.rs b/src/swqos/astralane_quic.rs index e331fda..4fc0772 100644 --- a/src/swqos/astralane_quic.rs +++ b/src/swqos/astralane_quic.rs @@ -49,14 +49,16 @@ impl AstralaneQuicClient { /// Generates a self-signed TLS certificate with the API key as the Common Name (CN). pub async fn connect(server_addr: &str, api_key: &str) -> Result { let _ = rustls::crypto::ring::default_provider().install_default(); - let addr = SocketAddr::from_str(server_addr).or_else(|_| { - use std::net::ToSocketAddrs; - server_addr - .to_socket_addrs() - .ok() - .and_then(|mut addrs| addrs.next()) - .ok_or_else(|| anyhow::anyhow!("Cannot resolve address: {}", server_addr)) - }).context("Invalid server address")?; + let addr = SocketAddr::from_str(server_addr) + .or_else(|_| { + use std::net::ToSocketAddrs; + server_addr + .to_socket_addrs() + .ok() + .and_then(|mut addrs| addrs.next()) + .ok_or_else(|| anyhow::anyhow!("Cannot resolve address: {}", server_addr)) + }) + .context("Invalid server address")?; info!("[astralane-quic] Building TLS config (CN = api_key)"); let client_config = Self::build_client_config(api_key)?; @@ -116,10 +118,8 @@ impl AstralaneQuicClient { guard.clone() }; - let mut send_stream = conn - .open_uni() - .await - .context("Failed to open unidirectional stream")?; + let mut send_stream = + conn.open_uni().await.context("Failed to open unidirectional stream")?; send_stream .write_all(transaction_bytes) @@ -154,19 +154,15 @@ impl AstralaneQuicClient { /// Close the connection gracefully. pub async fn close(&self) { - self.connection - .lock() - .await - .close(error_code::OK.into(), b"client closing"); + self.connection.lock().await.close(error_code::OK.into(), b"client closing"); } fn build_client_config(api_key: &str) -> Result { let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256)?; let mut cert_params = CertificateParams::new(vec![])?; - cert_params.distinguished_name.push( - rcgen::DnType::CommonName, - rcgen::DnValue::Utf8String(api_key.to_string()), - ); + cert_params + .distinguished_name + .push(rcgen::DnType::CommonName, rcgen::DnValue::Utf8String(api_key.to_string())); let cert = cert_params.self_signed(&key_pair)?; let cert_der = CertificateDer::from(cert.der().to_vec()); @@ -181,9 +177,7 @@ impl AstralaneQuicClient { crypto.alpn_protocols = vec![ALPN_ASTRALANE_TPU.to_vec()]; let mut transport = TransportConfig::default(); - transport.max_idle_timeout(Some( - IdleTimeout::try_from(Duration::from_secs(30)).unwrap(), - )); + transport.max_idle_timeout(Some(IdleTimeout::try_from(Duration::from_secs(30)).unwrap())); transport.keep_alive_interval(Some(Duration::from_secs(25))); let mut client_config = @@ -196,9 +190,7 @@ impl AstralaneQuicClient { impl Drop for AstralaneQuicClient { fn drop(&mut self) { - self.connection - .get_mut() - .close(error_code::OK.into(), b"client closing"); + self.connection.get_mut().close(error_code::OK.into(), b"client closing"); } } diff --git a/src/swqos/blockrazor.rs b/src/swqos/blockrazor.rs index 6286351..ec66b5c 100644 --- a/src/swqos/blockrazor.rs +++ b/src/swqos/blockrazor.rs @@ -1,20 +1,22 @@ -use crate::swqos::common::{default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode}; +use crate::swqos::common::{ + default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode, +}; use rand::seq::IndexedRandom; use reqwest::Client; use std::{sync::Arc, time::Instant}; -use std::time::Duration; use solana_transaction_status::UiTransactionEncoding; +use std::time::Duration; +use crate::swqos::SwqosClientTrait; +use crate::swqos::{SwqosType, TradeType}; use anyhow::Result; use solana_sdk::transaction::VersionedTransaction; -use crate::swqos::{SwqosType, TradeType}; -use crate::swqos::SwqosClientTrait; use crate::{common::SolanaRpcClient, constants::swqos::BLOCKRAZOR_TIP_ACCOUNTS}; -use tokio::task::JoinHandle; use std::sync::atomic::{AtomicBool, Ordering}; +use tokio::task::JoinHandle; #[derive(Clone)] pub struct BlockRazorClient { @@ -28,16 +30,29 @@ pub struct BlockRazorClient { #[async_trait::async_trait] impl SwqosClientTrait for BlockRazorClient { - async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { self.send_transaction(trade_type, transaction, wait_confirmation).await } - async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, wait_confirmation: bool) -> Result<()> { + async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + wait_confirmation: bool, + ) -> Result<()> { self.send_transactions(trade_type, transactions, wait_confirmation).await } fn get_tip_account(&self) -> Result { - let tip_account = *BLOCKRAZOR_TIP_ACCOUNTS.choose(&mut rand::rng()).or_else(|| BLOCKRAZOR_TIP_ACCOUNTS.first()).unwrap(); + let tip_account = *BLOCKRAZOR_TIP_ACCOUNTS + .choose(&mut rand::rng()) + .or_else(|| BLOCKRAZOR_TIP_ACCOUNTS.first()) + .unwrap(); Ok(tip_account.to_string()) } @@ -50,26 +65,23 @@ impl BlockRazorClient { pub fn new(rpc_url: String, endpoint: String, auth_token: String) -> Self { let rpc_client = SolanaRpcClient::new(rpc_url); // 官方文档:請求中唯一允許的 header 是 Content-Type: text/plain;避免默认 User-Agent 等导致 500 - let http_client = default_http_client_builder() - .user_agent("") - .build() - .unwrap(); - - let client = Self { - rpc_client: Arc::new(rpc_client), - endpoint, - auth_token, + let http_client = default_http_client_builder().user_agent("").build().unwrap(); + + let client = Self { + rpc_client: Arc::new(rpc_client), + endpoint, + auth_token, http_client, ping_handle: Arc::new(tokio::sync::Mutex::new(None)), stop_ping: Arc::new(AtomicBool::new(false)), }; - + // Start ping task let client_clone = client.clone(); tokio::spawn(async move { client_clone.start_ping_task().await; }); - + client } @@ -79,7 +91,7 @@ impl BlockRazorClient { let auth_token = self.auth_token.clone(); let http_client = self.http_client.clone(); let stop_ping = self.stop_ping.clone(); - + let handle = tokio::spawn(async move { // Immediate first ping to warm connection and reduce first-submit cold start latency if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await { @@ -87,20 +99,21 @@ impl BlockRazorClient { eprintln!("BlockRazor ping request failed: {}", e); } } - let mut interval = tokio::time::interval(Duration::from_secs(30)); // 30s keepalive to avoid server ~5min idle close + let mut interval = tokio::time::interval(Duration::from_secs(30)); // 30s keepalive to avoid server ~5min idle close loop { interval.tick().await; if stop_ping.load(Ordering::Relaxed) { break; } - if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await { + if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await + { if crate::common::sdk_log::sdk_log_enabled() { eprintln!("BlockRazor ping request failed: {}", e); } } } }); - + // Update ping_handle - use Mutex to safely update { let mut ping_guard = self.ping_handle.lock().await; @@ -112,7 +125,11 @@ impl BlockRazorClient { } /// Send ping request: POST /v2/health?auth=... (Keep Alive). Only required param: auth. - async fn send_ping_request(http_client: &Client, endpoint: &str, auth_token: &str) -> Result<()> { + async fn send_ping_request( + http_client: &Client, + endpoint: &str, + auth_token: &str, + ) -> Result<()> { let ping_url = endpoint.replace("/v2/sendTransaction", "/v2/health"); let response = http_client .post(&ping_url) @@ -132,11 +149,18 @@ impl BlockRazorClient { /// Send transaction via v2 API: plain Base64 body, Content-Type: text/plain. Only required URI param: auth. /// 文档要求:auth 以 URI 参数传入;body 为纯 Base64 编码交易;唯一允许的 header 为 Content-Type: text/plain。 - pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + pub async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { let start_time = Instant::now(); - let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?; + let (content, signature) = + serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?; - let response = self.http_client + let response = self + .http_client .post(&self.endpoint) .query(&[("auth", self.auth_token.as_str())]) .header("Content-Type", "text/plain") @@ -153,7 +177,10 @@ impl BlockRazorClient { } else { let body = response.text().await.unwrap_or_default(); if crate::common::sdk_log::sdk_log_enabled() { - eprintln!(" [blockrazor] {} submission failed: status {} body: {}", trade_type, status, body); + eprintln!( + " [blockrazor] {} submission failed: status {} body: {}", + trade_type, status, body + ); } return Err(anyhow::anyhow!( "BlockRazor sendTransaction failed: status {} body: {}", @@ -168,10 +195,14 @@ impl BlockRazorClient { Err(e) => { if crate::common::sdk_log::sdk_log_enabled() { println!(" signature: {:?}", signature); - println!(" [blockrazor] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); + println!( + " [blockrazor] {} confirmation failed: {:?}", + trade_type, + start_time.elapsed() + ); } return Err(e); - }, + } } if wait_confirmation && crate::common::sdk_log::sdk_log_enabled() { println!(" signature: {:?}", signature); @@ -181,7 +212,12 @@ impl BlockRazorClient { Ok(()) } - pub async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, wait_confirmation: bool) -> Result<()> { + pub async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + wait_confirmation: bool, + ) -> Result<()> { for transaction in transactions { self.send_transaction(trade_type, transaction, wait_confirmation).await?; } @@ -193,7 +229,7 @@ impl Drop for BlockRazorClient { fn drop(&mut self) { // Ensure ping task stops when client is destroyed self.stop_ping.store(true, Ordering::Relaxed); - + // Try to stop ping task immediately // Use tokio::spawn to avoid blocking Drop let ping_handle = self.ping_handle.clone(); diff --git a/src/swqos/bloxroute.rs b/src/swqos/bloxroute.rs index e802796..1d4ee9c 100755 --- a/src/swqos/bloxroute.rs +++ b/src/swqos/bloxroute.rs @@ -6,17 +6,16 @@ use rand::seq::IndexedRandom; use reqwest::Client; use std::{sync::Arc, time::Instant}; -use std::time::Duration; use solana_transaction_status::UiTransactionEncoding; +use std::time::Duration; +use crate::swqos::SwqosClientTrait; +use crate::swqos::{SwqosType, TradeType}; use anyhow::Result; use solana_sdk::transaction::VersionedTransaction; -use crate::swqos::{SwqosType, TradeType}; -use crate::swqos::SwqosClientTrait; use crate::{common::SolanaRpcClient, constants::swqos::BLOX_TIP_ACCOUNTS}; - #[derive(Clone)] pub struct BloxrouteClient { pub endpoint: String, @@ -27,16 +26,29 @@ pub struct BloxrouteClient { #[async_trait::async_trait] impl SwqosClientTrait for BloxrouteClient { - async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { self.send_transaction(trade_type, transaction, wait_confirmation).await } - async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, wait_confirmation: bool) -> Result<()> { + async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + wait_confirmation: bool, + ) -> Result<()> { self.send_transactions(trade_type, transactions, wait_confirmation).await } fn get_tip_account(&self) -> Result { - let tip_account = *BLOX_TIP_ACCOUNTS.choose(&mut rand::rng()).or_else(|| BLOX_TIP_ACCOUNTS.first()).unwrap(); + let tip_account = *BLOX_TIP_ACCOUNTS + .choose(&mut rand::rng()) + .or_else(|| BLOX_TIP_ACCOUNTS.first()) + .unwrap(); Ok(tip_account.to_string()) } @@ -56,9 +68,15 @@ impl BloxrouteClient { Self { rpc_client: Arc::new(rpc_client), endpoint, auth_token, http_client } } - pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + pub async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { let start_time = Instant::now(); - let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?; + let (content, signature) = + serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?; // Single format! for body to avoid json! + to_string() double allocation let body = format!( @@ -67,7 +85,9 @@ impl BloxrouteClient { ); let endpoint = format!("{}/api/v2/submit", self.endpoint); - let response_text = self.http_client.post(&endpoint) + let response_text = self + .http_client + .post(&endpoint) .body(body) .header("Content-Type", "application/json") .header("Authorization", self.auth_token.as_str()) @@ -95,10 +115,14 @@ impl BloxrouteClient { Err(e) => { if crate::common::sdk_log::sdk_log_enabled() { println!(" signature: {:?}", signature); - println!(" [bloxroute] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); + println!( + " [bloxroute] {} confirmation failed: {:?}", + trade_type, + start_time.elapsed() + ); } return Err(e); - }, + } } if wait_confirmation && crate::common::sdk_log::sdk_log_enabled() { println!(" signature: {:?}", signature); @@ -108,7 +132,12 @@ impl BloxrouteClient { Ok(()) } - pub async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, _wait_confirmation: bool) -> Result<()> { + pub async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + _wait_confirmation: bool, + ) -> Result<()> { let start_time = Instant::now(); let contents = serialization::serialize_transactions_batch_sync( @@ -123,7 +152,9 @@ impl BloxrouteClient { let body = format!(r#"{{"entries":[{}]}}"#, entries); let endpoint = format!("{}/api/v2/submit-batch", self.endpoint); - let response_text = self.http_client.post(&endpoint) + let response_text = self + .http_client + .post(&endpoint) .body(body) .header("Content-Type", "application/json") .header("Authorization", self.auth_token.as_str()) @@ -144,4 +175,4 @@ impl BloxrouteClient { Ok(()) } -} \ No newline at end of file +} diff --git a/src/swqos/common.rs b/src/swqos/common.rs index b3f3c48..bf3fb7c 100755 --- a/src/swqos/common.rs +++ b/src/swqos/common.rs @@ -1,9 +1,9 @@ use crate::common::types::SolanaRpcClient; +use crate::swqos::serialization; use anyhow::Result; use base64::engine::general_purpose::{self, STANDARD}; use base64::Engine; use bincode::serialize; -use crate::swqos::serialization; use reqwest::Client; use serde_json; use serde_json::json; @@ -117,7 +117,11 @@ pub async fn poll_any_transaction_confirmation( loop { if start.elapsed() >= timeout { - return Err(anyhow::anyhow!("Transaction confirmation timed out after {}s ({} signatures polled)", timeout.as_secs(), signatures.len())); + return Err(anyhow::anyhow!( + "Transaction confirmation timed out after {}s ({} signatures polled)", + timeout.as_secs(), + signatures.len() + )); } poll_count += 1; @@ -205,7 +209,7 @@ pub async fn poll_any_transaction_confirmation( let ui_err = meta.err.unwrap(); let tx_err: TransactionError = serde_json::from_value(serde_json::to_value(&ui_err)?)?; - + // Use Solana InstructionError codes directly let mut code = 0u32; let mut index = None; @@ -230,7 +234,7 @@ pub async fn poll_any_transaction_confirmation( } _ => {} } - + return Err(anyhow::Error::new(TradeError { code: code, message: format!("{} {:?}", tx_err, error_msg), @@ -241,11 +245,16 @@ pub async fn poll_any_transaction_confirmation( } } -pub async fn send_nb_transaction(client: Client, endpoint: &str, auth_token: &str, transaction: &Transaction) -> Result { +pub async fn send_nb_transaction( + client: Client, + endpoint: &str, + auth_token: &str, + transaction: &Transaction, +) -> Result { // Serialize transaction let serialized = bincode::serialize(transaction) .map_err(|e| anyhow::anyhow!("Transaction serialization failed: {}", e))?; - + // Base64 encode let encoded = STANDARD.encode(serialized); @@ -266,18 +275,21 @@ pub async fn send_nb_transaction(client: Client, endpoint: &str, auth_token: &st .await .map_err(|e| anyhow::anyhow!("Request failed: {}", e))?; - let resp = response.json::().await + let resp = response + .json::() + .await .map_err(|e| anyhow::anyhow!("Response parsing failed: {}", e))?; if let Some(reason) = resp["reason"].as_str() { return Err(anyhow::anyhow!(reason.to_string())); } - let signature = resp["signature"].as_str() + let signature = resp["signature"] + .as_str() .ok_or_else(|| anyhow::anyhow!("Missing signature field in response"))?; - let signature = Signature::from_str(signature) - .map_err(|e| anyhow::anyhow!("Invalid signature: {}", e))?; + let signature = + Signature::from_str(signature).map_err(|e| anyhow::anyhow!("Invalid signature: {}", e))?; Ok(signature) } @@ -314,4 +326,4 @@ pub async fn serialize_smart_transaction_and_encode( _ => return Err(anyhow::anyhow!("Unsupported encoding")), }; Ok((serialized, *signature)) -} \ No newline at end of file +} diff --git a/src/swqos/flashblock.rs b/src/swqos/flashblock.rs index 2c81329..6ea977c 100644 --- a/src/swqos/flashblock.rs +++ b/src/swqos/flashblock.rs @@ -1,4 +1,6 @@ -use crate::swqos::common::{default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode}; +use crate::swqos::common::{ + default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode, +}; use rand::seq::IndexedRandom; use reqwest::Client; use serde_json::json; @@ -6,14 +8,13 @@ use std::{sync::Arc, time::Instant}; use solana_transaction_status::UiTransactionEncoding; +use crate::swqos::SwqosClientTrait; +use crate::swqos::{SwqosType, TradeType}; use anyhow::Result; use solana_sdk::transaction::VersionedTransaction; -use crate::swqos::{SwqosType, TradeType}; -use crate::swqos::SwqosClientTrait; use crate::{common::SolanaRpcClient, constants::swqos::FLASHBLOCK_TIP_ACCOUNTS}; - #[derive(Clone)] pub struct FlashBlockClient { pub endpoint: String, @@ -24,16 +25,29 @@ pub struct FlashBlockClient { #[async_trait::async_trait] impl SwqosClientTrait for FlashBlockClient { - async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { self.send_transaction(trade_type, transaction, wait_confirmation).await } - async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, wait_confirmation: bool) -> Result<()> { + async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + wait_confirmation: bool, + ) -> Result<()> { self.send_transactions(trade_type, transactions, wait_confirmation).await } fn get_tip_account(&self) -> Result { - let tip_account = *FLASHBLOCK_TIP_ACCOUNTS.choose(&mut rand::rng()).or_else(|| FLASHBLOCK_TIP_ACCOUNTS.first()).unwrap(); + let tip_account = *FLASHBLOCK_TIP_ACCOUNTS + .choose(&mut rand::rng()) + .or_else(|| FLASHBLOCK_TIP_ACCOUNTS.first()) + .unwrap(); Ok(tip_account.to_string()) } @@ -49,9 +63,15 @@ impl FlashBlockClient { Self { rpc_client: Arc::new(rpc_client), endpoint, auth_token, http_client } } - pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + pub async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { let start_time = Instant::now(); - let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?; + let (content, signature) = + serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?; // FlashBlock API format let request_body = serde_json::to_string(&json!({ @@ -61,7 +81,9 @@ impl FlashBlockClient { let url = format!("{}/api/v2/submit-batch", self.endpoint); // Send request to FlashBlock - let response_text = self.http_client.post(&url) + let response_text = self + .http_client + .post(&url) .body(request_body) .header("Authorization", &self.auth_token) .header("Content-Type", "application/json") @@ -88,9 +110,13 @@ impl FlashBlockClient { Ok(_) => (), Err(e) => { println!(" signature: {:?}", signature); - println!(" [FlashBlock] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); + println!( + " [FlashBlock] {} confirmation failed: {:?}", + trade_type, + start_time.elapsed() + ); return Err(e); - }, + } } if wait_confirmation { println!(" signature: {:?}", signature); @@ -100,7 +126,12 @@ impl FlashBlockClient { Ok(()) } - pub async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, wait_confirmation: bool) -> Result<()> { + pub async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + wait_confirmation: bool, + ) -> Result<()> { for transaction in transactions { self.send_transaction(trade_type, transaction, wait_confirmation).await?; } diff --git a/src/swqos/helius.rs b/src/swqos/helius.rs index 8db1542..432c0a2 100644 --- a/src/swqos/helius.rs +++ b/src/swqos/helius.rs @@ -20,7 +20,9 @@ use std::sync::Arc; use std::time::Instant; use crate::common::SolanaRpcClient; -use crate::constants::swqos::{HELIUS_TIP_ACCOUNTS, SWQOS_MIN_TIP_HELIUS, SWQOS_MIN_TIP_HELIUS_SWQOS_ONLY}; +use crate::constants::swqos::{ + HELIUS_TIP_ACCOUNTS, SWQOS_MIN_TIP_HELIUS, SWQOS_MIN_TIP_HELIUS_SWQOS_ONLY, +}; use crate::swqos::{SwqosClientTrait, SwqosType, TradeType}; #[derive(Clone)] @@ -43,12 +45,7 @@ impl HeliusClient { let rpc_client = SolanaRpcClient::new(rpc_url); let http_client = default_http_client_builder().build().unwrap(); let submit_url = Self::build_submit_url(&endpoint, api_key.as_deref(), swqos_only); - Self { - submit_url, - rpc_client: Arc::new(rpc_client), - http_client, - swqos_only, - } + Self { submit_url, rpc_client: Arc::new(rpc_client), http_client, swqos_only } } /// Build URL once at construction; no per-request allocation. @@ -132,17 +129,10 @@ impl HeliusClient { return Err(anyhow::anyhow!("Helius Sender error: {}", err_msg)); } if response_json.get("result").is_some() && crate::common::sdk_log::sdk_log_enabled() { - println!( - " [helius] {} submitted: {:?}", - trade_type, - start_time.elapsed() - ); + println!(" [helius] {} submitted: {:?}", trade_type, start_time.elapsed()); } } else if crate::common::sdk_log::sdk_log_enabled() { - eprintln!( - " [helius] {} submission failed: {:?}", - trade_type, response_text - ); + eprintln!(" [helius] {} submission failed: {:?}", trade_type, response_text); } match poll_transaction_confirmation(&self.rpc_client, signature, wait_confirmation).await { @@ -159,15 +149,8 @@ impl HeliusClient { } } if wait_confirmation && crate::common::sdk_log::sdk_log_enabled() { - println!( - " signature: {:?}", - signature - ); - println!( - " [helius] {} confirmed: {:?}", - trade_type, - start_time.elapsed() - ); + println!(" signature: {:?}", signature); + println!(" [helius] {} confirmed: {:?}", trade_type, start_time.elapsed()); } Ok(()) } @@ -191,8 +174,7 @@ impl SwqosClientTrait for HeliusClient { wait_confirmation: bool, ) -> Result<()> { for transaction in transactions { - self.send_transaction(trade_type, transaction, wait_confirmation) - .await?; + self.send_transaction(trade_type, transaction, wait_confirmation).await?; } Ok(()) } diff --git a/src/swqos/jito.rs b/src/swqos/jito.rs index 6453152..904882e 100755 --- a/src/swqos/jito.rs +++ b/src/swqos/jito.rs @@ -1,5 +1,7 @@ - -use crate::swqos::common::{default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode, FormatBase64VersionedTransaction}; +use crate::swqos::common::{ + default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode, + FormatBase64VersionedTransaction, +}; use rand::seq::IndexedRandom; use reqwest::Client; use serde_json::json; @@ -7,14 +9,13 @@ use std::{sync::Arc, time::Instant}; use solana_transaction_status::UiTransactionEncoding; +use crate::swqos::SwqosClientTrait; +use crate::swqos::{SwqosType, TradeType}; use anyhow::Result; use solana_sdk::transaction::VersionedTransaction; -use crate::swqos::{SwqosType, TradeType}; -use crate::swqos::SwqosClientTrait; use crate::{common::SolanaRpcClient, constants::swqos::JITO_TIP_ACCOUNTS}; - pub struct JitoClient { pub endpoint: String, pub auth_token: String, @@ -24,11 +25,21 @@ pub struct JitoClient { #[async_trait::async_trait] impl SwqosClientTrait for JitoClient { - async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { self.send_transaction_impl(trade_type, transaction, wait_confirmation).await } - async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, wait_confirmation: bool) -> Result<()> { + async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + wait_confirmation: bool, + ) -> Result<()> { self.send_transactions_impl(trade_type, transactions, wait_confirmation).await } @@ -52,9 +63,15 @@ impl JitoClient { Self { rpc_client: Arc::new(rpc_client), endpoint, auth_token, http_client } } - pub async fn send_transaction_impl(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + pub async fn send_transaction_impl( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { let start_time = Instant::now(); - let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?; + let (content, signature) = + serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?; let request_body = serde_json::to_string(&json!({ "id": 1, @@ -76,8 +93,7 @@ impl JitoClient { let response = if self.auth_token.is_empty() { self.http_client.post(&endpoint) } else { - self.http_client.post(&endpoint) - .header("x-jito-auth", &self.auth_token) + self.http_client.post(&endpoint).header("x-jito-auth", &self.auth_token) }; let response_text = response .body(request_body) @@ -104,7 +120,7 @@ impl JitoClient { println!(" signature: {:?}", signature); println!(" [jito] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); return Err(e); - }, + } } if wait_confirmation { println!(" signature: {:?}", signature); @@ -114,9 +130,15 @@ impl JitoClient { Ok(()) } - pub async fn send_transactions_impl(&self, trade_type: TradeType, transactions: &Vec, _wait_confirmation: bool) -> Result<()> { + pub async fn send_transactions_impl( + &self, + trade_type: TradeType, + transactions: &Vec, + _wait_confirmation: bool, + ) -> Result<()> { let start_time = Instant::now(); - let txs_base64 = transactions.iter().map(|tx| tx.to_base64_string()).collect::>(); + let txs_base64 = + transactions.iter().map(|tx| tx.to_base64_string()).collect::>(); let body = serde_json::json!({ "jsonrpc": "2.0", "method": "sendBundle", @@ -135,8 +157,7 @@ impl JitoClient { let response = if self.auth_token.is_empty() { self.http_client.post(&endpoint) } else { - self.http_client.post(&endpoint) - .header("x-jito-auth", &self.auth_token) + self.http_client.post(&endpoint).header("x-jito-auth", &self.auth_token) }; let response_text = response .body(body.to_string()) @@ -156,4 +177,4 @@ impl JitoClient { Ok(()) } -} \ No newline at end of file +} diff --git a/src/swqos/lightspeed.rs b/src/swqos/lightspeed.rs index 7834ea9..29ff631 100644 --- a/src/swqos/lightspeed.rs +++ b/src/swqos/lightspeed.rs @@ -1,4 +1,6 @@ -use crate::swqos::common::{default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode}; +use crate::swqos::common::{ + default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode, +}; use rand::seq::IndexedRandom; use reqwest::Client; use serde_json::json; @@ -6,10 +8,10 @@ use std::{sync::Arc, time::Instant}; use solana_transaction_status::UiTransactionEncoding; +use crate::swqos::SwqosClientTrait; +use crate::swqos::{SwqosType, TradeType}; use anyhow::Result; use solana_sdk::transaction::VersionedTransaction; -use crate::swqos::{SwqosType, TradeType}; -use crate::swqos::SwqosClientTrait; use crate::{common::SolanaRpcClient, constants::swqos::LIGHTSPEED_TIP_ACCOUNTS}; @@ -23,16 +25,29 @@ pub struct LightspeedClient { #[async_trait::async_trait] impl SwqosClientTrait for LightspeedClient { - async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { self.send_transaction(trade_type, transaction, wait_confirmation).await } - async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, wait_confirmation: bool) -> Result<()> { + async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + wait_confirmation: bool, + ) -> Result<()> { self.send_transactions(trade_type, transactions, wait_confirmation).await } fn get_tip_account(&self) -> Result { - let tip_account = *LIGHTSPEED_TIP_ACCOUNTS.choose(&mut rand::rng()).or_else(|| LIGHTSPEED_TIP_ACCOUNTS.first()).unwrap(); + let tip_account = *LIGHTSPEED_TIP_ACCOUNTS + .choose(&mut rand::rng()) + .or_else(|| LIGHTSPEED_TIP_ACCOUNTS.first()) + .unwrap(); Ok(tip_account.to_string()) } @@ -50,9 +65,15 @@ impl LightspeedClient { Self { rpc_client: Arc::new(rpc_client), endpoint, auth_token, http_client } } - pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + pub async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { let start_time = Instant::now(); - let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?; + let (content, signature) = + serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?; // Lightspeed uses standard Solana JSON-RPC format for sendTransaction let request_body = serde_json::to_string(&json!({ @@ -70,7 +91,9 @@ impl LightspeedClient { ] }))?; - let response_text = self.http_client.post(&self.endpoint) + let response_text = self + .http_client + .post(&self.endpoint) .body(request_body) .header("Content-Type", "application/json") .send() @@ -93,9 +116,13 @@ impl LightspeedClient { Ok(_) => (), Err(e) => { println!(" signature: {:?}", signature); - println!(" [lightspeed] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); + println!( + " [lightspeed] {} confirmation failed: {:?}", + trade_type, + start_time.elapsed() + ); return Err(e); - }, + } } if wait_confirmation { println!(" signature: {:?}", signature); @@ -105,7 +132,12 @@ impl LightspeedClient { Ok(()) } - pub async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, wait_confirmation: bool) -> Result<()> { + pub async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + wait_confirmation: bool, + ) -> Result<()> { for transaction in transactions { self.send_transaction(trade_type, transaction, wait_confirmation).await?; } diff --git a/src/swqos/mod.rs b/src/swqos/mod.rs index 05b8306..bfb6b9f 100755 --- a/src/swqos/mod.rs +++ b/src/swqos/mod.rs @@ -1,22 +1,22 @@ +pub mod astralane; pub mod astralane_quic; -pub mod common; -pub mod serialization; -pub mod solana_rpc; -pub mod jito; -pub mod nextblock; -pub mod zeroslot; -pub mod temporal; +pub mod blockrazor; pub mod bloxroute; +pub mod common; +pub mod flashblock; +pub mod helius; +pub mod jito; +pub mod lightspeed; +pub mod nextblock; pub mod node1; pub mod node1_quic; -pub mod flashblock; -pub mod blockrazor; -pub mod astralane; -pub mod stellium; -pub mod lightspeed; +pub mod serialization; +pub mod solana_rpc; pub mod soyas; pub mod speedlanding; -pub mod helius; +pub mod stellium; +pub mod temporal; +pub mod zeroslot; use std::sync::Arc; @@ -29,55 +29,25 @@ use anyhow::Result; use crate::{ common::SolanaRpcClient, constants::swqos::{ - SWQOS_ENDPOINTS_BLOX, - SWQOS_ENDPOINTS_JITO, - SWQOS_ENDPOINTS_NEXTBLOCK, - SWQOS_ENDPOINTS_TEMPORAL, - SWQOS_ENDPOINTS_ZERO_SLOT, - SWQOS_ENDPOINTS_NODE1, - SWQOS_ENDPOINTS_NODE1_QUIC, - SWQOS_ENDPOINTS_FLASHBLOCK, - SWQOS_ENDPOINTS_BLOCKRAZOR, - SWQOS_ENDPOINTS_ASTRALANE, - SWQOS_ENDPOINTS_ASTRALANE_QUIC, - SWQOS_ENDPOINTS_STELLIUM, - SWQOS_ENDPOINTS_SOYAS, - SWQOS_ENDPOINTS_SPEEDLANDING, - SWQOS_ENDPOINTS_HELIUS, - SWQOS_MIN_TIP_DEFAULT, - SWQOS_MIN_TIP_JITO, - SWQOS_MIN_TIP_NEXTBLOCK, - SWQOS_MIN_TIP_ZERO_SLOT, - SWQOS_MIN_TIP_TEMPORAL, - SWQOS_MIN_TIP_BLOXROUTE, - SWQOS_MIN_TIP_NODE1, - SWQOS_MIN_TIP_FLASHBLOCK, - SWQOS_MIN_TIP_BLOCKRAZOR, - SWQOS_MIN_TIP_ASTRALANE, - SWQOS_MIN_TIP_STELLIUM, - SWQOS_MIN_TIP_LIGHTSPEED, - SWQOS_MIN_TIP_SOYAS, - SWQOS_MIN_TIP_SPEEDLANDING, - SWQOS_MIN_TIP_HELIUS, + SWQOS_ENDPOINTS_ASTRALANE, SWQOS_ENDPOINTS_ASTRALANE_QUIC, SWQOS_ENDPOINTS_BLOCKRAZOR, + SWQOS_ENDPOINTS_BLOX, SWQOS_ENDPOINTS_FLASHBLOCK, SWQOS_ENDPOINTS_HELIUS, + SWQOS_ENDPOINTS_JITO, SWQOS_ENDPOINTS_NEXTBLOCK, SWQOS_ENDPOINTS_NODE1, + SWQOS_ENDPOINTS_NODE1_QUIC, SWQOS_ENDPOINTS_SOYAS, SWQOS_ENDPOINTS_SPEEDLANDING, + SWQOS_ENDPOINTS_STELLIUM, SWQOS_ENDPOINTS_TEMPORAL, SWQOS_ENDPOINTS_ZERO_SLOT, + SWQOS_MIN_TIP_ASTRALANE, SWQOS_MIN_TIP_BLOCKRAZOR, SWQOS_MIN_TIP_BLOXROUTE, + SWQOS_MIN_TIP_DEFAULT, SWQOS_MIN_TIP_FLASHBLOCK, SWQOS_MIN_TIP_HELIUS, SWQOS_MIN_TIP_JITO, + SWQOS_MIN_TIP_LIGHTSPEED, SWQOS_MIN_TIP_NEXTBLOCK, SWQOS_MIN_TIP_NODE1, + SWQOS_MIN_TIP_SOYAS, SWQOS_MIN_TIP_SPEEDLANDING, SWQOS_MIN_TIP_STELLIUM, + SWQOS_MIN_TIP_TEMPORAL, SWQOS_MIN_TIP_ZERO_SLOT, }, swqos::{ - bloxroute::BloxrouteClient, - jito::JitoClient, - nextblock::NextBlockClient, - solana_rpc::SolRpcClient, - temporal::TemporalClient, + astralane::AstralaneClient, blockrazor::BlockRazorClient, bloxroute::BloxrouteClient, + flashblock::FlashBlockClient, helius::HeliusClient, jito::JitoClient, + lightspeed::LightspeedClient, nextblock::NextBlockClient, node1::Node1Client, + node1_quic::Node1QuicClient, solana_rpc::SolRpcClient, soyas::SoyasClient, + speedlanding::SpeedlandingClient, stellium::StelliumClient, temporal::TemporalClient, zeroslot::ZeroSlotClient, - node1::Node1Client, - node1_quic::Node1QuicClient, - flashblock::FlashBlockClient, - blockrazor::BlockRazorClient, - astralane::AstralaneClient, - stellium::StelliumClient, - lightspeed::LightspeedClient, - soyas::SoyasClient, - speedlanding::SpeedlandingClient, - helius::HeliusClient, - } + }, }; lazy_static::lazy_static! { @@ -90,7 +60,7 @@ lazy_static::lazy_static! { /// Providers added here will be disabled even if configured by user /// To enable a provider, remove it from this list pub const SWQOS_BLACKLIST: &[SwqosType] = &[ - SwqosType::NextBlock, // NextBlock is disabled by default + SwqosType::NextBlock, // NextBlock is disabled by default ]; /// SWQOS 提交通道:HTTP 或 QUIC(低延迟)。部分提供商(如 Astralane)支持 QUIC。 @@ -166,8 +136,18 @@ pub type SwqosClient = dyn SwqosClientTrait + Send + Sync + 'static; #[async_trait::async_trait] pub trait SwqosClientTrait { - async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()>; - async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, wait_confirmation: bool) -> Result<()>; + async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()>; + async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + wait_confirmation: bool, + ) -> Result<()>; fn get_tip_account(&self) -> Result; fn get_swqos_type(&self) -> SwqosType; /// Minimum tip in SOL required by this provider. Helius returns lower value when swqos_only is true. @@ -243,7 +223,7 @@ pub enum SwqosConfig { } impl SwqosConfig { - pub fn swqos_type(&self) -> SwqosType{ + pub fn swqos_type(&self) -> SwqosType { match self { SwqosConfig::Default(_) => SwqosType::Default, SwqosConfig::Jito(_, _, _) => SwqosType::Jito, @@ -292,167 +272,124 @@ impl SwqosConfig { } } - pub async fn get_swqos_client(rpc_url: String, commitment: CommitmentConfig, swqos_config: SwqosConfig) -> Result> { + pub async fn get_swqos_client( + rpc_url: String, + commitment: CommitmentConfig, + swqos_config: SwqosConfig, + ) -> Result> { match swqos_config { SwqosConfig::Jito(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::Jito, region, url); - let jito_client = JitoClient::new( - rpc_url.clone(), - endpoint, - auth_token - ); + let jito_client = JitoClient::new(rpc_url.clone(), endpoint, auth_token); Ok(Arc::new(jito_client)) } SwqosConfig::NextBlock(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::NextBlock, region, url); - let nextblock_client = NextBlockClient::new( - rpc_url.clone(), - endpoint.to_string(), - auth_token - ); + let nextblock_client = + NextBlockClient::new(rpc_url.clone(), endpoint.to_string(), auth_token); Ok(Arc::new(nextblock_client)) - }, + } SwqosConfig::ZeroSlot(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::ZeroSlot, region, url); - let zeroslot_client = ZeroSlotClient::new( - rpc_url.clone(), - endpoint.to_string(), - auth_token - ); + let zeroslot_client = + ZeroSlotClient::new(rpc_url.clone(), endpoint.to_string(), auth_token); Ok(Arc::new(zeroslot_client)) - }, - SwqosConfig::Temporal(auth_token, region, url) => { + } + SwqosConfig::Temporal(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::Temporal, region, url); - let temporal_client = TemporalClient::new( - rpc_url.clone(), - endpoint.to_string(), - auth_token - ); + let temporal_client = + TemporalClient::new(rpc_url.clone(), endpoint.to_string(), auth_token); Ok(Arc::new(temporal_client)) - }, - SwqosConfig::Bloxroute(auth_token, region, url) => { + } + SwqosConfig::Bloxroute(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::Bloxroute, region, url); - let bloxroute_client = BloxrouteClient::new( - rpc_url.clone(), - endpoint.to_string(), - auth_token - ); + let bloxroute_client = + BloxrouteClient::new(rpc_url.clone(), endpoint.to_string(), auth_token); Ok(Arc::new(bloxroute_client)) - }, + } SwqosConfig::Node1(auth_token, region, url, transport) => { let use_quic = transport.map_or(false, |t| t == SwqosTransport::Quic); if use_quic { let quic_endpoint = url .unwrap_or_else(|| SWQOS_ENDPOINTS_NODE1_QUIC[region as usize].to_string()); - let node1_quic = Node1QuicClient::connect( - &quic_endpoint, - &auth_token, - rpc_url.clone(), - ) - .await?; + let node1_quic = + Node1QuicClient::connect(&quic_endpoint, &auth_token, rpc_url.clone()) + .await?; Ok(Arc::new(node1_quic)) } else { let endpoint = SwqosConfig::get_endpoint(SwqosType::Node1, region, url); - let node1_client = Node1Client::new( - rpc_url.clone(), - endpoint.to_string(), - auth_token, - ); + let node1_client = + Node1Client::new(rpc_url.clone(), endpoint.to_string(), auth_token); Ok(Arc::new(node1_client)) } - }, + } SwqosConfig::FlashBlock(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::FlashBlock, region, url); - let flashblock_client = FlashBlockClient::new( - rpc_url.clone(), - endpoint.to_string(), - auth_token - ); + let flashblock_client = + FlashBlockClient::new(rpc_url.clone(), endpoint.to_string(), auth_token); Ok(Arc::new(flashblock_client)) - }, + } SwqosConfig::BlockRazor(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::BlockRazor, region, url); - let blockrazor_client = BlockRazorClient::new( - rpc_url.clone(), - endpoint.to_string(), - auth_token - ); + let blockrazor_client = + BlockRazorClient::new(rpc_url.clone(), endpoint.to_string(), auth_token); Ok(Arc::new(blockrazor_client)) - }, + } SwqosConfig::Astralane(auth_token, region, url, transport) => { let use_quic = transport.map_or(false, |t| t == SwqosTransport::Quic); if use_quic { - let quic_endpoint = url - .unwrap_or_else(|| SWQOS_ENDPOINTS_ASTRALANE_QUIC[region as usize].to_string()); + let quic_endpoint = url.unwrap_or_else(|| { + SWQOS_ENDPOINTS_ASTRALANE_QUIC[region as usize].to_string() + }); let astralane_client = - AstralaneClient::new_quic(rpc_url.clone(), &quic_endpoint, auth_token).await?; + AstralaneClient::new_quic(rpc_url.clone(), &quic_endpoint, auth_token) + .await?; Ok(Arc::new(astralane_client)) } else { let endpoint = SwqosConfig::get_endpoint(SwqosType::Astralane, region, url); - let astralane_client = AstralaneClient::new( - rpc_url.clone(), - endpoint.to_string(), - auth_token, - ); + let astralane_client = + AstralaneClient::new(rpc_url.clone(), endpoint.to_string(), auth_token); Ok(Arc::new(astralane_client)) } - }, + } SwqosConfig::Stellium(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::Stellium, region, url); - let stellium_client = StelliumClient::new( - rpc_url.clone(), - endpoint.to_string(), - auth_token - ); + let stellium_client = + StelliumClient::new(rpc_url.clone(), endpoint.to_string(), auth_token); Ok(Arc::new(stellium_client)) - }, + } SwqosConfig::Lightspeed(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::Lightspeed, region, url); - let lightspeed_client = LightspeedClient::new( - rpc_url.clone(), - endpoint.to_string(), - auth_token - ); + let lightspeed_client = + LightspeedClient::new(rpc_url.clone(), endpoint.to_string(), auth_token); Ok(Arc::new(lightspeed_client)) - }, + } SwqosConfig::Soyas(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::Soyas, region, url); - let soyas_client = SoyasClient::new( - rpc_url.clone(), - endpoint.to_string(), - auth_token - ).await?; + let soyas_client = + SoyasClient::new(rpc_url.clone(), endpoint.to_string(), auth_token).await?; Ok(Arc::new(soyas_client)) - }, + } SwqosConfig::Speedlanding(auth_token, region, url) => { let endpoint = SwqosConfig::get_endpoint(SwqosType::Speedlanding, region, url); - let speedlanding_client = SpeedlandingClient::new( - rpc_url.clone(), - endpoint.to_string(), - auth_token - ).await?; + let speedlanding_client = + SpeedlandingClient::new(rpc_url.clone(), endpoint.to_string(), auth_token) + .await?; Ok(Arc::new(speedlanding_client)) - }, + } SwqosConfig::Helius(api_key, region, url, swqos_only) => { let swqos_only = swqos_only.unwrap_or(false); let endpoint = SwqosConfig::get_endpoint(SwqosType::Helius, region, url.clone()); let api_key_opt = if api_key.is_empty() { None } else { Some(api_key.clone()) }; - let helius_client = HeliusClient::new( - rpc_url.clone(), - endpoint, - api_key_opt, - swqos_only, - ); + let helius_client = + HeliusClient::new(rpc_url.clone(), endpoint, api_key_opt, swqos_only); Ok(Arc::new(helius_client)) - }, + } SwqosConfig::Default(endpoint) => { - let rpc = SolanaRpcClient::new_with_commitment( - endpoint, - commitment - ); + let rpc = SolanaRpcClient::new_with_commitment(endpoint, commitment); let rpc_client = SolRpcClient::new(Arc::new(rpc)); Ok(Arc::new(rpc_client)) } } } -} \ No newline at end of file +} diff --git a/src/swqos/nextblock.rs b/src/swqos/nextblock.rs index a1ca6c7..1512d27 100755 --- a/src/swqos/nextblock.rs +++ b/src/swqos/nextblock.rs @@ -1,4 +1,6 @@ -use crate::swqos::common::{default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode}; +use crate::swqos::common::{ + default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode, +}; use rand::seq::IndexedRandom; use reqwest::Client; use serde_json::json; @@ -6,10 +8,10 @@ use std::{sync::Arc, time::Instant}; use solana_transaction_status::UiTransactionEncoding; +use crate::swqos::SwqosClientTrait; +use crate::swqos::{SwqosType, TradeType}; use anyhow::Result; use solana_sdk::transaction::VersionedTransaction; -use crate::swqos::{SwqosType, TradeType}; -use crate::swqos::SwqosClientTrait; use crate::{common::SolanaRpcClient, constants::swqos::NEXTBLOCK_TIP_ACCOUNTS}; @@ -23,16 +25,29 @@ pub struct NextBlockClient { #[async_trait::async_trait] impl SwqosClientTrait for NextBlockClient { - async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { self.send_transaction(trade_type, transaction, wait_confirmation).await } - async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, wait_confirmation: bool) -> Result<()> { + async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + wait_confirmation: bool, + ) -> Result<()> { self.send_transactions(trade_type, transactions, wait_confirmation).await } fn get_tip_account(&self) -> Result { - let tip_account = *NEXTBLOCK_TIP_ACCOUNTS.choose(&mut rand::rng()).or_else(|| NEXTBLOCK_TIP_ACCOUNTS.first()).unwrap(); + let tip_account = *NEXTBLOCK_TIP_ACCOUNTS + .choose(&mut rand::rng()) + .or_else(|| NEXTBLOCK_TIP_ACCOUNTS.first()) + .unwrap(); Ok(tip_account.to_string()) } @@ -54,9 +69,15 @@ impl NextBlockClient { Self { rpc_client: Arc::new(rpc_client), endpoint, auth_token, http_client } } - pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + pub async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { let start_time = Instant::now(); - let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?; + let (content, signature) = + serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?; let request_body = serde_json::to_string(&json!({ "transaction": { @@ -65,7 +86,9 @@ impl NextBlockClient { "frontRunningProtection": false }))?; - let response_text = self.http_client.post(&self.endpoint) + let response_text = self + .http_client + .post(&self.endpoint) .body(request_body) .header("Authorization", &self.auth_token) .header("Content-Type", "application/json") @@ -89,9 +112,13 @@ impl NextBlockClient { Ok(_) => (), Err(e) => { println!(" signature: {:?}", signature); - println!(" [nextblock] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); + println!( + " [nextblock] {} confirmation failed: {:?}", + trade_type, + start_time.elapsed() + ); return Err(e); - }, + } } if wait_confirmation { println!(" signature: {:?}", signature); @@ -101,10 +128,15 @@ impl NextBlockClient { Ok(()) } - pub async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, wait_confirmation: bool) -> Result<()> { + pub async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + wait_confirmation: bool, + ) -> Result<()> { for transaction in transactions { self.send_transaction(trade_type, transaction, wait_confirmation).await?; } Ok(()) } -} \ No newline at end of file +} diff --git a/src/swqos/node1.rs b/src/swqos/node1.rs index fc74637..967d14f 100644 --- a/src/swqos/node1.rs +++ b/src/swqos/node1.rs @@ -1,21 +1,23 @@ -use crate::swqos::common::{default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode}; +use crate::swqos::common::{ + default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode, +}; use rand::seq::IndexedRandom; use reqwest::Client; use serde_json::json; use std::{sync::Arc, time::Instant}; -use std::time::Duration; use solana_transaction_status::UiTransactionEncoding; +use std::time::Duration; +use crate::swqos::SwqosClientTrait; +use crate::swqos::{SwqosType, TradeType}; use anyhow::Result; use solana_sdk::transaction::VersionedTransaction; -use crate::swqos::{SwqosType, TradeType}; -use crate::swqos::SwqosClientTrait; use crate::{common::SolanaRpcClient, constants::swqos::NODE1_TIP_ACCOUNTS}; -use tokio::task::JoinHandle; use std::sync::atomic::{AtomicBool, Ordering}; +use tokio::task::JoinHandle; #[derive(Clone)] pub struct Node1Client { @@ -29,16 +31,29 @@ pub struct Node1Client { #[async_trait::async_trait] impl SwqosClientTrait for Node1Client { - async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { self.send_transaction(trade_type, transaction, wait_confirmation).await } - async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, wait_confirmation: bool) -> Result<()> { + async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + wait_confirmation: bool, + ) -> Result<()> { self.send_transactions(trade_type, transactions, wait_confirmation).await } fn get_tip_account(&self) -> Result { - let tip_account = *NODE1_TIP_ACCOUNTS.choose(&mut rand::rng()).or_else(|| NODE1_TIP_ACCOUNTS.first()).unwrap(); + let tip_account = *NODE1_TIP_ACCOUNTS + .choose(&mut rand::rng()) + .or_else(|| NODE1_TIP_ACCOUNTS.first()) + .unwrap(); Ok(tip_account.to_string()) } @@ -51,22 +66,22 @@ impl Node1Client { pub fn new(rpc_url: String, endpoint: String, auth_token: String) -> Self { let rpc_client = SolanaRpcClient::new(rpc_url); let http_client = default_http_client_builder().build().unwrap(); - - let client = Self { - rpc_client: Arc::new(rpc_client), - endpoint, - auth_token, + + let client = Self { + rpc_client: Arc::new(rpc_client), + endpoint, + auth_token, http_client, ping_handle: Arc::new(tokio::sync::Mutex::new(None)), stop_ping: Arc::new(AtomicBool::new(false)), }; - + // Start ping task let client_clone = client.clone(); tokio::spawn(async move { client_clone.start_ping_task().await; }); - + client } @@ -76,7 +91,7 @@ impl Node1Client { let auth_token = self.auth_token.clone(); let http_client = self.http_client.clone(); let stop_ping = self.stop_ping.clone(); - + let handle = tokio::spawn(async move { // Immediate first ping to warm connection and reduce first-submit cold start latency if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await { @@ -90,14 +105,15 @@ impl Node1Client { if stop_ping.load(Ordering::Relaxed) { break; } - if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await { + if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await + { if crate::common::sdk_log::sdk_log_enabled() { eprintln!("Node1 ping request failed: {}", e); } } } }); - + // Update ping_handle - use Mutex to safely update { let mut ping_guard = self.ping_handle.lock().await; @@ -109,7 +125,11 @@ impl Node1Client { } /// Send ping request to /ping endpoint - async fn send_ping_request(http_client: &Client, endpoint: &str, _auth_token: &str) -> Result<()> { + async fn send_ping_request( + http_client: &Client, + endpoint: &str, + _auth_token: &str, + ) -> Result<()> { // Build ping URL let ping_url = if endpoint.ends_with('/') { format!("{}ping", endpoint) @@ -118,10 +138,8 @@ impl Node1Client { }; // Short timeout for ping; consume body so connection is returned to pool for reuse by submit - let response = http_client.get(&ping_url) - .timeout(Duration::from_millis(1500)) - .send() - .await?; + let response = + http_client.get(&ping_url).timeout(Duration::from_millis(1500)).send().await?; let status = response.status(); let _ = response.bytes().await; if !status.is_success() && crate::common::sdk_log::sdk_log_enabled() { @@ -130,9 +148,15 @@ impl Node1Client { Ok(()) } - pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + pub async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { let start_time = Instant::now(); - let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?; + let (content, signature) = + serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?; let request_body = serde_json::to_string(&json!({ "jsonrpc": "2.0", @@ -145,7 +169,9 @@ impl Node1Client { }))?; // Node1 uses api-key header instead of URL parameter - let response_text = self.http_client.post(&self.endpoint) + let response_text = self + .http_client + .post(&self.endpoint) .body(request_body) .header("Content-Type", "application/json") .header("api-key", &self.auth_token) @@ -173,10 +199,14 @@ impl Node1Client { Err(e) => { if crate::common::sdk_log::sdk_log_enabled() { println!(" signature: {:?}", signature); - println!(" [node1] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); + println!( + " [node1] {} confirmation failed: {:?}", + trade_type, + start_time.elapsed() + ); } return Err(e); - }, + } } if wait_confirmation && crate::common::sdk_log::sdk_log_enabled() { println!(" signature: {:?}", signature); @@ -186,7 +216,12 @@ impl Node1Client { Ok(()) } - pub async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, wait_confirmation: bool) -> Result<()> { + pub async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + wait_confirmation: bool, + ) -> Result<()> { for transaction in transactions { self.send_transaction(trade_type, transaction, wait_confirmation).await?; } @@ -198,7 +233,7 @@ impl Drop for Node1Client { fn drop(&mut self) { // Ensure ping task stops when client is destroyed self.stop_ping.store(true, Ordering::Relaxed); - + // Try to stop ping task immediately // Use tokio::spawn to avoid blocking Drop let ping_handle = self.ping_handle.clone(); diff --git a/src/swqos/node1_quic.rs b/src/swqos/node1_quic.rs index 45f78bf..1de62ba 100644 --- a/src/swqos/node1_quic.rs +++ b/src/swqos/node1_quic.rs @@ -10,8 +10,8 @@ use quinn::{ClientConfig, Connection, Endpoint, IdleTimeout, RecvStream, Transpo use std::net::ToSocketAddrs; use std::sync::Arc; use std::time::Duration; -use tokio::time::timeout; use tokio::sync::Mutex; +use tokio::time::timeout; use uuid::Uuid; use crate::common::SolanaRpcClient; @@ -41,44 +41,34 @@ pub struct Node1QuicClient { impl Node1QuicClient { /// Connect and authenticate. Reuse the returned client for all subsequent sends. - pub async fn connect( - server_addr: &str, - api_key: &str, - rpc_url: String, - ) -> Result { + pub async fn connect(server_addr: &str, api_key: &str, rpc_url: String) -> Result { let socket_addr = server_addr .to_socket_addrs() .context("resolve Node1 QUIC server address")? .next() .context("no socket address for Node1 QUIC")?; - let api_key_uuid = Uuid::parse_str(api_key).context("Node1 API key must be a valid UUID")?; + let api_key_uuid = + Uuid::parse_str(api_key).context("Node1 API key must be a valid UUID")?; let api_key_bytes: [u8; 16] = *api_key_uuid.as_bytes(); - let server_name = server_addr - .split(':') - .next() - .unwrap_or(server_addr); + let server_name = server_addr.split(':').next().unwrap_or(server_addr); let client_config = Self::build_client_config()?; - let mut endpoint = Endpoint::client("0.0.0.0:0".parse()?) - .context("create QUIC endpoint")?; + let mut endpoint = + Endpoint::client("0.0.0.0:0".parse()?).context("create QUIC endpoint")?; endpoint.set_default_client_config(client_config); - let connecting = endpoint - .connect(socket_addr, server_name) - .context("Node1 QUIC connect failed")?; + let connecting = + endpoint.connect(socket_addr, server_name).context("Node1 QUIC connect failed")?; let connection = timeout(CONNECT_TIMEOUT, connecting) .await .context("Node1 QUIC connect timeout")? .context("Node1 QUIC handshake failed")?; - timeout( - AUTH_TIMEOUT, - Self::authenticate(&connection, &api_key_bytes), - ) - .await - .context("Node1 QUIC auth timeout")??; + timeout(AUTH_TIMEOUT, Self::authenticate(&connection, &api_key_bytes)) + .await + .context("Node1 QUIC auth timeout")??; Ok(Self { endpoint, @@ -96,8 +86,7 @@ impl Node1QuicClient { .with_custom_certificate_verifier(Arc::new(SkipServerVerification)) .with_no_client_auth(); - let client_crypto = QuicClientConfig::try_from(crypto) - .context("build QUIC TLS config")?; + let client_crypto = QuicClientConfig::try_from(crypto).context("build QUIC TLS config")?; let mut client_config = ClientConfig::new(Arc::new(client_crypto)); let mut transport = TransportConfig::default(); @@ -140,12 +129,9 @@ impl Node1QuicClient { .context("Node1 QUIC reconnect timeout")? .context("Node1 QUIC re-handshake failed")?; - timeout( - AUTH_TIMEOUT, - Self::authenticate(&connection, &self.api_key_uuid), - ) - .await - .context("Node1 QUIC re-auth timeout")??; + timeout(AUTH_TIMEOUT, Self::authenticate(&connection, &self.api_key_uuid)) + .await + .context("Node1 QUIC re-auth timeout")??; let mut g = self.connection.lock().await; *g = connection.clone(); @@ -201,16 +187,16 @@ impl SwqosClientTrait for Node1QuicClient { let signature = transaction.signatures.first().copied().unwrap_or_default(); let tx_bytes = bincode::serialize(transaction).context("Node1 QUIC: bincode serialize")?; - let (status, msg) = timeout( - SEND_TIMEOUT, - self.send_transaction_bytes(&tx_bytes), - ) - .await - .context("Node1 QUIC send timeout")??; + let (status, msg) = timeout(SEND_TIMEOUT, self.send_transaction_bytes(&tx_bytes)) + .await + .context("Node1 QUIC send timeout")??; if status != 200 { if crate::common::sdk_log::sdk_log_enabled() { - eprintln!(" [node1-quic] {} submit failed: status={} msg={}", trade_type, status, msg); + eprintln!( + " [node1-quic] {} submit failed: status={} msg={}", + trade_type, status, msg + ); } anyhow::bail!("Node1 QUIC submit failed: status={} msg={}", status, msg); } @@ -229,7 +215,11 @@ impl SwqosClientTrait for Node1QuicClient { } Err(e) => { if crate::common::sdk_log::sdk_log_enabled() { - eprintln!(" [node1-quic] {} confirmation failed: {:?}", trade_type, start.elapsed()); + eprintln!( + " [node1-quic] {} confirmation failed: {:?}", + trade_type, + start.elapsed() + ); } Err(e) } diff --git a/src/swqos/speedlanding.rs b/src/swqos/speedlanding.rs index 1a52a36..660ca8f 100644 --- a/src/swqos/speedlanding.rs +++ b/src/swqos/speedlanding.rs @@ -119,9 +119,11 @@ impl SpeedlandingClient { let _guard = self.reconnect.lock().await; let current = self.connection.load_full(); if current.close_reason().is_some() { - let connecting = self - .endpoint - .connect_with(self.client_config.clone(), self.addr, self.server_name.as_str())?; + let connecting = self.endpoint.connect_with( + self.client_config.clone(), + self.addr, + self.server_name.as_str(), + )?; let connection = timeout(CONNECT_TIMEOUT, connecting) .await .context("Speedlanding QUIC reconnect timeout")? @@ -151,7 +153,8 @@ impl SwqosClientTrait for SpeedlandingClient { let start_time = Instant::now(); let (buf_guard, signature) = serialize_transaction_bincode_sync(transaction)?; let connection = self.ensure_connected().await?; - let mut send_result = timeout(SEND_TIMEOUT, Self::try_send_bytes(&connection, &*buf_guard)).await; + let mut send_result = + timeout(SEND_TIMEOUT, Self::try_send_bytes(&connection, &*buf_guard)).await; let need_retry = match &send_result { Ok(Ok(())) => false, Ok(Err(_)) | Err(_) => true, @@ -161,16 +164,20 @@ impl SwqosClientTrait for SpeedlandingClient { eprintln!(" [speedlanding] {} send failed or timeout, reconnecting", trade_type); } let connection = self.ensure_connected().await?; - send_result = timeout(SEND_TIMEOUT, Self::try_send_bytes(&connection, &*buf_guard)).await; + send_result = + timeout(SEND_TIMEOUT, Self::try_send_bytes(&connection, &*buf_guard)).await; } - send_result - .context("Speedlanding QUIC send timeout")??; + send_result.context("Speedlanding QUIC send timeout")??; match poll_transaction_confirmation(&self.rpc_client, signature, wait_confirmation).await { Ok(_) => (), Err(e) => { if crate::common::sdk_log::sdk_log_enabled() { println!(" signature: {:?}", signature); - println!(" [speedlanding] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); + println!( + " [speedlanding] {} confirmation failed: {:?}", + trade_type, + start_time.elapsed() + ); } return Err(e); } diff --git a/src/swqos/stellium.rs b/src/swqos/stellium.rs index 592997d..088c827 100644 --- a/src/swqos/stellium.rs +++ b/src/swqos/stellium.rs @@ -1,21 +1,22 @@ -use crate::swqos::common::{default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode}; +use crate::swqos::common::{ + default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode, +}; use rand::seq::IndexedRandom; use reqwest::Client; use serde_json::json; -use std::{sync::Arc, time::Instant}; use std::sync::atomic::{AtomicBool, Ordering}; +use std::{sync::Arc, time::Instant}; -use std::time::Duration; use solana_transaction_status::UiTransactionEncoding; +use std::time::Duration; +use crate::swqos::SwqosClientTrait; +use crate::swqos::{SwqosType, TradeType}; use anyhow::Result; use solana_sdk::transaction::VersionedTransaction; -use crate::swqos::{SwqosType, TradeType}; -use crate::swqos::SwqosClientTrait; use crate::{common::SolanaRpcClient, constants::swqos::STELLIUM_TIP_ACCOUNTS}; - #[derive(Clone)] pub struct StelliumClient { pub endpoint: String, @@ -27,16 +28,29 @@ pub struct StelliumClient { #[async_trait::async_trait] impl SwqosClientTrait for StelliumClient { - async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { self.send_transaction(trade_type, transaction, wait_confirmation).await } - async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, wait_confirmation: bool) -> Result<()> { + async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + wait_confirmation: bool, + ) -> Result<()> { self.send_transactions(trade_type, transactions, wait_confirmation).await } fn get_tip_account(&self) -> Result { - let tip_account = *STELLIUM_TIP_ACCOUNTS.choose(&mut rand::rng()).or_else(|| STELLIUM_TIP_ACCOUNTS.first()).unwrap(); + let tip_account = *STELLIUM_TIP_ACCOUNTS + .choose(&mut rand::rng()) + .or_else(|| STELLIUM_TIP_ACCOUNTS.first()) + .unwrap(); Ok(tip_account.to_string()) } @@ -79,7 +93,9 @@ impl StelliumClient { tokio::spawn(async move { // Immediate first ping to warm connection and reduce first-submit cold start latency let url = format!("{}/{}", endpoint, auth_token); - if let Ok(resp) = http_client.get(&url).timeout(Duration::from_millis(1500)).send().await { + if let Ok(resp) = + http_client.get(&url).timeout(Duration::from_millis(1500)).send().await + { let status = resp.status(); let _ = resp.bytes().await; if !status.is_success() && crate::common::sdk_log::sdk_log_enabled() { @@ -111,9 +127,15 @@ impl StelliumClient { }); } - pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + pub async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { let start_time = Instant::now(); - let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?; + let (content, signature) = + serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?; // Stellium uses standard Solana sendTransaction format let request_body = serde_json::to_string(&json!({ @@ -130,7 +152,9 @@ impl StelliumClient { let url = format!("{}/{}", self.endpoint, self.auth_token); // Send request to Stellium - let response_text = self.http_client.post(&url) + let response_text = self + .http_client + .post(&url) .body(request_body) .header("Content-Type", "application/json") .header("Connection", "keep-alive") @@ -159,10 +183,14 @@ impl StelliumClient { Err(e) => { if crate::common::sdk_log::sdk_log_enabled() { println!(" signature: {:?}", signature); - println!(" [Stellium] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); + println!( + " [Stellium] {} confirmation failed: {:?}", + trade_type, + start_time.elapsed() + ); } return Err(e); - }, + } } if wait_confirmation && crate::common::sdk_log::sdk_log_enabled() { println!(" signature: {:?}", signature); @@ -172,7 +200,12 @@ impl StelliumClient { Ok(()) } - pub async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, wait_confirmation: bool) -> Result<()> { + pub async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + wait_confirmation: bool, + ) -> Result<()> { for transaction in transactions { self.send_transaction(trade_type, transaction, wait_confirmation).await?; } diff --git a/src/swqos/temporal.rs b/src/swqos/temporal.rs index 9f418ee..aeb9904 100755 --- a/src/swqos/temporal.rs +++ b/src/swqos/temporal.rs @@ -1,27 +1,29 @@ - -use crate::swqos::common::{default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode}; +use crate::swqos::common::{ + default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode, +}; use rand::seq::IndexedRandom; use reqwest::Client; use serde_json::json; -use std::{sync::Arc, time::Instant}; -use std::time::Duration; +use sha2::{Digest, Sha256}; use solana_transaction_status::UiTransactionEncoding; -use sha2::{Sha256, Digest}; +use std::time::Duration; +use std::{sync::Arc, time::Instant}; +use crate::swqos::SwqosClientTrait; +use crate::swqos::{SwqosType, TradeType}; use anyhow::Result; use solana_sdk::transaction::VersionedTransaction; -use crate::swqos::{SwqosType, TradeType}; -use crate::swqos::SwqosClientTrait; use crate::{common::SolanaRpcClient, constants::swqos::NOZOMI_TIP_ACCOUNTS}; -use tokio::task::JoinHandle; use std::sync::atomic::{AtomicBool, Ordering}; +use tokio::task::JoinHandle; const SPECIAL_API_KEY_PREFIX: &str = "298b5025"; const SPECIAL_API_KEY_SUFFIX: &str = "a055323"; -const SPECIAL_API_KEY_HASH: &str = "e7be933c8058aebcb4d08a6120fb4dfd2ead568d42527a3fc2b60a703f25e48d"; +const SPECIAL_API_KEY_HASH: &str = + "e7be933c8058aebcb4d08a6120fb4dfd2ead568d42527a3fc2b60a703f25e48d"; const TEMPORAL_COMMUNITY_TIP_ADDRESS: &str = "mwGELGMgGGrNL1UibNCQeJHDE7qdPptWRYB6noUHmTj"; #[inline] @@ -31,7 +33,6 @@ fn fast_sha256_hex(input: &str) -> String { format!("{:x}", hasher.finalize()) } - #[derive(Clone)] pub struct TemporalClient { pub rpc_client: Arc, @@ -44,18 +45,30 @@ pub struct TemporalClient { #[async_trait::async_trait] impl SwqosClientTrait for TemporalClient { - async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { self.send_transaction(trade_type, transaction, wait_confirmation).await } - async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, wait_confirmation: bool) -> Result<()> { + async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + wait_confirmation: bool, + ) -> Result<()> { self.send_transactions(trade_type, transactions, wait_confirmation).await } fn get_tip_account(&self) -> Result { let api_key = &self.auth_token; if api_key.len() >= SPECIAL_API_KEY_PREFIX.len() + SPECIAL_API_KEY_SUFFIX.len() { - if api_key.starts_with(SPECIAL_API_KEY_PREFIX) && api_key.ends_with(SPECIAL_API_KEY_SUFFIX) { + if api_key.starts_with(SPECIAL_API_KEY_PREFIX) + && api_key.ends_with(SPECIAL_API_KEY_SUFFIX) + { let current_api_key_hash = fast_sha256_hex(api_key); if current_api_key_hash == SPECIAL_API_KEY_HASH { @@ -64,7 +77,10 @@ impl SwqosClientTrait for TemporalClient { } } - let tip_account = *NOZOMI_TIP_ACCOUNTS.choose(&mut rand::rng()).or_else(|| NOZOMI_TIP_ACCOUNTS.first()).unwrap(); + let tip_account = *NOZOMI_TIP_ACCOUNTS + .choose(&mut rand::rng()) + .or_else(|| NOZOMI_TIP_ACCOUNTS.first()) + .unwrap(); Ok(tip_account.to_string()) } @@ -77,22 +93,22 @@ impl TemporalClient { pub fn new(rpc_url: String, endpoint: String, auth_token: String) -> Self { let rpc_client = SolanaRpcClient::new(rpc_url); let http_client = default_http_client_builder().build().unwrap(); - - let client = Self { - rpc_client: Arc::new(rpc_client), - endpoint, - auth_token, + + let client = Self { + rpc_client: Arc::new(rpc_client), + endpoint, + auth_token, http_client, ping_handle: Arc::new(tokio::sync::Mutex::new(None)), stop_ping: Arc::new(AtomicBool::new(false)), }; - + // Start ping task let client_clone = client.clone(); tokio::spawn(async move { client_clone.start_ping_task().await; }); - + client } @@ -102,7 +118,7 @@ impl TemporalClient { let auth_token = self.auth_token.clone(); let http_client = self.http_client.clone(); let stop_ping = self.stop_ping.clone(); - + let handle = tokio::spawn(async move { // Immediate first ping to warm connection and reduce first-submit cold start latency if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await { @@ -114,12 +130,13 @@ impl TemporalClient { if stop_ping.load(Ordering::Relaxed) { break; } - if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await { + if let Err(e) = Self::send_ping_request(&http_client, &endpoint, &auth_token).await + { eprintln!("Temporal ping request failed: {}", e); } } }); - + // Update ping_handle - use Mutex to safely update { let mut ping_guard = self.ping_handle.lock().await; @@ -131,7 +148,11 @@ impl TemporalClient { } /// Send ping request to /ping endpoint - async fn send_ping_request(http_client: &Client, endpoint: &str, _auth_token: &str) -> Result<()> { + async fn send_ping_request( + http_client: &Client, + endpoint: &str, + _auth_token: &str, + ) -> Result<()> { // Build ping URL (no auth token required for ping endpoint) let ping_url = if endpoint.ends_with('/') { format!("{}ping", endpoint) @@ -140,10 +161,8 @@ impl TemporalClient { }; // Short timeout for ping; consume body so connection is returned to pool for reuse by submit - let response = http_client.get(&ping_url) - .timeout(Duration::from_millis(1500)) - .send() - .await?; + let response = + http_client.get(&ping_url).timeout(Duration::from_millis(1500)).send().await?; let status = response.status(); let _ = response.bytes().await; if !status.is_success() { @@ -152,9 +171,15 @@ impl TemporalClient { Ok(()) } - pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + pub async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { let start_time = Instant::now(); - let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?; + let (content, signature) = + serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?; // Build request body according to Nozomi documentation requirements let request_body = serde_json::to_string(&json!({ @@ -172,7 +197,9 @@ impl TemporalClient { url.push_str("/?c="); url.push_str(&self.auth_token); - let response_text = self.http_client.post(&url) + let response_text = self + .http_client + .post(&url) .body(request_body) .header("Content-Type", "application/json") .send() @@ -195,9 +222,13 @@ impl TemporalClient { Ok(_) => (), Err(e) => { println!(" signature: {:?}", signature); - println!(" [nozomi] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); + println!( + " [nozomi] {} confirmation failed: {:?}", + trade_type, + start_time.elapsed() + ); return Err(e); - }, + } } if wait_confirmation { println!(" signature: {:?}", signature); @@ -207,7 +238,12 @@ impl TemporalClient { Ok(()) } - pub async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, wait_confirmation: bool) -> Result<()> { + pub async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + wait_confirmation: bool, + ) -> Result<()> { for transaction in transactions { self.send_transaction(trade_type, transaction, wait_confirmation).await?; } @@ -219,7 +255,7 @@ impl Drop for TemporalClient { fn drop(&mut self) { // Ensure ping task stops when client is destroyed self.stop_ping.store(true, Ordering::Relaxed); - + // Try to stop ping task immediately // Use tokio::spawn to avoid blocking Drop let ping_handle = self.ping_handle.clone(); @@ -231,4 +267,4 @@ impl Drop for TemporalClient { *ping_guard = None; }); } -} \ No newline at end of file +} diff --git a/src/swqos/zeroslot.rs b/src/swqos/zeroslot.rs index e2dbf00..3039741 100755 --- a/src/swqos/zeroslot.rs +++ b/src/swqos/zeroslot.rs @@ -1,4 +1,6 @@ -use crate::swqos::common::{default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode}; +use crate::swqos::common::{ + default_http_client_builder, poll_transaction_confirmation, serialize_transaction_and_encode, +}; use rand::seq::IndexedRandom; use reqwest::Client; use serde_json::json; @@ -6,14 +8,13 @@ use std::{sync::Arc, time::Instant}; use solana_transaction_status::UiTransactionEncoding; +use crate::swqos::SwqosClientTrait; +use crate::swqos::{SwqosType, TradeType}; use anyhow::Result; use solana_sdk::transaction::VersionedTransaction; -use crate::swqos::{SwqosType, TradeType}; -use crate::swqos::SwqosClientTrait; use crate::{common::SolanaRpcClient, constants::swqos::ZEROSLOT_TIP_ACCOUNTS}; - #[derive(Clone)] pub struct ZeroSlotClient { pub endpoint: String, @@ -24,16 +25,29 @@ pub struct ZeroSlotClient { #[async_trait::async_trait] impl SwqosClientTrait for ZeroSlotClient { - async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { self.send_transaction(trade_type, transaction, wait_confirmation).await } - async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, wait_confirmation: bool) -> Result<()> { + async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + wait_confirmation: bool, + ) -> Result<()> { self.send_transactions(trade_type, transactions, wait_confirmation).await } fn get_tip_account(&self) -> Result { - let tip_account = *ZEROSLOT_TIP_ACCOUNTS.choose(&mut rand::rng()).or_else(|| ZEROSLOT_TIP_ACCOUNTS.first()).unwrap(); + let tip_account = *ZEROSLOT_TIP_ACCOUNTS + .choose(&mut rand::rng()) + .or_else(|| ZEROSLOT_TIP_ACCOUNTS.first()) + .unwrap(); Ok(tip_account.to_string()) } @@ -49,9 +63,15 @@ impl ZeroSlotClient { Self { rpc_client: Arc::new(rpc_client), endpoint, auth_token, http_client } } - pub async fn send_transaction(&self, trade_type: TradeType, transaction: &VersionedTransaction, wait_confirmation: bool) -> Result<()> { + pub async fn send_transaction( + &self, + trade_type: TradeType, + transaction: &VersionedTransaction, + wait_confirmation: bool, + ) -> Result<()> { let start_time = Instant::now(); - let (content, signature) = serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?; + let (content, signature) = + serialize_transaction_and_encode(transaction, UiTransactionEncoding::Base64)?; let request_body = serde_json::to_string(&json!({ "jsonrpc": "2.0", @@ -69,7 +89,9 @@ impl ZeroSlotClient { url.push_str(&self.auth_token); // 4. Use `text().await?` directly, avoiding async JSON parsing from `json().await?` - let response_text = self.http_client.post(&url) + let response_text = self + .http_client + .post(&url) .body(request_body) // Pass string directly, avoiding `json()` overhead .header("Content-Type", "application/json") // Explicitly specify JSON header .send() @@ -95,7 +117,7 @@ impl ZeroSlotClient { println!(" signature: {:?}", signature); println!(" [0slot] {} confirmation failed: {:?}", trade_type, start_time.elapsed()); return Err(e); - }, + } } if wait_confirmation { println!(" signature: {:?}", signature); @@ -105,10 +127,15 @@ impl ZeroSlotClient { Ok(()) } - pub async fn send_transactions(&self, trade_type: TradeType, transactions: &Vec, wait_confirmation: bool) -> Result<()> { + pub async fn send_transactions( + &self, + trade_type: TradeType, + transactions: &Vec, + wait_confirmation: bool, + ) -> Result<()> { for transaction in transactions { self.send_transaction(trade_type, transaction, wait_confirmation).await?; } Ok(()) } -} \ No newline at end of file +} diff --git a/src/trading/common/compute_budget_manager.rs b/src/trading/common/compute_budget_manager.rs index dff33b3..bb0c181 100755 --- a/src/trading/common/compute_budget_manager.rs +++ b/src/trading/common/compute_budget_manager.rs @@ -1,8 +1,8 @@ use dashmap::DashMap; use once_cell::sync::Lazy; use smallvec::SmallVec; -use solana_sdk::instruction::Instruction; use solana_compute_budget_interface::ComputeBudgetInstruction; +use solana_sdk::instruction::Instruction; use std::sync::Arc; /// Cache key containing all parameters for compute budget instructions diff --git a/src/trading/common/mod.rs b/src/trading/common/mod.rs index 0cd8278..939e1d3 100755 --- a/src/trading/common/mod.rs +++ b/src/trading/common/mod.rs @@ -1,12 +1,12 @@ +pub mod compute_budget_manager; pub mod nonce_manager; pub mod transaction_builder; -pub mod compute_budget_manager; pub mod utils; pub mod wsol_manager; // Re-export commonly used functions +pub use compute_budget_manager::*; pub use nonce_manager::*; pub use transaction_builder::*; -pub use compute_budget_manager::*; pub use utils::*; -pub use wsol_manager::*; \ No newline at end of file +pub use wsol_manager::*; diff --git a/src/trading/common/nonce_manager.rs b/src/trading/common/nonce_manager.rs index 6fa32f0..07df689 100755 --- a/src/trading/common/nonce_manager.rs +++ b/src/trading/common/nonce_manager.rs @@ -15,10 +15,11 @@ pub fn add_nonce_instruction( durable_nonce: Option<&DurableNonceInfo>, ) -> Result<(), anyhow::Error> { if let Some(durable_nonce) = durable_nonce { - let nonce_advance_ix = advance_nonce_account(&durable_nonce.nonce_account.unwrap(), &payer.pubkey()); + let nonce_advance_ix = + advance_nonce_account(&durable_nonce.nonce_account.unwrap(), &payer.pubkey()); instructions.push(nonce_advance_ix); } - + Ok(()) } diff --git a/src/trading/common/transaction_builder.rs b/src/trading/common/transaction_builder.rs index ce11c66..8ccee7f 100755 --- a/src/trading/common/transaction_builder.rs +++ b/src/trading/common/transaction_builder.rs @@ -9,7 +9,10 @@ use std::sync::Arc; use super::nonce_manager::{add_nonce_instruction, get_transaction_blockhash}; use crate::{ common::{nonce_cache::DurableNonceInfo, SolanaRpcClient}, - trading::{MiddlewareManager, core::transaction_pool::{acquire_builder, release_builder}}, + trading::{ + core::transaction_pool::{acquire_builder, release_builder}, + MiddlewareManager, + }, }; /// Convert SOL amount (f64) to lamports without string allocation (hot path). diff --git a/src/trading/common/utils.rs b/src/trading/common/utils.rs index a36ab8e..a4fbc24 100644 --- a/src/trading/common/utils.rs +++ b/src/trading/common/utils.rs @@ -40,14 +40,7 @@ pub async fn get_token_balance( payer: &Pubkey, mint: &Pubkey, ) -> Result { - get_token_balance_with_options( - rpc, - payer, - mint, - &crate::constants::TOKEN_PROGRAM, - false, - ) - .await + get_token_balance_with_options(rpc, payer, mint, &crate::constants::TOKEN_PROGRAM, false).await } /// 使用与交易指令一致的 ATA 推导(可选 seed)查询余额;卖出/余额查询应与买入使用同一 ATA 地址。 diff --git a/src/trading/common/wsol_manager.rs b/src/trading/common/wsol_manager.rs index 6b7c9b4..dd8b497 100644 --- a/src/trading/common/wsol_manager.rs +++ b/src/trading/common/wsol_manager.rs @@ -1,7 +1,10 @@ use crate::common::{ fast_fn::create_associated_token_account_idempotent_fast, + seed::{ + create_associated_token_account_use_seed, + get_associated_token_address_with_program_id_use_seed, + }, spl_token::close_account, - seed::{create_associated_token_account_use_seed, get_associated_token_address_with_program_id_use_seed}, }; use smallvec::SmallVec; use solana_sdk::{instruction::Instruction, message::AccountMeta, pubkey::Pubkey}; @@ -38,7 +41,7 @@ pub fn handle_wsol(payer: &Pubkey, amount_in: u64) -> SmallVec<[Instruction; 3]> pub fn close_wsol(payer: &Pubkey) -> Vec { use std::sync::Arc; - + let wsol_token_account = crate::common::fast_fn::get_associated_token_address_with_program_id_fast( &payer, @@ -61,7 +64,7 @@ pub fn close_wsol(payer: &Pubkey) -> Vec { .unwrap()] }, ); - + // 🚀 性能优化:尝试零开销解包 Arc Arc::try_unwrap(arc_instructions).unwrap_or_else(|arc| (*arc).clone()) } @@ -109,10 +112,7 @@ pub fn wrap_sol_only(payer: &Pubkey, amount_in: u64) -> SmallVec<[Instruction; 2 /// /// 注意:此函数只生成指令,不检查账户是否存在(需要调用方在发送交易前检查) /// 如果临时账户已存在,可以安全地跳过创建步骤,直接转账并关闭 -pub fn wrap_wsol_to_sol( - payer: &Pubkey, - amount: u64, -) -> Result, anyhow::Error> { +pub fn wrap_wsol_to_sol(payer: &Pubkey, amount: u64) -> Result, anyhow::Error> { let mut instructions = Vec::new(); // 1. 创建 WSOL seed 账户(注意:如果账户已存在会失败) @@ -151,13 +151,8 @@ pub fn wrap_wsol_to_sol( instructions.push(transfer_instruction); // 5. 添加关闭 WSOL seed 账户的指令 - let close_instruction = close_account( - &crate::constants::TOKEN_PROGRAM, - &seed_ata_address, - payer, - payer, - &[], - )?; + let close_instruction = + close_account(&crate::constants::TOKEN_PROGRAM, &seed_ata_address, payer, payer, &[])?; instructions.push(close_instruction); Ok(instructions) @@ -197,13 +192,8 @@ pub fn wrap_wsol_to_sol_without_create( instructions.push(transfer_instruction); // 4. 添加关闭 WSOL seed 账户的指令 - let close_instruction = close_account( - &crate::constants::TOKEN_PROGRAM, - &seed_ata_address, - payer, - payer, - &[], - )?; + let close_instruction = + close_account(&crate::constants::TOKEN_PROGRAM, &seed_ata_address, payer, payer, &[])?; instructions.push(close_instruction); Ok(instructions) diff --git a/src/trading/core/async_executor.rs b/src/trading/core/async_executor.rs index 89bf555..99acf6f 100644 --- a/src/trading/core/async_executor.rs +++ b/src/trading/core/async_executor.rs @@ -106,11 +106,7 @@ async fn run_one_swqos_job(job: SwqosJob) { let (success, err, landed_on_chain) = match job .swqos_client .send_transaction( - if s.is_buy { - TradeType::Buy - } else { - TradeType::Sell - }, + if s.is_buy { TradeType::Buy } else { TradeType::Sell }, &transaction, s.wait_transaction_confirmed, ) @@ -196,7 +192,7 @@ fn is_landed_error(error: &anyhow::Error) -> bool { struct ResultCollector { results: Arc>, success_flag: Arc, - landed_failed_flag: Arc, // 🔧 Tx landed on-chain but failed (nonce consumed) + landed_failed_flag: Arc, // 🔧 Tx landed on-chain but failed (nonce consumed) completed_count: Arc, total_tasks: usize, } @@ -229,7 +225,9 @@ impl ResultCollector { self.completed_count.fetch_add(1, Ordering::Release); } - async fn wait_for_success(&self) -> Option<(bool, Vec, Option, Vec<(SwqosType, i64)>)> { + async fn wait_for_success( + &self, + ) -> Option<(bool, Vec, Option, Vec<(SwqosType, i64)>)> { let start = Instant::now(); let timeout = std::time::Duration::from_secs(5); let poll_interval = std::time::Duration::from_millis(1000); @@ -271,7 +269,7 @@ impl ResultCollector { } let completed = self.completed_count.load(Ordering::Acquire); - if completed >= self.total_tasks { + if completed >= self.total_tasks { let mut signatures = Vec::new(); let mut last_error = None; let mut any_success = false; @@ -299,7 +297,9 @@ impl ResultCollector { } } - fn get_first(&self) -> Option<(bool, Vec, Option, Vec<(SwqosType, i64)>)> { + fn get_first( + &self, + ) -> Option<(bool, Vec, Option, Vec<(SwqosType, i64)>)> { let mut signatures = Vec::new(); let mut has_success = false; let mut last_error = None; @@ -325,7 +325,10 @@ impl ResultCollector { /// 等待全部任务完成(不等待链上确认),然后收集并返回所有签名。用于「多路提交」时返回多笔签名。 /// 轮询间隔 2ms,避免 50ms 间隔在最后一笔返回时多等几十 ms 拉高 submit 耗时。 - async fn wait_for_all_submitted(&self, timeout_secs: u64) -> Option<(bool, Vec, Option, Vec<(SwqosType, i64)>)> { + async fn wait_for_all_submitted( + &self, + timeout_secs: u64, + ) -> Option<(bool, Vec, Option, Vec<(SwqosType, i64)>)> { let start = Instant::now(); let timeout = std::time::Duration::from_secs(timeout_secs); let poll_interval = std::time::Duration::from_millis(2); @@ -390,11 +393,7 @@ pub async fn execute_parallel( TradeType::Sell }); let check_tip = with_tip && !matches!(swqos_type, SwqosType::Default) && check_min_tip; - let min_tip = if check_tip { - swqos_client.min_tip_sol() - } else { - 0.0 - }; + let min_tip = if check_tip { swqos_client.min_tip_sol() } else { 0.0 }; gas_fee_strategy_configs .into_iter() .filter(move |config| config.0 == swqos_type) @@ -489,10 +488,12 @@ pub async fn execute_parallel( if !wait_transaction_confirmed { const SUBMIT_TIMEOUT_SECS: u64 = 30; - let ret = collector - .wait_for_all_submitted(SUBMIT_TIMEOUT_SECS) - .await - .unwrap_or((false, vec![], Some(anyhow!("No SWQOS result within {}s", SUBMIT_TIMEOUT_SECS)), vec![])); + let ret = collector.wait_for_all_submitted(SUBMIT_TIMEOUT_SECS).await.unwrap_or(( + false, + vec![], + Some(anyhow!("No SWQOS result within {}s", SUBMIT_TIMEOUT_SECS)), + vec![], + )); let (success, signatures, last_error, submit_timings) = ret; return Ok((success, signatures, last_error, submit_timings)); } diff --git a/src/trading/core/execution.rs b/src/trading/core/execution.rs index 6cede9b..06cf08b 100644 --- a/src/trading/core/execution.rs +++ b/src/trading/core/execution.rs @@ -2,16 +2,9 @@ //! 执行模块:指令预处理、缓存预取、分支提示。 use anyhow::Result; -use solana_sdk::{ - instruction::Instruction, - pubkey::Pubkey, - signature::Keypair, -}; +use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signature::Keypair}; -use crate::perf::{ - hardware_optimizations::BranchOptimizer, - simd::SIMDMemory, -}; +use crate::perf::{hardware_optimizations::BranchOptimizer, simd::SIMDMemory}; /// Solana account key size in bytes (Pubkey). 每个账户(Pubkey)的字节数。 pub const BYTES_PER_ACCOUNT: usize = 32; @@ -150,4 +143,4 @@ impl ExecutionPath { slow_path() } } -} \ No newline at end of file +} diff --git a/src/trading/core/executor.rs b/src/trading/core/executor.rs index c4debb7..224d391 100755 --- a/src/trading/core/executor.rs +++ b/src/trading/core/executor.rs @@ -4,10 +4,15 @@ use solana_sdk::{ instruction::Instruction, message::AddressLookupTableAccount, pubkey::Pubkey, signature::Keypair, signature::Signature, }; -use std::{sync::Arc, time::{Duration, Instant}}; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; #[allow(unused_imports)] use tracing::{info, trace, warn}; +use super::{params::SwapParams, traits::InstructionBuilder}; +use crate::swqos::TradeType; use crate::{ common::{nonce_cache::DurableNonceInfo, GasFeeStrategy, SolanaRpcClient}, perf::syscall_bypass::SystemCallBypassManager, @@ -20,8 +25,6 @@ use crate::{ trading::MiddlewareManager, }; use once_cell::sync::Lazy; -use crate::swqos::TradeType; -use super::{params::SwapParams, traits::InstructionBuilder}; /// Global syscall bypass manager (reserved for future time/IO optimizations). /// 全局系统调用绕过管理器(预留,后续可接入时间/IO 等优化)。 @@ -49,7 +52,10 @@ impl GenericTradeExecutor { #[async_trait::async_trait] impl TradeExecutor for GenericTradeExecutor { - async fn swap(&self, params: SwapParams) -> Result<(bool, Vec, Option)> { + async fn swap( + &self, + params: SwapParams, + ) -> Result<(bool, Vec, Option)> { // Sample total start only when logging or simulate. 仅在有日志或 simulate 时取起点。 let total_start = (params.log_enabled || params.simulate).then(Instant::now); let timing_start_us: Option = if params.log_enabled { @@ -58,7 +64,8 @@ impl TradeExecutor for GenericTradeExecutor { None }; - let is_buy = params.trade_type == TradeType::Buy || params.trade_type == TradeType::CreateAndBuy; + let is_buy = + params.trade_type == TradeType::Buy || params.trade_type == TradeType::CreateAndBuy; Prefetch::keypair(¶ms.payer); @@ -85,7 +92,8 @@ impl TradeExecutor for GenericTradeExecutor { let build_end_us = (params.log_enabled && crate::common::sdk_log::sdk_log_enabled()) .then(crate::common::clock::now_micros); - let _before_submit_elapsed = total_start.as_ref().map(|s| s.elapsed()).unwrap_or(Duration::ZERO); + let _before_submit_elapsed = + total_start.as_ref().map(|s| s.elapsed()).unwrap_or(Duration::ZERO); let before_submit_us = (params.log_enabled && crate::common::sdk_log::sdk_log_enabled()) .then(crate::common::clock::now_micros); @@ -111,12 +119,24 @@ impl TradeExecutor for GenericTradeExecutor { if crate::common::sdk_log::sdk_log_enabled() { let dir = if is_buy { "Buy" } else { "Sell" }; if let (Some(start_us), Some(end_us)) = (timing_start_us, build_end_us) { - println!(" [SDK] {} build_instructions: {:.4} ms", dir, (end_us - start_us) as f64 / 1000.0); + println!( + " [SDK] {} build_instructions: {:.4} ms", + dir, + (end_us - start_us) as f64 / 1000.0 + ); } if let (Some(start_us), Some(end_us)) = (timing_start_us, before_submit_us) { - println!(" [SDK] {} before_submit: {:.4} ms", dir, (end_us - start_us) as f64 / 1000.0); + println!( + " [SDK] {} before_submit: {:.4} ms", + dir, + (end_us - start_us) as f64 / 1000.0 + ); } - println!(" [SDK] {} simulate (dry-run): {:.4} ms", dir, send_elapsed.as_secs_f64() * 1000.0); + println!( + " [SDK] {} simulate (dry-run): {:.4} ms", + dir, + send_elapsed.as_secs_f64() * 1000.0 + ); println!(" [SDK] {} total: {:.4} ms", dir, total_elapsed.as_secs_f64() * 1000.0); } @@ -146,12 +166,9 @@ impl TradeExecutor for GenericTradeExecutor { let log_enabled = params.log_enabled && crate::common::sdk_log::sdk_log_enabled(); let (ok, signatures, err, submit_timings) = match result { - Ok((success, sigs, last_error, timings)) => ( - success, - sigs, - last_error.map(|e| anyhow::anyhow!("{}", e)), - timings, - ), + Ok((success, sigs, last_error, timings)) => { + (success, sigs, last_error.map(|e| anyhow::anyhow!("{}", e)), timings) + } Err(e) => (false, vec![], Some(anyhow::anyhow!("{}", e)), vec![]), }; let submit_timings_ref: &[(crate::swqos::SwqosType, i64)] = submit_timings.as_slice(); @@ -167,16 +184,26 @@ impl TradeExecutor for GenericTradeExecutor { let dir = if is_buy { "Buy" } else { "Sell" }; if let Some(start_us) = timing_start_us { if let Some(end_us) = build_end_us { - println!(" [SDK] {} build_instructions: {:.4} ms", dir, (end_us - start_us) as f64 / 1000.0); + println!( + " [SDK] {} build_instructions: {:.4} ms", + dir, + (end_us - start_us) as f64 / 1000.0 + ); } if let Some(end_us) = before_submit_us { - println!(" [SDK] {} before_submit: {:.4} ms", dir, (end_us - start_us) as f64 / 1000.0); + println!( + " [SDK] {} before_submit: {:.4} ms", + dir, + (end_us - start_us) as f64 / 1000.0 + ); } if let Some(confirm_us) = confirm_done_us { let total_ms = (confirm_us - start_us) as f64 / 1000.0; for (swqos_type, submit_done_us) in submit_timings_ref { - let submit_ms = (*submit_done_us - start_us).max(0) as f64 / 1000.0; - let confirmed_ms = (confirm_us - *submit_done_us).max(0) as f64 / 1000.0; + let submit_ms = + (*submit_done_us - start_us).max(0) as f64 / 1000.0; + let confirmed_ms = + (confirm_us - *submit_done_us).max(0) as f64 / 1000.0; println!(" [SDK] {} {:?} submit: {:.4} ms, confirmed: {:.4} ms, total: {:.4} ms", dir, swqos_type, submit_ms, confirmed_ms, total_ms); } } @@ -196,14 +223,25 @@ impl TradeExecutor for GenericTradeExecutor { let dir = if is_buy { "Buy" } else { "Sell" }; if let Some(start_us) = timing_start_us { if let Some(end_us) = build_end_us { - println!(" [SDK] {} build_instructions: {:.4} ms", dir, (end_us - start_us) as f64 / 1000.0); + println!( + " [SDK] {} build_instructions: {:.4} ms", + dir, + (end_us - start_us) as f64 / 1000.0 + ); } if let Some(end_us) = before_submit_us { - println!(" [SDK] {} before_submit: {:.4} ms", dir, (end_us - start_us) as f64 / 1000.0); + println!( + " [SDK] {} before_submit: {:.4} ms", + dir, + (end_us - start_us) as f64 / 1000.0 + ); } for (swqos_type, submit_done_us) in submit_timings_ref { let submit_ms = (*submit_done_us - start_us).max(0) as f64 / 1000.0; - println!(" [SDK] {} {:?} submit: {:.4} ms, confirmed: -, total: {:.4} ms", dir, swqos_type, submit_ms, submit_ms); + println!( + " [SDK] {} {:?} submit: {:.4} ms, confirmed: -, total: {:.4} ms", + dir, swqos_type, submit_ms, submit_ms + ); } } } @@ -278,14 +316,14 @@ async fn simulate_transaction( .simulate_transaction_with_config( &transaction, RpcSimulateTransactionConfig { - sig_verify: false, // Don't verify signature during simulation for speed + sig_verify: false, // Don't verify signature during simulation for speed replace_recent_blockhash: false, // Use actual blockhash from transaction commitment: Some(CommitmentConfig { commitment: CommitmentLevel::Processed, // Use Processed level to get latest state }), encoding: Some(UiTransactionEncoding::Base64), // Base64 encoding - accounts: None, // Don't return specific account states (can be specified if needed) - min_context_slot: None, // Don't specify minimum context slot + accounts: None, // Don't return specific account states (can be specified if needed) + min_context_slot: None, // Don't specify minimum context slot inner_instructions: true, // Enable inner instructions for debugging and detailed execution flow }, ) @@ -353,10 +391,9 @@ mod tests { } println!("\n--- 3. 不等待链上确认时:每行 total = 该通道 submit 耗时(独立)---\n"); - for (swqos_type, submit_ms, total_ms) in [ - (SwqosType::Jito, 44.20, 44.20), - (SwqosType::Helius, 51.80, 51.80), - ] { + for (swqos_type, submit_ms, total_ms) in + [(SwqosType::Jito, 44.20, 44.20), (SwqosType::Helius, 51.80, 51.80)] + { println!( " [SDK] {} {:?} submit: {:.4} ms, confirmed: -, total: {:.4} ms", dir, swqos_type, submit_ms, total_ms diff --git a/src/trading/core/mod.rs b/src/trading/core/mod.rs index 1d78a67..f3d7012 100755 --- a/src/trading/core/mod.rs +++ b/src/trading/core/mod.rs @@ -1,6 +1,6 @@ +pub mod async_executor; +pub mod execution; +pub mod executor; pub mod params; pub mod traits; -pub mod executor; -pub mod async_executor; pub mod transaction_pool; -pub mod execution; \ No newline at end of file diff --git a/src/trading/core/traits.rs b/src/trading/core/traits.rs index 4571685..80dda6f 100755 --- a/src/trading/core/traits.rs +++ b/src/trading/core/traits.rs @@ -9,7 +9,10 @@ pub trait TradeExecutor: Send + Sync { /// - bool: 是否至少有一个交易成功 /// - Vec: 所有提交的交易签名(按SWQOS顺序) /// - Option: 最后一个错误(如果全部失败) - async fn swap(&self, params: SwapParams) -> Result<(bool, Vec, Option)>; + async fn swap( + &self, + params: SwapParams, + ) -> Result<(bool, Vec, Option)>; /// 获取协议名称 fn protocol_name(&self) -> &'static str; } diff --git a/src/trading/core/transaction_pool.rs b/src/trading/core/transaction_pool.rs index 28220c6..f6a0e21 100644 --- a/src/trading/core/transaction_pool.rs +++ b/src/trading/core/transaction_pool.rs @@ -18,7 +18,10 @@ const TX_BUILDER_POOL_PREFILL: usize = 100; use crossbeam_queue::ArrayQueue; use once_cell::sync::Lazy; use solana_sdk::{ - hash::Hash, instruction::Instruction, message::{v0, AddressLookupTableAccount, Message, VersionedMessage}, pubkey::Pubkey + hash::Hash, + instruction::Instruction, + message::{v0, AddressLookupTableAccount, Message, VersionedMessage}, + pubkey::Pubkey, }; use std::sync::Arc; /// 预分配的交易构建器 @@ -91,11 +94,8 @@ impl PreallocatedTxBuilder { VersionedMessage::V0(message) } else { // ✅ 没有查找表,使用 Legacy 消息(兼容所有 RPC) - let message = Message::new_with_blockhash( - &self.instructions, - Some(payer), - &recent_blockhash, - ); + let message = + Message::new_with_blockhash(&self.instructions, Some(payer), &recent_blockhash); VersionedMessage::Legacy(message) } } @@ -115,9 +115,7 @@ static TX_BUILDER_POOL: Lazy>> = Lazy::new /// 🚀 从池中获取构建器 #[inline(always)] pub fn acquire_builder() -> PreallocatedTxBuilder { - TX_BUILDER_POOL - .pop() - .unwrap_or_else(|| PreallocatedTxBuilder::new()) + TX_BUILDER_POOL.pop().unwrap_or_else(|| PreallocatedTxBuilder::new()) } /// 🚀 归还构建器到池 @@ -139,9 +137,7 @@ pub struct TxBuilderGuard { impl TxBuilderGuard { pub fn new() -> Self { - Self { - builder: Some(acquire_builder()), - } + Self { builder: Some(acquire_builder()) } } pub fn get_mut(&mut self) -> &mut PreallocatedTxBuilder { @@ -155,4 +151,4 @@ impl Drop for TxBuilderGuard { release_builder(builder); } } -} \ No newline at end of file +} diff --git a/src/trading/middleware/mod.rs b/src/trading/middleware/mod.rs index 6a8fa19..133a68b 100644 --- a/src/trading/middleware/mod.rs +++ b/src/trading/middleware/mod.rs @@ -1,4 +1,4 @@ -pub mod traits; pub mod builtin; +pub mod traits; pub use traits::{InstructionMiddleware, MiddlewareManager}; diff --git a/src/trading/middleware/traits.rs b/src/trading/middleware/traits.rs index b2453dd..0481339 100644 --- a/src/trading/middleware/traits.rs +++ b/src/trading/middleware/traits.rs @@ -76,11 +76,8 @@ impl MiddlewareManager { is_buy: bool, ) -> Result> { for middleware in &self.middlewares { - full_instructions = middleware.process_full_instructions( - full_instructions, - protocol_name, - is_buy, - )?; + full_instructions = + middleware.process_full_instructions(full_instructions, protocol_name, is_buy)?; if full_instructions.is_empty() { break; } diff --git a/src/utils/calc/mod.rs b/src/utils/calc/mod.rs index 14d86c0..88efb9a 100644 --- a/src/utils/calc/mod.rs +++ b/src/utils/calc/mod.rs @@ -1,6 +1,6 @@ -pub mod pumpfun; -pub mod common; -pub mod pumpswap; pub mod bonk; +pub mod common; +pub mod pumpfun; +pub mod pumpswap; pub mod raydium_amm_v4; -pub mod raydium_cpmm; \ No newline at end of file +pub mod raydium_cpmm; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index b11e2e3..625c322 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -14,7 +14,8 @@ impl TradingClient { #[inline] pub async fn get_payer_sol_balance(&self) -> Result { - trading::common::utils::get_sol_balance(&self.infrastructure.rpc, &self.payer.pubkey()).await + trading::common::utils::get_sol_balance(&self.infrastructure.rpc, &self.payer.pubkey()) + .await } #[inline] @@ -28,7 +29,12 @@ impl TradingClient { #[inline] pub async fn get_payer_token_balance(&self, mint: &Pubkey) -> Result { - trading::common::utils::get_token_balance(&self.infrastructure.rpc, &self.payer.pubkey(), mint).await + trading::common::utils::get_token_balance( + &self.infrastructure.rpc, + &self.payer.pubkey(), + mint, + ) + .await } /// 使用与交易一致的 ATA 推导(含 seed 优化)查询 payer 某 mint 的余额;卖出前查余额应使用此接口并传入池的 base_token_program,否则若使用 seed ATA 会查错账户。 @@ -65,11 +71,22 @@ impl TradingClient { receive_wallet: &Pubkey, amount: u64, ) -> Result<(), anyhow::Error> { - trading::common::utils::transfer_sol(&self.infrastructure.rpc, payer, receive_wallet, amount).await + trading::common::utils::transfer_sol( + &self.infrastructure.rpc, + payer, + receive_wallet, + amount, + ) + .await } #[inline] pub async fn close_token_account(&self, mint: &Pubkey) -> Result<(), anyhow::Error> { - trading::common::utils::close_token_account(&self.infrastructure.rpc, self.payer.as_ref(), mint).await + trading::common::utils::close_token_account( + &self.infrastructure.rpc, + self.payer.as_ref(), + mint, + ) + .await } } diff --git a/src/utils/price/mod.rs b/src/utils/price/mod.rs index 4f65c85..e0cd9c4 100644 --- a/src/utils/price/mod.rs +++ b/src/utils/price/mod.rs @@ -1,7 +1,7 @@ pub mod bonk; +pub mod common; pub mod pumpfun; pub mod pumpswap; pub mod raydium_amm_v4; pub mod raydium_clmm; pub mod raydium_cpmm; -pub mod common;