Skip to content

Commit dc174ba

Browse files
committed
feat: add 'BlockRangeRootRetriever' trait in Cardano transaction signable builder
1 parent 512fe8d commit dc174ba

File tree

2 files changed

+94
-162
lines changed

2 files changed

+94
-162
lines changed

mithril-common/src/messages/cardano_transactions_proof.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -314,9 +314,11 @@ mod tests {
314314

315315
#[cfg(feature = "fs")]
316316
mod fs_only {
317-
use crate::entities::{CardanoDbBeacon, CardanoTransaction};
317+
use crate::crypto_helper::{MKMap, MKMapNode};
318+
use crate::entities::{BlockRange, CardanoDbBeacon, CardanoTransaction};
318319
use crate::signable_builder::{
319-
CardanoTransactionsSignableBuilder, MockTransactionsImporter, SignableBuilder,
320+
CardanoTransactionsSignableBuilder, MockBlockRangeRootRetriever,
321+
MockTransactionsImporter, SignableBuilder,
320322
};
321323
use slog::Logger;
322324
use std::sync::Arc;
@@ -376,13 +378,28 @@ mod tests {
376378
transactions: &[CardanoTransaction],
377379
immutable_file_number: u64,
378380
) -> ProtocolMessage {
379-
let transactions = transactions.to_vec();
380381
let mut transaction_importer = MockTransactionsImporter::new();
381382
transaction_importer
382383
.expect_import()
383-
.return_once(move |_| Ok(transactions));
384+
.return_once(move |_| Ok(()));
385+
let mut block_range_root_retriever = MockBlockRangeRootRetriever::new();
386+
387+
let transactions_imported = transactions.to_vec();
388+
block_range_root_retriever
389+
.expect_compute_merkle_map_from_block_range_roots()
390+
.return_once(move |_| {
391+
MKMap::<BlockRange, MKMapNode<BlockRange>>::new_from_iter(
392+
transactions_imported.into_iter().map(|tx| {
393+
(
394+
BlockRange::from_block_number(tx.block_number),
395+
MKMapNode::TreeNode(tx.transaction_hash.clone().into()),
396+
)
397+
}),
398+
)
399+
});
384400
let cardano_transaction_signable_builder = CardanoTransactionsSignableBuilder::new(
385401
Arc::new(transaction_importer),
402+
Arc::new(block_range_root_retriever),
386403
Logger::root(slog::Discard, slog::o!()),
387404
);
388405
cardano_transaction_signable_builder

mithril-common/src/signable_builder/cardano_transactions.rs

Lines changed: 73 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1-
use std::{collections::HashMap, sync::Arc};
1+
use std::sync::Arc;
22

33
use anyhow::Context;
44
use async_trait::async_trait;
55
use slog::{debug, Logger};
66

77
use crate::{
8-
crypto_helper::{MKMap, MKMapNode, MKTree, MKTreeNode},
9-
entities::{
10-
BlockRange, CardanoDbBeacon, CardanoTransaction, ProtocolMessage, ProtocolMessagePartKey,
11-
TransactionHash,
12-
},
8+
crypto_helper::{MKMap, MKMapNode, MKTreeNode},
9+
entities::{BlockRange, CardanoDbBeacon, ProtocolMessage, ProtocolMessagePartKey},
1310
signable_builder::SignableBuilder,
1411
StdResult,
1512
};
@@ -23,65 +20,55 @@ use mockall::automock;
2320
#[async_trait]
2421
pub trait TransactionsImporter: Send + Sync {
2522
/// Returns all transactions up to the given beacon
26-
async fn import(&self, up_to_beacon: ImmutableFileNumber)
27-
-> StdResult<Vec<CardanoTransaction>>;
23+
async fn import(&self, up_to_beacon: ImmutableFileNumber) -> StdResult<()>;
24+
}
25+
26+
/// Block Range Merkle roots retriever
27+
#[cfg_attr(test, automock)]
28+
#[async_trait]
29+
pub trait BlockRangeRootRetriever: Send + Sync {
30+
/// Returns a Merkle map of the block ranges roots up to a given beacon
31+
async fn retrieve_block_range_roots(
32+
&self,
33+
up_to_beacon: ImmutableFileNumber,
34+
) -> StdResult<Box<dyn Iterator<Item = (BlockRange, MKTreeNode)>>>;
35+
36+
/// Returns a Merkle map of the block ranges roots up to a given beacon
37+
async fn compute_merkle_map_from_block_range_roots(
38+
&self,
39+
up_to_beacon: ImmutableFileNumber,
40+
) -> StdResult<MKMap<BlockRange, MKMapNode<BlockRange>>> {
41+
let block_range_roots_iterator = self
42+
.retrieve_block_range_roots(up_to_beacon)
43+
.await?
44+
.map(|(block_range, root)| (block_range, root.into()));
45+
let mk_hash_map = MKMap::new_from_iter(block_range_roots_iterator)
46+
.with_context(|| "ProverService failed to compute the merkelized structure that proves ownership of the transaction")?;
47+
48+
Ok(mk_hash_map)
49+
}
2850
}
2951

3052
/// A [CardanoTransactionsSignableBuilder] builder
3153
pub struct CardanoTransactionsSignableBuilder {
3254
transaction_importer: Arc<dyn TransactionsImporter>,
55+
block_range_root_retriever: Arc<dyn BlockRangeRootRetriever>,
3356
logger: Logger,
3457
}
3558

3659
impl CardanoTransactionsSignableBuilder {
3760
/// Constructor
38-
pub fn new(transaction_importer: Arc<dyn TransactionsImporter>, logger: Logger) -> Self {
61+
pub fn new(
62+
transaction_importer: Arc<dyn TransactionsImporter>,
63+
block_range_root_retriever: Arc<dyn BlockRangeRootRetriever>,
64+
logger: Logger,
65+
) -> Self {
3966
Self {
4067
transaction_importer,
68+
block_range_root_retriever,
4169
logger,
4270
}
4371
}
44-
45-
// Note: Code duplicated from aggregator Prover service as is.
46-
// This will be not be the case when we use the cached intermediate merkle roots.
47-
fn compute_merkle_map_from_transactions(
48-
&self,
49-
transactions: Vec<CardanoTransaction>,
50-
) -> StdResult<MKMap<BlockRange, MKMapNode<BlockRange>>> {
51-
let mut transactions_by_block_ranges: HashMap<BlockRange, Vec<TransactionHash>> =
52-
HashMap::new();
53-
for transaction in transactions {
54-
let block_range = BlockRange::from_block_number(transaction.block_number);
55-
transactions_by_block_ranges
56-
.entry(block_range)
57-
.or_default()
58-
.push(transaction.transaction_hash);
59-
}
60-
let mk_hash_map = MKMap::new_from_iter(
61-
transactions_by_block_ranges
62-
.into_iter()
63-
.try_fold(
64-
vec![],
65-
|mut acc, (block_range, transactions)| -> StdResult<Vec<(_, MKMapNode<_>)>> {
66-
acc.push((block_range, MKTree::new(&transactions)?.into()));
67-
Ok(acc)
68-
},
69-
)?,
70-
)
71-
.with_context(|| "ProverService failed to compute the merkelized structure that proves ownership of the transaction")?;
72-
73-
Ok(mk_hash_map)
74-
}
75-
76-
fn compute_merkle_root(&self, transactions: Vec<CardanoTransaction>) -> StdResult<MKTreeNode> {
77-
let mk_map = self.compute_merkle_map_from_transactions(transactions)?;
78-
79-
let mk_root = mk_map.compute_root().with_context(|| {
80-
"CardanoTransactionsSignableBuilder failed to compute MKHashMap root"
81-
})?;
82-
83-
Ok(mk_root)
84-
}
8572
}
8673

8774
#[async_trait]
@@ -95,11 +82,15 @@ impl SignableBuilder<CardanoDbBeacon> for CardanoTransactionsSignableBuilder {
9582
"Compute protocol message for CardanoTransactions at beacon: {beacon}"
9683
);
9784

98-
let transactions = self
99-
.transaction_importer
85+
self.transaction_importer
10086
.import(beacon.immutable_file_number)
10187
.await?;
102-
let mk_root = self.compute_merkle_root(transactions)?;
88+
89+
let mk_root = self
90+
.block_range_root_retriever
91+
.compute_merkle_map_from_block_range_roots(beacon.immutable_file_number)
92+
.await?
93+
.compute_root()?;
10394

10495
let mut protocol_message = ProtocolMessage::new();
10596
protocol_message.set_message_part(
@@ -117,104 +108,21 @@ impl SignableBuilder<CardanoDbBeacon> for CardanoTransactionsSignableBuilder {
117108

118109
#[cfg(test)]
119110
mod tests {
120-
use crate::test_utils::TestLogger;
121-
122-
use super::*;
123-
124-
#[tokio::test]
125-
async fn test_compute_merkle_root_in_same_block_range() {
126-
let transaction_1 = CardanoTransaction::new("tx-hash-123", 1, 10, "block_hash", 1);
127-
let transaction_2 = CardanoTransaction::new("tx-hash-456", 2, 20, "block_hash", 1);
128-
let transaction_3 = CardanoTransaction::new("tx-hash-789", 3, 30, "block_hash", 1);
129-
let transaction_4 = CardanoTransaction::new("tx-hash-abc", 4, 40, "block_hash", 1);
130-
131-
for tx in [
132-
&transaction_1,
133-
&transaction_2,
134-
&transaction_3,
135-
&transaction_4,
136-
] {
137-
assert!(
138-
tx.block_number < BlockRange::LENGTH,
139-
"all manipulated transactions should be in the same block range"
140-
);
141-
}
142-
143-
let cardano_transaction_signable_builder = CardanoTransactionsSignableBuilder::new(
144-
Arc::new(MockTransactionsImporter::new()),
145-
TestLogger::stdout(),
146-
);
147111

148-
let merkle_root_reference = cardano_transaction_signable_builder
149-
.compute_merkle_root(vec![
150-
transaction_1.clone(),
151-
transaction_2.clone(),
152-
transaction_3.clone(),
153-
])
154-
.unwrap();
112+
use crate::{entities::CardanoTransaction, test_utils::TestLogger};
155113

156-
{
157-
let transactions_set = vec![transaction_1.clone()];
158-
let mk_root = cardano_transaction_signable_builder
159-
.compute_merkle_root(transactions_set)
160-
.unwrap();
161-
assert_ne!(merkle_root_reference, mk_root);
162-
}
163-
{
164-
let transactions_set = vec![transaction_1.clone(), transaction_2.clone()];
165-
let mk_root = cardano_transaction_signable_builder
166-
.compute_merkle_root(transactions_set)
167-
.unwrap();
168-
assert_ne!(merkle_root_reference, mk_root);
169-
}
170-
{
171-
let transactions_set = vec![
172-
transaction_1.clone(),
173-
transaction_2.clone(),
174-
transaction_3.clone(),
175-
transaction_4.clone(),
176-
];
177-
let mk_root = cardano_transaction_signable_builder
178-
.compute_merkle_root(transactions_set)
179-
.unwrap();
180-
assert_ne!(merkle_root_reference, mk_root);
181-
}
182-
183-
{
184-
// In a same block range Transactions in a different order return a different merkle root.
185-
let transactions_set = vec![
186-
transaction_1.clone(),
187-
transaction_3.clone(),
188-
transaction_2.clone(),
189-
];
190-
let mk_root = cardano_transaction_signable_builder
191-
.compute_merkle_root(transactions_set)
192-
.unwrap();
193-
assert_ne!(merkle_root_reference, mk_root);
194-
}
195-
}
196-
197-
#[tokio::test]
198-
async fn test_compute_merkle_root_order_of_block_range_does_not_matter() {
199-
let transaction_1 =
200-
CardanoTransaction::new("tx-hash-123", BlockRange::LENGTH - 1, 10, "block_hash", 1);
201-
let transaction_2 =
202-
CardanoTransaction::new("tx-hash-456", BlockRange::LENGTH + 1, 20, "block_hash", 1);
203-
204-
let cardano_transaction_signable_builder = CardanoTransactionsSignableBuilder::new(
205-
Arc::new(MockTransactionsImporter::new()),
206-
TestLogger::stdout(),
207-
);
208-
209-
let merkle_root_reference = cardano_transaction_signable_builder
210-
.compute_merkle_root(vec![transaction_1.clone(), transaction_2.clone()])
211-
.unwrap();
212-
213-
let mk_root = cardano_transaction_signable_builder
214-
.compute_merkle_root(vec![transaction_2.clone(), transaction_1.clone()])
215-
.unwrap();
114+
use super::*;
216115

217-
assert_eq!(merkle_root_reference, mk_root);
116+
fn compute_mk_map_from_transactions(
117+
transactions: Vec<CardanoTransaction>,
118+
) -> MKMap<BlockRange, MKMapNode<BlockRange>> {
119+
MKMap::new_from_iter(transactions.iter().map(|tx| {
120+
(
121+
BlockRange::from_block_number(tx.block_number),
122+
MKMapNode::TreeNode(tx.transaction_hash.clone().into()),
123+
)
124+
}))
125+
.unwrap()
218126
}
219127

220128
#[tokio::test]
@@ -229,13 +137,20 @@ mod tests {
229137
CardanoTransaction::new("tx-hash-456", 20, 2, "block_hash", 12),
230138
CardanoTransaction::new("tx-hash-789", 30, 3, "block_hash", 13),
231139
];
232-
let imported_transactions = transactions.clone();
140+
let mk_map = compute_mk_map_from_transactions(transactions.clone());
233141
let mut transaction_importer = MockTransactionsImporter::new();
234142
transaction_importer
235143
.expect_import()
236-
.return_once(move |_| Ok(imported_transactions));
144+
.return_once(move |_| Ok(()));
145+
let retrieved_transactions = transactions.clone();
146+
let mut block_range_root_retriever = MockBlockRangeRootRetriever::new();
147+
block_range_root_retriever
148+
.expect_compute_merkle_map_from_block_range_roots()
149+
.return_once(move |_| Ok(compute_mk_map_from_transactions(retrieved_transactions)));
150+
237151
let cardano_transactions_signable_builder = CardanoTransactionsSignableBuilder::new(
238152
Arc::new(transaction_importer),
153+
Arc::new(block_range_root_retriever),
239154
TestLogger::stdout(),
240155
);
241156

@@ -246,13 +161,10 @@ mod tests {
246161
.unwrap();
247162

248163
// Assert
249-
let mk_root = cardano_transactions_signable_builder
250-
.compute_merkle_root(transactions)
251-
.unwrap();
252164
let mut signable_expected = ProtocolMessage::new();
253165
signable_expected.set_message_part(
254166
ProtocolMessagePartKey::CardanoTransactionsMerkleRoot,
255-
mk_root.to_hex(),
167+
mk_map.compute_root().unwrap().to_hex(),
256168
);
257169
signable_expected.set_message_part(
258170
ProtocolMessagePartKey::LatestImmutableFileNumber,
@@ -262,14 +174,17 @@ mod tests {
262174
}
263175

264176
#[tokio::test]
265-
async fn test_compute_signable_with_no_transaction_return_error() {
177+
async fn test_compute_signable_with_no_block_range_root_return_error() {
266178
let beacon = CardanoDbBeacon::default();
267179
let mut transaction_importer = MockTransactionsImporter::new();
268-
transaction_importer
269-
.expect_import()
270-
.return_once(|_| Ok(vec![]));
180+
transaction_importer.expect_import().return_once(|_| Ok(()));
181+
let mut block_range_root_retriever = MockBlockRangeRootRetriever::new();
182+
block_range_root_retriever
183+
.expect_compute_merkle_map_from_block_range_roots()
184+
.return_once(move |_| Ok(compute_mk_map_from_transactions(vec![])));
271185
let cardano_transactions_signable_builder = CardanoTransactionsSignableBuilder::new(
272186
Arc::new(transaction_importer),
187+
Arc::new(block_range_root_retriever),
273188
TestLogger::stdout(),
274189
);
275190

0 commit comments

Comments
 (0)