Skip to content

Commit 0e40969

Browse files
authored
Merge pull request #1637 from input-output-hk/djo/1629/proof_gen_memory_optimisation
Proof generation memory optimisation
2 parents 5e22115 + 030936f commit 0e40969

File tree

11 files changed

+230
-125
lines changed

11 files changed

+230
-125
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mithril-aggregator/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mithril-aggregator"
3-
version = "0.4.58"
3+
version = "0.4.59"
44
description = "A Mithril Aggregator server"
55
authors = { workspace = true }
66
edition = { workspace = true }

mithril-aggregator/src/database/provider/cardano_transaction/get_cardano_transaction.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ impl<'client> GetCardanoTransactionProvider<'client> {
2929
)
3030
}
3131

32+
pub fn get_transaction_hashes_condition(
33+
&self,
34+
transactions_hashes: Vec<TransactionHash>,
35+
) -> WhereCondition {
36+
let hashes_values = transactions_hashes.into_iter().map(Value::String).collect();
37+
38+
WhereCondition::where_in("transaction_hash", hashes_values)
39+
}
40+
3241
pub fn get_transaction_up_to_beacon_condition(
3342
&self,
3443
beacon: ImmutableFileNumber,

mithril-aggregator/src/database/repository/cardano_transaction_repository.rs

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,20 @@ impl TransactionsRetriever for CardanoTransactionRepository {
159159
.collect::<Vec<CardanoTransaction>>()
160160
})
161161
}
162+
163+
async fn get_by_hashes(
164+
&self,
165+
hashes: Vec<TransactionHash>,
166+
) -> StdResult<Vec<CardanoTransaction>> {
167+
let provider = GetCardanoTransactionProvider::new(&self.connection);
168+
let filters = provider.get_transaction_hashes_condition(hashes);
169+
let transactions = provider.find(filters)?;
170+
171+
Ok(transactions
172+
.into_iter()
173+
.map(|record| record.into())
174+
.collect())
175+
}
162176
}
163177

164178
#[cfg(test)]
@@ -172,25 +186,67 @@ mod tests {
172186
let connection = Arc::new(cardano_tx_db_connection().unwrap());
173187
let repository = CardanoTransactionRepository::new(connection);
174188
repository
175-
.create_transaction("tx-hash-123", 10, 50, "block_hash-123", 99)
189+
.create_transactions(vec![
190+
CardanoTransaction::new("tx_hash-123", 10, 50, "block_hash-123", 99),
191+
CardanoTransaction::new("tx_hash-456", 11, 51, "block_hash-456", 100),
192+
])
176193
.await
177194
.unwrap();
195+
196+
{
197+
let transaction_result = repository.get_transaction("tx_hash-123").await.unwrap();
198+
assert_eq!(
199+
Some(CardanoTransactionRecord {
200+
transaction_hash: "tx_hash-123".to_string(),
201+
block_number: 10,
202+
slot_number: 50,
203+
block_hash: "block_hash-123".to_string(),
204+
immutable_file_number: 99
205+
}),
206+
transaction_result
207+
);
208+
}
209+
{
210+
let transaction_result = repository.get_transaction("not-exist").await.unwrap();
211+
assert_eq!(None, transaction_result);
212+
}
213+
}
214+
215+
#[tokio::test]
216+
async fn repository_get_transaction_by_hashes() {
217+
let connection = Arc::new(cardano_tx_db_connection().unwrap());
218+
let repository = CardanoTransactionRepository::new(connection);
178219
repository
179-
.create_transaction("tx-hash-456", 11, 51, "block_hash-456", 100)
220+
.create_transactions(vec![
221+
CardanoTransaction::new("tx_hash-123", 10, 50, "block_hash-123", 99),
222+
CardanoTransaction::new("tx_hash-456", 11, 51, "block_hash-456", 100),
223+
CardanoTransaction::new("tx_hash-789", 12, 52, "block_hash-789", 101),
224+
])
180225
.await
181226
.unwrap();
182-
let transaction_result = repository.get_transaction("tx-hash-123").await.unwrap();
183227

184-
assert_eq!(
185-
Some(CardanoTransactionRecord {
186-
transaction_hash: "tx-hash-123".to_string(),
187-
block_number: 10,
188-
slot_number: 50,
189-
block_hash: "block_hash-123".to_string(),
190-
immutable_file_number: 99
191-
}),
192-
transaction_result
193-
);
228+
{
229+
let transactions = repository
230+
.get_by_hashes(vec!["tx_hash-123".to_string(), "tx_hash-789".to_string()])
231+
.await
232+
.unwrap();
233+
234+
assert_eq!(
235+
vec![
236+
CardanoTransaction::new("tx_hash-123", 10, 50, "block_hash-123", 99),
237+
CardanoTransaction::new("tx_hash-789", 12, 52, "block_hash-789", 101),
238+
],
239+
transactions
240+
);
241+
}
242+
{
243+
let transactions = repository
244+
.get_by_hashes(vec!["not-exist".to_string()])
245+
.await
246+
.unwrap();
247+
248+
assert_eq!(Vec::<CardanoTransaction>::new(), transactions);
249+
}
194250
}
195251

