diff --git a/mithril-signer/src/database/migration.rs b/mithril-signer/src/database/migration.rs index c416cf281f5..9c263d2b974 100644 --- a/mithril-signer/src/database/migration.rs +++ b/mithril-signer/src/database/migration.rs @@ -83,6 +83,30 @@ insert into stake_pool (epoch, stake_pool_id, stake, created_at) from stake, json_each(stake.value) as stake_dis order by epoch asc; drop table stake; +"#, + ), + // Migration 5 + // Add the `protocol_initializer` table and migration data from the previous + // `protocol_initializer` JSON format. + SqlMigration::new( + 5, + r#" +create table new_protocol_initializer ( + epoch integer not null, + protocol json not null, + created_at text not null, + primary key (epoch) +); +create table if not exists protocol_initializer (key_hash text primary key, key json not null, value json not null); +insert into new_protocol_initializer (epoch, protocol, created_at) + select + protocol_initializer.key as epoch, + protocol_initializer.value, + strftime('%Y-%m-%dT%H:%M:%fZ', current_timestamp) + from protocol_initializer + order by epoch asc; +drop table protocol_initializer; +alter table new_protocol_initializer rename to protocol_initializer; "#, ), ] diff --git a/mithril-signer/src/database/query/mod.rs b/mithril-signer/src/database/query/mod.rs index e1a0abc7a10..2f5b5001218 100644 --- a/mithril-signer/src/database/query/mod.rs +++ b/mithril-signer/src/database/query/mod.rs @@ -1,7 +1,9 @@ //! Signer related database queries +mod protocol_initializer; mod signed_beacon; mod stake_pool; +pub use protocol_initializer::*; pub use signed_beacon::*; pub use stake_pool::*; diff --git a/mithril-signer/src/database/query/protocol_initializer/delete_protocol_initializer.rs b/mithril-signer/src/database/query/protocol_initializer/delete_protocol_initializer.rs new file mode 100644 index 00000000000..68a2e0f6e21 --- /dev/null +++ b/mithril-signer/src/database/query/protocol_initializer/delete_protocol_initializer.rs @@ -0,0 +1,42 @@ +use sqlite::Value; + +use mithril_common::entities::Epoch; +use mithril_persistence::sqlite::{Query, SourceAlias, SqLiteEntity, WhereCondition}; + +use crate::database::record::ProtocolInitializerRecord; + +/// Query to delete old [ProtocolInitializer] from the sqlite database +pub struct DeleteProtocolInitializerQuery { + condition: WhereCondition, +} + +impl Query for DeleteProtocolInitializerQuery { + type Entity = ProtocolInitializerRecord; + + fn filters(&self) -> WhereCondition { + self.condition.clone() + } + + fn get_definition(&self, condition: &str) -> String { + // it is important to alias the fields with the same name as the table + // since the table cannot be aliased in a RETURNING statement in SQLite. + let projection = Self::Entity::get_projection().expand(SourceAlias::new(&[( + "{:protocol_initializer:}", + "protocol_initializer", + )])); + + format!("delete from protocol_initializer where {condition} returning {projection}") + } +} + +impl DeleteProtocolInitializerQuery { + /// Create the SQL query to prune data older than the given Epoch. + pub fn below_epoch_threshold(epoch_threshold: Epoch) -> Self { + let condition = WhereCondition::new( + "epoch < ?*", + vec![Value::Integer(epoch_threshold.try_into().unwrap())], + ); + + Self { condition } + } +} diff --git a/mithril-signer/src/database/query/protocol_initializer/get_protocol_initializer.rs b/mithril-signer/src/database/query/protocol_initializer/get_protocol_initializer.rs new file mode 100644 index 00000000000..96ba2c55694 --- /dev/null +++ b/mithril-signer/src/database/query/protocol_initializer/get_protocol_initializer.rs @@ -0,0 +1,53 @@ +use sqlite::Value; + +use mithril_common::entities::Epoch; +use mithril_persistence::sqlite::{Query, SourceAlias, SqLiteEntity, WhereCondition}; + +use crate::database::record::ProtocolInitializerRecord; + +/// Simple queries to retrieve [ProtocolInitializer] from the sqlite database. +pub struct GetProtocolInitializerQuery { + condition: WhereCondition, + limit: Option, +} + +impl GetProtocolInitializerQuery { + /// Get all signed beacons that match the given signed entity types. + pub fn for_epoch(epoch: Epoch) -> Self { + let epoch_i64: i64 = epoch.try_into().unwrap(); + let condition = WhereCondition::new( + "protocol_initializer.epoch = ?", + vec![Value::Integer(epoch_i64)], + ); + + Self { + condition, + limit: None, + } + } + + pub fn last_n(limit: usize) -> Self { + let condition = WhereCondition::default(); + Self { + condition, + limit: Some(limit), + } + } +} + +impl Query for GetProtocolInitializerQuery { + type Entity = ProtocolInitializerRecord; + + fn filters(&self) -> WhereCondition { + self.condition.clone() + } + + fn get_definition(&self, condition: &str) -> String { + let aliases = SourceAlias::new(&[("{:protocol_initializer:}", "protocol_initializer")]); + let projection = Self::Entity::get_projection().expand(aliases); + let limit = self + .limit + .map_or("".to_string(), |limit| format!(" limit {}", limit)); + format!("select {projection} from protocol_initializer where {condition} order by rowid desc{limit}") + } +} diff --git a/mithril-signer/src/database/query/protocol_initializer/insert_protocol_initializer.rs b/mithril-signer/src/database/query/protocol_initializer/insert_protocol_initializer.rs new file mode 100644 index 00000000000..58bb70189e4 --- /dev/null +++ b/mithril-signer/src/database/query/protocol_initializer/insert_protocol_initializer.rs @@ -0,0 +1,46 @@ +use sqlite::Value; + +use mithril_common::StdResult; +use mithril_persistence::sqlite::{Query, SourceAlias, SqLiteEntity, WhereCondition}; + +use crate::database::record::ProtocolInitializerRecord; + +/// Query to insert or replace [ProtocolInitializerRecord] in the sqlite database +pub struct InsertOrReplaceProtocolInitializerQuery { + condition: WhereCondition, +} + +impl InsertOrReplaceProtocolInitializerQuery { + pub fn one(record: ProtocolInitializerRecord) -> StdResult { + let value = serde_json::to_string(&record.protocol_initializer).unwrap(); + let condition = WhereCondition::new( + "(epoch, protocol, created_at) values (?*, ?*, ?*)", + vec![ + Value::Integer(record.epoch.try_into()?), + Value::String(value), + Value::String(record.created_at.to_rfc3339()), + ], + ); + + Ok(Self { condition }) + } +} + +impl Query for InsertOrReplaceProtocolInitializerQuery { + type Entity = ProtocolInitializerRecord; + + fn filters(&self) -> WhereCondition { + self.condition.clone() + } + + fn get_definition(&self, condition: &str) -> String { + // it is important to alias the fields with the same name as the table + // since the table cannot be aliased in a RETURNING statement in SQLite. + let projection = Self::Entity::get_projection().expand(SourceAlias::new(&[( + "{:protocol_initializer:}", + "protocol_initializer", + )])); + + format!("insert or replace into protocol_initializer {condition} returning {projection}") + } +} diff --git a/mithril-signer/src/database/query/protocol_initializer/mod.rs b/mithril-signer/src/database/query/protocol_initializer/mod.rs new file mode 100644 index 00000000000..a33fce5893c --- /dev/null +++ b/mithril-signer/src/database/query/protocol_initializer/mod.rs @@ -0,0 +1,7 @@ +mod delete_protocol_initializer; +mod get_protocol_initializer; +mod insert_protocol_initializer; + +pub use delete_protocol_initializer::*; +pub use get_protocol_initializer::*; +pub use insert_protocol_initializer::*; diff --git a/mithril-signer/src/database/record/mod.rs b/mithril-signer/src/database/record/mod.rs index 1017f8153a6..53c95a39f66 100644 --- a/mithril-signer/src/database/record/mod.rs +++ b/mithril-signer/src/database/record/mod.rs @@ -1,7 +1,9 @@ //! Signer related database records +mod protocol_initializer_record; mod signed_beacon_record; mod stake_pool; +pub use protocol_initializer_record::*; pub use signed_beacon_record::*; pub use stake_pool::*; diff --git a/mithril-signer/src/database/record/protocol_initializer_record.rs b/mithril-signer/src/database/record/protocol_initializer_record.rs new file mode 100644 index 00000000000..b3c540139f3 --- /dev/null +++ b/mithril-signer/src/database/record/protocol_initializer_record.rs @@ -0,0 +1,60 @@ +use chrono::{DateTime, Utc}; + +use mithril_common::{crypto_helper::ProtocolInitializer, entities::Epoch}; +use mithril_persistence::sqlite::{HydrationError, Projection, SqLiteEntity}; + +/// Protocol initializer. +#[derive(Debug)] +pub struct ProtocolInitializerRecord { + /// Epoch + pub epoch: Epoch, + + /// Protocol Initializer + pub protocol_initializer: ProtocolInitializer, + + /// DateTime of the record creation. + pub created_at: DateTime, +} + +impl SqLiteEntity for ProtocolInitializerRecord { + fn hydrate(row: sqlite::Row) -> Result + where + Self: Sized, + { + let epoch_int = row.read::(0); + let protocol = row.read::<&str, _>(1); + let datetime = &row.read::<&str, _>(2); + + let record = Self { + protocol_initializer: serde_json::from_str(protocol).map_err(|e| { + HydrationError::InvalidData(format!( + "Could not cast string ({}) to ProtocolInitializer. Error: '{e}'", + protocol + )) + })?, + epoch: Epoch(epoch_int.try_into().map_err(|e| { + HydrationError::InvalidData(format!( + "Could not cast i64 ({epoch_int}) to u64. Error: '{e}'" + )) + })?), + created_at: DateTime::parse_from_rfc3339(datetime) + .map_err(|e| { + HydrationError::InvalidData(format!( + "Could not turn string '{datetime}' to rfc3339 Datetime. Error: {e}" + )) + })? + .with_timezone(&Utc), + }; + + Ok(record) + } + + fn get_projection() -> Projection { + let mut projection = Projection::default(); + projection.add_field("epoch", "{:protocol_initializer:}.epoch", "integer"); + projection.add_field("protocol", "{:protocol_initializer:}.protocol", "integer"); + projection.add_field("created_at", "{:protocol_initializer:}.created_at", "text"); + + projection + } +} diff --git a/mithril-signer/src/database/repository/mod.rs b/mithril-signer/src/database/repository/mod.rs index cff1538430c..162285b1809 100644 --- a/mithril-signer/src/database/repository/mod.rs +++ b/mithril-signer/src/database/repository/mod.rs @@ -1,8 +1,10 @@ //! Signer related database repositories mod cardano_transaction_repository; +mod protocol_initializer_repository; mod signed_beacon_repository; mod stake_pool_store; +pub use protocol_initializer_repository::*; pub use signed_beacon_repository::*; pub use stake_pool_store::*; diff --git a/mithril-signer/src/database/repository/protocol_initializer_repository.rs b/mithril-signer/src/database/repository/protocol_initializer_repository.rs new file mode 100644 index 00000000000..3fb456a3c79 --- /dev/null +++ b/mithril-signer/src/database/repository/protocol_initializer_repository.rs @@ -0,0 +1,316 @@ +use std::sync::Arc; + +use anyhow::Ok; +use async_trait::async_trait; + +use crate::database::query::{ + DeleteProtocolInitializerQuery, InsertOrReplaceProtocolInitializerQuery, +}; +use crate::database::record::ProtocolInitializerRecord; +use crate::{ + database::query::GetProtocolInitializerQuery, services::EpochPruningTask, + store::ProtocolInitializerStorer, +}; +use mithril_common::{crypto_helper::ProtocolInitializer, entities::Epoch, StdResult}; +use mithril_persistence::sqlite::ConnectionExtensions; +use mithril_persistence::{sqlite::SqliteConnection, store::adapter::StoreAdapter}; + +/// Implementation of the ProtocolInitializerStorer +pub struct ProtocolInitializerRepository { + connection: Arc, + retention_limit: Option, +} + +impl ProtocolInitializerRepository { + /// Create a new ProtocolInitializerRepository. + pub fn new(connection: Arc, retention_limit: Option) -> Self { + Self { + connection, + retention_limit, + } + } +} + +#[async_trait] +impl EpochPruningTask for ProtocolInitializerRepository { + fn pruned_data(&self) -> &'static str { + "Protocol initializer" + } + + async fn prune(&self, epoch: Epoch) -> StdResult<()> { + if let Some(threshold) = self.retention_limit { + self.connection + .apply(DeleteProtocolInitializerQuery::below_epoch_threshold( + epoch - threshold, + ))?; + } + Ok(()) + } +} + +#[async_trait] +impl ProtocolInitializerStorer for ProtocolInitializerRepository { + async fn save_protocol_initializer( + &self, + epoch: Epoch, + protocol_initializer: ProtocolInitializer, + ) -> StdResult> { + let previous_protocol_initializer = self.get_protocol_initializer(epoch).await?; + let record = ProtocolInitializerRecord { + epoch, + protocol_initializer: protocol_initializer.clone(), + created_at: chrono::Utc::now(), + }; + self.connection + .apply(InsertOrReplaceProtocolInitializerQuery::one(record).unwrap())?; + + Ok(previous_protocol_initializer) + } + + async fn get_protocol_initializer( + &self, + epoch: Epoch, + ) -> StdResult> { + let record = self + .connection + .fetch_first(GetProtocolInitializerQuery::for_epoch(epoch))?; + + Ok(record.map(|record| record.protocol_initializer)) + } + + async fn get_last_protocol_initializer( + &self, + last: usize, + ) -> StdResult> { + let record: Vec = self + .connection + .fetch_collect(GetProtocolInitializerQuery::last_n(last))?; + + Ok(record + .iter() + .map(|record| (record.epoch, record.protocol_initializer.to_owned())) + .collect()) + } +} + +#[cfg(test)] +mod tests { + use mithril_common::test_utils::fake_data; + use mithril_persistence::{ + sqlite::{ConnectionBuilder, ConnectionOptions}, + store::adapter::SQLiteAdapter, + }; + + use crate::database::test_helper::main_db_connection; + + use super::*; + + fn setup_protocol_initializers(nb_epoch: u64) -> Vec<(Epoch, ProtocolInitializer)> { + let mut values: Vec<(Epoch, ProtocolInitializer)> = Vec::new(); + for epoch in 1..=nb_epoch { + let stake = (epoch + 1) * 100; + let protocol_initializer = fake_data::protocol_initializer("1", stake); + values.push((Epoch(epoch), protocol_initializer)); + } + values + } + + async fn init_store( + nb_epoch: u64, + retention_limit: Option, + ) -> ProtocolInitializerRepository { + let store = ProtocolInitializerRepository::new( + Arc::new(main_db_connection().unwrap()), + retention_limit, + ); + + let values = setup_protocol_initializers(nb_epoch); + store_protocol_initializers(&store, &values).await; + + store + } + + async fn store_protocol_initializers( + store: &ProtocolInitializerRepository, + values: &[(Epoch, ProtocolInitializer)], + ) { + for value in values.iter() { + store + .save_protocol_initializer(value.0, value.1.clone()) + .await + .unwrap(); + } + } + + #[tokio::test] + async fn save_key_in_empty_store_return_none_as_previous_value() { + let protocol_initializers = setup_protocol_initializers(1); + + let store = init_store(0, None).await; + let res = store + .save_protocol_initializer( + protocol_initializers[0].0, + protocol_initializers[0].1.clone(), + ) + .await + .unwrap(); + + assert!(res.is_none()); + } + + #[tokio::test] + async fn update_protocol_initializer_in_store_return_previous_value() { + let protocol_initializers = setup_protocol_initializers(2); + let store = init_store(0, None).await; + store_protocol_initializers(&store, &protocol_initializers[0..1]).await; + + let res = store + .save_protocol_initializer( + protocol_initializers[0].0, + protocol_initializers[1].1.clone(), + ) + .await + .unwrap(); + + assert!(res.is_some()); + assert_eq!( + protocol_initializers[0].1.get_stake(), + res.unwrap().get_stake() + ); + } + + #[tokio::test] + async fn get_protocol_initializer_for_empty_epoch() { + let store = init_store(2, None).await; + let res = store.get_protocol_initializer(Epoch(0)).await.unwrap(); + + assert!(res.is_none()); + } + + #[tokio::test] + async fn get_protocol_initializer_for_existing_epoch() { + let store = init_store(2, None).await; + let res = store.get_protocol_initializer(Epoch(1)).await.unwrap(); + + assert!(res.is_some()); + } + + #[tokio::test] + async fn get_last_protocol_initializer_return_last_one_first() { + let store = init_store(0, None).await; + let values = setup_protocol_initializers(10); + store_protocol_initializers(&store, &values).await; + + let res = store.get_last_protocol_initializer(3).await.unwrap(); + + assert_eq!(3, res.len()); + assert_eq!(values[9].0, res[0].0); + assert_eq!(values[8].0, res[1].0); + assert_eq!(values[7].0, res[2].0); + } + + #[tokio::test] + async fn get_last_protocol_initializer_return_all_when_too_few_records() { + let store = init_store(0, None).await; + let values = setup_protocol_initializers(2); + store_protocol_initializers(&store, &values).await; + + let res = store.get_last_protocol_initializer(3).await.unwrap(); + + assert_eq!(2, res.len()); + assert_eq!(values[1].0, res[0].0); + assert_eq!(values[0].0, res[1].0); + } + + #[tokio::test] + async fn prune_epoch_older_than_threshold() { + const PROTOCOL_INITIALIZER_PRUNE_EPOCH_THRESHOLD: u64 = 10; + + let store = init_store(0, Some(PROTOCOL_INITIALIZER_PRUNE_EPOCH_THRESHOLD)).await; + let values = setup_protocol_initializers(2); + store_protocol_initializers(&store, &values).await; + + store + .prune(Epoch(2) + PROTOCOL_INITIALIZER_PRUNE_EPOCH_THRESHOLD) + .await + .unwrap(); + + let res = store.get_last_protocol_initializer(10).await.unwrap(); + + assert_eq!(1, res.len()); + assert_eq!(Epoch(2), res[0].0); + } + + #[tokio::test] + async fn without_threshold_nothing_is_pruned() { + let store = init_store(0, None).await; + let values = setup_protocol_initializers(2); + store_protocol_initializers(&store, &values).await; + + store.prune(Epoch(100)).await.unwrap(); + + let res = store.get_last_protocol_initializer(10).await.unwrap(); + assert_eq!(2, res.len()); + } + + #[tokio::test] + async fn should_migrate_data_from_adapter() { + let migrations = crate::database::migration::get_migrations(); + + // TODO: Do it in test_helper (it is done by build_main_db_connection) + fn create_connection_builder() -> ConnectionBuilder { + ConnectionBuilder::open_memory() + .with_options(&[ConnectionOptions::ForceDisableForeignKeys]) + } + let connection = Arc::new(create_connection_builder().build().unwrap()); + + // The adapter will create the table. + let mut adapter = SQLiteAdapter::::new( + "protocol_initializer", + connection.clone(), + ) + .unwrap(); + + assert!(connection + .prepare("select key_hash from protocol_initializer;") + .is_ok()); + assert!(connection.prepare("select * from db_version;").is_err()); + + // Here we can add some data with the old schema. + let (_, protocol_initializer_to_retrieve) = &setup_protocol_initializers(1)[0]; + + // If we don't want to use the adapter anymore, we can execute request directly. + assert!(adapter.get_record(&Epoch(5)).await.unwrap().is_none()); + adapter + .store_record(&Epoch(5), &protocol_initializer_to_retrieve) + .await + .unwrap(); + assert!(adapter.get_record(&Epoch(5)).await.unwrap().is_some()); + + // We finish the migration + create_connection_builder() + .apply_migrations(&connection, migrations) + .unwrap(); + + assert!(connection + .prepare("select key_hash from protocol_initializer;") + .is_err()); + assert!(connection + .prepare("select * from protocol_initializer;") + .is_ok()); + + let value: i64 = connection + .query_single_cell("select count(*) from protocol_initializer", &[]) + .unwrap(); + assert_eq!(value, 1); + + // We can check that data are migrated. + let store = ProtocolInitializerRepository::new(connection, None); + let protocol_initializer = store.get_protocol_initializer(Epoch(5)).await.unwrap(); + + assert_eq!( + protocol_initializer.unwrap().get_stake(), + protocol_initializer_to_retrieve.get_stake() + ); + } +} diff --git a/mithril-signer/src/dependency_injection/builder.rs b/mithril-signer/src/dependency_injection/builder.rs index a392c6d1862..784a2004a5b 100644 --- a/mithril-signer/src/dependency_injection/builder.rs +++ b/mithril-signer/src/dependency_injection/builder.rs @@ -32,9 +32,10 @@ use mithril_common::{MithrilTickerService, StdResult, TickerService}; use mithril_persistence::database::repository::CardanoTransactionRepository; use mithril_persistence::database::{ApplicationNodeType, SqlMigration}; use mithril_persistence::sqlite::{ConnectionBuilder, SqliteConnection, SqliteConnectionPool}; -use mithril_persistence::store::adapter::SQLiteAdapter; -use crate::database::repository::{SignedBeaconRepository, StakePoolStore}; +use crate::database::repository::{ + ProtocolInitializerRepository, SignedBeaconRepository, StakePoolStore, +}; use crate::dependency_injection::SignerDependencyContainer; use crate::services::{ AggregatorHTTPClient, CardanoTransactionsImporter, @@ -43,7 +44,7 @@ use crate::services::{ SignerUpkeepService, TransactionsImporterByChunk, TransactionsImporterWithPruner, TransactionsImporterWithVacuum, }; -use crate::store::{MKTreeStoreSqlite, ProtocolInitializerStore}; +use crate::store::MKTreeStoreSqlite; use crate::{ Configuration, MetricsService, HTTP_REQUEST_TIMEOUT_DURATION, SQLITE_FILE, SQLITE_FILE_CARDANO_TRANSACTION, @@ -212,13 +213,12 @@ impl<'a> DependenciesBuilder<'a> { ); let signed_entity_type_lock = Arc::new(SignedEntityTypeLock::default()); - let protocol_initializer_store = Arc::new(ProtocolInitializerStore::new( - Box::new(SQLiteAdapter::new( - "protocol_initializer", - sqlite_connection.clone(), - )?), - self.config.store_retention_limit, + + let protocol_initializer_store = Arc::new(ProtocolInitializerRepository::new( + sqlite_connection.clone(), + self.config.store_retention_limit.map(|limit| limit as u64), )); + let digester = Arc::new(CardanoImmutableDigester::new( network.to_string(), self.build_digester_cache_provider().await?, diff --git a/mithril-signer/src/runtime/runner.rs b/mithril-signer/src/runtime/runner.rs index efea10cd4e2..9f6ab0da0eb 100644 --- a/mithril-signer/src/runtime/runner.rs +++ b/mithril-signer/src/runtime/runner.rs @@ -340,9 +340,7 @@ mod tests { CardanoTransactionsPreloader, CardanoTransactionsPreloaderActivation, }, chain_observer::FakeObserver, - crypto_helper::{ - MKMap, MKMapNode, MKTreeNode, MKTreeStoreInMemory, MKTreeStorer, ProtocolInitializer, - }, + crypto_helper::{MKMap, MKMapNode, MKTreeNode, MKTreeStoreInMemory, MKTreeStorer}, digesters::{DumbImmutableDigester, DumbImmutableFileObserver}, entities::{BlockNumber, BlockRange, Epoch, SignedEntityTypeDiscriminants}, era::{adapters::EraReaderBootstrapAdapter, EraChecker, EraReader}, @@ -356,9 +354,10 @@ mod tests { test_utils::{fake_data, MithrilFixtureBuilder}, MithrilTickerService, TickerService, }; - use mithril_persistence::store::adapter::MemoryAdapter; - use crate::database::repository::{SignedBeaconRepository, StakePoolStore}; + use crate::database::repository::{ + ProtocolInitializerRepository, SignedBeaconRepository, StakePoolStore, + }; use crate::database::test_helper::main_db_connection; use crate::metrics::MetricsService; use crate::services::{ @@ -366,7 +365,6 @@ mod tests { MithrilSingleSigner, MockTransactionStore, MockUpkeepService, SignerCertifierService, SignerSignableSeedBuilder, SignerSignedEntityConfigProvider, }; - use crate::store::ProtocolInitializerStore; use crate::test_tools::TestLogger; use super::*; @@ -402,7 +400,6 @@ mod tests { async fn init_services() -> SignerDependencyContainer { let logger = TestLogger::stdout(); let sqlite_connection = Arc::new(main_db_connection().unwrap()); - let adapter: MemoryAdapter = MemoryAdapter::new(None).unwrap(); let stake_distribution_signers = fake_data::signers_with_stakes(2); let party_id = stake_distribution_signers[1].party_id.clone(); let network = fake_data::network(); @@ -450,8 +447,10 @@ mod tests { let cardano_stake_distribution_builder = Arc::new( CardanoStakeDistributionSignableBuilder::new(stake_store.clone()), ); - let protocol_initializer_store = - Arc::new(ProtocolInitializerStore::new(Box::new(adapter), None)); + let protocol_initializer_store = Arc::new(ProtocolInitializerRepository::new( + sqlite_connection.clone(), + None, + )); let epoch_service = Arc::new(RwLock::new(MithrilEpochService::new( stake_store.clone(), protocol_initializer_store.clone(), diff --git a/mithril-signer/src/services/epoch_service.rs b/mithril-signer/src/services/epoch_service.rs index 4dc62570ad6..0c9c42f5553 100644 --- a/mithril-signer/src/services/epoch_service.rs +++ b/mithril-signer/src/services/epoch_service.rs @@ -5,6 +5,7 @@ use std::collections::BTreeSet; use std::sync::Arc; use thiserror::Error; +use crate::database::repository::ProtocolInitializerRepository; use crate::dependency_injection::EpochServiceWrapper; use crate::entities::SignerEpochSettings; use crate::services::SignedEntityConfigProvider; @@ -327,16 +328,12 @@ impl MithrilEpochService { pub fn new_with_dumb_dependencies() -> Self { use crate::database::repository::StakePoolStore; use crate::database::test_helper::main_db_connection; - use crate::store::ProtocolInitializerStore; use crate::test_tools::TestLogger; - use mithril_persistence::store::adapter::DumbStoreAdapter; let sqlite_connection = Arc::new(main_db_connection().unwrap()); - let stake_store = Arc::new(StakePoolStore::new(sqlite_connection, None)); - let protocol_initializer_store = Arc::new(ProtocolInitializerStore::new( - Box::new(DumbStoreAdapter::new()), - None, - )); + let stake_store = Arc::new(StakePoolStore::new(sqlite_connection.clone(), None)); + let protocol_initializer_store = + Arc::new(ProtocolInitializerRepository::new(sqlite_connection, None)); Self::new( stake_store, @@ -437,13 +434,11 @@ mod tests { use mithril_common::entities::{Epoch, StakeDistribution}; use mithril_common::test_utils::{fake_data, MithrilFixtureBuilder}; - use mithril_persistence::store::adapter::{DumbStoreAdapter, MemoryAdapter}; use crate::database::repository::StakePoolStore; use crate::database::test_helper::main_db_connection; use crate::entities::SignerEpochSettings; use crate::services::MithrilProtocolInitializerBuilder; - use crate::store::ProtocolInitializerStore; use crate::test_tools::TestLogger; use super::*; @@ -459,14 +454,10 @@ mod tests { None, ) .unwrap(); - let stake_store = Arc::new(StakePoolStore::new( - Arc::new(main_db_connection().unwrap()), - None, - )); - let protocol_initializer_store = Arc::new(ProtocolInitializerStore::new( - Box::new(DumbStoreAdapter::new()), - None, - )); + let connection = Arc::new(main_db_connection().unwrap()); + let stake_store = Arc::new(StakePoolStore::new(connection.clone(), None)); + let protocol_initializer_store = + Arc::new(ProtocolInitializerRepository::new(connection, None)); let service = MithrilEpochService::new( stake_store, protocol_initializer_store, @@ -487,14 +478,11 @@ mod tests { .to_owned(); let epoch = Epoch(12); let signers = fixtures.signers(); - let stake_store = Arc::new(StakePoolStore::new( - Arc::new(main_db_connection().unwrap()), - None, - )); - let protocol_initializer_store = Arc::new(ProtocolInitializerStore::new( - Box::new(DumbStoreAdapter::new()), - None, - )); + + let connection = Arc::new(main_db_connection().unwrap()); + let stake_store = Arc::new(StakePoolStore::new(connection.clone(), None)); + let protocol_initializer_store = + Arc::new(ProtocolInitializerRepository::new(connection, None)); let epoch_settings = SignerEpochSettings { epoch, @@ -630,18 +618,14 @@ mod tests { .collect(); // Init stores - let stake_store = Arc::new(StakePoolStore::new( - Arc::new(main_db_connection().unwrap()), - None, - )); + let connection = Arc::new(main_db_connection().unwrap()); + let stake_store = Arc::new(StakePoolStore::new(connection.clone(), None)); stake_store .save_stakes(epoch, stake_distribution.clone()) .await .expect("save_stakes should not fail"); - let protocol_initializer_store = Arc::new(ProtocolInitializerStore::new( - Box::new(DumbStoreAdapter::new()), - None, - )); + let protocol_initializer_store = + Arc::new(ProtocolInitializerRepository::new(connection, None)); // Build service and register epoch settings let service = MithrilEpochService::new( @@ -667,14 +651,10 @@ mod tests { let signers = fake_data::signers(10); // Init stores - let stake_store = Arc::new(StakePoolStore::new( - Arc::new(main_db_connection().unwrap()), - None, - )); - let protocol_initializer_store = Arc::new(ProtocolInitializerStore::new( - Box::new(DumbStoreAdapter::new()), - None, - )); + let connection = Arc::new(main_db_connection().unwrap()); + let stake_store = Arc::new(StakePoolStore::new(connection.clone(), None)); + let protocol_initializer_store = + Arc::new(ProtocolInitializerRepository::new(connection, None)); // Epoch settings let epoch_settings = SignerEpochSettings { @@ -761,11 +741,9 @@ mod tests { let stake_distribution: StakeDistribution = build_stake_distribution(&signers, 100); let next_stake_distribution: StakeDistribution = build_stake_distribution(&signers, 500); + let connection = Arc::new(main_db_connection().unwrap()); let stake_store = { - let store = Arc::new(StakePoolStore::new( - Arc::new(main_db_connection().unwrap()), - None, - )); + let store = Arc::new(StakePoolStore::new(connection.clone(), None)); store .save_stakes( epoch.offset_to_signer_retrieval_epoch().unwrap(), @@ -782,10 +760,8 @@ mod tests { .unwrap(); store }; - let protocol_initializer_store = Arc::new(ProtocolInitializerStore::new( - Box::new(DumbStoreAdapter::new()), - None, - )); + let protocol_initializer_store = + Arc::new(ProtocolInitializerRepository::new(connection, None)); // Epoch settings let epoch_settings = SignerEpochSettings { @@ -833,20 +809,17 @@ mod tests { async fn test_protocol_initializer_is_available_after_register_epoch_settings_call_if_in_store() { let epoch = Epoch(12); - let stake_store = Arc::new(StakePoolStore::new( - Arc::new(main_db_connection().unwrap()), - None, - )); - let protocol_initializer_store = Arc::new(ProtocolInitializerStore::new( - Box::new( - MemoryAdapter::new(Some(vec![( - epoch.offset_to_signer_retrieval_epoch().unwrap(), - fake_data::protocol_initializer("seed", 1245), - )])) - .unwrap(), - ), - None, - )); + let connection = Arc::new(main_db_connection().unwrap()); + let stake_store = Arc::new(StakePoolStore::new(connection.clone(), None)); + let protocol_initializer_store = + Arc::new(ProtocolInitializerRepository::new(connection, None)); + protocol_initializer_store + .save_protocol_initializer( + epoch.offset_to_signer_retrieval_epoch().unwrap(), + fake_data::protocol_initializer("seed", 1245), + ) + .await + .unwrap(); let mut service = MithrilEpochService::new( stake_store, @@ -871,14 +844,10 @@ mod tests { #[tokio::test] async fn is_source_of_signed_entity_config() { - let stake_store = Arc::new(StakePoolStore::new( - Arc::new(main_db_connection().unwrap()), - None, - )); - let protocol_initializer_store = Arc::new(ProtocolInitializerStore::new( - Box::new(DumbStoreAdapter::new()), - None, - )); + let connection = Arc::new(main_db_connection().unwrap()); + let stake_store = Arc::new(StakePoolStore::new(connection.clone(), None)); + let protocol_initializer_store = + Arc::new(ProtocolInitializerRepository::new(connection, None)); let epoch_service = Arc::new(RwLock::new(MithrilEpochService::new( stake_store, protocol_initializer_store, diff --git a/mithril-signer/src/services/single_signer.rs b/mithril-signer/src/services/single_signer.rs index 92c7e2db3fe..418e09ce0a1 100644 --- a/mithril-signer/src/services/single_signer.rs +++ b/mithril-signer/src/services/single_signer.rs @@ -172,15 +172,13 @@ mod tests { use std::sync::Arc; use tokio::sync::RwLock; - use crate::database::repository::StakePoolStore; + use crate::database::repository::{ProtocolInitializerRepository, StakePoolStore}; use crate::database::test_helper::main_db_connection; use crate::services::MithrilEpochService; - use crate::store::ProtocolInitializerStore; use crate::test_tools::TestLogger; use mithril_common::crypto_helper::ProtocolClerk; use mithril_common::entities::{Epoch, ProtocolMessagePartKey}; use mithril_common::test_utils::MithrilFixtureBuilder; - use mithril_persistence::store::adapter::DumbStoreAdapter; use mithril_persistence::store::StakeStorer; use super::*; @@ -193,11 +191,9 @@ mod tests { let clerk = ProtocolClerk::from_signer(¤t_signer.protocol_signer); let avk = clerk.compute_avk(); let logger = TestLogger::stdout(); + let connection = Arc::new(main_db_connection().unwrap()); let stake_store = { - let store = Arc::new(StakePoolStore::new( - Arc::new(main_db_connection().unwrap()), - None, - )); + let store = Arc::new(StakePoolStore::new(connection.clone(), None)); store .save_stakes( Epoch(10).offset_to_signer_retrieval_epoch().unwrap(), @@ -207,10 +203,8 @@ mod tests { .unwrap(); store }; - let protocol_initializer_store = Arc::new(ProtocolInitializerStore::new( - Box::new(DumbStoreAdapter::new()), - None, - )); + let protocol_initializer_store = + Arc::new(ProtocolInitializerRepository::new(connection, None)); let epoch_service = MithrilEpochService::new(stake_store, protocol_initializer_store, logger.clone()) .set_data_to_default_or_fake(Epoch(10)) diff --git a/mithril-signer/src/store/protocol_initializer_store.rs b/mithril-signer/src/store/protocol_initializer_store.rs index a76fe31e564..139e15d8e93 100644 --- a/mithril-signer/src/store/protocol_initializer_store.rs +++ b/mithril-signer/src/store/protocol_initializer_store.rs @@ -1,12 +1,6 @@ use async_trait::async_trait; -use tokio::sync::RwLock; use mithril_common::{crypto_helper::ProtocolInitializer, entities::Epoch, StdResult}; -use mithril_persistence::store::{adapter::StoreAdapter, StorePruner}; - -use crate::services::EpochPruningTask; - -type Adapter = Box>; #[cfg_attr(test, mockall::automock)] #[async_trait] @@ -32,188 +26,3 @@ pub trait ProtocolInitializerStorer: Sync + Send { last: usize, ) -> StdResult>; } -/// Implementation of the ProtocolInitializerStorer -pub struct ProtocolInitializerStore { - adapter: RwLock, - retention_limit: Option, -} - -impl ProtocolInitializerStore { - /// Create a new ProtocolInitializerStore. - pub fn new(adapter: Adapter, retention_limit: Option) -> Self { - Self { - adapter: RwLock::new(adapter), - retention_limit, - } - } -} - -#[async_trait] -impl EpochPruningTask for ProtocolInitializerStore { - fn pruned_data(&self) -> &'static str { - "Protocol initializer" - } - - async fn prune(&self, _epoch: Epoch) -> StdResult<()> { - mithril_persistence::store::StorePruner::prune(self).await - } -} - -#[async_trait] -impl StorePruner for ProtocolInitializerStore { - type Key = Epoch; - type Record = ProtocolInitializer; - - fn get_adapter( - &self, - ) -> &RwLock>> { - &self.adapter - } - - fn get_max_records(&self) -> Option { - self.retention_limit - } -} - -#[async_trait] -impl ProtocolInitializerStorer for ProtocolInitializerStore { - async fn save_protocol_initializer( - &self, - epoch: Epoch, - protocol_initializer: ProtocolInitializer, - ) -> StdResult> { - let previous_protocol_initializer = self.adapter.read().await.get_record(&epoch).await?; - self.adapter - .write() - .await - .store_record(&epoch, &protocol_initializer) - .await?; - - Ok(previous_protocol_initializer) - } - - async fn get_protocol_initializer( - &self, - epoch: Epoch, - ) -> StdResult> { - let record = self.adapter.read().await.get_record(&epoch).await?; - Ok(record) - } - - async fn get_last_protocol_initializer( - &self, - last: usize, - ) -> StdResult> { - let records = self.adapter.read().await.get_last_n_records(last).await?; - - Ok(records) - } -} - -#[cfg(test)] -mod tests { - use mithril_common::test_utils::fake_data; - use mithril_persistence::store::adapter::MemoryAdapter; - - use super::*; - - fn setup_protocol_initializers(nb_epoch: u64) -> Vec<(Epoch, ProtocolInitializer)> { - let mut values: Vec<(Epoch, ProtocolInitializer)> = Vec::new(); - for epoch in 1..=nb_epoch { - let stake = (epoch + 1) * 100; - let protocol_initializer = fake_data::protocol_initializer("1", stake); - values.push((Epoch(epoch), protocol_initializer)); - } - values - } - - fn init_store(nb_epoch: u64, retention_limit: Option) -> ProtocolInitializerStore { - let values = setup_protocol_initializers(nb_epoch); - - let values = if !values.is_empty() { - Some(values) - } else { - None - }; - let adapter: MemoryAdapter = - MemoryAdapter::new(values).unwrap(); - ProtocolInitializerStore::new(Box::new(adapter), retention_limit) - } - - #[tokio::test] - async fn save_key_in_empty_store() { - let protocol_initializers = setup_protocol_initializers(1); - let store = init_store(0, None); - let res = store - .save_protocol_initializer( - protocol_initializers[0].0, - protocol_initializers[0].1.clone(), - ) - .await - .unwrap(); - - assert!(res.is_none()); - } - - #[tokio::test] - async fn update_protocol_initializer_in_store() { - let protocol_initializers = setup_protocol_initializers(2); - let store = init_store(1, None); - let res = store - .save_protocol_initializer( - protocol_initializers[0].0, - protocol_initializers[1].1.clone(), - ) - .await - .unwrap(); - - assert!(res.is_some()); - assert_eq!( - protocol_initializers[0].1.get_stake(), - res.unwrap().get_stake() - ); - } - - #[tokio::test] - async fn get_protocol_initializer_for_empty_epoch() { - let store = init_store(2, None); - let res = store.get_protocol_initializer(Epoch(0)).await.unwrap(); - - assert!(res.is_none()); - } - - #[tokio::test] - async fn get_protocol_initializer_for_existing_epoch() { - let store = init_store(2, None); - let res = store.get_protocol_initializer(Epoch(1)).await.unwrap(); - - assert!(res.is_some()); - } - - #[tokio::test] - async fn check_retention_limit() { - let store = init_store(3, Some(2)); - let _protocol_initializers = setup_protocol_initializers(1); - - assert!(store - .get_protocol_initializer(Epoch(1)) - .await - .unwrap() - .is_some()); - - // Whatever the epoch, it's the retention limit that matters. - EpochPruningTask::prune(&store, Epoch(99)).await.unwrap(); - - assert!(store - .get_protocol_initializer(Epoch(1)) - .await - .unwrap() - .is_none()); - - assert!(store - .get_protocol_initializer(Epoch(2)) - .await - .unwrap() - .is_some()); - } -} diff --git a/mithril-signer/tests/test_extensions/state_machine_tester.rs b/mithril-signer/tests/test_extensions/state_machine_tester.rs index e28fe9288ea..6df5441ad05 100644 --- a/mithril-signer/tests/test_extensions/state_machine_tester.rs +++ b/mithril-signer/tests/test_extensions/state_machine_tester.rs @@ -453,7 +453,7 @@ impl StateMachineTester { .map_err(TestError::SubsystemError)?; self.assert(maybe_protocol_initializer.is_some(), format!( - "there should be a protocol intializer in store for Epoch {}, here is the last 3 in store: {:?}", + "there should be a protocol initializer in store for Epoch {}, here is the last 3 in store: {:?}", epoch, self.protocol_initializer_store .get_last_protocol_initializer(2)