mithril_persistence/database/repository/
cardano_transaction_repository.rs

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