196252
#[tokio::test]

mithril-aggregator/src/services/prover.rs

Lines changed: 93 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,8 @@ use mithril_common::{
1212
StdResult,
1313
};
1414

15-
#[cfg(test)]
16-
use mockall::automock;
17-
1815
/// Prover service is the cryptographic engine in charge of producing cryptographic proofs for transactions
19-
#[cfg_attr(test, automock)]
16+
#[cfg_attr(test, mockall::automock)]
2017
#[async_trait]
2118
pub trait ProverService: Sync + Send {
2219
/// Compute the cryptographic proofs for the given transactions
@@ -28,11 +25,17 @@ pub trait ProverService: Sync + Send {
2825
}
2926

3027
/// Transactions retriever
31-
#[cfg_attr(test, automock)]
28+
#[cfg_attr(test, mockall::automock)]
3229
#[async_trait]
3330
pub trait TransactionsRetriever: Sync + Send {
34-
/// Get transactions up to given beacon using chronological order
31+
/// Get all transactions up to given beacon using chronological order
3532
async fn get_up_to(&self, beacon: &CardanoDbBeacon) -> StdResult<Vec<CardanoTransaction>>;
33+
34+
/// Get a list of transactions by hashes using chronological order
35+
async fn get_by_hashes(
36+
&self,
37+
hashes: Vec<TransactionHash>,
38+
) -> StdResult<Vec<CardanoTransaction>>;
3639
}
3740

3841
/// Mithril prover
@@ -48,9 +51,24 @@ impl MithrilProverService {
4851
}
4952
}
5053

