use anyhow::Context;
use std::sync::Arc;
use crate::{
CardanoTransactionSnapshot, CardanoTransactionSnapshotListItem, CardanoTransactionsProofs,
MithrilResult,
};
pub struct CardanoTransactionClient {
aggregator_requester: Arc<dyn CardanoTransactionAggregatorRequest>,
}
#[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 CardanoTransactionAggregatorRequest: Send + Sync {
async fn get_proof(
&self,
hashes: &[String],
) -> MithrilResult<Option<CardanoTransactionsProofs>>;
async fn list_latest_snapshots(&self)
-> MithrilResult<Vec<CardanoTransactionSnapshotListItem>>;
async fn get_snapshot(&self, hash: &str) -> MithrilResult<Option<CardanoTransactionSnapshot>>;
}
impl CardanoTransactionClient {
pub fn new(aggregator_requester: Arc<dyn CardanoTransactionAggregatorRequest>) -> Self {
Self {
aggregator_requester,
}
}
pub async fn get_proofs<T: ToString>(
&self,
transactions_hashes: &[T],
) -> MithrilResult<CardanoTransactionsProofs> {
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<CardanoTransactionSnapshotListItem>> {
self.aggregator_requester.list_latest_snapshots().await
}
pub async fn get_snapshot(
&self,
hash: &str,
) -> MithrilResult<Option<CardanoTransactionSnapshot>> {
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::{BlockNumber, test::Dummy};
use crate::{
CardanoTransactionSnapshot, CardanoTransactionSnapshotListItem, CardanoTransactionsProofs,
CardanoTransactionsSetProof,
};
use super::*;
#[tokio::test]
async fn get_cardano_transactions_snapshot_list() {
let aggregator_requester =
MockBuilder::<MockCardanoTransactionAggregatorRequest>::configure(|mock| {
let messages = vec![
CardanoTransactionSnapshotListItem {
hash: "hash-123".to_string(),
..Dummy::dummy()
},
CardanoTransactionSnapshotListItem {
hash: "hash-456".to_string(),
..Dummy::dummy()
},
];
mock.expect_list_latest_snapshots().return_once(|| Ok(messages));
});
let client = CardanoTransactionClient::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::<MockCardanoTransactionAggregatorRequest>::configure(|mock| {
let message = CardanoTransactionSnapshot {
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 = CardanoTransactionClient::new(aggregator_requester);
let cardano_transaction_snapshot = client
.get_snapshot("hash-123")
.await
.unwrap()
.expect("This test returns a cardano transaction 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 certificate_hash = "cert-hash-123".to_string();
let set_proof = CardanoTransactionsSetProof::dummy();
let expected_transactions_proofs = CardanoTransactionsProofs::new(
&certificate_hash,
vec![set_proof.clone()],
vec![],
BlockNumber(99999),
);
let aggregator_requester =
MockBuilder::<MockCardanoTransactionAggregatorRequest>::configure(|mock| {
let message = expected_transactions_proofs.clone();
mock.expect_get_proof()
.with(eq(message
.certified_transactions
.iter()
.flat_map(|tx| tx.transactions_hashes.clone())
.collect::<Vec<_>>()))
.return_once(|_| Ok(Some(message)));
});
let client = CardanoTransactionClient::new(aggregator_requester);
let transactions_proofs = client
.get_proofs(
&set_proof
.transactions_hashes
.iter()
.map(|h| h.as_str())
.collect::<Vec<_>>(),
)
.await
.unwrap();
assert_eq!(expected_transactions_proofs, transactions_proofs);
}
#[tokio::test]
async fn test_get_proof_ko() {
let aggregator_requester =
MockBuilder::<MockCardanoTransactionAggregatorRequest>::configure(|mock| {
mock.expect_get_proof()
.return_once(move |_| Err(anyhow::anyhow!("an error")));
});
let client = CardanoTransactionClient::new(aggregator_requester);
client
.get_proofs(&["tx-123"])
.await
.expect_err("The certificate client should fail here.");
}
}