1use std::ops::Range;
2use std::sync::Arc;
3
4use anyhow::Context;
5
6use mithril_common::StdResult;
7use mithril_common::crypto_helper::MKTreeNode;
8use mithril_common::entities::{
9 BlockHash, BlockNumber, BlockRange, CardanoBlockWithTransactions, CardanoTransaction,
10 ChainPoint, SlotNumber, TransactionHash,
11};
12
13use crate::database::query::{
14 DeleteBlockRangeRootQuery, DeleteCardanoBlockAndTransactionQuery,
15 DeleteLegacyBlockRangeRootQuery, GetBlockRangeRootQuery, GetCardanoBlockQuery,
16 GetCardanoBlockTransactionsQuery, GetCardanoTransactionQuery, GetLegacyBlockRangeRootQuery,
17 InsertBlockRangeRootQuery, InsertCardanoBlockQuery, InsertCardanoTransactionQuery,
18 InsertLegacyBlockRangeRootQuery,
19};
20use crate::database::record::{
21 BlockRangeRootRecord, CardanoBlockRecord, CardanoBlockTransactionsRecord,
22 CardanoTransactionRecord, IntoRecords, StorableCardanoTransactionRecord,
23};
24use crate::sqlite::{
25 ConnectionExtensions, OptimizeMode, SqliteCleaner, SqliteConnection, SqliteConnectionPool,
26};
27
28pub struct CardanoTransactionRepository {
33 connection_pool: Arc<SqliteConnectionPool>,
34}
35
36impl CardanoTransactionRepository {
37 pub fn new(connection_pool: Arc<SqliteConnectionPool>) -> Self {
39 Self { connection_pool }
40 }
41
42 pub fn optimize(&self) -> StdResult<()> {
44 let connection = self.connection_pool.connection()?;
45 SqliteCleaner::optimize(&connection, OptimizeMode::Default)
46 .with_context(|| "Failed to optimize database")?;
47 self.connection_pool
48 .renew_connections()
49 .with_context(|| "Failed to renew connection pool")?;
50
51 Ok(())
52 }
53
54 pub async fn get_all_transactions(&self) -> StdResult<Vec<CardanoTransactionRecord>> {
56 self.connection_pool
57 .connection()?
58 .fetch_collect(GetCardanoTransactionQuery::all())
59 }
60
61 pub async fn get_all_blocks(&self) -> StdResult<Vec<CardanoBlockRecord>> {
63 self.connection_pool
64 .connection()?
65 .fetch_collect(GetCardanoBlockQuery::all())
66 }
67
68 pub async fn get_transactions_in_range_blocks(
71 &self,
72 range: Range<BlockNumber>,
73 ) -> StdResult<Vec<CardanoTransactionRecord>> {
74 self.connection_pool
75 .connection()?
76 .fetch_collect(GetCardanoTransactionQuery::between_blocks(range))
77 }
78
79 pub async fn get_blocks_with_transactions_in_range_blocks(
82 &self,
83 range: Range<BlockNumber>,
84 ) -> StdResult<Vec<CardanoBlockTransactionsRecord>> {
85 self.connection_pool
86 .connection()?
87 .fetch_collect(GetCardanoBlockTransactionsQuery::between_blocks(range))
88 }
89
90 pub async fn get_block<T: Into<BlockHash>>(
92 &self,
93 block_hash: T,
94 ) -> StdResult<Option<CardanoBlockRecord>> {
95 self.connection_pool
96 .connection()?
97 .fetch_first(GetCardanoBlockQuery::by_block_hash(&block_hash.into()))
98 }
99
100 pub async fn get_transaction<T: Into<TransactionHash>>(
102 &self,
103 transaction_hash: T,
104 ) -> StdResult<Option<CardanoTransactionRecord>> {
105 self.connection_pool.connection()?.fetch_first(
106 GetCardanoTransactionQuery::by_transaction_hash(transaction_hash),
107 )
108 }
109
110 pub async fn create_block_and_transactions(
112 &self,
113 blocks_with_transactions: Vec<CardanoBlockWithTransactions>,
114 ) -> StdResult<()> {
115 let connection = self.connection_pool.connection()?;
116
117 self.create_block_and_transactions_with_connection(&connection, blocks_with_transactions)
118 .await
119 }
120
121 async fn create_block_and_transactions_with_connection(
123 &self,
124 connection: &SqliteConnection,
125 blocks_with_transactions: Vec<CardanoBlockWithTransactions>,
126 ) -> StdResult<()> {
127 if blocks_with_transactions.is_empty() {
128 return Ok(());
129 }
130 let (blocks_records, mut transactions_records) = blocks_with_transactions.into_records();
131 connection.apply(InsertCardanoBlockQuery::insert_many(blocks_records)?)?;
132
133 while !transactions_records.is_empty() {
134 let chunk: Vec<_> = transactions_records
135 .drain(
136 ..transactions_records
137 .len()
138 .min(StorableCardanoTransactionRecord::MAX_PER_INSERT),
139 )
140 .collect();
141 connection.apply(InsertCardanoTransactionQuery::insert_many(chunk)?)?;
142 }
143
144 Ok(())
145 }
146
147 pub async fn create_block_range_roots<T: Into<BlockRangeRootRecord>>(
149 &self,
150 block_ranges: Vec<T>,
151 ) -> StdResult<Vec<BlockRangeRootRecord>> {
152 let records: Vec<BlockRangeRootRecord> =
153 block_ranges.into_iter().map(|tx| tx.into()).collect();
154 let connection = self.connection_pool.connection()?;
155
156 connection.fetch_collect(InsertBlockRangeRootQuery::insert_many(records)?)
157 }
158
159 pub async fn create_legacy_block_range_roots<T: Into<BlockRangeRootRecord>>(
161 &self,
162 block_ranges: Vec<T>,
163 ) -> StdResult<Vec<BlockRangeRootRecord>> {
164 let records: Vec<BlockRangeRootRecord> =
165 block_ranges.into_iter().map(|tx| tx.into()).collect();
166 let connection = self.connection_pool.connection()?;
167
168 connection.fetch_collect(InsertLegacyBlockRangeRootQuery::insert_many(records)?)
169 }
170
171 pub async fn get_transaction_highest_chain_point(&self) -> StdResult<Option<ChainPoint>> {
173 let first_transaction_with_highest_block_number = self
174 .connection_pool
175 .connection()?
176 .fetch_first(GetCardanoBlockQuery::with_highest_block_number())?;
177
178 Ok(first_transaction_with_highest_block_number.map(|record| {
179 ChainPoint::new(record.slot_number, record.block_number, record.block_hash)
180 }))
181 }
182
183 pub async fn get_prune_blocks_threshold(&self) -> StdResult<Option<BlockNumber>> {
189 let highest: Option<i64> = self.connection_pool.connection()?.query_single_cell(
190 r#"
191select coalesce(min(max_new.highest, max_legacy.highest), max_new.highest, max_legacy.highest)
192from (select max(start) as highest from block_range_root) max_new,
193 (select max(start) as highest from block_range_root_legacy) max_legacy;"#,
194 &[],
195 )?;
196 highest
197 .map(u64::try_from)
198 .transpose()
199 .map(|num| num.map(BlockNumber))
200 .with_context(||
201 format!("Integer field max(start) (value={highest:?}) is incompatible with u64 representation.")
202 )
203 }
204
205 pub async fn retrieve_block_range_roots_up_to(
208 &self,
209 block_number: BlockNumber,
210 ) -> StdResult<Box<dyn Iterator<Item = (BlockRange, MKTreeNode)> + '_>> {
211 let block_range_roots = self
212 .connection_pool
213 .connection()?
214 .fetch(GetBlockRangeRootQuery::contains_or_below_block_number(
215 block_number,
216 ))?
217 .map(|record| -> (BlockRange, MKTreeNode) { record.into() })
218 .collect::<Vec<_>>(); Ok(Box::new(block_range_roots.into_iter()))
221 }
222
223 pub async fn retrieve_legacy_block_range_roots_up_to(
226 &self,
227 block_number: BlockNumber,
228 ) -> StdResult<Box<dyn Iterator<Item = (BlockRange, MKTreeNode)> + '_>> {
229 let block_range_roots = self
230 .connection_pool
231 .connection()?
232 .fetch(GetLegacyBlockRangeRootQuery::contains_or_below_block_number(block_number))?
233 .map(|record| -> (BlockRange, MKTreeNode) { record.into() })
234 .collect::<Vec<_>>(); Ok(Box::new(block_range_roots.into_iter()))
237 }
238
239 pub async fn retrieve_highest_block_range_root(
241 &self,
242 ) -> StdResult<Option<BlockRangeRootRecord>> {
243 self.connection_pool
244 .connection()?
245 .fetch_first(GetBlockRangeRootQuery::highest())
246 }
247
248 pub async fn retrieve_highest_legacy_block_range_root(
250 &self,
251 ) -> StdResult<Option<BlockRangeRootRecord>> {
252 self.connection_pool
253 .connection()?
254 .fetch_first(GetLegacyBlockRangeRootQuery::highest())
255 }
256
257 pub async fn get_all(&self) -> StdResult<Vec<CardanoTransaction>> {
259 let records = self
260 .connection_pool
261 .connection()?
262 .fetch(GetCardanoTransactionQuery::all())?
263 .map(|record| record.into())
264 .collect();
265
266 Ok(records)
267 }
268
269 pub fn get_all_block_range_root(&self) -> StdResult<Vec<BlockRangeRootRecord>> {
271 self.connection_pool
272 .connection()?
273 .fetch_collect(GetBlockRangeRootQuery::all())
274 }
275
276 pub fn get_all_legacy_block_range_root(&self) -> StdResult<Vec<BlockRangeRootRecord>> {
278 self.connection_pool
279 .connection()?
280 .fetch_collect(GetLegacyBlockRangeRootQuery::all())
281 }
282
283 pub async fn store_blocks_and_transactions(
287 &self,
288 blocks_with_transactions: Vec<CardanoBlockWithTransactions>,
289 ) -> StdResult<()> {
290 let mut remaining_blocks = blocks_with_transactions;
291
292 while !remaining_blocks.is_empty() {
293 let connection = self.connection_pool.connection()?;
294 let transaction = connection.begin_transaction()?;
295
296 let chunk: Vec<_> = remaining_blocks
297 .drain(..remaining_blocks.len().min(CardanoBlockRecord::MAX_PER_INSERT))
298 .collect();
299
300 self.create_block_and_transactions_with_connection(&connection, chunk)
301 .await
302 .with_context(
303 || "CardanoTransactionRepository can not store blocks and transactions",
304 )?;
305
306 transaction.commit()?;
307 }
308
309 Ok(())
310 }
311
312 pub async fn get_closest_block_number_above_slot_number(
314 &self,
315 slot_number: SlotNumber,
316 ) -> StdResult<Option<BlockNumber>> {
317 let query = GetCardanoBlockQuery::with_highest_block_number_below_slot_number(slot_number);
318 let record = self.connection_pool.connection()?.fetch_first(query)?;
319
320 Ok(record.map(|r| r.block_number))
321 }
322
323 pub async fn get_transaction_by_hashes<T: Into<TransactionHash>>(
325 &self,
326 hashes: Vec<T>,
327 up_to: BlockNumber,
328 ) -> StdResult<Vec<CardanoTransactionRecord>> {
329 let query = GetCardanoTransactionQuery::by_transaction_hashes(
330 hashes.into_iter().map(Into::into).collect(),
331 up_to,
332 );
333 self.connection_pool.connection()?.fetch_collect(query)
334 }
335
336 pub async fn get_transaction_by_block_ranges(
338 &self,
339 block_ranges: Vec<BlockRange>,
340 ) -> StdResult<Vec<CardanoTransactionRecord>> {
341 let mut transactions = vec![];
342 for block_range in block_ranges {
345 let block_range_transactions: Vec<CardanoTransactionRecord> = self
346 .connection_pool
347 .connection()?
348 .fetch_collect(GetCardanoTransactionQuery::between_blocks(block_range))?;
349 transactions.extend(block_range_transactions);
350 }
351
352 Ok(transactions)
353 }
354
355 pub async fn get_blocks_with_transactions_by_block_ranges(
357 &self,
358 block_ranges: Vec<BlockRange>,
359 ) -> StdResult<Vec<CardanoBlockTransactionsRecord>> {
360 let mut blocks_with_transactions = vec![];
361 for block_range in block_ranges {
364 let block_range_transactions: Vec<CardanoBlockTransactionsRecord> =
365 self.connection_pool.connection()?.fetch_collect(
366 GetCardanoBlockTransactionsQuery::between_blocks(block_range),
367 )?;
368 blocks_with_transactions.extend(block_range_transactions);
369 }
370
371 Ok(blocks_with_transactions)
372 }
373
374 pub async fn prune_transaction(&self, number_of_blocks_to_keep: BlockNumber) -> StdResult<()> {
377 if let Some(highest_block_range_start) = self.get_prune_blocks_threshold().await? {
378 let threshold = highest_block_range_start - number_of_blocks_to_keep;
379 let query =
380 DeleteCardanoBlockAndTransactionQuery::below_block_number_threshold(threshold)?;
381
382 let connection = self.connection_pool.connection()?;
383 connection.fetch_first(query)?;
384 }
385
386 Ok(())
387 }
388
389 pub async fn remove_rolled_back_transactions_and_block_range_by_block_number(
394 &self,
395 block_number: BlockNumber,
396 ) -> StdResult<()> {
397 let connection = self.connection_pool.connection()?;
398 let transaction = connection.begin_transaction()?;
399
400 connection.fetch_first(
401 DeleteCardanoBlockAndTransactionQuery::above_block_number_threshold(block_number)?,
402 )?;
403 connection.fetch_first(
404 DeleteBlockRangeRootQuery::contains_or_above_block_number_threshold(block_number)?,
405 )?;
406 connection.fetch_first(
407 DeleteLegacyBlockRangeRootQuery::contains_or_above_block_number_threshold(
408 block_number,
409 )?,
410 )?;
411
412 transaction.commit()?;
413 Ok(())
414 }
415
416 pub async fn remove_rolled_back_blocks_transactions_and_block_range_by_slot_number(
421 &self,
422 slot_number: SlotNumber,
423 ) -> StdResult<()> {
424 if let Some(block_number) =
425 self.get_closest_block_number_above_slot_number(slot_number).await?
426 {
427 self.remove_rolled_back_transactions_and_block_range_by_block_number(block_number)
428 .await?;
429 }
430
431 Ok(())
432 }
433}
434
435#[cfg(test)]
436mod tests {
437 use mithril_common::temp_dir_create;
438
439 use crate::database::query::GetLegacyBlockRangeRootQuery;
440 use crate::database::test_helper::cardano_tx_db_connection_builder;
441
442 use super::*;
443
444 #[tokio::test]
445 async fn repository_create_and_get_blocks_and_transactions() {
446 let temp_dir = temp_dir_create!();
447 let repository = CardanoTransactionRepository::new(Arc::new(
448 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
449 ));
450
451 repository
452 .create_block_and_transactions(vec![
453 CardanoBlockWithTransactions::new(
454 "block_hash-123",
455 BlockNumber(10),
456 SlotNumber(50),
457 vec!["tx_hash-123", "tx_hash-456"],
458 ),
459 CardanoBlockWithTransactions::new(
460 "block_hash-789",
461 BlockNumber(11),
462 SlotNumber(51),
463 vec!["tx_hash-789"],
464 ),
465 ])
466 .await
467 .unwrap();
468
469 {
470 let block_result = repository.get_block("block_hash-123").await.unwrap();
471 assert_eq!(
472 Some(CardanoBlockRecord {
473 block_hash: "block_hash-123".to_string(),
474 block_number: BlockNumber(10),
475 slot_number: SlotNumber(50),
476 }),
477 block_result
478 );
479
480 let transaction_result = repository.get_transaction("tx_hash-123").await.unwrap();
481 assert_eq!(
482 Some(CardanoTransactionRecord {
483 transaction_hash: "tx_hash-123".to_string(),
484 block_number: BlockNumber(10),
485 slot_number: SlotNumber(50),
486 block_hash: "block_hash-123".to_string(),
487 }),
488 transaction_result
489 );
490 }
491 {
492 let transaction_result = repository.get_transaction("not-exist").await.unwrap();
493 assert_eq!(None, transaction_result);
494 }
495 }
496
497 #[tokio::test]
498 async fn repository_create_and_get_blocks_and_transactions_dont_fail_if_empty_or_no_transactions()
499 {
500 let temp_dir = temp_dir_create!();
501 let repository = CardanoTransactionRepository::new(Arc::new(
502 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
503 ));
504
505 repository.create_block_and_transactions(vec![]).await.unwrap();
506 repository
507 .create_block_and_transactions(vec![CardanoBlockWithTransactions::new(
508 "block_hash-10",
509 BlockNumber(10),
510 SlotNumber(50),
511 Vec::<String>::new(),
512 )])
513 .await
514 .unwrap();
515 }
516
517 #[tokio::test]
518 async fn repository_get_transaction_by_hashes() {
519 let temp_dir = temp_dir_create!();
520 let repository = CardanoTransactionRepository::new(Arc::new(
521 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
522 ));
523
524 repository
525 .create_block_and_transactions(vec![
526 CardanoBlockWithTransactions::new(
527 "block_hash-10",
528 BlockNumber(10),
529 SlotNumber(50),
530 vec!["tx_hash-123", "tx_hash-456", "tx_hash-789"],
531 ),
532 CardanoBlockWithTransactions::new(
533 "block_hash-101",
534 BlockNumber(101),
535 SlotNumber(100),
536 vec!["tx_hash-000"],
537 ),
538 ])
539 .await
540 .unwrap();
541
542 {
543 let transactions = repository
544 .get_transaction_by_hashes(vec!["tx_hash-123", "tx_hash-789"], BlockNumber(100))
545 .await
546 .unwrap();
547
548 assert_eq!(
549 vec![
550 CardanoTransactionRecord::new(
551 "tx_hash-123",
552 BlockNumber(10),
553 SlotNumber(50),
554 "block_hash-10"
555 ),
556 CardanoTransactionRecord::new(
557 "tx_hash-789",
558 BlockNumber(10),
559 SlotNumber(50),
560 "block_hash-10"
561 ),
562 ],
563 transactions
564 );
565 }
566 {
567 let transactions = repository
568 .get_transaction_by_hashes(
569 vec!["tx_hash-123", "tx_hash-789", "tx_hash-000"],
570 BlockNumber(100),
571 )
572 .await
573 .unwrap();
574
575 assert_eq!(
576 vec![
577 CardanoTransactionRecord::new(
578 "tx_hash-123",
579 BlockNumber(10),
580 SlotNumber(50),
581 "block_hash-10"
582 ),
583 CardanoTransactionRecord::new(
584 "tx_hash-789",
585 BlockNumber(10),
586 SlotNumber(50),
587 "block_hash-10"
588 ),
589 ],
590 transactions
591 );
592 }
593 {
594 let transactions = repository
595 .get_transaction_by_hashes(
596 vec!["tx_hash-123", "tx_hash-789", "tx_hash-000"],
597 BlockNumber(101),
598 )
599 .await
600 .unwrap();
601
602 assert_eq!(
603 vec![
604 CardanoTransactionRecord::new(
605 "tx_hash-123",
606 BlockNumber(10),
607 SlotNumber(50),
608 "block_hash-10"
609 ),
610 CardanoTransactionRecord::new(
611 "tx_hash-789",
612 BlockNumber(10),
613 SlotNumber(50),
614 "block_hash-10"
615 ),
616 CardanoTransactionRecord::new(
617 "tx_hash-000",
618 BlockNumber(101),
619 SlotNumber(100),
620 "block_hash-101"
621 ),
622 ],
623 transactions
624 );
625 }
626 {
627 let transactions = repository
628 .get_transaction_by_hashes(vec!["not-exist".to_string()], BlockNumber(100))
629 .await
630 .unwrap();
631
632 assert_eq!(Vec::<CardanoTransactionRecord>::new(), transactions);
633 }
634 }
635
636 #[tokio::test]
637 async fn repository_create_ignore_further_blocks_when_exists() {
638 let temp_dir = temp_dir_create!();
639 let repository = CardanoTransactionRepository::new(Arc::new(
640 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
641 ));
642 let base_block = CardanoBlockWithTransactions::new(
643 "block_hash-1",
644 BlockNumber(10),
645 SlotNumber(50),
646 vec![""],
647 );
648 let expected_record: CardanoBlockRecord = base_block.clone().into();
649
650 repository
651 .create_block_and_transactions(vec![base_block.clone()])
652 .await
653 .unwrap();
654
655 {
657 repository
658 .create_block_and_transactions(vec![CardanoBlockWithTransactions {
659 block_hash: "block_hash-1-new".to_string(),
660 slot_number: base_block.slot_number + 10,
661 ..base_block.clone()
662 }])
663 .await
664 .unwrap();
665 let block_result = repository.get_block("block_hash-1").await.unwrap();
666
667 assert_eq!(Some(expected_record.clone()), block_result);
668 }
669 {
671 repository
672 .create_block_and_transactions(vec![CardanoBlockWithTransactions {
673 block_hash: "block_hash-1-new".to_string(),
674 block_number: base_block.block_number + 10,
675 ..base_block.clone()
676 }])
677 .await
678 .unwrap();
679 let block_result = repository.get_block("block_hash-1").await.unwrap();
680
681 assert_eq!(Some(expected_record.clone()), block_result);
682 }
683 {
685 repository
686 .create_block_and_transactions(vec![CardanoBlockWithTransactions {
687 block_number: base_block.block_number + 10,
688 slot_number: base_block.slot_number,
689 ..base_block.clone()
690 }])
691 .await
692 .unwrap();
693 let block_result = repository.get_block("block_hash-1").await.unwrap();
694
695 assert_eq!(Some(expected_record.clone()), block_result);
696 }
697 }
698
699 #[tokio::test]
700 async fn repository_create_ignore_further_transactions_when_exists() {
701 let temp_dir = temp_dir_create!();
702 let repository = CardanoTransactionRepository::new(Arc::new(
703 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
704 ));
705 let base_block_with_txs = CardanoBlockWithTransactions::new(
706 "block_hash-1",
707 BlockNumber(10),
708 SlotNumber(50),
709 vec!["tx_hash-1"],
710 );
711
712 repository
713 .create_block_and_transactions(vec![base_block_with_txs.clone()])
714 .await
715 .unwrap();
716 repository
717 .create_block_and_transactions(vec![base_block_with_txs.clone()])
718 .await
719 .unwrap();
720 let transactions_result = repository.get_all().await.unwrap();
721
722 assert_eq!(
723 vec![CardanoTransaction::new(
724 "tx_hash-1".to_string(),
725 BlockNumber(10),
726 SlotNumber(50),
727 "block_hash-1".to_string(),
728 )],
729 transactions_result
730 );
731 }
732
733 #[tokio::test]
734 async fn repository_create_blocks_and_transactions_and_get_stored_them_individually() {
735 let temp_dir = temp_dir_create!();
736 let repository = CardanoTransactionRepository::new(Arc::new(
737 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
738 ));
739
740 repository
741 .create_block_and_transactions(vec![CardanoBlockWithTransactions::new(
742 "block_hash-123",
743 BlockNumber(10),
744 SlotNumber(50),
745 vec!["tx_hash-123", "tx_hash-456"],
746 )])
747 .await
748 .unwrap();
749
750 let block_result = repository.get_block("block_hash-123").await.unwrap();
751 assert_eq!(
752 Some(CardanoBlockRecord {
753 block_hash: "block_hash-123".to_string(),
754 block_number: BlockNumber(10),
755 slot_number: SlotNumber(50),
756 }),
757 block_result
758 );
759
760 let transaction_result = repository.get_transaction("tx_hash-123").await.unwrap();
761 assert_eq!(
762 Some(CardanoTransactionRecord {
763 transaction_hash: "tx_hash-123".to_string(),
764 block_number: BlockNumber(10),
765 slot_number: SlotNumber(50),
766 block_hash: "block_hash-123".to_string(),
767 }),
768 transaction_result
769 );
770
771 let transaction_result = repository.get_transaction("tx_hash-456").await.unwrap();
772 assert_eq!(
773 Some(CardanoTransactionRecord {
774 transaction_hash: "tx_hash-456".to_string(),
775 block_number: BlockNumber(10),
776 slot_number: SlotNumber(50),
777 block_hash: "block_hash-123".to_string(),
778 }),
779 transaction_result
780 );
781 }
782
783 #[tokio::test]
784 async fn repository_store_blocks_and_transactions_bulk_insert_1_000_blocks_with_500_transactions_per_block()
785 {
786 let temp_dir = temp_dir_create!();
787 let repository = CardanoTransactionRepository::new(Arc::new(
788 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
789 ));
790
791 let blocks_to_insert: Vec<_> = (0..1_000)
792 .map(|i| {
793 CardanoBlockWithTransactions::new(
794 format!("block_hash-{i}"),
795 BlockNumber(i),
796 SlotNumber(i * 5),
797 (0..500).map(|j| format!("tx_hash-{i}-{j}")).collect(),
798 )
799 })
800 .collect();
801
802 repository
803 .store_blocks_and_transactions(blocks_to_insert)
804 .await
805 .unwrap();
806
807 let all_blocks = repository.get_all_blocks().await.unwrap();
808 assert_eq!(1_000, all_blocks.len());
809 let all_transactions = repository.get_all_transactions().await.unwrap();
810 assert_eq!(1_000 * 500, all_transactions.len());
811 }
812
813 #[tokio::test]
814 async fn repository_get_all_stored_blocks() {
815 let temp_dir = temp_dir_create!();
816 let repository = CardanoTransactionRepository::new(Arc::new(
817 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
818 ));
819
820 repository
821 .create_block_and_transactions(vec![
822 CardanoBlockWithTransactions::new(
823 "block_hash-1",
824 BlockNumber(10),
825 SlotNumber(50),
826 Vec::<String>::new(),
827 ),
828 CardanoBlockWithTransactions::new(
829 "block_hash-2",
830 BlockNumber(11),
831 SlotNumber(51),
832 Vec::<String>::new(),
833 ),
834 ])
835 .await
836 .unwrap();
837
838 let transactions_result = repository.get_all_blocks().await.unwrap();
839 assert_eq!(
840 vec![
841 CardanoBlockRecord::new(
842 "block_hash-1".to_string(),
843 BlockNumber(10),
844 SlotNumber(50),
845 ),
846 CardanoBlockRecord::new(
847 "block_hash-2".to_string(),
848 BlockNumber(11),
849 SlotNumber(51),
850 )
851 ],
852 transactions_result
853 )
854 }
855
856 #[tokio::test]
857 async fn repository_get_all_stored_transactions() {
858 let temp_dir = temp_dir_create!();
859 let repository = CardanoTransactionRepository::new(Arc::new(
860 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
861 ));
862
863 repository
864 .create_block_and_transactions(vec![CardanoBlockWithTransactions::new(
865 "block_hash-123",
866 BlockNumber(10),
867 SlotNumber(50),
868 vec!["tx_hash-123", "tx_hash-456"],
869 )])
870 .await
871 .unwrap();
872
873 let transactions_result = repository.get_all_transactions().await.unwrap();
874 assert_eq!(
875 vec![
876 CardanoTransactionRecord::new(
877 "tx_hash-123".to_string(),
878 BlockNumber(10),
879 SlotNumber(50),
880 "block_hash-123".to_string(),
881 ),
882 CardanoTransactionRecord::new(
883 "tx_hash-456".to_string(),
884 BlockNumber(10),
885 SlotNumber(50),
886 "block_hash-123".to_string(),
887 )
888 ],
889 transactions_result
890 )
891 }
892
893 #[tokio::test]
894 async fn repository_get_highest_chain_point_without_blocks_in_db() {
895 let temp_dir = temp_dir_create!();
896 let repository = CardanoTransactionRepository::new(Arc::new(
897 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
898 ));
899
900 let highest_beacon = repository.get_transaction_highest_chain_point().await.unwrap();
901 assert_eq!(None, highest_beacon);
902 }
903
904 #[tokio::test]
905 async fn repository_get_highest_chain_point_with_blocks_in_db() {
906 let temp_dir = temp_dir_create!();
907 let repository = CardanoTransactionRepository::new(Arc::new(
908 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
909 ));
910
911 repository
912 .create_block_and_transactions(vec![
913 CardanoBlockWithTransactions::new(
914 "block_hash-1",
915 BlockNumber(10),
916 SlotNumber(50),
917 Vec::<String>::new(),
918 ),
919 CardanoBlockWithTransactions::new(
920 "block_hash-2",
921 BlockNumber(100),
922 SlotNumber(150),
923 Vec::<String>::new(),
924 ),
925 ])
926 .await
927 .unwrap();
928
929 let highest_beacon = repository.get_transaction_highest_chain_point().await.unwrap();
930 assert_eq!(
931 Some(ChainPoint::new(
932 SlotNumber(150),
933 BlockNumber(100),
934 "block_hash-2"
935 )),
936 highest_beacon
937 );
938 }
939
940 #[tokio::test]
941 async fn repository_get_transactions_in_range_blocks() {
942 let temp_dir = temp_dir_create!();
943 let repository = CardanoTransactionRepository::new(Arc::new(
944 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
945 ));
946
947 let blocks = vec![
948 CardanoBlockWithTransactions::new(
949 "block_hash-1",
950 BlockNumber(10),
951 SlotNumber(50),
952 vec!["tx_hash-1"],
953 ),
954 CardanoBlockWithTransactions::new(
955 "block_hash-2",
956 BlockNumber(11),
957 SlotNumber(51),
958 vec!["tx_hash-2"],
959 ),
960 CardanoBlockWithTransactions::new(
961 "block_hash-3",
962 BlockNumber(12),
963 SlotNumber(52),
964 vec!["tx_hash-3"],
965 ),
966 ];
967 repository
968 .create_block_and_transactions(blocks.clone())
969 .await
970 .unwrap();
971 let expected_transactions: Vec<CardanoTransactionRecord> = blocks
972 .into_iter()
973 .flat_map(|b| b.into_transactions())
974 .map(Into::into)
975 .collect();
976
977 {
978 let transaction_result = repository
979 .get_transactions_in_range_blocks(BlockNumber(0)..BlockNumber(10))
980 .await
981 .unwrap();
982 assert_eq!(Vec::<CardanoTransactionRecord>::new(), transaction_result);
983 }
984 {
985 let transaction_result = repository
986 .get_transactions_in_range_blocks(BlockNumber(13)..BlockNumber(21))
987 .await
988 .unwrap();
989 assert_eq!(Vec::<CardanoTransactionRecord>::new(), transaction_result);
990 }
991 {
992 let transaction_result = repository
993 .get_transactions_in_range_blocks(BlockNumber(9)..BlockNumber(12))
994 .await
995 .unwrap();
996 assert_eq!(expected_transactions[0..=1].to_vec(), transaction_result);
997 }
998 {
999 let transaction_result = repository
1000 .get_transactions_in_range_blocks(BlockNumber(10)..BlockNumber(13))
1001 .await
1002 .unwrap();
1003 assert_eq!(expected_transactions.clone(), transaction_result);
1004 }
1005 {
1006 let transaction_result = repository
1007 .get_transactions_in_range_blocks(BlockNumber(11)..BlockNumber(14))
1008 .await
1009 .unwrap();
1010 assert_eq!(expected_transactions[1..=2].to_vec(), transaction_result);
1011 }
1012 }
1013
1014 #[tokio::test]
1015 async fn repository_get_blocks_with_transactions_in_range_blocks() {
1016 let temp_dir = temp_dir_create!();
1017 let repository = CardanoTransactionRepository::new(Arc::new(
1018 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
1019 ));
1020
1021 let blocks = vec![
1022 CardanoBlockWithTransactions::new(
1023 "block_hash-1",
1024 BlockNumber(10),
1025 SlotNumber(50),
1026 vec!["tx_hash-1"],
1027 ),
1028 CardanoBlockWithTransactions::new(
1029 "block_hash-2",
1030 BlockNumber(11),
1031 SlotNumber(51),
1032 vec!["tx_hash-2"],
1033 ),
1034 CardanoBlockWithTransactions::new(
1035 "block_hash-3",
1036 BlockNumber(12),
1037 SlotNumber(52),
1038 vec!["tx_hash-3"],
1039 ),
1040 ];
1041 repository
1042 .create_block_and_transactions(blocks.clone())
1043 .await
1044 .unwrap();
1045 let expected_blocks: Vec<CardanoBlockTransactionsRecord> =
1046 blocks.into_iter().map(Into::into).collect();
1047
1048 {
1049 let blocks = repository
1050 .get_blocks_with_transactions_in_range_blocks(BlockNumber(0)..BlockNumber(10))
1051 .await
1052 .unwrap();
1053 assert_eq!(Vec::<CardanoBlockTransactionsRecord>::new(), blocks);
1054 }
1055 {
1056 let blocks = repository
1057 .get_blocks_with_transactions_in_range_blocks(BlockNumber(13)..BlockNumber(21))
1058 .await
1059 .unwrap();
1060 assert_eq!(Vec::<CardanoBlockTransactionsRecord>::new(), blocks);
1061 }
1062 {
1063 let blocks = repository
1064 .get_blocks_with_transactions_in_range_blocks(BlockNumber(9)..BlockNumber(12))
1065 .await
1066 .unwrap();
1067 assert_eq!(expected_blocks[0..=1].to_vec(), blocks);
1068 }
1069 {
1070 let blocks = repository
1071 .get_blocks_with_transactions_in_range_blocks(BlockNumber(10)..BlockNumber(13))
1072 .await
1073 .unwrap();
1074 assert_eq!(expected_blocks.clone(), blocks);
1075 }
1076 {
1077 let blocks = repository
1078 .get_blocks_with_transactions_in_range_blocks(BlockNumber(11)..BlockNumber(14))
1079 .await
1080 .unwrap();
1081 assert_eq!(expected_blocks[1..=2].to_vec(), blocks);
1082 }
1083 }
1084
1085 #[tokio::test]
1086 async fn repository_get_transactions_by_block_ranges() {
1087 let temp_dir = temp_dir_create!();
1088 let repository = CardanoTransactionRepository::new(Arc::new(
1089 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
1090 ));
1091
1092 let blocks = vec![
1093 CardanoBlockWithTransactions::new(
1094 "block_hash-1",
1095 BlockNumber(10),
1096 SlotNumber(50),
1097 vec!["tx_hash-1"],
1098 ),
1099 CardanoBlockWithTransactions::new(
1100 "block_hash-2",
1101 BlockNumber(11),
1102 SlotNumber(51),
1103 vec!["tx_hash-2"],
1104 ),
1105 CardanoBlockWithTransactions::new(
1106 "block_hash-3",
1107 BlockNumber(20),
1108 SlotNumber(52),
1109 vec!["tx_hash-3"],
1110 ),
1111 CardanoBlockWithTransactions::new(
1112 "block_hash-4",
1113 BlockNumber(31),
1114 SlotNumber(53),
1115 vec!["tx_hash-4"],
1116 ),
1117 CardanoBlockWithTransactions::new(
1118 "block_hash-5",
1119 BlockNumber(35),
1120 SlotNumber(54),
1121 vec!["tx_hash-5"],
1122 ),
1123 CardanoBlockWithTransactions::new(
1124 "block_hash-6",
1125 BlockNumber(46),
1126 SlotNumber(55),
1127 vec!["tx_hash-6"],
1128 ),
1129 ];
1130 repository
1131 .create_block_and_transactions(blocks.clone())
1132 .await
1133 .unwrap();
1134 let expected_transactions: Vec<CardanoTransactionRecord> = blocks
1135 .into_iter()
1136 .flat_map(|b| b.into_transactions())
1137 .map(Into::into)
1138 .collect();
1139
1140 {
1141 let transaction_result = repository
1142 .get_transaction_by_block_ranges(vec![BlockRange::from_block_number(BlockNumber(
1143 100,
1144 ))])
1145 .await
1146 .unwrap();
1147 assert_eq!(Vec::<CardanoTransactionRecord>::new(), transaction_result);
1148 }
1149 {
1150 let transaction_result = repository
1151 .get_transaction_by_block_ranges(vec![BlockRange::from_block_number(BlockNumber(
1152 0,
1153 ))])
1154 .await
1155 .unwrap();
1156 assert_eq!(expected_transactions[0..=1].to_vec(), transaction_result);
1157 }
1158 {
1159 let transaction_result = repository
1160 .get_transaction_by_block_ranges(vec![
1161 BlockRange::from_block_number(BlockNumber(0)),
1162 BlockRange::from_block_number(BlockNumber(15)),
1163 ])
1164 .await
1165 .unwrap();
1166 assert_eq!(expected_transactions[0..=2].to_vec(), transaction_result);
1167 }
1168 {
1169 let transaction_result = repository
1170 .get_transaction_by_block_ranges(vec![
1171 BlockRange::from_block_number(BlockNumber(0)),
1172 BlockRange::from_block_number(BlockNumber(30)),
1173 ])
1174 .await
1175 .unwrap();
1176 assert_eq!(
1177 [
1178 expected_transactions[0..=1].to_vec(),
1179 expected_transactions[3..=4].to_vec()
1180 ]
1181 .concat(),
1182 transaction_result
1183 );
1184 }
1185 }
1186
1187 #[tokio::test]
1188 async fn repository_get_blocks_with_transactions_by_block_ranges() {
1189 let temp_dir = temp_dir_create!();
1190 let repository = CardanoTransactionRepository::new(Arc::new(
1191 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
1192 ));
1193
1194 let blocks = vec![
1195 CardanoBlockWithTransactions::new(
1196 "block_hash-1",
1197 BlockNumber(10),
1198 SlotNumber(50),
1199 vec!["tx_hash-1"],
1200 ),
1201 CardanoBlockWithTransactions::new(
1202 "block_hash-2",
1203 BlockNumber(11),
1204 SlotNumber(51),
1205 vec!["tx_hash-2"],
1206 ),
1207 CardanoBlockWithTransactions::new(
1208 "block_hash-3",
1209 BlockNumber(20),
1210 SlotNumber(52),
1211 vec!["tx_hash-3"],
1212 ),
1213 CardanoBlockWithTransactions::new(
1214 "block_hash-4",
1215 BlockNumber(31),
1216 SlotNumber(53),
1217 vec!["tx_hash-4"],
1218 ),
1219 CardanoBlockWithTransactions::new(
1220 "block_hash-5",
1221 BlockNumber(35),
1222 SlotNumber(54),
1223 vec!["tx_hash-5"],
1224 ),
1225 CardanoBlockWithTransactions::new(
1226 "block_hash-6",
1227 BlockNumber(46),
1228 SlotNumber(55),
1229 vec!["tx_hash-6"],
1230 ),
1231 ];
1232 repository
1233 .create_block_and_transactions(blocks.clone())
1234 .await
1235 .unwrap();
1236 let expected_blocks: Vec<CardanoBlockTransactionsRecord> =
1237 blocks.into_iter().map(Into::into).collect();
1238
1239 {
1240 let transaction_result = repository
1241 .get_blocks_with_transactions_by_block_ranges(vec![BlockRange::from_block_number(
1242 BlockNumber(100),
1243 )])
1244 .await
1245 .unwrap();
1246 assert_eq!(
1247 Vec::<CardanoBlockTransactionsRecord>::new(),
1248 transaction_result
1249 );
1250 }
1251 {
1252 let transaction_result = repository
1253 .get_blocks_with_transactions_by_block_ranges(vec![BlockRange::from_block_number(
1254 BlockNumber(0),
1255 )])
1256 .await
1257 .unwrap();
1258 assert_eq!(expected_blocks[0..=1].to_vec(), transaction_result);
1259 }
1260 {
1261 let transaction_result = repository
1262 .get_blocks_with_transactions_by_block_ranges(vec![
1263 BlockRange::from_block_number(BlockNumber(0)),
1264 BlockRange::from_block_number(BlockNumber(15)),
1265 ])
1266 .await
1267 .unwrap();
1268 assert_eq!(expected_blocks[0..=2].to_vec(), transaction_result);
1269 }
1270 {
1271 let transaction_result = repository
1272 .get_blocks_with_transactions_by_block_ranges(vec![
1273 BlockRange::from_block_number(BlockNumber(0)),
1274 BlockRange::from_block_number(BlockNumber(30)),
1275 ])
1276 .await
1277 .unwrap();
1278 assert_eq!(
1279 [expected_blocks[0..=1].to_vec(), expected_blocks[3..=4].to_vec()].concat(),
1280 transaction_result
1281 );
1282 }
1283 }
1284
1285 #[tokio::test]
1286 async fn repository_get_closest_block_number_by_slot_number() {
1287 let temp_dir = temp_dir_create!();
1288 let repository = CardanoTransactionRepository::new(Arc::new(
1289 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
1290 ));
1291
1292 let blocks = vec![
1293 CardanoBlockWithTransactions::new(
1294 "block_hash-1",
1295 BlockNumber(100),
1296 SlotNumber(500),
1297 Vec::<String>::new(),
1298 ),
1299 CardanoBlockWithTransactions::new(
1300 "block_hash-2",
1301 BlockNumber(101),
1302 SlotNumber(501),
1303 Vec::<String>::new(),
1304 ),
1305 ];
1306 repository
1307 .create_block_and_transactions(blocks.clone())
1308 .await
1309 .unwrap();
1310
1311 let transaction_block_number_retrieved = repository
1312 .get_closest_block_number_above_slot_number(SlotNumber(500))
1313 .await
1314 .unwrap();
1315
1316 assert_eq!(transaction_block_number_retrieved, Some(BlockNumber(100)));
1317 }
1318
1319 #[tokio::test]
1320 async fn repository_store_legacy_block_range() {
1321 let temp_dir = temp_dir_create!();
1322 let repository = CardanoTransactionRepository::new(Arc::new(
1323 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
1324 ));
1325
1326 repository
1327 .create_legacy_block_range_roots(vec![
1328 (
1329 BlockRange::from_block_number(BlockNumber(0)),
1330 MKTreeNode::from_hex("AAAA").unwrap(),
1331 ),
1332 (
1333 BlockRange::from_block_number(BlockRange::LENGTH),
1334 MKTreeNode::from_hex("BBBB").unwrap(),
1335 ),
1336 ])
1337 .await
1338 .unwrap();
1339
1340 let connection = repository.connection_pool.connection().unwrap();
1341 let records: Vec<BlockRangeRootRecord> =
1342 connection.fetch_collect(GetLegacyBlockRangeRootQuery::all()).unwrap();
1343 assert_eq!(
1344 vec![
1345 BlockRangeRootRecord {
1346 range: BlockRange::from_block_number(BlockNumber(0)),
1347 merkle_root: MKTreeNode::from_hex("AAAA").unwrap(),
1348 },
1349 BlockRangeRootRecord {
1350 range: BlockRange::from_block_number(BlockRange::LENGTH),
1351 merkle_root: MKTreeNode::from_hex("BBBB").unwrap(),
1352 }
1353 ],
1354 records
1355 );
1356 }
1357
1358 #[tokio::test]
1359 async fn repository_store_legacy_block_range_with_existing_hash_doesnt_erase_existing_data() {
1360 let temp_dir = temp_dir_create!();
1361 let repository = CardanoTransactionRepository::new(Arc::new(
1362 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
1363 ));
1364 let range = BlockRange::from_block_number(BlockNumber(0));
1365
1366 repository
1367 .create_legacy_block_range_roots(vec![(
1368 range.clone(),
1369 MKTreeNode::from_hex("AAAA").unwrap(),
1370 )])
1371 .await
1372 .unwrap();
1373 repository
1374 .create_legacy_block_range_roots(vec![(
1375 range.clone(),
1376 MKTreeNode::from_hex("BBBB").unwrap(),
1377 )])
1378 .await
1379 .unwrap();
1380
1381 let record: Vec<BlockRangeRootRecord> = repository
1382 .connection_pool
1383 .connection()
1384 .unwrap()
1385 .fetch_collect(GetLegacyBlockRangeRootQuery::all())
1386 .unwrap();
1387 assert_eq!(
1388 vec![BlockRangeRootRecord {
1389 range,
1390 merkle_root: MKTreeNode::from_hex("AAAA").unwrap()
1391 }],
1392 record
1393 );
1394 }
1395
1396 #[tokio::test]
1397 async fn repository_retrieve_legacy_block_range_roots_up_to() {
1398 let temp_dir = temp_dir_create!();
1399 let repository = CardanoTransactionRepository::new(Arc::new(
1400 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
1401 ));
1402
1403 let block_range_roots = vec![
1404 (
1405 BlockRange::from_block_number(BlockNumber(15)),
1406 MKTreeNode::from_hex("AAAA").unwrap(),
1407 ),
1408 (
1409 BlockRange::from_block_number(BlockNumber(30)),
1410 MKTreeNode::from_hex("BBBB").unwrap(),
1411 ),
1412 (
1413 BlockRange::from_block_number(BlockNumber(45)),
1414 MKTreeNode::from_hex("CCCC").unwrap(),
1415 ),
1416 ];
1417 repository
1418 .create_legacy_block_range_roots(block_range_roots.clone())
1419 .await
1420 .unwrap();
1421
1422 let retrieved_block_ranges = repository
1423 .retrieve_legacy_block_range_roots_up_to(BlockNumber(45))
1424 .await
1425 .unwrap();
1426 assert_eq!(
1427 block_range_roots[0..2].to_vec(),
1428 retrieved_block_ranges.collect::<Vec<_>>()
1429 );
1430 }
1431
1432 #[tokio::test]
1433 async fn repository_retrieve_highest_legacy_block_range_roots() {
1434 let temp_dir = temp_dir_create!();
1435 let repository = CardanoTransactionRepository::new(Arc::new(
1436 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
1437 ));
1438
1439 let block_range_roots = vec![
1440 BlockRangeRootRecord {
1441 range: BlockRange::from_block_number(BlockNumber(15)),
1442 merkle_root: MKTreeNode::from_hex("AAAA").unwrap(),
1443 },
1444 BlockRangeRootRecord {
1445 range: BlockRange::from_block_number(BlockNumber(30)),
1446 merkle_root: MKTreeNode::from_hex("BBBB").unwrap(),
1447 },
1448 BlockRangeRootRecord {
1449 range: BlockRange::from_block_number(BlockNumber(45)),
1450 merkle_root: MKTreeNode::from_hex("CCCC").unwrap(),
1451 },
1452 ];
1453 repository
1454 .create_legacy_block_range_roots(block_range_roots.clone())
1455 .await
1456 .unwrap();
1457
1458 let retrieved_block_range =
1459 repository.retrieve_highest_legacy_block_range_root().await.unwrap();
1460 assert_eq!(block_range_roots.last().cloned(), retrieved_block_range);
1461 }
1462
1463 #[tokio::test]
1464 async fn repository_store_block_range() {
1465 let temp_dir = temp_dir_create!();
1466 let repository = CardanoTransactionRepository::new(Arc::new(
1467 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
1468 ));
1469
1470 repository
1471 .create_block_range_roots(vec![
1472 (
1473 BlockRange::from_block_number(BlockNumber(0)),
1474 MKTreeNode::from_hex("AAAA").unwrap(),
1475 ),
1476 (
1477 BlockRange::from_block_number(BlockRange::LENGTH),
1478 MKTreeNode::from_hex("BBBB").unwrap(),
1479 ),
1480 ])
1481 .await
1482 .unwrap();
1483
1484 let connection = repository.connection_pool.connection().unwrap();
1485 let records: Vec<BlockRangeRootRecord> =
1486 connection.fetch_collect(GetBlockRangeRootQuery::all()).unwrap();
1487 assert_eq!(
1488 vec![
1489 BlockRangeRootRecord {
1490 range: BlockRange::from_block_number(BlockNumber(0)),
1491 merkle_root: MKTreeNode::from_hex("AAAA").unwrap(),
1492 },
1493 BlockRangeRootRecord {
1494 range: BlockRange::from_block_number(BlockRange::LENGTH),
1495 merkle_root: MKTreeNode::from_hex("BBBB").unwrap(),
1496 }
1497 ],
1498 records
1499 );
1500 }
1501
1502 #[tokio::test]
1503 async fn repository_store_block_range_with_existing_hash_doesnt_erase_existing_data() {
1504 let temp_dir = temp_dir_create!();
1505 let repository = CardanoTransactionRepository::new(Arc::new(
1506 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
1507 ));
1508 let range = BlockRange::from_block_number(BlockNumber(0));
1509
1510 repository
1511 .create_block_range_roots(vec![(range.clone(), MKTreeNode::from_hex("AAAA").unwrap())])
1512 .await
1513 .unwrap();
1514 repository
1515 .create_block_range_roots(vec![(range.clone(), MKTreeNode::from_hex("BBBB").unwrap())])
1516 .await
1517 .unwrap();
1518
1519 let record: Vec<BlockRangeRootRecord> = repository
1520 .connection_pool
1521 .connection()
1522 .unwrap()
1523 .fetch_collect(GetBlockRangeRootQuery::all())
1524 .unwrap();
1525 assert_eq!(
1526 vec![BlockRangeRootRecord {
1527 range,
1528 merkle_root: MKTreeNode::from_hex("AAAA").unwrap()
1529 }],
1530 record
1531 );
1532 }
1533
1534 #[tokio::test]
1535 async fn repository_retrieve_block_range_roots_up_to() {
1536 let temp_dir = temp_dir_create!();
1537 let repository = CardanoTransactionRepository::new(Arc::new(
1538 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
1539 ));
1540 let block_range_roots = vec![
1541 (
1542 BlockRange::from_block_number(BlockNumber(15)),
1543 MKTreeNode::from_hex("AAAA").unwrap(),
1544 ),
1545 (
1546 BlockRange::from_block_number(BlockNumber(30)),
1547 MKTreeNode::from_hex("BBBB").unwrap(),
1548 ),
1549 (
1550 BlockRange::from_block_number(BlockNumber(45)),
1551 MKTreeNode::from_hex("CCCC").unwrap(),
1552 ),
1553 ];
1554 repository
1555 .create_block_range_roots(block_range_roots.clone())
1556 .await
1557 .unwrap();
1558
1559 let retrieved_block_ranges = repository
1560 .retrieve_block_range_roots_up_to(BlockNumber(45))
1561 .await
1562 .unwrap();
1563 assert_eq!(
1564 block_range_roots[0..2].to_vec(),
1565 retrieved_block_ranges.collect::<Vec<_>>()
1566 );
1567 }
1568
1569 #[tokio::test]
1570 async fn repository_retrieve_highest_block_range_roots() {
1571 let temp_dir = temp_dir_create!();
1572 let repository = CardanoTransactionRepository::new(Arc::new(
1573 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
1574 ));
1575 let block_range_roots = vec![
1576 BlockRangeRootRecord {
1577 range: BlockRange::from_block_number(BlockNumber(15)),
1578 merkle_root: MKTreeNode::from_hex("AAAA").unwrap(),
1579 },
1580 BlockRangeRootRecord {
1581 range: BlockRange::from_block_number(BlockNumber(30)),
1582 merkle_root: MKTreeNode::from_hex("BBBB").unwrap(),
1583 },
1584 BlockRangeRootRecord {
1585 range: BlockRange::from_block_number(BlockNumber(45)),
1586 merkle_root: MKTreeNode::from_hex("CCCC").unwrap(),
1587 },
1588 ];
1589 repository
1590 .create_block_range_roots(block_range_roots.clone())
1591 .await
1592 .unwrap();
1593
1594 let retrieved_block_range = repository.retrieve_highest_block_range_root().await.unwrap();
1595 assert_eq!(block_range_roots.last().cloned(), retrieved_block_range);
1596 }
1597
1598 #[tokio::test]
1599 async fn repository_prune_blocks_and_transactions() {
1600 let temp_dir = temp_dir_create!();
1601 let repository = CardanoTransactionRepository::new(Arc::new(
1602 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
1603 ));
1604
1605 let blocks = vec![
1606 CardanoBlockWithTransactions::new(
1607 "block_hash-1",
1608 BlockNumber(24),
1609 SlotNumber(50),
1610 vec!["tx_hash-1"],
1611 ),
1612 CardanoBlockWithTransactions::new(
1613 "block_hash-2",
1614 BlockNumber(25),
1615 SlotNumber(51),
1616 vec!["tx_hash-2"],
1617 ),
1618 CardanoBlockWithTransactions::new(
1619 "block_hash-3",
1620 BlockNumber(26),
1621 SlotNumber(52),
1622 vec!["tx_hash-3", "tx_hash-4"],
1623 ),
1624 ];
1625 repository.create_block_and_transactions(blocks).await.unwrap();
1626 repository
1628 .create_legacy_block_range_roots(vec![(
1629 BlockRange::from_block_number(BlockNumber(45)),
1630 MKTreeNode::from_hex("BBBB").unwrap(),
1631 )])
1632 .await
1633 .unwrap();
1634
1635 let stored_transactions = repository.get_all_transactions().await.unwrap();
1636 assert_eq!(4, stored_transactions.len());
1637 let stored_blocks = repository.get_all_blocks().await.unwrap();
1638 assert_eq!(3, stored_blocks.len());
1639
1640 repository.prune_transaction(BlockNumber(10_000_000)).await.unwrap();
1643 let stored_transactions = repository.get_all_transactions().await.unwrap();
1644 assert_eq!(4, stored_transactions.len());
1645 let stored_blocks = repository.get_all_blocks().await.unwrap();
1646 assert_eq!(3, stored_blocks.len());
1647
1648 repository.prune_transaction(BlockNumber(20)).await.unwrap();
1651 let transaction_result = repository
1652 .get_transactions_in_range_blocks(BlockNumber(0)..BlockNumber(25))
1653 .await
1654 .unwrap();
1655 assert_eq!(Vec::<CardanoTransactionRecord>::new(), transaction_result);
1656
1657 let transaction_result = repository
1658 .get_transactions_in_range_blocks(BlockNumber(25)..BlockNumber(1000))
1659 .await
1660 .unwrap();
1661 assert_eq!(3, transaction_result.len());
1662
1663 let stored_blocks = repository.get_all_blocks().await.unwrap();
1664 assert_eq!(2, stored_blocks.len());
1665 }
1666
1667 #[tokio::test]
1668 async fn get_prune_blocks_threshold() {
1669 let temp_dir = temp_dir_create!();
1670 let repository = CardanoTransactionRepository::new(Arc::new(
1671 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
1672 ));
1673
1674 {
1676 let highest = repository.get_prune_blocks_threshold().await.unwrap();
1677 assert_eq!(None, highest);
1678 }
1679 {
1681 repository
1682 .create_block_range_roots(vec![
1683 (
1684 BlockRange::from_block_number(BlockNumber(45)),
1685 MKTreeNode::from_hex("AAAA").unwrap(),
1686 ),
1687 (
1688 BlockRange::from_block_number(BlockNumber(60)),
1689 MKTreeNode::from_hex("BBBB").unwrap(),
1690 ),
1691 ])
1692 .await
1693 .unwrap();
1694 let highest = repository.get_prune_blocks_threshold().await.unwrap();
1695 assert_eq!(Some(BlockNumber(60)), highest);
1696 }
1697 {
1699 repository
1700 .create_legacy_block_range_roots(vec![(
1701 BlockRange::from_block_number(BlockNumber(30)),
1702 MKTreeNode::from_hex("DDDD").unwrap(),
1703 )])
1704 .await
1705 .unwrap();
1706
1707 let highest = repository.get_prune_blocks_threshold().await.unwrap();
1708 assert_eq!(Some(BlockNumber(30)), highest);
1709 }
1710 {
1712 repository
1713 .create_legacy_block_range_roots(vec![(
1714 BlockRange::from_block_number(BlockNumber(75)),
1715 MKTreeNode::from_hex("CCCC").unwrap(),
1716 )])
1717 .await
1718 .unwrap();
1719
1720 let highest = repository.get_prune_blocks_threshold().await.unwrap();
1721 assert_eq!(Some(BlockNumber(60)), highest);
1722 }
1723 }
1724
1725 #[tokio::test]
1726 async fn remove_blocks_transactions_and_block_ranges_greater_than_given_block_number() {
1727 let temp_dir = temp_dir_create!();
1728 let repository = CardanoTransactionRepository::new(Arc::new(
1729 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
1730 ));
1731
1732 let blocks = vec![
1733 CardanoBlockWithTransactions::new(
1734 "block_hash-1",
1735 BlockRange::LENGTH,
1736 SlotNumber(50),
1737 vec!["tx_hash-1", "tx_hash-2"],
1738 ),
1739 CardanoBlockWithTransactions::new(
1740 "block_hash-2",
1741 BlockRange::LENGTH * 3,
1742 SlotNumber(51),
1743 vec!["tx_hash-3", "tx_hash-4"],
1744 ),
1745 CardanoBlockWithTransactions::new(
1746 "block_hash-3",
1747 BlockRange::LENGTH * 3 + 1,
1748 SlotNumber(52),
1749 vec!["tx_hash-5", "tx_hash-6"],
1750 ),
1751 ];
1752 repository.create_block_and_transactions(blocks).await.unwrap();
1753 repository
1754 .create_legacy_block_range_roots(vec![
1755 (
1756 BlockRange::from_block_number(BlockRange::LENGTH),
1757 MKTreeNode::from_hex("AAAA").unwrap(),
1758 ),
1759 (
1760 BlockRange::from_block_number(BlockRange::LENGTH * 2),
1761 MKTreeNode::from_hex("AAAA").unwrap(),
1762 ),
1763 (
1764 BlockRange::from_block_number(BlockRange::LENGTH * 3),
1765 MKTreeNode::from_hex("AAAA").unwrap(),
1766 ),
1767 ])
1768 .await
1769 .unwrap();
1770 repository
1771 .create_block_range_roots(vec![
1772 (
1773 BlockRange::from_block_number(BlockNumber(0)),
1774 MKTreeNode::from_hex("9999").unwrap(),
1775 ),
1776 (
1777 BlockRange::from_block_number(BlockRange::LENGTH),
1778 MKTreeNode::from_hex("AAAA").unwrap(),
1779 ),
1780 (
1781 BlockRange::from_block_number(BlockRange::LENGTH * 2),
1782 MKTreeNode::from_hex("AAAA").unwrap(),
1783 ),
1784 (
1785 BlockRange::from_block_number(BlockRange::LENGTH * 3),
1786 MKTreeNode::from_hex("AAAA").unwrap(),
1787 ),
1788 ])
1789 .await
1790 .unwrap();
1791
1792 repository
1793 .remove_rolled_back_transactions_and_block_range_by_block_number(BlockRange::LENGTH * 3)
1794 .await
1795 .unwrap();
1796 assert_eq!(4, repository.get_all_transactions().await.unwrap().len());
1797 assert_eq!(
1798 2,
1799 repository.get_all_legacy_block_range_root().unwrap().len()
1800 );
1801 assert_eq!(3, repository.get_all_block_range_root().unwrap().len());
1802 assert_eq!(2, repository.get_all_blocks().await.unwrap().len());
1803 }
1804
1805 #[tokio::test]
1806 async fn remove_rolled_back_blocks_transactions_and_block_range_by_slot_number() {
1807 fn transaction_record(
1808 block_number: BlockNumber,
1809 slot_number: SlotNumber,
1810 tx_hash: &str,
1811 ) -> CardanoTransactionRecord {
1812 CardanoTransactionRecord::new(
1813 tx_hash,
1814 block_number,
1815 slot_number,
1816 format!("block_hash-{block_number}"),
1817 )
1818 }
1819
1820 let temp_dir = temp_dir_create!();
1821 let repository = CardanoTransactionRepository::new(Arc::new(
1822 cardano_tx_db_connection_builder(&temp_dir).build_pool(1).unwrap(),
1823 ));
1824
1825 let blocks = vec![
1826 CardanoBlockWithTransactions::new(
1827 "block_hash-10",
1828 BlockNumber(10),
1829 SlotNumber(50),
1830 vec!["tx_hash-1"],
1831 ),
1832 CardanoBlockWithTransactions::new(
1833 "block_hash-13",
1834 BlockNumber(13),
1835 SlotNumber(52),
1836 vec!["tx_hash-2"],
1837 ),
1838 CardanoBlockWithTransactions::new(
1839 "block_hash-101",
1840 BlockNumber(101),
1841 SlotNumber(100),
1842 vec!["tx_hash-3", "tx_hash-4"],
1843 ),
1844 ];
1845 repository.create_block_and_transactions(blocks).await.unwrap();
1846
1847 {
1848 repository
1849 .remove_rolled_back_blocks_transactions_and_block_range_by_slot_number(SlotNumber(
1850 110,
1851 ))
1852 .await
1853 .expect("Failed to remove rolled back transactions");
1854
1855 let transactions = repository.get_all_transactions().await.unwrap();
1856 assert_eq!(
1857 vec![
1858 transaction_record(BlockNumber(10), SlotNumber(50), "tx_hash-1"),
1859 transaction_record(BlockNumber(13), SlotNumber(52), "tx_hash-2"),
1860 transaction_record(BlockNumber(101), SlotNumber(100), "tx_hash-3"),
1861 transaction_record(BlockNumber(101), SlotNumber(100), "tx_hash-4"),
1862 ],
1863 transactions
1864 );
1865 }
1866
1867 {
1868 repository
1869 .remove_rolled_back_blocks_transactions_and_block_range_by_slot_number(SlotNumber(
1870 53,
1871 ))
1872 .await
1873 .expect("Failed to remove rolled back transactions");
1874
1875 let transactions = repository.get_all_transactions().await.unwrap();
1876 assert_eq!(
1877 vec![
1878 transaction_record(BlockNumber(10), SlotNumber(50), "tx_hash-1"),
1879 transaction_record(BlockNumber(13), SlotNumber(52), "tx_hash-2"),
1880 ],
1881 transactions
1882 );
1883 }
1884
1885 {
1886 repository
1887 .remove_rolled_back_blocks_transactions_and_block_range_by_slot_number(SlotNumber(
1888 51,
1889 ))
1890 .await
1891 .expect("Failed to remove rolled back transactions");
1892
1893 let transactions = repository.get_all_transactions().await.unwrap();
1894 assert_eq!(
1895 vec![transaction_record(BlockNumber(10), SlotNumber(50), "tx_hash-1")],
1896 transactions
1897 );
1898 }
1899 }
1900}