use std::error::Error;
use crate::testing::performance::{
PerformanceTestable, TestConfig, TestResult, PerfTestError, Result, Timer, MetricType
};
use bitcoin::{Transaction, Network};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;
use rand::{thread_rng, Rng};
#[derive(Debug, Clone)]
pub struct TxGenConfig {
pub inputs_per_tx: usize,
pub outputs_per_tx: usize,
pub use_taproot: bool,
pub network: Network,
pub multithreaded: bool,
pub thread_count: usize,
}
impl Default for TxGenConfig {
fn default() -> Self {
Self {
inputs_per_tx: 2,
outputs_per_tx: 2,
use_taproot: true,
network: Network::Testnet,
multithreaded: false,
thread_count: 4,
}
}
}
pub struct TransactionThroughputTest {
tx_gen_config: TxGenConfig,
}
impl TransactionThroughputTest {
pub fn new(tx_gen_config: TxGenConfig) -> Self {
Self {
tx_gen_config,
}
}
fn generate_transaction(&self) -> Result<Transaction> {
let tx = Transaction {
version: bitcoin::transaction::Version(2),
lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![],
output: vec![],
};
Ok(tx)
}
fn run_single_threaded_test(&self, config: &TestConfig) -> Result<TestResult> {
let iterations = config.iterations;
let warmup_iterations = config.warmup_iterations;
let mut parameters = HashMap::new();
parameters.insert("inputs_per_tx".to_string(), self.tx_gen_config.inputs_per_tx.to_string());
parameters.insert("outputs_per_tx".to_string(), self.tx_gen_config.outputs_per_tx.to_string());
parameters.insert("use_taproot".to_string(), self.tx_gen_config.use_taproot.to_string());
parameters.insert("network".to_string(), format!("{:?}", self.tx_gen_config.network));
parameters.insert("multithreaded".to_string(), "false".to_string());
println!("Warming up for {} iterations...", warmup_iterations);
for _ in 0..warmup_iterations {
let tx = self.generate_transaction()?;
let _ = self.validator.validate_taproot_transaction(&tx);
}
println!("Running test for {} iterations...", iterations);
let mut timer = Timer::new();
timer.start();
for i in 0..iterations {
if i % 100 == 0 {
println!("Progress: {}/{}", i, iterations);
}
let tx = self.generate_transaction()?;
let _ = self.validator.validate_taproot_transaction(&tx);
}
timer.stop();
let duration_ms = timer.elapsed_ms()?;
let transactions_per_second = (iterations as f64) / (duration_ms as f64 / 1000.0);
let mut metrics = HashMap::new();
metrics.insert("transactions_per_second".to_string(), transactions_per_second);
let mut metric_types = HashMap::new();
metric_types.insert("transactions_per_second".to_string(), MetricType::TPS);
Ok(TestResult {
name: format!("{}_single_threaded", self.name()),
timestamp: chrono::Utc::now().to_rfc3339(),
duration_ms,
metrics,
metric_types,
parameters,
})
}
fn run_multi_threaded_test(&self, config: &TestConfig) -> Result<TestResult> {
let iterations = config.iterations;
let warmup_iterations = config.warmup_iterations;
let thread_count = self.tx_gen_config.thread_count;
let mut parameters = HashMap::new();
parameters.insert("inputs_per_tx".to_string(), self.tx_gen_config.inputs_per_tx.to_string());
parameters.insert("outputs_per_tx".to_string(), self.tx_gen_config.outputs_per_tx.to_string());
parameters.insert("use_taproot".to_string(), self.tx_gen_config.use_taproot.to_string());
parameters.insert("network".to_string(), format!("{:?}", self.tx_gen_config.network));
parameters.insert("multithreaded".to_string(), "true".to_string());
parameters.insert("thread_count".to_string(), thread_count.to_string());
println!("Warming up for {} iterations...", warmup_iterations);
for _ in 0..warmup_iterations {
let tx = self.generate_transaction()?;
let _ = self.validator.validate_taproot_transaction(&tx);
}
println!("Running test for {} iterations with {} threads...", iterations, thread_count);
let iterations_per_thread = iterations / thread_count;
let mut timer = Timer::new();
timer.start();
let counter = Arc::new(Mutex::new(0));
let mut handles = Vec::new();
for _ in 0..thread_count {
let counter = Arc::clone(&counter);
let tx_gen_config = self.tx_gen_config.clone();
let handle = thread::spawn(move || -> std::result::Result<(), String> {
let validator = TransactionValidator::new();
let test = TransactionThroughputTest::new(validator, tx_gen_config);
for _ in 0..iterations_per_thread {
match test.generate_transaction() {
Ok(tx) => {
let _ = test.validator.validate_taproot_transaction(&tx);
let mut counter = counter.lock().map_err(|e| format!("Mutex lock error: {}", e))?;
*counter += 1;
if *counter % 100 == 0 {
println!("Progress: {}/{}", *counter, iterations);
}
}
Err(_) => {
}
}
}
Ok(())
});
handles.push(handle);
}
for handle in handles {
let _ = handle.join();
}
timer.stop();
let duration_ms = timer.elapsed_ms()?;
let transactions_per_second = (iterations as f64) / (duration_ms as f64 / 1000.0);
let mut metrics = HashMap::new();
metrics.insert("transactions_per_second".to_string(), transactions_per_second);
let mut metric_types = HashMap::new();
metric_types.insert("transactions_per_second".to_string(), MetricType::TPS);
Ok(TestResult {
name: format!("{}_multi_threaded", self.name()),
timestamp: chrono::Utc::now().to_rfc3339(),
duration_ms,
metrics,
metric_types,
parameters,
})
}
}
impl PerformanceTestable for TransactionThroughputTest {
fn run_test(&self, config: &TestConfig) -> Result<TestResult> {
if self.tx_gen_config.multithreaded {
self.run_multi_threaded_test(config)
} else {
self.run_single_threaded_test(config)
}
}
fn name(&self) -> &str {
"transaction_throughput"
}
}