anya_core/testing/performance/
transaction.rs

1/// Transaction performance testing
2use crate::testing::performance::{
3    MetricType, PerformanceTestable, Result, TestConfig, TestResult, Timer,
4};
5use chrono;
6use rand::{thread_rng, Rng};
7use std::collections::HashMap;
8use std::time::Instant;
9
10/// Transaction test configuration
11#[derive(Debug, Clone)]
12pub struct TransactionTestConfig {
13    /// Number of transactions to test
14    pub transaction_count: usize,
15
16    /// Number of inputs per transaction
17    pub inputs_per_tx: usize,
18
19    /// Number of outputs per transaction
20    pub outputs_per_tx: usize,
21
22    /// Transaction value range (min, max)
23    pub value_range: (u64, u64),
24
25    /// Include signature verification
26    pub verify_signatures: bool,
27}
28
29impl Default for TransactionTestConfig {
30    fn default() -> Self {
31        Self {
32            transaction_count: 1000,
33            inputs_per_tx: 2,
34            outputs_per_tx: 2,
35            value_range: (1000, 1000000),
36            verify_signatures: true,
37        }
38    }
39}
40
41/// Mock transaction for testing
42#[derive(Debug, Clone)]
43pub struct MockTransaction {
44    /// Transaction ID
45    pub id: String,
46
47    /// Input count
48    pub inputs: Vec<MockInput>,
49
50    /// Output count
51    pub outputs: Vec<MockOutput>,
52
53    /// Transaction fee
54    pub fee: u64,
55
56    /// Timestamp
57    pub timestamp: u64,
58}
59
60/// Mock transaction input
61#[derive(Debug, Clone)]
62pub struct MockInput {
63    /// Previous transaction ID
64    pub prev_tx_id: String,
65
66    /// Output index
67    pub output_index: u32,
68
69    /// Script signature
70    pub script_sig: Vec<u8>,
71}
72
73/// Mock transaction output
74#[derive(Debug, Clone)]
75pub struct MockOutput {
76    /// Output value
77    pub value: u64,
78
79    /// Script public key
80    pub script_pubkey: Vec<u8>,
81}
82
83/// Transaction performance test
84pub struct TransactionPerformanceTest {
85    /// Test configuration
86    config: TransactionTestConfig,
87
88    /// Generated transactions
89    transactions: Vec<MockTransaction>,
90}
91
92impl TransactionPerformanceTest {
93    /// Create a new transaction performance test
94    pub fn new(config: TransactionTestConfig) -> Self {
95        let mut test = Self {
96            config,
97            transactions: Vec::new(),
98        };
99        test.generate_transactions();
100        test
101    }
102
103    /// Generate test transactions
104    fn generate_transactions(&mut self) {
105        let mut rng = thread_rng();
106
107        for i in 0..self.config.transaction_count {
108            let inputs = (0..self.config.inputs_per_tx)
109                .map(|j| MockInput {
110                    prev_tx_id: format!("prev_tx_{i}_{j}"),
111                    output_index: rng.gen_range(0..10),
112                    script_sig: vec![0u8; rng.gen_range(50..200)],
113                })
114                .collect();
115
116            let outputs = (0..self.config.outputs_per_tx)
117                .map(|_| MockOutput {
118                    value: rng.gen_range(self.config.value_range.0..=self.config.value_range.1),
119                    script_pubkey: vec![0u8; rng.gen_range(20..100)],
120                })
121                .collect();
122
123            let transaction = MockTransaction {
124                id: format!("tx_{i}"),
125                inputs,
126                outputs,
127                fee: rng.gen_range(100..10000),
128                timestamp: Instant::now().elapsed().as_secs(),
129            };
130
131            self.transactions.push(transaction);
132        }
133    }
134
135    /// Validate a transaction (simplified validation)
136    fn validate_transaction(&self, tx: &MockTransaction) -> bool {
137        // Basic validation checks
138        if tx.inputs.is_empty() || tx.outputs.is_empty() {
139            return false;
140        }
141
142        // Check that total outputs don't exceed total inputs (simplified)
143        let total_output: u64 = tx.outputs.iter().map(|o| o.value).sum();
144        let _assumed_input_value = total_output + tx.fee; // Simplified assumption
145
146        // Basic range checks
147        if tx.fee > 100000 {
148            // Max reasonable fee
149            return false;
150        }
151
152        if self.config.verify_signatures {
153            // Simplified signature verification (just check script size)
154            for input in &tx.inputs {
155                if input.script_sig.is_empty() {
156                    return false;
157                }
158            }
159        }
160
161        true
162    }
163
164    /// Process transactions and measure performance
165    fn process_transactions(&self) -> (usize, usize) {
166        let mut valid_count = 0;
167        let mut invalid_count = 0;
168
169        for tx in &self.transactions {
170            if self.validate_transaction(tx) {
171                valid_count += 1;
172            } else {
173                invalid_count += 1;
174            }
175        }
176
177        (valid_count, invalid_count)
178    }
179}
180
181impl PerformanceTestable for TransactionPerformanceTest {
182    fn run_test(&self, test_config: &TestConfig) -> Result<TestResult> {
183        let mut timer = Timer::new();
184        let mut metrics = HashMap::new();
185
186        // Start timing
187        timer.start();
188
189        // Run the actual test
190        let (valid_count, invalid_count) = self.process_transactions();
191
192        // Stop timing
193        timer.stop();
194        let duration_ms = timer.elapsed_ms()?;
195        let duration_secs = timer.elapsed_secs()?;
196
197        // Calculate metrics
198        let total_transactions = valid_count + invalid_count;
199        let throughput = total_transactions as f64 / duration_secs;
200
201        metrics.insert("total_transactions".to_string(), total_transactions as f64);
202        metrics.insert("valid_transactions".to_string(), valid_count as f64);
203        metrics.insert("invalid_transactions".to_string(), invalid_count as f64);
204        metrics.insert("throughput_tx_per_sec".to_string(), throughput);
205        metrics.insert(
206            "avg_processing_time_ms".to_string(),
207            (duration_ms as f64) / total_transactions as f64,
208        );
209
210        Ok(TestResult {
211            name: test_config.name.clone(),
212            timestamp: chrono::Utc::now().to_rfc3339(),
213            duration_ms,
214            metrics,
215            metric_types: {
216                let mut metric_types = HashMap::new();
217                metric_types.insert("throughput_tx_per_sec".to_string(), MetricType::TPS);
218                metric_types.insert("avg_processing_time_ms".to_string(), MetricType::LatencyMs);
219                metric_types
220            },
221            parameters: test_config.parameters.clone(),
222        })
223    }
224
225    fn name(&self) -> &str {
226        "Transaction Performance Test"
227    }
228}
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233
234    #[test]
235    fn test_transaction_creation() {
236        let config = TransactionTestConfig {
237            transaction_count: 10,
238            ..Default::default()
239        };
240
241        let test = TransactionPerformanceTest::new(config);
242        assert_eq!(test.transactions.len(), 10);
243    }
244
245    #[test]
246    fn test_transaction_validation() {
247        let config = TransactionTestConfig::default();
248        let test = TransactionPerformanceTest::new(config);
249
250        // Test with a valid transaction
251        if let Some(tx) = test.transactions.first() {
252            assert!(test.validate_transaction(tx));
253        }
254    }
255
256    #[test]
257    fn test_performance_test_run() {
258        let config = TransactionTestConfig {
259            transaction_count: 100,
260            ..Default::default()
261        };
262        let test = TransactionPerformanceTest::new(config);
263
264        let test_config = TestConfig {
265            name: "Test Transaction Performance".to_string(),
266            iterations: 1,
267            warmup_iterations: 0,
268            duration_limit_secs: 60,
269            parameters: HashMap::new(),
270        };
271
272        let result = test.run_test(&test_config);
273        assert!(result.is_ok());
274
275        let result = result.unwrap();
276        // Check that the test result has valid data
277        assert!(!result.name.is_empty());
278        assert!(result.metrics.contains_key("throughput_tx_per_sec"));
279    }
280}