Skip to content

Commit 69c9769

Browse files
authored
Merge pull request #2734 from input-output-hk/djo/2704/cdb-by-epoch-route
feat: add `/artifact/cardano-database/epoch/{epoch}` route + support epoch expansion from `latest` in some routes
2 parents 046a4f8 + 99b441b commit 69c9769

File tree

20 files changed

+1044
-324
lines changed

20 files changed

+1044
-324
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.7.87"
3+
version = "0.7.88"
44
description = "A Mithril Aggregator server"
55
authors = { workspace = true }
66
edition = { workspace = true }

mithril-aggregator/src/database/migration.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,5 +239,17 @@ create table immutable_file_digest (
239239
drop table pending_certificate;
240240
"#,
241241
),
242+
// Migration 36
243+
// Add `epoch` virtual column to `signed_entity` table.
244+
//
245+
// Note: because the epoch in the `beacon` field can be either stored directly as an integer
246+
// or as a JSON property, we need to use a coalesce function to get the epoch value.
247+
SqlMigration::new(
248+
36,
249+
r#"
250+
alter table signed_entity add column epoch as (coalesce(json_extract(beacon, '$.epoch'), beacon));
251+
create index signed_entity_epoch on signed_entity(epoch);
252+
"#,
253+
),
242254
]
243255
}

mithril-aggregator/src/database/query/signed_entity/get_signed_entity.rs

Lines changed: 103 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,16 @@ impl GetSignedEntityRecordQuery {
6161
})
6262
}
6363

