use anyhow::Result; use solana_hash::Hash; use solana_sdk::{ instruction::Instruction, message::AddressLookupTableAccount, pubkey::Pubkey, signature::Keypair, signature::Signature, }; use std::{sync::Arc, time::{Duration, Instant}}; #[allow(unused_imports)] use tracing::{info, trace, warn}; use crate::{ common::{nonce_cache::DurableNonceInfo, GasFeeStrategy, SolanaRpcClient}, perf::syscall_bypass::SystemCallBypassManager, swqos::common::poll_transaction_confirmation, trading::core::{ async_executor::execute_parallel, execution::{InstructionProcessor, Prefetch}, traits::TradeExecutor, }, 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 等优化)。 #[allow(dead_code)] static SYSCALL_BYPASS: Lazy = Lazy::new(|| { use crate::perf::syscall_bypass::SyscallBypassConfig; SystemCallBypassManager::new(SyscallBypassConfig::default()) .expect("Failed to create SystemCallBypassManager") }); /// Generic trade executor implementation pub struct GenericTradeExecutor { instruction_builder: Arc, protocol_name: &'static str, } impl GenericTradeExecutor { pub fn new( instruction_builder: Arc, protocol_name: &'static str, ) -> Self { Self { instruction_builder, protocol_name } } } #[async_trait::async_trait] impl TradeExecutor for GenericTradeExecutor { 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 { Some(params.grpc_recv_us.unwrap_or_else(crate::common::clock::now_micros)) } else { None }; let is_buy = params.trade_type == TradeType::Buy || params.trade_type == TradeType::CreateAndBuy; Prefetch::keypair(¶ms.payer); // Time build only when log_enabled to avoid cold-path syscalls. 仅 log_enabled 时计时,减少冷路径 syscall。 let build_start = params.log_enabled.then(Instant::now); let instructions = if is_buy { self.instruction_builder.build_buy_instructions(¶ms).await? } else { self.instruction_builder.build_sell_instructions(¶ms).await? }; let build_elapsed = build_start.map(|s| s.elapsed()).unwrap_or(Duration::ZERO); InstructionProcessor::preprocess(&instructions)?; let final_instructions = match ¶ms.middleware_manager { Some(middleware_manager) => middleware_manager .apply_middlewares_process_protocol_instructions( instructions, self.protocol_name.to_string(), is_buy, )?, None => instructions, }; let before_submit_elapsed = total_start.as_ref().map(|s| s.elapsed()).unwrap_or(Duration::ZERO); if params.simulate { let send_start = crate::common::sdk_log::sdk_log_enabled().then(Instant::now); let result = simulate_transaction( params.rpc, params.payer, final_instructions, params.address_lookup_table_account, params.recent_blockhash, params.durable_nonce, params.middleware_manager, self.protocol_name, is_buy, if is_buy { true } else { params.with_tip }, params.gas_fee_strategy, ) .await; let send_elapsed = send_start.map(|s| s.elapsed()).unwrap_or(Duration::ZERO); let total_elapsed = total_start.as_ref().map(|s| s.elapsed()).unwrap_or(Duration::ZERO); if crate::common::sdk_log::sdk_log_enabled() { let dir = if is_buy { "Buy" } else { "Sell" }; println!(" [SDK] {} timing(sim) build_instructions: {:.2}ms before_submit: {:.2}ms simulate: {:.2}ms total: {:.2}ms", dir, build_elapsed.as_secs_f64() * 1000.0, before_submit_elapsed.as_secs_f64() * 1000.0, send_elapsed.as_secs_f64() * 1000.0, total_elapsed.as_secs_f64() * 1000.0); } return result; } let need_confirm = params.wait_transaction_confirmed; let send_start = params.log_enabled.then(Instant::now); let result = execute_parallel( ¶ms.swqos_clients, params.payer, params.rpc.clone(), final_instructions, params.address_lookup_table_account, params.recent_blockhash, params.durable_nonce, params.middleware_manager, self.protocol_name, is_buy, false, // submit only here; confirmation and log timing handled below if is_buy { true } else { params.with_tip }, params.gas_fee_strategy, params.use_core_affinity, ) .await; let send_elapsed = send_start.map(|s| s.elapsed()).unwrap_or(Duration::ZERO); if params.log_enabled && crate::common::sdk_log::sdk_log_enabled() { let dir = if is_buy { "Buy" } else { "Sell" }; let build_ms = build_elapsed.as_secs_f64() * 1000.0; let before_ms = before_submit_elapsed.as_secs_f64() * 1000.0; let send_ms = send_elapsed.as_secs_f64() * 1000.0; if let Some(start_us) = timing_start_us { let now_us = crate::common::clock::now_micros(); let start_to_submit_us = (now_us - start_us).max(0); println!(" [SDK] {} timing(after_submit) build_instructions: {:.2}ms before_submit: {:.2}ms submit: {:.2}ms start_to_submit: {} μs", dir, build_ms, before_ms, send_ms, start_to_submit_us); } else { println!(" [SDK] {} timing(after_submit) build_instructions: {:.2}ms before_submit: {:.2}ms submit: {:.2}ms", dir, build_ms, before_ms, send_ms); } } let result = if need_confirm { let (ok, sigs, err) = match &result { Ok((success, signatures, last_error)) => ( *success, signatures.clone(), last_error.as_ref().map(|e| anyhow::anyhow!("{}", e)), ), Err(e) => (false, vec![], Some(anyhow::anyhow!("{}", e))), }; let first_sig = sigs.first().copied(); let confirm_result = if let (Some(rpc), Some(sig)) = (params.rpc.as_ref(), first_sig) { let confirm_start = (params.log_enabled && crate::common::sdk_log::sdk_log_enabled()).then(Instant::now); let poll_res = poll_transaction_confirmation(rpc, sig, true).await; let confirm_elapsed = confirm_start.map(|s| s.elapsed()).unwrap_or(Duration::ZERO); if params.log_enabled && crate::common::sdk_log::sdk_log_enabled() { let dir = if is_buy { "Buy" } else { "Sell" }; let confirm_ms = confirm_elapsed.as_secs_f64() * 1000.0; let total_ms = total_start.as_ref().map(|s| s.elapsed()).unwrap_or(Duration::ZERO).as_secs_f64() * 1000.0; println!(" [SDK] {} timing(after_confirm) confirm: {:.2}ms total: {:.2}ms", dir, confirm_ms, total_ms); } match poll_res { Ok(_) => (true, sigs, None), Err(e) => (false, sigs, Some(e)), } } else { (ok, sigs, err) }; Ok(confirm_result) } else { if params.log_enabled && crate::common::sdk_log::sdk_log_enabled() { let total_ms = total_start.as_ref().map(|s| s.elapsed()).unwrap_or(Duration::ZERO).as_secs_f64() * 1000.0; let dir = if is_buy { "Buy" } else { "Sell" }; println!(" [SDK] {} timing total: {:.2}ms", dir, total_ms); } result }; result } fn protocol_name(&self) -> &'static str { self.protocol_name } } /// Simulate mode: single RPC simulation, returns Vec for API consistency. /// 模拟模式:单次 RPC 模拟,返回 Vec 以与 API 一致。 async fn simulate_transaction( rpc: Option>, payer: Arc, instructions: Vec, address_lookup_table_account: Option, recent_blockhash: Option, durable_nonce: Option, middleware_manager: Option>, protocol_name: &'static str, is_buy: bool, with_tip: bool, gas_fee_strategy: GasFeeStrategy, ) -> Result<(bool, Vec, Option)> { use crate::trading::common::build_transaction; use solana_client::rpc_config::RpcSimulateTransactionConfig; use solana_commitment_config::CommitmentLevel; use solana_transaction_status::UiTransactionEncoding; let rpc = rpc.ok_or_else(|| anyhow::anyhow!("RPC client is required for simulation"))?; // Get gas fee strategy for simulation (use Default swqos type) let trade_type = if is_buy { crate::swqos::TradeType::Buy } else { crate::swqos::TradeType::Sell }; let gas_fee_configs = gas_fee_strategy.get_strategies(trade_type); let default_config = gas_fee_configs .iter() .find(|config| config.0 == crate::swqos::SwqosType::Default) .ok_or_else(|| anyhow::anyhow!("No default gas fee strategy found"))?; let tip = if with_tip { default_config.2.tip } else { 0.0 }; let unit_limit = default_config.2.cu_limit; let unit_price = default_config.2.cu_price; // Build transaction for simulation let transaction = build_transaction( payer.clone(), Some(rpc.clone()), unit_limit, unit_price, &instructions, address_lookup_table_account, recent_blockhash, middleware_manager, protocol_name, is_buy, false, // simulate doesn't need tip instruction &Pubkey::default(), tip, durable_nonce, ) .await?; // Simulate the transaction use solana_commitment_config::CommitmentConfig; let simulate_result = rpc .simulate_transaction_with_config( &transaction, RpcSimulateTransactionConfig { 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 inner_instructions: true, // Enable inner instructions for debugging and detailed execution flow }, ) .await?; let signature = transaction .signatures .first() .ok_or_else(|| anyhow::anyhow!("Transaction has no signatures"))? .clone(); if let Some(err) = simulate_result.value.err { #[cfg(feature = "perf-trace")] { warn!(target: "sol_trade_sdk", "[Simulation Failed] error={:?} signature={:?}", err, signature); if let Some(logs) = &simulate_result.value.logs { trace!(target: "sol_trade_sdk", "Transaction logs: {:?}", logs); } if let Some(units_consumed) = simulate_result.value.units_consumed { trace!(target: "sol_trade_sdk", "Compute Units Consumed: {}", units_consumed); } } return Ok((false, vec![signature], Some(anyhow::anyhow!("{:?}", err)))); } // Simulation succeeded #[cfg(feature = "perf-trace")] { info!(target: "sol_trade_sdk", "[Simulation Succeeded] signature={:?}", signature); if let Some(units_consumed) = simulate_result.value.units_consumed { trace!(target: "sol_trade_sdk", "Compute Units Consumed: {}", units_consumed); } if let Some(logs) = &simulate_result.value.logs { trace!(target: "sol_trade_sdk", "Transaction logs: {:?}", logs); } } Ok((true, vec![signature], None)) }