Skip to content

Commit 871912f

Browse files
authored
Merge pull request #1660 from input-output-hk/ensemble/1634-sign-cardano-tx-with-block-range-roots
Sign `Cardano Transactions` with `Block Range Merkle Roots`
2 parents 512fe8d + 7094e3a commit 871912f

File tree

22 files changed

+393
-253
lines changed

22 files changed

+393
-253
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
As a minor extension, we have adopted a slightly different versioning convention for the Mithril distributions (https://mithril.network/doc/adr/3#decision)
99

10-
## Mithril Distribution [2416.0] - UNRELEASED
10+
## Mithril Distribution [XXXX.X] - UNRELEASED
11+
12+
- Support computation of the Cardano Transactions signature with the pre-computed Block Range Merkle Roots retrieved from the database.
13+
14+
## Mithril Distribution [2418.1] - UNRELEASED
1115

1216
- **BREAKING** changes in Mithril client CLI:
1317
- Certificate chain structure has been modified to remove coupling with immutable file number.

Cargo.lock

Lines changed: 3 additions & 3 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.5.0"
3+
version = "0.5.1"
44
description = "A Mithril Aggregator server"
55
authors = { workspace = true }
66
edition = { workspace = true }

mithril-aggregator/src/database/provider/block_range_root/get_block_range_root.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
use mithril_persistence::sqlite::{Provider, SourceAlias, SqLiteEntity, SqliteConnection};
1+
use mithril_common::entities::BlockNumber;
2+
use mithril_persistence::sqlite::{
3+
Provider, SourceAlias, SqLiteEntity, SqliteConnection, WhereCondition,
4+
};
5+
use sqlite::Value;
26

37
use crate::database::record::BlockRangeRootRecord;
48

@@ -8,11 +12,14 @@ pub struct GetBlockRangeRootProvider<'client> {
812
}
913

1014
impl<'client> GetBlockRangeRootProvider<'client> {
11-
#[cfg(test)]
1215
/// Create a new instance
1316
pub fn new(connection: &'client SqliteConnection) -> Self {
1417
Self { connection }
1518
}
19+
20+
pub fn get_up_to_block_number_condition(&self, block_number: BlockNumber) -> WhereCondition {
21+
WhereCondition::new("end < ?*", vec![Value::Integer(block_number as i64)])
22+
}
1623
}
1724

1825
#[cfg(test)]

mithril-aggregator/src/database/provider/block_range_root/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ mod get_block_range_root;
22
mod get_interval_without_block_range_provider;
33
mod insert_block_range;
44

5-
#[cfg(test)]
65
pub use get_block_range_root::*;
76
pub use get_interval_without_block_range_provider::*;
87
pub use insert_block_range::*;

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

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ use std::ops::Range;
22

33
use sqlite::Value;
44

5-
use mithril_common::entities::{BlockNumber, ImmutableFileNumber, TransactionHash};
5+
use mithril_common::entities::{BlockNumber, TransactionHash};
66
use mithril_persistence::sqlite::{
77
Provider, SourceAlias, SqLiteEntity, SqliteConnection, WhereCondition,
88
};
99

10+
#[cfg(test)]
11+
use mithril_persistence::sqlite::GetAllCondition;
12+
1013
use crate::database::record::CardanoTransactionRecord;
1114

1215
/// Simple queries to retrieve [CardanoTransaction] from the sqlite database.
@@ -40,16 +43,6 @@ impl<'client> GetCardanoTransactionProvider<'client> {
4043
WhereCondition::where_in("transaction_hash", hashes_values)
4144
}
4245

43-
pub fn get_transaction_up_to_beacon_condition(
44-
&self,
45-
beacon: ImmutableFileNumber,
46-
) -> WhereCondition {
47-
WhereCondition::new(
48-
"immutable_file_number <= ?*",
49-
vec![Value::Integer(beacon as i64)],
50-
)
51-
}
52-
5346
pub fn get_transaction_between_blocks_condition(
5447
&self,
5548
range: Range<BlockNumber>,
@@ -79,3 +72,6 @@ impl<'client> Provider<'client> for GetCardanoTransactionProvider<'client> {
7972
format!("select {projection} from cardano_tx where {condition} order by rowid")
8073
}
8174
}
75+
76+
#[cfg(test)]
77+
impl GetAllCondition for GetCardanoTransactionProvider<'_> {}

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

