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#[cfg_attr(test, mockall::automock)]
23#[async_trait]
24pub trait ProverService: Sync + Send {
25 async fn compute_transactions_proofs(
27 &self,
28 up_to: BlockNumber,
29 transaction_hashes: &[TransactionHash],
30 ) -> StdResult<Vec<CardanoTransactionsSetProof>>;
31
32 async fn compute_cache(&self, up_to: BlockNumber) -> StdResult<()>;
34}
35
36#[cfg_attr(test, mockall::automock)]
38#[async_trait]
39pub trait TransactionsRetriever: Sync + Send {
40 async fn get_by_hashes(
42 &self,
43 hashes: Vec<TransactionHash>,
44 up_to: BlockNumber,
45 ) -> StdResult<Vec<CardanoTransaction>>;
46
47 async fn get_by_block_ranges(
49 &self,
50 block_ranges: Vec<BlockRange>,
51 ) -> StdResult<Vec<CardanoTransaction>>;
52}
53
54pub 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 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 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 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 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 let acquire_timeout = Duration::from_millis(1000);
141 let mut mk_map = self.mk_map_pool.acquire_resource(acquire_timeout)?;
142
143 for (block_range, mk_tree) in mk_trees {
145 mk_map.replace(block_range, mk_tree.into())?;
146 }
147
148 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}