mithril_common/test/builder/
cardano_transactions_builder.rs

1use crate::entities::{BlockNumber, BlockRange, CardanoTransaction, SlotNumber};
2
3/// Builder to easily build transactions with consistent values.
4///
5/// Note: the names generated for the transaction hashes and block hashes are not meaningful.
6///
7/// # Example 'build_transactions'
8///
9/// ```
10///     use mithril_common::entities::{BlockNumber, CardanoTransaction, SlotNumber};
11///     use mithril_common::test::builder::CardanoTransactionsBuilder;
12///
13///     let txs = CardanoTransactionsBuilder::new()
14///         .max_transactions_per_block(3)
15///         .blocks_per_block_range(2)
16///         .build_transactions(8);
17///
18///     assert_eq!(8, txs.len());
19///     assert_eq!(
20///         vec![
21///             CardanoTransaction::new("tx-hash-0-100", BlockNumber(0), SlotNumber(100), "block-hash-0"),
22///             CardanoTransaction::new("tx-hash-0-101", BlockNumber(0), SlotNumber(101), "block-hash-0"),
23///             CardanoTransaction::new("tx-hash-0-102", BlockNumber(0), SlotNumber(102), "block-hash-0"),
24///             CardanoTransaction::new("tx-hash-1-103", BlockNumber(1), SlotNumber(103), "block-hash-1"),
25///             CardanoTransaction::new("tx-hash-1-104", BlockNumber(1), SlotNumber(104), "block-hash-1"),
26///             CardanoTransaction::new("tx-hash-1-105", BlockNumber(1), SlotNumber(105), "block-hash-1"),
27///             CardanoTransaction::new("tx-hash-15-106", BlockNumber(15), SlotNumber(106), "block-hash-15"),
28///             CardanoTransaction::new("tx-hash-15-107", BlockNumber(15), SlotNumber(107), "block-hash-15")
29///         ],
30///         txs
31///     );
32/// ```
33///
34/// # Example 'build_block_ranges'
35///
36/// ```
37///     use mithril_common::entities::{BlockNumber, CardanoTransaction, SlotNumber};
38///     use mithril_common::test::builder::CardanoTransactionsBuilder;
39///
40///     let txs = CardanoTransactionsBuilder::new()
41///         .max_transactions_per_block(3)
42///         .blocks_per_block_range(2)
43///         .build_block_ranges(2);
44///
45///     assert_eq!(3 * 2 * 2, txs.len());
46///     assert_eq!(
47///         vec![
48///             CardanoTransaction::new("tx-hash-0-100", BlockNumber(0), SlotNumber(100), "block-hash-0"),
49///             CardanoTransaction::new("tx-hash-0-101", BlockNumber(0), SlotNumber(101), "block-hash-0"),
50///             CardanoTransaction::new("tx-hash-0-102", BlockNumber(0), SlotNumber(102), "block-hash-0"),
51///             CardanoTransaction::new("tx-hash-1-103", BlockNumber(1), SlotNumber(103), "block-hash-1"),
52///             CardanoTransaction::new("tx-hash-1-104", BlockNumber(1), SlotNumber(104), "block-hash-1"),
53///             CardanoTransaction::new("tx-hash-1-105", BlockNumber(1), SlotNumber(105), "block-hash-1"),
54///             CardanoTransaction::new("tx-hash-15-106", BlockNumber(15), SlotNumber(106), "block-hash-15"),
55///             CardanoTransaction::new("tx-hash-15-107", BlockNumber(15), SlotNumber(107), "block-hash-15"),
56///             CardanoTransaction::new("tx-hash-15-108", BlockNumber(15), SlotNumber(108), "block-hash-15"),
57///             CardanoTransaction::new("tx-hash-16-109", BlockNumber(16), SlotNumber(109), "block-hash-16"),
58///             CardanoTransaction::new("tx-hash-16-110", BlockNumber(16), SlotNumber(110), "block-hash-16"),
59///             CardanoTransaction::new("tx-hash-16-111", BlockNumber(16), SlotNumber(111), "block-hash-16"),
60///         ],
61///         txs
62///     );
63/// ```
64pub 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    /// [CardanoTransactionsBuilder] constructor.
77    pub fn new() -> Self {
78        Self {
79            max_transactions_per_block: 1,
80            max_blocks_per_block_range: 1,
81        }
82    }
83
84    /// Define how many transactions we generate in each block.
85    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    /// Define how many blocks we generate in each block_range.
91    /// If we set too many blocks for a block_range, this function panic.
92    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    /// Build the number of transactions requested.
104    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    /// Build a list of transactions to get the number of block range requested.
117    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    /// Create a transaction with a given index and block number.
136    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        // block range 1 - block 0  - 1, 2, 3, 4, 5
315        // block range 1 - block 1  - 6, 7, 8, 7, 10
316        // block range 2 - block 15 - 11, 12, 13, 14, 15
317        // block range 2 - block 16 - 16, 17, 18
318
319        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}