Skip to content

Commit 400090b

Browse files
committed
Add artifact routes
For Cardano Immutable Files Full Snapshot and Mithril Stake Distribution signed entity types.
1 parent 54e8cf9 commit 400090b

File tree

6 files changed

+370
-1
lines changed

6 files changed

+370
-1
lines changed

mithril-aggregator/src/artifact_builder/mithril_stake_distribution.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,23 @@ impl Artifact for MithrilStakeDistribution {
5151
}
5252
}
5353

54+
/// Mithril Stake Distribution Summary
55+
// TODO: This type should probably be listed in the common entities?
56+
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
57+
pub struct MithrilStakeDistributionSummary {
58+
epoch: Epoch,
59+
hash: String,
60+
}
61+
62+
impl From<MithrilStakeDistribution> for MithrilStakeDistributionSummary {
63+
fn from(other: MithrilStakeDistribution) -> Self {
64+
Self {
65+
epoch: other.epoch,
66+
hash: other.hash,
67+
}
68+
}
69+
}
70+
5471
/// A [MithrilStakeDistributionArtifact] builder
5572
pub struct MithrilStakeDistributionArtifactBuilder {
5673
multi_signer: Arc<RwLock<dyn MultiSigner>>,

mithril-aggregator/src/database/provider/signed_entity.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,13 @@ pub trait SignedEntityStorer: Sync + Send {
264264
&self,
265265
signed_entity_id: String,
266266
) -> StdResult<Option<SignedEntityRecord>>;
267+
268+
/// Get last signed entities by signed entity type
269+
async fn get_last_signed_entities_by_type(
270+
&self,
271+
signed_entity_type: &SignedEntityType,
272+
total: usize,
273+
) -> StdResult<Vec<SignedEntityRecord>>;
267274
}
268275

269276
/// Service to deal with signed_entity (read & write).
@@ -301,6 +308,21 @@ impl SignedEntityStorer for SignedEntityStoreAdapter {
301308

302309
Ok(signed_entity)
303310
}
311+
312+
async fn get_last_signed_entities_by_type(
313+
&self,
314+
signed_entity_type: &SignedEntityType,
315+
total: usize,
316+
) -> StdResult<Vec<SignedEntityRecord>> {
317+
let connection = &*self.connection.lock().await;
318+
let provider = SignedEntityRecordProvider::new(connection);
319+
let cursor = provider
320+
.get_by_signed_entity_type(signed_entity_type.to_owned())
321+
.map_err(|e| AdapterError::GeneralError(format!("{e}")))?;
322+
let signed_entities: Vec<SignedEntityRecord> = cursor.take(total).collect();
323+
324+
Ok(signed_entities)
325+
}
304326
}
305327

