mithril_persistence/database/record/
cardano_block_transactions.rs

1use sqlite::Row;
2
3use mithril_common::entities::{
4    BlockHash, BlockNumber, CardanoBlockTransactionMkTreeNode, CardanoBlockWithTransactions,
5    SlotNumber, TransactionHash,
6};
7
8use crate::database::Hydrator;
9use crate::sqlite::{HydrationError, Projection, SqLiteEntity};
10
11/// Cardano block record is the representation of a cardano block stored in the sqlite database.
12#[derive(Debug, PartialEq, Clone)]
13pub struct CardanoBlockTransactionsRecord {
14    /// Hash of the block
15    pub block_hash: BlockHash,
16
17    /// Number of the block
18    pub block_number: BlockNumber,
19
20    /// Slot number of the block
21    pub slot_number: SlotNumber,
22
23    /// List of transaction hashes included in the block
24    pub transactions_hashes: Vec<TransactionHash>,
25}
26
27impl CardanoBlockTransactionsRecord {
28    /// Factory
29    pub fn new<U: Into<BlockHash>, T: Into<TransactionHash>>(
30        block_hash: U,
31        block_number: BlockNumber,
32        slot_number: SlotNumber,
33        tx_hashes: Vec<T>,
34    ) -> Self {
35        Self {
36            block_hash: block_hash.into(),
37            block_number,
38            slot_number,
39            transactions_hashes: tx_hashes.into_iter().map(Into::into).collect(),
40        }
41    }
42}
43
44impl SqLiteEntity for CardanoBlockTransactionsRecord {
45    fn hydrate(row: Row) -> Result<Self, HydrationError>
46    where
47        Self: Sized,
48    {
49        let block_hash = row.read::<&str, _>(0);
50        let block_number =
51            Hydrator::try_to_u64("cardano_block.block_number", row.read::<i64, _>(1))?;
52        let slot_number = Hydrator::try_to_u64("cardano_block.slot_number", row.read::<i64, _>(2))?;
53        let transactions_hashes = row
54            .read::<Option<&str>, _>(3)
55            .map(|hashes| hashes.split(',').map(|s| s.to_string()).collect())
56            .unwrap_or_default();
57
58        Ok(Self {
59            block_hash: block_hash.to_string(),
60            block_number: BlockNumber(block_number),
61            slot_number: SlotNumber(slot_number),
62            transactions_hashes,
63        })
64    }
65
66    fn get_projection() -> Projection {
67        Projection::from(&[
68            ("block_hash", "{:cardano_block:}.block_hash", "text"),
69            ("block_number", "{:cardano_block:}.block_number", "int"),
70            ("slot_number", "{:cardano_block:}.slot_number", "int"),
71            (
72                "transactions_hashes",
73                "group_concat({:cardano_tx:}.transaction_hash, ',')",
74                "text",
75            ),
76        ])
77    }
78}
79
80impl From<CardanoBlockWithTransactions> for CardanoBlockTransactionsRecord {
81    fn from(block: CardanoBlockWithTransactions) -> Self {
82        Self::new(
83            block.block_hash,
84            block.block_number,
85            block.slot_number,
86            block.transactions_hashes,
87        )
88    }
89}
90
91impl CardanoBlockTransactionsRecord {
92    /// Converts the block into a vector of [CardanoBlockTransactionMkTreeNode].
93    pub fn into_mk_tree_nodes(self) -> Vec<CardanoBlockTransactionMkTreeNode> {
94        let mut result = Vec::with_capacity(self.transactions_hashes.len() + 1);
95        result.push(CardanoBlockTransactionMkTreeNode::Block {
96            block_hash: self.block_hash.clone(),
97            block_number: self.block_number,
98            slot_number: self.slot_number,
99        });
100        result.extend(self.transactions_hashes.into_iter().map(|tx_hash| {
101            CardanoBlockTransactionMkTreeNode::Transaction {
102                transaction_hash: tx_hash,
103                block_hash: self.block_hash.clone(),
104                block_number: self.block_number,
105                slot_number: self.slot_number,
106            }
107        }));
108
109        result
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn convert_blocks_without_transactions_to_mk_tree_node() {
119        assert_eq!(
120            vec![CardanoBlockTransactionMkTreeNode::Block {
121                block_hash: "block_hash-10".to_string(),
122                block_number: BlockNumber(10),
123                slot_number: SlotNumber(50),
124            }],
125            CardanoBlockTransactionsRecord::new(
126                "block_hash-10",
127                BlockNumber(10),
128                SlotNumber(50),
129                Vec::<&str>::new()
130            )
131            .into_mk_tree_nodes()
132        );
133    }
134
135    #[test]
136    fn convert_blocks_with_transactions_to_mk_tree_node() {
137        assert_eq!(
138            vec![
139                CardanoBlockTransactionMkTreeNode::Block {
140                    block_hash: "block_hash-10".to_string(),
141                    block_number: BlockNumber(10),
142                    slot_number: SlotNumber(50),
143                },
144                CardanoBlockTransactionMkTreeNode::Transaction {
145                    transaction_hash: "tx-1".to_string(),
146                    block_hash: "block_hash-10".to_string(),
147                    block_number: BlockNumber(10),
148                    slot_number: SlotNumber(50),
149                },
150                CardanoBlockTransactionMkTreeNode::Transaction {
151                    transaction_hash: "tx-2".to_string(),
152                    block_hash: "block_hash-10".to_string(),
153                    block_number: BlockNumber(10),
154                    slot_number: SlotNumber(50),
155                },
156            ],
157            CardanoBlockTransactionsRecord::new(
158                "block_hash-10",
159                BlockNumber(10),
160                SlotNumber(50),
161                vec!["tx-1", "tx-2"]
162            )
163            .into_mk_tree_nodes()
164        );
165    }
166}