use async_trait::async_trait;
use mithril_common::entities::ImmutableFileNumber;
use serde::Serialize;
use slog::{Logger, info};
use std::sync::{Arc, RwLock};
use strum::Display;
use uuid::Uuid;
#[derive(Debug, Clone, Eq, PartialEq, Display, Serialize)]
#[strum(serialize_all = "PascalCase")]
#[serde(untagged)]
pub enum MithrilEventCardanoDatabase {
Started {
download_id: String,
total_immutable_files: u64,
include_ancillary: bool,
},
Completed {
download_id: String,
},
ImmutableDownloadStarted {
immutable_file_number: ImmutableFileNumber,
download_id: String,
size: u64,
},
ImmutableDownloadProgress {
immutable_file_number: ImmutableFileNumber,
download_id: String,
downloaded_bytes: u64,
size: u64,
},
ImmutableDownloadCompleted {
immutable_file_number: ImmutableFileNumber,
download_id: String,
},
AncillaryDownloadStarted {
download_id: String,
size: u64,
},
AncillaryDownloadProgress {
download_id: String,
downloaded_bytes: u64,
size: u64,
},
AncillaryDownloadCompleted {
download_id: String,
},
DigestDownloadStarted {
download_id: String,
size: u64,
},
DigestDownloadProgress {
download_id: String,
downloaded_bytes: u64,
size: u64,
},
DigestDownloadCompleted {
download_id: String,
},
}
#[derive(Debug, Clone, Eq, PartialEq, Display, Serialize)]
#[strum(serialize_all = "PascalCase")]
#[serde(untagged)]
pub enum MithrilEvent {
SnapshotDownloadStarted {
digest: String,
download_id: String,
size: u64,
},
SnapshotDownloadProgress {
download_id: String,
downloaded_bytes: u64,
size: u64,
},
SnapshotDownloadCompleted {
download_id: String,
},
SnapshotAncillaryDownloadStarted {
download_id: String,
size: u64,
},
SnapshotAncillaryDownloadProgress {
download_id: String,
downloaded_bytes: u64,
size: u64,
},
SnapshotAncillaryDownloadCompleted {
download_id: String,
},
CardanoDatabase(MithrilEventCardanoDatabase),
CertificateChainValidationStarted {
certificate_chain_validation_id: String,
},
CertificateValidated {
certificate_chain_validation_id: String,
certificate_hash: String,
},
CertificateFetchedFromCache {
certificate_chain_validation_id: String,
certificate_hash: String,
},
CertificateChainValidated {
certificate_chain_validation_id: String,
},
}
impl MithrilEvent {
pub fn new_snapshot_download_id() -> String {
Uuid::new_v4().to_string()
}
pub fn new_cardano_database_download_id() -> String {
Uuid::new_v4().to_string()
}
pub fn new_certificate_chain_validation_id() -> String {
Uuid::new_v4().to_string()
}
#[cfg(test)]
pub(crate) fn event_id(&self) -> &str {
match self {
MithrilEvent::SnapshotDownloadStarted { download_id, .. } => download_id,
MithrilEvent::SnapshotDownloadProgress { download_id, .. } => download_id,
MithrilEvent::SnapshotDownloadCompleted { download_id } => download_id,
MithrilEvent::SnapshotAncillaryDownloadStarted { download_id, .. } => download_id,
MithrilEvent::SnapshotAncillaryDownloadProgress { download_id, .. } => download_id,
MithrilEvent::SnapshotAncillaryDownloadCompleted { download_id } => download_id,
MithrilEvent::CardanoDatabase(MithrilEventCardanoDatabase::Started {
download_id,
..
}) => download_id,
MithrilEvent::CardanoDatabase(MithrilEventCardanoDatabase::Completed {
download_id,
..
}) => download_id,
MithrilEvent::CardanoDatabase(
MithrilEventCardanoDatabase::ImmutableDownloadStarted { download_id, .. },
) => download_id,
MithrilEvent::CardanoDatabase(
MithrilEventCardanoDatabase::ImmutableDownloadProgress { download_id, .. },
) => download_id,
MithrilEvent::CardanoDatabase(
MithrilEventCardanoDatabase::ImmutableDownloadCompleted { download_id, .. },
) => download_id,
MithrilEvent::CardanoDatabase(
MithrilEventCardanoDatabase::AncillaryDownloadStarted { download_id, .. },
) => download_id,
MithrilEvent::CardanoDatabase(
MithrilEventCardanoDatabase::AncillaryDownloadProgress { download_id, .. },
) => download_id,
MithrilEvent::CardanoDatabase(
MithrilEventCardanoDatabase::AncillaryDownloadCompleted { download_id, .. },
) => download_id,
MithrilEvent::CardanoDatabase(MithrilEventCardanoDatabase::DigestDownloadStarted {
download_id,
..
}) => download_id,
MithrilEvent::CardanoDatabase(
MithrilEventCardanoDatabase::DigestDownloadProgress { download_id, .. },
) => download_id,
MithrilEvent::CardanoDatabase(
MithrilEventCardanoDatabase::DigestDownloadCompleted { download_id, .. },
) => download_id,
MithrilEvent::CertificateChainValidationStarted {
certificate_chain_validation_id,
} => certificate_chain_validation_id,
MithrilEvent::CertificateValidated {
certificate_chain_validation_id,
..
} => certificate_chain_validation_id,
MithrilEvent::CertificateFetchedFromCache {
certificate_chain_validation_id,
..
} => certificate_chain_validation_id,
MithrilEvent::CertificateChainValidated {
certificate_chain_validation_id,
} => certificate_chain_validation_id,
}
}
}
#[derive(Clone)]
pub struct FeedbackSender {
receivers: Vec<Arc<dyn FeedbackReceiver>>,
}
impl FeedbackSender {
pub fn new(receivers: &[Arc<dyn FeedbackReceiver>]) -> FeedbackSender {
Self {
receivers: receivers.to_vec(),
}
}
pub async fn send_event(&self, event: MithrilEvent) {
for receiver in &self.receivers {
receiver.handle_event(event.clone()).await;
}
}
}
#[cfg_attr(target_family = "wasm", async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), async_trait)]
pub trait FeedbackReceiver: Sync + Send {
async fn handle_event(&self, event: MithrilEvent);
}
pub struct SlogFeedbackReceiver {
logger: Logger,
}
impl SlogFeedbackReceiver {
pub fn new(logger: Logger) -> SlogFeedbackReceiver {
Self { logger }
}
}
#[cfg_attr(target_family = "wasm", async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), async_trait)]
impl FeedbackReceiver for SlogFeedbackReceiver {
async fn handle_event(&self, event: MithrilEvent) {
match event {
MithrilEvent::SnapshotDownloadStarted {
digest,
download_id,
size,
} => {
info!(
self.logger, "Snapshot download started";
"size" => size, "digest" => digest, "download_id" => download_id,
);
}
MithrilEvent::SnapshotDownloadProgress {
download_id,
downloaded_bytes,
size,
} => {
info!(
self.logger, "Snapshot download in progress ...";
"downloaded_bytes" => downloaded_bytes, "size" => size, "download_id" => download_id,
);
}
MithrilEvent::SnapshotDownloadCompleted { download_id } => {
info!(self.logger, "Snapshot download completed"; "download_id" => download_id);
}
MithrilEvent::SnapshotAncillaryDownloadStarted { download_id, size } => {
info!(
self.logger, "Snapshot ancillary download started";
"size" => size, "download_id" => download_id,
);
}
MithrilEvent::SnapshotAncillaryDownloadProgress {
download_id,
downloaded_bytes,
size,
} => {
info!(
self.logger, "Snapshot ancillary download in progress ...";
"downloaded_bytes" => downloaded_bytes, "size" => size, "download_id" => download_id,
);
}
MithrilEvent::SnapshotAncillaryDownloadCompleted { download_id } => {
info!(self.logger, "Snapshot ancillary download completed"; "download_id" => download_id);
}
MithrilEvent::CardanoDatabase(MithrilEventCardanoDatabase::Started {
download_id,
total_immutable_files,
include_ancillary,
}) => {
info!(
self.logger, "Cardano database download started"; "download_id" => download_id, "total_immutable_files" => total_immutable_files, "include_ancillary" => include_ancillary,
);
}
MithrilEvent::CardanoDatabase(MithrilEventCardanoDatabase::Completed {
download_id,
}) => {
info!(
self.logger, "Cardano database download completed"; "download_id" => download_id,
);
}
MithrilEvent::CardanoDatabase(
MithrilEventCardanoDatabase::ImmutableDownloadStarted {
immutable_file_number,
download_id,
size,
},
) => {
info!(
self.logger, "Immutable download started";
"immutable_file_number" => immutable_file_number, "download_id" => download_id, "size" => size
);
}
MithrilEvent::CardanoDatabase(
MithrilEventCardanoDatabase::ImmutableDownloadProgress {
immutable_file_number,
download_id,
downloaded_bytes,
size,
},
) => {
info!(
self.logger, "Immutable download in progress ...";
"immutable_file_number" => immutable_file_number, "downloaded_bytes" => downloaded_bytes, "size" => size, "download_id" => download_id,
);
}
MithrilEvent::CardanoDatabase(
MithrilEventCardanoDatabase::ImmutableDownloadCompleted {
immutable_file_number,
download_id,
},
) => {
info!(self.logger, "Immutable download completed"; "immutable_file_number" => immutable_file_number, "download_id" => download_id);
}
MithrilEvent::CardanoDatabase(
MithrilEventCardanoDatabase::AncillaryDownloadStarted { download_id, size },
) => {
info!(
self.logger, "Ancillary download started";
"download_id" => download_id,
"size" => size,
);
}
MithrilEvent::CardanoDatabase(
MithrilEventCardanoDatabase::AncillaryDownloadProgress {
download_id,
downloaded_bytes,
size,
},
) => {
info!(
self.logger, "Ancillary download in progress ...";
"downloaded_bytes" => downloaded_bytes, "size" => size, "download_id" => download_id,
);
}
MithrilEvent::CardanoDatabase(
MithrilEventCardanoDatabase::AncillaryDownloadCompleted { download_id },
) => {
info!(self.logger, "Ancillary download completed"; "download_id" => download_id);
}
MithrilEvent::CardanoDatabase(MithrilEventCardanoDatabase::DigestDownloadStarted {
download_id,
size,
}) => {
info!(
self.logger, "Digest download started";
"download_id" => download_id,
"size" => size,
);
}
MithrilEvent::CardanoDatabase(
MithrilEventCardanoDatabase::DigestDownloadProgress {
download_id,
downloaded_bytes,
size,
},
) => {
info!(
self.logger, "Digest download in progress ...";
"downloaded_bytes" => downloaded_bytes, "size" => size, "download_id" => download_id,
);
}
MithrilEvent::CardanoDatabase(
MithrilEventCardanoDatabase::DigestDownloadCompleted { download_id },
) => {
info!(self.logger, "Digest download completed"; "download_id" => download_id);
}
MithrilEvent::CertificateChainValidationStarted {
certificate_chain_validation_id,
} => {
info!(
self.logger, "Certificate chain validation started";
"certificate_chain_validation_id" => certificate_chain_validation_id,
);
}
MithrilEvent::CertificateValidated {
certificate_hash,
certificate_chain_validation_id,
} => {
info!(
self.logger, "Certificate validated";
"certificate_hash" => certificate_hash,
"certificate_chain_validation_id" => certificate_chain_validation_id,
);
}
MithrilEvent::CertificateFetchedFromCache {
certificate_hash,
certificate_chain_validation_id,
} => {
info!(
self.logger, "Cached";
"certificate_hash" => certificate_hash,
"certificate_chain_validation_id" => certificate_chain_validation_id,
);
}
MithrilEvent::CertificateChainValidated {
certificate_chain_validation_id,
} => {
info!(
self.logger, "Certificate chain validated";
"certificate_chain_validation_id" => certificate_chain_validation_id,
);
}
};
}
}
pub struct StackFeedbackReceiver {
stacked_events: RwLock<Vec<MithrilEvent>>,
}
impl StackFeedbackReceiver {
pub fn new() -> StackFeedbackReceiver {
Self {
stacked_events: RwLock::new(vec![]),
}
}
pub fn stacked_events(&self) -> Vec<MithrilEvent> {
let events = self.stacked_events.read().unwrap();
events.clone()
}
}
impl Default for StackFeedbackReceiver {
fn default() -> Self {
Self::new()
}
}
#[cfg_attr(target_family = "wasm", async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), async_trait)]
impl FeedbackReceiver for StackFeedbackReceiver {
async fn handle_event(&self, event: MithrilEvent) {
let mut events = self.stacked_events.write().unwrap();
events.push(event);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::feedback::MithrilEvent::{SnapshotDownloadCompleted, SnapshotDownloadStarted};
use crate::test_utils::TestLogger;
use std::time::Duration;
use tokio::task::JoinSet;
#[tokio::test]
async fn send_event_same_thread() {
let receiver = Arc::new(StackFeedbackReceiver::new());
let receiver_clone = receiver.clone() as Arc<dyn FeedbackReceiver>;
let sender = FeedbackSender::new(&[receiver_clone]);
sender
.send_event(SnapshotDownloadStarted {
digest: "digest".to_string(),
download_id: "download_id".to_string(),
size: 10,
})
.await;
sender
.send_event(SnapshotDownloadCompleted {
download_id: "download_id".to_string(),
})
.await;
assert_eq!(
receiver.stacked_events(),
vec![
SnapshotDownloadStarted {
digest: "digest".to_string(),
download_id: "download_id".to_string(),
size: 10
},
SnapshotDownloadCompleted {
download_id: "download_id".to_string()
}
]
);
}
#[tokio::test]
async fn send_event_multiple_thread() {
let receiver = Arc::new(StackFeedbackReceiver::new());
let sender = FeedbackSender::new(&[
receiver.clone(),
Arc::new(SlogFeedbackReceiver::new(TestLogger::stdout())),
]);
let sender2 = sender.clone();
let mut join_set = JoinSet::new();
join_set.spawn(async move {
sender
.send_event(SnapshotDownloadStarted {
digest: "digest1".to_string(),
download_id: "download1".to_string(),
size: 1,
})
.await;
tokio::time::sleep(Duration::from_millis(2)).await;
sender
.send_event(SnapshotDownloadCompleted {
download_id: "download3".to_string(),
})
.await;
sender
.send_event(SnapshotDownloadStarted {
digest: "digest2".to_string(),
download_id: "download2".to_string(),
size: 2,
})
.await;
});
join_set.spawn(async move {
sender2
.send_event(SnapshotDownloadCompleted {
download_id: "download1".to_string(),
})
.await;
sender2
.send_event(SnapshotDownloadStarted {
digest: "digest3".to_string(),
download_id: "download3".to_string(),
size: 3,
})
.await;
tokio::time::sleep(Duration::from_millis(5)).await;
sender2
.send_event(SnapshotDownloadCompleted {
download_id: "download2".to_string(),
})
.await;
});
while let Some(res) = join_set.join_next().await {
res.unwrap();
}
assert_eq!(
receiver.stacked_events(),
vec![
SnapshotDownloadStarted {
digest: "digest1".to_string(),
download_id: "download1".to_string(),
size: 1
},
SnapshotDownloadCompleted {
download_id: "download1".to_string()
},
SnapshotDownloadStarted {
digest: "digest3".to_string(),
download_id: "download3".to_string(),
size: 3
},
SnapshotDownloadCompleted {
download_id: "download3".to_string()
},
SnapshotDownloadStarted {
digest: "digest2".to_string(),
download_id: "download2".to_string(),
size: 2
},
SnapshotDownloadCompleted {
download_id: "download2".to_string()
},
]
);
}
#[tokio::test]
async fn send_event_in_one_thread_and_receive_in_another_thread() {
let receiver = Arc::new(StackFeedbackReceiver::new());
let receiver_clone = receiver.clone() as Arc<dyn FeedbackReceiver>;
let receiver2 = receiver.clone();
let sender = FeedbackSender::new(&[receiver_clone]);
let mut join_set = JoinSet::new();
join_set.spawn(async move {
sender
.send_event(SnapshotDownloadStarted {
digest: "digest1".to_string(),
download_id: "download1".to_string(),
size: 1,
})
.await;
tokio::time::sleep(Duration::from_millis(10)).await;
sender
.send_event(SnapshotDownloadCompleted {
download_id: "download1".to_string(),
})
.await;
sender
.send_event(SnapshotDownloadStarted {
digest: "digest2".to_string(),
download_id: "download2".to_string(),
size: 2,
})
.await;
tokio::time::sleep(Duration::from_millis(10)).await;
sender
.send_event(SnapshotDownloadCompleted {
download_id: "download2".to_string(),
})
.await;
sender
.send_event(SnapshotDownloadStarted {
digest: "digest3".to_string(),
download_id: "download3".to_string(),
size: 3,
})
.await;
tokio::time::sleep(Duration::from_millis(10)).await;
sender
.send_event(SnapshotDownloadCompleted {
download_id: "download3".to_string(),
})
.await;
});
join_set.spawn(async move {
tokio::time::sleep(Duration::from_millis(3)).await;
assert_eq!(
receiver2.stacked_events(),
vec![SnapshotDownloadStarted {
digest: "digest1".to_string(),
download_id: "download1".to_string(),
size: 1
},]
);
tokio::time::sleep(Duration::from_millis(10)).await;
assert_eq!(
receiver2.stacked_events(),
vec![
SnapshotDownloadStarted {
digest: "digest1".to_string(),
download_id: "download1".to_string(),
size: 1
},
SnapshotDownloadCompleted {
download_id: "download1".to_string()
},
SnapshotDownloadStarted {
digest: "digest2".to_string(),
download_id: "download2".to_string(),
size: 2
},
]
);
tokio::time::sleep(Duration::from_millis(10)).await;
assert_eq!(
receiver2.stacked_events(),
vec![
SnapshotDownloadStarted {
digest: "digest1".to_string(),
download_id: "download1".to_string(),
size: 1
},
SnapshotDownloadCompleted {
download_id: "download1".to_string()
},
SnapshotDownloadStarted {
digest: "digest2".to_string(),
download_id: "download2".to_string(),
size: 2
},
SnapshotDownloadCompleted {
download_id: "download2".to_string()
},
SnapshotDownloadStarted {
digest: "digest3".to_string(),
download_id: "download3".to_string(),
size: 3
},
]
);
});
while let Some(res) = join_set.join_next().await {
res.unwrap();
}
assert_eq!(
receiver.stacked_events(),
vec![
SnapshotDownloadStarted {
digest: "digest1".to_string(),
download_id: "download1".to_string(),
size: 1
},
SnapshotDownloadCompleted {
download_id: "download1".to_string()
},
SnapshotDownloadStarted {
digest: "digest2".to_string(),
download_id: "download2".to_string(),
size: 2
},
SnapshotDownloadCompleted {
download_id: "download2".to_string()
},
SnapshotDownloadStarted {
digest: "digest3".to_string(),
download_id: "download3".to_string(),
size: 3
},
SnapshotDownloadCompleted {
download_id: "download3".to_string()
},
]
);
}
}