Skip to main content

mithril_common/entities/
cardano_block_transaction_mktree_node.rs

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