Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block Number beacon in cardano transaction #1727

Merged
merged 31 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ad690a7
Implement `Display` into `ChainPoint`
Alenar May 29, 2024
f9479f9
Change CardanoTransactions signed entity beacon to `(Epoch,ChainPoint)`
Alenar May 29, 2024
765309d
Update `hydrate_signed_entity_type` in `mithril-persistence`
Alenar May 29, 2024
d680428
Partialy adapt aggregator
Alenar May 29, 2024
b0697b1
Make CardanoTransactionsSignableBuilder use ChainPoint beacon
Alenar May 29, 2024
203ceaf
Rename `LatestImmutableFileNumber` protocol message part to `LatestBl…
Alenar May 29, 2024
754ca9a
Update BlockScanner signature to 'from BlockNumber until ChainPoint'
Alenar May 30, 2024
cf581e1
Make CardanoBlockScanner ignore it's upper bound
Alenar May 30, 2024
a16aef2
Make CardanoBlockScanner find it's lower bound using a trait
Alenar May 30, 2024
488cf82
Add `get_transaction_highest_block_number` to shared CardanoTransacti…
Alenar May 30, 2024
017b28b
Adapt Signer to the ChainPoint based Scanner & BlockRange retriever
Alenar May 30, 2024
0e1abf4
Update aggregator dependency construction
Alenar May 31, 2024
fabb426
Make aggregator TransactionImporter use ChainPoint
Alenar May 31, 2024
b005c51
Make aggregator services & cardano transaction artifact builder use C…
Alenar May 31, 2024
3847963
Make CardanoTransactionSnapshot entity use a ChainPoint beacon
Alenar May 31, 2024
0ab07cd
Use chain point in common CardanoTransaction messages
Alenar May 31, 2024
ba6f7c9
Adapt aggregator to the updated CardanoTransactionSnapshot artifact
Alenar May 31, 2024
7c7da5a
Adapt client, client-cli and end-to-end to the ChainPoint beacon
Alenar May 31, 2024
6eade6c
Remove now unused repository method fetching data using Immutables
Alenar May 31, 2024
686e219
Simplify CardanoTransaction signed entity type to `Epoch,BlockNumber`
Alenar Jun 3, 2024
8d1e1b8
Make SignableBuilder use a BlockNumber as target
Alenar Jun 3, 2024
dcceb41
Transfer immutable offsetting responsability to block scanner
Alenar Jun 3, 2024
e95a839
Introduce CardanoTransactionSigning config
Alenar Jun 3, 2024
6505bc7
Add `cardano_transactions_signing_config` to Aggregator configuration
Alenar Jun 4, 2024
865d9f3
Cleanup CardanoDbBeacon based certificate & signed entity of CardanoT…
Alenar Jun 4, 2024
a5b5e4c
Update aggregator fake default data
Alenar Jun 4, 2024
fc41440
Update explorer to new CardanoTransaction signed entity beacon
Alenar Jun 4, 2024
8840169
Add `cardano_transactions_signing_config` in aggregator docusaurus doc
Alenar Jun 5, 2024
c80f17e
Adjustements from PR reviews & fix broken doc tests
Alenar Jun 5, 2024
026f3d6
PR Review adjusments: enhance tests
Alenar Jun 5, 2024
70a8a61
Upgrade crates versions and update Changelog
Alenar Jun 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 31 additions & 5 deletions internal/mithril-persistence/src/database/hydrator.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! Shared hydrator helpers for persistence

use serde::Deserialize;

use mithril_common::entities::{
CardanoDbBeacon, Epoch, SignedEntityType, SignedEntityTypeDiscriminants,
BlockNumber, CardanoDbBeacon, Epoch, SignedEntityType, SignedEntityTypeDiscriminants,
};

use crate::sqlite::HydrationError;
Expand Down Expand Up @@ -69,15 +71,39 @@ impl Hydrator {
SignedEntityType::CardanoImmutableFilesFull(beacon)
}
SignedEntityTypeDiscriminants::CardanoTransactions => {
let beacon: CardanoDbBeacon = serde_json::from_str(beacon_str).map_err(|e| {
HydrationError::InvalidData(format!(
#[derive(Deserialize)]
struct CardanoTransactionsBeacon {
epoch: Epoch,
block_number: BlockNumber,
}

let beacon: CardanoTransactionsBeacon =
serde_json::from_str(beacon_str).map_err(|e| {
HydrationError::InvalidData(format!(
"Invalid Beacon JSON in open_message.beacon: '{beacon_str}'. Error: {e}"
))
})?;
SignedEntityType::CardanoTransactions(beacon)
})?;
SignedEntityType::CardanoTransactions(beacon.epoch, beacon.block_number)
}
};

Ok(signed_entity)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn hydrate_cardano_transaction_signed_entity_type() {
let expected = SignedEntityType::CardanoTransactions(Epoch(35), 77);
let signed_entity = Hydrator::hydrate_signed_entity_type(
SignedEntityTypeDiscriminants::CardanoTransactions.index(),
&expected.get_json_beacon().unwrap(),
)
.unwrap();

assert_eq!(expected, signed_entity);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ impl GetCardanoTransactionQuery {

Self { condition }
}

pub fn with_highest_block_number() -> Self {
Self {
condition: WhereCondition::new(
"block_number = (select max(block_number) from cardano_tx)",
vec![],
),
}
}
}

impl Query for GetCardanoTransactionQuery {
Expand All @@ -80,3 +89,49 @@ impl Query for GetCardanoTransactionQuery {
format!("select {projection} from cardano_tx where {condition} order by block_number, transaction_hash")
}
}

#[cfg(test)]
mod tests {
use crate::database::query::InsertCardanoTransactionQuery;
use crate::database::test_helper::cardano_tx_db_connection;
use crate::sqlite::{ConnectionExtensions, SqliteConnection};

use super::*;

fn insert_transactions(connection: &SqliteConnection, records: Vec<CardanoTransactionRecord>) {
connection
.fetch_first(InsertCardanoTransactionQuery::insert_many(records).unwrap())
.unwrap();
}

#[test]
fn with_highest_block_number() {
let connection = cardano_tx_db_connection().unwrap();

let cursor = connection
.fetch(GetCardanoTransactionQuery::with_highest_block_number())
.unwrap();
assert_eq!(0, cursor.count());

insert_transactions(
&connection,
vec![
CardanoTransactionRecord::new("tx-hash-0", 10, 50, "block-hash-10", 1),
CardanoTransactionRecord::new("tx-hash-1", 10, 51, "block-hash-10", 1),
CardanoTransactionRecord::new("tx-hash-2", 11, 54, "block-hash-11", 1),
CardanoTransactionRecord::new("tx-hash-3", 11, 55, "block-hash-11", 1),
],
);

let records: Vec<CardanoTransactionRecord> = connection
.fetch_collect(GetCardanoTransactionQuery::with_highest_block_number())
.unwrap();
assert_eq!(
vec![
CardanoTransactionRecord::new("tx-hash-2", 11, 54, "block-hash-11", 1),
CardanoTransactionRecord::new("tx-hash-3", 11, 55, "block-hash-11", 1),
],
records
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ use std::sync::Arc;

use anyhow::Context;
use async_trait::async_trait;
use sqlite::Value;

use mithril_common::cardano_block_scanner::ImmutableLowerBoundFinder;
use mithril_common::crypto_helper::MKTreeNode;
use mithril_common::entities::{
BlockHash, BlockNumber, BlockRange, CardanoTransaction, ImmutableFileNumber, SlotNumber,
TransactionHash,
BlockHash, BlockNumber, BlockRange, CardanoTransaction, ChainPoint, ImmutableFileNumber,
SlotNumber, TransactionHash,
};
use mithril_common::signable_builder::BlockRangeRootRetriever;
use mithril_common::StdResult;
Expand Down Expand Up @@ -51,27 +51,6 @@ impl CardanoTransactionRepository {
.fetch_collect(GetCardanoTransactionQuery::between_blocks(range))
}

/// Return all the [CardanoTransactionRecord]s in the database up to the given beacon.
///
/// Note: until we rely on block number based beacons, this function needs to compute the highest block number for the given immutable file number.
pub async fn get_transactions_up_to(
&self,
beacon: ImmutableFileNumber,
) -> StdResult<Vec<CardanoTransactionRecord>> {
// Get the highest block number for the given immutable number.
// This is a temporary fix that will be removed when the retrieval is based on block number instead of immutable number.

if let Some(block_number) = self
.get_highest_block_number_for_immutable_number(beacon)
.await?
{
self.get_transactions_in_range_blocks(0..block_number + 1)
.await
} else {
Ok(vec![])
}
}

/// Return the [CardanoTransactionRecord] for the given transaction hash.
pub async fn get_transaction<T: Into<TransactionHash>>(
&self,
Expand Down Expand Up @@ -127,22 +106,15 @@ impl CardanoTransactionRepository {
.fetch_collect(InsertBlockRangeRootQuery::insert_many(records)?)
}

// TODO: remove this function when the Cardano transaction signature is based on block number instead of immutable number
/// Get the highest [BlockNumber] of the cardano transactions stored in the database.
Alenar marked this conversation as resolved.
Show resolved Hide resolved
pub async fn get_highest_block_number_for_immutable_number(
&self,
immutable_file_number: ImmutableFileNumber,
) -> StdResult<Option<BlockNumber>> {
let highest: Option<i64> = self.connection.query_single_cell(
"select max(block_number) as highest from cardano_tx where immutable_file_number <= ?;",
&[Value::Integer(immutable_file_number as i64)],
)?;
highest
.map(u64::try_from)
.transpose()
.with_context(||
format!("Integer field max(block_number) (value={highest:?}) is incompatible with u64 representation.")
)
pub async fn get_transaction_highest_chain_point(&self) -> StdResult<Option<ChainPoint>> {
let first_transaction_with_highest_block_number = self
.connection
.fetch_first(GetCardanoTransactionQuery::with_highest_block_number())?;

Ok(first_transaction_with_highest_block_number.map(|record| {
ChainPoint::new(record.slot_number, record.block_number, record.block_hash)
}))
}

/// Get the highest start [BlockNumber] of the block range roots stored in the database.
Expand Down Expand Up @@ -288,24 +260,24 @@ impl CardanoTransactionRepository {
impl BlockRangeRootRetriever for CardanoTransactionRepository {
async fn retrieve_block_range_roots(
&self,
up_to_beacon: ImmutableFileNumber,
up_to_beacon: BlockNumber,
) -> StdResult<Box<dyn Iterator<Item = (BlockRange, MKTreeNode)>>> {
// Get the highest block number for the given immutable number.
// This is a temporary fix that will be removed when the retrieval is based on block number instead of immutable number.
let block_number = self
.get_highest_block_number_for_immutable_number(up_to_beacon)
.await?
.unwrap_or(0);

let iterator = self
.retrieve_block_range_roots_up_to(block_number)
.retrieve_block_range_roots_up_to(up_to_beacon)
.await?
.collect::<Vec<_>>() // TODO: remove this collect to return the iterator directly
.into_iter();
Ok(Box::new(iterator))
}
}

#[async_trait]
impl ImmutableLowerBoundFinder for CardanoTransactionRepository {
async fn find_lower_bound(&self) -> StdResult<Option<ImmutableFileNumber>> {
self.get_transaction_highest_immutable_file_number().await
}
}

#[cfg(test)]
mod tests {
use mithril_common::test_utils::CardanoTransactionsBuilder;
Expand Down Expand Up @@ -450,60 +422,6 @@ mod tests {
);
}

#[tokio::test]
async fn repository_get_up_to_beacon_transactions() {
let connection = Arc::new(cardano_tx_db_connection().unwrap());
let repository = CardanoTransactionRepository::new(connection);

let cardano_transactions: Vec<CardanoTransactionRecord> = CardanoTransactionsBuilder::new()
.max_transactions_per_immutable_file(10)
.first_immutable_file(120)
.build_transactions(40)
.into_iter()
.map(CardanoTransactionRecord::from)
.collect();

repository
.create_transactions(cardano_transactions.clone())
.await
.unwrap();

let transaction_result = repository.get_transactions_up_to(120).await.unwrap();
let transaction_up_to_immutable_file_number_12 = cardano_transactions[0..10].to_vec();
assert_eq!(
transaction_up_to_immutable_file_number_12,
transaction_result
);

let transaction_result = repository.get_transactions_up_to(300).await.unwrap();
let transaction_all = cardano_transactions[..].to_vec();
assert_eq!(transaction_all, transaction_result);

let transaction_result = repository.get_transactions_up_to(90).await.unwrap();
assert_eq!(Vec::<CardanoTransactionRecord>::new(), transaction_result);
}

#[tokio::test]
async fn get_transactions_up_to_return_empty_list_when_no_record_found_with_provided_immutable_file_number(
) {
let connection = Arc::new(cardano_tx_db_connection().unwrap());
let repository = CardanoTransactionRepository::new(connection);

repository
.create_transactions(vec![CardanoTransaction::new(
"tx-hash-123".to_string(),
0,
50,
"block-hash-0",
99,
)])
.await
.unwrap();

let transaction_result = repository.get_transactions_up_to(90).await.unwrap();
assert_eq!(Vec::<CardanoTransactionRecord>::new(), transaction_result);
}

#[tokio::test]
async fn repository_get_all_stored_transactions() {
let connection = Arc::new(cardano_tx_db_connection().unwrap());
Expand Down Expand Up @@ -563,6 +481,46 @@ mod tests {
);
}

#[tokio::test]
async fn repository_get_transaction_highest_chain_point_without_transactions_in_db() {
let connection = Arc::new(cardano_tx_db_connection().unwrap());
let repository = CardanoTransactionRepository::new(connection);

let highest_beacon = repository
.get_transaction_highest_chain_point()
.await
.unwrap();
assert_eq!(None, highest_beacon);
}

#[tokio::test]
async fn repository_get_transaction_highest_chain_point_with_transactions_in_db() {
let connection = Arc::new(cardano_tx_db_connection().unwrap());
let repository = CardanoTransactionRepository::new(connection);

let cardano_transactions = vec![
CardanoTransaction::new("tx-hash-123", 10, 50, "block-hash-10", 50),
CardanoTransaction::new("tx-hash-456", 25, 51, "block-hash-25", 100),
Alenar marked this conversation as resolved.
Show resolved Hide resolved
];
repository
.create_transactions(cardano_transactions)
.await
.unwrap();

let highest_beacon = repository
.get_transaction_highest_chain_point()
.await
.unwrap();
assert_eq!(
Some(ChainPoint {
slot_number: 51,
block_number: 25,
block_hash: "block-hash-25".to_string()
}),
highest_beacon
);
}

#[tokio::test]
async fn repository_get_transaction_highest_immutable_file_number_without_transactions_in_db() {
let connection = Arc::new(cardano_tx_db_connection().unwrap());
Expand Down Expand Up @@ -953,4 +911,22 @@ mod tests {
.unwrap();
assert_eq!(Some(30), highest);
}

#[tokio::test]
async fn find_block_scanner_lower_bound() {
let connection = Arc::new(cardano_tx_db_connection().unwrap());
let repository = CardanoTransactionRepository::new(connection);

let cardano_transactions = vec![
CardanoTransaction::new("tx-hash-123".to_string(), 10, 50, "block-hash-123", 50),
CardanoTransaction::new("tx-hash-456".to_string(), 11, 51, "block-hash-456", 100),
];
repository
.create_transactions(cardano_transactions)
.await
.unwrap();

let highest_beacon = repository.find_lower_bound().await.unwrap();
assert_eq!(Some(100), highest_beacon);
}
}
4 changes: 2 additions & 2 deletions internal/mithril-persistence/src/database/version_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,10 @@ pub struct SqlMigration {

impl SqlMigration {
/// Create a new SQL migration instance.
pub fn new(version: DbVersion, alteration: &str) -> Self {
pub fn new<T: Into<String>>(version: DbVersion, alteration: T) -> Self {
Self {
version,
alterations: alteration.to_string(),
alterations: alteration.into(),
}
}
}
Expand Down
Loading
Loading