Lines changed: 96 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,22 @@ use mithril_common::entities::{
99
BlockHash, BlockNumber, BlockRange, CardanoDbBeacon, CardanoTransaction, ImmutableFileNumber,
1010
SlotNumber, TransactionHash,
1111
};
12+
use mithril_common::signable_builder::BlockRangeRootRetriever;
1213
use mithril_common::StdResult;
1314
use mithril_persistence::sqlite::{Provider, SqliteConnection, WhereCondition};
15+
use sqlite::Value;
1416

1517
use crate::database::provider::{
16-
GetCardanoTransactionProvider, GetIntervalWithoutBlockRangeRootProvider,
17-
InsertBlockRangeRootProvider, InsertCardanoTransactionProvider,
18+
GetBlockRangeRootProvider, GetCardanoTransactionProvider,
19+
GetIntervalWithoutBlockRangeRootProvider, InsertBlockRangeRootProvider,
20+
InsertCardanoTransactionProvider,
1821
};
1922
use crate::database::record::{BlockRangeRootRecord, CardanoTransactionRecord};
2023
use crate::services::{TransactionStore, TransactionsRetriever};
2124

25+
#[cfg(test)]
26+
use mithril_persistence::sqlite::GetAllProvider;
27+
2228
/// ## Cardano transaction repository
2329
///
2430
/// This is a business oriented layer to perform actions on the database through
@@ -55,13 +61,20 @@ impl CardanoTransactionRepository {
5561
}
5662

5763
/// Return all the [CardanoTransactionRecord]s in the database up to the given beacon using
58-
/// chronological order.
64+
/// order of insertion.
65+
/// Note: until we rely on block number based beacons, this function needs to compute the highest block number for the given immutable file number.
5966
pub async fn get_transactions_up_to(
6067
&self,
6168
beacon: ImmutableFileNumber,
6269
) -> StdResult<Vec<CardanoTransactionRecord>> {
70+
// Get the highest block number for the given immutable number.
71+
// This is a temporary fix that will be removed when the retrieval is based on block number instead of immutable number.
72+
let block_number = self
73+
.get_highest_block_number_for_immutable_number(beacon)
74+
.await?
75+
.unwrap_or(0);
6376
let provider = GetCardanoTransactionProvider::new(&self.connection);
64-
let filters = provider.get_transaction_up_to_beacon_condition(beacon);
77+
let filters = provider.get_transaction_between_blocks_condition(0..block_number + 1);
6578
let transactions = provider.find(filters)?;
6679

6780
Ok(transactions.collect())
@@ -130,6 +143,47 @@ impl CardanoTransactionRepository {
130143

131144
Ok(cursor.collect())
132145
}
146+
147+
// TODO: remove this function when the Cardano transaction signature is based on block number instead of immutable number
148+
async fn get_highest_block_number_for_immutable_number(
149+
&self,
150+
immutable_file_number: ImmutableFileNumber,
151+
) -> StdResult<Option<BlockNumber>> {
152+
let sql =
153+
"select max(block_number) as highest from cardano_tx where immutable_file_number <= $1;";
154+
match self
155+
.connection
156+
.prepare(sql)
157+
.with_context(|| {
158+
format!(
159+
"Prepare query error: SQL=`{}`",
160+
&sql.replace('\n', " ").trim()
161+
)
162+
})?
163+
.iter()
164+
.bind::<&[(_, Value)]>(&[(1, Value::Integer(immutable_file_number as i64))])?
165+
.next()
166+
{
167+
None => Ok(None),
168+
Some(row) => {
169+
let highest = row?.read::<Option<i64>, _>(0);
170+
highest
171+
.map(u64::try_from)
172+
.transpose()
173+
.with_context(||
174+
format!("Integer field max(block_number) (value={highest:?}) is incompatible with u64 representation.")
175+
)
176+
}
177+
}
178+
}
179+
180+
#[cfg(test)]
181+
pub(crate) async fn get_all(&self) -> StdResult<Vec<CardanoTransaction>> {
182+
let provider = GetCardanoTransactionProvider::new(&self.connection);
183+
let records = provider.get_all()?;
184+
185+
Ok(records.map(|record| record.into()).collect())
186+
}
133187
}
134188

