diff --git a/masq_lib/src/constants.rs b/masq_lib/src/constants.rs index 155bff5cc..bcd1b94c7 100644 --- a/masq_lib/src/constants.rs +++ b/masq_lib/src/constants.rs @@ -5,7 +5,7 @@ use crate::data_version::DataVersion; use const_format::concatcp; pub const DEFAULT_CHAIN: Chain = Chain::PolyMainnet; -pub const CURRENT_SCHEMA_VERSION: usize = 10; +pub const CURRENT_SCHEMA_VERSION: usize = 11; pub const HIGHEST_RANDOM_CLANDESTINE_PORT: u16 = 9999; pub const HTTP_PORT: u16 = 80; diff --git a/node/src/database/db_initializer.rs b/node/src/database/db_initializer.rs index be5547576..0f9685c2b 100644 --- a/node/src/database/db_initializer.rs +++ b/node/src/database/db_initializer.rs @@ -135,6 +135,7 @@ impl DbInitializerReal { Self::create_config_table(conn); Self::initialize_config(conn, external_params); Self::create_payable_table(conn); + Self::create_sent_payable_table(conn); Self::create_pending_payable_table(conn); Self::create_receivable_table(conn); Self::create_banned_table(conn); @@ -258,6 +259,31 @@ impl DbInitializerReal { Self::set_config_value(conn, "max_block_count", None, false, "maximum block count"); } + pub fn create_sent_payable_table(conn: &Connection) { + conn.execute( + "create table if not exists sent_payable ( + rowid integer primary key, + tx_hash text not null, + receiver_address text not null, + amount_high_b integer not null, + amount_low_b integer not null, + timestamp integer not null, + gas_price_wei integer not null, + nonce integer not null, + status text not null, + retried integer not null + )", + [], + ) + .expect("Can't create sent_payable table"); + + conn.execute( + "CREATE UNIQUE INDEX sent_payable_tx_hash_idx ON sent_payable (tx_hash)", + [], + ) + .expect("Can't create transaction hash index in sent payments"); + } + pub fn create_pending_payable_table(conn: &Connection) { conn.execute( "create table if not exists pending_payable ( @@ -621,6 +647,7 @@ impl Debug for DbInitializationConfig { mod tests { use super::*; use crate::database::db_initializer::InitializationError::SqliteError; + use crate::database::test_utils::SQL_ATTRIBUTES_FOR_CREATING_SENT_PAYABLE; use crate::db_config::config_dao::{ConfigDao, ConfigDaoReal}; use crate::test_utils::database_utils::{ assert_create_table_stm_contains_all_parts, @@ -652,7 +679,7 @@ mod tests { #[test] fn constants_have_correct_values() { assert_eq!(DATABASE_FILE, "node-data.db"); - assert_eq!(CURRENT_SCHEMA_VERSION, 10); + assert_eq!(CURRENT_SCHEMA_VERSION, 11); } #[test] @@ -713,6 +740,34 @@ mod tests { ) } + #[test] + fn db_initialize_creates_sent_payable_table() { + let home_dir = ensure_node_home_directory_does_not_exist( + "db_initializer", + "db_initialize_creates_sent_payable_table", + ); + let subject = DbInitializerReal::default(); + + let conn = subject + .initialize(&home_dir, DbInitializationConfig::test_default()) + .unwrap(); + + let mut stmt = conn.prepare("select rowid, tx_hash, receiver_address, amount_high_b, amount_low_b, timestamp, gas_price_wei, nonce, status, retried from sent_payable").unwrap(); + let mut sent_payable_contents = stmt.query_map([], |_| Ok(42)).unwrap(); + assert!(sent_payable_contents.next().is_none()); + assert_create_table_stm_contains_all_parts( + &*conn, + "sent_payable", + SQL_ATTRIBUTES_FOR_CREATING_SENT_PAYABLE, + ); + let expected_key_words: &[&[&str]] = &[&["tx_hash"]]; + assert_index_stm_is_coupled_with_right_parameter( + conn.as_ref(), + "sent_payable_tx_hash_idx", + expected_key_words, + ) + } + #[test] fn db_initialize_creates_payable_table() { let home_dir = ensure_node_home_directory_does_not_exist( diff --git a/node/src/database/db_migrations/db_migrator.rs b/node/src/database/db_migrations/db_migrator.rs index 7d1ec4f8c..369a78788 100644 --- a/node/src/database/db_migrations/db_migrator.rs +++ b/node/src/database/db_migrations/db_migrator.rs @@ -2,6 +2,7 @@ use crate::database::db_initializer::ExternalData; use crate::database::db_migrations::migrations::migration_0_to_1::Migrate_0_to_1; +use crate::database::db_migrations::migrations::migration_10_to_11::Migrate_10_to_11; use crate::database::db_migrations::migrations::migration_1_to_2::Migrate_1_to_2; use crate::database::db_migrations::migrations::migration_2_to_3::Migrate_2_to_3; use crate::database::db_migrations::migrations::migration_3_to_4::Migrate_3_to_4; @@ -80,6 +81,7 @@ impl DbMigratorReal { &Migrate_7_to_8, &Migrate_8_to_9, &Migrate_9_to_10, + &Migrate_10_to_11, ] } diff --git a/node/src/database/db_migrations/migrations/migration_10_to_11.rs b/node/src/database/db_migrations/migrations/migration_10_to_11.rs new file mode 100644 index 000000000..4b683208d --- /dev/null +++ b/node/src/database/db_migrations/migrations/migration_10_to_11.rs @@ -0,0 +1,85 @@ +use crate::database::db_migrations::db_migrator::DatabaseMigration; +use crate::database::db_migrations::migrator_utils::DBMigDeclarator; + +#[allow(non_camel_case_types)] +pub struct Migrate_10_to_11; + +impl DatabaseMigration for Migrate_10_to_11 { + fn migrate<'a>( + &self, + declaration_utils: Box, + ) -> rusqlite::Result<()> { + let sql_statement = "create table if not exists sent_payable ( + rowid integer primary key, + tx_hash text not null, + receiver_address text not null, + amount_high_b integer not null, + amount_low_b integer not null, + timestamp integer not null, + gas_price_wei integer not null, + nonce integer not null, + status text not null, + retried integer not null + )"; + + declaration_utils.execute_upon_transaction(&[&sql_statement]) + } + + fn old_version(&self) -> usize { + 10 + } +} + +#[cfg(test)] +mod tests { + use crate::database::db_initializer::{ + DbInitializationConfig, DbInitializer, DbInitializerReal, DATABASE_FILE, + }; + use crate::database::test_utils::SQL_ATTRIBUTES_FOR_CREATING_SENT_PAYABLE; + use crate::test_utils::database_utils::{ + assert_create_table_stm_contains_all_parts, assert_table_exists, + bring_db_0_back_to_life_and_return_connection, make_external_data, + }; + use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + use masq_lib::test_utils::utils::ensure_node_home_directory_exists; + use std::fs::create_dir_all; + + #[test] + fn migration_from_10_to_11_is_applied_correctly() { + init_test_logging(); + let dir_path = ensure_node_home_directory_exists( + "db_migrations", + "migration_from_10_to_11_is_properly_set", + ); + create_dir_all(&dir_path).unwrap(); + let db_path = dir_path.join(DATABASE_FILE); + let _ = bring_db_0_back_to_life_and_return_connection(&db_path); + let subject = DbInitializerReal::default(); + + let _prev_connection = subject + .initialize_to_version( + &dir_path, + 10, + DbInitializationConfig::create_or_migrate(make_external_data()), + ) + .unwrap(); + + let connection = subject + .initialize_to_version( + &dir_path, + 11, + DbInitializationConfig::create_or_migrate(make_external_data()), + ) + .unwrap(); + + assert_table_exists(connection.as_ref(), "sent_payable"); + assert_create_table_stm_contains_all_parts( + &*connection, + "sent_payable", + SQL_ATTRIBUTES_FOR_CREATING_SENT_PAYABLE, + ); + TestLogHandler::new().assert_logs_contain_in_order(vec![ + "DbMigrator: Database successfully migrated from version 10 to 11", + ]); + } +} diff --git a/node/src/database/db_migrations/migrations/migration_8_to_9.rs b/node/src/database/db_migrations/migrations/migration_8_to_9.rs index 4bf95e955..eb89ac002 100644 --- a/node/src/database/db_migrations/migrations/migration_8_to_9.rs +++ b/node/src/database/db_migrations/migrations/migration_8_to_9.rs @@ -43,21 +43,22 @@ mod tests { let _ = bring_db_0_back_to_life_and_return_connection(&db_path); let subject = DbInitializerReal::default(); - let result = subject.initialize_to_version( - &dir_path, - 8, - DbInitializationConfig::create_or_migrate(make_external_data()), - ); - - assert!(result.is_ok()); + let _prev_connection = subject + .initialize_to_version( + &dir_path, + 8, + DbInitializationConfig::create_or_migrate(make_external_data()), + ) + .unwrap(); - let result = subject.initialize_to_version( - &dir_path, - 9, - DbInitializationConfig::create_or_migrate(make_external_data()), - ); + let connection = subject + .initialize_to_version( + &dir_path, + 9, + DbInitializationConfig::create_or_migrate(make_external_data()), + ) + .unwrap(); - let connection = result.unwrap(); let (mp_value, mp_encrypted) = retrieve_config_row(connection.as_ref(), "max_block_count"); let (cs_value, cs_encrypted) = retrieve_config_row(connection.as_ref(), "schema_version"); assert_eq!(mp_value, None); diff --git a/node/src/database/db_migrations/migrations/migration_9_to_10.rs b/node/src/database/db_migrations/migrations/migration_9_to_10.rs index 7622ef01f..be240429a 100644 --- a/node/src/database/db_migrations/migrations/migration_9_to_10.rs +++ b/node/src/database/db_migrations/migrations/migration_9_to_10.rs @@ -43,21 +43,22 @@ mod tests { let _ = bring_db_0_back_to_life_and_return_connection(&db_path); let subject = DbInitializerReal::default(); - let result = subject.initialize_to_version( - &dir_path, - 9, - DbInitializationConfig::create_or_migrate(make_external_data()), - ); - - assert!(result.is_ok()); + let _prev_connection = subject + .initialize_to_version( + &dir_path, + 9, + DbInitializationConfig::create_or_migrate(make_external_data()), + ) + .unwrap(); - let result = subject.initialize_to_version( - &dir_path, - 10, - DbInitializationConfig::create_or_migrate(make_external_data()), - ); + let connection = subject + .initialize_to_version( + &dir_path, + 10, + DbInitializationConfig::create_or_migrate(make_external_data()), + ) + .unwrap(); - let connection = result.unwrap(); let (mp_value, mp_encrypted) = retrieve_config_row(connection.as_ref(), "max_block_count"); let (cs_value, cs_encrypted) = retrieve_config_row(connection.as_ref(), "schema_version"); assert_eq!(mp_value, Some(100_000u64.to_string())); diff --git a/node/src/database/db_migrations/migrations/mod.rs b/node/src/database/db_migrations/migrations/mod.rs index bcdb14176..e093df006 100644 --- a/node/src/database/db_migrations/migrations/mod.rs +++ b/node/src/database/db_migrations/migrations/mod.rs @@ -10,3 +10,5 @@ pub mod migration_6_to_7; pub mod migration_7_to_8; pub mod migration_8_to_9; pub mod migration_9_to_10; +#[rustfmt::skip] +pub mod migration_10_to_11; diff --git a/node/src/database/test_utils/mod.rs b/node/src/database/test_utils/mod.rs index e8b9060f1..a9c5fac77 100644 --- a/node/src/database/test_utils/mod.rs +++ b/node/src/database/test_utils/mod.rs @@ -12,6 +12,19 @@ use std::fmt::Debug; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; +pub const SQL_ATTRIBUTES_FOR_CREATING_SENT_PAYABLE: &[&[&str]] = &[ + &["rowid", "integer", "primary", "key"], + &["tx_hash", "text", "not", "null"], + &["receiver_address", "text", "not", "null"], + &["amount_high_b", "integer", "not", "null"], + &["amount_low_b", "integer", "not", "null"], + &["timestamp", "integer", "not", "null"], + &["gas_price_wei", "integer", "not", "null"], + &["nonce", "integer", "not", "null"], + &["status", "text", "not", "null"], + &["retried", "integer", "not", "null"], +]; + #[derive(Debug, Default)] pub struct ConnectionWrapperMock<'conn> { prepare_params: Arc>>, @@ -114,3 +127,27 @@ impl DbInitializerMock { self } } + +#[cfg(test)] +mod tests { + use crate::database::test_utils::SQL_ATTRIBUTES_FOR_CREATING_SENT_PAYABLE; + + #[test] + fn constants_have_correct_values() { + assert_eq!( + SQL_ATTRIBUTES_FOR_CREATING_SENT_PAYABLE, + &[ + &["rowid", "integer", "primary", "key"], + &["tx_hash", "text", "not", "null"], + &["receiver_address", "text", "not", "null"], + &["amount_high_b", "integer", "not", "null"], + &["amount_low_b", "integer", "not", "null"], + &["timestamp", "integer", "not", "null"], + &["gas_price_wei", "integer", "not", "null"], + &["nonce", "integer", "not", "null"], + &["status", "text", "not", "null"], + &["retried", "integer", "not", "null"] + ] + ); + } +} diff --git a/node/src/test_utils/database_utils.rs b/node/src/test_utils/database_utils.rs index 02ba441a4..fb8ba3a83 100644 --- a/node/src/test_utils/database_utils.rs +++ b/node/src/test_utils/database_utils.rs @@ -103,6 +103,11 @@ pub fn retrieve_config_row(conn: &dyn ConnectionWrapper, name: &str) -> (Option< }) } +pub fn assert_table_exists(conn: &dyn ConnectionWrapper, table_name: &str) { + let result = conn.prepare(&format!("select * from {}", table_name)); + assert!(result.is_ok(), "Table {} should exist", table_name); +} + pub fn assert_table_does_not_exist(conn: &dyn ConnectionWrapper, table_name: &str) { let error_stm = conn .prepare(&format!("select * from {}", table_name))