mithril_signer/database/repository/
cardano_transaction_repository.rs1use 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
18pub struct SignerCardanoChainDataRepository {
20 inner: CardanoTransactionRepository,
21}
22
23impl SignerCardanoChainDataRepository {
24 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}