Skip to content

Commit

Permalink
check version regression
Browse files Browse the repository at this point in the history
  • Loading branch information
ghubertpalo committed Nov 16, 2022
1 parent ebf42a7 commit e17517a
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 101 deletions.
1 change: 0 additions & 1 deletion mithril-aggregator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ flate2 = "1.0.23"
hex = "0.4.3"
mithril-common = { path = "../mithril-common" }
reqwest = { version = "0.11", features = ["json"] }
semver = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.9.10"
Expand Down
64 changes: 14 additions & 50 deletions mithril-aggregator/src/command_args.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
use chrono::Local;
use clap::{Parser, Subcommand};
use config::{builder::DefaultState, ConfigBuilder, Map, Source, Value, ValueKind};
use semver::{Version, VersionReq};
use slog::Level;
use slog_scope::{debug, warn};
use sqlite::Connection;
use slog_scope::debug;
use std::error::Error;
use std::fs;
use std::path::PathBuf;
Expand All @@ -15,9 +12,7 @@ use tokio::time::Duration;
use mithril_common::certificate_chain::MithrilCertificateVerifier;
use mithril_common::chain_observer::{CardanoCliRunner, ChainObserver};
use mithril_common::crypto_helper::ProtocolGenesisVerifier;
use mithril_common::database::{
ApplicationNodeType, ApplicationVersion, VersionProvider, VersionUpdaterProvider,
};
use mithril_common::database::{ApplicationNodeType, ApplicationVersionChecker};
use mithril_common::digesters::{CardanoImmutableDigester, ImmutableFileSystemObserver};
use mithril_common::entities::{Epoch, HexEncodedGenesisSecretKey};
use mithril_common::store::adapter::SQLiteAdapter;
Expand All @@ -38,52 +33,16 @@ use crate::{
ProtocolParametersStorer, SingleSignatureStore, VerificationKeyStore,
};

fn check_database_version(filepath: &Option<PathBuf>) -> Result<(), Box<dyn Error>> {
let connection = match filepath {
Some(file) => Connection::open(file)?,
None => Connection::open(":memory:")?,
};
let provider = VersionProvider::new(&connection);
provider.create_table_if_not_exists()?;
let application_type = ApplicationNodeType::new("aggregator")?;
let maybe_option = provider.get_application_version(&application_type)?;
let current_version = ApplicationVersion {
semver: Version::parse(env!("CARGO_PKG_VERSION"))?,
application_type,
updated_at: Local::now().naive_local(),
};

match maybe_option {
None => {
let provider = VersionUpdaterProvider::new(&connection);
let _ = provider.save(current_version)?;
debug!("application version saved in database");
}
Some(version) => {
let req = VersionReq::parse(&current_version.semver.to_string()).unwrap();

if !req.matches(&version.semver) {
warn!(
"application version '{}' is out of date, new version is '{}'. Upgrading database…",
version.semver, current_version.semver
);
let upgrader_provider = VersionUpdaterProvider::new(&connection);
upgrader_provider.save(current_version)?;
debug!("database updated");
} else {
debug!("database up to date");
}
}
};

Ok(())
}

