mithril_common/entities/
cardano_block.rs

1use std::cmp::Ordering;
2
3use crate::crypto_helper::MKTreeNode;
4use crate::entities::{BlockHash, BlockNumber, CardanoTransaction, SlotNumber, TransactionHash};
5
6/// Cardano block representation
7#[derive(Debug, Clone, PartialEq)]
8pub struct CardanoBlock {
9    /// Block hash
10    pub block_hash: BlockHash,
11    /// Block number
12    pub block_number: BlockNumber,
13    /// Slot number of the block
14    pub slot_number: SlotNumber,
15}
16
17impl CardanoBlock {
18    /// CardanoBlock factory
19    pub fn new<U: Into<BlockHash>>(
20        block_hash: U,
21        block_number: BlockNumber,
22        slot_number: SlotNumber,
23    ) -> Self {
24        Self {
25            block_hash: block_hash.into(),
26            block_number,
27            slot_number,
28        }
29    }
30}
31
32/// Cardano block representation, including the hashes of the transactions in the block
33#[derive(Debug, Clone, PartialEq)]
34pub struct CardanoBlockWithTransactions {
35    /// Block hash
36    pub block_hash: BlockHash,
37    /// Block number
38    pub block_number: BlockNumber,
39    /// Slot number of the block
40    pub slot_number: SlotNumber,
41    /// Hashes of the transactions in the block
42    pub transactions_hashes: Vec<TransactionHash>,
43}
44
45impl CardanoBlockWithTransactions {
46    /// CardanoBlockWithTransactions factory
47    pub fn new<U: Into<BlockHash>, T: Into<TransactionHash>>(
48        block_hash: U,
49        block_number: BlockNumber,
50        slot_number: SlotNumber,
51        tx_hashes: Vec<T>,
52    ) -> Self {
53        Self {
54            block_hash: block_hash.into(),
55            block_number,
56            slot_number,
57            transactions_hashes: tx_hashes.into_iter().map(Into::into).collect(),
58        }
59    }
60
61    /// Converts the block into a vector of transactions.
62    pub fn into_transactions(self) -> Vec<CardanoTransaction> {
63        self.transactions_hashes
64            .into_iter()
65            .map(|tx_hash| {
66                CardanoTransaction::new(
67                    tx_hash,
68                    self.block_number,
69                    self.slot_number,
70                    self.block_hash.clone(),
71                )
72            })
73            .collect()
74    }
75
76    /// Converts the block into a vector of [CardanoBlockTransactionMkTreeNode].
77    pub fn into_mk_tree_node(self) -> Vec<CardanoBlockTransactionMkTreeNode> {
78        let mut result = Vec::with_capacity(self.transactions_hashes.len() + 1);
79        result.push(CardanoBlockTransactionMkTreeNode::Block {
80            block_hash: self.block_hash.clone(),
81            block_number: self.block_number,
82            slot_number: self.slot_number,
83        });
84        result.extend(self.transactions_hashes.into_iter().map(|tx_hash| {
85            CardanoBlockTransactionMkTreeNode::Transaction {
86                transaction_hash: tx_hash,
87                block_hash: self.block_hash.clone(),
88                block_number: self.block_number,
89                slot_number: self.slot_number,
90            }
91        }));
92
93        result
94    }
95
96    /// Returns the number of transactions in the block.
97    pub fn transactions_count(&self) -> usize {
98        self.transactions_hashes.len()
99    }
100}
101
102/// Leaf of the Merkle tree representing blocks and transactions
103///
104/// When ordering in collections:
105/// - all blocks are first, then all transactions
106/// - blocks are ordered by block number, then slot number, then block hash
107/// - transactions are ordered by block number, then slot number, then block hash, then transaction hash
108#[derive(Debug, Clone, PartialEq, Eq)]
109pub enum CardanoBlockTransactionMkTreeNode {
110    /// Leaf representing a block
111    Block {
112        /// Block hash
113        block_hash: BlockHash,
114        /// Block number
115        block_number: BlockNumber,
116        /// Slot number of the block
117        slot_number: SlotNumber,
118    },
119    /// Leaf representing a transaction
120    Transaction {
121        /// Unique hash of the transaction
122        transaction_hash: TransactionHash,
123        /// Block number of the transaction
124        block_number: BlockNumber,
125        /// Slot number of the transaction
126        slot_number: SlotNumber,
127        /// Block hash of the transaction
128        block_hash: BlockHash,
129    },
130}
131
132impl CardanoBlockTransactionMkTreeNode {
133    fn leaf_identifier(&self) -> Vec<u8> {
134        match self {
135            Self::Block {
136                block_hash,
137                block_number,
138                slot_number,
139            } => format!("Block/{block_hash}/{block_number}/{slot_number}").into_bytes(),
140            Self::Transaction {
141                transaction_hash,
142                block_hash,
143                block_number,
144                slot_number,
145            } => format!("Tx/{transaction_hash}/{block_hash}/{block_number}/{slot_number}",)
146                .into_bytes(),
147        }
148    }
149}
150
151impl From<CardanoBlockTransactionMkTreeNode> for MKTreeNode {
152    fn from(value: CardanoBlockTransactionMkTreeNode) -> Self {
153        MKTreeNode::new(value.leaf_identifier())
154    }
155}
156
157impl PartialOrd for CardanoBlockTransactionMkTreeNode {
158    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
159        Some(Ord::cmp(self, other))
160    }
161}
162
163impl Ord for CardanoBlockTransactionMkTreeNode {
164    fn cmp(&self, other: &Self) -> Ordering {
165        use CardanoBlockTransactionMkTreeNode::{Block, Transaction};
166
167        match (self, other) {
168            (Block { .. }, Transaction { .. }) => Ordering::Less,
169            (Transaction { .. }, Block { .. }) => Ordering::Greater,
170            (
171                Block {
172                    block_number,
173                    block_hash,
174                    slot_number,
175                },
176                Block {
177                    block_number: other_block_number,
178                    block_hash: other_block_hash,
179                    slot_number: other_slot_number,
180                },
181            ) => block_number
182                .cmp(other_block_number)
183                .then(slot_number.cmp(other_slot_number))
184                .then(block_hash.cmp(other_block_hash)),
185            (
186                Transaction {
187                    block_number,
188                    slot_number,
189                    block_hash,
190                    transaction_hash,
191                },
192                Transaction {
193                    block_number: other_block_number,
194                    slot_number: other_slot_number,
195                    block_hash: other_block_hash,
196                    transaction_hash: other_transaction_hash,
197                },
198            ) => block_number
199                .cmp(other_block_number)
200                .then(slot_number.cmp(other_slot_number))
201                .then(block_hash.cmp(other_block_hash))
202                .then(transaction_hash.cmp(other_transaction_hash)),
203        }
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    macro_rules! block_node {
212        (num: $block_number:expr, slot: $slot_number:expr) => {
213            block_node!(hash:format!("block_hash-{}", $block_number), num: $block_number, slot: $slot_number)
214        };
215        (hash: $block_hash:expr, num: $block_number:expr, slot: $slot_number:expr) => {
216            CardanoBlockTransactionMkTreeNode::Block {
217                block_hash: $block_hash.to_string(),
218                block_number: BlockNumber($block_number),
219                slot_number: SlotNumber($slot_number),
220            }
221        };
222    }
223
224    macro_rules! tx_node {
225        (hash: $tx_hash:expr, block: $block_number:expr, slot: $slot_number:expr) => {
226            tx_node!(hash: $tx_hash, block_hash: format!("block_hash-{}", $tx_hash), block: $block_number, slot: $slot_number)
227        };
228        (hash: $tx_hash:expr, block_hash: $block_hash:expr, block: $block_number:expr, slot: $slot_number:expr) => {
229            CardanoBlockTransactionMkTreeNode::Transaction {
230                transaction_hash: $tx_hash.to_string(),
231                block_hash: $block_hash.to_string(),
232                block_number: BlockNumber($block_number),
233                slot_number: SlotNumber($slot_number),
234            }
235        };
236    }
237
238    #[test]
239    fn block_node_leaf_identifier() {
240        // expected: "Block"/BlockHash/BlockNumber/SlotNumber
241        assert_eq!(
242            "Block/block_hash-5/5/6".to_string().into_bytes(),
243            block_node!(hash: "block_hash-5", num: 5, slot: 6).leaf_identifier()
244        );
245        assert_eq!(
246            "Block/block_hash-10/9/13".to_string().into_bytes(),
247            block_node!(hash: "block_hash-10", num: 9, slot: 13).leaf_identifier()
248        );
249    }
250
251    #[test]
252    fn transaction_node_leaf_identifier() {
253        // expected: "Tx"/TransactionHash/BlockHash/BlockNumber/SlotNumber
254        assert_eq!(
255            "Tx/tx_hash-5/block_hash-5/5/6".to_string().into_bytes(),
256            tx_node!(hash: "tx_hash-5", block_hash: "block_hash-5", block: 5, slot: 6)
257                .leaf_identifier()
258        );
259        assert_eq!(
260            "Tx/tx_hash-10/block_hash-10/9/13".to_string().into_bytes(),
261            tx_node!(hash: "tx_hash-10", block_hash: "block_hash-10", block: 9, slot: 13)
262                .leaf_identifier()
263        );
264    }
265
266    #[test]
267    fn convert_block_node_into_mktree_nodes() {
268        let block = block_node!(hash: "block_hash-5", num: 5, slot: 6);
269        let expected: MKTreeNode = MKTreeNode::new(block.leaf_identifier());
270
271        let computed_node: MKTreeNode = block.into();
272        assert_eq!(expected, computed_node);
273        assert_ne!(
274            computed_node,
275            block_node!(hash: "other", num: 5, slot: 6).into()
276        );
277        assert_ne!(
278            computed_node,
279            block_node!(hash: "block_hash-10", num: 1000, slot: 6).into()
280        );
281        assert_ne!(
282            computed_node,
283            block_node!(hash: "block_hash-10", num: 5, slot: 1000).into()
284        );
285    }
286
287    #[test]
288    fn convert_tx_node_into_mktree_nodes() {
289        let transaction =
290            tx_node!(hash: "tx_hash-5", block_hash: "block_hash-5", block: 5, slot: 6);
291        let expected: MKTreeNode = MKTreeNode::new(transaction.leaf_identifier());
292
293        let computed_node: MKTreeNode = transaction.into();
294        assert_eq!(expected, computed_node);
295        assert_ne!(
296            computed_node,
297            tx_node!(hash: "other", block_hash: "block_hash-5", block: 5, slot: 6).into(),
298        );
299        assert_ne!(
300            computed_node,
301            tx_node!(hash: "tx_hash-5", block_hash: "other", block: 5, slot: 6).into(),
302        );
303        assert_ne!(
304            computed_node,
305            tx_node!(hash: "tx_hash-5", block_hash: "block_hash-5", block: 1000, slot: 6).into(),
306        );
307        assert_ne!(
308            computed_node,
309            tx_node!(hash: "tx_hash-5", block_hash: "block_hash-5", block: 5, slot: 1000).into(),
310        );
311    }
312
313    mod mk_tree_node_ordering {
314        use super::*;
315
316        #[test]
317        fn same_value_yield_equal_order() {
318            let block = block_node!(num: 5, slot: 6);
319            let tx = tx_node!(hash: "tx_hash-1", block: 5, slot: 6);
320
321            assert_eq!(Ordering::Equal, block.cmp(&block));
322            assert_eq!(Ordering::Equal, tx.cmp(&tx));
323        }
324
325        #[test]
326        fn order_block_nodes_first_then_transaction_nodes() {
327            let block = block_node!(num: 5, slot: 6);
328            let tx = tx_node!(hash: "tx_hash-1", block: 5, slot: 6);
329
330            assert_eq!(Ordering::Less, block.cmp(&tx));
331            assert_eq!(Ordering::Greater, tx.cmp(&block));
332        }
333
334        #[test]
335        fn order_blocks_by_block_number_first() {
336            let block = block_node!(hash: "block_hash-5", num: 5, slot: 6);
337
338            assert_eq!(
339                Ordering::Less,
340                block.cmp(&block_node!(hash: "block_hash-1", num: 10, slot: 1))
341            );
342            assert_eq!(
343                Ordering::Greater,
344                block.cmp(&block_node!(hash: "block_hash-9", num: 1, slot: 9))
345            );
346        }
347
348        #[test]
349        fn order_blocks_by_slot_number_second() {
350            let block = block_node!(hash: "block_hash-5", num: 5, slot: 6);
351
352            assert_eq!(
353                Ordering::Less,
354                block.cmp(&block_node!(hash: "block_hash-1", num: 5, slot: 9))
355            );
356            assert_eq!(
357                Ordering::Greater,
358                block.cmp(&block_node!(hash: "block_hash-9", num: 5, slot: 1))
359            );
360        }
361
362        #[test]
363        fn order_blocks_by_block_hash_third() {
364            let block = block_node!(hash: "block_hash-5", num: 5, slot: 6);
365
366            assert_eq!(
367                Ordering::Less,
368                block.cmp(&block_node!(hash: "block_hash-9", num: 5, slot: 6))
369            );
370            assert_eq!(
371                Ordering::Greater,
372                block.cmp(&block_node!(hash: "block_hash-1", num: 5, slot: 6))
373            );
374        }
375
376        #[test]
377        fn order_transactions_by_block_number_first() {
378            let tx = tx_node!(hash: "tx_hash-5", block_hash: "block_hash-5", block: 5, slot: 6);
379
380            assert_eq!(
381                Ordering::Less,
382                tx.cmp(
383                    &tx_node!(hash: "tx_hash-1", block_hash: "block_hash-1", block: 10, slot: 1)
384                )
385            );
386            assert_eq!(
387                Ordering::Greater,
388                tx.cmp(&tx_node!(hash: "tx_hash-9", block_hash: "block_hash-9", block: 1, slot: 9))
389            );
390        }
391
392        #[test]
393        fn order_transactions_by_slot_number_second() {
394            let tx = tx_node!(hash: "tx_hash-5", block_hash: "block_hash-5", block: 5, slot: 6);
395
396            assert_eq!(
397                Ordering::Less,
398                tx.cmp(&tx_node!(hash: "tx_hash-1", block_hash: "block_hash-1", block: 5, slot: 9))
399            );
400            assert_eq!(
401                Ordering::Greater,
402                tx.cmp(&tx_node!(hash: "tx_hash-9", block_hash: "block_hash-9", block: 5, slot: 1))
403            );
404        }
405
406        #[test]
407        fn order_transactions_by_block_hash_third() {
408            let tx = tx_node!(hash: "tx_hash-5", block_hash: "block_hash-5", block: 5, slot: 6);
409
410            assert_eq!(
411                Ordering::Less,
412                tx.cmp(&tx_node!(hash: "tx_hash-1", block_hash: "block_hash-9", block: 5, slot: 6))
413            );
414            assert_eq!(
415                Ordering::Greater,
416                tx.cmp(&tx_node!(hash: "tx_hash-9", block_hash: "block_hash-1", block: 5, slot: 6))
417            );
418        }
419
420        #[test]
421        fn order_transactions_by_transaction_hash_fourth() {
422            let tx = tx_node!(hash: "tx_hash-5", block_hash: "block_hash-5", block: 5, slot: 6);
423
424            assert_eq!(
425                Ordering::Less,
426                tx.cmp(&tx_node!(hash: "tx_hash-9", block_hash: "block_hash-5", block: 5, slot: 6))
427            );
428            assert_eq!(
429                Ordering::Greater,
430                tx.cmp(&tx_node!(hash: "tx_hash-1", block_hash: "block_hash-5", block: 5, slot: 6))
431            );
432        }
433
434        #[test]
435        fn sorting_a_vec() {
436            let mut list = vec![
437                tx_node!(hash: "tx_hash-70", block: 300, slot: 35),
438                tx_node!(hash: "tx_hash-200", block: 100, slot: 100),
439                tx_node!(hash: "tx_hash-50", block: 200, slot: 25),
440                block_node!(num: 100, slot: 100),
441                tx_node!(hash: "tx_hash-100", block: 100, slot: 100),
442                block_node!(num: 200, slot: 35),
443                block_node!(num: 200, slot: 25),
444            ];
445            list.sort();
446
447            assert_eq!(
448                list,
449                vec![
450                    block_node!(num: 100, slot: 100),
451                    block_node!(num: 200, slot: 25),
452                    block_node!(num: 200, slot: 35),
453                    tx_node!(hash: "tx_hash-100", block: 100, slot: 100),
454                    tx_node!(hash: "tx_hash-200", block: 100, slot: 100),
455                    tx_node!(hash: "tx_hash-50", block: 200, slot: 25),
456                    tx_node!(hash: "tx_hash-70", block: 300, slot: 35),
457                ]
458            );
459        }
460    }
461}