diff --git a/src/constants/swqos.rs b/src/constants/swqos.rs index 08d42aa..e7eba5d 100755 --- a/src/constants/swqos.rs +++ b/src/constants/swqos.rs @@ -172,10 +172,16 @@ pub const SPEEDLANDING_TIP_ACCOUNTS: &[Pubkey] = &[ ]; pub const SOLAMI_TIP_ACCOUNTS: &[Pubkey] = &[ - pubkey!("6993ZufwyEDNdB94kciDTGB17ANXguiNH22VmMQU1ami"), - pubkey!("E1BkG293HQocKfCkfPS7tEvs8Enh6FK8pUKi17EH1ami"), - pubkey!("2Ga87xvZwpP9WRsSdiPiWre21cQbDcFhGLmT5EMo1ami"), - pubkey!("CGzT5jzT68vGUWVagQAQqESfrCEHSKYe9DAjfgfC1ami"), + pubkey!("15qWd4huAkoxvhDsHMfpUn27TW1YBYMMJJ2jkAkbeam"), + pubkey!("9XuGciSwr5wb7dLTQm91JhuBTvj3GG8WjuRDc3obeam"), + pubkey!("kiQioJNyFG7pU36ELLsRKXkeT48kFbk3b6rSgrWbeam"), + pubkey!("kjmVhW1UzJrW2sU5bY5NtZ79jpvjSStsj37Pzmabeam"), + pubkey!("kREnjPWFpt4AHeY5pijPmyXaCrMnbatUQJo7d3Xbeam"), + pubkey!("praRZG6N6MdbsT4EFpKgZJWReZGXQhAMFcH68oCbeam"), + pubkey!("SqoKQKU5uwBxovq3R7yEBxFwptc4z7vwoghU3M9beam"), + pubkey!("sV72TY66T1RfmDSeHPPbwX6wwJ3bBv5hd4ehJ8tbeam"), + pubkey!("swf8MyEeLo7gtRUo27UuJj6naCASUrypU7dbteSbeam"), + pubkey!("uiuaQsxA47JybQAVN4FTfYuoEDkMiXV1r591Aewbeam"), ]; // `SwqosRegion` 与下列各 `SWQOS_ENDPOINTS_*` 下标严格对应(共 10 项): @@ -442,16 +448,16 @@ pub const SWQOS_ENDPOINTS_HELIUS: [&str; 10] = [ ]; pub const SWQOS_ENDPOINTS_SOLAMI: [&str; 10] = [ - "nyc.landing.solami.fast:11000", - "fra.landing.solami.fast:11000", - "ams.landing.solami.fast:11000", - "ams.landing.solami.fast:11000", // Dublin: no IE PoP; use nearest published EU endpoint - "nyc.landing.solami.fast:11000", // SLC: no Mountain/West PoP published; US fallback - "sgp.landing.solami.fast:11000", // Tokyo: PR only provided SGP for APAC - "sgp.landing.solami.fast:11000", - "fra.landing.solami.fast:11000", - "nyc.landing.solami.fast:11000", - "landing.solami.fast:11000", + "beam.solami.dev:11000", + "beam.solami.dev:11000", + "beam.solami.dev:11000", + "beam.solami.dev:11000", + "beam.solami.dev:11000", + "beam.solami.dev:11000", + "beam.solami.dev:11000", + "beam.solami.dev:11000", + "beam.solami.dev:11000", + "beam.solami.dev:11000", ]; pub const SWQOS_MIN_TIP_DEFAULT: f64 = 0.00001; // 其它SWQOS默认最低小费 diff --git a/src/swqos/solami.rs b/src/swqos/solami.rs index fb4f991..1415e5b 100644 --- a/src/swqos/solami.rs +++ b/src/swqos/solami.rs @@ -7,6 +7,7 @@ use quinn::{ }; use rand::seq::IndexedRandom as _; use solana_client::rpc_client::SerializableTransaction; +use solana_sdk::signer::Signer; use solana_sdk::{signature::Keypair, transaction::VersionedTransaction}; use solana_tls_utils::{new_dummy_x509_certificate, SkipServerVerification}; use std::time::Instant; @@ -16,6 +17,7 @@ use std::{ time::Duration, }; use tokio::sync::Mutex; +use tokio::time::timeout; use crate::common::SolanaRpcClient; use crate::swqos::common::poll_transaction_confirmation; @@ -26,9 +28,11 @@ use crate::{ }; const ALPN_TPU_PROTOCOL_ID: &[u8] = b"solana-tpu"; -const SOLAMI_SERVER: &str = "solami-landing"; +const SOLAMI_SERVER: &str = "solami-beam"; const KEEP_ALIVE_INTERVAL: Duration = Duration::from_secs(25); const MAX_IDLE_TIMEOUT: Duration = Duration::from_secs(5 * 60); +const CONNECT_TIMEOUT: Duration = Duration::from_secs(5); +const SEND_TIMEOUT: Duration = Duration::from_secs(5); pub struct SolamiClient { pub rpc_client: Arc, @@ -42,7 +46,12 @@ pub struct SolamiClient { impl SolamiClient { pub async fn new(rpc_url: String, endpoint_string: String, api_key: String) -> Result { let rpc_client = SolanaRpcClient::new(rpc_url); - let keypair = Keypair::from_base58_string(&api_key); + let keypair_bytes = bs58::decode(api_key.trim()) + .into_vec() + .map_err(|e| anyhow::anyhow!("Solami api_token base58 decode failed: {}", e))?; + let keypair = Keypair::try_from(keypair_bytes.as_slice()).map_err(|e| { + anyhow::anyhow!("Solami api_token is not a valid Solana keypair: {}", e) + })?; let (cert, key) = new_dummy_x509_certificate(&keypair); let mut crypto = rustls::ClientConfig::builder() .dangerous() @@ -66,7 +75,17 @@ impl SolamiClient { .to_socket_addrs()? .next() .ok_or_else(|| anyhow::anyhow!("Address not resolved"))?; - let connection = endpoint.connect(addr, SOLAMI_SERVER)?.await?; + let connecting = endpoint.connect(addr, SOLAMI_SERVER)?; + let connection = timeout(CONNECT_TIMEOUT, connecting) + .await + .context("Solami QUIC connect timeout")? + .with_context(|| { + format!( + "Solami QUIC handshake failed (verify wallet pubkey {} is registered and UDP {} is reachable)", + keypair.pubkey(), + endpoint_string + ) + })?; Ok(Self { rpc_client: Arc::new(rpc_client), @@ -78,14 +97,29 @@ impl SolamiClient { }) } - async fn reconnect(&self) -> Result<()> { - let _guard = self.reconnect.try_lock()?; - let connection = self - .endpoint - .connect_with(self.client_config.clone(), self.addr, SOLAMI_SERVER)? - .await?; - self.connection.store(Arc::new(connection)); - Ok(()) + async fn ensure_connected(&self) -> Result> { + let current = self.connection.load_full(); + if current.close_reason().is_none() { + return Ok(current); + } + 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, SOLAMI_SERVER)?; + let connection = timeout(CONNECT_TIMEOUT, connecting) + .await + .context("Solami QUIC reconnect timeout")? + .with_context(|| { + format!( + "Solami QUIC re-handshake failed (peer {} SNI {})", + self.addr, SOLAMI_SERVER + ) + })?; + self.connection.store(Arc::new(connection)); + return Ok(self.connection.load_full()); + } + Ok(current) } async fn try_send_bytes(connection: &Connection, payload: &[u8]) -> Result<()> { @@ -107,31 +141,77 @@ impl SwqosClientTrait for SolamiClient { let start_time = Instant::now(); let signature = transaction.get_signature(); let serialized_tx = bincode::serialize(transaction)?; - let connection = self.connection.load_full(); - if Self::try_send_bytes(&connection, &serialized_tx).await.is_err() { - eprintln!(" [Solami] {} submission failed, reconnecting", trade_type); - self.reconnect().await?; - let connection = self.connection.load_full(); - if let Err(e) = Self::try_send_bytes(&connection, &serialized_tx).await { - eprintln!(" [Solami] {} submission failed: {:?}", trade_type, e); - return Err(e.into()); + let connection = self.ensure_connected().await?; + let mut send_result = + timeout(SEND_TIMEOUT, Self::try_send_bytes(&connection, &serialized_tx)).await; + let need_retry = matches!(&send_result, Ok(Err(_)) | Err(_)); + if need_retry { + if crate::common::sdk_log::sdk_log_enabled() { + crate::common::sdk_log::log_swqos_submission_failed( + "Solami", + trade_type, + start_time.elapsed(), + "reconnecting", + ); + } + let connection = self.ensure_connected().await?; + send_result = + timeout(SEND_TIMEOUT, Self::try_send_bytes(&connection, &serialized_tx)).await; + } + match send_result { + Ok(Ok(())) => {} + Ok(Err(e)) => { + if crate::common::sdk_log::sdk_log_enabled() { + crate::common::sdk_log::log_swqos_submission_failed( + "Solami", + trade_type, + start_time.elapsed(), + &e, + ); + } + return Err(e); + } + Err(_) => { + if crate::common::sdk_log::sdk_log_enabled() { + crate::common::sdk_log::log_swqos_submission_failed( + "Solami", + trade_type, + start_time.elapsed(), + "timeout", + ); + } + anyhow::bail!("Solami QUIC send timeout"); } } + if crate::common::sdk_log::sdk_log_enabled() { + crate::common::sdk_log::log_swqos_submitted("Solami", trade_type, start_time.elapsed()); + } + let start_time = Instant::now(); match poll_transaction_confirmation(&self.rpc_client, *signature, wait_confirmation).await { Ok(_) => (), Err(e) => { - println!(" signature: {:?}", signature); - println!( - " [Solami] {} confirmation failed: {:?}", - trade_type, - start_time.elapsed() - ); + if crate::common::sdk_log::sdk_log_enabled() { + println!(" signature: {:?}", signature); + println!( + " [{:width$}] {} confirmation failed: {:?}", + "Solami", + trade_type, + start_time.elapsed(), + width = crate::common::sdk_log::SWQOS_LABEL_WIDTH + ); + } return Err(e); } } - if wait_confirmation { + if wait_confirmation && crate::common::sdk_log::sdk_log_enabled() { println!(" signature: {:?}", signature); - println!(" [Solami] {} confirmed: {:?}", trade_type, start_time.elapsed()); + println!( + " [{:width$}] {} confirmed: {:?}", + "Solami", + trade_type, + start_time.elapsed(), + width = crate::common::sdk_log::SWQOS_LABEL_WIDTH + ); } Ok(()) }