mithril_aggregator/services/
prover.rs

1use async_trait::async_trait;
2use rayon::prelude::*;
3use slog::{debug, info, Logger};
4use std::{
5    collections::{BTreeMap, BTreeSet, HashMap},
6    sync::Arc,
7    time::Duration,
8};
9
10use mithril_common::{
11    crypto_helper::{MKMap, MKMapNode, MKTree, MKTreeStorer},
12    entities::{
13        BlockNumber, BlockRange, CardanoTransaction, CardanoTransactionsSetProof, TransactionHash,
14    },
15    logging::LoggerExtensions,
16    signable_builder::BlockRangeRootRetriever,
17    StdResult,
18};
19use mithril_resource_pool::ResourcePool;
20
21/// Prover service is the cryptographic engine in charge of producing cryptographic proofs for transactions
22#[cfg_attr(test, mockall::automock)]
23#[async_trait]
24pub trait ProverService: Sync + Send {
25    /// Compute the cryptographic proofs for the given transactions
26    async fn compute_transactions_proofs(
27        &self,
28        up_to: BlockNumber,
29        transaction_hashes: &[TransactionHash],
30    ) -> StdResult<Vec<CardanoTransactionsSetProof>>;
31
32    /// Compute the cache
33    async fn compute_cache(&self, up_to: BlockNumber) -> StdResult<()>;
34}
35
36/// Transactions retriever
37#[cfg_attr(test, mockall::automock)]
38#[async_trait]
39pub trait TransactionsRetriever: Sync + Send {
40    /// Get a list of transactions by hashes using chronological order
41    async fn get_by_hashes(
42        &self,
43        hashes: Vec<TransactionHash>,
44        up_to: BlockNumber,
45    ) -> StdResult<Vec<CardanoTransaction>>;
46
47    /// Get by block ranges
48    async fn get_by_block_ranges(
49        &self,
50        block_ranges: Vec<BlockRange>,
51    ) -> StdResult<Vec<CardanoTransaction>>;
52}
53
54/// Mithril prover
55pub struct MithrilProverService<S: MKTreeStorer> {
56    transaction_retriever: Arc<dyn TransactionsRetriever>,
57    block_range_root_retriever: Arc<dyn BlockRangeRootRetriever<S>>,
58    mk_map_pool: ResourcePool<MKMap<BlockRange, MKMapNode<BlockRange, S>, S>>,
59    logger: Logger,
60}
61
62impl<S: MKTreeStorer> MithrilProverService<S> {
63    /// Create a new Mithril prover
64    pub fn new(
65        transaction_retriever: Arc<dyn TransactionsRetriever>,
66        block_range_root_retriever: Arc<dyn BlockRangeRootRetriever<S>>,
67        mk_map_pool_size: usize,
68        logger: Logger,
69    ) -> Self {
70        Self {
71            transaction_retriever,
72            block_range_root_retriever,
73            mk_map_pool: ResourcePool::new(mk_map_pool_size, vec![]),
74            logger: logger.new_with_component_name::<Self>(),
75        }
76    }
77
78    async fn get_block_ranges(
79        &self,
80        transaction_hashes: &[TransactionHash],
81        up_to: BlockNumber,
82    ) -> StdResult<Vec<BlockRange>> {
83        let transactions = self
84            .transaction_retriever
85            .get_by_hashes(transaction_hashes.to_vec(), up_to)
86            .await?;
87        let block_ranges = transactions
88            .iter()
89            .map(|t| BlockRange::from_block_number(t.block_number))
90            .collect::<BTreeSet<_>>();
91
92        Ok(block_ranges.into_iter().collect::<Vec<_>>())
93    }
94
95    /// Get all the transactions of the block ranges
96    async fn get_all_transactions_for_block_ranges(
97        &self,
98        block_ranges: &[BlockRange],
99    ) -> StdResult<HashMap<BlockRange, Vec<CardanoTransaction>>> {
100        let mut block_ranges_map = HashMap::new();
101        let transactions = self
102            .transaction_retriever
103            .get_by_block_ranges(block_ranges.to_vec())
104            .await?;
105        for transaction in transactions {
106            let block_range = BlockRange::from_block_number(transaction.block_number);
107            let block_range_transactions: &mut Vec<_> =
108                block_ranges_map.entry(block_range).or_insert(vec![]);
109            block_range_transactions.push(transaction)
110        }
111
112        Ok(block_ranges_map)
113    }
114}
115
116#[async_trait]
117impl<S: MKTreeStorer> ProverService for MithrilProverService<S> {
118    async fn compute_transactions_proofs(
119        &self,
120        up_to: BlockNumber,
121        transaction_hashes: &[TransactionHash],
122    ) -> StdResult<Vec<CardanoTransactionsSetProof>> {
123        // 1 - Compute the set of block ranges with transactions to prove
124        let block_ranges_transactions = self.get_block_ranges(transaction_hashes, up_to).await?;
125        let block_range_transactions = self
126            .get_all_transactions_for_block_ranges(&block_ranges_transactions)
127            .await?;
128
129        // 2 - Compute block ranges sub Merkle trees
130        let mk_trees: StdResult<Vec<(BlockRange, MKTree<S>)>> = block_range_transactions
131            .into_iter()
132            .map(|(block_range, transactions)| {
133                let mk_tree = MKTree::new(&transactions)?;
134                Ok((block_range, mk_tree))
135            })
136            .collect();
137        let mk_trees = BTreeMap::from_iter(mk_trees?);
138
139        // 3 - Compute block range roots Merkle map
140        let acquire_timeout = Duration::from_millis(1000);
141        let mut mk_map = self.mk_map_pool.acquire_resource(acquire_timeout)?;
142
143        // 4 - Enrich the Merkle map with the block ranges Merkle trees
144        for (block_range, mk_tree) in mk_trees {
145            mk_map.replace(block_range, mk_tree.into())?;
146        }
147
148        // 5 - Compute the proof for all transactions
149        if let Ok(mk_proof) = mk_map.compute_proof(transaction_hashes) {
150            self.mk_map_pool.give_back_resource_pool_item(mk_map)?;
151            let mk_proof_leaves = mk_proof.leaves();
152            let transaction_hashes_certified: Vec<TransactionHash> = transaction_hashes
153                .iter()
154                .filter(|hash| mk_proof_leaves.contains(&hash.as_str().into()))
155                .cloned()
156                .collect();
157
158            Ok(vec![CardanoTransactionsSetProof::new(
159                transaction_hashes_certified,
160                mk_proof,
161            )])
162        } else {
163            Ok(vec![])
164        }
165    }
166
167    async fn compute_cache(&self, up_to: BlockNumber) -> StdResult<()> {
168        let pool_size = self.mk_map_pool.size();
169        info!(
170            self.logger, "Starts computing the Merkle map pool resource of size {pool_size}";
171            "up_to_block_number" => *up_to,
172        );
173        let mk_map_cache = self
174            .block_range_root_retriever
175            .compute_merkle_map_from_block_range_roots(up_to)
176            .await?;
177        let mk_maps_new = (1..=pool_size)
178            .into_par_iter()
179            .map(|i| {
180                debug!(
181                    self.logger,
182                    "Computing the Merkle map pool resource {i}/{pool_size}"
183                );
184                mk_map_cache.clone()
185            })
186            .collect::<Vec<MKMap<_, _, _>>>();
187        debug!(self.logger, "Draining the Merkle map pool");
188        let discriminant_new = self.mk_map_pool.discriminant()? + 1;
189        self.mk_map_pool.set_discriminant(discriminant_new)?;
190        self.mk_map_pool.clear();
191        debug!(
192            self.logger,
193            "Giving back new resources to the Merkle map pool"
194        );
195        mk_maps_new
196            .into_iter()
197            .map(|mk_map| {
198                self.mk_map_pool
199                    .give_back_resource(mk_map, discriminant_new)
200            })
201            .collect::<StdResult<Vec<_>>>()?;
202        info!(
203            self.logger,
204            "Completed computing the Merkle map pool resource of size {pool_size}"
205        );
206
207        Ok(())
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use anyhow::anyhow;
214    use mithril_common::crypto_helper::{
215        MKMap, MKMapNode, MKTreeNode, MKTreeStoreInMemory, MKTreeStorer,
216    };
217    use mithril_common::entities::CardanoTransaction;
218    use mithril_common::test_utils::CardanoTransactionsBuilder;
219    use mockall::mock;
220    use mockall::predicate::eq;
221
222    use crate::test_tools::TestLogger;
223
224    use super::*;
225
226    mock! {
227        pub BlockRangeRootRetrieverImpl<S: MKTreeStorer> { }
228
229        #[async_trait]
230        impl<S: MKTreeStorer> BlockRangeRootRetriever<S> for BlockRangeRootRetrieverImpl<S> {
231            async fn retrieve_block_range_roots<'a>(
232                &'a self,
233                up_to_beacon: BlockNumber,
234            ) -> StdResult<Box<dyn Iterator<Item = (BlockRange, MKTreeNode)> + 'a>>;
235
236            async fn compute_merkle_map_from_block_range_roots(
237                &self,
238                up_to_beacon: BlockNumber,
239            ) -> StdResult<MKMap<BlockRange, MKMapNode<BlockRange, S>, S>>;
240        }
241    }
242
243    mod test_data {
244        use mithril_common::crypto_helper::MKTreeStoreInMemory;
245
246        use super::*;
247
248        pub fn filter_transactions_for_indices(
249            indices: &[usize],
250            transactions: &[CardanoTransaction],
251        ) -> Vec<CardanoTransaction> {
252            transactions
253                .iter()
254                .enumerate()
255                .filter(|(i, _)| indices.contains(i))
256                .map(|(_, t)| t.to_owned())
257                .collect()
258        }
259
260        pub fn map_to_transaction_hashes(
261            transactions: &[CardanoTransaction],
262        ) -> Vec<TransactionHash> {
263            transactions
264                .iter()
265                .map(|t| t.transaction_hash.clone())
266                .collect()
267        }
268
269        pub fn transactions_group_by_block_range(
270            transactions: &[CardanoTransaction],
271        ) -> BTreeMap<BlockRange, Vec<CardanoTransaction>> {
272            let mut block_ranges_map = BTreeMap::new();
273            for transaction in transactions {
274                let block_range = BlockRange::from_block_number(transaction.block_number);
275                let block_range_transactions: &mut Vec<_> =
276                    block_ranges_map.entry(block_range).or_insert(vec![]);
277                block_range_transactions.push(transaction.to_owned())
278            }
279
280            block_ranges_map
281        }
282
283        pub fn filter_transactions_for_block_ranges(
284            block_ranges: &[BlockRange],
285            transactions: &[CardanoTransaction],
286        ) -> Vec<CardanoTransaction> {
287            transactions
288                .iter()
289                .filter(|t| block_ranges.contains(&BlockRange::from_block_number(t.block_number)))
290                .map(|t| t.to_owned())
291                .collect()
292        }
293
294        pub fn compute_mk_map_from_block_ranges_map(
295            block_ranges_map: BTreeMap<BlockRange, Vec<CardanoTransaction>>,
296        ) -> MKMap<BlockRange, MKMapNode<BlockRange, MKTreeStoreInMemory>, MKTreeStoreInMemory>
297        {
298            MKMap::new_from_iter(
299                block_ranges_map
300                    .into_iter()
301                    .map(|(block_range, transactions)| {
302                        (
303                            block_range,
304                            MKMapNode::TreeNode(
305                                MKTree::<MKTreeStoreInMemory>::new(&transactions)
306                                    .unwrap()
307                                    .compute_root()
308                                    .unwrap()
309                                    .clone(),
310                            ),
311                        )
312                    }),
313            )
314            .unwrap()
315        }
316
317        pub fn compute_beacon_from_transactions(
318            transactions: &[CardanoTransaction],
319        ) -> BlockNumber {
320            let max_transaction = transactions.iter().max_by_key(|t| t.block_number).unwrap();
321            max_transaction.block_number
322        }
323
324        pub struct TestData {
325            pub transaction_hashes_to_prove: Vec<TransactionHash>,
326            pub block_ranges_map: BTreeMap<BlockRange, Vec<CardanoTransaction>>,
327            pub block_ranges_to_prove: Vec<BlockRange>,
328            pub all_transactions_in_block_ranges_to_prove: Vec<CardanoTransaction>,
329            pub beacon: BlockNumber,
330        }
331
332        pub fn build_test_data(
333            transactions_to_prove: &[CardanoTransaction],
334            transactions: &[CardanoTransaction],
335        ) -> TestData {
336            let transaction_hashes_to_prove = map_to_transaction_hashes(transactions_to_prove);
337            let block_ranges_map = transactions_group_by_block_range(transactions);
338            let block_ranges_map_to_prove =
339                transactions_group_by_block_range(transactions_to_prove);
340            let block_ranges_to_prove = block_ranges_map_to_prove
341                .keys()
342                .cloned()
343                .collect::<Vec<_>>();
344            let all_transactions_in_block_ranges_to_prove =
345                filter_transactions_for_block_ranges(&block_ranges_to_prove, transactions);
346            let beacon = compute_beacon_from_transactions(transactions);
347
348            TestData {
349                transaction_hashes_to_prove,
350                block_ranges_map,
351                block_ranges_to_prove,
352                all_transactions_in_block_ranges_to_prove,
353                beacon,
354            }
355        }
356    }
357
358    fn build_prover<F, G, S: MKTreeStorer + 'static>(
359        transaction_retriever_mock_config: F,
360        block_range_root_retriever_mock_config: G,
361    ) -> MithrilProverService<S>
362    where
363        F: FnOnce(&mut MockTransactionsRetriever),
364        G: FnOnce(&mut MockBlockRangeRootRetrieverImpl<S>),
365    {
366        let mut transaction_retriever = MockTransactionsRetriever::new();
367        transaction_retriever_mock_config(&mut transaction_retriever);
368        let mut block_range_root_retriever = MockBlockRangeRootRetrieverImpl::new();
369        block_range_root_retriever_mock_config(&mut block_range_root_retriever);
370        let mk_map_pool_size = 1;
371
372        MithrilProverService::new(
373            Arc::new(transaction_retriever),
374            Arc::new(block_range_root_retriever),
375            mk_map_pool_size,
376            TestLogger::stdout(),
377        )
378    }
379
380    #[tokio::test]
381    async fn compute_proof_for_one_set_of_three_certified_transactions() {
382        let transactions = CardanoTransactionsBuilder::new()
383            .max_transactions_per_block(1)
384            .blocks_per_block_range(3)
385            .build_block_ranges(5);
386        let transactions_to_prove =
387            test_data::filter_transactions_for_indices(&[1, 2, 4], &transactions);
388        let test_data = test_data::build_test_data(&transactions_to_prove, &transactions);
389        let prover = build_prover(
390            |transaction_retriever_mock| {
391                let transaction_hashes_to_prove = test_data.transaction_hashes_to_prove.clone();
392                let transactions_to_prove = transactions_to_prove.clone();
393                transaction_retriever_mock
394                    .expect_get_by_hashes()
395                    .with(eq(transaction_hashes_to_prove), eq(test_data.beacon))
396                    .return_once(move |_, _| Ok(transactions_to_prove));
397
398                let block_ranges_to_prove = test_data.block_ranges_to_prove.clone();
399                let all_transactions_in_block_ranges_to_prove =
400                    test_data.all_transactions_in_block_ranges_to_prove.clone();
401                transaction_retriever_mock
402                    .expect_get_by_block_ranges()
403                    .with(eq(block_ranges_to_prove))
404                    .return_once(move |_| Ok(all_transactions_in_block_ranges_to_prove));
405            },
406            |block_range_root_retriever_mock| {
407                let block_ranges_map = test_data.block_ranges_map.clone();
408                block_range_root_retriever_mock
409                    .expect_compute_merkle_map_from_block_range_roots()
410                    .return_once(|_| {
411                        Ok(test_data::compute_mk_map_from_block_ranges_map(
412                            block_ranges_map,
413                        ))
414                    });
415            },
416        );
417        prover.compute_cache(test_data.beacon).await.unwrap();
418
419        let transactions_set_proof = prover
420            .compute_transactions_proofs(test_data.beacon, &test_data.transaction_hashes_to_prove)
421            .await
422            .unwrap();
423
424        assert_eq!(transactions_set_proof.len(), 1);
425        assert_eq!(
426            transactions_set_proof[0].transactions_hashes(),
427            test_data.transaction_hashes_to_prove
428        );
429        transactions_set_proof[0].verify().unwrap();
430    }
431
432    #[tokio::test]
433    async fn cant_compute_proof_for_not_yet_certified_transaction() {
434        let transactions = CardanoTransactionsBuilder::new()
435            .max_transactions_per_block(1)
436            .blocks_per_block_range(3)
437            .build_block_ranges(5);
438        let transactions_to_prove =
439            test_data::filter_transactions_for_indices(&[1, 2, 4], &transactions);
440        let test_data = test_data::build_test_data(&transactions_to_prove, &transactions);
441        let prover = build_prover(
442            |transaction_retriever_mock| {
443                let transaction_hashes_to_prove = test_data.transaction_hashes_to_prove.clone();
444                transaction_retriever_mock
445                    .expect_get_by_hashes()
446                    .with(eq(transaction_hashes_to_prove), eq(test_data.beacon))
447                    .return_once(move |_, _| Ok(vec![]));
448                transaction_retriever_mock
449                    .expect_get_by_block_ranges()
450                    .with(eq(vec![]))
451                    .return_once(move |_| Ok(vec![]));
452            },
453            |block_range_root_retriever_mock| {
454                let block_ranges_map = test_data.block_ranges_map.clone();
455                block_range_root_retriever_mock
456                    .expect_compute_merkle_map_from_block_range_roots()
457                    .return_once(|_| {
458                        Ok(test_data::compute_mk_map_from_block_ranges_map(
459                            block_ranges_map,
460                        ))
461                    });
462            },
463        );
464        prover.compute_cache(test_data.beacon).await.unwrap();
465
466        let transactions_set_proof = prover
467            .compute_transactions_proofs(test_data.beacon, &test_data.transaction_hashes_to_prove)
468            .await
469            .unwrap();
470
471        assert_eq!(transactions_set_proof.len(), 0);
472    }
473
474    #[tokio::test]
475    async fn cant_compute_proof_for_unknown_transaction() {
476        let transactions = CardanoTransactionsBuilder::new()
477            .max_transactions_per_block(1)
478            .blocks_per_block_range(3)
479            .build_block_ranges(5);
480        let transactions_to_prove = test_data::filter_transactions_for_indices(&[], &transactions);
481        let mut test_data = test_data::build_test_data(&transactions_to_prove, &transactions);
482        test_data.transaction_hashes_to_prove = vec!["tx-unknown-123".to_string()];
483        let prover = build_prover(
484            |transaction_retriever_mock| {
485                let transaction_hashes_to_prove = test_data.transaction_hashes_to_prove.clone();
486                let transactions_to_prove = transactions_to_prove.clone();
487                transaction_retriever_mock
488                    .expect_get_by_hashes()
489                    .with(eq(transaction_hashes_to_prove), eq(test_data.beacon))
490                    .return_once(move |_, _| Ok(transactions_to_prove));
491
492                let block_ranges_to_prove = test_data.block_ranges_to_prove.clone();
493                let all_transactions_in_block_ranges_to_prove =
494                    test_data.all_transactions_in_block_ranges_to_prove.clone();
495                transaction_retriever_mock
496                    .expect_get_by_block_ranges()
497                    .with(eq(block_ranges_to_prove))
498                    .return_once(move |_| Ok(all_transactions_in_block_ranges_to_prove));
499            },
500            |block_range_root_retriever_mock| {
501                let block_ranges_map = test_data.block_ranges_map.clone();
502                block_range_root_retriever_mock
503                    .expect_compute_merkle_map_from_block_range_roots()
504                    .return_once(|_| {
505                        Ok(test_data::compute_mk_map_from_block_ranges_map(
506                            block_ranges_map,
507                        ))
508                    });
509            },
510        );
511        prover.compute_cache(test_data.beacon).await.unwrap();
512
513        let transactions_set_proof = prover
514            .compute_transactions_proofs(test_data.beacon, &test_data.transaction_hashes_to_prove)
515            .await
516            .unwrap();
517
518        assert_eq!(transactions_set_proof.len(), 0);
519    }
520
521    #[tokio::test]
522    async fn compute_proof_for_one_set_of_three_certified_transactions_and_two_unknowns() {
523        let transactions = CardanoTransactionsBuilder::new()
524            .max_transactions_per_block(1)
525            .blocks_per_block_range(3)
526            .build_block_ranges(5);
527        let transactions_to_prove =
528            test_data::filter_transactions_for_indices(&[1, 2, 4], &transactions);
529        let transaction_hashes_unknown =
530            vec!["tx-unknown-123".to_string(), "tx-unknown-456".to_string()];
531        let mut test_data = test_data::build_test_data(&transactions_to_prove, &transactions);
532        let transaction_hashes_known = test_data.transaction_hashes_to_prove.clone();
533        test_data.transaction_hashes_to_prove = [
534            test_data.transaction_hashes_to_prove.clone(),
535            transaction_hashes_unknown,
536        ]
537        .concat();
538        let prover = build_prover(
539            |transaction_retriever_mock| {
540                let transaction_hashes_to_prove = test_data.transaction_hashes_to_prove.clone();
541                let transactions_to_prove = transactions_to_prove.clone();
542                transaction_retriever_mock
543                    .expect_get_by_hashes()
544                    .with(eq(transaction_hashes_to_prove), eq(test_data.beacon))
545                    .return_once(move |_, _| Ok(transactions_to_prove));
546
547                let block_ranges_to_prove = test_data.block_ranges_to_prove.clone();
548                let all_transactions_in_block_ranges_to_prove =
549                    test_data.all_transactions_in_block_ranges_to_prove.clone();
550                transaction_retriever_mock
551                    .expect_get_by_block_ranges()
552                    .with(eq(block_ranges_to_prove))
553                    .return_once(move |_| Ok(all_transactions_in_block_ranges_to_prove));
554            },
555            |block_range_root_retriever_mock| {
556                let block_ranges_map = test_data.block_ranges_map.clone();
557                block_range_root_retriever_mock
558                    .expect_compute_merkle_map_from_block_range_roots()
559                    .return_once(|_| {
560                        Ok(test_data::compute_mk_map_from_block_ranges_map(
561                            block_ranges_map,
562                        ))
563                    });
564            },
565        );
566        prover.compute_cache(test_data.beacon).await.unwrap();
567
568        let transactions_set_proof = prover
569            .compute_transactions_proofs(test_data.beacon, &test_data.transaction_hashes_to_prove)
570            .await
571            .unwrap();
572
573        assert_eq!(transactions_set_proof.len(), 1);
574        assert_eq!(
575            transactions_set_proof[0].transactions_hashes(),
576            transaction_hashes_known
577        );
578        transactions_set_proof[0].verify().unwrap();
579    }
580
581    #[tokio::test]
582    async fn cant_compute_proof_if_transaction_retriever_fails() {
583        let transactions = CardanoTransactionsBuilder::new()
584            .max_transactions_per_block(1)
585            .blocks_per_block_range(3)
586            .build_block_ranges(5);
587        let transactions_to_prove =
588            test_data::filter_transactions_for_indices(&[1, 2, 4], &transactions);
589        let test_data = test_data::build_test_data(&transactions_to_prove, &transactions);
590        let prover = build_prover::<_, _, MKTreeStoreInMemory>(
591            |transaction_retriever_mock| {
592                transaction_retriever_mock
593                    .expect_get_by_hashes()
594                    .returning(|_, _| Err(anyhow!("Error")));
595            },
596            |block_range_root_retriever_mock| {
597                block_range_root_retriever_mock
598                    .expect_compute_merkle_map_from_block_range_roots()
599                    .return_once(|_| MKMap::new(&[]));
600            },
601        );
602        prover.compute_cache(test_data.beacon).await.unwrap();
603
604        prover
605            .compute_transactions_proofs(test_data.beacon, &test_data.transaction_hashes_to_prove)
606            .await
607            .expect_err("Should have failed because of transaction retriever failure");
608    }
609
610    #[tokio::test]
611    async fn cant_compute_proof_if_block_range_root_retriever_fails() {
612        let transactions = CardanoTransactionsBuilder::new()
613            .max_transactions_per_block(1)
614            .blocks_per_block_range(3)
615            .build_block_ranges(5);
616        let transactions_to_prove =
617            test_data::filter_transactions_for_indices(&[1, 2, 4], &transactions);
618        let test_data = test_data::build_test_data(&transactions_to_prove, &transactions);
619        let prover = build_prover::<_, _, MKTreeStoreInMemory>(
620            |transaction_retriever_mock| {
621                let transactions_to_prove = transactions_to_prove.clone();
622                transaction_retriever_mock
623                    .expect_get_by_hashes()
624                    .return_once(move |_, _| Ok(transactions_to_prove));
625
626                let all_transactions_in_block_ranges_to_prove =
627                    test_data.all_transactions_in_block_ranges_to_prove.clone();
628                transaction_retriever_mock
629                    .expect_get_by_block_ranges()
630                    .return_once(move |_| Ok(all_transactions_in_block_ranges_to_prove));
631            },
632            |block_range_root_retriever_mock| {
633                block_range_root_retriever_mock
634                    .expect_compute_merkle_map_from_block_range_roots()
635                    .return_once(|_| Err(anyhow!("Error")));
636            },
637        );
638
639        prover
640            .compute_transactions_proofs(test_data.beacon, &test_data.transaction_hashes_to_prove)
641            .await
642            .expect_err("Should have failed because of block range root retriever failure");
643    }
644}