mithril_common/test/builder/
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.entry(extract_value(t)).or_insert(Vec::new()).push(t);
170 }
171 grouped_by_block
172 }
173
174 #[test]
175 fn return_given_number_of_transactions_with_distinct_values() {
176 let txs = CardanoTransactionsBuilder::new().build_transactions(3);
177
178 assert_eq!(txs.len(), 3);
179
180 assert_eq!(
181 3,
182 count_distinct_values(&txs, &|t| t.transaction_hash.clone())
183 );
184 assert_eq!(3, count_distinct_values(&txs, &|t| t.block_number));
185 assert_eq!(3, count_distinct_values(&txs, &|t| t.slot_number));
186 assert_eq!(3, count_distinct_values(&txs, &|t| t.block_hash.clone()));
187 }
188
189 #[test]
190 fn return_all_transactions_in_same_block_when_ask_less_transactions_than_transactions_per_block()
191 {
192 let txs = CardanoTransactionsBuilder::new()
193 .max_transactions_per_block(10)
194 .build_transactions(3);
195
196 assert_eq!(txs.len(), 3);
197
198 assert_eq!(
199 3,
200 count_distinct_values(&txs, &|t| t.transaction_hash.clone())
201 );
202 assert_eq!(1, count_distinct_values(&txs, &|t| t.block_number));
203 assert_eq!(1, count_distinct_values(&txs, &|t| t.block_hash.clone()));
204 }
205
206 #[test]
207 fn return_no_more_transactions_in_a_same_block_than_number_per_block_requested() {
208 let txs = CardanoTransactionsBuilder::new()
209 .max_transactions_per_block(3)
210 .build_transactions(12);
211
212 assert_eq!(txs.len(), 12);
213
214 assert_eq!(
215 12,
216 count_distinct_values(&txs, &|t| t.transaction_hash.clone())
217 );
218 assert_eq!(4, count_distinct_values(&txs, &|t| t.block_number));
219 assert_eq!(4, count_distinct_values(&txs, &|t| t.block_hash.clone()));
220 }
221
222 #[test]
223 fn only_the_last_block_is_not_full_when_we_can_not_fill_all_blocks() {
224 let txs = CardanoTransactionsBuilder::new()
225 .max_transactions_per_block(5)
226 .build_transactions(12);
227
228 assert_eq!(txs.len(), 12);
229
230 assert_eq!(
231 12,
232 count_distinct_values(&txs, &|t| t.transaction_hash.clone())
233 );
234 assert_eq!(3, count_distinct_values(&txs, &|t| t.block_number));
235
236 let grouped_by_block = group_by(&txs, &|t| t.block_number);
237 let mut txs_per_block: Vec<_> = grouped_by_block.values().map(|v| v.len()).collect();
238 txs_per_block.sort();
239 assert_eq!(vec![2, 5, 5], txs_per_block);
240 }
241
242 #[test]
243 fn generate_one_block_range_return_one_transaction_by_default() {
244 let txs = CardanoTransactionsBuilder::new().build_block_ranges(1);
245 assert_eq!(txs.len(), 1);
246 }
247
248 #[test]
249 fn build_block_ranges_return_the_number_of_block_ranges_requested() {
250 let block_ranges = 3;
251 let txs = CardanoTransactionsBuilder::new().build_block_ranges(block_ranges);
252
253 assert_eq!(txs.len(), 3);
254
255 assert_eq!(
256 3,
257 count_distinct_values(&txs, &|t| BlockRange::start(t.block_number))
258 );
259 }
260
261 #[test]
262 fn build_block_ranges_return_many_transactions_per_block_when_requested() {
263 let txs = CardanoTransactionsBuilder::new()
264 .max_transactions_per_block(5)
265 .build_block_ranges(3);
266
267 assert_eq!(txs.len(), 3 * 5);
268
269 assert_eq!(
270 3 * 5,
271 count_distinct_values(&txs, &|t| t.transaction_hash.clone())
272 );
273
274 assert_eq!(
275 3,
276 count_distinct_values(&txs, &|t| BlockRange::start(t.block_number))
277 );
278 assert_eq!(3, count_distinct_values(&txs, &|t| t.block_number));
279 assert_eq!(3, count_distinct_values(&txs, &|t| t.block_hash.clone()));
280 }
281
282 #[test]
283 fn build_block_ranges_with_many_blocks_per_block_ranges() {
284 let txs = CardanoTransactionsBuilder::new()
285 .max_transactions_per_block(5)
286 .blocks_per_block_range(2)
287 .build_block_ranges(3);
288
289 assert_eq!(txs.len(), 3 * 2 * 5);
290
291 assert_eq!(
292 3 * 2 * 5,
293 count_distinct_values(&txs, &|t| t.transaction_hash.clone())
294 );
295
296 assert_eq!(
297 3,
298 count_distinct_values(&txs, &|t| BlockRange::start(t.block_number))
299 );
300 assert_eq!(3 * 2, count_distinct_values(&txs, &|t| t.block_number));
301 assert_eq!(
302 3 * 2,
303 count_distinct_values(&txs, &|t| t.block_hash.clone())
304 );
305 }
306
307 #[test]
308 fn build_transactions_with_many_blocks_per_block_ranges() {
309 let txs = CardanoTransactionsBuilder::new()
310 .max_transactions_per_block(5)
311 .blocks_per_block_range(2)
312 .build_transactions(18);
313
314 assert_eq!(txs.len(), 18);
320
321 assert_eq!(
322 18,
323 count_distinct_values(&txs, &|t| t.transaction_hash.clone())
324 );
325
326 assert_eq!(
327 2,
328 count_distinct_values(&txs, &|t| BlockRange::start(t.block_number))
329 );
330 assert_eq!(4, count_distinct_values(&txs, &|t| t.block_number));
331 assert_eq!(4, count_distinct_values(&txs, &|t| t.block_hash.clone()));
332 }
333
334 #[test]
335 #[should_panic]
336 fn should_panic_when_too_many_blocks_per_block_range() {
337 CardanoTransactionsBuilder::new().blocks_per_block_range(*BlockRange::LENGTH as usize + 1);
338 }
339}