Skip to content

Commit cbdf37d

Browse files
committed
Implement Cardano Immutable Files Full artifact builder in aggregator
1 parent e7c947f commit cbdf37d

File tree

5 files changed

+264
-5
lines changed

5 files changed

+264
-5
lines changed
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
use async_trait::async_trait;
2+
use chrono::Utc;
3+
use slog_scope::{debug, warn};
4+
use std::sync::Arc;
5+
use thiserror::Error;
6+
7+
use crate::{
8+
snapshot_uploaders::SnapshotLocation, snapshotter::OngoingSnapshot, SnapshotError,
9+
SnapshotUploader, Snapshotter,
10+
};
11+
12+
use super::ArtifactBuilder;
13+
use mithril_common::{
14+
entities::{Beacon, Certificate, ProtocolMessage, ProtocolMessagePartKey, Snapshot},
15+
StdResult,
16+
};
17+
18+
/// [CardanoImmutableFilesFullArtifact] error
19+
/// to fail.
20+
#[derive(Debug, Error)]
21+
pub enum CardanoImmutableFilesFullArtifactError {
22+
/// Protocol message part is missing
23+
#[error("Missing protocol message: '{0}'.")]
24+
MissingProtocolMessage(String),
25+
}
26+
27+
/// A [CardanoImmutableFilesFullArtifact] builder
28+
pub struct CardanoImmutableFilesFullArtifactBuilder {
29+
snapshotter: Arc<dyn Snapshotter>,
30+
snapshot_uploader: Arc<dyn SnapshotUploader>,
31+
}
32+
33+
impl CardanoImmutableFilesFullArtifactBuilder {
34+
/// CardanoImmutableFilesFull artifact builder factory
35+
pub fn new(
36+
snapshotter: Arc<dyn Snapshotter>,
37+
snapshot_uploader: Arc<dyn SnapshotUploader>,
38+
) -> Self {
39+
Self {
40+
snapshotter,
41+
snapshot_uploader,
42+
}
43+
}
44+
45+
async fn create_snapshot_archive(
46+
&self,
47+
beacon: &Beacon,
48+
protocol_message: &ProtocolMessage,
49+
) -> StdResult<OngoingSnapshot> {
50+
debug!("CardanoImmutableFilesFullArtifactBuilder: create snapshot archive");
51+
52+
let snapshotter = self.snapshotter.clone();
53+
let snapshot_digest = protocol_message
54+
.get_message_part(&ProtocolMessagePartKey::SnapshotDigest)
55+
.ok_or_else(|| {
56+
CardanoImmutableFilesFullArtifactError::MissingProtocolMessage(format!(
57+
"no digest message part found for beacon '{beacon:?}'."
58+
))
59+
})?;
60+
let snapshot_name = format!(
61+
"{}-e{}-i{}.{}.tar.gz",
62+
beacon.network, beacon.epoch.0, beacon.immutable_file_number, snapshot_digest
63+
);
64+
// spawn a separate thread to prevent blocking
65+
let ongoing_snapshot =
66+
tokio::task::spawn_blocking(move || -> Result<OngoingSnapshot, SnapshotError> {
67+
snapshotter.snapshot(&snapshot_name)
68+
})
69+
.await??;
70+
71+
debug!(" > snapshot created: '{:?}'", ongoing_snapshot);
72+
73+
Ok(ongoing_snapshot)
74+
}
75+
76+
async fn upload_snapshot_archive(
77+
&self,
78+
ongoing_snapshot: &OngoingSnapshot,
79+
) -> StdResult<Vec<SnapshotLocation>> {
80+
debug!("CardanoImmutableFilesFullArtifactBuilder: upload snapshot archive");
81+
let location = self
82+
.snapshot_uploader
83+
.upload_snapshot(ongoing_snapshot.get_file_path())
84+
.await?;
85+
86+
if let Err(error) = tokio::fs::remove_file(ongoing_snapshot.get_file_path()).await {
87+
warn!(
88+
" > Post upload ongoing snapshot file removal failure: {}",
89+
error
90+
);
91+
}
92+
93+
Ok(vec![location])
94+
}
95+
96+
async fn create_snapshot(
97+
&self,
98+
certificate: &Certificate,
99+
ongoing_snapshot: &OngoingSnapshot,
100+
remote_locations: Vec<String>,
101+
) -> StdResult<Snapshot> {
102+
debug!("CardanoImmutableFilesFullArtifactBuilder: create snapshot");
103+
let snapshot_digest = certificate
104+
.protocol_message
105+
.get_message_part(&ProtocolMessagePartKey::SnapshotDigest)
106+
.ok_or_else(|| {
107+
CardanoImmutableFilesFullArtifactError::MissingProtocolMessage(format!(
108+
"message part 'digest' not found for snapshot '{}'.",
109+
ongoing_snapshot.get_file_path().display()
110+
))
111+
})?
112+
.to_owned();
113+
let snapshot = Snapshot::new(
114+
snapshot_digest,
115+
certificate.beacon.to_owned(),
116+
certificate.hash.to_owned(),
117+
*ongoing_snapshot.get_file_size(),
118+
format!("{:?}", Utc::now()),
119+
remote_locations,
120+
);
121+
122+
Ok(snapshot)
123+
}
124+
}
125+
126+
#[async_trait]
127+
impl ArtifactBuilder<Beacon, Snapshot> for CardanoImmutableFilesFullArtifactBuilder {
128+
async fn compute_artifact(
129+
&self,
130+
beacon: Beacon,
131+
certificate: &Certificate,
132+
) -> StdResult<Snapshot> {
133+
let ongoing_snapshot = self
134+
.create_snapshot_archive(&beacon, &certificate.protocol_message)
135+
.await?;
136+
let locations = self.upload_snapshot_archive(&ongoing_snapshot).await?;
137+
138+
let snapshot = self
139+
.create_snapshot(certificate, &ongoing_snapshot, locations)
140+
.await?;
141+
142+
Ok(snapshot)
143+
}
144+
}
145+
146+
#[cfg(test)]
147+
mod tests {
148+
use std::path::Path;
149+
150+
use mithril_common::test_utils::fake_data;
151+
use tempfile::NamedTempFile;
152+
153+
use super::*;
154+
155+
use crate::{DumbSnapshotUploader, DumbSnapshotter};
156+
157+
#[tokio::test]
158+
async fn should_compute_valid_artifact() {
159+
let beacon = fake_data::beacon();
160+
let certificate = fake_data::certificate("cert-123".to_string());
161+
let snapshot_digest = certificate
162+
.protocol_message
163+
.get_message_part(&ProtocolMessagePartKey::SnapshotDigest)
164+
.unwrap();
165+
166+
let dumb_snapshotter = Arc::new(DumbSnapshotter::new());
167+
let dumb_snapshot_uploader = Arc::new(DumbSnapshotUploader::new());
168+
169+
let cardano_immutable_files_full_artifact_builder =
170+
CardanoImmutableFilesFullArtifactBuilder::new(
171+
dumb_snapshotter.clone(),
172+
dumb_snapshot_uploader.clone(),
173+
);
174+
let artifact = cardano_immutable_files_full_artifact_builder
175+
.compute_artifact(beacon, &certificate)
176+
.await
177+
.unwrap();
178+
let last_ongoing_snapshot = dumb_snapshotter
179+
.get_last_snapshot()
180+
.unwrap()
181+
.expect("A snapshot should have been 'created'");
182+
183+
let remote_locations = vec![dumb_snapshot_uploader
184+
.get_last_upload()
185+
.unwrap()
186+
.expect("A snapshot should have been 'uploaded'")];
187+
let artifact_expected = Snapshot::new(
188+
snapshot_digest.to_owned(),
189+
certificate.beacon.to_owned(),
190+
certificate.hash.to_owned(),
191+
*last_ongoing_snapshot.get_file_size(),
192+
artifact.created_at.clone(),
193+
remote_locations,
194+
);
195+
assert_eq!(artifact_expected, artifact);
196+
}
197+
198+
#[tokio::test]
199+
async fn remove_snapshot_archive_after_upload() {
200+
let file = NamedTempFile::new().unwrap();
201+
let file_path = file.path();
202+
let snapshot = OngoingSnapshot::new(file_path.to_path_buf(), 7331);
203+
204+
let cardano_immutable_files_full_artifact_builder =
205+
CardanoImmutableFilesFullArtifactBuilder::new(
206+
Arc::new(DumbSnapshotter::new()),
207+
Arc::new(DumbSnapshotUploader::new()),
208+
);
209+
210+
cardano_immutable_files_full_artifact_builder
211+
.upload_snapshot_archive(&snapshot)
212+
.await
213+
.expect("Snapshot upload should not fail");
214+
215+
assert!(
216+
!file_path.exists(),
217+
"Ongoing snapshot file should have been removed after upload"
218+
);
219+
}
220+
221+
#[tokio::test]
222+
async fn snapshot_archive_name_after_beacon_values() {
223+
let beacon = Beacon::new("network".to_string(), 20, 145);
224+
let mut message = ProtocolMessage::new();
225+
message.set_message_part(
226+
ProtocolMessagePartKey::SnapshotDigest,
227+
"test+digest".to_string(),
228+
);
229+
230+
let cardano_immutable_files_full_artifact_builder =
231+
CardanoImmutableFilesFullArtifactBuilder::new(
232+
Arc::new(DumbSnapshotter::new()),
233+
Arc::new(DumbSnapshotUploader::new()),
234+
);
235+
236+
let ongoing_snapshot = cardano_immutable_files_full_artifact_builder
237+
.create_snapshot_archive(&beacon, &message)
238+
.await
239+
.expect("create_snapshot_archive should not fail");
240+
241+
assert_eq!(
242+
Path::new(
243+
format!(
244+
"{}-e{}-i{}.{}.tar.gz",
245+
beacon.network, beacon.epoch.0, beacon.immutable_file_number, "test+digest"
246+
)
247+
.as_str()
248+
),
249+
ongoing_snapshot.get_file_path()
250+
);
251+
}
252+
}

