Skip to content

Commit cadff9f

Browse files
committed
add open_message entity and main provider
1 parent 2dea4f4 commit cadff9f

File tree

5 files changed

+238
-4
lines changed

5 files changed

+238
-4
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Aggregator related database providers
22
mod certificate;
33
mod epoch_setting;
4+
mod open_message;
45
mod stake_pool;
56

67
pub use certificate::*;
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
use chrono::NaiveDateTime;
2+
use mithril_common::entities::Beacon;
3+
use mithril_common::entities::Epoch;
4+
5+
use mithril_common::sqlite::Provider;
6+
use mithril_common::sqlite::SourceAlias;
7+
use mithril_common::{
8+
entities::SignedEntityType,
9+
sqlite::{HydrationError, Projection, SqLiteEntity, WhereCondition},
10+
};
11+
use sqlite::Row;
12+
use sqlite::{Connection, Value};
13+
14+
/// ## OpenMessage
15+
///
16+
/// An open message is a message open for signatures. Every signer may send a
17+
/// single signature for this message from which a multi signature will be
18+
/// generated if possible.
19+
struct OpenMessage {
20+
open_message_id: String,
21+
epoch: Epoch,
22+
beacon: Beacon,
23+
signed_entity_type: SignedEntityType,
24+
message: String,
25+
created_at: NaiveDateTime,
26+
}
27+
28+
impl SqLiteEntity for OpenMessage {
29+
fn hydrate(row: Row) -> Result<Self, HydrationError>
30+
where
31+
Self: Sized,
32+
{
33+
let open_message_id = row.get::<String, _>(0);
34+
let message = row.get::<String, _>(4);
35+
let epoch_settings_id = row.get::<i64, _>(1);
36+
let epoch_val = u64::try_from(epoch_settings_id)
37+
.map_err(|e| panic!("Integer field open_message.epoch_settings_id (value={epoch_settings_id}) is incompatible with u64 Epoch representation. Error = {e}"))?;
38+
39+
let signed_entity_type_id = usize::try_from(row.get::<i64, _>(3)).map_err(|e| {
40+
panic!(
41+
"Integer field open_message.signed_entity_type_id cannot be turned into usize: {e}"
42+
)
43+
})?;
44+
let signed_entity_type = SignedEntityType::from_repr(signed_entity_type_id)
45+
.ok_or_else(|| HydrationError::InvalidData(format!(
46+
"Field open_message.signed_type_id can be either 0, 1 or 2, ({signed_entity_type_id} given)."
47+
)))?;
48+
let beacon_str = row.get::<String, _>(2);
49+
let beacon: Beacon = serde_json::from_str(&beacon_str).map_err(|e| {
50+
HydrationError::InvalidData(format!(
51+
"Invalid Beacon JSON in open_message.beacon: '{beacon_str}'. Error: {e}"
52+
))
53+
})?;
54+
let datetime = &row.get::<String, _>(5);
55+
let created_at =
56+
NaiveDateTime::parse_from_str(datetime, "%Y-%m-%d %H:%M:%S").map_err(|e| {
57+
HydrationError::InvalidData(format!(
58+
"Could not turn open_message.created_at field value '{datetime}' to NaiveDateTime. Error: {e}"
59+
))
60+
})?;
61+
62+
let open_message = Self {
63+
open_message_id,
64+
epoch: Epoch(epoch_val),
65+
beacon,
66+
signed_entity_type,
67+
message,
68+
created_at,
69+
};
70+
71+
Ok(open_message)
72+
}
73+
74+
fn get_projection() -> Projection {
75+
Projection::from(&[
76+
("open_message_id", "{:open_message:}.open_message_id", "int"),
77+
(
78+
"epoch_settings_id",
79+
"{:open_message:}.epoch_settings_id",
80+
"int",
81+
),
82+
("beacon", "{:open_message:}.beacon", "text"),
83+
(
84+
"signed_entity_type_id",
85+
"{:open_message:}.signed_entity_type_id",
86+
"int",
87+
),
88+
("message", "{:open_message:}.message", "text"),
89+
("created_at", "{:open_message:}.created_at", "text"),
90+
])
91+
}
92+
}
93+
94+
struct OpenMessageProvider<'client> {
95+
connection: &'client Connection,
96+
}
97+
98+
impl<'client> OpenMessageProvider<'client> {
99+
fn new(connection: &'client Connection) -> Self {
100+
Self { connection }
101+
}
102+
103+
fn get_epoch_condition(&self, epoch: Epoch) -> WhereCondition {
104+
WhereCondition::new(
105+
"{:open_message:}.epoch_settings_id = ?*",
106+
vec![Value::Integer(epoch.0 as i64)],
107+
)
108+
}
109+
110+
fn get_signed_entity_type_condition(
111+
&self,
112+
signed_entity_type: &SignedEntityType,
113+
) -> WhereCondition {
114+
WhereCondition::new(
115+
"{:open_message:}.signed_entity_type_id = ?*",
116+
vec![Value::Integer(*signed_entity_type as i64)],
117+
)
118+
}
119+
120+
fn get_open_message_id_condition(&self, open_message_id: &str) -> WhereCondition {
121+
WhereCondition::new(
122+
"{:open_message:}.open_message_id = ?*",
123+
vec![Value::String(open_message_id.to_owned())],
124+
)
125+
}
126+
}
127+
128+
impl<'client> Provider<'client> for OpenMessageProvider<'client> {
129+
type Entity = OpenMessage;
130+
131+
fn get_definition(&self, condition: &str) -> String {
132+
let aliases = SourceAlias::new(&[("{:open_message:}", "open_message")]);
133+
let projection = Self::Entity::get_projection().expand(aliases);
134+
135+
format!("select {projection} from open_message where {condition} order by created_at desc")
136+
}
137+
138+
fn get_connection(&'client self) -> &'client Connection {
139+
self.connection
140+
}
141+
}
142+
143+
#[cfg(test)]
144+
mod tests {
145+
use mithril_common::sqlite::SourceAlias;
146+
147+
use super::*;
148+
149+
#[test]
150+
fn open_message_projection() {
151+
let projection = OpenMessage::get_projection();
152+
let aliases = SourceAlias::new(&[("{:open_message:}", "open_message")]);
153+
154+
assert_eq!(
155+
"open_message.open_message_id as open_message_id, open_message.epoch_settings_id as epoch_settings_id, open_message.beacon as beacon, open_message.signed_entity_type_id as signed_entity_type_id, open_message.message as message, open_message.created_at as created_at".to_string(),
156+
projection.expand(aliases)
157+
)
158+
}
159+
160+
#[test]
161+
fn provider_epoch_condition() {
162+
let connection = Connection::open(":memory:").unwrap();
163+
let provider = OpenMessageProvider::new(&connection);
164+
let (expr, params) = provider.get_epoch_condition(Epoch(12)).expand();
165+
166+
assert_eq!("{:open_message:}.epoch_settings_id = ?1".to_string(), expr);
167+
assert_eq!(vec![Value::Integer(12)], params,);
168+
}
169+
170+
#[test]
171+
fn provider_message_type_condition() {
172+
let connection = Connection::open(":memory:").unwrap();
173+
let provider = OpenMessageProvider::new(&connection);
174+
let (expr, params) = provider
175+
.get_signed_entity_type_condition(&SignedEntityType::CardanoImmutableFilesFull)
176+
.expand();
177+
178+
assert_eq!(
179+
"{:open_message:}.signed_entity_type_id = ?1".to_string(),
180+
expr
181+
);
182+
assert_eq!(vec![Value::Integer(2)], params,);
183+
}
184+
185+
#[test]
186+
fn provider_message_id_condition() {
187+
let connection = Connection::open(":memory:").unwrap();
188+
let provider = OpenMessageProvider::new(&connection);
189+
let (expr, params) = provider
190+
.get_open_message_id_condition("cecd7983-8b3a-42b1-b778-6d75e87828ee")
191+
.expand();
192+
193+
assert_eq!("{:open_message:}.open_message_id = ?1".to_string(), expr);
194+
assert_eq!(
195+
vec![Value::String(
196+
"cecd7983-8b3a-42b1-b778-6d75e87828ee".to_string()
197+
)],
198+
params,
199+
);
200+
}
201+
}

