mithril_common/test_utils/
cardano_transactions_builder.rs1use crate::entities::{BlockNumber, BlockRange, CardanoTransaction, SlotNumber};
2
3pub struct CardanoTransactionsBuilder {
65 max_transactions_per_block: usize,
66 max_blocks_per_block_range: usize,
67}
68
69impl Default for CardanoTransactionsBuilder {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75impl CardanoTransactionsBuilder {
76 pub fn new() -> Self {
78 Self {
79 max_transactions_per_block: 1,
80 max_blocks_per_block_range: 1,
81 }
82 }
83
84 pub fn max_transactions_per_block(mut self, transactions_per_block: usize) -> Self {
86 self.max_transactions_per_block = transactions_per_block;
87 self
88 }
89
90 pub fn blocks_per_block_range(mut self, blocks_per_block_range: usize) -> Self {
93 if blocks_per_block_range > *BlockRange::LENGTH as usize {
94 panic!(
95 "blocks_per_block_range should be less than {}",
96 BlockRange::LENGTH
97 );
98 }
99 self.max_blocks_per_block_range = blocks_per_block_range;
100 self
101 }
102
103 pub fn build_transactions(self, transactions_count: usize) -> Vec<CardanoTransaction> {
105 let mut transactions = Vec::new();
106 let first_transaction_number = 100;
107 for tx_index in 0..transactions_count {
108 let block_number = self.block_number_from_transaction_index(tx_index);
109 let slot_number = SlotNumber(tx_index as u64 + first_transaction_number);
110 transactions.push(self.create_transaction(slot_number, block_number))
111 }
112
113 transactions
114 }
115
116 pub fn build_block_ranges(self, block_ranges_count: usize) -> Vec<CardanoTransaction> {
118 let nb_txs =
119 block_ranges_count * self.max_blocks_per_block_range * self.max_transactions_per_block;
120
121 self.build_transactions(nb_txs)
122 }
123
124 fn block_number_from_transaction_index(&self, tx_index: usize) -> BlockNumber {
125 let max_transactions_per_block_range =
126 self.max_transactions_per_block * self.max_blocks_per_block_range;
127 let index_block_range = tx_index / max_transactions_per_block_range;
128 let block_index_global = tx_index as u64 / self.max_transactions_per_block as u64;
129 let block_index_in_block_range =
130 block_index_global % self.max_blocks_per_block_range as u64;
131
132 index_block_range as u64 * BlockRange::LENGTH + block_index_in_block_range
133 }
134
135 fn create_transaction(
137 &self,
138 slot_number: SlotNumber,
139 block_number: BlockNumber,
140 ) -> CardanoTransaction {
141 CardanoTransaction::new(
142 format!("tx-hash-{}-{}", block_number, slot_number),
143 block_number,
144 slot_number,
145 format!("block-hash-{block_number}"),
146 )
147 }
148}
149
150#[cfg(test)]
151mod test {
152 use std::collections::{HashMap, HashSet};
153
154 use super::*;
155
156 fn count_distinct_values<T, R>(list: &[T], extract_value: &dyn Fn(&T) -> R) -> usize
157 where
158 R: Eq + std::hash::Hash,
159 {
160 list.iter().map(extract_value).collect::<HashSet<R>>().len()
161 }
162
163 fn group_by<'a, T, R>(list: &'a [T], extract_value: &dyn Fn(&T) -> R) -> HashMap<R, Vec<&'a T>>
164 where
165 R: Eq + std::hash::Hash,
166 {
167 let mut grouped_by_block = HashMap::new();
168 for t in list {
169 grouped_by_block
170 .entry(extract_value(t))
171 .or_insert(Vec::new())
172 .push(t);
173 }
174 grouped_by_block
175 }
176
177 #[test]
178 fn return_given_number_of_transactions_with_distinct_values() {
179 let txs = CardanoTransactionsBuilder::new().build_transactions(3);
180
181 assert_eq!(txs.len(), 3);
182
183 assert_eq!(
184 3,
185 count_distinct_values(&txs, &|t| t.transaction_hash.clone())
186 );
187 assert_eq!(3, count_distinct_values(&txs, &|t| t.block_number));
188 assert_eq!(3, count_distinct_values(&txs, &|t| t.slot_number));
189 assert_eq!(3, count_distinct_values(&txs, &|t| t.block_hash.clone()));
190 }
191
192 #[test]
193 fn return_all_transactions_in_same_block_when_ask_less_transactions_than_transactions_per_block(
194 ) {
195 let txs = CardanoTransactionsBuilder::new()
196 .max_transactions_per_block(10)
197 .build_transactions(3);
198
199 assert_eq!(txs.len(), 3);
200
201 assert_eq!(
202 3,
203 count_distinct_values(&txs, &|t| t.transaction_hash.clone())
204 );
205 assert_eq!(1, count_distinct_values(&txs, &|t| t.block_number));
206 assert_eq!(1, count_distinct_values(&txs, &|t| t.block_hash.clone()));
207 }
208
209 #[test]
210 fn return_no_more_transactions_in_a_same_block_than_number_per_block_requested() {
211 let txs = CardanoTransactionsBuilder::new()
212 .max_transactions_per_block(3)
213 .build_transactions(12);
214
215 assert_eq!(txs.len(), 12);
216
217 assert_eq!(
218 12,
219 count_distinct_values(&txs, &|t| t.transaction_hash.clone())
220 );
221 assert_eq!(4, count_distinct_values(&txs, &|t| t.block_number));
222 assert_eq!(4, count_distinct_values(&txs, &|t| t.block_hash.clone()));
223 }
224
225 #[test]
226 fn only_the_last_block_is_not_full_when_we_can_not_fill_all_blocks() {
227 let txs = CardanoTransactionsBuilder::new()
228 .max_transactions_per_block(5)
229 .build_transactions(12);
230
231 assert_eq!(txs.len(), 12);
232
233 assert_eq!(
234 12,
235 count_distinct_values(&txs, &|t| t.transaction_hash.clone())
236 );
237 assert_eq!(3, count_distinct_values(&txs, &|t| t.block_number));
238
239 let grouped_by_block = group_by(&txs, &|t| t.block_number);
240 let mut txs_per_block: Vec<_> = grouped_by_block.values().map(|v| v.len()).collect();
241 txs_per_block.sort();
242 assert_eq!(vec![2, 5, 5], txs_per_block);
243 }
244
245 #[test]
246 fn generate_one_block_range_return_one_transaction_by_default() {
247 let txs = CardanoTransactionsBuilder::new().build_block_ranges(1);
248 assert_eq!(txs.len(), 1);
249 }
250
251 #[test]
252 fn build_block_ranges_return_the_number_of_block_ranges_requested() {
253 let block_ranges = 3;
254 let txs = CardanoTransactionsBuilder::new().build_block_ranges(block_ranges);
255
256 assert_eq!(txs.len(), 3);
257
258 assert_eq!(
259 3,
260 count_distinct_values(&txs, &|t| BlockRange::start(t.block_number))
261 );
262 }
263
264 #[test]
265 fn build_block_ranges_return_many_transactions_per_block_when_requested() {
266 let txs = CardanoTransactionsBuilder::new()
267 .max_transactions_per_block(5)
268 .build_block_ranges(3);
269
270 assert_eq!(txs.len(), 3 * 5);
271
272 assert_eq!(
273 3 * 5,
274 count_distinct_values(&txs, &|t| t.transaction_hash.clone())
275 );
276
277 assert_eq!(
278 3,
279 count_distinct_values(&txs, &|t| BlockRange::start(t.block_number))
280 );
281 assert_eq!(3, count_distinct_values(&txs, &|t| t.block_number));
282 assert_eq!(3, count_distinct_values(&txs, &|t| t.block_hash.clone()));
283 }
284
285 #[test]
286 fn build_block_ranges_with_many_blocks_per_block_ranges() {
287 let txs = CardanoTransactionsBuilder::new()
288 .max_transactions_per_block(5)
289 .blocks_per_block_range(2)
290 .build_block_ranges(3);
291
292 assert_eq!(txs.len(), 3 * 2 * 5);
293
294 assert_eq!(
295 3 * 2 * 5,
296 count_distinct_values(&txs, &|t| t.transaction_hash.clone())
297 );
298
299 assert_eq!(
300 3,
301 count_distinct_values(&txs, &|t| BlockRange::start(t.block_number))
302 );
303 assert_eq!(3 * 2, count_distinct_values(&txs, &|t| t.block_number));
304 assert_eq!(
305 3 * 2,
306 count_distinct_values(&txs, &|t| t.block_hash.clone())
307 );
308 }
309
310 #[test]
311 fn build_transactions_with_many_blocks_per_block_ranges() {
312 let txs = CardanoTransactionsBuilder::new()
313 .max_transactions_per_block(5)
314 .blocks_per_block_range(2)
315 .build_transactions(18);
316
317 assert_eq!(txs.len(), 18);
323
324 assert_eq!(
325 18,
326 count_distinct_values(&txs, &|t| t.transaction_hash.clone())
327 );
328
329 assert_eq!(
330 2,
331 count_distinct_values(&txs, &|t| BlockRange::start(t.block_number))
332 );
333 assert_eq!(4, count_distinct_values(&txs, &|t| t.block_number));
334 assert_eq!(4, count_distinct_values(&txs, &|t| t.block_hash.clone()));
335 }
336
337 #[test]
338 #[should_panic]
339 fn should_panic_when_too_many_blocks_per_block_range() {
340 CardanoTransactionsBuilder::new().blocks_per_block_range(*BlockRange::LENGTH as usize + 1);
341 }
342}