mithril_signer/database/repository/
cardano_transaction_repository.rs

1use std::collections::BTreeSet;
2use std::ops::{Deref, Range};
3use std::sync::Arc;
4
5use mithril_cardano_node_chain::chain_importer::{
6    ChainDataPruner, ChainDataStore, HighestStoredBlockNumberGetter,
7};
8use mithril_common::StdResult;
9use mithril_common::crypto_helper::{MKTreeNode, MKTreeStorer};
10use mithril_common::entities::{
11    BlockNumber, BlockRange, CardanoBlockTransactionMkTreeNode, CardanoBlockWithTransactions,
12    CardanoTransaction, ChainPoint, SlotNumber,
13};
14use mithril_common::signable_builder::{BlockRangeRootRetriever, LegacyBlockRangeRootRetriever};
15use mithril_persistence::database::repository::CardanoTransactionRepository;
16use mithril_persistence::sqlite::SqliteConnectionPool;
17
18/// Wrapper around [CardanoTransactionRepository] to allow traits implementations
19pub struct SignerCardanoChainDataRepository {
20    inner: CardanoTransactionRepository,
21}
22
23impl SignerCardanoChainDataRepository {
24    /// Instantiate a new `SignerCardanoChainDataRepository`
25    pub fn new(connection_pool: Arc<SqliteConnectionPool>) -> Self {
26        Self {
27            inner: CardanoTransactionRepository::new(connection_pool),
28        }
29    }
30}
31
32impl Deref for SignerCardanoChainDataRepository {
33    type Target = CardanoTransactionRepository;
34
35    fn deref(&self) -> &Self::Target {
36        &self.inner
37    }
38}
39
40#[async_trait::async_trait]
41impl ChainDataStore for SignerCardanoChainDataRepository {
42    async fn get_highest_beacon(&self) -> StdResult<Option<ChainPoint>> {
43        self.inner.get_transaction_highest_chain_point().await
44    }
45
46    async fn get_highest_block_range(&self) -> StdResult<Option<BlockRange>> {
47        let record = self.inner.retrieve_highest_block_range_root().await?;
48        Ok(record.map(|record| record.range))
49    }
50
51    async fn get_highest_legacy_block_range(&self) -> StdResult<Option<BlockRange>> {
52        let record = self.inner.retrieve_highest_legacy_block_range_root().await?;
53        Ok(record.map(|record| record.range))
54    }
55
56    async fn store_blocks_and_transactions(
57        &self,
58        block_with_transactions: Vec<CardanoBlockWithTransactions>,
59    ) -> StdResult<()> {
60        self.inner
61            .store_blocks_and_transactions(block_with_transactions)
62            .await
63    }
64
65    async fn get_blocks_and_transactions_in_range(
66        &self,
67        range: Range<BlockNumber>,
68    ) -> StdResult<BTreeSet<CardanoBlockTransactionMkTreeNode>> {
69        let records = self.inner.get_blocks_with_transactions_in_range_blocks(range).await?;
70        Ok(records.into_iter().flat_map(|b| b.into_mk_tree_nodes()).collect())
71    }
72
73    async fn get_transactions_in_range(
74        &self,
75        range: Range<BlockNumber>,
76    ) -> StdResult<Vec<CardanoTransaction>> {
77        self.get_transactions_in_range_blocks(range).await.map(|v| {
78            v.into_iter()
79                .map(|record| record.into())
80                .collect::<Vec<CardanoTransaction>>()
81        })
82    }
83
84    async fn store_block_range_roots(
85        &self,
86        block_ranges: Vec<(BlockRange, MKTreeNode)>,
87    ) -> StdResult<()> {
88        if !block_ranges.is_empty() {
89            self.inner.create_block_range_roots(block_ranges).await?;
90        }
91        Ok(())
92    }
93
94    async fn store_legacy_block_range_roots(
95        &self,
96        block_ranges: Vec<(BlockRange, MKTreeNode)>,
97    ) -> StdResult<()> {
98        if !block_ranges.is_empty() {
99            self.inner.create_legacy_block_range_roots(block_ranges).await?;
100        }
101        Ok(())
102    }
103
104    async fn remove_rolled_chain_data_and_block_range(
105        &self,
106        slot_number: SlotNumber,
107    ) -> StdResult<()> {
108        self.inner
109            .remove_rolled_back_blocks_transactions_and_block_range_by_slot_number(slot_number)
110            .await
111    }
112}
113
114#[async_trait::async_trait]
115impl ChainDataPruner for SignerCardanoChainDataRepository {
116    async fn prune(&self, number_of_blocks_to_keep: BlockNumber) -> StdResult<()> {
117        self.inner.prune_transaction(number_of_blocks_to_keep).await
118    }
119}
120
121#[async_trait::async_trait]
122impl HighestStoredBlockNumberGetter for SignerCardanoChainDataRepository {
123    async fn get(&self) -> StdResult<Option<BlockNumber>> {
124        let highest_chain_point = self.inner.get_transaction_highest_chain_point().await?;
125        Ok(highest_chain_point.map(|c| c.block_number))
126    }
127}
128
129#[async_trait::async_trait]
130impl<S: MKTreeStorer> LegacyBlockRangeRootRetriever<S> for SignerCardanoChainDataRepository {
131    async fn retrieve_block_range_roots<'a>(
132        &'a self,
133        up_to_beacon: BlockNumber,
134    ) -> StdResult<Box<dyn Iterator<Item = (BlockRange, MKTreeNode)> + 'a>> {
135        self.inner.retrieve_legacy_block_range_roots_up_to(up_to_beacon).await
136    }
137}
138
139#[async_trait::async_trait]
140impl<S: MKTreeStorer> BlockRangeRootRetriever<S> for SignerCardanoChainDataRepository {
141    async fn retrieve_block_range_roots<'a>(
142        &'a self,
143        up_to_beacon: BlockNumber,
144    ) -> StdResult<Box<dyn Iterator<Item = (BlockRange, MKTreeNode)> + 'a>> {
145        self.inner.retrieve_block_range_roots_up_to(up_to_beacon).await
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use mithril_cardano_node_chain::chain_importer::{CardanoChainDataImporter, ChainDataImporter};
152    use mithril_cardano_node_chain::entities::ScannedBlock;
153    use mithril_cardano_node_chain::test::double::DumbBlockScanner;
154
155    use crate::database::test_helper::cardano_tx_db_connection;
156    use crate::test::TestLogger;
157
158    use super::*;
159
160    fn into_transactions(blocks: &[ScannedBlock]) -> Vec<CardanoTransaction> {
161        blocks.iter().flat_map(|b| b.clone().into_transactions()).collect()
162    }
163
164    #[tokio::test]
165    async fn importing_twice_starting_with_nothing_in_a_real_db_should_yield_transactions_in_same_order()
166     {
167        let blocks = vec![
168            ScannedBlock::new(
169                "block_hash-1",
170                BlockNumber(10),
171                SlotNumber(15),
172                vec!["tx_hash-1", "tx_hash-2"],
173            ),
174            ScannedBlock::new(
175                "block_hash-2",
176                BlockNumber(20),
177                SlotNumber(25),
178                vec!["tx_hash-3", "tx_hash-4"],
179            ),
180        ];
181        let up_to_block_number = BlockNumber(1000);
182        let transactions = into_transactions(&blocks);
183
184        let (importer, repository) = {
185            let connection = cardano_tx_db_connection().unwrap();
186            let connection_pool = Arc::new(SqliteConnectionPool::build_from_connection(connection));
187            let repository = Arc::new(SignerCardanoChainDataRepository::new(connection_pool));
188            let importer = CardanoChainDataImporter::new(
189                Arc::new(DumbBlockScanner::new().forwards(vec![blocks.clone()])),
190                repository.clone(),
191                TestLogger::stdout(),
192            );
193            (importer, repository)
194        };
195
196        importer
197            .import(up_to_block_number)
198            .await
199            .expect("Transactions Importer should succeed");
200        let cold_imported_transactions = repository.get_all().await.unwrap();
201
202        importer
203            .import(up_to_block_number)
204            .await
205            .expect("Transactions Importer should succeed");
206        let warm_imported_transactions = repository.get_all().await.unwrap();
207
208        assert_eq!(transactions, cold_imported_transactions);
209        assert_eq!(cold_imported_transactions, warm_imported_transactions);
210    }
211}