64-
pub fn cardano_stake_distribution_by_epoch(epoch: Epoch) -> Self {
65-
let signed_entity_type_id =
66-
SignedEntityTypeDiscriminants::CardanoStakeDistribution.index() as i64;
64+
pub fn by_signed_entity_type_and_epoch(
65+
signed_entity_type: &SignedEntityTypeDiscriminants,
66+
epoch: Epoch,
67+
) -> Self {
68+
let signed_entity_type_id = signed_entity_type.index() as i64;
6769
let epoch = *epoch as i64;
6870

6971
Self {
7072
condition: WhereCondition::new(
71-
"signed_entity_type_id = ?* and beacon = ?*",
73+
"signed_entity_type_id = ?* and epoch = ?*",
7274
vec![Value::Integer(signed_entity_type_id), Value::Integer(epoch)],
7375
),
7476
}
@@ -93,134 +95,147 @@ impl Query for GetSignedEntityRecordQuery {
9395

9496
#[cfg(test)]
9597
mod tests {
96-
use chrono::DateTime;
97-
use mithril_common::{
98-
entities::{CardanoDbBeacon, SignedEntityType},
99-
test::double::fake_data,
100-
};
98+
use mithril_common::entities::{BlockNumber, CardanoDbBeacon, SignedEntityType};
10199
use mithril_persistence::sqlite::ConnectionExtensions;
102100
use sqlite::ConnectionThreadSafe;
103101

104102
use crate::database::test_helper::{insert_signed_entities, main_db_connection};
105103

106104
use super::*;
107105

108-
fn create_database_with_cardano_stake_distributions<T: Into<SignedEntityRecord>>(
109-
cardano_stake_distributions: Vec<T>,
110-
) -> (ConnectionThreadSafe, Vec<SignedEntityRecord>) {
111-
let records = cardano_stake_distributions
112-
.into_iter()
113-
.map(|cardano_stake_distribution| cardano_stake_distribution.into())
114-
.collect::<Vec<_>>();
115-
116-
let connection = create_database(&records);
117-
118-
(connection, records)
119-
}
120-
121106
fn create_database(records: &[SignedEntityRecord]) -> ConnectionThreadSafe {
122107
let connection = main_db_connection().unwrap();
123108
insert_signed_entities(&connection, records.to_vec()).unwrap();
109+
124110
connection
125111
}
126112

127113
#[test]
128-
fn cardano_stake_distribution_by_epoch_returns_records_filtered_by_epoch() {
129-
let mut cardano_stake_distributions = fake_data::cardano_stake_distributions(3);
130-
cardano_stake_distributions[0].epoch = Epoch(3);
131-
cardano_stake_distributions[1].epoch = Epoch(4);
132-
cardano_stake_distributions[2].epoch = Epoch(5);
114+
fn by_signed_entity_and_epoch_returns_records_filtered_by_epoch() {
115+
let records = vec![
116+
SignedEntityRecord::fake_with_signed_entity(
117+
SignedEntityType::CardanoStakeDistribution(Epoch(3)),
118+
),
119+
SignedEntityRecord::fake_with_signed_entity(
120+
SignedEntityType::CardanoStakeDistribution(Epoch(4)),
121+
),
122+
SignedEntityRecord::fake_with_signed_entity(
123+
SignedEntityType::CardanoStakeDistribution(Epoch(5)),
124+
),
125+
];
133126

134-
let (connection, records) =
135-
create_database_with_cardano_stake_distributions(cardano_stake_distributions);
127+
let connection = create_database(&records);
136128

137129
let records_retrieved: Vec<SignedEntityRecord> = connection
138-
.fetch_collect(
139-
GetSignedEntityRecordQuery::cardano_stake_distribution_by_epoch(Epoch(4)),
140-
)
130+
.fetch_collect(GetSignedEntityRecordQuery::by_signed_entity_type_and_epoch(
131+
&SignedEntityTypeDiscriminants::CardanoStakeDistribution,
132+
Epoch(4),
133+
))
141134
.unwrap();
142135

143136
assert_eq!(vec![records[1].clone()], records_retrieved);
144137
}
145138

146139
#[test]
147-
fn cardano_stake_distribution_by_epoch_returns_records_returns_only_cardano_stake_distribution_records()
148-
{
149-
let cardano_stake_distributions_record: SignedEntityRecord = {
150-
let mut cardano_stake_distribution = fake_data::cardano_stake_distribution(Epoch(4));
151-
cardano_stake_distribution.hash = "hash-123".to_string();
152-
cardano_stake_distribution.into()
153-
};
154-
155-
let snapshots_record = {
156-
let mut snapshot = fake_data::snapshots(1)[0].clone();
157-
snapshot.beacon.epoch = Epoch(4);
158-
SignedEntityRecord::from_snapshot(snapshot, "whatever".to_string(), DateTime::default())
159-
};
160-
161-
let mithril_stake_distribution_record: SignedEntityRecord = {
162-
let mithril_stake_distributions = fake_data::mithril_stake_distributions(1);
163-
let mut mithril_stake_distribution = mithril_stake_distributions[0].clone();
164-
mithril_stake_distribution.epoch = Epoch(4);
165-
mithril_stake_distribution.into()
166-
};
167-
168-
let connection = create_database(&[
169-
cardano_stake_distributions_record.clone(),
170-
snapshots_record,
171-
mithril_stake_distribution_record,
172-
]);
140+
fn by_signed_entity_and_epoch_returns_records_filtered_by_discriminant() {
141+
let records = vec![
142+
SignedEntityRecord::fake_with_signed_entity(
143+
SignedEntityType::CardanoStakeDistribution(Epoch(3)),
144+
),
145+
SignedEntityRecord::fake_with_signed_entity(
146+
SignedEntityType::MithrilStakeDistribution(Epoch(3)),
147+
),
148+
SignedEntityRecord::fake_with_signed_entity(SignedEntityType::CardanoDatabase(
149+
CardanoDbBeacon::new(3, 98),
150+
)),
151+
];
173152

174-
let records_retrieved: Vec<SignedEntityRecord> = connection
175-
.fetch_collect(
176-
GetSignedEntityRecordQuery::cardano_stake_distribution_by_epoch(Epoch(4)),
177-
)
153+
let connection = create_database(&records);
154+
155+
let fetched_msd_records: Vec<SignedEntityRecord> = connection
156+
.fetch_collect(GetSignedEntityRecordQuery::by_signed_entity_type_and_epoch(
157+
&SignedEntityTypeDiscriminants::MithrilStakeDistribution,
158+
Epoch(3),
159+
))
178160
.unwrap();
161+
assert_eq!(vec![records[1].clone()], fetched_msd_records);
179162

180-
assert_eq!(
181-
vec![cardano_stake_distributions_record.clone()],
182-
records_retrieved,
183-
);
163+
let fetched_cdb_records: Vec<SignedEntityRecord> = connection
164+
.fetch_collect(GetSignedEntityRecordQuery::by_signed_entity_type_and_epoch(
165+
&SignedEntityTypeDiscriminants::CardanoDatabase,
166+
Epoch(3),
167+
))
168+
.unwrap();
169+
assert_eq!(vec![records[2].clone()], fetched_cdb_records);
184170
}
185171

186172
#[test]
187-
fn test_get_signed_entity_records() {
188-
let signed_entity_records = SignedEntityRecord::fake_records(5);
173+
fn test_get_record_by_id() {
174+
let signed_entity_records = vec![
175+
SignedEntityRecord::fake_with_signed_entity(
176+
SignedEntityType::CardanoStakeDistribution(Epoch(3)),
177+
),
178+
SignedEntityRecord::fake_with_signed_entity(SignedEntityType::CardanoTransactions(
179+
Epoch(4),
180+
BlockNumber(5),
181+
)),
182+
];
189183

190184
let connection = main_db_connection().unwrap();
191185
insert_signed_entities(&connection, signed_entity_records.clone()).unwrap();
192186

193-
let first_signed_entity_type = signed_entity_records.first().unwrap().to_owned();
194-
let signed_entity_records: Vec<SignedEntityRecord> = connection
195-
.fetch_collect(GetSignedEntityRecordQuery::by_signed_entity_id(
187+
let first_signed_entity_type = signed_entity_records[0].clone();
188+
let fetched_record = connection
189+
.fetch_first(GetSignedEntityRecordQuery::by_signed_entity_id(
196190
&first_signed_entity_type.signed_entity_id,
197191
))
198192
.unwrap();
199-
assert_eq!(vec![first_signed_entity_type], signed_entity_records);
193+
assert_eq!(Some(first_signed_entity_type), fetched_record);
194+
}
200195

201-
let signed_entity_records: Vec<SignedEntityRecord> = connection
196+
#[test]
197+
fn test_get_record_by_signed_entity_type() {
198+
let signed_entity_records = vec![
199+
SignedEntityRecord::fake_with_signed_entity(
200+
SignedEntityType::MithrilStakeDistribution(Epoch(2)),
201+
),
202+
SignedEntityRecord::fake_with_signed_entity(SignedEntityType::CardanoTransactions(
203+
Epoch(4),
204+
BlockNumber(5),
205+
)),
206+
SignedEntityRecord::fake_with_signed_entity(SignedEntityType::CardanoTransactions(
207+
Epoch(5),
208+
BlockNumber(9),
209+
)),
210+
];
211+
212+
let connection = main_db_connection().unwrap();
213+
insert_signed_entities(&connection, signed_entity_records.clone()).unwrap();
214+
215+
let fetched_tx_records: Vec<SignedEntityRecord> = connection
202216
.fetch_collect(
203217
GetSignedEntityRecordQuery::by_signed_entity_type(
204-
&SignedEntityTypeDiscriminants::CardanoImmutableFilesFull,
218+
&SignedEntityTypeDiscriminants::CardanoTransactions,
205219
)
206220
.unwrap(),
207221
)
208222
.unwrap();
209-
let expected_signed_entity_records: Vec<SignedEntityRecord> = signed_entity_records
210-
.iter()
211-
.filter_map(|se| {
212-
(se.signed_entity_type.index()
213-
== SignedEntityType::CardanoImmutableFilesFull(CardanoDbBeacon::default())
214-
.index())
215-
.then_some(se.to_owned())
216-
})
217-
.collect();
218-
assert_eq!(expected_signed_entity_records, signed_entity_records);
223+
let expected_tx_records: Vec<SignedEntityRecord> =
224+
vec![signed_entity_records[2].clone(), signed_entity_records[1].clone()];
225+
assert_eq!(expected_tx_records, fetched_tx_records);
226+
}
227+
228+
#[test]
229+
fn test_get_all_records() {
230+
let signed_entity_records = SignedEntityRecord::fake_records(5);
231+
232+
let connection = main_db_connection().unwrap();
233+
insert_signed_entities(&connection, signed_entity_records.clone()).unwrap();
219234

220-
let signed_entity_records: Vec<SignedEntityRecord> =
235+
let fetched_records: Vec<SignedEntityRecord> =
221236
connection.fetch_collect(GetSignedEntityRecordQuery::all()).unwrap();
222-
let expected_signed_entity_records: Vec<SignedEntityRecord> =
223-
signed_entity_records.iter().map(|c| c.to_owned()).collect();
224-
assert_eq!(expected_signed_entity_records, signed_entity_records);
237+
let expected_signed_entity_records: Vec<_> =
238+
signed_entity_records.into_iter().rev().collect();
239+
assert_eq!(expected_signed_entity_records, fetched_records);
225240
}
226241
}

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

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,68 @@ impl From<MithrilStakeDistribution> for SignedEntityRecord {
6464
}
6565
}
6666

67+
#[cfg(test)]
68+
impl From<CardanoDatabaseSnapshot> for SignedEntityRecord {
69+
fn from(value: CardanoDatabaseSnapshot) -> Self {
70+
let entity = serde_json::to_string(&value).unwrap();
71+
72+
SignedEntityRecord {
73+
signed_entity_id: value.hash.clone(),
74+
signed_entity_type: SignedEntityType::CardanoDatabase(value.beacon),
75+
certificate_id: format!("certificate-{}", value.hash),
76+
artifact: entity,
77+
created_at: DateTime::parse_from_rfc3339("2023-01-19T13:43:05.618857482Z")
78+
.unwrap()
79+
.with_timezone(&Utc),
80+
}
81+
}
82+
}
83+
6784
#[cfg(test)]
6885
impl SignedEntityRecord {
86+
pub(crate) fn fake_with_signed_entity(signed_entity_type: SignedEntityType) -> Self {
87+
use mithril_common::test::double::fake_data;
88+
fn get_id_and_artifact(artifact: &(impl Artifact + serde::Serialize)) -> (String, String) {
89+
(artifact.get_id(), serde_json::to_string(artifact).unwrap())
90+
}
91+
92+
let (id, artifact) = match signed_entity_type.clone() {
93+
SignedEntityType::MithrilStakeDistribution(epoch) => {
94+
let artifact = fake_data::mithril_stake_distribution(epoch, vec![]);
95+
get_id_and_artifact(&artifact)
96+
}
97+
SignedEntityType::CardanoStakeDistribution(epoch) => {
98+
let artifact = fake_data::cardano_stake_distribution(epoch);
99+
get_id_and_artifact(&artifact)
100+
}
101+
SignedEntityType::CardanoImmutableFilesFull(cardano_db_beacon) => {
102+
let mut artifact = fake_data::snapshot(cardano_db_beacon.immutable_file_number);
103+
artifact.beacon = cardano_db_beacon;
104+
get_id_and_artifact(&artifact)
105+
}
106+
SignedEntityType::CardanoDatabase(cardano_db_beacon) => {
107+
let mut artifact =
108+
fake_data::cardano_database_snapshot(cardano_db_beacon.immutable_file_number);
109+
artifact.beacon = cardano_db_beacon;
110+
get_id_and_artifact(&artifact)
111+
}
112+
SignedEntityType::CardanoTransactions(_epoch, block_number) => {
113+
let artifact = fake_data::cardano_transactions_snapshot(block_number);
114+
get_id_and_artifact(&artifact)
115+
}
116+
};
117+
118+
SignedEntityRecord {
119+
signed_entity_id: id.clone(),
120+
signed_entity_type,
121+
certificate_id: format!("certificate-{id}"),
122+
artifact,
123+
created_at: DateTime::parse_from_rfc3339("2023-01-19T13:43:05.618857482Z")
124+
.unwrap()
125+
.with_timezone(&Utc),
126+
}
127+
}
128+
69129
pub(crate) fn from_snapshot(
70130
snapshot: Snapshot,
71131
certificate_id: String,
@@ -441,8 +501,7 @@ mod tests {
441501

442502
#[test]
443503
fn test_convert_signed_entity() {
444-
let snapshots = fake_data::snapshots(1);
445-
let snapshot = snapshots.first().unwrap().to_owned();
504+
let snapshot = fake_data::snapshot(1);
446505
let snapshot_expected = snapshot.clone();
447506

448507
let signed_entity: SignedEntityRecord = SignedEntityRecord::from_snapshot(

0 commit comments

Comments
 (0)