mithril-common/src/entities/signed_entity_type.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
use strum::IntoEnumIterator;
22
use strum_macros::{Display, EnumIter, EnumString, FromRepr};
33

4-
/// The signed entity type that represents a type of data signed by the Mithril protocol
5-
/// Note: Each variant of this enum must be associated to an entry in the `signed_entity_type` tables of the signer/aggregator nodes.
6-
/// The variant are identified by their discriminant (i.e. index in the enum), thus the modification of this type should only ever consist of appending new variants.
4+
/// The signed entity type that represents a type of data signed by the Mithril
5+
/// protocol Note: Each variant of this enum must be associated to an entry in
6+
/// the `signed_entity_type` table of the signer/aggregator nodes. The variant
7+
/// are identified by their discriminant (i.e. index in the enum), thus the
8+
/// modification of this type should only ever consist of appending new
9+
/// variants.
710
#[derive(Display, FromRepr, EnumString, EnumIter, Debug, Clone, Copy, PartialEq, Eq)]
811
#[strum(serialize_all = "PascalCase")]
912
pub enum SignedEntityType {

mithril-common/src/sqlite/projection.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ impl Projection {
4949
Self { fields }
5050
}
5151

52+
/// Create a Projection from a list of tuples `&[(name, definition, sql_type)]`.
53+
pub fn from(fields: &[(&str, &str, &str)]) -> Self {
54+
let field_defs: Vec<ProjectionField> = fields
55+
.into_iter()
56+
.map(|(name, definition, sql_type)| ProjectionField::new(name, definition, &sql_type))
57+
.collect();
58+
59+
Self::new(field_defs)
60+
}
61+
5262
/// Add a new field to the definition. This is one of the projection
5363
/// building tool to create a projection out of an existing structure.
5464
/// This is a blanket implementation.
@@ -107,4 +117,20 @@ mod tests {
107117
projection.expand(aliases)
108118
)
109119
}
120+
121+
#[test]
122+
fn list_constructor() {
123+
let projection = Projection::from(&[
124+
("something_id", "{:test:}.something_id", "integer"),
125+
("name", "{:test:}.name", "text"),
126+
("created_at", "{:test:}.created_at", "timestamp"),
127+
]);
128+
129+
let aliases = SourceAlias::new(&[("{:test:}", "test")]);
130+
131+
assert_eq!(
132+
"test.something_id as something_id, test.name as name, test.created_at as created_at",
133+
projection.expand(aliases)
134+
);
135+
}
110136
}

mithril-common/src/sqlite/source_alias.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use std::collections::{hash_map::Iter, HashMap};
22

3-
/// Handful tool to store SQL source aliases
3+
/// Handful tool to store SQL source aliases.
4+
/// ```
5+
/// let aliases = SourceAlias::new(&[("first", "one"), ("second", "two")]);
6+
/// ```
47
#[derive(Debug, Default, Clone)]
58
pub struct SourceAlias {
69
/// Internal HashMap of source_name => source_alias

0 commit comments

Comments
 (0)