306328
// TODO: this StoreAdapter implementation is temporary and concerns only the snapshots for the CardanoImmutableFilesFull signed entity type
Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
use crate::artifact_builder::{MithrilStakeDistribution, MithrilStakeDistributionSummary};
2+
use crate::http_server::routes::middlewares;
3+
use crate::DependencyManager;
4+
use mithril_common::entities::{Beacon, Epoch, SignedEntityType, Snapshot};
5+
use std::sync::Arc;
6+
use warp::Filter;
7+
8+
pub fn routes(
9+
dependency_manager: Arc<DependencyManager>,
10+
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
11+
artifact_mithril_stake_distributions(dependency_manager.clone())
12+
.or(artifact_mithril_stake_distribution_by_id(
13+
dependency_manager.clone(),
14+
))
15+
.or(artifact_cardano_full_immutable_snapshots(
16+
dependency_manager.clone(),
17+
))
18+
.or(artifact_cardano_full_immutable_snapshot_by_id(
19+
dependency_manager,
20+
))
21+
}
22+
23+
/// GET /artifact/mithril-stake-distributions
24+
fn artifact_mithril_stake_distributions(
25+
dependency_manager: Arc<DependencyManager>,
26+
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
27+
warp::path!("artifact" / "mithril-stake-distributions")
28+
.and(warp::get())
29+
.and(middlewares::with_signed_entity_storer(dependency_manager))
30+
.and(middlewares::with_signed_entity_type(
31+
SignedEntityType::MithrilStakeDistribution(Epoch::default()),
32+
))
33+
.and_then(handlers::artifacts_by_signed_entity_type::<MithrilStakeDistributionSummary>)
34+
}
35+
36+
/// GET /artifact/mithril-stake-distribution/:id
37+
fn artifact_mithril_stake_distribution_by_id(
38+
dependency_manager: Arc<DependencyManager>,
39+
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
40+
warp::path!("artifact" / "mithril-stake-distribution" / String)
41+
.and(warp::get())
42+
.and(middlewares::with_signed_entity_storer(dependency_manager))
43+
.and_then(handlers::artifact_by_signed_entity_id::<MithrilStakeDistribution>)
44+
}
45+
46+
/// GET /artifact/snapshots
47+
fn artifact_cardano_full_immutable_snapshots(
48+
dependency_manager: Arc<DependencyManager>,
49+
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
50+
warp::path!("artifact" / "snapshots")
51+
.and(warp::get())
52+
.and(middlewares::with_signed_entity_storer(dependency_manager))
53+
.and(middlewares::with_signed_entity_type(
54+
SignedEntityType::CardanoImmutableFilesFull(Beacon::default()),
55+
))
56+
.and_then(handlers::artifacts_by_signed_entity_type::<Snapshot>)
57+
}
58+
59+
/// GET /artifact/snapshot/:id
60+
fn artifact_cardano_full_immutable_snapshot_by_id(
61+
dependency_manager: Arc<DependencyManager>,
62+
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
63+
warp::path!("artifact" / "snapshot" / String)
64+
.and(warp::get())
65+
.and(middlewares::with_signed_entity_storer(dependency_manager))
66+
.and_then(handlers::artifact_by_signed_entity_id::<Snapshot>)
67+
}
68+
69+
mod handlers {
70+
use crate::database::provider::SignedEntityStorer;
71+
use crate::http_server::routes::reply;
72+
use mithril_common::entities::SignedEntityType;
73+
use serde::{Deserialize, Serialize};
74+
use slog_scope::{debug, warn};
75+
use std::convert::Infallible;
76+
use std::sync::Arc;
77+
use warp::http::StatusCode;
78+
79+
pub const LIST_MAX_ITEMS: usize = 20;
80+
81+
/// Artifacts by signed entity type
82+
pub async fn artifacts_by_signed_entity_type<T: Serialize + for<'a> Deserialize<'a>>(
83+
signed_entity_storer: Arc<dyn SignedEntityStorer>,
84+
signed_entity_type: SignedEntityType,
85+
) -> Result<impl warp::Reply, Infallible> {
86+
debug!("⇄ HTTP SERVER: artifacts");
87+
88+
match signed_entity_storer
89+
.get_last_signed_entities_by_type(&signed_entity_type, LIST_MAX_ITEMS)
90+
.await
91+
{
92+
Ok(signed_entities) => {
93+
let mut artifacts = Vec::new();
94+
for signed_entity in signed_entities {
95+
if let Ok(artifact) = serde_json::from_str::<T>(&signed_entity.artifact) {
96+
artifacts.push(artifact)
97+
}
98+
}
99+
Ok(reply::json(&artifacts, StatusCode::OK))
100+
}
101+
Err(err) => {
102+
warn!("artifacts_by_signed_entity_type"; "signed_entity_type" => ?signed_entity_type, "error" => ?err);
103+
Ok(reply::internal_server_error(err.to_string()))
104+
}
105+
}
106+
}
107+
108+
/// Artifact by signed entity id
109+
pub async fn artifact_by_signed_entity_id<T: Serialize + for<'a> Deserialize<'a>>(
110+
signed_entity_id: String,
111+
signed_entity_storer: Arc<dyn SignedEntityStorer>,
112+
) -> Result<impl warp::Reply, Infallible> {
113+
debug!("⇄ HTTP SERVER: artifact/{signed_entity_id}");
114+
115+
match signed_entity_storer
116+
.get_signed_entity(signed_entity_id.clone())
117+
.await
118+
{
119+
Ok(signed_entity) => match signed_entity {
120+
Some(signed_entity) => match serde_json::from_str::<T>(&signed_entity.artifact) {
121+
Ok(artifact) => Ok(reply::json(&artifact, StatusCode::OK)),
122+
Err(err) => Ok(reply::internal_server_error(err.to_string())),
123+
},
124+
None => Ok(reply::empty(StatusCode::NOT_FOUND)),
125+
},
126+
Err(err) => {
127+
warn!("artifact_by_signed_entity_id"; "signed_entity_id" => ?signed_entity_id, "error" => ?err);
128+
Ok(reply::internal_server_error(err.to_string()))
129+
}
130+
}
131+
}
132+
}
133+
134+
/* #[cfg(test)]
135+
mod tests {
136+
use crate::http_server::SERVER_BASE_PATH;
137+
use mithril_common::test_utils::apispec::APISpec;
138+
use mithril_common::test_utils::fake_data;
139+
use serde_json::Value::Null;
140+
141+
use crate::initialize_dependencies;
142+
use warp::http::Method;
143+
use warp::test::request;
144+
145+
use super::*;
146+
use crate::snapshot_stores::{MockSnapshotStore, SnapshotStoreError};
147+
148+
fn setup_router(
149+
dependency_manager: Arc<DependencyManager>,
150+
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
151+
let cors = warp::cors()
152+
.allow_any_origin()
153+
.allow_headers(vec!["content-type"])
154+
.allow_methods(vec![Method::GET, Method::POST, Method::OPTIONS]);
155+
156+
warp::any()
157+
.and(warp::path(SERVER_BASE_PATH))
158+
.and(routes(dependency_manager).with(cors))
159+
}
160+
161+
#[tokio::test]
162+
async fn test_snapshots_get_ok() {
163+
let fake_snapshots = fake_data::snapshots(5);
164+
let mut mock_snapshot_store = MockSnapshotStore::new();
165+
mock_snapshot_store
166+
.expect_list_snapshots()
167+
.return_const(Ok(fake_snapshots))
168+
.once();
169+
let mut dependency_manager = initialize_dependencies().await;
170+
dependency_manager.snapshot_store = Arc::new(mock_snapshot_store);
171+
172+
let method = Method::GET.as_str();
173+
let path = "/snapshots";
174+
175+
let response = request()
176+
.method(method)
177+
.path(&format!("/{SERVER_BASE_PATH}{path}"))
178+
.reply(&setup_router(Arc::new(dependency_manager)))
179+
.await;
180+
181+
APISpec::verify_conformity(
182+
APISpec::get_all_spec_files(),
183+
method,
184+
path,
185+
"application/json",
186+
&Null,
187+
&response,
188+
);
189+
}
190+
191+
#[tokio::test]
192+
async fn test_snapshots_get_ko() {
193+
let mut mock_snapshot_store = MockSnapshotStore::new();
194+
mock_snapshot_store
195+
.expect_list_snapshots()
196+
.return_const(Err(SnapshotStoreError::Manifest(
197+
"an error occurred".to_string(),
198+
)))
199+
.once();
200+
let mut dependency_manager = initialize_dependencies().await;
201+
dependency_manager.snapshot_store = Arc::new(mock_snapshot_store);
202+
203+
let method = Method::GET.as_str();
204+
let path = "/snapshots";
205+
206+
let response = request()
207+
.method(method)
208+
.path(&format!("/{SERVER_BASE_PATH}{path}"))
209+
.reply(&setup_router(Arc::new(dependency_manager)))
210+
.await;
211+
212+
APISpec::verify_conformity(
213+
APISpec::get_all_spec_files(),
214+
method,
215+
path,
216+
"application/json",
217+
&Null,
218+
&response,
219+
);
220+
}
221+
222+
#[tokio::test]
223+
async fn test_snapshot_digest_get_ok() {
224+
let fake_snapshot = fake_data::snapshots(1).first().unwrap().to_owned();
225+
let mut mock_snapshot_store = MockSnapshotStore::new();
226+
mock_snapshot_store
227+
.expect_get_snapshot_details()
228+
.return_const(Ok(Some(fake_snapshot)))
229+
.once();
230+
let mut dependency_manager = initialize_dependencies().await;
231+
dependency_manager.snapshot_store = Arc::new(mock_snapshot_store);
232+
233+
let method = Method::GET.as_str();
234+
let path = "/snapshot/{digest}";
235+
236+
let response = request()
237+
.method(method)
238+
.path(&format!("/{SERVER_BASE_PATH}{path}"))
239+
.reply(&setup_router(Arc::new(dependency_manager)))
240+
.await;
241+
242+
APISpec::verify_conformity(
243+
APISpec::get_all_spec_files(),
244+
method,
245+
path,
246+
"application/json",
247+
&Null,
248+
&response,
249+
);
250+
}
251+
252+
#[tokio::test]
253+
async fn test_snapshot_digest_get_ok_nosnapshot() {
254+
let mut mock_snapshot_store = MockSnapshotStore::new();
255+
mock_snapshot_store
256+
.expect_get_snapshot_details()
257+
.return_const(Ok(None))
258+
.once();
259+
let mut dependency_manager = initialize_dependencies().await;
260+
dependency_manager.snapshot_store = Arc::new(mock_snapshot_store);
261+
262+
let method = Method::GET.as_str();
263+
let path = "/snapshot/{digest}";
264+
265+
let response = request()
266+
.method(method)
267+
.path(&format!("/{SERVER_BASE_PATH}{path}"))
268+
.reply(&setup_router(Arc::new(dependency_manager)))
269+
.await;
270+
271+
APISpec::verify_conformity(
272+
APISpec::get_all_spec_files(),
273+
method,
274+
path,
275+
"application/json",
276+
&Null,
277+
&response,
278+
);
279+
}
280+
281+
#[tokio::test]
282+
async fn test_snapshot_digest_get_ko() {
283+
let mut mock_snapshot_store = MockSnapshotStore::new();
284+
mock_snapshot_store
285+
.expect_get_snapshot_details()
286+
.return_const(Err(SnapshotStoreError::Manifest(
287+
"an error occurred".to_string(),
288+
)))
289+
.once();
290+
let mut dependency_manager = initialize_dependencies().await;
291+
dependency_manager.snapshot_store = Arc::new(mock_snapshot_store);
292+
293+
let method = Method::GET.as_str();
294+
let path = "/snapshot/{digest}";
295+
296+
let response = request()
297+
.method(method)
298+
.path(&format!("/{SERVER_BASE_PATH}{path}"))
299+
.reply(&setup_router(Arc::new(dependency_manager)))
300+
.await;
301+
302+
APISpec::verify_conformity(
303+
APISpec::get_all_spec_files(),
304+
method,
305+
path,
306+
"application/json",
307+
&Null,
308+
&response,
309+
);
310+
}
311+
} */

0 commit comments

Comments
 (0)