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#[cfg_attr(test, mockall::automock)]
20#[async_trait]
21pub trait BlocksTransactionsImporter: Send + Sync {
22 async fn import(&self, up_to_beacon: BlockNumber) -> StdResult<()>;
24}
25
26#[cfg_attr(test, mockall::automock)]
28#[async_trait]
29pub trait BlockRangeRootRetriever<S: MKTreeStorer>: Send + Sync {
30 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 async fn retrieve_block_ranges_nodes(
40 &self,
41 range: Range<BlockNumber>,
42 ) -> StdResult<BTreeSet<CardanoBlockTransactionMkTreeNode>>;
43
44 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 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
93pub 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 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 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 let signable = signable_builder
197 .compute_protocol_message((block_number, block_number_offset))
198 .await
199 .unwrap();
200
201 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 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 &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}