mithril-aggregator/src/artifact_builder/mithril_stake_distribution.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ mod tests {
6262
use crate::multi_signer::MockMultiSigner;
6363

6464
#[tokio::test]
65-
async fn test_compute_artifact() {
65+
async fn should_compute_valid_artifact() {
6666
let signers_with_stake = fake_data::signers_with_stakes(5);
6767
let signers_with_stake_clone = signers_with_stake.clone();
6868
let certificate = fake_data::certificate("cert-123".to_string());
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
//! The module used for building artifact
22
33
mod artifact_builder_service;
4+
mod cardano_immutable_files_full;
45
mod dummy_artifact;
56
mod interface;
67
mod mithril_stake_distribution;
78

89
pub use artifact_builder_service::*;
10+
pub use cardano_immutable_files_full::*;
911
pub use dummy_artifact::*;
1012
pub use interface::*;
1113
pub use mithril_stake_distribution::*;

mithril-aggregator/src/snapshot_uploaders/dumb_snapshot_uploader.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,10 @@ impl SnapshotUploader for DumbSnapshotUploader {
4545
.write()
4646
.map_err(|e| format!("Error while saving filepath location: {e}"))?;
4747

48-
*value = Some(snapshot_filepath.to_string_lossy().to_string());
48+
let location = snapshot_filepath.to_string_lossy().to_string();
49+
*value = Some(location.clone());
4950

50-
Ok("http://whatev.er".into())
51+
Ok(location)
5152
}
5253
}
5354

@@ -62,10 +63,11 @@ mod tests {
6263
.get_last_upload()
6364
.expect("uploader should not fail")
6465
.is_none());
65-
let _res = uploader
66+
let res = uploader
6667
.upload_snapshot(Path::new("/tmp/whatever"))
6768
.await
6869
.expect("uploading with a dumb uploader should not fail");
70+
assert_eq!(res, "/tmp/whatever".to_string());
6971
assert_eq!(
7072
Some("/tmp/whatever".to_string()),
7173
uploader

mithril-common/src/entities/snapshot.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::entities::Beacon;
1+
use crate::{entities::Beacon, signable_builder::Artifact};
22
use serde::{Deserialize, Serialize};
33

44
/// Snapshot represents a snapshot file and its metadata
@@ -43,3 +43,6 @@ impl Snapshot {
4343
}
4444
}
4545
}
46+
47+
#[typetag::serde]
48+
impl Artifact for Snapshot {}

0 commit comments

Comments
 (0)