Skip to main content

mithril_common/signable_builder/
cardano_blocks_transactions.rs

1use std::collections::BTreeSet;
2use std::ops::Range;
3use std::sync::Arc;
4
5use anyhow::Context;
6use async_trait::async_trait;
7
8use crate::{
9    StdResult,
10    crypto_helper::{MKMap, MKMapNode, MKTree, MKTreeNode, MKTreeStorer},
11    entities::{
12        BlockNumber, BlockNumberOffset, BlockRange, CardanoBlockTransactionMkTreeNode,
13        ProtocolMessage, ProtocolMessagePartKey,
14    },
15    signable_builder::SignableBuilder,
16};
17
18/// Cardano blocks and transactions importer
19#[cfg_attr(test, mockall::automock)]
20#[async_trait]
21pub trait BlocksTransactionsImporter: Send + Sync {
22    /// Import all transactions up to the given beacon into the system
23    async fn import(&self, up_to_beacon: BlockNumber) -> StdResult<()>;
24}
25
26/// Block Range Merkle roots retriever
27#[cfg_attr(test, mockall::automock)]
28#[async_trait]
29pub trait BlockRangeRootRetriever<S: MKTreeStorer>: Send + Sync {
30    /// Returns the Merkle root of the block ranges up to a given beacon
31    ///
32    /// Only complete block ranges should be considered
33    async fn retrieve_block_range_roots<'a>(
34        &'a self,
35        up_to_beacon: BlockNumber,
36    ) -> StdResult<Box<dyn Iterator<Item = (BlockRange, MKTreeNode)> + 'a>>;
37
38    /// Returns the all nodes in the given ranges of block numbers
39    async fn retrieve_block_ranges_nodes(
40        &self,
41        range: Range<BlockNumber>,
42    ) -> StdResult<BTreeSet<CardanoBlockTransactionMkTreeNode>>;
43
44    /// Returns a Merkle map of the block ranges roots up to a given beacon
45    async fn compute_merkle_map_from_block_range_roots(
46        &self,
47        up_to_beacon: BlockNumber,
48    ) -> StdResult<MKMap<BlockRange, MKMapNode<BlockRange, S>, S>> {
49        let block_range_roots_iterator = self
50            .retrieve_block_range_roots(up_to_beacon)
51            .await?
52            .map(|(block_range, root)| (block_range, root.into()));
53        let mut mk_hash_map = MKMap::new_from_iter(block_range_roots_iterator)
54            .with_context(|| "BlockRangeRootRetriever failed to compute the merkelized structure that proves ownership of the transaction")?;
55
56        // Notes about partial block ranges handling
57        // Precision: `up_to_beacon` is "partial" if it's not a value of a block range end (a multiple of 15 - 1)
58        //
59        // Several cases:
60        // - `up_to_beacon` is not partial: no partial block range to compute and insert
61        // - `up_to_beacon` is partial but contained in last already stored range: no partial block range to compute and insert
62        // - `up_to_beacon` is partial and not contained in last already computed range: compute and insert the partial block range
63        let is_beacon_contained_in_last_computed_range = mk_hash_map
64            .keys()
65            .next_back()
66            .map(|range| range.contains(&up_to_beacon))
67            .unwrap_or_default();
68
69        let latest_block_range = BlockRange::from_block_number(up_to_beacon);
70        if !latest_block_range.is_fully_covered_at(up_to_beacon)
71            && !is_beacon_contained_in_last_computed_range
72        {
73            let range = latest_block_range.start..latest_block_range.end.min(up_to_beacon + 1);
74            let latest_partial_block_range_nodes = self.retrieve_block_ranges_nodes(range).await?;
75
76            if !latest_partial_block_range_nodes.is_empty() {
77                let latest_partial_block_range_root =
78                    MKTree::<S>::new_from_iter(latest_partial_block_range_nodes).with_context(
79                        || "BlockRangeRootRetriever failed to compute partial latest block range",
80                    )?;
81                mk_hash_map
82                    .insert(latest_block_range, latest_partial_block_range_root.into())
83                    .with_context(
84                        || "BlockRangeRootRetriever failed to insert partial latest block range",
85                    )?;
86            }
87        }
88
89        Ok(mk_hash_map)
90    }
91}
92
93/// A [CardanoBlocksTransactionsSignableBuilder] builder
94pub struct CardanoBlocksTransactionsSignableBuilder<S: MKTreeStorer> {
95    blocks_transactions_importer: Arc<dyn BlocksTransactionsImporter>,
96    block_range_root_retriever: Arc<dyn BlockRangeRootRetriever<S>>,
97}
98
99impl<S: MKTreeStorer> CardanoBlocksTransactionsSignableBuilder<S> {
100    /// Constructor
101    pub fn new(
102        blocks_transactions_importer: Arc<dyn BlocksTransactionsImporter>,
103        block_range_root_retriever: Arc<dyn BlockRangeRootRetriever<S>>,
104    ) -> Self {
105        Self {
106            blocks_transactions_importer,
107            block_range_root_retriever,
108        }
109    }
110}
111
112#[async_trait]
113impl<S: MKTreeStorer> SignableBuilder<(BlockNumber, BlockNumberOffset)>
114    for CardanoBlocksTransactionsSignableBuilder<S>
115{
116    async fn compute_protocol_message(
117        &self,
118        beacon: (BlockNumber, BlockNumberOffset),
119    ) -> StdResult<ProtocolMessage> {
120        let (block_number, block_number_offset) = beacon;
121
122        self.blocks_transactions_importer.import(block_number).await?;
123
124        let mk_root = self
125            .block_range_root_retriever
126            .compute_merkle_map_from_block_range_roots(block_number)
127            .await?
128            .compute_root()?;
129
130        let mut protocol_message = ProtocolMessage::new();
131        protocol_message.set_message_part(
132            ProtocolMessagePartKey::CardanoBlocksTransactionsMerkleRoot,
133            mk_root.to_hex(),
134        );
135        protocol_message.set_message_part(
136            ProtocolMessagePartKey::LatestBlockNumber,
137            block_number.to_string(),
138        );
139        protocol_message.set_message_part(
140            ProtocolMessagePartKey::CardanoBlocksTransactionsBlockNumberOffset,
141            block_number_offset.to_string(),
142        );
143
144        Ok(protocol_message)
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use crate::{
151        crypto_helper::MKTreeStoreInMemory,
152        entities::{CardanoTransaction, SlotNumber},
153        test::{
154            builder::CardanoTransactionsBuilder,
155            crypto_helper::{MKMapTestExtension, MKTreeTestExtension},
156        },
157    };
158
159    use super::*;
160
161    fn compute_mk_map_from_transactions(
162        transactions: Vec<CardanoTransaction>,
163    ) -> MKMap<BlockRange, MKMapNode<BlockRange, MKTreeStoreInMemory>, MKTreeStoreInMemory> {
164        MKMap::new_from_iter(transactions.iter().map(|tx| {
165            (
166                BlockRange::from_block_number(tx.block_number),
167                MKMapNode::TreeNode(tx.transaction_hash.clone().into()),
168            )
169        }))
170        .unwrap()
171    }
172
173    #[tokio::test]
174    async fn test_compute_signable() {
175        // Arrange
176        let block_number = BlockNumber(1453);
177        let block_number_offset = BlockNumberOffset(10);
178        let transactions = CardanoTransactionsBuilder::new().build_transactions(3);
179        let mk_map = compute_mk_map_from_transactions(transactions.clone());
180        let mut blocks_transactions_importer = MockBlocksTransactionsImporter::new();
181        blocks_transactions_importer
182            .expect_import()
183            .return_once(move |_| Ok(()));
184        let retrieved_transactions = transactions.clone();
185        let mut block_range_root_retriever = MockBlockRangeRootRetriever::new();
186        block_range_root_retriever
187            .expect_compute_merkle_map_from_block_range_roots()
188            .return_once(move |_| Ok(compute_mk_map_from_transactions(retrieved_transactions)));
189
190        let signable_builder = CardanoBlocksTransactionsSignableBuilder::new(
191            Arc::new(blocks_transactions_importer),
192            Arc::new(block_range_root_retriever),
193        );
194
195        // Action
196        let signable = signable_builder
197            .compute_protocol_message((block_number, block_number_offset))
198            .await
199            .unwrap();
200
201        // Assert
202        let mut signable_expected = ProtocolMessage::new();
203        signable_expected.set_message_part(
204            ProtocolMessagePartKey::CardanoBlocksTransactionsMerkleRoot,
205            mk_map.compute_root().unwrap().to_hex(),
206        );
207        signable_expected.set_message_part(
208            ProtocolMessagePartKey::LatestBlockNumber,
209            format!("{block_number}"),
210        );
211        signable_expected.set_message_part(
212            ProtocolMessagePartKey::CardanoBlocksTransactionsBlockNumberOffset,
213            format!("{block_number_offset}"),
214        );
215        assert_eq!(signable_expected, signable);
216    }
217
218    #[tokio::test]
219    async fn test_compute_signable_with_no_block_range_root_return_error() {
220        let block_number = BlockNumber(50);
221        let block_number_offset = BlockNumberOffset(10);
222        let mut blocks_transactions_importer = MockBlocksTransactionsImporter::new();
223        blocks_transactions_importer.expect_import().return_once(|_| Ok(()));
224        let mut block_range_root_retriever = MockBlockRangeRootRetriever::new();
225        block_range_root_retriever
226            .expect_compute_merkle_map_from_block_range_roots()
227            .return_once(move |_| Ok(compute_mk_map_from_transactions(vec![])));
228        let signable_builder = CardanoBlocksTransactionsSignableBuilder::new(
229            Arc::new(blocks_transactions_importer),
230            Arc::new(block_range_root_retriever),
231        );
232
233        let result = signable_builder
234            .compute_protocol_message((block_number, block_number_offset))
235            .await;
236
237        assert!(result.is_err());
238    }
239
240    mod compute_merkle_map_from_block_range_roots {
241        use super::*;
242
243        /// A dummy [BlockRangeRootRetriever] that returns a fixed set of block ranges and ignores
244        /// the beacons given.
245        ///
246        /// Most only be used to test the behavior of [BlockRangeRootRetriever::compute_merkle_map_from_block_range_roots]
247        struct DumbBlockRangeRootRetriever<S: MKTreeStorer> {
248            complete_block_ranges: Vec<(BlockRange, MKTreeNode)>,
249            retrieve_block_ranges_nodes_result: BTreeSet<CardanoBlockTransactionMkTreeNode>,
250            _phantom: std::marker::PhantomData<S>,
251        }
252
253        impl DumbBlockRangeRootRetriever<MKTreeStoreInMemory> {
254            fn new(
255                complete_block_ranges: Vec<(BlockRange, MKTreeNode)>,
256                retrieve_block_ranges_nodes_result: BTreeSet<CardanoBlockTransactionMkTreeNode>,
257            ) -> Self {
258                Self {
259                    complete_block_ranges,
260                    retrieve_block_ranges_nodes_result,
261                    _phantom: std::marker::PhantomData,
262                }
263            }
264        }
265
266        #[async_trait]
267        impl<S: MKTreeStorer> BlockRangeRootRetriever<S> for DumbBlockRangeRootRetriever<S> {
268            async fn retrieve_block_range_roots<'a>(
269                &'a self,
270                _up_to_beacon: BlockNumber,
271            ) -> StdResult<Box<dyn Iterator<Item = (BlockRange, MKTreeNode)> + 'a>> {
272                Ok(Box::new(self.complete_block_ranges.iter().cloned()))
273            }
274
275            async fn retrieve_block_ranges_nodes(
276                &self,
277                range: Range<BlockNumber>,
278            ) -> StdResult<BTreeSet<CardanoBlockTransactionMkTreeNode>> {
279                Ok(self
280                    .retrieve_block_ranges_nodes_result
281                    .iter()
282                    .filter(|n| range.contains(&n.block_number()))
283                    .cloned()
284                    .collect())
285            }
286        }
287
288        #[tokio::test]
289        async fn compute_with_only_complete_block_ranges() {
290            let block_range_roots = vec![
291                (
292                    BlockRange::from_block_number(BlockNumber(15)),
293                    MKTreeNode::from_hex("AAAA").unwrap(),
294                ),
295                (
296                    BlockRange::from_block_number(BlockNumber(30)),
297                    MKTreeNode::from_hex("BBBB").unwrap(),
298                ),
299                (
300                    BlockRange::from_block_number(BlockNumber(45)),
301                    MKTreeNode::from_hex("CCCC").unwrap(),
302                ),
303            ];
304
305            let retriever =
306                DumbBlockRangeRootRetriever::new(block_range_roots.clone(), BTreeSet::new());
307
308            let retrieved_mk_map = retriever
309                .compute_merkle_map_from_block_range_roots(BlockNumber(59))
310                .await
311                .unwrap();
312
313            let retrieved_mk_map_root = retrieved_mk_map.compute_root().unwrap();
314            let expected_mk_map_root = MKMap::<
315                BlockRange,
316                MKMapNode<BlockRange, MKTreeStoreInMemory>,
317                MKTreeStoreInMemory,
318            >::compute_root_from_iter(
319                block_range_roots.into_iter().map(|(k, v)| (k, v.into()))
320            )
321            .unwrap();
322            assert_eq!(expected_mk_map_root, retrieved_mk_map_root);
323        }
324
325        #[tokio::test]
326        async fn compute_with_a_partial_block_range_when_last_range_is_not_empty() {
327            let stored_block_ranges_roots = vec![
328                (
329                    BlockRange::from_block_number(BlockNumber(15)),
330                    MKTreeNode::from_hex("AAAA").unwrap(),
331                ),
332                (
333                    BlockRange::from_block_number(BlockNumber(30)),
334                    MKTreeNode::from_hex("BBBB").unwrap(),
335                ),
336                (
337                    BlockRange::from_block_number(BlockNumber(45)),
338                    MKTreeNode::from_hex("CCCC").unwrap(),
339                ),
340            ];
341            let latest_partial_block_range_nodes = BTreeSet::from([
342                CardanoBlockTransactionMkTreeNode::Block {
343                    block_hash: "block_hash-62".to_string(),
344                    block_number: BlockNumber(62),
345                    slot_number: SlotNumber(162),
346                },
347                CardanoBlockTransactionMkTreeNode::Transaction {
348                    transaction_hash: "tx_hash-1".to_string(),
349                    block_number: BlockNumber(62),
350                    slot_number: SlotNumber(162),
351                    block_hash: "block_hash-62".to_string(),
352                },
353            ]);
354            let up_to_beacon = BlockNumber(62);
355
356            let retriever = DumbBlockRangeRootRetriever::new(
357                stored_block_ranges_roots.clone(),
358                latest_partial_block_range_nodes
359                    .union(
360                        // Note: Add data with block number higher than the beacon given for the computation, it should be ignored
361                        &BTreeSet::from([CardanoBlockTransactionMkTreeNode::Block {
362                            block_hash: "block_hash-63".to_string(),
363                            block_number: up_to_beacon + 1,
364                            slot_number: SlotNumber(163),
365                        }]),
366                    )
367                    .cloned()
368                    .collect(),
369            );
370
371            let retrieved_mk_map = retriever
372                .compute_merkle_map_from_block_range_roots(up_to_beacon)
373                .await
374                .unwrap();
375
376            let retrieved_mk_map_root = retrieved_mk_map.compute_root().unwrap();
377            let expected_mk_map_root = MKMap::<
378                BlockRange,
379                MKMapNode<BlockRange, MKTreeStoreInMemory>,
380                MKTreeStoreInMemory,
381            >::compute_root_from_iter(
382                [
383                    stored_block_ranges_roots,
384                    vec![(
385                        BlockRange::from_block_number(BlockNumber(60)),
386                        MKTree::<MKTreeStoreInMemory>::compute_root_from_iter(
387                            latest_partial_block_range_nodes
388                                .into_iter()
389                                .filter(|n| n.block_number() <= up_to_beacon),
390                        )
391                        .unwrap(),
392                    )],
393                ]
394                .concat()
395                .into_iter()
396                .map(|(k, v)| (k, v.into())),
397            )
398            .unwrap();
399            assert_eq!(expected_mk_map_root, retrieved_mk_map_root);
400        }
401
402        #[tokio::test]
403        async fn compute_when_given_block_is_partial_but_a_range_was_already_computed() {
404            let stored_block_ranges_roots = vec![
405                (
406                    BlockRange::from_block_number(BlockNumber(15)),
407                    MKTreeNode::from_hex("AAAA").unwrap(),
408                ),
409                (
410                    BlockRange::from_block_number(BlockNumber(30)),
411                    MKTreeNode::from_hex("BBBB").unwrap(),
412                ),
413                (
414                    BlockRange::from_block_number(BlockNumber(45)),
415                    MKTreeNode::from_hex("CCCC").unwrap(),
416                ),
417            ];
418            let up_to_beacon = BlockNumber(47);
419
420            let retriever = DumbBlockRangeRootRetriever::new(
421                stored_block_ranges_roots.clone(),
422                BTreeSet::from([CardanoBlockTransactionMkTreeNode::Block {
423                    block_hash: "block_hash-47".to_string(),
424                    block_number: up_to_beacon,
425                    slot_number: SlotNumber(163),
426                }]),
427            );
428
429            let retrieved_mk_map = retriever
430                .compute_merkle_map_from_block_range_roots(up_to_beacon)
431                .await
432                .unwrap();
433
434            let retrieved_mk_map_root = retrieved_mk_map.compute_root().unwrap();
435            let expected_mk_map_root = MKMap::<
436                BlockRange,
437                MKMapNode<BlockRange, MKTreeStoreInMemory>,
438                MKTreeStoreInMemory,
439            >::compute_root_from_iter(
440                stored_block_ranges_roots.into_iter().map(|(k, v)| (k, v.into())),
441            )
442            .unwrap();
443            assert_eq!(expected_mk_map_root, retrieved_mk_map_root);
444        }
445
446        #[tokio::test]
447        async fn compute_with_a_partial_block_range_when_last_range_is_empty() {
448            let stored_block_ranges_roots = vec![
449                (
450                    BlockRange::from_block_number(BlockNumber(15)),
451                    MKTreeNode::from_hex("AAAA").unwrap(),
452                ),
453                (
454                    BlockRange::from_block_number(BlockNumber(30)),
455                    MKTreeNode::from_hex("BBBB").unwrap(),
456                ),
457                (
458                    BlockRange::from_block_number(BlockNumber(45)),
459                    MKTreeNode::from_hex("CCCC").unwrap(),
460                ),
461            ];
462
463            let retriever = DumbBlockRangeRootRetriever::new(
464                stored_block_ranges_roots.clone(),
465                BTreeSet::new(),
466            );
467
468            let retrieved_mk_map = retriever
469                .compute_merkle_map_from_block_range_roots(BlockNumber(63))
470                .await
471                .unwrap();
472
473            let retrieved_mk_map_root = retrieved_mk_map.compute_root().unwrap();
474            let expected_mk_map_root = MKMap::<
475                BlockRange,
476                MKMapNode<BlockRange, MKTreeStoreInMemory>,
477                MKTreeStoreInMemory,
478            >::compute_root_from_iter(
479                stored_block_ranges_roots.into_iter().map(|(k, v)| (k, v.into())),
480            )
481            .unwrap();
482            assert_eq!(expected_mk_map_root, retrieved_mk_map_root);
483        }
484    }
485}