Skip to content

Commit 0c2476b

Browse files
authored
Merge pull request #900 from input-output-hk/ensemble/871-artifact-builder-cardano-full-immutables
Cardano Immutable Files Full Artifact builder in aggregator
2 parents e7c947f + 9f71e91 commit 0c2476b

File tree

10 files changed

+332
-23
lines changed

10 files changed

+332
-23
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mithril-aggregator/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mithril-aggregator"
3-
version = "0.3.11"
3+
version = "0.3.12"
44
description = "A Mithril Aggregator server"
55
authors = { workspace = true }
66
edition = { workspace = true }

mithril-aggregator/src/artifact_builder/artifact_builder_service.rs

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use async_trait::async_trait;
33
use std::sync::Arc;
44

55
use mithril_common::{
6-
entities::{Certificate, Epoch, SignedEntityType},
6+
entities::{Beacon, Certificate, Epoch, SignedEntityType, Snapshot},
77
signable_builder::Artifact,
88
StdResult,
99
};
@@ -31,6 +31,7 @@ pub trait ArtifactBuilderService: Send + Sync {
3131
pub struct MithrilArtifactBuilderService {
3232
mithril_stake_distribution_artifact_builder:
3333
Arc<dyn ArtifactBuilder<Epoch, MithrilStakeDistribution>>,
34+
cardano_immutable_files_full_artifact_builder: Arc<dyn ArtifactBuilder<Beacon, Snapshot>>,
3435
}
3536

3637
impl MithrilArtifactBuilderService {
@@ -40,9 +41,11 @@ impl MithrilArtifactBuilderService {
4041
mithril_stake_distribution_artifact_builder: Arc<
4142
dyn ArtifactBuilder<Epoch, MithrilStakeDistribution>,
4243
>,
44+
cardano_immutable_files_full_artifact_builder: Arc<dyn ArtifactBuilder<Beacon, Snapshot>>,
4345
) -> Self {
4446
Self {
4547
mithril_stake_distribution_artifact_builder,
48+
cardano_immutable_files_full_artifact_builder,
4649
}
4750
}
4851
}
@@ -55,16 +58,19 @@ impl ArtifactBuilderService for MithrilArtifactBuilderService {
5558
signed_entity_type: SignedEntityType,
5659
certificate: &Certificate,
5760
) -> StdResult<Arc<dyn Artifact>> {
58-
let artifact = match signed_entity_type {
59-
SignedEntityType::MithrilStakeDistribution(e) => Arc::new(
61+
match signed_entity_type {
62+
SignedEntityType::MithrilStakeDistribution(epoch) => Ok(Arc::new(
6063
self.mithril_stake_distribution_artifact_builder
61-
.compute_artifact(e, certificate)
64+
.compute_artifact(epoch, certificate)
6265
.await?,
63-
),
66+
)),
67+
SignedEntityType::CardanoImmutableFilesFull(beacon) => Ok(Arc::new(
68+
self.cardano_immutable_files_full_artifact_builder
69+
.compute_artifact(beacon, certificate)
70+
.await?,
71+
)),
6472
_ => todo!(),
65-
};
66-
67-
Ok(artifact)
73+
}
6874
}
6975
}
7076

@@ -77,7 +83,8 @@ mod tests {
7783
use crate::artifact_builder::MockArtifactBuilder;
7884

7985
#[tokio::test]
80-
async fn test_artifact_builder_service_mithril_stake_distribution() {
86+
async fn build_mithril_stake_distribution_artifact_when_given_mithril_stake_distribution_entity_type(
87+
) {
8188
let signers_with_stake = fake_data::signers_with_stakes(5);
8289
let mithril_stake_distribution_expected = MithrilStakeDistribution::new(signers_with_stake);
8390
let mithril_stake_distribution_clone = mithril_stake_distribution_expected.clone();
@@ -88,9 +95,13 @@ mod tests {
8895
.once()
8996
.return_once(move |_, _| Ok(mithril_stake_distribution_clone));
9097

91-
let artifact_builder_service = MithrilArtifactBuilderService::new(Arc::new(
92-
mock_mithril_stake_distribution_artifact_builder,
93-
));
98+
let mock_cardano_immutable_files_full_artifact_builder =
99+
MockArtifactBuilder::<Beacon, Snapshot>::new();
100+
101+
let artifact_builder_service = MithrilArtifactBuilderService::new(
102+
Arc::new(mock_mithril_stake_distribution_artifact_builder),
103+
Arc::new(mock_cardano_immutable_files_full_artifact_builder),
104+
);
94105
let certificate = Certificate::default();
95106

96107
let signed_entity_type = SignedEntityType::MithrilStakeDistribution(Epoch(1));
@@ -105,4 +116,37 @@ mod tests {
105116
serde_json::to_string(&mithril_stake_distribution_computed).unwrap()
106117
);
107118
}
119+
120+
#[tokio::test]
121+
async fn build_snapshot_artifact_when_given_cardano_immutable_files_full_entity_type() {
122+
let snapshot_expected = fake_data::snapshots(1).first().unwrap().to_owned();
123+
let snapshot_expected_clone = snapshot_expected.clone();
124+
let mock_mithril_stake_distribution_artifact_builder =
125+
MockArtifactBuilder::<Epoch, MithrilStakeDistribution>::new();
126+
127+
let mut mock_cardano_immutable_files_full_artifact_builder =
128+
MockArtifactBuilder::<Beacon, Snapshot>::new();
129+
mock_cardano_immutable_files_full_artifact_builder
130+
.expect_compute_artifact()
131+
.once()
132+
.return_once(move |_, _| Ok(snapshot_expected_clone));
133+
134+
let artifact_builder_service = MithrilArtifactBuilderService::new(
135+
Arc::new(mock_mithril_stake_distribution_artifact_builder),
136+
Arc::new(mock_cardano_immutable_files_full_artifact_builder),
137+
);
138+
let certificate = Certificate::default();
139+
140+
let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(Beacon::default());
141+
let artifact = artifact_builder_service
142+
.compute_artifact(signed_entity_type, &certificate)
143+
.await
144+
.unwrap();
145+
let snapshot_computed: Snapshot =
146+
serde_json::from_str(&serde_json::to_string(&artifact).unwrap()).unwrap();
147+
assert_eq!(
148+
serde_json::to_string(&snapshot_expected).unwrap(),
149+
serde_json::to_string(&snapshot_computed).unwrap()
150+
);
151+
}
108152
}
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+
}

0 commit comments

Comments
 (0)