fix: CI Clippy warnings

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