fn setup_genesis_dependencies(
config: &GenesisConfiguration,
) -> Result<GenesisToolsDependency, Box<dyn std::error::Error>> {
let sqlite_db_path = Some(config.get_sqlite_file());
check_database_version(&sqlite_db_path)?;
ApplicationVersionChecker::new(
slog_scope::logger(),
ApplicationNodeType::Aggregator,
config.get_sqlite_file(),
)
.check(env!("CARGO_PKG_VERSION"))?;

let chain_observer = Arc::new(
mithril_common::chain_observer::CardanoCliChainObserver::new(Box::new(
Expand Down Expand Up @@ -345,7 +304,12 @@ impl ServeCommand {
.map_err(|e| format!("configuration deserialize error: {}", e))?;
debug!("SERVE command"; "config" => format!("{:?}", config));
let sqlite_db_path = Some(config.get_sqlite_file());
check_database_version(&sqlite_db_path)?;
ApplicationVersionChecker::new(
slog_scope::logger(),
ApplicationNodeType::Aggregator,
config.get_sqlite_file(),
)
.check(env!("CARGO_PKG_VERSION"))?;

// Init dependencies
let snapshot_store = config.build_snapshot_store()?;
Expand Down
118 changes: 116 additions & 2 deletions mithril-common/src/database/db_version.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::{collections::HashMap, error::Error, fmt::Display};
use std::{cmp::Ordering, collections::HashMap, error::Error, fmt::Display, path::PathBuf};

use chrono::NaiveDateTime;
use chrono::{Local, NaiveDateTime};
use semver::Version;
use slog::{debug, error, warn, Logger};
use sqlite::{Connection, Row, Value};

use crate::sqlite::{HydrationError, Projection, ProjectionField, Provider, SqLiteEntity};
Expand Down Expand Up @@ -232,6 +233,87 @@ returning {projection}
}
}

/// Struct to perform application version check in the database.
#[derive(Debug)]
pub struct ApplicationVersionChecker {
/// Pathbuf to the SQLite3 file.
sqlite_file_path: PathBuf,

/// Application type which vesion is verified.
application_type: ApplicationNodeType,

/// logger
logger: Logger,
}

impl ApplicationVersionChecker {
/// constructor
pub fn new(
logger: Logger,
application_type: ApplicationNodeType,
sqlite_file_path: PathBuf,
) -> Self {
Self {
sqlite_file_path,
application_type,
logger,
}
}

/// Performs an actual version check in the database. This method creates a
/// connection to the SQLite3 file and drops it at the end.
pub fn check(&self, current_semver: &str) -> Result<(), Box<dyn Error>> {
debug!(
&self.logger,
"check application version, database file = '{}'",
self.sqlite_file_path.display()
);
let connection = Connection::open(&self.sqlite_file_path)?;
let provider = VersionProvider::new(&connection);
provider.create_table_if_not_exists()?;
let updater = VersionUpdaterProvider::new(&connection);
let maybe_option = provider.get_application_version(&self.application_type)?;
let current_version = ApplicationVersion {
semver: Version::parse(current_semver)?,
application_type: self.application_type.clone(),
updated_at: Local::now().naive_local(),
};

match maybe_option {
None => {
let _ = updater.save(current_version)?;
debug!(&self.logger, "Application version saved in database.");
}
Some(version) => match current_version.semver.cmp(&version.semver) {
Ordering::Greater => {
warn!(
&self.logger,
"Application version '{}' is out of date, new version is '{}'. Upgrading database…",
version.semver, current_version.semver
);
updater.save(current_version)?;
debug!(&self.logger, "database updated");
}
Ordering::Less => {
error!(
&self.logger,
"Software version '{}' is older than database structure version '{}'.",
current_version.semver,
version.semver
);

Err("This software version is older than the database structure. Aborting launch to prevent possible data corruption.")?;
}
Ordering::Equal => {
debug!(&self.logger, "database up to date");
}
},
};

Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -278,4 +360,36 @@ returning app_version.semver as semver, app_version.application_type as applicat
provider.get_definition(None)
)
}

fn check_database_version(filepath: &PathBuf, semver: &str) {
let connection = Connection::open(filepath).unwrap();
let provider = VersionProvider::new(&connection);
let version = provider
.get_application_version(&ApplicationNodeType::Aggregator)
.unwrap()
.expect("there should be a version in the database");

assert_eq!(semver, version.semver.to_string());
}

#[test]
fn test_application_version_checker() {
let filepath = std::env::temp_dir().join("test.sqlite3");

if filepath.exists() {
std::fs::remove_file(filepath.as_path()).unwrap();
}
let app_checker = ApplicationVersionChecker::new(
slog_scope::logger(),
ApplicationNodeType::Aggregator,
filepath.clone(),
);
let _ = app_checker.check("1.0.0").unwrap();
check_database_version(&filepath, "1.0.0");
let _ = app_checker.check("1.0.0").unwrap();
check_database_version(&filepath, "1.0.0");
let _ = app_checker.check("1.1.0").unwrap();
check_database_version(&filepath, "1.1.0");
let _ = app_checker.check("1.0.1").unwrap_err();
}
}
3 changes: 0 additions & 3 deletions mithril-signer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,19 @@ repository = { workspace = true }

[dependencies]
async-trait = "0.1.52"
chrono = "0.4"
clap = { version = "4.0", features = ["derive", "env"] }
config = "0.13.1"
hex = "0.4.3"
mithril-common = { path = "../mithril-common" }
rand_chacha = "0.3.1"
rand_core = "0.6.3"
reqwest = { version = "0.11", features = ["json", "stream"] }
semver = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
slog = { version = "2.7.0", features = ["max_level_trace", "release_max_level_debug"] }
slog-async = "2.7.0"
slog-bunyan = "2.4.0"
slog-scope = "4.4.0"
sqlite = "0.28.1"
thiserror = "1.0.31"
tokio = { version = "1.17.0", features = ["full"] }

Expand Down
53 changes: 8 additions & 45 deletions mithril-signer/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
use chrono::Local;
use clap::Parser;
use semver::{Version, VersionReq};
use slog::{o, Drain, Level, Logger};
use slog_scope::{debug, warn};
use sqlite::Connection;
use slog_scope::debug;
use std::sync::Arc;
use std::time::Duration;
use std::{error::Error, path::PathBuf};

use mithril_common::database::{
ApplicationNodeType, ApplicationVersion, VersionProvider, VersionUpdaterProvider,
};
use mithril_common::database::{ApplicationNodeType, ApplicationVersionChecker};
use mithril_signer::{
Config, ProductionServiceBuilder, ServiceBuilder, SignerRunner, SignerState, StateMachine,
};
Expand Down Expand Up @@ -66,43 +61,6 @@ fn build_logger(min_level: Level) -> Logger {
Logger::root(Arc::new(drain), o!())
}

fn check_database(filepath: &PathBuf) -> Result<(), Box<dyn Error>> {
let connection = Connection::open(filepath)?;
let provider = VersionProvider::new(&connection);
provider.create_table_if_not_exists()?;
let application_type = ApplicationNodeType::new("aggregator")?;
let maybe_option = provider.get_application_version(&application_type)?;
let current_version = ApplicationVersion {
semver: Version::parse(env!("CARGO_PKG_VERSION"))?,
application_type,
updated_at: Local::now().naive_local(),
};

match maybe_option {
None => {
let provider = VersionUpdaterProvider::new(&connection);
let _ = provider.save(current_version)?;
debug!("application version saved in database");
}
Some(version) => {
let req = VersionReq::parse(&current_version.semver.to_string()).unwrap();

if !req.matches(&version.semver) {
warn!(
"application version '{}' is out of date, new version is '{}'. Upgrading database…",
version.semver, current_version.semver
);
let upgrader_provider = VersionUpdaterProvider::new(&connection);
upgrader_provider.save(current_version)?;
debug!("database updated");
} else {
debug!("database up to date");
}
}
};
Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Load args
Expand All @@ -125,7 +83,12 @@ async fn main() -> Result<(), Box<dyn Error>> {
.try_deserialize()
.map_err(|e| format!("configuration deserialize error: {}", e))?;
let services = ProductionServiceBuilder::new(&config).build()?;
check_database(&config.data_stores_directory)?;
ApplicationVersionChecker::new(
slog_scope::logger(),
ApplicationNodeType::Signer,
config.get_sqlite_file(),
)
.check(env!("CARGO_PKG_VERSION"))?;
debug!("Started"; "run_mode" => &args.run_mode, "config" => format!("{:?}", config));

let mut state_machine = StateMachine::new(
Expand Down

0 comments on commit e17517a

Please sign in to comment.