use anyhow::Context;
use std::sync::Arc;
use crate::{
CardanoBlocksTransactionsSnapshot, CardanoBlocksTransactionsSnapshotListItem,
CardanoTransactionsProofsV2, MithrilResult,
};
pub struct CardanoTransactionV2Client {
aggregator_requester: Arc<dyn CardanoTransactionV2AggregatorRequest>,
}
#[cfg_attr(test, mockall::automock)]
#[cfg_attr(target_family = "wasm", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), async_trait::async_trait)]
pub trait CardanoTransactionV2AggregatorRequest: Send + Sync {
async fn get_proof(
&self,
hashes: &[String],
) -> MithrilResult<Option<CardanoTransactionsProofsV2>>;
async fn list_latest_snapshots(
&self,
) -> MithrilResult<Vec<CardanoBlocksTransactionsSnapshotListItem>>;
async fn get_snapshot(
&self,
hash: &str,
) -> MithrilResult<Option<CardanoBlocksTransactionsSnapshot>>;
}
impl CardanoTransactionV2Client {
pub fn new(aggregator_requester: Arc<dyn CardanoTransactionV2AggregatorRequest>) -> Self {
Self {
aggregator_requester,
}
}
pub async fn get_proof<T: ToString>(
&self,
transactions_hashes: &[T],
) -> MithrilResult<CardanoTransactionsProofsV2> {
let transactions_hashes: Vec<String> =
transactions_hashes.iter().map(|h| h.to_string()).collect();
self.aggregator_requester
.get_proof(&transactions_hashes)
.await?
.with_context(|| {
format!("No proof found for transactions hashes: {transactions_hashes:?}",)
})
}
pub async fn list_snapshots(
&self,
) -> MithrilResult<Vec<CardanoBlocksTransactionsSnapshotListItem>> {
self.aggregator_requester.list_latest_snapshots().await
}
pub async fn get_snapshot(
&self,
hash: &str,
) -> MithrilResult<Option<CardanoBlocksTransactionsSnapshot>> {
self.aggregator_requester.get_snapshot(hash).await
}
}
#[cfg(test)]
mod tests {
use mockall::predicate::eq;
use mithril_common::test::mock_extensions::MockBuilder;
use crate::common::test::Dummy;
use crate::{CardanoBlocksTransactionsSnapshot, CardanoTransaction, MkSetProof};
use super::*;
#[tokio::test]
async fn get_cardano_transactions_snapshot_list() {
let aggregator_requester =
MockBuilder::<MockCardanoTransactionV2AggregatorRequest>::configure(|mock| {
let messages = vec![
CardanoBlocksTransactionsSnapshotListItem {
hash: "hash-123".to_string(),
..Dummy::dummy()
},
CardanoBlocksTransactionsSnapshotListItem {
hash: "hash-456".to_string(),
..Dummy::dummy()
},
];
mock.expect_list_latest_snapshots().return_once(|| Ok(messages));
});
let client = CardanoTransactionV2Client::new(aggregator_requester);
let items = client.list_snapshots().await.unwrap();
assert_eq!(2, items.len());
assert_eq!("hash-123".to_string(), items[0].hash);
assert_eq!("hash-456".to_string(), items[1].hash);
}
#[tokio::test]
async fn get_cardano_transactions_snapshot() {
let aggregator_requester =
MockBuilder::<MockCardanoTransactionV2AggregatorRequest>::configure(|mock| {
let message = CardanoBlocksTransactionsSnapshot {
hash: "hash-123".to_string(),
merkle_root: "mk-123".to_string(),
..Dummy::dummy()
};
mock.expect_get_snapshot()
.with(eq(message.hash.clone()))
.return_once(|_| Ok(Some(message)));
});
let client = CardanoTransactionV2Client::new(aggregator_requester);
let cardano_transaction_snapshot = client
.get_snapshot("hash-123")
.await
.unwrap()
.expect("This test returns a cardano transaction V2 snapshot");
assert_eq!("hash-123", &cardano_transaction_snapshot.hash);
assert_eq!("mk-123", &cardano_transaction_snapshot.merkle_root);
}
#[tokio::test]
async fn test_get_proof_ok() {
let set_proof = MkSetProof::<CardanoTransaction>::dummy();
let expected_transactions_proofs = CardanoTransactionsProofsV2 {
certified_transactions: Some(set_proof.clone()),
..Dummy::dummy()
};
let aggregator_requester =
MockBuilder::<MockCardanoTransactionV2AggregatorRequest>::configure(|mock| {
let message = expected_transactions_proofs.clone();
mock.expect_get_proof()
.with(eq(message
.certified_transactions
.iter()
.flat_map(|proof| proof.items.clone())
.map(|tx| tx.transaction_hash)
.collect::<Vec<_>>()))
.return_once(|_| Ok(Some(message)));
});
let client = CardanoTransactionV2Client::new(aggregator_requester);
let transactions_proofs = client
.get_proof(
&set_proof
.items
.iter()
.map(|h| h.transaction_hash.clone())
.collect::<Vec<_>>(),
)
.await
.unwrap();
assert_eq!(expected_transactions_proofs, transactions_proofs);
}
#[tokio::test]
async fn test_get_proof_ko() {
let aggregator_requester =
MockBuilder::<MockCardanoTransactionV2AggregatorRequest>::configure(|mock| {
mock.expect_get_proof()
.return_once(move |_| Err(anyhow::anyhow!("an error")));
});
let client = CardanoTransactionV2Client::new(aggregator_requester);
client
.get_proof(&["tx-123"])
.await
.expect_err("The certificate client should fail here.");
}
}