Skip to content

Commit 66a6bfd

Browse files
committed
Create CardanoTransactionsBuilder for tests
1 parent 1ef4fb7 commit 66a6bfd

File tree

2 files changed

+373
-0
lines changed

2 files changed

+373
-0
lines changed
Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
use std::cmp::max;
2+
3+
use crate::entities::{BlockRange, CardanoTransaction};
4+
5+
/// Builder to easily build transactions with consistent values.
6+
pub struct CardanoTransactionsBuilder {
7+
max_transactions_per_block: usize,
8+
max_blocks_per_block_range: usize,
9+
}
10+
11+
impl CardanoTransactionsBuilder {
12+
pub fn new() -> Self {
13+
Self {
14+
max_transactions_per_block: 1,
15+
max_blocks_per_block_range: 1,
16+
}
17+
}
18+
19+
/// Define how many transactions we generate in each block.
20+
pub fn per_block(mut self, transactions_per_block: usize) -> Self {
21+
self.max_transactions_per_block = transactions_per_block;
22+
self
23+
}
24+
25+
/// Define how many blocks we generate in each block_range.
26+
/// If we set too many blocks for a block_range, this function panic.
27+
pub fn blocks_per_block_range(mut self, blocks_per_block_range: usize) -> Self {
28+
if blocks_per_block_range > BlockRange::LENGTH as usize {
29+
panic!(
30+
"blocks_per_block_range should be less than {}",
31+
BlockRange::LENGTH
32+
);
33+
}
34+
self.max_blocks_per_block_range = blocks_per_block_range;
35+
self
36+
}
37+
38+
/// TODO Do we keep this function ? If yes, ido we keep it public ?
39+
fn print_transactions(txs: &Vec<CardanoTransaction>) {
40+
for tx in txs {
41+
println!("{:?}", tx);
42+
}
43+
}
44+
45+
/// Default build that build only one transaction.
46+
pub fn build(self) -> Vec<CardanoTransaction> {
47+
self.build_transactions(1)
48+
}
49+
50+
/// Build the number of transactions requested.
51+
pub fn build_transactions(self, transactions_count: usize) -> Vec<CardanoTransaction> {
52+
let mut transactions = Vec::new();
53+
let first_transaction_number = 100;
54+
for tx_index in 0..transactions_count {
55+
let block_number = self.block_number_from_transaction_index(tx_index);
56+
transactions
57+
.push(self.create_transaction(tx_index + first_transaction_number, block_number))
58+
}
59+
60+
transactions
61+
}
62+
63+
/// Build a list of transactions to get the number of block range requested.
64+
pub fn build_block_ranges(self, block_ranges_count: usize) -> Vec<CardanoTransaction> {
65+
let nb_txs =
66+
block_ranges_count * self.max_blocks_per_block_range * self.max_transactions_per_block;
67+
68+
self.build_transactions(nb_txs)
69+
}
70+
71+
fn block_number_from_transaction_index(&self, tx_index: usize) -> u64 {
72+
let max_transactions_per_block_range =
73+
self.max_transactions_per_block * self.max_blocks_per_block_range;
74+
let index_block_range = tx_index / max_transactions_per_block_range;
75+
let block_index_global = tx_index as u64 / self.max_transactions_per_block as u64;
76+
let block_index_in_block_range =
77+
block_index_global % self.max_blocks_per_block_range as u64;
78+
79+
index_block_range as u64 * BlockRange::LENGTH + block_index_in_block_range
80+
}
81+
82+
/// Create a transaction with a given index and block number.
83+
fn create_transaction(
84+
&self,
85+
transaction_index: usize,
86+
block_number: u64,
87+
) -> CardanoTransaction {
88+
CardanoTransaction::new(
89+
format!("tx-hash-{}-{}", block_number, transaction_index),
90+
block_number,
91+
transaction_index as u64,
92+
format!("block-hash-{block_number}"),
93+
transaction_index as u64,
94+
)
95+
}
96+
97+
// TODO to remove when builder is finished
98+
pub fn generate_transactions_with_block_ranges(
99+
total_block_ranges: usize,
100+
total_transactions_per_block_range: usize,
101+
) -> Vec<CardanoTransaction> {
102+
let block_range_length = BlockRange::LENGTH as usize;
103+
let max_transaction_per_block_number =
104+
max(1, total_transactions_per_block_range / block_range_length);
105+
let mut transactions = vec![];
106+
107+
for i in 0..total_block_ranges {
108+
let block_range = BlockRange::from_block_number((i * block_range_length) as u64);
109+
for j in 0..total_transactions_per_block_range {
110+
let transaction_index = i * total_transactions_per_block_range + j;
111+
let block_number =
112+
block_range.start + (j / max_transaction_per_block_number) as u64;
113+
let slot_number = 100 * block_number;
114+
let immutable_file_number = block_number / 5;
115+
let tx_hash = format!(
116+
"tx-br-{}..{}-{}-idx-{}",
117+
block_range.start, block_range.end, j, transaction_index
118+
);
119+
let block_hash = format!("block_hash-{block_number}");
120+
transactions.push(CardanoTransaction::new(
121+
&tx_hash,
122+
block_number,
123+
slot_number,
124+
block_hash,
125+
immutable_file_number,
126+
));
127+
}
128+
}
129+
130+
transactions
131+
}
132+
}
133+
134+
#[cfg(test)]
135+
mod test {
136+
use std::collections::{HashMap, HashSet};
137+
138+
use super::*;
139+
140+
#[test]
141+
fn return_one_transaction_by_default() {
142+
let transactions = CardanoTransactionsBuilder::new().build();
143+
144+
assert_eq!(transactions.len(), 1);
145+
}
146+
147+
fn count_distinct_values<T, R>(list: &[T], extract_value: &dyn Fn(&T) -> R) -> usize
148+
where
149+
R: Eq + std::hash::Hash,
150+
{
151+
list.iter().map(extract_value).collect::<HashSet<R>>().len()
152+
}
153+
154+
fn group_by<'a, T, R>(list: &'a [T], extract_value: &dyn Fn(&T) -> R) -> HashMap<R, Vec<&'a T>>
155+
where
156+
R: Eq + std::hash::Hash,
157+
{
158+
let mut grouped_by_block = HashMap::new();
159+
for t in list {
160+
grouped_by_block
161+
.entry(extract_value(t))
162+
.or_insert(Vec::new())
163+
.push(t);
164+
}
165+
grouped_by_block
166+
}
167+
168+
#[test]
169+
fn generate_transactions_with_block_ranges_test() {
170+
let total_block_ranges = 3;
171+
let total_transactions_per_block_range = 10;
172+
let transactions = CardanoTransactionsBuilder::generate_transactions_with_block_ranges(
173+
total_block_ranges,
174+
total_transactions_per_block_range,
175+
);
176+
177+
CardanoTransactionsBuilder::print_transactions(&transactions);
178+
179+
println!("-------------------");
180+
181+
let transactions = CardanoTransactionsBuilder::new()
182+
.per_block(1)
183+
.blocks_per_block_range(total_transactions_per_block_range)
184+
.build_block_ranges(total_block_ranges);
185+
186+
CardanoTransactionsBuilder::print_transactions(&transactions);
187+
}
188+
189+
#[test]
190+
fn return_given_number_of_transactions_with_distinct_values() {
191+
let txs = CardanoTransactionsBuilder::new().build_transactions(3);
192+
193+
assert_eq!(txs.len(), 3);
194+
195+
assert_eq!(
196+
3,
197+
count_distinct_values(&txs, &|t| t.transaction_hash.clone())
198+
);
199+
assert_eq!(3, count_distinct_values(&txs, &|t| t.block_number));
200+
assert_eq!(3, count_distinct_values(&txs, &|t| t.slot_number));
201+
assert_eq!(3, count_distinct_values(&txs, &|t| t.block_hash.clone()));
202+
assert_eq!(3, count_distinct_values(&txs, &|t| t.immutable_file_number));
203+
}
204+
205+
#[test]
206+
fn return_all_transactions_in_same_block_when_ask_less_transactions_than_transactions_per_block(
207+
) {
208+
let txs = CardanoTransactionsBuilder::new()
209+
.per_block(10)
210+
.build_transactions(3);
211+
212+
assert_eq!(txs.len(), 3);
213+
214+
assert_eq!(
215+
3,
216+
count_distinct_values(&txs, &|t| t.transaction_hash.clone())
217+
);
218+
assert_eq!(1, count_distinct_values(&txs, &|t| t.block_number));
219+
assert_eq!(1, count_distinct_values(&txs, &|t| t.block_hash.clone()));
220+
}
221+
222+
#[test]
223+
fn return_no_more_transactions_in_a_same_block_than_number_per_block_requested() {
224+
let txs = CardanoTransactionsBuilder::new()
225+
.per_block(3)
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!(4, count_distinct_values(&txs, &|t| t.block_number));
235+
assert_eq!(4, count_distinct_values(&txs, &|t| t.block_hash.clone()));
236+
}
237+
238+
#[test]
239+
fn only_the_last_block_is_not_full_when_we_can_not_fill_all_blocks() {
240+
let txs = CardanoTransactionsBuilder::new()
241+
.per_block(5)
242+
.build_transactions(12);
243+
244+
assert_eq!(txs.len(), 12);
245+
246+
assert_eq!(
247+
12,
248+
count_distinct_values(&txs, &|t| t.transaction_hash.clone())
249+
);
250+
assert_eq!(3, count_distinct_values(&txs, &|t| t.block_number));
251+
252+
let grouped_by_block = group_by(&txs, &|t| t.block_number);
253+
let mut txs_per_block: Vec<_> = grouped_by_block.values().map(|v| v.len()).collect();
254+
txs_per_block.sort();
255+
assert_eq!(vec![2, 5, 5], txs_per_block);
256+
}
257+
258+
// TODO to remove when builder is finished #[test]
259+
fn test_generate_transactions_with_block_ranges() {
260+
let total_block_ranges = 3;
261+
let total_transactions_per_block_range = 10;
262+
let transactions = CardanoTransactionsBuilder::generate_transactions_with_block_ranges(
263+
total_block_ranges,
264+
total_transactions_per_block_range,
265+
);
266+
267+
CardanoTransactionsBuilder::print_transactions(&transactions);
268+
}
269+
270+
#[test]
271+
fn generate_one_block_range_return_one_transaction_by_default() {
272+
let txs = CardanoTransactionsBuilder::new().build_block_ranges(1);
273+
assert_eq!(txs.len(), 1);
274+
}
275+
276+
fn extract_by<T, R>(list: &[T], extract_value: &dyn Fn(&T) -> R) -> Vec<R> {
277+
list.iter().map(extract_value).collect()
278+
}
279+
280+
#[test]
281+
fn build_block_ranges_return_the_number_of_block_ranges_requested() {
282+
let block_ranges = 3;
283+
let txs = CardanoTransactionsBuilder::new().build_block_ranges(block_ranges);
284+
285+
assert_eq!(txs.len(), 3);
286+
287+
assert_eq!(
288+
3,
289+
count_distinct_values(&txs, &|t| BlockRange::start(t.block_number))
290+
);
291+
}
292+
293+
#[test]
294+
fn build_block_ranges_return_many_transactions_per_block_when_requested() {
295+
let txs = CardanoTransactionsBuilder::new()
296+
.per_block(5)
297+
.build_block_ranges(3);
298+
299+
assert_eq!(txs.len(), 3 * 5);
300+
301+
assert_eq!(
302+
3 * 5,
303+
count_distinct_values(&txs, &|t| t.transaction_hash.clone())
304+
);
305+
306+
assert_eq!(
307+
3,
308+
count_distinct_values(&txs, &|t| BlockRange::start(t.block_number))
309+
);
310+
assert_eq!(3, count_distinct_values(&txs, &|t| t.block_number));
311+
assert_eq!(3, count_distinct_values(&txs, &|t| t.block_hash.clone()));
312+
}
313+
314+
#[test]
315+
fn build_block_ranges_with_many_blocks_per_block_ranges() {
316+
let txs = CardanoTransactionsBuilder::new()
317+
.per_block(5)
318+
.blocks_per_block_range(2)
319+
.build_block_ranges(3);
320+
321+
assert_eq!(txs.len(), 3 * 2 * 5);
322+
323+
assert_eq!(
324+
3 * 2 * 5,
325+
count_distinct_values(&txs, &|t| t.transaction_hash.clone())
326+
);
327+
328+
assert_eq!(
329+
3,
330+
count_distinct_values(&txs, &|t| BlockRange::start(t.block_number))
331+
);
332+
assert_eq!(3 * 2, count_distinct_values(&txs, &|t| t.block_number));
333+
assert_eq!(
334+
3 * 2,
335+
count_distinct_values(&txs, &|t| t.block_hash.clone())
336+
);
337+
}
338+
339+
#[test]
340+
fn build_transactions_with_many_blocks_per_block_ranges() {
341+
let txs = CardanoTransactionsBuilder::new()
342+
.per_block(5)
343+
.blocks_per_block_range(2)
344+
.build_transactions(18);
345+
346+
// block range 1 - block 0 - 1, 2, 3, 4, 5
347+
// block range 1 - block 1 - 6, 7, 8, 7, 10
348+
// block range 2 - block 15 - 11, 12, 13, 14, 15
349+
// block range 2 - block 16 - 16, 17, 18
350+
351+
assert_eq!(txs.len(), 18);
352+
353+
assert_eq!(
354+
18,
355+
count_distinct_values(&txs, &|t| t.transaction_hash.clone())
356+
);
357+
358+
assert_eq!(
359+
2,
360+
count_distinct_values(&txs, &|t| BlockRange::start(t.block_number))
361+
);
362+
assert_eq!(4, count_distinct_values(&txs, &|t| t.block_number));
363+
assert_eq!(4, count_distinct_values(&txs, &|t| t.block_hash.clone()));
364+
}
365+
366+
#[test]
367+
#[should_panic]
368+
fn should_panic_when_too_many_blocks_per_block_range() {
369+
CardanoTransactionsBuilder::new().blocks_per_block_range(BlockRange::LENGTH as usize + 1);
370+
}
371+
}

mithril-common/src/test_utils/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub mod apispec;
1313
pub mod fake_data;
1414
pub mod fake_keys;
1515

16+
mod cardano_transactions_builder;
1617
mod fixture_builder;
1718
mod mithril_fixture;
1819

@@ -22,6 +23,7 @@ mod temp_dir;
2223
#[cfg_attr(docsrs, doc(cfg(feature = "test_http_server")))]
2324
pub mod test_http_server;
2425

26+
pub use cardano_transactions_builder::CardanoTransactionsBuilder;
2527
pub use fixture_builder::{MithrilFixtureBuilder, StakeDistributionGenerationMethod};
2628
pub use mithril_fixture::{MithrilFixture, SignerFixture};
2729
pub use temp_dir::*;

0 commit comments

Comments
 (0)