anya_core/testing/performance/
transaction.rs1use 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#[derive(Debug, Clone)]
12pub struct TransactionTestConfig {
13 pub transaction_count: usize,
15
16 pub inputs_per_tx: usize,
18
19 pub outputs_per_tx: usize,
21
22 pub value_range: (u64, u64),
24
25 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#[derive(Debug, Clone)]
43pub struct MockTransaction {
44 pub id: String,
46
47 pub inputs: Vec<MockInput>,
49
50 pub outputs: Vec<MockOutput>,
52
53 pub fee: u64,
55
56 pub timestamp: u64,
58}
59
60#[derive(Debug, Clone)]
62pub struct MockInput {
63 pub prev_tx_id: String,
65
66 pub output_index: u32,
68
69 pub script_sig: Vec<u8>,
71}
72
73#[derive(Debug, Clone)]
75pub struct MockOutput {
76 pub value: u64,
78
79 pub script_pubkey: Vec<u8>,
81}
82
83pub struct TransactionPerformanceTest {
85 config: TransactionTestConfig,
87
88 transactions: Vec<MockTransaction>,
90}
91
92impl TransactionPerformanceTest {
93 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 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 fn validate_transaction(&self, tx: &MockTransaction) -> bool {
137 if tx.inputs.is_empty() || tx.outputs.is_empty() {
139 return false;
140 }
141
142 let total_output: u64 = tx.outputs.iter().map(|o| o.value).sum();
144 let _assumed_input_value = total_output + tx.fee; if tx.fee > 100000 {
148 return false;
150 }
151
152 if self.config.verify_signatures {
153 for input in &tx.inputs {
155 if input.script_sig.is_empty() {
156 return false;
157 }
158 }
159 }
160
161 true
162 }
163
164 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 timer.start();
188
189 let (valid_count, invalid_count) = self.process_transactions();
191
192 timer.stop();
194 let duration_ms = timer.elapsed_ms()?;
195 let duration_secs = timer.elapsed_secs()?;
196
197 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 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 assert!(!result.name.is_empty());
278 assert!(result.metrics.contains_key("throughput_tx_per_sec"));
279 }
280}