54+
async fn get_transactions_by_hashes_with_block_range(
55+
&self,
56+
hashes: Vec<TransactionHash>,
57+
) -> StdResult<Vec<(BlockRange, CardanoTransaction)>> {
58+
let transactions = self.transaction_retriever.get_by_hashes(hashes).await?;
59+
let transactions_with_block_range = transactions
60+
.into_iter()
61+
.map(|transaction| {
62+
let block_range = BlockRange::from_block_number(transaction.block_number);
63+
(block_range, transaction)
64+
})
65+
.collect::<Vec<_>>();
66+
Ok(transactions_with_block_range)
67+
}
68+
5169
fn compute_merkle_map_from_transactions(
5270
&self,
53-
transactions: &[CardanoTransaction],
71+
transactions: Vec<CardanoTransaction>,
5472
) -> StdResult<MKMap<BlockRange, MKMapNode<BlockRange>>> {
5573
let mut transactions_by_block_ranges: HashMap<BlockRange, Vec<TransactionHash>> =
5674
HashMap::new();
@@ -59,9 +77,9 @@ impl MithrilProverService {
5977
transactions_by_block_ranges
6078
.entry(block_range)
6179
.or_default()
62-
.push(transaction.transaction_hash.to_owned());
80+
.push(transaction.transaction_hash);
6381
}
64-
let mk_hash_map = MKMap::new(
82+
let mk_hash_map = MKMap::new_from_iter(
6583
transactions_by_block_ranges
6684
.into_iter()
6785
.try_fold(
@@ -70,8 +88,7 @@ impl MithrilProverService {
7088
acc.push((block_range, MKTree::new(&transactions)?.into()));
7189
Ok(acc)
7290
},
73-
)?
74-
.as_slice(),
91+
)?,
7592
)
7693
.with_context(|| "ProverService failed to compute the merkelized structure that proves ownership of the transaction")?;
7794

@@ -86,17 +103,16 @@ impl ProverService for MithrilProverService {
86103
up_to: &CardanoDbBeacon,
87104
transaction_hashes: &[TransactionHash],
88105
) -> StdResult<Vec<CardanoTransactionsSetProof>> {
106+
// 1 - Get transactions to prove per block range
107+
let transactions_to_prove = self
108+
.get_transactions_by_hashes_with_block_range(transaction_hashes.to_vec())
109+
.await?;
110+
111+
// 2 - Compute Transactions Merkle Tree
89112
let transactions = self.transaction_retriever.get_up_to(up_to).await?;
90-
let mk_map = self.compute_merkle_map_from_transactions(&transactions)?;
91-
let transactions_to_prove = transactions
92-
.iter()
93-
.filter_map(|transaction| {
94-
let block_range = BlockRange::from_block_number(transaction.block_number);
95-
transaction_hashes
96-
.contains(&transaction.transaction_hash)
97-
.then(|| (block_range, transaction.to_owned()))
98-
})
99-
.collect::<Vec<_>>();
113+
let mk_map = self.compute_merkle_map_from_transactions(transactions)?;
114+
115+
// 3 - Compute proof for each transaction to prove
100116
let mut transaction_hashes_certified = vec![];
101117
for (_block_range, transaction) in transactions_to_prove {
102118
let mk_tree_node_transaction_hash: MKTreeNode =
@@ -105,9 +121,10 @@ impl ProverService for MithrilProverService {
105121
.compute_proof(&[mk_tree_node_transaction_hash])
106122
.is_ok()
107123
{
108-
transaction_hashes_certified.push(transaction.transaction_hash.to_string());
124+
transaction_hashes_certified.push(transaction.transaction_hash);
109125
}
110126
}
127+
111128
if !transaction_hashes_certified.is_empty() {
112129
let mk_leaves: Vec<MKTreeNode> = transaction_hashes_certified
113130
.iter()
@@ -154,15 +171,31 @@ mod tests {
154171
(hashes, transactions)
155172
}
156173

174+
fn build_prover<F>(retriever_mock_config: F) -> MithrilProverService
175+
where
176+
F: FnOnce(&mut MockTransactionsRetriever),
177+
{
178+
let mut transaction_retriever = MockTransactionsRetriever::new();
179+
retriever_mock_config(&mut transaction_retriever);
180+
181+
MithrilProverService::new(Arc::new(transaction_retriever))
182+
}
183+
157184
#[tokio::test]
158185
async fn compute_proof_for_one_set_with_multiple_transactions() {
159186
let (transaction_hashes, transactions) = generate_transactions(3);
160-
let mut transaction_retriever = MockTransactionsRetriever::new();
161-
transaction_retriever
162-
.expect_get_up_to()
163-
.with(eq(fake_data::beacon()))
164-
.return_once(move |_| Ok(transactions));
165-
let prover = MithrilProverService::new(Arc::new(transaction_retriever));
187+
let prover = build_prover(|retriever_mock| {
188+
let transactions_by_hashes_res = transactions.clone();
189+
retriever_mock
190+
.expect_get_by_hashes()
191+
.with(eq(transaction_hashes.clone()))
192+
.return_once(move |_| Ok(transactions_by_hashes_res));
193+
retriever_mock
194+
.expect_get_up_to()
195+
.with(eq(fake_data::beacon()))
196+
.return_once(move |_| Ok(transactions));
197+
});
198+
166199
let transactions_set_proof = prover
167200
.compute_transactions_proofs(&fake_data::beacon(), &transaction_hashes)
168201
.await
@@ -178,13 +211,18 @@ mod tests {
178211

179212
#[tokio::test]
180213
async fn cant_compute_proof_for_unknown_transaction() {
181-
let (transaction_hashes, _transactions) = generate_transactions(3);
182-
let mut transaction_retriever = MockTransactionsRetriever::new();
183-
transaction_retriever
184-
.expect_get_up_to()
185-
.with(eq(fake_data::beacon()))
186-
.returning(|_| Ok(vec![]));
187-
let prover = MithrilProverService::new(Arc::new(transaction_retriever));
214+
let (transaction_hashes, transactions) = generate_transactions(3);
215+
let prover = build_prover(|retriever_mock| {
216+
retriever_mock
217+
.expect_get_by_hashes()
218+
.with(eq(transaction_hashes.clone()))
219+
.return_once(move |_| Ok(transactions));
220+
retriever_mock
221+
.expect_get_up_to()
222+
.with(eq(fake_data::beacon()))
223+
.returning(|_| Ok(vec![]));
224+
});
225+
188226
let transactions_set_proof = prover
189227
.compute_transactions_proofs(&fake_data::beacon(), &transaction_hashes)
190228
.await
@@ -196,14 +234,20 @@ mod tests {
196234
#[tokio::test]
197235
async fn compute_proof_for_one_set_of_three_known_transactions_and_two_unknowns() {
198236
let (transaction_hashes, transactions) = generate_transactions(5);
199-
// The last two are not in the "store"
200-
let transactions = transactions[0..=2].to_vec();
201-
let mut transaction_retriever = MockTransactionsRetriever::new();
202-
transaction_retriever
203-
.expect_get_up_to()
204-
.with(eq(fake_data::beacon()))
205-
.return_once(move |_| Ok(transactions));
206-
let prover = MithrilProverService::new(Arc::new(transaction_retriever));
237+
let prover = build_prover(|retriever_mock| {
238+
// The last two are not in the "store"
239+
let transactions = transactions[0..=2].to_vec();
240+
let transactions_by_hashes_res = transactions.clone();
241+
retriever_mock
242+
.expect_get_by_hashes()
243+
.with(eq(transaction_hashes.clone()))
244+
.return_once(move |_| Ok(transactions_by_hashes_res));
245+
retriever_mock
246+
.expect_get_up_to()
247+
.with(eq(fake_data::beacon()))
248+
.return_once(move |_| Ok(transactions));
249+
});
250+
207251
let transactions_set_proof = prover
208252
.compute_transactions_proofs(&fake_data::beacon(), &transaction_hashes)
209253
.await
@@ -220,13 +264,13 @@ mod tests {
220264
#[tokio::test]
221265
async fn cant_compute_proof_if_retriever_fail() {
222266
let (transaction_hashes, _transactions) = generate_transactions(3);
223-
let mut transaction_retriever = MockTransactionsRetriever::new();
224-
transaction_retriever
225-
.expect_get_up_to()
226-
.with(eq(fake_data::beacon()))
227-
.returning(|_| Err(anyhow!("Error")));
267+
let prover = build_prover(|retriever_mock| {
268+
retriever_mock
269+
.expect_get_by_hashes()
270+
.with(eq(transaction_hashes.clone()))
271+
.returning(|_| Err(anyhow!("Error")));
272+
});
228273

229-
let prover = MithrilProverService::new(Arc::new(transaction_retriever));
230274
prover
231275
.compute_transactions_proofs(&fake_data::beacon(), &transaction_hashes)
232276
.await

mithril-common/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mithril-common"
3-
version = "0.3.31"
3+
version = "0.3.32"
44
description = "Common types, interfaces, and utilities for Mithril nodes."
55
authors = { workspace = true }
66
edition = { workspace = true }

0 commit comments

Comments
 (0)