135189
#[cfg(test)]
@@ -261,6 +315,31 @@ impl TransactionsRetriever for CardanoTransactionRepository {
261315
}
262316
}
263317

318+
#[async_trait]
319+
impl BlockRangeRootRetriever for CardanoTransactionRepository {
320+
async fn retrieve_block_range_roots(
321+
&self,
322+
up_to_beacon: ImmutableFileNumber,
323+
) -> StdResult<Box<dyn Iterator<Item = (BlockRange, MKTreeNode)>>> {
324+
// Get the highest block number for the given immutable number.
325+
// This is a temporary fix that will be removed when the retrieval is based on block number instead of immutable number.
326+
let block_number = self
327+
.get_highest_block_number_for_immutable_number(up_to_beacon)
328+
.await?
329+
.unwrap_or(0);
330+
let provider = GetBlockRangeRootProvider::new(&self.connection);
331+
let filters = provider.get_up_to_block_number_condition(block_number);
332+
let block_range_roots = provider.find(filters)?;
333+
let block_range_roots = block_range_roots
334+
.into_iter()
335+
.map(|record| -> (BlockRange, MKTreeNode) { record.into() })
336+
.collect::<Vec<_>>() // TODO: remove this collect when we should ba able return the iterator directly
337+
.into_iter();
338+
339+
Ok(Box::new(block_range_roots))
340+
}
341+
}
342+
264343
#[cfg(test)]
265344
mod tests {
266345
use mithril_persistence::sqlite::GetAllProvider;
@@ -410,27 +489,34 @@ mod tests {
410489
let connection = Arc::new(cardano_tx_db_connection().unwrap());
411490
let repository = CardanoTransactionRepository::new(connection);
412491

492+
// Build transactions with block numbers from 10 to 40 and immutable file numbers from 12 to 14
413493
let cardano_transactions: Vec<CardanoTransactionRecord> = (20..=40)
414494
.map(|i| CardanoTransactionRecord {
415495
transaction_hash: format!("tx-hash-{i}"),
416-
block_number: i % 10,
496+
block_number: i,
417497
slot_number: i * 100,
418498
block_hash: format!("block-hash-{i}"),
419-
immutable_file_number: i,
499+
immutable_file_number: i / 10 + 10,
420500
})
421501
.collect();
502+
422503
repository
423504
.create_transactions(cardano_transactions.clone())
424505
.await
425506
.unwrap();
426507

427-
let transaction_result = repository.get_transactions_up_to(34).await.unwrap();
428-
assert_eq!(cardano_transactions[0..=14].to_vec(), transaction_result);
508+
let transaction_result = repository.get_transactions_up_to(12).await.unwrap();
509+
let transaction_up_to_immutable_file_number_12 = cardano_transactions[0..10].to_vec();
510+
assert_eq!(
511+
transaction_up_to_immutable_file_number_12,
512+
transaction_result
513+
);
429514

430515
let transaction_result = repository.get_transactions_up_to(300).await.unwrap();
431-
assert_eq!(cardano_transactions.clone(), transaction_result);
516+
let transaction_all = cardano_transactions[..].to_vec();
517+
assert_eq!(transaction_all, transaction_result);
432518

433-
let transaction_result = repository.get_transactions_up_to(19).await.unwrap();
519+
let transaction_result = repository.get_transactions_up_to(9).await.unwrap();
434520
assert_eq!(Vec::<CardanoTransactionRecord>::new(), transaction_result);
435521
}
436522

mithril-aggregator/src/dependency_injection/builder.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,8 +1062,10 @@ impl DependenciesBuilder {
10621062
Some(1),
10631063
self.get_logger().await?,
10641064
));
1065+
let block_range_root_retriever = self.get_transaction_repository().await?;
10651066
let cardano_transactions_builder = Arc::new(CardanoTransactionsSignableBuilder::new(
10661067
transactions_importer,
1068+
block_range_root_retriever,
10671069
self.get_logger().await?,
10681070
));
10691071
let signable_builder_service = Arc::new(MithrilSignableBuilderService::new(

mithril-aggregator/src/services/cardano_transactions_importer.rs

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -174,15 +174,9 @@ impl CardanoTransactionsImporter {
174174

175175
#[async_trait]
176176
impl TransactionsImporter for CardanoTransactionsImporter {
177-
async fn import(
178-
&self,
179-
up_to_beacon: ImmutableFileNumber,
180-
) -> StdResult<Vec<CardanoTransaction>> {
177+
async fn import(&self, up_to_beacon: ImmutableFileNumber) -> StdResult<()> {
181178
self.import_transactions(up_to_beacon).await?;
182-
self.import_block_ranges().await?;
183-
184-
let transactions = self.transaction_store.get_up_to(up_to_beacon).await?;
185-
Ok(transactions)
179+
self.import_block_ranges().await
186180
}
187181
}
188182

@@ -288,7 +282,7 @@ mod tests {
288282
.await
289283
.expect("Transactions Importer should succeed");
290284

291-
let stored_transactions = repository.get_up_to(10000).await.unwrap();
285+
let stored_transactions = repository.get_all().await.unwrap();
292286
assert_eq!(expected_transactions, stored_transactions);
293287
}
294288

@@ -410,7 +404,7 @@ mod tests {
410404
.await
411405
.expect("Transactions Importer should succeed");
412406

413-
let transactions = repository.get_up_to(10000).await.unwrap();
407+
let transactions = repository.get_all().await.unwrap();
414408
assert_eq!(vec![last_tx], transactions);
415409
}
416410

@@ -448,15 +442,15 @@ mod tests {
448442
CardanoTransactionsImporter::new_for_test(Arc::new(scanner_mock), repository.clone())
449443
};
450444

451-
let stored_transactions = repository.get_up_to(10000).await.unwrap();
445+
let stored_transactions = repository.get_all().await.unwrap();
452446
assert_eq!(stored_block.into_transactions(), stored_transactions);
453447

454448
importer
455449
.import_transactions(up_to_beacon)
456450
.await
457451
.expect("Transactions Importer should succeed");
458452

459-
let stored_transactions = repository.get_up_to(10000).await.unwrap();
453+
let stored_transactions = repository.get_all().await.unwrap();
460454
assert_eq!(expected_transactions, stored_transactions);
461455
}
462456

@@ -619,24 +613,27 @@ mod tests {
619613
ScannedBlock::new("block_hash-2", 20, 25, 12, vec!["tx_hash-3", "tx_hash-4"]),
620614
];
621615
let transactions = into_transactions(&blocks);
622-
let importer = {
623-
let connection = cardano_tx_db_connection().unwrap();
624-
625-
CardanoTransactionsImporter::new_for_test(
616+
let (importer, repository) = {
617+
let connection = Arc::new(cardano_tx_db_connection().unwrap());
618+
let repository = Arc::new(CardanoTransactionRepository::new(connection.clone()));
619+
let importer = CardanoTransactionsImporter::new_for_test(
626620
Arc::new(DumbBlockScanner::new(blocks.clone())),
627-
Arc::new(CardanoTransactionRepository::new(Arc::new(connection))),
628-
)
621+
Arc::new(CardanoTransactionRepository::new(connection.clone())),
622+
);
623+
(importer, repository)
629624
};
630625

631-
let cold_imported_transactions = importer
626+
importer
632627
.import(12)
633628
.await
634629
.expect("Transactions Importer should succeed");
630+
let cold_imported_transactions = repository.get_all().await.unwrap();
635631

636-
let warm_imported_transactions = importer
632+
importer
637633
.import(12)
638634
.await
639635
.expect("Transactions Importer should succeed");
636+
let warm_imported_transactions = repository.get_all().await.unwrap();
640637

641638
assert_eq!(transactions, cold_imported_transactions);
642639
assert_eq!(cold_imported_transactions, warm_imported_transactions);

0 commit comments

Comments
 (0)