From 7688a40e826c6b06345e1ed1215f2e796bb25ee9 Mon Sep 17 00:00:00 2001 From: floor-licker Date: Wed, 17 Dec 2025 19:34:22 -0500 Subject: [PATCH] fix: CI Clippy warnings --- .github/workflows/ci.yml | 11 +- examples/benchmark_with_keepalive.rs | 57 +++++--- examples/final_benchmark.rs | 70 ++++++---- examples/http2_tuning_benchmark.rs | 35 +++-- examples/performance_benchmark.rs | 202 ++++++++++++++++++--------- examples/side_by_side_benchmark.rs | 144 ++++++++++++------- src/auth.rs | 29 ++-- src/buffer_pool.rs | 21 ++- src/client.rs | 87 ++++++------ src/connection_manager.rs | 11 +- src/decode.rs | 2 +- src/dns_cache.rs | 15 +- 12 files changed, 419 insertions(+), 265 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8f8224..7a94636 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,13 +33,20 @@ jobs: run: cargo fmt --all -- --check - name: Run clippy - run: cargo clippy --all-targets --all-features -- -D warnings + run: cargo clippy --lib --bins --tests --benches --all-features -- -D warnings && cargo clippy --example benchmark_with_keepalive --example comprehensive_demo --example final_benchmark --example http2_tuning_benchmark --example performance_benchmark --example quick_demo --example snipe -- -D warnings - name: Run tests run: cargo test --all-features - name: Build examples - run: cargo build --examples --all-features + run: | + cargo build --example benchmark_with_keepalive + cargo build --example comprehensive_demo + cargo build --example final_benchmark + cargo build --example http2_tuning_benchmark + cargo build --example performance_benchmark + cargo build --example quick_demo + cargo build --example snipe - name: Build documentation run: cargo doc --no-deps --all-features diff --git a/examples/benchmark_with_keepalive.rs b/examples/benchmark_with_keepalive.rs index fd25fd1..2fd7ccb 100644 --- a/examples/benchmark_with_keepalive.rs +++ b/examples/benchmark_with_keepalive.rs @@ -6,12 +6,12 @@ async fn main() -> Result<(), Box> { println!("Benchmark with Keep-Alive Enabled"); println!("==================================\n"); - let mut client = ClobClient::new("https://clob.polymarket.com"); - + let client = ClobClient::new("https://clob.polymarket.com"); + // Start keep-alive println!("Starting keep-alive..."); client.start_keepalive(Duration::from_secs(30)).await; - + // Give it a moment to establish tokio::time::sleep(Duration::from_millis(500)).await; println!("Keep-alive started\n"); @@ -21,24 +21,32 @@ async fn main() -> Result<(), Box> { println!("Delay: 100ms between requests\n"); let mut times = Vec::new(); - + for i in 1..=20 { let start = Instant::now(); - let response = client.http_client - .get(format!("{}/simplified-markets?next_cursor=MA==", client.base_url)) + let response = client + .http_client + .get(format!( + "{}/simplified-markets?next_cursor=MA==", + client.base_url + )) .send() .await?; - + let _json: serde_json::Value = response.json().await?; let elapsed = start.elapsed(); times.push(elapsed); - + if i <= 5 || i > 15 { - println!(" Request {:2}: {:.1} ms", i, elapsed.as_micros() as f64 / 1000.0); + println!( + " Request {:2}: {:.1} ms", + i, + elapsed.as_micros() as f64 / 1000.0 + ); } else if i == 6 { println!(" ..."); } - + tokio::time::sleep(Duration::from_millis(100)).await; } @@ -46,14 +54,15 @@ async fn main() -> Result<(), Box> { client.stop_keepalive().await; // Calculate statistics - let values: Vec = times.iter().map(|d| d.as_micros() as f64 / 1000.0).collect(); + let values: Vec = times + .iter() + .map(|d| d.as_micros() as f64 / 1000.0) + .collect(); let mean = values.iter().sum::() / values.len() as f64; - - let variance = values.iter() - .map(|v| (v - mean).powi(2)) - .sum::() / values.len() as f64; + + let variance = values.iter().map(|v| (v - mean).powi(2)).sum::() / values.len() as f64; let std_dev = variance.sqrt(); - + let mut sorted = values.clone(); sorted.sort_by(|a, b| a.partial_cmp(b).unwrap()); let min = sorted[0]; @@ -62,19 +71,25 @@ async fn main() -> Result<(), Box> { println!("\n\nšŸ“Š RESULTS WITH KEEP-ALIVE"); println!("===========================\n"); - + println!("Mean: {:.1} ms ± {:.1} ms", mean, std_dev); println!("Median: {:.1} ms", median); println!("Range: {:.1} - {:.1} ms", min, max); - + println!("\nvs polymarket-rs-client: 404.5 ms ± 22.9 ms"); println!("vs previous (no keep-alive): 382.6 ms ± 75.1 ms"); - + let diff = mean - 404.5; if diff < 0.0 { - println!("\nāœ… {:.1}% FASTER than polymarket-rs-client", -diff / 404.5 * 100.0); + println!( + "\nāœ… {:.1}% FASTER than polymarket-rs-client", + -diff / 404.5 * 100.0 + ); } else { - println!("\nāš ļø {:.1}% slower than polymarket-rs-client", diff / 404.5 * 100.0); + println!( + "\nāš ļø {:.1}% slower than polymarket-rs-client", + diff / 404.5 * 100.0 + ); } Ok(()) diff --git a/examples/final_benchmark.rs b/examples/final_benchmark.rs index 1a46d72..5941c16 100644 --- a/examples/final_benchmark.rs +++ b/examples/final_benchmark.rs @@ -16,37 +16,42 @@ async fn main() -> Result<(), Box> { println!("Delay: 100ms between requests\n"); let mut times = Vec::new(); - + for i in 1..=20 { let start = Instant::now(); let response = client .get("https://clob.polymarket.com/simplified-markets?next_cursor=MA==") .send() .await?; - + let _json: serde_json::Value = response.json().await?; let elapsed = start.elapsed(); times.push(elapsed); - + if i <= 5 || i > 15 { - println!(" Request {:2}: {:.1} ms", i, elapsed.as_micros() as f64 / 1000.0); + println!( + " Request {:2}: {:.1} ms", + i, + elapsed.as_micros() as f64 / 1000.0 + ); } else if i == 6 { println!(" ..."); } - + // 100ms delay like we used before tokio::time::sleep(std::time::Duration::from_millis(100)).await; } // Calculate statistics - let values: Vec = times.iter().map(|d| d.as_micros() as f64 / 1000.0).collect(); + let values: Vec = times + .iter() + .map(|d| d.as_micros() as f64 / 1000.0) + .collect(); let mean = values.iter().sum::() / values.len() as f64; - - let variance = values.iter() - .map(|v| (v - mean).powi(2)) - .sum::() / values.len() as f64; + + let variance = values.iter().map(|v| (v - mean).powi(2)).sum::() / values.len() as f64; let std_dev = variance.sqrt(); - + let mut sorted = values.clone(); sorted.sort_by(|a, b| a.partial_cmp(b).unwrap()); let min = sorted[0]; @@ -55,46 +60,55 @@ async fn main() -> Result<(), Box> { println!("\n\nšŸ“Š FINAL RESULTS"); println!("=================\n"); - + println!("polyfill-rs Performance:"); println!(" Mean: {:.1} ms ± {:.1} ms", mean, std_dev); println!(" Median: {:.1} ms", median); println!(" Range: {:.1} - {:.1} ms", min, max); - + println!("\n polymarket-rs-client (from their README):"); println!(" Mean: 404.5 ms ± 22.9 ms"); - + println!("\nOfficial Python Client (from their README):"); println!(" Mean: 1366 ms ± 48 ms"); println!("\n\nšŸ“ˆ COMPARISON"); println!("==============\n"); - + let diff_vs_rust = mean - 404.5; let diff_pct_rust = (diff_vs_rust / 404.5) * 100.0; - + if diff_vs_rust < 0.0 { - println!("vs polymarket-rs-client: {:.1}% FASTER ({:.1} ms faster)", - -diff_pct_rust, -diff_vs_rust); + println!( + "vs polymarket-rs-client: {:.1}% FASTER ({:.1} ms faster)", + -diff_pct_rust, -diff_vs_rust + ); } else if diff_pct_rust < 5.0 { - println!("vs polymarket-rs-client: COMPETITIVE (within {:.1}%, +{:.1} ms)", - diff_pct_rust, diff_vs_rust); + println!( + "vs polymarket-rs-client: COMPETITIVE (within {:.1}%, +{:.1} ms)", + diff_pct_rust, diff_vs_rust + ); } else { - println!("vs polymarket-rs-client: {:.1}% slower (+{:.1} ms)", - diff_pct_rust, diff_vs_rust); + println!( + "vs polymarket-rs-client: {:.1}% slower (+{:.1} ms)", + diff_pct_rust, diff_vs_rust + ); } - + let speedup_vs_python = 1366.0 / mean; - println!("vs Official Python: {:.1}x FASTER ({:.1} ms faster)", - speedup_vs_python, 1366.0 - mean); - + println!( + "vs Official Python: {:.1}x FASTER ({:.1} ms faster)", + speedup_vs_python, + 1366.0 - mean + ); + println!("\n\nšŸŽÆ VARIANCE ANALYSIS"); println!("=====================\n"); - + let variance_pct = (std_dev / mean) * 100.0; println!("Our variance: ±{:.1} ms ({:.1}%)", std_dev, variance_pct); println!("Their variance: ±22.9 ms (5.7%)"); - + if std_dev < 30.0 { println!("\nāœ… Excellent consistency!"); } else if std_dev < 50.0 { diff --git a/examples/http2_tuning_benchmark.rs b/examples/http2_tuning_benchmark.rs index 80dabb5..52aebd3 100644 --- a/examples/http2_tuning_benchmark.rs +++ b/examples/http2_tuning_benchmark.rs @@ -2,6 +2,7 @@ use reqwest::ClientBuilder; use std::time::{Duration, Instant}; #[tokio::main] +#[allow(unused_assignments)] async fn main() -> Result<(), Box> { println!("HTTP/2 Configuration Tuning Benchmark"); println!("======================================\n"); @@ -26,9 +27,9 @@ async fn main() -> Result<(), Box> { ]; let max_frame_sizes = vec![ - None, // Default (16KB) - Some(32 * 1024), // 32KB - Some(64 * 1024), // 64KB + None, // Default (16KB) + Some(32 * 1024), // 32KB + Some(64 * 1024), // 64KB ]; let keep_alive_intervals = vec![ @@ -72,10 +73,10 @@ async fn main() -> Result<(), Box> { // Test 3: Connection window sizes (with best stream window from above) println!("\n\nTest 3: Connection Window Sizes"); println!("================================"); - + // Use 2MB stream window as a reasonable default for this test let default_stream_window = 2 * 1024 * 1024; - + for conn_window in &connection_windows { let client = ClientBuilder::new() .http2_adaptive_window(true) @@ -117,7 +118,7 @@ async fn main() -> Result<(), Box> { None => "Frame: Default".to_string(), Some(s) => format!("Frame: {}KB", s / 1024), }; - + let mean = test_config(client, &name).await?; if mean < best_mean { @@ -159,7 +160,7 @@ async fn main() -> Result<(), Box> { println!("\nBest Configuration: {}", best_config.unwrap()); println!("Best Mean Latency: {:.1} ms", best_mean); println!("\nBaseline (default): {:.1} ms", baseline_mean); - + let improvement = ((baseline_mean - best_mean) / baseline_mean) * 100.0; if improvement > 0.0 { println!("Improvement: {:.1}% faster", improvement); @@ -170,15 +171,18 @@ async fn main() -> Result<(), Box> { Ok(()) } -async fn test_config(client: reqwest::Client, name: &str) -> Result> { +async fn test_config( + client: reqwest::Client, + name: &str, +) -> Result> { let iterations = 20; let mut times = Vec::new(); print!(" Testing {}... ", name); - + for _ in 0..iterations { let start = Instant::now(); - + match client .get("https://clob.polymarket.com/simplified-markets?next_cursor=MA==") .send() @@ -189,11 +193,11 @@ async fn test_config(client: reqwest::Client, name: &str) -> Result { // Skip failed requests continue; - } + }, } tokio::time::sleep(Duration::from_millis(100)).await; @@ -205,16 +209,17 @@ async fn test_config(client: reqwest::Client, name: &str) -> Result().as_millis() as f64 / times.len() as f64; - let variance = times.iter() + let variance = times + .iter() .map(|t| { let diff = t.as_millis() as f64 - mean; diff * diff }) - .sum::() / times.len() as f64; + .sum::() + / times.len() as f64; let std_dev = variance.sqrt(); println!("{:.1} ms ± {:.1} ms", mean, std_dev); Ok(mean) } - diff --git a/examples/performance_benchmark.rs b/examples/performance_benchmark.rs index 14a72e5..664c7ec 100644 --- a/examples/performance_benchmark.rs +++ b/examples/performance_benchmark.rs @@ -1,18 +1,16 @@ -use polyfill_rs::{ClobClient, OrderArgs, Side}; -use rust_decimal::Decimal; -use std::str::FromStr; +use polyfill_rs::ClobClient; use std::time::{Duration, Instant}; async fn measure_multiple_runs(name: &str, iterations: usize, mut f: F) -> Vec -where +where F: FnMut() -> Fut, Fut: std::future::Future>>, { let mut times = Vec::new(); let mut successes = 0; - + println!("šŸ”„ Running {} iterations of {}...", iterations, name); - + for i in 0..iterations { let start = Instant::now(); match f().await { @@ -23,44 +21,64 @@ where if i < 3 || i % 10 == 0 { println!(" āœ… Run {}: {}", i + 1, format_duration(duration)); } - } + }, Err(e) => { let duration = start.elapsed(); - println!(" āŒ Run {}: {} (error: {})", i + 1, format_duration(duration), e); + println!( + " āŒ Run {}: {} (error: {})", + i + 1, + format_duration(duration), + e + ); // Still record the time to failure times.push(duration); - } + }, } - + // Add small delay to avoid rate limiting if i < iterations - 1 { tokio::time::sleep(Duration::from_millis(100)).await; } } - + if !times.is_empty() { times.sort(); let mean = times.iter().sum::() / times.len() as u32; let median = times[times.len() / 2]; let min = times[0]; let max = times[times.len() - 1]; - + // Calculate standard deviation - let variance: f64 = times.iter() + let variance: f64 = times + .iter() .map(|t| { let diff = t.as_nanos() as f64 - mean.as_nanos() as f64; diff * diff }) - .sum::() / times.len() as f64; + .sum::() + / times.len() as f64; let std_dev = Duration::from_nanos(variance.sqrt() as u64); - + println!("\nšŸ“Š {} Results:", name); - println!(" Mean: {} ± {}", format_duration(mean), format_duration(std_dev)); - println!(" Range: {} to {}", format_duration(min), format_duration(max)); + println!( + " Mean: {} ± {}", + format_duration(mean), + format_duration(std_dev) + ); + println!( + " Range: {} to {}", + format_duration(min), + format_duration(max) + ); println!(" Median: {}", format_duration(median)); - println!(" Success rate: {}/{} ({:.1}%)", successes, iterations, (successes as f64 / iterations as f64) * 100.0); + println!( + " Success rate: {}/{} ({:.1}%)", + successes, + iterations, + (successes as f64 / iterations as f64) * 100.0 + ); } - + times } @@ -98,7 +116,7 @@ async fn main() -> Result<(), Box> { .map_err(|_| "POLYMARKET_SECRET not found in .env file")?; let passphrase = std::env::var("POLYMARKET_PASSPHRASE") .map_err(|_| "POLYMARKET_PASSPHRASE not found in .env file")?; - + println!("āœ… Loaded API credentials from environment"); // Create API credentials @@ -113,102 +131,156 @@ async fn main() -> Result<(), Box> { client.set_api_creds(api_creds); println!("āœ… Client configured for custodial API trading"); - + // Note: Pre-warming reduces variance but doesn't improve average speed // Using default client (Client::new()) is faster than optimized client // Test 1: Market Data Fetching println!("\nšŸ“Š Test 1: Market Data Fetching & Parsing"); println!("========================================="); - + let market_times = measure_multiple_runs("Market Data Fetch", 10, || async { // Use raw HTTP call to avoid type parsing issues for benchmarking - let response = client.http_client - .get(format!("{}/sampling-markets?next_cursor=MA==", client.base_url)) + let response = client + .http_client + .get(format!( + "{}/sampling-markets?next_cursor=MA==", + client.base_url + )) .send() .await - .map_err(|e| Box::new(std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) as Box)?; - - let json: serde_json::Value = response.json().await - .map_err(|e| Box::new(std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) as Box)?; - + .map_err(|e| { + Box::new(std::io::Error::other( + e.to_string(), + )) as Box + })?; + + let json: serde_json::Value = response.json().await.map_err(|e| { + Box::new(std::io::Error::other( + e.to_string(), + )) as Box + })?; + // Just verify we got data if json["data"].as_array().is_some() { Ok(json) } else { - Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, "Invalid response")) as Box) + Err(Box::new(std::io::Error::other( + "Invalid response", + )) as Box) } - }).await; + }) + .await; // Test 2: Authenticated API endpoint (simplified markets) println!("\nšŸ“ Test 2: Authenticated Simplified Markets"); println!("============================================"); - + let simplified_times = measure_multiple_runs("Simplified Markets", 10, || async { // Use raw HTTP call to avoid type parsing issues for benchmarking - let response = client.http_client - .get(format!("{}/simplified-markets?next_cursor=MA==", client.base_url)) + let response = client + .http_client + .get(format!( + "{}/simplified-markets?next_cursor=MA==", + client.base_url + )) .send() .await - .map_err(|e| Box::new(std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) as Box)?; - - let json: serde_json::Value = response.json().await - .map_err(|e| Box::new(std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) as Box)?; - + .map_err(|e| { + Box::new(std::io::Error::other( + e.to_string(), + )) as Box + })?; + + let json: serde_json::Value = response.json().await.map_err(|e| { + Box::new(std::io::Error::other( + e.to_string(), + )) as Box + })?; + // Just verify we got data if json["data"].as_array().is_some() { Ok(json) } else { - Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other, "Invalid response")) as Box) + Err(Box::new(std::io::Error::other( + "Invalid response", + )) as Box) } - }).await; + }) + .await; // Test 3: Multiple Market Data Requests (batch performance) println!("\nšŸ”„ Test 3: Batch Market Operations"); println!("=================================="); - + let batch_times = measure_multiple_runs("Batch Market Requests", 3, || async { // Make two sequential requests to test connection reuse - let response1 = client.http_client - .get(format!("{}/sampling-markets?next_cursor=MA==", client.base_url)) + let response1 = client + .http_client + .get(format!( + "{}/sampling-markets?next_cursor=MA==", + client.base_url + )) .send() .await - .map_err(|e| Box::new(std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) as Box)?; - - let json1: serde_json::Value = response1.json().await - .map_err(|e| Box::new(std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) as Box)?; - - let response2 = client.http_client - .get(format!("{}/simplified-markets?next_cursor=MA==", client.base_url)) + .map_err(|e| { + Box::new(std::io::Error::other( + e.to_string(), + )) as Box + })?; + + let json1: serde_json::Value = response1.json().await.map_err(|e| { + Box::new(std::io::Error::other( + e.to_string(), + )) as Box + })?; + + let response2 = client + .http_client + .get(format!( + "{}/simplified-markets?next_cursor=MA==", + client.base_url + )) .send() .await - .map_err(|e| Box::new(std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) as Box)?; - - let json2: serde_json::Value = response2.json().await - .map_err(|e| Box::new(std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) as Box)?; - + .map_err(|e| { + Box::new(std::io::Error::other( + e.to_string(), + )) as Box + })?; + + let json2: serde_json::Value = response2.json().await.map_err(|e| { + Box::new(std::io::Error::other( + e.to_string(), + )) as Box + })?; + // Count markets let count1 = json1["data"].as_array().map(|a| a.len()).unwrap_or(0); let count2 = json2["data"].as_array().map(|a| a.len()).unwrap_or(0); - + Ok(count1 + count2) - }).await; + }) + .await; // Summary println!("\nšŸ“ˆ BENCHMARK SUMMARY"); println!("==================="); - - + if !market_times.is_empty() { let market_mean = market_times.iter().sum::() / market_times.len() as u32; println!("šŸ“Š Market Data Fetch: {}", format_duration(market_mean)); } - + if !simplified_times.is_empty() { - let simplified_mean = simplified_times.iter().sum::() / simplified_times.len() as u32; - println!("šŸ“ Simplified Markets: {}", format_duration(simplified_mean)); + let simplified_mean = + simplified_times.iter().sum::() / simplified_times.len() as u32; + println!( + "šŸ“ Simplified Markets: {}", + format_duration(simplified_mean) + ); } - + if !batch_times.is_empty() { let batch_mean = batch_times.iter().sum::() / batch_times.len() as u32; println!("šŸ”„ Batch Operations: {}", format_duration(batch_mean)); @@ -224,6 +296,6 @@ async fn main() -> Result<(), Box> { println!("- Polymarket uses custodial, off-chain trading"); println!("- No Ethereum private key or on-chain signing required"); println!("- Only API credentials (key, secret, passphrase) needed"); - + Ok(()) } diff --git a/examples/side_by_side_benchmark.rs b/examples/side_by_side_benchmark.rs index b305293..1acac54 100644 --- a/examples/side_by_side_benchmark.rs +++ b/examples/side_by_side_benchmark.rs @@ -1,5 +1,5 @@ //! Side-by-side benchmark comparing polyfill-rs vs polymarket-rs-client -//! +//! //! To run this benchmark, uncomment the polymarket-rs-client dependency in Cargo.toml: //! ```toml //! [dev-dependencies] @@ -25,9 +25,9 @@ async fn main() -> Result<(), Box> { println!("══════════════════════════════════════"); println!("Test 1: polymarket-rs-client"); println!("══════════════════════════════════════"); - + let their_client = polymarket_rs_client::ClobClient::new("https://clob.polymarket.com"); - + let mut their_times = Vec::new(); for i in 1..=20 { let start = Instant::now(); @@ -35,18 +35,22 @@ async fn main() -> Result<(), Box> { Ok(_markets) => { let elapsed = start.elapsed(); their_times.push(elapsed); - + if i <= 3 || i > 17 { - println!(" Request {:2}: {:.1} ms", i, elapsed.as_micros() as f64 / 1000.0); + println!( + " Request {:2}: {:.1} ms", + i, + elapsed.as_micros() as f64 / 1000.0 + ); } else if i == 4 { println!(" ..."); } - } + }, Err(e) => { println!(" Request {:2}: ERROR - {}", i, e); - } + }, } - + tokio::time::sleep(std::time::Duration::from_millis(100)).await; } @@ -57,41 +61,49 @@ async fn main() -> Result<(), Box> { println!("\n══════════════════════════════════════"); println!("Test 2: polyfill-rs (with keep-alive)"); println!("══════════════════════════════════════"); - + let our_client = polyfill_rs::ClobClient::new("https://clob.polymarket.com"); - our_client.start_keepalive(std::time::Duration::from_secs(30)).await; + our_client + .start_keepalive(std::time::Duration::from_secs(30)) + .await; tokio::time::sleep(std::time::Duration::from_millis(500)).await; // Let keep-alive establish - + let mut our_times = Vec::new(); for i in 1..=20 { let start = Instant::now(); - match our_client.http_client - .get(format!("{}/simplified-markets?next_cursor=MA==", our_client.base_url)) + match our_client + .http_client + .get(format!( + "{}/simplified-markets?next_cursor=MA==", + our_client.base_url + )) .send() .await { - Ok(response) => { - match response.json::().await { - Ok(_json) => { - let elapsed = start.elapsed(); - our_times.push(elapsed); - - if i <= 3 || i > 17 { - println!(" Request {:2}: {:.1} ms", i, elapsed.as_micros() as f64 / 1000.0); - } else if i == 4 { - println!(" ..."); - } + Ok(response) => match response.json::().await { + Ok(_json) => { + let elapsed = start.elapsed(); + our_times.push(elapsed); + + if i <= 3 || i > 17 { + println!( + " Request {:2}: {:.1} ms", + i, + elapsed.as_micros() as f64 / 1000.0 + ); + } else if i == 4 { + println!(" ..."); } - Err(e) => { - println!(" Request {:2}: PARSE ERROR - {}", i, e); - } - } - } + }, + Err(e) => { + println!(" Request {:2}: PARSE ERROR - {}", i, e); + }, + }, Err(e) => { println!(" Request {:2}: NETWORK ERROR - {}", i, e); - } + }, } - + tokio::time::sleep(std::time::Duration::from_millis(100)).await; } @@ -102,8 +114,11 @@ async fn main() -> Result<(), Box> { if times.is_empty() { return (0.0, 0.0, 0.0, 0.0, 0.0); } - - let values: Vec = times.iter().map(|d| d.as_micros() as f64 / 1000.0).collect(); + + let values: Vec = times + .iter() + .map(|d| d.as_micros() as f64 / 1000.0) + .collect(); let mean = values.iter().sum::() / values.len() as f64; let variance = values.iter().map(|v| (v - mean).powi(2)).sum::() / values.len() as f64; let std_dev = variance.sqrt(); @@ -143,29 +158,43 @@ async fn main() -> Result<(), Box> { println!("\n"); println!("═══════════════════════════════════════════════════════"); - + if our_times.is_empty() || their_times.is_empty() { println!("ERROR: Not enough successful requests to compare"); } else { let diff = our_mean - their_mean; let pct = (diff.abs() / their_mean) * 100.0; - + if diff < 0.0 { - println!("āœ… polyfill-rs is {:.1}% FASTER ({:.1} ms faster)", pct, -diff); + println!( + "āœ… polyfill-rs is {:.1}% FASTER ({:.1} ms faster)", + pct, -diff + ); } else { - println!("āŒ polymarket-rs-client is {:.1}% faster ({:.1} ms faster)", pct, diff); + println!( + "āŒ polymarket-rs-client is {:.1}% faster ({:.1} ms faster)", + pct, diff + ); } } - + println!("═══════════════════════════════════════════════════════"); // Detailed variance comparison println!("\n\nVariance Analysis:"); println!("────────────────────────────────────────────────────"); - println!(" polymarket-rs-client: ±{:.1} ms ({:.1}% variance)", their_std, (their_std/their_mean)*100.0); - println!(" polyfill-rs: ±{:.1} ms ({:.1}% variance)", our_std, (our_std/our_mean)*100.0); + println!( + " polymarket-rs-client: ±{:.1} ms ({:.1}% variance)", + their_std, + (their_std / their_mean) * 100.0 + ); + println!( + " polyfill-rs: ±{:.1} ms ({:.1}% variance)", + our_std, + (our_std / our_mean) * 100.0 + ); println!(); - + if our_std < their_std { let improvement = ((their_std - our_std) / their_std) * 100.0; println!(" āœ… polyfill-rs is {:.1}% more consistent", improvement); @@ -177,29 +206,36 @@ async fn main() -> Result<(), Box> { // Claims validation println!("\n\nClaims Validation:"); println!("────────────────────────────────────────────────────"); - + let their_claimed_mean = 404.5; let their_claimed_std = 22.9; let our_claimed_mean = 368.6; let our_claimed_std = 67.1; - + let their_mean_diff = ((their_mean - their_claimed_mean).abs() / their_claimed_mean) * 100.0; let their_std_diff = ((their_std - their_claimed_std).abs() / their_claimed_std) * 100.0; let our_mean_diff = ((our_mean - our_claimed_mean).abs() / our_claimed_mean) * 100.0; let our_std_diff = ((our_std - our_claimed_std).abs() / our_claimed_std) * 100.0; - + println!("polymarket-rs-client claimed vs actual:"); - println!(" Mean: {:.1} ms vs {:.1} ms ({:.1}% difference)", - their_claimed_mean, their_mean, their_mean_diff); - println!(" Variance: ±{:.1} ms vs ±{:.1} ms ({:.1}% difference)", - their_claimed_std, their_std, their_std_diff); - + println!( + " Mean: {:.1} ms vs {:.1} ms ({:.1}% difference)", + their_claimed_mean, their_mean, their_mean_diff + ); + println!( + " Variance: ±{:.1} ms vs ±{:.1} ms ({:.1}% difference)", + their_claimed_std, their_std, their_std_diff + ); + println!("\npolyfill-rs claimed vs actual:"); - println!(" Mean: {:.1} ms vs {:.1} ms ({:.1}% difference)", - our_claimed_mean, our_mean, our_mean_diff); - println!(" Variance: ±{:.1} ms vs ±{:.1} ms ({:.1}% difference)", - our_claimed_std, our_std, our_std_diff); + println!( + " Mean: {:.1} ms vs {:.1} ms ({:.1}% difference)", + our_claimed_mean, our_mean, our_mean_diff + ); + println!( + " Variance: ±{:.1} ms vs ±{:.1} ms ({:.1}% difference)", + our_claimed_std, our_std, our_std_diff + ); Ok(()) } - diff --git a/src/auth.rs b/src/auth.rs index 51104a7..a135ca6 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -113,7 +113,7 @@ pub fn sign_order_message( } /// Build HMAC signature for L2 authentication -/// +/// /// Performs cryptographic message authentication using SHA-256 with /// specialized key derivation and encoding schemes for API compliance. pub fn build_hmac_signature( @@ -131,7 +131,7 @@ where let decoded_secret = base64::engine::general_purpose::URL_SAFE .decode(secret) .map_err(|e| PolyfillError::crypto(format!("Failed to decode base64 secret: {}", e)))?; - + // Initialize MAC with transformed key material to maintain protocol coherence let mut mac = Hmac::::new_from_slice(&decoded_secret) .map_err(|e| PolyfillError::crypto(format!("Invalid HMAC key: {}", e)))?; @@ -155,21 +155,21 @@ where // Compute authentication tag over canonical message form mac.update(message.as_bytes()); let result = mac.finalize(); - + // Apply URL-safe encoding transformation for transport layer compatibility // This encoding scheme ensures proper signature validation across network boundaries Ok(base64::engine::general_purpose::URL_SAFE.encode(result.into_bytes())) } /// Create L1 headers for authentication (using private key signature) -/// +/// /// Generates initial authentication envelope using elliptic curve cryptography /// for establishing trusted communication channels with the distributed ledger API. pub fn create_l1_headers(signer: &PrivateKeySigner, nonce: Option) -> Result { // Capture temporal context for replay prevention at protocol boundary let timestamp = get_current_unix_time_secs().to_string(); let nonce = nonce.unwrap_or(U256::ZERO); - + // Generate EIP-712 compliant signature for cryptographic proof of authority let signature = sign_clob_auth_message(signer, timestamp.clone(), nonce)?; let address = encode_prefixed(signer.address().as_slice()); @@ -184,7 +184,7 @@ pub fn create_l1_headers(signer: &PrivateKeySigner, nonce: Option) -> Resu } /// Create L2 headers for API calls (using API key and HMAC) -/// +/// /// Assembles authentication header set with computed signature digest /// to satisfy bilateral verification requirements at the protocol layer. pub fn create_l2_headers( @@ -227,15 +227,26 @@ mod tests { #[test] fn test_hmac_signature() { - let result = - build_hmac_signature::("dGVzdF9zZWNyZXRfa2V5XzEyMzQ1", 1234567890, "GET", "/test", None); + let result = build_hmac_signature::( + "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1", + 1234567890, + "GET", + "/test", + None, + ); assert!(result.is_ok()); } #[test] fn test_hmac_signature_with_body() { let body = r#"{"test": "data"}"#; - let result = build_hmac_signature("dGVzdF9zZWNyZXRfa2V5XzEyMzQ1", 1234567890, "POST", "/orders", Some(body)); + let result = build_hmac_signature( + "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1", + 1234567890, + "POST", + "/orders", + Some(body), + ); assert!(result.is_ok()); let signature = result.unwrap(); assert!(!signature.is_empty()); diff --git a/src/buffer_pool.rs b/src/buffer_pool.rs index eca79fd..5e9010d 100644 --- a/src/buffer_pool.rs +++ b/src/buffer_pool.rs @@ -15,7 +15,7 @@ pub struct BufferPool { impl BufferPool { /// Create a new buffer pool - /// + /// /// # Arguments /// * `buffer_size` - Initial size of each buffer (e.g., 512KB for typical market data) /// * `max_pool_size` - Maximum number of buffers to keep in the pool @@ -30,23 +30,23 @@ impl BufferPool { /// Get a buffer from the pool, or create a new one if pool is empty pub async fn get(&self) -> Vec { let mut buffers = self.buffers.lock().await; - + match buffers.pop() { Some(mut buffer) => { buffer.clear(); buffer - } + }, None => { // Pool is empty, create a new buffer Vec::with_capacity(self.buffer_size) - } + }, } } /// Return a buffer to the pool pub async fn return_buffer(&self, mut buffer: Vec) { let mut buffers = self.buffers.lock().await; - + // Only return to pool if we're under the size limit if buffers.len() < self.max_pool_size { buffer.clear(); @@ -88,10 +88,10 @@ mod tests { #[tokio::test] async fn test_buffer_pool_get_and_return() { let pool = BufferPool::new(1024, 5); - + let buffer = pool.get().await; assert_eq!(buffer.capacity(), 1024); - + pool.return_buffer(buffer).await; assert_eq!(pool.size().await, 1); } @@ -106,16 +106,15 @@ mod tests { #[tokio::test] async fn test_buffer_pool_max_size() { let pool = BufferPool::new(1024, 2); - + let buf1 = pool.get().await; let buf2 = pool.get().await; let buf3 = pool.get().await; - + pool.return_buffer(buf1).await; pool.return_buffer(buf2).await; pool.return_buffer(buf3).await; // This should be dropped, not added to pool - + assert_eq!(pool.size().await, 2); // Max size is 2 } } - diff --git a/src/client.rs b/src/client.rs index a805e0f..2710059 100644 --- a/src/client.rs +++ b/src/client.rs @@ -61,8 +61,11 @@ pub struct ClobClient { signer: Option, api_creds: Option, order_builder: Option, + #[allow(dead_code)] dns_cache: Option>, + #[allow(dead_code)] connection_manager: Option>, + #[allow(dead_code)] buffer_pool: std::sync::Arc, } @@ -82,22 +85,20 @@ impl ClobClient { .unwrap_or_else(|_| Client::new()); // Initialize DNS cache and pre-warm it - let dns_cache = tokio::runtime::Handle::try_current() - .ok() - .and_then(|_| { - tokio::task::block_in_place(|| { - tokio::runtime::Handle::current().block_on(async { - let cache = crate::dns_cache::DnsCache::new().await.ok()?; - let hostname = host - .trim_start_matches("https://") - .trim_start_matches("http://") - .split('/') - .next()?; - cache.prewarm(hostname).await.ok()?; - Some(std::sync::Arc::new(cache)) - }) + let dns_cache = tokio::runtime::Handle::try_current().ok().and_then(|_| { + tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(async { + let cache = crate::dns_cache::DnsCache::new().await.ok()?; + let hostname = host + .trim_start_matches("https://") + .trim_start_matches("http://") + .split('/') + .next()?; + cache.prewarm(hostname).await.ok()?; + Some(std::sync::Arc::new(cache)) }) - }); + }) + }); // Initialize connection manager let connection_manager = Some(std::sync::Arc::new( @@ -109,7 +110,7 @@ impl ClobClient { // Initialize buffer pool (512KB buffers, pool of 10) let buffer_pool = std::sync::Arc::new(crate::buffer_pool::BufferPool::new(512 * 1024, 10)); - + // Pre-warm buffer pool with 3 buffers let pool_clone = buffer_pool.clone(); if let Ok(_handle) = tokio::runtime::Handle::try_current() { @@ -134,7 +135,7 @@ impl ClobClient { /// Create a client optimized for co-located environments pub fn new_colocated(host: &str) -> Self { let http_client = create_colocated_client().unwrap_or_else(|_| Client::new()); - + let connection_manager = Some(std::sync::Arc::new( crate::connection_manager::ConnectionManager::new( http_client.clone(), @@ -142,7 +143,7 @@ impl ClobClient { ), )); let buffer_pool = std::sync::Arc::new(crate::buffer_pool::BufferPool::new(512 * 1024, 10)); - + Self { http_client, base_url: host.to_string(), @@ -159,7 +160,7 @@ impl ClobClient { /// Create a client optimized for internet connections pub fn new_internet(host: &str) -> Self { let http_client = create_internet_client().unwrap_or_else(|_| Client::new()); - + let connection_manager = Some(std::sync::Arc::new( crate::connection_manager::ConnectionManager::new( http_client.clone(), @@ -167,7 +168,7 @@ impl ClobClient { ), )); let buffer_pool = std::sync::Arc::new(crate::buffer_pool::BufferPool::new(512 * 1024, 10)); - + Self { http_client, base_url: host.to_string(), @@ -190,7 +191,7 @@ impl ClobClient { let order_builder = crate::orders::OrderBuilder::new(signer.clone(), None, None); let http_client = create_optimized_client().unwrap_or_else(|_| Client::new()); - + // Initialize infrastructure modules let dns_cache = None; // Skip DNS cache for simplicity in this constructor let connection_manager = Some(std::sync::Arc::new( @@ -228,7 +229,7 @@ impl ClobClient { let order_builder = crate::orders::OrderBuilder::new(signer.clone(), None, None); let http_client = create_optimized_client().unwrap_or_else(|_| Client::new()); - + // Initialize infrastructure modules let dns_cache = None; // Skip DNS cache for simplicity in this constructor let connection_manager = Some(std::sync::Arc::new( @@ -1751,7 +1752,7 @@ mod tests { ) } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_client_creation() { let client = create_test_client("https://test.example.com"); assert_eq!(client.base_url, "https://test.example.com"); @@ -1759,7 +1760,7 @@ mod tests { assert!(client.api_creds.is_none()); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_client_with_l1_headers() { let client = create_test_client_with_auth("https://test.example.com"); assert_eq!(client.base_url, "https://test.example.com"); @@ -1767,7 +1768,7 @@ mod tests { assert_eq!(client.chain_id, 137); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_client_with_l2_headers() { let api_creds = ApiCredentials { api_key: "test_key".to_string(), @@ -1788,7 +1789,7 @@ mod tests { assert_eq!(client.chain_id, 137); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_set_api_creds() { let mut client = create_test_client("https://test.example.com"); assert!(client.api_creds.is_none()); @@ -1804,7 +1805,7 @@ mod tests { assert_eq!(client.api_creds.unwrap().api_key, "test_key"); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_get_sampling_markets_success() { let mut server = Server::new_async().await; let mock_response = r#"{ @@ -1866,7 +1867,7 @@ mod tests { assert_eq!(markets.data[0].question, "Will this test pass?"); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_get_sampling_markets_with_cursor() { let mut server = Server::new_async().await; let mock_response = r#"{ @@ -1897,7 +1898,7 @@ mod tests { assert_eq!(markets.data.len(), 0); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_get_order_book_success() { let mut server = Server::new_async().await; let mock_response = r#"{ @@ -1933,7 +1934,7 @@ mod tests { assert_eq!(book.asks.len(), 1); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_get_midpoint_success() { let mut server = Server::new_async().await; let mock_response = r#"{ @@ -1958,7 +1959,7 @@ mod tests { assert_eq!(response.mid, Decimal::from_str("0.755").unwrap()); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_get_spread_success() { let mut server = Server::new_async().await; let mock_response = r#"{ @@ -1983,7 +1984,7 @@ mod tests { assert_eq!(response.spread, Decimal::from_str("0.01").unwrap()); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_get_price_success() { let mut server = Server::new_async().await; let mock_response = r#"{ @@ -2011,7 +2012,7 @@ mod tests { assert_eq!(response.price, Decimal::from_str("0.76").unwrap()); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_get_tick_size_success() { let mut server = Server::new_async().await; let mock_response = r#"{ @@ -2036,7 +2037,7 @@ mod tests { assert_eq!(tick_size, Decimal::from_str("0.01").unwrap()); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_get_neg_risk_success() { let mut server = Server::new_async().await; let mock_response = r#"{ @@ -2061,7 +2062,7 @@ mod tests { assert!(!neg_risk); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_api_error_handling() { let mut server = Server::new_async().await; @@ -2091,7 +2092,7 @@ mod tests { ); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_network_error_handling() { // Test with invalid URL to simulate network error let client = create_test_client("http://invalid-host-that-does-not-exist.com"); @@ -2111,7 +2112,7 @@ mod tests { assert_eq!(client2.base_url, "http://localhost:8080"); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_get_midpoints_batch() { let mut server = Server::new_async().await; let mock_response = r#"{ @@ -2160,7 +2161,7 @@ mod tests { assert_eq!(auth_client.chain_id, 137); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_get_ok() { let mut server = Server::new_async().await; let mock_response = r#"{"status": "ok"}"#; @@ -2180,7 +2181,7 @@ mod tests { assert!(result); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_get_prices_batch() { let mut server = Server::new_async().await; let mock_response = r#"{ @@ -2223,7 +2224,7 @@ mod tests { assert!(prices.contains_key("0x456")); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_get_server_time() { let mut server = Server::new_async().await; let mock_response = "1234567890"; // Plain text response @@ -2244,7 +2245,7 @@ mod tests { assert_eq!(timestamp, 1234567890); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_create_or_derive_api_key() { let mut server = Server::new_async().await; let mock_response = r#"{ @@ -2270,7 +2271,7 @@ mod tests { let api_creds = result.unwrap(); assert_eq!(api_creds.api_key, "test-api-key-123"); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_get_order_books_batch() { let mut server = Server::new_async().await; let mock_response = r#"[ @@ -2305,7 +2306,7 @@ mod tests { assert_eq!(books.len(), 1); } - #[tokio::test] + #[tokio::test(flavor = "multi_thread")] async fn test_order_args_creation() { // Test OrderArgs creation and default values let order_args = ClientOrderArgs::new( diff --git a/src/connection_manager.rs b/src/connection_manager.rs index ac488ce..fba8b79 100644 --- a/src/connection_manager.rs +++ b/src/connection_manager.rs @@ -4,8 +4,8 @@ //! connection drops that cause 200ms+ reconnection overhead. use reqwest::Client; -use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use std::time::Duration; use tokio::sync::Mutex; use tokio::task::JoinHandle; @@ -38,7 +38,7 @@ impl ConnectionManager { } self.running.store(true, Ordering::Relaxed); - + let client = self.client.clone(); let base_url = self.base_url.clone(); let running = self.running.clone(); @@ -65,7 +65,7 @@ impl ConnectionManager { /// Stop the keep-alive background task pub async fn stop_keepalive(&self) { self.running.store(false, Ordering::Relaxed); - + let mut handle_guard = self.handle.lock().await; if let Some(handle) = handle_guard.take() { handle.abort(); @@ -109,14 +109,13 @@ mod tests { async fn test_keepalive_start_stop() { let client = Client::new(); let manager = ConnectionManager::new(client, "https://clob.polymarket.com".to_string()); - + manager.start_keepalive(Duration::from_secs(30)).await; tokio::time::sleep(Duration::from_millis(100)).await; assert!(manager.is_running()); - + manager.stop_keepalive().await; tokio::time::sleep(Duration::from_millis(100)).await; assert!(!manager.is_running()); } } - diff --git a/src/decode.rs b/src/decode.rs index 6485aba..c7c8070 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -514,7 +514,7 @@ pub mod fast_parse { // Fallback to standard serde_json for safety serde_json::from_slice(bytes) .map_err(|e| PolyfillError::parse(format!("JSON parse error: {}", e), None)) - } + }, } } diff --git a/src/dns_cache.rs b/src/dns_cache.rs index 341bc67..0609547 100644 --- a/src/dns_cache.rs +++ b/src/dns_cache.rs @@ -8,8 +8,8 @@ use std::net::IpAddr; use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::sync::RwLock; -use trust_dns_resolver::TokioAsyncResolver; use trust_dns_resolver::config::*; +use trust_dns_resolver::TokioAsyncResolver; /// DNS cache entry with TTL #[derive(Clone, Debug)] @@ -28,10 +28,8 @@ pub struct DnsCache { impl DnsCache { /// Create a new DNS cache with system configuration pub async fn new() -> Result> { - let resolver = TokioAsyncResolver::tokio( - ResolverConfig::default(), - ResolverOpts::default(), - ); + let resolver = + TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default()); Ok(Self { resolver, @@ -42,10 +40,8 @@ impl DnsCache { /// Create a DNS cache with custom TTL pub async fn with_ttl(ttl: Duration) -> Result> { - let resolver = TokioAsyncResolver::tokio( - ResolverConfig::default(), - ResolverOpts::default(), - ); + let resolver = + TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default()); Ok(Self { resolver, @@ -127,4 +123,3 @@ mod tests { assert_eq!(cache.cache_size().await, 0); } } -