From cedb1b3abda12866d384c444fe6a48ab54d5eb24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Wed, 23 Aug 2023 20:33:43 +0200 Subject: [PATCH 01/52] begining with test server_initializer_collected_params_combine_vlcs_properly --- .../node_configurator_standard.rs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 1094f7f18..1fbca988f 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -737,6 +737,47 @@ mod tests { assert_eq!(config.crash_point, CrashPoint::Panic); } + #[test] + fn server_initializer_collected_params_combine_vlcs_properly () { + running_test(); + let home_dir = PathBuf::from("/unexisting_home/unexisting_alice"); + let data_dir = home_dir.join("data_dir"); + vec![ + ("MASQ_BLOCKCHAIN_SERVICE_URL", "https://example3.com"), + ("MASQ_CHAIN", TEST_DEFAULT_CHAIN.rec().literal_identifier), + ("MASQ_CLANDESTINE_PORT", "1234"), + ("MASQ_CONSUMING_PRIVATE_KEY", "0011223344556677001122334455667700112233445566770011223344556677"), + ("MASQ_CRASH_POINT", "Error"), + ("MASQ_DATA_DIRECTORY", home_dir.to_str().unwrap()), + ("MASQ_DB_PASSWORD", "password"), + ("MASQ_DNS_SERVERS", "8.8.8.8"), + ("MASQ_EARNING_WALLET", "0x0123456789012345678901234567890123456789"), + ("MASQ_GAS_PRICE", "50"), + ("MASQ_IP", "4.3.2.1"), + ("MASQ_LOG_LEVEL", "error"), + ("MASQ_MAPPING_PROTOCOL", "pmp"), + ("MASQ_MIN_HOPS", "2"), + ("MASQ_NEIGHBORHOOD_MODE", "originate-only"), + ("MASQ_NEIGHBORS", "masq://eth-ropsten:MTIzNDU2Nzg5MTEyMzQ1Njc4OTIxMjM0NTY3ODkzMTI@1.2.3.4:1234,masq://eth-ropsten:MTIzNDU2Nzg5MTEyMzQ1Njc4OTIxMjM0NTY3ODkzMTI@5.6.7.8:5678"), + ("MASQ_PAYMENT_THRESHOLDS","12345|50000|1000|1234|19000|20000"), + ("MASQ_RATE_PACK","1|3|3|8"), + #[cfg(not(target_os = "windows"))] + ("MASQ_REAL_USER", "9999:9999:booga"), + ("MASQ_SCANS", "off"), + ("MASQ_SCAN_INTERVALS","133|133|111") + ].into_iter() + .for_each (|(name, value)| std::env::set_var (name, value)); + let args = ArgsBuilder::new() + .param("--chain", "polygon-mainnet"); + let args_vec: Vec = args.into(); + let dir_wrapper = DirsWrapperMock::new() + .home_dir_result(Some(home_dir)) + .data_dir_result(Some(data_dir)); + let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); + println!("full_multi_config: {:#?}",result.unwrap().multi_config); + + } + #[test] fn server_initializer_collected_params_senses_when_user_specifies_config_file() { running_test(); From 32dd2cbe99aa52a65d3ced0698e2e54f5513a9fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Mon, 28 Aug 2023 18:09:16 +0200 Subject: [PATCH 02/52] removed GatheredParams from server_initializer_collected_params, added bools for data_dir, config_file and real_user to MultiConfig, fixing the test --- .../node_configurator_standard.rs | 175 +++++++++++++----- node/src/server_initializer.rs | 18 +- node/src/test_utils/mod.rs | 2 +- 3 files changed, 140 insertions(+), 55 deletions(-) diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 1fbca988f..227467f83 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -10,7 +10,6 @@ use masq_lib::shared_schema::ConfiguratorError; use masq_lib::utils::NeighborhoodModeLight; use std::net::SocketAddr; use std::net::{IpAddr, Ipv4Addr}; -use std::path::PathBuf; use clap::value_t; use log::LevelFilter; @@ -26,7 +25,6 @@ use crate::node_configurator::unprivileged_parse_args_configuration::{ use crate::node_configurator::{ data_directory_from_context, determine_fundamentals, real_user_data_directory_path_and_chain, }; -use crate::server_initializer::GatheredParams; use crate::sub_lib::cryptde::PublicKey; use crate::sub_lib::cryptde_null::CryptDENull; use crate::sub_lib::utils::make_new_multi_config; @@ -139,10 +137,10 @@ fn collect_externals_from_multi_config( pub fn server_initializer_collected_params<'a>( dirs_wrapper: &dyn DirsWrapper, args: &[String], -) -> Result, ConfiguratorError> { +) -> Result, ConfiguratorError> { let app = app_node(); - let (config_file_path, user_specified, data_directory, real_user) = + let (config_file_path, user_specified, data_directory, _real_user) = determine_fundamentals(dirs_wrapper, &app, args)?; let config_file_vcl = match ConfigFileVcl::new(&config_file_path, user_specified) { @@ -152,19 +150,15 @@ pub fn server_initializer_collected_params<'a>( let full_multi_config = make_new_multi_config( &app, vec![ - Box::new(CommandLineVcl::new(args.to_vec())), Box::new(EnvironmentVcl::new(&app)), config_file_vcl, + Box::new(CommandLineVcl::new(args.to_vec())), + Box::new(CommandLineVcl::new(vec![ + "--data-directory ".to_string() + data_directory.to_str().unwrap(), + ])), ], )?; - let config_file_path = - value_m!(full_multi_config, "config-file", PathBuf).expect("defaulted param"); - Ok(GatheredParams::new( - full_multi_config, - config_file_path, - real_user, - data_directory, - )) + Ok(full_multi_config) } pub fn establish_port_configurations(config: &mut BootstrapperConfig) { @@ -313,7 +307,7 @@ mod tests { use masq_lib::utils::{running_test, slice_of_strs_to_vec_of_strings}; use rustc_hex::FromHex; use std::convert::TryFrom; - use std::fs::File; + use std::fs::{create_dir_all, File}; use std::io::Write; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; @@ -473,13 +467,12 @@ mod tests { } let directory_wrapper = make_pre_populated_mocked_directory_wrapper(); - let gathered_params = server_initializer_collected_params( + let multi_config = server_initializer_collected_params( &directory_wrapper, &slice_of_strs_to_vec_of_strings(&["", "--data-directory", home_dir.to_str().unwrap()]), ) .unwrap(); - let multi_config = gathered_params.multi_config; assert_eq!( value_m!(multi_config, "dns-servers", String).unwrap(), "111.111.111.111,222.222.222.222".to_string() @@ -737,45 +730,133 @@ mod tests { assert_eq!(config.crash_point, CrashPoint::Panic); } + fn fill_up_config_file(mut config_file: File) { + { + config_file + .write_all(b"blockchain-service-url = \"https://www.mainnet.com\"\n") + .unwrap(); + config_file + .write_all(b"clandestine-port = \"7788\"\n") + .unwrap(); + config_file.write_all(b"consuming-private-key = \"00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF\"\n").unwrap(); + config_file.write_all(b"crash-point = \"None\"\n").unwrap(); + config_file + .write_all(b"dns-servers = \"5.6.7.8\"\n") + .unwrap(); + config_file + .write_all(b"earning-wallet = \"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n") + .unwrap(); + config_file.write_all(b"gas-price = \"77\"\n").unwrap(); + config_file.write_all(b"log-level = \"trace\"\n").unwrap(); + config_file + .write_all(b"mapping-protocol = \"pcp\"\n") + .unwrap(); + config_file.write_all(b"min-hops = \"6\"\n").unwrap(); + config_file + .write_all(b"neighborhood-mode = \"zero-hop\"\n") + .unwrap(); + config_file.write_all(b"scans = \"off\"\n").unwrap(); + config_file.write_all(b"rate-pack = \"2|2|2|2\"\n").unwrap(); + config_file + .write_all(b"payment-thresholds = \"3333|55|33|646|999|999\"\n") + .unwrap(); + config_file + .write_all(b"scan-intervals = \"111|100|99\"\n") + .unwrap() + } + } + #[test] fn server_initializer_collected_params_combine_vlcs_properly () { running_test(); - let home_dir = PathBuf::from("/unexisting_home/unexisting_alice"); - let data_dir = home_dir.join("data_dir"); + let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_combine_vlcs_properly"); + let data_dir = &home_dir.join("data_dir"); + let config_file = File::create(home_dir.join("config.toml")).unwrap(); + let data_dir_vcl = create_dir_all(PathBuf::from("/tmp/cooga/masqhome")); + { + fill_up_config_file(config_file); + match data_dir_vcl { + Ok(..) => { + let config_file_vcl = File::create(PathBuf::from("/tmp/cooga/masqhome").join("config.toml")).unwrap(); + fill_up_config_file(config_file_vcl); + } + Err(e) => panic!("unable to create directory {}", e) + } + } + vec![ - ("MASQ_BLOCKCHAIN_SERVICE_URL", "https://example3.com"), - ("MASQ_CHAIN", TEST_DEFAULT_CHAIN.rec().literal_identifier), - ("MASQ_CLANDESTINE_PORT", "1234"), - ("MASQ_CONSUMING_PRIVATE_KEY", "0011223344556677001122334455667700112233445566770011223344556677"), - ("MASQ_CRASH_POINT", "Error"), + ("MASQ_CONFIG_FILE", "config.toml"), ("MASQ_DATA_DIRECTORY", home_dir.to_str().unwrap()), - ("MASQ_DB_PASSWORD", "password"), - ("MASQ_DNS_SERVERS", "8.8.8.8"), - ("MASQ_EARNING_WALLET", "0x0123456789012345678901234567890123456789"), - ("MASQ_GAS_PRICE", "50"), - ("MASQ_IP", "4.3.2.1"), - ("MASQ_LOG_LEVEL", "error"), - ("MASQ_MAPPING_PROTOCOL", "pmp"), - ("MASQ_MIN_HOPS", "2"), - ("MASQ_NEIGHBORHOOD_MODE", "originate-only"), - ("MASQ_NEIGHBORS", "masq://eth-ropsten:MTIzNDU2Nzg5MTEyMzQ1Njc4OTIxMjM0NTY3ODkzMTI@1.2.3.4:1234,masq://eth-ropsten:MTIzNDU2Nzg5MTEyMzQ1Njc4OTIxMjM0NTY3ODkzMTI@5.6.7.8:5678"), - ("MASQ_PAYMENT_THRESHOLDS","12345|50000|1000|1234|19000|20000"), - ("MASQ_RATE_PACK","1|3|3|8"), #[cfg(not(target_os = "windows"))] ("MASQ_REAL_USER", "9999:9999:booga"), - ("MASQ_SCANS", "off"), - ("MASQ_SCAN_INTERVALS","133|133|111") ].into_iter() .for_each (|(name, value)| std::env::set_var (name, value)); - let args = ArgsBuilder::new() - .param("--chain", "polygon-mainnet"); + let args = ArgsBuilder::new(); let args_vec: Vec = args.into(); let dir_wrapper = DirsWrapperMock::new() - .home_dir_result(Some(home_dir)) - .data_dir_result(Some(data_dir)); + .home_dir_result(Some(home_dir.clone())) + .data_dir_result(Some(data_dir.to_path_buf())); let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); - println!("full_multi_config: {:#?}",result.unwrap().multi_config); + let env_multicnfig = result.unwrap(); + + let args = ArgsBuilder::new() + .param("--config-file", "config.toml") + .param("--data-directory", "/tmp/cooga/masqhome") + .param("--real-user", "1001:1001:cooga"); + let args_vec: Vec = args.into(); + let params = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); + let result = params.as_ref().expect("REASON"); + let vcl_multicnfig = result; + + //println!("env_multicnfig: {:#?}", env_multicnfig); + assert_eq!( + value_m!(env_multicnfig, "config-file", String).unwrap(), + "config.toml".to_string() + );/* + assert_eq!( + value_m!(env_multicnfig, "data-directory", String).unwrap(), + "generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home".to_string() + );*/ + #[cfg(not(target_os = "windows"))] + match env_multicnfig.real_user_specified { + true => { + assert_eq!( + value_m!(env_multicnfig, "real-user", String).unwrap(), + "9999:9999:booga".to_string() + ) + } + false => { + println!("real-user is not user specified"); + () + } + } + //println!("env_multicnfig: {:#?}", vcl_multicnfig); + #[cfg(not(target_os = "windows"))] + assert_eq!( + value_m!(vcl_multicnfig, "real-user", String).unwrap(), + "1001:1001:cooga".to_string() + ); + match vcl_multicnfig.data_directory_user_specified { + true => { + assert_eq!( + value_m!(vcl_multicnfig, "data-directory", String).unwrap(), + "/tmp/cooga/masqhome".to_string() + ) + } + false => { + println!("data-directory is not user specified"); + () + } + } + assert_eq!( + value_m!(vcl_multicnfig, "config-file", String).unwrap(), + "config.toml".to_string() + ); + assert_eq!( + value_m!(vcl_multicnfig, "real-user", String).unwrap(), + "1001:1001:cooga".to_string() + ); } #[test] @@ -1097,12 +1178,12 @@ mod tests { }; let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()) - .unwrap() - .data_directory - .to_string_lossy() - .to_string(); + .unwrap(); - assert_eq!(result, expected.unwrap()); + assert_eq!( + value_m!(result, "data-directory", String).unwrap(), + expected.unwrap() + ); } #[test] diff --git a/node/src/server_initializer.rs b/node/src/server_initializer.rs index 76a4c97d5..eac4a5ac0 100644 --- a/node/src/server_initializer.rs +++ b/node/src/server_initializer.rs @@ -8,6 +8,7 @@ use crate::node_configurator::{DirsWrapper, DirsWrapperReal}; use crate::run_modes_factories::{RunModeResult, ServerInitializer}; use crate::sub_lib::socket_server::ConfiguredByPrivilege; use backtrace::Backtrace; +use clap::value_t; use flexi_logger::{ Cleanup, Criterion, DeferredNow, Duplicate, LevelFilter, LogSpecBuilder, Logger, Naming, Record, }; @@ -37,34 +38,36 @@ pub struct ServerInitializerReal { impl ServerInitializer for ServerInitializerReal { fn go(&mut self, streams: &mut StdStreams<'_>, args: &[String]) -> RunModeResult { let params = server_initializer_collected_params(self.dirs_wrapper.as_ref(), args)?; + let real_user = value_m!(params, "real-user", RealUser).unwrap(); + let data_directory = value_m!(params, "data-directory", String).unwrap(); let result: RunModeResult = Ok(()) .combine_results( self.dns_socket_server .as_mut() - .initialize_as_privileged(¶ms.multi_config), + .initialize_as_privileged(¶ms), ) .combine_results( self.bootstrapper .as_mut() - .initialize_as_privileged(¶ms.multi_config), + .initialize_as_privileged(¶ms), ); self.privilege_dropper - .chown(¶ms.data_directory, ¶ms.real_user); + .chown(Path::new(data_directory.as_str()), &real_user); - self.privilege_dropper.drop_privileges(¶ms.real_user); + self.privilege_dropper.drop_privileges(&real_user); - result + result .combine_results( self.dns_socket_server .as_mut() - .initialize_as_unprivileged(¶ms.multi_config, streams), + .initialize_as_unprivileged(¶ms, streams), ) .combine_results( self.bootstrapper .as_mut() - .initialize_as_unprivileged(¶ms.multi_config, streams), + .initialize_as_unprivileged(¶ms, streams), ) } implement_as_any!(); @@ -115,6 +118,7 @@ impl ResultsCombiner for RunModeResult { } } +#[derive(Debug)] pub struct GatheredParams<'a> { pub multi_config: MultiConfig<'a>, pub config_file_path: PathBuf, diff --git a/node/src/test_utils/mod.rs b/node/src/test_utils/mod.rs index 3dfd8839e..0a382f353 100644 --- a/node/src/test_utils/mod.rs +++ b/node/src/test_utils/mod.rs @@ -643,7 +643,7 @@ pub mod unshared_test_utils { let mut app_args = vec!["MASQNode".to_string()]; app_args.append(&mut slice_of_strs_to_vec_of_strings(&args)); let arg_matches = app_node().get_matches_from_safe(app_args).unwrap(); - MultiConfig::new_test_only(arg_matches) + MultiConfig::new_test_only(arg_matches, false, false, false) } pub const ZERO: u32 = 0b0; From 690b099ef05ac16a42f82ba940a13388e080b6bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Tue, 29 Aug 2023 21:56:57 +0200 Subject: [PATCH 03/52] add ComputedVcl struct and its implementation, add VclType enum, to be able to push into Vcl Vector, add processing in MultiConfig, add processing in server_initializer_collected_params --- node/src/daemon/setup_reporter.rs | 6 +- node/src/node_configurator/mod.rs | 16 +++-- .../node_configurator_standard.rs | 67 ++++++++++++------- node/src/sub_lib/utils.rs | 3 +- node/src/test_utils/mod.rs | 4 +- 5 files changed, 59 insertions(+), 37 deletions(-) diff --git a/node/src/daemon/setup_reporter.rs b/node/src/daemon/setup_reporter.rs index f336864e9..287897738 100644 --- a/node/src/daemon/setup_reporter.rs +++ b/node/src/daemon/setup_reporter.rs @@ -18,7 +18,7 @@ use crate::node_configurator::unprivileged_parse_args_configuration::{ UnprivilegedParseArgsConfigurationDaoReal, }; use crate::node_configurator::{ - data_directory_from_context, determine_fundamentals, DirsWrapper, DirsWrapperReal, + data_directory_from_context, determine_user_specific_data, DirsWrapperReal, DirsWrapper }; use crate::sub_lib::accountant::PaymentThresholds as PaymentThresholdsFromAccountant; use crate::sub_lib::accountant::DEFAULT_SCAN_INTERVALS; @@ -495,8 +495,8 @@ impl SetupReporterReal { Some(command_line) => command_line, None => vec![], }; - let (config_file_path, user_specified, _data_directory, _real_user) = - determine_fundamentals(dirs_wrapper, &app, &command_line)?; + let (config_file_path, user_specified, _data_directory, _data_directory_specified, _real_user, _real_user_specified) = + determine_user_specific_data(dirs_wrapper, &app, &command_line)?; let config_file_vcl = match ConfigFileVcl::new(&config_file_path, user_specified) { Ok(cfv) => cfv, Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index e2baff293..a72ce5f97 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -29,11 +29,11 @@ pub trait NodeConfigurator { fn configure(&self, multi_config: &MultiConfig) -> Result; } -pub fn determine_fundamentals( +pub fn determine_user_specific_data ( dirs_wrapper: &dyn DirsWrapper, app: &App, args: &[String], -) -> Result<(PathBuf, bool, PathBuf, RealUser), ConfiguratorError> { +) -> Result<(PathBuf, bool, PathBuf, bool, RealUser, bool), ConfiguratorError> { let orientation_schema = App::new("MASQNode") .arg(chain_arg()) .arg(real_user_arg()) @@ -57,7 +57,9 @@ pub fn determine_fundamentals( let multi_config = make_new_multi_config(&orientation_schema, vec![Box::new(orientation_vcl)])?; let config_file_path = value_m!(multi_config, "config-file", PathBuf) .expect("config-file parameter is not properly defaulted by clap"); - let user_specified = multi_config.occurrences_of("config-file") > 0; + let config_user_specified = multi_config.occurrences_of("config-file") > 0; + let data_directory_specified = multi_config.occurrences_of("data-directory") > 0; + let real_user_specified = multi_config.occurrences_of("real-user") > 0; let (real_user, data_directory_path, chain) = real_user_data_directory_path_and_chain(dirs_wrapper, &multi_config); let data_directory = match data_directory_path { @@ -70,7 +72,7 @@ pub fn determine_fundamentals( config_file_path }; - Ok((config_file_path, user_specified, data_directory, real_user)) + Ok((config_file_path, config_user_specified, data_directory, data_directory_specified, real_user, real_user_specified)) } pub fn initialize_database( @@ -211,7 +213,7 @@ mod tests { .param("--config-file", "booga.toml"); let args_vec: Vec = args.into(); - let (config_file_path, user_specified, _data_dir, _real_user) = determine_fundamentals( + let (config_file_path, user_specified, _data_dir, _data_dir_specified, _real_user, _real_user_specified) = determine_user_specific_data( &DirsWrapperReal {}, &determine_config_file_path_app(), args_vec.as_slice(), @@ -240,7 +242,7 @@ mod tests { ); std::env::set_var("MASQ_CONFIG_FILE", "booga.toml"); - let (config_file_path, user_specified, _data_dir, _real_user) = determine_fundamentals( + let (config_file_path, user_specified, _data_dir, _data_dir_specified, _real_user, _real_user_specified) = determine_user_specific_data( &DirsWrapperReal {}, &determine_config_file_path_app(), args_vec.as_slice(), @@ -263,7 +265,7 @@ mod tests { .param("--config-file", "/tmp/booga.toml"); let args_vec: Vec = args.into(); - let (config_file_path, user_specified, _data_dir, _real_user) = determine_fundamentals( + let (config_file_path, user_specified, _data_dir, _data_dir_specified, _real_user, _real_user_specified) = determine_user_specific_data( &DirsWrapperReal {}, &determine_config_file_path_app(), args_vec.as_slice(), diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 227467f83..3b71b8eb3 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -5,7 +5,7 @@ use crate::node_configurator::DirsWrapperReal; use crate::node_configurator::{initialize_database, DirsWrapper, NodeConfigurator}; use masq_lib::crash_point::CrashPoint; use masq_lib::logger::Logger; -use masq_lib::multi_config::MultiConfig; +use masq_lib::multi_config::{ComputedVcl, MultiConfig}; use masq_lib::shared_schema::ConfiguratorError; use masq_lib::utils::NeighborhoodModeLight; use std::net::SocketAddr; @@ -23,7 +23,7 @@ use crate::node_configurator::unprivileged_parse_args_configuration::{ UnprivilegedParseArgsConfiguration, UnprivilegedParseArgsConfigurationDaoReal, }; use crate::node_configurator::{ - data_directory_from_context, determine_fundamentals, real_user_data_directory_path_and_chain, + data_directory_from_context, determine_user_specific_data, real_user_data_directory_path_and_chain, }; use crate::sub_lib::cryptde::PublicKey; use crate::sub_lib::cryptde_null::CryptDENull; @@ -134,29 +134,47 @@ fn collect_externals_from_multi_config( ) } +#[derive(Clone)] +pub enum VclType { + Environment(EnvironmentVcl), + ConfigFile(ConfigFileVcl), + CommandLine(CommandLineVcl), + Computed(ComputedVcl), +} + pub fn server_initializer_collected_params<'a>( dirs_wrapper: &dyn DirsWrapper, args: &[String], ) -> Result, ConfiguratorError> { let app = app_node(); - let (config_file_path, user_specified, data_directory, _real_user) = - determine_fundamentals(dirs_wrapper, &app, args)?; - - let config_file_vcl = match ConfigFileVcl::new(&config_file_path, user_specified) { - Ok(cfv) => Box::new(cfv), + let (config_file_path, config_user_specified, data_directory, data_directory_specified, real_user, real_user_specified) = + determine_user_specific_data(dirs_wrapper, &app, args)?; + println!("determine_user_specific_data data-dir: {}", data_directory.to_string_lossy()); + let config_file_vcl = match ConfigFileVcl::new(&config_file_path, real_user_specified) { + Ok(cfv) => Box::new(VclType::ConfigFile(cfv)), Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), }; + + // let config_file_path_from_multiconfig = + // value_m!(multi_config, "config-file", PathBuf).expect("defaulted param"); + let mut vcl_vec: Vec> = vec![]; + vcl_vec.push(Box::new(VclType::Environment(EnvironmentVcl::new(&app)))); + vcl_vec.push(config_file_vcl); + vcl_vec.push(Box::new(VclType::CommandLine(CommandLineVcl::new(args.to_vec())))); + if data_directory_specified { + vcl_vec.push(Box::new(VclType::CommandLine(CommandLineVcl::new(vec![ + "--data-directory ".to_string() + data_directory.to_str().unwrap(), + ])))); + } else { + vcl_vec.push(Box::new(VclType::Computed(ComputedVcl::new(vec![ + "--data-directory ".to_string() + data_directory.to_str().unwrap(), + ])))); + + } let full_multi_config = make_new_multi_config( &app, - vec![ - Box::new(EnvironmentVcl::new(&app)), - config_file_vcl, - Box::new(CommandLineVcl::new(args.to_vec())), - Box::new(CommandLineVcl::new(vec![ - "--data-directory ".to_string() + data_directory.to_str().unwrap(), - ])), - ], + vcl_vec )?; Ok(full_multi_config) } @@ -767,17 +785,17 @@ mod tests { } #[test] - fn server_initializer_collected_params_combine_vlcs_properly () { + fn server_initializer_collected_params_combine_vlcs_properly() { running_test(); let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_combine_vlcs_properly"); let data_dir = &home_dir.join("data_dir"); let config_file = File::create(home_dir.join("config.toml")).unwrap(); - let data_dir_vcl = create_dir_all(PathBuf::from("/tmp/cooga/masqhome")); + let data_dir_vcl = create_dir_all(PathBuf::from("booga/data_dir/MASQ/polygon-mainnet")); { fill_up_config_file(config_file); match data_dir_vcl { Ok(..) => { - let config_file_vcl = File::create(PathBuf::from("/tmp/cooga/masqhome").join("config.toml")).unwrap(); + let config_file_vcl = File::create(PathBuf::from("booga/data_dir/MASQ/polygon-mainnet").join("config.toml")).unwrap(); fill_up_config_file(config_file_vcl); } Err(e) => panic!("unable to create directory {}", e) @@ -785,8 +803,8 @@ mod tests { } vec![ - ("MASQ_CONFIG_FILE", "config.toml"), - ("MASQ_DATA_DIRECTORY", home_dir.to_str().unwrap()), + //("MASQ_CONFIG_FILE", "config.toml"), + //("MASQ_DATA_DIRECTORY", home_dir.to_str().unwrap()), #[cfg(not(target_os = "windows"))] ("MASQ_REAL_USER", "9999:9999:booga"), ].into_iter() @@ -800,8 +818,8 @@ mod tests { let env_multicnfig = result.unwrap(); let args = ArgsBuilder::new() - .param("--config-file", "config.toml") - .param("--data-directory", "/tmp/cooga/masqhome") + //.param("--config-file", "config.toml") + //.param("--data-directory", "/tmp/cooga/masqhome") .param("--real-user", "1001:1001:cooga"); let args_vec: Vec = args.into(); let params = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); @@ -818,7 +836,8 @@ mod tests { "generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home".to_string() );*/ #[cfg(not(target_os = "windows"))] - match env_multicnfig.real_user_specified { + //let real_user = value_m!(env_multicnfig, "real-user", String).unwrap(); + match env_multicnfig.is_user_specified("real-user") { true => { assert_eq!( value_m!(env_multicnfig, "real-user", String).unwrap(), @@ -837,7 +856,7 @@ mod tests { value_m!(vcl_multicnfig, "real-user", String).unwrap(), "1001:1001:cooga".to_string() ); - match vcl_multicnfig.data_directory_user_specified { + match vcl_multicnfig.is_user_specified("data-directory") { true => { assert_eq!( value_m!(vcl_multicnfig, "data-directory", String).unwrap(), diff --git a/node/src/sub_lib/utils.rs b/node/src/sub_lib/utils.rs index cad371e09..6e056eb6e 100644 --- a/node/src/sub_lib/utils.rs +++ b/node/src/sub_lib/utils.rs @@ -15,6 +15,7 @@ use std::io::ErrorKind; use std::marker::PhantomData; use std::path::Path; use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use crate::node_configurator::node_configurator_standard::VclType; static DEAD_STREAM_ERRORS: [ErrorKind; 5] = [ ErrorKind::BrokenPipe, @@ -93,7 +94,7 @@ pub fn to_string_s(data: &[u8]) -> String { pub fn make_new_multi_config<'a>( schema: &App<'a, 'a>, - vcls: Vec>, + vcls: Vec>, ) -> Result, ConfiguratorError> { MultiConfig::try_new(schema, vcls) } diff --git a/node/src/test_utils/mod.rs b/node/src/test_utils/mod.rs index 0a382f353..f8bf58b02 100644 --- a/node/src/test_utils/mod.rs +++ b/node/src/test_utils/mod.rs @@ -573,7 +573,7 @@ pub mod unshared_test_utils { use masq_lib::utils::slice_of_strs_to_vec_of_strings; use std::any::TypeId; use std::cell::RefCell; - use std::collections::HashMap; + use std::collections::{HashMap, HashSet}; use std::num::ParseIntError; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::path::{Path, PathBuf}; @@ -643,7 +643,7 @@ pub mod unshared_test_utils { let mut app_args = vec!["MASQNode".to_string()]; app_args.append(&mut slice_of_strs_to_vec_of_strings(&args)); let arg_matches = app_node().get_matches_from_safe(app_args).unwrap(); - MultiConfig::new_test_only(arg_matches, false, false, false) + MultiConfig::new_test_only(arg_matches, HashSet::new()) } pub const ZERO: u32 = 0b0; From 0d7427b59ea92ca4de9b0ba4e6a092fdce7f5117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Wed, 30 Aug 2023 18:14:23 +0200 Subject: [PATCH 04/52] back to VirtualCommandLine type without enum, passing different types to MultiConfig and selecting them to computed_value_names on behalf of their type --- .../node_configurator_standard.rs | 120 +++++++++--------- node/src/sub_lib/utils.rs | 3 +- 2 files changed, 60 insertions(+), 63 deletions(-) diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 3b71b8eb3..b672f5d65 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -5,7 +5,7 @@ use crate::node_configurator::DirsWrapperReal; use crate::node_configurator::{initialize_database, DirsWrapper, NodeConfigurator}; use masq_lib::crash_point::CrashPoint; use masq_lib::logger::Logger; -use masq_lib::multi_config::{ComputedVcl, MultiConfig}; +use masq_lib::multi_config::{ComputedVcl, MultiConfig, VirtualCommandLine}; use masq_lib::shared_schema::ConfiguratorError; use masq_lib::utils::NeighborhoodModeLight; use std::net::SocketAddr; @@ -134,48 +134,48 @@ fn collect_externals_from_multi_config( ) } -#[derive(Clone)] -pub enum VclType { - Environment(EnvironmentVcl), - ConfigFile(ConfigFileVcl), - CommandLine(CommandLineVcl), - Computed(ComputedVcl), -} - pub fn server_initializer_collected_params<'a>( dirs_wrapper: &dyn DirsWrapper, args: &[String], ) -> Result, ConfiguratorError> { let app = app_node(); - let (config_file_path, config_user_specified, data_directory, data_directory_specified, real_user, real_user_specified) = determine_user_specific_data(dirs_wrapper, &app, args)?; - println!("determine_user_specific_data data-dir: {}", data_directory.to_string_lossy()); - let config_file_vcl = match ConfigFileVcl::new(&config_file_path, real_user_specified) { - Ok(cfv) => Box::new(VclType::ConfigFile(cfv)), + let config_file_vcl = match ConfigFileVcl::new(&config_file_path, config_user_specified) { + Ok(cfv) => Box::new(cfv), Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), }; // let config_file_path_from_multiconfig = // value_m!(multi_config, "config-file", PathBuf).expect("defaulted param"); - let mut vcl_vec: Vec> = vec![]; - vcl_vec.push(Box::new(VclType::Environment(EnvironmentVcl::new(&app)))); - vcl_vec.push(config_file_vcl); - vcl_vec.push(Box::new(VclType::CommandLine(CommandLineVcl::new(args.to_vec())))); - if data_directory_specified { - vcl_vec.push(Box::new(VclType::CommandLine(CommandLineVcl::new(vec![ - "--data-directory ".to_string() + data_directory.to_str().unwrap(), - ])))); - } else { - vcl_vec.push(Box::new(VclType::Computed(ComputedVcl::new(vec![ - "--data-directory ".to_string() + data_directory.to_str().unwrap(), - ])))); + let mut full_multi_config_vec: Vec> = vec![ + Box::new(EnvironmentVcl::new(&app)), + config_file_vcl, + Box::new(CommandLineVcl::new(args.to_vec())), + ]; - } - let full_multi_config = make_new_multi_config( - &app, - vcl_vec - )?; + let data_directory_spcified_box: Box = Box::new(CommandLineVcl::new(vec![ + "".to_string(), "--data-directory".to_string(), data_directory.to_string_lossy().to_string(), + ])); + let data_directory_unspcified_box: Box = Box::new(ComputedVcl::new(vec![ + "".to_string(), "--data-directory".to_string(), data_directory.to_string_lossy().to_string(), + ])); + match data_directory_specified { + true => full_multi_config_vec.push(data_directory_spcified_box), + false => full_multi_config_vec.push(data_directory_unspcified_box) + }; + let real_user_spcified_box: Box = Box::new(CommandLineVcl::new(vec![ + "".to_string(), "--real-user".to_string(), real_user.to_string(), + ])); + let real_user_unspcified_box: Box = Box::new(ComputedVcl::new(vec![ + "".to_string(), "--real-user".to_string(), real_user.to_string(), + ])); + match real_user_specified { + true => full_multi_config_vec.push(real_user_spcified_box), + false => full_multi_config_vec.push(real_user_unspcified_box) + }; + let full_multi_config = make_new_multi_config(&app,full_multi_config_vec)?; + //println!("full_multi_config: {:#?}", full_multi_config); Ok(full_multi_config) } @@ -789,13 +789,13 @@ mod tests { running_test(); let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_combine_vlcs_properly"); let data_dir = &home_dir.join("data_dir"); - let config_file = File::create(home_dir.join("config.toml")).unwrap(); - let data_dir_vcl = create_dir_all(PathBuf::from("booga/data_dir/MASQ/polygon-mainnet")); + let config_file = File::create(&home_dir.join("config.toml")).unwrap(); + let data_dir_vcl = create_dir_all(PathBuf::from(home_dir.to_string_lossy().as_ref().to_owned() + "/data_dir/MASQ/polygon-mainnet")); { fill_up_config_file(config_file); match data_dir_vcl { Ok(..) => { - let config_file_vcl = File::create(PathBuf::from("booga/data_dir/MASQ/polygon-mainnet").join("config.toml")).unwrap(); + let config_file_vcl = File::create(PathBuf::from(home_dir.to_string_lossy().as_ref().to_owned() + "/data_dir/MASQ/polygon-mainnet").join("config.toml")).unwrap(); fill_up_config_file(config_file_vcl); } Err(e) => panic!("unable to create directory {}", e) @@ -805,8 +805,9 @@ mod tests { vec![ //("MASQ_CONFIG_FILE", "config.toml"), //("MASQ_DATA_DIRECTORY", home_dir.to_str().unwrap()), - #[cfg(not(target_os = "windows"))] - ("MASQ_REAL_USER", "9999:9999:booga"), + //#[cfg(not(target_os = "windows"))] + //("MASQ_REAL_USER", "9999:9999:booga"), + ("MASQ_MIN_HOPS", "3"), ].into_iter() .for_each (|(name, value)| std::env::set_var (name, value)); let args = ArgsBuilder::new(); @@ -817,27 +818,21 @@ mod tests { let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let env_multicnfig = result.unwrap(); - let args = ArgsBuilder::new() + let args = ArgsBuilder::new(); //.param("--config-file", "config.toml") //.param("--data-directory", "/tmp/cooga/masqhome") - .param("--real-user", "1001:1001:cooga"); + //.param("--real-user", "1001:1001:cooga"); let args_vec: Vec = args.into(); let params = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let result = params.as_ref().expect("REASON"); - let vcl_multicnfig = result; - - //println!("env_multicnfig: {:#?}", env_multicnfig); + let vcl_multiconfig = result; + assert_eq!( value_m!(env_multicnfig, "config-file", String).unwrap(), "config.toml".to_string() - );/* - assert_eq!( - value_m!(env_multicnfig, "data-directory", String).unwrap(), - "generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home".to_string() - );*/ + ); #[cfg(not(target_os = "windows"))] - //let real_user = value_m!(env_multicnfig, "real-user", String).unwrap(); - match env_multicnfig.is_user_specified("real-user") { + match env_multicnfig.is_user_specified("--real-user") { true => { assert_eq!( value_m!(env_multicnfig, "real-user", String).unwrap(), @@ -845,37 +840,40 @@ mod tests { ) } false => { - println!("real-user is not user specified"); + println!("real-user is not user specified in Environment"); () } } - //println!("env_multicnfig: {:#?}", vcl_multicnfig); #[cfg(not(target_os = "windows"))] - assert_eq!( - value_m!(vcl_multicnfig, "real-user", String).unwrap(), - "1001:1001:cooga".to_string() - ); - match vcl_multicnfig.is_user_specified("data-directory") { + match vcl_multiconfig.is_user_specified("--real-user") { + true => { + assert_eq!( + value_m!(vcl_multiconfig, "real-user", String).unwrap(), + "9999:9999:booga".to_string() + ) + } + false => { + println!("real-user is not user specified in Command-line"); + () + } + } + match vcl_multiconfig.is_user_specified("--data-directory") { true => { assert_eq!( - value_m!(vcl_multicnfig, "data-directory", String).unwrap(), + value_m!(vcl_multiconfig, "data-directory", String).unwrap(), "/tmp/cooga/masqhome".to_string() ) } false => { - println!("data-directory is not user specified"); + println!("data-directory is not user specified in Command-line"); () } } assert_eq!( - value_m!(vcl_multicnfig, "config-file", String).unwrap(), + value_m!(vcl_multiconfig, "config-file", String).unwrap(), "config.toml".to_string() ); - assert_eq!( - value_m!(vcl_multicnfig, "real-user", String).unwrap(), - "1001:1001:cooga".to_string() - ); } #[test] diff --git a/node/src/sub_lib/utils.rs b/node/src/sub_lib/utils.rs index 6e056eb6e..cad371e09 100644 --- a/node/src/sub_lib/utils.rs +++ b/node/src/sub_lib/utils.rs @@ -15,7 +15,6 @@ use std::io::ErrorKind; use std::marker::PhantomData; use std::path::Path; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use crate::node_configurator::node_configurator_standard::VclType; static DEAD_STREAM_ERRORS: [ErrorKind; 5] = [ ErrorKind::BrokenPipe, @@ -94,7 +93,7 @@ pub fn to_string_s(data: &[u8]) -> String { pub fn make_new_multi_config<'a>( schema: &App<'a, 'a>, - vcls: Vec>, + vcls: Vec>, ) -> Result, ConfiguratorError> { MultiConfig::try_new(schema, vcls) } From 22e607b6e23f7a737be4748d5f97e768eb27afb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Thu, 31 Aug 2023 17:40:50 +0200 Subject: [PATCH 05/52] fixed data flow with Evironment and Command line arguments, need to fix config file arguments data --- node/src/node_configurator/mod.rs | 1 + .../node_configurator_standard.rs | 71 +++++++++++++++---- 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index a72ce5f97..a98097bd7 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -54,6 +54,7 @@ pub fn determine_user_specific_data ( .map(|vcl_arg| vcl_arg.dup()) .collect(); let orientation_vcl = CommandLineVcl::from(orientation_args); + println!("determine_user_specific_data"); let multi_config = make_new_multi_config(&orientation_schema, vec![Box::new(orientation_vcl)])?; let config_file_path = value_m!(multi_config, "config-file", PathBuf) .expect("config-file parameter is not properly defaulted by clap"); diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index b672f5d65..614673d83 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -145,9 +145,11 @@ pub fn server_initializer_collected_params<'a>( Ok(cfv) => Box::new(cfv), Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), }; - + println!("config_file_path: {} {:#?}", config_user_specified, &config_file_path); + println!("config_file_vcl: {:#?}", config_file_vcl); // let config_file_path_from_multiconfig = // value_m!(multi_config, "config-file", PathBuf).expect("defaulted param"); + //let multi_config_vec = create_multi_config_vec(dirs_wrapper, &app, args); let mut full_multi_config_vec: Vec> = vec![ Box::new(EnvironmentVcl::new(&app)), config_file_vcl, @@ -174,6 +176,7 @@ pub fn server_initializer_collected_params<'a>( true => full_multi_config_vec.push(real_user_spcified_box), false => full_multi_config_vec.push(real_user_unspcified_box) }; + println!("full_multi_config_vec: {:#?}", full_multi_config_vec); let full_multi_config = make_new_multi_config(&app,full_multi_config_vec)?; //println!("full_multi_config: {:#?}", full_multi_config); Ok(full_multi_config) @@ -751,7 +754,10 @@ mod tests { fn fill_up_config_file(mut config_file: File) { { config_file - .write_all(b"blockchain-service-url = \"https://www.mainnet.com\"\n") + .write_all(b"real-user = \"1002:1002:wooga\"\n") + .unwrap(); + config_file + .write_all(b"blockchain-service-url = \"https://www.mainnet2.com\"\n") .unwrap(); config_file .write_all(b"clandestine-port = \"7788\"\n") @@ -803,7 +809,7 @@ mod tests { } vec![ - //("MASQ_CONFIG_FILE", "config.toml"), + ("MASQ_CONFIG_FILE", "config.toml"), //("MASQ_DATA_DIRECTORY", home_dir.to_str().unwrap()), //#[cfg(not(target_os = "windows"))] //("MASQ_REAL_USER", "9999:9999:booga"), @@ -816,7 +822,7 @@ mod tests { .home_dir_result(Some(home_dir.clone())) .data_dir_result(Some(data_dir.to_path_buf())); let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); - let env_multicnfig = result.unwrap(); + let env_multiconfig = result.unwrap(); let args = ArgsBuilder::new(); //.param("--config-file", "config.toml") @@ -828,14 +834,27 @@ mod tests { let vcl_multiconfig = result; assert_eq!( - value_m!(env_multicnfig, "config-file", String).unwrap(), + value_m!(env_multiconfig, "config-file", String).unwrap(), "config.toml".to_string() ); + match env_multiconfig.is_user_specified("--data-directory") { + true => { + assert_eq!( + value_m!(env_multiconfig, "data-directory", String).unwrap(), + "generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home".to_string() + ) + } + false => { + println!("data-directory is not user specified in Environment"); + () + } + } + println!("env_multiconfig: {:#?}", env_multiconfig); #[cfg(not(target_os = "windows"))] - match env_multicnfig.is_user_specified("--real-user") { + match env_multiconfig.is_user_specified("--real-user") { true => { assert_eq!( - value_m!(env_multicnfig, "real-user", String).unwrap(), + value_m!(env_multiconfig, "real-user", String).unwrap(), "9999:9999:booga".to_string() ) } @@ -848,10 +867,21 @@ mod tests { #[cfg(not(target_os = "windows"))] match vcl_multiconfig.is_user_specified("--real-user") { true => { - assert_eq!( - value_m!(vcl_multiconfig, "real-user", String).unwrap(), - "9999:9999:booga".to_string() - ) + match env_multiconfig.is_user_specified("--real-user") { + true => { + println!("real-user is inherited from Environment {}", value_m!(vcl_multiconfig, "real-user", String).unwrap()); + assert_eq!( + value_m!(vcl_multiconfig, "real-user", String).unwrap(), + "9999:9999:booga".to_string() + ) + } + false => { + assert_eq!( + value_m!(vcl_multiconfig, "real-user", String).unwrap(), + "1001:1001:cooga".to_string() + ) + } + } } false => { println!("real-user is not user specified in Command-line"); @@ -860,10 +890,21 @@ mod tests { } match vcl_multiconfig.is_user_specified("--data-directory") { true => { - assert_eq!( - value_m!(vcl_multiconfig, "data-directory", String).unwrap(), - "/tmp/cooga/masqhome".to_string() - ) + match env_multiconfig.is_user_specified("--data-directory") { + true => { + println!("data-directory is inherited from Environment"); + assert_eq!( + value_m!(vcl_multiconfig, "data-directory", String).unwrap(), + "generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home".to_string() + ) + } + false => { + assert_eq!( + value_m!(vcl_multiconfig, "data-directory", String).unwrap(), + "/tmp/cooga/masqhome".to_string() + ) + } + } } false => { println!("data-directory is not user specified in Command-line"); From b9a6e85c4f85e47114bea933cb65745c8bd0de31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Mon, 4 Sep 2023 23:10:35 +0200 Subject: [PATCH 06/52] changes in test to access config.file for various scenarios, add machinery to determine if real-user is specified in config-file --- node/src/node_configurator/mod.rs | 47 +++++++++++- .../node_configurator_standard.rs | 75 ++++++++++++------- 2 files changed, 93 insertions(+), 29 deletions(-) diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index a98097bd7..ed8da956b 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -16,7 +16,7 @@ use clap::{value_t, App}; use dirs::{data_local_dir, home_dir}; use masq_lib::blockchains::chains::Chain; use masq_lib::constants::DEFAULT_CHAIN; -use masq_lib::multi_config::{merge, CommandLineVcl, EnvironmentVcl, MultiConfig, VclArg}; +use masq_lib::multi_config::{merge, CommandLineVcl, EnvironmentVcl, MultiConfig, VclArg, VirtualCommandLine, ConfigFileVcl}; use masq_lib::shared_schema::{ chain_arg, config_file_arg, data_directory_arg, real_user_arg, ConfiguratorError, DATA_DIRECTORY_HELP, @@ -29,19 +29,58 @@ pub trait NodeConfigurator { fn configure(&self, multi_config: &MultiConfig) -> Result; } +fn config_file_and_data_dir_from_enumerate(configs: Vec) -> (String, String) { + let mut config_match = "".to_string(); + let mut datadir_match = "".to_string(); + for (i, arg) in configs.iter().enumerate() { + if arg.as_str() == "--config-file" { + config_match = configs[i + 1].to_string() + } + if arg.as_str() == "--data-directory".to_string() { + datadir_match = configs[i + 1].to_string() + } + }; + (config_match, datadir_match) +} + pub fn determine_user_specific_data ( dirs_wrapper: &dyn DirsWrapper, app: &App, args: &[String], -) -> Result<(PathBuf, bool, PathBuf, bool, RealUser, bool), ConfiguratorError> { +) -> Result<(PathBuf, bool, PathBuf, bool, RealUser, bool), ConfiguratorError> { //, Box let orientation_schema = App::new("MASQNode") .arg(chain_arg()) .arg(real_user_arg()) .arg(data_directory_arg(DATA_DIRECTORY_HELP)) .arg(config_file_arg()); - let orientation_args: Vec> = merge( + println!("ENV"); + let configargs = Box::new(EnvironmentVcl::new(&app)).args(); + let (env_config_file, env_data_dir) = config_file_and_data_dir_from_enumerate(configargs); + println!("ENV configargs: {} - {}", env_config_file, env_data_dir); + println!("CMD"); + let argstovec = args.to_vec(); + //println!("args to vec: {:#?}", argstovec); + let (cmd_config_file, cmd_data_dir) = config_file_and_data_dir_from_enumerate(argstovec); + println!("CMD configargs: {} - {}", cmd_config_file, cmd_data_dir); + + let mut combined_vcl = vec![]; + if !cmd_data_dir.is_empty() {combined_vcl.push(cmd_data_dir); } + else if !env_data_dir.is_empty() { combined_vcl.push(env_data_dir); } + if !cmd_config_file.is_empty() { combined_vcl.push(cmd_config_file); } + else if !env_config_file.is_empty() { combined_vcl.push(env_config_file); } + + let config_file_path = Path::new(&combined_vcl[0].to_string()).join(Path::new(&combined_vcl[1].to_string())); + let config_file_vcl = match ConfigFileVcl::new(&config_file_path, true) { + Ok(cfv) => Box::new(cfv), + Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), + }; + let preorientation_args: Box = merge( Box::new(EnvironmentVcl::new(app)), - Box::new(CommandLineVcl::new(args.to_vec())), + config_file_vcl, + ); + let orientation_args: Vec> = merge( + preorientation_args, + Box::new(CommandLineVcl::new(args.to_vec())) ) .vcl_args() .into_iter() diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 614673d83..36cacf2ac 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -141,18 +141,15 @@ pub fn server_initializer_collected_params<'a>( let app = app_node(); let (config_file_path, config_user_specified, data_directory, data_directory_specified, real_user, real_user_specified) = determine_user_specific_data(dirs_wrapper, &app, args)?; - let config_file_vcl = match ConfigFileVcl::new(&config_file_path, config_user_specified) { - Ok(cfv) => Box::new(cfv), - Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), - }; + println!("config_file_path: {} {:#?}", config_user_specified, &config_file_path); - println!("config_file_vcl: {:#?}", config_file_vcl); + //println!("config_file_vcl: {:#?}", config_file_vcl); // let config_file_path_from_multiconfig = // value_m!(multi_config, "config-file", PathBuf).expect("defaulted param"); //let multi_config_vec = create_multi_config_vec(dirs_wrapper, &app, args); let mut full_multi_config_vec: Vec> = vec![ Box::new(EnvironmentVcl::new(&app)), - config_file_vcl, + //config_file_vcl, Box::new(CommandLineVcl::new(args.to_vec())), ]; @@ -176,7 +173,7 @@ pub fn server_initializer_collected_params<'a>( true => full_multi_config_vec.push(real_user_spcified_box), false => full_multi_config_vec.push(real_user_unspcified_box) }; - println!("full_multi_config_vec: {:#?}", full_multi_config_vec); + //println!("full_multi_config_vec: {:#?}", full_multi_config_vec); let full_multi_config = make_new_multi_config(&app,full_multi_config_vec)?; //println!("full_multi_config: {:#?}", full_multi_config); Ok(full_multi_config) @@ -796,10 +793,18 @@ mod tests { let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_combine_vlcs_properly"); let data_dir = &home_dir.join("data_dir"); let config_file = File::create(&home_dir.join("config.toml")).unwrap(); - let data_dir_vcl = create_dir_all(PathBuf::from(home_dir.to_string_lossy().as_ref().to_owned() + "/data_dir/MASQ/polygon-mainnet")); + let data_dir_vcl = create_dir_all(PathBuf::from("/tmp/cooga/masqhome")); + let data_dir_sys = create_dir_all(PathBuf::from(home_dir.to_string_lossy().as_ref().to_owned() + "/data_dir/MASQ/polygon-mainnet")); { fill_up_config_file(config_file); match data_dir_vcl { + Ok(..) => { + let config_file_vcl = File::create(PathBuf::from("/tmp/cooga/masqhome").join("config.toml")).unwrap(); + fill_up_config_file(config_file_vcl); + } + Err(e) => panic!("unable to create directory {}", e) + } + match data_dir_sys { Ok(..) => { let config_file_vcl = File::create(PathBuf::from(home_dir.to_string_lossy().as_ref().to_owned() + "/data_dir/MASQ/polygon-mainnet").join("config.toml")).unwrap(); fill_up_config_file(config_file_vcl); @@ -808,31 +813,41 @@ mod tests { } } - vec![ + let env_vec_array = vec![ ("MASQ_CONFIG_FILE", "config.toml"), - //("MASQ_DATA_DIRECTORY", home_dir.to_str().unwrap()), + ("MASQ_DATA_DIRECTORY", home_dir.to_str().unwrap()), //#[cfg(not(target_os = "windows"))] //("MASQ_REAL_USER", "9999:9999:booga"), ("MASQ_MIN_HOPS", "3"), - ].into_iter() + ]; + env_vec_array.clone().into_iter() .for_each (|(name, value)| std::env::set_var (name, value)); let args = ArgsBuilder::new(); let args_vec: Vec = args.into(); let dir_wrapper = DirsWrapperMock::new() .home_dir_result(Some(home_dir.clone())) .data_dir_result(Some(data_dir.to_path_buf())); + let mut env_data_directory = false; + for (item, _vaue) in env_vec_array.to_vec().as_slice() { + if item == &"MASQ_DATA_DIRECTORY" { env_data_directory = true; } + } + println!("env_multiconfig creation"); let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let env_multiconfig = result.unwrap(); - let args = ArgsBuilder::new(); + let args = ArgsBuilder::new() //.param("--config-file", "config.toml") - //.param("--data-directory", "/tmp/cooga/masqhome") + .param("--data-directory", "/tmp/cooga/masqhome"); //.param("--real-user", "1001:1001:cooga"); let args_vec: Vec = args.into(); + let mut cmd_data_directory = false; + for item in args_vec.clone().as_slice() { + if item == &"--data-directory".to_string() { cmd_data_directory = true; } + } let params = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); - let result = params.as_ref().expect("REASON"); - let vcl_multiconfig = result; - + let result2 = params.as_ref().expect("REASON"); + let vcl_multiconfig = result2; + assert_eq!( value_m!(env_multiconfig, "config-file", String).unwrap(), "config.toml".to_string() @@ -849,13 +864,13 @@ mod tests { () } } - println!("env_multiconfig: {:#?}", env_multiconfig); + //println!("env_multiconfig: {:#?}", env_multiconfig); #[cfg(not(target_os = "windows"))] match env_multiconfig.is_user_specified("--real-user") { true => { assert_eq!( value_m!(env_multiconfig, "real-user", String).unwrap(), - "9999:9999:booga".to_string() + "1002:1002:wooga".to_string() ) } false => { @@ -872,7 +887,7 @@ mod tests { println!("real-user is inherited from Environment {}", value_m!(vcl_multiconfig, "real-user", String).unwrap()); assert_eq!( value_m!(vcl_multiconfig, "real-user", String).unwrap(), - "9999:9999:booga".to_string() + "1002:1002:wooga".to_string() ) } false => { @@ -888,9 +903,10 @@ mod tests { () } } + println!("env_data_directory {:#?}", env_data_directory); match vcl_multiconfig.is_user_specified("--data-directory") { true => { - match env_multiconfig.is_user_specified("--data-directory") { + match env_data_directory && !cmd_data_directory { true => { println!("data-directory is inherited from Environment"); assert_eq!( @@ -899,12 +915,21 @@ mod tests { ) } false => { - assert_eq!( - value_m!(vcl_multiconfig, "data-directory", String).unwrap(), - "/tmp/cooga/masqhome".to_string() - ) + match cmd_data_directory { + true => { + assert_eq!( + value_m!(vcl_multiconfig, "data-directory", String).unwrap(), + "/tmp/cooga/masqhome".to_string() + ) + } + false => { + println!("data-directory is not user specified in ENV") + } + } + } - } + }; + } false => { println!("data-directory is not user specified in Command-line"); From 08349e2f19e2c78bb29463cbc2da7febb5be6391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Thu, 14 Sep 2023 00:54:08 +0200 Subject: [PATCH 07/52] try to get rid of redundant MultiConfig construction in determine_user_specific_data --- node/src/daemon/setup_reporter.rs | 2 +- node/src/node_configurator/mod.rs | 192 ++++++++++++------ .../node_configurator_standard.rs | 176 ++++++++++++---- 3 files changed, 276 insertions(+), 94 deletions(-) diff --git a/node/src/daemon/setup_reporter.rs b/node/src/daemon/setup_reporter.rs index 287897738..79b4a34b8 100644 --- a/node/src/daemon/setup_reporter.rs +++ b/node/src/daemon/setup_reporter.rs @@ -495,7 +495,7 @@ impl SetupReporterReal { Some(command_line) => command_line, None => vec![], }; - let (config_file_path, user_specified, _data_directory, _data_directory_specified, _real_user, _real_user_specified) = + let (config_file_path, user_specified, _data_directory, _data_directory_specified, _real_user, _real_user_specified, _preorientation_args) = determine_user_specific_data(dirs_wrapper, &app, &command_line)?; let config_file_vcl = match ConfigFileVcl::new(&config_file_path, user_specified) { Ok(cfv) => cfv, diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index ed8da956b..b2aefcd7d 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -5,18 +5,19 @@ pub mod node_configurator_initialization; pub mod node_configurator_standard; pub mod unprivileged_parse_args_configuration; +use std::env::{current_dir}; use crate::bootstrapper::RealUser; use crate::database::db_initializer::DbInitializationConfig; use crate::database::db_initializer::{DbInitializer, DbInitializerReal}; use crate::db_config::persistent_configuration::{ PersistentConfiguration, PersistentConfigurationReal, }; -use crate::sub_lib::utils::{db_connection_launch_panic, make_new_multi_config}; +use crate::sub_lib::utils::db_connection_launch_panic; use clap::{value_t, App}; use dirs::{data_local_dir, home_dir}; use masq_lib::blockchains::chains::Chain; use masq_lib::constants::DEFAULT_CHAIN; -use masq_lib::multi_config::{merge, CommandLineVcl, EnvironmentVcl, MultiConfig, VclArg, VirtualCommandLine, ConfigFileVcl}; +use masq_lib::multi_config::{merge, CommandLineVcl, EnvironmentVcl, MultiConfig, VclArg, VirtualCommandLine, ConfigFileVcl, ConfigFileVclError}; use masq_lib::shared_schema::{ chain_arg, config_file_arg, data_directory_arg, real_user_arg, ConfiguratorError, DATA_DIRECTORY_HELP, @@ -29,58 +30,42 @@ pub trait NodeConfigurator { fn configure(&self, multi_config: &MultiConfig) -> Result; } -fn config_file_and_data_dir_from_enumerate(configs: Vec) -> (String, String) { - let mut config_match = "".to_string(); - let mut datadir_match = "".to_string(); +fn config_file_and_data_dir_from_enumerate(configs: Vec) -> (String, String, String) { + let config_match = argument_from_enumerate(configs.clone(), "--config-file".to_string()).unwrap_or("".to_string()); + let data_dir_match = argument_from_enumerate(configs.clone(), "--data-directory".to_string()).unwrap_or("".to_string()); + let real_user_match = argument_from_enumerate(configs, "--real-user".to_string()).unwrap_or("".to_string()); + (config_match, data_dir_match, real_user_match) +} + +fn argument_from_enumerate(configs: Vec, needle: String) -> Option { + let mut arg_match = None; for (i, arg) in configs.iter().enumerate() { - if arg.as_str() == "--config-file" { - config_match = configs[i + 1].to_string() - } - if arg.as_str() == "--data-directory".to_string() { - datadir_match = configs[i + 1].to_string() + if arg.as_str() == needle { + arg_match = Some(configs[i + 1].to_string()) } }; - (config_match, datadir_match) + arg_match } pub fn determine_user_specific_data ( dirs_wrapper: &dyn DirsWrapper, app: &App, args: &[String], -) -> Result<(PathBuf, bool, PathBuf, bool, RealUser, bool), ConfiguratorError> { //, Box +) -> Result<(PathBuf, bool, PathBuf, bool, RealUser, bool, Box), ConfiguratorError> { //, Box let orientation_schema = App::new("MASQNode") .arg(chain_arg()) .arg(real_user_arg()) .arg(data_directory_arg(DATA_DIRECTORY_HELP)) .arg(config_file_arg()); - println!("ENV"); - let configargs = Box::new(EnvironmentVcl::new(&app)).args(); - let (env_config_file, env_data_dir) = config_file_and_data_dir_from_enumerate(configargs); - println!("ENV configargs: {} - {}", env_config_file, env_data_dir); - println!("CMD"); - let argstovec = args.to_vec(); - //println!("args to vec: {:#?}", argstovec); - let (cmd_config_file, cmd_data_dir) = config_file_and_data_dir_from_enumerate(argstovec); - println!("CMD configargs: {} - {}", cmd_config_file, cmd_data_dir); - - let mut combined_vcl = vec![]; - if !cmd_data_dir.is_empty() {combined_vcl.push(cmd_data_dir); } - else if !env_data_dir.is_empty() { combined_vcl.push(env_data_dir); } - if !cmd_config_file.is_empty() { combined_vcl.push(cmd_config_file); } - else if !env_config_file.is_empty() { combined_vcl.push(env_config_file); } - - let config_file_path = Path::new(&combined_vcl[0].to_string()).join(Path::new(&combined_vcl[1].to_string())); - let config_file_vcl = match ConfigFileVcl::new(&config_file_path, true) { - Ok(cfv) => Box::new(cfv), - Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), + let env_args = Box::new(EnvironmentVcl::new(&app)).args(); + let args_to_vec = args.to_vec(); + let pre_orientation_args = match create_preorientation_args(env_args, args_to_vec.clone(), &app) { + Ok(pre_orientation_args) => pre_orientation_args, + Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())) }; - let preorientation_args: Box = merge( - Box::new(EnvironmentVcl::new(app)), - config_file_vcl, - ); let orientation_args: Vec> = merge( - preorientation_args, - Box::new(CommandLineVcl::new(args.to_vec())) + pre_orientation_args, + Box::new(CommandLineVcl::new(args_to_vec)) ) .vcl_args() .into_iter() @@ -93,26 +78,117 @@ pub fn determine_user_specific_data ( .map(|vcl_arg| vcl_arg.dup()) .collect(); let orientation_vcl = CommandLineVcl::from(orientation_args); - println!("determine_user_specific_data"); - let multi_config = make_new_multi_config(&orientation_schema, vec![Box::new(orientation_vcl)])?; - let config_file_path = value_m!(multi_config, "config-file", PathBuf) - .expect("config-file parameter is not properly defaulted by clap"); - let config_user_specified = multi_config.occurrences_of("config-file") > 0; - let data_directory_specified = multi_config.occurrences_of("data-directory") > 0; - let real_user_specified = multi_config.occurrences_of("real-user") > 0; - let (real_user, data_directory_path, chain) = - real_user_data_directory_path_and_chain(dirs_wrapper, &multi_config); - let data_directory = match data_directory_path { - Some(data_dir) => data_dir, - None => data_directory_from_context(dirs_wrapper, &real_user, chain), + let (config_file, data_dir, mut real_user) = config_file_and_data_dir_from_enumerate(orientation_vcl.args()); + let config_user_specified = !config_file.is_empty(); + let data_directory_specified = !data_dir.is_empty(); + let real_user_specified = !real_user.is_empty(); + if real_user.is_empty() { + let multi_config = MultiConfig::try_new(&orientation_schema, vec![]); + match multi_config { + Ok(multi_config) => { + real_user = real_user_from_multi_config_or_populate(&multi_config, dirs_wrapper).to_string(); + } + Err(e) => return Err(e) + } + + } + let chain = match argument_from_enumerate(orientation_vcl.args(), "--chain".to_string()) { + Some(chain) => { + Chain::from(chain.as_str()) + }, + None => DEFAULT_CHAIN }; - let config_file_path = if config_file_path.is_relative() { - data_directory.join(config_file_path) - } else { - config_file_path + let real_user_split: Vec<&str> = real_user.split(":").collect(); + let real_user_obj = RealUser::new( + Some(real_user_split[0].parse::().expect("expected user id")), + Some(real_user_split[1].parse::().expect("expected user group")), + Some(PathBuf::from(real_user_split[2]))); + let data_directory = match data_dir.is_empty() { + false => PathBuf::from(data_dir), + true => data_directory_from_context(dirs_wrapper, &real_user_obj, chain), + }; + Ok(( + PathBuf::from(config_file), + config_user_specified, + data_directory, + data_directory_specified, + real_user_obj, + real_user_specified, + Box::new(orientation_vcl))) +} + +struct CombinedVcl { + content: Vec +} + +impl CombinedVcl { + fn len(&self) -> u32 { + *&self.content.as_slice().iter().count() as u32 + } +} + +fn create_preorientation_args(envargs: Vec, argstovec: Vec, app: &App) -> Result, ConfigFileVclError> { + let (env_config_file, env_data_dir, env_real_user) = config_file_and_data_dir_from_enumerate(envargs); + let (cmd_config_file, cmd_data_dir, cmd_real_user) = config_file_and_data_dir_from_enumerate(argstovec); + let mut combined_vcl: CombinedVcl = CombinedVcl { content: vec![] }; + let combine_vcl = | name: String, vcl: &mut CombinedVcl, cmd_str: &String, env_str: &String | { + if !cmd_str.is_empty() { + vcl.content.push(name); + vcl.content.push(cmd_str.to_string()); + } + else if !env_str.is_empty() { + vcl.content.push(name); + vcl.content.push(env_str.to_string()); + } + else { + vcl.content.push(name); + vcl.content.push("".to_string()); + } }; + combine_vcl("--data-directory".to_string(), &mut combined_vcl, &cmd_data_dir, &env_data_dir); + combine_vcl("--config-file".to_string(), &mut combined_vcl, &cmd_config_file, &env_config_file); + combine_vcl("--real-user".to_string(), &mut combined_vcl, &cmd_real_user, &env_real_user); + if combined_vcl.len() > 0 { + let (mut config_file, data_directory, _real_user) = config_file_and_data_dir_from_enumerate(combined_vcl.content); + if !config_file.is_empty() && + (!config_file.starts_with("/") && !config_file.starts_with("./") && !config_file.starts_with("../")) + && data_directory.is_empty() { + return Err(ConfigFileVclError::InvalidConfig( + PathBuf::from(&config_file), + "Config file defined in Environment with relative path needs data-directory to be defined too".to_string() + )); + } - Ok((config_file_path, config_user_specified, data_directory, data_directory_specified, real_user, real_user_specified)) + if config_file.starts_with("./") { + let pwd = current_dir().expect("expected current directory"); + config_file = config_file.replacen(".", pwd.to_string_lossy().to_string().as_str(), 1); + } + + let mut config_file_path = PathBuf::from(&config_file.to_string()); + let user_specified = !config_file.is_empty(); + if config_file_path.is_relative() && !data_directory.is_empty() { + config_file_path = PathBuf::from(data_directory.to_string()).join(config_file_path); + } + let config_file_vcl = match ConfigFileVcl::new(&config_file_path, user_specified) { + Ok(cfv) => Box::new(cfv), + Err(e) => return Err(e), + }; + let args = merge( + Box::new(EnvironmentVcl::new(app)), + config_file_vcl, + ); + let args = merge( + args, + Box::new(CommandLineVcl::new( + vec!["".to_string(), + "--config-file".to_string(), + config_file_path.to_string_lossy().to_string()] + )) + ); + Ok(args) + } else { + Ok(Box::new(EnvironmentVcl::new(app))) + } } pub fn initialize_database( @@ -253,7 +329,7 @@ mod tests { .param("--config-file", "booga.toml"); let args_vec: Vec = args.into(); - let (config_file_path, user_specified, _data_dir, _data_dir_specified, _real_user, _real_user_specified) = determine_user_specific_data( + let (config_file_path, user_specified, _data_dir, _data_dir_specified, _real_user, _real_user_specified, _preorientation) = determine_user_specific_data( &DirsWrapperReal {}, &determine_config_file_path_app(), args_vec.as_slice(), @@ -282,7 +358,7 @@ mod tests { ); std::env::set_var("MASQ_CONFIG_FILE", "booga.toml"); - let (config_file_path, user_specified, _data_dir, _data_dir_specified, _real_user, _real_user_specified) = determine_user_specific_data( + let (config_file_path, user_specified, _data_dir, _data_dir_specified, _real_user, _real_user_specified, _preorientation_args) = determine_user_specific_data( &DirsWrapperReal {}, &determine_config_file_path_app(), args_vec.as_slice(), @@ -305,7 +381,7 @@ mod tests { .param("--config-file", "/tmp/booga.toml"); let args_vec: Vec = args.into(); - let (config_file_path, user_specified, _data_dir, _data_dir_specified, _real_user, _real_user_specified) = determine_user_specific_data( + let (config_file_path, user_specified, _data_dir, _data_dir_specified, _real_user, _real_user_specified, _preorientation_args) = determine_user_specific_data( &DirsWrapperReal {}, &determine_config_file_path_app(), args_vec.as_slice(), diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 36cacf2ac..95faebd3b 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -139,20 +139,28 @@ pub fn server_initializer_collected_params<'a>( args: &[String], ) -> Result, ConfiguratorError> { let app = app_node(); - let (config_file_path, config_user_specified, data_directory, data_directory_specified, real_user, real_user_specified) = - determine_user_specific_data(dirs_wrapper, &app, args)?; - - println!("config_file_path: {} {:#?}", config_user_specified, &config_file_path); - //println!("config_file_vcl: {:#?}", config_file_vcl); - // let config_file_path_from_multiconfig = - // value_m!(multi_config, "config-file", PathBuf).expect("defaulted param"); - //let multi_config_vec = create_multi_config_vec(dirs_wrapper, &app, args); + let (config_file_path, + config_user_specified, + data_directory, + data_directory_specified, + real_user, + real_user_specified, + preorientation_args) = determine_user_specific_data(dirs_wrapper, &app, args)?; let mut full_multi_config_vec: Vec> = vec![ Box::new(EnvironmentVcl::new(&app)), - //config_file_vcl, - Box::new(CommandLineVcl::new(args.to_vec())), + preorientation_args ]; + let config_spcified_box: Box = Box::new(CommandLineVcl::new(vec![ + "".to_string(), "--config-file".to_string(), config_file_path.to_string_lossy().to_string(), + ])); + let config_unspcified_box: Box = Box::new(ComputedVcl::new(vec![ + "".to_string(), "--config-file".to_string(), config_file_path.to_string_lossy().to_string(), + ])); + match config_user_specified { + true => full_multi_config_vec.push(config_spcified_box), + false => full_multi_config_vec.push(config_unspcified_box) + }; let data_directory_spcified_box: Box = Box::new(CommandLineVcl::new(vec![ "".to_string(), "--data-directory".to_string(), data_directory.to_string_lossy().to_string(), ])); @@ -173,9 +181,9 @@ pub fn server_initializer_collected_params<'a>( true => full_multi_config_vec.push(real_user_spcified_box), false => full_multi_config_vec.push(real_user_unspcified_box) }; - //println!("full_multi_config_vec: {:#?}", full_multi_config_vec); + let full_multi_config = make_new_multi_config(&app,full_multi_config_vec)?; - //println!("full_multi_config: {:#?}", full_multi_config); + Ok(full_multi_config) } @@ -325,6 +333,7 @@ mod tests { use masq_lib::utils::{running_test, slice_of_strs_to_vec_of_strings}; use rustc_hex::FromHex; use std::convert::TryFrom; + use std::env::current_dir; use std::fs::{create_dir_all, File}; use std::io::Write; use std::path::{Path, PathBuf}; @@ -787,37 +796,138 @@ mod tests { } } + #[test] + fn server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot() { + running_test(); + let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot"); + let data_dir = &home_dir.join("data_dir"); + let config_file_relative = File::create(PathBuf::from("./generated").join("config.toml")).unwrap(); + fill_up_config_file(config_file_relative); + let env_vec_array = vec![ + ("MASQ_CONFIG_FILE", "./generated/config.toml"), + #[cfg(not(target_os = "windows"))] + ("MASQ_REAL_USER", "9999:9999:booga"), + ]; + env_vec_array.clone().into_iter() + .for_each (|(name, value)| std::env::set_var (name, value)); + let args = ArgsBuilder::new(); + let args_vec: Vec = args.into(); + let dir_wrapper = DirsWrapperMock::new() + .home_dir_result(Some(home_dir.clone())) + .data_dir_result(Some(data_dir.to_path_buf())); + + let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); + let env_multiconfig = result.unwrap(); + + match env_multiconfig.is_user_specified("--data-directory") { + true => (), + false => { + assert_eq!( + value_m!(env_multiconfig, "data-directory", String).unwrap(), + "wooga/data_dir/MASQ/polygon-mainnet".to_string() + ) + } + } + + match (env_multiconfig.is_user_specified("--config-file"), env_multiconfig.is_user_specified("--data-directory")) { + (true, true) => { + let pwd = PathBuf::from( value_m!(env_multiconfig, "data-directory", String).unwrap()); + assert_eq!( + value_m!(env_multiconfig, "config-file", String).unwrap(), + pwd.join(PathBuf::from( "generated/config.toml")).to_string_lossy().to_string() + ) + }, + (true, false) => { + let pwd = current_dir().unwrap(); + assert_eq!( + value_m!(env_multiconfig, "config-file", String).unwrap(), + pwd.join(PathBuf::from( "generated/config.toml")).to_string_lossy().to_string() + ) + }, + (_, _) => () + } + } + + #[test] + fn server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_data_directory() { + running_test(); + let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot"); + let data_dir = &home_dir.join("data_dir"); + let config_file_relative = File::create(PathBuf::from("./generated").join("config.toml")).unwrap(); + fill_up_config_file(config_file_relative); + let current_directory = current_dir().unwrap(); + vec![ + ("MASQ_CONFIG_FILE", "./generated/config.toml"), + ("MASQ_DATA_DIRECTORY", ¤t_directory.display().to_string().as_str()), + #[cfg(not(target_os = "windows"))] + ("MASQ_REAL_USER", "9999:9999:booga"), + ].into_iter() + .for_each (|(name, value)| std::env::set_var (name, value)); + let args = ArgsBuilder::new(); + let args_vec: Vec = args.into(); + let dir_wrapper = DirsWrapperMock::new() + .home_dir_result(Some(home_dir.clone())) + .data_dir_result(Some(data_dir.to_path_buf())); + + let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); + let env_multiconfig = result.unwrap(); + + match env_multiconfig.is_user_specified("--data-directory") { + true => { + assert_eq!( + &value_m!(env_multiconfig, "data-directory", String).unwrap(), + ¤t_directory.to_string_lossy().to_string() + ) + }, + false => () + } + + match env_multiconfig.is_user_specified("--real-user") { + true => { + assert_eq!( + &value_m!(env_multiconfig, "real-user", String).unwrap(), + "1002:1002:wooga" + ) + }, + false => () + } + + match (env_multiconfig.is_user_specified("--config-file"), env_multiconfig.is_user_specified("--data-directory")) { + (true, true) => { + let pwd = PathBuf::from( value_m!(env_multiconfig, "data-directory", String).unwrap()); + assert_eq!( + value_m!(env_multiconfig, "config-file", String).unwrap(), + pwd.join(PathBuf::from( "generated/config.toml")).to_string_lossy().to_string() + ) + }, + (_, _) => () + } + } + #[test] fn server_initializer_collected_params_combine_vlcs_properly() { running_test(); let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_combine_vlcs_properly"); let data_dir = &home_dir.join("data_dir"); let config_file = File::create(&home_dir.join("config.toml")).unwrap(); - let data_dir_vcl = create_dir_all(PathBuf::from("/tmp/cooga/masqhome")); + let current_directory = current_dir().unwrap(); let data_dir_sys = create_dir_all(PathBuf::from(home_dir.to_string_lossy().as_ref().to_owned() + "/data_dir/MASQ/polygon-mainnet")); { fill_up_config_file(config_file); - match data_dir_vcl { - Ok(..) => { - let config_file_vcl = File::create(PathBuf::from("/tmp/cooga/masqhome").join("config.toml")).unwrap(); - fill_up_config_file(config_file_vcl); - } - Err(e) => panic!("unable to create directory {}", e) - } match data_dir_sys { Ok(..) => { let config_file_vcl = File::create(PathBuf::from(home_dir.to_string_lossy().as_ref().to_owned() + "/data_dir/MASQ/polygon-mainnet").join("config.toml")).unwrap(); fill_up_config_file(config_file_vcl); } - Err(e) => panic!("unable to create directory {}", e) + Err(e) => panic!("unable to create config file: {}", e) } } let env_vec_array = vec![ - ("MASQ_CONFIG_FILE", "config.toml"), - ("MASQ_DATA_DIRECTORY", home_dir.to_str().unwrap()), + ("MASQ_CONFIG_FILE", "./generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home/config.toml"), + //("MASQ_DATA_DIRECTORY", home_dir.to_str().unwrap()), //#[cfg(not(target_os = "windows"))] - //("MASQ_REAL_USER", "9999:9999:booga"), + ("MASQ_REAL_USER", "9999:9999:booga"), ("MASQ_MIN_HOPS", "3"), ]; env_vec_array.clone().into_iter() @@ -831,14 +941,14 @@ mod tests { for (item, _vaue) in env_vec_array.to_vec().as_slice() { if item == &"MASQ_DATA_DIRECTORY" { env_data_directory = true; } } - println!("env_multiconfig creation"); + let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let env_multiconfig = result.unwrap(); let args = ArgsBuilder::new() //.param("--config-file", "config.toml") - .param("--data-directory", "/tmp/cooga/masqhome"); - //.param("--real-user", "1001:1001:cooga"); + .param("--data-directory", current_directory.join(Path::new("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home")).to_string_lossy().to_string().as_str()) + .param("--real-user", "1001:1001:cooga"); let args_vec: Vec = args.into(); let mut cmd_data_directory = false; for item in args_vec.clone().as_slice() { @@ -850,7 +960,7 @@ mod tests { assert_eq!( value_m!(env_multiconfig, "config-file", String).unwrap(), - "config.toml".to_string() + current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home/config.toml").to_string_lossy().to_string() ); match env_multiconfig.is_user_specified("--data-directory") { true => { @@ -887,13 +997,13 @@ mod tests { println!("real-user is inherited from Environment {}", value_m!(vcl_multiconfig, "real-user", String).unwrap()); assert_eq!( value_m!(vcl_multiconfig, "real-user", String).unwrap(), - "1002:1002:wooga".to_string() + "1001:1001:cooga".to_string() ) } false => { assert_eq!( value_m!(vcl_multiconfig, "real-user", String).unwrap(), - "1001:1001:cooga".to_string() + "1002:1002:wooga".to_string() ) } } @@ -919,7 +1029,7 @@ mod tests { true => { assert_eq!( value_m!(vcl_multiconfig, "data-directory", String).unwrap(), - "/tmp/cooga/masqhome".to_string() + current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home").to_string_lossy().to_string() ) } false => { @@ -936,10 +1046,6 @@ mod tests { () } } - assert_eq!( - value_m!(vcl_multiconfig, "config-file", String).unwrap(), - "config.toml".to_string() - ); } #[test] From 8aead2a44240a2d83d12928b84fb57cada3c1961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Fri, 15 Sep 2023 12:22:24 +0200 Subject: [PATCH 08/52] handle computed arguments in multiconfig, closure for server_initializer_collected_params, closure for keeping in node_configurator, final removal of redundant construction of multiconfig --- masq_lib/src/multi_config.rs | 110 ++++++++++++++++-- node/src/node_configurator/mod.rs | 36 +++--- .../node_configurator_standard.rs | 57 +++------ 3 files changed, 129 insertions(+), 74 deletions(-) diff --git a/masq_lib/src/multi_config.rs b/masq_lib/src/multi_config.rs index 8cc6574f5..c389bb23d 100644 --- a/masq_lib/src/multi_config.rs +++ b/masq_lib/src/multi_config.rs @@ -50,6 +50,7 @@ macro_rules! values_m { #[derive(Debug)] pub struct MultiConfig<'a> { arg_matches: ArgMatches<'a>, + computed_value_names: HashSet } impl<'a> MultiConfig<'a> { @@ -61,24 +62,41 @@ impl<'a> MultiConfig<'a> { schema: &App<'a, 'a>, vcls: Vec>, ) -> Result, ConfiguratorError> { + let mut computed_value_names = HashSet::new(); let initial: Box = Box::new(CommandLineVcl::new(vec![String::new()])); - let merged: Box = vcls - .into_iter() - .fold(initial, |so_far, vcl| merge(so_far, vcl)); + let vlc_copy = &vcls; + { + vlc_copy.into_iter().for_each(|vcl| { + vcl.vcl_args().iter().for_each(|vcl_arg| { + match vcl.is_computed() { + true => computed_value_names.insert(vcl_arg.name().to_string()), + false => computed_value_names.remove(&vcl_arg.name().to_string()) + }; + }) + }); + } + let merged: Box = vcls.into_iter().fold(initial, |so_far, vcl | -> Box { + merge(so_far, vcl) + }); + let arg_matches = match schema .clone() .get_matches_from_safe(merged.args().into_iter()) { - Ok(matches) => matches, - Err(e) => match e.kind { - clap::ErrorKind::HelpDisplayed | clap::ErrorKind::VersionDisplayed => { - unreachable!("The program's entry check failed to catch this.") + Ok(matches) => { + matches + }, + Err(e) => { + match e.kind { + clap::ErrorKind::HelpDisplayed | clap::ErrorKind::VersionDisplayed => { + unreachable!("The program's entry check failed to catch this.") + } + _ => return Err(Self::make_configurator_error(e)), } - _ => return Err(Self::make_configurator_error(e)), }, }; - Ok(MultiConfig { arg_matches }) + Ok(MultiConfig { arg_matches, computed_value_names }) } fn check_for_invalid_value_err( @@ -139,6 +157,13 @@ impl<'a> MultiConfig<'a> { ConfiguratorError::required("", &format!("Unfamiliar message: {}", e.message)) } + pub fn is_user_specified(&self, value_name: &str) -> bool { + match self.computed_value_names.contains(value_name) { + true => false, + false => true + } + } + pub fn occurrences_of(&self, parameter: &str) -> u64 { self.arg_matches.occurrences_of(parameter) } @@ -224,6 +249,7 @@ impl NameOnlyVclArg { pub trait VirtualCommandLine { fn vcl_args(&self) -> Vec<&dyn VclArg>; fn args(&self) -> Vec; + fn is_computed(&self) -> bool { false } } impl Debug for dyn VirtualCommandLine { @@ -263,6 +289,24 @@ pub fn merge( }) } +pub struct ComputedVcl { + vcl_args: Vec>, +} + +impl VirtualCommandLine for ComputedVcl { + fn vcl_args(&self) -> Vec<&dyn VclArg> { + vcl_args_to_vcl_args(&self.vcl_args) + } + + fn args(&self) -> Vec { + vcl_args_to_args(&self.vcl_args) + } + + fn is_computed(&self) -> bool { + true + } +} + pub struct CommandLineVcl { vcl_args: Vec>, } @@ -283,6 +327,33 @@ impl From>> for CommandLineVcl { } } +impl ComputedVcl { + pub fn new(mut args: Vec) -> ComputedVcl { + args.remove(0); // remove command + let mut vcl_args = vec![]; + while let Some(vcl_arg) = Self::next_vcl_arg(&mut args) { + vcl_args.push(vcl_arg); + } + ComputedVcl { vcl_args } + } + + fn next_vcl_arg(args: &mut Vec) -> Option> { + if args.is_empty() { + return None; + } + let name = args.remove(0); + if !name.starts_with("--") { + panic!("Expected option beginning with '--', not {}", name) + } + if args.is_empty() || args[0].starts_with("--") { + Some(Box::new(NameOnlyVclArg::new(&name))) + } else { + let value = args.remove(0); + Some(Box::new(NameValueVclArg::new(&name, &value))) + } + } +} + impl CommandLineVcl { pub fn new(mut args: Vec) -> CommandLineVcl { args.remove(0); // remove command @@ -347,6 +418,7 @@ impl EnvironmentVcl { } } +#[derive(Debug)] pub struct ConfigFileVcl { vcl_args: Vec>, } @@ -360,6 +432,16 @@ impl VirtualCommandLine for ConfigFileVcl { vcl_args_to_args(&self.vcl_args) } } +/* +impl VirtualCommandLine for dyn VclArg { + fn vcl_args(&self) -> Vec<&dyn VclArg> { + vcl_args_to_vcl_args(&[Box::new(self.vcl_args().as_slice())]) + } + + fn args(&self) -> Vec { + vcl_args_to_args(&[Box::new(self.vcl_args().as_slice())]) + } +}*/ #[derive(Debug)] pub enum ConfigFileVclError { @@ -491,8 +573,14 @@ fn append(ts: Vec, t: T) -> Vec { #[cfg(not(feature = "no_test_share"))] impl<'a> MultiConfig<'a> { - pub fn new_test_only(arg_matches: ArgMatches<'a>) -> Self { - Self { arg_matches } + pub fn new_test_only( + arg_matches: ArgMatches<'a>, + computed_value_names: HashSet + ) -> Self { + Self { + arg_matches, + computed_value_names + } } } diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index b2aefcd7d..0513c70ff 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -18,10 +18,7 @@ use dirs::{data_local_dir, home_dir}; use masq_lib::blockchains::chains::Chain; use masq_lib::constants::DEFAULT_CHAIN; use masq_lib::multi_config::{merge, CommandLineVcl, EnvironmentVcl, MultiConfig, VclArg, VirtualCommandLine, ConfigFileVcl, ConfigFileVclError}; -use masq_lib::shared_schema::{ - chain_arg, config_file_arg, data_directory_arg, real_user_arg, ConfiguratorError, - DATA_DIRECTORY_HELP, -}; +use masq_lib::shared_schema::{config_file_arg, data_directory_arg, ConfiguratorError,DATA_DIRECTORY_HELP}; use masq_lib::utils::{add_masq_and_chain_directories, localhost}; use std::net::{SocketAddr, TcpListener}; use std::path::{Path, PathBuf}; @@ -30,7 +27,7 @@ pub trait NodeConfigurator { fn configure(&self, multi_config: &MultiConfig) -> Result; } -fn config_file_and_data_dir_from_enumerate(configs: Vec) -> (String, String, String) { +fn config_file_real_user_data_dir_from_enumerate(configs: Vec) -> (String, String, String) { let config_match = argument_from_enumerate(configs.clone(), "--config-file".to_string()).unwrap_or("".to_string()); let data_dir_match = argument_from_enumerate(configs.clone(), "--data-directory".to_string()).unwrap_or("".to_string()); let real_user_match = argument_from_enumerate(configs, "--real-user".to_string()).unwrap_or("".to_string()); @@ -51,12 +48,7 @@ pub fn determine_user_specific_data ( dirs_wrapper: &dyn DirsWrapper, app: &App, args: &[String], -) -> Result<(PathBuf, bool, PathBuf, bool, RealUser, bool, Box), ConfiguratorError> { //, Box - let orientation_schema = App::new("MASQNode") - .arg(chain_arg()) - .arg(real_user_arg()) - .arg(data_directory_arg(DATA_DIRECTORY_HELP)) - .arg(config_file_arg()); +) -> Result<(PathBuf, bool, PathBuf, bool, RealUser, bool, Box), ConfiguratorError> { let env_args = Box::new(EnvironmentVcl::new(&app)).args(); let args_to_vec = args.to_vec(); let pre_orientation_args = match create_preorientation_args(env_args, args_to_vec.clone(), &app) { @@ -78,19 +70,12 @@ pub fn determine_user_specific_data ( .map(|vcl_arg| vcl_arg.dup()) .collect(); let orientation_vcl = CommandLineVcl::from(orientation_args); - let (config_file, data_dir, mut real_user) = config_file_and_data_dir_from_enumerate(orientation_vcl.args()); + let (config_file, data_dir, mut real_user) = config_file_real_user_data_dir_from_enumerate(orientation_vcl.args()); let config_user_specified = !config_file.is_empty(); let data_directory_specified = !data_dir.is_empty(); let real_user_specified = !real_user.is_empty(); if real_user.is_empty() { - let multi_config = MultiConfig::try_new(&orientation_schema, vec![]); - match multi_config { - Ok(multi_config) => { - real_user = real_user_from_multi_config_or_populate(&multi_config, dirs_wrapper).to_string(); - } - Err(e) => return Err(e) - } - + real_user = RealUser::new(None, None, None).populate(dirs_wrapper).to_string(); } let chain = match argument_from_enumerate(orientation_vcl.args(), "--chain".to_string()) { Some(chain) => { @@ -128,8 +113,8 @@ impl CombinedVcl { } fn create_preorientation_args(envargs: Vec, argstovec: Vec, app: &App) -> Result, ConfigFileVclError> { - let (env_config_file, env_data_dir, env_real_user) = config_file_and_data_dir_from_enumerate(envargs); - let (cmd_config_file, cmd_data_dir, cmd_real_user) = config_file_and_data_dir_from_enumerate(argstovec); + let (env_config_file, env_data_dir, env_real_user) = config_file_real_user_data_dir_from_enumerate(envargs); + let (cmd_config_file, cmd_data_dir, cmd_real_user) = config_file_real_user_data_dir_from_enumerate(argstovec); let mut combined_vcl: CombinedVcl = CombinedVcl { content: vec![] }; let combine_vcl = | name: String, vcl: &mut CombinedVcl, cmd_str: &String, env_str: &String | { if !cmd_str.is_empty() { @@ -148,8 +133,13 @@ fn create_preorientation_args(envargs: Vec, argstovec: Vec, app: combine_vcl("--data-directory".to_string(), &mut combined_vcl, &cmd_data_dir, &env_data_dir); combine_vcl("--config-file".to_string(), &mut combined_vcl, &cmd_config_file, &env_config_file); combine_vcl("--real-user".to_string(), &mut combined_vcl, &cmd_real_user, &env_real_user); + + // In case we define config file in ENV and we can find real-user in that file, therefore we need + // to specify also data-directory in Environment or Command line, to be Node able figure out the + // config file location, when config file is specified without absolute path or current directory eg.: "./" or "../" + if combined_vcl.len() > 0 { - let (mut config_file, data_directory, _real_user) = config_file_and_data_dir_from_enumerate(combined_vcl.content); + let (mut config_file, data_directory, _real_user) = config_file_real_user_data_dir_from_enumerate(combined_vcl.content); if !config_file.is_empty() && (!config_file.starts_with("/") && !config_file.starts_with("./") && !config_file.starts_with("../")) && data_directory.is_empty() { diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 95faebd3b..ef1a18e5b 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -145,42 +145,21 @@ pub fn server_initializer_collected_params<'a>( data_directory_specified, real_user, real_user_specified, - preorientation_args) = determine_user_specific_data(dirs_wrapper, &app, args)?; + pre_orientation_args) = determine_user_specific_data(dirs_wrapper, &app, args)?; let mut full_multi_config_vec: Vec> = vec![ Box::new(EnvironmentVcl::new(&app)), - preorientation_args + pre_orientation_args ]; - let config_spcified_box: Box = Box::new(CommandLineVcl::new(vec![ - "".to_string(), "--config-file".to_string(), config_file_path.to_string_lossy().to_string(), - ])); - let config_unspcified_box: Box = Box::new(ComputedVcl::new(vec![ - "".to_string(), "--config-file".to_string(), config_file_path.to_string_lossy().to_string(), - ])); - match config_user_specified { - true => full_multi_config_vec.push(config_spcified_box), - false => full_multi_config_vec.push(config_unspcified_box) - }; - let data_directory_spcified_box: Box = Box::new(CommandLineVcl::new(vec![ - "".to_string(), "--data-directory".to_string(), data_directory.to_string_lossy().to_string(), - ])); - let data_directory_unspcified_box: Box = Box::new(ComputedVcl::new(vec![ - "".to_string(), "--data-directory".to_string(), data_directory.to_string_lossy().to_string(), - ])); - match data_directory_specified { - true => full_multi_config_vec.push(data_directory_spcified_box), - false => full_multi_config_vec.push(data_directory_unspcified_box) - }; - let real_user_spcified_box: Box = Box::new(CommandLineVcl::new(vec![ - "".to_string(), "--real-user".to_string(), real_user.to_string(), - ])); - let real_user_unspcified_box: Box = Box::new(ComputedVcl::new(vec![ - "".to_string(), "--real-user".to_string(), real_user.to_string(), - ])); - match real_user_specified { - true => full_multi_config_vec.push(real_user_spcified_box), - false => full_multi_config_vec.push(real_user_unspcified_box) + let mut fill_specified_or_unspecified_box = |key: String, value: String, specified: bool | { + match specified { + true => full_multi_config_vec.push(Box::new(CommandLineVcl::new(vec!["".to_string(), key, value,]))), + false => full_multi_config_vec.push(Box::new(ComputedVcl::new(vec!["".to_string(), key, value,]))) + }; }; + fill_specified_or_unspecified_box("--config-file".to_string(), config_file_path.to_string_lossy().to_string(), config_user_specified); + fill_specified_or_unspecified_box("--data-directory".to_string(), data_directory.to_string_lossy().to_string(), data_directory_specified); + fill_specified_or_unspecified_box("--real-user".to_string(), real_user.to_string(), real_user_specified); let full_multi_config = make_new_multi_config(&app,full_multi_config_vec)?; @@ -760,7 +739,7 @@ mod tests { fn fill_up_config_file(mut config_file: File) { { config_file - .write_all(b"real-user = \"1002:1002:wooga\"\n") + .write_all(b"real-user = \"1002:1002:/home/wooga\"\n") .unwrap(); config_file .write_all(b"blockchain-service-url = \"https://www.mainnet2.com\"\n") @@ -824,7 +803,7 @@ mod tests { false => { assert_eq!( value_m!(env_multiconfig, "data-directory", String).unwrap(), - "wooga/data_dir/MASQ/polygon-mainnet".to_string() + "/home/wooga/data_dir/MASQ/polygon-mainnet".to_string() ) } } @@ -886,7 +865,7 @@ mod tests { true => { assert_eq!( &value_m!(env_multiconfig, "real-user", String).unwrap(), - "1002:1002:wooga" + "1002:1002:/home/wooga" ) }, false => () @@ -926,9 +905,8 @@ mod tests { let env_vec_array = vec![ ("MASQ_CONFIG_FILE", "./generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home/config.toml"), //("MASQ_DATA_DIRECTORY", home_dir.to_str().unwrap()), - //#[cfg(not(target_os = "windows"))] + #[cfg(not(target_os = "windows"))] ("MASQ_REAL_USER", "9999:9999:booga"), - ("MASQ_MIN_HOPS", "3"), ]; env_vec_array.clone().into_iter() .for_each (|(name, value)| std::env::set_var (name, value)); @@ -946,7 +924,6 @@ mod tests { let env_multiconfig = result.unwrap(); let args = ArgsBuilder::new() - //.param("--config-file", "config.toml") .param("--data-directory", current_directory.join(Path::new("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home")).to_string_lossy().to_string().as_str()) .param("--real-user", "1001:1001:cooga"); let args_vec: Vec = args.into(); @@ -974,13 +951,13 @@ mod tests { () } } - //println!("env_multiconfig: {:#?}", env_multiconfig); + #[cfg(not(target_os = "windows"))] match env_multiconfig.is_user_specified("--real-user") { true => { assert_eq!( value_m!(env_multiconfig, "real-user", String).unwrap(), - "1002:1002:wooga".to_string() + "1002:1002:/home/wooga".to_string() ) } false => { @@ -1003,7 +980,7 @@ mod tests { false => { assert_eq!( value_m!(vcl_multiconfig, "real-user", String).unwrap(), - "1002:1002:wooga".to_string() + "1002:1002:/home/wooga".to_string() ) } } From 61678568c6d87b05fd8e4c3f9b66dde5c7216be9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Thu, 21 Sep 2023 14:41:12 +0200 Subject: [PATCH 09/52] computing env_args and cmd_args in create_preorientation_args fn! --- masq_lib/src/multi_config.rs | 33 +-- node/src/daemon/setup_reporter.rs | 4 +- node/src/node_configurator/mod.rs | 212 +++++++++++------- .../node_configurator_standard.rs | 72 +++++- 4 files changed, 209 insertions(+), 112 deletions(-) diff --git a/masq_lib/src/multi_config.rs b/masq_lib/src/multi_config.rs index c389bb23d..0540710ed 100644 --- a/masq_lib/src/multi_config.rs +++ b/masq_lib/src/multi_config.rs @@ -62,21 +62,18 @@ impl<'a> MultiConfig<'a> { schema: &App<'a, 'a>, vcls: Vec>, ) -> Result, ConfiguratorError> { - let mut computed_value_names = HashSet::new(); let initial: Box = Box::new(CommandLineVcl::new(vec![String::new()])); - let vlc_copy = &vcls; - { - vlc_copy.into_iter().for_each(|vcl| { - vcl.vcl_args().iter().for_each(|vcl_arg| { - match vcl.is_computed() { - true => computed_value_names.insert(vcl_arg.name().to_string()), - false => computed_value_names.remove(&vcl_arg.name().to_string()) - }; - }) - }); - } - let merged: Box = vcls.into_iter().fold(initial, |so_far, vcl | -> Box { + let mut computed_value_names = HashSet::new(); + vcls.iter().for_each(|vcl| { + vcl.vcl_args().iter().for_each(|vcl_arg| { + match vcl.is_computed() { + true => computed_value_names.insert(vcl_arg.name().to_string()), + false => computed_value_names.remove(&vcl_arg.name().to_string()) + }; + }) + }); + let merged = vcls.into_iter().fold(initial, |so_far, vcl | { merge(so_far, vcl) }); @@ -432,16 +429,6 @@ impl VirtualCommandLine for ConfigFileVcl { vcl_args_to_args(&self.vcl_args) } } -/* -impl VirtualCommandLine for dyn VclArg { - fn vcl_args(&self) -> Vec<&dyn VclArg> { - vcl_args_to_vcl_args(&[Box::new(self.vcl_args().as_slice())]) - } - - fn args(&self) -> Vec { - vcl_args_to_args(&[Box::new(self.vcl_args().as_slice())]) - } -}*/ #[derive(Debug)] pub enum ConfigFileVclError { diff --git a/node/src/daemon/setup_reporter.rs b/node/src/daemon/setup_reporter.rs index 79b4a34b8..fd060b145 100644 --- a/node/src/daemon/setup_reporter.rs +++ b/node/src/daemon/setup_reporter.rs @@ -495,9 +495,9 @@ impl SetupReporterReal { Some(command_line) => command_line, None => vec![], }; - let (config_file_path, user_specified, _data_directory, _data_directory_specified, _real_user, _real_user_specified, _preorientation_args) = + let user_specific_data = determine_user_specific_data(dirs_wrapper, &app, &command_line)?; - let config_file_vcl = match ConfigFileVcl::new(&config_file_path, user_specified) { + let config_file_vcl = match ConfigFileVcl::new(&user_specific_data.config_file, user_specific_data.config_file_specified) { Ok(cfv) => cfv, Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), }; diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index 0513c70ff..8ab7f9d23 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -27,14 +27,14 @@ pub trait NodeConfigurator { fn configure(&self, multi_config: &MultiConfig) -> Result; } -fn config_file_real_user_data_dir_from_enumerate(configs: Vec) -> (String, String, String) { - let config_match = argument_from_enumerate(configs.clone(), "--config-file".to_string()).unwrap_or("".to_string()); - let data_dir_match = argument_from_enumerate(configs.clone(), "--data-directory".to_string()).unwrap_or("".to_string()); - let real_user_match = argument_from_enumerate(configs, "--real-user".to_string()).unwrap_or("".to_string()); +fn config_file_data_dir_real_user_from_enumerate(configs: &Vec) -> (String, String, String) { + let config_match = argument_from_enumerate(&configs, "--config-file").unwrap_or("".to_string()); + let data_dir_match = argument_from_enumerate(&configs, "--data-directory").unwrap_or("".to_string()); + let real_user_match = argument_from_enumerate(configs, "--real-user").unwrap_or("".to_string()); (config_match, data_dir_match, real_user_match) } -fn argument_from_enumerate(configs: Vec, needle: String) -> Option { +fn argument_from_enumerate(configs: &Vec, needle: &str) -> Option { let mut arg_match = None; for (i, arg) in configs.iter().enumerate() { if arg.as_str() == needle { @@ -44,21 +44,27 @@ fn argument_from_enumerate(configs: Vec, needle: String) -> Option +} + pub fn determine_user_specific_data ( dirs_wrapper: &dyn DirsWrapper, app: &App, args: &[String], -) -> Result<(PathBuf, bool, PathBuf, bool, RealUser, bool, Box), ConfiguratorError> { - let env_args = Box::new(EnvironmentVcl::new(&app)).args(); - let args_to_vec = args.to_vec(); - let pre_orientation_args = match create_preorientation_args(env_args, args_to_vec.clone(), &app) { +) -> Result { + + let pre_orientation_args = match create_preorientation_args(args, &app, dirs_wrapper) { Ok(pre_orientation_args) => pre_orientation_args, Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())) }; - let orientation_args: Vec> = merge( - pre_orientation_args, - Box::new(CommandLineVcl::new(args_to_vec)) - ) + let orientation_args: Vec> = pre_orientation_args .vcl_args() .into_iter() .filter(|vcl_arg| { @@ -70,14 +76,14 @@ pub fn determine_user_specific_data ( .map(|vcl_arg| vcl_arg.dup()) .collect(); let orientation_vcl = CommandLineVcl::from(orientation_args); - let (config_file, data_dir, mut real_user) = config_file_real_user_data_dir_from_enumerate(orientation_vcl.args()); + let (mut config_file, data_dir, mut real_user) = config_file_data_dir_real_user_from_enumerate(&orientation_vcl.args()); let config_user_specified = !config_file.is_empty(); let data_directory_specified = !data_dir.is_empty(); let real_user_specified = !real_user.is_empty(); if real_user.is_empty() { real_user = RealUser::new(None, None, None).populate(dirs_wrapper).to_string(); } - let chain = match argument_from_enumerate(orientation_vcl.args(), "--chain".to_string()) { + let chain = match argument_from_enumerate(&orientation_vcl.args(), "--chain") { Some(chain) => { Chain::from(chain.as_str()) }, @@ -92,14 +98,27 @@ pub fn determine_user_specific_data ( false => PathBuf::from(data_dir), true => data_directory_from_context(dirs_wrapper, &real_user_obj, chain), }; - Ok(( - PathBuf::from(config_file), - config_user_specified, + // let final_orientation_args; + // if !config_user_specified { + // config_file = data_directory.join("config.toml").to_string_lossy().to_string(); + // final_orientation_args = merge( + // Box::new(orientation_vcl), + // Box::new(CommandLineVcl::new(vec!["".to_string(), "--config-file".to_string(), config_file.clone()])) + // ); + // } else { + // final_orientation_args = Box::new(orientation_vcl); + // } + + Ok( UserSpecificData { + config_file: PathBuf::from(config_file), + config_file_specified: config_user_specified, data_directory, data_directory_specified, - real_user_obj, + real_user: real_user_obj, real_user_specified, - Box::new(orientation_vcl))) + pre_orientation_args: Box::new(orientation_vcl) + } + ) } struct CombinedVcl { @@ -112,9 +131,11 @@ impl CombinedVcl { } } -fn create_preorientation_args(envargs: Vec, argstovec: Vec, app: &App) -> Result, ConfigFileVclError> { - let (env_config_file, env_data_dir, env_real_user) = config_file_real_user_data_dir_from_enumerate(envargs); - let (cmd_config_file, cmd_data_dir, cmd_real_user) = config_file_real_user_data_dir_from_enumerate(argstovec); +fn create_preorientation_args(cmd_args: &[String], app: &App, dirs_wrapper: &dyn DirsWrapper,) -> Result, ConfigFileVclError> { + let env_args = Box::new(EnvironmentVcl::new(&app)).args(); + let cmd_args = cmd_args.to_vec(); + let (env_config_file, env_data_dir, env_real_user) = config_file_data_dir_real_user_from_enumerate(&env_args); + let (cmd_config_file, cmd_data_dir, cmd_real_user) = config_file_data_dir_real_user_from_enumerate(&cmd_args); let mut combined_vcl: CombinedVcl = CombinedVcl { content: vec![] }; let combine_vcl = | name: String, vcl: &mut CombinedVcl, cmd_str: &String, env_str: &String | { if !cmd_str.is_empty() { @@ -139,36 +160,79 @@ fn create_preorientation_args(envargs: Vec, argstovec: Vec, app: // config file location, when config file is specified without absolute path or current directory eg.: "./" or "../" if combined_vcl.len() > 0 { - let (mut config_file, data_directory, _real_user) = config_file_real_user_data_dir_from_enumerate(combined_vcl.content); - if !config_file.is_empty() && - (!config_file.starts_with("/") && !config_file.starts_with("./") && !config_file.starts_with("../")) - && data_directory.is_empty() { - return Err(ConfigFileVclError::InvalidConfig( - PathBuf::from(&config_file), - "Config file defined in Environment with relative path needs data-directory to be defined too".to_string() - )); + let (mut config_file, data_directory, real_user) = config_file_data_dir_real_user_from_enumerate(&combined_vcl.content); + if !config_file.is_empty() { + if (!config_file.starts_with("/") && !config_file.starts_with("./") && !config_file.starts_with("../")) + && data_directory.is_empty() { + return Err(ConfigFileVclError::InvalidConfig( + PathBuf::from(&config_file), + "Config file defined in Environment with relative path needs data-directory to be defined too".to_string() + )); + } + + if config_file.starts_with("./") { + let pwd = current_dir().expect("expected current directory"); + config_file = config_file.replacen(".", pwd.to_string_lossy().to_string().as_str(), 1); + } + + let mut config_file_path = PathBuf::from(&config_file.to_string()); + let user_specified = !config_file.is_empty(); + if config_file_path.is_relative() && !data_directory.is_empty() { + config_file_path = PathBuf::from(data_directory.to_string()).join(config_file_path); + } + let config_file_vcl = match ConfigFileVcl::new(&config_file_path, user_specified) { + Ok(cfv) => Box::new(cfv), + Err(e) => return Err(e), + }; + let args = merge( + Box::new(EnvironmentVcl::new(app)), + config_file_vcl, + ); + let args = merge( + args, + Box::new(CommandLineVcl::new( + vec!["".to_string(), + "--config-file".to_string(), + config_file_path.to_string_lossy().to_string()] + )) + ); + Ok(args) + } else { + let config_file = PathBuf::from("config.toml"); + let config_file_path = match data_directory.as_str() { + "" => PathBuf::from().join(config_file), + _ => + }; + let args = merge( + Box::new(EnvironmentVcl::new(app)), + Box::new(CommandLineVcl::new( + vec!["".to_string(), + "--config-file".to_string(), + config_file_path.to_string_lossy().to_string()] + )) + ); + Ok(args) } - if config_file.starts_with("./") { - let pwd = current_dir().expect("expected current directory"); - config_file = config_file.replacen(".", pwd.to_string_lossy().to_string().as_str(), 1); - } - - let mut config_file_path = PathBuf::from(&config_file.to_string()); - let user_specified = !config_file.is_empty(); - if config_file_path.is_relative() && !data_directory.is_empty() { - config_file_path = PathBuf::from(data_directory.to_string()).join(config_file_path); - } - let config_file_vcl = match ConfigFileVcl::new(&config_file_path, user_specified) { - Ok(cfv) => Box::new(cfv), - Err(e) => return Err(e), + } else { + //Ok(Box::new(EnvironmentVcl::new(app))) + let config_file = PathBuf::from("config.toml"); + let real_user = &RealUser::new(None, None, None).populate(dirs_wrapper); + let cmd_chain = argument_from_enumerate(&cmd_args, "--chain").expect("expected chain"); + let env_chain = argument_from_enumerate(&env_args, "--chain").expect("expected chain"); + let chain = match cmd_chain.is_empty() { + true => { + match env_chain.is_empty() { + true => DEFAULT_CHAIN.rec().literal_identifier, + false => env_chain.as_str() + } + } + false => cmd_chain.as_str() }; + let data_dir = data_directory_from_context(dirs_wrapper, real_user, Chain::from(chain)); + let config_file_path = PathBuf::from(data_dir).join(config_file); let args = merge( Box::new(EnvironmentVcl::new(app)), - config_file_vcl, - ); - let args = merge( - args, Box::new(CommandLineVcl::new( vec!["".to_string(), "--config-file".to_string(), @@ -176,8 +240,6 @@ fn create_preorientation_args(envargs: Vec, argstovec: Vec, app: )) ); Ok(args) - } else { - Ok(Box::new(EnvironmentVcl::new(app))) } } @@ -319,18 +381,18 @@ mod tests { .param("--config-file", "booga.toml"); let args_vec: Vec = args.into(); - let (config_file_path, user_specified, _data_dir, _data_dir_specified, _real_user, _real_user_specified, _preorientation) = determine_user_specific_data( + let user_specific_data = determine_user_specific_data( &DirsWrapperReal {}, &determine_config_file_path_app(), args_vec.as_slice(), ) .unwrap(); assert_eq!( - &format!("{}", config_file_path.parent().unwrap().display()), - &data_directory.to_string_lossy().to_string(), + &format!("{}", user_specific_data.config_file.parent().unwrap().display()), + &user_specific_data.data_directory.to_string_lossy().to_string(), ); - assert_eq!("booga.toml", config_file_path.file_name().unwrap()); - assert_eq!(true, user_specified); + assert_eq!("booga.toml", user_specific_data.config_file.file_name().unwrap()); + assert_eq!(true, user_specific_data.real_user_specified); } #[test] @@ -348,18 +410,18 @@ mod tests { ); std::env::set_var("MASQ_CONFIG_FILE", "booga.toml"); - let (config_file_path, user_specified, _data_dir, _data_dir_specified, _real_user, _real_user_specified, _preorientation_args) = determine_user_specific_data( + let user_specific_data = determine_user_specific_data( &DirsWrapperReal {}, &determine_config_file_path_app(), args_vec.as_slice(), ) .unwrap(); assert_eq!( - format!("{}", config_file_path.parent().unwrap().display()), - data_directory.to_string_lossy().to_string(), + format!("{}", user_specific_data.config_file.parent().unwrap().display()), + user_specific_data.data_directory.to_string_lossy().to_string(), ); - assert_eq!("booga.toml", config_file_path.file_name().unwrap()); - assert_eq!(true, user_specified); + assert_eq!("booga.toml", user_specific_data.config_file.file_name().unwrap()); + assert_eq!(true, user_specific_data.real_user_specified); } #[cfg(not(target_os = "windows"))] @@ -371,7 +433,7 @@ mod tests { .param("--config-file", "/tmp/booga.toml"); let args_vec: Vec = args.into(); - let (config_file_path, user_specified, _data_dir, _data_dir_specified, _real_user, _real_user_specified, _preorientation_args) = determine_user_specific_data( + let user_specific_data = determine_user_specific_data( &DirsWrapperReal {}, &determine_config_file_path_app(), args_vec.as_slice(), @@ -380,9 +442,9 @@ mod tests { assert_eq!( "/tmp/booga.toml", - &format!("{}", config_file_path.display()) + &format!("{}", user_specific_data.config_file.display()) ); - assert_eq!(true, user_specified); + assert_eq!(true, user_specific_data.real_user_specified); } #[cfg(target_os = "windows")] @@ -394,7 +456,7 @@ mod tests { .param("--config-file", r"\tmp\booga.toml"); let args_vec: Vec = args.into(); - let (config_file_path, user_specified, _data_dir, _real_user) = determine_fundamentals( + let user_specific_data = determine_user_specific_data( &DirsWrapperReal {}, &determine_config_file_path_app(), args_vec.as_slice(), @@ -403,9 +465,9 @@ mod tests { assert_eq!( r"\tmp\booga.toml", - &format!("{}", config_file_path.display()) + &format!("{}", user_specific_data.config_file.display()) ); - assert_eq!(true, user_specified); + assert_eq!(true, user_specific_data.real_user_specified); } #[cfg(target_os = "windows")] @@ -417,7 +479,7 @@ mod tests { .param("--config-file", r"c:\tmp\booga.toml"); let args_vec: Vec = args.into(); - let (config_file_path, user_specified, _data_dir, _real_user) = determine_fundamentals( + let user_specific_data = determine_user_specific_data( &DirsWrapperReal {}, &determine_config_file_path_app(), args_vec.as_slice(), @@ -426,9 +488,9 @@ mod tests { assert_eq!( r"c:\tmp\booga.toml", - &format!("{}", config_file_path.display()) + &format!("{}", user_specific_data.config_file.display()) ); - assert_eq!(true, user_specified); + assert_eq!(true, user_specific_data.real_user_specified); } #[cfg(target_os = "windows")] @@ -440,7 +502,7 @@ mod tests { .param("--config-file", r"\\TMP\booga.toml"); let args_vec: Vec = args.into(); - let (config_file_path, user_specified, _data_dir, _real_user) = determine_fundamentals( + let user_specific_data = determine_user_specific_data( &DirsWrapperReal {}, &determine_config_file_path_app(), args_vec.as_slice(), @@ -449,9 +511,9 @@ mod tests { assert_eq!( r"\\TMP\booga.toml", - &format!("{}", config_file_path.display()) + &format!("{}", user_specific_data.config_file.display()) ); - assert_eq!(true, user_specified); + assert_eq!(true, user_specific_data.real_user_specified); } #[cfg(target_os = "windows")] @@ -464,7 +526,7 @@ mod tests { .param("--config-file", r"c:tmp\booga.toml"); let args_vec: Vec = args.into(); - let (config_file_path, user_specified, _data_dir, _real_user) = determine_fundamentals( + let user_specific_data = determine_user_specific_data( &DirsWrapperReal {}, &determine_config_file_path_app(), args_vec.as_slice(), @@ -473,9 +535,9 @@ mod tests { assert_eq!( r"c:tmp\booga.toml", - &format!("{}", config_file_path.display()) + &format!("{}", user_specific_data.config_file.display()) ); - assert_eq!(true, user_specified); + assert_eq!(true, user_specific_data.real_user_specified); } #[test] diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index ef1a18e5b..3bfb68ad1 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -139,16 +139,10 @@ pub fn server_initializer_collected_params<'a>( args: &[String], ) -> Result, ConfiguratorError> { let app = app_node(); - let (config_file_path, - config_user_specified, - data_directory, - data_directory_specified, - real_user, - real_user_specified, - pre_orientation_args) = determine_user_specific_data(dirs_wrapper, &app, args)?; + let user_specific_data = determine_user_specific_data(dirs_wrapper, &app, args)?; let mut full_multi_config_vec: Vec> = vec![ Box::new(EnvironmentVcl::new(&app)), - pre_orientation_args + user_specific_data.pre_orientation_args ]; let mut fill_specified_or_unspecified_box = |key: String, value: String, specified: bool | { @@ -157,9 +151,9 @@ pub fn server_initializer_collected_params<'a>( false => full_multi_config_vec.push(Box::new(ComputedVcl::new(vec!["".to_string(), key, value,]))) }; }; - fill_specified_or_unspecified_box("--config-file".to_string(), config_file_path.to_string_lossy().to_string(), config_user_specified); - fill_specified_or_unspecified_box("--data-directory".to_string(), data_directory.to_string_lossy().to_string(), data_directory_specified); - fill_specified_or_unspecified_box("--real-user".to_string(), real_user.to_string(), real_user_specified); + fill_specified_or_unspecified_box("--config-file".to_string(), user_specific_data.config_file.to_string_lossy().to_string(), user_specific_data.config_file_specified); + fill_specified_or_unspecified_box("--data-directory".to_string(), user_specific_data.data_directory.to_string_lossy().to_string(), user_specific_data.data_directory_specified); + fill_specified_or_unspecified_box("--real-user".to_string(), user_specific_data.real_user.to_string(), user_specific_data.real_user_specified); let full_multi_config = make_new_multi_config(&app,full_multi_config_vec)?; @@ -477,8 +471,10 @@ mod tests { &directory_wrapper, &slice_of_strs_to_vec_of_strings(&["", "--data-directory", home_dir.to_str().unwrap()]), ) + // generated/test/node_configurator_standard/server_initializer_collected_params_can_read_parameters_from_config_file/home + // /Users/vojtechparkan/Projekty/Node/node/generated/test/node_configurator_standard/server_initializer_collected_params_can_read_parameters_from_config_file/home .unwrap(); - + println!("multiconfig: {:#?}", multi_config); assert_eq!( value_m!(multi_config, "dns-servers", String).unwrap(), "111.111.111.111,222.222.222.222".to_string() @@ -775,6 +771,58 @@ mod tests { } } + #[test] + fn multi_config_vcl_is_computed_do_right_job() { + running_test(); + let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot"); + let data_dir = &home_dir.join("data_dir"); + let config_file_relative = File::create(PathBuf::from("./generated").join("config.toml")).unwrap(); + fill_up_config_file(config_file_relative); + let env_vec_array = vec![ + ("MASQ_CONFIG_FILE", "./generated/config.toml"), + #[cfg(not(target_os = "windows"))] + ("MASQ_REAL_USER", "9999:9999:booga"), + ]; + env_vec_array.clone().into_iter() + .for_each (|(name, value)| std::env::set_var (name, value)); + let args = ArgsBuilder::new(); + let args_vec: Vec = args.into(); + let dir_wrapper = DirsWrapperMock::new() + .home_dir_result(Some(home_dir.clone())) + .data_dir_result(Some(data_dir.to_path_buf())); + + let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); + let env_multiconfig = result.unwrap(); + + match env_multiconfig.is_user_specified("--data-directory") { + true => (), + false => { + assert_eq!( + value_m!(env_multiconfig, "data-directory", String).unwrap(), + "/home/wooga/data_dir/MASQ/polygon-mainnet".to_string() + ) + } + } + + match (env_multiconfig.is_user_specified("--config-file"), env_multiconfig.is_user_specified("--data-directory")) { + (true, true) => { + let pwd = PathBuf::from( value_m!(env_multiconfig, "data-directory", String).unwrap()); + assert_eq!( + value_m!(env_multiconfig, "config-file", String).unwrap(), + pwd.join(PathBuf::from( "generated/config.toml")).to_string_lossy().to_string() + ) + }, + (true, false) => { + let pwd = current_dir().unwrap(); + assert_eq!( + value_m!(env_multiconfig, "config-file", String).unwrap(), + pwd.join(PathBuf::from( "generated/config.toml")).to_string_lossy().to_string() + ) + }, + (_, _) => () + } + } + #[test] fn server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot() { running_test(); From 7286ee887657e8745ac61040cca40dde03c3fec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Wed, 4 Oct 2023 15:05:05 +0200 Subject: [PATCH 10/52] fix passing test server_initializer_collected_params_can_read_parameters_from_config_file, some optimization --- node/src/node_configurator/mod.rs | 97 ++++++++++++------- .../node_configurator_standard.rs | 15 +-- 2 files changed, 70 insertions(+), 42 deletions(-) diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index 8ab7f9d23..0464295e5 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -116,8 +116,8 @@ pub fn determine_user_specific_data ( data_directory_specified, real_user: real_user_obj, real_user_specified, - pre_orientation_args: Box::new(orientation_vcl) - } + pre_orientation_args + } ) } @@ -159,9 +159,13 @@ fn create_preorientation_args(cmd_args: &[String], app: &App, dirs_wrapper: &dyn // to specify also data-directory in Environment or Command line, to be Node able figure out the // config file location, when config file is specified without absolute path or current directory eg.: "./" or "../" + let computed_real_user = &RealUser::new(None, None, None).populate(dirs_wrapper); + let preargs: Box; + let mut config_file_path: PathBuf; if combined_vcl.len() > 0 { - let (mut config_file, data_directory, real_user) = config_file_data_dir_real_user_from_enumerate(&combined_vcl.content); + let (mut config_file, data_directory, _real_user) = config_file_data_dir_real_user_from_enumerate(&combined_vcl.content); if !config_file.is_empty() { + println!("if"); if (!config_file.starts_with("/") && !config_file.starts_with("./") && !config_file.starts_with("../")) && data_directory.is_empty() { return Err(ConfigFileVclError::InvalidConfig( @@ -175,7 +179,7 @@ fn create_preorientation_args(cmd_args: &[String], app: &App, dirs_wrapper: &dyn config_file = config_file.replacen(".", pwd.to_string_lossy().to_string().as_str(), 1); } - let mut config_file_path = PathBuf::from(&config_file.to_string()); + config_file_path = PathBuf::from(&config_file.to_string()); let user_specified = !config_file.is_empty(); if config_file_path.is_relative() && !data_directory.is_empty() { config_file_path = PathBuf::from(data_directory.to_string()).join(config_file_path); @@ -184,40 +188,52 @@ fn create_preorientation_args(cmd_args: &[String], app: &App, dirs_wrapper: &dyn Ok(cfv) => Box::new(cfv), Err(e) => return Err(e), }; - let args = merge( - Box::new(EnvironmentVcl::new(app)), + preargs = merge( config_file_vcl, + Box::new(EnvironmentVcl::new(app)), ); - let args = merge( - args, - Box::new(CommandLineVcl::new( - vec!["".to_string(), - "--config-file".to_string(), - config_file_path.to_string_lossy().to_string()] - )) + } else if !data_directory.as_str().is_empty() { + println!("data dir not empty"); + let config_file = PathBuf::from("config.toml"); + config_file_path = PathBuf::from(data_directory).join(config_file); + let config_file_vcl = match ConfigFileVcl::new(&config_file_path, false) { + Ok(cfv) => Box::new(cfv), + Err(e) => return Err(e), + }; + preargs = merge( + config_file_vcl, + Box::new(EnvironmentVcl::new(app)), ); - Ok(args) } else { + println!("data dir is empty"); let config_file = PathBuf::from("config.toml"); - let config_file_path = match data_directory.as_str() { - "" => PathBuf::from().join(config_file), - _ => + + let cmd_chain = argument_from_enumerate(&cmd_args, "--chain").expect("expected chain"); + let env_chain = argument_from_enumerate(&env_args, "--chain").expect("expected chain"); + let chain = match cmd_chain.is_empty() { + true => { + match env_chain.is_empty() { + true => DEFAULT_CHAIN.rec().literal_identifier, + false => env_chain.as_str() + } + } + false => cmd_chain.as_str() + }; + let data_dir = data_directory_from_context(dirs_wrapper, computed_real_user, Chain::from(chain)); + config_file_path = PathBuf::from(data_dir).join(config_file); + let config_file_vcl = match ConfigFileVcl::new(&config_file_path, false) { + Ok(cfv) => Box::new(cfv), + Err(e) => return Err(e), }; - let args = merge( + preargs = merge( + config_file_vcl, Box::new(EnvironmentVcl::new(app)), - Box::new(CommandLineVcl::new( - vec!["".to_string(), - "--config-file".to_string(), - config_file_path.to_string_lossy().to_string()] - )) ); - Ok(args) } } else { - //Ok(Box::new(EnvironmentVcl::new(app))) let config_file = PathBuf::from("config.toml"); - let real_user = &RealUser::new(None, None, None).populate(dirs_wrapper); + // let real_user = &RealUser::new(None, None, None).populate(dirs_wrapper); let cmd_chain = argument_from_enumerate(&cmd_args, "--chain").expect("expected chain"); let env_chain = argument_from_enumerate(&env_args, "--chain").expect("expected chain"); let chain = match cmd_chain.is_empty() { @@ -229,18 +245,29 @@ fn create_preorientation_args(cmd_args: &[String], app: &App, dirs_wrapper: &dyn } false => cmd_chain.as_str() }; - let data_dir = data_directory_from_context(dirs_wrapper, real_user, Chain::from(chain)); - let config_file_path = PathBuf::from(data_dir).join(config_file); - let args = merge( + let data_dir = data_directory_from_context(dirs_wrapper, computed_real_user, Chain::from(chain)); + config_file_path = PathBuf::from(data_dir).join(config_file); + let config_file_vcl = match ConfigFileVcl::new(&config_file_path, false) { + Ok(cfv) => Box::new(cfv), + Err(e) => return Err(e), + }; + preargs = merge( + config_file_vcl, Box::new(EnvironmentVcl::new(app)), - Box::new(CommandLineVcl::new( - vec!["".to_string(), - "--config-file".to_string(), - config_file_path.to_string_lossy().to_string()] - )) ); - Ok(args) } + println!("completition"); + //Ok(Box::new(EnvironmentVcl::new(app))) + + let args = merge( + preargs, + Box::new(CommandLineVcl::new( + vec!["".to_string(), + "--config-file".to_string(), + config_file_path.to_string_lossy().to_string()] + )) + ); + Ok(args) } pub fn initialize_database( diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 3bfb68ad1..c98eb3801 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -140,21 +140,22 @@ pub fn server_initializer_collected_params<'a>( ) -> Result, ConfiguratorError> { let app = app_node(); let user_specific_data = determine_user_specific_data(dirs_wrapper, &app, args)?; + println!("user_specific_data.pre_orientation_args: {:#?}", user_specific_data.pre_orientation_args); let mut full_multi_config_vec: Vec> = vec![ Box::new(EnvironmentVcl::new(&app)), user_specific_data.pre_orientation_args ]; - let mut fill_specified_or_unspecified_box = |key: String, value: String, specified: bool | { + let mut fill_specified_or_unspecified_box = |key: &str, value: &str, specified: bool | { match specified { - true => full_multi_config_vec.push(Box::new(CommandLineVcl::new(vec!["".to_string(), key, value,]))), - false => full_multi_config_vec.push(Box::new(ComputedVcl::new(vec!["".to_string(), key, value,]))) + true => full_multi_config_vec.push(Box::new(CommandLineVcl::new(vec!["".to_string(), key.to_string(), value.to_string(),]))), + false => full_multi_config_vec.push(Box::new(ComputedVcl::new(vec!["".to_string(), key.to_string(), value.to_string(),]))) }; }; - fill_specified_or_unspecified_box("--config-file".to_string(), user_specific_data.config_file.to_string_lossy().to_string(), user_specific_data.config_file_specified); - fill_specified_or_unspecified_box("--data-directory".to_string(), user_specific_data.data_directory.to_string_lossy().to_string(), user_specific_data.data_directory_specified); - fill_specified_or_unspecified_box("--real-user".to_string(), user_specific_data.real_user.to_string(), user_specific_data.real_user_specified); - + fill_specified_or_unspecified_box("--config-file", user_specific_data.config_file.to_string_lossy().as_ref(), user_specific_data.config_file_specified); + fill_specified_or_unspecified_box("--data-directory", user_specific_data.data_directory.to_string_lossy().as_ref(), user_specific_data.data_directory_specified); + fill_specified_or_unspecified_box("--real-user", user_specific_data.real_user.to_string().as_str(), user_specific_data.real_user_specified); + println!("full_multi_config_vec: {:#?}", full_multi_config_vec); let full_multi_config = make_new_multi_config(&app,full_multi_config_vec)?; Ok(full_multi_config) From fce6fe10ab4ad50d704b2618ef7a71b1cfd0b455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Wed, 11 Oct 2023 19:11:51 +0200 Subject: [PATCH 11/52] fixed VclArg problem with withdrawing data from VirtualCommandline, sorted server_initializer_collected_params and determine_user_specific_data fncs, new fns value_opt for VclArg and tests for them --- masq_lib/src/multi_config.rs | 46 +- node/src/node_configurator/mod.rs | 465 ++++++++++-------- .../node_configurator_standard.rs | 380 +++++++------- 3 files changed, 504 insertions(+), 387 deletions(-) diff --git a/masq_lib/src/multi_config.rs b/masq_lib/src/multi_config.rs index 0540710ed..8f1c51e49 100644 --- a/masq_lib/src/multi_config.rs +++ b/masq_lib/src/multi_config.rs @@ -172,6 +172,7 @@ impl<'a> MultiConfig<'a> { pub trait VclArg: Debug { fn name(&self) -> &str; + fn value_opt(&self) -> Option<&str>; fn to_args(&self) -> Vec; fn dup(&self) -> Box; } @@ -197,7 +198,9 @@ impl VclArg for NameValueVclArg { fn name(&self) -> &str { &self.name } - + fn value_opt(&self) -> Option<&str> { + Some(self.value.as_str()) + } fn to_args(&self) -> Vec { vec![self.name.clone(), self.value.clone()] } @@ -225,7 +228,9 @@ impl VclArg for NameOnlyVclArg { fn name(&self) -> &str { &self.name } - + fn value_opt(&self) -> Option<&str> { + None + } fn to_args(&self) -> Vec { vec![self.name.clone()] } @@ -579,6 +584,7 @@ pub mod tests { use clap::Arg; use std::fs::File; use std::io::Write; + use std::ops::Deref; #[test] fn config_file_vcl_error_displays_open_error() { @@ -1024,6 +1030,42 @@ pub mod tests { assert_eq!(subject.args(), command_line); } + #[test] + fn command_line_vcl_return_value_from_vcl_args_by_name() { + let command_line: Vec = vec![ + "", + "--first-value", + "/nonexistent/directory", + "--takes_no_value", + "--other_takes_no_value", + ] + .into_iter() + .map(|s| s.to_string()) + .collect(); + + let subject = CommandLineVcl::new(command_line.clone()); + let existing_value = match subject.vcl_args + .iter() + .find(|vcl_arg_box| vcl_arg_box.deref().name() == "--first-value" ) { + Some(vcl_arg_box) => vcl_arg_box.deref().value_opt(), + None => None + }; + let non_existing_value = match subject.vcl_args + .iter() + .find(|vcl_arg_box| vcl_arg_box.deref().name() == "--takes_no_value" ) { + Some(vcl_arg_box) => vcl_arg_box.deref().value_opt(), + None => None + }; + assert_eq!( + existing_value.unwrap(), + "/nonexistent/directory" + ); + assert_eq!( + non_existing_value, + None + ); + } + #[test] #[should_panic(expected = "Expected option beginning with '--', not value")] fn command_line_vcl_panics_when_given_value_without_name() { diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index 0464295e5..801431e7b 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -7,6 +7,7 @@ pub mod unprivileged_parse_args_configuration; use std::env::{current_dir}; use crate::bootstrapper::RealUser; +use core::option::Option; use crate::database::db_initializer::DbInitializationConfig; use crate::database::db_initializer::{DbInitializer, DbInitializerReal}; use crate::db_config::persistent_configuration::{ @@ -18,40 +19,71 @@ use dirs::{data_local_dir, home_dir}; use masq_lib::blockchains::chains::Chain; use masq_lib::constants::DEFAULT_CHAIN; use masq_lib::multi_config::{merge, CommandLineVcl, EnvironmentVcl, MultiConfig, VclArg, VirtualCommandLine, ConfigFileVcl, ConfigFileVclError}; -use masq_lib::shared_schema::{config_file_arg, data_directory_arg, ConfiguratorError,DATA_DIRECTORY_HELP}; +use masq_lib::shared_schema::{config_file_arg, data_directory_arg, ConfiguratorError, DATA_DIRECTORY_HELP, chain_arg, real_user_arg}; use masq_lib::utils::{add_masq_and_chain_directories, localhost}; use std::net::{SocketAddr, TcpListener}; +use std::ops::Deref; use std::path::{Path, PathBuf}; +use libc::option; + pub trait NodeConfigurator { fn configure(&self, multi_config: &MultiConfig) -> Result; } -fn config_file_data_dir_real_user_from_enumerate(configs: &Vec) -> (String, String, String) { - let config_match = argument_from_enumerate(&configs, "--config-file").unwrap_or("".to_string()); - let data_dir_match = argument_from_enumerate(&configs, "--data-directory").unwrap_or("".to_string()); - let real_user_match = argument_from_enumerate(configs, "--real-user").unwrap_or("".to_string()); - (config_match, data_dir_match, real_user_match) -} +fn config_file_data_dir_real_user_chain_from_enumerate(dirs_wrapper: &dyn DirsWrapper, configs: Vec>) + -> (Option, Option, Option, Option) { + let configs = configs.as_slice(); + let chain = match argument_from_enumerate(configs, "--chain") { + Some(chain) => Chain::from( chain), + None => DEFAULT_CHAIN + }; + let real_user = match argument_from_enumerate(configs, "--real-user") { + Some(user) => { + let real_user_split: Vec<&str> = user.split(":").collect(); + RealUser::new( + Some(real_user_split[0].parse::().expect("expected user id")), + Some(real_user_split[1].parse::().expect("expected user group")), + Some(PathBuf::from(real_user_split[2])) + ) -fn argument_from_enumerate(configs: &Vec, needle: &str) -> Option { - let mut arg_match = None; - for (i, arg) in configs.iter().enumerate() { - if arg.as_str() == needle { - arg_match = Some(configs[i + 1].to_string()) + }, + None => RealUser::new(None, None, None).populate(dirs_wrapper) + }; + let data_directory = match argument_from_enumerate(configs, "--data-directory") { + Some(data_dir) => PathBuf::from(&data_dir), + None => data_directory_from_context(dirs_wrapper, &real_user, chain) + }; + let config_match = match argument_from_enumerate(configs, "--config-file") { + Some(config_str) => { + match PathBuf::from(config_str).is_relative() { + true => current_dir().expect("expected curerrnt dir").join(PathBuf::from(config_str)), + false => PathBuf::from(config_str) + } } + None => PathBuf::from(&data_directory).join(PathBuf::from("config.toml")) }; - arg_match + (Some(config_match), Some(data_directory), Some(real_user), Some(chain)) +} + +fn argument_from_enumerate<'a>(configs: &'a [Box], needle: &'a str) -> Option<&'a str> { + match configs + .iter() + .find(|vcl_arg_box| vcl_arg_box.deref().name() == needle ) { + Some(vcl_arg_box) => vcl_arg_box.deref().value_opt(), + None => None + } } +#[derive(Debug)] pub struct UserSpecificData { pub(crate) config_file: PathBuf, pub(crate) config_file_specified: bool, - pub(crate) data_directory: PathBuf, + pub(crate) data_directory: Option, pub(crate) data_directory_specified: bool, - pub(crate) real_user: RealUser, + pub(crate) real_user: Option, pub(crate) real_user_specified: bool, - pub(crate) pre_orientation_args: Box + // pub config_file_vcl: Box, } pub fn determine_user_specific_data ( @@ -60,63 +92,97 @@ pub fn determine_user_specific_data ( args: &[String], ) -> Result { - let pre_orientation_args = match create_preorientation_args(args, &app, dirs_wrapper) { - Ok(pre_orientation_args) => pre_orientation_args, - Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())) - }; - let orientation_args: Vec> = pre_orientation_args - .vcl_args() - .into_iter() - .filter(|vcl_arg| { - (vcl_arg.name() == "--chain") - || (vcl_arg.name() == "--real-user") - || (vcl_arg.name() == "--data-directory") - || (vcl_arg.name() == "--config-file") - }) - .map(|vcl_arg| vcl_arg.dup()) - .collect(); - let orientation_vcl = CommandLineVcl::from(orientation_args); - let (mut config_file, data_dir, mut real_user) = config_file_data_dir_real_user_from_enumerate(&orientation_vcl.args()); - let config_user_specified = !config_file.is_empty(); - let data_directory_specified = !data_dir.is_empty(); - let real_user_specified = !real_user.is_empty(); - if real_user.is_empty() { - real_user = RealUser::new(None, None, None).populate(dirs_wrapper).to_string(); - } - let chain = match argument_from_enumerate(&orientation_vcl.args(), "--chain") { - Some(chain) => { - Chain::from(chain.as_str()) - }, - None => DEFAULT_CHAIN - }; - let real_user_split: Vec<&str> = real_user.split(":").collect(); - let real_user_obj = RealUser::new( - Some(real_user_split[0].parse::().expect("expected user id")), - Some(real_user_split[1].parse::().expect("expected user group")), - Some(PathBuf::from(real_user_split[2]))); - let data_directory = match data_dir.is_empty() { - false => PathBuf::from(data_dir), - true => data_directory_from_context(dirs_wrapper, &real_user_obj, chain), + // let pre_orientation_args = match create_preorientation_args(args, &app, dirs_wrapper); + let mut data_directory_specified: bool = false; + let mut real_user_specified: bool = false; + let mut config_file_specified: bool = false; + let config_file_path: PathBuf; + let orientation_schema = App::new("MASQNode") + .arg(chain_arg()) + .arg(real_user_arg()) + .arg(data_directory_arg(DATA_DIRECTORY_HELP)) + .arg(config_file_arg()); + let orientation_args: Vec> = merge( + Box::new(EnvironmentVcl::new(&orientation_schema)), + Box::new(CommandLineVcl::new(args.to_vec())), + ) + .vcl_args() + .into_iter() + .filter(|vcl_arg| { + (vcl_arg.name() == "--chain") + || (vcl_arg.name() == "--real-user") + || (vcl_arg.name() == "--data-directory") + || (vcl_arg.name() == "--config-file") + }) + .map(|vcl_arg| vcl_arg.dup()) + .collect(); + let (config_file,data_directory,real_user, chain) = + config_file_data_dir_real_user_chain_from_enumerate( dirs_wrapper, orientation_args); + match config_file { + Some(config_file_pth) => { + config_file_specified = true; + config_file_path = config_file_pth; + // match ConfigFileVcl::new(&config_file_path, config_file_specified) { + // Ok(cfv) => Box::new(cfv), + // Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), + // } + } + None => { + config_file_specified = false; + config_file_path = data_directory.clone().expect("expect data directory").join("config.toml"); + // match ConfigFileVcl::new(&config_file_path, config_file_specified) { + // Ok(cfv) => Box::new(cfv), + // Err(e) => e, + // } + } }; - // let final_orientation_args; - // if !config_user_specified { - // config_file = data_directory.join("config.toml").to_string_lossy().to_string(); - // final_orientation_args = merge( - // Box::new(orientation_vcl), - // Box::new(CommandLineVcl::new(vec!["".to_string(), "--config-file".to_string(), config_file.clone()])) - // ); - // } else { - // final_orientation_args = Box::new(orientation_vcl); - // } - + //println!("determine_user_specific_data orientation_args {:#?}", &orientation_args); + {// let config_file_vcl = match ConfigFileVcl::new(&config_file_path, config_file_specified) { + // Ok(cfv) => Box::new(cfv), + // Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), + // }; + + // let config_user_specified = !config_file.is_empty(); + // let data_directory_specified = !data_dir.is_empty(); + // let real_user_specified = !real_user.is_empty(); + // if real_user.is_empty() { + // real_user = RealUser::new(None, None, None).populate(dirs_wrapper).to_string(); + // } + // let chain = match argument_from_enumerate(&orientation_vcl.args(), "--chain") { + // Some(chain) => { + // Chain::from(chain.as_str()) + // }, + // None => DEFAULT_CHAIN + // }; + // let real_user_split: Vec<&str> = real_user.split(":").collect(); + // let real_user_obj = RealUser::new( + // Some(real_user_split[0].parse::().expect("expected user id")), + // Some(real_user_split[1].parse::().expect("expected user group")), + // Some(PathBuf::from(real_user_split[2]))); + // let data_directory = match data_dir.is_empty() { + // false => PathBuf::from(data_dir), + // true => data_directory_from_context(dirs_wrapper, &real_user_obj, chain), + // }; + // let final_orientation_args; + // if !config_user_specified { + // config_file = data_directory.join("config.toml").to_string_lossy().to_string(); + // final_orientation_args = merge( + // Box::new(orientation_vcl), + // Box::new(CommandLineVcl::new(vec!["".to_string(), "--config-file".to_string(), config_file.clone()])) + // ); + // } else { + // final_orientation_args = Box::new(orientation_vcl); + // } + // let final_orientation_args = Box::new(orientation_vcl); + } Ok( UserSpecificData { - config_file: PathBuf::from(config_file), - config_file_specified: config_user_specified, + config_file: config_file_path, + config_file_specified, data_directory, data_directory_specified, - real_user: real_user_obj, + real_user, real_user_specified, - pre_orientation_args + // config_file_vcl } ) } @@ -131,143 +197,136 @@ impl CombinedVcl { } } -fn create_preorientation_args(cmd_args: &[String], app: &App, dirs_wrapper: &dyn DirsWrapper,) -> Result, ConfigFileVclError> { - let env_args = Box::new(EnvironmentVcl::new(&app)).args(); - let cmd_args = cmd_args.to_vec(); - let (env_config_file, env_data_dir, env_real_user) = config_file_data_dir_real_user_from_enumerate(&env_args); - let (cmd_config_file, cmd_data_dir, cmd_real_user) = config_file_data_dir_real_user_from_enumerate(&cmd_args); - let mut combined_vcl: CombinedVcl = CombinedVcl { content: vec![] }; - let combine_vcl = | name: String, vcl: &mut CombinedVcl, cmd_str: &String, env_str: &String | { - if !cmd_str.is_empty() { - vcl.content.push(name); - vcl.content.push(cmd_str.to_string()); - } - else if !env_str.is_empty() { - vcl.content.push(name); - vcl.content.push(env_str.to_string()); - } - else { - vcl.content.push(name); - vcl.content.push("".to_string()); - } - }; - combine_vcl("--data-directory".to_string(), &mut combined_vcl, &cmd_data_dir, &env_data_dir); - combine_vcl("--config-file".to_string(), &mut combined_vcl, &cmd_config_file, &env_config_file); - combine_vcl("--real-user".to_string(), &mut combined_vcl, &cmd_real_user, &env_real_user); - - // In case we define config file in ENV and we can find real-user in that file, therefore we need - // to specify also data-directory in Environment or Command line, to be Node able figure out the - // config file location, when config file is specified without absolute path or current directory eg.: "./" or "../" - - let computed_real_user = &RealUser::new(None, None, None).populate(dirs_wrapper); - let preargs: Box; - let mut config_file_path: PathBuf; - if combined_vcl.len() > 0 { - let (mut config_file, data_directory, _real_user) = config_file_data_dir_real_user_from_enumerate(&combined_vcl.content); - if !config_file.is_empty() { - println!("if"); - if (!config_file.starts_with("/") && !config_file.starts_with("./") && !config_file.starts_with("../")) - && data_directory.is_empty() { - return Err(ConfigFileVclError::InvalidConfig( - PathBuf::from(&config_file), - "Config file defined in Environment with relative path needs data-directory to be defined too".to_string() - )); - } +struct UserDefinedData { + real_user: String, + data_directory: String, + config_file: String, + real_user_type: RealUser, + data_directory_path: PathBuf, + config_file_path: PathBuf +} - if config_file.starts_with("./") { - let pwd = current_dir().expect("expected current directory"); - config_file = config_file.replacen(".", pwd.to_string_lossy().to_string().as_str(), 1); - } +// fn create_preorientation_args(cmd_args: &[String], app: &App, dirs_wrapper: &dyn DirsWrapper,) -> Result, ConfigFileVclError> { +// let env_args = Box::new(EnvironmentVcl::new(&app)).args(); +// let cmd_args = cmd_args.to_vec(); +// let (env_config_file, env_data_dir, env_real_user) = config_file_data_dir_real_user_chain_from_enumerate(Box::new(dirs_wrapper), &env_args); +// let (cmd_config_file, cmd_data_dir, cmd_real_user) = config_file_data_dir_real_user_chain_from_enumerate(Box::new(dirs_wrapper), &cmd_args); +// let mut combined_vcl: CombinedVcl = CombinedVcl { content: vec![] }; +// let combine_vcl = | name: String, vcl: &mut CombinedVcl, cmd_str: &String, env_str: &String | { +// if !cmd_str.is_empty() { +// vcl.content.push(name); +// vcl.content.push(cmd_str.to_string()); +// } +// else if !env_str.is_empty() { +// vcl.content.push(name); +// vcl.content.push(env_str.to_string()); +// } +// else { +// vcl.content.push(name); +// vcl.content.push("".to_string()); +// } +// }; +// combine_vcl("--data-directory".to_string(), &mut combined_vcl, &cmd_data_dir, &env_data_dir); +// combine_vcl("--config-file".to_string(), &mut combined_vcl, &cmd_config_file, &env_config_file); +// combine_vcl("--real-user".to_string(), &mut combined_vcl, &cmd_real_user, &env_real_user); +// +// // In case we define config file in ENV and we can find real-user in that file, therefore we need +// // to specify also data-directory in Environment or Command line, to be Node able figure out the +// // config file location, when config file is specified without absolute path or current directory eg.: "./" or "../" +// +// let mut user_defined = UserDefinedData { +// real_user: Default::default(), +// data_directory: Default::default(), +// config_file: Default::default(), +// real_user_type: RealUser::new(None, None, None).populate(dirs_wrapper), +// data_directory_path: data_directory_from_context(dirs_wrapper, &RealUser::new(None, None, None).populate(dirs_wrapper), DEFAULT_CHAIN), +// config_file_path: Default::default(), +// }; +// +// let preargs: Box; +// +// match combined_vcl.len() > 0 { +// true => { +// (user_defined.config_file, user_defined.data_directory, user_defined.real_user) = config_file_data_dir_real_user_from_enumerate(&combined_vcl.content); +// match user_defined.config_file.is_empty() { +// true => { +// user_defined.config_file = "config.toml".to_string(); +// check_status_of_data_directory(&mut user_defined); +// user_defined.config_file_path = user_defined.data_directory_path.join(user_defined.config_file_path); +// } +// false => { +// match check_status_of_config_file(&user_defined) { +// Ok(_) => {} +// Err(path) => { +// return Err(ConfigFileVclError::InvalidConfig( +// path, +// "Config file defined in Environment with relative path needs data-directory to be defined too".to_string() +// )) +// } +// } +// } +// } +// set_user_defined_config_file_and_path(&mut user_defined); +// let user_specified = !user_defined.config_file.is_empty(); +// let config_file_vcl = match ConfigFileVcl::new(&user_defined.config_file_path, user_specified) { +// Ok(cfv) => Box::new(cfv), +// Err(e) => return Err(e), +// }; +// preargs = merge( +// config_file_vcl, +// Box::new(EnvironmentVcl::new(app)), +// ); +// println!("preargs in first match config-file empty: {:#?}", preargs); +// } +// false => { +// user_defined.config_file_path = PathBuf::from(user_defined.data_directory).join(user_defined.config_file); +// let config_file_vcl = match ConfigFileVcl::new(&user_defined.config_file_path, false) { +// Ok(cfv) => Box::new(cfv), +// Err(e) => return Err(e), +// }; +// preargs = merge( +// config_file_vcl, +// Box::new(EnvironmentVcl::new(app)), +// ); +// } +// } +// +// let args = merge( +// preargs, +// Box::new(CommandLineVcl::new( +// vec!["".to_string(), +// "--config-file".to_string(), +// user_defined.config_file_path.to_string_lossy().to_string()] +// )) +// ); +// Ok(args) +// } + +fn set_user_defined_config_file_and_path(user_defined: &mut UserDefinedData) { + if user_defined.config_file.starts_with("./") { + let pwd = current_dir().expect("expected current directory from system call"); + user_defined.config_file = user_defined.config_file.replacen(".", pwd.to_string_lossy().to_string().as_str(), 1); + } + user_defined.config_file_path = PathBuf::from(&user_defined.config_file.to_string()); + if user_defined.config_file_path.is_relative() && !user_defined.data_directory.is_empty() { + user_defined.config_file_path = PathBuf::from(user_defined.data_directory.clone().to_string()).join(&user_defined.config_file_path); + } +} - config_file_path = PathBuf::from(&config_file.to_string()); - let user_specified = !config_file.is_empty(); - if config_file_path.is_relative() && !data_directory.is_empty() { - config_file_path = PathBuf::from(data_directory.to_string()).join(config_file_path); - } - let config_file_vcl = match ConfigFileVcl::new(&config_file_path, user_specified) { - Ok(cfv) => Box::new(cfv), - Err(e) => return Err(e), - }; - preargs = merge( - config_file_vcl, - Box::new(EnvironmentVcl::new(app)), - ); - } else if !data_directory.as_str().is_empty() { - println!("data dir not empty"); - let config_file = PathBuf::from("config.toml"); - config_file_path = PathBuf::from(data_directory).join(config_file); - let config_file_vcl = match ConfigFileVcl::new(&config_file_path, false) { - Ok(cfv) => Box::new(cfv), - Err(e) => return Err(e), - }; - preargs = merge( - config_file_vcl, - Box::new(EnvironmentVcl::new(app)), - ); - } else { - println!("data dir is empty"); - let config_file = PathBuf::from("config.toml"); - - let cmd_chain = argument_from_enumerate(&cmd_args, "--chain").expect("expected chain"); - let env_chain = argument_from_enumerate(&env_args, "--chain").expect("expected chain"); - let chain = match cmd_chain.is_empty() { - true => { - match env_chain.is_empty() { - true => DEFAULT_CHAIN.rec().literal_identifier, - false => env_chain.as_str() - } - } - false => cmd_chain.as_str() - }; - let data_dir = data_directory_from_context(dirs_wrapper, computed_real_user, Chain::from(chain)); - config_file_path = PathBuf::from(data_dir).join(config_file); - let config_file_vcl = match ConfigFileVcl::new(&config_file_path, false) { - Ok(cfv) => Box::new(cfv), - Err(e) => return Err(e), - }; - preargs = merge( - config_file_vcl, - Box::new(EnvironmentVcl::new(app)), - ); +fn check_status_of_data_directory(user_defined: &mut UserDefinedData) { + match user_defined.data_directory.is_empty() { + true => {}, + false => { + user_defined.data_directory_path = PathBuf::from(&user_defined.data_directory); } - - } else { - let config_file = PathBuf::from("config.toml"); - // let real_user = &RealUser::new(None, None, None).populate(dirs_wrapper); - let cmd_chain = argument_from_enumerate(&cmd_args, "--chain").expect("expected chain"); - let env_chain = argument_from_enumerate(&env_args, "--chain").expect("expected chain"); - let chain = match cmd_chain.is_empty() { - true => { - match env_chain.is_empty() { - true => DEFAULT_CHAIN.rec().literal_identifier, - false => env_chain.as_str() - } - } - false => cmd_chain.as_str() - }; - let data_dir = data_directory_from_context(dirs_wrapper, computed_real_user, Chain::from(chain)); - config_file_path = PathBuf::from(data_dir).join(config_file); - let config_file_vcl = match ConfigFileVcl::new(&config_file_path, false) { - Ok(cfv) => Box::new(cfv), - Err(e) => return Err(e), - }; - preargs = merge( - config_file_vcl, - Box::new(EnvironmentVcl::new(app)), - ); } - println!("completition"); - //Ok(Box::new(EnvironmentVcl::new(app))) - - let args = merge( - preargs, - Box::new(CommandLineVcl::new( - vec!["".to_string(), - "--config-file".to_string(), - config_file_path.to_string_lossy().to_string()] - )) - ); - Ok(args) +} + +fn check_status_of_config_file(user_defined: &UserDefinedData) -> Result<(), PathBuf> { + return if (!user_defined.config_file.starts_with("/") && !user_defined.config_file.starts_with("./") && !user_defined.config_file.starts_with("../")) + && user_defined.data_directory.is_empty() { + Err(PathBuf::from(&user_defined.config_file)) + } else { Ok(()) } } pub fn initialize_database( @@ -416,7 +475,7 @@ mod tests { .unwrap(); assert_eq!( &format!("{}", user_specific_data.config_file.parent().unwrap().display()), - &user_specific_data.data_directory.to_string_lossy().to_string(), + &user_specific_data.data_directory.unwrap().to_string_lossy().to_string(), ); assert_eq!("booga.toml", user_specific_data.config_file.file_name().unwrap()); assert_eq!(true, user_specific_data.real_user_specified); @@ -445,7 +504,7 @@ mod tests { .unwrap(); assert_eq!( format!("{}", user_specific_data.config_file.parent().unwrap().display()), - user_specific_data.data_directory.to_string_lossy().to_string(), + user_specific_data.data_directory.unwrap().to_string_lossy().to_string(), ); assert_eq!("booga.toml", user_specific_data.config_file.file_name().unwrap()); assert_eq!(true, user_specific_data.real_user_specified); diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index c98eb3801..fe2e6bd48 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -1,15 +1,16 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::bootstrapper::BootstrapperConfig; -use crate::node_configurator::DirsWrapperReal; +use crate::node_configurator::{argument_from_enumerate, DirsWrapperReal}; use crate::node_configurator::{initialize_database, DirsWrapper, NodeConfigurator}; use masq_lib::crash_point::CrashPoint; use masq_lib::logger::Logger; -use masq_lib::multi_config::{ComputedVcl, MultiConfig, VirtualCommandLine}; -use masq_lib::shared_schema::ConfiguratorError; +use masq_lib::multi_config::{ComputedVcl, MultiConfig, VclArg, VirtualCommandLine}; +use masq_lib::shared_schema::{ConfiguratorError, RealUser}; use masq_lib::utils::NeighborhoodModeLight; use std::net::SocketAddr; use std::net::{IpAddr, Ipv4Addr}; +use std::ops::Deref; use clap::value_t; use log::LevelFilter; @@ -32,6 +33,7 @@ use crate::tls_discriminator_factory::TlsDiscriminatorFactory; use masq_lib::constants::{DEFAULT_UI_PORT, HTTP_PORT, TLS_PORT}; use masq_lib::multi_config::{CommandLineVcl, ConfigFileVcl, EnvironmentVcl}; use std::str::FromStr; +use itertools::Itertools; pub struct NodeConfiguratorStandardPrivileged { dirs_wrapper: Box, @@ -140,22 +142,45 @@ pub fn server_initializer_collected_params<'a>( ) -> Result, ConfiguratorError> { let app = app_node(); let user_specific_data = determine_user_specific_data(dirs_wrapper, &app, args)?; - println!("user_specific_data.pre_orientation_args: {:#?}", user_specific_data.pre_orientation_args); + let config_file_vcl = match ConfigFileVcl::new(&user_specific_data.config_file, user_specific_data.config_file_specified) { + Ok(cfv) => cfv, + Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())) + }; + let config_file_specified = &user_specific_data.config_file_specified; + let config_file_path = &user_specific_data.config_file; + let to_iter_config = &config_file_vcl.args(); + let config_real_user_args = to_iter_config + .iter().enumerate() + .filter(|(index, vcl_arg)| { + vcl_arg.deref() == &"--real-user".to_string() + }).collect_vec(); + let (real_user, real_user_specified) = match config_real_user_args.is_empty() { + false => { + let real_user_str = to_iter_config[config_real_user_args[0].0 + 1].clone(); + (real_user_str, true) + }, + true => (user_specific_data.real_user.unwrap().to_string(), false) + }; + let mut full_multi_config_vec: Vec> = vec![ + Box::new(config_file_vcl), Box::new(EnvironmentVcl::new(&app)), - user_specific_data.pre_orientation_args + Box::new(CommandLineVcl::new(args.to_vec())), ]; - + //TODO write test for MultiConfig "try_new" merge line 76 let mut fill_specified_or_unspecified_box = |key: &str, value: &str, specified: bool | { match specified { true => full_multi_config_vec.push(Box::new(CommandLineVcl::new(vec!["".to_string(), key.to_string(), value.to_string(),]))), false => full_multi_config_vec.push(Box::new(ComputedVcl::new(vec!["".to_string(), key.to_string(), value.to_string(),]))) }; }; - fill_specified_or_unspecified_box("--config-file", user_specific_data.config_file.to_string_lossy().as_ref(), user_specific_data.config_file_specified); - fill_specified_or_unspecified_box("--data-directory", user_specific_data.data_directory.to_string_lossy().as_ref(), user_specific_data.data_directory_specified); - fill_specified_or_unspecified_box("--real-user", user_specific_data.real_user.to_string().as_str(), user_specific_data.real_user_specified); - println!("full_multi_config_vec: {:#?}", full_multi_config_vec); + fill_specified_or_unspecified_box("--config-file", &user_specific_data.config_file.as_path().to_string_lossy().as_ref(), user_specific_data.config_file_specified); + fill_specified_or_unspecified_box("--data-directory", &user_specific_data.data_directory.unwrap().to_string_lossy().as_ref(), user_specific_data.data_directory_specified); + fill_specified_or_unspecified_box("--real-user", real_user.to_string().as_str(), real_user_specified); + //TODO check if following two lines can't overwrite the computed values wrongly + full_multi_config_vec.push( Box::new(EnvironmentVcl::new(&app))); + full_multi_config_vec.push( Box::new(CommandLineVcl::new(args.to_vec()))); + // println!("full_multi_config_vec: {:#?}", full_multi_config_vec); let full_multi_config = make_new_multi_config(&app,full_multi_config_vec)?; Ok(full_multi_config) @@ -472,10 +497,7 @@ mod tests { &directory_wrapper, &slice_of_strs_to_vec_of_strings(&["", "--data-directory", home_dir.to_str().unwrap()]), ) - // generated/test/node_configurator_standard/server_initializer_collected_params_can_read_parameters_from_config_file/home - // /Users/vojtechparkan/Projekty/Node/node/generated/test/node_configurator_standard/server_initializer_collected_params_can_read_parameters_from_config_file/home .unwrap(); - println!("multiconfig: {:#?}", multi_config); assert_eq!( value_m!(multi_config, "dns-servers", String).unwrap(), "111.111.111.111,222.222.222.222".to_string() @@ -800,7 +822,7 @@ mod tests { false => { assert_eq!( value_m!(env_multiconfig, "data-directory", String).unwrap(), - "/home/wooga/data_dir/MASQ/polygon-mainnet".to_string() + "booga/data_dir/MASQ/polygon-mainnet".to_string() ) } } @@ -880,15 +902,14 @@ mod tests { fn server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_data_directory() { running_test(); let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot"); + println!("home_dir {:#?}", &home_dir); let data_dir = &home_dir.join("data_dir"); let config_file_relative = File::create(PathBuf::from("./generated").join("config.toml")).unwrap(); fill_up_config_file(config_file_relative); let current_directory = current_dir().unwrap(); vec![ - ("MASQ_CONFIG_FILE", "./generated/config.toml"), - ("MASQ_DATA_DIRECTORY", ¤t_directory.display().to_string().as_str()), - #[cfg(not(target_os = "windows"))] - ("MASQ_REAL_USER", "9999:9999:booga"), + ("MASQ_CONFIG_FILE", "generated/config.toml"), + //("MASQ_DATA_DIRECTORY", ¤t_directory.display().to_string().as_str()), ].into_iter() .for_each (|(name, value)| std::env::set_var (name, value)); let args = ArgsBuilder::new(); @@ -898,182 +919,177 @@ mod tests { .data_dir_result(Some(data_dir.to_path_buf())); let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); - let env_multiconfig = result.unwrap(); + let multiconfig = result.unwrap(); - match env_multiconfig.is_user_specified("--data-directory") { + match multiconfig.is_user_specified("--data-directory") { true => { assert_eq!( - &value_m!(env_multiconfig, "data-directory", String).unwrap(), + &value_m!(multiconfig, "data-directory", String).unwrap(), ¤t_directory.to_string_lossy().to_string() ) }, false => () } - - match env_multiconfig.is_user_specified("--real-user") { - true => { - assert_eq!( - &value_m!(env_multiconfig, "real-user", String).unwrap(), - "1002:1002:/home/wooga" - ) - }, - false => () - } - - match (env_multiconfig.is_user_specified("--config-file"), env_multiconfig.is_user_specified("--data-directory")) { - (true, true) => { - let pwd = PathBuf::from( value_m!(env_multiconfig, "data-directory", String).unwrap()); - assert_eq!( - value_m!(env_multiconfig, "config-file", String).unwrap(), - pwd.join(PathBuf::from( "generated/config.toml")).to_string_lossy().to_string() - ) - }, - (_, _) => () - } - } - - #[test] - fn server_initializer_collected_params_combine_vlcs_properly() { - running_test(); - let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_combine_vlcs_properly"); - let data_dir = &home_dir.join("data_dir"); - let config_file = File::create(&home_dir.join("config.toml")).unwrap(); - let current_directory = current_dir().unwrap(); - let data_dir_sys = create_dir_all(PathBuf::from(home_dir.to_string_lossy().as_ref().to_owned() + "/data_dir/MASQ/polygon-mainnet")); - { - fill_up_config_file(config_file); - match data_dir_sys { - Ok(..) => { - let config_file_vcl = File::create(PathBuf::from(home_dir.to_string_lossy().as_ref().to_owned() + "/data_dir/MASQ/polygon-mainnet").join("config.toml")).unwrap(); - fill_up_config_file(config_file_vcl); - } - Err(e) => panic!("unable to create config file: {}", e) - } - } - - let env_vec_array = vec![ - ("MASQ_CONFIG_FILE", "./generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home/config.toml"), - //("MASQ_DATA_DIRECTORY", home_dir.to_str().unwrap()), - #[cfg(not(target_os = "windows"))] - ("MASQ_REAL_USER", "9999:9999:booga"), - ]; - env_vec_array.clone().into_iter() - .for_each (|(name, value)| std::env::set_var (name, value)); - let args = ArgsBuilder::new(); - let args_vec: Vec = args.into(); - let dir_wrapper = DirsWrapperMock::new() - .home_dir_result(Some(home_dir.clone())) - .data_dir_result(Some(data_dir.to_path_buf())); - let mut env_data_directory = false; - for (item, _vaue) in env_vec_array.to_vec().as_slice() { - if item == &"MASQ_DATA_DIRECTORY" { env_data_directory = true; } - } - - let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); - let env_multiconfig = result.unwrap(); - - let args = ArgsBuilder::new() - .param("--data-directory", current_directory.join(Path::new("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home")).to_string_lossy().to_string().as_str()) - .param("--real-user", "1001:1001:cooga"); - let args_vec: Vec = args.into(); - let mut cmd_data_directory = false; - for item in args_vec.clone().as_slice() { - if item == &"--data-directory".to_string() { cmd_data_directory = true; } - } - let params = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); - let result2 = params.as_ref().expect("REASON"); - let vcl_multiconfig = result2; - + println!("real-user {}", value_m!(multiconfig, "real-user", String).unwrap()); assert_eq!( - value_m!(env_multiconfig, "config-file", String).unwrap(), - current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home/config.toml").to_string_lossy().to_string() - ); - match env_multiconfig.is_user_specified("--data-directory") { - true => { - assert_eq!( - value_m!(env_multiconfig, "data-directory", String).unwrap(), - "generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home".to_string() - ) - } - false => { - println!("data-directory is not user specified in Environment"); - () - } - } - - #[cfg(not(target_os = "windows"))] - match env_multiconfig.is_user_specified("--real-user") { - true => { - assert_eq!( - value_m!(env_multiconfig, "real-user", String).unwrap(), - "1002:1002:/home/wooga".to_string() - ) - } - false => { - println!("real-user is not user specified in Environment"); - () - } - } - - #[cfg(not(target_os = "windows"))] - match vcl_multiconfig.is_user_specified("--real-user") { - true => { - match env_multiconfig.is_user_specified("--real-user") { - true => { - println!("real-user is inherited from Environment {}", value_m!(vcl_multiconfig, "real-user", String).unwrap()); - assert_eq!( - value_m!(vcl_multiconfig, "real-user", String).unwrap(), - "1001:1001:cooga".to_string() - ) - } - false => { - assert_eq!( - value_m!(vcl_multiconfig, "real-user", String).unwrap(), - "1002:1002:/home/wooga".to_string() - ) - } - } - } - false => { - println!("real-user is not user specified in Command-line"); - () - } - } - println!("env_data_directory {:#?}", env_data_directory); - match vcl_multiconfig.is_user_specified("--data-directory") { - true => { - match env_data_directory && !cmd_data_directory { - true => { - println!("data-directory is inherited from Environment"); - assert_eq!( - value_m!(vcl_multiconfig, "data-directory", String).unwrap(), - "generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home".to_string() - ) - } - false => { - match cmd_data_directory { - true => { - assert_eq!( - value_m!(vcl_multiconfig, "data-directory", String).unwrap(), - current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home").to_string_lossy().to_string() - ) - } - false => { - println!("data-directory is not user specified in ENV") - } - } - - } - }; + &value_m!(multiconfig, "real-user", String).unwrap(), + "1002:1002:/home/wooga" + ) - } - false => { - println!("data-directory is not user specified in Command-line"); - () - } - } + // match (multiconfig.is_user_specified("--config-file"), env_multiconfig.is_user_specified("--data-directory")) { + // (true, true) => { + // let pwd = PathBuf::from( value_m!(multiconfig, "data-directory", String).unwrap()); + // assert_eq!( + // value_m!(env_multiconfig, "config-file", String).unwrap(), + // pwd.join(PathBuf::from( "generated/config.toml")).to_string_lossy().to_string() + // ) + // }, + // (_, _) => () + // } } + // #[test] + // fn server_initializer_collected_params_combine_vlcs_properly() { + // running_test(); + // let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_combine_vlcs_properly"); + // let data_dir = &home_dir.join("data_dir"); + // let config_file = File::create(&home_dir.join("config.toml")).unwrap(); + // let current_directory = current_dir().unwrap(); + // let data_dir_sys = create_dir_all(PathBuf::from(home_dir.to_string_lossy().as_ref().to_owned() + "/data_dir/MASQ/polygon-mainnet")); + // { + // fill_up_config_file(config_file); + // match data_dir_sys { + // Ok(..) => { + // let config_file_vcl = File::create(PathBuf::from(home_dir.to_string_lossy().as_ref().to_owned() + "/data_dir/MASQ/polygon-mainnet").join("config.toml")).unwrap(); + // fill_up_config_file(config_file_vcl); + // } + // Err(e) => panic!("unable to create config file: {}", e) + // } + // } + // + // let env_vec_array = vec![ + // ("MASQ_CONFIG_FILE", "./generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home/config.toml"), + // //("MASQ_DATA_DIRECTORY", home_dir.to_str().unwrap()), + // #[cfg(not(target_os = "windows"))] + // ("MASQ_REAL_USER", "9999:9999:booga"), + // ]; + // env_vec_array.clone().into_iter() + // .for_each (|(name, value)| std::env::set_var (name, value)); + // let args = ArgsBuilder::new(); + // let args_vec: Vec = args.into(); + // let dir_wrapper = DirsWrapperMock::new() + // .home_dir_result(Some(home_dir.clone())) + // .data_dir_result(Some(data_dir.to_path_buf())); + // let mut env_data_directory = false; + // for (item, _vaue) in env_vec_array.to_vec().as_slice() { + // if item == &"MASQ_DATA_DIRECTORY" { env_data_directory = true; } + // } + // + // let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); + // let env_multiconfig = result.unwrap(); + // + // let args = ArgsBuilder::new() + // .param("--data-directory", current_directory.join(Path::new("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home")).to_string_lossy().to_string().as_str()) + // .param("--real-user", "1001:1001:cooga"); + // let args_vec: Vec = args.into(); + // let mut cmd_data_directory = false; + // for item in args_vec.clone().as_slice() { + // if item == &"--data-directory".to_string() { cmd_data_directory = true; } + // } + // let params = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); + // let result2 = params.as_ref().expect("REASON"); + // let vcl_multiconfig = result2; + // + // assert_eq!( + // value_m!(env_multiconfig, "config-file", String).unwrap(), + // current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home/config.toml").to_string_lossy().to_string() + // ); + // match env_multiconfig.is_user_specified("--data-directory") { + // true => { + // assert_eq!( + // value_m!(env_multiconfig, "data-directory", String).unwrap(), + // "generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home".to_string() + // ) + // } + // false => { + // println!("data-directory is not user specified in Environment"); + // () + // } + // } + // + // #[cfg(not(target_os = "windows"))] + // match env_multiconfig.is_user_specified("--real-user") { + // true => { + // assert_eq!( + // value_m!(env_multiconfig, "real-user", String).unwrap(), + // "1002:1002:/home/wooga".to_string() + // ) + // } + // false => { + // println!("real-user is not user specified in Environment"); + // () + // } + // } + // + // #[cfg(not(target_os = "windows"))] + // match vcl_multiconfig.is_user_specified("--real-user") { + // true => { + // match env_multiconfig.is_user_specified("--real-user") { + // true => { + // println!("real-user is inherited from Environment {}", value_m!(vcl_multiconfig, "real-user", String).unwrap()); + // assert_eq!( + // value_m!(vcl_multiconfig, "real-user", String).unwrap(), + // "1001:1001:cooga".to_string() + // ) + // } + // false => { + // assert_eq!( + // value_m!(vcl_multiconfig, "real-user", String).unwrap(), + // "1002:1002:/home/wooga".to_string() + // ) + // } + // } + // } + // false => { + // println!("real-user is not user specified in Command-line"); + // () + // } + // } + // println!("env_data_directory {:#?}", env_data_directory); + // match vcl_multiconfig.is_user_specified("--data-directory") { + // true => { + // match env_data_directory && !cmd_data_directory { + // true => { + // println!("data-directory is inherited from Environment"); + // assert_eq!( + // value_m!(vcl_multiconfig, "data-directory", String).unwrap(), + // "generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home".to_string() + // ) + // } + // false => { + // match cmd_data_directory { + // true => { + // assert_eq!( + // value_m!(vcl_multiconfig, "data-directory", String).unwrap(), + // current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home").to_string_lossy().to_string() + // ) + // } + // false => { + // println!("data-directory is not user specified in ENV") + // } + // } + // + // } + // }; + // + // } + // false => { + // println!("data-directory is not user specified in Command-line"); + // () + // } + // } + // } + #[test] fn server_initializer_collected_params_senses_when_user_specifies_config_file() { running_test(); From 4dd0932edc3ee27e760a6d527703d73a86a190c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Mon, 23 Oct 2023 18:31:37 +0200 Subject: [PATCH 12/52] finalize preparing guts for multiconfig in server_initializer_collected_params, added handling for tilde in data_directory and config_file paths --- masq_lib/src/multi_config.rs | 94 +-- node/src/daemon/setup_reporter.rs | 7 +- node/src/node_configurator/mod.rs | 506 +++++++-------- .../node_configurator_standard.rs | 574 +++++++++--------- node/src/server_initializer.rs | 8 +- node/tests/utils.rs | 2 + 6 files changed, 563 insertions(+), 628 deletions(-) diff --git a/masq_lib/src/multi_config.rs b/masq_lib/src/multi_config.rs index 8f1c51e49..c3e3e49c3 100644 --- a/masq_lib/src/multi_config.rs +++ b/masq_lib/src/multi_config.rs @@ -50,7 +50,7 @@ macro_rules! values_m { #[derive(Debug)] pub struct MultiConfig<'a> { arg_matches: ArgMatches<'a>, - computed_value_names: HashSet + computed_value_names: HashSet, } impl<'a> MultiConfig<'a> { @@ -69,31 +69,30 @@ impl<'a> MultiConfig<'a> { vcl.vcl_args().iter().for_each(|vcl_arg| { match vcl.is_computed() { true => computed_value_names.insert(vcl_arg.name().to_string()), - false => computed_value_names.remove(&vcl_arg.name().to_string()) + false => computed_value_names.remove(&vcl_arg.name().to_string()), }; }) }); - let merged = vcls.into_iter().fold(initial, |so_far, vcl | { - merge(so_far, vcl) - }); + let merged = vcls + .into_iter() + .fold(initial, |so_far, vcl| merge(so_far, vcl)); let arg_matches = match schema .clone() .get_matches_from_safe(merged.args().into_iter()) { - Ok(matches) => { - matches - }, - Err(e) => { - match e.kind { - clap::ErrorKind::HelpDisplayed | clap::ErrorKind::VersionDisplayed => { - unreachable!("The program's entry check failed to catch this.") - } - _ => return Err(Self::make_configurator_error(e)), + Ok(matches) => matches, + Err(e) => match e.kind { + clap::ErrorKind::HelpDisplayed | clap::ErrorKind::VersionDisplayed => { + unreachable!("The program's entry check failed to catch this.") } + _ => return Err(Self::make_configurator_error(e)), }, }; - Ok(MultiConfig { arg_matches, computed_value_names }) + Ok(MultiConfig { + arg_matches, + computed_value_names, + }) } fn check_for_invalid_value_err( @@ -155,9 +154,9 @@ impl<'a> MultiConfig<'a> { } pub fn is_user_specified(&self, value_name: &str) -> bool { - match self.computed_value_names.contains(value_name) { + match self.computed_value_names.contains(value_name) { true => false, - false => true + false => true, } } @@ -251,7 +250,9 @@ impl NameOnlyVclArg { pub trait VirtualCommandLine { fn vcl_args(&self) -> Vec<&dyn VclArg>; fn args(&self) -> Vec; - fn is_computed(&self) -> bool { false } + fn is_computed(&self) -> bool { + false + } } impl Debug for dyn VirtualCommandLine { @@ -291,7 +292,7 @@ pub fn merge( }) } -pub struct ComputedVcl { +pub struct ComputedVcl { vcl_args: Vec>, } @@ -407,7 +408,7 @@ impl EnvironmentVcl { .collect(); let mut vcl_args: Vec> = vec![]; for (upper_name, value) in std::env::vars() { - if (upper_name.len() < 5) || (&upper_name[0..5] != "MASQ_") { + if (upper_name.len() < 5) || (&upper_name[0..5] != "MASQ_") || (value == *"") { continue; } let lower_name = str::replace(&upper_name[5..].to_lowercase(), "_", "-"); @@ -567,11 +568,11 @@ fn append(ts: Vec, t: T) -> Vec { impl<'a> MultiConfig<'a> { pub fn new_test_only( arg_matches: ArgMatches<'a>, - computed_value_names: HashSet + computed_value_names: HashSet, ) -> Self { Self { arg_matches, - computed_value_names + computed_value_names, } } } @@ -1039,31 +1040,29 @@ pub mod tests { "--takes_no_value", "--other_takes_no_value", ] - .into_iter() - .map(|s| s.to_string()) - .collect(); + .into_iter() + .map(|s| s.to_string()) + .collect(); let subject = CommandLineVcl::new(command_line.clone()); - let existing_value = match subject.vcl_args + let existing_value = match subject + .vcl_args .iter() - .find(|vcl_arg_box| vcl_arg_box.deref().name() == "--first-value" ) { + .find(|vcl_arg_box| vcl_arg_box.deref().name() == "--first-value") + { Some(vcl_arg_box) => vcl_arg_box.deref().value_opt(), - None => None + None => None, }; - let non_existing_value = match subject.vcl_args + let non_existing_value = match subject + .vcl_args .iter() - .find(|vcl_arg_box| vcl_arg_box.deref().name() == "--takes_no_value" ) { + .find(|vcl_arg_box| vcl_arg_box.deref().name() == "--takes_no_value") + { Some(vcl_arg_box) => vcl_arg_box.deref().value_opt(), - None => None + None => None, }; - assert_eq!( - existing_value.unwrap(), - "/nonexistent/directory" - ); - assert_eq!( - non_existing_value, - None - ); + assert_eq!(existing_value.unwrap(), "/nonexistent/directory"); + assert_eq!(non_existing_value, None); } #[test] @@ -1080,12 +1079,19 @@ pub mod tests { #[test] fn environment_vcl_works() { let _guard = EnvironmentGuard::new(); - let schema = App::new("test").arg( - Arg::with_name("numeric-arg") - .long("numeric-arg") - .takes_value(true), - ); + let schema = App::new("test") + .arg( + Arg::with_name("numeric-arg") + .long("numeric-arg") + .takes_value(true), + ) + .arg( + Arg::with_name("empty-arg") + .long("empty-arg") + .takes_value(true), + ); std::env::set_var("MASQ_NUMERIC_ARG", "47"); + std::env::set_var("MASQ_EMPTY_ARG", ""); let subject = EnvironmentVcl::new(&schema); diff --git a/node/src/daemon/setup_reporter.rs b/node/src/daemon/setup_reporter.rs index fd060b145..20980f02c 100644 --- a/node/src/daemon/setup_reporter.rs +++ b/node/src/daemon/setup_reporter.rs @@ -18,7 +18,7 @@ use crate::node_configurator::unprivileged_parse_args_configuration::{ UnprivilegedParseArgsConfigurationDaoReal, }; use crate::node_configurator::{ - data_directory_from_context, determine_user_specific_data, DirsWrapperReal, DirsWrapper + data_directory_from_context, determine_user_specific_data, DirsWrapper, DirsWrapperReal, }; use crate::sub_lib::accountant::PaymentThresholds as PaymentThresholdsFromAccountant; use crate::sub_lib::accountant::DEFAULT_SCAN_INTERVALS; @@ -497,7 +497,10 @@ impl SetupReporterReal { }; let user_specific_data = determine_user_specific_data(dirs_wrapper, &app, &command_line)?; - let config_file_vcl = match ConfigFileVcl::new(&user_specific_data.config_file, user_specific_data.config_file_specified) { + let config_file_vcl = match ConfigFileVcl::new( + &user_specific_data.config_file, + user_specific_data.config_file_spec, + ) { Ok(cfv) => cfv, Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), }; diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index 801431e7b..1e769aaed 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -5,9 +5,7 @@ pub mod node_configurator_initialization; pub mod node_configurator_standard; pub mod unprivileged_parse_args_configuration; -use std::env::{current_dir}; use crate::bootstrapper::RealUser; -use core::option::Option; use crate::database::db_initializer::DbInitializationConfig; use crate::database::db_initializer::{DbInitializer, DbInitializerReal}; use crate::db_config::persistent_configuration::{ @@ -15,318 +13,223 @@ use crate::db_config::persistent_configuration::{ }; use crate::sub_lib::utils::db_connection_launch_panic; use clap::{value_t, App}; +use core::option::Option; use dirs::{data_local_dir, home_dir}; use masq_lib::blockchains::chains::Chain; use masq_lib::constants::DEFAULT_CHAIN; -use masq_lib::multi_config::{merge, CommandLineVcl, EnvironmentVcl, MultiConfig, VclArg, VirtualCommandLine, ConfigFileVcl, ConfigFileVclError}; -use masq_lib::shared_schema::{config_file_arg, data_directory_arg, ConfiguratorError, DATA_DIRECTORY_HELP, chain_arg, real_user_arg}; +use masq_lib::multi_config::{merge, CommandLineVcl, EnvironmentVcl, MultiConfig, VclArg}; +use masq_lib::shared_schema::ConfiguratorError; use masq_lib::utils::{add_masq_and_chain_directories, localhost}; +use std::env::current_dir; use std::net::{SocketAddr, TcpListener}; use std::ops::Deref; use std::path::{Path, PathBuf}; -use libc::option; - pub trait NodeConfigurator { fn configure(&self, multi_config: &MultiConfig) -> Result; } -fn config_file_data_dir_real_user_chain_from_enumerate(dirs_wrapper: &dyn DirsWrapper, configs: Vec>) - -> (Option, Option, Option, Option) { - let configs = configs.as_slice(); - let chain = match argument_from_enumerate(configs, "--chain") { - Some(chain) => Chain::from( chain), - None => DEFAULT_CHAIN - }; - let real_user = match argument_from_enumerate(configs, "--real-user") { +#[derive(Debug)] +pub struct UserSpecifiedData { + pub(crate) chain: Chain, + pub(crate) chain_spec: bool, + pub(crate) real_user: RealUser, + pub(crate) real_user_spec: bool, + pub(crate) data_directory: PathBuf, + pub(crate) data_directory_spec: bool, + pub(crate) config_file: PathBuf, + pub(crate) config_file_spec: bool, +} + +fn get_chain_from_vcl(configs: &[Box]) -> (Chain, bool) { + match argument_from_enumerate(configs, "--chain") { + Some(chain) => (Chain::from(chain), true), + None => (DEFAULT_CHAIN, false), + } +} + +fn get_real_user_from_vcl( + configs: &[Box], + dirs_wrapper: &dyn DirsWrapper, +) -> (RealUser, bool) { + match argument_from_enumerate(configs, "--real-user") { Some(user) => { - let real_user_split: Vec<&str> = user.split(":").collect(); - RealUser::new( - Some(real_user_split[0].parse::().expect("expected user id")), - Some(real_user_split[1].parse::().expect("expected user group")), - Some(PathBuf::from(real_user_split[2])) + let real_user_split: Vec<&str> = user.split(':').collect(); + ( + RealUser::new( + Some(real_user_split[0].parse::().expect("expected user id")), + Some( + real_user_split[1] + .parse::() + .expect("expected user group"), + ), + Some(PathBuf::from(real_user_split[2])), + ), + true, ) + } + None => ( + RealUser::new(None, None, None).populate(dirs_wrapper), + false, + ), + } +} +fn get_data_directory_from_vcl( + configs: &[Box], + dirs_wrapper: &dyn DirsWrapper, + real_user: &RealUser, + chain: &Chain, +) -> (PathBuf, bool) { + match argument_from_enumerate(configs, "--data-directory") { + Some(data_dir) => match PathBuf::from(data_dir).starts_with("~/") { + true => { + let home_dir_from_wrapper = dirs_wrapper + .home_dir() + .expect("expexted users home dir") + .to_str() + .expect("expect home dir") + .to_string(); + let replaced_tilde_dir = + data_dir + .to_string() + .replacen('~', home_dir_from_wrapper.as_str(), 1); + (PathBuf::from(replaced_tilde_dir), true) + } + false => (PathBuf::from(&data_dir), true), }, - None => RealUser::new(None, None, None).populate(dirs_wrapper) - }; - let data_directory = match argument_from_enumerate(configs, "--data-directory") { - Some(data_dir) => PathBuf::from(&data_dir), - None => data_directory_from_context(dirs_wrapper, &real_user, chain) - }; - let config_match = match argument_from_enumerate(configs, "--config-file") { + None => ( + data_directory_from_context(dirs_wrapper, real_user, *chain), + false, + ), + } +} + +fn get_config_file_from_vcl( + configs: &[Box], + data_directory: &PathBuf, + data_directory_def: bool, + dirs_wrapper: &dyn DirsWrapper, +) -> (PathBuf, bool) { + match argument_from_enumerate(configs, "--config-file") { Some(config_str) => { - match PathBuf::from(config_str).is_relative() { - true => current_dir().expect("expected curerrnt dir").join(PathBuf::from(config_str)), - false => PathBuf::from(config_str) + let path = match PathBuf::from(config_str).is_relative() { + true => { + match PathBuf::from(config_str).file_name().expect("expected file name") == config_str { + true => PathBuf::from(data_directory).join(PathBuf::from(config_str)), + false => match PathBuf::from(config_str).starts_with("./") || PathBuf::from(config_str).starts_with("../") { + true => current_dir().expect("expected curerrnt dir").join(PathBuf::from(config_str)), + false => match PathBuf::from(config_str).starts_with("~") { + true => { + let home_dir_from_wrapper = dirs_wrapper + .home_dir() + .expect("expexted users home dir") + .to_str() + .expect("expect home dir") + .to_string(); + let replaced_tilde_dir = + config_str + .to_string() + .replacen('~', home_dir_from_wrapper.as_str(), 1); + PathBuf::from(replaced_tilde_dir) + } + false => match data_directory_def { + true => PathBuf::from(data_directory).join(PathBuf::from(config_str)), + false => panic!("You need to define data-directory to be able define config file with naked directory 'dirname/config.toml'.") + } + } + } + } + } + false => PathBuf::from(config_str), + }; + (path, true) + } + None => { + let path = PathBuf::from(data_directory).join(PathBuf::from("config.toml")); + match path.is_file() { + true => (path, true), + false => (path, false), } } - None => PathBuf::from(&data_directory).join(PathBuf::from("config.toml")) + } +} + +fn config_file_data_dir_real_user_chain_from_enumerate( + dirs_wrapper: &dyn DirsWrapper, + configs: Vec>, +) -> UserSpecifiedData { + //TODO break down this function to collection of small one purpose functions + let mut user_specified_data = UserSpecifiedData { + chain: Default::default(), + chain_spec: false, + real_user: Default::default(), + real_user_spec: false, + data_directory: Default::default(), + data_directory_spec: false, + config_file: Default::default(), + config_file_spec: false, }; - (Some(config_match), Some(data_directory), Some(real_user), Some(chain)) + let configs = configs.as_slice(); + (user_specified_data.chain, user_specified_data.chain_spec) = get_chain_from_vcl(configs); + ( + user_specified_data.real_user, + user_specified_data.real_user_spec, + ) = get_real_user_from_vcl(configs, dirs_wrapper); + ( + user_specified_data.data_directory, + user_specified_data.data_directory_spec, + ) = get_data_directory_from_vcl( + configs, + dirs_wrapper, + &user_specified_data.real_user, + &user_specified_data.chain, + ); + ( + user_specified_data.config_file, + user_specified_data.config_file_spec, + ) = get_config_file_from_vcl( + configs, + &user_specified_data.data_directory, + user_specified_data.data_directory_spec, + dirs_wrapper, + ); + user_specified_data } fn argument_from_enumerate<'a>(configs: &'a [Box], needle: &'a str) -> Option<&'a str> { match configs .iter() - .find(|vcl_arg_box| vcl_arg_box.deref().name() == needle ) { + .find(|vcl_arg_box| vcl_arg_box.deref().name() == needle) + { Some(vcl_arg_box) => vcl_arg_box.deref().value_opt(), - None => None + None => None, } } -#[derive(Debug)] -pub struct UserSpecificData { - pub(crate) config_file: PathBuf, - pub(crate) config_file_specified: bool, - pub(crate) data_directory: Option, - pub(crate) data_directory_specified: bool, - pub(crate) real_user: Option, - pub(crate) real_user_specified: bool, - // pub config_file_vcl: Box, -} - -pub fn determine_user_specific_data ( +pub fn determine_user_specific_data( dirs_wrapper: &dyn DirsWrapper, app: &App, args: &[String], -) -> Result { - - // let pre_orientation_args = match create_preorientation_args(args, &app, dirs_wrapper); - let mut data_directory_specified: bool = false; - let mut real_user_specified: bool = false; - let mut config_file_specified: bool = false; - let config_file_path: PathBuf; - let orientation_schema = App::new("MASQNode") - .arg(chain_arg()) - .arg(real_user_arg()) - .arg(data_directory_arg(DATA_DIRECTORY_HELP)) - .arg(config_file_arg()); +) -> Result { let orientation_args: Vec> = merge( - Box::new(EnvironmentVcl::new(&orientation_schema)), + Box::new(EnvironmentVcl::new(app)), Box::new(CommandLineVcl::new(args.to_vec())), ) - .vcl_args() - .into_iter() - .filter(|vcl_arg| { - (vcl_arg.name() == "--chain") - || (vcl_arg.name() == "--real-user") - || (vcl_arg.name() == "--data-directory") - || (vcl_arg.name() == "--config-file") - }) - .map(|vcl_arg| vcl_arg.dup()) - .collect(); - let (config_file,data_directory,real_user, chain) = - config_file_data_dir_real_user_chain_from_enumerate( dirs_wrapper, orientation_args); - match config_file { - Some(config_file_pth) => { - config_file_specified = true; - config_file_path = config_file_pth; - // match ConfigFileVcl::new(&config_file_path, config_file_specified) { - // Ok(cfv) => Box::new(cfv), - // Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), - // } - } - None => { - config_file_specified = false; - config_file_path = data_directory.clone().expect("expect data directory").join("config.toml"); - // match ConfigFileVcl::new(&config_file_path, config_file_specified) { - // Ok(cfv) => Box::new(cfv), - // Err(e) => e, - // } - } - }; - //println!("determine_user_specific_data orientation_args {:#?}", &orientation_args); - {// let config_file_vcl = match ConfigFileVcl::new(&config_file_path, config_file_specified) { - // Ok(cfv) => Box::new(cfv), - // Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), - // }; - - // let config_user_specified = !config_file.is_empty(); - // let data_directory_specified = !data_dir.is_empty(); - // let real_user_specified = !real_user.is_empty(); - // if real_user.is_empty() { - // real_user = RealUser::new(None, None, None).populate(dirs_wrapper).to_string(); - // } - // let chain = match argument_from_enumerate(&orientation_vcl.args(), "--chain") { - // Some(chain) => { - // Chain::from(chain.as_str()) - // }, - // None => DEFAULT_CHAIN - // }; - // let real_user_split: Vec<&str> = real_user.split(":").collect(); - // let real_user_obj = RealUser::new( - // Some(real_user_split[0].parse::().expect("expected user id")), - // Some(real_user_split[1].parse::().expect("expected user group")), - // Some(PathBuf::from(real_user_split[2]))); - // let data_directory = match data_dir.is_empty() { - // false => PathBuf::from(data_dir), - // true => data_directory_from_context(dirs_wrapper, &real_user_obj, chain), - // }; - // let final_orientation_args; - // if !config_user_specified { - // config_file = data_directory.join("config.toml").to_string_lossy().to_string(); - // final_orientation_args = merge( - // Box::new(orientation_vcl), - // Box::new(CommandLineVcl::new(vec!["".to_string(), "--config-file".to_string(), config_file.clone()])) - // ); - // } else { - // final_orientation_args = Box::new(orientation_vcl); - // } - // let final_orientation_args = Box::new(orientation_vcl); - } - Ok( UserSpecificData { - config_file: config_file_path, - config_file_specified, - data_directory, - data_directory_specified, - real_user, - real_user_specified, - // config_file_vcl - } - ) -} - -struct CombinedVcl { - content: Vec -} - -impl CombinedVcl { - fn len(&self) -> u32 { - *&self.content.as_slice().iter().count() as u32 - } -} - -struct UserDefinedData { - real_user: String, - data_directory: String, - config_file: String, - real_user_type: RealUser, - data_directory_path: PathBuf, - config_file_path: PathBuf -} - -// fn create_preorientation_args(cmd_args: &[String], app: &App, dirs_wrapper: &dyn DirsWrapper,) -> Result, ConfigFileVclError> { -// let env_args = Box::new(EnvironmentVcl::new(&app)).args(); -// let cmd_args = cmd_args.to_vec(); -// let (env_config_file, env_data_dir, env_real_user) = config_file_data_dir_real_user_chain_from_enumerate(Box::new(dirs_wrapper), &env_args); -// let (cmd_config_file, cmd_data_dir, cmd_real_user) = config_file_data_dir_real_user_chain_from_enumerate(Box::new(dirs_wrapper), &cmd_args); -// let mut combined_vcl: CombinedVcl = CombinedVcl { content: vec![] }; -// let combine_vcl = | name: String, vcl: &mut CombinedVcl, cmd_str: &String, env_str: &String | { -// if !cmd_str.is_empty() { -// vcl.content.push(name); -// vcl.content.push(cmd_str.to_string()); -// } -// else if !env_str.is_empty() { -// vcl.content.push(name); -// vcl.content.push(env_str.to_string()); -// } -// else { -// vcl.content.push(name); -// vcl.content.push("".to_string()); -// } -// }; -// combine_vcl("--data-directory".to_string(), &mut combined_vcl, &cmd_data_dir, &env_data_dir); -// combine_vcl("--config-file".to_string(), &mut combined_vcl, &cmd_config_file, &env_config_file); -// combine_vcl("--real-user".to_string(), &mut combined_vcl, &cmd_real_user, &env_real_user); -// -// // In case we define config file in ENV and we can find real-user in that file, therefore we need -// // to specify also data-directory in Environment or Command line, to be Node able figure out the -// // config file location, when config file is specified without absolute path or current directory eg.: "./" or "../" -// -// let mut user_defined = UserDefinedData { -// real_user: Default::default(), -// data_directory: Default::default(), -// config_file: Default::default(), -// real_user_type: RealUser::new(None, None, None).populate(dirs_wrapper), -// data_directory_path: data_directory_from_context(dirs_wrapper, &RealUser::new(None, None, None).populate(dirs_wrapper), DEFAULT_CHAIN), -// config_file_path: Default::default(), -// }; -// -// let preargs: Box; -// -// match combined_vcl.len() > 0 { -// true => { -// (user_defined.config_file, user_defined.data_directory, user_defined.real_user) = config_file_data_dir_real_user_from_enumerate(&combined_vcl.content); -// match user_defined.config_file.is_empty() { -// true => { -// user_defined.config_file = "config.toml".to_string(); -// check_status_of_data_directory(&mut user_defined); -// user_defined.config_file_path = user_defined.data_directory_path.join(user_defined.config_file_path); -// } -// false => { -// match check_status_of_config_file(&user_defined) { -// Ok(_) => {} -// Err(path) => { -// return Err(ConfigFileVclError::InvalidConfig( -// path, -// "Config file defined in Environment with relative path needs data-directory to be defined too".to_string() -// )) -// } -// } -// } -// } -// set_user_defined_config_file_and_path(&mut user_defined); -// let user_specified = !user_defined.config_file.is_empty(); -// let config_file_vcl = match ConfigFileVcl::new(&user_defined.config_file_path, user_specified) { -// Ok(cfv) => Box::new(cfv), -// Err(e) => return Err(e), -// }; -// preargs = merge( -// config_file_vcl, -// Box::new(EnvironmentVcl::new(app)), -// ); -// println!("preargs in first match config-file empty: {:#?}", preargs); -// } -// false => { -// user_defined.config_file_path = PathBuf::from(user_defined.data_directory).join(user_defined.config_file); -// let config_file_vcl = match ConfigFileVcl::new(&user_defined.config_file_path, false) { -// Ok(cfv) => Box::new(cfv), -// Err(e) => return Err(e), -// }; -// preargs = merge( -// config_file_vcl, -// Box::new(EnvironmentVcl::new(app)), -// ); -// } -// } -// -// let args = merge( -// preargs, -// Box::new(CommandLineVcl::new( -// vec!["".to_string(), -// "--config-file".to_string(), -// user_defined.config_file_path.to_string_lossy().to_string()] -// )) -// ); -// Ok(args) -// } - -fn set_user_defined_config_file_and_path(user_defined: &mut UserDefinedData) { - if user_defined.config_file.starts_with("./") { - let pwd = current_dir().expect("expected current directory from system call"); - user_defined.config_file = user_defined.config_file.replacen(".", pwd.to_string_lossy().to_string().as_str(), 1); - } - user_defined.config_file_path = PathBuf::from(&user_defined.config_file.to_string()); - if user_defined.config_file_path.is_relative() && !user_defined.data_directory.is_empty() { - user_defined.config_file_path = PathBuf::from(user_defined.data_directory.clone().to_string()).join(&user_defined.config_file_path); - } -} - -fn check_status_of_data_directory(user_defined: &mut UserDefinedData) { - match user_defined.data_directory.is_empty() { - true => {}, - false => { - user_defined.data_directory_path = PathBuf::from(&user_defined.data_directory); - } - } -} - -fn check_status_of_config_file(user_defined: &UserDefinedData) -> Result<(), PathBuf> { - return if (!user_defined.config_file.starts_with("/") && !user_defined.config_file.starts_with("./") && !user_defined.config_file.starts_with("../")) - && user_defined.data_directory.is_empty() { - Err(PathBuf::from(&user_defined.config_file)) - } else { Ok(()) } + .vcl_args() + .into_iter() + .filter(|vcl_arg| { + (vcl_arg.name() == "--chain") + || (vcl_arg.name() == "--real-user") + || (vcl_arg.name() == "--data-directory") + || (vcl_arg.name() == "--config-file") + }) + .map(|vcl_arg| vcl_arg.dup()) + .collect(); + let user_specified_data = + config_file_data_dir_real_user_chain_from_enumerate(dirs_wrapper, orientation_args); + + Ok(user_specified_data) } pub fn initialize_database( @@ -415,6 +318,7 @@ mod tests { use super::*; use crate::node_test_utils::DirsWrapperMock; use crate::test_utils::ArgsBuilder; + use masq_lib::shared_schema::{config_file_arg, data_directory_arg, DATA_DIRECTORY_HELP}; use masq_lib::test_utils::environment_guard::EnvironmentGuard; use masq_lib::test_utils::utils::ensure_node_home_directory_exists; use masq_lib::utils::find_free_port; @@ -474,11 +378,20 @@ mod tests { ) .unwrap(); assert_eq!( - &format!("{}", user_specific_data.config_file.parent().unwrap().display()), - &user_specific_data.data_directory.unwrap().to_string_lossy().to_string(), + &format!( + "{}", + user_specific_data.config_file.parent().unwrap().display() + ), + &user_specific_data + .data_directory + .to_string_lossy() + .to_string(), ); - assert_eq!("booga.toml", user_specific_data.config_file.file_name().unwrap()); - assert_eq!(true, user_specific_data.real_user_specified); + assert_eq!( + "booga.toml", + user_specific_data.config_file.file_name().unwrap() + ); + assert_eq!(user_specific_data.real_user_spec, false); } #[test] @@ -503,11 +416,20 @@ mod tests { ) .unwrap(); assert_eq!( - format!("{}", user_specific_data.config_file.parent().unwrap().display()), - user_specific_data.data_directory.unwrap().to_string_lossy().to_string(), + format!( + "{}", + user_specific_data.config_file.parent().unwrap().display() + ), + user_specific_data + .data_directory + .to_string_lossy() + .to_string(), + ); + assert_eq!( + "booga.toml", + user_specific_data.config_file.file_name().unwrap() ); - assert_eq!("booga.toml", user_specific_data.config_file.file_name().unwrap()); - assert_eq!(true, user_specific_data.real_user_specified); + assert_eq!(user_specific_data.real_user_spec, false); //all these assertions of 'real_user_specified' was incorrect, no idea how this tests could pass before } #[cfg(not(target_os = "windows"))] @@ -530,7 +452,7 @@ mod tests { "/tmp/booga.toml", &format!("{}", user_specific_data.config_file.display()) ); - assert_eq!(true, user_specific_data.real_user_specified); + assert_eq!(user_specific_data.real_user_spec, false); } #[cfg(target_os = "windows")] @@ -553,7 +475,7 @@ mod tests { r"\tmp\booga.toml", &format!("{}", user_specific_data.config_file.display()) ); - assert_eq!(true, user_specific_data.real_user_specified); + assert_eq!(user_specific_data.real_user_specified, false); } #[cfg(target_os = "windows")] @@ -576,7 +498,7 @@ mod tests { r"c:\tmp\booga.toml", &format!("{}", user_specific_data.config_file.display()) ); - assert_eq!(true, user_specific_data.real_user_specified); + assert_eq!(user_specific_data.real_user_specified, false); } #[cfg(target_os = "windows")] @@ -599,7 +521,7 @@ mod tests { r"\\TMP\booga.toml", &format!("{}", user_specific_data.config_file.display()) ); - assert_eq!(true, user_specific_data.real_user_specified); + assert_eq!(user_specific_data.real_user_specified, false); } #[cfg(target_os = "windows")] @@ -623,7 +545,7 @@ mod tests { r"c:tmp\booga.toml", &format!("{}", user_specific_data.config_file.display()) ); - assert_eq!(true, user_specific_data.real_user_specified); + assert_eq!(user_specific_data.real_user_specified, false); } #[test] diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index fe2e6bd48..e921df71b 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -1,12 +1,12 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::bootstrapper::BootstrapperConfig; -use crate::node_configurator::{argument_from_enumerate, DirsWrapperReal}; +use crate::node_configurator::DirsWrapperReal; use crate::node_configurator::{initialize_database, DirsWrapper, NodeConfigurator}; use masq_lib::crash_point::CrashPoint; use masq_lib::logger::Logger; -use masq_lib::multi_config::{ComputedVcl, MultiConfig, VclArg, VirtualCommandLine}; -use masq_lib::shared_schema::{ConfiguratorError, RealUser}; +use masq_lib::multi_config::{ComputedVcl, MultiConfig, VirtualCommandLine}; +use masq_lib::shared_schema::ConfiguratorError; use masq_lib::utils::NeighborhoodModeLight; use std::net::SocketAddr; use std::net::{IpAddr, Ipv4Addr}; @@ -24,16 +24,17 @@ use crate::node_configurator::unprivileged_parse_args_configuration::{ UnprivilegedParseArgsConfiguration, UnprivilegedParseArgsConfigurationDaoReal, }; use crate::node_configurator::{ - data_directory_from_context, determine_user_specific_data, real_user_data_directory_path_and_chain, + data_directory_from_context, determine_user_specific_data, + real_user_data_directory_path_and_chain, }; use crate::sub_lib::cryptde::PublicKey; use crate::sub_lib::cryptde_null::CryptDENull; use crate::sub_lib::utils::make_new_multi_config; use crate::tls_discriminator_factory::TlsDiscriminatorFactory; +use itertools::Itertools; use masq_lib::constants::{DEFAULT_UI_PORT, HTTP_PORT, TLS_PORT}; use masq_lib::multi_config::{CommandLineVcl, ConfigFileVcl, EnvironmentVcl}; use std::str::FromStr; -use itertools::Itertools; pub struct NodeConfiguratorStandardPrivileged { dirs_wrapper: Box, @@ -142,47 +143,103 @@ pub fn server_initializer_collected_params<'a>( ) -> Result, ConfiguratorError> { let app = app_node(); let user_specific_data = determine_user_specific_data(dirs_wrapper, &app, args)?; - let config_file_vcl = match ConfigFileVcl::new(&user_specific_data.config_file, user_specific_data.config_file_specified) { + let config_file_vcl = match ConfigFileVcl::new( + &user_specific_data.config_file, + user_specific_data.config_file_spec, + ) { Ok(cfv) => cfv, - Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())) + Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), }; - let config_file_specified = &user_specific_data.config_file_specified; - let config_file_path = &user_specific_data.config_file; - let to_iter_config = &config_file_vcl.args(); - let config_real_user_args = to_iter_config - .iter().enumerate() - .filter(|(index, vcl_arg)| { - vcl_arg.deref() == &"--real-user".to_string() - }).collect_vec(); - let (real_user, real_user_specified) = match config_real_user_args.is_empty() { - false => { - let real_user_str = to_iter_config[config_real_user_args[0].0 + 1].clone(); - (real_user_str, true) - }, - true => (user_specific_data.real_user.unwrap().to_string(), false) - }; - + // println!("user_specific_data: {:#?}", user_specific_data); + let environment_vcl = EnvironmentVcl::new(&app); + let commandline_vcl = CommandLineVcl::new(args.to_vec()); + let config_file_specified = user_specific_data.config_file_spec; + let config_file_path = user_specific_data.config_file; + let extract_value_from_vcl = + |vcl: &dyn VirtualCommandLine, name: &str, var: &str, spec: bool| { + let args = vcl.args(); + let extracted_args = args + .iter() + .enumerate() + .filter(|(_index, vcl_arg)| vcl_arg.deref() == &name.to_string()) + .collect_vec(); + match extracted_args.is_empty() { + false => { + let val_str = args[extracted_args[0].0 + 1].clone(); + (val_str, true) + } + true => (var.to_string(), spec), + } + }; + let (cf_real_user, cf_real_user_specified) = extract_value_from_vcl( + &config_file_vcl, + "--real-user", + user_specific_data.real_user.to_string().as_str(), + user_specific_data.real_user_spec, + ); + let (env_real_user, env_real_user_specified) = extract_value_from_vcl( + &environment_vcl, + "--real-user", + user_specific_data.real_user.to_string().as_str(), + user_specific_data.real_user_spec, + ); + let (cmd_real_user, cmd_real_user_specified) = extract_value_from_vcl( + &commandline_vcl, + "--real-user", + user_specific_data.real_user.to_string().as_str(), + user_specific_data.real_user_spec, + ); let mut full_multi_config_vec: Vec> = vec![ - Box::new(config_file_vcl), - Box::new(EnvironmentVcl::new(&app)), - Box::new(CommandLineVcl::new(args.to_vec())), - ]; + Box::new(config_file_vcl), + Box::new(environment_vcl), + Box::new(commandline_vcl), + ]; //TODO write test for MultiConfig "try_new" merge line 76 - let mut fill_specified_or_unspecified_box = |key: &str, value: &str, specified: bool | { + let mut fill_specified_or_unspecified_box = |key: &str, value: &str, specified: bool| { match specified { - true => full_multi_config_vec.push(Box::new(CommandLineVcl::new(vec!["".to_string(), key.to_string(), value.to_string(),]))), - false => full_multi_config_vec.push(Box::new(ComputedVcl::new(vec!["".to_string(), key.to_string(), value.to_string(),]))) + true => match value.is_empty() { + true => (), + false => full_multi_config_vec.push(Box::new(CommandLineVcl::new(vec![ + "".to_string(), + key.to_string(), + value.to_string(), + ]))), + }, + false => match value.is_empty() { + true => (), + false => full_multi_config_vec.push(Box::new(ComputedVcl::new(vec![ + "".to_string(), + key.to_string(), + value.to_string(), + ]))), + }, }; }; - fill_specified_or_unspecified_box("--config-file", &user_specific_data.config_file.as_path().to_string_lossy().as_ref(), user_specific_data.config_file_specified); - fill_specified_or_unspecified_box("--data-directory", &user_specific_data.data_directory.unwrap().to_string_lossy().as_ref(), user_specific_data.data_directory_specified); - fill_specified_or_unspecified_box("--real-user", real_user.to_string().as_str(), real_user_specified); - //TODO check if following two lines can't overwrite the computed values wrongly - full_multi_config_vec.push( Box::new(EnvironmentVcl::new(&app))); - full_multi_config_vec.push( Box::new(CommandLineVcl::new(args.to_vec()))); - // println!("full_multi_config_vec: {:#?}", full_multi_config_vec); - let full_multi_config = make_new_multi_config(&app,full_multi_config_vec)?; + fill_specified_or_unspecified_box( + "--config-file", + config_file_path.as_path().to_string_lossy().as_ref(), + config_file_specified, + ); + fill_specified_or_unspecified_box( + "--data-directory", + user_specific_data.data_directory.to_string_lossy().as_ref(), + user_specific_data.data_directory_spec, + ); + fill_specified_or_unspecified_box("--real-user", cf_real_user.as_str(), cf_real_user_specified); + fill_specified_or_unspecified_box( + "--real-user", + env_real_user.as_str(), + env_real_user_specified, + ); + fill_specified_or_unspecified_box( + "--real-user", + cmd_real_user.as_str(), + cmd_real_user_specified, + ); + // println!("full_multi_config_vec: {:#?}", full_multi_config_vec); + let full_multi_config = make_new_multi_config(&app, full_multi_config_vec)?; + // println!("full_multi_config: {:#?}", full_multi_config); Ok(full_multi_config) } @@ -329,7 +386,7 @@ mod tests { use masq_lib::multi_config::VirtualCommandLine; use masq_lib::test_utils::environment_guard::{ClapGuard, EnvironmentGuard}; use masq_lib::test_utils::utils::{ensure_node_home_directory_exists, TEST_DEFAULT_CHAIN}; - use masq_lib::utils::{running_test, slice_of_strs_to_vec_of_strings}; + use masq_lib::utils::running_test; use rustc_hex::FromHex; use std::convert::TryFrom; use std::env::current_dir; @@ -481,6 +538,7 @@ mod tests { fn server_initializer_collected_params_can_read_parameters_from_config_file() { running_test(); let _guard = EnvironmentGuard::new(); + let _clap_guard = ClapGuard::new(); let home_dir = ensure_node_home_directory_exists( "node_configurator_standard", "server_initializer_collected_params_can_read_parameters_from_config_file", @@ -488,16 +546,20 @@ mod tests { { let mut config_file = File::create(home_dir.join("config.toml")).unwrap(); config_file - .write_all(b"dns-servers = \"111.111.111.111,222.222.222.222\"\n") + .write_all(b"dns-servers = \"111.111.111.111,222.222.222.222\"\nreal-user = \"1004:1004:/home/gooba\"\n") .unwrap(); } + std::env::set_var("MASQ_DNS_SERVERS", ""); + std::env::set_var("MASQ_CONFIG_FILE", ""); let directory_wrapper = make_pre_populated_mocked_directory_wrapper(); - - let multi_config = server_initializer_collected_params( - &directory_wrapper, - &slice_of_strs_to_vec_of_strings(&["", "--data-directory", home_dir.to_str().unwrap()]), - ) - .unwrap(); + let args = ArgsBuilder::new().param("--data-directory", home_dir.to_str().unwrap()); + let args_vec: Vec = args.into(); + let multi_config = + server_initializer_collected_params(&directory_wrapper, args_vec.as_slice()).unwrap(); + assert_eq!( + value_m!(multi_config, "data-directory", String).unwrap(), + home_dir.to_str().unwrap() + ); assert_eq!( value_m!(multi_config, "dns-servers", String).unwrap(), "111.111.111.111,222.222.222.222".to_string() @@ -797,17 +859,24 @@ mod tests { #[test] fn multi_config_vcl_is_computed_do_right_job() { running_test(); - let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot"); + let _env_guard = EnvironmentGuard::new(); + let _clap_guard = ClapGuard::new(); + let home_dir = ensure_node_home_directory_exists( + "node_configurator_standard", + "multi_config_vcl_is_computed_do_right_job", + ); let data_dir = &home_dir.join("data_dir"); - let config_file_relative = File::create(PathBuf::from("./generated").join("config.toml")).unwrap(); + let config_file_relative = File::create(PathBuf::from("./generated/test/node_configurator_standard/multi_config_vcl_is_computed_do_right_job").join("config.toml")).unwrap(); fill_up_config_file(config_file_relative); let env_vec_array = vec![ - ("MASQ_CONFIG_FILE", "./generated/config.toml"), + ("MASQ_CONFIG_FILE", "./generated/test/node_configurator_standard/multi_config_vcl_is_computed_do_right_job/config.toml"), #[cfg(not(target_os = "windows"))] ("MASQ_REAL_USER", "9999:9999:booga"), ]; - env_vec_array.clone().into_iter() - .for_each (|(name, value)| std::env::set_var (name, value)); + env_vec_array + .clone() + .into_iter() + .for_each(|(name, value)| std::env::set_var(name, value)); let args = ArgsBuilder::new(); let args_vec: Vec = args.into(); let dir_wrapper = DirsWrapperMock::new() @@ -817,49 +886,37 @@ mod tests { let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let env_multiconfig = result.unwrap(); - match env_multiconfig.is_user_specified("--data-directory") { - true => (), - false => { - assert_eq!( - value_m!(env_multiconfig, "data-directory", String).unwrap(), - "booga/data_dir/MASQ/polygon-mainnet".to_string() - ) - } - } - - match (env_multiconfig.is_user_specified("--config-file"), env_multiconfig.is_user_specified("--data-directory")) { - (true, true) => { - let pwd = PathBuf::from( value_m!(env_multiconfig, "data-directory", String).unwrap()); - assert_eq!( - value_m!(env_multiconfig, "config-file", String).unwrap(), - pwd.join(PathBuf::from( "generated/config.toml")).to_string_lossy().to_string() - ) - }, - (true, false) => { - let pwd = current_dir().unwrap(); - assert_eq!( - value_m!(env_multiconfig, "config-file", String).unwrap(), - pwd.join(PathBuf::from( "generated/config.toml")).to_string_lossy().to_string() - ) - }, - (_, _) => () - } + assert_eq!( + value_m!(env_multiconfig, "data-directory", String).unwrap(), + "booga/data_dir/MASQ/polygon-mainnet".to_string() + ); + assert_eq!( + value_m!(env_multiconfig, "config-file", String).unwrap(), + current_dir().expect("expected curerrnt dir") + .join(PathBuf::from( "./generated/test/node_configurator_standard/multi_config_vcl_is_computed_do_right_job/config.toml")) + .to_string_lossy().to_string() + ); } #[test] - fn server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot() { + fn server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot( + ) { running_test(); + let _guard = EnvironmentGuard::new(); + let _clap_guard = ClapGuard::new(); let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot"); let data_dir = &home_dir.join("data_dir"); - let config_file_relative = File::create(PathBuf::from("./generated").join("config.toml")).unwrap(); + let config_file_relative = File::create(PathBuf::from("./generated/test/node_configurator_standard/server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot").join("config.toml")).unwrap(); fill_up_config_file(config_file_relative); let env_vec_array = vec![ - ("MASQ_CONFIG_FILE", "./generated/config.toml"), + ("MASQ_CONFIG_FILE", "./generated/test/node_configurator_standard/server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot/config.toml"), #[cfg(not(target_os = "windows"))] ("MASQ_REAL_USER", "9999:9999:booga"), ]; - env_vec_array.clone().into_iter() - .for_each (|(name, value)| std::env::set_var (name, value)); + env_vec_array + .clone() + .into_iter() + .for_each(|(name, value)| std::env::set_var(name, value)); let args = ArgsBuilder::new(); let args_vec: Vec = args.into(); let dir_wrapper = DirsWrapperMock::new() @@ -869,50 +926,83 @@ mod tests { let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let env_multiconfig = result.unwrap(); - match env_multiconfig.is_user_specified("--data-directory") { - true => (), - false => { - assert_eq!( - value_m!(env_multiconfig, "data-directory", String).unwrap(), - "/home/wooga/data_dir/MASQ/polygon-mainnet".to_string() - ) - } - } + assert_eq!(env_multiconfig.is_user_specified("--data-directory"), false); + assert_eq!( + value_m!(env_multiconfig, "data-directory", String).unwrap(), + "booga/data_dir/MASQ/polygon-mainnet".to_string() + ); + assert_eq!( + value_m!(env_multiconfig, "config-file", String).unwrap(), + current_dir().unwrap().join(PathBuf::from( "./generated/test/node_configurator_standard/server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot/config.toml")).to_string_lossy().to_string() + ); + } - match (env_multiconfig.is_user_specified("--config-file"), env_multiconfig.is_user_specified("--data-directory")) { - (true, true) => { - let pwd = PathBuf::from( value_m!(env_multiconfig, "data-directory", String).unwrap()); - assert_eq!( - value_m!(env_multiconfig, "config-file", String).unwrap(), - pwd.join(PathBuf::from( "generated/config.toml")).to_string_lossy().to_string() - ) - }, - (true, false) => { - let pwd = current_dir().unwrap(); - assert_eq!( - value_m!(env_multiconfig, "config-file", String).unwrap(), - pwd.join(PathBuf::from( "generated/config.toml")).to_string_lossy().to_string() - ) - }, - (_, _) => () - } + #[test] + fn server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_data_dir_started_by_tilde( + ) { + running_test(); + let _guard = EnvironmentGuard::new(); + let _clap_guard = ClapGuard::new(); + let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_data_dir_started_by_tilde"); + let data_dir = &home_dir.join("data_dir"); + let _create_data_dir = create_dir_all(data_dir); + let config_file_relative = File::create(home_dir.join("config.toml")).unwrap(); + fill_up_config_file(config_file_relative); + let env_vec_array = vec![ + ("MASQ_DATA_DIRECTORY", "~/data_dir"), + ("MASQ_CONFIGFILE", "~/config.toml"), + #[cfg(not(target_os = "windows"))] + ("MASQ_REAL_USER", "9999:9999:booga"), + ]; + env_vec_array + .clone() + .into_iter() + .for_each(|(name, value)| std::env::set_var(name, value)); + let args = ArgsBuilder::new(); + let args_vec: Vec = args.into(); + let dir_wrapper = DirsWrapperMock::new() + .home_dir_result(Some(current_dir().expect("expect current directory").join("generated/test/node_configurator_standard/server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_tilde"))) + .data_dir_result(Some(data_dir.to_path_buf())); + let result_data_dir = current_dir().expect("expect current directory").join("generated/test/node_configurator_standard/server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_tilde/data_dir"); + + let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); + let env_multiconfig = result.unwrap(); + + assert_eq!(env_multiconfig.is_user_specified("--data-directory"), true); + assert_eq!( + value_m!(env_multiconfig, "data-directory", String).unwrap(), + result_data_dir.to_string_lossy().to_string() + ); + assert_eq!( + value_m!(env_multiconfig, "config-file", String).unwrap(), + result_data_dir + .join(PathBuf::from("config.toml")) + .to_string_lossy() + .to_string() + ); } #[test] - fn server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_data_directory() { + fn server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_data_directory( + ) { running_test(); - let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot"); - println!("home_dir {:#?}", &home_dir); + let _guard = EnvironmentGuard::new(); + let _clap_guard = ClapGuard::new(); + let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_data_directory"); let data_dir = &home_dir.join("data_dir"); - let config_file_relative = File::create(PathBuf::from("./generated").join("config.toml")).unwrap(); + create_dir_all(home_dir.join("config")).expect("expected directory for config"); + let config_file_relative = File::create(&home_dir.join("config/config.toml")).unwrap(); fill_up_config_file(config_file_relative); - let current_directory = current_dir().unwrap(); vec![ - ("MASQ_CONFIG_FILE", "generated/config.toml"), - //("MASQ_DATA_DIRECTORY", ¤t_directory.display().to_string().as_str()), - ].into_iter() - .for_each (|(name, value)| std::env::set_var (name, value)); - let args = ArgsBuilder::new(); + ("MASQ_CONFIG_FILE", "config/config.toml"), + ("MASQ_DATA_DIRECTORY", "/unexistent/directory"), + ("MASQ_REAL_USER", "999:999:/home/malooga"), + ] + .into_iter() + .for_each(|(name, value)| std::env::set_var(name, value)); + let args = ArgsBuilder::new() + .param("--real-user", "1001:1001:cooga") + .param("--data-directory", &home_dir.to_string_lossy().to_string()); let args_vec: Vec = args.into(); let dir_wrapper = DirsWrapperMock::new() .home_dir_result(Some(home_dir.clone())) @@ -921,174 +1011,88 @@ mod tests { let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let multiconfig = result.unwrap(); - match multiconfig.is_user_specified("--data-directory") { - true => { - assert_eq!( - &value_m!(multiconfig, "data-directory", String).unwrap(), - ¤t_directory.to_string_lossy().to_string() - ) - }, - false => () - } - println!("real-user {}", value_m!(multiconfig, "real-user", String).unwrap()); + assert_eq!(multiconfig.is_user_specified("--data-directory"), true); + assert_eq!( + &value_m!(multiconfig, "data-directory", String).unwrap(), + &home_dir.to_string_lossy().to_string() + ); assert_eq!( &value_m!(multiconfig, "real-user", String).unwrap(), - "1002:1002:/home/wooga" - ) + "1001:1001:cooga" + ); + } + + #[test] + #[should_panic( + expected = "You need to define data-directory to be able define config file with naked directory 'dirname/config.toml'." + )] + fn server_initializer_collected_params_fails_on_naked_dir_config_file_without_data_directory() { + running_test(); + let _guard = EnvironmentGuard::new(); + let _clap_guard = ClapGuard::new(); + let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot"); + + let data_dir = &home_dir.join("data_dir"); + vec![("MASQ_CONFIG_FILE", "config/config.toml")] + .into_iter() + .for_each(|(name, value)| std::env::set_var(name, value)); + let args = ArgsBuilder::new(); + let args_vec: Vec = args.into(); + let dir_wrapper = DirsWrapperMock::new() + .home_dir_result(Some(home_dir.clone())) + .data_dir_result(Some(data_dir.to_path_buf())); - // match (multiconfig.is_user_specified("--config-file"), env_multiconfig.is_user_specified("--data-directory")) { - // (true, true) => { - // let pwd = PathBuf::from( value_m!(multiconfig, "data-directory", String).unwrap()); - // assert_eq!( - // value_m!(env_multiconfig, "config-file", String).unwrap(), - // pwd.join(PathBuf::from( "generated/config.toml")).to_string_lossy().to_string() - // ) - // }, - // (_, _) => () - // } + let _result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); } - // #[test] - // fn server_initializer_collected_params_combine_vlcs_properly() { - // running_test(); - // let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_combine_vlcs_properly"); - // let data_dir = &home_dir.join("data_dir"); - // let config_file = File::create(&home_dir.join("config.toml")).unwrap(); - // let current_directory = current_dir().unwrap(); - // let data_dir_sys = create_dir_all(PathBuf::from(home_dir.to_string_lossy().as_ref().to_owned() + "/data_dir/MASQ/polygon-mainnet")); - // { - // fill_up_config_file(config_file); - // match data_dir_sys { - // Ok(..) => { - // let config_file_vcl = File::create(PathBuf::from(home_dir.to_string_lossy().as_ref().to_owned() + "/data_dir/MASQ/polygon-mainnet").join("config.toml")).unwrap(); - // fill_up_config_file(config_file_vcl); - // } - // Err(e) => panic!("unable to create config file: {}", e) - // } - // } - // - // let env_vec_array = vec![ - // ("MASQ_CONFIG_FILE", "./generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home/config.toml"), - // //("MASQ_DATA_DIRECTORY", home_dir.to_str().unwrap()), - // #[cfg(not(target_os = "windows"))] - // ("MASQ_REAL_USER", "9999:9999:booga"), - // ]; - // env_vec_array.clone().into_iter() - // .for_each (|(name, value)| std::env::set_var (name, value)); - // let args = ArgsBuilder::new(); - // let args_vec: Vec = args.into(); - // let dir_wrapper = DirsWrapperMock::new() - // .home_dir_result(Some(home_dir.clone())) - // .data_dir_result(Some(data_dir.to_path_buf())); - // let mut env_data_directory = false; - // for (item, _vaue) in env_vec_array.to_vec().as_slice() { - // if item == &"MASQ_DATA_DIRECTORY" { env_data_directory = true; } - // } - // - // let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); - // let env_multiconfig = result.unwrap(); - // - // let args = ArgsBuilder::new() - // .param("--data-directory", current_directory.join(Path::new("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home")).to_string_lossy().to_string().as_str()) - // .param("--real-user", "1001:1001:cooga"); - // let args_vec: Vec = args.into(); - // let mut cmd_data_directory = false; - // for item in args_vec.clone().as_slice() { - // if item == &"--data-directory".to_string() { cmd_data_directory = true; } - // } - // let params = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); - // let result2 = params.as_ref().expect("REASON"); - // let vcl_multiconfig = result2; - // - // assert_eq!( - // value_m!(env_multiconfig, "config-file", String).unwrap(), - // current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home/config.toml").to_string_lossy().to_string() - // ); - // match env_multiconfig.is_user_specified("--data-directory") { - // true => { - // assert_eq!( - // value_m!(env_multiconfig, "data-directory", String).unwrap(), - // "generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home".to_string() - // ) - // } - // false => { - // println!("data-directory is not user specified in Environment"); - // () - // } - // } - // - // #[cfg(not(target_os = "windows"))] - // match env_multiconfig.is_user_specified("--real-user") { - // true => { - // assert_eq!( - // value_m!(env_multiconfig, "real-user", String).unwrap(), - // "1002:1002:/home/wooga".to_string() - // ) - // } - // false => { - // println!("real-user is not user specified in Environment"); - // () - // } - // } - // - // #[cfg(not(target_os = "windows"))] - // match vcl_multiconfig.is_user_specified("--real-user") { - // true => { - // match env_multiconfig.is_user_specified("--real-user") { - // true => { - // println!("real-user is inherited from Environment {}", value_m!(vcl_multiconfig, "real-user", String).unwrap()); - // assert_eq!( - // value_m!(vcl_multiconfig, "real-user", String).unwrap(), - // "1001:1001:cooga".to_string() - // ) - // } - // false => { - // assert_eq!( - // value_m!(vcl_multiconfig, "real-user", String).unwrap(), - // "1002:1002:/home/wooga".to_string() - // ) - // } - // } - // } - // false => { - // println!("real-user is not user specified in Command-line"); - // () - // } - // } - // println!("env_data_directory {:#?}", env_data_directory); - // match vcl_multiconfig.is_user_specified("--data-directory") { - // true => { - // match env_data_directory && !cmd_data_directory { - // true => { - // println!("data-directory is inherited from Environment"); - // assert_eq!( - // value_m!(vcl_multiconfig, "data-directory", String).unwrap(), - // "generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home".to_string() - // ) - // } - // false => { - // match cmd_data_directory { - // true => { - // assert_eq!( - // value_m!(vcl_multiconfig, "data-directory", String).unwrap(), - // current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home").to_string_lossy().to_string() - // ) - // } - // false => { - // println!("data-directory is not user specified in ENV") - // } - // } - // - // } - // }; - // - // } - // false => { - // println!("data-directory is not user specified in Command-line"); - // () - // } - // } - // } + #[test] + fn server_initializer_collected_params_combine_vlcs_properly() { + running_test(); + let _guard = EnvironmentGuard::new(); + let _clap_guard = ClapGuard::new(); + let home_dir = ensure_node_home_directory_exists( + "node_configurator_standard", + "server_initializer_collected_params_combine_vlcs_properly", + ); + let data_dir = &home_dir.join("data_dir"); + let config_file = File::create(&home_dir.join("config.toml")).unwrap(); + let current_directory = current_dir().unwrap(); + fill_up_config_file(config_file); + + let env_vec_array = vec![ + ("MASQ_CONFIG_FILE", "config.toml"), + ("MASQ_DATA_DIRECTORY", "/nonexistent/directory/home"), + #[cfg(not(target_os = "windows"))] + ("MASQ_REAL_USER", "9999:9999:booga"), + ]; + env_vec_array + .clone() + .into_iter() + .for_each(|(name, value)| std::env::set_var(name, value)); + let dir_wrapper = DirsWrapperMock::new() + .home_dir_result(Some(home_dir.clone())) + .data_dir_result(Some(data_dir.to_path_buf())); + let args = ArgsBuilder::new() + .param("--data-directory", current_directory.join(Path::new("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home")).to_string_lossy().to_string().as_str()) + .param("--real-user", "1001:1001:cooga"); + let args_vec: Vec = args.into(); + let params = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); + let result = params.as_ref().expect("REASON"); + let multiconfig = result; + + assert_eq!( + value_m!(multiconfig, "config-file", String).unwrap(), + current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home/config.toml").to_string_lossy().to_string() + ); + assert_eq!(multiconfig.is_user_specified("--data-directory"), true); + + #[cfg(not(target_os = "windows"))] + assert_eq!(multiconfig.is_user_specified("--real-user"), true); + assert_eq!( + value_m!(multiconfig, "real-user", String).unwrap(), + "1001:1001:cooga".to_string() + ); + } #[test] fn server_initializer_collected_params_senses_when_user_specifies_config_file() { @@ -1232,6 +1236,7 @@ mod tests { #[test] fn server_initializer_collected_params_rejects_invalid_gas_price() { running_test(); + let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); let args = ArgsBuilder::new().param("--gas-price", "unleaded"); let args_vec: Vec = args.into(); @@ -1408,8 +1413,8 @@ mod tests { .data_dir_result(Some(PathBuf::from(standard_data_dir))), }; - let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()) - .unwrap(); + let result = + server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()).unwrap(); assert_eq!( value_m!(result, "data-directory", String).unwrap(), @@ -1420,6 +1425,7 @@ mod tests { #[test] fn server_initializer_collected_params_senses_when_user_specifies_data_directory_without_chain_specific_directory( ) { + let _guard = EnvironmentGuard::new(); running_test(); let home_dir = Path::new("/home/cooga"); let home_dir_poly_main = home_dir.join(".local").join("MASQ").join("polygon-mainnet"); diff --git a/node/src/server_initializer.rs b/node/src/server_initializer.rs index eac4a5ac0..f7d4961ad 100644 --- a/node/src/server_initializer.rs +++ b/node/src/server_initializer.rs @@ -47,18 +47,14 @@ impl ServerInitializer for ServerInitializerReal { .as_mut() .initialize_as_privileged(¶ms), ) - .combine_results( - self.bootstrapper - .as_mut() - .initialize_as_privileged(¶ms), - ); + .combine_results(self.bootstrapper.as_mut().initialize_as_privileged(¶ms)); self.privilege_dropper .chown(Path::new(data_directory.as_str()), &real_user); self.privilege_dropper.drop_privileges(&real_user); - result + result .combine_results( self.dns_socket_server .as_mut() diff --git a/node/tests/utils.rs b/node/tests/utils.rs index b60017063..bc80d7d39 100644 --- a/node/tests/utils.rs +++ b/node/tests/utils.rs @@ -273,6 +273,8 @@ impl MASQNode { #[allow(dead_code)] pub fn wait_for_exit(&mut self) -> Option { + //TODO Put the body of this function in a background thread and wait on the thread for a few + // seconds. If the thread doesn't terminate, leak the thread and return None. let child_opt = self.child.take(); let output_opt = self.output.take(); match (child_opt, output_opt) { From 413ffc599013ae25de9aa54dcf12d5032705306b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Mon, 23 Oct 2023 20:52:56 +0200 Subject: [PATCH 13/52] fix windows test of real_user soecified --- node/src/node_configurator/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index 1e769aaed..a7c36e7d9 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -475,7 +475,7 @@ mod tests { r"\tmp\booga.toml", &format!("{}", user_specific_data.config_file.display()) ); - assert_eq!(user_specific_data.real_user_specified, false); + assert_eq!(user_specific_data.real_user_spec, false); } #[cfg(target_os = "windows")] @@ -498,7 +498,7 @@ mod tests { r"c:\tmp\booga.toml", &format!("{}", user_specific_data.config_file.display()) ); - assert_eq!(user_specific_data.real_user_specified, false); + assert_eq!(user_specific_data.real_user_spec, false); } #[cfg(target_os = "windows")] @@ -521,7 +521,7 @@ mod tests { r"\\TMP\booga.toml", &format!("{}", user_specific_data.config_file.display()) ); - assert_eq!(user_specific_data.real_user_specified, false); + assert_eq!(user_specific_data.real_user_spec, false); } #[cfg(target_os = "windows")] @@ -545,7 +545,7 @@ mod tests { r"c:tmp\booga.toml", &format!("{}", user_specific_data.config_file.display()) ); - assert_eq!(user_specific_data.real_user_specified, false); + assert_eq!(user_specific_data.real_user_spec, false); } #[test] From 18d0cc1dd5aa8585adcb68094dead6b11fa0f631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Wed, 25 Oct 2023 18:15:47 +0200 Subject: [PATCH 14/52] renaming, adding TODOs, removing resolved TODOS --- masq_lib/src/multi_config.rs | 1 + node/src/node_configurator/mod.rs | 22 +++++++++---------- .../node_configurator_standard.rs | 3 +-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/masq_lib/src/multi_config.rs b/masq_lib/src/multi_config.rs index c3e3e49c3..b2b5ec21e 100644 --- a/masq_lib/src/multi_config.rs +++ b/masq_lib/src/multi_config.rs @@ -73,6 +73,7 @@ impl<'a> MultiConfig<'a> { }; }) }); + // TODO pull this out to function to use in determine_user_specific_data let merged = vcls .into_iter() .fold(initial, |so_far, vcl| merge(so_far, vcl)); diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index a7c36e7d9..380c038a8 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -42,7 +42,7 @@ pub struct UserSpecifiedData { } fn get_chain_from_vcl(configs: &[Box]) -> (Chain, bool) { - match argument_from_enumerate(configs, "--chain") { + match argument_from_vcl(configs, "--chain") { Some(chain) => (Chain::from(chain), true), None => (DEFAULT_CHAIN, false), } @@ -52,7 +52,7 @@ fn get_real_user_from_vcl( configs: &[Box], dirs_wrapper: &dyn DirsWrapper, ) -> (RealUser, bool) { - match argument_from_enumerate(configs, "--real-user") { + match argument_from_vcl(configs, "--real-user") { Some(user) => { let real_user_split: Vec<&str> = user.split(':').collect(); ( @@ -81,7 +81,7 @@ fn get_data_directory_from_vcl( real_user: &RealUser, chain: &Chain, ) -> (PathBuf, bool) { - match argument_from_enumerate(configs, "--data-directory") { + match argument_from_vcl(configs, "--data-directory") { Some(data_dir) => match PathBuf::from(data_dir).starts_with("~/") { true => { let home_dir_from_wrapper = dirs_wrapper @@ -111,7 +111,7 @@ fn get_config_file_from_vcl( data_directory_def: bool, dirs_wrapper: &dyn DirsWrapper, ) -> (PathBuf, bool) { - match argument_from_enumerate(configs, "--config-file") { + match argument_from_vcl(configs, "--config-file") { Some(config_str) => { let path = match PathBuf::from(config_str).is_relative() { true => { @@ -123,9 +123,9 @@ fn get_config_file_from_vcl( true => { let home_dir_from_wrapper = dirs_wrapper .home_dir() - .expect("expexted users home dir") + .expect("expexted users home_dir") .to_str() - .expect("expect home dir") + .expect("expect str home_dir") .to_string(); let replaced_tilde_dir = config_str @@ -135,7 +135,7 @@ fn get_config_file_from_vcl( } false => match data_directory_def { true => PathBuf::from(data_directory).join(PathBuf::from(config_str)), - false => panic!("You need to define data-directory to be able define config file with naked directory 'dirname/config.toml'.") + false => panic!("You need to define data-directory to define config file with naked directory 'dirname/config.toml'.") } } } @@ -155,11 +155,10 @@ fn get_config_file_from_vcl( } } -fn config_file_data_dir_real_user_chain_from_enumerate( +fn config_file_data_dir_real_user_chain_from_vcl( dirs_wrapper: &dyn DirsWrapper, configs: Vec>, ) -> UserSpecifiedData { - //TODO break down this function to collection of small one purpose functions let mut user_specified_data = UserSpecifiedData { chain: Default::default(), chain_spec: false, @@ -197,7 +196,7 @@ fn config_file_data_dir_real_user_chain_from_enumerate( user_specified_data } -fn argument_from_enumerate<'a>(configs: &'a [Box], needle: &'a str) -> Option<&'a str> { +fn argument_from_vcl<'a>(configs: &'a [Box], needle: &'a str) -> Option<&'a str> { match configs .iter() .find(|vcl_arg_box| vcl_arg_box.deref().name() == needle) @@ -226,8 +225,9 @@ pub fn determine_user_specific_data( }) .map(|vcl_arg| vcl_arg.dup()) .collect(); + //TODO probably need to implement new merge from config_vcl with orientation_args let user_specified_data = - config_file_data_dir_real_user_chain_from_enumerate(dirs_wrapper, orientation_args); + config_file_data_dir_real_user_chain_from_vcl(dirs_wrapper, orientation_args); Ok(user_specified_data) } diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index e921df71b..42609aa62 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -150,7 +150,6 @@ pub fn server_initializer_collected_params<'a>( Ok(cfv) => cfv, Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), }; - // println!("user_specific_data: {:#?}", user_specific_data); let environment_vcl = EnvironmentVcl::new(&app); let commandline_vcl = CommandLineVcl::new(args.to_vec()); let config_file_specified = user_specific_data.config_file_spec; @@ -1024,7 +1023,7 @@ mod tests { #[test] #[should_panic( - expected = "You need to define data-directory to be able define config file with naked directory 'dirname/config.toml'." + expected = "You need to define data-directory to define config file with naked directory 'dirname/config.toml'." )] fn server_initializer_collected_params_fails_on_naked_dir_config_file_without_data_directory() { running_test(); From efd3fbb57d07f360354338dbb9790513ecee233f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Mon, 30 Oct 2023 15:07:08 +0100 Subject: [PATCH 15/52] fixing tests for windows os --- .../node_configurator_standard.rs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 42609aa62..917297c96 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -224,12 +224,15 @@ pub fn server_initializer_collected_params<'a>( user_specific_data.data_directory.to_string_lossy().as_ref(), user_specific_data.data_directory_spec, ); + #[cfg(not(target_os = "windows"))] fill_specified_or_unspecified_box("--real-user", cf_real_user.as_str(), cf_real_user_specified); + #[cfg(not(target_os = "windows"))] fill_specified_or_unspecified_box( "--real-user", env_real_user.as_str(), env_real_user_specified, ); + #[cfg(not(target_os = "windows"))] fill_specified_or_unspecified_box( "--real-user", cmd_real_user.as_str(), @@ -885,10 +888,17 @@ mod tests { let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let env_multiconfig = result.unwrap(); + #[cfg(not(target_os = "windows"))] assert_eq!( value_m!(env_multiconfig, "data-directory", String).unwrap(), "booga/data_dir/MASQ/polygon-mainnet".to_string() ); + #[cfg(target_os = "windows")] + assert_eq!( + value_m!(env_multiconfig, "data-directory", String).unwrap(), + "generated/test/node_configurator_standard/multi_config_vcl_is_computed_do_right_job/home\\data_dir\\MASQ\\polygon-mainnet".to_string() + ); + #[cfg(not(target_os = "windows"))] assert_eq!( value_m!(env_multiconfig, "config-file", String).unwrap(), current_dir().expect("expected curerrnt dir") @@ -926,10 +936,17 @@ mod tests { let env_multiconfig = result.unwrap(); assert_eq!(env_multiconfig.is_user_specified("--data-directory"), false); + #[cfg(not(target_os = "windows"))] assert_eq!( value_m!(env_multiconfig, "data-directory", String).unwrap(), "booga/data_dir/MASQ/polygon-mainnet".to_string() ); + #[cfg(target_os = "windows")] + assert_eq!( + value_m!(env_multiconfig, "data-directory", String).unwrap(), + "generated/test/node_configurator_standard/multi_config_vcl_is_computed_do_right_job/home\\data_dir\\MASQ\\polygon-mainnet".to_string() + ); + #[cfg(not(target_os = "windows"))] assert_eq!( value_m!(env_multiconfig, "config-file", String).unwrap(), current_dir().unwrap().join(PathBuf::from( "./generated/test/node_configurator_standard/server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot/config.toml")).to_string_lossy().to_string() @@ -995,6 +1012,7 @@ mod tests { vec![ ("MASQ_CONFIG_FILE", "config/config.toml"), ("MASQ_DATA_DIRECTORY", "/unexistent/directory"), + #[cfg(not(target_os = "windows"))] ("MASQ_REAL_USER", "999:999:/home/malooga"), ] .into_iter() @@ -1015,6 +1033,7 @@ mod tests { &value_m!(multiconfig, "data-directory", String).unwrap(), &home_dir.to_string_lossy().to_string() ); + #[cfg(not(target_os = "windows"))] assert_eq!( &value_m!(multiconfig, "real-user", String).unwrap(), "1001:1001:cooga" @@ -1078,15 +1097,20 @@ mod tests { let params = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let result = params.as_ref().expect("REASON"); let multiconfig = result; - + #[cfg(not(target_os = "windows"))] assert_eq!( value_m!(multiconfig, "config-file", String).unwrap(), current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home/config.toml").to_string_lossy().to_string() ); + #[cfg(target_os = "windows")] + assert_eq!( + value_m!(multiconfig, "config-file", String).unwrap(), + current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home\\config.toml").to_string_lossy().to_string() + ); assert_eq!(multiconfig.is_user_specified("--data-directory"), true); - #[cfg(not(target_os = "windows"))] assert_eq!(multiconfig.is_user_specified("--real-user"), true); + #[cfg(not(target_os = "windows"))] assert_eq!( value_m!(multiconfig, "real-user", String).unwrap(), "1001:1001:cooga".to_string() From cf6da59bc0efc45634ecb113451673efcc45b543 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 31 Oct 2023 06:38:53 -0700 Subject: [PATCH 16/52] fixed on windows --- node/src/node_configurator/node_configurator_standard.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 917297c96..9845707c4 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -10,6 +10,7 @@ use masq_lib::shared_schema::ConfiguratorError; use masq_lib::utils::NeighborhoodModeLight; use std::net::SocketAddr; use std::net::{IpAddr, Ipv4Addr}; +#[cfg(not(target_os = "windows"))] use std::ops::Deref; use clap::value_t; @@ -31,6 +32,7 @@ use crate::sub_lib::cryptde::PublicKey; use crate::sub_lib::cryptde_null::CryptDENull; use crate::sub_lib::utils::make_new_multi_config; use crate::tls_discriminator_factory::TlsDiscriminatorFactory; +#[cfg(not(target_os = "windows"))] use itertools::Itertools; use masq_lib::constants::{DEFAULT_UI_PORT, HTTP_PORT, TLS_PORT}; use masq_lib::multi_config::{CommandLineVcl, ConfigFileVcl, EnvironmentVcl}; @@ -154,6 +156,7 @@ pub fn server_initializer_collected_params<'a>( let commandline_vcl = CommandLineVcl::new(args.to_vec()); let config_file_specified = user_specific_data.config_file_spec; let config_file_path = user_specific_data.config_file; + #[cfg(not(target_os = "windows"))] let extract_value_from_vcl = |vcl: &dyn VirtualCommandLine, name: &str, var: &str, spec: bool| { let args = vcl.args(); @@ -170,18 +173,21 @@ pub fn server_initializer_collected_params<'a>( true => (var.to_string(), spec), } }; + #[cfg(not(target_os = "windows"))] let (cf_real_user, cf_real_user_specified) = extract_value_from_vcl( &config_file_vcl, "--real-user", user_specific_data.real_user.to_string().as_str(), user_specific_data.real_user_spec, ); + #[cfg(not(target_os = "windows"))] let (env_real_user, env_real_user_specified) = extract_value_from_vcl( &environment_vcl, "--real-user", user_specific_data.real_user.to_string().as_str(), user_specific_data.real_user_spec, ); + #[cfg(not(target_os = "windows"))] let (cmd_real_user, cmd_real_user_specified) = extract_value_from_vcl( &commandline_vcl, "--real-user", @@ -944,7 +950,7 @@ mod tests { #[cfg(target_os = "windows")] assert_eq!( value_m!(env_multiconfig, "data-directory", String).unwrap(), - "generated/test/node_configurator_standard/multi_config_vcl_is_computed_do_right_job/home\\data_dir\\MASQ\\polygon-mainnet".to_string() + "generated/test/node_configurator_standard/server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot/home\\data_dir\\MASQ\\polygon-mainnet".to_string() ); #[cfg(not(target_os = "windows"))] assert_eq!( From 589587932e511b29025baae45f692ee0e743c400 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 1 Nov 2023 17:12:51 -0700 Subject: [PATCH 17/52] fixed real user for windows --- node/src/node_configurator/mod.rs | 9 +++++++- .../node_configurator_standard.rs | 21 +++++++++---------- node/src/server_initializer.rs | 6 ++++-- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index 380c038a8..2cadad6cc 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -69,7 +69,14 @@ fn get_real_user_from_vcl( ) } None => ( - RealUser::new(None, None, None).populate(dirs_wrapper), + #[cfg(target_os = "windows")] + { + RealUser::new(Some(999999), Some(999999), None).populate(dirs_wrapper) + }, + #[cfg(not(target_os = "windows"))] + { + RealUser::new(None, None, None).populate(dirs_wrapper) + }, false, ), } diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 9845707c4..afad98c50 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -10,7 +10,6 @@ use masq_lib::shared_schema::ConfiguratorError; use masq_lib::utils::NeighborhoodModeLight; use std::net::SocketAddr; use std::net::{IpAddr, Ipv4Addr}; -#[cfg(not(target_os = "windows"))] use std::ops::Deref; use clap::value_t; @@ -32,7 +31,6 @@ use crate::sub_lib::cryptde::PublicKey; use crate::sub_lib::cryptde_null::CryptDENull; use crate::sub_lib::utils::make_new_multi_config; use crate::tls_discriminator_factory::TlsDiscriminatorFactory; -#[cfg(not(target_os = "windows"))] use itertools::Itertools; use masq_lib::constants::{DEFAULT_UI_PORT, HTTP_PORT, TLS_PORT}; use masq_lib::multi_config::{CommandLineVcl, ConfigFileVcl, EnvironmentVcl}; @@ -156,7 +154,7 @@ pub fn server_initializer_collected_params<'a>( let commandline_vcl = CommandLineVcl::new(args.to_vec()); let config_file_specified = user_specific_data.config_file_spec; let config_file_path = user_specific_data.config_file; - #[cfg(not(target_os = "windows"))] + let extract_value_from_vcl = |vcl: &dyn VirtualCommandLine, name: &str, var: &str, spec: bool| { let args = vcl.args(); @@ -173,21 +171,21 @@ pub fn server_initializer_collected_params<'a>( true => (var.to_string(), spec), } }; - #[cfg(not(target_os = "windows"))] + let (cf_real_user, cf_real_user_specified) = extract_value_from_vcl( &config_file_vcl, "--real-user", user_specific_data.real_user.to_string().as_str(), user_specific_data.real_user_spec, ); - #[cfg(not(target_os = "windows"))] + let (env_real_user, env_real_user_specified) = extract_value_from_vcl( &environment_vcl, "--real-user", user_specific_data.real_user.to_string().as_str(), user_specific_data.real_user_spec, ); - #[cfg(not(target_os = "windows"))] + let (cmd_real_user, cmd_real_user_specified) = extract_value_from_vcl( &commandline_vcl, "--real-user", @@ -200,6 +198,7 @@ pub fn server_initializer_collected_params<'a>( Box::new(commandline_vcl), ]; //TODO write test for MultiConfig "try_new" merge line 76 + // TODO use vector from line 206 and push into it and then construct the CommandLineVcl same with ComputedVcl let mut fill_specified_or_unspecified_box = |key: &str, value: &str, specified: bool| { match specified { true => match value.is_empty() { @@ -230,24 +229,24 @@ pub fn server_initializer_collected_params<'a>( user_specific_data.data_directory.to_string_lossy().as_ref(), user_specific_data.data_directory_spec, ); - #[cfg(not(target_os = "windows"))] + fill_specified_or_unspecified_box("--real-user", cf_real_user.as_str(), cf_real_user_specified); - #[cfg(not(target_os = "windows"))] + fill_specified_or_unspecified_box( "--real-user", env_real_user.as_str(), env_real_user_specified, ); - #[cfg(not(target_os = "windows"))] + fill_specified_or_unspecified_box( "--real-user", cmd_real_user.as_str(), cmd_real_user_specified, ); - // println!("full_multi_config_vec: {:#?}", full_multi_config_vec); + //println!("full_multi_config_vec: {:#?}", full_multi_config_vec); let full_multi_config = make_new_multi_config(&app, full_multi_config_vec)?; - // println!("full_multi_config: {:#?}", full_multi_config); + //println!("full_multi_config: {:#?}", full_multi_config); Ok(full_multi_config) } diff --git a/node/src/server_initializer.rs b/node/src/server_initializer.rs index f7d4961ad..eba5f945f 100644 --- a/node/src/server_initializer.rs +++ b/node/src/server_initializer.rs @@ -38,8 +38,10 @@ pub struct ServerInitializerReal { impl ServerInitializer for ServerInitializerReal { fn go(&mut self, streams: &mut StdStreams<'_>, args: &[String]) -> RunModeResult { let params = server_initializer_collected_params(self.dirs_wrapper.as_ref(), args)?; - let real_user = value_m!(params, "real-user", RealUser).unwrap(); - let data_directory = value_m!(params, "data-directory", String).unwrap(); + let real_user = value_m!(params, "real-user", RealUser) + .expect("ServerInitializer: Real user not present in Multi Config"); + let data_directory = value_m!(params, "data-directory", String) + .expect("ServerInitializer: Data directory not present in Multi Config"); let result: RunModeResult = Ok(()) .combine_results( From 62e99e33e171232e579033c0d8c4e8f6f58157b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Wed, 1 Nov 2023 19:12:23 +0100 Subject: [PATCH 18/52] removed filling specified and unspecified boxes from server_initializer_collected_params, fixed formatting for get_real_user_from_vcl --- node/src/node_configurator/mod.rs | 13 +-- .../node_configurator_standard.rs | 104 +++++++++++------- 2 files changed, 72 insertions(+), 45 deletions(-) diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index 2cadad6cc..d746e60e5 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -68,17 +68,16 @@ fn get_real_user_from_vcl( true, ) } - None => ( + None => { #[cfg(target_os = "windows")] { - RealUser::new(Some(999999), Some(999999), None).populate(dirs_wrapper) - }, + (RealUser::new(Some(999999), Some(999999), None).populate(dirs_wrapper), false) + } #[cfg(not(target_os = "windows"))] { - RealUser::new(None, None, None).populate(dirs_wrapper) - }, - false, - ), + (RealUser::new(None, None, None).populate(dirs_wrapper), false) + } + }, } } diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index afad98c50..c7f93f88a 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -1,7 +1,7 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::bootstrapper::BootstrapperConfig; -use crate::node_configurator::DirsWrapperReal; +use crate::node_configurator::{DirsWrapperReal, UserSpecifiedData}; use crate::node_configurator::{initialize_database, DirsWrapper, NodeConfigurator}; use masq_lib::crash_point::CrashPoint; use masq_lib::logger::Logger; @@ -137,24 +137,16 @@ fn collect_externals_from_multi_config( ) } -pub fn server_initializer_collected_params<'a>( - dirs_wrapper: &dyn DirsWrapper, - args: &[String], -) -> Result, ConfiguratorError> { - let app = app_node(); - let user_specific_data = determine_user_specific_data(dirs_wrapper, &app, args)?; - let config_file_vcl = match ConfigFileVcl::new( - &user_specific_data.config_file, - user_specific_data.config_file_spec, - ) { - Ok(cfv) => cfv, - Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), - }; - let environment_vcl = EnvironmentVcl::new(&app); - let commandline_vcl = CommandLineVcl::new(args.to_vec()); +fn extract_values_and_fill_boxes( + config_file_vcl: Box, + environment_vcl: Box, + commandline_vcl: Box, + user_specific_data: UserSpecifiedData +) -> (Vec, Vec) { + let mut unspecified_vec: Vec = vec!["".to_string()]; + let mut specified_vec: Vec = vec!["".to_string()]; let config_file_specified = user_specific_data.config_file_spec; let config_file_path = user_specific_data.config_file; - let extract_value_from_vcl = |vcl: &dyn VirtualCommandLine, name: &str, var: &str, spec: bool| { let args = vcl.args(); @@ -171,51 +163,42 @@ pub fn server_initializer_collected_params<'a>( true => (var.to_string(), spec), } }; - let (cf_real_user, cf_real_user_specified) = extract_value_from_vcl( - &config_file_vcl, + &*config_file_vcl, "--real-user", user_specific_data.real_user.to_string().as_str(), user_specific_data.real_user_spec, ); let (env_real_user, env_real_user_specified) = extract_value_from_vcl( - &environment_vcl, + &*environment_vcl, "--real-user", user_specific_data.real_user.to_string().as_str(), user_specific_data.real_user_spec, ); let (cmd_real_user, cmd_real_user_specified) = extract_value_from_vcl( - &commandline_vcl, + &*commandline_vcl, "--real-user", user_specific_data.real_user.to_string().as_str(), user_specific_data.real_user_spec, ); - let mut full_multi_config_vec: Vec> = vec![ - Box::new(config_file_vcl), - Box::new(environment_vcl), - Box::new(commandline_vcl), - ]; - //TODO write test for MultiConfig "try_new" merge line 76 - // TODO use vector from line 206 and push into it and then construct the CommandLineVcl same with ComputedVcl + let mut fill_specified_or_unspecified_box = |key: &str, value: &str, specified: bool| { match specified { true => match value.is_empty() { true => (), - false => full_multi_config_vec.push(Box::new(CommandLineVcl::new(vec![ - "".to_string(), - key.to_string(), - value.to_string(), - ]))), + false => { + unspecified_vec.push(key.to_string()); + unspecified_vec.push(value.to_string()); + } }, false => match value.is_empty() { true => (), - false => full_multi_config_vec.push(Box::new(ComputedVcl::new(vec![ - "".to_string(), - key.to_string(), - value.to_string(), - ]))), + false => { + specified_vec.push(key.to_string()); + specified_vec.push(value.to_string()); + } }, }; }; @@ -243,6 +226,51 @@ pub fn server_initializer_collected_params<'a>( cmd_real_user.as_str(), cmd_real_user_specified, ); + (unspecified_vec, specified_vec) +} + +pub fn server_initializer_collected_params<'a>( + dirs_wrapper: &dyn DirsWrapper, + args: &[String], +) -> Result, ConfiguratorError> { + let app = app_node(); + let user_specific_data = determine_user_specific_data(dirs_wrapper, &app, args)?; + let config_file_vcl = match ConfigFileVcl::new( + &user_specific_data.config_file, + user_specific_data.config_file_spec, + ) { + Ok(cfv) => cfv, + Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), + }; + //TODO get rid of this second construction of ConfigFileVcl + let config_file_vcl_next = match ConfigFileVcl::new( + &user_specific_data.config_file, + user_specific_data.config_file_spec, + ) { + Ok(cfv) => cfv, + Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), + }; + let environment_vcl = EnvironmentVcl::new(&app); + let commandline_vcl = CommandLineVcl::new(args.to_vec()); + let (unspecified_vec, specified_vec) = extract_values_and_fill_boxes( + Box::new(config_file_vcl), + Box::new(EnvironmentVcl::new(&app)), + Box::new(CommandLineVcl::new(commandline_vcl.args())), + user_specific_data + ); + let mut full_multi_config_vec: Vec> = vec![ + Box::new(config_file_vcl_next), + Box::new(environment_vcl), + Box::new(commandline_vcl), + ]; + + //TODO write test for MultiConfig "try_new" merge line 76 + if unspecified_vec.len() > 1 { + full_multi_config_vec.push(Box::new(ComputedVcl::new(unspecified_vec))); + } + if specified_vec.len() > 1 { + full_multi_config_vec.push(Box::new(CommandLineVcl::new(specified_vec))); + } //println!("full_multi_config_vec: {:#?}", full_multi_config_vec); let full_multi_config = make_new_multi_config(&app, full_multi_config_vec)?; From 34b8fea084cf68f97f0b4a981c299bd64336bec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Wed, 1 Nov 2023 19:19:00 +0100 Subject: [PATCH 19/52] formatting --- node/src/node_configurator/mod.rs | 12 +++++++++--- .../node_configurator/node_configurator_standard.rs | 6 +++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index d746e60e5..bda38980b 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -71,13 +71,19 @@ fn get_real_user_from_vcl( None => { #[cfg(target_os = "windows")] { - (RealUser::new(Some(999999), Some(999999), None).populate(dirs_wrapper), false) + ( + RealUser::new(Some(999999), Some(999999), None).populate(dirs_wrapper), + false, + ) } #[cfg(not(target_os = "windows"))] { - (RealUser::new(None, None, None).populate(dirs_wrapper), false) + ( + RealUser::new(None, None, None).populate(dirs_wrapper), + false, + ) } - }, + } } } diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index c7f93f88a..d306d7317 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -1,8 +1,8 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::bootstrapper::BootstrapperConfig; -use crate::node_configurator::{DirsWrapperReal, UserSpecifiedData}; use crate::node_configurator::{initialize_database, DirsWrapper, NodeConfigurator}; +use crate::node_configurator::{DirsWrapperReal, UserSpecifiedData}; use masq_lib::crash_point::CrashPoint; use masq_lib::logger::Logger; use masq_lib::multi_config::{ComputedVcl, MultiConfig, VirtualCommandLine}; @@ -141,7 +141,7 @@ fn extract_values_and_fill_boxes( config_file_vcl: Box, environment_vcl: Box, commandline_vcl: Box, - user_specific_data: UserSpecifiedData + user_specific_data: UserSpecifiedData, ) -> (Vec, Vec) { let mut unspecified_vec: Vec = vec!["".to_string()]; let mut specified_vec: Vec = vec!["".to_string()]; @@ -256,7 +256,7 @@ pub fn server_initializer_collected_params<'a>( Box::new(config_file_vcl), Box::new(EnvironmentVcl::new(&app)), Box::new(CommandLineVcl::new(commandline_vcl.args())), - user_specific_data + user_specific_data, ); let mut full_multi_config_vec: Vec> = vec![ Box::new(config_file_vcl_next), From 8369ff98efffca5e530666b04ab44f69ed94400c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Mon, 6 Nov 2023 17:54:24 +0100 Subject: [PATCH 20/52] last fixes of extract_values_and_fill_boxes, and test server_initializer_collected_params_handle_config_file_from_commandline_and_real_user_from_config_file_with_data_dir_started_by_tilde --- masq_lib/src/multi_config.rs | 8 ++ .../node_configurator_standard.rs | 100 +++++++++++------- 2 files changed, 69 insertions(+), 39 deletions(-) diff --git a/masq_lib/src/multi_config.rs b/masq_lib/src/multi_config.rs index b2b5ec21e..1663290db 100644 --- a/masq_lib/src/multi_config.rs +++ b/masq_lib/src/multi_config.rs @@ -427,6 +427,14 @@ pub struct ConfigFileVcl { vcl_args: Vec>, } +impl Clone for ConfigFileVcl { + fn clone(&self) -> Self { + ConfigFileVcl { + vcl_args: self.vcl_args.iter().map(|arg| arg.dup()).collect(), + } + } +} + impl VirtualCommandLine for ConfigFileVcl { fn vcl_args(&self) -> Vec<&dyn VclArg> { vcl_args_to_vcl_args(&self.vcl_args) diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index d306d7317..6ca0d730a 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -184,24 +184,40 @@ fn extract_values_and_fill_boxes( user_specific_data.real_user_spec, ); - let mut fill_specified_or_unspecified_box = |key: &str, value: &str, specified: bool| { - match specified { - true => match value.is_empty() { - true => (), - false => { - unspecified_vec.push(key.to_string()); - unspecified_vec.push(value.to_string()); - } - }, - false => match value.is_empty() { - true => (), - false => { - specified_vec.push(key.to_string()); - specified_vec.push(value.to_string()); - } + let mut fill_specified_or_unspecified_box = + |key: &str, value: &str, specified: bool| match value.is_empty() { + true => (), + false => match specified { + true => match specified_vec.contains(&key.to_string()) { + true => { + let index = specified_vec + .iter() + .position(|r| r == key) + .expect("expected index of vcl name") + + 1; + specified_vec[index] = value.to_string(); + } + false => { + specified_vec.push(key.to_string()); + specified_vec.push(value.to_string()); + } + }, + false => match unspecified_vec.contains(&key.to_string()) { + true => { + let index = unspecified_vec + .iter() + .position(|r| r == key) + .expect("expected index of vcl name") + + 1; + unspecified_vec[index] = value.to_string(); + } + false => { + unspecified_vec.push(key.to_string()); + unspecified_vec.push(value.to_string()); + } + }, }, }; - }; fill_specified_or_unspecified_box( "--config-file", config_file_path.as_path().to_string_lossy().as_ref(), @@ -242,28 +258,20 @@ pub fn server_initializer_collected_params<'a>( Ok(cfv) => cfv, Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), }; - //TODO get rid of this second construction of ConfigFileVcl - let config_file_vcl_next = match ConfigFileVcl::new( - &user_specific_data.config_file, - user_specific_data.config_file_spec, - ) { - Ok(cfv) => cfv, - Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), - }; + let environment_vcl = EnvironmentVcl::new(&app); let commandline_vcl = CommandLineVcl::new(args.to_vec()); let (unspecified_vec, specified_vec) = extract_values_and_fill_boxes( - Box::new(config_file_vcl), + Box::new(config_file_vcl.clone()), Box::new(EnvironmentVcl::new(&app)), Box::new(CommandLineVcl::new(commandline_vcl.args())), user_specific_data, ); let mut full_multi_config_vec: Vec> = vec![ - Box::new(config_file_vcl_next), + Box::new(config_file_vcl), Box::new(environment_vcl), Box::new(commandline_vcl), ]; - //TODO write test for MultiConfig "try_new" merge line 76 if unspecified_vec.len() > 1 { full_multi_config_vec.push(Box::new(ComputedVcl::new(unspecified_vec))); @@ -987,19 +995,19 @@ mod tests { } #[test] - fn server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_data_dir_started_by_tilde( + fn server_initializer_collected_params_handle_config_file_from_commandline_and_real_user_from_config_file_with_data_dir_started_by_tilde( ) { running_test(); let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_data_dir_started_by_tilde"); - let data_dir = &home_dir.join("data_dir"); + let data_dir = &home_dir.join("masqhome"); let _create_data_dir = create_dir_all(data_dir); - let config_file_relative = File::create(home_dir.join("config.toml")).unwrap(); + let config_file_relative = File::create(data_dir.join("config.toml")).unwrap(); fill_up_config_file(config_file_relative); let env_vec_array = vec![ - ("MASQ_DATA_DIRECTORY", "~/data_dir"), - ("MASQ_CONFIGFILE", "~/config.toml"), + ("MASQ_IP", "9.8.7.6"), + ("MASQ_BLOCKCHAIN-SERVICE-URL", "https://www.mainnet2.com"), #[cfg(not(target_os = "windows"))] ("MASQ_REAL_USER", "9999:9999:booga"), ]; @@ -1007,28 +1015,42 @@ mod tests { .clone() .into_iter() .for_each(|(name, value)| std::env::set_var(name, value)); - let args = ArgsBuilder::new(); + let args = ArgsBuilder::new() + .param("--blockchain-service-url", "https://www.mainnet1.com") + .param("--config-file", "~/masqhome/config.toml") + .param("--data-directory", "~/masqhome"); let args_vec: Vec = args.into(); let dir_wrapper = DirsWrapperMock::new() - .home_dir_result(Some(current_dir().expect("expect current directory").join("generated/test/node_configurator_standard/server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_tilde"))) + .home_dir_result(Some(current_dir().expect("expect current directory").join("generated/test/node_configurator_standard/server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_data_dir_started_by_tilde/home"))) .data_dir_result(Some(data_dir.to_path_buf())); - let result_data_dir = current_dir().expect("expect current directory").join("generated/test/node_configurator_standard/server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_tilde/data_dir"); + let result_data_dir = current_dir().expect("expect current directory").join("generated/test/node_configurator_standard/server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_data_dir_started_by_tilde/home/masqhome"); let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); - let env_multiconfig = result.unwrap(); + let multiconfig = result.unwrap(); - assert_eq!(env_multiconfig.is_user_specified("--data-directory"), true); + assert_eq!(multiconfig.is_user_specified("--data-directory"), true); assert_eq!( - value_m!(env_multiconfig, "data-directory", String).unwrap(), + value_m!(multiconfig, "data-directory", String).unwrap(), result_data_dir.to_string_lossy().to_string() ); + assert_eq!(multiconfig.is_user_specified("--real-user"), true); assert_eq!( - value_m!(env_multiconfig, "config-file", String).unwrap(), + value_m!(multiconfig, "real-user", String).unwrap(), + "9999:9999:booga" + ); + assert_eq!(multiconfig.is_user_specified("--config-file"), true); + assert_eq!( + value_m!(multiconfig, "config-file", String).unwrap(), result_data_dir .join(PathBuf::from("config.toml")) .to_string_lossy() .to_string() ); + assert_eq!(value_m!(multiconfig, "ip", String).unwrap(), "9.8.7.6"); + assert_eq!( + value_m!(multiconfig, "blockchain-service-url", String).unwrap(), + "https://www.mainnet1.com" + ); } #[test] From 5b02822f62a90f3f168de7d241794771e99ed3ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Wed, 8 Nov 2023 09:51:11 +0100 Subject: [PATCH 21/52] fixed windows test for tilde --- .../node_configurator_standard.rs | 77 ++++++++++--------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 6ca0d730a..311a4e240 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -930,22 +930,23 @@ mod tests { let env_multiconfig = result.unwrap(); #[cfg(not(target_os = "windows"))] - assert_eq!( - value_m!(env_multiconfig, "data-directory", String).unwrap(), - "booga/data_dir/MASQ/polygon-mainnet".to_string() - ); + { + assert_eq!( + value_m!(env_multiconfig, "data-directory", String).unwrap(), + "booga/data_dir/MASQ/polygon-mainnet".to_string() + ); + assert_eq!( + value_m!(env_multiconfig, "config-file", String).unwrap(), + current_dir().expect("expected curerrnt dir") + .join(PathBuf::from( "./generated/test/node_configurator_standard/multi_config_vcl_is_computed_do_right_job/config.toml")) + .to_string_lossy().to_string() + ); + } #[cfg(target_os = "windows")] assert_eq!( value_m!(env_multiconfig, "data-directory", String).unwrap(), "generated/test/node_configurator_standard/multi_config_vcl_is_computed_do_right_job/home\\data_dir\\MASQ\\polygon-mainnet".to_string() ); - #[cfg(not(target_os = "windows"))] - assert_eq!( - value_m!(env_multiconfig, "config-file", String).unwrap(), - current_dir().expect("expected curerrnt dir") - .join(PathBuf::from( "./generated/test/node_configurator_standard/multi_config_vcl_is_computed_do_right_job/config.toml")) - .to_string_lossy().to_string() - ); } #[test] @@ -978,20 +979,21 @@ mod tests { assert_eq!(env_multiconfig.is_user_specified("--data-directory"), false); #[cfg(not(target_os = "windows"))] - assert_eq!( - value_m!(env_multiconfig, "data-directory", String).unwrap(), - "booga/data_dir/MASQ/polygon-mainnet".to_string() - ); + { + assert_eq!( + value_m!(env_multiconfig, "data-directory", String).unwrap(), + "booga/data_dir/MASQ/polygon-mainnet".to_string() + ); + assert_eq!( + value_m!(env_multiconfig, "config-file", String).unwrap(), + current_dir().unwrap().join(PathBuf::from( "./generated/test/node_configurator_standard/server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot/config.toml")).to_string_lossy().to_string() + ); + } #[cfg(target_os = "windows")] assert_eq!( value_m!(env_multiconfig, "data-directory", String).unwrap(), "generated/test/node_configurator_standard/server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot/home\\data_dir\\MASQ\\polygon-mainnet".to_string() ); - #[cfg(not(target_os = "windows"))] - assert_eq!( - value_m!(env_multiconfig, "config-file", String).unwrap(), - current_dir().unwrap().join(PathBuf::from( "./generated/test/node_configurator_standard/server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot/config.toml")).to_string_lossy().to_string() - ); } #[test] @@ -1033,11 +1035,14 @@ mod tests { value_m!(multiconfig, "data-directory", String).unwrap(), result_data_dir.to_string_lossy().to_string() ); - assert_eq!(multiconfig.is_user_specified("--real-user"), true); - assert_eq!( - value_m!(multiconfig, "real-user", String).unwrap(), - "9999:9999:booga" - ); + #[cfg(not(target_os = "windows"))] + { + assert_eq!(multiconfig.is_user_specified("--real-user"), true); + assert_eq!( + value_m!(multiconfig, "real-user", String).unwrap(), + "9999:9999:booga" + ); + } assert_eq!(multiconfig.is_user_specified("--config-file"), true); assert_eq!( value_m!(multiconfig, "config-file", String).unwrap(), @@ -1153,23 +1158,23 @@ mod tests { let result = params.as_ref().expect("REASON"); let multiconfig = result; #[cfg(not(target_os = "windows"))] - assert_eq!( - value_m!(multiconfig, "config-file", String).unwrap(), - current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home/config.toml").to_string_lossy().to_string() - ); + { + assert_eq!( + value_m!(multiconfig, "config-file", String).unwrap(), + current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home/config.toml").to_string_lossy().to_string() + ); + assert_eq!(multiconfig.is_user_specified("--real-user"), true); + assert_eq!( + value_m!(multiconfig, "real-user", String).unwrap(), + "1001:1001:cooga".to_string() + ); + } #[cfg(target_os = "windows")] assert_eq!( value_m!(multiconfig, "config-file", String).unwrap(), current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home\\config.toml").to_string_lossy().to_string() ); assert_eq!(multiconfig.is_user_specified("--data-directory"), true); - #[cfg(not(target_os = "windows"))] - assert_eq!(multiconfig.is_user_specified("--real-user"), true); - #[cfg(not(target_os = "windows"))] - assert_eq!( - value_m!(multiconfig, "real-user", String).unwrap(), - "1001:1001:cooga".to_string() - ); } #[test] From 0603f700fd9423441a141e9a24ac36f2b74cdf79 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 8 Nov 2023 11:57:25 -0800 Subject: [PATCH 22/52] fix tilde test for windows --- node/src/node_configurator/node_configurator_standard.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 311a4e240..b0cd688a0 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -1017,10 +1017,16 @@ mod tests { .clone() .into_iter() .for_each(|(name, value)| std::env::set_var(name, value)); + #[cfg(not(target_os = "windows"))] let args = ArgsBuilder::new() .param("--blockchain-service-url", "https://www.mainnet1.com") .param("--config-file", "~/masqhome/config.toml") .param("--data-directory", "~/masqhome"); + #[cfg(target_os = "windows")] + let args = ArgsBuilder::new() + .param("--blockchain-service-url", "https://www.mainnet1.com") + .param("--config-file", "~/masqhome\\config.toml") + .param("--data-directory", "~/masqhome"); let args_vec: Vec = args.into(); let dir_wrapper = DirsWrapperMock::new() .home_dir_result(Some(current_dir().expect("expect current directory").join("generated/test/node_configurator_standard/server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_data_dir_started_by_tilde/home"))) From 2ec7e12b29ba1ddbadb5cb9402d9ce5f15eb1346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Wed, 8 Nov 2023 18:15:46 +0100 Subject: [PATCH 23/52] renaming of tests and functions --- .../node_configurator_standard.rs | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index b0cd688a0..c257b1f23 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -137,7 +137,7 @@ fn collect_externals_from_multi_config( ) } -fn extract_values_and_fill_boxes( +fn extract_values_vcl_fill_multiconfig_vec( config_file_vcl: Box, environment_vcl: Box, commandline_vcl: Box, @@ -261,28 +261,27 @@ pub fn server_initializer_collected_params<'a>( let environment_vcl = EnvironmentVcl::new(&app); let commandline_vcl = CommandLineVcl::new(args.to_vec()); - let (unspecified_vec, specified_vec) = extract_values_and_fill_boxes( + let (unspecified_vec, specified_vec) = extract_values_vcl_fill_multiconfig_vec( Box::new(config_file_vcl.clone()), Box::new(EnvironmentVcl::new(&app)), Box::new(CommandLineVcl::new(commandline_vcl.args())), user_specific_data, ); - let mut full_multi_config_vec: Vec> = vec![ + let mut multi_config_args_vec: Vec> = vec![ Box::new(config_file_vcl), Box::new(environment_vcl), Box::new(commandline_vcl), ]; //TODO write test for MultiConfig "try_new" merge line 76 if unspecified_vec.len() > 1 { - full_multi_config_vec.push(Box::new(ComputedVcl::new(unspecified_vec))); + multi_config_args_vec.push(Box::new(ComputedVcl::new(unspecified_vec))); } if specified_vec.len() > 1 { - full_multi_config_vec.push(Box::new(CommandLineVcl::new(specified_vec))); + multi_config_args_vec.push(Box::new(CommandLineVcl::new(specified_vec))); } - //println!("full_multi_config_vec: {:#?}", full_multi_config_vec); - let full_multi_config = make_new_multi_config(&app, full_multi_config_vec)?; - //println!("full_multi_config: {:#?}", full_multi_config); + let full_multi_config = make_new_multi_config(&app, multi_config_args_vec)?; + Ok(full_multi_config) } @@ -950,17 +949,19 @@ mod tests { } #[test] - fn server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot( - ) { + fn server_initializer_collected_params_handle_dot_in_path_and_real_user_in_config_file() { running_test(); let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); - let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot"); + let home_dir = ensure_node_home_directory_exists( + "node_configurator_standard", + "server_initializer_collected_params_handle_dot_in_path_and_real_user_in_config_file", + ); let data_dir = &home_dir.join("data_dir"); - let config_file_relative = File::create(PathBuf::from("./generated/test/node_configurator_standard/server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot").join("config.toml")).unwrap(); + let config_file_relative = File::create(PathBuf::from("./generated/test/node_configurator_standard/server_initializer_collected_params_handle_dot_in_path_and_real_user_in_config_file").join("config.toml")).unwrap(); fill_up_config_file(config_file_relative); let env_vec_array = vec![ - ("MASQ_CONFIG_FILE", "./generated/test/node_configurator_standard/server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot/config.toml"), + ("MASQ_CONFIG_FILE", "./generated/test/node_configurator_standard/server_initializer_collected_params_handle_dot_in_path_and_real_user_in_config_file/config.toml"), #[cfg(not(target_os = "windows"))] ("MASQ_REAL_USER", "9999:9999:booga"), ]; @@ -986,23 +987,23 @@ mod tests { ); assert_eq!( value_m!(env_multiconfig, "config-file", String).unwrap(), - current_dir().unwrap().join(PathBuf::from( "./generated/test/node_configurator_standard/server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot/config.toml")).to_string_lossy().to_string() + current_dir().unwrap().join(PathBuf::from( "./generated/test/node_configurator_standard/server_initializer_collected_params_handle_dot_in_path_and_real_user_in_config_file/config.toml")).to_string_lossy().to_string() ); } #[cfg(target_os = "windows")] assert_eq!( value_m!(env_multiconfig, "data-directory", String).unwrap(), - "generated/test/node_configurator_standard/server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot/home\\data_dir\\MASQ\\polygon-mainnet".to_string() + "generated/test/node_configurator_standard/server_initializer_collected_params_handle_dot_in_path_and_real_user_in_config_file/home\\data_dir\\MASQ\\polygon-mainnet".to_string() ); } #[test] - fn server_initializer_collected_params_handle_config_file_from_commandline_and_real_user_from_config_file_with_data_dir_started_by_tilde( + fn server_initializer_collected_params_handle_tilde_in_path_config_file_from_commandline_and_real_user_from_config_file( ) { running_test(); let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); - let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_data_dir_started_by_tilde"); + let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_handle_tilde_in_path_config_file_from_commandline_and_real_user_from_config_file"); let data_dir = &home_dir.join("masqhome"); let _create_data_dir = create_dir_all(data_dir); let config_file_relative = File::create(data_dir.join("config.toml")).unwrap(); @@ -1029,9 +1030,9 @@ mod tests { .param("--data-directory", "~/masqhome"); let args_vec: Vec = args.into(); let dir_wrapper = DirsWrapperMock::new() - .home_dir_result(Some(current_dir().expect("expect current directory").join("generated/test/node_configurator_standard/server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_data_dir_started_by_tilde/home"))) + .home_dir_result(Some(current_dir().expect("expect current directory").join("generated/test/node_configurator_standard/server_initializer_collected_params_handle_tilde_in_path_config_file_from_commandline_and_real_user_from_config_file/home"))) .data_dir_result(Some(data_dir.to_path_buf())); - let result_data_dir = current_dir().expect("expect current directory").join("generated/test/node_configurator_standard/server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_data_dir_started_by_tilde/home/masqhome"); + let result_data_dir = current_dir().expect("expect current directory").join("generated/test/node_configurator_standard/server_initializer_collected_params_handle_tilde_in_path_config_file_from_commandline_and_real_user_from_config_file/home/masqhome"); let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let multiconfig = result.unwrap(); From fe491ab7607b0bc2544ec4542904b54ce15115f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Fri, 10 Nov 2023 12:04:52 +0100 Subject: [PATCH 24/52] remove GatheredParams (the goal), optimizing is_user_specified in MultiConfig, dealing with comments --- masq_lib/src/multi_config.rs | 5 +---- node/src/server_initializer.rs | 25 ------------------------- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/masq_lib/src/multi_config.rs b/masq_lib/src/multi_config.rs index 1663290db..5c87da2c8 100644 --- a/masq_lib/src/multi_config.rs +++ b/masq_lib/src/multi_config.rs @@ -155,10 +155,7 @@ impl<'a> MultiConfig<'a> { } pub fn is_user_specified(&self, value_name: &str) -> bool { - match self.computed_value_names.contains(value_name) { - true => false, - false => true, - } + !self.computed_value_names.contains(value_name) } pub fn occurrences_of(&self, parameter: &str) -> u64 { diff --git a/node/src/server_initializer.rs b/node/src/server_initializer.rs index eba5f945f..ece182c5d 100644 --- a/node/src/server_initializer.rs +++ b/node/src/server_initializer.rs @@ -18,7 +18,6 @@ use log::{log, Level}; use masq_lib::command::StdStreams; use masq_lib::logger; use masq_lib::logger::{real_format_function, POINTER_TO_FORMAT_FUNCTION}; -use masq_lib::multi_config::MultiConfig; use masq_lib::shared_schema::ConfiguratorError; use std::any::Any; use std::io; @@ -116,30 +115,6 @@ impl ResultsCombiner for RunModeResult { } } -#[derive(Debug)] -pub struct GatheredParams<'a> { - pub multi_config: MultiConfig<'a>, - pub config_file_path: PathBuf, - pub real_user: RealUser, - pub data_directory: PathBuf, -} - -impl<'a> GatheredParams<'a> { - pub fn new( - multi_config: MultiConfig<'a>, - config_file_path: PathBuf, - real_user: RealUser, - data_directory: PathBuf, - ) -> Self { - Self { - multi_config, - config_file_path, - real_user, - data_directory, - } - } -} - lazy_static! { pub static ref LOGFILE_NAME: Mutex = Mutex::new(PathBuf::from("uninitialized")); } From 53feaac9015f7312dc2b2dddcae3a20d6ea904be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Mon, 20 Nov 2023 19:53:39 +0100 Subject: [PATCH 25/52] remove value_opt form VclArg, remove test for that, change MultiConfig and schema (config-file no longer defaulted), change value_from_vcl to multiconfig and value_m --- masq_lib/src/multi_config.rs | 61 ++++------------------------------- masq_lib/src/shared_schema.rs | 1 - 2 files changed, 7 insertions(+), 55 deletions(-) diff --git a/masq_lib/src/multi_config.rs b/masq_lib/src/multi_config.rs index 5c87da2c8..c78447130 100644 --- a/masq_lib/src/multi_config.rs +++ b/masq_lib/src/multi_config.rs @@ -62,8 +62,6 @@ impl<'a> MultiConfig<'a> { schema: &App<'a, 'a>, vcls: Vec>, ) -> Result, ConfiguratorError> { - let initial: Box = - Box::new(CommandLineVcl::new(vec![String::new()])); let mut computed_value_names = HashSet::new(); vcls.iter().for_each(|vcl| { vcl.vcl_args().iter().for_each(|vcl_arg| { @@ -73,7 +71,8 @@ impl<'a> MultiConfig<'a> { }; }) }); - // TODO pull this out to function to use in determine_user_specific_data + let initial: Box = + Box::new(CommandLineVcl::new(vec![String::new()])); let merged = vcls .into_iter() .fold(initial, |so_far, vcl| merge(so_far, vcl)); @@ -90,6 +89,7 @@ impl<'a> MultiConfig<'a> { _ => return Err(Self::make_configurator_error(e)), }, }; + Ok(MultiConfig { arg_matches, computed_value_names, @@ -169,7 +169,6 @@ impl<'a> MultiConfig<'a> { pub trait VclArg: Debug { fn name(&self) -> &str; - fn value_opt(&self) -> Option<&str>; fn to_args(&self) -> Vec; fn dup(&self) -> Box; } @@ -195,9 +194,7 @@ impl VclArg for NameValueVclArg { fn name(&self) -> &str { &self.name } - fn value_opt(&self) -> Option<&str> { - Some(self.value.as_str()) - } + fn to_args(&self) -> Vec { vec![self.name.clone(), self.value.clone()] } @@ -225,9 +222,7 @@ impl VclArg for NameOnlyVclArg { fn name(&self) -> &str { &self.name } - fn value_opt(&self) -> Option<&str> { - None - } + fn to_args(&self) -> Vec { vec![self.name.clone()] } @@ -406,7 +401,7 @@ impl EnvironmentVcl { .collect(); let mut vcl_args: Vec> = vec![]; for (upper_name, value) in std::env::vars() { - if (upper_name.len() < 5) || (&upper_name[0..5] != "MASQ_") || (value == *"") { + if (upper_name.len() < 5) || (&upper_name[0..5] != "MASQ_") { continue; } let lower_name = str::replace(&upper_name[5..].to_lowercase(), "_", "-"); @@ -574,11 +569,10 @@ fn append(ts: Vec, t: T) -> Vec { impl<'a> MultiConfig<'a> { pub fn new_test_only( arg_matches: ArgMatches<'a>, - computed_value_names: HashSet, ) -> Self { Self { arg_matches, - computed_value_names, + computed_value_names: HashSet::new() } } } @@ -591,7 +585,6 @@ pub mod tests { use clap::Arg; use std::fs::File; use std::io::Write; - use std::ops::Deref; #[test] fn config_file_vcl_error_displays_open_error() { @@ -1037,40 +1030,6 @@ pub mod tests { assert_eq!(subject.args(), command_line); } - #[test] - fn command_line_vcl_return_value_from_vcl_args_by_name() { - let command_line: Vec = vec![ - "", - "--first-value", - "/nonexistent/directory", - "--takes_no_value", - "--other_takes_no_value", - ] - .into_iter() - .map(|s| s.to_string()) - .collect(); - - let subject = CommandLineVcl::new(command_line.clone()); - let existing_value = match subject - .vcl_args - .iter() - .find(|vcl_arg_box| vcl_arg_box.deref().name() == "--first-value") - { - Some(vcl_arg_box) => vcl_arg_box.deref().value_opt(), - None => None, - }; - let non_existing_value = match subject - .vcl_args - .iter() - .find(|vcl_arg_box| vcl_arg_box.deref().name() == "--takes_no_value") - { - Some(vcl_arg_box) => vcl_arg_box.deref().value_opt(), - None => None, - }; - assert_eq!(existing_value.unwrap(), "/nonexistent/directory"); - assert_eq!(non_existing_value, None); - } - #[test] #[should_panic(expected = "Expected option beginning with '--', not value")] fn command_line_vcl_panics_when_given_value_without_name() { @@ -1090,14 +1049,8 @@ pub mod tests { Arg::with_name("numeric-arg") .long("numeric-arg") .takes_value(true), - ) - .arg( - Arg::with_name("empty-arg") - .long("empty-arg") - .takes_value(true), ); std::env::set_var("MASQ_NUMERIC_ARG", "47"); - std::env::set_var("MASQ_EMPTY_ARG", ""); let subject = EnvironmentVcl::new(&schema); diff --git a/masq_lib/src/shared_schema.rs b/masq_lib/src/shared_schema.rs index e221c2e88..709b39fc5 100644 --- a/masq_lib/src/shared_schema.rs +++ b/masq_lib/src/shared_schema.rs @@ -223,7 +223,6 @@ pub fn config_file_arg<'a>() -> Arg<'a, 'a> { Arg::with_name("config-file") .long("config-file") .value_name("FILE-PATH") - .default_value("config.toml") .min_values(0) .max_values(1) .required(false) From 71ad41ef98965700cb5f19c8b6c16388e87a0b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Tue, 21 Nov 2023 18:03:54 +0100 Subject: [PATCH 26/52] resolving comments from review --- node/src/daemon/setup_reporter.rs | 4 +- node/src/node_configurator/mod.rs | 277 ++++++++---------- .../node_configurator_standard.rs | 269 ++++++++++++----- node/src/server_initializer.rs | 14 +- node/src/test_utils/mod.rs | 4 +- node/tests/utils.rs | 2 - 6 files changed, 330 insertions(+), 240 deletions(-) diff --git a/node/src/daemon/setup_reporter.rs b/node/src/daemon/setup_reporter.rs index 20980f02c..2f01b3df5 100644 --- a/node/src/daemon/setup_reporter.rs +++ b/node/src/daemon/setup_reporter.rs @@ -498,8 +498,8 @@ impl SetupReporterReal { let user_specific_data = determine_user_specific_data(dirs_wrapper, &app, &command_line)?; let config_file_vcl = match ConfigFileVcl::new( - &user_specific_data.config_file, - user_specific_data.config_file_spec, + &user_specific_data.config_file.item, + user_specific_data.config_file.user_specified, ) { Ok(cfv) => cfv, Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index bda38980b..882f98ea3 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -11,74 +11,78 @@ use crate::database::db_initializer::{DbInitializer, DbInitializerReal}; use crate::db_config::persistent_configuration::{ PersistentConfiguration, PersistentConfigurationReal, }; -use crate::sub_lib::utils::db_connection_launch_panic; +use crate::sub_lib::utils::{db_connection_launch_panic, make_new_multi_config}; use clap::{value_t, App}; use core::option::Option; use dirs::{data_local_dir, home_dir}; use masq_lib::blockchains::chains::Chain; use masq_lib::constants::DEFAULT_CHAIN; -use masq_lib::multi_config::{merge, CommandLineVcl, EnvironmentVcl, MultiConfig, VclArg}; +use masq_lib::multi_config::{merge, CommandLineVcl, EnvironmentVcl, MultiConfig, VirtualCommandLine}; use masq_lib::shared_schema::ConfiguratorError; use masq_lib::utils::{add_masq_and_chain_directories, localhost}; use std::env::current_dir; use std::net::{SocketAddr, TcpListener}; -use std::ops::Deref; use std::path::{Path, PathBuf}; pub trait NodeConfigurator { fn configure(&self, multi_config: &MultiConfig) -> Result; } +#[derive(Default)] #[derive(Debug)] -pub struct UserSpecifiedData { - pub(crate) chain: Chain, - pub(crate) chain_spec: bool, - pub(crate) real_user: RealUser, - pub(crate) real_user_spec: bool, - pub(crate) data_directory: PathBuf, - pub(crate) data_directory_spec: bool, - pub(crate) config_file: PathBuf, - pub(crate) config_file_spec: bool, +pub struct FieldPair { + pub(crate) item: T, + pub(crate) user_specified: bool } -fn get_chain_from_vcl(configs: &[Box]) -> (Chain, bool) { - match argument_from_vcl(configs, "--chain") { - Some(chain) => (Chain::from(chain), true), - None => (DEFAULT_CHAIN, false), +impl FieldPair { + fn new(item: T, user_specified: bool) -> Self { + FieldPair { + item, + user_specified, + } + } +} + +#[derive(Debug)] +pub struct ConfigInitializationData { + pub(crate) chain: FieldPair, + pub(crate) real_user: FieldPair, + pub(crate) data_directory: FieldPair, + pub(crate) config_file: FieldPair, +} + +fn get_chain_from_mc(multi_config: &MultiConfig) -> FieldPair { + let chain = value_m!(multi_config, "chain", String); + match chain { + Some(chain) => FieldPair::new(Chain::from(&*chain), true), + None => FieldPair::new(DEFAULT_CHAIN, false), } } -fn get_real_user_from_vcl( - configs: &[Box], +fn get_real_user_from_mc( + multi_config: &MultiConfig, dirs_wrapper: &dyn DirsWrapper, -) -> (RealUser, bool) { - match argument_from_vcl(configs, "--real-user") { +) -> FieldPair { + let rel_user = value_m!(multi_config, "real-user", RealUser); + match rel_user { Some(user) => { - let real_user_split: Vec<&str> = user.split(':').collect(); - ( - RealUser::new( - Some(real_user_split[0].parse::().expect("expected user id")), - Some( - real_user_split[1] - .parse::() - .expect("expected user group"), - ), - Some(PathBuf::from(real_user_split[2])), - ), + FieldPair::new( + user, true, ) } None => { #[cfg(target_os = "windows")] { - ( + FieldPair::new( RealUser::new(Some(999999), Some(999999), None).populate(dirs_wrapper), false, ) } #[cfg(not(target_os = "windows"))] { - ( + FieldPair::new( RealUser::new(None, None, None).populate(dirs_wrapper), false, ) @@ -87,161 +91,131 @@ fn get_real_user_from_vcl( } } -fn get_data_directory_from_vcl( - configs: &[Box], +fn get_data_directory_from_mc( + multi_config: &MultiConfig, dirs_wrapper: &dyn DirsWrapper, real_user: &RealUser, chain: &Chain, -) -> (PathBuf, bool) { - match argument_from_vcl(configs, "--data-directory") { - Some(data_dir) => match PathBuf::from(data_dir).starts_with("~/") { +) -> FieldPair { + let data_directory = value_m!(multi_config, "data-directory", PathBuf); + match data_directory { + Some(data_dir) => match data_dir.starts_with("~/") { true => { let home_dir_from_wrapper = dirs_wrapper .home_dir() - .expect("expexted users home dir") + .expect("expected users home dir") .to_str() - .expect("expect home dir") + .expect("expected home dir") .to_string(); let replaced_tilde_dir = - data_dir + data_dir.display() .to_string() .replacen('~', home_dir_from_wrapper.as_str(), 1); - (PathBuf::from(replaced_tilde_dir), true) + FieldPair::new(PathBuf::from(replaced_tilde_dir), true) } - false => (PathBuf::from(&data_dir), true), + false => FieldPair::new(data_dir, true), }, - None => ( + None => FieldPair::new( data_directory_from_context(dirs_wrapper, real_user, *chain), false, ), } } -fn get_config_file_from_vcl( - configs: &[Box], +fn get_config_file_from_mc( + multi_config: &MultiConfig, data_directory: &PathBuf, data_directory_def: bool, dirs_wrapper: &dyn DirsWrapper, -) -> (PathBuf, bool) { - match argument_from_vcl(configs, "--config-file") { - Some(config_str) => { - let path = match PathBuf::from(config_str).is_relative() { +) -> FieldPair { + let config_file = value_m!(multi_config, "config-file", PathBuf); + match config_file { + Some(config_path) => { + let path = match config_path.is_relative() { true => { - match PathBuf::from(config_str).file_name().expect("expected file name") == config_str { - true => PathBuf::from(data_directory).join(PathBuf::from(config_str)), - false => match PathBuf::from(config_str).starts_with("./") || PathBuf::from(config_str).starts_with("../") { - true => current_dir().expect("expected curerrnt dir").join(PathBuf::from(config_str)), - false => match PathBuf::from(config_str).starts_with("~") { + match config_path.file_name().expect("expected file name") == config_path { + true => data_directory.join(PathBuf::from(config_path)), + false => match config_path.starts_with("./") || config_path.starts_with("../") { + true => current_dir().expect("expected current dir").join(config_path), + false => match config_path.starts_with("~") { true => { let home_dir_from_wrapper = dirs_wrapper .home_dir() - .expect("expexted users home_dir") + .expect("expected users home_dir") .to_str() - .expect("expect str home_dir") + .expect("expected str home_dir") .to_string(); let replaced_tilde_dir = - config_str + config_path.display() .to_string() .replacen('~', home_dir_from_wrapper.as_str(), 1); PathBuf::from(replaced_tilde_dir) } false => match data_directory_def { - true => PathBuf::from(data_directory).join(PathBuf::from(config_str)), - false => panic!("You need to define data-directory to define config file with naked directory 'dirname/config.toml'.") + true => data_directory.join(config_path), + false => panic!("You need to define data-directory to define config file with naked directory {}.", config_path.to_string_lossy()) } } } } } - false => PathBuf::from(config_str), + false => config_path, }; - (path, true) + FieldPair::new(path, true) } None => { - let path = PathBuf::from(data_directory).join(PathBuf::from("config.toml")); + let path = data_directory.join(PathBuf::from("config.toml")); match path.is_file() { - true => (path, true), - false => (path, false), + true => FieldPair::new(path, true), + false => FieldPair::new(path, false), } } } } -fn config_file_data_dir_real_user_chain_from_vcl( +fn config_file_data_dir_real_user_chain_from_mc( dirs_wrapper: &dyn DirsWrapper, - configs: Vec>, -) -> UserSpecifiedData { - let mut user_specified_data = UserSpecifiedData { + multi_config: MultiConfig, +) -> ConfigInitializationData { + let mut initialization_data = ConfigInitializationData { chain: Default::default(), - chain_spec: false, real_user: Default::default(), - real_user_spec: false, data_directory: Default::default(), - data_directory_spec: false, config_file: Default::default(), - config_file_spec: false, }; - let configs = configs.as_slice(); - (user_specified_data.chain, user_specified_data.chain_spec) = get_chain_from_vcl(configs); - ( - user_specified_data.real_user, - user_specified_data.real_user_spec, - ) = get_real_user_from_vcl(configs, dirs_wrapper); - ( - user_specified_data.data_directory, - user_specified_data.data_directory_spec, - ) = get_data_directory_from_vcl( - configs, + + initialization_data.chain = get_chain_from_mc(&multi_config); + initialization_data.real_user = get_real_user_from_mc(&multi_config, dirs_wrapper); + initialization_data.data_directory = get_data_directory_from_mc( + &multi_config, dirs_wrapper, - &user_specified_data.real_user, - &user_specified_data.chain, + &initialization_data.real_user.item, + &initialization_data.chain.item, ); - ( - user_specified_data.config_file, - user_specified_data.config_file_spec, - ) = get_config_file_from_vcl( - configs, - &user_specified_data.data_directory, - user_specified_data.data_directory_spec, + initialization_data.config_file = get_config_file_from_mc( + &multi_config, + &initialization_data.data_directory.item, + initialization_data.data_directory.user_specified, dirs_wrapper, ); - user_specified_data -} - -fn argument_from_vcl<'a>(configs: &'a [Box], needle: &'a str) -> Option<&'a str> { - match configs - .iter() - .find(|vcl_arg_box| vcl_arg_box.deref().name() == needle) - { - Some(vcl_arg_box) => vcl_arg_box.deref().value_opt(), - None => None, - } + initialization_data } pub fn determine_user_specific_data( dirs_wrapper: &dyn DirsWrapper, app: &App, args: &[String], -) -> Result { - let orientation_args: Vec> = merge( +) -> Result { + let orientation_args: Box = merge( Box::new(EnvironmentVcl::new(app)), - Box::new(CommandLineVcl::new(args.to_vec())), - ) - .vcl_args() - .into_iter() - .filter(|vcl_arg| { - (vcl_arg.name() == "--chain") - || (vcl_arg.name() == "--real-user") - || (vcl_arg.name() == "--data-directory") - || (vcl_arg.name() == "--config-file") - }) - .map(|vcl_arg| vcl_arg.dup()) - .collect(); - //TODO probably need to implement new merge from config_vcl with orientation_args - let user_specified_data = - config_file_data_dir_real_user_chain_from_vcl(dirs_wrapper, orientation_args); - - Ok(user_specified_data) + Box::new(CommandLineVcl::new(args.to_vec())) + ); + /* We create multiconfig to retrieve chain, real-user, data-directory and config file, to establish ConfigVcl */ + let first_multi_config = make_new_multi_config(app, vec![orientation_args]).expect("expected MultiConfig"); + let initialization_data = + config_file_data_dir_real_user_chain_from_mc(dirs_wrapper, first_multi_config); + + Ok(initialization_data) } pub fn initialize_database( @@ -375,35 +349,35 @@ mod tests { ); let _guard = EnvironmentGuard::new(); let args = ArgsBuilder::new() - .param("--clandestine-port", "2345") .param( "--data-directory", &data_directory.to_string_lossy().to_string(), ) .param("--config-file", "booga.toml"); let args_vec: Vec = args.into(); - + let app = determine_config_file_path_app(); let user_specific_data = determine_user_specific_data( &DirsWrapperReal {}, - &determine_config_file_path_app(), + &app, args_vec.as_slice(), ) .unwrap(); + assert_eq!( &format!( "{}", - user_specific_data.config_file.parent().unwrap().display() + user_specific_data.config_file.item.parent().unwrap().display() ), &user_specific_data - .data_directory - .to_string_lossy() - .to_string(), + .data_directory.item + .to_string_lossy() + .to_string(), ); assert_eq!( "booga.toml", - user_specific_data.config_file.file_name().unwrap() + user_specific_data.config_file.item.file_name().unwrap() ); - assert_eq!(user_specific_data.real_user_spec, false); + assert_eq!(user_specific_data.real_user.user_specified, false); } #[test] @@ -420,28 +394,32 @@ mod tests { &data_directory.to_string_lossy().to_string(), ); std::env::set_var("MASQ_CONFIG_FILE", "booga.toml"); - + let app = determine_config_file_path_app(); let user_specific_data = determine_user_specific_data( &DirsWrapperReal {}, - &determine_config_file_path_app(), + &app, args_vec.as_slice(), ) .unwrap(); assert_eq!( format!( "{}", - user_specific_data.config_file.parent().unwrap().display() + user_specific_data.config_file.item.parent().unwrap().display() ), user_specific_data - .data_directory + .data_directory.item .to_string_lossy() .to_string(), ); assert_eq!( "booga.toml", - user_specific_data.config_file.file_name().unwrap() + user_specific_data.config_file.item.file_name().unwrap() ); - assert_eq!(user_specific_data.real_user_spec, false); //all these assertions of 'real_user_specified' was incorrect, no idea how this tests could pass before + assert_eq!( + user_specific_data.config_file.user_specified, + true + ); + assert_eq!(user_specific_data.real_user.user_specified, false); //all these assertions of 'real_user_specified' was incorrect, no idea how this tests could pass before } #[cfg(not(target_os = "windows"))] @@ -453,18 +431,19 @@ mod tests { .param("--config-file", "/tmp/booga.toml"); let args_vec: Vec = args.into(); + let app = determine_config_file_path_app(); let user_specific_data = determine_user_specific_data( &DirsWrapperReal {}, - &determine_config_file_path_app(), + &app, args_vec.as_slice(), ) .unwrap(); assert_eq!( "/tmp/booga.toml", - &format!("{}", user_specific_data.config_file.display()) + &format!("{}", user_specific_data.config_file.item.display()) ); - assert_eq!(user_specific_data.real_user_spec, false); + assert_eq!(user_specific_data.real_user.user_specified, false); } #[cfg(target_os = "windows")] @@ -485,9 +464,9 @@ mod tests { assert_eq!( r"\tmp\booga.toml", - &format!("{}", user_specific_data.config_file.display()) + &format!("{}", user_specific_data.config_file.item.display()) ); - assert_eq!(user_specific_data.real_user_spec, false); + assert_eq!(user_specific_data.real_user.user_specified, false); } #[cfg(target_os = "windows")] @@ -508,9 +487,9 @@ mod tests { assert_eq!( r"c:\tmp\booga.toml", - &format!("{}", user_specific_data.config_file.display()) + &format!("{}", user_specific_data.config_file.item.display()) ); - assert_eq!(user_specific_data.real_user_spec, false); + assert_eq!(user_specific_data.real_user.user_specified, false); } #[cfg(target_os = "windows")] @@ -531,9 +510,9 @@ mod tests { assert_eq!( r"\\TMP\booga.toml", - &format!("{}", user_specific_data.config_file.display()) + &format!("{}", user_specific_data.config_file.item.display()) ); - assert_eq!(user_specific_data.real_user_spec, false); + assert_eq!(user_specific_data.real_user.user_specified, false); } #[cfg(target_os = "windows")] @@ -555,9 +534,9 @@ mod tests { assert_eq!( r"c:tmp\booga.toml", - &format!("{}", user_specific_data.config_file.display()) + &format!("{}", user_specific_data.config_file.item.display()) ); - assert_eq!(user_specific_data.real_user_spec, false); + assert_eq!(user_specific_data.real_user.user_specified, false); } #[test] diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index c257b1f23..272d046e2 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -2,7 +2,7 @@ use crate::bootstrapper::BootstrapperConfig; use crate::node_configurator::{initialize_database, DirsWrapper, NodeConfigurator}; -use crate::node_configurator::{DirsWrapperReal, UserSpecifiedData}; +use crate::node_configurator::{DirsWrapperReal, ConfigInitializationData}; use masq_lib::crash_point::CrashPoint; use masq_lib::logger::Logger; use masq_lib::multi_config::{ComputedVcl, MultiConfig, VirtualCommandLine}; @@ -10,9 +10,8 @@ use masq_lib::shared_schema::ConfiguratorError; use masq_lib::utils::NeighborhoodModeLight; use std::net::SocketAddr; use std::net::{IpAddr, Ipv4Addr}; -use std::ops::Deref; -use clap::value_t; +use clap::{App, value_t}; use log::LevelFilter; use crate::apps::app_node; @@ -31,7 +30,6 @@ use crate::sub_lib::cryptde::PublicKey; use crate::sub_lib::cryptde_null::CryptDENull; use crate::sub_lib::utils::make_new_multi_config; use crate::tls_discriminator_factory::TlsDiscriminatorFactory; -use itertools::Itertools; use masq_lib::constants::{DEFAULT_UI_PORT, HTTP_PORT, TLS_PORT}; use masq_lib::multi_config::{CommandLineVcl, ConfigFileVcl, EnvironmentVcl}; use std::str::FromStr; @@ -138,54 +136,29 @@ fn collect_externals_from_multi_config( } fn extract_values_vcl_fill_multiconfig_vec( - config_file_vcl: Box, - environment_vcl: Box, - commandline_vcl: Box, - user_specific_data: UserSpecifiedData, + multi_config_vec: Vec>, + initialization_data: ConfigInitializationData, + app: &App ) -> (Vec, Vec) { - let mut unspecified_vec: Vec = vec!["".to_string()]; - let mut specified_vec: Vec = vec!["".to_string()]; - let config_file_specified = user_specific_data.config_file_spec; - let config_file_path = user_specific_data.config_file; - let extract_value_from_vcl = - |vcl: &dyn VirtualCommandLine, name: &str, var: &str, spec: bool| { - let args = vcl.args(); - let extracted_args = args - .iter() - .enumerate() - .filter(|(_index, vcl_arg)| vcl_arg.deref() == &name.to_string()) - .collect_vec(); - match extracted_args.is_empty() { - false => { - let val_str = args[extracted_args[0].0 + 1].clone(); - (val_str, true) - } - true => (var.to_string(), spec), + let full_multi_config = make_new_multi_config(&app, multi_config_vec).expect("expected multi_config"); + let config_file_specified = initialization_data.config_file.user_specified; + let config_file_path = initialization_data.config_file.item; + let check_value_from_mc = + |value: Option, var: &str, spec: bool| { + match value { + Some(arg) => (arg, true), + None => (var.to_string(), spec), } }; - let (cf_real_user, cf_real_user_specified) = extract_value_from_vcl( - &*config_file_vcl, - "--real-user", - user_specific_data.real_user.to_string().as_str(), - user_specific_data.real_user_spec, + let (cf_real_user, cf_real_user_specified) = check_value_from_mc( + value_m!(full_multi_config, "real-user", String), + initialization_data.real_user.item.to_string().as_str(), + initialization_data.real_user.user_specified, ); - - let (env_real_user, env_real_user_specified) = extract_value_from_vcl( - &*environment_vcl, - "--real-user", - user_specific_data.real_user.to_string().as_str(), - user_specific_data.real_user_spec, - ); - - let (cmd_real_user, cmd_real_user_specified) = extract_value_from_vcl( - &*commandline_vcl, - "--real-user", - user_specific_data.real_user.to_string().as_str(), - user_specific_data.real_user_spec, - ); - + let mut unspecified_vec: Vec = vec!["".to_string()]; + let mut specified_vec: Vec = vec!["".to_string()]; let mut fill_specified_or_unspecified_box = - |key: &str, value: &str, specified: bool| match value.is_empty() { + | key: &str, value: &str, specified: bool| match value.is_empty() { true => (), false => match specified { true => match specified_vec.contains(&key.to_string()) { @@ -225,23 +198,11 @@ fn extract_values_vcl_fill_multiconfig_vec( ); fill_specified_or_unspecified_box( "--data-directory", - user_specific_data.data_directory.to_string_lossy().as_ref(), - user_specific_data.data_directory_spec, + initialization_data.data_directory.item.to_string_lossy().as_ref(), + initialization_data.data_directory.user_specified, ); - fill_specified_or_unspecified_box("--real-user", cf_real_user.as_str(), cf_real_user_specified); - fill_specified_or_unspecified_box( - "--real-user", - env_real_user.as_str(), - env_real_user_specified, - ); - - fill_specified_or_unspecified_box( - "--real-user", - cmd_real_user.as_str(), - cmd_real_user_specified, - ); (unspecified_vec, specified_vec) } @@ -250,10 +211,10 @@ pub fn server_initializer_collected_params<'a>( args: &[String], ) -> Result, ConfiguratorError> { let app = app_node(); - let user_specific_data = determine_user_specific_data(dirs_wrapper, &app, args)?; + let initialization_data = determine_user_specific_data(dirs_wrapper, &app, args)?; let config_file_vcl = match ConfigFileVcl::new( - &user_specific_data.config_file, - user_specific_data.config_file_spec, + &initialization_data.config_file.item, + initialization_data.config_file.user_specified, ) { Ok(cfv) => cfv, Err(e) => return Err(ConfiguratorError::required("config-file", &e.to_string())), @@ -262,17 +223,17 @@ pub fn server_initializer_collected_params<'a>( let environment_vcl = EnvironmentVcl::new(&app); let commandline_vcl = CommandLineVcl::new(args.to_vec()); let (unspecified_vec, specified_vec) = extract_values_vcl_fill_multiconfig_vec( - Box::new(config_file_vcl.clone()), + vec![Box::new(config_file_vcl.clone()), Box::new(EnvironmentVcl::new(&app)), - Box::new(CommandLineVcl::new(commandline_vcl.args())), - user_specific_data, + Box::new(CommandLineVcl::new(commandline_vcl.args()))], + initialization_data, + &app ); let mut multi_config_args_vec: Vec> = vec![ Box::new(config_file_vcl), Box::new(environment_vcl), Box::new(commandline_vcl), ]; - //TODO write test for MultiConfig "try_new" merge line 76 if unspecified_vec.len() > 1 { multi_config_args_vec.push(Box::new(ComputedVcl::new(unspecified_vec))); } @@ -281,7 +242,7 @@ pub fn server_initializer_collected_params<'a>( } let full_multi_config = make_new_multi_config(&app, multi_config_args_vec)?; - + //println!("full_multi_config: {:#?}", full_multi_config); Ok(full_multi_config) } @@ -437,6 +398,7 @@ mod tests { use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use std::vec; + use masq_lib::shared_schema::ParamError; #[test] fn node_configurator_standard_unprivileged_uses_parse_args_configurator_dao_real() { @@ -591,8 +553,6 @@ mod tests { .write_all(b"dns-servers = \"111.111.111.111,222.222.222.222\"\nreal-user = \"1004:1004:/home/gooba\"\n") .unwrap(); } - std::env::set_var("MASQ_DNS_SERVERS", ""); - std::env::set_var("MASQ_CONFIG_FILE", ""); let directory_wrapper = make_pre_populated_mocked_directory_wrapper(); let args = ArgsBuilder::new().param("--data-directory", home_dir.to_str().unwrap()); let args_vec: Vec = args.into(); @@ -638,8 +598,8 @@ mod tests { let multi_config = make_new_multi_config( &app_node(), vec![ - Box::new(CommandLineVcl::new(args.into())), Box::new(ConfigFileVcl::new(&config_file_path, false).unwrap()), + Box::new(CommandLineVcl::new(args.into())), ], ) .unwrap(); @@ -760,7 +720,7 @@ mod tests { privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); assert_eq!( - Some(PathBuf::from("config.toml")), + None, value_m!(multi_config, "config-file", PathBuf) ); assert_eq!( @@ -817,7 +777,7 @@ mod tests { privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); assert_eq!( - Some(PathBuf::from("config.toml")), + None, value_m!(multi_config, "config-file", PathBuf) ); assert_eq!( @@ -934,6 +894,9 @@ mod tests { value_m!(env_multiconfig, "data-directory", String).unwrap(), "booga/data_dir/MASQ/polygon-mainnet".to_string() ); + assert_eq!(env_multiconfig.is_user_specified("--data-directory"), false); + assert_eq!(env_multiconfig.is_user_specified("--config-file"), true); + assert_eq!(env_multiconfig.is_user_specified("--real-user"), true); assert_eq!( value_m!(env_multiconfig, "config-file", String).unwrap(), current_dir().expect("expected curerrnt dir") @@ -962,8 +925,6 @@ mod tests { fill_up_config_file(config_file_relative); let env_vec_array = vec![ ("MASQ_CONFIG_FILE", "./generated/test/node_configurator_standard/server_initializer_collected_params_handle_dot_in_path_and_real_user_in_config_file/config.toml"), - #[cfg(not(target_os = "windows"))] - ("MASQ_REAL_USER", "9999:9999:booga"), ]; env_vec_array .clone() @@ -983,7 +944,11 @@ mod tests { { assert_eq!( value_m!(env_multiconfig, "data-directory", String).unwrap(), - "booga/data_dir/MASQ/polygon-mainnet".to_string() + home_dir.clone().join("data_dir/MASQ/polygon-mainnet").to_string_lossy() + ); + assert_eq!( + value_m!(env_multiconfig, "real-user", String).unwrap(), + "1002:1002:/home/wooga".to_string() ); assert_eq!( value_m!(env_multiconfig, "config-file", String).unwrap(), @@ -997,6 +962,150 @@ mod tests { ); } + #[test] + fn server_initializer_collected_params_handles_only_path_in_config_file_param() + { + running_test(); + let _guard = EnvironmentGuard::new(); + let _clap_guard = ClapGuard::new(); + let home_dir = ensure_node_home_directory_exists( + "node_configurator_standard", + "server_initializer_collected_params_handles_only_path_in_config_file_param", + ); + let data_dir = &home_dir.join("data_dir"); + + let args = ArgsBuilder::new() + .param("--data-directory", home_dir.clone().display().to_string().as_str()) + .param("--config-file", home_dir.clone().display().to_string().as_str()); + let args_vec: Vec = args.into(); + let dir_wrapper = DirsWrapperMock::new() + .home_dir_result(Some(home_dir.clone())) + .data_dir_result(Some(data_dir.to_path_buf())); + + let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()).unwrap_err(); + let expected = ConfiguratorError::new(vec![ ParamError::new("config-file", "Couldn't open configuration file \"generated/test/node_configurator_standard/server_initializer_collected_params_handles_only_path_in_config_file_param/home/generated/test/node_configurator_standard/server_initializer_collected_params_handles_only_path_in_config_file_param/home\". Are you sure it exists?")]); + + assert_eq!(result, expected); + } + + #[test] + fn server_initializer_collected_params_rewrite_config_files_parameters_from_command_line() { + running_test(); + let _guard = EnvironmentGuard::new(); + let _clap_guard = ClapGuard::new(); + let home_dir = ensure_node_home_directory_exists( + "node_configurator_standard", + "server_initializer_collected_params_rewrite_config_files_parameters_from_command_line", + ); + let data_dir = &home_dir.join("data_dir"); + let config_file_relative = File::create(PathBuf::from("./generated/test/node_configurator_standard/server_initializer_collected_params_rewrite_config_files_parameters_from_command_line").join("config.toml")).unwrap(); + fill_up_config_file(config_file_relative); + let env_vec_array = vec![ + ("MASQ_CONFIG_FILE", "./generated/test/node_configurator_standard/server_initializer_collected_params_rewrite_config_files_parameters_from_command_line/config.toml"), + ]; + env_vec_array + .clone() + .into_iter() + .for_each(|(name, value)| std::env::set_var(name, value)); + let args = ArgsBuilder::new() + .param("--blockchain-service-url", "https://www.mainnet0.com") + .param("--real-user", "9999:9999:/home/booga") + .param("--ip", "8.5.7.6") + .param("--neighborhood-mode", "standard") + .param("--clandestine-port", "2345"); + let args_vec: Vec = args.into(); + let dir_wrapper = DirsWrapperMock::new() + .home_dir_result(Some(home_dir.clone())) + .data_dir_result(Some(data_dir.to_path_buf())); + + let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); + let env_multiconfig = result.unwrap(); + + assert_eq!(env_multiconfig.is_user_specified("--data-directory"), false); + #[cfg(not(target_os = "windows"))] + { + assert_eq!( + value_m!(env_multiconfig, "blockchain-service-url", String).unwrap(), + "https://www.mainnet0.com".to_string() + ); + assert_eq!( + value_m!(env_multiconfig, "real-user", String).unwrap(), + "9999:9999:/home/booga".to_string() + ); + assert_eq!( + value_m!(env_multiconfig, "config-file", String).unwrap(), + current_dir().unwrap().join(PathBuf::from( "./generated/test/node_configurator_standard/server_initializer_collected_params_rewrite_config_files_parameters_from_command_line/config.toml")).to_string_lossy().to_string() + ); + } + assert_eq!( + value_m!(env_multiconfig, "ip", String).unwrap(), + "8.5.7.6".to_string() + ); + #[cfg(target_os = "windows")] + assert_eq!( + value_m!(env_multiconfig, "data-directory", String).unwrap(), + "generated/test/node_configurator_standard/server_initializer_collected_params_rewrite_config_files_parameters_from_command_line/home\\data_dir\\MASQ\\polygon-mainnet".to_string() + ); + } + + #[test] + fn server_initializer_collected_params_rewrite_config_files_parameters_from_environment() { + running_test(); + let _guard = EnvironmentGuard::new(); + let _clap_guard = ClapGuard::new(); + let home_dir = ensure_node_home_directory_exists( + "node_configurator_standard", + "server_initializer_collected_params_rewrite_config_files_parameters_from_environment", + ); + let data_dir = &home_dir.join("data_dir"); + let config_file_relative = File::create(PathBuf::from("./generated/test/node_configurator_standard/server_initializer_collected_params_rewrite_config_files_parameters_from_environment").join("config.toml")).unwrap(); + fill_up_config_file(config_file_relative); + let env_vec_array = vec![ + ("MASQ_CONFIG_FILE", "./generated/test/node_configurator_standard/server_initializer_collected_params_rewrite_config_files_parameters_from_environment/config.toml"), + ("MASQ_BLOCKCHAIN_SERVICE_URL", "https://www.mainnet0.com"), + ("MASQ_REAL_USER", "9999:9999:/home/booga"), + ("MASQ_IP", "8.5.7.6"), + ]; + env_vec_array + .clone() + .into_iter() + .for_each(|(name, value)| std::env::set_var(name, value)); + let args = ArgsBuilder::new(); + let args_vec: Vec = args.into(); + let dir_wrapper = DirsWrapperMock::new() + .home_dir_result(Some(home_dir.clone())) + .data_dir_result(Some(data_dir.to_path_buf())); + + let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); + let env_multiconfig = result.unwrap(); + + assert_eq!(env_multiconfig.is_user_specified("--data-directory"), false); + #[cfg(not(target_os = "windows"))] + { + assert_eq!( + value_m!(env_multiconfig, "real-user", String).unwrap(), + "9999:9999:/home/booga".to_string() + ); + assert_eq!( + value_m!(env_multiconfig, "config-file", String).unwrap(), + current_dir().unwrap().join(PathBuf::from( "./generated/test/node_configurator_standard/server_initializer_collected_params_rewrite_config_files_parameters_from_environment/config.toml")).to_string_lossy().to_string() + ); + } + assert_eq!( + value_m!(env_multiconfig, "blockchain-service-url", String).unwrap(), + "https://www.mainnet0.com".to_string() + ); + assert_eq!( + value_m!(env_multiconfig, "ip", String).unwrap(), + "8.5.7.6".to_string() + ); + #[cfg(target_os = "windows")] + assert_eq!( + value_m!(env_multiconfig, "data-directory", String).unwrap(), + "generated/test/node_configurator_standard/server_initializer_collected_params_rewrite_config_files_parameters_from_environment/home\\data_dir\\MASQ\\polygon-mainnet".to_string() + ); + } + #[test] fn server_initializer_collected_params_handle_tilde_in_path_config_file_from_commandline_and_real_user_from_config_file( ) { @@ -1109,7 +1218,7 @@ mod tests { #[test] #[should_panic( - expected = "You need to define data-directory to define config file with naked directory 'dirname/config.toml'." + expected = "You need to define data-directory to define config file with naked directory config/config.toml." )] fn server_initializer_collected_params_fails_on_naked_dir_config_file_without_data_directory() { running_test(); @@ -1323,6 +1432,9 @@ mod tests { assert_eq!(config.blockchain_bridge_config.gas_price, 1); } + #[should_panic( + expected = "expected MultiConfig: ConfiguratorError { param_errors: [ParamError { parameter: \"gas-price\", reason: \"Invalid value: unleaded\" }] }" + )] #[test] fn server_initializer_collected_params_rejects_invalid_gas_price() { running_test(); @@ -1516,6 +1628,7 @@ mod tests { fn server_initializer_collected_params_senses_when_user_specifies_data_directory_without_chain_specific_directory( ) { let _guard = EnvironmentGuard::new(); + let _clap_guard = ClapGuard::new(); running_test(); let home_dir = Path::new("/home/cooga"); let home_dir_poly_main = home_dir.join(".local").join("MASQ").join("polygon-mainnet"); diff --git a/node/src/server_initializer.rs b/node/src/server_initializer.rs index ece182c5d..dd46f76f2 100644 --- a/node/src/server_initializer.rs +++ b/node/src/server_initializer.rs @@ -36,19 +36,19 @@ pub struct ServerInitializerReal { impl ServerInitializer for ServerInitializerReal { fn go(&mut self, streams: &mut StdStreams<'_>, args: &[String]) -> RunModeResult { - let params = server_initializer_collected_params(self.dirs_wrapper.as_ref(), args)?; - let real_user = value_m!(params, "real-user", RealUser) + let multi_config = server_initializer_collected_params(self.dirs_wrapper.as_ref(), args)?; + let real_user = value_m!(multi_config, "real-user", RealUser) .expect("ServerInitializer: Real user not present in Multi Config"); - let data_directory = value_m!(params, "data-directory", String) + let data_directory = value_m!(multi_config, "data-directory", String) .expect("ServerInitializer: Data directory not present in Multi Config"); let result: RunModeResult = Ok(()) .combine_results( self.dns_socket_server .as_mut() - .initialize_as_privileged(¶ms), + .initialize_as_privileged(&multi_config), ) - .combine_results(self.bootstrapper.as_mut().initialize_as_privileged(¶ms)); + .combine_results(self.bootstrapper.as_mut().initialize_as_privileged(&multi_config)); self.privilege_dropper .chown(Path::new(data_directory.as_str()), &real_user); @@ -59,12 +59,12 @@ impl ServerInitializer for ServerInitializerReal { .combine_results( self.dns_socket_server .as_mut() - .initialize_as_unprivileged(¶ms, streams), + .initialize_as_unprivileged(&multi_config, streams), ) .combine_results( self.bootstrapper .as_mut() - .initialize_as_unprivileged(¶ms, streams), + .initialize_as_unprivileged(&multi_config, streams), ) } implement_as_any!(); diff --git a/node/src/test_utils/mod.rs b/node/src/test_utils/mod.rs index f8bf58b02..3dfd8839e 100644 --- a/node/src/test_utils/mod.rs +++ b/node/src/test_utils/mod.rs @@ -573,7 +573,7 @@ pub mod unshared_test_utils { use masq_lib::utils::slice_of_strs_to_vec_of_strings; use std::any::TypeId; use std::cell::RefCell; - use std::collections::{HashMap, HashSet}; + use std::collections::HashMap; use std::num::ParseIntError; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::path::{Path, PathBuf}; @@ -643,7 +643,7 @@ pub mod unshared_test_utils { let mut app_args = vec!["MASQNode".to_string()]; app_args.append(&mut slice_of_strs_to_vec_of_strings(&args)); let arg_matches = app_node().get_matches_from_safe(app_args).unwrap(); - MultiConfig::new_test_only(arg_matches, HashSet::new()) + MultiConfig::new_test_only(arg_matches) } pub const ZERO: u32 = 0b0; diff --git a/node/tests/utils.rs b/node/tests/utils.rs index bc80d7d39..b60017063 100644 --- a/node/tests/utils.rs +++ b/node/tests/utils.rs @@ -273,8 +273,6 @@ impl MASQNode { #[allow(dead_code)] pub fn wait_for_exit(&mut self) -> Option { - //TODO Put the body of this function in a background thread and wait on the thread for a few - // seconds. If the thread doesn't terminate, leak the thread and return None. let child_opt = self.child.take(); let output_opt = self.output.take(); match (child_opt, output_opt) { From fcd1c0d14ecf7fc1396169bf123e9b1cdeb706ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Mon, 4 Dec 2023 11:13:04 +0100 Subject: [PATCH 27/52] optimization of get_config_file_from_mc fn --- masq_lib/src/multi_config.rs | 85 +----- node/src/daemon/setup_reporter.rs | 18 +- node/src/node_configurator/mod.rs | 187 +++++++----- .../node_configurator_standard.rs | 275 +++++++----------- node/src/server_initializer.rs | 6 +- 5 files changed, 252 insertions(+), 319 deletions(-) diff --git a/masq_lib/src/multi_config.rs b/masq_lib/src/multi_config.rs index c78447130..dfc1c7184 100644 --- a/masq_lib/src/multi_config.rs +++ b/masq_lib/src/multi_config.rs @@ -50,7 +50,6 @@ macro_rules! values_m { #[derive(Debug)] pub struct MultiConfig<'a> { arg_matches: ArgMatches<'a>, - computed_value_names: HashSet, } impl<'a> MultiConfig<'a> { @@ -62,15 +61,6 @@ impl<'a> MultiConfig<'a> { schema: &App<'a, 'a>, vcls: Vec>, ) -> Result, ConfiguratorError> { - let mut computed_value_names = HashSet::new(); - vcls.iter().for_each(|vcl| { - vcl.vcl_args().iter().for_each(|vcl_arg| { - match vcl.is_computed() { - true => computed_value_names.insert(vcl_arg.name().to_string()), - false => computed_value_names.remove(&vcl_arg.name().to_string()), - }; - }) - }); let initial: Box = Box::new(CommandLineVcl::new(vec![String::new()])); let merged = vcls @@ -90,10 +80,7 @@ impl<'a> MultiConfig<'a> { }, }; - Ok(MultiConfig { - arg_matches, - computed_value_names, - }) + Ok(MultiConfig { arg_matches }) } fn check_for_invalid_value_err( @@ -154,10 +141,6 @@ impl<'a> MultiConfig<'a> { ConfiguratorError::required("", &format!("Unfamiliar message: {}", e.message)) } - pub fn is_user_specified(&self, value_name: &str) -> bool { - !self.computed_value_names.contains(value_name) - } - pub fn occurrences_of(&self, parameter: &str) -> u64 { self.arg_matches.occurrences_of(parameter) } @@ -285,24 +268,6 @@ pub fn merge( }) } -pub struct ComputedVcl { - vcl_args: Vec>, -} - -impl VirtualCommandLine for ComputedVcl { - fn vcl_args(&self) -> Vec<&dyn VclArg> { - vcl_args_to_vcl_args(&self.vcl_args) - } - - fn args(&self) -> Vec { - vcl_args_to_args(&self.vcl_args) - } - - fn is_computed(&self) -> bool { - true - } -} - pub struct CommandLineVcl { vcl_args: Vec>, } @@ -323,33 +288,6 @@ impl From>> for CommandLineVcl { } } -impl ComputedVcl { - pub fn new(mut args: Vec) -> ComputedVcl { - args.remove(0); // remove command - let mut vcl_args = vec![]; - while let Some(vcl_arg) = Self::next_vcl_arg(&mut args) { - vcl_args.push(vcl_arg); - } - ComputedVcl { vcl_args } - } - - fn next_vcl_arg(args: &mut Vec) -> Option> { - if args.is_empty() { - return None; - } - let name = args.remove(0); - if !name.starts_with("--") { - panic!("Expected option beginning with '--', not {}", name) - } - if args.is_empty() || args[0].starts_with("--") { - Some(Box::new(NameOnlyVclArg::new(&name))) - } else { - let value = args.remove(0); - Some(Box::new(NameValueVclArg::new(&name, &value))) - } - } -} - impl CommandLineVcl { pub fn new(mut args: Vec) -> CommandLineVcl { args.remove(0); // remove command @@ -567,19 +505,15 @@ fn append(ts: Vec, t: T) -> Vec { #[cfg(not(feature = "no_test_share"))] impl<'a> MultiConfig<'a> { - pub fn new_test_only( - arg_matches: ArgMatches<'a>, - ) -> Self { - Self { - arg_matches, - computed_value_names: HashSet::new() - } + pub fn new_test_only(arg_matches: ArgMatches<'a>) -> Self { + Self { arg_matches } } } #[cfg(test)] pub mod tests { use super::*; + // use crate::shared_schema::official_chain_names; use crate::test_utils::environment_guard::EnvironmentGuard; use crate::test_utils::utils::ensure_node_home_directory_exists; use clap::Arg; @@ -1044,12 +978,11 @@ pub mod tests { #[test] fn environment_vcl_works() { let _guard = EnvironmentGuard::new(); - let schema = App::new("test") - .arg( - Arg::with_name("numeric-arg") - .long("numeric-arg") - .takes_value(true), - ); + let schema = App::new("test").arg( + Arg::with_name("numeric-arg") + .long("numeric-arg") + .takes_value(true), + ); std::env::set_var("MASQ_NUMERIC_ARG", "47"); let subject = EnvironmentVcl::new(&schema); diff --git a/node/src/daemon/setup_reporter.rs b/node/src/daemon/setup_reporter.rs index 2f01b3df5..effd4dc90 100644 --- a/node/src/daemon/setup_reporter.rs +++ b/node/src/daemon/setup_reporter.rs @@ -497,6 +497,7 @@ impl SetupReporterReal { }; let user_specific_data = determine_user_specific_data(dirs_wrapper, &app, &command_line)?; + println!("user_specific_data {:?}", &user_specific_data); let config_file_vcl = match ConfigFileVcl::new( &user_specific_data.config_file.item, user_specific_data.config_file.user_specified, @@ -1298,7 +1299,7 @@ mod tests { fn everything_in_defaults_is_properly_constructed() { let result = SetupReporterReal::get_default_params(); - assert_eq!(result.is_empty(), false, "{:?}", result); // if we don't have any defaults, let's get rid of all this + assert_eq!(result.is_empty(), true, "{:?}", result); // if we have any defaults, let's get back to false statement here and assert right value line below result.into_iter().for_each(|(name, value)| { assert_eq!(name, value.name); assert_eq!(value.status, Default); @@ -1393,7 +1394,7 @@ mod tests { ), ("chain", DEFAULT_CHAIN.rec().literal_identifier, Default), ("clandestine-port", "1234", Configured), - ("config-file", "config.toml", Default), + ("config-file", "", Blank), ("consuming-private-key", "", Blank), ("crash-point", "", Blank), ( @@ -1572,7 +1573,7 @@ mod tests { ("blockchain-service-url", "https://example2.com", Set), ("chain", TEST_DEFAULT_CHAIN.rec().literal_identifier, Set), ("clandestine-port", "1234", Set), - ("config-file", "config.toml", Default), + ("config-file", "", Blank), ("consuming-private-key", "0011223344556677001122334455667700112233445566770011223344556677", Set), ("crash-point", "Message", Set), ("data-directory", chain_specific_data_dir.to_str().unwrap(), Set), @@ -1645,7 +1646,7 @@ mod tests { ("blockchain-service-url", "https://example3.com", Configured), ("chain", TEST_DEFAULT_CHAIN.rec().literal_identifier, Configured), ("clandestine-port", "1234", Configured), - ("config-file", "config.toml", Default), + ("config-file", "", Blank), ("consuming-private-key", "0011223344556677001122334455667700112233445566770011223344556677", Configured), ("crash-point", "Error", Configured), ("data-directory", home_dir.to_str().unwrap(), Configured), @@ -1795,7 +1796,7 @@ mod tests { ), ("chain", TEST_DEFAULT_CHAIN.rec().literal_identifier, Set), ("clandestine-port", "8877", Configured), - ("config-file", "config.toml", Default), + ("config-file", "", Blank), ( "consuming-private-key", "FFEEDDCCBBAA99887766554433221100FFEEDDCCBBAA99887766554433221100", @@ -1954,7 +1955,7 @@ mod tests { ("blockchain-service-url", "", Required), ("chain", TEST_DEFAULT_CHAIN.rec().literal_identifier, Configured), ("clandestine-port", "1234", Configured), - ("config-file", "config.toml", Default), + ("config-file", "", Blank), ("consuming-private-key", "0011223344556677001122334455667700112233445566770011223344556677", Configured), ("crash-point", "Panic", Configured), ("data-directory", home_dir.to_str().unwrap(), Configured), @@ -2633,10 +2634,7 @@ mod tests { .calculate_configured_setup(&setup, &data_directory) .0; - assert_eq!( - result.get("config-file").unwrap().value, - "config.toml".to_string() - ); + assert_eq!(result.get("config-file").unwrap().value, "".to_string()); assert_eq!( result.get("gas-price").unwrap().value, GasPrice {} diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index 882f98ea3..1d6651d75 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -17,9 +17,12 @@ use core::option::Option; use dirs::{data_local_dir, home_dir}; use masq_lib::blockchains::chains::Chain; use masq_lib::constants::DEFAULT_CHAIN; -use masq_lib::multi_config::{merge, CommandLineVcl, EnvironmentVcl, MultiConfig, VirtualCommandLine}; +use masq_lib::multi_config::{ + merge, CommandLineVcl, EnvironmentVcl, MultiConfig, VirtualCommandLine, +}; use masq_lib::shared_schema::ConfiguratorError; use masq_lib::utils::{add_masq_and_chain_directories, localhost}; +use nix::NixPath; use std::env::current_dir; use std::net::{SocketAddr, TcpListener}; use std::path::{Path, PathBuf}; @@ -28,11 +31,10 @@ pub trait NodeConfigurator { fn configure(&self, multi_config: &MultiConfig) -> Result; } -#[derive(Default)] -#[derive(Debug)] +#[derive(Default, Debug)] pub struct FieldPair { pub(crate) item: T, - pub(crate) user_specified: bool + pub(crate) user_specified: bool, } impl FieldPair { @@ -66,12 +68,7 @@ fn get_real_user_from_mc( ) -> FieldPair { let rel_user = value_m!(multi_config, "real-user", RealUser); match rel_user { - Some(user) => { - FieldPair::new( - user, - true, - ) - } + Some(user) => FieldPair::new(user, true), None => { #[cfg(target_os = "windows")] { @@ -108,7 +105,8 @@ fn get_data_directory_from_mc( .expect("expected home dir") .to_string(); let replaced_tilde_dir = - data_dir.display() + data_dir + .display() .to_string() .replacen('~', home_dir_from_wrapper.as_str(), 1); FieldPair::new(PathBuf::from(replaced_tilde_dir), true) @@ -122,46 +120,95 @@ fn get_data_directory_from_mc( } } +fn replace_tilde(config_path: &PathBuf, dirs_wrapper: &dyn DirsWrapper) -> PathBuf { + let mut replaced_tilde_dir = "".to_string(); + if config_path.starts_with("~") { + let home_dir_from_wrapper = dirs_wrapper + .home_dir() + .expect("expected users home_dir") + .to_str() + .expect("expected str home_dir") + .to_string(); + replaced_tilde_dir = + config_path + .display() + .to_string() + .replacen('~', home_dir_from_wrapper.as_str(), 1); + }; + PathBuf::from(replaced_tilde_dir) +} + +fn replace_dots(config_path: &PathBuf, panic: &mut bool) -> PathBuf { + let origin_path = config_path.clone(); + let mut new_path: PathBuf = Default::default(); + if config_path.starts_with("./") || origin_path.starts_with("../") { + *panic = false; + new_path = current_dir() + .expect("expected current dir") + .join(origin_path.clone()); + }; + new_path +} + +fn replace_relative_path( + config_path: &PathBuf, + data_directory_def: bool, + data_directory: &Path, + panic: &mut bool, +) -> PathBuf { + let mut path = Default::default(); + if config_path.is_relative() { + match data_directory_def { + true => { + *panic = false; + path = data_directory.join(config_path); + } + false => { + *panic = true; + path = config_path.to_path_buf(); + } + } + }; + path +} + fn get_config_file_from_mc( multi_config: &MultiConfig, - data_directory: &PathBuf, + data_directory: &Path, data_directory_def: bool, dirs_wrapper: &dyn DirsWrapper, ) -> FieldPair { + let mut panic: bool = false; let config_file = value_m!(multi_config, "config-file", PathBuf); match config_file { Some(config_path) => { - let path = match config_path.is_relative() { - true => { - match config_path.file_name().expect("expected file name") == config_path { - true => data_directory.join(PathBuf::from(config_path)), - false => match config_path.starts_with("./") || config_path.starts_with("../") { - true => current_dir().expect("expected current dir").join(config_path), - false => match config_path.starts_with("~") { - true => { - let home_dir_from_wrapper = dirs_wrapper - .home_dir() - .expect("expected users home_dir") - .to_str() - .expect("expected str home_dir") - .to_string(); - let replaced_tilde_dir = - config_path.display() - .to_string() - .replacen('~', home_dir_from_wrapper.as_str(), 1); - PathBuf::from(replaced_tilde_dir) - } - false => match data_directory_def { - true => data_directory.join(config_path), - false => panic!("You need to define data-directory to define config file with naked directory {}.", config_path.to_string_lossy()) - } - } - } + let config_file_pth_tilde: PathBuf = replace_tilde(&config_path, dirs_wrapper); + let config_file_pth_dot = replace_dots(&config_path, &mut panic); + let config_file_pth_relative = + replace_relative_path(&config_path, data_directory_def, data_directory, &mut panic); + let config_file_pth = match config_file_pth_tilde.is_empty() { + true => match config_file_pth_dot.is_empty() { + true => match config_file_pth_relative.is_empty() { + true => PathBuf::from("config.toml"), + false => config_file_pth_relative, + }, + false => { + panic = false; + config_file_pth_dot } + }, + false => { + panic = false; + config_file_pth_tilde } - false => config_path, }; - FieldPair::new(path, true) + if panic { + panic!( + "You need to define data-directory to define config file with naked directory {}.", + config_file_pth.to_string_lossy() + ); + } + FieldPair::new(config_file_pth, true) } None => { let path = data_directory.join(PathBuf::from("config.toml")); @@ -208,10 +255,11 @@ pub fn determine_user_specific_data( ) -> Result { let orientation_args: Box = merge( Box::new(EnvironmentVcl::new(app)), - Box::new(CommandLineVcl::new(args.to_vec())) + Box::new(CommandLineVcl::new(args.to_vec())), ); /* We create multiconfig to retrieve chain, real-user, data-directory and config file, to establish ConfigVcl */ - let first_multi_config = make_new_multi_config(app, vec![orientation_args]).expect("expected MultiConfig"); + let first_multi_config = + make_new_multi_config(app, vec![orientation_args]).expect("expected MultiConfig"); let initialization_data = config_file_data_dir_real_user_chain_from_mc(dirs_wrapper, first_multi_config); @@ -356,22 +404,24 @@ mod tests { .param("--config-file", "booga.toml"); let args_vec: Vec = args.into(); let app = determine_config_file_path_app(); - let user_specific_data = determine_user_specific_data( - &DirsWrapperReal {}, - &app, - args_vec.as_slice(), - ) - .unwrap(); + let user_specific_data = + determine_user_specific_data(&DirsWrapperReal {}, &app, args_vec.as_slice()).unwrap(); assert_eq!( &format!( "{}", - user_specific_data.config_file.item.parent().unwrap().display() + user_specific_data + .config_file + .item + .parent() + .unwrap() + .display() ), &user_specific_data - .data_directory.item - .to_string_lossy() - .to_string(), + .data_directory + .item + .to_string_lossy() + .to_string(), ); assert_eq!( "booga.toml", @@ -395,19 +445,21 @@ mod tests { ); std::env::set_var("MASQ_CONFIG_FILE", "booga.toml"); let app = determine_config_file_path_app(); - let user_specific_data = determine_user_specific_data( - &DirsWrapperReal {}, - &app, - args_vec.as_slice(), - ) - .unwrap(); + let user_specific_data = + determine_user_specific_data(&DirsWrapperReal {}, &app, args_vec.as_slice()).unwrap(); assert_eq!( format!( "{}", - user_specific_data.config_file.item.parent().unwrap().display() + user_specific_data + .config_file + .item + .parent() + .unwrap() + .display() ), user_specific_data - .data_directory.item + .data_directory + .item .to_string_lossy() .to_string(), ); @@ -415,10 +467,7 @@ mod tests { "booga.toml", user_specific_data.config_file.item.file_name().unwrap() ); - assert_eq!( - user_specific_data.config_file.user_specified, - true - ); + assert_eq!(user_specific_data.config_file.user_specified, true); assert_eq!(user_specific_data.real_user.user_specified, false); //all these assertions of 'real_user_specified' was incorrect, no idea how this tests could pass before } @@ -432,17 +481,14 @@ mod tests { let args_vec: Vec = args.into(); let app = determine_config_file_path_app(); - let user_specific_data = determine_user_specific_data( - &DirsWrapperReal {}, - &app, - args_vec.as_slice(), - ) - .unwrap(); + let user_specific_data = + determine_user_specific_data(&DirsWrapperReal {}, &app, args_vec.as_slice()).unwrap(); assert_eq!( "/tmp/booga.toml", &format!("{}", user_specific_data.config_file.item.display()) ); + assert_eq!(user_specific_data.config_file.user_specified, true); assert_eq!(user_specific_data.real_user.user_specified, false); } @@ -466,6 +512,7 @@ mod tests { r"\tmp\booga.toml", &format!("{}", user_specific_data.config_file.item.display()) ); + assert_eq!(user_specific_data.config_file.user_specified, true); assert_eq!(user_specific_data.real_user.user_specified, false); } diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 272d046e2..d40e5aa16 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -2,16 +2,16 @@ use crate::bootstrapper::BootstrapperConfig; use crate::node_configurator::{initialize_database, DirsWrapper, NodeConfigurator}; -use crate::node_configurator::{DirsWrapperReal, ConfigInitializationData}; +use crate::node_configurator::{ConfigInitializationData, DirsWrapperReal}; use masq_lib::crash_point::CrashPoint; use masq_lib::logger::Logger; -use masq_lib::multi_config::{ComputedVcl, MultiConfig, VirtualCommandLine}; +use masq_lib::multi_config::{MultiConfig, VirtualCommandLine}; use masq_lib::shared_schema::ConfiguratorError; use masq_lib::utils::NeighborhoodModeLight; use std::net::SocketAddr; use std::net::{IpAddr, Ipv4Addr}; -use clap::{App, value_t}; +use clap::{value_t, App}; use log::LevelFilter; use crate::apps::app_node; @@ -138,18 +138,16 @@ fn collect_externals_from_multi_config( fn extract_values_vcl_fill_multiconfig_vec( multi_config_vec: Vec>, initialization_data: ConfigInitializationData, - app: &App + app: &App, ) -> (Vec, Vec) { - let full_multi_config = make_new_multi_config(&app, multi_config_vec).expect("expected multi_config"); + let full_multi_config = + make_new_multi_config(app, multi_config_vec).expect("expected multi_config"); let config_file_specified = initialization_data.config_file.user_specified; let config_file_path = initialization_data.config_file.item; - let check_value_from_mc = - |value: Option, var: &str, spec: bool| { - match value { - Some(arg) => (arg, true), - None => (var.to_string(), spec), - } - }; + let check_value_from_mc = |value: Option, var: &str, spec: bool| match value { + Some(arg) => (arg, true), + None => (var.to_string(), spec), + }; let (cf_real_user, cf_real_user_specified) = check_value_from_mc( value_m!(full_multi_config, "real-user", String), initialization_data.real_user.item.to_string().as_str(), @@ -157,38 +155,38 @@ fn extract_values_vcl_fill_multiconfig_vec( ); let mut unspecified_vec: Vec = vec!["".to_string()]; let mut specified_vec: Vec = vec!["".to_string()]; + + let fill_the_box = + |key: &str, value: &str, contains: bool, vec: &mut Vec| match contains { + true => { + let index = vec + .iter() + .position(|r| r == key) + .expect("expected index of vcl name") + + 1; + vec[index] = value.to_string(); + } + false => { + vec.push(key.to_string()); + vec.push(value.to_string()); + } + }; let mut fill_specified_or_unspecified_box = - | key: &str, value: &str, specified: bool| match value.is_empty() { + |key: &str, value: &str, specified: bool| match value.is_empty() { true => (), false => match specified { - true => match specified_vec.contains(&key.to_string()) { - true => { - let index = specified_vec - .iter() - .position(|r| r == key) - .expect("expected index of vcl name") - + 1; - specified_vec[index] = value.to_string(); - } - false => { - specified_vec.push(key.to_string()); - specified_vec.push(value.to_string()); - } - }, - false => match unspecified_vec.contains(&key.to_string()) { - true => { - let index = unspecified_vec - .iter() - .position(|r| r == key) - .expect("expected index of vcl name") - + 1; - unspecified_vec[index] = value.to_string(); - } - false => { - unspecified_vec.push(key.to_string()); - unspecified_vec.push(value.to_string()); - } - }, + true => fill_the_box( + key, + value, + specified_vec.contains(&key.to_string()), + &mut specified_vec, + ), + false => fill_the_box( + key, + value, + unspecified_vec.contains(&key.to_string()), + &mut unspecified_vec, + ), }, }; fill_specified_or_unspecified_box( @@ -198,7 +196,11 @@ fn extract_values_vcl_fill_multiconfig_vec( ); fill_specified_or_unspecified_box( "--data-directory", - initialization_data.data_directory.item.to_string_lossy().as_ref(), + initialization_data + .data_directory + .item + .to_string_lossy() + .as_ref(), initialization_data.data_directory.user_specified, ); fill_specified_or_unspecified_box("--real-user", cf_real_user.as_str(), cf_real_user_specified); @@ -223,23 +225,21 @@ pub fn server_initializer_collected_params<'a>( let environment_vcl = EnvironmentVcl::new(&app); let commandline_vcl = CommandLineVcl::new(args.to_vec()); let (unspecified_vec, specified_vec) = extract_values_vcl_fill_multiconfig_vec( - vec![Box::new(config_file_vcl.clone()), - Box::new(EnvironmentVcl::new(&app)), - Box::new(CommandLineVcl::new(commandline_vcl.args()))], + vec![ + Box::new(config_file_vcl.clone()), + Box::new(EnvironmentVcl::new(&app)), + Box::new(CommandLineVcl::new(commandline_vcl.args())), + ], initialization_data, - &app + &app, ); let mut multi_config_args_vec: Vec> = vec![ Box::new(config_file_vcl), Box::new(environment_vcl), Box::new(commandline_vcl), ]; - if unspecified_vec.len() > 1 { - multi_config_args_vec.push(Box::new(ComputedVcl::new(unspecified_vec))); - } - if specified_vec.len() > 1 { - multi_config_args_vec.push(Box::new(CommandLineVcl::new(specified_vec))); - } + multi_config_args_vec.push(Box::new(CommandLineVcl::new(unspecified_vec))); + multi_config_args_vec.push(Box::new(CommandLineVcl::new(specified_vec))); let full_multi_config = make_new_multi_config(&app, multi_config_args_vec)?; //println!("full_multi_config: {:#?}", full_multi_config); @@ -387,6 +387,7 @@ mod tests { use masq_lib::blockchains::chains::Chain; use masq_lib::constants::DEFAULT_CHAIN; use masq_lib::multi_config::VirtualCommandLine; + use masq_lib::shared_schema::ParamError; use masq_lib::test_utils::environment_guard::{ClapGuard, EnvironmentGuard}; use masq_lib::test_utils::utils::{ensure_node_home_directory_exists, TEST_DEFAULT_CHAIN}; use masq_lib::utils::running_test; @@ -398,7 +399,6 @@ mod tests { use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use std::vec; - use masq_lib::shared_schema::ParamError; #[test] fn node_configurator_standard_unprivileged_uses_parse_args_configurator_dao_real() { @@ -550,7 +550,7 @@ mod tests { { let mut config_file = File::create(home_dir.join("config.toml")).unwrap(); config_file - .write_all(b"dns-servers = \"111.111.111.111,222.222.222.222\"\nreal-user = \"1004:1004:/home/gooba\"\n") + .write_all(b"dns-servers = \"111.111.111.111,222.222.222.222\"\n") .unwrap(); } let directory_wrapper = make_pre_populated_mocked_directory_wrapper(); @@ -719,10 +719,7 @@ mod tests { privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); - assert_eq!( - None, - value_m!(multi_config, "config-file", PathBuf) - ); + assert_eq!(None, value_m!(multi_config, "config-file", PathBuf)); assert_eq!( config.dns_servers, vec!(SocketAddr::from_str("1.1.1.1:53").unwrap()) @@ -776,10 +773,7 @@ mod tests { privileged_parse_args(&DirsWrapperReal {}, &multi_config, &mut config).unwrap(); - assert_eq!( - None, - value_m!(multi_config, "config-file", PathBuf) - ); + assert_eq!(None, value_m!(multi_config, "config-file", PathBuf)); assert_eq!( config.dns_servers, vec!(SocketAddr::from_str("1.1.1.1:53").unwrap()) @@ -821,9 +815,6 @@ mod tests { fn fill_up_config_file(mut config_file: File) { { - config_file - .write_all(b"real-user = \"1002:1002:/home/wooga\"\n") - .unwrap(); config_file .write_all(b"blockchain-service-url = \"https://www.mainnet2.com\"\n") .unwrap(); @@ -839,6 +830,7 @@ mod tests { .write_all(b"earning-wallet = \"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n") .unwrap(); config_file.write_all(b"gas-price = \"77\"\n").unwrap(); + config_file.write_all(b"ip = \"6.6.6.6\"\n").unwrap(); config_file.write_all(b"log-level = \"trace\"\n").unwrap(); config_file .write_all(b"mapping-protocol = \"pcp\"\n") @@ -847,33 +839,35 @@ mod tests { config_file .write_all(b"neighborhood-mode = \"zero-hop\"\n") .unwrap(); - config_file.write_all(b"scans = \"off\"\n").unwrap(); - config_file.write_all(b"rate-pack = \"2|2|2|2\"\n").unwrap(); config_file .write_all(b"payment-thresholds = \"3333|55|33|646|999|999\"\n") .unwrap(); + config_file.write_all(b"rate-pack = \"2|2|2|2\"\n").unwrap(); + config_file + .write_all(b"real-user = \"1002:1002:/home/wooga\"\n") + .unwrap(); config_file .write_all(b"scan-intervals = \"111|100|99\"\n") - .unwrap() + .unwrap(); + config_file.write_all(b"scans = \"off\"\n").unwrap(); } } #[test] - fn multi_config_vcl_is_computed_do_right_job() { + fn server_initializer_collected_params_handle_dot_config_file_path_and_reads_arguments_from_cf() + { running_test(); - let _env_guard = EnvironmentGuard::new(); + let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); let home_dir = ensure_node_home_directory_exists( "node_configurator_standard", - "multi_config_vcl_is_computed_do_right_job", + "server_initializer_collected_params_handle_dot_config_file_path_and_reads_arguments_from_cf", ); let data_dir = &home_dir.join("data_dir"); - let config_file_relative = File::create(PathBuf::from("./generated/test/node_configurator_standard/multi_config_vcl_is_computed_do_right_job").join("config.toml")).unwrap(); + let config_file_relative = File::create(PathBuf::from("./generated/test/node_configurator_standard/server_initializer_collected_params_handle_dot_config_file_path_and_reads_arguments_from_cf").join("config.toml")).unwrap(); fill_up_config_file(config_file_relative); let env_vec_array = vec![ - ("MASQ_CONFIG_FILE", "./generated/test/node_configurator_standard/multi_config_vcl_is_computed_do_right_job/config.toml"), - #[cfg(not(target_os = "windows"))] - ("MASQ_REAL_USER", "9999:9999:booga"), + ("MASQ_CONFIG_FILE", "./generated/test/node_configurator_standard/server_initializer_collected_params_handle_dot_config_file_path_and_reads_arguments_from_cf/config.toml"), ]; env_vec_array .clone() @@ -888,83 +882,32 @@ mod tests { let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let env_multiconfig = result.unwrap(); - #[cfg(not(target_os = "windows"))] - { - assert_eq!( - value_m!(env_multiconfig, "data-directory", String).unwrap(), - "booga/data_dir/MASQ/polygon-mainnet".to_string() - ); - assert_eq!(env_multiconfig.is_user_specified("--data-directory"), false); - assert_eq!(env_multiconfig.is_user_specified("--config-file"), true); - assert_eq!(env_multiconfig.is_user_specified("--real-user"), true); - assert_eq!( - value_m!(env_multiconfig, "config-file", String).unwrap(), - current_dir().expect("expected curerrnt dir") - .join(PathBuf::from( "./generated/test/node_configurator_standard/multi_config_vcl_is_computed_do_right_job/config.toml")) - .to_string_lossy().to_string() - ); - } - #[cfg(target_os = "windows")] + // assert_eq!(env_multiconfig.is_user_specified("--config-file"), true); assert_eq!( - value_m!(env_multiconfig, "data-directory", String).unwrap(), - "generated/test/node_configurator_standard/multi_config_vcl_is_computed_do_right_job/home\\data_dir\\MASQ\\polygon-mainnet".to_string() - ); - } - - #[test] - fn server_initializer_collected_params_handle_dot_in_path_and_real_user_in_config_file() { - running_test(); - let _guard = EnvironmentGuard::new(); - let _clap_guard = ClapGuard::new(); - let home_dir = ensure_node_home_directory_exists( - "node_configurator_standard", - "server_initializer_collected_params_handle_dot_in_path_and_real_user_in_config_file", + value_m!(env_multiconfig, "dns-servers", String).unwrap(), + "5.6.7.8".to_string() ); - let data_dir = &home_dir.join("data_dir"); - let config_file_relative = File::create(PathBuf::from("./generated/test/node_configurator_standard/server_initializer_collected_params_handle_dot_in_path_and_real_user_in_config_file").join("config.toml")).unwrap(); - fill_up_config_file(config_file_relative); - let env_vec_array = vec![ - ("MASQ_CONFIG_FILE", "./generated/test/node_configurator_standard/server_initializer_collected_params_handle_dot_in_path_and_real_user_in_config_file/config.toml"), - ]; - env_vec_array - .clone() - .into_iter() - .for_each(|(name, value)| std::env::set_var(name, value)); - let args = ArgsBuilder::new(); - let args_vec: Vec = args.into(); - let dir_wrapper = DirsWrapperMock::new() - .home_dir_result(Some(home_dir.clone())) - .data_dir_result(Some(data_dir.to_path_buf())); - - let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); - let env_multiconfig = result.unwrap(); - - assert_eq!(env_multiconfig.is_user_specified("--data-directory"), false); #[cfg(not(target_os = "windows"))] { - assert_eq!( - value_m!(env_multiconfig, "data-directory", String).unwrap(), - home_dir.clone().join("data_dir/MASQ/polygon-mainnet").to_string_lossy() - ); + // assert_eq!(env_multiconfig.is_user_specified("--real-user"), true); assert_eq!( value_m!(env_multiconfig, "real-user", String).unwrap(), "1002:1002:/home/wooga".to_string() ); assert_eq!( value_m!(env_multiconfig, "config-file", String).unwrap(), - current_dir().unwrap().join(PathBuf::from( "./generated/test/node_configurator_standard/server_initializer_collected_params_handle_dot_in_path_and_real_user_in_config_file/config.toml")).to_string_lossy().to_string() + current_dir().unwrap().join(PathBuf::from( "./generated/test/node_configurator_standard/server_initializer_collected_params_handle_dot_config_file_path_and_reads_arguments_from_cf/config.toml")).to_string_lossy().to_string() ); } #[cfg(target_os = "windows")] assert_eq!( value_m!(env_multiconfig, "data-directory", String).unwrap(), - "generated/test/node_configurator_standard/server_initializer_collected_params_handle_dot_in_path_and_real_user_in_config_file/home\\data_dir\\MASQ\\polygon-mainnet".to_string() + "generated/test/node_configurator_standard/server_initializer_collected_params_handle_dot_config_file_path_and_reads_arguments_from_cf/home\\data_dir\\MASQ\\polygon-mainnet".to_string() ); } #[test] - fn server_initializer_collected_params_handles_only_path_in_config_file_param() - { + fn server_initializer_collected_params_handles_only_path_in_config_file_param() { running_test(); let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); @@ -975,14 +918,21 @@ mod tests { let data_dir = &home_dir.join("data_dir"); let args = ArgsBuilder::new() - .param("--data-directory", home_dir.clone().display().to_string().as_str()) - .param("--config-file", home_dir.clone().display().to_string().as_str()); + .param( + "--data-directory", + home_dir.clone().display().to_string().as_str(), + ) + .param( + "--config-file", + home_dir.clone().display().to_string().as_str(), + ); let args_vec: Vec = args.into(); let dir_wrapper = DirsWrapperMock::new() .home_dir_result(Some(home_dir.clone())) .data_dir_result(Some(data_dir.to_path_buf())); - let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()).unwrap_err(); + let result = + server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()).unwrap_err(); let expected = ConfiguratorError::new(vec![ ParamError::new("config-file", "Couldn't open configuration file \"generated/test/node_configurator_standard/server_initializer_collected_params_handles_only_path_in_config_file_param/home/generated/test/node_configurator_standard/server_initializer_collected_params_handles_only_path_in_config_file_param/home\". Are you sure it exists?")]); assert_eq!(result, expected); @@ -1021,7 +971,7 @@ mod tests { let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let env_multiconfig = result.unwrap(); - assert_eq!(env_multiconfig.is_user_specified("--data-directory"), false); + // assert_eq!(env_multiconfig.is_user_specified("--data-directory"), false); #[cfg(not(target_os = "windows"))] { assert_eq!( @@ -1079,7 +1029,7 @@ mod tests { let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let env_multiconfig = result.unwrap(); - assert_eq!(env_multiconfig.is_user_specified("--data-directory"), false); + // assert_eq!(env_multiconfig.is_user_specified("--data-directory"), false); #[cfg(not(target_os = "windows"))] { assert_eq!( @@ -1118,8 +1068,7 @@ mod tests { let config_file_relative = File::create(data_dir.join("config.toml")).unwrap(); fill_up_config_file(config_file_relative); let env_vec_array = vec![ - ("MASQ_IP", "9.8.7.6"), - ("MASQ_BLOCKCHAIN-SERVICE-URL", "https://www.mainnet2.com"), + ("MASQ_BLOCKCHAIN_SERVICE_URL", "https://www.mainnet2.com"), #[cfg(not(target_os = "windows"))] ("MASQ_REAL_USER", "9999:9999:booga"), ]; @@ -1139,39 +1088,39 @@ mod tests { .param("--data-directory", "~/masqhome"); let args_vec: Vec = args.into(); let dir_wrapper = DirsWrapperMock::new() - .home_dir_result(Some(current_dir().expect("expect current directory").join("generated/test/node_configurator_standard/server_initializer_collected_params_handle_tilde_in_path_config_file_from_commandline_and_real_user_from_config_file/home"))) + .home_dir_result(Some(home_dir.to_path_buf())) .data_dir_result(Some(data_dir.to_path_buf())); - let result_data_dir = current_dir().expect("expect current directory").join("generated/test/node_configurator_standard/server_initializer_collected_params_handle_tilde_in_path_config_file_from_commandline_and_real_user_from_config_file/home/masqhome"); let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let multiconfig = result.unwrap(); - assert_eq!(multiconfig.is_user_specified("--data-directory"), true); + // assert_eq!(multiconfig.is_user_specified("--data-directory"), true); assert_eq!( value_m!(multiconfig, "data-directory", String).unwrap(), - result_data_dir.to_string_lossy().to_string() + data_dir.to_string_lossy().to_string() ); #[cfg(not(target_os = "windows"))] { - assert_eq!(multiconfig.is_user_specified("--real-user"), true); + // assert_eq!(multiconfig.is_user_specified("--real-user"), true); assert_eq!( value_m!(multiconfig, "real-user", String).unwrap(), "9999:9999:booga" ); } - assert_eq!(multiconfig.is_user_specified("--config-file"), true); + // assert_eq!(multiconfig.is_user_specified("--config-file"), true); assert_eq!( value_m!(multiconfig, "config-file", String).unwrap(), - result_data_dir + data_dir .join(PathBuf::from("config.toml")) .to_string_lossy() .to_string() ); - assert_eq!(value_m!(multiconfig, "ip", String).unwrap(), "9.8.7.6"); assert_eq!( value_m!(multiconfig, "blockchain-service-url", String).unwrap(), "https://www.mainnet1.com" ); + // finally we assert some value from config-file to proof we are reading it + assert_eq!(value_m!(multiconfig, "ip", String).unwrap(), "6.6.6.6"); } #[test] @@ -1204,11 +1153,12 @@ mod tests { let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let multiconfig = result.unwrap(); - assert_eq!(multiconfig.is_user_specified("--data-directory"), true); + // assert_eq!(multiconfig.is_user_specified("--data-directory"), true); assert_eq!( &value_m!(multiconfig, "data-directory", String).unwrap(), &home_dir.to_string_lossy().to_string() ); + assert_eq!(value_m!(multiconfig, "ip", String).unwrap(), "6.6.6.6"); #[cfg(not(target_os = "windows"))] assert_eq!( &value_m!(multiconfig, "real-user", String).unwrap(), @@ -1240,21 +1190,21 @@ mod tests { } #[test] - fn server_initializer_collected_params_combine_vlcs_properly() { + fn server_initializer_collected_params_combine_vcls_properly() { running_test(); let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); let home_dir = ensure_node_home_directory_exists( "node_configurator_standard", - "server_initializer_collected_params_combine_vlcs_properly", + "server_initializer_collected_params_combine_vcls_properly", ); let data_dir = &home_dir.join("data_dir"); - let config_file = File::create(&home_dir.join("config.toml")).unwrap(); + let config_file = File::create(&home_dir.join("booga.toml")).unwrap(); let current_directory = current_dir().unwrap(); fill_up_config_file(config_file); let env_vec_array = vec![ - ("MASQ_CONFIG_FILE", "config.toml"), + ("MASQ_CONFIG_FILE", "booga.toml"), ("MASQ_DATA_DIRECTORY", "/nonexistent/directory/home"), #[cfg(not(target_os = "windows"))] ("MASQ_REAL_USER", "9999:9999:booga"), @@ -1267,19 +1217,20 @@ mod tests { .home_dir_result(Some(home_dir.clone())) .data_dir_result(Some(data_dir.to_path_buf())); let args = ArgsBuilder::new() - .param("--data-directory", current_directory.join(Path::new("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home")).to_string_lossy().to_string().as_str()) + .param("--data-directory", current_directory.join(Path::new("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vcls_properly/home")).to_string_lossy().to_string().as_str()) .param("--real-user", "1001:1001:cooga"); let args_vec: Vec = args.into(); + let params = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); - let result = params.as_ref().expect("REASON"); - let multiconfig = result; + let multiconfig = params.as_ref().unwrap(); + #[cfg(not(target_os = "windows"))] { assert_eq!( value_m!(multiconfig, "config-file", String).unwrap(), - current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home/config.toml").to_string_lossy().to_string() + current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vcls_properly/home/booga.toml").to_string_lossy().to_string() ); - assert_eq!(multiconfig.is_user_specified("--real-user"), true); + // assert_eq!(multiconfig.is_user_specified("--real-user"), true); assert_eq!( value_m!(multiconfig, "real-user", String).unwrap(), "1001:1001:cooga".to_string() @@ -1288,9 +1239,9 @@ mod tests { #[cfg(target_os = "windows")] assert_eq!( value_m!(multiconfig, "config-file", String).unwrap(), - current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vlcs_properly/home\\config.toml").to_string_lossy().to_string() + current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vcls_properly/home\\booga.toml").to_string_lossy().to_string() ); - assert_eq!(multiconfig.is_user_specified("--data-directory"), true); + // assert_eq!(multiconfig.is_user_specified("--data-directory"), true); } #[test] @@ -1299,7 +1250,7 @@ mod tests { let home_dir = PathBuf::from("/unexisting_home/unexisting_alice"); let data_dir = home_dir.join("data_dir"); let args = ArgsBuilder::new() - .param("--config-file", "booga.toml") // nonexistent config file: should return error because user-specified + .param("--config-file", "/home/booga/booga.toml") // nonexistent config file: should return error because user-specified .param("--chain", "polygon-mainnet"); let args_vec: Vec = args.into(); let dir_wrapper = DirsWrapperMock::new() @@ -1433,7 +1384,7 @@ mod tests { } #[should_panic( - expected = "expected MultiConfig: ConfiguratorError { param_errors: [ParamError { parameter: \"gas-price\", reason: \"Invalid value: unleaded\" }] }" + expected = "expected MultiConfig: ConfiguratorError { param_errors: [ParamError { parameter: \"gas-price\", reason: \"Invalid value: unleaded\" }] }" )] #[test] fn server_initializer_collected_params_rejects_invalid_gas_price() { diff --git a/node/src/server_initializer.rs b/node/src/server_initializer.rs index dd46f76f2..c2f921c15 100644 --- a/node/src/server_initializer.rs +++ b/node/src/server_initializer.rs @@ -48,7 +48,11 @@ impl ServerInitializer for ServerInitializerReal { .as_mut() .initialize_as_privileged(&multi_config), ) - .combine_results(self.bootstrapper.as_mut().initialize_as_privileged(&multi_config)); + .combine_results( + self.bootstrapper + .as_mut() + .initialize_as_privileged(&multi_config), + ); self.privilege_dropper .chown(Path::new(data_directory.as_str()), &real_user); From 0c8a8665cfa402d1cdb7420447a8f99bb1696861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Mon, 4 Dec 2023 12:51:16 +0100 Subject: [PATCH 28/52] fixed the config_path reference bin replace_dots fn --- node/src/node_configurator/mod.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index 1d6651d75..5a21510a7 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -120,7 +120,7 @@ fn get_data_directory_from_mc( } } -fn replace_tilde(config_path: &PathBuf, dirs_wrapper: &dyn DirsWrapper) -> PathBuf { +fn replace_tilde(config_path: &Path, dirs_wrapper: &dyn DirsWrapper) -> PathBuf { let mut replaced_tilde_dir = "".to_string(); if config_path.starts_with("~") { let home_dir_from_wrapper = dirs_wrapper @@ -138,20 +138,19 @@ fn replace_tilde(config_path: &PathBuf, dirs_wrapper: &dyn DirsWrapper) -> PathB PathBuf::from(replaced_tilde_dir) } -fn replace_dots(config_path: &PathBuf, panic: &mut bool) -> PathBuf { - let origin_path = config_path.clone(); +fn replace_dots(config_path: &Path, panic: &mut bool) -> PathBuf { let mut new_path: PathBuf = Default::default(); - if config_path.starts_with("./") || origin_path.starts_with("../") { + if config_path.starts_with("./") || config_path.starts_with("../") { *panic = false; new_path = current_dir() .expect("expected current dir") - .join(origin_path.clone()); + .join(&(*config_path).to_owned()); }; new_path } fn replace_relative_path( - config_path: &PathBuf, + config_path: &Path, data_directory_def: bool, data_directory: &Path, panic: &mut bool, From 3a46f000617b686ca556d6ed4589d462507cfcfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Mon, 4 Dec 2023 13:03:37 +0100 Subject: [PATCH 29/52] solving dereferencing path in replace_dots fn --- node/src/node_configurator/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index 5a21510a7..f559149fd 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -144,7 +144,7 @@ fn replace_dots(config_path: &Path, panic: &mut bool) -> PathBuf { *panic = false; new_path = current_dir() .expect("expected current dir") - .join(&(*config_path).to_owned()); + .join(&(*config_path)); }; new_path } From 8205800b82c9f793228925651bd180fcc56b4a1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Mon, 4 Dec 2023 13:15:20 +0100 Subject: [PATCH 30/52] solving config_path in replace_dots fn --- node/src/node_configurator/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index f559149fd..e0f23dc18 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -144,7 +144,7 @@ fn replace_dots(config_path: &Path, panic: &mut bool) -> PathBuf { *panic = false; new_path = current_dir() .expect("expected current dir") - .join(&(*config_path)); + .join(config_path); }; new_path } From 151ab18f3289a1917e89efc5e0ea36819c83c85a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Mon, 4 Dec 2023 14:50:14 +0100 Subject: [PATCH 31/52] fixed get_config_file_from_mc fn - optimized for readability --- node/src/node_configurator/mod.rs | 97 +++++++++++++++++-------------- 1 file changed, 52 insertions(+), 45 deletions(-) diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index e0f23dc18..6a1776953 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -22,7 +22,6 @@ use masq_lib::multi_config::{ }; use masq_lib::shared_schema::ConfiguratorError; use masq_lib::utils::{add_masq_and_chain_directories, localhost}; -use nix::NixPath; use std::env::current_dir; use std::net::{SocketAddr, TcpListener}; use std::path::{Path, PathBuf}; @@ -120,33 +119,35 @@ fn get_data_directory_from_mc( } } -fn replace_tilde(config_path: &Path, dirs_wrapper: &dyn DirsWrapper) -> PathBuf { - let mut replaced_tilde_dir = "".to_string(); - if config_path.starts_with("~") { - let home_dir_from_wrapper = dirs_wrapper - .home_dir() - .expect("expected users home_dir") - .to_str() - .expect("expected str home_dir") - .to_string(); - replaced_tilde_dir = - config_path +fn replace_tilde(config_path: &Path, dirs_wrapper: &dyn DirsWrapper) -> Option { + match config_path.starts_with("~") { + true => { + let home_dir_from_wrapper = dirs_wrapper + .home_dir() + .expect("expected users home_dir") + .to_str() + .expect("expected str home_dir") + .to_string(); + Some(PathBuf::from(config_path .display() .to_string() - .replacen('~', home_dir_from_wrapper.as_str(), 1); - }; - PathBuf::from(replaced_tilde_dir) + .replacen('~', home_dir_from_wrapper.as_str(), 1))) + } + false => None + } + } -fn replace_dots(config_path: &Path, panic: &mut bool) -> PathBuf { - let mut new_path: PathBuf = Default::default(); - if config_path.starts_with("./") || config_path.starts_with("../") { - *panic = false; - new_path = current_dir() - .expect("expected current dir") - .join(config_path); - }; - new_path +fn replace_dots(config_path: &Path, panic: &mut bool) -> Option { + match config_path.starts_with("./") || config_path.starts_with("../") { + true => { + *panic = false; + Some(current_dir() + .expect("expected current dir") + .join(config_path)) + } + false => None + } } fn replace_relative_path( @@ -154,21 +155,20 @@ fn replace_relative_path( data_directory_def: bool, data_directory: &Path, panic: &mut bool, -) -> PathBuf { - let mut path = Default::default(); - if config_path.is_relative() { - match data_directory_def { +) -> Option { + match config_path.is_relative() { + true => match data_directory_def { true => { *panic = false; - path = data_directory.join(config_path); + Some(data_directory.join(config_path)) } false => { *panic = true; - path = config_path.to_path_buf(); + Some(config_path.to_path_buf()) } } - }; - path + false => None + } } fn get_config_file_from_mc( @@ -181,24 +181,31 @@ fn get_config_file_from_mc( let config_file = value_m!(multi_config, "config-file", PathBuf); match config_file { Some(config_path) => { - let config_file_pth_tilde: PathBuf = replace_tilde(&config_path, dirs_wrapper); + let config_file_pth_tilde = replace_tilde(&config_path, dirs_wrapper); let config_file_pth_dot = replace_dots(&config_path, &mut panic); let config_file_pth_relative = replace_relative_path(&config_path, data_directory_def, data_directory, &mut panic); - let config_file_pth = match config_file_pth_tilde.is_empty() { - true => match config_file_pth_dot.is_empty() { - true => match config_file_pth_relative.is_empty() { - true => PathBuf::from("config.toml"), - false => config_file_pth_relative, - }, - false => { + let config_file_pth = match config_file_pth_tilde { + Some(path) => { panic = false; - config_file_pth_dot - } + path }, - false => { - panic = false; - config_file_pth_tilde + None => match config_file_pth_dot { + Some(config_path) => { + panic = false; + config_path + } + None => { + match config_file_pth_relative { + Some(path_rel) => { + path_rel + } + None => { + panic = false; + PathBuf::from("config.toml") + } + } + } } }; if panic { From dd290ea3dd7cccfb54cf3088aa449a36449bd22b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Mon, 4 Dec 2023 14:56:12 +0100 Subject: [PATCH 32/52] formatting --- node/src/node_configurator/mod.rs | 50 +++++++++++++++---------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index 6a1776953..16d2c6230 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -128,25 +128,27 @@ fn replace_tilde(config_path: &Path, dirs_wrapper: &dyn DirsWrapper) -> Option

None + false => None, } - } fn replace_dots(config_path: &Path, panic: &mut bool) -> Option { match config_path.starts_with("./") || config_path.starts_with("../") { true => { *panic = false; - Some(current_dir() - .expect("expected current dir") - .join(config_path)) + Some( + current_dir() + .expect("expected current dir") + .join(config_path), + ) } - false => None + false => None, } } @@ -166,8 +168,8 @@ fn replace_relative_path( *panic = true; Some(config_path.to_path_buf()) } - } - false => None + }, + false => None, } } @@ -187,26 +189,22 @@ fn get_config_file_from_mc( replace_relative_path(&config_path, data_directory_def, data_directory, &mut panic); let config_file_pth = match config_file_pth_tilde { Some(path) => { - panic = false; - path - }, + panic = false; + path + } None => match config_file_pth_dot { Some(config_path) => { panic = false; config_path } - None => { - match config_file_pth_relative { - Some(path_rel) => { - path_rel - } - None => { - panic = false; - PathBuf::from("config.toml") - } + None => match config_file_pth_relative { + Some(path_rel) => path_rel, + None => { + panic = false; + PathBuf::from("config.toml") } - } - } + }, + }, }; if panic { panic!( From 08930b98ce995df0cefe4e37da705666e49f8546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Wed, 6 Dec 2023 12:56:59 +0100 Subject: [PATCH 33/52] removed println, and formated --- node/src/daemon/setup_reporter.rs | 1 - node/src/node_configurator/mod.rs | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/node/src/daemon/setup_reporter.rs b/node/src/daemon/setup_reporter.rs index effd4dc90..4816a8810 100644 --- a/node/src/daemon/setup_reporter.rs +++ b/node/src/daemon/setup_reporter.rs @@ -497,7 +497,6 @@ impl SetupReporterReal { }; let user_specific_data = determine_user_specific_data(dirs_wrapper, &app, &command_line)?; - println!("user_specific_data {:?}", &user_specific_data); let config_file_vcl = match ConfigFileVcl::new( &user_specific_data.config_file.item, user_specific_data.config_file.user_specified, diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index 16d2c6230..1b5f8716d 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -193,15 +193,15 @@ fn get_config_file_from_mc( path } None => match config_file_pth_dot { - Some(config_path) => { + Some(config_path_dot) => { panic = false; - config_path + config_path_dot } None => match config_file_pth_relative { Some(path_rel) => path_rel, None => { panic = false; - PathBuf::from("config.toml") + config_path } }, }, From 15e66e6aeefb6540baa734b1703416136b8e5e8c Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 8 Dec 2023 06:24:25 -0800 Subject: [PATCH 34/52] fixed windows tests --- masq_lib/src/multi_config.rs | 4 ++-- .../node_configurator_standard.rs | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/masq_lib/src/multi_config.rs b/masq_lib/src/multi_config.rs index dfc1c7184..f93b3c97c 100644 --- a/masq_lib/src/multi_config.rs +++ b/masq_lib/src/multi_config.rs @@ -389,8 +389,8 @@ impl Display for ConfigFileVclError { match self { ConfigFileVclError::OpenError(path, _) => write!( fmt, - "Couldn't open configuration file {:?}. Are you sure it exists?", - path + "Couldn't open configuration file \"{}\". Are you sure it exists?", + path.to_string_lossy() ), ConfigFileVclError::CorruptUtf8(path) => write!( fmt, diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index d40e5aa16..21c0d3ec8 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -933,7 +933,9 @@ mod tests { let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()).unwrap_err(); - let expected = ConfiguratorError::new(vec![ ParamError::new("config-file", "Couldn't open configuration file \"generated/test/node_configurator_standard/server_initializer_collected_params_handles_only_path_in_config_file_param/home/generated/test/node_configurator_standard/server_initializer_collected_params_handles_only_path_in_config_file_param/home\". Are you sure it exists?")]); + + let result_path = format!("Couldn't open configuration file \"{}\". Are you sure it exists?", home_dir.as_path().join(home_dir.as_path()).to_str().unwrap()); + let expected = ConfiguratorError::new(vec![ ParamError::new("config-file", result_path.as_str())]); assert_eq!(result, expected); } @@ -994,7 +996,7 @@ mod tests { #[cfg(target_os = "windows")] assert_eq!( value_m!(env_multiconfig, "data-directory", String).unwrap(), - "generated/test/node_configurator_standard/server_initializer_collected_params_rewrite_config_files_parameters_from_command_line/home\\data_dir\\MASQ\\polygon-mainnet".to_string() + "/home/booga\\data_dir\\MASQ\\polygon-mainnet".to_string() ); } @@ -1052,7 +1054,7 @@ mod tests { #[cfg(target_os = "windows")] assert_eq!( value_m!(env_multiconfig, "data-directory", String).unwrap(), - "generated/test/node_configurator_standard/server_initializer_collected_params_rewrite_config_files_parameters_from_environment/home\\data_dir\\MASQ\\polygon-mainnet".to_string() + "/home/booga\\data_dir\\MASQ\\polygon-mainnet".to_string() ); } @@ -1084,8 +1086,8 @@ mod tests { #[cfg(target_os = "windows")] let args = ArgsBuilder::new() .param("--blockchain-service-url", "https://www.mainnet1.com") - .param("--config-file", "~/masqhome\\config.toml") - .param("--data-directory", "~/masqhome"); + .param("--config-file", "~\\masqhome\\config.toml") + .param("--data-directory", "~\\masqhome"); let args_vec: Vec = args.into(); let dir_wrapper = DirsWrapperMock::new() .home_dir_result(Some(home_dir.to_path_buf())) @@ -1249,9 +1251,14 @@ mod tests { running_test(); let home_dir = PathBuf::from("/unexisting_home/unexisting_alice"); let data_dir = home_dir.join("data_dir"); + #[cfg(not(target_os = "windows"))] let args = ArgsBuilder::new() .param("--config-file", "/home/booga/booga.toml") // nonexistent config file: should return error because user-specified .param("--chain", "polygon-mainnet"); + #[cfg(target_os = "windows")] + let args = ArgsBuilder::new() + .param("--config-file", "C:\\home\\booga\\booga.toml") // nonexistent config file: should return error because user-specified + .param("--chain", "polygon-mainnet"); let args_vec: Vec = args.into(); let dir_wrapper = DirsWrapperMock::new() .home_dir_result(Some(home_dir)) From 1391842fc64025c0390ec475b237ef7f274a4139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Sun, 10 Dec 2023 12:06:36 +0100 Subject: [PATCH 35/52] formatting --- .../node_configurator/node_configurator_standard.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 21c0d3ec8..4f467213e 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -934,8 +934,16 @@ mod tests { let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()).unwrap_err(); - let result_path = format!("Couldn't open configuration file \"{}\". Are you sure it exists?", home_dir.as_path().join(home_dir.as_path()).to_str().unwrap()); - let expected = ConfiguratorError::new(vec![ ParamError::new("config-file", result_path.as_str())]); + let result_path = format!( + "Couldn't open configuration file \"{}\". Are you sure it exists?", + home_dir + .as_path() + .join(home_dir.as_path()) + .to_str() + .unwrap() + ); + let expected = + ConfiguratorError::new(vec![ParamError::new("config-file", result_path.as_str())]); assert_eq!(result, expected); } From 6973082a1f670f0221a74a59b12fc5cd2eb71196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Mon, 18 Dec 2023 11:46:56 +0100 Subject: [PATCH 36/52] workaround for actual_server_drop test --- .../tests/connection_termination_test.rs | 75 ++++++++++++++----- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/multinode_integration_tests/tests/connection_termination_test.rs b/multinode_integration_tests/tests/connection_termination_test.rs index de880e3d7..7855533cb 100644 --- a/multinode_integration_tests/tests/connection_termination_test.rs +++ b/multinode_integration_tests/tests/connection_termination_test.rs @@ -114,22 +114,28 @@ fn actual_server_drop() { let mut server = real_node.make_server(server_port); let masquerader = JsonMasquerader::new(); let (stream_key, return_route_id) = arbitrary_context(); - mock_node - .transmit_package( - mock_node.port_list()[0], - create_request_icp( - &mock_node, - &real_node, - stream_key, - return_route_id, - &server, - cluster.chain, - ), - &masquerader, - real_node.main_public_key(), - real_node.socket_addr(PortSelector::First), - ) - .unwrap(); + let index: u64 = 0; + request_server_payload( + index, + &cluster, + &real_node, + &mock_node, + &mut server, + &masquerader, + stream_key, + return_route_id, + ); + let index: u64 = 1; + request_server_payload( + index, + &cluster, + &real_node, + &mock_node, + &mut server, + &masquerader, + stream_key, + return_route_id, + ); server.wait_for_chunk(Duration::from_secs(2)).unwrap(); server.send_chunk(HTTP_RESPONSE); mock_node @@ -163,6 +169,40 @@ fn actual_server_drop() { assert!(payload.sequenced_packet.last_data); } +fn request_server_payload( + index: u64, + cluster: &MASQNodeCluster, + real_node: &MASQRealNode, + mock_node: &MASQMockNode, + server: &mut MASQNodeServer, + masquerader: &JsonMasquerader, + stream_key: StreamKey, + return_route_id: u32, +) { + mock_node + .transmit_package( + mock_node.port_list()[0], + create_request_icp( + index, + &mock_node, + &real_node, + stream_key, + return_route_id, + &server, + cluster.chain, + ), + masquerader, + real_node.main_public_key(), + real_node.socket_addr(PortSelector::First), + ) + .unwrap(); + server.wait_for_chunk(Duration::from_secs(2)).unwrap(); + server.send_chunk(HTTP_RESPONSE); + mock_node + .wait_for_package(masquerader, Duration::from_secs(2)) + .unwrap(); +} + #[test] // Given: Exit Node is real_node; originating Node is mock_node. // Given: A stream is established through the exit Node to a server. @@ -302,6 +342,7 @@ fn arbitrary_context() -> (StreamKey, u32) { } fn create_request_icp( + index: u64, originating_node: &MASQMockNode, exit_node: &MASQRealNode, stream_key: StreamKey, @@ -336,7 +377,7 @@ fn create_request_icp( &node_lib::sub_lib::migrations::client_request_payload::MIGRATIONS, &ClientRequestPayload_0v1 { stream_key, - sequenced_packet: SequencedPacket::new(Vec::from(HTTP_REQUEST), 0, false), + sequenced_packet: SequencedPacket::new(Vec::from(HTTP_REQUEST), index, false), target_hostname: Some(format!("{}", server.local_addr().ip())), target_port: server.local_addr().port(), protocol: ProxyProtocol::HTTP, From 9ad74134b7d48ff62d4b3d9aedbd494252bc19b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Mon, 18 Dec 2023 12:32:26 +0100 Subject: [PATCH 37/52] forgotten u64 for another test in connection_termination_test.rs --- .../tests/connection_termination_test.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/multinode_integration_tests/tests/connection_termination_test.rs b/multinode_integration_tests/tests/connection_termination_test.rs index 7855533cb..0c6354086 100644 --- a/multinode_integration_tests/tests/connection_termination_test.rs +++ b/multinode_integration_tests/tests/connection_termination_test.rs @@ -216,10 +216,12 @@ fn reported_client_drop() { let mut server = real_node.make_server(server_port); let masquerader = JsonMasquerader::new(); let (stream_key, return_route_id) = arbitrary_context(); + let index: u64 = 0; mock_node .transmit_package( mock_node.port_list()[0], create_request_icp( + index, &mock_node, &real_node, stream_key, From ccdb0adb01daf0020f7da8651286ce480167bad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Mon, 18 Dec 2023 22:48:54 +0100 Subject: [PATCH 38/52] add one more request to actual_serve_drop --- .../tests/connection_termination_test.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/multinode_integration_tests/tests/connection_termination_test.rs b/multinode_integration_tests/tests/connection_termination_test.rs index 0c6354086..fee778121 100644 --- a/multinode_integration_tests/tests/connection_termination_test.rs +++ b/multinode_integration_tests/tests/connection_termination_test.rs @@ -136,6 +136,17 @@ fn actual_server_drop() { stream_key, return_route_id, ); + let index: u64 = 2; + request_server_payload( + index, + &cluster, + &real_node, + &mock_node, + &mut server, + &masquerader, + stream_key, + return_route_id, + ); server.wait_for_chunk(Duration::from_secs(2)).unwrap(); server.send_chunk(HTTP_RESPONSE); mock_node From d50c740c59889f2a20b05469da5c7d0378aba538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Tue, 19 Dec 2023 14:06:14 +0100 Subject: [PATCH 39/52] remove forgotten piece of code --- .../tests/connection_termination_test.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/multinode_integration_tests/tests/connection_termination_test.rs b/multinode_integration_tests/tests/connection_termination_test.rs index fee778121..1466e2e51 100644 --- a/multinode_integration_tests/tests/connection_termination_test.rs +++ b/multinode_integration_tests/tests/connection_termination_test.rs @@ -136,22 +136,6 @@ fn actual_server_drop() { stream_key, return_route_id, ); - let index: u64 = 2; - request_server_payload( - index, - &cluster, - &real_node, - &mock_node, - &mut server, - &masquerader, - stream_key, - return_route_id, - ); - server.wait_for_chunk(Duration::from_secs(2)).unwrap(); - server.send_chunk(HTTP_RESPONSE); - mock_node - .wait_for_package(&masquerader, Duration::from_secs(2)) - .unwrap(); server.shutdown(); From 2606ebd511db69e367c8e371c1be511f8907f176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Thu, 4 Jan 2024 15:23:37 +0100 Subject: [PATCH 40/52] fixing comments from review2 --- masq_lib/src/multi_config.rs | 1 - masq_lib/src/test_utils/utils.rs | 9 +- node/src/node_configurator/mod.rs | 93 ++++------ .../node_configurator_standard.rs | 166 ++++++++---------- node/tests/utils.rs | 2 + 5 files changed, 117 insertions(+), 154 deletions(-) diff --git a/masq_lib/src/multi_config.rs b/masq_lib/src/multi_config.rs index f93b3c97c..23a62713e 100644 --- a/masq_lib/src/multi_config.rs +++ b/masq_lib/src/multi_config.rs @@ -513,7 +513,6 @@ impl<'a> MultiConfig<'a> { #[cfg(test)] pub mod tests { use super::*; - // use crate::shared_schema::official_chain_names; use crate::test_utils::environment_guard::EnvironmentGuard; use crate::test_utils::utils::ensure_node_home_directory_exists; use clap::Arg; diff --git a/masq_lib/src/test_utils/utils.rs b/masq_lib/src/test_utils/utils.rs index ddc32e7bc..87a0f8405 100644 --- a/masq_lib/src/test_utils/utils.rs +++ b/masq_lib/src/test_utils/utils.rs @@ -2,6 +2,7 @@ use crate::blockchains::chains::Chain; use crate::test_utils::environment_guard::EnvironmentGuard; +use std::env::current_dir; use std::fs; use std::path::{Path, PathBuf}; use std::time::Duration; @@ -12,7 +13,13 @@ pub const BASE_TEST_DIR: &str = "generated/test"; const MASQ_SOURCE_CODE_UNAVAILABLE: &str = "MASQ_SOURCE_CODE_UNAVAILABLE"; pub fn node_home_directory(module: &str, name: &str) -> PathBuf { - let home_dir_string = format!("{}/{}/{}/home", BASE_TEST_DIR, module, name); + let home_dir_string = format!( + "{}/{}/{}/{}/home", + current_dir().expect("expected current dir").display(), + BASE_TEST_DIR, + module, + name + ); PathBuf::from(home_dir_string.as_str()) } diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index 1b5f8716d..a116debeb 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -65,8 +65,8 @@ fn get_real_user_from_mc( multi_config: &MultiConfig, dirs_wrapper: &dyn DirsWrapper, ) -> FieldPair { - let rel_user = value_m!(multi_config, "real-user", RealUser); - match rel_user { + let real_user = value_m!(multi_config, "real-user", RealUser); + match real_user { Some(user) => FieldPair::new(user, true), None => { #[cfg(target_os = "windows")] @@ -119,57 +119,49 @@ fn get_data_directory_from_mc( } } -fn replace_tilde(config_path: &Path, dirs_wrapper: &dyn DirsWrapper) -> Option { +fn replace_tilde<'a>(config_path: PathBuf, dirs_wrapper: &'a dyn DirsWrapper) -> PathBuf { match config_path.starts_with("~") { true => { - let home_dir_from_wrapper = dirs_wrapper - .home_dir() - .expect("expected users home_dir") - .to_str() - .expect("expected str home_dir") - .to_string(); - Some(PathBuf::from(config_path.display().to_string().replacen( - '~', - home_dir_from_wrapper.as_str(), - 1, - ))) + let home_dir_from_wrapper = dirs_wrapper.home_dir(); + PathBuf::from( + config_path.display().to_string().replacen( + '~', + home_dir_from_wrapper + .expect("expected users home_dir") + .to_str() + .expect("expected str home_dir"), + 1, + ), + ) } - false => None, + false => config_path, } } -fn replace_dots(config_path: &Path, panic: &mut bool) -> Option { +fn replace_dots(config_path: PathBuf) -> PathBuf { match config_path.starts_with("./") || config_path.starts_with("../") { - true => { - *panic = false; - Some( - current_dir() - .expect("expected current dir") - .join(config_path), - ) - } - false => None, + true => current_dir() + .expect("expected current dir") + .join(config_path), + false => config_path, } } -fn replace_relative_path( - config_path: &Path, +fn replace_relative_path<'a>( + config_path: PathBuf, data_directory_def: bool, data_directory: &Path, panic: &mut bool, -) -> Option { +) -> PathBuf { match config_path.is_relative() { true => match data_directory_def { - true => { - *panic = false; - Some(data_directory.join(config_path)) - } + true => data_directory.join(config_path), false => { *panic = true; - Some(config_path.to_path_buf()) + config_path } }, - false => None, + false => config_path, } } @@ -183,36 +175,17 @@ fn get_config_file_from_mc( let config_file = value_m!(multi_config, "config-file", PathBuf); match config_file { Some(config_path) => { - let config_file_pth_tilde = replace_tilde(&config_path, dirs_wrapper); - let config_file_pth_dot = replace_dots(&config_path, &mut panic); - let config_file_pth_relative = - replace_relative_path(&config_path, data_directory_def, data_directory, &mut panic); - let config_file_pth = match config_file_pth_tilde { - Some(path) => { - panic = false; - path - } - None => match config_file_pth_dot { - Some(config_path_dot) => { - panic = false; - config_path_dot - } - None => match config_file_pth_relative { - Some(path_rel) => path_rel, - None => { - panic = false; - config_path - } - }, - }, - }; + let config_path = replace_tilde(config_path, dirs_wrapper); + let config_path = replace_dots(config_path); + let config_path = + replace_relative_path(config_path, data_directory_def, data_directory, &mut panic); if panic { panic!( - "You need to define data-directory to define config file with naked directory {}.", - config_file_pth.to_string_lossy() + "If the config file is given with a naked relative path ({}), the data directory must be given to serve as the root for the config-file path.", + config_path.to_string_lossy() ); } - FieldPair::new(config_file_pth, true) + FieldPair::new(config_path.to_path_buf(), true) } None => { let path = data_directory.join(PathBuf::from("config.toml")); diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 4f467213e..a7aa199d5 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -1,7 +1,7 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::bootstrapper::BootstrapperConfig; -use crate::node_configurator::{initialize_database, DirsWrapper, NodeConfigurator}; +use crate::node_configurator::{initialize_database, DirsWrapper, FieldPair, NodeConfigurator}; use crate::node_configurator::{ConfigInitializationData, DirsWrapperReal}; use masq_lib::crash_point::CrashPoint; use masq_lib::logger::Logger; @@ -11,7 +11,7 @@ use masq_lib::utils::NeighborhoodModeLight; use std::net::SocketAddr; use std::net::{IpAddr, Ipv4Addr}; -use clap::{value_t, App}; +use clap::value_t; use log::LevelFilter; use crate::apps::app_node; @@ -136,28 +136,31 @@ fn collect_externals_from_multi_config( } fn extract_values_vcl_fill_multiconfig_vec( - multi_config_vec: Vec>, + full_multi_config: MultiConfig, initialization_data: ConfigInitializationData, - app: &App, -) -> (Vec, Vec) { - let full_multi_config = - make_new_multi_config(app, multi_config_vec).expect("expected multi_config"); - let config_file_specified = initialization_data.config_file.user_specified; +) -> Vec { let config_file_path = initialization_data.config_file.item; - let check_value_from_mc = |value: Option, var: &str, spec: bool| match value { - Some(arg) => (arg, true), - None => (var.to_string(), spec), - }; - let (cf_real_user, cf_real_user_specified) = check_value_from_mc( + let check_value_from_mc = + |multi_config_value: Option, + initialization_data_val: &str, + initialization_data_spec: bool| match multi_config_value { + Some(arg) => FieldPair { + item: arg, + user_specified: true, + }, + None => FieldPair { + item: initialization_data_val.to_string(), + user_specified: initialization_data_spec, + }, + }; + let cf_real_user = check_value_from_mc( value_m!(full_multi_config, "real-user", String), initialization_data.real_user.item.to_string().as_str(), initialization_data.real_user.user_specified, ); - let mut unspecified_vec: Vec = vec!["".to_string()]; let mut specified_vec: Vec = vec!["".to_string()]; - let fill_the_box = - |key: &str, value: &str, contains: bool, vec: &mut Vec| match contains { + |key: &str, value: &str, vec: &mut Vec| match vec.contains(&key.to_string()) { true => { let index = vec .iter() @@ -171,41 +174,27 @@ fn extract_values_vcl_fill_multiconfig_vec( vec.push(value.to_string()); } }; - let mut fill_specified_or_unspecified_box = - |key: &str, value: &str, specified: bool| match value.is_empty() { - true => (), - false => match specified { - true => fill_the_box( - key, - value, - specified_vec.contains(&key.to_string()), - &mut specified_vec, - ), - false => fill_the_box( - key, - value, - unspecified_vec.contains(&key.to_string()), - &mut unspecified_vec, - ), - }, - }; - fill_specified_or_unspecified_box( + fill_the_box( "--config-file", config_file_path.as_path().to_string_lossy().as_ref(), - config_file_specified, + &mut specified_vec, ); - fill_specified_or_unspecified_box( + fill_the_box( "--data-directory", initialization_data .data_directory .item .to_string_lossy() .as_ref(), - initialization_data.data_directory.user_specified, + &mut specified_vec, + ); + fill_the_box( + "--real-user", + cf_real_user.item.as_str(), + &mut specified_vec, ); - fill_specified_or_unspecified_box("--real-user", cf_real_user.as_str(), cf_real_user_specified); - (unspecified_vec, specified_vec) + specified_vec } pub fn server_initializer_collected_params<'a>( @@ -224,25 +213,28 @@ pub fn server_initializer_collected_params<'a>( let environment_vcl = EnvironmentVcl::new(&app); let commandline_vcl = CommandLineVcl::new(args.to_vec()); - let (unspecified_vec, specified_vec) = extract_values_vcl_fill_multiconfig_vec( + let multiconfig_for_values_extraction = make_new_multi_config( + &app, vec![ Box::new(config_file_vcl.clone()), Box::new(EnvironmentVcl::new(&app)), Box::new(CommandLineVcl::new(commandline_vcl.args())), ], + ) + .expect("expexted MultiConfig"); + let specified_vec = extract_values_vcl_fill_multiconfig_vec( + multiconfig_for_values_extraction, initialization_data, - &app, ); let mut multi_config_args_vec: Vec> = vec![ Box::new(config_file_vcl), Box::new(environment_vcl), Box::new(commandline_vcl), ]; - multi_config_args_vec.push(Box::new(CommandLineVcl::new(unspecified_vec))); multi_config_args_vec.push(Box::new(CommandLineVcl::new(specified_vec))); let full_multi_config = make_new_multi_config(&app, multi_config_args_vec)?; - //println!("full_multi_config: {:#?}", full_multi_config); + Ok(full_multi_config) } @@ -882,14 +874,12 @@ mod tests { let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let env_multiconfig = result.unwrap(); - // assert_eq!(env_multiconfig.is_user_specified("--config-file"), true); assert_eq!( value_m!(env_multiconfig, "dns-servers", String).unwrap(), "5.6.7.8".to_string() ); #[cfg(not(target_os = "windows"))] { - // assert_eq!(env_multiconfig.is_user_specified("--real-user"), true); assert_eq!( value_m!(env_multiconfig, "real-user", String).unwrap(), "1002:1002:/home/wooga".to_string() @@ -935,8 +925,9 @@ mod tests { server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()).unwrap_err(); let result_path = format!( - "Couldn't open configuration file \"{}\". Are you sure it exists?", - home_dir + "The permissions on configuration file \"{}\" make it unreadable.", + current_dir() + .expect("expected current dir") .as_path() .join(home_dir.as_path()) .to_str() @@ -958,11 +949,9 @@ mod tests { "server_initializer_collected_params_rewrite_config_files_parameters_from_command_line", ); let data_dir = &home_dir.join("data_dir"); - let config_file_relative = File::create(PathBuf::from("./generated/test/node_configurator_standard/server_initializer_collected_params_rewrite_config_files_parameters_from_command_line").join("config.toml")).unwrap(); + let config_file_relative = File::create(home_dir.join("config.toml")).unwrap(); fill_up_config_file(config_file_relative); - let env_vec_array = vec![ - ("MASQ_CONFIG_FILE", "./generated/test/node_configurator_standard/server_initializer_collected_params_rewrite_config_files_parameters_from_command_line/config.toml"), - ]; + let env_vec_array = vec![("MASQ_CONFIG_FILE", home_dir.join("config.toml"))]; env_vec_array .clone() .into_iter() @@ -981,22 +970,19 @@ mod tests { let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let env_multiconfig = result.unwrap(); - // assert_eq!(env_multiconfig.is_user_specified("--data-directory"), false); #[cfg(not(target_os = "windows"))] - { - assert_eq!( - value_m!(env_multiconfig, "blockchain-service-url", String).unwrap(), - "https://www.mainnet0.com".to_string() - ); - assert_eq!( - value_m!(env_multiconfig, "real-user", String).unwrap(), - "9999:9999:/home/booga".to_string() - ); - assert_eq!( - value_m!(env_multiconfig, "config-file", String).unwrap(), - current_dir().unwrap().join(PathBuf::from( "./generated/test/node_configurator_standard/server_initializer_collected_params_rewrite_config_files_parameters_from_command_line/config.toml")).to_string_lossy().to_string() - ); - } + assert_eq!( + value_m!(env_multiconfig, "real-user", String).unwrap(), + "9999:9999:/home/booga".to_string() + ); + assert_eq!( + value_m!(env_multiconfig, "config-file", String).unwrap(), + home_dir.join("config.toml").display().to_string() + ); + assert_eq!( + value_m!(env_multiconfig, "blockchain-service-url", String).unwrap(), + "https://www.mainnet0.com".to_string() + ); assert_eq!( value_m!(env_multiconfig, "ip", String).unwrap(), "8.5.7.6".to_string() @@ -1018,16 +1004,21 @@ mod tests { "server_initializer_collected_params_rewrite_config_files_parameters_from_environment", ); let data_dir = &home_dir.join("data_dir"); - let config_file_relative = File::create(PathBuf::from("./generated/test/node_configurator_standard/server_initializer_collected_params_rewrite_config_files_parameters_from_environment").join("config.toml")).unwrap(); + let config_file_relative = File::create(home_dir.join("config.toml")).unwrap(); fill_up_config_file(config_file_relative); let env_vec_array = vec![ - ("MASQ_CONFIG_FILE", "./generated/test/node_configurator_standard/server_initializer_collected_params_rewrite_config_files_parameters_from_environment/config.toml"), - ("MASQ_BLOCKCHAIN_SERVICE_URL", "https://www.mainnet0.com"), - ("MASQ_REAL_USER", "9999:9999:/home/booga"), - ("MASQ_IP", "8.5.7.6"), + ( + "MASQ_CONFIG_FILE", + home_dir.clone().join("config.toml").display().to_string(), + ), + ( + "MASQ_BLOCKCHAIN_SERVICE_URL", + "https://www.mainnet0.com".to_string(), + ), + ("MASQ_REAL_USER", "9999:9999:/home/booga".to_string()), + ("MASQ_IP", "8.5.7.6".to_string()), ]; env_vec_array - .clone() .into_iter() .for_each(|(name, value)| std::env::set_var(name, value)); let args = ArgsBuilder::new(); @@ -1039,18 +1030,15 @@ mod tests { let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let env_multiconfig = result.unwrap(); - // assert_eq!(env_multiconfig.is_user_specified("--data-directory"), false); #[cfg(not(target_os = "windows"))] - { - assert_eq!( - value_m!(env_multiconfig, "real-user", String).unwrap(), - "9999:9999:/home/booga".to_string() - ); - assert_eq!( - value_m!(env_multiconfig, "config-file", String).unwrap(), - current_dir().unwrap().join(PathBuf::from( "./generated/test/node_configurator_standard/server_initializer_collected_params_rewrite_config_files_parameters_from_environment/config.toml")).to_string_lossy().to_string() - ); - } + assert_eq!( + value_m!(env_multiconfig, "real-user", String).unwrap(), + "9999:9999:/home/booga".to_string() + ); + assert_eq!( + value_m!(env_multiconfig, "config-file", String).unwrap(), + home_dir.join("config.toml").display().to_string() + ); assert_eq!( value_m!(env_multiconfig, "blockchain-service-url", String).unwrap(), "https://www.mainnet0.com".to_string() @@ -1104,20 +1092,17 @@ mod tests { let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let multiconfig = result.unwrap(); - // assert_eq!(multiconfig.is_user_specified("--data-directory"), true); assert_eq!( value_m!(multiconfig, "data-directory", String).unwrap(), data_dir.to_string_lossy().to_string() ); #[cfg(not(target_os = "windows"))] { - // assert_eq!(multiconfig.is_user_specified("--real-user"), true); assert_eq!( value_m!(multiconfig, "real-user", String).unwrap(), "9999:9999:booga" ); } - // assert_eq!(multiconfig.is_user_specified("--config-file"), true); assert_eq!( value_m!(multiconfig, "config-file", String).unwrap(), data_dir @@ -1163,7 +1148,6 @@ mod tests { let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let multiconfig = result.unwrap(); - // assert_eq!(multiconfig.is_user_specified("--data-directory"), true); assert_eq!( &value_m!(multiconfig, "data-directory", String).unwrap(), &home_dir.to_string_lossy().to_string() @@ -1178,13 +1162,13 @@ mod tests { #[test] #[should_panic( - expected = "You need to define data-directory to define config file with naked directory config/config.toml." + expected = "If the config file is given with a naked relative path (config/config.toml), the data directory must be given to serve as the root for the config-file path." )] fn server_initializer_collected_params_fails_on_naked_dir_config_file_without_data_directory() { running_test(); let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); - let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_handle_config_file_from_environment_and_real_user_from_config_file_with_path_started_by_dot"); + let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_fails_on_naked_dir_config_file_without_data_directory"); let data_dir = &home_dir.join("data_dir"); vec![("MASQ_CONFIG_FILE", "config/config.toml")] @@ -1240,7 +1224,6 @@ mod tests { value_m!(multiconfig, "config-file", String).unwrap(), current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vcls_properly/home/booga.toml").to_string_lossy().to_string() ); - // assert_eq!(multiconfig.is_user_specified("--real-user"), true); assert_eq!( value_m!(multiconfig, "real-user", String).unwrap(), "1001:1001:cooga".to_string() @@ -1251,7 +1234,6 @@ mod tests { value_m!(multiconfig, "config-file", String).unwrap(), current_directory.join("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vcls_properly/home\\booga.toml").to_string_lossy().to_string() ); - // assert_eq!(multiconfig.is_user_specified("--data-directory"), true); } #[test] diff --git a/node/tests/utils.rs b/node/tests/utils.rs index b60017063..9841dc918 100644 --- a/node/tests/utils.rs +++ b/node/tests/utils.rs @@ -273,6 +273,8 @@ impl MASQNode { #[allow(dead_code)] pub fn wait_for_exit(&mut self) -> Option { + // TODO Put the body of this function in a background thread and wait on the thread for a few + // seconds. If the thread doesn't terminate, leak the thread and return None. let child_opt = self.child.take(); let output_opt = self.output.take(); match (child_opt, output_opt) { From 553ac0b427ee75d93c71a054645191446f95cf3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Thu, 4 Jan 2024 16:22:08 +0100 Subject: [PATCH 41/52] remove redundant lifetime, and remove convertion to PathBuf --- node/src/node_configurator/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index a116debeb..ce002c7b6 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -119,7 +119,7 @@ fn get_data_directory_from_mc( } } -fn replace_tilde<'a>(config_path: PathBuf, dirs_wrapper: &'a dyn DirsWrapper) -> PathBuf { +fn replace_tilde(config_path: PathBuf, dirs_wrapper: &dyn DirsWrapper) -> PathBuf { match config_path.starts_with("~") { true => { let home_dir_from_wrapper = dirs_wrapper.home_dir(); @@ -147,7 +147,7 @@ fn replace_dots(config_path: PathBuf) -> PathBuf { } } -fn replace_relative_path<'a>( +fn replace_relative_path( config_path: PathBuf, data_directory_def: bool, data_directory: &Path, @@ -185,7 +185,7 @@ fn get_config_file_from_mc( config_path.to_string_lossy() ); } - FieldPair::new(config_path.to_path_buf(), true) + FieldPair::new(config_path, true) } None => { let path = data_directory.join(PathBuf::from("config.toml")); From a3bce207a0b3b904d4a79a3988aee049b7aa2523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Fri, 5 Jan 2024 13:52:28 +0100 Subject: [PATCH 42/52] fixing absolute path for tests of handle config-file and data directory relative --- masq_lib/src/test_utils/utils.rs | 9 +-------- node/src/accountant/database_access_objects/dao_utils.rs | 1 + node/src/accountant/mod.rs | 2 +- node/src/node_configurator/node_configurator_standard.rs | 6 +++++- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/masq_lib/src/test_utils/utils.rs b/masq_lib/src/test_utils/utils.rs index 87a0f8405..ddc32e7bc 100644 --- a/masq_lib/src/test_utils/utils.rs +++ b/masq_lib/src/test_utils/utils.rs @@ -2,7 +2,6 @@ use crate::blockchains::chains::Chain; use crate::test_utils::environment_guard::EnvironmentGuard; -use std::env::current_dir; use std::fs; use std::path::{Path, PathBuf}; use std::time::Duration; @@ -13,13 +12,7 @@ pub const BASE_TEST_DIR: &str = "generated/test"; const MASQ_SOURCE_CODE_UNAVAILABLE: &str = "MASQ_SOURCE_CODE_UNAVAILABLE"; pub fn node_home_directory(module: &str, name: &str) -> PathBuf { - let home_dir_string = format!( - "{}/{}/{}/{}/home", - current_dir().expect("expected current dir").display(), - BASE_TEST_DIR, - module, - name - ); + let home_dir_string = format!("{}/{}/{}/home", BASE_TEST_DIR, module, name); PathBuf::from(home_dir_string.as_str()) } diff --git a/node/src/accountant/database_access_objects/dao_utils.rs b/node/src/accountant/database_access_objects/dao_utils.rs index 39671afe9..99bd572d0 100644 --- a/node/src/accountant/database_access_objects/dao_utils.rs +++ b/node/src/accountant/database_access_objects/dao_utils.rs @@ -17,6 +17,7 @@ use rusqlite::{Row, Statement, ToSql}; use std::fmt::{Debug, Display}; use std::iter::FlatMap; use std::path::{Path, PathBuf}; +use std::string::ToString; use std::time::Duration; use std::time::SystemTime; diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index 444f497c7..aa1f920f9 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -4297,7 +4297,7 @@ mod tests { let factory = Accountant::dao_factory(data_dir); factory.make(); }; - + println!("make_dao_factory_uses_panic_on_migration {:?}", &data_dir); assert_on_initialization_with_panic_on_migration(&data_dir, &act); } } diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index a7aa199d5..89d817012 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -386,7 +386,7 @@ mod tests { use rustc_hex::FromHex; use std::convert::TryFrom; use std::env::current_dir; - use std::fs::{create_dir_all, File}; + use std::fs::{canonicalize, create_dir_all, File}; use std::io::Write; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; @@ -905,6 +905,7 @@ mod tests { "node_configurator_standard", "server_initializer_collected_params_handles_only_path_in_config_file_param", ); + let home_dir = canonicalize(home_dir).unwrap(); let data_dir = &home_dir.join("data_dir"); let args = ArgsBuilder::new() @@ -948,6 +949,7 @@ mod tests { "node_configurator_standard", "server_initializer_collected_params_rewrite_config_files_parameters_from_command_line", ); + let home_dir = canonicalize(home_dir).unwrap(); let data_dir = &home_dir.join("data_dir"); let config_file_relative = File::create(home_dir.join("config.toml")).unwrap(); fill_up_config_file(config_file_relative); @@ -1003,6 +1005,7 @@ mod tests { "node_configurator_standard", "server_initializer_collected_params_rewrite_config_files_parameters_from_environment", ); + let home_dir = canonicalize(home_dir).unwrap(); let data_dir = &home_dir.join("data_dir"); let config_file_relative = File::create(home_dir.join("config.toml")).unwrap(); fill_up_config_file(config_file_relative); @@ -1061,6 +1064,7 @@ mod tests { let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_handle_tilde_in_path_config_file_from_commandline_and_real_user_from_config_file"); + let home_dir = canonicalize(home_dir).unwrap(); let data_dir = &home_dir.join("masqhome"); let _create_data_dir = create_dir_all(data_dir); let config_file_relative = File::create(data_dir.join("config.toml")).unwrap(); From e7fdf8e4e3ed50587893af7845370ba288dabb30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Fri, 5 Jan 2024 14:05:09 +0100 Subject: [PATCH 43/52] run the actions From e5b8a6e3f306ff680f40bd7d45c580c9981c3c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Fri, 5 Jan 2024 14:19:32 +0100 Subject: [PATCH 44/52] merge master into GH-731 --- .gitignore | 1 + .../pcp_pmp_common/macos_specific.rs | 10 +- masq/src/command_context.rs | 25 +- masq/src/command_processor.rs | 8 +- masq/src/commands/change_password_command.rs | 6 +- masq/src/commands/check_password_command.rs | 9 +- masq/src/commands/configuration_command.rs | 21 +- .../src/commands/connection_status_command.rs | 6 +- .../financials_command/pretty_print_utils.rs | 3 +- masq/src/commands/generate_wallets_command.rs | 15 +- masq/src/commands/recover_wallets_command.rs | 13 +- .../src/commands/set_configuration_command.rs | 91 +- masq/src/commands/setup_command.rs | 6 +- masq/src/commands/wallet_addresses_command.rs | 6 +- masq/src/communications/broadcast_handler.rs | 8 +- masq/src/non_interactive_clap.rs | 5 +- masq/src/non_interactive_mode.rs | 9 +- masq/src/test_utils/mocks.rs | 6 +- .../src/blockchains/blockchain_records.rs | 53 +- masq_lib/src/blockchains/chains.rs | 8 - masq_lib/src/constants.rs | 2 +- masq_lib/src/lib.rs | 1 + masq_lib/src/messages.rs | 7 +- masq_lib/src/multi_config.rs | 8 +- masq_lib/src/shared_schema.rs | 63 +- masq_lib/src/type_obfuscation.rs | 83 + masq_lib/src/utils.rs | 84 +- .../docker/macos/Dockerfile | 10 +- multinode_integration_tests/src/masq_node.rs | 3 +- .../src/masq_real_node.rs | 14 +- multinode_integration_tests/src/utils.rs | 6 +- .../tests/blockchain_interaction_test.rs | 2 +- .../tests/bookkeeping_test.rs | 6 +- .../tests/connection_termination_test.rs | 9 +- .../tests/data_routing_test.rs | 47 +- .../tests/min_hops_tests.rs | 123 ++ .../tests/verify_bill_payment.rs | 26 +- node/Cargo.lock | 1 + node/Cargo.toml | 1 + .../banned_dao.rs | 5 +- .../mod.rs | 2 +- .../payable_dao.rs | 45 +- .../pending_payable_dao.rs | 83 +- .../receivable_dao.rs | 41 +- .../utils.rs} | 16 +- .../big_int_db_processor.rs | 14 +- .../big_int_divider.rs | 4 +- .../mod.rs | 0 .../test_utils.rs | 6 +- node/src/accountant/financials.rs | 4 +- node/src/accountant/mod.rs | 856 +++++--- node/src/accountant/payment_adjuster.rs | 105 + .../scanners/mid_scan_msg_handling/mod.rs | 3 + .../payable_scanner/agent_null.rs | 192 ++ .../payable_scanner/agent_web3.rs | 140 ++ .../payable_scanner/blockchain_agent.rs | 37 + .../payable_scanner/mod.rs | 68 + .../payable_scanner/msgs.rs | 72 + .../payable_scanner/test_utils.rs | 80 + node/src/accountant/scanners/mod.rs | 706 ++++--- .../src/accountant/scanners/scanners_utils.rs | 81 +- node/src/accountant/scanners/test_utils.rs | 10 + node/src/accountant/test_utils.rs | 326 ++- node/src/actor_system_factory.rs | 485 ++--- node/src/blockchain/bip32.rs | 65 +- node/src/blockchain/blockchain_bridge.rs | 1244 +++++++----- .../lower_level_interface_null.rs | 112 ++ .../blockchain_interface_null/mod.rs | 291 +++ .../batch_payable_tools.rs | 4 +- .../lower_level_interface_web3.rs | 395 ++++ .../blockchain_interface_web3/mod.rs} | 1749 +++++++---------- .../blockchain_interface_web3/test_utils.rs | 172 ++ .../data_structures/errors.rs | 262 +++ .../data_structures/mod.rs | 29 + .../lower_level_interface.rs | 21 + .../blockchain/blockchain_interface/mod.rs | 52 + .../blockchain_interface/test_utils.rs | 131 ++ .../blockchain_interface_initializer.rs | 75 + node/src/blockchain/mod.rs | 3 +- node/src/blockchain/payer.rs | 2 +- node/src/blockchain/test_utils.rs | 397 +--- node/src/bootstrapper.rs | 17 +- node/src/daemon/daemon_initializer.rs | 4 +- node/src/daemon/setup_reporter.rs | 7 +- node/src/database/config_dumper.rs | 4 +- node/src/database/db_initializer.rs | 4 +- .../src/database/db_migrations/db_migrator.rs | 2 + .../migrations/migration_3_to_4.rs | 8 +- .../migrations/migration_4_to_5.rs | 4 +- .../migrations/migration_6_to_7.rs | 4 +- .../migrations/migration_8_to_9.rs | 70 + .../database/db_migrations/migrations/mod.rs | 1 + .../database/db_migrations/migrator_utils.rs | 4 +- node/src/database/db_migrations/test_utils.rs | 3 +- node/src/db_config/config_dao.rs | 17 +- node/src/db_config/config_dao_null.rs | 2 + node/src/db_config/mocks.rs | 3 +- .../src/db_config/persistent_configuration.rs | 75 +- node/src/dispatcher.rs | 7 +- node/src/hopper/routing_service.rs | 27 +- node/src/neighborhood/gossip_acceptor.rs | 37 +- node/src/neighborhood/mod.rs | 288 ++- .../src/neighborhood/neighborhood_database.rs | 10 +- .../neighborhood/overall_connection_status.rs | 29 +- node/src/node_configurator/configurator.rs | 410 +++- .../node_configurator_standard.rs | 6 +- .../unprivileged_parse_args_configuration.rs | 28 +- node/src/node_test_utils.rs | 11 - node/src/proxy_client/mod.rs | 33 +- node/src/proxy_client/stream_establisher.rs | 5 +- node/src/proxy_client/stream_handler_pool.rs | 48 +- node/src/proxy_client/stream_reader.rs | 33 +- node/src/proxy_client/stream_writer.rs | 27 +- .../client_request_payload_factory.rs | 63 +- node/src/proxy_server/mod.rs | 290 +-- node/src/run_modes_factories.rs | 8 +- node/src/server_initializer.rs | 2 +- node/src/stream_messages.rs | 27 +- node/src/stream_reader.rs | 11 +- node/src/sub_lib/accountant.rs | 38 +- node/src/sub_lib/blockchain_bridge.rs | 80 +- node/src/sub_lib/combined_parameters.rs | 6 +- node/src/sub_lib/configurator.rs | 1 + .../migrations/client_request_payload.rs | 7 +- .../migrations/client_response_payload.rs | 8 +- .../sub_lib/migrations/dns_resolve_failure.rs | 8 +- node/src/sub_lib/mod.rs | 1 - node/src/sub_lib/neighborhood.rs | 30 +- node/src/sub_lib/proxy_client.rs | 3 +- node/src/sub_lib/proxy_server.rs | 3 - .../sub_lib/set_consuming_wallet_message.rs | 9 - node/src/sub_lib/stream_key.rs | 104 +- node/src/sub_lib/utils.rs | 10 +- node/src/sub_lib/wallet.rs | 69 +- node/src/test_utils/actor_system_factory.rs | 16 + node/src/test_utils/database_utils.rs | 8 +- node/src/test_utils/http_test_server.rs | 73 + node/src/test_utils/mod.rs | 103 +- .../src/test_utils/neighborhood_test_utils.rs | 1 + .../persistent_configuration_mock.rs | 125 +- node/src/test_utils/recorder.rs | 264 ++- .../test_utils/recorder_stop_conditions.rs | 50 +- node/tests/financials_test.rs | 8 +- 143 files changed, 7883 insertions(+), 3961 deletions(-) create mode 100644 masq_lib/src/type_obfuscation.rs create mode 100644 multinode_integration_tests/tests/min_hops_tests.rs rename node/src/accountant/{database_access_objects => db_access_objects}/banned_dao.rs (99%) rename node/src/accountant/{database_access_objects => db_access_objects}/mod.rs (90%) rename node/src/accountant/{database_access_objects => db_access_objects}/payable_dao.rs (97%) rename node/src/accountant/{database_access_objects => db_access_objects}/pending_payable_dao.rs (92%) rename node/src/accountant/{database_access_objects => db_access_objects}/receivable_dao.rs (98%) rename node/src/accountant/{database_access_objects/dao_utils.rs => db_access_objects/utils.rs} (98%) rename node/src/accountant/{big_int_processing => db_big_integer}/big_int_db_processor.rs (98%) rename node/src/accountant/{big_int_processing => db_big_integer}/big_int_divider.rs (99%) rename node/src/accountant/{big_int_processing => db_big_integer}/mod.rs (100%) rename node/src/accountant/{big_int_processing => db_big_integer}/test_utils.rs (75%) create mode 100644 node/src/accountant/payment_adjuster.rs create mode 100644 node/src/accountant/scanners/mid_scan_msg_handling/mod.rs create mode 100644 node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_null.rs create mode 100644 node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_web3.rs create mode 100644 node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/blockchain_agent.rs create mode 100644 node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/mod.rs create mode 100644 node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/msgs.rs create mode 100644 node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/test_utils.rs create mode 100644 node/src/accountant/scanners/test_utils.rs create mode 100644 node/src/blockchain/blockchain_interface/blockchain_interface_null/lower_level_interface_null.rs create mode 100644 node/src/blockchain/blockchain_interface/blockchain_interface_null/mod.rs rename node/src/blockchain/{ => blockchain_interface/blockchain_interface_web3}/batch_payable_tools.rs (96%) create mode 100644 node/src/blockchain/blockchain_interface/blockchain_interface_web3/lower_level_interface_web3.rs rename node/src/blockchain/{blockchain_interface.rs => blockchain_interface/blockchain_interface_web3/mod.rs} (62%) create mode 100644 node/src/blockchain/blockchain_interface/blockchain_interface_web3/test_utils.rs create mode 100644 node/src/blockchain/blockchain_interface/data_structures/errors.rs create mode 100644 node/src/blockchain/blockchain_interface/data_structures/mod.rs create mode 100644 node/src/blockchain/blockchain_interface/lower_level_interface.rs create mode 100644 node/src/blockchain/blockchain_interface/mod.rs create mode 100644 node/src/blockchain/blockchain_interface/test_utils.rs create mode 100644 node/src/blockchain/blockchain_interface_initializer.rs create mode 100644 node/src/database/db_migrations/migrations/migration_8_to_9.rs delete mode 100644 node/src/sub_lib/set_consuming_wallet_message.rs create mode 100644 node/src/test_utils/actor_system_factory.rs create mode 100644 node/src/test_utils/http_test_server.rs diff --git a/.gitignore b/.gitignore index ebd355a8f..88a2ab5f9 100644 --- a/.gitignore +++ b/.gitignore @@ -65,6 +65,7 @@ Temporary Items **/.idea/**/dataSources.ids **/.idea/**/dataSources.xml **/.idea/**/dataSources.local.xml +**/.idea/**/dbnavigator.xml **/.idea/**/sqlDataSources.xml **/.idea/**/dynamic.xml **/.idea/**/uiDesigner.xml diff --git a/automap/src/comm_layer/pcp_pmp_common/macos_specific.rs b/automap/src/comm_layer/pcp_pmp_common/macos_specific.rs index b3cc6f116..d82ad7a1a 100644 --- a/automap/src/comm_layer/pcp_pmp_common/macos_specific.rs +++ b/automap/src/comm_layer/pcp_pmp_common/macos_specific.rs @@ -1,8 +1,10 @@ // Copyright (c) 2019-2021, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + #![cfg(target_os = "macos")] use crate::comm_layer::pcp_pmp_common::FindRoutersCommand; use crate::comm_layer::AutomapError; +use masq_lib::utils::to_string; use std::net::IpAddr; use std::str::FromStr; @@ -13,13 +15,9 @@ pub fn macos_find_routers(command: &dyn FindRoutersCommand) -> Result>() - }) + .map(|line| line.split(": ").map(to_string).collect::>()) .filter(|pieces| pieces.len() > 1) .map(|pieces| IpAddr::from_str(&pieces[1]).expect("Bad syntax from route -n get default")) .collect::>(); diff --git a/masq/src/command_context.rs b/masq/src/command_context.rs index de30503bb..41308e5bb 100644 --- a/masq/src/command_context.rs +++ b/masq/src/command_context.rs @@ -11,6 +11,8 @@ use std::fmt::{Debug, Formatter}; use std::io; use std::io::{Read, Write}; +pub const DEFAULT_TRANSACT_TIMEOUT_MILLIS: u64 = 1000; + #[derive(Clone, Debug, PartialEq, Eq)] pub enum ContextError { ConnectionRefused(String), @@ -151,6 +153,7 @@ mod tests { ConnectionDropped, ConnectionRefused, PayloadError, }; use crate::communications::broadcast_handler::BroadcastHandleInactive; + use crate::test_utils::mocks::TRANSACT_TIMEOUT_MILLIS_FOR_TESTS; use masq_lib::messages::{FromMessageBody, UiCrashRequest, UiSetupRequest}; use masq_lib::messages::{ToMessageBody, UiShutdownRequest, UiShutdownResponse}; use masq_lib::test_utils::fake_stream_holder::{ByteArrayReader, ByteArrayWriter}; @@ -160,6 +163,11 @@ mod tests { use masq_lib::ui_traffic_converter::{TrafficConversionError, UnmarshalError}; use masq_lib::utils::{find_free_port, running_test}; + #[test] + fn constant_has_correct_values() { + assert_eq!(DEFAULT_TRANSACT_TIMEOUT_MILLIS, 1000); + } + #[test] fn error_conversion_happy_path() { running_test(); @@ -233,7 +241,12 @@ mod tests { subject.stdout = Box::new(stdout); subject.stderr = Box::new(stderr); - let response = subject.transact(UiShutdownRequest {}.tmb(1), 1000).unwrap(); + let response = subject + .transact( + UiShutdownRequest {}.tmb(1), + TRANSACT_TIMEOUT_MILLIS_FOR_TESTS, + ) + .unwrap(); let mut input = String::new(); subject.stdin().read_to_string(&mut input).unwrap(); write!(subject.stdout(), "This is stdout.").unwrap(); @@ -283,7 +296,10 @@ mod tests { let broadcast_handle = BroadcastHandleInactive; let mut subject = CommandContextReal::new(port, None, Box::new(broadcast_handle)).unwrap(); - let response = subject.transact(UiSetupRequest { values: vec![] }.tmb(1), 1000); + let response = subject.transact( + UiSetupRequest { values: vec![] }.tmb(1), + TRANSACT_TIMEOUT_MILLIS_FOR_TESTS, + ); assert_eq!(response, Err(PayloadError(101, "booga".to_string()))); stop_handle.stop(); @@ -298,7 +314,10 @@ mod tests { let broadcast_handle = BroadcastHandleInactive; let mut subject = CommandContextReal::new(port, None, Box::new(broadcast_handle)).unwrap(); - let response = subject.transact(UiSetupRequest { values: vec![] }.tmb(1), 1000); + let response = subject.transact( + UiSetupRequest { values: vec![] }.tmb(1), + TRANSACT_TIMEOUT_MILLIS_FOR_TESTS, + ); match response { Err(ConnectionDropped(_)) => (), diff --git a/masq/src/command_processor.rs b/masq/src/command_processor.rs index ade770c61..4438e2b0e 100644 --- a/masq/src/command_processor.rs +++ b/masq/src/command_processor.rs @@ -75,7 +75,7 @@ impl CommandProcessor for CommandProcessorReal { #[cfg(test)] mod tests { use super::*; - use crate::command_context::CommandContext; + use crate::command_context::{CommandContext, DEFAULT_TRANSACT_TIMEOUT_MILLIS}; use crate::commands::check_password_command::CheckPasswordCommand; use crate::communications::broadcast_handler::{ BroadcastHandleInactive, BroadcastHandler, BroadcastHandlerReal, @@ -85,7 +85,7 @@ mod tests { use masq_lib::messages::UiShutdownRequest; use masq_lib::messages::{ToMessageBody, UiCheckPasswordResponse, UiUndeliveredFireAndForget}; use masq_lib::test_utils::mock_websockets_server::MockWebSocketsServer; - use masq_lib::utils::{find_free_port, running_test}; + use masq_lib::utils::{find_free_port, running_test, to_string}; use std::thread; use std::time::Duration; @@ -94,7 +94,7 @@ mod tests { impl Command for TestCommand { fn execute<'a>(&self, context: &mut dyn CommandContext) -> Result<(), CommandError> { - match context.transact(UiShutdownRequest {}.tmb(1), 1000) { + match context.transact(UiShutdownRequest {}.tmb(1), DEFAULT_TRANSACT_TIMEOUT_MILLIS) { Ok(_) => Ok(()), Err(e) => Err(CommandError::Other(format!("{:?}", e))), } @@ -210,7 +210,7 @@ mod tests { fn whole_message() -> String { TameCommand::MESSAGE_IN_PIECES .iter() - .map(|str| str.to_string()) + .map(to_string) .collect() } } diff --git a/masq/src/commands/change_password_command.rs b/masq/src/commands/change_password_command.rs index ef2fb020c..9dcc71075 100644 --- a/masq/src/commands/change_password_command.rs +++ b/masq/src/commands/change_password_command.rs @@ -9,9 +9,7 @@ use clap::{App, Arg, SubCommand}; use masq_lib::messages::{ UiChangePasswordRequest, UiChangePasswordResponse, UiNewPasswordBroadcast, }; -use masq_lib::{implement_as_any, short_writeln}; -#[cfg(test)] -use std::any::Any; +use masq_lib::{as_any_in_trait_impl, short_writeln}; use std::io::Write; #[derive(Debug, PartialEq, Eq)] @@ -81,7 +79,7 @@ impl Command for ChangePasswordCommand { Ok(()) } - implement_as_any!(); + as_any_in_trait_impl!(); } pub fn change_password_subcommand() -> App<'static, 'static> { diff --git a/masq/src/commands/check_password_command.rs b/masq/src/commands/check_password_command.rs index 3dcc5abb2..ee7081e7d 100644 --- a/masq/src/commands/check_password_command.rs +++ b/masq/src/commands/check_password_command.rs @@ -5,11 +5,10 @@ use crate::commands::commands_common::{ transaction, Command, CommandError, STANDARD_COMMAND_TIMEOUT_MILLIS, }; use clap::{App, Arg, SubCommand}; -use masq_lib::implement_as_any; +use masq_lib::as_any_in_trait_impl; use masq_lib::messages::{UiCheckPasswordRequest, UiCheckPasswordResponse}; use masq_lib::short_writeln; -#[cfg(test)] -use std::any::Any; +use masq_lib::utils::to_string; #[derive(Debug, PartialEq, Eq)] pub struct CheckPasswordCommand { @@ -52,7 +51,7 @@ impl Command for CheckPasswordCommand { Ok(()) } - implement_as_any!(); + as_any_in_trait_impl!(); } impl CheckPasswordCommand { @@ -62,7 +61,7 @@ impl CheckPasswordCommand { Err(e) => return Err(format!("{}", e)), }; Ok(Self { - db_password_opt: matches.value_of("db-password").map(|r| r.to_string()), + db_password_opt: matches.value_of("db-password").map(to_string), }) } } diff --git a/masq/src/commands/configuration_command.rs b/masq/src/commands/configuration_command.rs index fea412a37..766eedc6d 100644 --- a/masq/src/commands/configuration_command.rs +++ b/masq/src/commands/configuration_command.rs @@ -6,12 +6,11 @@ use crate::commands::commands_common::{ dump_parameter_line, transaction, Command, CommandError, STANDARD_COMMAND_TIMEOUT_MILLIS, }; use clap::{App, Arg, SubCommand}; +use masq_lib::as_any_in_trait_impl; use masq_lib::constants::NODE_NOT_RUNNING_ERROR; -use masq_lib::implement_as_any; use masq_lib::messages::{UiConfigurationRequest, UiConfigurationResponse}; use masq_lib::short_writeln; -#[cfg(test)] -use std::any::Any; +use masq_lib::utils::to_string; use std::fmt::{Debug, Display}; use std::io::Write; use std::iter::once; @@ -65,7 +64,7 @@ impl Command for ConfigurationCommand { } } - implement_as_any!(); + as_any_in_trait_impl!(); } impl ConfigurationCommand { @@ -76,7 +75,7 @@ impl ConfigurationCommand { }; Ok(ConfigurationCommand { - db_password: matches.value_of("db-password").map(|s| s.to_string()), + db_password: matches.value_of("db-password").map(to_string), }) } @@ -111,6 +110,14 @@ impl ConfigurationCommand { &Self::interpret_option(&configuration.earning_wallet_address_opt), ); dump_parameter_line(stream, "Gas price:", &configuration.gas_price.to_string()); + dump_parameter_line( + stream, + "Max block count:", + &configuration + .max_block_count_opt + .map(|m| m.separate_with_commas()) + .unwrap_or_else(|| "[Unlimited]".to_string()), + ); dump_parameter_line( stream, "Neighborhood mode:", @@ -306,6 +313,7 @@ mod tests { chain_name: "ropsten".to_string(), gas_price: 2345, neighborhood_mode: "standard".to_string(), + max_block_count_opt: None, consuming_wallet_private_key_opt: Some("consuming wallet private key".to_string()), consuming_wallet_address_opt: Some("consuming wallet address".to_string()), earning_wallet_address_opt: Some("earning address".to_string()), @@ -367,6 +375,7 @@ mod tests { |Current schema version: schema version\n\ |Earning wallet address: earning address\n\ |Gas price: 2345\n\ +|Max block count: [Unlimited]\n\ |Neighborhood mode: standard\n\ |Port mapping protocol: PCP\n\ |Start block: 3456\n\ @@ -403,6 +412,7 @@ mod tests { clandestine_port: 1234, chain_name: "mumbai".to_string(), gas_price: 2345, + max_block_count_opt: Some(100_000), neighborhood_mode: "zero-hop".to_string(), consuming_wallet_address_opt: None, consuming_wallet_private_key_opt: None, @@ -463,6 +473,7 @@ mod tests { |Current schema version: schema version\n\ |Earning wallet address: earning wallet\n\ |Gas price: 2345\n\ +|Max block count: 100,000\n\ |Neighborhood mode: zero-hop\n\ |Port mapping protocol: PCP\n\ |Start block: 3456\n\ diff --git a/masq/src/commands/connection_status_command.rs b/masq/src/commands/connection_status_command.rs index bc3ece105..fc96c6716 100644 --- a/masq/src/commands/connection_status_command.rs +++ b/masq/src/commands/connection_status_command.rs @@ -6,14 +6,12 @@ use crate::commands::commands_common::{ transaction, Command, CommandError, STANDARD_COMMAND_TIMEOUT_MILLIS, }; use clap::{App, SubCommand}; +use masq_lib::as_any_in_trait_impl; use masq_lib::constants::NODE_NOT_RUNNING_ERROR; -use masq_lib::implement_as_any; use masq_lib::messages::{ UiConnectionStage, UiConnectionStatusRequest, UiConnectionStatusResponse, }; use masq_lib::short_writeln; -#[cfg(test)] -use std::any::Any; use std::fmt::Debug; #[derive(Debug, PartialEq, Eq)] @@ -64,7 +62,7 @@ impl Command for ConnectionStatusCommand { } } - implement_as_any!(); + as_any_in_trait_impl!(); } impl ConnectionStatusCommand { diff --git a/masq/src/commands/financials_command/pretty_print_utils.rs b/masq/src/commands/financials_command/pretty_print_utils.rs index 16088b7d5..2adbfbad4 100644 --- a/masq/src/commands/financials_command/pretty_print_utils.rs +++ b/masq/src/commands/financials_command/pretty_print_utils.rs @@ -11,6 +11,7 @@ pub(in crate::commands::financials_command) mod restricted { use masq_lib::constants::WALLET_ADDRESS_LENGTH; use masq_lib::messages::{UiPayableAccount, UiReceivableAccount}; use masq_lib::short_writeln; + use masq_lib::utils::to_string; use std::fmt::{Debug, Display}; use std::io::Write; use thousands::Separable; @@ -163,7 +164,7 @@ pub(in crate::commands::financials_command) mod restricted { fn prepare_headings_of_records(is_gwei: bool) -> (HeadingsHolder, HeadingsHolder) { fn to_owned_strings(words: Vec<&str>) -> Vec { - words.iter().map(|str| str.to_string()).collect() + words.iter().map(to_string).collect() } let balance = gwei_or_masq_balance(is_gwei); ( diff --git a/masq/src/commands/generate_wallets_command.rs b/masq/src/commands/generate_wallets_command.rs index 7cc388393..6bfd8b359 100644 --- a/masq/src/commands/generate_wallets_command.rs +++ b/masq/src/commands/generate_wallets_command.rs @@ -1,19 +1,16 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -#[cfg(test)] -use std::any::Any; - use crate::command_context::CommandContext; use crate::commands::commands_common::{ transaction, Command, CommandError, STANDARD_COMMAND_TIMEOUT_MILLIS, }; use clap::{App, Arg, SubCommand}; use lazy_static::lazy_static; -use masq_lib::implement_as_any; +use masq_lib::as_any_in_trait_impl; use masq_lib::messages::{UiGenerateSeedSpec, UiGenerateWalletsRequest, UiGenerateWalletsResponse}; use masq_lib::short_writeln; -use masq_lib::utils::DEFAULT_CONSUMING_DERIVATION_PATH; use masq_lib::utils::DEFAULT_EARNING_DERIVATION_PATH; +use masq_lib::utils::{to_string, DEFAULT_CONSUMING_DERIVATION_PATH}; lazy_static! { static ref CONSUMING_PATH_HELP: String = format!( @@ -85,8 +82,8 @@ impl GenerateWalletsCommand { Err(e) => return Err(format!("{}", e)), }; - let consuming_path_opt = matches.value_of("consuming-path").map(|p| p.to_string()); - let earning_path_opt = matches.value_of("earning-path").map(|p| p.to_string()); + let consuming_path_opt = matches.value_of("consuming-path").map(to_string); + let earning_path_opt = matches.value_of("earning-path").map(to_string); let seed_spec_opt = if consuming_path_opt.is_some() || earning_path_opt.is_some() { Some(SeedSpec { word_count: matches @@ -99,7 +96,7 @@ impl GenerateWalletsCommand { .value_of("language") .expect("language not properly defaulted") .to_string(), - passphrase_opt: matches.value_of("passphrase").map(|s| s.to_string()), + passphrase_opt: matches.value_of("passphrase").map(to_string), }) } else { None @@ -168,7 +165,7 @@ impl Command for GenerateWalletsCommand { Ok(()) } - implement_as_any!(); + as_any_in_trait_impl!(); } pub fn generate_wallets_subcommand() -> App<'static, 'static> { diff --git a/masq/src/commands/recover_wallets_command.rs b/masq/src/commands/recover_wallets_command.rs index 34ea03179..b7f3e6bc1 100644 --- a/masq/src/commands/recover_wallets_command.rs +++ b/masq/src/commands/recover_wallets_command.rs @@ -6,11 +6,10 @@ use crate::commands::commands_common::{ }; use clap::{App, Arg, ArgGroup, SubCommand}; use itertools::{Either, Itertools}; -use masq_lib::implement_as_any; +use masq_lib::as_any_in_trait_impl; use masq_lib::messages::{UiRecoverSeedSpec, UiRecoverWalletsRequest, UiRecoverWalletsResponse}; use masq_lib::short_writeln; -#[cfg(test)] -use std::any::Any; +use masq_lib::utils::to_string; #[derive(Debug, PartialEq, Eq)] pub struct SeedSpec { @@ -36,12 +35,12 @@ impl RecoverWalletsCommand { let mnemonic_phrase_opt = matches .value_of("mnemonic-phrase") - .map(|mpv| mpv.split(' ').map(|x| x.to_string()).collect_vec()); + .map(|mpv| mpv.split(' ').map(to_string).collect_vec()); let language = matches .value_of("language") .expect("language is not properly defaulted by clap") .to_string(); - let passphrase_opt = matches.value_of("passphrase").map(|mp| mp.to_string()); + let passphrase_opt = matches.value_of("passphrase").map(to_string); let seed_spec_opt = mnemonic_phrase_opt.map(|mnemonic_phrase| SeedSpec { mnemonic_phrase, language, @@ -121,7 +120,7 @@ impl Command for RecoverWalletsCommand { Ok(()) } - implement_as_any!(); + as_any_in_trait_impl!(); } const RECOVER_WALLETS_ABOUT: &str = @@ -340,7 +339,7 @@ mod tests { db_password: "password".to_string(), seed_spec_opt: Some (SeedSpec { mnemonic_phrase: "river message view churn potato cabbage craft luggage tape month observe obvious" - .split(" ").into_iter().map(|x| x.to_string()).collect(), + .split(" ").into_iter().map(to_string).collect(), passphrase_opt: Some("booga".to_string()), language: "English".to_string(), }), diff --git a/masq/src/commands/set_configuration_command.rs b/masq/src/commands/set_configuration_command.rs index df4dbfbc4..72fabc498 100644 --- a/masq/src/commands/set_configuration_command.rs +++ b/masq/src/commands/set_configuration_command.rs @@ -1,14 +1,12 @@ use crate::command_context::CommandContext; use crate::commands::commands_common::{transaction, Command, CommandError}; use clap::{App, Arg, ArgGroup, SubCommand}; -use masq_lib::implement_as_any; +use masq_lib::as_any_in_trait_impl; use masq_lib::messages::{UiSetConfigurationRequest, UiSetConfigurationResponse}; -use masq_lib::shared_schema::common_validators; -use masq_lib::shared_schema::GAS_PRICE_HELP; +use masq_lib::shared_schema::gas_price_arg; +use masq_lib::shared_schema::min_hops_arg; use masq_lib::short_writeln; use masq_lib::utils::ExpectValue; -#[cfg(test)] -use std::any::Any; #[derive(Debug, PartialEq, Eq)] pub struct SetConfigurationCommand { @@ -55,7 +53,7 @@ impl Command for SetConfigurationCommand { Ok(()) } - implement_as_any!(); + as_any_in_trait_impl!(); } const SET_CONFIGURATION_ABOUT: &str = @@ -63,18 +61,15 @@ const SET_CONFIGURATION_ABOUT: &str = const START_BLOCK_HELP: &str = "Ordinal number of the Ethereum block where scanning for transactions will start."; +pub fn set_configurationify<'a>(shared_schema_arg: Arg<'a, 'a>) -> Arg<'a, 'a> { + shared_schema_arg.takes_value(true).min_values(1) +} + pub fn set_configuration_subcommand() -> App<'static, 'static> { SubCommand::with_name("set-configuration") .about(SET_CONFIGURATION_ABOUT) - .arg( - Arg::with_name("gas-price") - .help(&GAS_PRICE_HELP) - .long("gas-price") - .value_name("GAS-PRICE") - .takes_value(true) - .required(false) - .validator(common_validators::validate_gas_price), - ) + .arg(set_configurationify(gas_price_arg())) + .arg(set_configurationify(min_hops_arg())) .arg( Arg::with_name("start-block") .help(START_BLOCK_HELP) @@ -86,7 +81,7 @@ pub fn set_configuration_subcommand() -> App<'static, 'static> { ) .group( ArgGroup::with_name("parameter") - .args(&["gas-price", "start-block"]) + .args(&["gas-price", "min-hops", "start-block"]) .required(true), ) } @@ -135,16 +130,51 @@ mod tests { #[test] fn command_execution_works_all_fine() { + test_command_execution("--start-block", "123456"); + test_command_execution("--gas-price", "123456"); + test_command_execution("--min-hops", "6"); + } + + #[test] + fn set_configuration_command_throws_err_for_missing_values() { + set_configuration_command_throws_err_for_missing_value("--start-block"); + set_configuration_command_throws_err_for_missing_value("--gas-price"); + set_configuration_command_throws_err_for_missing_value("--min-hops"); + } + + #[test] + fn set_configuration_command_throws_err_for_invalid_arg() { + let (invalid_arg, some_value) = ("--invalid-arg", "123"); + + let result = SetConfigurationCommand::new(&[ + "set-configuration".to_string(), + invalid_arg.to_string(), + some_value.to_string(), + ]); + + let err_msg = result.unwrap_err(); + assert!(err_msg.contains("Found argument"), "{}", err_msg); + assert!(err_msg.contains("--invalid-arg"), "{}", err_msg); + assert!( + err_msg.contains("which wasn't expected, or isn't valid in this context"), + "{}", + err_msg + ); + } + + fn test_command_execution(name: &str, value: &str) { let transact_params_arc = Arc::new(Mutex::new(vec![])); let mut context = CommandContextMock::new() .transact_params(&transact_params_arc) .transact_result(Ok(UiSetConfigurationResponse {}.tmb(4321))); let stdout_arc = context.stdout_arc(); let stderr_arc = context.stderr_arc(); - let subject = SetConfigurationCommand { - name: "start-block".to_string(), - value: "123456".to_string(), - }; + let subject = SetConfigurationCommand::new(&[ + "set-configuration".to_string(), + name.to_string(), + value.to_string(), + ]) + .unwrap(); let result = subject.execute(&mut context); @@ -154,16 +184,31 @@ mod tests { *transact_params, vec![( UiSetConfigurationRequest { - name: "start-block".to_string(), - value: "123456".to_string() + name: name[2..].to_string(), + value: value.to_string(), } .tmb(0), 1000 )] ); let stderr = stderr_arc.lock().unwrap(); - assert_eq!(*stderr.get_string(), String::new()); + assert_eq!(&stderr.get_string(), ""); let stdout = stdout_arc.lock().unwrap(); assert_eq!(&stdout.get_string(), "Parameter was successfully set\n"); } + + fn set_configuration_command_throws_err_for_missing_value(name: &str) { + let result = + SetConfigurationCommand::new(&["set-configuration".to_string(), name.to_string()]); + + let err_msg_fragment = "requires a value but none was supplied"; + let actual_err_msg = result.err().unwrap(); + assert_eq!( + actual_err_msg.contains(err_msg_fragment), + true, + "'{}' did not contain '{}'", + actual_err_msg, + err_msg_fragment + ); + } } diff --git a/masq/src/commands/setup_command.rs b/masq/src/commands/setup_command.rs index 7c4e7514f..baec35e1b 100644 --- a/masq/src/commands/setup_command.rs +++ b/masq/src/commands/setup_command.rs @@ -4,8 +4,8 @@ use crate::command_context::CommandContext; use crate::commands::commands_common::{transaction, Command, CommandError}; use crate::terminal::terminal_interface::TerminalWrapper; use clap::{value_t, App, SubCommand}; +use masq_lib::as_any_in_trait_impl; use masq_lib::constants::SETUP_ERROR; -use masq_lib::implement_as_any; use masq_lib::messages::{ UiSetupBroadcast, UiSetupInner, UiSetupRequest, UiSetupRequestValue, UiSetupResponse, UiSetupResponseValue, UiSetupResponseValueStatus, @@ -13,8 +13,6 @@ use masq_lib::messages::{ use masq_lib::shared_schema::{data_directory_arg, shared_app}; use masq_lib::short_writeln; use masq_lib::utils::{index_of_from, DATA_DIRECTORY_DAEMON_HELP}; -#[cfg(test)] -use std::any::Any; use std::fmt::Debug; use std::io::Write; use std::iter::Iterator; @@ -53,7 +51,7 @@ impl Command for SetupCommand { Err(e) => Err(e), } } - implement_as_any!(); + as_any_in_trait_impl!(); } impl SetupCommand { diff --git a/masq/src/commands/wallet_addresses_command.rs b/masq/src/commands/wallet_addresses_command.rs index e1f90e4fc..edc2e2a0f 100644 --- a/masq/src/commands/wallet_addresses_command.rs +++ b/masq/src/commands/wallet_addresses_command.rs @@ -6,9 +6,7 @@ use crate::commands::commands_common::{ }; use clap::{App, Arg, SubCommand}; use masq_lib::messages::{UiWalletAddressesRequest, UiWalletAddressesResponse}; -use masq_lib::{implement_as_any, short_writeln}; -#[cfg(test)] -use std::any::Any; +use masq_lib::{as_any_in_trait_impl, short_writeln}; #[derive(Debug, PartialEq, Eq)] pub struct WalletAddressesCommand { @@ -68,7 +66,7 @@ impl Command for WalletAddressesCommand { ); Ok(()) } - implement_as_any!(); + as_any_in_trait_impl!(); } #[cfg(test)] diff --git a/masq/src/communications/broadcast_handler.rs b/masq/src/communications/broadcast_handler.rs index 54ad2af8f..e4903ef17 100644 --- a/masq/src/communications/broadcast_handler.rs +++ b/masq/src/communications/broadcast_handler.rs @@ -11,18 +11,16 @@ use masq_lib::messages::{ }; use masq_lib::ui_gateway::MessageBody; use masq_lib::utils::ExpectValue; -use masq_lib::{declare_as_any, implement_as_any, short_writeln}; +use masq_lib::{as_any_in_trait, as_any_in_trait_impl, short_writeln}; use std::fmt::Debug; use std::io::Write; use std::thread; use crate::notifications::connection_change_notification::ConnectionChangeNotification; -#[cfg(test)] -use std::any::Any; pub trait BroadcastHandle: Send { fn send(&self, message_body: MessageBody); - declare_as_any!(); + as_any_in_trait!(); } pub struct BroadcastHandleInactive; @@ -30,7 +28,7 @@ pub struct BroadcastHandleInactive; impl BroadcastHandle for BroadcastHandleInactive { //simply dropped (unless we find a better use for such a message) fn send(&self, _message_body: MessageBody) {} - implement_as_any!(); + as_any_in_trait_impl!(); } pub struct BroadcastHandleGeneric { diff --git a/masq/src/non_interactive_clap.rs b/masq/src/non_interactive_clap.rs index 14b5fa7db..46f60957a 100644 --- a/masq/src/non_interactive_clap.rs +++ b/masq/src/non_interactive_clap.rs @@ -40,13 +40,14 @@ fn handle_help_or_version_if_required<'a>(args: &[String]) -> ArgMatches<'a> { mod tests { use super::*; use masq_lib::constants::DEFAULT_UI_PORT; + use masq_lib::utils::to_string; #[test] fn non_interactive_clap_real_produces_default_value_for_ui_port() { let result = NonInteractiveClapReal.non_interactive_initial_clap_operations( &vec!["masq", "setup", "--chain"] .iter() - .map(|str| str.to_string()) + .map(to_string) .collect::>(), ); @@ -58,7 +59,7 @@ mod tests { let result = NonInteractiveClapReal.non_interactive_initial_clap_operations( &vec!["masq", "--ui-port", "10000", "setup", "--log-level", "off"] .iter() - .map(|str| str.to_string()) + .map(to_string) .collect::>(), ); diff --git a/masq/src/non_interactive_mode.rs b/masq/src/non_interactive_mode.rs index 290e57cc1..164b9c293 100644 --- a/masq/src/non_interactive_mode.rs +++ b/masq/src/non_interactive_mode.rs @@ -176,6 +176,7 @@ mod tests { use masq_lib::intentionally_blank; use masq_lib::messages::{ToMessageBody, UiNewPasswordBroadcast, UiShutdownRequest}; use masq_lib::test_utils::fake_stream_holder::FakeStreamHolder; + use masq_lib::utils::to_string; use std::any::Any; use std::sync::{Arc, Mutex}; @@ -217,7 +218,7 @@ mod tests { "--param3", ] .iter() - .map(|str| str.to_string()) + .map(to_string) .collect::>(), ); @@ -228,7 +229,7 @@ mod tests { vec![ vec!["subcommand", "--param1", "value1", "--param2", "--param3"] .iter() - .map(|str| str.to_string()) + .map(to_string) .collect::>(), ] ); @@ -478,7 +479,7 @@ mod tests { fn extract_subcommands_can_process_normal_non_interactive_request() { let args = vec!["masq", "setup", "--log-level", "off"] .iter() - .map(|str| str.to_string()) + .map(to_string) .collect::>(); let result = Main::extract_subcommand(&args); @@ -497,7 +498,7 @@ mod tests { fn extract_subcommands_can_process_non_interactive_request_including_special_port() { let args = vec!["masq", "--ui-port", "10000", "setup", "--log-level", "off"] .iter() - .map(|str| str.to_string()) + .map(to_string) .collect::>(); let result = Main::extract_subcommand(&args); diff --git a/masq/src/test_utils/mocks.rs b/masq/src/test_utils/mocks.rs index 2ce41c058..955fa578a 100644 --- a/masq/src/test_utils/mocks.rs +++ b/masq/src/test_utils/mocks.rs @@ -1,6 +1,6 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::command_context::{CommandContext, ContextError}; +use crate::command_context::{CommandContext, ContextError, DEFAULT_TRANSACT_TIMEOUT_MILLIS}; use crate::command_factory::{CommandFactory, CommandFactoryError}; use crate::command_processor::{CommandProcessor, CommandProcessorFactory}; use crate::commands::commands_common::CommandError::Transmission; @@ -24,6 +24,8 @@ use std::sync::{Arc, Mutex}; use std::time::Duration; use std::{io, thread}; +pub const TRANSACT_TIMEOUT_MILLIS_FOR_TESTS: u64 = DEFAULT_TRANSACT_TIMEOUT_MILLIS; + #[derive(Default)] pub struct CommandFactoryMock { make_params: Arc>>>, @@ -290,7 +292,7 @@ impl Command for MockCommand { fn execute(&self, context: &mut dyn CommandContext) -> Result<(), CommandError> { write!(context.stdout(), "MockCommand output").unwrap(); write!(context.stderr(), "MockCommand error").unwrap(); - match context.transact(self.message.clone(), 1000) { + match context.transact(self.message.clone(), TRANSACT_TIMEOUT_MILLIS_FOR_TESTS) { Ok(_) => self.execute_results.borrow_mut().remove(0), Err(e) => Err(Transmission(format!("{:?}", e))), } diff --git a/masq_lib/src/blockchains/blockchain_records.rs b/masq_lib/src/blockchains/blockchain_records.rs index 1bd13e622..f9b4bcab0 100644 --- a/masq_lib/src/blockchains/blockchain_records.rs +++ b/masq_lib/src/blockchains/blockchain_records.rs @@ -1,6 +1,6 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::blockchains::chains::{Chain, ChainFamily}; +use crate::blockchains::chains::Chain; use crate::constants::{ DEV_CHAIN_FULL_IDENTIFIER, ETH_MAINNET_CONTRACT_CREATION_BLOCK, ETH_MAINNET_FULL_IDENTIFIER, ETH_ROPSTEN_FULL_IDENTIFIER, MULTINODE_TESTNET_CONTRACT_CREATION_BLOCK, @@ -15,7 +15,6 @@ pub const CHAINS: [BlockchainRecord; 5] = [ BlockchainRecord { self_id: Chain::PolyMainnet, num_chain_id: 137, - chain_family: ChainFamily::Polygon, literal_identifier: POLYGON_MAINNET_FULL_IDENTIFIER, contract: POLYGON_MAINNET_CONTRACT_ADDRESS, contract_creation_block: POLYGON_MAINNET_CONTRACT_CREATION_BLOCK, @@ -23,7 +22,6 @@ pub const CHAINS: [BlockchainRecord; 5] = [ BlockchainRecord { self_id: Chain::EthMainnet, num_chain_id: 1, - chain_family: ChainFamily::Eth, literal_identifier: ETH_MAINNET_FULL_IDENTIFIER, contract: ETH_MAINNET_CONTRACT_ADDRESS, contract_creation_block: ETH_MAINNET_CONTRACT_CREATION_BLOCK, @@ -31,7 +29,6 @@ pub const CHAINS: [BlockchainRecord; 5] = [ BlockchainRecord { self_id: Chain::PolyMumbai, num_chain_id: 80001, - chain_family: ChainFamily::Polygon, literal_identifier: POLYGON_MUMBAI_FULL_IDENTIFIER, contract: MUMBAI_TESTNET_CONTRACT_ADDRESS, contract_creation_block: MUMBAI_TESTNET_CONTRACT_CREATION_BLOCK, @@ -39,7 +36,6 @@ pub const CHAINS: [BlockchainRecord; 5] = [ BlockchainRecord { self_id: Chain::EthRopsten, num_chain_id: 3, - chain_family: ChainFamily::Eth, literal_identifier: ETH_ROPSTEN_FULL_IDENTIFIER, contract: ROPSTEN_TESTNET_CONTRACT_ADDRESS, contract_creation_block: ROPSTEN_TESTNET_CONTRACT_CREATION_BLOCK, @@ -47,7 +43,6 @@ pub const CHAINS: [BlockchainRecord; 5] = [ BlockchainRecord { self_id: Chain::Dev, num_chain_id: 2, - chain_family: ChainFamily::Dev, literal_identifier: DEV_CHAIN_FULL_IDENTIFIER, contract: MULTINODE_TESTNET_CONTRACT_ADDRESS, contract_creation_block: MULTINODE_TESTNET_CONTRACT_CREATION_BLOCK, @@ -58,7 +53,6 @@ pub const CHAINS: [BlockchainRecord; 5] = [ pub struct BlockchainRecord { pub self_id: Chain, pub num_chain_id: u64, - pub chain_family: ChainFamily, pub literal_identifier: &'static str, pub contract: Address, pub contract_creation_block: u64, @@ -172,26 +166,8 @@ mod tests { literal_identifier: "eth-mainnet", contract: ETH_MAINNET_CONTRACT_ADDRESS, contract_creation_block: ETH_MAINNET_CONTRACT_CREATION_BLOCK, - chain_family: ChainFamily::Eth } - ) - } - - #[test] - fn multinode_testnet_chain_record_is_properly_declared() { - let examined_chain = Chain::Dev; - let chain_record = return_examined(examined_chain); - assert_eq!( - chain_record, - &BlockchainRecord { - num_chain_id: 2, - self_id: examined_chain, - literal_identifier: "dev", - contract: MULTINODE_TESTNET_CONTRACT_ADDRESS, - contract_creation_block: 0, - chain_family: ChainFamily::Dev - } - ) + ); } #[test] @@ -206,9 +182,8 @@ mod tests { literal_identifier: "eth-ropsten", contract: ROPSTEN_TESTNET_CONTRACT_ADDRESS, contract_creation_block: ROPSTEN_TESTNET_CONTRACT_CREATION_BLOCK, - chain_family: ChainFamily::Eth } - ) + ); } #[test] @@ -223,9 +198,8 @@ mod tests { literal_identifier: "polygon-mainnet", contract: POLYGON_MAINNET_CONTRACT_ADDRESS, contract_creation_block: POLYGON_MAINNET_CONTRACT_CREATION_BLOCK, - chain_family: ChainFamily::Polygon } - ) + ); } #[test] @@ -240,9 +214,24 @@ mod tests { literal_identifier: "polygon-mumbai", contract: MUMBAI_TESTNET_CONTRACT_ADDRESS, contract_creation_block: MUMBAI_TESTNET_CONTRACT_CREATION_BLOCK, - chain_family: ChainFamily::Polygon } - ) + ); + } + + #[test] + fn multinode_testnet_chain_record_is_properly_declared() { + let examined_chain = Chain::Dev; + let chain_record = return_examined(examined_chain); + assert_eq!( + chain_record, + &BlockchainRecord { + num_chain_id: 2, + self_id: examined_chain, + literal_identifier: "dev", + contract: MULTINODE_TESTNET_CONTRACT_ADDRESS, + contract_creation_block: 0, + } + ); } fn return_examined<'a>(chain: Chain) -> &'a BlockchainRecord { diff --git a/masq_lib/src/blockchains/chains.rs b/masq_lib/src/blockchains/chains.rs index be0d9f79f..ac3fbbac0 100644 --- a/masq_lib/src/blockchains/chains.rs +++ b/masq_lib/src/blockchains/chains.rs @@ -16,13 +16,6 @@ pub enum Chain { Dev, } -#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] -pub enum ChainFamily { - Eth, - Polygon, - Dev, -} - impl Default for Chain { fn default() -> Self { DEFAULT_CHAIN @@ -143,7 +136,6 @@ mod tests { literal_identifier: "", contract: Default::default(), contract_creation_block: 0, - chain_family: ChainFamily::Polygon, } } diff --git a/masq_lib/src/constants.rs b/masq_lib/src/constants.rs index 1371c0b63..7bdcbe1e1 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 = 8; +pub const CURRENT_SCHEMA_VERSION: usize = 9; pub const HIGHEST_RANDOM_CLANDESTINE_PORT: u16 = 9999; pub const HTTP_PORT: u16 = 80; diff --git a/masq_lib/src/lib.rs b/masq_lib/src/lib.rs index db70ff4cf..e5232b221 100644 --- a/masq_lib/src/lib.rs +++ b/masq_lib/src/lib.rs @@ -22,5 +22,6 @@ pub mod crash_point; pub mod data_version; pub mod shared_schema; pub mod test_utils; +pub mod type_obfuscation; pub mod ui_gateway; pub mod ui_traffic_converter; diff --git a/masq_lib/src/messages.rs b/masq_lib/src/messages.rs index b0d6ae15f..59522171e 100644 --- a/masq_lib/src/messages.rs +++ b/masq_lib/src/messages.rs @@ -4,6 +4,7 @@ use crate::messages::UiMessageError::{DeserializationError, PayloadError, Unexpe use crate::shared_schema::ConfiguratorError; use crate::ui_gateway::MessageBody; use crate::ui_gateway::MessagePath::{Conversation, FireAndForget}; +use crate::utils::to_string; use itertools::Itertools; use serde::de::DeserializeOwned; use serde_derive::{Deserialize, Serialize}; @@ -243,7 +244,7 @@ impl UiSetupRequest { .into_iter() .map(|(name, value)| UiSetupRequestValue { name: name.to_string(), - value: value.map(|v| v.to_string()), + value: value.map(to_string), }) .collect(), } @@ -484,6 +485,8 @@ pub struct UiConfigurationResponse { pub earning_wallet_address_opt: Option, #[serde(rename = "gasPrice")] pub gas_price: u64, + #[serde(rename = "maxBlockCount")] + pub max_block_count_opt: Option, #[serde(rename = "neighborhoodMode")] pub neighborhood_mode: String, #[serde(rename = "portMappingProtocol")] @@ -775,7 +778,7 @@ conversation_message!(UiRecoverWalletsRequest, "recoverWallets"); pub struct UiRecoverWalletsResponse {} conversation_message!(UiRecoverWalletsResponse, "recoverWallets"); -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy, Hash)] pub enum ScanType { Payables, Receivables, diff --git a/masq_lib/src/multi_config.rs b/masq_lib/src/multi_config.rs index 23a62713e..418dbf240 100644 --- a/masq_lib/src/multi_config.rs +++ b/masq_lib/src/multi_config.rs @@ -515,6 +515,7 @@ pub mod tests { use super::*; use crate::test_utils::environment_guard::EnvironmentGuard; use crate::test_utils::utils::ensure_node_home_directory_exists; + use crate::utils::to_string; use clap::Arg; use std::fs::File; use std::io::Write; @@ -943,7 +944,7 @@ pub mod tests { "--other_takes_no_value", ] .into_iter() - .map(|s| s.to_string()) + .map(to_string) .collect(); let subject = CommandLineVcl::new(command_line.clone()); @@ -966,10 +967,7 @@ pub mod tests { #[test] #[should_panic(expected = "Expected option beginning with '--', not value")] fn command_line_vcl_panics_when_given_value_without_name() { - let command_line: Vec = vec!["", "value"] - .into_iter() - .map(|s| s.to_string()) - .collect(); + let command_line: Vec = vec!["", "value"].into_iter().map(to_string).collect(); CommandLineVcl::new(command_line.clone()); } diff --git a/masq_lib/src/shared_schema.rs b/masq_lib/src/shared_schema.rs index 709b39fc5..fc7e8ced2 100644 --- a/masq_lib/src/shared_schema.rs +++ b/masq_lib/src/shared_schema.rs @@ -1,3 +1,5 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + use crate::constants::{ DEFAULT_GAS_PRICE, DEFAULT_UI_PORT, DEV_CHAIN_FULL_IDENTIFIER, ETH_MAINNET_FULL_IDENTIFIER, ETH_ROPSTEN_FULL_IDENTIFIER, HIGHEST_USABLE_PORT, LOWEST_USABLE_INSECURE_PORT, @@ -219,6 +221,16 @@ lazy_static! { // These Args are needed in more than one clap schema. To avoid code duplication, they're defined here and referred // to from multiple places. +pub fn chain_arg<'a>() -> Arg<'a, 'a> { + Arg::with_name("chain") + .long("chain") + .value_name("CHAIN") + .min_values(0) + .max_values(1) + .possible_values(official_chain_names()) + .help(CHAIN_HELP) +} + pub fn config_file_arg<'a>() -> Arg<'a, 'a> { Arg::with_name("config-file") .long("config-file") @@ -240,16 +252,6 @@ pub fn data_directory_arg(help: &str) -> Arg { .help(help) } -pub fn chain_arg<'a>() -> Arg<'a, 'a> { - Arg::with_name("chain") - .long("chain") - .value_name("CHAIN") - .min_values(0) - .max_values(1) - .possible_values(official_chain_names()) - .help(CHAIN_HELP) -} - pub fn official_chain_names() -> &'static [&'static str] { &[ POLYGON_MAINNET_FULL_IDENTIFIER, @@ -285,6 +287,26 @@ where .help(help) } +pub fn gas_price_arg<'a>() -> Arg<'a, 'a> { + Arg::with_name("gas-price") + .long("gas-price") + .value_name("GAS-PRICE") + .min_values(0) + .max_values(1) + .validator(common_validators::validate_gas_price) + .help(&GAS_PRICE_HELP) +} + +pub fn min_hops_arg<'a>() -> Arg<'a, 'a> { + Arg::with_name("min-hops") + .long("min-hops") + .value_name("MIN-HOPS") + .min_values(0) + .max_values(1) + .possible_values(&["1", "2", "3", "4", "5", "6"]) + .help(MIN_HOPS_HELP) +} + #[cfg(not(target_os = "windows"))] pub fn real_user_arg<'a>() -> Arg<'a, 'a> { Arg::with_name("real-user") @@ -389,15 +411,7 @@ pub fn shared_app(head: App<'static, 'static>) -> App<'static, 'static> { .max_values(1) .hidden(true), ) - .arg( - Arg::with_name("gas-price") - .long("gas-price") - .value_name("GAS-PRICE") - .min_values(0) - .max_values(1) - .validator(common_validators::validate_gas_price) - .help(&GAS_PRICE_HELP), - ) + .arg(gas_price_arg()) .arg( Arg::with_name("ip") .long("ip") @@ -427,16 +441,7 @@ pub fn shared_app(head: App<'static, 'static>) -> App<'static, 'static> { .case_insensitive(true) .help(MAPPING_PROTOCOL_HELP), ) - .arg( - Arg::with_name("min-hops") - .long("min-hops") - .value_name("MIN_HOPS") - .required(false) - .min_values(0) - .max_values(1) - .possible_values(&["1", "2", "3", "4", "5", "6"]) - .help(MIN_HOPS_HELP), - ) + .arg(min_hops_arg()) .arg( Arg::with_name("neighborhood-mode") .long("neighborhood-mode") diff --git a/masq_lib/src/type_obfuscation.rs b/masq_lib/src/type_obfuscation.rs new file mode 100644 index 000000000..1f3c79258 --- /dev/null +++ b/masq_lib/src/type_obfuscation.rs @@ -0,0 +1,83 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use std::any::TypeId; +use std::mem::transmute; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Obfuscated { + type_id: TypeId, + bytes: Vec, +} + +impl Obfuscated { + // Although we're asking the compiler for a cast between two types + // where one is generic and both could possibly be of a different + // size, which almost applies to an unsupported kind of operation, + // the compiler stays calm here. The use of vectors at the input as + // well as output lets us avoid the above depicted situation. + // + // If you wish to write an implementation allowing more arbitrary + // types on your own, instead of helping yourself by a library like + // 'bytemuck', consider these functions from the std library, + // 'mem::transmute_copy' or 'mem::forget()', which will renew + // the compiler's trust for you. However, the true adventure will + // begin when you are supposed to write code to realign the plain + // bytes backwards to your desired type... + + pub fn obfuscate_vector(data: Vec) -> Obfuscated { + let bytes = unsafe { transmute::, Vec>(data) }; + + Obfuscated { + type_id: TypeId::of::(), + bytes, + } + } + + pub fn expose_vector(self) -> Vec { + if self.type_id != TypeId::of::() { + panic!("Forbidden! You're trying to interpret obfuscated data as the wrong type.") + } + + unsafe { transmute::, Vec>(self.bytes) } + } + + // Proper casting from a non vec structure into a vector of bytes + // is difficult and ideally requires an involvement of a library + // like bytemuck. + // If you think we do need such cast, place other methods in here + // and don't remove the ones above because: + // a) bytemuck will force you to implement its 'Pod' trait which + // might imply an (at minimum) ugly implementation for a std + // type like a Vec because both the trait and the type have + // their definitions situated externally to our project, + // therefore you might need to solve it by introducing + // a super-trait from our code + // b) using our simple 'obfuscate_vector' function will always + // be fairly more efficient than if done with help of + // the other library +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn obfuscation_works() { + let data = vec!["I'm fearing of losing my entire identity".to_string()]; + + let obfuscated_data = Obfuscated::obfuscate_vector(data.clone()); + let fenix_like_data: Vec = obfuscated_data.expose_vector(); + + assert_eq!(data, fenix_like_data) + } + + #[test] + #[should_panic( + expected = "Forbidden! You're trying to interpret obfuscated data as the wrong type." + )] + fn obfuscation_attempt_to_reinterpret_to_wrong_type() { + let data = vec![0_u64]; + let obfuscated_data = Obfuscated::obfuscate_vector(data.clone()); + let _: Vec = obfuscated_data.expose_vector(); + } +} diff --git a/masq_lib/src/utils.rs b/masq_lib/src/utils.rs index a86415b86..24dc6d320 100644 --- a/masq_lib/src/utils.rs +++ b/masq_lib/src/utils.rs @@ -321,10 +321,7 @@ pub fn exit_process_with_sigterm(message: &str) { } pub fn slice_of_strs_to_vec_of_strings(slice: &[&str]) -> Vec { - slice - .iter() - .map(|item| item.to_string()) - .collect::>() + slice.iter().map(to_string).collect::>() } pub trait ExpectValue { @@ -389,6 +386,14 @@ where fn helper_access(&mut self) -> &mut Option; } +// A handy function for closures +pub fn to_string(displayable: D) -> String +where + D: Display, +{ + displayable.to_string() +} + #[macro_export] macro_rules! short_writeln { ($dst:expr) => ( @@ -407,10 +412,10 @@ macro_rules! intentionally_blank { } #[macro_export] -macro_rules! declare_as_any { +macro_rules! as_any_in_trait { () => { #[cfg(test)] - fn as_any(&self) -> &dyn Any { + fn as_any(&self) -> &dyn std::any::Any { use masq_lib::intentionally_blank; intentionally_blank!() } @@ -418,18 +423,48 @@ macro_rules! declare_as_any { } #[macro_export] -macro_rules! implement_as_any { +macro_rules! as_any_in_trait_impl { () => { #[cfg(test)] - fn as_any(&self) -> &dyn Any { + fn as_any(&self) -> &dyn std::any::Any { self } }; } +#[macro_export] +macro_rules! test_only_use { + ($($use_clause: item),+) => { + $( + #[cfg(test)] + $use_clause + )+ + } +} + +#[macro_export(local_inner_macros)] +macro_rules! hashmap { + () => { + ::std::collections::HashMap::new() + }; + ($($key:expr => $val:expr,)+) => { + hashmap!($($key => $val),+) + }; + ($($key:expr => $value:expr),+) => { + { + let mut _hm = ::std::collections::HashMap::new(); + $( + let _ = _hm.insert($key, $value); + )* + _hm + } + }; +} + #[cfg(test)] mod tests { use super::*; + use std::collections::HashMap; use std::env::current_dir; use std::fmt::Write; use std::fs::{create_dir_all, File, OpenOptions}; @@ -751,4 +786,37 @@ mod tests { let result = type_name_of(running_test); assert_eq!(result, "masq_lib::utils::running_test") } + + #[test] + fn hashmap_macro_works() { + let empty_hashmap: HashMap = hashmap!(); + let hashmap_with_one_element = hashmap!(1 => 2); + let hashmap_with_multiple_elements = hashmap!(1 => 2, 10 => 20, 12 => 42); + let hashmap_with_trailing_comma = hashmap!(1 => 2, 10 => 20,); + let hashmap_of_string = hashmap!("key" => "val"); + + let expected_empty_hashmap: HashMap = HashMap::new(); + let mut expected_hashmap_with_one_element = HashMap::new(); + expected_hashmap_with_one_element.insert(1, 2); + let mut expected_hashmap_with_multiple_elements = HashMap::new(); + expected_hashmap_with_multiple_elements.insert(1, 2); + expected_hashmap_with_multiple_elements.insert(10, 20); + expected_hashmap_with_multiple_elements.insert(12, 42); + let mut expected_hashmap_with_trailing_comma = HashMap::new(); + expected_hashmap_with_trailing_comma.insert(1, 2); + expected_hashmap_with_trailing_comma.insert(10, 20); + let mut expected_hashmap_of_string = HashMap::new(); + expected_hashmap_of_string.insert("key", "val"); + assert_eq!(empty_hashmap, expected_empty_hashmap); + assert_eq!(hashmap_with_one_element, expected_hashmap_with_one_element); + assert_eq!( + hashmap_with_multiple_elements, + expected_hashmap_with_multiple_elements + ); + assert_eq!( + hashmap_with_trailing_comma, + expected_hashmap_with_trailing_comma + ); + assert_eq!(hashmap_of_string, expected_hashmap_of_string); + } } diff --git a/multinode_integration_tests/docker/macos/Dockerfile b/multinode_integration_tests/docker/macos/Dockerfile index dd9b3074c..a4a86f156 100644 --- a/multinode_integration_tests/docker/macos/Dockerfile +++ b/multinode_integration_tests/docker/macos/Dockerfile @@ -1,13 +1,13 @@ -FROM rust:stretch +FROM rust:bullseye ARG uid ARG gid -RUN (addgroup substratum --gid $gid || continue) \ - && adduser --disabled-password --uid $uid --gid $gid --home /home/substratum substratum \ - && chown -R $uid:$gid /home/substratum +RUN (addgroup masq --gid $gid || continue) \ + && adduser --disabled-password --uid $uid --gid $gid --home /home/masq masq \ + && chown -R $uid:$gid /home/masq RUN apt-get update && apt-get install -y sudo curl && rustup component add rustfmt clippy \ && cargo install sccache && chown -R $uid:$gid /usr/local/cargo /usr/local/rustup -USER substratum +USER masq diff --git a/multinode_integration_tests/src/masq_node.rs b/multinode_integration_tests/src/masq_node.rs index 02e6d7cea..5f293828c 100644 --- a/multinode_integration_tests/src/masq_node.rs +++ b/multinode_integration_tests/src/masq_node.rs @@ -6,6 +6,7 @@ use masq_lib::constants::{ CENTRAL_DELIMITER, CHAIN_IDENTIFIER_DELIMITER, CURRENT_LOGFILE_NAME, HIGHEST_USABLE_PORT, MASQ_URL_PREFIX, }; +use masq_lib::utils::to_string; use node_lib::sub_lib::cryptde::{CryptDE, PublicKey}; use node_lib::sub_lib::cryptde_null::CryptDENull; use node_lib::sub_lib::neighborhood::{NodeDescriptor, RatePack}; @@ -81,7 +82,7 @@ impl fmt::Display for NodeReference { Some(node_addr) => node_addr .ports() .iter() - .map(|port| port.to_string()) + .map(to_string) .collect::>() .join(NodeAddr::PORTS_SEPARATOR), None => String::new(), diff --git a/multinode_integration_tests/src/masq_real_node.rs b/multinode_integration_tests/src/masq_real_node.rs index d986f6645..77b82054c 100644 --- a/multinode_integration_tests/src/masq_real_node.rs +++ b/multinode_integration_tests/src/masq_real_node.rs @@ -12,9 +12,9 @@ use log::Level; use masq_lib::blockchains::chains::Chain; use masq_lib::constants::{CURRENT_LOGFILE_NAME, DEFAULT_UI_PORT}; use masq_lib::test_utils::utils::TEST_DEFAULT_MULTINODE_CHAIN; -use masq_lib::utils::localhost; +use masq_lib::utils::{localhost, to_string}; use masq_lib::utils::{DEFAULT_CONSUMING_DERIVATION_PATH, DEFAULT_EARNING_DERIVATION_PATH}; -use node_lib::blockchain::bip32::Bip32ECKeyProvider; +use node_lib::blockchain::bip32::Bip32EncryptionKeyProvider; use node_lib::neighborhood::DEFAULT_MIN_HOPS; use node_lib::sub_lib::accountant::{ PaymentThresholds, DEFAULT_EARNING_WALLET, DEFAULT_PAYMENT_THRESHOLDS, @@ -260,7 +260,7 @@ impl NodeStartupConfig { } fn slices_to_strings(strs: Vec<&str>) -> Vec { - strs.into_iter().map(|x| x.to_string()).collect() + strs.into_iter().map(to_string).collect() } fn make_establish_wallet_args(&self) -> Option> { @@ -377,7 +377,7 @@ impl NodeStartupConfig { EarningWalletInfo::Address(address) => Wallet::from_str(address).unwrap(), EarningWalletInfo::DerivationPath(phrase, derivation_path) => { let mnemonic = Mnemonic::from_phrase(phrase.as_str(), Language::English).unwrap(); - let keypair = Bip32ECKeyProvider::try_from(( + let keypair = Bip32EncryptionKeyProvider::try_from(( Seed::new(&mnemonic, "passphrase").as_ref(), derivation_path.as_str(), )) @@ -392,12 +392,12 @@ impl NodeStartupConfig { ConsumingWalletInfo::None => None, ConsumingWalletInfo::PrivateKey(key) => { let key_bytes = key.from_hex::>().unwrap(); - let keypair = Bip32ECKeyProvider::from_raw_secret(&key_bytes).unwrap(); + let keypair = Bip32EncryptionKeyProvider::from_raw_secret(&key_bytes).unwrap(); Some(Wallet::from(keypair)) } ConsumingWalletInfo::DerivationPath(phrase, derivation_path) => { let mnemonic = Mnemonic::from_phrase(phrase, Language::English).unwrap(); - let keypair = Bip32ECKeyProvider::try_from(( + let keypair = Bip32EncryptionKeyProvider::try_from(( Seed::new(&mnemonic, "passphrase").as_ref(), derivation_path.as_str(), )) @@ -642,7 +642,7 @@ impl NodeStartupConfigBuilder { } pub fn db_password(mut self, value: Option<&str>) -> Self { - self.db_password = value.map(|str| str.to_string()); + self.db_password = value.map(to_string); self } diff --git a/multinode_integration_tests/src/utils.rs b/multinode_integration_tests/src/utils.rs index 10ca36c9f..47f452881 100644 --- a/multinode_integration_tests/src/utils.rs +++ b/multinode_integration_tests/src/utils.rs @@ -5,10 +5,8 @@ use crate::masq_node::{MASQNode, MASQNodeUtils}; use crate::masq_real_node::MASQRealNode; use masq_lib::test_utils::utils::TEST_DEFAULT_MULTINODE_CHAIN; use masq_lib::utils::NeighborhoodModeLight; -use node_lib::accountant::database_access_objects::payable_dao::{PayableDao, PayableDaoReal}; -use node_lib::accountant::database_access_objects::receivable_dao::{ - ReceivableDao, ReceivableDaoReal, -}; +use node_lib::accountant::db_access_objects::payable_dao::{PayableDao, PayableDaoReal}; +use node_lib::accountant::db_access_objects::receivable_dao::{ReceivableDao, ReceivableDaoReal}; use node_lib::database::connection_wrapper::ConnectionWrapper; use node_lib::database::db_initializer::{ DbInitializationConfig, DbInitializer, DbInitializerReal, ExternalData, diff --git a/multinode_integration_tests/tests/blockchain_interaction_test.rs b/multinode_integration_tests/tests/blockchain_interaction_test.rs index 020db13d2..1eb6a3ca7 100644 --- a/multinode_integration_tests/tests/blockchain_interaction_test.rs +++ b/multinode_integration_tests/tests/blockchain_interaction_test.rs @@ -22,7 +22,7 @@ use multinode_integration_tests_lib::utils::{ config_dao, node_chain_specific_data_directory, open_all_file_permissions, receivable_dao, UrlHolder, }; -use node_lib::accountant::database_access_objects::dao_utils::CustomQuery; +use node_lib::accountant::db_access_objects::utils::CustomQuery; use node_lib::sub_lib::wallet::Wallet; #[test] diff --git a/multinode_integration_tests/tests/bookkeeping_test.rs b/multinode_integration_tests/tests/bookkeeping_test.rs index 1de7745a1..41642a724 100644 --- a/multinode_integration_tests/tests/bookkeeping_test.rs +++ b/multinode_integration_tests/tests/bookkeeping_test.rs @@ -6,9 +6,9 @@ use multinode_integration_tests_lib::masq_real_node::{ STANDARD_CLIENT_TIMEOUT_MILLIS, }; use multinode_integration_tests_lib::utils::{payable_dao, receivable_dao}; -use node_lib::accountant::database_access_objects::dao_utils::CustomQuery; -use node_lib::accountant::database_access_objects::payable_dao::PayableAccount; -use node_lib::accountant::database_access_objects::receivable_dao::ReceivableAccount; +use node_lib::accountant::db_access_objects::payable_dao::PayableAccount; +use node_lib::accountant::db_access_objects::receivable_dao::ReceivableAccount; +use node_lib::accountant::db_access_objects::utils::CustomQuery; use node_lib::sub_lib::wallet::Wallet; use std::collections::HashMap; use std::thread; diff --git a/multinode_integration_tests/tests/connection_termination_test.rs b/multinode_integration_tests/tests/connection_termination_test.rs index 1466e2e51..3f0873c3d 100644 --- a/multinode_integration_tests/tests/connection_termination_test.rs +++ b/multinode_integration_tests/tests/connection_termination_test.rs @@ -27,7 +27,6 @@ use node_lib::sub_lib::route::{Route, RouteSegment}; use node_lib::sub_lib::sequence_buffer::SequencedPacket; use node_lib::sub_lib::stream_key::StreamKey; use node_lib::sub_lib::versioned_data::VersionedData; -use node_lib::test_utils::make_meaningless_stream_key; use node_lib::test_utils::neighborhood_test_utils::{db_from_node, make_node_record}; use std::io; use std::net::SocketAddr; @@ -335,7 +334,10 @@ fn context_from_request_lcp( } fn arbitrary_context() -> (StreamKey, u32) { - (make_meaningless_stream_key(), 12345678) + ( + StreamKey::make_meaningful_stream_key("arbitrary_context"), + 12345678, + ) } fn create_request_icp( @@ -391,7 +393,8 @@ fn create_meaningless_icp( exit_node: &MASQRealNode, ) -> IncipientCoresPackage { let socket_addr = SocketAddr::from_str("3.2.1.0:7654").unwrap(); - let stream_key = StreamKey::new(PublicKey::new(&[9, 8, 7, 6]), socket_addr); + let stream_key = + StreamKey::make_meaningful_stream_key("Chancellor on brink of second bailout for banks"); IncipientCoresPackage::new( originating_node.main_cryptde_null().unwrap(), Route::round_trip( diff --git a/multinode_integration_tests/tests/data_routing_test.rs b/multinode_integration_tests/tests/data_routing_test.rs index 61a6d6281..0b3fa9d21 100644 --- a/multinode_integration_tests/tests/data_routing_test.rs +++ b/multinode_integration_tests/tests/data_routing_test.rs @@ -13,7 +13,6 @@ use native_tls::TlsConnector; use native_tls::TlsStream; use node_lib::proxy_server::protocol_pack::ServerImpersonator; use node_lib::proxy_server::server_impersonator_http::ServerImpersonatorHttp; -use node_lib::sub_lib::neighborhood::Hops; use node_lib::test_utils::{handle_connection_error, read_until_timeout}; use std::io::Write; use std::net::{IpAddr, SocketAddr, TcpStream}; @@ -58,7 +57,7 @@ fn http_end_to_end_routing_test() { thread::sleep(Duration::from_millis(500)); - let mut client = last_node.make_client(8080, STANDARD_CLIENT_TIMEOUT_MILLIS); + let mut client = last_node.make_client(8080, 5000); client.send_chunk(b"GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"); let response = client.wait_for_chunk(); @@ -70,50 +69,6 @@ fn http_end_to_end_routing_test() { ); } -fn assert_http_end_to_end_routing_test(min_hops: Hops) { - let mut cluster = MASQNodeCluster::start().unwrap(); - let config = NodeStartupConfigBuilder::standard() - .min_hops(min_hops) - .chain(cluster.chain) - .consuming_wallet_info(make_consuming_wallet_info("first_node")) - .build(); - let first_node = cluster.start_real_node(config); - - let nodes_count = 2 * (min_hops as usize) + 1; - let nodes = (0..nodes_count) - .map(|_| { - cluster.start_real_node( - NodeStartupConfigBuilder::standard() - .neighbor(first_node.node_reference()) - .chain(cluster.chain) - .build(), - ) - }) - .collect::>(); - - thread::sleep(Duration::from_millis(500 * (nodes.len() as u64))); - - let mut client = first_node.make_client(8080, 5000); - client.send_chunk(b"GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"); - let response = client.wait_for_chunk(); - - assert_eq!( - index_of(&response, &b"

Example Domain

"[..]).is_some(), - true, - "Actual response:\n{}", - String::from_utf8(response).unwrap() - ); -} - -#[test] -fn http_end_to_end_routing_test_with_different_min_hops() { - // This test fails sometimes due to a timeout: "Couldn't read chunk: Kind(TimedOut)" - // You may fix it by increasing the timeout for the client. - assert_http_end_to_end_routing_test(Hops::OneHop); - assert_http_end_to_end_routing_test(Hops::TwoHops); - assert_http_end_to_end_routing_test(Hops::SixHops); -} - #[test] fn http_end_to_end_routing_test_with_consume_and_originate_only_nodes() { let mut cluster = MASQNodeCluster::start().unwrap(); diff --git a/multinode_integration_tests/tests/min_hops_tests.rs b/multinode_integration_tests/tests/min_hops_tests.rs new file mode 100644 index 000000000..ed0ef34f4 --- /dev/null +++ b/multinode_integration_tests/tests/min_hops_tests.rs @@ -0,0 +1,123 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use masq_lib::messages::{ToMessageBody, UiSetConfigurationRequest}; +use masq_lib::utils::{find_free_port, index_of}; +use multinode_integration_tests_lib::masq_node::MASQNode; +use multinode_integration_tests_lib::masq_node_cluster::MASQNodeCluster; +use multinode_integration_tests_lib::masq_real_node::{ + make_consuming_wallet_info, MASQRealNode, NodeStartupConfigBuilder, +}; +use node_lib::sub_lib::neighborhood::Hops; +use std::thread; +use std::time::Duration; + +#[test] +fn data_can_be_routed_using_different_min_hops() { + // This test fails sometimes due to a timeout: "Couldn't read chunk: Kind(TimedOut)" + // You may fix it by increasing the timeout for the client. + assert_http_end_to_end_routing(Hops::OneHop); + assert_http_end_to_end_routing(Hops::TwoHops); + assert_http_end_to_end_routing(Hops::SixHops); +} + +fn assert_http_end_to_end_routing(min_hops: Hops) { + let mut cluster = MASQNodeCluster::start().unwrap(); + let config = NodeStartupConfigBuilder::standard() + .min_hops(min_hops) + .chain(cluster.chain) + .consuming_wallet_info(make_consuming_wallet_info("first_node")) + .build(); + let first_node = cluster.start_real_node(config); + + // For 1-hop route, 3 nodes are necessary if we use last node as the originating node + let nodes_count = (min_hops as usize) + 2; + let nodes = (0..nodes_count) + .map(|i| { + cluster.start_real_node( + NodeStartupConfigBuilder::standard() + .neighbor(first_node.node_reference()) + .consuming_wallet_info(make_consuming_wallet_info(&format!("node_{i}"))) + .chain(cluster.chain) + .build(), + ) + }) + .collect::>(); + + thread::sleep(Duration::from_millis(500 * (nodes.len() as u64))); + + let last_node = nodes.last().unwrap(); + let mut client = last_node.make_client(8080, 5000); + client.send_chunk(b"GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"); + let response = client.wait_for_chunk(); + + assert_eq!( + index_of(&response, &b"

Example Domain

"[..]).is_some(), + true, + "Actual response:\n{}", + String::from_utf8(response).unwrap() + ); +} + +#[test] +fn min_hops_can_be_changed_during_runtime() { + let initial_min_hops = Hops::OneHop; + let new_min_hops = Hops::TwoHops; + let mut cluster = MASQNodeCluster::start().unwrap(); + let ui_port = find_free_port(); + let first_node_config = NodeStartupConfigBuilder::standard() + .min_hops(initial_min_hops) + .chain(cluster.chain) + .consuming_wallet_info(make_consuming_wallet_info("first_node")) + .ui_port(ui_port) + .build(); + let first_node = cluster.start_real_node(first_node_config); + let ui_client = first_node.make_ui(ui_port); + + for _ in 0..initial_min_hops as u8 { + cluster.start_real_node( + NodeStartupConfigBuilder::standard() + .neighbor(first_node.node_reference()) + .chain(cluster.chain) + .build(), + ); + } + thread::sleep(Duration::from_millis(1000)); + + let mut client = first_node.make_client(8080, 5000); + client.send_chunk(b"GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"); + let response = client.wait_for_chunk(); + + // Client shutdown is necessary to re-initialize stream keys for old requests + client.shutdown(); + + assert_eq!( + index_of(&response, &b"

Example Domain

"[..]).is_some(), + true, + "Actual response:\n{}", + String::from_utf8(response).unwrap() + ); + + ui_client.send_request( + UiSetConfigurationRequest { + name: "min-hops".to_string(), + value: new_min_hops.to_string(), + } + .tmb(1), + ); + let response = ui_client.wait_for_response(1, Duration::from_secs(2)); + assert!(response.payload.is_ok()); + + let mut client = first_node.make_client(8080, 5000); + client.send_chunk(b"GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n"); + let response = client.wait_for_chunk(); + assert_eq!( + index_of( + &response, + &b"

Subtitle: Can't find a route to www.example.com

"[..] + ) + .is_some(), + true, + "Actual response:\n{}", + String::from_utf8(response).unwrap() + ); +} diff --git a/multinode_integration_tests/tests/verify_bill_payment.rs b/multinode_integration_tests/tests/verify_bill_payment.rs index 95d1ccbb2..5e9b50347 100644 --- a/multinode_integration_tests/tests/verify_bill_payment.rs +++ b/multinode_integration_tests/tests/verify_bill_payment.rs @@ -13,14 +13,13 @@ use multinode_integration_tests_lib::masq_real_node::{ use multinode_integration_tests_lib::utils::{ node_chain_specific_data_directory, open_all_file_permissions, UrlHolder, }; -use node_lib::accountant::database_access_objects::payable_dao::{PayableDao, PayableDaoReal}; -use node_lib::accountant::database_access_objects::receivable_dao::{ - ReceivableDao, ReceivableDaoReal, -}; -use node_lib::blockchain::bip32::Bip32ECKeyProvider; -use node_lib::blockchain::blockchain_interface::{ - BlockchainInterface, BlockchainInterfaceWeb3, REQUESTS_IN_PARALLEL, +use node_lib::accountant::db_access_objects::payable_dao::{PayableDao, PayableDaoReal}; +use node_lib::accountant::db_access_objects::receivable_dao::{ReceivableDao, ReceivableDaoReal}; +use node_lib::blockchain::bip32::Bip32EncryptionKeyProvider; +use node_lib::blockchain::blockchain_interface::blockchain_interface_web3::{ + BlockchainInterfaceWeb3, REQUESTS_IN_PARALLEL, }; +use node_lib::blockchain::blockchain_interface::BlockchainInterface; use node_lib::database::db_initializer::{ DbInitializationConfig, DbInitializer, DbInitializerReal, ExternalData, }; @@ -49,7 +48,7 @@ fn verify_bill_payment() { blockchain_server.start(); blockchain_server.wait_until_ready(); let url = blockchain_server.url().to_string(); - let (_event_loop_handle, http) = Http::with_max_parallel(&url, REQUESTS_IN_PARALLEL).unwrap(); + let (event_loop_handle, http) = Http::with_max_parallel(&url, REQUESTS_IN_PARALLEL).unwrap(); let web3 = Web3::new(http.clone()); let deriv_path = derivation_path(0, 0); let seed = make_seed(); @@ -61,8 +60,7 @@ fn verify_bill_payment() { "Ganache is not as predictable as we thought: Update blockchain_interface::MULTINODE_CONTRACT_ADDRESS with {:?}", contract_addr ); - let blockchain_interface = - BlockchainInterfaceWeb3::new(http, _event_loop_handle, cluster.chain); + let blockchain_interface = BlockchainInterfaceWeb3::new(http, event_loop_handle, cluster.chain); assert_balances( &contract_owner_wallet, &blockchain_interface, @@ -326,6 +324,7 @@ fn assert_balances( expected_token_balance: &str, ) { let eth_balance = blockchain_interface + .lower_interface() .get_transaction_fee_balance(&wallet) .unwrap_or_else(|_| panic!("Failed to retrieve gas balance for {}", wallet)); assert_eq!( @@ -336,8 +335,9 @@ fn assert_balances( expected_eth_balance ); let token_balance = blockchain_interface - .get_token_balance(&wallet) - .unwrap_or_else(|_| panic!("Failed to retrieve token balance for {}", wallet)); + .lower_interface() + .get_service_fee_balance(&wallet) + .unwrap_or_else(|_| panic!("Failed to retrieve masq balance for {}", wallet)); assert_eq!( token_balance, web3::types::U256::from_dec_str(expected_token_balance).unwrap(), @@ -393,7 +393,7 @@ fn make_node_wallet(seed: &Seed, derivation_path: &str) -> (Wallet, String) { let secret = extended_priv_key.secret().to_hex::(); ( - Wallet::from(Bip32ECKeyProvider::from_key(extended_priv_key)), + Wallet::from(Bip32EncryptionKeyProvider::from_key(extended_priv_key)), secret, ) } diff --git a/node/Cargo.lock b/node/Cargo.lock index acfbd14ba..67aed1891 100644 --- a/node/Cargo.lock +++ b/node/Cargo.lock @@ -2157,6 +2157,7 @@ dependencies = [ "trust-dns-proto 0.8.0", "trust-dns-resolver 0.12.0", "unindent", + "uuid", "variant_count", "web3", "websocket", diff --git a/node/Cargo.toml b/node/Cargo.toml index aac0d3b57..3635fc1de 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -64,6 +64,7 @@ variant_count = "1.1.0" web3 = {version = "0.11.0", default-features = false, features = ["http", "tls"]} websocket = {version = "0.26.2", default-features = false, features = ["async", "sync"]} secp256k1secrets = {package = "secp256k1", version = "0.17.2"} +uuid = "0.7.4" [target.'cfg(target_os = "macos")'.dependencies] system-configuration = "0.4.0" diff --git a/node/src/accountant/database_access_objects/banned_dao.rs b/node/src/accountant/db_access_objects/banned_dao.rs similarity index 99% rename from node/src/accountant/database_access_objects/banned_dao.rs rename to node/src/accountant/db_access_objects/banned_dao.rs index 44088e973..24b3ced81 100644 --- a/node/src/accountant/database_access_objects/banned_dao.rs +++ b/node/src/accountant/db_access_objects/banned_dao.rs @@ -1,7 +1,6 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::database_access_objects::dao_utils::{ - DaoFactoryReal, VigilantRusqliteFlatten, -}; + +use crate::accountant::db_access_objects::utils::{DaoFactoryReal, VigilantRusqliteFlatten}; use crate::database::connection_wrapper::ConnectionWrapper; use crate::sub_lib::wallet::Wallet; use lazy_static::lazy_static; diff --git a/node/src/accountant/database_access_objects/mod.rs b/node/src/accountant/db_access_objects/mod.rs similarity index 90% rename from node/src/accountant/database_access_objects/mod.rs rename to node/src/accountant/db_access_objects/mod.rs index 7c4fc5ffb..a350148ab 100644 --- a/node/src/accountant/database_access_objects/mod.rs +++ b/node/src/accountant/db_access_objects/mod.rs @@ -1,7 +1,7 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. pub mod banned_dao; -pub mod dao_utils; pub mod payable_dao; pub mod pending_payable_dao; pub mod receivable_dao; +pub mod utils; diff --git a/node/src/accountant/database_access_objects/payable_dao.rs b/node/src/accountant/db_access_objects/payable_dao.rs similarity index 97% rename from node/src/accountant/database_access_objects/payable_dao.rs rename to node/src/accountant/db_access_objects/payable_dao.rs index fe89ed1e0..16f72d5d4 100644 --- a/node/src/accountant/database_access_objects/payable_dao.rs +++ b/node/src/accountant/db_access_objects/payable_dao.rs @@ -1,21 +1,21 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::big_int_processing::big_int_db_processor::KnownKeyVariants::{ +use crate::accountant::db_big_integer::big_int_db_processor::KnownKeyVariants::{ PendingPayableRowid, WalletAddress, }; -use crate::accountant::big_int_processing::big_int_db_processor::WeiChange::{ +use crate::accountant::db_big_integer::big_int_db_processor::WeiChange::{ Addition, Subtraction, }; -use crate::accountant::big_int_processing::big_int_db_processor::{ +use crate::accountant::db_big_integer::big_int_db_processor::{ BigIntDbProcessor, BigIntSqlConfig, Param, SQLParamsBuilder, TableNameDAO, }; -use crate::accountant::big_int_processing::big_int_divider::BigIntDivider; -use crate::accountant::database_access_objects::dao_utils; -use crate::accountant::database_access_objects::dao_utils::{ +use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; +use crate::accountant::db_access_objects::utils; +use crate::accountant::db_access_objects::utils::{ sum_i128_values_from_table, to_time_t, AssemblerFeeder, CustomQuery, DaoFactoryReal, RangeStmConfig, TopStmConfig, VigilantRusqliteFlatten, }; -use crate::accountant::database_access_objects::payable_dao::mark_pending_payable_associated_functions::{ +use crate::accountant::db_access_objects::payable_dao::mark_pending_payable_associated_functions::{ compose_case_expression, execute_command, serialize_wallets, }; use crate::accountant::{checked_conversion, sign_conversion, PendingPayableId}; @@ -48,21 +48,6 @@ pub struct PayableAccount { pub pending_payable_opt: Option, } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct PendingPayable { - pub recipient_wallet: Wallet, - pub hash: H256, -} - -impl PendingPayable { - pub fn new(recipient_wallet: Wallet, hash: H256) -> Self { - Self { - recipient_wallet, - hash, - } - } -} - pub trait PayableDao: Debug + Send { fn more_money_payable( &self, @@ -207,7 +192,7 @@ impl PayableDao for PayableDaoReal { balance_wei: checked_conversion::(BigIntDivider::reconstitute( high_b, low_b, )), - last_paid_timestamp: dao_utils::from_time_t(last_paid_timestamp), + last_paid_timestamp: utils::from_time_t(last_paid_timestamp), pending_payable_opt: None, }) } @@ -293,7 +278,7 @@ impl PayableDao for PayableDaoReal { balance_wei: checked_conversion::(BigIntDivider::reconstitute( high_bytes, low_bytes, )), - last_paid_timestamp: dao_utils::from_time_t(last_paid_timestamp), + last_paid_timestamp: utils::from_time_t(last_paid_timestamp), pending_payable_opt: match rowid { Some(rowid) => Some(PendingPayableId::new( u64::try_from(rowid).unwrap(), @@ -349,7 +334,7 @@ impl PayableDaoReal { balance_wei: checked_conversion::(BigIntDivider::reconstitute( high_bytes, low_bytes, )), - last_paid_timestamp: dao_utils::from_time_t(last_paid_timestamp), + last_paid_timestamp: utils::from_time_t(last_paid_timestamp), pending_payable_opt: rowid_opt.map(|rowid| { let hash_str = hash_opt.expect("database corrupt; missing hash but existing rowid"); @@ -402,10 +387,10 @@ impl TableNameDAO for PayableDaoReal { mod mark_pending_payable_associated_functions { use crate::accountant::comma_joined_stringifiable; - use crate::accountant::database_access_objects::dao_utils::{ + use crate::accountant::db_access_objects::payable_dao::PayableDaoError; + use crate::accountant::db_access_objects::utils::{ update_rows_and_return_valid_count, VigilantRusqliteFlatten, }; - use crate::accountant::database_access_objects::payable_dao::PayableDaoError; use crate::database::connection_wrapper::ConnectionWrapper; use crate::sub_lib::wallet::Wallet; use itertools::Itertools; @@ -552,9 +537,9 @@ mod mark_pending_payable_associated_functions { #[cfg(test)] mod tests { use super::*; - use crate::accountant::database_access_objects::dao_utils::{from_time_t, now_time_t, to_time_t}; + use crate::accountant::db_access_objects::utils::{from_time_t, now_time_t, to_time_t}; use crate::accountant::gwei_to_wei; - use crate::accountant::database_access_objects::payable_dao::mark_pending_payable_associated_functions::explanatory_extension; + use crate::accountant::db_access_objects::payable_dao::mark_pending_payable_associated_functions::explanatory_extension; use crate::accountant::test_utils::{ assert_account_creation_fn_fails_on_finding_wrong_columns_and_value_types, make_pending_payable_fingerprint, @@ -1585,7 +1570,7 @@ mod tests { let conn = DbInitializerReal::default() .initialize(&home_dir, DbInitializationConfig::test_default()) .unwrap(); - let timestamp = dao_utils::now_time_t(); + let timestamp = utils::now_time_t(); insert_payable_record_fn( &*conn, "0x1111111111111111111111111111111111111111", diff --git a/node/src/accountant/database_access_objects/pending_payable_dao.rs b/node/src/accountant/db_access_objects/pending_payable_dao.rs similarity index 92% rename from node/src/accountant/database_access_objects/pending_payable_dao.rs rename to node/src/accountant/db_access_objects/pending_payable_dao.rs index 582c12a36..a918c7175 100644 --- a/node/src/accountant/database_access_objects/pending_payable_dao.rs +++ b/node/src/accountant/db_access_objects/pending_payable_dao.rs @@ -1,12 +1,13 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::big_int_processing::big_int_divider::BigIntDivider; -use crate::accountant::database_access_objects::dao_utils::{ +use crate::accountant::db_access_objects::utils::{ from_time_t, to_time_t, DaoFactoryReal, VigilantRusqliteFlatten, }; +use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::accountant::{checked_conversion, comma_joined_stringifiable}; use crate::blockchain::blockchain_bridge::PendingPayableFingerprint; use crate::database::connection_wrapper::ConnectionWrapper; +use crate::sub_lib::wallet::Wallet; use itertools::Itertools; use masq_lib::utils::ExpectValue; use rusqlite::Row; @@ -26,6 +27,7 @@ pub enum PendingPayableDaoError { } pub trait PendingPayableDao { + // Note that the order of the returned results is not guaranteed fn fingerprints_rowids(&self, hashes: &[H256]) -> Vec<(Option, H256)>; fn return_all_errorless_fingerprints(&self) -> Vec; fn insert_new_fingerprints( @@ -65,9 +67,7 @@ impl PendingPayableDao for PendingPayableDaoReal<'_> { .filter(|hash| !all_found_records.keys().contains(hash)); all_found_records .iter() - .sorted() .map(|(hash, rowid)| (Some(*rowid), *hash)) - .rev() .chain(hashes_of_missing_rowids.map(|hash| (None, *hash))) .collect() } @@ -210,13 +210,18 @@ impl PendingPayableDao for PendingPayableDaoReal<'_> { } } -pub trait PendingPayableDaoFactory { - fn make(&self) -> Box; +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PendingPayable { + pub recipient_wallet: Wallet, + pub hash: H256, } -impl PendingPayableDaoFactory for DaoFactoryReal { - fn make(&self) -> Box { - Box::new(PendingPayableDaoReal::new(self.make_connection())) +impl PendingPayable { + pub fn new(recipient_wallet: Wallet, hash: H256) -> Self { + Self { + recipient_wallet, + hash, + } } } @@ -239,14 +244,24 @@ impl<'a> PendingPayableDaoReal<'a> { } } +pub trait PendingPayableDaoFactory { + fn make(&self) -> Box; +} + +impl PendingPayableDaoFactory for DaoFactoryReal { + fn make(&self) -> Box { + Box::new(PendingPayableDaoReal::new(self.make_connection())) + } +} + #[cfg(test)] mod tests { - use crate::accountant::big_int_processing::big_int_divider::BigIntDivider; use crate::accountant::checked_conversion; - use crate::accountant::database_access_objects::dao_utils::from_time_t; - use crate::accountant::database_access_objects::pending_payable_dao::{ + use crate::accountant::db_access_objects::pending_payable_dao::{ PendingPayableDao, PendingPayableDaoError, PendingPayableDaoReal, }; + use crate::accountant::db_access_objects::utils::from_time_t; + use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::blockchain::blockchain_bridge::PendingPayableFingerprint; use crate::blockchain::test_utils::make_tx_hash; use crate::database::connection_wrapper::ConnectionWrapperReal; @@ -388,14 +403,28 @@ mod tests { let result = subject.fingerprints_rowids(&[hash_1, hash_2]); - assert_eq!(result, vec![(Some(1), hash_1), (Some(2), hash_2)]) + let first_expected_pair = (Some(1), hash_1); + assert!( + result.contains(&first_expected_pair), + "Returned rowid pairs should have contained {:?} but all it did is {:?}", + first_expected_pair, + result + ); + let second_expected_pair = (Some(2), hash_2); + assert!( + result.contains(&second_expected_pair), + "Returned rowid pairs should have contained {:?} but all it did is {:?}", + second_expected_pair, + result + ); + assert_eq!(result.len(), 2); } #[test] - fn fingerprints_rowids_when_nonexistent_record() { + fn fingerprints_rowids_when_nonexistent_records() { let home_dir = ensure_node_home_directory_exists( "pending_payable_dao", - "fingerprints_rowids_when_nonexistent_record", + "fingerprints_rowids_when_nonexistent_records", ); let wrapped_conn = DbInitializerReal::default() .initialize(&home_dir, DbInitializationConfig::test_default()) @@ -403,10 +432,30 @@ mod tests { let subject = PendingPayableDaoReal::new(wrapped_conn); let hash_1 = make_tx_hash(11119); let hash_2 = make_tx_hash(22229); + let hash_3 = make_tx_hash(33339); + let hash_4 = make_tx_hash(44449); + // For more illustrative results, I use the official tooling but also generate one extra record before the chief one for + // this test, and in the end, I delete the first one. It leaves a single record still in but with the rowid 2 instead of + // just an ambiguous 1 + subject + .insert_new_fingerprints(&[(hash_2, 8901234)], SystemTime::now()) + .unwrap(); + subject + .insert_new_fingerprints(&[(hash_3, 1234567)], SystemTime::now()) + .unwrap(); + subject.delete_fingerprints(&[1]).unwrap(); - let result = subject.fingerprints_rowids(&[hash_1, hash_2]); + let result = subject.fingerprints_rowids(&[hash_1, hash_2, hash_3, hash_4]); - assert_eq!(result, vec![(None, hash_1), (None, hash_2)]) + assert_eq!( + result, + vec![ + (Some(2), hash_3), + (None, hash_1), + (None, hash_2), + (None, hash_4) + ] + ) } #[test] diff --git a/node/src/accountant/database_access_objects/receivable_dao.rs b/node/src/accountant/db_access_objects/receivable_dao.rs similarity index 98% rename from node/src/accountant/database_access_objects/receivable_dao.rs rename to node/src/accountant/db_access_objects/receivable_dao.rs index be626d1bb..14fe4b4e9 100644 --- a/node/src/accountant/database_access_objects/receivable_dao.rs +++ b/node/src/accountant/db_access_objects/receivable_dao.rs @@ -1,22 +1,20 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::big_int_processing::big_int_db_processor::KnownKeyVariants::WalletAddress; -use crate::accountant::big_int_processing::big_int_db_processor::WeiChange::{ - Addition, Subtraction, -}; -use crate::accountant::big_int_processing::big_int_db_processor::{ - BigIntDbProcessor, BigIntSqlConfig, Param, SQLParamsBuilder, TableNameDAO, -}; -use crate::accountant::big_int_processing::big_int_divider::BigIntDivider; use crate::accountant::checked_conversion; -use crate::accountant::database_access_objects::dao_utils; -use crate::accountant::database_access_objects::dao_utils::{ +use crate::accountant::db_access_objects::receivable_dao::ReceivableDaoError::RusqliteError; +use crate::accountant::db_access_objects::utils; +use crate::accountant::db_access_objects::utils::{ sum_i128_values_from_table, to_time_t, AssemblerFeeder, CustomQuery, DaoFactoryReal, RangeStmConfig, ThresholdUtils, TopStmConfig, VigilantRusqliteFlatten, }; -use crate::accountant::database_access_objects::receivable_dao::ReceivableDaoError::RusqliteError; +use crate::accountant::db_big_integer::big_int_db_processor::KnownKeyVariants::WalletAddress; +use crate::accountant::db_big_integer::big_int_db_processor::WeiChange::{Addition, Subtraction}; +use crate::accountant::db_big_integer::big_int_db_processor::{ + BigIntDbProcessor, BigIntSqlConfig, Param, SQLParamsBuilder, TableNameDAO, +}; +use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::accountant::gwei_to_wei; -use crate::blockchain::blockchain_interface::BlockchainTransaction; +use crate::blockchain::blockchain_interface::data_structures::BlockchainTransaction; use crate::database::connection_wrapper::ConnectionWrapper; use crate::database::db_initializer::{connection_or_panic, DbInitializerReal}; use crate::db_config::persistent_configuration::PersistentConfigError; @@ -31,8 +29,6 @@ use masq_lib::utils::{plus, ExpectValue}; use rusqlite::OptionalExtension; use rusqlite::Row; use rusqlite::{named_params, Error}; -#[cfg(test)] -use std::any::Any; use std::time::SystemTime; #[derive(Debug, PartialEq, Eq)] @@ -86,7 +82,7 @@ pub trait ReceivableDao: Send { //test-only-like method but because of share with multi-node tests #[cfg(test)] is disallowed fn account_status(&self, wallet: &Wallet) -> Option; - declare_as_any!(); + as_any_in_trait!(); } pub trait ReceivableDaoFactory { @@ -269,7 +265,7 @@ impl ReceivableDao for ReceivableDaoReal { } } - implement_as_any!(); + as_any_in_trait_impl!(); } impl ReceivableDaoReal { @@ -335,7 +331,7 @@ impl ReceivableDaoReal { Ok(ReceivableAccount { wallet, balance_wei: BigIntDivider::reconstitute(high_bytes, low_bytes), - last_received_timestamp: dao_utils::from_time_t(last_received_timestamp), + last_received_timestamp: utils::from_time_t(last_received_timestamp), }) } e => panic!( @@ -413,16 +409,17 @@ impl TableNameDAO for ReceivableDaoReal { #[cfg(test)] mod tests { use super::*; - use crate::accountant::database_access_objects::dao_utils::{ - from_time_t, now_time_t, to_time_t, + use crate::accountant::db_access_objects::utils::{ + from_time_t, now_time_t, to_time_t, CustomQuery, }; use crate::accountant::gwei_to_wei; use crate::accountant::test_utils::{ assert_account_creation_fn_fails_on_finding_wrong_columns_and_value_types, make_receivable_account, }; - use crate::database::db_initializer::{DbInitializationConfig, DbInitializer}; - use crate::database::db_initializer::{DbInitializerReal, ExternalData}; + use crate::database::db_initializer::{ + DbInitializationConfig, DbInitializer, DbInitializerReal, ExternalData, + }; use crate::db_config::persistent_configuration::PersistentConfigError; use crate::test_utils::assert_contains; use crate::test_utils::make_wallet; @@ -1438,7 +1435,7 @@ mod tests { .unwrap(); let insert = insert_account_by_separate_values; - let timestamp = dao_utils::now_time_t(); + let timestamp = utils::now_time_t(); insert( &*conn, "0x1111111111111111111111111111111111111111", diff --git a/node/src/accountant/database_access_objects/dao_utils.rs b/node/src/accountant/db_access_objects/utils.rs similarity index 98% rename from node/src/accountant/database_access_objects/dao_utils.rs rename to node/src/accountant/db_access_objects/utils.rs index 99bd572d0..e7acb8f9d 100644 --- a/node/src/accountant/database_access_objects/dao_utils.rs +++ b/node/src/accountant/db_access_objects/utils.rs @@ -1,8 +1,8 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::big_int_processing::big_int_divider::BigIntDivider; -use crate::accountant::database_access_objects::payable_dao::PayableAccount; -use crate::accountant::database_access_objects::receivable_dao::ReceivableAccount; +use crate::accountant::db_access_objects::payable_dao::PayableAccount; +use crate::accountant::db_access_objects::receivable_dao::ReceivableAccount; +use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::accountant::{checked_conversion, gwei_to_wei, sign_conversion}; use crate::database::connection_wrapper::ConnectionWrapper; use crate::database::db_initializer::{ @@ -289,11 +289,11 @@ pub fn remap_receivable_accounts(accounts: Vec) -> Vec, + consuming_wallet_opt: Option, earning_wallet: Rc, payable_dao: Box, receivable_dao: Box, pending_payable_dao: Box, crashable: bool, scanners: Scanners, - scan_timings: ScanTimings, + scan_schedulers: ScanSchedulers, financial_statistics: Rc>, - report_accounts_payable_sub_opt: Option>, - request_balances_to_pay_payables_sub_opt: Option>, + outbound_payments_instructions_sub_opt: Option>, + qualified_payables_sub_opt: Option>, retrieve_transactions_sub_opt: Option>, request_transaction_receipts_subs_opt: Option>, report_inbound_payments_sub_opt: Option>, @@ -133,13 +132,6 @@ pub struct SentPayables { pub response_skeleton_opt: Option, } -#[derive(Debug, Message, PartialEq, Eq)] -pub struct ConsumingWalletBalancesAndQualifiedPayables { - pub qualified_payables: Vec, - pub consuming_wallet_balances: ConsumingWalletBalances, - pub response_skeleton_opt: Option, -} - #[derive(Debug, Message, Default, PartialEq, Eq, Clone, Copy)] pub struct ScanForPayables { pub response_skeleton_opt: Option, @@ -212,23 +204,15 @@ impl Handler for Accountant { } } -impl Handler for Accountant { +impl Handler for Accountant { type Result = (); fn handle( &mut self, - msg: ConsumingWalletBalancesAndQualifiedPayables, + msg: BlockchainAgentWithContextMessage, _ctx: &mut Self::Context, ) -> Self::Result { - //TODO GH-672 with PaymentAdjuster hasn't been implemented yet - self.report_accounts_payable_sub_opt - .as_ref() - .expect("BlockchainBridge is unbound") - .try_send(ReportAccountsPayable { - accounts: msg.qualified_payables, - response_skeleton_opt: msg.response_skeleton_opt, - }) - .expect("BlockchainBridge is dead") + self.handle_payable_payment_setup(msg) } } @@ -251,9 +235,7 @@ impl Handler for Accountant { fn handle(&mut self, msg: ScanForPayables, ctx: &mut Self::Context) -> Self::Result { self.handle_request_of_scan_for_payable(msg.response_skeleton_opt); - self.scan_timings - .payable - .schedule_another_periodic_scan(ctx); + self.schedule_next_scan(ScanType::Payables, ctx); } } @@ -262,9 +244,7 @@ impl Handler for Accountant { fn handle(&mut self, msg: ScanForPendingPayables, ctx: &mut Self::Context) -> Self::Result { self.handle_request_of_scan_for_pending_payable(msg.response_skeleton_opt); - self.scan_timings - .pending_payable - .schedule_another_periodic_scan(ctx); + self.schedule_next_scan(ScanType::PendingPayables, ctx); } } @@ -273,9 +253,7 @@ impl Handler for Accountant { fn handle(&mut self, msg: ScanForReceivables, ctx: &mut Self::Context) -> Self::Result { self.handle_request_of_scan_for_receivable(msg.response_skeleton_opt); - self.scan_timings - .receivable - .schedule_another_periodic_scan(ctx); + self.schedule_next_scan(ScanType::Receivables, ctx); } } @@ -444,17 +422,17 @@ impl Accountant { Accountant { suppress_initial_scans: config.suppress_initial_scans, - consuming_wallet: config.consuming_wallet_opt.clone(), + consuming_wallet_opt: config.consuming_wallet_opt.clone(), earning_wallet: Rc::clone(&earning_wallet), payable_dao, receivable_dao, pending_payable_dao, scanners, crashable: config.crash_point == CrashPoint::Message, - scan_timings: ScanTimings::new(scan_intervals), + scan_schedulers: ScanSchedulers::new(scan_intervals), financial_statistics: Rc::clone(&financial_statistics), - report_accounts_payable_sub_opt: None, - request_balances_to_pay_payables_sub_opt: None, + outbound_payments_instructions_sub_opt: None, + qualified_payables_sub_opt: None, report_sent_payables_sub_opt: None, retrieve_transactions_sub_opt: None, report_inbound_payments_sub_opt: None, @@ -472,10 +450,7 @@ impl Accountant { report_routing_service_provided: recipient!(addr, ReportRoutingServiceProvidedMessage), report_exit_service_provided: recipient!(addr, ReportExitServiceProvidedMessage), report_services_consumed: recipient!(addr, ReportServicesConsumedMessage), - report_consuming_wallet_balances_and_qualified_payables: recipient!( - addr, - ConsumingWalletBalancesAndQualifiedPayables - ), + report_payable_payments_setup: recipient!(addr, BlockchainAgentWithContextMessage), report_inbound_payments: recipient!(addr, ReceivedPayments), init_pending_payable_fingerprints: recipient!(addr, PendingPayableFingerprintSeeds), report_transaction_receipts: recipient!(addr, ReportTransactionReceipts), @@ -559,24 +534,24 @@ impl Accountant { } fn our_wallet(&self, wallet: &Wallet) -> bool { - match &self.consuming_wallet { + match &self.consuming_wallet_opt { Some(ref consuming) if consuming.address() == wallet.address() => true, _ => wallet.address() == self.earning_wallet.address(), } } fn handle_bind_message(&mut self, msg: BindMessage) { - self.report_accounts_payable_sub_opt = - Some(msg.peer_actors.blockchain_bridge.report_accounts_payable); + self.outbound_payments_instructions_sub_opt = Some( + msg.peer_actors + .blockchain_bridge + .outbound_payments_instructions, + ); self.retrieve_transactions_sub_opt = Some(msg.peer_actors.blockchain_bridge.retrieve_transactions); self.report_inbound_payments_sub_opt = Some(msg.peer_actors.accountant.report_inbound_payments); - self.request_balances_to_pay_payables_sub_opt = Some( - msg.peer_actors - .blockchain_bridge - .request_balances_to_pay_payables, - ); + self.qualified_payables_sub_opt = + Some(msg.peer_actors.blockchain_bridge.qualified_payables); self.report_sent_payables_sub_opt = Some(msg.peer_actors.accountant.report_sent_payments); self.ui_message_sub_opt = Some(msg.peer_actors.ui_gateway.node_to_ui_message_sub); self.request_transaction_receipts_subs_opt = Some( @@ -587,6 +562,14 @@ impl Accountant { info!(self.logger, "Accountant bound"); } + fn schedule_next_scan(&self, scan_type: ScanType, ctx: &mut Context) { + self.scan_schedulers + .schedulers + .get(&scan_type) + .unwrap_or_else(|| panic!("Scan Scheduler {:?} not properly prepared", scan_type)) + .schedule(ctx) + } + fn handle_report_routing_service_provided_message( &mut self, msg: ReportRoutingServiceProvidedMessage, @@ -667,6 +650,29 @@ impl Accountant { }) } + fn handle_payable_payment_setup(&mut self, msg: BlockchainAgentWithContextMessage) { + let blockchain_bridge_instructions = match self + .scanners + .payable + .try_skipping_payment_adjustment(msg, &self.logger) + { + Ok(Either::Left(finalized_msg)) => finalized_msg, + Ok(Either::Right(unaccepted_msg)) => { + //TODO we will eventually query info from Neighborhood before the adjustment, according to GH-699 + self.scanners + .payable + .perform_payment_adjustment(unaccepted_msg, &self.logger) + } + Err(_e) => todo!("be completed by GH-711"), + }; + self.outbound_payments_instructions_sub_opt + .as_ref() + .expect("BlockchainBridge is unbound") + .try_send(blockchain_bridge_instructions) + .expect("BlockchainBridge is dead") + //TODO implement send point for ScanError; be completed by GH-711 + } + fn handle_financials(&self, msg: &UiFinancialsRequest, client_id: u64, context_id: u64) { let body: MessageBody = self.compute_financials(msg, context_id); self.ui_message_sub_opt @@ -805,7 +811,7 @@ impl Accountant { &self.logger, ) { Ok(scan_message) => { - self.request_balances_to_pay_payables_sub_opt + self.qualified_payables_sub_opt .as_ref() .expect("BlockchainBridge is unbound") .try_send(scan_message) @@ -975,68 +981,46 @@ pub fn wei_to_gwei, S: Display + Copy + Div + From(wei.div(S::from(WEIS_IN_GWEI as u32))) } -#[cfg(test)] -pub mod check_sqlite_fns { - use super::*; - use crate::sub_lib::accountant::DEFAULT_PAYMENT_THRESHOLDS; - use actix::System; - - #[derive(Message)] - pub struct TestUserDefinedSqliteFnsForNewDelinquencies {} - - impl Handler for Accountant { - type Result = (); - - fn handle( - &mut self, - _msg: TestUserDefinedSqliteFnsForNewDelinquencies, - _ctx: &mut Self::Context, - ) -> Self::Result { - //will crash a test if our user-defined SQLite fns have been unregistered - self.receivable_dao - .new_delinquencies(SystemTime::now(), &DEFAULT_PAYMENT_THRESHOLDS); - System::current().stop(); - } - } -} - #[cfg(test)] mod tests { use super::*; - use crate::accountant::database_access_objects::dao_utils::from_time_t; - use crate::accountant::database_access_objects::dao_utils::{to_time_t, CustomQuery}; - use crate::accountant::database_access_objects::payable_dao::{ - PayableAccount, PayableDaoError, PayableDaoFactory, PendingPayable, + use crate::accountant::db_access_objects::payable_dao::{ + PayableAccount, PayableDaoError, PayableDaoFactory, + }; + use crate::accountant::db_access_objects::pending_payable_dao::{ + PendingPayable, PendingPayableDaoError, }; - use crate::accountant::database_access_objects::pending_payable_dao::PendingPayableDaoError; - use crate::accountant::database_access_objects::receivable_dao::ReceivableAccount; - use crate::accountant::scanners::{BeginScanError, NullScanner, ScannerMock}; + use crate::accountant::db_access_objects::receivable_dao::ReceivableAccount; + use crate::accountant::db_access_objects::utils::{from_time_t, to_time_t, CustomQuery}; + use crate::accountant::payment_adjuster::Adjustment; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::test_utils::BlockchainAgentMock; + use crate::accountant::scanners::test_utils::protect_payables_in_test; + use crate::accountant::scanners::BeginScanError; use crate::accountant::test_utils::DaoWithDestination::{ ForAccountantBody, ForPayableScanner, ForPendingPayableScanner, ForReceivableScanner, }; use crate::accountant::test_utils::{ bc_from_earning_wallet, bc_from_wallets, make_payable_account, make_payables, - BannedDaoFactoryMock, MessageIdGeneratorMock, PayableDaoFactoryMock, PayableDaoMock, - PendingPayableDaoFactoryMock, PendingPayableDaoMock, ReceivableDaoFactoryMock, - ReceivableDaoMock, + BannedDaoFactoryMock, MessageIdGeneratorMock, NullScanner, PayableDaoFactoryMock, + PayableDaoMock, PayableScannerBuilder, PaymentAdjusterMock, PendingPayableDaoFactoryMock, + PendingPayableDaoMock, ReceivableDaoFactoryMock, ReceivableDaoMock, ScannerMock, }; use crate::accountant::test_utils::{AccountantBuilder, BannedDaoMock}; use crate::accountant::Accountant; use crate::blockchain::blockchain_bridge::BlockchainBridge; - use crate::blockchain::blockchain_interface::BlockchainTransaction; - use crate::blockchain::blockchain_interface::ProcessedPayableFallible::Correct; use crate::blockchain::test_utils::{make_tx_hash, BlockchainInterfaceMock}; use crate::match_every_type_id; use crate::sub_lib::accountant::{ ExitServiceConsumed, PaymentThresholds, RoutingServiceConsumed, ScanIntervals, - DEFAULT_PAYMENT_THRESHOLDS, + DEFAULT_EARNING_WALLET, DEFAULT_PAYMENT_THRESHOLDS, }; - use crate::sub_lib::utils::NotifyLaterHandleReal; + use crate::sub_lib::blockchain_bridge::OutboundPaymentsInstructions; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; use crate::test_utils::recorder::make_recorder; use crate::test_utils::recorder::peer_actors_builder; use crate::test_utils::recorder::Recorder; use crate::test_utils::recorder_stop_conditions::{StopCondition, StopConditions}; + use crate::test_utils::unshared_test_utils::arbitrary_id_stamp::ArbitraryIdStamp; use crate::test_utils::unshared_test_utils::notify_handlers::NotifyLaterHandleMock; use crate::test_utils::unshared_test_utils::system_killer_actor::SystemKillerActor; use crate::test_utils::unshared_test_utils::{ @@ -1069,7 +1053,7 @@ mod tests { use std::sync::Mutex; use std::time::Duration; use std::vec; - use web3::types::{TransactionReceipt, U256}; + use web3::types::TransactionReceipt; impl Handler> for Accountant { type Result = (); @@ -1173,24 +1157,33 @@ mod tests { ); let financial_statistics = result.financial_statistics().clone(); - let scan_timings = result.scan_timings; - scan_timings - .pending_payable - .handle - .as_any() - .downcast_ref::>() - .unwrap(); - scan_timings - .payable - .handle - .as_any() - .downcast_ref::>() - .unwrap(); - scan_timings - .receivable - .handle - .as_any() - .downcast_ref::>(); + let assert_scan_scheduler = |scan_type: ScanType, expected_scan_interval: Duration| { + assert_eq!( + result + .scan_schedulers + .schedulers + .get(&scan_type) + .unwrap() + .interval(), + expected_scan_interval + ) + }; + let default_scan_intervals = ScanIntervals::default(); + assert_scan_scheduler( + ScanType::Payables, + default_scan_intervals.payable_scan_interval, + ); + assert_scan_scheduler( + ScanType::PendingPayables, + default_scan_intervals.pending_payable_scan_interval, + ); + assert_scan_scheduler( + ScanType::Receivables, + default_scan_intervals.receivable_scan_interval, + ); + assert_eq!(result.consuming_wallet_opt, None); + assert_eq!(*result.earning_wallet, *DEFAULT_EARNING_WALLET); + assert_eq!(result.suppress_initial_scans, false); result .message_id_generator .as_any() @@ -1326,13 +1319,13 @@ mod tests { system.run(); let blockchain_bridge_recording = blockchain_bridge_recording_arc.lock().unwrap(); assert_eq!( - blockchain_bridge_recording.get_record::(0), - &RequestBalancesToPayPayables { - accounts: vec![payable_account], + blockchain_bridge_recording.get_record::(0), + &QualifiedPayablesMessage { + protected_qualified_payables: protect_payables_in_test(vec![payable_account]), response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, context_id: 4321, - }), + }) } ); } @@ -1354,7 +1347,7 @@ mod tests { let peer_actors = peer_actors_builder().ui_gateway(ui_gateway).build(); subject_addr.try_send(BindMessage { peer_actors }).unwrap(); let sent_payable = SentPayables { - payment_procedure_result: Ok(vec![Correct(PendingPayable { + payment_procedure_result: Ok(vec![Ok(PendingPayable { recipient_wallet: make_wallet("blah"), hash: make_tx_hash(123), })]), @@ -1379,49 +1372,209 @@ mod tests { } #[test] - fn received_balances_and_qualified_payables_considered_feasible_payments_thus_all_forwarded_to_blockchain_bridge( + fn received_balances_and_qualified_payables_under_our_money_limit_thus_all_forwarded_to_blockchain_bridge( ) { + // the numbers for balances don't do real math, they need not to match either the condition for + // the payment adjustment or the actual values that come from the payable size reducing algorithm; + // all that is mocked in this test + init_test_logging(); + let test_name = "received_balances_and_qualified_payables_under_our_money_limit_thus_all_forwarded_to_blockchain_bridge"; + let is_adjustment_required_params_arc = Arc::new(Mutex::new(vec![])); + let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); + let instructions_recipient = blockchain_bridge + .system_stop_conditions(match_every_type_id!(OutboundPaymentsInstructions)) + .start() + .recipient(); let mut subject = AccountantBuilder::default().build(); + let payment_adjuster = PaymentAdjusterMock::default() + .is_adjustment_required_params(&is_adjustment_required_params_arc) + .is_adjustment_required_result(Ok(None)); + let payable_scanner = PayableScannerBuilder::new() + .payment_adjuster(payment_adjuster) + .build(); + subject.scanners.payable = Box::new(payable_scanner); + subject.outbound_payments_instructions_sub_opt = Some(instructions_recipient); + subject.logger = Logger::new(test_name); + let subject_addr = subject.start(); + let account_1 = make_payable_account(44_444); + let account_2 = make_payable_account(333_333); + let system = System::new("test"); + let agent_id_stamp = ArbitraryIdStamp::new(); + let agent = BlockchainAgentMock::default().set_arbitrary_id_stamp(agent_id_stamp); + let accounts = vec![account_1, account_2]; + let msg = BlockchainAgentWithContextMessage { + protected_qualified_payables: protect_payables_in_test(accounts.clone()), + agent: Box::new(agent), + response_skeleton_opt: Some(ResponseSkeleton { + client_id: 1234, + context_id: 4321, + }), + }; + + subject_addr.try_send(msg).unwrap(); + + system.run(); + let mut is_adjustment_required_params = is_adjustment_required_params_arc.lock().unwrap(); + let (blockchain_agent_with_context_msg_actual, logger_clone) = + is_adjustment_required_params.remove(0); + assert_eq!( + blockchain_agent_with_context_msg_actual.protected_qualified_payables, + protect_payables_in_test(accounts.clone()) + ); + assert_eq!( + blockchain_agent_with_context_msg_actual.response_skeleton_opt, + Some(ResponseSkeleton { + client_id: 1234, + context_id: 4321, + }) + ); + assert_eq!( + blockchain_agent_with_context_msg_actual + .agent + .arbitrary_id_stamp(), + agent_id_stamp + ); + assert!(is_adjustment_required_params.is_empty()); + let blockchain_bridge_recording = blockchain_bridge_recording_arc.lock().unwrap(); + let payments_instructions = + blockchain_bridge_recording.get_record::(0); + assert_eq!(payments_instructions.affordable_accounts, accounts); + assert_eq!( + payments_instructions.response_skeleton_opt, + Some(ResponseSkeleton { + client_id: 1234, + context_id: 4321, + }) + ); + assert_eq!( + payments_instructions.agent.arbitrary_id_stamp(), + agent_id_stamp + ); + assert_eq!(blockchain_bridge_recording.len(), 1); + test_use_of_the_same_logger(&logger_clone, test_name) + // adjust_payments() did not need a prepared result which means it wasn't reached + // because otherwise this test would've panicked + } + + fn test_use_of_the_same_logger(logger_clone: &Logger, test_name: &str) { + let experiment_msg = format!("DEBUG: {test_name}: hello world"); + let log_handler = TestLogHandler::default(); + log_handler.exists_no_log_containing(&experiment_msg); + debug!(logger_clone, "hello world"); + log_handler.exists_log_containing(&experiment_msg); + } + + #[test] + fn received_qualified_payables_exceeding_our_masq_balance_are_adjusted_before_forwarded_to_blockchain_bridge( + ) { + // the numbers for balances don't do real math, they need not to match either the condition for + // the payment adjustment or the actual values that come from the payable size reducing algorithm; + // all that is mocked in this test + init_test_logging(); + let test_name = "received_qualified_payables_exceeding_our_masq_balance_are_adjusted_before_forwarded_to_blockchain_bridge"; + let adjust_payments_params_arc = Arc::new(Mutex::new(vec![])); let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); let report_recipient = blockchain_bridge - .system_stop_conditions(match_every_type_id!(ReportAccountsPayable)) + .system_stop_conditions(match_every_type_id!(OutboundPaymentsInstructions)) .start() .recipient(); - subject.report_accounts_payable_sub_opt = Some(report_recipient); + let mut subject = AccountantBuilder::default().build(); + let unadjusted_account_1 = make_payable_account(111_111); + let unadjusted_account_2 = make_payable_account(222_222); + let adjusted_account_1 = PayableAccount { + balance_wei: gwei_to_wei(55_550_u64), + ..unadjusted_account_1.clone() + }; + let adjusted_account_2 = PayableAccount { + balance_wei: gwei_to_wei(100_000_u64), + ..unadjusted_account_2.clone() + }; + let response_skeleton = ResponseSkeleton { + client_id: 12, + context_id: 55, + }; + let agent_id_stamp_first_phase = ArbitraryIdStamp::new(); + let agent = + BlockchainAgentMock::default().set_arbitrary_id_stamp(agent_id_stamp_first_phase); + let payable_payments_setup_msg = BlockchainAgentWithContextMessage { + protected_qualified_payables: protect_payables_in_test(vec![ + unadjusted_account_1.clone(), + unadjusted_account_2.clone(), + ]), + agent: Box::new(agent), + response_skeleton_opt: Some(response_skeleton), + }; + // In the real world the agents are identical, here they bear different ids + // so that we can watch their journey better + let agent_id_stamp_second_phase = ArbitraryIdStamp::new(); + let agent = + BlockchainAgentMock::default().set_arbitrary_id_stamp(agent_id_stamp_second_phase); + let affordable_accounts = vec![adjusted_account_1.clone(), adjusted_account_2.clone()]; + let payments_instructions = OutboundPaymentsInstructions { + affordable_accounts: affordable_accounts.clone(), + agent: Box::new(agent), + response_skeleton_opt: Some(response_skeleton), + }; + let payment_adjuster = PaymentAdjusterMock::default() + .is_adjustment_required_result(Ok(Some(Adjustment::MasqToken))) + .adjust_payments_params(&adjust_payments_params_arc) + .adjust_payments_result(payments_instructions); + let payable_scanner = PayableScannerBuilder::new() + .payment_adjuster(payment_adjuster) + .build(); + subject.scanners.payable = Box::new(payable_scanner); + subject.outbound_payments_instructions_sub_opt = Some(report_recipient); + subject.logger = Logger::new(test_name); let subject_addr = subject.start(); - let half_of_u32_max_in_wei = u32::MAX as u64 / (2 * WEIS_IN_GWEI as u64); - let account_1 = make_payable_account(half_of_u32_max_in_wei); - let account_2 = account_1.clone(); let system = System::new("test"); - let consuming_balances_and_qualified_payments = - ConsumingWalletBalancesAndQualifiedPayables { - qualified_payables: vec![account_1.clone(), account_2.clone()], - consuming_wallet_balances: ConsumingWalletBalances { - gas_currency: U256::from(u32::MAX), - masq_tokens: U256::from(u32::MAX), - }, - response_skeleton_opt: Some(ResponseSkeleton { - client_id: 1234, - context_id: 4321, - }), - }; - subject_addr - .try_send(consuming_balances_and_qualified_payments) - .unwrap(); + subject_addr.try_send(payable_payments_setup_msg).unwrap(); - system.run(); + let before = SystemTime::now(); + assert_eq!(system.run(), 0); + let after = SystemTime::now(); + let mut adjust_payments_params = adjust_payments_params_arc.lock().unwrap(); + let (actual_prepared_adjustment, captured_now, logger_clone) = + adjust_payments_params.remove(0); + assert_eq!(actual_prepared_adjustment.adjustment, Adjustment::MasqToken); + assert_eq!( + actual_prepared_adjustment + .original_setup_msg + .protected_qualified_payables, + protect_payables_in_test(vec![unadjusted_account_1, unadjusted_account_2]) + ); + assert_eq!( + actual_prepared_adjustment + .original_setup_msg + .agent + .arbitrary_id_stamp(), + agent_id_stamp_first_phase + ); + assert!( + before <= captured_now && captured_now <= after, + "captured timestamp should have been between {:?} and {:?} but was {:?}", + before, + after, + captured_now + ); + assert!(adjust_payments_params.is_empty()); let blockchain_bridge_recording = blockchain_bridge_recording_arc.lock().unwrap(); + let payments_instructions = + blockchain_bridge_recording.get_record::(0); assert_eq!( - blockchain_bridge_recording.get_record::(0), - &ReportAccountsPayable { - accounts: vec![account_1, account_2], - response_skeleton_opt: Some(ResponseSkeleton { - client_id: 1234, - context_id: 4321, - }) - } + payments_instructions.affordable_accounts, + affordable_accounts ); + assert_eq!( + payments_instructions.response_skeleton_opt, + Some(response_skeleton) + ); + assert_eq!( + payments_instructions.agent.arbitrary_id_stamp(), + agent_id_stamp_second_phase + ); + assert_eq!(blockchain_bridge_recording.len(), 1); + test_use_of_the_same_logger(&logger_clone, test_name) } #[test] @@ -1593,7 +1746,7 @@ mod tests { .build(); let expected_payable = PendingPayable::new(expected_wallet.clone(), expected_hash.clone()); let sent_payable = SentPayables { - payment_procedure_result: Ok(vec![Correct(expected_payable.clone())]), + payment_procedure_result: Ok(vec![Ok(expected_payable.clone())]), response_skeleton_opt: None, }; let subject = accountant.start(); @@ -1615,8 +1768,7 @@ mod tests { } #[test] - fn accountant_sends_asks_blockchain_bridge_about_consuming_wallet_balances_when_qualified_payable_found( - ) { + fn accountant_sends_initial_payable_payments_msg_when_qualified_payable_found() { let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); let now = SystemTime::now(); let payment_thresholds = PaymentThresholds::default(); @@ -1624,7 +1776,9 @@ mod tests { make_payables(now, &payment_thresholds); let payable_dao = PayableDaoMock::new().non_pending_payables_result(all_non_pending_payables); - let system = System::new("request for balances forwarded to blockchain_bridge"); + let system = System::new( + "accountant_sends_initial_payable_payments_msg_when_qualified_payable_found", + ); let mut subject = AccountantBuilder::default() .bootstrapper_config(bc_from_earning_wallet(make_wallet("some_wallet_address"))) .payable_daos(vec![ForPayableScanner(payable_dao)]) @@ -1644,24 +1798,23 @@ mod tests { system.run(); let blockchain_bridge_recorder = blockchain_bridge_recording_arc.lock().unwrap(); assert_eq!(blockchain_bridge_recorder.len(), 1); - let message = blockchain_bridge_recorder.get_record::(0); + let message = blockchain_bridge_recorder.get_record::(0); assert_eq!( message, - &RequestBalancesToPayPayables { - accounts: qualified_payables, + &QualifiedPayablesMessage { + protected_qualified_payables: protect_payables_in_test(qualified_payables), response_skeleton_opt: None, } ); } #[test] - fn accountant_sends_request_to_blockchain_bridge_to_scan_for_received_payments() { + fn accountant_requests_blockchain_bridge_to_scan_for_received_payments() { init_test_logging(); let (blockchain_bridge, _, blockchain_bridge_recording_arc) = make_recorder(); let earning_wallet = make_wallet("someearningwallet"); - let system = System::new( - "accountant_sends_request_to_blockchain_bridge_to_scan_for_received_payments", - ); + let system = + System::new("accountant_requests_blockchain_bridge_to_scan_for_received_payments"); let receivable_dao = ReceivableDaoMock::new() .new_delinquencies_result(vec![]) .paid_delinquencies_result(vec![]); @@ -1696,7 +1849,7 @@ mod tests { } #[test] - fn accountant_processes_msg_with_received_payments_using_receivables_dao() { + fn accountant_uses_receivables_dao_to_process_received_payments() { let now = SystemTime::now(); let earning_wallet = make_wallet("earner3000"); let expected_receivable_1 = BlockchainTransaction { @@ -1717,8 +1870,7 @@ mod tests { .bootstrapper_config(bc_from_earning_wallet(earning_wallet.clone())) .receivable_daos(vec![ForReceivableScanner(receivable_dao)]) .build(); - let system = - System::new("accountant_processes_msg_with_received_payments_using_receivables_dao"); + let system = System::new("accountant_uses_receivables_dao_to_process_received_payments"); let subject = accountant.start(); subject @@ -1818,7 +1970,7 @@ mod tests { recipient: make_wallet("some_recipient"), response_skeleton_opt: None, })) - .stop_the_system(); + .stop_the_system_after_last_msg(); let mut config = make_bc_with_defaults(); config.scan_intervals_opt = Some(ScanIntervals { payable_scan_interval: Duration::from_secs(100), @@ -1832,10 +1984,14 @@ mod tests { subject.scanners.payable = Box::new(NullScanner::new()); // Skipping subject.scanners.pending_payable = Box::new(NullScanner::new()); // Skipping subject.scanners.receivable = Box::new(receivable_scanner); - subject.scan_timings.receivable.handle = Box::new( - NotifyLaterHandleMock::default() - .notify_later_params(¬ify_later_receivable_params_arc) - .permit_to_send_out(), + subject.scan_schedulers.update_scheduler( + ScanType::Receivables, + Some(Box::new( + NotifyLaterHandleMock::default() + .notify_later_params(¬ify_later_receivable_params_arc) + .capture_msg_and_let_it_fly_on(), + )), + None, ); let subject_addr = subject.start(); let subject_subs = Accountant::make_subs_from(&subject_addr); @@ -1885,7 +2041,7 @@ mod tests { pending_payable: vec![], response_skeleton_opt: None, })) - .stop_the_system(); + .stop_the_system_after_last_msg(); let mut config = make_bc_with_defaults(); config.scan_intervals_opt = Some(ScanIntervals { payable_scan_interval: Duration::from_secs(100), @@ -1899,10 +2055,14 @@ mod tests { subject.scanners.payable = Box::new(NullScanner::new()); //skipping subject.scanners.pending_payable = Box::new(pending_payable_scanner); subject.scanners.receivable = Box::new(NullScanner::new()); //skipping - subject.scan_timings.pending_payable.handle = Box::new( - NotifyLaterHandleMock::default() - .notify_later_params(¬ify_later_pending_payable_params_arc) - .permit_to_send_out(), + subject.scan_schedulers.update_scheduler( + ScanType::PendingPayables, + Some(Box::new( + NotifyLaterHandleMock::default() + .notify_later_params(¬ify_later_pending_payable_params_arc) + .capture_msg_and_let_it_fly_on(), + )), + None, ); let subject_addr: Addr = subject.start(); let subject_subs = Accountant::make_subs_from(&subject_addr); @@ -1949,11 +2109,13 @@ mod tests { let payable_scanner = ScannerMock::new() .begin_scan_params(&begin_scan_params_arc) .begin_scan_result(Err(BeginScanError::NothingToProcess)) - .begin_scan_result(Ok(RequestBalancesToPayPayables { - accounts: vec![], + .begin_scan_result(Ok(QualifiedPayablesMessage { + protected_qualified_payables: protect_payables_in_test(vec![make_payable_account( + 123, + )]), response_skeleton_opt: None, })) - .stop_the_system(); + .stop_the_system_after_last_msg(); let mut config = bc_from_earning_wallet(make_wallet("hi")); config.scan_intervals_opt = Some(ScanIntervals { payable_scan_interval: Duration::from_millis(97), @@ -1967,10 +2129,14 @@ mod tests { subject.scanners.payable = Box::new(payable_scanner); subject.scanners.pending_payable = Box::new(NullScanner::new()); //skipping subject.scanners.receivable = Box::new(NullScanner::new()); //skipping - subject.scan_timings.payable.handle = Box::new( - NotifyLaterHandleMock::default() - .notify_later_params(¬ify_later_payables_params_arc) - .permit_to_send_out(), + subject.scan_schedulers.update_scheduler( + ScanType::Payables, + Some(Box::new( + NotifyLaterHandleMock::default() + .notify_later_params(¬ify_later_payables_params_arc) + .capture_msg_and_let_it_fly_on(), + )), + None, ); let subject_addr = subject.start(); let subject_subs = Accountant::make_subs_from(&subject_addr); @@ -2093,13 +2259,13 @@ mod tests { "scan_for_payable_message_does_not_trigger_payment_for_balances_below_the_curve", ); let blockchain_bridge_addr: Addr = blockchain_bridge.start(); - let report_accounts_payable_sub = - blockchain_bridge_addr.recipient::(); + let outbound_payments_instructions_sub = + blockchain_bridge_addr.recipient::(); let mut subject = AccountantBuilder::default() .bootstrapper_config(config) .payable_daos(vec![ForPayableScanner(payable_dao)]) .build(); - subject.report_accounts_payable_sub_opt = Some(report_accounts_payable_sub); + subject.outbound_payments_instructions_sub_opt = Some(outbound_payments_instructions_sub); let _result = subject .scanners @@ -2122,7 +2288,7 @@ mod tests { receivable_scan_interval: Duration::from_secs(50_000), }); let now = to_time_t(SystemTime::now()); - let accounts = vec![ + let qualified_payables = vec![ // slightly above minimum balance, to the right of the curve (time intersection) PayableAccount { wallet: make_wallet("wallet0"), @@ -2150,10 +2316,11 @@ mod tests { pending_payable_opt: None, }, ]; - let payable_dao = PayableDaoMock::default().non_pending_payables_result(accounts.clone()); + let payable_dao = + PayableDaoMock::default().non_pending_payables_result(qualified_payables.clone()); let (blockchain_bridge, _, blockchain_bridge_recordings_arc) = make_recorder(); let blockchain_bridge = blockchain_bridge - .system_stop_conditions(match_every_type_id!(RequestBalancesToPayPayables)); + .system_stop_conditions(match_every_type_id!(QualifiedPayablesMessage)); let system = System::new("scan_for_payable_message_triggers_payment_for_balances_over_the_curve"); let peer_actors = peer_actors_builder() @@ -2173,11 +2340,11 @@ mod tests { system.run(); let blockchain_bridge_recordings = blockchain_bridge_recordings_arc.lock().unwrap(); - let message = blockchain_bridge_recordings.get_record::(0); + let message = blockchain_bridge_recordings.get_record::(0); assert_eq!( message, - &RequestBalancesToPayPayables { - accounts, + &QualifiedPayablesMessage { + protected_qualified_payables: protect_payables_in_test(qualified_payables), response_skeleton_opt: None, } ); @@ -2191,11 +2358,11 @@ mod tests { let (blockchain_bridge, _, blockchain_bridge_recording) = make_recorder(); let blockchain_bridge_addr = blockchain_bridge .system_stop_conditions(match_every_type_id!( - RequestBalancesToPayPayables, - RequestBalancesToPayPayables + QualifiedPayablesMessage, + QualifiedPayablesMessage )) .start(); - let request_balances_to_pay_payables_sub = blockchain_bridge_addr.clone().recipient(); + let pps_for_blockchain_bridge_sub = blockchain_bridge_addr.clone().recipient(); let last_paid_timestamp = to_time_t(SystemTime::now()) - DEFAULT_PAYMENT_THRESHOLDS.maturity_threshold_sec as i64 - 1; @@ -2227,8 +2394,7 @@ mod tests { context_id: 444, }), }; - subject.request_balances_to_pay_payables_sub_opt = - Some(request_balances_to_pay_payables_sub); + subject.qualified_payables_sub_opt = Some(pps_for_blockchain_bridge_sub); let addr = subject.start(); addr.try_send(message_before.clone()).unwrap(); @@ -2254,12 +2420,12 @@ mod tests { let recording = blockchain_bridge_recording.lock().unwrap(); let messages_received = recording.len(); assert_eq!(messages_received, 2); - let first_message: &RequestBalancesToPayPayables = recording.get_record(0); + let first_message: &QualifiedPayablesMessage = recording.get_record(0); assert_eq!( first_message.response_skeleton_opt, message_before.response_skeleton_opt ); - let second_message: &RequestBalancesToPayPayables = recording.get_record(1); + let second_message: &QualifiedPayablesMessage = recording.get_record(1); assert_eq!( second_message.response_skeleton_opt, message_after.response_skeleton_opt @@ -2960,6 +3126,7 @@ mod tests { #[test] fn pending_transaction_is_registered_and_monitored_until_it_gets_confirmed_or_canceled() { init_test_logging(); + let build_blockchain_agent_params = Arc::new(Mutex::new(vec![])); let mark_pending_payable_params_arc = Arc::new(Mutex::new(vec![])); let transactions_confirmed_params_arc = Arc::new(Mutex::new(vec![])); let get_transaction_receipt_params_arc = Arc::new(Mutex::new(vec![])); @@ -2970,7 +3137,7 @@ mod tests { let delete_record_params_arc = Arc::new(Mutex::new(vec![])); let notify_later_scan_for_pending_payable_params_arc = Arc::new(Mutex::new(vec![])); let notify_later_scan_for_pending_payable_arc_cloned = - notify_later_scan_for_pending_payable_params_arc.clone(); //because it moves into a closure + notify_later_scan_for_pending_payable_params_arc.clone(); // because it moves into a closure let pending_tx_hash_1 = make_tx_hash(0x7b); let pending_tx_hash_2 = make_tx_hash(0x237); let rowid_for_account_1 = 3; @@ -2994,24 +3161,23 @@ mod tests { let transaction_receipt_tx_1_second_round = TransactionReceipt::default(); let transaction_receipt_tx_2_second_round = TransactionReceipt::default(); let mut transaction_receipt_tx_1_third_round = TransactionReceipt::default(); - transaction_receipt_tx_1_third_round.status = Some(U64::from(0)); //failure + transaction_receipt_tx_1_third_round.status = Some(U64::from(0)); // failure let transaction_receipt_tx_2_third_round = TransactionReceipt::default(); let mut transaction_receipt_tx_2_fourth_round = TransactionReceipt::default(); transaction_receipt_tx_2_fourth_round.status = Some(U64::from(1)); // confirmed + let agent = BlockchainAgentMock::default(); let blockchain_interface = BlockchainInterfaceMock::default() - .get_transaction_fee_balance_result(Ok(U256::from(u128::MAX))) - .get_token_balance_result(Ok(U256::from(u128::MAX))) - .get_transaction_count_result(Ok(web3::types::U256::from(1))) - .get_transaction_count_result(Ok(web3::types::U256::from(2))) - //because we cannot have both, resolution on the high level and also of what's inside blockchain interface, - //there is one component missing in this wholesome test - the part where we send a request for - //a fingerprint of that payable in the DB - this happens inside send_raw_transaction() - .send_payables_within_batch_result(Ok(vec![ - Correct(PendingPayable { + .build_blockchain_agent_params(&build_blockchain_agent_params) + .build_blockchain_agent_result(Ok(Box::new(agent))) + // because we cannot have both, resolution on the high level and also of what's inside blockchain interface, + // there is one component missing in this wholesome test - the part where we send a request for + // a fingerprint of that payable in the DB - this happens inside send_raw_transaction() + .send_batch_of_payables_result(Ok(vec![ + Ok(PendingPayable { recipient_wallet: wallet_account_1.clone(), hash: pending_tx_hash_1, }), - Correct(PendingPayable { + Ok(PendingPayable { recipient_wallet: wallet_account_2.clone(), hash: pending_tx_hash_2, }), @@ -3026,12 +3192,14 @@ mod tests { .get_transaction_receipt_result(Ok(Some(transaction_receipt_tx_2_fourth_round))); let consuming_wallet = make_paying_wallet(b"wallet"); let system = System::new("pending_transaction"); - let persistent_config = PersistentConfigurationMock::default().gas_price_result(Ok(130)); + let persistent_config_id_stamp = ArbitraryIdStamp::new(); + let persistent_config = PersistentConfigurationMock::default() + .set_arbitrary_id_stamp(persistent_config_id_stamp); let blockchain_bridge = BlockchainBridge::new( Box::new(blockchain_interface), Box::new(persistent_config), false, - Some(consuming_wallet), + Some(consuming_wallet.clone()), ); let account_1 = PayableAccount { wallet: wallet_account_1.clone(), @@ -3045,7 +3213,7 @@ mod tests { last_paid_timestamp: past_payable_timestamp_2, pending_payable_opt: None, }; - let pending_payable_scan_interval = 200; //should be slightly less than 1/5 of the time until shutting the system + let pending_payable_scan_interval = 200; // should be slightly less than 1/5 of the time until shutting the system let payable_dao_for_payable_scanner = PayableDaoMock::new() .non_pending_payables_params(&non_pending_payables_params_arc) .non_pending_payables_result(vec![account_1, account_2]) @@ -3056,8 +3224,8 @@ mod tests { .transactions_confirmed_result(Ok(())); let mut bootstrapper_config = bc_from_earning_wallet(make_wallet("some_wallet_address")); bootstrapper_config.scan_intervals_opt = Some(ScanIntervals { - payable_scan_interval: Duration::from_secs(1_000_000), //we don't care about this scan - receivable_scan_interval: Duration::from_secs(1_000_000), //we don't care about this scan + payable_scan_interval: Duration::from_secs(1_000_000), // we don't care about this scan + receivable_scan_interval: Duration::from_secs(1_000_000), // we don't care about this scan pending_payable_scan_interval: Duration::from_millis(pending_payable_scan_interval), }); let fingerprint_1_first_round = PendingPayableFingerprint { @@ -3126,10 +3294,10 @@ mod tests { .increment_scan_attempts_result(Ok(())) .increment_scan_attempts_result(Ok(())) .mark_failures_params(&mark_failure_params_arc) - //we don't have a better solution yet, so we mark this down + // we don't have a better solution yet, so we mark this down .mark_failures_result(Ok(())) .delete_fingerprints_params(&delete_record_params_arc) - //this is used during confirmation of the successful one + // this is used during confirmation of the successful one .delete_fingerprints_result(Ok(())); pending_payable_dao_for_pending_payable_scanner .have_return_all_errorless_fingerprints_shut_down_the_system = true; @@ -3150,8 +3318,12 @@ mod tests { subject.scanners.receivable = Box::new(NullScanner::new()); let notify_later_half_mock = NotifyLaterHandleMock::default() .notify_later_params(¬ify_later_scan_for_pending_payable_arc_cloned) - .permit_to_send_out(); - subject.scan_timings.pending_payable.handle = Box::new(notify_later_half_mock); + .capture_msg_and_let_it_fly_on(); + subject.scan_schedulers.update_scheduler( + ScanType::PendingPayables, + Some(Box::new(notify_later_half_mock)), + None, + ); subject }); let mut peer_actors = peer_actors_builder().build(); @@ -3182,10 +3354,10 @@ mod tests { assert_eq!(second_payable.1, rowid_for_account_2); let return_all_errorless_fingerprints_params = return_all_errorless_fingerprints_params_arc.lock().unwrap(); - //it varies with machines and sometimes we manage more cycles than necessary + // it varies with machines and sometimes we manage more cycles than necessary assert!(return_all_errorless_fingerprints_params.len() >= 5); let non_pending_payables_params = non_pending_payables_params_arc.lock().unwrap(); - assert_eq!(*non_pending_payables_params, vec![()]); //because we disabled further scanning for payables + assert_eq!(*non_pending_payables_params, vec![()]); // because we disabled further scanning for payables let get_transaction_receipt_params = get_transaction_receipt_params_arc.lock().unwrap(); assert_eq!( *get_transaction_receipt_params, @@ -3199,6 +3371,11 @@ mod tests { pending_tx_hash_2, ] ); + let build_blockchain_agent_params = build_blockchain_agent_params.lock().unwrap(); + assert_eq!( + *build_blockchain_agent_params, + vec![(consuming_wallet, persistent_config_id_stamp)] + ); let update_fingerprints_params = update_fingerprint_params_arc.lock().unwrap(); assert_eq!( *update_fingerprints_params, @@ -3227,7 +3404,7 @@ mod tests { notify_later_scan_for_pending_payable_params_arc .lock() .unwrap(); - //it varies with machines and sometimes we manage more cycles than necessary + // it varies with machines and sometimes we manage more cycles than necessary let vector_of_first_five_cycles = notify_later_check_for_confirmation .drain(0..=4) .collect_vec(); @@ -3243,10 +3420,17 @@ mod tests { ); let log_handler = TestLogHandler::new(); log_handler.exists_log_containing( - "WARN: Accountant: Broken transactions 0x000000000000000000000000000000000000000000000000000000000000007b marked as an error. \ - You should take over the care of those to make sure your debts are going to be settled properly. At the moment, there is no automated process fixing that without your assistance"); - log_handler.exists_log_matching("INFO: Accountant: Transaction 0x0000000000000000000000000000000000000000000000000000000000000237 has been added to the blockchain; detected locally at attempt 4 at \\d{2,}ms after its sending"); - log_handler.exists_log_containing("INFO: Accountant: Transactions 0x0000000000000000000000000000000000000000000000000000000000000237 completed their confirmation process succeeding"); + "WARN: Accountant: Broken transactions 0x00000000000000000000000000000000000000000000000\ + 0000000000000007b marked as an error. You should take over the care of those to make sure \ + your debts are going to be settled properly. At the moment, there is no automated process \ + fixing that without your assistance"); + log_handler.exists_log_matching("INFO: Accountant: Transaction 0x000000000000000000000000000\ + 0000000000000000000000000000000000237 has been added to the blockchain; detected locally at \ + attempt 4 at \\d{2,}ms after its sending"); + log_handler.exists_log_containing( + "INFO: Accountant: Transactions 0x000000000000000000000000\ + 0000000000000000000000000000000000000237 completed their confirmation process succeeding", + ); } #[test] @@ -4301,3 +4485,199 @@ mod tests { assert_on_initialization_with_panic_on_migration(&data_dir, &act); } } + +#[cfg(test)] +pub mod exportable_test_parts { + use super::*; + use crate::accountant::test_utils::bc_from_earning_wallet; + use crate::actor_system_factory::SubsFactory; + use crate::database::db_initializer::{DbInitializer, DbInitializerReal}; + use crate::sub_lib::accountant::DEFAULT_PAYMENT_THRESHOLDS; + use crate::test_utils::actor_system_factory::BannedCacheLoaderMock; + use crate::test_utils::make_wallet; + use crate::test_utils::recorder::make_accountant_subs_from_recorder; + use crate::test_utils::unshared_test_utils::{AssertionsMessage, SubsFactoryTestAddrLeaker}; + use actix::System; + use crossbeam_channel::bounded; + use masq_lib::test_utils::utils::ShouldWeRunTheTest::{GoAhead, Skip}; + use masq_lib::test_utils::utils::{ + check_if_source_code_is_attached, ensure_node_home_directory_exists, ShouldWeRunTheTest, + }; + use regex::Regex; + use std::env::current_dir; + use std::fs::File; + use std::io::{BufRead, BufReader}; + use std::path::PathBuf; + + impl SubsFactory for SubsFactoryTestAddrLeaker { + fn make(&self, addr: &Addr) -> AccountantSubs { + self.send_leaker_msg_and_return_meaningless_subs( + addr, + make_accountant_subs_from_recorder, + ) + } + } + + fn verify_presence_of_user_defined_sqlite_fns_in_new_delinquencies_for_receivable_dao( + ) -> ShouldWeRunTheTest { + fn skip_down_to_first_line_saying_new_delinquencies( + previous: impl Iterator, + ) -> impl Iterator { + previous + .skip_while(|line| { + let adjusted_line: String = line + .chars() + .skip_while(|char| char.is_whitespace()) + .collect(); + !adjusted_line.starts_with("fn new_delinquencies(") + }) + .skip(1) + } + fn assert_is_not_trait_definition(body_lines: impl Iterator) -> String { + fn yield_if_contains_semicolon(line: &str) -> Option { + line.contains(';').then(|| line.to_string()) + } + let mut semicolon_line_opt = None; + let line_undivided_fn_body = body_lines + .map(|line| { + if semicolon_line_opt.is_none() { + if let Some(result) = yield_if_contains_semicolon(&line) { + semicolon_line_opt = Some(result) + } + } + line + }) + .collect::(); + if let Some(line) = semicolon_line_opt { + let regex = Regex::new(r"Vec<\w+>;").unwrap(); + if regex.is_match(&line) { + // The important part of the regex is the semicolon at the end. Trait + // implementations don't use it. They go on with an opening bracket of + // the function body. Its presence therefore signifies we have to do + // with a trait definition + panic!( + "The second parsed chunk of code is a trait definition \ + and the implementation lies before it. Conventions say the opposite. Simply \ + change the placement order in the production code." + ) + } + } + line_undivided_fn_body + } + fn scope_fn_new_delinquency_alone(reader: BufReader) -> String { + let all_lines_in_the_file = reader.lines().flatten(); + let lines_with_cut_fn_trait_definition = + skip_down_to_first_line_saying_new_delinquencies(all_lines_in_the_file); + let assumed_implemented_function_body = + skip_down_to_first_line_saying_new_delinquencies( + lines_with_cut_fn_trait_definition, + ) + .take_while(|line| { + let adjusted_line: String = line + .chars() + .skip_while(|char| char.is_whitespace()) + .collect(); + !adjusted_line.starts_with("fn") + }); + assert_is_not_trait_definition(assumed_implemented_function_body) + } + fn user_defined_functions_detected(line_undivided_fn_body: &str) -> bool { + line_undivided_fn_body.contains(" slope_drop_high_bytes(") + && line_undivided_fn_body.contains(" slope_drop_low_bytes(") + } + + let current_dir = current_dir().unwrap(); + let file_path = current_dir.join(PathBuf::from_iter([ + "src", + "accountant", + "db_access_objects", + "receivable_dao.rs", + ])); + let file = match File::open(file_path) { + Ok(file) => file, + Err(_) => match check_if_source_code_is_attached(¤t_dir) { + Skip => return Skip, + _ => panic!( + "if panics, the file receivable_dao.rs probably doesn't exist or \ + has moved to an unexpected location" + ), + }, + }; + let reader = BufReader::new(file); + let function_body_ready_for_final_check = scope_fn_new_delinquency_alone(reader); + if user_defined_functions_detected(&function_body_ready_for_final_check) { + GoAhead + } else { + panic!( + "was about to test user-defined SQLite functions (slope_drop_high_bytes and + slope_drop_low_bytes) in new_delinquencies() but found out those are absent at the + expected place and would leave falsely positive results" + ) + } + } + + pub fn test_accountant_is_constructed_with_upgraded_db_connection_recognizing_our_extra_sqlite_functions< + A, + >( + test_module: &str, + test_name: &str, + act: A, + ) where + A: FnOnce( + BootstrapperConfig, + DbInitializerReal, + BannedCacheLoaderMock, + SubsFactoryTestAddrLeaker, + ) -> AccountantSubs, + { + // precondition: .new_delinquencies() still encompasses the considered functions, otherwise + // the test is false-positive + if let Skip = + verify_presence_of_user_defined_sqlite_fns_in_new_delinquencies_for_receivable_dao() + { + eprintln!( + "skipping test {test_name} due to having been unable to find receivable_dao.rs" + ); + return; + }; + let data_dir = ensure_node_home_directory_exists(test_module, test_name); + let _ = DbInitializerReal::default() + .initialize(data_dir.as_ref(), DbInitializationConfig::test_default()) + .unwrap(); + let mut bootstrapper_config = bc_from_earning_wallet(make_wallet("mine")); + bootstrapper_config.data_directory = data_dir; + let db_initializer = DbInitializerReal::default(); + let banned_cache_loader = BannedCacheLoaderMock::default(); + let (tx, accountant_addr_rv) = bounded(1); + let address_leaker = SubsFactoryTestAddrLeaker { address_leaker: tx }; + let system = System::new(test_name); + + act( + bootstrapper_config, + db_initializer, + banned_cache_loader, + address_leaker, + ); + + let accountant_addr = accountant_addr_rv.try_recv().unwrap(); + let assertion_msg = AssertionsMessage { + assertions: Box::new(|accountant: &mut Accountant| { + // Will crash a test if our user-defined SQLite fns have been unreachable; + // We cannot rely on failures in the DAO tests, because Account's database connection + // has to be set up specially first (we teach it about the extra functions) as we're + // creating the actor + + accountant + .receivable_dao + .new_delinquencies(SystemTime::now(), &DEFAULT_PAYMENT_THRESHOLDS); + // Don't move this to the main test, it could produce a deceiving result. + // It wouldn't actually process this message. I don't know why exactly + System::current().stop(); + }), + }; + accountant_addr.try_send(assertion_msg).unwrap(); + assert_eq!(system.run(), 0); + // We didn't blow up, it recognized the functions. + // This is an example of the error: "no such function: slope_drop_high_bytes" + } +} diff --git a/node/src/accountant/payment_adjuster.rs b/node/src/accountant/payment_adjuster.rs new file mode 100644 index 000000000..8d230484a --- /dev/null +++ b/node/src/accountant/payment_adjuster.rs @@ -0,0 +1,105 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::BlockchainAgentWithContextMessage; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::PreparedAdjustment; +use crate::sub_lib::blockchain_bridge::OutboundPaymentsInstructions; +use masq_lib::logger::Logger; +use std::time::SystemTime; + +pub trait PaymentAdjuster { + fn search_for_indispensable_adjustment( + &self, + msg: &BlockchainAgentWithContextMessage, + logger: &Logger, + ) -> Result, AnalysisError>; + + fn adjust_payments( + &self, + setup: PreparedAdjustment, + now: SystemTime, + logger: &Logger, + ) -> OutboundPaymentsInstructions; + + as_any_in_trait!(); +} + +pub struct PaymentAdjusterReal {} + +impl PaymentAdjuster for PaymentAdjusterReal { + fn search_for_indispensable_adjustment( + &self, + _msg: &BlockchainAgentWithContextMessage, + _logger: &Logger, + ) -> Result, AnalysisError> { + Ok(None) + } + + fn adjust_payments( + &self, + _setup: PreparedAdjustment, + _now: SystemTime, + _logger: &Logger, + ) -> OutboundPaymentsInstructions { + todo!("this function is dead until the card GH-711 is played") + } + + as_any_in_trait_impl!(); +} + +impl PaymentAdjusterReal { + pub fn new() -> Self { + Self {} + } +} + +impl Default for PaymentAdjusterReal { + fn default() -> Self { + Self::new() + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Adjustment { + MasqToken, + TransactionFeeCurrency { limiting_count: u16 }, + Both, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum AnalysisError {} + +#[cfg(test)] +mod tests { + use crate::accountant::payment_adjuster::{PaymentAdjuster, PaymentAdjusterReal}; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::BlockchainAgentWithContextMessage; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::test_utils::BlockchainAgentMock; + use crate::accountant::scanners::test_utils::protect_payables_in_test; + use crate::accountant::test_utils::make_payable_account; + use masq_lib::logger::Logger; + use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + + #[test] + fn search_for_indispensable_adjustment_always_returns_none() { + init_test_logging(); + let test_name = "is_adjustment_required_always_returns_none"; + let mut payable = make_payable_account(111); + payable.balance_wei = 100_000_000; + let agent = BlockchainAgentMock::default(); + let setup_msg = BlockchainAgentWithContextMessage { + protected_qualified_payables: protect_payables_in_test(vec![payable]), + agent: Box::new(agent), + response_skeleton_opt: None, + }; + let logger = Logger::new(test_name); + let subject = PaymentAdjusterReal::new(); + + let result = subject.search_for_indispensable_adjustment(&setup_msg, &logger); + + assert_eq!(result, Ok(None)); + TestLogHandler::default().exists_no_log_containing(test_name); + // Nobody in this test asked about the wallet balances and the transaction fee + // requirement, yet we got through with the final None. + // How do we know? The mock agent didn't blow up while missing these + // results + } +} diff --git a/node/src/accountant/scanners/mid_scan_msg_handling/mod.rs b/node/src/accountant/scanners/mid_scan_msg_handling/mod.rs new file mode 100644 index 000000000..16331e4bf --- /dev/null +++ b/node/src/accountant/scanners/mid_scan_msg_handling/mod.rs @@ -0,0 +1,3 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +pub mod payable_scanner; diff --git a/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_null.rs b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_null.rs new file mode 100644 index 000000000..57615ba30 --- /dev/null +++ b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_null.rs @@ -0,0 +1,192 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; + +use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; +use crate::sub_lib::wallet::Wallet; +use ethereum_types::U256; +use masq_lib::logger::Logger; + +#[derive(Clone)] +pub struct BlockchainAgentNull { + wallet: Wallet, + logger: Logger, +} + +impl BlockchainAgent for BlockchainAgentNull { + fn estimated_transaction_fee_total(&self, _number_of_transactions: usize) -> u128 { + self.log_function_call("estimated_transaction_fee_total()"); + 0 + } + + fn consuming_wallet_balances(&self) -> ConsumingWalletBalances { + self.log_function_call("consuming_wallet_balances()"); + ConsumingWalletBalances { + transaction_fee_balance_in_minor_units: U256::zero(), + masq_token_balance_in_minor_units: U256::zero(), + } + } + + fn agreed_fee_per_computation_unit(&self) -> u64 { + self.log_function_call("agreed_fee_per_computation_unit()"); + 0 + } + + fn consuming_wallet(&self) -> &Wallet { + self.log_function_call("consuming_wallet()"); + &self.wallet + } + + fn pending_transaction_id(&self) -> U256 { + self.log_function_call("pending_transaction_id()"); + U256::zero() + } + + #[cfg(test)] + fn dup(&self) -> Box { + intentionally_blank!() + } + + #[cfg(test)] + as_any_in_trait_impl!(); +} + +impl BlockchainAgentNull { + pub fn new() -> Self { + Self { + wallet: Wallet::null(), + logger: Logger::new("BlockchainAgentNull"), + } + } + + fn log_function_call(&self, function_call: &str) { + error!( + self.logger, + "calling null version of {function_call} for BlockchainAgentNull will be without effect", + ); + } +} + +impl Default for BlockchainAgentNull { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::agent_null::BlockchainAgentNull; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; + + use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; + use crate::sub_lib::wallet::Wallet; + + use masq_lib::logger::Logger; + use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + use web3::types::U256; + + fn blockchain_agent_null_constructor_works(constructor: C) + where + C: Fn() -> BlockchainAgentNull, + { + init_test_logging(); + + let result = constructor(); + + assert_eq!(result.wallet, Wallet::null()); + warning!(result.logger, "blockchain_agent_null_constructor_works"); + TestLogHandler::default().exists_log_containing( + "WARN: BlockchainAgentNull: \ + blockchain_agent_null_constructor_works", + ); + } + + #[test] + fn blockchain_agent_null_constructor_works_for_new() { + blockchain_agent_null_constructor_works(BlockchainAgentNull::new) + } + + #[test] + fn blockchain_agent_null_constructor_works_for_default() { + blockchain_agent_null_constructor_works(BlockchainAgentNull::default) + } + + fn assert_error_log(test_name: &str, expected_operation: &str) { + TestLogHandler::default().exists_log_containing(&format!( + "ERROR: {test_name}: calling \ + null version of {expected_operation}() for BlockchainAgentNull \ + will be without effect" + )); + } + + #[test] + fn null_agent_estimated_transaction_fee_total() { + init_test_logging(); + let test_name = "null_agent_estimated_transaction_fee_total"; + let mut subject = BlockchainAgentNull::new(); + subject.logger = Logger::new(test_name); + + let result = subject.estimated_transaction_fee_total(4); + + assert_eq!(result, 0); + assert_error_log(test_name, "estimated_transaction_fee_total"); + } + + #[test] + fn null_agent_consuming_wallet_balances() { + init_test_logging(); + let test_name = "null_agent_consuming_wallet_balances"; + let mut subject = BlockchainAgentNull::new(); + subject.logger = Logger::new(test_name); + + let result = subject.consuming_wallet_balances(); + + assert_eq!( + result, + ConsumingWalletBalances { + transaction_fee_balance_in_minor_units: U256::zero(), + masq_token_balance_in_minor_units: U256::zero() + } + ); + assert_error_log(test_name, "consuming_wallet_balances") + } + + #[test] + fn null_agent_agreed_fee_per_computation_unit() { + init_test_logging(); + let test_name = "null_agent_agreed_fee_per_computation_unit"; + let mut subject = BlockchainAgentNull::new(); + subject.logger = Logger::new(test_name); + + let result = subject.agreed_fee_per_computation_unit(); + + assert_eq!(result, 0); + assert_error_log(test_name, "agreed_fee_per_computation_unit") + } + + #[test] + fn null_agent_consuming_wallet() { + init_test_logging(); + let test_name = "null_agent_consuming_wallet"; + let mut subject = BlockchainAgentNull::new(); + subject.logger = Logger::new(test_name); + + let result = subject.consuming_wallet(); + + assert_eq!(result, &Wallet::null()); + assert_error_log(test_name, "consuming_wallet") + } + + #[test] + fn null_agent_pending_transaction_id() { + init_test_logging(); + let test_name = "null_agent_pending_transaction_id"; + let mut subject = BlockchainAgentNull::new(); + subject.logger = Logger::new(test_name); + + let result = subject.pending_transaction_id(); + + assert_eq!(result, U256::zero()); + assert_error_log(test_name, "pending_transaction_id"); + } +} diff --git a/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_web3.rs b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_web3.rs new file mode 100644 index 000000000..d54224352 --- /dev/null +++ b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/agent_web3.rs @@ -0,0 +1,140 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; + +use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; +use crate::sub_lib::wallet::Wallet; + +use web3::types::U256; + +#[derive(Debug, Clone)] +pub struct BlockchainAgentWeb3 { + gas_price_gwei: u64, + gas_limit_const_part: u64, + maximum_added_gas_margin: u64, + consuming_wallet: Wallet, + consuming_wallet_balances: ConsumingWalletBalances, + pending_transaction_id: U256, +} + +impl BlockchainAgent for BlockchainAgentWeb3 { + fn estimated_transaction_fee_total(&self, number_of_transactions: usize) -> u128 { + let gas_price = self.gas_price_gwei as u128; + let max_gas_limit = (self.maximum_added_gas_margin + self.gas_limit_const_part) as u128; + number_of_transactions as u128 * gas_price * max_gas_limit + } + + fn consuming_wallet_balances(&self) -> ConsumingWalletBalances { + self.consuming_wallet_balances + } + + fn agreed_fee_per_computation_unit(&self) -> u64 { + self.gas_price_gwei + } + + fn consuming_wallet(&self) -> &Wallet { + &self.consuming_wallet + } + + fn pending_transaction_id(&self) -> U256 { + self.pending_transaction_id + } +} + +// 64 * (64 - 12) ... std transaction has data of 64 bytes and 12 bytes are never used with us; +// each non-zero byte costs 64 units of gas +pub const WEB3_MAXIMAL_GAS_LIMIT_MARGIN: u64 = 3328; + +impl BlockchainAgentWeb3 { + pub fn new( + gas_price_gwei: u64, + gas_limit_const_part: u64, + consuming_wallet: Wallet, + consuming_wallet_balances: ConsumingWalletBalances, + pending_transaction_id: U256, + ) -> Self { + Self { + gas_price_gwei, + gas_limit_const_part, + consuming_wallet, + maximum_added_gas_margin: WEB3_MAXIMAL_GAS_LIMIT_MARGIN, + consuming_wallet_balances, + pending_transaction_id, + } + } +} + +#[cfg(test)] +mod tests { + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::agent_web3::{ + BlockchainAgentWeb3, WEB3_MAXIMAL_GAS_LIMIT_MARGIN, + }; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; + + use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; + use crate::test_utils::make_wallet; + + use web3::types::U256; + + #[test] + fn constants_are_correct() { + assert_eq!(WEB3_MAXIMAL_GAS_LIMIT_MARGIN, 3328) + } + + #[test] + fn blockchain_agent_can_return_non_computed_input_values() { + let gas_price_gwei = 123; + let gas_limit_const_part = 44_000; + let consuming_wallet = make_wallet("abcde"); + let consuming_wallet_balances = ConsumingWalletBalances { + transaction_fee_balance_in_minor_units: U256::from(456_789), + masq_token_balance_in_minor_units: U256::from(123_000_000), + }; + let pending_transaction_id = U256::from(777); + + let subject = BlockchainAgentWeb3::new( + gas_price_gwei, + gas_limit_const_part, + consuming_wallet.clone(), + consuming_wallet_balances, + pending_transaction_id, + ); + + assert_eq!(subject.agreed_fee_per_computation_unit(), gas_price_gwei); + assert_eq!(subject.consuming_wallet(), &consuming_wallet); + assert_eq!( + subject.consuming_wallet_balances(), + consuming_wallet_balances + ); + assert_eq!(subject.pending_transaction_id(), pending_transaction_id) + } + + #[test] + fn estimated_transaction_fee_works() { + let consuming_wallet = make_wallet("efg"); + let consuming_wallet_balances = ConsumingWalletBalances { + transaction_fee_balance_in_minor_units: Default::default(), + masq_token_balance_in_minor_units: Default::default(), + }; + let nonce = U256::from(55); + let agent = BlockchainAgentWeb3::new( + 444, + 77_777, + consuming_wallet, + consuming_wallet_balances, + nonce, + ); + + let result = agent.estimated_transaction_fee_total(3); + + assert_eq!(agent.gas_limit_const_part, 77_777); + assert_eq!( + agent.maximum_added_gas_margin, + WEB3_MAXIMAL_GAS_LIMIT_MARGIN + ); + assert_eq!( + result, + (3 * (77_777 + WEB3_MAXIMAL_GAS_LIMIT_MARGIN)) as u128 * 444 + ); + } +} diff --git a/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/blockchain_agent.rs b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/blockchain_agent.rs new file mode 100644 index 000000000..704704457 --- /dev/null +++ b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/blockchain_agent.rs @@ -0,0 +1,37 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::arbitrary_id_stamp_in_trait; +use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; +use crate::sub_lib::wallet::Wallet; +use web3::types::U256; + +// Table of chains by +// +// a) adoption of the fee market (variations on "gas price") +// b) customizable limit of allowed computation ("gas limit") +// +// CHAINS a) | b) +//-------------------------------+------ +// Ethereum yes | yes +// Polygon yes | yes +// Qtum yes | yes +// NEO yes | no* +// Cardano no | yes +// Bitcoin yes | no + +//* defaulted limit + +pub trait BlockchainAgent: Send { + fn estimated_transaction_fee_total(&self, number_of_transactions: usize) -> u128; + fn consuming_wallet_balances(&self) -> ConsumingWalletBalances; + fn agreed_fee_per_computation_unit(&self) -> u64; + fn consuming_wallet(&self) -> &Wallet; + fn pending_transaction_id(&self) -> U256; + + #[cfg(test)] + fn dup(&self) -> Box { + intentionally_blank!() + } + as_any_in_trait!(); + arbitrary_id_stamp_in_trait!(); +} diff --git a/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/mod.rs b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/mod.rs new file mode 100644 index 000000000..257c88fde --- /dev/null +++ b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/mod.rs @@ -0,0 +1,68 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +pub mod agent_null; +pub mod agent_web3; +pub mod blockchain_agent; +pub mod msgs; +pub mod test_utils; + +use crate::accountant::payment_adjuster::Adjustment; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::BlockchainAgentWithContextMessage; +use crate::accountant::scanners::Scanner; +use crate::sub_lib::blockchain_bridge::OutboundPaymentsInstructions; +use actix::Message; +use itertools::Either; +use masq_lib::logger::Logger; + +pub trait MultistagePayableScanner: + Scanner + SolvencySensitivePaymentInstructor +where + BeginMessage: Message, + EndMessage: Message, +{ +} + +pub trait SolvencySensitivePaymentInstructor { + fn try_skipping_payment_adjustment( + &self, + msg: BlockchainAgentWithContextMessage, + logger: &Logger, + ) -> Result, String>; + + fn perform_payment_adjustment( + &self, + setup: PreparedAdjustment, + logger: &Logger, + ) -> OutboundPaymentsInstructions; +} + +pub struct PreparedAdjustment { + pub original_setup_msg: BlockchainAgentWithContextMessage, + pub adjustment: Adjustment, +} + +impl PreparedAdjustment { + pub fn new( + original_setup_msg: BlockchainAgentWithContextMessage, + adjustment: Adjustment, + ) -> Self { + Self { + original_setup_msg, + adjustment, + } + } +} + +#[cfg(test)] +mod tests { + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::PreparedAdjustment; + + impl Clone for PreparedAdjustment { + fn clone(&self) -> Self { + Self { + original_setup_msg: self.original_setup_msg.clone(), + adjustment: self.adjustment.clone(), + } + } + } +} diff --git a/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/msgs.rs b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/msgs.rs new file mode 100644 index 000000000..d4cd40be4 --- /dev/null +++ b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/msgs.rs @@ -0,0 +1,72 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; +use crate::accountant::{ResponseSkeleton, SkeletonOptHolder}; +use actix::Message; +use masq_lib::type_obfuscation::Obfuscated; +use std::fmt::Debug; + +#[derive(Debug, Message, PartialEq, Eq, Clone)] +pub struct QualifiedPayablesMessage { + pub protected_qualified_payables: Obfuscated, + pub response_skeleton_opt: Option, +} + +impl QualifiedPayablesMessage { + pub(in crate::accountant) fn new( + protected_qualified_payables: Obfuscated, + response_skeleton_opt: Option, + ) -> Self { + Self { + protected_qualified_payables, + response_skeleton_opt, + } + } +} + +impl SkeletonOptHolder for QualifiedPayablesMessage { + fn skeleton_opt(&self) -> Option { + self.response_skeleton_opt + } +} + +#[derive(Message)] +pub struct BlockchainAgentWithContextMessage { + pub protected_qualified_payables: Obfuscated, + pub agent: Box, + pub response_skeleton_opt: Option, +} + +impl BlockchainAgentWithContextMessage { + pub fn new( + qualified_payables: Obfuscated, + blockchain_agent: Box, + response_skeleton_opt: Option, + ) -> Self { + Self { + protected_qualified_payables: qualified_payables, + agent: blockchain_agent, + response_skeleton_opt, + } + } +} + +#[cfg(test)] +mod tests { + + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::BlockchainAgentWithContextMessage; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::test_utils::BlockchainAgentMock; + + impl Clone for BlockchainAgentWithContextMessage { + fn clone(&self) -> Self { + let original_agent_id = self.agent.arbitrary_id_stamp(); + let cloned_agent = + BlockchainAgentMock::default().set_arbitrary_id_stamp(original_agent_id); + Self { + protected_qualified_payables: self.protected_qualified_payables.clone(), + agent: Box::new(cloned_agent), + response_skeleton_opt: self.response_skeleton_opt, + } + } + } +} diff --git a/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/test_utils.rs b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/test_utils.rs new file mode 100644 index 000000000..5de2a6b06 --- /dev/null +++ b/node/src/accountant/scanners/mid_scan_msg_handling/payable_scanner/test_utils.rs @@ -0,0 +1,80 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +#![cfg(test)] + +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; +use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; +use crate::sub_lib::wallet::Wallet; +use crate::test_utils::unshared_test_utils::arbitrary_id_stamp::ArbitraryIdStamp; +use crate::{arbitrary_id_stamp_in_trait_impl, set_arbitrary_id_stamp_in_mock_impl}; +use ethereum_types::U256; +use std::cell::RefCell; + +#[derive(Default)] +pub struct BlockchainAgentMock { + consuming_wallet_balances_results: RefCell>, + agreed_fee_per_computation_unit_results: RefCell>, + consuming_wallet_result_opt: Option, + pending_transaction_id_results: RefCell>, + arbitrary_id_stamp_opt: Option, +} + +impl BlockchainAgent for BlockchainAgentMock { + fn estimated_transaction_fee_total(&self, _number_of_transactions: usize) -> u128 { + todo!("to be implemented by GH-711") + } + + fn consuming_wallet_balances(&self) -> ConsumingWalletBalances { + todo!("to be implemented by GH-711") + } + + fn agreed_fee_per_computation_unit(&self) -> u64 { + self.agreed_fee_per_computation_unit_results + .borrow_mut() + .remove(0) + } + + fn consuming_wallet(&self) -> &Wallet { + self.consuming_wallet_result_opt.as_ref().unwrap() + } + + fn pending_transaction_id(&self) -> U256 { + self.pending_transaction_id_results.borrow_mut().remove(0) + } + + fn dup(&self) -> Box { + intentionally_blank!() + } + + arbitrary_id_stamp_in_trait_impl!(); +} + +impl BlockchainAgentMock { + pub fn consuming_wallet_balances_result(self, result: ConsumingWalletBalances) -> Self { + self.consuming_wallet_balances_results + .borrow_mut() + .push(result); + self + } + + pub fn agreed_fee_per_computation_unit_result(self, result: u64) -> Self { + self.agreed_fee_per_computation_unit_results + .borrow_mut() + .push(result); + self + } + + pub fn consuming_wallet_result(mut self, consuming_wallet_result: Wallet) -> Self { + self.consuming_wallet_result_opt = Some(consuming_wallet_result); + self + } + + pub fn pending_transaction_id_result(self, result: U256) -> Self { + self.pending_transaction_id_results + .borrow_mut() + .push(result); + self + } + + set_arbitrary_id_stamp_in_mock_impl!(); +} diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index bbf62709d..0e4174e83 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -1,10 +1,13 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. +pub mod mid_scan_msg_handling; pub mod scanners_utils; +pub mod test_utils; -use crate::accountant::database_access_objects::payable_dao::{PayableAccount, PayableDao, PendingPayable}; -use crate::accountant::database_access_objects::pending_payable_dao::PendingPayableDao; -use crate::accountant::database_access_objects::receivable_dao::ReceivableDao; +use crate::accountant::db_access_objects::payable_dao::{PayableAccount, PayableDao}; +use crate::accountant::db_access_objects::pending_payable_dao::{PendingPayable, PendingPayableDao}; +use crate::accountant::db_access_objects::receivable_dao::ReceivableDao; +use crate::accountant::payment_adjuster::{PaymentAdjuster, PaymentAdjusterReal}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableTransactingErrorEnum::{ LocallyCausedError, RemotelyCausedErrors, }; @@ -12,7 +15,7 @@ use crate::accountant::scanners::scanners_utils::payable_scanner_utils::{ debugging_summary_after_error_separation, err_msg_if_failed_without_existing_fingerprints, investigate_debt_extremes, mark_pending_payable_fatal_error, payables_debug_summary, separate_errors, separate_rowids_and_hashes, PayableThresholdsGauge, - PayableThresholdsGaugeReal, PayableTransactingErrorEnum, PendingPayableTriple, + PayableThresholdsGaugeReal, PayableTransactingErrorEnum, PendingPayableMetadata, VecOfRowidOptAndHash, }; use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::{ @@ -26,17 +29,18 @@ use crate::accountant::{ ReportTransactionReceipts, RequestTransactionReceipts, ResponseSkeleton, ScanForPayables, ScanForPendingPayables, ScanForReceivables, SentPayables, }; -use crate::accountant::database_access_objects::banned_dao::BannedDao; +use crate::accountant::db_access_objects::banned_dao::BannedDao; use crate::blockchain::blockchain_bridge::{PendingPayableFingerprint, RetrieveTransactions}; -use crate::blockchain::blockchain_interface::PayableTransactionError; use crate::sub_lib::accountant::{ DaoFactories, FinancialStatistics, PaymentThresholds, ScanIntervals, }; -use crate::sub_lib::blockchain_bridge::RequestBalancesToPayPayables; +use crate::sub_lib::blockchain_bridge::{ + OutboundPaymentsInstructions, +}; use crate::sub_lib::utils::{NotifyLaterHandle, NotifyLaterHandleReal}; use crate::sub_lib::wallet::Wallet; -use actix::{Context, Message, System}; -use itertools::Itertools; +use actix::{Context, Message}; +use itertools::{Either, Itertools}; use masq_lib::logger::Logger; use masq_lib::logger::TIME_FORMATTING_STRING; use masq_lib::messages::{ScanType, ToMessageBody, UiScanResponse}; @@ -45,15 +49,19 @@ use masq_lib::utils::ExpectValue; #[cfg(test)] use std::any::Any; use std::cell::RefCell; +use std::collections::HashMap; use std::rc::Rc; -use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; use time::format_description::parse; use time::OffsetDateTime; use web3::types::{TransactionReceipt, H256}; +use masq_lib::type_obfuscation::Obfuscated; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::{PreparedAdjustment, MultistagePayableScanner, SolvencySensitivePaymentInstructor}; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::{BlockchainAgentWithContextMessage, QualifiedPayablesMessage}; +use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; pub struct Scanners { - pub payable: Box>, + pub payable: Box>, pub pending_payable: Box>, pub receivable: Box>, } @@ -71,6 +79,7 @@ impl Scanners { dao_factories.payable_dao_factory.make(), dao_factories.pending_payable_dao_factory.make(), Rc::clone(&payment_thresholds), + Box::new(PaymentAdjusterReal::new()), )), pending_payable: Box::new(PendingPayableScanner::new( dao_factories.payable_dao_factory.make(), @@ -105,7 +114,8 @@ where fn scan_started_at(&self) -> Option; fn mark_as_started(&mut self, timestamp: SystemTime); fn mark_as_ended(&mut self, logger: &Logger); - declare_as_any!(); + + as_any_in_trait!(); } pub struct ScannerCommon { @@ -169,15 +179,16 @@ pub struct PayableScanner { pub payable_dao: Box, pub pending_payable_dao: Box, pub payable_threshold_gauge: Box, + pub payment_adjuster: Box, } -impl Scanner for PayableScanner { +impl Scanner for PayableScanner { fn begin_scan( &mut self, timestamp: SystemTime, response_skeleton_opt: Option, logger: &Logger, - ) -> Result { + ) -> Result { if let Some(timestamp) = self.scan_started_at() { return Err(BeginScanError::ScanAlreadyRunning(timestamp)); } @@ -191,10 +202,10 @@ impl Scanner for PayableScanner { investigate_debt_extremes(timestamp, &all_non_pending_payables) ); - let qualified_payable = + let qualified_payables = self.sniff_out_alarming_payables_and_maybe_log_them(all_non_pending_payables, logger); - match qualified_payable.is_empty() { + match qualified_payables.is_empty() { true => { self.mark_as_ended(logger); Err(BeginScanError::NothingToProcess) @@ -203,12 +214,12 @@ impl Scanner for PayableScanner { info!( logger, "Chose {} qualified debts to pay", - qualified_payable.len() + qualified_payables.len() ); - Ok(RequestBalancesToPayPayables { - accounts: qualified_payable, - response_skeleton_opt, - }) + let protected_payables = self.protect_payables(qualified_payables); + let outgoing_msg = + QualifiedPayablesMessage::new(protected_payables, response_skeleton_opt); + Ok(outgoing_msg) } } } @@ -238,20 +249,58 @@ impl Scanner for PayableScanner { time_marking_methods!(Payables); - implement_as_any!(); + as_any_in_trait_impl!(); } +impl SolvencySensitivePaymentInstructor for PayableScanner { + fn try_skipping_payment_adjustment( + &self, + msg: BlockchainAgentWithContextMessage, + logger: &Logger, + ) -> Result, String> { + match self + .payment_adjuster + .search_for_indispensable_adjustment(&msg, logger) + { + Ok(None) => { + let protected = msg.protected_qualified_payables; + let unprotected = self.expose_payables(protected); + Ok(Either::Left(OutboundPaymentsInstructions::new( + unprotected, + msg.agent, + msg.response_skeleton_opt, + ))) + } + Ok(Some(adjustment)) => Ok(Either::Right(PreparedAdjustment::new(msg, adjustment))), + Err(_e) => todo!("be implemented with GH-711"), + } + } + + fn perform_payment_adjustment( + &self, + setup: PreparedAdjustment, + logger: &Logger, + ) -> OutboundPaymentsInstructions { + let now = SystemTime::now(); + self.payment_adjuster.adjust_payments(setup, now, logger) + } +} + +impl MultistagePayableScanner for PayableScanner {} + impl PayableScanner { pub fn new( payable_dao: Box, pending_payable_dao: Box, payment_thresholds: Rc, + payment_adjuster: Box, ) -> Self { Self { common: ScannerCommon::new(payment_thresholds), payable_dao, pending_payable_dao, payable_threshold_gauge: Box::new(PayableThresholdsGaugeReal::default()), + payment_adjuster, } } @@ -317,30 +366,71 @@ impl PayableScanner { } } - fn separate_id_triples_by_existent_and_nonexistent_fingerprints<'a>( + fn separate_existent_and_nonexistent_fingerprints<'a>( &'a self, - sent_payments: &'a [&'a PendingPayable], - ) -> (Vec, Vec) { - let hashes = sent_payments + sent_payables: &'a [&'a PendingPayable], + ) -> (Vec, Vec) { + fn sew_metadata( + ((rowid_opt, hash), pending_payable): ((Option, H256), &PendingPayable), + ) -> PendingPayableMetadata { + PendingPayableMetadata::new(&pending_payable.recipient_wallet, hash, rowid_opt) + } + + let hashes = sent_payables .iter() .map(|pending_payable| pending_payable.hash) .collect::>(); - self.pending_payable_dao + + let sent_payables_sorted_by_hashes = sent_payables + .iter() + .sorted_by(|a, b| Ord::cmp(&a.hash, &b.hash)) + .copied() + .collect::>(); + + let rowid_pairs_sorted_by_hashes = self + .pending_payable_dao .fingerprints_rowids(&hashes) .into_iter() - .zip(sent_payments.iter()) - .map( - |((rowid_opt, hash), pending_payable)| PendingPayableTriple { - recipient: &pending_payable.recipient_wallet, - hash, - rowid_opt, - }, + .sorted_by(|(_, hash_a), (_, hash_b)| Ord::cmp(&hash_a, &hash_b)) + .collect::, H256)>>(); + + if !Self::is_symmetrical( + &sent_payables_sorted_by_hashes, + &rowid_pairs_sorted_by_hashes, + ) { + panic!( + "Inconsistency in two maps, they cannot be matched by hashes. Data set directly \ + sent from BlockchainBridge: {:?}, set derived from the DB: {:?}", + sent_payables_sorted_by_hashes, rowid_pairs_sorted_by_hashes ) + } + + rowid_pairs_sorted_by_hashes + .into_iter() + .zip(sent_payables_sorted_by_hashes.into_iter()) + .map(sew_metadata) .partition(|pp_triple| pp_triple.rowid_opt.is_some()) } + fn is_symmetrical( + sent_payables_sorted_by_hashes: &[&PendingPayable], + rowid_pairs_sorted_by_hashes: &[(Option, H256)], + ) -> bool { + let map_a = sent_payables_sorted_by_hashes + .iter() + .map(|pp| pp.hash) + .sorted() + .collect::>(); + let map_b = rowid_pairs_sorted_by_hashes + .iter() + .map(|(_, hash)| *hash) + .sorted() + .collect::>(); + map_a == map_b + } + fn mark_pending_payable(&self, sent_payments: &[&PendingPayable], logger: &Logger) { - fn missing_fingerprints_msg(nonexistent: &[PendingPayableTriple]) -> String { + fn missing_fingerprints_msg(nonexistent: &[PendingPayableMetadata]) -> String { format!( "Expected pending payable fingerprints for {} were not found; system unreliable", comma_joined_stringifiable(nonexistent, |pp_triple| format!( @@ -350,7 +440,7 @@ impl PayableScanner { ) } fn ready_data_for_supply<'a>( - existent: &'a [PendingPayableTriple], + existent: &'a [PendingPayableMetadata], ) -> Vec<(&'a Wallet, u64)> { existent .iter() @@ -359,7 +449,7 @@ impl PayableScanner { } let (existent, nonexistent) = - self.separate_id_triples_by_existent_and_nonexistent_fingerprints(sent_payments); + self.separate_existent_and_nonexistent_fingerprints(sent_payments); let mark_pp_input_data = ready_data_for_supply(&existent); if !mark_pp_input_data.is_empty() { if let Err(e) = self @@ -449,6 +539,14 @@ impl PayableScanner { panic!("{}", msg) }; } + + fn protect_payables(&self, payables: Vec) -> Obfuscated { + Obfuscated::obfuscate_vector(payables) + } + + fn expose_payables(&self, obfuscated: Obfuscated) -> Vec { + obfuscated.expose_vector() + } } pub struct PendingPayableScanner { @@ -520,7 +618,7 @@ impl Scanner for PendingP time_marking_methods!(PendingPayables); - implement_as_any!(); + as_any_in_trait_impl!(); } impl PendingPayableScanner { @@ -780,7 +878,7 @@ impl Scanner for ReceivableScanner { time_marking_methods!(Receivables); - implement_as_any!(); + as_any_in_trait_impl!(); } impl ReceivableScanner { @@ -891,193 +989,88 @@ impl BeginScanError { } } -pub struct NullScanner {} - -impl Scanner for NullScanner -where - BeginMessage: Message, - EndMessage: Message, -{ - fn begin_scan( - &mut self, - _timestamp: SystemTime, - _response_skeleton_opt: Option, - _logger: &Logger, - ) -> Result { - Err(BeginScanError::CalledFromNullScanner) - } - - fn finish_scan(&mut self, _message: EndMessage, _logger: &Logger) -> Option { - panic!("Called finish_scan() from NullScanner"); - } - - fn scan_started_at(&self) -> Option { - panic!("Called scan_started_at() from NullScanner"); - } - - fn mark_as_started(&mut self, _timestamp: SystemTime) { - panic!("Called mark_as_started() from NullScanner"); - } - - fn mark_as_ended(&mut self, _logger: &Logger) { - panic!("Called mark_as_ended() from NullScanner"); - } - - implement_as_any!(); -} - -impl Default for NullScanner { - fn default() -> Self { - Self::new() - } +pub struct ScanSchedulers { + pub schedulers: HashMap>, } -impl NullScanner { - pub fn new() -> Self { - Self {} +impl ScanSchedulers { + pub fn new(scan_intervals: ScanIntervals) -> Self { + let schedulers = HashMap::from_iter([ + ( + ScanType::Payables, + Box::new(PeriodicalScanScheduler:: { + handle: Box::new(NotifyLaterHandleReal::default()), + interval: scan_intervals.payable_scan_interval, + }) as Box, + ), + ( + ScanType::PendingPayables, + Box::new(PeriodicalScanScheduler:: { + handle: Box::new(NotifyLaterHandleReal::default()), + interval: scan_intervals.pending_payable_scan_interval, + }), + ), + ( + ScanType::Receivables, + Box::new(PeriodicalScanScheduler:: { + handle: Box::new(NotifyLaterHandleReal::default()), + interval: scan_intervals.receivable_scan_interval, + }), + ), + ]); + ScanSchedulers { schedulers } } } -pub struct ScannerMock { - begin_scan_params: Arc>>, - begin_scan_results: RefCell>>, - end_scan_params: Arc>>, - end_scan_results: RefCell>>, - stop_system_after_last_message: RefCell, +pub struct PeriodicalScanScheduler { + pub handle: Box>, + pub interval: Duration, } -impl Scanner - for ScannerMock -where - BeginMessage: Message, - EndMessage: Message, -{ - fn begin_scan( - &mut self, - _timestamp: SystemTime, - _response_skeleton_opt: Option, - _logger: &Logger, - ) -> Result { - self.begin_scan_params.lock().unwrap().push(()); - if self.is_allowed_to_stop_the_system() && self.is_last_message() { - System::current().stop(); - } - self.begin_scan_results.borrow_mut().remove(0) - } - - fn finish_scan(&mut self, message: EndMessage, _logger: &Logger) -> Option { - self.end_scan_params.lock().unwrap().push(message); - if self.is_allowed_to_stop_the_system() && self.is_last_message() { - System::current().stop(); - } - self.end_scan_results.borrow_mut().remove(0) - } - - fn scan_started_at(&self) -> Option { - intentionally_blank!() - } - - fn mark_as_started(&mut self, _timestamp: SystemTime) { +pub trait ScanScheduler { + fn schedule(&self, ctx: &mut Context); + fn interval(&self) -> Duration { intentionally_blank!() } - fn mark_as_ended(&mut self, _logger: &Logger) { - intentionally_blank!() - } -} + as_any_in_trait!(); -impl Default for ScannerMock { - fn default() -> Self { - Self::new() - } + #[cfg(test)] + fn as_any_mut(&mut self) -> &mut dyn Any; } -impl ScannerMock { - pub fn new() -> Self { - Self { - begin_scan_params: Arc::new(Mutex::new(vec![])), - begin_scan_results: RefCell::new(vec![]), - end_scan_params: Arc::new(Mutex::new(vec![])), - end_scan_results: RefCell::new(vec![]), - stop_system_after_last_message: RefCell::new(false), - } +impl ScanScheduler for PeriodicalScanScheduler { + fn schedule(&self, ctx: &mut Context) { + // the default of the message implies response_skeleton_opt to be None + // because scheduled scans don't respond + let _ = self.handle.notify_later(T::default(), self.interval, ctx); } - - pub fn begin_scan_params(mut self, params: &Arc>>) -> Self { - self.begin_scan_params = params.clone(); - self + fn interval(&self) -> Duration { + self.interval } - pub fn begin_scan_result(self, result: Result) -> Self { - self.begin_scan_results.borrow_mut().push(result); - self - } + as_any_in_trait_impl!(); - pub fn stop_the_system(self) -> Self { - self.stop_system_after_last_message.replace(true); + #[cfg(test)] + fn as_any_mut(&mut self) -> &mut dyn Any { self } - - pub fn is_allowed_to_stop_the_system(&self) -> bool { - *self.stop_system_after_last_message.borrow() - } - - pub fn is_last_message(&self) -> bool { - self.is_last_message_from_begin_scan() || self.is_last_message_from_end_scan() - } - - pub fn is_last_message_from_begin_scan(&self) -> bool { - self.begin_scan_results.borrow().len() == 1 && self.end_scan_results.borrow().is_empty() - } - - pub fn is_last_message_from_end_scan(&self) -> bool { - self.end_scan_results.borrow().len() == 1 && self.begin_scan_results.borrow().is_empty() - } -} - -pub struct ScanTimings { - pub pending_payable: PeriodicalScanConfig, - pub payable: PeriodicalScanConfig, - pub receivable: PeriodicalScanConfig, -} - -impl ScanTimings { - pub fn new(scan_intervals: ScanIntervals) -> Self { - ScanTimings { - pending_payable: PeriodicalScanConfig { - handle: Box::new(NotifyLaterHandleReal::default()), - interval: scan_intervals.pending_payable_scan_interval, - }, - payable: PeriodicalScanConfig { - handle: Box::new(NotifyLaterHandleReal::default()), - interval: scan_intervals.payable_scan_interval, - }, - receivable: PeriodicalScanConfig { - handle: Box::new(NotifyLaterHandleReal::default()), - interval: scan_intervals.receivable_scan_interval, - }, - } - } -} - -pub struct PeriodicalScanConfig { - pub handle: Box>, - pub interval: Duration, -} - -impl PeriodicalScanConfig { - pub fn schedule_another_periodic_scan(&self, ctx: &mut Context) { - // the default of the message implies response_skeleton_opt to be None - // because scheduled scans don't respond - let _ = self.handle.notify_later(T::default(), self.interval, ctx); - } } #[cfg(test)] mod tests { + use crate::accountant::db_access_objects::payable_dao::{PayableAccount, PayableDaoError}; + use crate::accountant::db_access_objects::pending_payable_dao::{ + PendingPayable, PendingPayableDaoError, + }; + use crate::accountant::db_access_objects::utils::{from_time_t, to_time_t}; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::QualifiedPayablesMessage; + use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PendingPayableMetadata; + use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::PendingPayableScanReport; + use crate::accountant::scanners::test_utils::protect_payables_in_test; use crate::accountant::scanners::{ - BeginScanError, PayableScanner, PendingPayableScanner, ReceivableScanner, Scanner, - ScannerCommon, Scanners, + BeginScanError, PayableScanner, PendingPayableScanner, ReceivableScanner, ScanSchedulers, + Scanner, ScannerCommon, Scanners, }; use crate::accountant::test_utils::{ make_custom_payment_thresholds, make_payable_account, make_payables, @@ -1092,26 +1085,15 @@ mod tests { RequestTransactionReceipts, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC, }; use crate::blockchain::blockchain_bridge::{PendingPayableFingerprint, RetrieveTransactions}; - use std::cell::RefCell; - use std::ops::Sub; - use std::panic::{catch_unwind, AssertUnwindSafe}; - - use crate::accountant::database_access_objects::dao_utils::{from_time_t, to_time_t}; - use crate::accountant::database_access_objects::payable_dao::{ - PayableAccount, PayableDaoError, PendingPayable, - }; - use crate::accountant::database_access_objects::pending_payable_dao::PendingPayableDaoError; - use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableThresholdsGaugeReal; - use crate::accountant::scanners::scanners_utils::pending_payable_scanner_utils::PendingPayableScanReport; - use crate::blockchain::blockchain_interface::ProcessedPayableFallible::{Correct, Failed}; - use crate::blockchain::blockchain_interface::{ - BlockchainTransaction, PayableTransactionError, RpcPayableFailure, + use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; + use crate::blockchain::blockchain_interface::data_structures::{ + BlockchainTransaction, RpcPayablesFailure, }; use crate::blockchain::test_utils::make_tx_hash; use crate::sub_lib::accountant::{ - DaoFactories, FinancialStatistics, PaymentThresholds, DEFAULT_PAYMENT_THRESHOLDS, + DaoFactories, FinancialStatistics, PaymentThresholds, ScanIntervals, + DEFAULT_PAYMENT_THRESHOLDS, }; - use crate::sub_lib::blockchain_bridge::RequestBalancesToPayPayables; use crate::test_utils::make_wallet; use actix::{Message, System}; use ethereum_types::U64; @@ -1119,6 +1101,9 @@ mod tests { use masq_lib::messages::ScanType; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use regex::Regex; + use std::cell::RefCell; + use std::ops::Sub; + use std::panic::{catch_unwind, AssertUnwindSafe}; use std::rc::Rc; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; @@ -1179,11 +1164,6 @@ mod tests { &payment_thresholds ); assert_eq!(payable_scanner.common.initiated_at_opt.is_some(), false); - payable_scanner - .payable_threshold_gauge - .as_any() - .downcast_ref::() - .unwrap(); assert_eq!( pending_payable_scanner.when_pending_too_long_sec, when_pending_too_long_sec @@ -1219,6 +1199,18 @@ mod tests { ); } + #[test] + fn protected_payables_can_be_cast_from_and_back_to_vec_of_payable_accounts_by_payable_scanner() + { + let initial_unprotected = vec![make_payable_account(123), make_payable_account(456)]; + let subject = PayableScannerBuilder::new().build(); + + let protected = subject.protect_payables(initial_unprotected.clone()); + let again_unprotected: Vec = subject.expose_payables(protected); + + assert_eq!(initial_unprotected, again_unprotected) + } + #[test] fn payable_scanner_can_initiate_a_scan() { init_test_logging(); @@ -1238,8 +1230,10 @@ mod tests { assert_eq!(timestamp, Some(now)); assert_eq!( result, - Ok(RequestBalancesToPayPayables { - accounts: qualified_payable_accounts.clone(), + Ok(QualifiedPayablesMessage { + protected_qualified_payables: protect_payables_in_test( + qualified_payable_accounts.clone() + ), response_skeleton_opt: None, }) ); @@ -1306,7 +1300,7 @@ mod tests { let failure_payable_hash_2 = make_tx_hash(0xde); let failure_payable_rowid_2 = 126; let failure_payable_wallet_2 = make_wallet("hihihi"); - let failure_payable_2 = RpcPayableFailure { + let failure_payable_2 = RpcPayablesFailure { rpc_error: Error::InvalidResponse( "Learn how to write before you send your garbage!".to_string(), ), @@ -1321,8 +1315,8 @@ mod tests { let pending_payable_dao = PendingPayableDaoMock::default() .fingerprints_rowids_params(&fingerprints_rowids_params_arc) .fingerprints_rowids_result(vec![ - (Some(correct_payable_rowid_1), correct_payable_hash_1), (Some(correct_payable_rowid_3), correct_payable_hash_3), + (Some(correct_payable_rowid_1), correct_payable_hash_1), ]) .fingerprints_rowids_result(vec![( Some(failure_payable_rowid_2), @@ -1341,9 +1335,9 @@ mod tests { let logger = Logger::new(test_name); let sent_payable = SentPayables { payment_procedure_result: Ok(vec![ - Correct(correct_pending_payable_1), - Failed(failure_payable_2), - Correct(correct_pending_payable_3), + Ok(correct_pending_payable_1), + Err(failure_payable_2), + Ok(correct_pending_payable_3), ]), response_skeleton_opt: None, }; @@ -1397,11 +1391,183 @@ mod tests { )); } + #[test] + fn entries_must_be_kept_consistent_and_aligned() { + let wallet_1 = make_wallet("abc"); + let hash_1 = make_tx_hash(123); + let wallet_2 = make_wallet("def"); + let hash_2 = make_tx_hash(345); + let wallet_3 = make_wallet("ghi"); + let hash_3 = make_tx_hash(546); + let wallet_4 = make_wallet("jkl"); + let hash_4 = make_tx_hash(678); + let pending_payables_owned = vec![ + PendingPayable::new(wallet_1.clone(), hash_1), + PendingPayable::new(wallet_2.clone(), hash_2), + PendingPayable::new(wallet_3.clone(), hash_3), + PendingPayable::new(wallet_4.clone(), hash_4), + ]; + let pending_payables_ref = pending_payables_owned + .iter() + .collect::>(); + let pending_payable_dao = PendingPayableDaoMock::new().fingerprints_rowids_result(vec![ + (Some(4), hash_4), + (Some(1), hash_1), + (Some(3), hash_3), + (Some(2), hash_2), + ]); + let subject = PayableScannerBuilder::new() + .pending_payable_dao(pending_payable_dao) + .build(); + + let (existent, nonexistent) = + subject.separate_existent_and_nonexistent_fingerprints(&pending_payables_ref); + + assert_eq!( + existent, + vec![ + PendingPayableMetadata::new(&wallet_1, hash_1, Some(1)), + PendingPayableMetadata::new(&wallet_2, hash_2, Some(2)), + PendingPayableMetadata::new(&wallet_3, hash_3, Some(3)), + PendingPayableMetadata::new(&wallet_4, hash_4, Some(4)) + ] + ); + assert!(nonexistent.is_empty()) + } + + struct TestingMismatchedDataAboutPendingPayables { + pending_payables: Vec, + common_hash_1: H256, + common_hash_3: H256, + intruder_for_hash_2: H256, + } + + fn prepare_values_for_mismatched_setting() -> TestingMismatchedDataAboutPendingPayables { + let hash_1 = make_tx_hash(123); + let hash_2 = make_tx_hash(456); + let hash_3 = make_tx_hash(789); + let intruder = make_tx_hash(567); + let pending_payables = vec![ + PendingPayable::new(make_wallet("abc"), hash_1), + PendingPayable::new(make_wallet("def"), hash_2), + PendingPayable::new(make_wallet("ghi"), hash_3), + ]; + TestingMismatchedDataAboutPendingPayables { + pending_payables, + common_hash_1: hash_1, + common_hash_3: hash_3, + intruder_for_hash_2: intruder, + } + } + + #[test] + #[should_panic( + expected = "Inconsistency in two maps, they cannot be matched by hashes. \ + Data set directly sent from BlockchainBridge: \ + [PendingPayable { recipient_wallet: Wallet { kind: Address(0x0000000000000000000000000000000000616263) }, \ + hash: 0x000000000000000000000000000000000000000000000000000000000000007b }, \ + PendingPayable { recipient_wallet: Wallet { kind: Address(0x0000000000000000000000000000000000646566) }, \ + hash: 0x00000000000000000000000000000000000000000000000000000000000001c8 }, \ + PendingPayable { recipient_wallet: Wallet { kind: Address(0x0000000000000000000000000000000000676869) }, \ + hash: 0x0000000000000000000000000000000000000000000000000000000000000315 }], \ + set derived from the DB: \ + [(Some(4), 0x000000000000000000000000000000000000000000000000000000000000007b), \ + (Some(1), 0x0000000000000000000000000000000000000000000000000000000000000237), \ + (Some(3), 0x0000000000000000000000000000000000000000000000000000000000000315)]" + )] + fn two_sourced_information_of_new_pending_payables_and_their_fingerprints_is_not_symmetrical() { + let vals = prepare_values_for_mismatched_setting(); + let pending_payables_ref = vals + .pending_payables + .iter() + .collect::>(); + let pending_payable_dao = PendingPayableDaoMock::new().fingerprints_rowids_result(vec![ + (Some(4), vals.common_hash_1), + (Some(1), vals.intruder_for_hash_2), + (Some(3), vals.common_hash_3), + ]); + let subject = PayableScannerBuilder::new() + .pending_payable_dao(pending_payable_dao) + .build(); + + subject.separate_existent_and_nonexistent_fingerprints(&pending_payables_ref); + } + + #[test] + fn symmetry_check_happy_path() { + let hash_1 = make_tx_hash(123); + let hash_2 = make_tx_hash(456); + let hash_3 = make_tx_hash(789); + let pending_payables_sent_from_blockchain_bridge = vec![ + PendingPayable::new(make_wallet("abc"), hash_1), + PendingPayable::new(make_wallet("def"), hash_2), + PendingPayable::new(make_wallet("ghi"), hash_3), + ]; + let pending_payables_ref = pending_payables_sent_from_blockchain_bridge + .iter() + .collect::>(); + let rowids_and_hashes_from_fingerprints = + vec![(Some(3), hash_1), (Some(5), hash_2), (Some(6), hash_3)]; + + let result = PayableScanner::is_symmetrical( + &pending_payables_ref, + &rowids_and_hashes_from_fingerprints, + ); + + assert_eq!(result, true) + } + + #[test] + fn symmetry_check_sad_path_for_intruder() { + let vals = prepare_values_for_mismatched_setting(); + let pending_payables_ref_from_blockchain_bridge = vals + .pending_payables + .iter() + .collect::>(); + let rowids_and_hashes_from_fingerprints = vec![ + (Some(3), vals.common_hash_1), + (Some(5), vals.intruder_for_hash_2), + (Some(6), vals.common_hash_3), + ]; + + let result = PayableScanner::is_symmetrical( + &pending_payables_ref_from_blockchain_bridge, + &rowids_and_hashes_from_fingerprints, + ); + + assert_eq!(result, false) + } + + #[test] + fn symmetry_check_indifferent_to_wrong_order_on_the_input() { + let hash_1 = make_tx_hash(123); + let hash_2 = make_tx_hash(456); + let hash_3 = make_tx_hash(789); + let pending_payables_sent_from_blockchain_bridge = vec![ + PendingPayable::new(make_wallet("abc"), hash_1), + PendingPayable::new(make_wallet("def"), hash_2), + PendingPayable::new(make_wallet("ghi"), hash_3), + ]; + let bb_returned_p_payables_ref = pending_payables_sent_from_blockchain_bridge + .iter() + .collect::>(); + // Not in an ascending order + let rowids_and_hashes_from_fingerprints = + vec![(Some(3), hash_1), (Some(5), hash_3), (Some(6), hash_2)]; + + let result = PayableScanner::is_symmetrical( + &bb_returned_p_payables_ref, + &rowids_and_hashes_from_fingerprints, + ); + + assert_eq!(result, true) + } + #[test] #[should_panic( - expected = "Expected pending payable fingerprints for (tx: 0x0000000000000000000000000000000000000000000000000000000000000315, \ - to wallet: 0x000000000000000000000000000000626f6f6761), (tx: 0x000000000000000000000000000000000000000000000000000000000000007b, \ - to wallet: 0x00000000000000000000000000000061676f6f62) were not found; system unreliable" + expected = "Expected pending payable fingerprints for (tx: 0x000000000000000000000000000000000000000000000000000000000000007b, \ + to wallet: 0x00000000000000000000000000000061676f6f62), (tx: 0x0000000000000000000000000000000000000000000000000000000000000315, \ + to wallet: 0x000000000000000000000000000000626f6f6761) were not found; system unreliable" )] fn payable_scanner_panics_when_fingerprints_for_correct_payments_not_found() { let hash_1 = make_tx_hash(0x315); @@ -1416,7 +1582,7 @@ mod tests { .pending_payable_dao(pending_payable_dao) .build(); let sent_payable = SentPayables { - payment_procedure_result: Ok(vec![Correct(payment_1), Correct(payment_2)]), + payment_procedure_result: Ok(vec![Ok(payment_1), Ok(payment_2)]), response_skeleton_opt: None, }; @@ -1439,7 +1605,7 @@ mod tests { .pending_payable_dao(pending_payable_dao) .build(); let sent_payables = SentPayables { - payment_procedure_result: Ok(vec![Correct(payable_1), Correct(payable_2)]), + payment_procedure_result: Ok(vec![Ok(payable_1), Ok(payable_2)]), response_skeleton_opt: None, }; @@ -1473,10 +1639,7 @@ mod tests { hash_2, ); - // the panic captured in the shared body cannot take record in the test log collection, - // despite it does get "ERROR: ..." printed in the reality; - // for that reason we can look for evidences of no ERROR log, which would've spoken for - // missing fingerprints otherwise + // Missing fingerprints, being an additional issue, would provoke an error log, but not here. TestLogHandler::new().exists_no_log_containing(&format!("ERROR: {test_name}:")); } @@ -1559,7 +1722,7 @@ mod tests { Deleting fingerprints for failed transactions 0x00000000000000000000000000000000000000000000000000000000000015b3, \ 0x0000000000000000000000000000000000000000000000000000000000003039", )); - //we haven't supplied any result for mark_pending_payable() and so it's proved uncalled + // we haven't supplied any result for mark_pending_payable() and so it's proved uncalled } #[test] @@ -1623,16 +1786,16 @@ mod tests { 00000000000000000000000000000000000000000315 failed due to RecordDeletion(\"Gosh, I overslept \ without an alarm set\")"); let log_handler = TestLogHandler::new(); - // there is a situation when we also stumble over missing fingerprints and so we log this - // actuality. Here we don't and so that ERROR log shouldn't be there + // There is a possible situation when we stumble over missing fingerprints and so we log it. + // Here we don't and so any ERROR log shouldn't turn up log_handler.exists_no_log_containing(&format!("ERROR: {}", test_name)) } #[test] - fn payable_scanner_panics_for_missing_fingerprints_of_some_failed_payments_but_deletion_works() - { + fn payable_scanner_panics_for_missing_fingerprints_but_deletion_of_some_works() { init_test_logging(); - let test_name = "payable_scanner_panics_for_missing_fingerprints_of_some_failed_payments_but_deletion_works"; + let test_name = + "payable_scanner_panics_for_missing_fingerprints_but_deletion_of_some_works"; let hash_1 = make_tx_hash(0x1b669); let hash_2 = make_tx_hash(0x3039); let hash_3 = make_tx_hash(0x223d); @@ -1673,11 +1836,12 @@ mod tests { } #[test] - fn payable_scanner_failed_txs_with_fingerprint_missing_and_deletion_of_the_other_one_fails() { - // two fatal failures, missing fingerprints and fingerprint deletion error are both legitimate - // reasons for panic + fn payable_scanner_for_failed_rpcs_one_fingerprint_missing_and_deletion_of_the_other_one_fails() + { + // Two fatal failures at once, missing fingerprints and fingerprint deletion error are both + // legitimate reasons for panic init_test_logging(); - let test_name = "payable_scanner_failed_txs_with_fingerprint_missing_and_deletion_of_the_other_one_fails"; + let test_name = "payable_scanner_for_failed_rpcs_one_fingerprint_missing_and_deletion_of_the_other_one_fails"; let existent_record_hash = make_tx_hash(0xb26e); let nonexistent_record_hash = make_tx_hash(0x4d2); let pending_payable_dao = PendingPayableDaoMock::default() @@ -1691,12 +1855,12 @@ mod tests { let mut subject = PayableScannerBuilder::new() .pending_payable_dao(pending_payable_dao) .build(); - let failed_payment_1 = Failed(RpcPayableFailure { + let failed_payment_1 = Err(RpcPayablesFailure { rpc_error: Error::Unreachable, recipient_wallet: make_wallet("abc"), hash: existent_record_hash, }); - let failed_payment_2 = Failed(RpcPayableFailure { + let failed_payment_2 = Err(RpcPayablesFailure { rpc_error: Error::Internal, recipient_wallet: make_wallet("def"), hash: nonexistent_record_hash, @@ -1755,7 +1919,7 @@ mod tests { threshold_value, DEFAULT_PAYMENT_THRESHOLDS.maturity_threshold_sec ) - //no other method was called (absence of panic) and that means we returned early + // No panic and so no other method was called, which means an early return } #[test] @@ -2923,7 +3087,7 @@ mod tests { let logger = Logger::new(test_name); let log_handler = TestLogHandler::new(); - assert_elapsed_time_in_mark_as_ended::( + assert_elapsed_time_in_mark_as_ended::( &mut PayableScannerBuilder::new().build(), "Payables", test_name, @@ -2945,4 +3109,40 @@ mod tests { &log_handler, ); } + + #[test] + fn scan_schedulers_can_be_properly_initialized() { + let scan_intervals = ScanIntervals { + payable_scan_interval: Duration::from_secs(240), + pending_payable_scan_interval: Duration::from_secs(300), + receivable_scan_interval: Duration::from_secs(360), + }; + + let result = ScanSchedulers::new(scan_intervals); + + assert_eq!( + result + .schedulers + .get(&ScanType::Payables) + .unwrap() + .interval(), + scan_intervals.payable_scan_interval + ); + assert_eq!( + result + .schedulers + .get(&ScanType::PendingPayables) + .unwrap() + .interval(), + scan_intervals.pending_payable_scan_interval + ); + assert_eq!( + result + .schedulers + .get(&ScanType::Receivables) + .unwrap() + .interval(), + scan_intervals.receivable_scan_interval + ); + } } diff --git a/node/src/accountant/scanners/scanners_utils.rs b/node/src/accountant/scanners/scanners_utils.rs index ec9e3c0b1..a68718a36 100644 --- a/node/src/accountant/scanners/scanners_utils.rs +++ b/node/src/accountant/scanners/scanners_utils.rs @@ -1,28 +1,25 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. pub mod payable_scanner_utils { - use crate::accountant::database_access_objects::dao_utils::ThresholdUtils; - use crate::accountant::database_access_objects::payable_dao::{PayableAccount, PayableDaoError, PendingPayable}; + use crate::accountant::db_access_objects::utils::ThresholdUtils; + use crate::accountant::db_access_objects::payable_dao::{PayableAccount, PayableDaoError}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableTransactingErrorEnum::{ LocallyCausedError, RemotelyCausedErrors, }; - use crate::accountant::{comma_joined_stringifiable, SentPayables}; - use crate::blockchain::blockchain_interface::ProcessedPayableFallible::{Correct, Failed}; - use crate::blockchain::blockchain_interface::{ - PayableTransactionError, ProcessedPayableFallible, RpcPayableFailure, - }; + use crate::accountant::{comma_joined_stringifiable, ProcessedPayableFallible, SentPayables}; use crate::masq_lib::utils::ExpectValue; use crate::sub_lib::accountant::PaymentThresholds; use crate::sub_lib::wallet::Wallet; use itertools::Itertools; use masq_lib::logger::Logger; - #[cfg(test)] - use std::any::Any; use std::cmp::Ordering; use std::ops::Not; use std::time::SystemTime; use thousands::Separable; use web3::types::H256; + use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; + use crate::blockchain::blockchain_interface::data_structures::errors::PayableTransactionError; + use crate::blockchain::blockchain_interface::data_structures::{RpcPayablesFailure}; pub type VecOfRowidOptAndHash = Vec<(Option, H256)>; @@ -157,8 +154,8 @@ pub mod payable_scanner_utils { logger: &'b Logger, ) -> SeparateTxsByResult<'a> { match rpc_result { - Correct(pending_payable) => add_pending_payable(acc, pending_payable), - Failed(RpcPayableFailure { + Ok(pending_payable) => add_pending_payable(acc, pending_payable), + Err(RpcPayablesFailure { rpc_error, recipient_wallet, hash, @@ -223,17 +220,32 @@ pub mod payable_scanner_utils { } } - pub struct PendingPayableTriple<'a> { + #[derive(Debug, PartialEq, Eq)] + pub struct PendingPayableMetadata<'a> { pub recipient: &'a Wallet, pub hash: H256, pub rowid_opt: Option, } + impl<'a> PendingPayableMetadata<'a> { + pub fn new( + recipient: &'a Wallet, + hash: H256, + rowid_opt: Option, + ) -> PendingPayableMetadata<'a> { + PendingPayableMetadata { + recipient, + hash, + rowid_opt, + } + } + } + pub fn mark_pending_payable_fatal_error( sent_payments: &[&PendingPayable], - nonexistent: &[PendingPayableTriple], + nonexistent: &[PendingPayableMetadata], error: PayableDaoError, - missing_fingerprints_msg_maker: fn(&[PendingPayableTriple]) -> String, + missing_fingerprints_msg_maker: fn(&[PendingPayableMetadata]) -> String, logger: &Logger, ) { if !nonexistent.is_empty() { @@ -279,7 +291,7 @@ pub mod payable_scanner_utils { payment_thresholds: &PaymentThresholds, x: u64, ) -> u128; - declare_as_any!(); + as_any_in_trait!(); } #[derive(Default)] @@ -301,7 +313,7 @@ pub mod payable_scanner_utils { ) -> u128 { ThresholdUtils::calculate_finite_debt_limit_by_age(payment_thresholds, debt_age) } - implement_as_any!(); + as_any_in_trait_impl!(); } } @@ -399,7 +411,7 @@ pub mod pending_payable_scanner_utils { } pub mod receivable_scanner_utils { - use crate::accountant::database_access_objects::receivable_dao::ReceivableAccount; + use crate::accountant::db_access_objects::receivable_dao::ReceivableAccount; use crate::accountant::wei_to_gwei; use std::time::{Duration, SystemTime}; use thousands::Separable; @@ -415,9 +427,9 @@ pub mod receivable_scanner_utils { #[cfg(test)] mod tests { - use crate::accountant::database_access_objects::dao_utils::{from_time_t, to_time_t}; - use crate::accountant::database_access_objects::payable_dao::{PayableAccount, PendingPayable}; - use crate::accountant::database_access_objects::receivable_dao::ReceivableAccount; + use crate::accountant::db_access_objects::utils::{from_time_t, to_time_t}; + use crate::accountant::db_access_objects::payable_dao::{PayableAccount}; + use crate::accountant::db_access_objects::receivable_dao::ReceivableAccount; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableTransactingErrorEnum::{ LocallyCausedError, RemotelyCausedErrors, }; @@ -428,10 +440,6 @@ mod tests { }; use crate::accountant::scanners::scanners_utils::receivable_scanner_utils::balance_and_age; use crate::accountant::{checked_conversion, gwei_to_wei, SentPayables}; - use crate::blockchain::blockchain_interface::ProcessedPayableFallible::{Correct, Failed}; - use crate::blockchain::blockchain_interface::{ - BlockchainError, PayableTransactionError, RpcPayableFailure, - }; use crate::blockchain::test_utils::make_tx_hash; use crate::sub_lib::accountant::PaymentThresholds; use crate::test_utils::make_wallet; @@ -439,6 +447,9 @@ mod tests { use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use std::time::SystemTime; + use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; + use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainError, PayableTransactionError}; + use crate::blockchain::blockchain_interface::data_structures::{RpcPayablesFailure}; #[test] fn investigate_debt_extremes_picks_the_most_relevant_records() { @@ -508,8 +519,8 @@ mod tests { }; let sent_payable = SentPayables { payment_procedure_result: Ok(vec![ - Correct(correct_payment_1.clone()), - Correct(correct_payment_2.clone()), + Ok(correct_payment_1.clone()), + Ok(correct_payment_2.clone()), ]), response_skeleton_opt: None, }; @@ -550,16 +561,13 @@ mod tests { recipient_wallet: make_wallet("blah"), hash: make_tx_hash(123), }; - let bad_rpc_call = RpcPayableFailure { - rpc_error: web3::Error::InvalidResponse("that donkey screwed it up".to_string()), + let bad_rpc_call = RpcPayablesFailure { + rpc_error: web3::Error::InvalidResponse("That jackass screwed it up".to_string()), recipient_wallet: make_wallet("whooa"), hash: make_tx_hash(0x315), }; let sent_payable = SentPayables { - payment_procedure_result: Ok(vec![ - Correct(payable_ok.clone()), - Failed(bad_rpc_call.clone()), - ]), + payment_procedure_result: Ok(vec![Ok(payable_ok.clone()), Err(bad_rpc_call.clone())]), response_skeleton_opt: None, }; @@ -567,9 +575,10 @@ mod tests { assert_eq!(oks, vec![&payable_ok]); assert_eq!(errs, Some(RemotelyCausedErrors(vec![make_tx_hash(0x315)]))); - TestLogHandler::new().exists_log_containing("WARN: test_logger: Remote transaction failure: 'Got invalid response: \ - that donkey screwed it up' for payment to 0x00000000000000000000000000000077686f6f61 and transaction hash \ - 0x0000000000000000000000000000000000000000000000000000000000000315. Please check your blockchain service URL configuration."); + TestLogHandler::new().exists_log_containing("WARN: test_logger: Remote transaction failure: \ + 'Got invalid response: That jackass screwed it up' for payment to 0x000000000000000000000000\ + 00000077686f6f61 and transaction hash 0x0000000000000000000000000000000000000000000000000000\ + 000000000315. Please check your blockchain service URL configuration."); } #[test] @@ -755,7 +764,7 @@ mod tests { #[test] fn count_total_errors_says_unknown_number_for_early_local_errors() { let early_local_errors = [ - PayableTransactionError::TransactionCount(BlockchainError::QueryFailed( + PayableTransactionError::TransactionID(BlockchainError::QueryFailed( "blah".to_string(), )), PayableTransactionError::MissingConsumingWallet, diff --git a/node/src/accountant/scanners/test_utils.rs b/node/src/accountant/scanners/test_utils.rs new file mode 100644 index 000000000..c43d6f71b --- /dev/null +++ b/node/src/accountant/scanners/test_utils.rs @@ -0,0 +1,10 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +#![cfg(test)] + +use crate::accountant::db_access_objects::payable_dao::PayableAccount; +use masq_lib::type_obfuscation::Obfuscated; + +pub fn protect_payables_in_test(payables: Vec) -> Obfuscated { + Obfuscated::obfuscate_vector(payables) +} diff --git a/node/src/accountant/test_utils.rs b/node/src/accountant/test_utils.rs index 7ecfc125a..f63370578 100644 --- a/node/src/accountant/test_utils.rs +++ b/node/src/accountant/test_utils.rs @@ -2,42 +2,58 @@ #![cfg(test)] -use crate::accountant::database_access_objects::banned_dao::{BannedDao, BannedDaoFactory}; -use crate::accountant::database_access_objects::dao_utils::{from_time_t, to_time_t, CustomQuery}; -use crate::accountant::database_access_objects::payable_dao::{ +use crate::accountant::db_access_objects::banned_dao::{BannedDao, BannedDaoFactory}; +use crate::accountant::db_access_objects::payable_dao::{ PayableAccount, PayableDao, PayableDaoError, PayableDaoFactory, }; -use crate::accountant::database_access_objects::pending_payable_dao::{ +use crate::accountant::db_access_objects::pending_payable_dao::{ PendingPayableDao, PendingPayableDaoError, PendingPayableDaoFactory, }; -use crate::accountant::database_access_objects::receivable_dao::{ +use crate::accountant::db_access_objects::receivable_dao::{ ReceivableAccount, ReceivableDao, ReceivableDaoError, ReceivableDaoFactory, }; +use crate::accountant::db_access_objects::utils::{from_time_t, to_time_t, CustomQuery}; +use crate::accountant::payment_adjuster::{Adjustment, AnalysisError, PaymentAdjuster}; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::{ + BlockchainAgentWithContextMessage, QualifiedPayablesMessage, +}; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::{ + MultistagePayableScanner, PreparedAdjustment, SolvencySensitivePaymentInstructor, +}; use crate::accountant::scanners::scanners_utils::payable_scanner_utils::PayableThresholdsGauge; -use crate::accountant::scanners::{PayableScanner, PendingPayableScanner, ReceivableScanner}; -use crate::accountant::{gwei_to_wei, Accountant, DEFAULT_PENDING_TOO_LONG_SEC}; +use crate::accountant::scanners::{ + BeginScanError, PayableScanner, PendingPayableScanner, PeriodicalScanScheduler, + ReceivableScanner, ScanSchedulers, Scanner, +}; +use crate::accountant::{ + gwei_to_wei, Accountant, ResponseSkeleton, SentPayables, DEFAULT_PENDING_TOO_LONG_SEC, +}; use crate::blockchain::blockchain_bridge::PendingPayableFingerprint; -use crate::blockchain::blockchain_interface::BlockchainTransaction; +use crate::blockchain::blockchain_interface::data_structures::BlockchainTransaction; use crate::blockchain::test_utils::make_tx_hash; use crate::bootstrapper::BootstrapperConfig; use crate::db_config::config_dao::{ConfigDao, ConfigDaoFactory}; use crate::db_config::mocks::ConfigDaoMock; use crate::sub_lib::accountant::{DaoFactories, FinancialStatistics}; use crate::sub_lib::accountant::{MessageIdGenerator, PaymentThresholds}; +use crate::sub_lib::blockchain_bridge::OutboundPaymentsInstructions; +use crate::sub_lib::utils::NotifyLaterHandle; use crate::sub_lib::wallet::Wallet; use crate::test_utils::make_wallet; use crate::test_utils::unshared_test_utils::make_bc_with_defaults; -use actix::System; +use actix::{Message, System}; use ethereum_types::H256; +use itertools::Either; use masq_lib::logger::Logger; -use masq_lib::utils::plus; +use masq_lib::messages::ScanType; +use masq_lib::ui_gateway::NodeToUiMessage; use rusqlite::{Connection, Row}; use std::any::type_name; use std::cell::RefCell; use std::fmt::Debug; use std::rc::Rc; use std::sync::{Arc, Mutex}; -use std::time::SystemTime; +use std::time::{Duration, SystemTime}; pub fn make_receivable_account(n: u64, expected_delinquent: bool) -> ReceivableAccount { let now = to_time_t(SystemTime::now()); @@ -140,7 +156,7 @@ impl DaoWithDestination { fn guts_for_dao_factory_queue_initialization( customized_supplied_daos: &mut Vec>, - acc: Vec>, + mut acc: Vec>, used_input: usize, position: DestinationMarker, ) -> (Vec>, usize) { @@ -151,9 +167,13 @@ fn guts_for_dao_factory_queue_initialization( Some(idx) => { let customized_dao = customized_supplied_daos.remove(idx).into_inner(); let used_input_updated = used_input + 1; - (plus(acc, Box::new(customized_dao)), used_input_updated) + acc.push(Box::new(customized_dao)); + (acc, used_input_updated) + } + None => { + acc.push(Box::new(Default::default())); + (acc, used_input) } - None => (plus(acc, Box::new(Default::default())), used_input), } } @@ -1040,6 +1060,7 @@ pub struct PayableScannerBuilder { payable_dao: PayableDaoMock, pending_payable_dao: PendingPayableDaoMock, payment_thresholds: PaymentThresholds, + payment_adjuster: PaymentAdjusterMock, } impl PayableScannerBuilder { @@ -1048,6 +1069,7 @@ impl PayableScannerBuilder { payable_dao: PayableDaoMock::new(), pending_payable_dao: PendingPayableDaoMock::new(), payment_thresholds: PaymentThresholds::default(), + payment_adjuster: PaymentAdjusterMock::default(), } } @@ -1056,6 +1078,14 @@ impl PayableScannerBuilder { self } + pub fn payment_adjuster( + mut self, + payment_adjuster: PaymentAdjusterMock, + ) -> PayableScannerBuilder { + self.payment_adjuster = payment_adjuster; + self + } + pub fn payment_thresholds(mut self, payment_thresholds: PaymentThresholds) -> Self { self.payment_thresholds = payment_thresholds; self @@ -1074,6 +1104,7 @@ impl PayableScannerBuilder { Box::new(self.payable_dao), Box::new(self.pending_payable_dao), Rc::new(self.payment_thresholds), + Box::new(self.payment_adjuster), ) } } @@ -1361,3 +1392,270 @@ impl PayableThresholdsGaugeMock { self } } + +#[derive(Default)] +pub struct PaymentAdjusterMock { + search_for_indispensable_adjustment_params: + Arc>>, + search_for_indispensable_adjustment_results: + RefCell, AnalysisError>>>, + adjust_payments_params: Arc>>, + adjust_payments_results: RefCell>, +} + +impl PaymentAdjuster for PaymentAdjusterMock { + fn search_for_indispensable_adjustment( + &self, + msg: &BlockchainAgentWithContextMessage, + logger: &Logger, + ) -> Result, AnalysisError> { + self.search_for_indispensable_adjustment_params + .lock() + .unwrap() + .push((msg.clone(), logger.clone())); + self.search_for_indispensable_adjustment_results + .borrow_mut() + .remove(0) + } + + fn adjust_payments( + &self, + setup: PreparedAdjustment, + now: SystemTime, + logger: &Logger, + ) -> OutboundPaymentsInstructions { + self.adjust_payments_params + .lock() + .unwrap() + .push((setup.clone(), now, logger.clone())); + self.adjust_payments_results.borrow_mut().remove(0) + } +} + +impl PaymentAdjusterMock { + pub fn is_adjustment_required_params( + mut self, + params: &Arc>>, + ) -> Self { + self.search_for_indispensable_adjustment_params = params.clone(); + self + } + + pub fn is_adjustment_required_result( + self, + result: Result, AnalysisError>, + ) -> Self { + self.search_for_indispensable_adjustment_results + .borrow_mut() + .push(result); + self + } + + pub fn adjust_payments_params( + mut self, + params: &Arc>>, + ) -> Self { + self.adjust_payments_params = params.clone(); + self + } + + pub fn adjust_payments_result(self, result: OutboundPaymentsInstructions) -> Self { + self.adjust_payments_results.borrow_mut().push(result); + self + } +} + +macro_rules! formal_traits_for_payable_mid_scan_msg_handling { + ($scanner:ty) => { + impl MultistagePayableScanner for $scanner {} + + impl SolvencySensitivePaymentInstructor for $scanner { + fn try_skipping_payment_adjustment( + &self, + _msg: BlockchainAgentWithContextMessage, + _logger: &Logger, + ) -> Result, String> { + intentionally_blank!() + } + + fn perform_payment_adjustment( + &self, + _setup: PreparedAdjustment, + _logger: &Logger, + ) -> OutboundPaymentsInstructions { + intentionally_blank!() + } + } + }; +} + +pub struct NullScanner {} + +impl Scanner for NullScanner +where + BeginMessage: Message, + EndMessage: Message, +{ + fn begin_scan( + &mut self, + _timestamp: SystemTime, + _response_skeleton_opt: Option, + _logger: &Logger, + ) -> Result { + Err(BeginScanError::CalledFromNullScanner) + } + + fn finish_scan(&mut self, _message: EndMessage, _logger: &Logger) -> Option { + panic!("Called finish_scan() from NullScanner"); + } + + fn scan_started_at(&self) -> Option { + panic!("Called scan_started_at() from NullScanner"); + } + + fn mark_as_started(&mut self, _timestamp: SystemTime) { + panic!("Called mark_as_started() from NullScanner"); + } + + fn mark_as_ended(&mut self, _logger: &Logger) { + panic!("Called mark_as_ended() from NullScanner"); + } + + as_any_in_trait_impl!(); +} + +formal_traits_for_payable_mid_scan_msg_handling!(NullScanner); + +impl Default for NullScanner { + fn default() -> Self { + Self::new() + } +} + +impl NullScanner { + pub fn new() -> Self { + Self {} + } +} + +pub struct ScannerMock { + begin_scan_params: Arc>>, + begin_scan_results: RefCell>>, + end_scan_params: Arc>>, + end_scan_results: RefCell>>, + stop_system_after_last_message: RefCell, +} + +impl Scanner + for ScannerMock +where + BeginMessage: Message, + EndMessage: Message, +{ + fn begin_scan( + &mut self, + _timestamp: SystemTime, + _response_skeleton_opt: Option, + _logger: &Logger, + ) -> Result { + self.begin_scan_params.lock().unwrap().push(()); + if self.is_allowed_to_stop_the_system() && self.is_last_message() { + System::current().stop(); + } + self.begin_scan_results.borrow_mut().remove(0) + } + + fn finish_scan(&mut self, message: EndMessage, _logger: &Logger) -> Option { + self.end_scan_params.lock().unwrap().push(message); + if self.is_allowed_to_stop_the_system() && self.is_last_message() { + System::current().stop(); + } + self.end_scan_results.borrow_mut().remove(0) + } + + fn scan_started_at(&self) -> Option { + intentionally_blank!() + } + + fn mark_as_started(&mut self, _timestamp: SystemTime) { + intentionally_blank!() + } + + fn mark_as_ended(&mut self, _logger: &Logger) { + intentionally_blank!() + } +} + +impl Default for ScannerMock { + fn default() -> Self { + Self::new() + } +} + +impl ScannerMock { + pub fn new() -> Self { + Self { + begin_scan_params: Arc::new(Mutex::new(vec![])), + begin_scan_results: RefCell::new(vec![]), + end_scan_params: Arc::new(Mutex::new(vec![])), + end_scan_results: RefCell::new(vec![]), + stop_system_after_last_message: RefCell::new(false), + } + } + + pub fn begin_scan_params(mut self, params: &Arc>>) -> Self { + self.begin_scan_params = params.clone(); + self + } + + pub fn begin_scan_result(self, result: Result) -> Self { + self.begin_scan_results.borrow_mut().push(result); + self + } + + pub fn stop_the_system_after_last_msg(self) -> Self { + self.stop_system_after_last_message.replace(true); + self + } + + pub fn is_allowed_to_stop_the_system(&self) -> bool { + *self.stop_system_after_last_message.borrow() + } + + pub fn is_last_message(&self) -> bool { + self.is_last_message_from_begin_scan() || self.is_last_message_from_end_scan() + } + + pub fn is_last_message_from_begin_scan(&self) -> bool { + self.begin_scan_results.borrow().len() == 1 && self.end_scan_results.borrow().is_empty() + } + + pub fn is_last_message_from_end_scan(&self) -> bool { + self.end_scan_results.borrow().len() == 1 && self.begin_scan_results.borrow().is_empty() + } +} + +formal_traits_for_payable_mid_scan_msg_handling!(ScannerMock); + +impl ScanSchedulers { + pub fn update_scheduler( + &mut self, + scan_type: ScanType, + handle_opt: Option>>, + interval_opt: Option, + ) { + let scheduler = self + .schedulers + .get_mut(&scan_type) + .unwrap() + .as_any_mut() + .downcast_mut::>() + .unwrap(); + if let Some(new_handle) = handle_opt { + scheduler.handle = new_handle + } + if let Some(new_interval) = interval_opt { + scheduler.interval = new_interval + } + } +} diff --git a/node/src/actor_system_factory.rs b/node/src/actor_system_factory.rs index 8ea9c7646..870460dd1 100644 --- a/node/src/actor_system_factory.rs +++ b/node/src/actor_system_factory.rs @@ -10,18 +10,14 @@ use super::stream_handler_pool::StreamHandlerPool; use super::stream_handler_pool::StreamHandlerPoolSubs; use super::stream_messages::PoolBindMessage; use super::ui_gateway::UiGateway; -use crate::accountant::database_access_objects::banned_dao::{ - BannedCacheLoader, BannedCacheLoaderReal, -}; -use crate::blockchain::blockchain_bridge::BlockchainBridge; +use crate::accountant::db_access_objects::banned_dao::{BannedCacheLoader, BannedCacheLoaderReal}; +use crate::blockchain::blockchain_bridge::{BlockchainBridge, BlockchainBridgeSubsFactoryReal}; use crate::bootstrapper::CryptDEPair; use crate::database::db_initializer::DbInitializationConfig; use crate::database::db_initializer::{connection_or_panic, DbInitializer, DbInitializerReal}; use crate::db_config::persistent_configuration::PersistentConfiguration; use crate::node_configurator::configurator::Configurator; -use crate::sub_lib::accountant::{ - AccountantSubs, AccountantSubsFactory, AccountantSubsFactoryReal, DaoFactories, -}; +use crate::sub_lib::accountant::{AccountantSubs, AccountantSubsFactoryReal, DaoFactories}; use crate::sub_lib::blockchain_bridge::BlockchainBridgeSubs; use crate::sub_lib::configurator::ConfiguratorSubs; use crate::sub_lib::cryptde::CryptDE; @@ -71,18 +67,13 @@ impl ActorSystemFactory for ActorSystemFactoryReal { &self, config: BootstrapperConfig, actor_factory: Box, - persist_config: Box, + persistent_config: Box, ) -> StreamHandlerPoolSubs { - self.tools.validate_database_chain( - persist_config.as_ref(), - config.blockchain_bridge_config.chain, - ); - self.tools.prepare_initial_messages( - self.tools.cryptdes(), - config, - persist_config, - actor_factory, - ) + self.tools + .validate_database_chain(&*persistent_config, config.blockchain_bridge_config.chain); + let cryptdes = self.tools.cryptdes(); + self.tools + .prepare_initial_messages(cryptdes, config, persistent_config, actor_factory) } } @@ -157,7 +148,8 @@ impl ActorSystemFactoryTools for ActorSystemFactoryToolsReal { is_decentralized: config.neighborhood_config.mode.is_decentralized(), crashable: is_crashable(&config), }); - let blockchain_bridge_subs = actor_factory.make_and_start_blockchain_bridge(&config); + let blockchain_bridge_subs = actor_factory + .make_and_start_blockchain_bridge(&config, &BlockchainBridgeSubsFactoryReal {}); let neighborhood_subs = actor_factory.make_and_start_neighborhood(cryptdes.main, &config); let accountant_subs = actor_factory.make_and_start_accountant( config.clone(), @@ -370,7 +362,7 @@ pub trait ActorFactory { config: BootstrapperConfig, db_initializer: &dyn DbInitializer, banned_cache_loader: &dyn BannedCacheLoader, - accountant_subs_factory: &dyn AccountantSubsFactory, + subs_factory: &dyn SubsFactory, ) -> AccountantSubs; fn make_and_start_ui_gateway(&self, config: &BootstrapperConfig) -> UiGatewaySubs; fn make_and_start_stream_handler_pool( @@ -378,8 +370,11 @@ pub trait ActorFactory { config: &BootstrapperConfig, ) -> StreamHandlerPoolSubs; fn make_and_start_proxy_client(&self, config: ProxyClientConfig) -> ProxyClientSubs; - fn make_and_start_blockchain_bridge(&self, config: &BootstrapperConfig) - -> BlockchainBridgeSubs; + fn make_and_start_blockchain_bridge( + &self, + config: &BootstrapperConfig, + subs_factory: &dyn SubsFactory, + ) -> BlockchainBridgeSubs; fn make_and_start_configurator(&self, config: &BootstrapperConfig) -> ConfiguratorSubs; } @@ -449,7 +444,7 @@ impl ActorFactory for ActorFactoryReal { config: BootstrapperConfig, db_initializer: &dyn DbInitializer, banned_cache_loader: &dyn BannedCacheLoader, - accountant_subs_factory: &dyn AccountantSubsFactory, + subs_factory: &dyn SubsFactory, ) -> AccountantSubs { let data_directory = config.data_directory.as_path(); let payable_dao_factory = Box::new(Accountant::dao_factory(data_directory)); @@ -469,7 +464,7 @@ impl ActorFactory for ActorFactoryReal { }, ) }); - accountant_subs_factory.make(&addr) + subs_factory.make(&addr) } fn make_and_start_ui_gateway(&self, config: &BootstrapperConfig) -> UiGatewaySubs { @@ -502,6 +497,7 @@ impl ActorFactory for ActorFactoryReal { fn make_and_start_blockchain_bridge( &self, config: &BootstrapperConfig, + subs_factory: &dyn SubsFactory, ) -> BlockchainBridgeSubs { let blockchain_service_url_opt = config .blockchain_bridge_config @@ -510,14 +506,15 @@ impl ActorFactory for ActorFactoryReal { let crashable = is_crashable(config); let wallet_opt = config.consuming_wallet_opt.clone(); let data_directory = config.data_directory.clone(); - let chain_id = config.blockchain_bridge_config.chain; + let chain = config.blockchain_bridge_config.chain; let arbiter = Arbiter::builder().stop_system_on_panic(true); let addr: Addr = arbiter.start(move |_| { - let (blockchain_interface, persistent_config) = BlockchainBridge::make_connections( + let blockchain_interface = BlockchainBridge::initialize_blockchain_interface( blockchain_service_url_opt, - data_directory, - chain_id, + chain, ); + let persistent_config = + BlockchainBridge::initialize_persistent_configuration(&data_directory); BlockchainBridge::new( blockchain_interface, persistent_config, @@ -525,7 +522,7 @@ impl ActorFactory for ActorFactoryReal { wallet_opt, ) }); - BlockchainBridge::make_subs_from(&addr) + subs_factory.make(&addr) } fn make_and_start_configurator(&self, config: &BootstrapperConfig) -> ConfiguratorSubs { @@ -615,18 +612,26 @@ impl LogRecipientSetter for LogRecipientSetterReal { } } +// Test writing easing stuff. If further examination of the actor +// starting methods in ActorFactory is desirable. +// This allows to get the started actor's address and then messages +// can be sent to it, possibly the AssertionMessage. +pub trait SubsFactory +where + Actor: actix::Actor, +{ + fn make(&self, addr: &Addr) -> ActorSubs; +} + #[cfg(test)] mod tests { use super::*; - use crate::accountant::check_sqlite_fns::TestUserDefinedSqliteFnsForNewDelinquencies; - use crate::accountant::test_utils::bc_from_earning_wallet; + use crate::accountant::exportable_test_parts::test_accountant_is_constructed_with_upgraded_db_connection_recognizing_our_extra_sqlite_functions; use crate::accountant::DEFAULT_PENDING_TOO_LONG_SEC; - use crate::actor_system_factory::tests::ShouldWeRunTheTest::{GoAhead, Skip}; + use crate::blockchain::blockchain_bridge::exportable_test_parts::test_blockchain_bridge_is_constructed_with_correctly_functioning_connections; use crate::bootstrapper::{Bootstrapper, RealUser}; - use crate::database::connection_wrapper::ConnectionWrapper; use crate::node_test_utils::{ - make_stream_handler_pool_subs_from, make_stream_handler_pool_subs_from_recorder, - start_recorder_refcell_opt, + make_stream_handler_pool_subs_from_recorder, start_recorder_refcell_opt, }; use crate::sub_lib::accountant::{PaymentThresholds, ScanIntervals}; use crate::sub_lib::blockchain_bridge::BlockchainBridgeConfig; @@ -640,20 +645,23 @@ mod tests { use crate::sub_lib::peer_actors::StartMessage; use crate::sub_lib::stream_handler_pool::TransmitDataMsg; use crate::sub_lib::ui_gateway::UiGatewayConfig; + use crate::test_utils::actor_system_factory::BannedCacheLoaderMock; use crate::test_utils::automap_mocks::{AutomapControlFactoryMock, AutomapControlMock}; use crate::test_utils::make_wallet; use crate::test_utils::neighborhood_test_utils::MIN_HOPS_FOR_TEST; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; use crate::test_utils::recorder::{ - make_accountant_subs_from_recorder, make_blockchain_bridge_subs_from, - make_configurator_subs_from, make_hopper_subs_from, make_neighborhood_subs_from, - make_proxy_client_subs_from, make_proxy_server_subs_from, - make_ui_gateway_subs_from_recorder, Recording, + make_accountant_subs_from_recorder, make_blockchain_bridge_subs_from_recorder, + make_configurator_subs_from_recorder, make_hopper_subs_from_recorder, + make_neighborhood_subs_from_recorder, make_proxy_client_subs_from_recorder, + make_proxy_server_subs_from_recorder, make_ui_gateway_subs_from_recorder, Recording, }; use crate::test_utils::recorder::{make_recorder, Recorder}; use crate::test_utils::unshared_test_utils::arbitrary_id_stamp::ArbitraryIdStamp; - use crate::test_utils::unshared_test_utils::assert_on_initialization_with_panic_on_migration; use crate::test_utils::unshared_test_utils::system_killer_actor::SystemKillerActor; + use crate::test_utils::unshared_test_utils::{ + assert_on_initialization_with_panic_on_migration, SubsFactoryTestAddrLeaker, + }; use crate::test_utils::{alias_cryptde, rate_pack}; use crate::test_utils::{main_cryptde, make_cryptde_pair}; use crate::{hopper, proxy_client, proxy_server, stream_handler_pool, ui_gateway}; @@ -663,27 +671,20 @@ mod tests { use automap_lib::mocks::{ parameterizable_automap_control, TransactorMock, PUBLIC_IP, ROUTER_IP, }; - use crossbeam_channel::{bounded, unbounded, Sender}; + use crossbeam_channel::unbounded; use log::LevelFilter; use masq_lib::constants::DEFAULT_CHAIN; use masq_lib::crash_point::CrashPoint; #[cfg(feature = "log_recipient_test")] use masq_lib::logger::INITIALIZATION_COUNTER; use masq_lib::messages::{ToMessageBody, UiCrashRequest, UiDescriptorRequest}; - use masq_lib::test_utils::utils::{ - check_if_source_code_is_attached, ensure_node_home_directory_exists, ShouldWeRunTheTest, - TEST_DEFAULT_CHAIN, - }; + use masq_lib::test_utils::utils::{ensure_node_home_directory_exists, TEST_DEFAULT_CHAIN}; use masq_lib::ui_gateway::NodeFromUiMessage; use masq_lib::utils::running_test; use masq_lib::utils::AutomapProtocol::Igdp; - use regex::Regex; use std::cell::RefCell; use std::collections::HashMap; use std::convert::TryFrom; - use std::env::current_dir; - use std::fs::File; - use std::io::{BufRead, BufReader}; use std::net::Ipv4Addr; use std::net::{IpAddr, SocketAddr, SocketAddrV4}; use std::path::PathBuf; @@ -797,17 +798,6 @@ mod tests { } } - #[derive(Default)] - struct BannedCacheLoaderMock { - pub load_params: Arc>>>, - } - - impl BannedCacheLoader for BannedCacheLoaderMock { - fn load(&self, conn: Box) { - self.load_params.lock().unwrap().push(conn); - } - } - struct ActorFactoryMock<'a> { dispatcher: RefCell>, proxy_client: RefCell>, @@ -856,7 +846,7 @@ mod tests { .unwrap() .get_or_insert((cryptdes, config.clone())); let addr: Addr = ActorFactoryMock::start_recorder(&self.proxy_server); - make_proxy_server_subs_from(&addr) + make_proxy_server_subs_from_recorder(&addr) } fn make_and_start_hopper(&self, config: HopperConfig) -> HopperSubs { @@ -866,7 +856,7 @@ mod tests { .unwrap() .get_or_insert(config); let addr: Addr = start_recorder_refcell_opt(&self.hopper); - make_hopper_subs_from(&addr) + make_hopper_subs_from_recorder(&addr) } fn make_and_start_neighborhood( @@ -880,7 +870,7 @@ mod tests { .unwrap() .get_or_insert((cryptde, config.clone())); let addr: Addr = start_recorder_refcell_opt(&self.neighborhood); - make_neighborhood_subs_from(&addr) + make_neighborhood_subs_from_recorder(&addr) } fn make_and_start_accountant( @@ -888,7 +878,7 @@ mod tests { config: BootstrapperConfig, _db_initializer: &dyn DbInitializer, _banned_cache_loader: &dyn BannedCacheLoader, - _accountant_subs_factory: &dyn AccountantSubsFactory, + _accountant_subs_factory: &dyn SubsFactory, ) -> AccountantSubs { self.parameters .accountant_params @@ -924,12 +914,13 @@ mod tests { .unwrap() .get_or_insert(config); let addr: Addr = start_recorder_refcell_opt(&self.proxy_client); - make_proxy_client_subs_from(&addr) + make_proxy_client_subs_from_recorder(&addr) } fn make_and_start_blockchain_bridge( &self, config: &BootstrapperConfig, + _subs_factory: &dyn SubsFactory, ) -> BlockchainBridgeSubs { self.parameters .blockchain_bridge_params @@ -937,7 +928,7 @@ mod tests { .unwrap() .get_or_insert(config.clone()); let addr: Addr = start_recorder_refcell_opt(&self.blockchain_bridge); - make_blockchain_bridge_subs_from(&addr) + make_blockchain_bridge_subs_from_recorder(&addr) } fn make_and_start_configurator(&self, config: &BootstrapperConfig) -> ConfiguratorSubs { @@ -947,7 +938,7 @@ mod tests { .unwrap() .get_or_insert(config.clone()); let addr: Addr = start_recorder_refcell_opt(&self.configurator); - make_configurator_subs_from(&addr) + make_configurator_subs_from_recorder(&addr) } } @@ -1050,6 +1041,107 @@ mod tests { } } + #[test] + fn make_and_start_actors_happy_path() { + let validate_database_chain_params_arc = Arc::new(Mutex::new(vec![])); + let prepare_initial_messages_params_arc = Arc::new(Mutex::new(vec![])); + let (stream_handler_pool, _, stream_handler_pool_recording_arc) = make_recorder(); + let main_cryptde = main_cryptde(); + let alias_cryptde = alias_cryptde(); + let cryptde_pair = CryptDEPair { + main: main_cryptde, + alias: alias_cryptde, + }; + let main_cryptde_public_key_expected = pk_from_cryptde_null(main_cryptde); + let alias_cryptde_public_key_expected = pk_from_cryptde_null(alias_cryptde); + let actor_factory = Box::new(ActorFactoryReal {}); + let actor_factory_raw_address_expected = addr_of!(*actor_factory); + let persistent_config_expected_arbitrary_id = ArbitraryIdStamp::new(); + let persistent_config = Box::new( + PersistentConfigurationMock::default() + .set_arbitrary_id_stamp(persistent_config_expected_arbitrary_id), + ); + let stream_holder_pool_subs = + make_stream_handler_pool_subs_from_recorder(&stream_handler_pool.start()); + let actor_system_factor_tools = ActorSystemFactoryToolsMock::default() + .validate_database_chain_params(&validate_database_chain_params_arc) + .cryptdes_result(cryptde_pair) + .prepare_initial_messages_params(&prepare_initial_messages_params_arc) + .prepare_initial_messages_result(stream_holder_pool_subs); + let data_dir = PathBuf::new().join("parent_directory/child_directory"); + let subject = ActorSystemFactoryReal::new(Box::new(actor_system_factor_tools)); + let mut bootstrapper_config = BootstrapperConfig::new(); + bootstrapper_config.blockchain_bridge_config.chain = Chain::PolyMainnet; + bootstrapper_config.data_directory = data_dir.clone(); + bootstrapper_config.db_password_opt = Some("password".to_string()); + + let result = + subject.make_and_start_actors(bootstrapper_config, actor_factory, persistent_config); + + let mut validate_database_chain_params = validate_database_chain_params_arc.lock().unwrap(); + let (persistent_config_actual_arbitrary_id, actual_chain) = + validate_database_chain_params.remove(0); + assert_eq!( + persistent_config_actual_arbitrary_id, + persistent_config_expected_arbitrary_id + ); + assert_eq!(actual_chain, Chain::PolyMainnet); + assert!(validate_database_chain_params.is_empty()); + let mut prepare_initial_messages_params = + prepare_initial_messages_params_arc.lock().unwrap(); + let ( + main_cryptde_actual, + alias_cryptde_actual, + bootstrapper_config_actual, + actor_factory_actual, + persistent_config_actual, + ) = prepare_initial_messages_params.remove(0); + let main_cryptde_public_key_actual = pk_from_cryptde_null(main_cryptde_actual.as_ref()); + assert_eq!( + main_cryptde_public_key_actual, + main_cryptde_public_key_expected + ); + let alias_cryptde_public_key_actual = pk_from_cryptde_null(alias_cryptde_actual.as_ref()); + assert_eq!( + alias_cryptde_public_key_actual, + alias_cryptde_public_key_expected + ); + assert_eq!(bootstrapper_config_actual.data_directory, data_dir); + assert_eq!( + bootstrapper_config_actual.db_password_opt, + Some("password".to_string()) + ); + assert_eq!( + addr_of!(*actor_factory_actual), + actor_factory_raw_address_expected + ); + assert_eq!( + persistent_config_actual.arbitrary_id_stamp(), + persistent_config_expected_arbitrary_id + ); + assert!(prepare_initial_messages_params.is_empty()); + verify_recipient(&result.node_from_ui_sub, &stream_handler_pool_recording_arc) + } + + fn verify_recipient( + recipient: &Recipient, + recording_arc: &Arc>, + ) { + let system = System::new("verifying_recipient_returned_in_test"); + let expected_msg = NodeFromUiMessage { + client_id: 5, + body: UiDescriptorRequest {}.tmb(1), + }; + + recipient.try_send(expected_msg.clone()).unwrap(); + + System::current().stop_with_code(0); + system.run(); + let recording = recording_arc.lock().unwrap(); + let actual_msg = recording.get_record::(0); + assert_eq!(actual_msg, &expected_msg); + } + #[test] fn make_and_start_actors_sends_bind_messages() { let actor_factory = ActorFactoryMock::new(); @@ -1865,242 +1957,41 @@ mod tests { ); } - struct AccountantSubsFactoryTestOnly { - address_leaker: Sender>, - } - - impl AccountantSubsFactory for AccountantSubsFactoryTestOnly { - fn make(&self, addr: &Addr) -> AccountantSubs { - self.address_leaker.try_send(addr.clone()).unwrap(); - let nonsensical_addr = Recorder::new().start(); - make_accountant_subs_from_recorder(&nonsensical_addr) - } - } - - fn check_ongoing_usage_of_user_defined_fns_within_new_delinquencies_for_receivable_dao( - ) -> ShouldWeRunTheTest { - fn skip_down_to_first_line_saying_new_delinquencies( - previous: impl Iterator, - ) -> impl Iterator { - previous - .skip_while(|line| { - let adjusted_line: String = line - .chars() - .skip_while(|char| char.is_whitespace()) - .collect(); - !adjusted_line.starts_with("fn new_delinquencies(") - }) - .skip(1) - } - fn user_defined_functions_detected(line_undivided_fn_body: &str) -> bool { - line_undivided_fn_body.contains(" slope_drop_high_bytes(") - && line_undivided_fn_body.contains(" slope_drop_low_bytes(") - } - fn assert_is_not_trait_definition(body_lines: impl Iterator) -> String { - fn yield_if_contains_semicolon(line: &str) -> Option { - line.contains(';').then(|| line.to_string()) - } - let mut semicolon_line_opt = None; - let line_undivided_fn_body = body_lines - .map(|line| { - if semicolon_line_opt.is_none() { - if let Some(result) = yield_if_contains_semicolon(&line) { - semicolon_line_opt = Some(result) - } - } - line - }) - .collect::(); - if let Some(line) = semicolon_line_opt { - let regex = Regex::new(r"Vec<\w+>;").unwrap(); - if regex.is_match(&line) { - // The important part of the regex is the ending semicolon. Trait implementations don't use it; - // they just go on with an opening bracket of the function body. Its presence therefore signifies - // we have to do with a trait definition - panic!("the second parsed chunk of code is a trait definition and the implementation lies first") - } - } else { - () //means is a clean function body without semicolon - } - line_undivided_fn_body - } - - let current_dir = current_dir().unwrap(); - let file_path = current_dir.join(PathBuf::from_iter([ - "src", - "accountant", - "database_access_objects", - "receivable_dao.rs", - ])); - let file = match File::open(file_path) { - Ok(file) => file, - Err(_) => match check_if_source_code_is_attached(¤t_dir) { - Skip => return Skip, - _ => panic!( - "if panics, the file receivable_dao.rs probably doesn't exist or \ - has been moved to an unexpected location" - ), - }, - }; - let reader = BufReader::new(file); - let lines_without_fn_trait_definition = - skip_down_to_first_line_saying_new_delinquencies(reader.lines().flatten()); - let function_body_ready_for_final_check = { - let assumed_implemented_function_body = - skip_down_to_first_line_saying_new_delinquencies(lines_without_fn_trait_definition) - .take_while(|line| { - let adjusted_line: String = line - .chars() - .skip_while(|char| char.is_whitespace()) - .collect(); - !adjusted_line.starts_with("fn") - }); - assert_is_not_trait_definition(assumed_implemented_function_body) - }; - if user_defined_functions_detected(&function_body_ready_for_final_check) { - GoAhead - } else { - panic!("was about to test user-defined SQLite functions (slope_drop_high_bytes and slope_drop_low_bytes) - in new_delinquencies() but found out those are absent at the expected place and would leave falsely positive results") - } - } - #[test] - fn our_big_int_sqlite_functions_are_linked_to_receivable_dao_within_accountant() { - //condition: .new_delinquencies() still encompasses our user defined functions (that's why a formal check opens this test) - if let Skip = - check_ongoing_usage_of_user_defined_fns_within_new_delinquencies_for_receivable_dao() - { - eprintln!("skipping test our_big_int_sqlite_functions_are_linked_to_receivable_dao_within_accountant; - was unable to find receivable_dao.rs"); - return; + fn accountant_is_constructed_with_upgraded_db_connection_recognizing_our_extra_sqlite_functions( + ) { + let act = |bootstrapper_config: BootstrapperConfig, + db_initializer: DbInitializerReal, + banned_cache_loader: BannedCacheLoaderMock, + address_leaker: SubsFactoryTestAddrLeaker| { + ActorFactoryReal {}.make_and_start_accountant( + bootstrapper_config, + &db_initializer, + &banned_cache_loader, + &address_leaker, + ) }; - let data_dir = ensure_node_home_directory_exists( - "actor_system_factory", - "our_big_int_sqlite_functions_are_linked_to_receivable_dao_within_accountant", - ); - let _ = DbInitializerReal::default() - .initialize(data_dir.as_ref(), DbInitializationConfig::test_default()) - .unwrap(); - let mut b_config = bc_from_earning_wallet(make_wallet("mine")); - b_config.data_directory = data_dir; - let system = System::new( - "our_big_int_sqlite_functions_are_linked_to_receivable_dao_within_accountant", - ); - let (addr_tx, addr_rv) = bounded(1); - let subject = ActorFactoryReal {}; - - subject.make_and_start_accountant( - b_config, - &DbInitializerReal::default(), - &BannedCacheLoaderMock::default(), - &AccountantSubsFactoryTestOnly { - address_leaker: addr_tx, - }, - ); - let accountant_addr = addr_rv.try_recv().unwrap(); - //this message also stops the system after the check - accountant_addr - .try_send(TestUserDefinedSqliteFnsForNewDelinquencies {}) - .unwrap(); - assert_eq!(system.run(), 0); - //we didn't blow up, it recognized the functions - //this is an example of the error: "no such function: slope_drop_high_bytes" + test_accountant_is_constructed_with_upgraded_db_connection_recognizing_our_extra_sqlite_functions( + "actor_system_factory", + "accountant_is_constructed_with_upgraded_db_connection_recognizing_our_extra_sqlite_functions", + act, + ) } #[test] - fn make_and_start_actors_happy_path() { - let validate_database_chain_params_arc = Arc::new(Mutex::new(vec![])); - let prepare_initial_messages_params_arc = Arc::new(Mutex::new(vec![])); - let (recorder, _, recording_arc) = make_recorder(); - let stream_holder_pool_subs = make_stream_handler_pool_subs_from(Some(recorder)); - let mut bootstrapper_config = BootstrapperConfig::new(); - let irrelevant_data_dir = PathBuf::new().join("big_directory/small_directory"); - bootstrapper_config.blockchain_bridge_config.chain = Chain::PolyMainnet; - bootstrapper_config.data_directory = irrelevant_data_dir.clone(); - bootstrapper_config.db_password_opt = Some("chameleon".to_string()); - let main_cryptde = main_cryptde(); - let main_cryptde_public_key_before = public_key_for_dyn_cryptde_being_null(main_cryptde); - let alias_cryptde = alias_cryptde(); - let alias_cryptde_public_key_before = public_key_for_dyn_cryptde_being_null(alias_cryptde); - let actor_factory = Box::new(ActorFactoryReal {}) as Box; - let actor_factory_before_raw_address = addr_of!(*actor_factory); - let persistent_config_id = ArbitraryIdStamp::new(); - let persistent_config = Box::new( - PersistentConfigurationMock::default().set_arbitrary_id_stamp(persistent_config_id), - ); - let persistent_config_before_raw = addr_of!(*persistent_config); - let tools = ActorSystemFactoryToolsMock::default() - .cryptdes_result(CryptDEPair { - main: main_cryptde, - alias: alias_cryptde, - }) - .validate_database_chain_params(&validate_database_chain_params_arc) - .prepare_initial_messages_params(&prepare_initial_messages_params_arc) - .prepare_initial_messages_result(stream_holder_pool_subs); - let subject = ActorSystemFactoryReal::new(Box::new(tools)); - - let result = - subject.make_and_start_actors(bootstrapper_config, actor_factory, persistent_config); - - let mut validate_database_chain_params = validate_database_chain_params_arc.lock().unwrap(); - let (persistent_config_id_captured, chain) = validate_database_chain_params.remove(0); - assert!(validate_database_chain_params.is_empty()); - assert_eq!(persistent_config_id_captured, persistent_config_id); - assert_eq!(chain, Chain::PolyMainnet); - let mut prepare_initial_messages_params = - prepare_initial_messages_params_arc.lock().unwrap(); - let ( - main_cryptde_after, - alias_cryptde_after, - bootstrapper_config_after, - actor_factory_after, - persistent_config_after, - ) = prepare_initial_messages_params.remove(0); - assert!(prepare_initial_messages_params.is_empty()); - let main_cryptde_public_key_after = - public_key_for_dyn_cryptde_being_null(main_cryptde_after.as_ref()); - assert_eq!( - main_cryptde_public_key_after, - main_cryptde_public_key_before - ); - let alias_cryptde_public_key_after = - public_key_for_dyn_cryptde_being_null(alias_cryptde_after.as_ref()); - assert_eq!( - alias_cryptde_public_key_after, - alias_cryptde_public_key_before - ); - assert_eq!( - bootstrapper_config_after.data_directory, - irrelevant_data_dir - ); - assert_eq!( - bootstrapper_config_after.db_password_opt, - Some("chameleon".to_string()) - ); - assert_eq!( - addr_of!(*actor_factory_after), - actor_factory_before_raw_address - ); - assert_eq!( - addr_of!(*persistent_config_after), - persistent_config_before_raw - ); - let system = System::new("make_and_start_actors_happy_path"); - let msg_of_irrelevant_choice = NodeFromUiMessage { - client_id: 5, - body: UiDescriptorRequest {}.tmb(1), + fn blockchain_bridge_is_constructed_with_correctly_functioning_connections() { + let act = |bootstrapper_config: BootstrapperConfig, + address_leaker: SubsFactoryTestAddrLeaker| { + ActorFactoryReal {} + .make_and_start_blockchain_bridge(&bootstrapper_config, &address_leaker) }; - result - .node_from_ui_sub - .try_send(msg_of_irrelevant_choice.clone()) - .unwrap(); - System::current().stop_with_code(0); - system.run(); - let recording = recording_arc.lock().unwrap(); - let msg = recording.get_record::(0); - assert_eq!(msg, &msg_of_irrelevant_choice); + + test_blockchain_bridge_is_constructed_with_correctly_functioning_connections( + "actor_system_factory", + "blockchain_bridge_is_constructed_with_correctly_functioning_connections", + act, + ) } #[test] @@ -2121,7 +2012,7 @@ mod tests { assert_on_initialization_with_panic_on_migration(&data_dir, &act); } - fn public_key_for_dyn_cryptde_being_null(cryptde: &dyn CryptDE) -> &PublicKey { + fn pk_from_cryptde_null(cryptde: &dyn CryptDE) -> &PublicKey { let null_cryptde = <&CryptDENull>::from(cryptde); null_cryptde.public_key() } diff --git a/node/src/blockchain/bip32.rs b/node/src/blockchain/bip32.rs index 1234d0d03..6b869d96b 100644 --- a/node/src/blockchain/bip32.rs +++ b/node/src/blockchain/bip32.rs @@ -11,32 +11,32 @@ use std::hash::{Hash, Hasher}; use std::num::NonZeroU32; use tiny_hderive::bip32::ExtendedPrivKey; -#[allow(clippy::upper_case_acronyms)] #[derive(Debug)] -pub struct Bip32ECKeyProvider { +#[allow(clippy::upper_case_acronyms)] +pub struct Bip32EncryptionKeyProvider { secret_raw: Vec, } #[allow(clippy::from_over_into)] -impl Into for &Bip32ECKeyProvider { +impl Into for &Bip32EncryptionKeyProvider { fn into(self) -> Secp256k1SecretKey { secp256k1secrets::key::SecretKey::from_slice(&self.secret_raw).expect("internal error") } } #[allow(clippy::from_over_into)] -impl Into for &Bip32ECKeyProvider { +impl Into for &Bip32EncryptionKeyProvider { fn into(self) -> EthsignSecretKey { EthsignSecretKey::from_raw(self.secret_raw.as_ref()).expect("internal error") } } -impl Bip32ECKeyProvider { +impl Bip32EncryptionKeyProvider { const SECRET_KEY_LENGTH: usize = 32; pub fn from_raw_secret(secret_raw: &[u8]) -> Result { Self::validate_raw_input(secret_raw)?; - Ok(Bip32ECKeyProvider { + Ok(Bip32EncryptionKeyProvider { secret_raw: secret_raw.to_vec(), }) } @@ -83,7 +83,7 @@ impl Bip32ECKeyProvider { } } -impl TryFrom<(&[u8], &str)> for Bip32ECKeyProvider { +impl TryFrom<(&[u8], &str)> for Bip32EncryptionKeyProvider { type Error = String; fn try_from(seed_path: (&[u8], &str)) -> Result { @@ -99,7 +99,7 @@ impl TryFrom<(&[u8], &str)> for Bip32ECKeyProvider { } } -impl<'de> Deserialize<'de> for Bip32ECKeyProvider { +impl<'de> Deserialize<'de> for Bip32EncryptionKeyProvider { fn deserialize(deserializer: D) -> Result>::Error> where D: Deserializer<'de>, @@ -112,7 +112,7 @@ impl<'de> Deserialize<'de> for Bip32ECKeyProvider { } } -impl Serialize for Bip32ECKeyProvider { +impl Serialize for Bip32EncryptionKeyProvider { fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where S: Serializer, @@ -128,15 +128,15 @@ impl Serialize for Bip32ECKeyProvider { } } -impl PartialEq for Bip32ECKeyProvider { - fn eq(&self, other: &Bip32ECKeyProvider) -> bool { +impl PartialEq for Bip32EncryptionKeyProvider { + fn eq(&self, other: &Bip32EncryptionKeyProvider) -> bool { self.public_key().bytes().as_ref() == other.public_key().bytes().as_ref() } } -impl Eq for Bip32ECKeyProvider {} +impl Eq for Bip32EncryptionKeyProvider {} -impl Hash for Bip32ECKeyProvider { +impl Hash for Bip32EncryptionKeyProvider { fn hash(&self, state: &mut H) { self.public_key().bytes().hash(state); } @@ -153,7 +153,7 @@ mod tests { #[test] fn constants_have_correct_values() { - assert_eq!(Bip32ECKeyProvider::SECRET_KEY_LENGTH, 32); + assert_eq!(Bip32EncryptionKeyProvider::SECRET_KEY_LENGTH, 32); } #[test] @@ -165,7 +165,7 @@ mod tests { ) .unwrap(); let seed = Seed::new(&mnemonic, "Test123!"); - let bip32eckey_provider = Bip32ECKeyProvider::try_from(( + let bip32eckey_provider = Bip32EncryptionKeyProvider::try_from(( seed.as_ref(), DEFAULT_CONSUMING_DERIVATION_PATH.as_str(), )) @@ -186,9 +186,11 @@ mod tests { ) .unwrap(); let seed = Seed::new(&mnemonic, "Test123!"); - let bip32eckey_provider = - Bip32ECKeyProvider::try_from((seed.as_ref(), DEFAULT_EARNING_DERIVATION_PATH.as_str())) - .unwrap(); + let bip32eckey_provider = Bip32EncryptionKeyProvider::try_from(( + seed.as_ref(), + DEFAULT_EARNING_DERIVATION_PATH.as_str(), + )) + .unwrap(); let address: Address = bip32eckey_provider.address(); let expected_address: Address = serde_json::from_str::
("\"0x20eF925bBbFca786bd426BaED8c6Ae45e4284e12\"") @@ -207,7 +209,7 @@ mod tests { let mnemonic = Mnemonic::from_phrase(phrase, Language::English).unwrap(); let seed = Seed::new(&mnemonic, ""); - let key_provider = Bip32ECKeyProvider::try_from(( + let key_provider = Bip32EncryptionKeyProvider::try_from(( seed.as_bytes(), DEFAULT_CONSUMING_DERIVATION_PATH.as_str(), )) @@ -248,7 +250,7 @@ mod tests { .unwrap(); let seed = Seed::new(&mnemonic, "Test123!"); assert_eq!( - Bip32ECKeyProvider::try_from((seed.as_ref(), "")).unwrap_err(), + Bip32EncryptionKeyProvider::try_from((seed.as_ref(), "")).unwrap_err(), "InvalidDerivationPath".to_string() ); } @@ -256,23 +258,26 @@ mod tests { #[test] fn bip32_try_from_errors_with_empty_seed() { assert_eq!( - Bip32ECKeyProvider::try_from(("".as_ref(), DEFAULT_CONSUMING_DERIVATION_PATH.as_str())) - .unwrap_err(), + Bip32EncryptionKeyProvider::try_from(( + "".as_ref(), + DEFAULT_CONSUMING_DERIVATION_PATH.as_str() + )) + .unwrap_err(), "Invalid Seed Length: 0".to_string() ); } - fn keypair_a() -> Bip32ECKeyProvider { + fn keypair_a() -> Bip32EncryptionKeyProvider { let numbers = (0u8..32u8).collect::>(); - Bip32ECKeyProvider::from_raw_secret(&numbers).unwrap() + Bip32EncryptionKeyProvider::from_raw_secret(&numbers).unwrap() } - fn keypair_b() -> Bip32ECKeyProvider { + fn keypair_b() -> Bip32EncryptionKeyProvider { let numbers = (1u8..33u8).collect::>(); - Bip32ECKeyProvider::from_raw_secret(&numbers).unwrap() + Bip32EncryptionKeyProvider::from_raw_secret(&numbers).unwrap() } - fn hash(keypair: &Bip32ECKeyProvider) -> u64 { + fn hash(keypair: &Bip32EncryptionKeyProvider) -> u64 { let mut hasher = DefaultHasher::new(); keypair.hash(&mut hasher); hasher.finish() @@ -304,7 +309,7 @@ mod tests { fn from_raw_secret_validates_correct_length_happy_path() { let secret_raw: Vec = (0..32u8).collect(); - let result = Bip32ECKeyProvider::from_raw_secret(secret_raw.as_slice()).unwrap(); + let result = Bip32EncryptionKeyProvider::from_raw_secret(secret_raw.as_slice()).unwrap(); assert_eq!(result.secret_raw, secret_raw) } @@ -313,7 +318,7 @@ mod tests { fn from_raw_secret_complains_about_input_too_long() { let secret_raw: Vec = (0..33u8).collect(); - let result = Bip32ECKeyProvider::from_raw_secret(secret_raw.as_slice()); + let result = Bip32EncryptionKeyProvider::from_raw_secret(secret_raw.as_slice()); assert_eq!( result, @@ -325,7 +330,7 @@ mod tests { fn from_raw_secret_complains_about_input_too_short() { let secret_raw: Vec = (0..31u8).collect(); - let result = Bip32ECKeyProvider::from_raw_secret(secret_raw.as_slice()); + let result = Bip32EncryptionKeyProvider::from_raw_secret(secret_raw.as_slice()); assert_eq!( result, diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index d3bb43cf4..9b170709a 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -1,25 +1,29 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. +use crate::accountant::db_access_objects::payable_dao::PayableAccount; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::{ + BlockchainAgentWithContextMessage, QualifiedPayablesMessage, +}; use crate::accountant::{ - ConsumingWalletBalancesAndQualifiedPayables, ReceivedPayments, ResponseSkeleton, ScanError, - SentPayables, SkeletonOptHolder, + ReceivedPayments, ResponseSkeleton, ScanError, SentPayables, SkeletonOptHolder, }; use crate::accountant::{ReportTransactionReceipts, RequestTransactionReceipts}; -use crate::blockchain::blockchain_interface::{ - BlockchainError, BlockchainInterface, BlockchainInterfaceNull, BlockchainInterfaceWeb3, - PayableTransactionError, ProcessedPayableFallible, +use crate::actor_system_factory::SubsFactory; +use crate::blockchain::blockchain_interface::blockchain_interface_null::BlockchainInterfaceNull; +use crate::blockchain::blockchain_interface::data_structures::errors::{ + BlockchainError, PayableTransactionError, }; +use crate::blockchain::blockchain_interface::data_structures::ProcessedPayableFallible; +use crate::blockchain::blockchain_interface::BlockchainInterface; +use crate::blockchain::blockchain_interface_initializer::BlockchainInterfaceInitializer; use crate::database::db_initializer::{DbInitializationConfig, DbInitializer, DbInitializerReal}; use crate::db_config::config_dao::ConfigDaoReal; use crate::db_config::persistent_configuration::{ PersistentConfiguration, PersistentConfigurationReal, }; -use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; -use crate::sub_lib::blockchain_bridge::{ - BlockchainBridgeSubs, ReportAccountsPayable, RequestBalancesToPayPayables, -}; +use crate::sub_lib::blockchain_bridge::{BlockchainBridgeSubs, OutboundPaymentsInstructions}; use crate::sub_lib::peer_actors::BindMessage; -use crate::sub_lib::set_consuming_wallet_message::SetConsumingWalletMessage; use crate::sub_lib::utils::{db_connection_launch_panic, handle_ui_crash_request}; use crate::sub_lib::wallet::Wallet; use actix::Actor; @@ -32,22 +36,21 @@ use masq_lib::blockchains::chains::Chain; use masq_lib::logger::Logger; use masq_lib::messages::ScanType; use masq_lib::ui_gateway::NodeFromUiMessage; -use std::path::PathBuf; +use masq_lib::utils::to_string; +use regex::Regex; +use std::path::Path; use std::time::SystemTime; -use web3::transports::Http; -use web3::types::{TransactionReceipt, H256}; -use web3::Transport; +use web3::types::{BlockNumber, TransactionReceipt, H256}; pub const CRASH_KEY: &str = "BLOCKCHAINBRIDGE"; -pub struct BlockchainBridge { +pub struct BlockchainBridge { consuming_wallet_opt: Option, - blockchain_interface: Box>, + blockchain_interface: Box, logger: Logger, persistent_config: Box, - set_consuming_wallet_subs_opt: Option>>, sent_payable_subs_opt: Option>, - balances_and_payables_sub_opt: Option>, + payable_payments_setup_subs_opt: Option>, received_payments_subs_opt: Option>, scan_error_subs_opt: Option>, crashable: bool, @@ -67,21 +70,14 @@ impl Handler for BlockchainBridge { type Result = (); fn handle(&mut self, msg: BindMessage, _ctx: &mut Self::Context) -> Self::Result { - self.set_consuming_wallet_subs_opt = Some(vec![ - msg.peer_actors.neighborhood.set_consuming_wallet_sub, - msg.peer_actors.proxy_server.set_consuming_wallet_sub, - ]); self.pending_payable_confirmation .new_pp_fingerprints_sub_opt = Some(msg.peer_actors.accountant.init_pending_payable_fingerprints); self.pending_payable_confirmation .report_transaction_receipts_sub_opt = Some(msg.peer_actors.accountant.report_transaction_receipts); - self.balances_and_payables_sub_opt = Some( - msg.peer_actors - .accountant - .report_consuming_wallet_balances_and_qualified_payables, - ); + self.payable_payments_setup_subs_opt = + Some(msg.peer_actors.accountant.report_payable_payments_setup); self.sent_payable_subs_opt = Some(msg.peer_actors.accountant.report_sent_payments); self.received_payments_subs_opt = Some(msg.peer_actors.accountant.report_inbound_payments); self.scan_error_subs_opt = Some(msg.peer_actors.accountant.scan_errors); @@ -138,24 +134,20 @@ impl Handler for BlockchainBridge { } } -impl Handler for BlockchainBridge { +impl Handler for BlockchainBridge { type Result = (); - fn handle(&mut self, msg: RequestBalancesToPayPayables, _ctx: &mut Self::Context) { - self.handle_scan( - Self::handle_request_balances_to_pay_payables, - ScanType::Payables, - msg, - ); + fn handle(&mut self, msg: QualifiedPayablesMessage, _ctx: &mut Self::Context) { + self.handle_scan(Self::handle_qualified_payable_msg, ScanType::Payables, msg); } } -impl Handler for BlockchainBridge { +impl Handler for BlockchainBridge { type Result = (); - fn handle(&mut self, msg: ReportAccountsPayable, _ctx: &mut Self::Context) { + fn handle(&mut self, msg: OutboundPaymentsInstructions, _ctx: &mut Self::Context) { self.handle_scan( - Self::handle_report_accounts_payable, + Self::handle_outbound_payments_instructions, ScanType::Payables, msg, ) @@ -170,11 +162,11 @@ pub struct PendingPayableFingerprintSeeds { #[derive(Debug, PartialEq, Eq, Clone)] pub struct PendingPayableFingerprint { - //Sqlite begins counting from 1 + // Sqlite begins counting from 1 pub rowid: u64, pub timestamp: SystemTime, pub hash: H256, - //Sqlite begins counting from 1 + // We have Sqlite begin counting from 1 pub attempt: u16, pub amount: u128, pub process_error: Option, @@ -199,9 +191,8 @@ impl BlockchainBridge { consuming_wallet_opt, blockchain_interface, persistent_config, - set_consuming_wallet_subs_opt: None, sent_payable_subs_opt: None, - balances_and_payables_sub_opt: None, + payable_payments_setup_subs_opt: None, received_payments_subs_opt: None, scan_error_subs_opt: None, crashable, @@ -213,114 +204,85 @@ impl BlockchainBridge { } } - pub fn make_connections( - blockchain_service_url: Option, - data_directory: PathBuf, - chain: Chain, - ) -> ( - Box, - Box, - ) { - let blockchain_interface: Box = - { - match blockchain_service_url { - Some(url) => match Http::new(&url) { - Ok((event_loop_handle, transport)) => Box::new( - BlockchainInterfaceWeb3::new(transport, event_loop_handle, chain), - ), - Err(e) => panic!("Invalid blockchain node URL: {:?}", e), - }, - None => Box::new(BlockchainInterfaceNull::default()), - } - }; + pub fn initialize_persistent_configuration( + data_directory: &Path, + ) -> Box { let config_dao = Box::new(ConfigDaoReal::new( DbInitializerReal::default() - .initialize( - &data_directory, - DbInitializationConfig::panic_on_migration(), - ) - .unwrap_or_else(|err| db_connection_launch_panic(err, &data_directory)), + .initialize(data_directory, DbInitializationConfig::panic_on_migration()) + .unwrap_or_else(|err| db_connection_launch_panic(err, data_directory)), )); - ( - blockchain_interface, - Box::new(PersistentConfigurationReal::new(config_dao)), - ) + Box::new(PersistentConfigurationReal::new(config_dao)) + } + + pub fn initialize_blockchain_interface( + blockchain_service_url_opt: Option, + chain: Chain, + ) -> Box { + match blockchain_service_url_opt { + Some(url) => { + // TODO if we decided to have interchangeably runtime switchable or simultaneously usable interfaces we will + // probably want to make BlockchainInterfaceInitializer a collaborator that's a part of the actor + BlockchainInterfaceInitializer {}.initialize_interface(&url, chain) + } + None => Box::new(BlockchainInterfaceNull::default()), + } } pub fn make_subs_from(addr: &Addr) -> BlockchainBridgeSubs { BlockchainBridgeSubs { bind: recipient!(addr, BindMessage), - report_accounts_payable: recipient!(addr, ReportAccountsPayable), - request_balances_to_pay_payables: recipient!(addr, RequestBalancesToPayPayables), + outbound_payments_instructions: recipient!(addr, OutboundPaymentsInstructions), + qualified_payables: recipient!(addr, QualifiedPayablesMessage), retrieve_transactions: recipient!(addr, RetrieveTransactions), ui_sub: recipient!(addr, NodeFromUiMessage), request_transaction_receipts: recipient!(addr, RequestTransactionReceipts), } } - fn handle_request_balances_to_pay_payables( + fn handle_qualified_payable_msg( &mut self, - msg: RequestBalancesToPayPayables, + incoming_message: QualifiedPayablesMessage, ) -> Result<(), String> { - let consuming_wallet = match self.consuming_wallet_opt.as_ref() { - Some(wallet) => wallet, - None => { - return Err( - "Cannot inspect available balances for payables while consuming wallet \ - is missing" - .to_string(), - ) - } - }; - //TODO rewrite this into a batch call as soon as GH-629 gets into master - let gas_balance = match self - .blockchain_interface - .get_transaction_fee_balance(consuming_wallet) - { - Ok(gas_balance) => gas_balance, - Err(e) => { - return Err(format!( - "Did not find out gas balance of the consuming wallet: {:?}", - e - )) - } + let consuming_wallet = if let Some(wallet) = self.consuming_wallet_opt.as_ref() { + wallet + } else { + return Err( + "Cannot inspect available balances for payables while consuming wallet is missing" + .to_string(), + ); }; - let token_balance = match self + + let agent = self .blockchain_interface - .get_token_balance(consuming_wallet) - { - Ok(token_balance) => token_balance, - Err(e) => { - return Err(format!( - "Did not find out token balance of the consuming wallet: {:?}", - e - )) - } - }; - let consuming_wallet_balances = { - ConsumingWalletBalances { - gas_currency: gas_balance, - masq_tokens: token_balance, - } - }; - self.balances_and_payables_sub_opt + .build_blockchain_agent(consuming_wallet, &*self.persistent_config) + .map_err(to_string)?; + + let outgoing_message = BlockchainAgentWithContextMessage::new( + incoming_message.protected_qualified_payables, + agent, + incoming_message.response_skeleton_opt, + ); + + self.payable_payments_setup_subs_opt .as_ref() .expect("Accountant is unbound") - .try_send(ConsumingWalletBalancesAndQualifiedPayables { - qualified_payables: msg.accounts, - consuming_wallet_balances, - response_skeleton_opt: msg.response_skeleton_opt, - }) + .try_send(outgoing_message) .expect("Accountant is dead"); Ok(()) } - fn handle_report_accounts_payable(&mut self, msg: ReportAccountsPayable) -> Result<(), String> { + fn handle_outbound_payments_instructions( + &mut self, + msg: OutboundPaymentsInstructions, + ) -> Result<(), String> { let skeleton_opt = msg.response_skeleton_opt; - let result = self.process_payments(&msg); + let agent = msg.agent; + let checked_accounts = msg.affordable_accounts; + let result = self.process_payments(agent, checked_accounts); - let local_processing_result = match &result { + let locally_produced_result = match &result { Err(e) => Err(format!("ReportAccountsPayable: {}", e)), Ok(_) => Ok(()), }; @@ -334,25 +296,57 @@ impl BlockchainBridge { }) .expect("Accountant is dead"); - local_processing_result + locally_produced_result } fn handle_retrieve_transactions(&mut self, msg: RetrieveTransactions) -> Result<(), String> { - let start_block = match self.persistent_config.start_block() { + let start_block_nbr = match self.persistent_config.start_block() { Ok (sb) => sb, Err (e) => panic! ("Cannot retrieve start block from database; payments to you may not be processed: {:?}", e) }; - let retrieved_transactions = self + let max_block_count = match self.persistent_config.max_block_count() { + Ok(Some(mbc)) => mbc, + _ => u64::MAX, + }; + let end_block = match self .blockchain_interface - .retrieve_transactions(start_block, &msg.recipient); - + .lower_interface() + .get_block_number() + { + Ok(eb) => { + if u64::MAX == max_block_count { + BlockNumber::Number(eb) + } else { + BlockNumber::Number(eb.as_u64().min(start_block_nbr + max_block_count).into()) + } + } + Err(e) => { + info!( + self.logger, + "Using 'latest' block number instead of a literal number. {:?}", e + ); + if max_block_count == u64::MAX { + BlockNumber::Latest + } else { + BlockNumber::Number((start_block_nbr + max_block_count).into()) + } + } + }; + let start_block = BlockNumber::Number(start_block_nbr.into()); + let retrieved_transactions = + self.blockchain_interface + .retrieve_transactions(start_block, end_block, &msg.recipient); match retrieved_transactions { Ok(transactions) => { + debug!( + self.logger, + "Write new start block: {}", transactions.new_start_block + ); if let Err(e) = self .persistent_config .set_start_block(transactions.new_start_block) { - panic! ("Cannot set start block in database; payments to you may not be processed: {:?}", e) + panic! ("Cannot set start block {} in database; payments to you may not be processed: {:?}", transactions.new_start_block, e) }; if transactions.transactions.is_empty() { debug!(self.logger, "No new receivable detected"); @@ -368,10 +362,33 @@ impl BlockchainBridge { .expect("Accountant is dead."); Ok(()) } - Err(e) => Err(format!( - "Tried to retrieve received payments but failed: {:?}", - e - )), + Err(e) => { + if let Some(max_block_count) = self.extract_max_block_count(e.clone()) { + debug!(self.logger, "Writing max_block_count({})", max_block_count); + self.persistent_config + .set_max_block_count(Some(max_block_count)) + .map_or_else( + |_| { + warning!(self.logger, "{} update max_block_count to {}. Scheduling next scan with that limit.", e, max_block_count); + Err(format!("{} updated max_block_count to {}. Scheduling next scan with that limit.", e, max_block_count)) + }, + |e| { + warning!(self.logger, "Writing max_block_count failed: {:?}", e); + Err(format!("Writing max_block_count failed: {:?}", e)) + }, + ) + } else { + warning!( + self.logger, + "Attempted to retrieve received payments but failed: {:?}", + e + ); + Err(format!( + "Attempted to retrieve received payments but failed: {:?}", + e + )) + } + } } } @@ -447,43 +464,47 @@ impl BlockchainBridge { fn process_payments( &self, - msg: &ReportAccountsPayable, + agent: Box, + affordable_accounts: Vec, ) -> Result, PayableTransactionError> { - let (consuming_wallet, gas_price) = match self.consuming_wallet_opt.as_ref() { - Some(consuming_wallet) => match self.persistent_config.gas_price() { - Ok(gas_price) => (consuming_wallet, gas_price), - Err(e) => { - return Err(PayableTransactionError::GasPriceQueryFailed(format!( - "{:?}", - e - ))) - } - }, - None => return Err(PayableTransactionError::MissingConsumingWallet), - }; + let new_fingerprints_recipient = self.new_fingerprints_recipient(); - let pending_nonce = self - .blockchain_interface - .get_transaction_count(consuming_wallet) - .map_err(PayableTransactionError::TransactionCount)?; - - let new_fingerprints_recipient = self.get_new_fingerprints_recipient(); - - self.blockchain_interface.send_payables_within_batch( - consuming_wallet, - gas_price, - pending_nonce, + self.blockchain_interface.send_batch_of_payables( + agent, new_fingerprints_recipient, - &msg.accounts, + &affordable_accounts, ) } - fn get_new_fingerprints_recipient(&self) -> &Recipient { + fn new_fingerprints_recipient(&self) -> &Recipient { self.pending_payable_confirmation .new_pp_fingerprints_sub_opt .as_ref() .expect("Accountant unbound") } + + pub fn extract_max_block_count(&self, error: BlockchainError) -> Option { + let regex_result = + Regex::new(r".* (max: |allowed for your plan: |is limited to |block range limit \()(?P\d+).*") + .expect("Invalid regex"); + let max_block_count = match error { + BlockchainError::QueryFailed(msg) => match regex_result.captures(msg.as_str()) { + Some(captures) => match captures.name("max_block_count") { + Some(m) => match m.as_str().parse::() { + Ok(value) => Some(value), + Err(_) => None, + }, + _ => None, + }, + None => match msg.as_str() { + "Got invalid response: Expected batch, got single." => Some(1000), + _ => None, + }, + }, + _ => None, + }; + max_block_count + } } #[derive(Debug, PartialEq, Eq, Clone)] @@ -492,46 +513,72 @@ struct PendingTxInfo { when_sent: SystemTime, } +pub struct BlockchainBridgeSubsFactoryReal {} + +impl SubsFactory for BlockchainBridgeSubsFactoryReal { + fn make(&self, addr: &Addr) -> BlockchainBridgeSubs { + BlockchainBridge::make_subs_from(addr) + } +} + #[cfg(test)] mod tests { use super::*; - use crate::accountant::database_access_objects::dao_utils::from_time_t; - use crate::accountant::database_access_objects::payable_dao::{PayableAccount, PendingPayable}; + use crate::accountant::db_access_objects::payable_dao::PayableAccount; + use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; + use crate::accountant::db_access_objects::utils::from_time_t; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::test_utils::BlockchainAgentMock; + use crate::accountant::scanners::test_utils::protect_payables_in_test; use crate::accountant::test_utils::make_pending_payable_fingerprint; - use crate::accountant::ConsumingWalletBalancesAndQualifiedPayables; - use crate::blockchain::bip32::Bip32ECKeyProvider; - use crate::blockchain::blockchain_interface::ProcessedPayableFallible::Correct; - use crate::blockchain::blockchain_interface::{ - BlockchainError, BlockchainTransaction, RetrievedBlockchainTransactions, + use crate::blockchain::bip32::Bip32EncryptionKeyProvider; + use crate::blockchain::blockchain_interface::blockchain_interface_null::BlockchainInterfaceNull; + use crate::blockchain::blockchain_interface::data_structures::errors::{ + BlockchainAgentBuildError, PayableTransactionError, + }; + use crate::blockchain::blockchain_interface::data_structures::{ + BlockchainTransaction, RetrievedBlockchainTransactions, }; + use crate::blockchain::blockchain_interface::lower_level_interface::LatestBlockNumber; + use crate::blockchain::blockchain_interface::test_utils::LowBlockchainIntMock; use crate::blockchain::test_utils::{make_tx_hash, BlockchainInterfaceMock}; use crate::db_config::persistent_configuration::PersistentConfigError; use crate::match_every_type_id; use crate::node_test_utils::check_timestamp; - use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; use crate::test_utils::recorder::{make_recorder, peer_actors_builder}; use crate::test_utils::recorder_stop_conditions::StopCondition; use crate::test_utils::recorder_stop_conditions::StopConditions; + use crate::test_utils::unshared_test_utils::arbitrary_id_stamp::ArbitraryIdStamp; use crate::test_utils::unshared_test_utils::{ assert_on_initialization_with_panic_on_migration, configure_default_persistent_config, - prove_that_crash_request_handler_is_hooked_up, ZERO, + prove_that_crash_request_handler_is_hooked_up, AssertionsMessage, ZERO, }; use crate::test_utils::{make_paying_wallet, make_wallet}; use actix::System; use ethereum_types::U64; use ethsign_crypto::Keccak256; - use masq_lib::constants::DEFAULT_CHAIN; use masq_lib::messages::ScanType; use masq_lib::test_utils::logging::init_test_logging; use masq_lib::test_utils::logging::TestLogHandler; - use masq_lib::test_utils::utils::ensure_node_home_directory_exists; + use masq_lib::test_utils::utils::{ensure_node_home_directory_exists, TEST_DEFAULT_CHAIN}; use rustc_hex::FromHex; use std::any::TypeId; use std::path::Path; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; - use web3::types::{TransactionReceipt, H160, H256, U256}; + use web3::types::{TransactionReceipt, H160, H256}; + + impl Handler> for BlockchainBridge { + type Result = (); + + fn handle( + &mut self, + msg: AssertionsMessage, + _ctx: &mut Self::Context, + ) -> Self::Result { + (msg.assertions)(self) + } + } #[test] fn constants_have_correct_values() { @@ -548,7 +595,8 @@ mod tests { let secret: Vec = "cc46befe8d169b89db447bd725fc2368b12542113555302598430cb5d5c74ea9" .from_hex() .unwrap(); - let consuming_wallet = Wallet::from(Bip32ECKeyProvider::from_raw_secret(&secret).unwrap()); + let consuming_wallet = + Wallet::from(Bip32EncryptionKeyProvider::from_raw_secret(&secret).unwrap()); let subject = BlockchainBridge::new( stub_bi(), Box::new(configure_default_persistent_config(ZERO)), @@ -571,6 +619,16 @@ mod tests { )); } + #[test] + fn blockchain_interface_null_as_result_of_missing_blockchain_service_url() { + let result = BlockchainBridge::initialize_blockchain_interface(None, TEST_DEFAULT_CHAIN); + + result + .as_any() + .downcast_ref::() + .unwrap(); + } + #[test] fn blockchain_bridge_receives_bind_message_without_consuming_private_key() { init_test_logging(); @@ -596,90 +654,42 @@ mod tests { } #[test] - #[should_panic(expected = "Invalid blockchain node URL")] - fn invalid_blockchain_url_produces_panic() { - let data_directory = PathBuf::new(); //never reached - let blockchain_service_url = Some("http://λ:8545".to_string()); - let _ = BlockchainBridge::make_connections( - blockchain_service_url, - data_directory, - DEFAULT_CHAIN, - ); - } - - #[test] - fn report_accounts_payable_returns_error_when_there_is_no_consuming_wallet_configured() { - let blockchain_interface_mock = BlockchainInterfaceMock::default(); - let persistent_configuration_mock = PersistentConfigurationMock::default(); - let (accountant, _, accountant_recording_arc) = make_recorder(); - let recipient = accountant.start().recipient(); - let mut subject = BlockchainBridge::new( - Box::new(blockchain_interface_mock), - Box::new(persistent_configuration_mock), - false, - None, - ); - subject.sent_payable_subs_opt = Some(recipient); - let request = ReportAccountsPayable { - accounts: vec![PayableAccount { - wallet: make_wallet("blah"), - balance_wei: 42, - last_paid_timestamp: SystemTime::now(), - pending_payable_opt: None, - }], - response_skeleton_opt: None, - }; - let system = System::new("test"); - - let result = subject.handle_report_accounts_payable(request); - - System::current().stop(); - assert_eq!(system.run(), 0); - assert_eq!( - result, - Err("ReportAccountsPayable: Missing consuming wallet to pay payable from".to_string()) - ); - let accountant_recording = accountant_recording_arc.lock().unwrap(); - let sent_payables_msg = accountant_recording.get_record::(0); - assert_eq!( - sent_payables_msg, - &SentPayables { - payment_procedure_result: Err(PayableTransactionError::MissingConsumingWallet), - response_skeleton_opt: None - } - ); - assert_eq!(accountant_recording.len(), 1) - } - - #[test] - fn handle_request_balances_to_pay_payables_reports_balances_and_payables_back_to_accountant() { + fn qualified_payables_msg_is_handled_and_new_msg_with_an_added_blockchain_agent_returns_to_accountant( + ) { let system = System::new( - "handle_request_balances_to_pay_payables_reports_balances_and_payables_back_to_accountant", + "qualified_payables_msg_is_handled_and_new_msg_with_an_added_blockchain_agent_returns_to_accountant", ); - let get_transaction_fee_balance_params_arc = Arc::new(Mutex::new(vec![])); - let get_token_balance_params_arc = Arc::new(Mutex::new(vec![])); + let build_blockchain_agent_params_arc = Arc::new(Mutex::new(vec![])); let (accountant, _, accountant_recording_arc) = make_recorder(); - let gas_balance = U256::from(4455); - let token_balance = U256::from(112233); - let wallet_balances_found = ConsumingWalletBalances { - gas_currency: gas_balance, - masq_tokens: token_balance, - }; + let agent_id_stamp = ArbitraryIdStamp::new(); + let agent = BlockchainAgentMock::default().set_arbitrary_id_stamp(agent_id_stamp); let blockchain_interface = BlockchainInterfaceMock::default() - .get_transaction_fee_balance_params(&get_transaction_fee_balance_params_arc) - .get_transaction_fee_balance_result(Ok(gas_balance)) - .get_token_balance_params(&get_token_balance_params_arc) - .get_token_balance_result(Ok(token_balance)); + .build_blockchain_agent_params(&build_blockchain_agent_params_arc) + .build_blockchain_agent_result(Ok(Box::new(agent))); let consuming_wallet = make_paying_wallet(b"somewallet"); - let persistent_configuration = PersistentConfigurationMock::default(); - let qualified_accounts = vec![PayableAccount { - wallet: make_wallet("booga"), - balance_wei: 78_654_321, - last_paid_timestamp: SystemTime::now() - .checked_sub(Duration::from_secs(1000)) - .unwrap(), - pending_payable_opt: None, - }]; + let persistent_config_id_stamp = ArbitraryIdStamp::new(); + let persistent_configuration = PersistentConfigurationMock::default() + .set_arbitrary_id_stamp(persistent_config_id_stamp); + let wallet_1 = make_wallet("booga"); + let wallet_2 = make_wallet("gulp"); + let qualified_payables = vec![ + PayableAccount { + wallet: wallet_1.clone(), + balance_wei: 78_654_321_124, + last_paid_timestamp: SystemTime::now() + .checked_sub(Duration::from_secs(1000)) + .unwrap(), + pending_payable_opt: None, + }, + PayableAccount { + wallet: wallet_2.clone(), + balance_wei: 60_457_111_003, + last_paid_timestamp: SystemTime::now() + .checked_sub(Duration::from_secs(500)) + .unwrap(), + pending_payable_opt: None, + }, + ]; let subject = BlockchainBridge::new( Box::new(blockchain_interface), Box::new(persistent_configuration), @@ -689,50 +699,47 @@ mod tests { let addr = subject.start(); let subject_subs = BlockchainBridge::make_subs_from(&addr); let peer_actors = peer_actors_builder().accountant(accountant).build(); - send_bind_message!(subject_subs, peer_actors); - - addr.try_send(RequestBalancesToPayPayables { - accounts: qualified_accounts.clone(), + let qualified_payables = protect_payables_in_test(qualified_payables.clone()); + let qualified_payables_msg = QualifiedPayablesMessage { + protected_qualified_payables: qualified_payables.clone(), response_skeleton_opt: Some(ResponseSkeleton { client_id: 11122, context_id: 444, }), - }) - .unwrap(); + }; + send_bind_message!(subject_subs, peer_actors); + + addr.try_send(qualified_payables_msg).unwrap(); System::current().stop(); system.run(); - let get_transaction_fee_balance_params = - get_transaction_fee_balance_params_arc.lock().unwrap(); + + let build_blockchain_agent_params = build_blockchain_agent_params_arc.lock().unwrap(); assert_eq!( - *get_transaction_fee_balance_params, - vec![consuming_wallet.clone()] + *build_blockchain_agent_params, + vec![(consuming_wallet.clone(), persistent_config_id_stamp)] ); - let get_token_balance_params = get_token_balance_params_arc.lock().unwrap(); - assert_eq!(*get_token_balance_params, vec![consuming_wallet]); let accountant_received_payment = accountant_recording_arc.lock().unwrap(); - assert_eq!(accountant_received_payment.len(), 1); - let reported_balances_and_qualified_accounts: &ConsumingWalletBalancesAndQualifiedPayables = + let blockchain_agent_with_context_msg_actual: &BlockchainAgentWithContextMessage = accountant_received_payment.get_record(0); assert_eq!( - reported_balances_and_qualified_accounts, - &ConsumingWalletBalancesAndQualifiedPayables { - qualified_payables: qualified_accounts, - consuming_wallet_balances: wallet_balances_found, - response_skeleton_opt: Some(ResponseSkeleton { - client_id: 11122, - context_id: 444 - }) - } + blockchain_agent_with_context_msg_actual.protected_qualified_payables, + qualified_payables + ); + assert_eq!( + blockchain_agent_with_context_msg_actual + .agent + .arbitrary_id_stamp(), + agent_id_stamp ); + assert_eq!(accountant_received_payment.len(), 1); } - fn assert_failure_during_balance_inspection( - test_name: &str, - blockchain_interface: BlockchainInterfaceMock, - error_msg: &str, - ) { + #[test] + fn build_of_blockchain_agent_throws_err_out_and_ends_handling_qualified_payables_message() { init_test_logging(); + let test_name = + "build_of_blockchain_agent_throws_err_out_and_ends_handling_qualified_payables_message"; let (accountant, _, accountant_recording_arc) = make_recorder(); let scan_error_recipient: Recipient = accountant .system_stop_conditions(match_every_type_id!(ScanError)) @@ -740,6 +747,10 @@ mod tests { .recipient(); let persistent_configuration = PersistentConfigurationMock::default(); let consuming_wallet = make_wallet(test_name); + let blockchain_interface = BlockchainInterfaceMock::default() + .build_blockchain_agent_result(Err(BlockchainAgentBuildError::GasPrice( + PersistentConfigError::NotPresent, + ))); let mut subject = BlockchainBridge::new( Box::new(blockchain_interface), Box::new(persistent_configuration), @@ -748,13 +759,13 @@ mod tests { ); subject.logger = Logger::new(test_name); subject.scan_error_subs_opt = Some(scan_error_recipient); - let request = RequestBalancesToPayPayables { - accounts: vec![PayableAccount { + let request = QualifiedPayablesMessage { + protected_qualified_payables: protect_payables_in_test(vec![PayableAccount { wallet: make_wallet("blah"), balance_wei: 42, last_paid_timestamp: SystemTime::now(), pending_payable_opt: None, - }], + }]), response_skeleton_opt: Some(ResponseSkeleton { client_id: 11, context_id: 2323, @@ -771,6 +782,8 @@ mod tests { let recording = accountant_recording_arc.lock().unwrap(); let message = recording.get_record::(0); assert_eq!(recording.len(), 1); + let expected_error_msg = "Blockchain agent construction failed at fetching gas \ + price from the database: NotPresent"; assert_eq!( message, &ScanError { @@ -779,43 +792,15 @@ mod tests { client_id: 11, context_id: 2323 }), - msg: error_msg.to_string() + msg: expected_error_msg.to_string() } ); - TestLogHandler::new().exists_log_containing(&format!("WARN: {}: {}", test_name, error_msg)); - } - - #[test] - fn handle_request_balances_to_pay_payables_fails_on_inspection_of_gas_balance() { - let test_name = - "handle_request_balances_to_pay_payables_fails_on_inspection_of_gas_balance"; - let blockchain_interface = BlockchainInterfaceMock::default() - .get_transaction_fee_balance_result(Err(BlockchainError::QueryFailed( - "Lazy and yet you're asking for balances?".to_string(), - ))); - let error_msg = "Did not find out gas balance of the consuming wallet: \ - QueryFailed(\"Lazy and yet you're asking for balances?\")"; - - assert_failure_during_balance_inspection(test_name, blockchain_interface, error_msg) - } - - #[test] - fn handle_request_balances_to_pay_payables_fails_on_inspection_of_token_balance() { - let test_name = - "handle_request_balances_to_pay_payables_fails_on_inspection_of_token_balance"; - let blockchain_interface = BlockchainInterfaceMock::default() - .get_transaction_fee_balance_result(Ok(U256::from(45678))) - .get_token_balance_result(Err(BlockchainError::QueryFailed( - "Go get you a job. This balance must be deserved".to_string(), - ))); - let error_msg = "Did not find out token balance of the consuming wallet: QueryFailed(\ - \"Go get you a job. This balance must be deserved\")"; - - assert_failure_during_balance_inspection(test_name, blockchain_interface, error_msg) + TestLogHandler::new() + .exists_log_containing(&format!("WARN: {test_name}: {expected_error_msg}")); } #[test] - fn handle_request_balances_to_pay_payables_fails_at_missing_consuming_wallet() { + fn handle_qualified_payable_msg_fails_at_missing_consuming_wallet() { let blockchain_interface = BlockchainInterfaceMock::default(); let persistent_configuration = PersistentConfigurationMock::default(); let mut subject = BlockchainBridge::new( @@ -824,17 +809,17 @@ mod tests { false, None, ); - let request = RequestBalancesToPayPayables { - accounts: vec![PayableAccount { + let request = QualifiedPayablesMessage { + protected_qualified_payables: protect_payables_in_test(vec![PayableAccount { wallet: make_wallet("blah"), balance_wei: 4254, last_paid_timestamp: SystemTime::now(), pending_payable_opt: None, - }], + }]), response_skeleton_opt: None, }; - let result = subject.handle_request_balances_to_pay_payables(request); + let result = subject.handle_qualified_payable_msg(request); assert_eq!( result, @@ -846,37 +831,34 @@ mod tests { } #[test] - fn handle_report_accounts_payable_transacts_and_sends_finished_payments_back_to_accountant() { + fn handle_outbound_payments_instructions_sees_payments_happen_and_sends_payment_results_back_to_accountant( + ) { let system = - System::new("handle_report_accounts_payable_transacts_and_sends_finished_payments_back_to_accountant"); - let get_transaction_count_params_arc = Arc::new(Mutex::new(vec![])); - let send_payables_within_batch_params_arc = Arc::new(Mutex::new(vec![])); + System::new("handle_outbound_payments_instructions_sees_payments_happen_and_sends_payment_results_back_to_accountant"); + let send_batch_of_payables_params_arc = Arc::new(Mutex::new(vec![])); let (accountant, _, accountant_recording_arc) = make_recorder(); let accountant = accountant.system_stop_conditions(match_every_type_id!(PendingPayableFingerprintSeeds)); let wallet_account_1 = make_wallet("blah"); let wallet_account_2 = make_wallet("foo"); + let blockchain_interface_id_stamp = ArbitraryIdStamp::new(); let blockchain_interface_mock = BlockchainInterfaceMock::default() - .get_transaction_count_params(&get_transaction_count_params_arc) - .get_transaction_count_result(Ok(U256::from(1u64))) - .send_payables_within_batch_params(&send_payables_within_batch_params_arc) - .send_payables_within_batch_result(Ok(vec![ - Correct(PendingPayable { + .set_arbitrary_id_stamp(blockchain_interface_id_stamp) + .send_batch_of_payables_params(&send_batch_of_payables_params_arc) + .send_batch_of_payables_result(Ok(vec![ + Ok(PendingPayable { recipient_wallet: wallet_account_1.clone(), hash: H256::from("sometransactionhash".keccak256()), }), - Correct(PendingPayable { + Ok(PendingPayable { recipient_wallet: wallet_account_2.clone(), hash: H256::from("someothertransactionhash".keccak256()), }), ])); - let expected_gas_price = 145u64; - let persistent_configuration_mock = - PersistentConfigurationMock::default().gas_price_result(Ok(expected_gas_price)); let consuming_wallet = make_paying_wallet(b"somewallet"); let subject = BlockchainBridge::new( Box::new(blockchain_interface_mock), - Box::new(persistent_configuration_mock), + Box::new(PersistentConfigurationMock::default()), false, Some(consuming_wallet.clone()), ); @@ -897,11 +879,14 @@ mod tests { pending_payable_opt: None, }, ]; + let agent_id_stamp = ArbitraryIdStamp::new(); + let agent = BlockchainAgentMock::default().set_arbitrary_id_stamp(agent_id_stamp); send_bind_message!(subject_subs, peer_actors); let _ = addr - .try_send(ReportAccountsPayable { - accounts: accounts.clone(), + .try_send(OutboundPaymentsInstructions { + affordable_accounts: accounts.clone(), + agent: Box::new(agent), response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, context_id: 4321, @@ -911,34 +896,24 @@ mod tests { System::current().stop(); system.run(); - let mut send_payables_within_batch_params = - send_payables_within_batch_params_arc.lock().unwrap(); + let mut send_batch_of_payables_params = send_batch_of_payables_params_arc.lock().unwrap(); //cannot assert on the captured recipient as its actor is gone after the System stops spinning - let ( - consuming_wallet_actual, - gas_price_actual, - nonce_actual, - _recipient_actual, - accounts_actual, - ) = send_payables_within_batch_params.remove(0); - assert!(send_payables_within_batch_params.is_empty()); - assert_eq!(consuming_wallet_actual, consuming_wallet.clone()); - assert_eq!(gas_price_actual, expected_gas_price); - assert_eq!(nonce_actual, U256::from(1u64)); + let (actual_agent_id_stamp, _recipient_actual, accounts_actual) = + send_batch_of_payables_params.remove(0); + assert!(send_batch_of_payables_params.is_empty()); + assert_eq!(actual_agent_id_stamp, agent_id_stamp); assert_eq!(accounts_actual, accounts); - let get_transaction_count_params = get_transaction_count_params_arc.lock().unwrap(); - assert_eq!(*get_transaction_count_params, vec![consuming_wallet]); let accountant_recording = accountant_recording_arc.lock().unwrap(); let sent_payments_msg = accountant_recording.get_record::(0); assert_eq!( *sent_payments_msg, SentPayables { payment_procedure_result: Ok(vec![ - Correct(PendingPayable { + Ok(PendingPayable { recipient_wallet: wallet_account_1, hash: H256::from("sometransactionhash".keccak256()) }), - Correct(PendingPayable { + Ok(PendingPayable { recipient_wallet: wallet_account_2, hash: H256::from("someothertransactionhash".keccak256()) }) @@ -953,9 +928,9 @@ mod tests { } #[test] - fn handle_report_accounts_payable_transmits_eleventh_hour_error_back_to_accountant() { + fn handle_outbound_payments_instructions_sends_eleventh_hour_error_back_to_accountant() { let system = System::new( - "handle_report_accounts_payable_transmits_eleventh_hour_error_back_to_accountant", + "handle_outbound_payments_instructions_sends_eleventh_hour_error_back_to_accountant", ); let (accountant, _, accountant_recording_arc) = make_recorder(); let hash = make_tx_hash(0xde); @@ -967,10 +942,8 @@ mod tests { hashes: vec![hash], }); let blockchain_interface_mock = BlockchainInterfaceMock::default() - .get_transaction_count_result(Ok(U256::from(1u64))) - .send_payables_within_batch_result(expected_error.clone()); - let persistent_configuration_mock = - PersistentConfigurationMock::default().gas_price_result(Ok(123)); + .send_batch_of_payables_result(expected_error.clone()); + let persistent_configuration_mock = PersistentConfigurationMock::default(); let consuming_wallet = make_paying_wallet(b"somewallet"); let subject = BlockchainBridge::new( Box::new(blockchain_interface_mock), @@ -987,11 +960,13 @@ mod tests { last_paid_timestamp: from_time_t(150_000_000), pending_payable_opt: None, }]; + let agent = BlockchainAgentMock::default(); send_bind_message!(subject_subs, peer_actors); let _ = addr - .try_send(ReportAccountsPayable { - accounts, + .try_send(OutboundPaymentsInstructions { + affordable_accounts: accounts, + agent: Box::new(agent), response_skeleton_opt: Some(ResponseSkeleton { client_id: 1234, context_id: 4321, @@ -1032,149 +1007,46 @@ mod tests { assert_eq!(accountant_recording.len(), 2) } - #[test] - fn report_accounts_payable_returns_error_fetching_pending_nonce() { - let blockchain_interface_mock = BlockchainInterfaceMock::default() - .get_transaction_count_result(Err(BlockchainError::QueryFailed( - "What the hack...??".to_string(), - ))); - let consuming_wallet = make_wallet("somewallet"); - let persistent_configuration_mock = - PersistentConfigurationMock::new().gas_price_result(Ok(3u64)); - let subject = BlockchainBridge::new( - Box::new(blockchain_interface_mock), - Box::new(persistent_configuration_mock), - false, - Some(consuming_wallet), - ); - let request = ReportAccountsPayable { - accounts: vec![PayableAccount { - wallet: make_wallet("blah"), - balance_wei: 123_456, - last_paid_timestamp: SystemTime::now(), - pending_payable_opt: None, - }], - response_skeleton_opt: None, - }; - - let result = subject.process_payments(&request); - - assert_eq!( - result, - Err(PayableTransactionError::TransactionCount( - BlockchainError::QueryFailed("What the hack...??".to_string()) - )) - ); - } - #[test] fn process_payments_returns_error_from_sending_batch() { let transaction_hash = make_tx_hash(789); let blockchain_interface_mock = BlockchainInterfaceMock::default() - .get_transaction_count_result(Ok(web3::types::U256::from(1))) - .send_payables_within_batch_result(Err(PayableTransactionError::Sending { - msg: "failure from exhaustion".to_string(), + .send_batch_of_payables_result(Err(PayableTransactionError::Sending { + msg: "failure from chronic exhaustion".to_string(), hashes: vec![transaction_hash], })); let consuming_wallet = make_wallet("somewallet"); - let persistent_configuration_mock = - PersistentConfigurationMock::new().gas_price_result(Ok(3u64)); + let persistent_configuration_mock = PersistentConfigurationMock::new(); let mut subject = BlockchainBridge::new( Box::new(blockchain_interface_mock), Box::new(persistent_configuration_mock), false, Some(consuming_wallet.clone()), ); - let request = ReportAccountsPayable { - accounts: vec![PayableAccount { - wallet: make_wallet("blah"), - balance_wei: 424_454, - last_paid_timestamp: SystemTime::now(), - pending_payable_opt: None, - }], - response_skeleton_opt: None, - }; + let checked_accounts = vec![PayableAccount { + wallet: make_wallet("blah"), + balance_wei: 424_454, + last_paid_timestamp: SystemTime::now(), + pending_payable_opt: None, + }]; + let agent = Box::new(BlockchainAgentMock::default()); let (accountant, _, _) = make_recorder(); let fingerprint_recipient = accountant.start().recipient(); subject .pending_payable_confirmation .new_pp_fingerprints_sub_opt = Some(fingerprint_recipient); - let result = subject.process_payments(&request); + let result = subject.process_payments(agent, checked_accounts); assert_eq!( result, Err(PayableTransactionError::Sending { - msg: "failure from exhaustion".to_string(), + msg: "failure from chronic exhaustion".to_string(), hashes: vec![transaction_hash] }) ); } - #[test] - fn handle_report_accounts_payable_manages_gas_price_error() { - init_test_logging(); - let (accountant, _, accountant_recording_arc) = make_recorder(); - let accountant_addr = accountant - .system_stop_conditions(match_every_type_id!(ScanError)) - .start(); - let sent_payables_recipient = accountant_addr.clone().recipient(); - let scan_error_recipient = accountant_addr.recipient(); - let blockchain_interface_mock = BlockchainInterfaceMock::default() - .get_transaction_count_result(Ok(web3::types::U256::from(1))); - let persistent_configuration_mock = PersistentConfigurationMock::new() - .gas_price_result(Err(PersistentConfigError::TransactionError)); - let consuming_wallet = make_wallet("somewallet"); - let mut subject = BlockchainBridge::new( - Box::new(blockchain_interface_mock), - Box::new(persistent_configuration_mock), - false, - Some(consuming_wallet), - ); - subject.sent_payable_subs_opt = Some(sent_payables_recipient); - subject.scan_error_subs_opt = Some(scan_error_recipient); - let request = ReportAccountsPayable { - accounts: vec![PayableAccount { - wallet: make_wallet("blah"), - balance_wei: 42, - last_paid_timestamp: SystemTime::now(), - pending_payable_opt: None, - }], - response_skeleton_opt: None, - }; - let subject_addr = subject.start(); - let system = System::new("test"); - - subject_addr.try_send(request).unwrap(); - - system.run(); - let recording = accountant_recording_arc.lock().unwrap(); - let actual_sent_payable_msg = recording.get_record::(0); - assert_eq!( - actual_sent_payable_msg, - &SentPayables { - payment_procedure_result: Err(PayableTransactionError::GasPriceQueryFailed( - "TransactionError".to_string() - )), - response_skeleton_opt: None - } - ); - let actual_scan_err_msg = recording.get_record::(1); - assert_eq!( - actual_scan_err_msg, - &ScanError { - scan_type: ScanType::Payables, - response_skeleton_opt: None, - msg: "ReportAccountsPayable: Unsuccessful gas price query: \"TransactionError\"" - .to_string() - } - ); - assert_eq!(recording.len(), 2); - TestLogHandler::new().exists_log_containing( - "WARN: BlockchainBridge: ReportAccountsPayable: Unsuccessful gas price query: \"TransactionError\"", - ); - } - #[test] fn blockchain_bridge_processes_requests_for_transaction_receipts_when_all_were_ok() { let get_transaction_receipt_params_arc = Arc::new(Mutex::new(vec![])); @@ -1251,10 +1123,16 @@ mod tests { .system_stop_conditions(match_every_type_id!(ScanError)) .start() .recipient(); - let blockchain_interface = BlockchainInterfaceMock::default().retrieve_transactions_result( - Err(BlockchainError::QueryFailed("we have no luck".to_string())), - ); - let persistent_config = PersistentConfigurationMock::new().start_block_result(Ok(5)); // no set_start_block_result: set_start_block() must not be called + let lower_interface = LowBlockchainIntMock::default() + .get_block_number_result(LatestBlockNumber::Ok(U64::from(1234u64))); + let blockchain_interface = BlockchainInterfaceMock::default() + .retrieve_transactions_result(Err(BlockchainError::QueryFailed( + "we have no luck".to_string(), + ))) + .lower_interface_results(Box::new(lower_interface)); + let persistent_config = PersistentConfigurationMock::new() + .max_block_count_result(Ok(Some(100_000))) + .start_block_result(Ok(5)); // no set_start_block_result: set_start_block() must not be called let mut subject = BlockchainBridge::new( Box::new(blockchain_interface), Box::new(persistent_config), @@ -1279,12 +1157,12 @@ mod tests { &ScanError { scan_type: ScanType::Receivables, response_skeleton_opt: None, - msg: "Tried to retrieve received payments but failed: QueryFailed(\"we have no luck\")".to_string() + msg: "Attempted to retrieve received payments but failed: QueryFailed(\"we have no luck\")".to_string() } ); assert_eq!(recording.len(), 1); TestLogHandler::new().exists_log_containing( - "WARN: BlockchainBridge: Tried to retrieve \ + "WARN: BlockchainBridge: Attempted to retrieve \ received payments but failed: QueryFailed(\"we have no luck\")", ); } @@ -1406,7 +1284,7 @@ mod tests { let (accountant, _, accountant_recording) = make_recorder(); let recipient = accountant.start().recipient(); let mut subject = BlockchainBridge::new( - Box::new(BlockchainInterfaceNull::default()), + Box::new(BlockchainInterfaceMock::default()), Box::new(PersistentConfigurationMock::default()), false, Some(Wallet::new("mine")), @@ -1514,6 +1392,101 @@ mod tests { receipt for '0x000000000000000000000000000000000000000000000000000000000001b2e6' failed due to 'QueryFailed(\"booga\")'"); } + #[test] + fn handle_retrieve_transactions_uses_latest_block_number_upon_get_block_number_error() { + init_test_logging(); + let retrieve_transactions_params_arc = Arc::new(Mutex::new(vec![])); + let system = System::new( + "handle_retrieve_transactions_uses_latest_block_number_upon_get_block_number_error", + ); + let (accountant, _, accountant_recording_arc) = make_recorder(); + let earning_wallet = make_wallet("somewallet"); + let amount = 42; + let amount2 = 55; + let expected_transactions = RetrievedBlockchainTransactions { + new_start_block: 8675309u64, + transactions: vec![ + BlockchainTransaction { + block_number: 7, + from: earning_wallet.clone(), + wei_amount: amount, + }, + BlockchainTransaction { + block_number: 9, + from: earning_wallet.clone(), + wei_amount: amount2, + }, + ], + }; + let lower_interface = + LowBlockchainIntMock::default().get_block_number_result(LatestBlockNumber::Err( + BlockchainError::QueryFailed("Failed to read the latest block number".to_string()), + )); + let blockchain_interface_mock = BlockchainInterfaceMock::default() + .retrieve_transactions_params(&retrieve_transactions_params_arc) + .retrieve_transactions_result(Ok(expected_transactions.clone())) + .lower_interface_results(Box::new(lower_interface)); + let set_start_block_params_arc = Arc::new(Mutex::new(vec![])); + let persistent_config = PersistentConfigurationMock::new() + .max_block_count_result(Ok(Some(10000u64))) + .start_block_result(Ok(6)) + .set_start_block_params(&set_start_block_params_arc) + .set_start_block_result(Ok(())); + let subject = BlockchainBridge::new( + Box::new(blockchain_interface_mock), + Box::new(persistent_config), + false, + Some(make_wallet("consuming")), + ); + + let addr = subject.start(); + let subject_subs = BlockchainBridge::make_subs_from(&addr); + let peer_actors = peer_actors_builder().accountant(accountant).build(); + send_bind_message!(subject_subs, peer_actors); + let retrieve_transactions = RetrieveTransactions { + recipient: earning_wallet.clone(), + response_skeleton_opt: Some(ResponseSkeleton { + client_id: 1234, + context_id: 4321, + }), + }; + let before = SystemTime::now(); + + let _ = addr.try_send(retrieve_transactions).unwrap(); + + System::current().stop(); + system.run(); + let after = SystemTime::now(); + let set_start_block_params = set_start_block_params_arc.lock().unwrap(); + assert_eq!(*set_start_block_params, vec![8675309u64]); + let retrieve_transactions_params = retrieve_transactions_params_arc.lock().unwrap(); + assert_eq!( + *retrieve_transactions_params, + vec![( + BlockNumber::Number(6u64.into()), + BlockNumber::Number(10006u64.into()), + earning_wallet + )] + ); + let accountant_received_payment = accountant_recording_arc.lock().unwrap(); + assert_eq!(accountant_received_payment.len(), 1); + let received_payments = accountant_received_payment.get_record::(0); + check_timestamp(before, received_payments.timestamp, after); + assert_eq!( + received_payments, + &ReceivedPayments { + timestamp: received_payments.timestamp, + payments: expected_transactions.transactions, + response_skeleton_opt: Some(ResponseSkeleton { + client_id: 1234, + context_id: 4321 + }), + } + ); + + TestLogHandler::new().exists_log_containing("INFO: BlockchainBridge: Using 'latest' block number instead of a literal number. QueryFailed(\"Failed to read the latest block number\")"); + } + #[test] fn handle_retrieve_transactions_sends_received_payments_back_to_accountant() { let retrieve_transactions_params_arc = Arc::new(Mutex::new(vec![])); @@ -1538,11 +1511,16 @@ mod tests { }, ], }; + let latest_block_number = LatestBlockNumber::Ok(1024u64.into()); + let lower_interface = + LowBlockchainIntMock::default().get_block_number_result(latest_block_number); let blockchain_interface_mock = BlockchainInterfaceMock::default() .retrieve_transactions_params(&retrieve_transactions_params_arc) - .retrieve_transactions_result(Ok(expected_transactions.clone())); + .retrieve_transactions_result(Ok(expected_transactions.clone())) + .lower_interface_results(Box::new(lower_interface)); let set_start_block_params_arc = Arc::new(Mutex::new(vec![])); let persistent_config = PersistentConfigurationMock::new() + .max_block_count_result(Ok(Some(10000u64))) .start_block_result(Ok(6)) .set_start_block_params(&set_start_block_params_arc) .set_start_block_result(Ok(())); @@ -1571,9 +1549,16 @@ mod tests { system.run(); let after = SystemTime::now(); let set_start_block_params = set_start_block_params_arc.lock().unwrap(); - assert_eq!(*set_start_block_params, vec![1234]); + assert_eq!(*set_start_block_params, vec![1234u64]); let retrieve_transactions_params = retrieve_transactions_params_arc.lock().unwrap(); - assert_eq!(*retrieve_transactions_params, vec![(6, earning_wallet)]); + assert_eq!( + *retrieve_transactions_params, + vec![( + BlockNumber::Number(6u64.into()), + BlockNumber::Number(1024u64.into()), + earning_wallet + )] + ); let accountant_received_payment = accountant_recording_arc.lock().unwrap(); assert_eq!(accountant_received_payment.len(), 1); let received_payments = accountant_received_payment.get_record::(0); @@ -1594,13 +1579,17 @@ mod tests { #[test] fn processing_of_received_payments_continues_even_if_no_payments_are_detected() { init_test_logging(); + let lower_interface = + LowBlockchainIntMock::default().get_block_number_result(Ok(0u64.into())); let blockchain_interface_mock = BlockchainInterfaceMock::default() .retrieve_transactions_result(Ok(RetrievedBlockchainTransactions { new_start_block: 7, transactions: vec![], - })); + })) + .lower_interface_results(Box::new(lower_interface)); let set_start_block_params_arc = Arc::new(Mutex::new(vec![])); let persistent_config = PersistentConfigurationMock::new() + .max_block_count_result(Ok(Some(10000u64))) .start_block_result(Ok(6)) .set_start_block_params(&set_start_block_params_arc) .set_start_block_result(Ok(())); @@ -1657,10 +1646,14 @@ mod tests { expected = "Cannot retrieve start block from database; payments to you may not be processed: TransactionError" )] fn handle_retrieve_transactions_panics_if_start_block_cannot_be_read() { + let lower_interface = + LowBlockchainIntMock::default().get_block_number_result(Ok(0u64.into())); + let blockchain_interface = + BlockchainInterfaceMock::default().lower_interface_results(Box::new(lower_interface)); let persistent_config = PersistentConfigurationMock::new() .start_block_result(Err(PersistentConfigError::TransactionError)); let mut subject = BlockchainBridge::new( - Box::new(BlockchainInterfaceMock::default()), + Box::new(blockchain_interface), Box::new(persistent_config), false, None, //not needed in this test @@ -1675,22 +1668,25 @@ mod tests { #[test] #[should_panic( - expected = "Cannot set start block in database; payments to you may not be processed: TransactionError" + expected = "Cannot set start block 1234 in database; payments to you may not be processed: TransactionError" )] fn handle_retrieve_transactions_panics_if_start_block_cannot_be_written() { let persistent_config = PersistentConfigurationMock::new() .start_block_result(Ok(1234)) + .max_block_count_result(Ok(Some(10000u64))) .set_start_block_result(Err(PersistentConfigError::TransactionError)); - let blockchain_interface = BlockchainInterfaceMock::default().retrieve_transactions_result( - Ok(RetrievedBlockchainTransactions { + let lower_interface = + LowBlockchainIntMock::default().get_block_number_result(Ok(0u64.into())); + let blockchain_interface = BlockchainInterfaceMock::default() + .retrieve_transactions_result(Ok(RetrievedBlockchainTransactions { new_start_block: 1234, transactions: vec![BlockchainTransaction { block_number: 1000, from: make_wallet("somewallet"), wei_amount: 2345, }], - }), - ); + })) + .lower_interface_results(Box::new(lower_interface)); let mut subject = BlockchainBridge::new( Box::new(blockchain_interface), Box::new(persistent_config), @@ -1848,6 +1844,127 @@ mod tests { prove_that_crash_request_handler_is_hooked_up(subject, CRASH_KEY); } + #[test] + fn extract_max_block_range_from_error_response() { + let result = BlockchainError::QueryFailed("RPC error: Error { code: ServerError(-32005), message: \"eth_getLogs block range too large, range: 33636, max: 3500\", data: None }".to_string()); + let subject = BlockchainBridge::new( + Box::new(BlockchainInterfaceMock::default()), + Box::new(PersistentConfigurationMock::default()), + false, + None, + ); + let max_block_count = subject.extract_max_block_count(result); + + assert_eq!(Some(3500u64), max_block_count); + } + + #[test] + fn extract_max_block_range_from_pokt_error_response() { + let result = BlockchainError::QueryFailed("Rpc(Error { code: ServerError(-32001), message: \"Relay request failed validation: invalid relay request: eth_getLogs block range limit (100000 blocks) exceeded\", data: None })".to_string()); + let subject = BlockchainBridge::new( + Box::new(BlockchainInterfaceMock::default()), + Box::new(PersistentConfigurationMock::default()), + false, + None, + ); + let max_block_count = subject.extract_max_block_count(result); + + assert_eq!(Some(100000u64), max_block_count); + } + /* + POKT (Polygon mainnet and mumbai) + {"jsonrpc":"2.0","id":7,"error":{"message":"You cannot query logs for more than 100000 blocks at once.","code":-32064}} + */ + /* + Ankr + {"jsonrpc":"2.0","error":{"code":-32600,"message":"block range is too wide"},"id":null}% + */ + #[test] + fn extract_max_block_range_for_ankr_error_response() { + let result = BlockchainError::QueryFailed("RPC error: Error { code: ServerError(-32600), message: \"block range is too wide\", data: None }".to_string()); + let subject = BlockchainBridge::new( + Box::new(BlockchainInterfaceMock::default()), + Box::new(PersistentConfigurationMock::default()), + false, + None, + ); + let max_block_count = subject.extract_max_block_count(result); + + assert_eq!(None, max_block_count); + } + + /* + MaticVigil + [{"error":{"message":"Blockheight too far in the past. Check params passed to eth_getLogs or eth_call requests.Range of blocks allowed for your plan: 1000","code":-32005},"jsonrpc":"2.0","id":7},{"error":{"message":"Blockheight too far in the past. Check params passed to eth_getLogs or eth_call requests.Range of blocks allowed for your plan: 1000","code":-32005},"jsonrpc":"2.0","id":8}]% + */ + #[test] + fn extract_max_block_range_for_matic_vigil_error_response() { + let result = BlockchainError::QueryFailed("RPC error: Error { code: ServerError(-32005), message: \"Blockheight too far in the past. Check params passed to eth_getLogs or eth_call requests.Range of blocks allowed for your plan: 1000\", data: None }".to_string()); + let subject = BlockchainBridge::new( + Box::new(BlockchainInterfaceMock::default()), + Box::new(PersistentConfigurationMock::default()), + false, + None, + ); + let max_block_count = subject.extract_max_block_count(result); + + assert_eq!(Some(1000), max_block_count); + } + + /* + Blockpi + [{"jsonrpc":"2.0","id":7,"result":"0x21db466"},{"jsonrpc":"2.0","id":8,"error":{"code":-32602,"message":"eth_getLogs is limited to 1024 block range. Please check the parameter requirements at https://docs.blockpi.io/documentations/api-reference"}}] + */ + #[test] + fn extract_max_block_range_for_blockpi_error_response() { + let result = BlockchainError::QueryFailed("RPC error: Error { code: ServerError(-32005), message: \"eth_getLogs is limited to 1024 block range. Please check the parameter requirements at https://docs.blockpi.io/documentations/api-reference\", data: None }".to_string()); + let subject = BlockchainBridge::new( + Box::new(BlockchainInterfaceMock::default()), + Box::new(PersistentConfigurationMock::default()), + false, + None, + ); + let max_block_count = subject.extract_max_block_count(result); + + assert_eq!(Some(1024), max_block_count); + } + + /* + blastapi - completely rejected call on Public endpoint as won't handle eth_getLogs method on public API + [{"jsonrpc":"2.0","id":2,"error":{"code":-32601,"message":"Method not found","data":{"method":""}}},{"jsonrpc":"2.0","id":1,"error":{"code":-32600,"message":"Invalid Request","data":{"message":"Cancelled due to validation errors in batch request"}}}] (edited) + [8:50 AM] + */ + + #[test] + fn extract_max_block_range_for_blastapi_error_response() { + let result = BlockchainError::QueryFailed("RPC error: Error { code: ServerError(-32601), message: \"Method not found\", data: \"'eth_getLogs' is not available on our public API. Head over to https://docs.blastapi.io/blast-documentation/tutorials-and-guides/using-blast-to-get-a-blockchain-endpoint for more information\" }".to_string()); + let subject = BlockchainBridge::new( + Box::new(BlockchainInterfaceMock::default()), + Box::new(PersistentConfigurationMock::default()), + false, + None, + ); + let max_block_count = subject.extract_max_block_count(result); + + assert_eq!(None, max_block_count); + } + + #[test] + fn extract_max_block_range_for_expected_batch_got_single_error_response() { + let result = BlockchainError::QueryFailed( + "Got invalid response: Expected batch, got single.".to_string(), + ); + let subject = BlockchainBridge::new( + Box::new(BlockchainInterfaceMock::default()), + Box::new(PersistentConfigurationMock::default()), + false, + None, + ); + let max_block_count = subject.extract_max_block_count(result); + + assert_eq!(Some(1000), max_block_count); + } + #[test] fn make_connections_implements_panic_on_migration() { let data_dir = ensure_node_home_directory_exists( @@ -1856,13 +1973,150 @@ mod tests { ); let act = |data_dir: &Path| { - BlockchainBridge::make_connections( - Some("http://127.0.0.1".to_string()), - data_dir.to_path_buf(), - Chain::PolyMumbai, - ); + BlockchainBridge::initialize_persistent_configuration(data_dir); }; assert_on_initialization_with_panic_on_migration(&data_dir, &act); } } + +#[cfg(test)] +pub mod exportable_test_parts { + use super::*; + use crate::bootstrapper::BootstrapperConfig; + use crate::test_utils::http_test_server::TestServer; + use crate::test_utils::make_wallet; + use crate::test_utils::recorder::make_blockchain_bridge_subs_from_recorder; + use crate::test_utils::unshared_test_utils::{AssertionsMessage, SubsFactoryTestAddrLeaker}; + use actix::System; + use crossbeam_channel::{bounded, Receiver}; + use masq_lib::test_utils::utils::{ensure_node_home_directory_exists, TEST_DEFAULT_CHAIN}; + use masq_lib::utils::find_free_port; + use serde_json::Value::Object; + use serde_json::{Map, Value}; + use std::net::Ipv4Addr; + + impl SubsFactory + for SubsFactoryTestAddrLeaker + { + fn make(&self, addr: &Addr) -> BlockchainBridgeSubs { + self.send_leaker_msg_and_return_meaningless_subs( + addr, + make_blockchain_bridge_subs_from_recorder, + ) + } + } + + pub fn test_blockchain_bridge_is_constructed_with_correctly_functioning_connections( + test_module: &str, + test_name: &str, + act: A, + ) where + A: FnOnce( + BootstrapperConfig, + SubsFactoryTestAddrLeaker, + ) -> BlockchainBridgeSubs, + { + fn prepare_db_with_unique_value(data_dir: &Path, gas_price: u64) { + let mut persistent_config = { + let conn = DbInitializerReal::default() + .initialize(data_dir, DbInitializationConfig::test_default()) + .unwrap(); + PersistentConfigurationReal::from(conn) + }; + persistent_config.set_gas_price(gas_price).unwrap() + } + fn launch_prepared_test_server() -> (TestServer, String) { + let port = find_free_port(); + let server_url = format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port); + ( + TestServer::start( + port, + vec![br#"{"jsonrpc":"2.0","id":0,"result":someGarbage}"#.to_vec()], + ), + server_url, + ) + } + fn send_rpc_request_to_assert_on_later_and_assert_db_connection( + actor_addr_rx: Receiver>, + wallet: Wallet, + expected_gas_price: u64, + ) { + let blockchain_bridge_addr = actor_addr_rx.try_recv().unwrap(); + let msg = AssertionsMessage { + assertions: Box::new(move |bb: &mut BlockchainBridge| { + // We will assert on soundness of the connection by checking the receipt of + // this request + let _result = bb + .blockchain_interface + .lower_interface() + .get_service_fee_balance(&wallet); + + // Asserting that we can look into the expected db from here, meaning the + // PersistentConfiguration was set up correctly + assert_eq!( + bb.persistent_config.gas_price().unwrap(), + expected_gas_price + ); + // I don't know why exactly but the standard position + // of this call doesn't work + System::current().stop(); + }), + }; + blockchain_bridge_addr.try_send(msg).unwrap(); + } + fn assert_blockchain_interface_connection(test_server: &TestServer, wallet: Wallet) { + let requests = test_server.requests_so_far(); + let bodies: Vec = requests + .into_iter() + .map(|request| serde_json::from_slice(&request.body()).unwrap()) + .collect(); + let params = &bodies[0]["params"]; + let expected_params = { + let mut map = Map::new(); + let hashed_data = format!( + "0x70a08231000000000000000000000000{}", + &wallet.to_string()[2..] + ); + map.insert("data".to_string(), Value::String(hashed_data)); + map.insert( + "to".to_string(), + Value::String(format!("{:?}", TEST_DEFAULT_CHAIN.rec().contract)), + ); + map + }; + assert_eq!( + params, + &Value::Array(vec![ + Object(expected_params), + Value::String("latest".to_string()) + ]) + ); + } + + let data_dir = ensure_node_home_directory_exists(test_module, test_name); + let gas_price = 444; + prepare_db_with_unique_value(&data_dir, gas_price); + let (test_server, server_url) = launch_prepared_test_server(); + let wallet = make_wallet("abc"); + let mut bootstrapper_config = BootstrapperConfig::new(); + bootstrapper_config + .blockchain_bridge_config + .blockchain_service_url_opt = Some(server_url); + bootstrapper_config.blockchain_bridge_config.chain = TEST_DEFAULT_CHAIN; + bootstrapper_config.data_directory = data_dir; + let (tx, blockchain_bridge_addr_rx) = bounded(1); + let address_leaker = SubsFactoryTestAddrLeaker { address_leaker: tx }; + let system = System::new(test_name); + + act(bootstrapper_config, address_leaker); + + send_rpc_request_to_assert_on_later_and_assert_db_connection( + blockchain_bridge_addr_rx, + wallet.clone(), + gas_price, + ); + assert_eq!(system.run(), 0); + assert_blockchain_interface_connection(&test_server, wallet) + } +} diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_null/lower_level_interface_null.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_null/lower_level_interface_null.rs new file mode 100644 index 000000000..bde60a6bc --- /dev/null +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_null/lower_level_interface_null.rs @@ -0,0 +1,112 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainError; +use crate::blockchain::blockchain_interface::lower_level_interface::{ + LatestBlockNumber, LowBlockchainInt, ResultForBalance, ResultForNonce, +}; +use crate::sub_lib::wallet::Wallet; +use masq_lib::logger::Logger; + +pub struct LowBlockChainIntNull { + logger: Logger, +} + +impl LowBlockchainInt for LowBlockChainIntNull { + fn get_transaction_fee_balance(&self, _wallet: &Wallet) -> ResultForBalance { + Err(self.handle_null_call("transaction fee balance")) + } + + fn get_service_fee_balance(&self, _wallet: &Wallet) -> ResultForBalance { + Err(self.handle_null_call("masq balance")) + } + + fn get_block_number(&self) -> LatestBlockNumber { + Err(self.handle_null_call("block number")) + } + + fn get_transaction_id(&self, _wallet: &Wallet) -> ResultForNonce { + Err(self.handle_null_call("transaction id")) + } +} + +impl LowBlockChainIntNull { + pub fn new(logger: &Logger) -> Self { + Self { + logger: logger.clone(), + } + } + + fn handle_null_call(&self, operation: &str) -> BlockchainError { + error!(self.logger, "Null version can't fetch {operation}"); + BlockchainError::UninitializedBlockchainInterface + } +} + +#[cfg(test)] +mod tests { + use crate::blockchain::blockchain_interface::BlockchainError; + use crate::blockchain::blockchain_interface::blockchain_interface_null::lower_level_interface_null::LowBlockChainIntNull; + use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; + use crate::sub_lib::wallet::Wallet; + use crate::test_utils::make_wallet; + use masq_lib::logger::Logger; + use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + use std::fmt::Debug; + + #[test] + fn low_bci_null_gets_no_transaction_fee_balance() { + let test_name = "low_bci_null_gets_no_transaction_fee_balance"; + let act = |subject: &LowBlockChainIntNull, wallet: &Wallet| { + subject.get_transaction_fee_balance(wallet) + }; + + test_null_method(test_name, act, "transaction fee balance"); + } + + #[test] + fn low_bci_null_gets_no_masq_balance() { + let test_name = "low_bci_null_gets_no_masq_balance"; + let act = |subject: &LowBlockChainIntNull, wallet: &Wallet| { + subject.get_service_fee_balance(wallet) + }; + + test_null_method(test_name, act, "masq balance"); + } + + #[test] + fn low_bci_null_gets_no_block_number() { + let test_name = "low_bci_null_gets_no_block_number"; + let act = |subject: &LowBlockChainIntNull, _wallet: &Wallet| subject.get_block_number(); + + test_null_method(test_name, act, "block number"); + } + + #[test] + fn low_bci_null_gets_no_transaction_id() { + let test_name = "low_bci_null_gets_no_transaction_id"; + let act = + |subject: &LowBlockChainIntNull, wallet: &Wallet| subject.get_transaction_id(wallet); + + test_null_method(test_name, act, "transaction id"); + } + + fn test_null_method( + test_name: &str, + act: fn(&LowBlockChainIntNull, &Wallet) -> Result, + expected_method_name: &str, + ) { + init_test_logging(); + let wallet = make_wallet("blah"); + let subject = LowBlockChainIntNull::new(&Logger::new(test_name)); + + let result = act(&subject, &wallet); + + assert_eq!( + result, + Err(BlockchainError::UninitializedBlockchainInterface) + ); + let _expected_log_msg = TestLogHandler::new().exists_log_containing(&format!( + "ERROR: {test_name}: Null version can't fetch {expected_method_name}" + )); + } +} diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_null/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_null/mod.rs new file mode 100644 index 000000000..e33ff06ab --- /dev/null +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_null/mod.rs @@ -0,0 +1,291 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +pub mod lower_level_interface_null; + +use crate::accountant::db_access_objects::payable_dao::PayableAccount; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; +use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; +use crate::blockchain::blockchain_interface::blockchain_interface_null::lower_level_interface_null::LowBlockChainIntNull; +use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; +use crate::db_config::persistent_configuration::PersistentConfiguration; +use crate::sub_lib::wallet::Wallet; +use actix::Recipient; +use masq_lib::logger::Logger; +use web3::types::{Address, BlockNumber, H160, H256}; +use crate::blockchain::blockchain_interface::BlockchainInterface; +use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainAgentBuildError, BlockchainError, PayableTransactionError, ResultForReceipt}; +use crate::blockchain::blockchain_interface::data_structures::{ProcessedPayableFallible, RetrievedBlockchainTransactions}; + +pub struct BlockchainInterfaceNull { + logger: Logger, + lower_level_interface: Box, +} + +impl BlockchainInterface for BlockchainInterfaceNull { + fn contract_address(&self) -> Address { + self.log_uninitialized_for_operation("get contract address"); + H160::zero() + } + + fn retrieve_transactions( + &self, + _start_block: BlockNumber, + _end_block: BlockNumber, + _wallet: &Wallet, + ) -> Result { + self.handle_uninitialized_interface("retrieve transactions") + } + + fn build_blockchain_agent( + &self, + _consuming_wallet: &Wallet, + _persistent_config: &dyn PersistentConfiguration, + ) -> Result, BlockchainAgentBuildError> { + self.handle_uninitialized_interface("build blockchain agent") + } + + fn send_batch_of_payables( + &self, + _agent: Box, + _new_fingerprints_recipient: &Recipient, + _accounts: &[PayableAccount], + ) -> Result, PayableTransactionError> { + self.handle_uninitialized_interface("pay for payables") + } + + fn get_transaction_receipt(&self, _hash: H256) -> ResultForReceipt { + self.handle_uninitialized_interface("get transaction receipt") + } + + fn lower_interface(&self) -> &dyn LowBlockchainInt { + error!( + self.logger, + "Provides the null version of lower blockchain interface only" + ); + &*self.lower_level_interface + } + + as_any_in_trait_impl!(); +} + +impl Default for BlockchainInterfaceNull { + fn default() -> Self { + Self::new() + } +} + +trait BlockchainInterfaceUninitializedError { + fn error() -> Self; +} + +macro_rules! impl_bci_uninitialized { + ($($error_type: ty),+) => { + $( + impl BlockchainInterfaceUninitializedError for $error_type { + fn error() -> Self { + Self::UninitializedBlockchainInterface + } + } + )+ + } +} + +impl_bci_uninitialized!( + PayableTransactionError, + BlockchainError, + BlockchainAgentBuildError +); + +impl BlockchainInterfaceNull { + pub fn new() -> Self { + let logger = Logger::new("BlockchainInterface"); + let lower_level_interface = Box::new(LowBlockChainIntNull::new(&logger)); + BlockchainInterfaceNull { + logger, + lower_level_interface, + } + } + + fn handle_uninitialized_interface( + &self, + operation: &str, + ) -> Result + where + E: BlockchainInterfaceUninitializedError, + { + self.log_uninitialized_for_operation(operation); + let err = E::error(); + Err(err) + } + + fn log_uninitialized_for_operation(&self, operation: &str) { + error!( + self.logger, + "Failed to {} with uninitialized blockchain \ + interface. Parameter blockchain-service-url is missing.", + operation + ) + } +} + +#[cfg(test)] +mod tests { + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::agent_null::BlockchainAgentNull; + use crate::accountant::test_utils::make_payable_account; + use crate::blockchain::blockchain_interface::blockchain_interface_null::lower_level_interface_null::LowBlockChainIntNull; + use crate::blockchain::blockchain_interface::blockchain_interface_null::{ + BlockchainInterfaceNull, BlockchainInterfaceUninitializedError, + }; + use crate::blockchain::test_utils::make_tx_hash; + use crate::test_utils::make_wallet; + use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; + use crate::test_utils::recorder::make_recorder; + use actix::Actor; + use ethereum_types::U64; + use masq_lib::logger::Logger; + use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; + use web3::types::{BlockNumber, H160}; + use crate::blockchain::blockchain_interface::BlockchainInterface; + use crate::blockchain::blockchain_interface::data_structures::errors::{BlockchainAgentBuildError, BlockchainError, PayableTransactionError}; + + fn make_subject(test_name: &str) -> BlockchainInterfaceNull { + let logger = Logger::new(test_name); + let lower_level_interface = Box::new(LowBlockChainIntNull::new(&logger)); + BlockchainInterfaceNull { + logger, + lower_level_interface, + } + } + + #[test] + fn blockchain_interface_null_returns_contract_address() { + let result = make_subject("irrelevant").contract_address(); + + assert_eq!(result, H160::zero()) + } + + #[test] + fn blockchain_interface_null_retrieves_no_transactions() { + init_test_logging(); + let test_name = "blockchain_interface_null_retrieves_no_transactions"; + let wallet = make_wallet("blah"); + + let result = make_subject(test_name).retrieve_transactions( + BlockNumber::Number(U64::zero()), + BlockNumber::Latest, + &wallet, + ); + + assert_eq!( + result, + Err(BlockchainError::UninitializedBlockchainInterface) + ); + let expected_msg = "Failed to retrieve transactions with uninitialized blockchain \ + interface. Parameter blockchain-service-url is missing."; + let expected_log_msg = format!("ERROR: {test_name}: {}", expected_msg); + TestLogHandler::new().exists_log_containing(expected_log_msg.as_str()); + } + + #[test] + fn blockchain_interface_null_builds_null_agent() { + init_test_logging(); + let test_name = "blockchain_interface_null_builds_null_agent"; + let wallet = make_wallet("blah"); + let persistent_config = PersistentConfigurationMock::new(); + let subject = make_subject(test_name); + + let result = subject.build_blockchain_agent(&wallet, &persistent_config); + + let err = match result { + Ok(_) => panic!("we expected an error but got ok"), + Err(e) => e, + }; + assert_eq!( + err, + BlockchainAgentBuildError::UninitializedBlockchainInterface + ); + let expected_log_msg = format!( + "ERROR: {test_name}: Failed to build blockchain agent with uninitialized blockchain \ + interface. Parameter blockchain-service-url is missing." + ); + TestLogHandler::new().exists_log_containing(expected_log_msg.as_str()); + } + + #[test] + fn blockchain_interface_null_cannot_send_batch_of_payables() { + init_test_logging(); + let test_name = "blockchain_interface_null_cannot_send_batch_of_payables"; + let (recorder, _, _) = make_recorder(); + let recipient = recorder.start().recipient(); + let accounts = vec![make_payable_account(111)]; + let agent = Box::new(BlockchainAgentNull::new()); + + let result = make_subject(test_name).send_batch_of_payables(agent, &recipient, &accounts); + + assert_eq!( + result, + Err(PayableTransactionError::UninitializedBlockchainInterface) + ); + let expected_log_msg = format!( + "ERROR: {test_name}: Failed to pay for payables with uninitialized blockchain \ + interface. Parameter blockchain-service-url is missing." + ); + TestLogHandler::new().exists_log_containing(expected_log_msg.as_str()); + } + + #[test] + fn blockchain_interface_null_gets_no_transaction_receipt() { + init_test_logging(); + let test_name = "blockchain_interface_null_gets_no_transaction_receipt"; + let tx_hash = make_tx_hash(123); + + let result = make_subject(test_name).get_transaction_receipt(tx_hash); + + assert_eq!( + result, + Err(BlockchainError::UninitializedBlockchainInterface) + ); + let expected_log_msg = format!( + "ERROR: {test_name}: Failed to get transaction receipt with uninitialized \ + blockchain interface. Parameter blockchain-service-url is missing." + ); + TestLogHandler::new().exists_log_containing(expected_log_msg.as_str()); + } + + #[test] + fn blockchain_interface_null_gives_null_lower_interface() { + init_test_logging(); + let test_name = "blockchain_interface_null_gives_null_lower_interface"; + let wallet = make_wallet("abc"); + + let _ = make_subject(test_name) + .lower_interface() + .get_transaction_id(&wallet); + + let expected_log_msg_from_low_level_interface_call = format!( + "ERROR: {test_name}: Provides the null version of lower blockchain interface only" + ); + let expected_log_msg_from_rcp_call = + format!("ERROR: {test_name}: Null version can't fetch transaction id"); + TestLogHandler::new().assert_logs_contain_in_order(vec![ + expected_log_msg_from_low_level_interface_call.as_str(), + expected_log_msg_from_rcp_call.as_str(), + ]); + } + + #[test] + fn blockchain_interface_null_error_is_implemented_for_blockchain_error() { + assert_eq!( + BlockchainError::error(), + BlockchainError::UninitializedBlockchainInterface + ) + } + + #[test] + fn blockchain_interface_null_error_is_implemented_for_payable_transaction_error() { + assert_eq!( + PayableTransactionError::error(), + PayableTransactionError::UninitializedBlockchainInterface + ) + } +} diff --git a/node/src/blockchain/batch_payable_tools.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/batch_payable_tools.rs similarity index 96% rename from node/src/blockchain/batch_payable_tools.rs rename to node/src/blockchain/blockchain_interface/blockchain_interface_web3/batch_payable_tools.rs index f57a3f12c..ed0e21d15 100644 --- a/node/src/blockchain/batch_payable_tools.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/batch_payable_tools.rs @@ -92,8 +92,10 @@ impl BatchPayableTools for BatchPayableToolsReal +where + T: BatchTransport, +{ + web3: Rc>, + // TODO waiting for GH-707 (note: consider to query the balances together with the id) + _batch_web3: Rc>>, + contract: Contract, +} + +impl LowBlockchainInt for LowBlockchainIntWeb3 +where + T: BatchTransport, +{ + fn get_transaction_fee_balance(&self, wallet: &Wallet) -> ResultForBalance { + self.web3 + .eth() + .balance(wallet.address(), None) + .map_err(|e| BlockchainError::QueryFailed(format!("{} for wallet {}", e, wallet))) + .wait() + } + + fn get_service_fee_balance(&self, wallet: &Wallet) -> ResultForBalance { + self.contract + .query( + "balanceOf", + wallet.address(), + None, + Options::default(), + None, + ) + .map_err(|e| BlockchainError::QueryFailed(format!("{} for wallet {}", e, wallet))) + .wait() + } + + fn get_block_number(&self) -> LatestBlockNumber { + self.web3 + .eth() + .block_number() + .map_err(|e| BlockchainError::QueryFailed(e.to_string())) + .wait() + } + + fn get_transaction_id(&self, wallet: &Wallet) -> ResultForNonce { + self.web3 + .eth() + .transaction_count(wallet.address(), Some(BlockNumber::Pending)) + .map_err(|e| BlockchainError::QueryFailed(format!("{} for wallet {}", e, wallet))) + .wait() + } +} + +impl LowBlockchainIntWeb3 +where + T: BatchTransport, +{ + pub fn new(web3: Rc>, batch_web3: Rc>>, contract: Contract) -> Self { + Self { + web3, + _batch_web3: batch_web3, + contract, + } + } +} + +#[cfg(test)] +mod tests { + use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::LowBlockchainIntWeb3; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::{ + CONTRACT_ABI, REQUESTS_IN_PARALLEL, + }; + use crate::blockchain::blockchain_interface::lower_level_interface::{LowBlockchainInt, ResultForBalance}; + use crate::blockchain::blockchain_interface::BlockchainError; + use crate::sub_lib::wallet::Wallet; + use crate::test_utils::http_test_server::TestServer; + use crate::test_utils::make_paying_wallet; + use ethereum_types::U64; + use masq_lib::blockchains::chains::Chain; + use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; + use masq_lib::utils::find_free_port; + use serde_json::{json, Value}; + use std::net::Ipv4Addr; + use std::rc::Rc; + use std::str::FromStr; + use std::sync::{Arc, Mutex}; + use web3::contract::Contract; + use web3::transports::{Batch, Http}; + use web3::types::U256; + use web3::{BatchTransport, Web3}; + use crate::blockchain::test_utils::TestTransport; + + #[test] + fn low_interface_web3_transaction_fee_balance_works() { + let port = find_free_port(); + let test_server = TestServer::start( + port, + vec![br#"{"jsonrpc":"2.0","id":0,"result":"0xDEADBEEF"}"#.to_vec()], + ); + let (_event_loop_handle, transport) = Http::with_max_parallel( + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), + REQUESTS_IN_PARALLEL, + ) + .unwrap(); + let chain = TEST_DEFAULT_CHAIN; + let subject = make_subject(transport, chain); + + let result = subject + .get_transaction_fee_balance( + &Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(), + ) + .unwrap(); + + assert_eq!(result, U256::from(0xDEADBEEF_u64)); + let requests = test_server.requests_so_far(); + let bodies: Vec = requests + .into_iter() + .map(|request| serde_json::from_slice(&request.body()).unwrap()) + .collect(); + assert_eq!(bodies[0]["method"].to_string(), "\"eth_getBalance\"",); + assert_eq!( + bodies[0]["params"][0].to_string(), + "\"0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc\"", + ); + assert_eq!(bodies.len(), 1) + } + + #[test] + #[should_panic(expected = "No address for an uninitialized wallet!")] + fn low_interface_web3_get_transaction_fee_balance_returns_err_for_an_invalid_wallet() { + let port = 8545; + let (_event_loop_handle, transport) = Http::with_max_parallel( + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), + REQUESTS_IN_PARALLEL, + ) + .unwrap(); + let chain = TEST_DEFAULT_CHAIN; + let subject = make_subject(transport, chain); + + let result = subject.get_transaction_fee_balance(&Wallet::new("0x_invalid_wallet_address")); + + assert_eq!(result, Err(BlockchainError::InvalidAddress)); + } + + #[test] + fn low_interface_web3_get_transaction_fee_balance_returns_err_for_unintelligible_response() { + let act = |subject: &LowBlockchainIntWeb3, wallet: &Wallet| { + subject.get_transaction_fee_balance(wallet) + }; + + assert_error_from_unintelligible_response(act, "invalid hex character"); + } + + #[test] + fn low_interface_web3_get_masq_balance_works() { + let port = find_free_port(); + let test_server = TestServer::start (port, vec![ + br#"{"jsonrpc":"2.0","id":0,"result":"0x00000000000000000000000000000000000000000000000000000000DEADBEEF"}"#.to_vec() + ]); + let (_event_loop_handle, transport) = Http::with_max_parallel( + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), + REQUESTS_IN_PARALLEL, + ) + .unwrap(); + let chain = TEST_DEFAULT_CHAIN; + let subject = make_subject(transport, chain); + + let result = subject + .get_service_fee_balance( + &Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(), + ) + .unwrap(); + + assert_eq!(result, U256::from(0xDEADBEEF_u64)); + let requests = test_server.requests_so_far(); + let bodies: Vec = requests + .into_iter() + .map(|request| serde_json::from_slice(&request.body()).unwrap()) + .collect(); + assert_eq!(bodies[0]["method"].to_string(), "\"eth_call\"",); + let contract_address = chain.rec().contract; + assert_eq!( + bodies[0]["params"][0]["to"].to_string(), + format!("\"{:?}\"", contract_address), + ); + assert_eq!(bodies.len(), 1) + } + + #[test] + #[should_panic(expected = "No address for an uninitialized wallet!")] + fn low_interface_web3_get_masq_balance_returns_err_for_an_invalid_wallet() { + let port = 8545; + let (_event_loop_handle, transport) = Http::with_max_parallel( + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), + REQUESTS_IN_PARALLEL, + ) + .unwrap(); + let chain = TEST_DEFAULT_CHAIN; + let subject = make_subject(transport, chain); + + let result = subject.get_service_fee_balance(&Wallet::new("0x_invalid_wallet_address")); + + assert_eq!(result, Err(BlockchainError::InvalidAddress)); + } + + #[test] + fn low_interface_web3_get_masq_balance_returns_err_for_unintelligible_response() { + let act = |subject: &LowBlockchainIntWeb3, wallet: &Wallet| { + subject.get_service_fee_balance(wallet) + }; + + assert_error_from_unintelligible_response(act, "Invalid hex"); + } + + #[test] + fn low_interface_web3_can_fetch_latest_block_number_successfully() { + let prepare_params_arc = Arc::new(Mutex::new(vec![])); + let transport = TestTransport::default() + .prepare_params(&prepare_params_arc) + .send_result(json!("0x1e37066")); + let subject = make_subject(transport, TEST_DEFAULT_CHAIN); + + let latest_block_number = subject.get_block_number().unwrap(); + + assert_eq!(latest_block_number, U64::from(0x1e37066u64)); + let mut prepare_params = prepare_params_arc.lock().unwrap(); + let (method_name, actual_arguments) = prepare_params.remove(0); + assert!(prepare_params.is_empty()); + assert_eq!(method_name, "eth_blockNumber".to_string()); + let expected_arguments: Vec = vec![]; + assert_eq!(actual_arguments, expected_arguments); + } + + #[test] + fn low_interface_web3_handles_latest_null_block_number_error() { + let prepare_params_arc = Arc::new(Mutex::new(vec![])); + let transport = TestTransport::default() + .prepare_params(&prepare_params_arc) + .send_result(Value::Null); + let subject = make_subject(transport, TEST_DEFAULT_CHAIN); + + let expected_error = subject.get_block_number().unwrap_err(); + + assert_eq!( + expected_error, + BlockchainError::QueryFailed( + "Decoder error: Error(\"invalid type: null, expected \ + a 0x-prefixed hex string with length between (0; 16]\", line: 0, column: 0)" + .to_string() + ) + ); + let mut prepare_params = prepare_params_arc.lock().unwrap(); + let (method_name, actual_arguments) = prepare_params.remove(0); + assert!(prepare_params.is_empty()); + assert_eq!(method_name, "eth_blockNumber".to_string()); + let expected_arguments: Vec = vec![]; + assert_eq!(actual_arguments, expected_arguments); + } + + #[test] + fn low_interface_web3_can_handle_latest_string_block_number_error() { + let prepare_params_arc: Arc)>>> = + Arc::new(Mutex::new(vec![])); + let transport = TestTransport::default() + .prepare_params(&prepare_params_arc) + .send_result(Value::String("this is an invalid block number".to_string())); + let subject = make_subject(transport, TEST_DEFAULT_CHAIN); + + let expected_error = subject.get_block_number().unwrap_err(); + + assert_eq!( + expected_error, + BlockchainError::QueryFailed( + "Decoder error: Error(\"0x prefix is missing\", line: 0, column: 0)".to_string() + ) + ); + let mut prepare_params = prepare_params_arc.lock().unwrap(); + let (method_name, actual_arguments) = prepare_params.remove(0); + assert!(prepare_params.is_empty()); + assert_eq!(method_name, "eth_blockNumber".to_string()); + let expected_arguments: Vec = vec![]; + assert_eq!(actual_arguments, expected_arguments); + } + + #[test] + fn low_interface_web3_get_transaction_id_works() { + let prepare_params_arc = Arc::new(Mutex::new(vec![])); + let send_params_arc = Arc::new(Mutex::new(vec![])); + let transport = TestTransport::default() + .prepare_params(&prepare_params_arc) + .send_params(&send_params_arc) + .send_result(json!( + "0x0000000000000000000000000000000000000000000000000000000000000001" + )); + let chain = TEST_DEFAULT_CHAIN; + let subject = make_subject(transport, chain); + + let result = subject.get_transaction_id(&make_paying_wallet(b"gdasgsa")); + + assert_eq!(result, Ok(U256::from(1))); + let mut prepare_params = prepare_params_arc.lock().unwrap(); + let (method_name, actual_arguments) = prepare_params.remove(0); + assert!(prepare_params.is_empty()); + let actual_arguments: Vec = actual_arguments + .into_iter() + .map(|arg| serde_json::to_string(&arg).unwrap()) + .collect(); + assert_eq!(method_name, "eth_getTransactionCount".to_string()); + assert_eq!( + actual_arguments, + vec![ + String::from(r#""0x5c361ba8d82fcf0e5538b2a823e9d457a2296725""#), + String::from(r#""pending""#), + ] + ); + let send_params = send_params_arc.lock().unwrap(); + let rpc_call_params = vec![ + Value::String(String::from("0x5c361ba8d82fcf0e5538b2a823e9d457a2296725")), + Value::String(String::from("pending")), + ]; + let expected_request = + web3::helpers::build_request(1, "eth_getTransactionCount", rpc_call_params); + assert_eq!(*send_params, vec![(1, expected_request)]) + } + + #[test] + fn low_interface_web3_get_transaction_id_handles_err() { + let act = |subject: &LowBlockchainIntWeb3, wallet: &Wallet| { + subject.get_transaction_id(wallet) + }; + + assert_error_from_unintelligible_response(act, "invalid hex character") + } + + fn assert_error_from_unintelligible_response(act: F, expected_err_fragment: &str) + where + F: FnOnce(&LowBlockchainIntWeb3, &Wallet) -> ResultForBalance, + { + let port = find_free_port(); + let _test_server = TestServer::start (port, vec![ + br#"{"jsonrpc":"2.0","id":0,"result":"0x000000000000000000000000000000000000000000000000000000000000FFFQ"}"#.to_vec() + ]); + let (_event_loop_handle, transport) = Http::with_max_parallel( + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), + REQUESTS_IN_PARALLEL, + ) + .unwrap(); + let chain = TEST_DEFAULT_CHAIN; + let wallet = Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(); + let subject = make_subject(transport, chain); + + let result = act(&subject, &wallet); + + let err_msg = match result { + Err(BlockchainError::QueryFailed(msg)) => msg, + x => panic!("Expected BlockchainError::QueryFailed, but got {:?}", x), + }; + assert!( + err_msg.contains(expected_err_fragment), + "Expected this fragment \"{}\" in this err msg: {}", + expected_err_fragment, + err_msg + ); + assert!( + err_msg.contains("for wallet 0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc"), + "Expected the wallet to be cited in the err msg like \" for wallet {}\" but wasn't", + wallet + ) + } + + fn make_subject(transport: T, chain: Chain) -> LowBlockchainIntWeb3 + where + T: BatchTransport, + { + let web3 = Web3::new(transport.clone()); + let web3_batch = Web3::new(Batch::new(transport.clone())); + let contract = + Contract::from_json(web3.eth(), chain.rec().contract, CONTRACT_ABI.as_bytes()) + .expect("Unable to initialize contract."); + LowBlockchainIntWeb3::new(Rc::new(web3), Rc::new(web3_batch), contract) + } +} diff --git a/node/src/blockchain/blockchain_interface.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs similarity index 62% rename from node/src/blockchain/blockchain_interface.rs rename to node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index de03b40e3..083f75822 100644 --- a/node/src/blockchain/blockchain_interface.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -1,38 +1,45 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::comma_joined_stringifiable; -use crate::accountant::database_access_objects::payable_dao::{PayableAccount, PendingPayable}; -use crate::blockchain::batch_payable_tools::{BatchPayableTools, BatchPayableToolsReal}; +mod batch_payable_tools; +pub mod lower_level_interface_web3; +mod test_utils; + +use crate::accountant::db_access_objects::payable_dao::{PayableAccount}; +use crate::accountant::{gwei_to_wei}; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::agent_web3::BlockchainAgentWeb3; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; -use crate::blockchain::blockchain_interface::BlockchainError::{ - InvalidAddress, InvalidResponse, InvalidUrl, QueryFailed, UninitializedBlockchainInterface, +use crate::blockchain::blockchain_interface::blockchain_interface_web3::batch_payable_tools::{ + BatchPayableTools, BatchPayableToolsReal, }; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::lower_level_interface_web3::LowBlockchainIntWeb3; +use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; +use crate::blockchain::blockchain_interface::{BlockchainAgentBuildError, BlockchainError, BlockchainInterface, PayableTransactionError, ResultForReceipt, RetrievedBlockchainTransactions}; +use crate::db_config::persistent_configuration::PersistentConfiguration; +use crate::masq_lib::utils::ExpectValue; +use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; use crate::sub_lib::wallet::Wallet; -use actix::{Message, Recipient}; -use futures::{future, Future}; +use actix::Recipient; +use futures::Future; use indoc::indoc; -use itertools::Either::{Left, Right}; -use masq_lib::blockchains::chains::{Chain, ChainFamily}; +use masq_lib::blockchains::chains::Chain; use masq_lib::logger::Logger; -use masq_lib::utils::ExpectValue; use serde_json::Value; -use std::convert::{From, TryFrom, TryInto}; -use std::fmt; -use std::fmt::{Debug, Display, Formatter}; +use std::fmt::Debug; use std::iter::once; +use std::rc::Rc; use thousands::Separable; -use variant_count::VariantCount; -use web3::contract::{Contract, Options}; -use web3::transports::{Batch, EventLoopHandle, Http}; +use web3::contract::Contract; +use web3::transports::{Batch, EventLoopHandle}; use web3::types::{ Address, BlockNumber, Bytes, FilterBuilder, Log, SignedTransaction, TransactionParameters, - TransactionReceipt, H160, H256, U256, + H160, H256, U256, }; -use web3::{BatchTransport, Error, Transport, Web3}; +use web3::{BatchTransport, Error, Web3}; +use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; +use crate::blockchain::blockchain_interface::data_structures::{BlockchainTransaction, ProcessedPayableFallible, RpcPayablesFailure}; -pub const REQUESTS_IN_PARALLEL: usize = 1; - -pub const CONTRACT_ABI: &str = indoc!( +const CONTRACT_ABI: &str = indoc!( r#"[{ "constant":true, "inputs":[{"name":"owner","type":"address"}], @@ -59,243 +66,26 @@ const TRANSACTION_LITERAL: H256 = H256([ const TRANSFER_METHOD_ID: [u8; 4] = [0xa9, 0x05, 0x9c, 0xbb]; -const BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED: &str = "To avoid being delinquency-banned, you should \ -restart the Node with a value for blockchain-service-url"; - -#[derive(Clone, Debug, Eq, Message, PartialEq)] -pub struct BlockchainTransaction { - pub block_number: u64, - pub from: Wallet, - pub wei_amount: u128, -} - -impl fmt::Display for BlockchainTransaction { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { - write!( - f, - "{}gw from {} ({})", - self.wei_amount, self.from, self.block_number - ) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, VariantCount)] -pub enum BlockchainError { - InvalidUrl, - InvalidAddress, - InvalidResponse, - QueryFailed(String), - UninitializedBlockchainInterface, -} - -impl Display for BlockchainError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let description = match self { - InvalidUrl => Left("Invalid url"), - InvalidAddress => Left("Invalid address"), - InvalidResponse => Left("Invalid response"), - QueryFailed(msg) => Right(format!("Query failed: {}", msg)), - UninitializedBlockchainInterface => Left(BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED), - }; - write!(f, "Blockchain error: {}", description) - } -} - -impl BlockchainInterfaceUninitializedError for BlockchainError { - fn error() -> Self { - Self::UninitializedBlockchainInterface - } -} - -pub type BlockchainResult = Result; -pub type ResultForBalance = BlockchainResult; -pub type ResultForBothBalances = BlockchainResult<(web3::types::U256, web3::types::U256)>; -pub type ResultForNonce = BlockchainResult; -pub type ResultForReceipt = BlockchainResult>; - -#[derive(Clone, Debug, PartialEq, Eq, VariantCount)] -pub enum PayableTransactionError { - MissingConsumingWallet, - GasPriceQueryFailed(String), - TransactionCount(BlockchainError), - UnusableWallet(String), - Signing(String), - Sending { msg: String, hashes: Vec }, - UninitializedBlockchainInterface, -} - -impl Display for PayableTransactionError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let description = match self { - Self::MissingConsumingWallet => Left("Missing consuming wallet to pay payable from"), - Self::GasPriceQueryFailed(msg) => { - Right(format!("Unsuccessful gas price query: \"{}\"", msg)) - } - Self::TransactionCount(blockchain_err) => Right(format!( - "Transaction count fetching failed for: {}", - blockchain_err - )), - Self::UnusableWallet(msg) => Right(format!( - "Unusable wallet for signing payable transactions: \"{}\"", - msg - )), - Self::Signing(msg) => Right(format!("Signing phase: \"{}\"", msg)), - Self::Sending { msg, hashes } => Right(format!( - "Sending phase: \"{}\". Signed and hashed transactions: {}", - msg, - comma_joined_stringifiable(hashes, |hash| format!("{:?}", hash)) - )), - Self::UninitializedBlockchainInterface => Left(BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED), - }; - write!(f, "{}", description) - } -} - -impl BlockchainInterfaceUninitializedError for PayableTransactionError { - fn error() -> Self { - Self::UninitializedBlockchainInterface - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RetrievedBlockchainTransactions { - pub new_start_block: u64, - pub transactions: Vec, -} - -pub trait BlockchainInterface { - fn contract_address(&self) -> Address; - - fn retrieve_transactions( - &self, - start_block: u64, - recipient: &Wallet, - ) -> Result; - - fn send_payables_within_batch( - &self, - consuming_wallet: &Wallet, - gas_price: u64, - pending_nonce: U256, - new_fingerprints_recipient: &Recipient, - accounts: &[PayableAccount], - ) -> Result, PayableTransactionError>; - - fn get_transaction_fee_balance(&self, address: &Wallet) -> ResultForBalance; - - fn get_token_balance(&self, address: &Wallet) -> ResultForBalance; - - fn get_transaction_count(&self, address: &Wallet) -> ResultForNonce; - - fn get_transaction_receipt(&self, hash: H256) -> ResultForReceipt; -} - -pub struct BlockchainInterfaceNull { - logger: Logger, -} - -impl Default for BlockchainInterfaceNull { - fn default() -> Self { - Self::new() - } -} - -impl BlockchainInterface for BlockchainInterfaceNull { - fn contract_address(&self) -> Address { - self.log_uninitialized_for_operation("get contract address"); - H160::zero() - } - - fn retrieve_transactions( - &self, - _start_block: u64, - _recipient: &Wallet, - ) -> Result { - self.handle_uninitialized_interface("retrieve transactions") - } - - fn send_payables_within_batch( - &self, - _consuming_wallet: &Wallet, - _gas_price: u64, - _last_nonce: U256, - _new_fingerprints_recipient: &Recipient, - _accounts: &[PayableAccount], - ) -> Result, PayableTransactionError> { - self.handle_uninitialized_interface("pay payables") - } - - fn get_transaction_fee_balance(&self, _address: &Wallet) -> ResultForBalance { - self.handle_uninitialized_interface("get transaction fee balance") - } - - fn get_token_balance(&self, _address: &Wallet) -> ResultForBalance { - self.handle_uninitialized_interface("get token balance") - } - - fn get_transaction_count(&self, _address: &Wallet) -> ResultForNonce { - self.handle_uninitialized_interface("get transaction count") - } - - fn get_transaction_receipt(&self, _hash: H256) -> ResultForReceipt { - self.handle_uninitialized_interface("get transaction receipt") - } -} - -trait BlockchainInterfaceUninitializedError { - fn error() -> Self; -} - -impl BlockchainInterfaceNull { - pub fn new() -> Self { - BlockchainInterfaceNull { - logger: Logger::new("BlockchainInterfaceNull"), - } - } - - fn handle_uninitialized_interface( - &self, - operation: &str, - ) -> Result - where - E: BlockchainInterfaceUninitializedError, - { - self.log_uninitialized_for_operation(operation); - let err = E::error(); - Err(err) - } - - fn log_uninitialized_for_operation(&self, operation: &str) { - error!( - self.logger, - "Failed to {} with uninitialized blockchain \ - interface. Parameter blockchain-service-url is missing.", - operation - ) - } -} +pub const REQUESTS_IN_PARALLEL: usize = 1; -pub struct BlockchainInterfaceWeb3 { +pub struct BlockchainInterfaceWeb3 +where + T: 'static + BatchTransport + Debug, +{ logger: Logger, chain: Chain, + gas_limit_const_part: u64, // This must not be dropped for Web3 requests to be completed _event_loop_handle: EventLoopHandle, - web3: Web3, - batch_web3: Web3>, + web3: Rc>, + web3_batch: Rc>>, batch_payable_tools: Box>, - contract: Contract, -} - -const GWEI: U256 = U256([1_000_000_000u64, 0, 0, 0]); - -pub fn to_wei(gwub: u64) -> U256 { - let subgwei = U256::from(gwub); - subgwei.full_mul(GWEI).try_into().expect("Internal Error") + lower_interface: Box, } impl BlockchainInterface for BlockchainInterfaceWeb3 where - T: BatchTransport + Debug + 'static, + T: 'static + BatchTransport + Debug, { fn contract_address(&self) -> Address { self.chain.rec().contract @@ -303,21 +93,23 @@ where fn retrieve_transactions( &self, - start_block: u64, + start_block: BlockNumber, + end_block: BlockNumber, recipient: &Wallet, ) -> Result { debug!( self.logger, - "Retrieving transactions from start block: {} for: {} chain_id: {} contract: {:#x}", + "Retrieving transactions from start block: {:?} to end block: {:?} for: {} chain_id: {} contract: {:#x}", start_block, + end_block, recipient, self.chain.rec().num_chain_id, self.contract_address() ); let filter = FilterBuilder::default() .address(vec![self.contract_address()]) - .from_block(BlockNumber::Number(ethereum_types::U64::from(start_block))) - .to_block(BlockNumber::Latest) + .from_block(start_block) + .to_block(end_block) .topics( Some(vec![TRANSACTION_LITERAL]), None, @@ -326,13 +118,39 @@ where ) .build(); - let log_request = self.web3.eth().logs(filter); + let fallback_start_block_number = match end_block { + BlockNumber::Number(eb) => eb.as_u64(), + _ => { + if let BlockNumber::Number(start_block_number) = start_block { + start_block_number.as_u64() + 1u64 + } else { + panic!("start_block of Latest, Earliest, and Pending are not supported"); + } + } + }; + let block_request = self.web3_batch.eth().block_number(); + let log_request = self.web3_batch.eth().logs(filter); + let logger = self.logger.clone(); - log_request - .then(|logs| { - debug!(logger, "Transaction retrieval completed: {:?}", logs); - future::result::(match logs { + match self.web3_batch.transport().submit_batch().wait() { + Ok(_) => { + let response_block_number = match block_request.wait() { + Ok(block_nbr) => { + debug!(logger, "Latest block number: {}", block_nbr.as_u64()); + block_nbr.as_u64() + } + Err(_) => { + debug!( + logger, + "Using fallback block number: {}", fallback_start_block_number + ); + fallback_start_block_number + } + }; + + match log_request.wait() { Ok(logs) => { + let logs_len = logs.len(); if logs .iter() .any(|log| log.topics.len() < 2 || log.data.0.len() > 32) @@ -344,55 +162,118 @@ where ); Err(BlockchainError::InvalidResponse) } else { - let transactions: Vec = logs - .iter() - .filter_map(|log: &Log| match log.block_number { - Some(block_number) => { - let amount: U256 = U256::from(log.data.0.as_slice()); - let wei_amount_result = u128::try_from(amount); - wei_amount_result.ok().map(|wei_amount| { - BlockchainTransaction { - block_number: u64::try_from(block_number) - .expect("Internal Error"), - from: Wallet::from(log.topics[1]), - wei_amount, - } - }) - } - None => None, - }) - .collect(); + let transactions: Vec = + self.extract_transactions_from_logs(logs); debug!(logger, "Retrieved transactions: {:?}", transactions); + if transactions.is_empty() && logs_len != transactions.len() { + warning!( + logger, + "Retrieving transactions: logs: {}, transactions: {}", + logs_len, + transactions.len() + ) + } // Get the largest transaction block number, unless there are no - // transactions, in which case use start_block. - let last_transaction_block = - transactions.iter().fold(start_block, |so_far, elem| { - if elem.block_number > so_far { - elem.block_number - } else { - so_far - } - }); + // transactions, in which case use end_block, unless get_latest_block() + // was not successful. + let transaction_max_block_number = self + .find_largest_transaction_block_number( + response_block_number, + &transactions, + ); + debug!( + logger, + "Discovered transaction max block nbr: {}", + transaction_max_block_number + ); Ok(RetrievedBlockchainTransactions { - new_start_block: last_transaction_block + 1, + new_start_block: 1u64 + transaction_max_block_number, transactions, }) } } - Err(e) => Err(BlockchainError::QueryFailed(e.to_string())), - }) - }) - .wait() + Err(e) => { + error!(self.logger, "Retrieving transactions: {:?}", e); + Err(BlockchainError::QueryFailed(e.to_string())) + } + } + } + Err(e) => Err(BlockchainError::QueryFailed(e.to_string())), + } } - fn send_payables_within_batch( + fn build_blockchain_agent( &self, consuming_wallet: &Wallet, - gas_price: u64, - pending_nonce: U256, + persistent_config: &dyn PersistentConfiguration, + ) -> Result, BlockchainAgentBuildError> { + let gas_price_gwei = match persistent_config.gas_price() { + Ok(price) => price, + Err(e) => return Err(BlockchainAgentBuildError::GasPrice(e)), + }; + + let transaction_fee_balance = match self + .lower_interface + .get_transaction_fee_balance(consuming_wallet) + { + Ok(balance) => balance, + Err(e) => { + return Err(BlockchainAgentBuildError::TransactionFeeBalance( + consuming_wallet.clone(), + e, + )) + } + }; + + let masq_token_balance = match self + .lower_interface + .get_service_fee_balance(consuming_wallet) + { + Ok(balance) => balance, + Err(e) => { + return Err(BlockchainAgentBuildError::ServiceFeeBalance( + consuming_wallet.clone(), + e, + )) + } + }; + + let pending_transaction_id = match self.lower_interface.get_transaction_id(consuming_wallet) + { + Ok(id) => id, + Err(e) => { + return Err(BlockchainAgentBuildError::TransactionID( + consuming_wallet.clone(), + e, + )) + } + }; + + let consuming_wallet_balances = ConsumingWalletBalances { + transaction_fee_balance_in_minor_units: transaction_fee_balance, + masq_token_balance_in_minor_units: masq_token_balance, + }; + let consuming_wallet = consuming_wallet.clone(); + + Ok(Box::new(BlockchainAgentWeb3::new( + gas_price_gwei, + self.gas_limit_const_part, + consuming_wallet, + consuming_wallet_balances, + pending_transaction_id, + ))) + } + + fn send_batch_of_payables( + &self, + agent: Box, new_fingerprints_recipient: &Recipient, accounts: &[PayableAccount], ) -> Result, PayableTransactionError> { + let consuming_wallet = agent.consuming_wallet(); + let gas_price = agent.agreed_fee_per_computation_unit(); + let pending_nonce = agent.pending_transaction_id(); + debug!( self.logger, "Common attributes of payables to be transacted: sender wallet: {}, contract: {:?}, chain_id: {}, gas_price: {}", @@ -422,7 +303,7 @@ where self.transmission_log(accounts, gas_price) ); - match self.batch_payable_tools.submit_batch(&self.batch_web3) { + match self.batch_payable_tools.submit_batch(&self.web3_batch) { Ok(responses) => Ok(Self::merged_output_data( responses, hashes_and_paid_amounts, @@ -432,35 +313,6 @@ where } } - fn get_transaction_fee_balance(&self, wallet: &Wallet) -> ResultForBalance { - self.web3 - .eth() - .balance(wallet.address(), None) - .map_err(|e| BlockchainError::QueryFailed(e.to_string())) - .wait() - } - - fn get_token_balance(&self, wallet: &Wallet) -> ResultForBalance { - self.contract - .query( - "balanceOf", - wallet.address(), - None, - Options::default(), - None, - ) - .map_err(|e| BlockchainError::QueryFailed(e.to_string())) - .wait() - } - - fn get_transaction_count(&self, wallet: &Wallet) -> ResultForNonce { - self.web3 - .eth() - .transaction_count(wallet.address(), Some(BlockNumber::Pending)) - .map_err(|e| BlockchainError::QueryFailed(e.to_string())) - .wait() - } - fn get_transaction_receipt(&self, hash: H256) -> ResultForReceipt { self.web3 .eth() @@ -468,43 +320,39 @@ where .map_err(|e| BlockchainError::QueryFailed(e.to_string())) .wait() } -} -#[derive(Debug, PartialEq, Clone)] -pub enum ProcessedPayableFallible { - Correct(PendingPayable), - Failed(RpcPayableFailure), -} - -#[derive(Debug, PartialEq, Clone)] -pub struct RpcPayableFailure { - pub rpc_error: Error, - pub recipient_wallet: Wallet, - pub hash: H256, + fn lower_interface(&self) -> &dyn LowBlockchainInt { + &*self.lower_interface + } } -type HashAndAmountResult = Result, PayableTransactionError>; - impl BlockchainInterfaceWeb3 where - T: BatchTransport + Debug + 'static, + T: 'static + BatchTransport + Debug, { pub fn new(transport: T, event_loop_handle: EventLoopHandle, chain: Chain) -> Self { - let web3 = Web3::new(transport.clone()); - let batch_web3 = Web3::new(Batch::new(transport)); + let web3 = Rc::new(Web3::new(transport.clone())); + let web3_batch = Rc::new(Web3::new(Batch::new(transport))); let batch_payable_tools = Box::new(BatchPayableToolsReal::::default()); let contract = Contract::from_json(web3.eth(), chain.rec().contract, CONTRACT_ABI.as_bytes()) .expect("Unable to initialize contract."); + let lower_level_blockchain_interface = Box::new(LowBlockchainIntWeb3::new( + Rc::clone(&web3), + Rc::clone(&web3_batch), + contract, + )); + let gas_limit_const_part = Self::web3_gas_limit_const_part(chain); Self { logger: Logger::new("BlockchainInterface"), chain, + gas_limit_const_part, _event_loop_handle: event_loop_handle, web3, - batch_web3, + web3_batch, + lower_interface: lower_level_blockchain_interface, batch_payable_tools, - contract, } } @@ -608,11 +456,11 @@ where .zip(accounts.iter()); iterator_with_all_data .map(|((rpc_result, (hash, _)), account)| match rpc_result { - Ok(_) => ProcessedPayableFallible::Correct(PendingPayable { + Ok(_) => Ok(PendingPayable { recipient_wallet: account.wallet.clone(), hash, }), - Err(rpc_error) => ProcessedPayableFallible::Failed(RpcPayableFailure { + Err(rpc_error) => Err(RpcPayablesFailure { rpc_error, recipient_wallet: account.wallet.clone(), hash, @@ -646,7 +494,7 @@ where let signed_tx = self.sign_transaction(recipient, consuming_wallet, amount, nonce, gas_price)?; self.batch_payable_tools - .append_transaction_to_batch(signed_tx.raw_transaction, &self.batch_web3); + .append_transaction_to_batch(signed_tx.raw_transaction, &self.web3_batch); Ok(signed_tx.transaction_hash) } @@ -658,29 +506,11 @@ where nonce: U256, gas_price: u64, ) -> Result { - let mut data = [0u8; 4 + 32 + 32]; - data[0..4].copy_from_slice(&TRANSFER_METHOD_ID); - data[16..36].copy_from_slice(&recipient.address().0[..]); - U256::try_from(amount) - .expect("shouldn't overflow") - .to_big_endian(&mut data[36..68]); - let base_gas_limit = Self::base_gas_limit(self.chain); - let gas_limit = - ethereum_types::U256::try_from(data.iter().fold(base_gas_limit, |acc, v| { - acc + if v == &0u8 { 4 } else { 68 } - })) - .expect("Internal error"); - let converted_nonce = serde_json::from_value::( - serde_json::to_value(nonce).expect("Internal error"), - ) - .expect("Internal error"); - let gas_price = serde_json::from_value::( - serde_json::to_value(to_wei(gas_price)).expect("Internal error"), - ) - .expect("Internal error"); - + let data = Self::transaction_data(recipient, amount); + let gas_limit = self.compute_gas_limit(data.as_slice()); + let gas_price = gwei_to_wei::(gas_price); let transaction_parameters = TransactionParameters { - nonce: Some(converted_nonce), + nonce: Some(nonce), to: Some(H160(self.contract_address().0)), gas: gas_limit, gas_price: Some(gas_price), @@ -695,7 +525,7 @@ where }; self.batch_payable_tools - .sign_transaction(transaction_parameters, &self.batch_web3, &key) + .sign_transaction(transaction_parameters, &self.web3_batch, &key) .map_err(|e| PayableTransactionError::Signing(e.to_string())) } @@ -729,65 +559,135 @@ where introduction.chain(body).collect() } - fn base_gas_limit(chain: Chain) -> u64 { - match chain.rec().chain_family { - ChainFamily::Polygon => 70_000, - ChainFamily::Eth => 55_000, - ChainFamily::Dev => 55_000, + fn transaction_data(recipient: &Wallet, amount: u128) -> [u8; 68] { + let mut data = [0u8; 4 + 32 + 32]; + data[0..4].copy_from_slice(&TRANSFER_METHOD_ID); + data[16..36].copy_from_slice(&recipient.address().0[..]); + U256::try_from(amount) + .expect("shouldn't overflow") + .to_big_endian(&mut data[36..68]); + data + } + + fn compute_gas_limit(&self, data: &[u8]) -> U256 { + ethereum_types::U256::try_from(data.iter().fold(self.gas_limit_const_part, |acc, v| { + acc + if v == &0u8 { 4 } else { 68 } + })) + .expect("Internal error") + } + + fn web3_gas_limit_const_part(chain: Chain) -> u64 { + match chain { + Chain::EthMainnet | Chain::EthRopsten | Chain::Dev => 55_000, + Chain::PolyMainnet | Chain::PolyMumbai => 70_000, } } - #[cfg(test)] - fn web3(&self) -> &Web3 { - &self.web3 + fn extract_transactions_from_logs(&self, logs: Vec) -> Vec { + logs.iter() + .filter_map(|log: &Log| match log.block_number { + None => None, + Some(block_number) => { + let wei_amount = U256::from(log.data.0.as_slice()).as_u128(); + Some(BlockchainTransaction { + block_number: block_number.as_u64(), + from: Wallet::from(log.topics[1]), + wei_amount, + }) + } + }) + .collect() + } + + fn find_largest_transaction_block_number( + &self, + response_block_number: u64, + transactions: &[BlockchainTransaction], + ) -> u64 { + if transactions.is_empty() { + response_block_number + } else { + transactions + .iter() + .fold(response_block_number, |a, b| a.max(b.block_number)) + } } } +type HashAndAmountResult = Result, PayableTransactionError>; + #[cfg(test)] mod tests { - use super::*; - use crate::accountant::database_access_objects::dao_utils::from_time_t; + use crate::accountant::db_access_objects::utils::from_time_t; use crate::accountant::gwei_to_wei; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::agent_web3::WEB3_MAXIMAL_GAS_LIMIT_MARGIN; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; use crate::accountant::test_utils::{ make_payable_account, make_payable_account_with_wallet_and_balance_and_timestamp_opt, }; - use crate::blockchain::bip32::Bip32ECKeyProvider; - use crate::blockchain::blockchain_interface::ProcessedPayableFallible::{Correct, Failed}; + use crate::blockchain::bip32::Bip32EncryptionKeyProvider; + use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; + + use crate::blockchain::blockchain_interface::blockchain_interface_web3::{ + BlockchainInterfaceWeb3, CONTRACT_ABI, REQUESTS_IN_PARALLEL, TRANSACTION_LITERAL, + TRANSFER_METHOD_ID, + }; + use crate::blockchain::blockchain_interface::test_utils::{ + test_blockchain_interface_is_connected_and_functioning, LowBlockchainIntMock, + }; + use crate::blockchain::blockchain_interface::{ + BlockchainAgentBuildError, BlockchainError, BlockchainInterface, PayableTransactionError, + RetrievedBlockchainTransactions, + }; use crate::blockchain::test_utils::{ - make_default_signed_transaction, make_fake_event_loop_handle, make_tx_hash, - BatchPayableToolsMock, TestTransport, + all_chains, make_fake_event_loop_handle, make_tx_hash, TestTransport, }; + use crate::db_config::persistent_configuration::PersistentConfigError; + use crate::sub_lib::blockchain_bridge::ConsumingWalletBalances; use crate::sub_lib::wallet::Wallet; - use crate::test_utils::make_paying_wallet; + use crate::test_utils::assert_string_contains; + use crate::test_utils::http_test_server::TestServer; + use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; use crate::test_utils::recorder::{make_recorder, Recorder}; use crate::test_utils::unshared_test_utils::decode_hex; - use crate::test_utils::{make_wallet, TestRawTransaction}; + use crate::test_utils::{make_paying_wallet, make_wallet, TestRawTransaction}; use actix::{Actor, System}; - use crossbeam_channel::{unbounded, Receiver}; use ethereum_types::U64; use ethsign_crypto::Keccak256; + use futures::Future; use jsonrpc_core::Version::V2; - use jsonrpc_core::{Call, Error, ErrorCode, Id, MethodCall, Params}; + use jsonrpc_core::{Call, Error as RPCError, ErrorCode, Id, MethodCall, Params}; + use masq_lib::blockchains::chains::Chain; + use masq_lib::logger::Logger; use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; - use masq_lib::utils::{find_free_port, slice_of_strs_to_vec_of_strings}; + use masq_lib::utils::find_free_port; use serde_derive::Deserialize; - use serde_json::json; - use serde_json::Value; - use simple_server::{Request, Server}; - use std::io::Write; - use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream}; - use std::ops::Add; + use serde_json::{json, Value}; + use std::net::Ipv4Addr; + + use crate::accountant::db_access_objects::payable_dao::PayableAccount; + use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; + use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::test_utils::BlockchainAgentMock; + use crate::blockchain::blockchain_interface::blockchain_interface_web3::test_utils::{ + make_default_signed_transaction, BatchPayableToolsMock, + }; + use crate::blockchain::blockchain_interface::data_structures::{ + BlockchainTransaction, RpcPayablesFailure, + }; + use indoc::indoc; use std::str::FromStr; use std::sync::{Arc, Mutex}; - use std::thread; - use std::time::{Duration, Instant, SystemTime}; - use web3::transports::Http; - use web3::types::H2048; + use std::time::SystemTime; + use web3::transports::{Batch, Http}; + use web3::types::{ + Address, BlockNumber, Bytes, TransactionParameters, TransactionReceipt, H2048, H256, U256, + }; use web3::Error as Web3Error; + use web3::Web3; #[test] - fn constants_have_correct_values() { + fn constants_are_correct() { let contract_abi_expected: &str = indoc!( r#"[{ "constant":true, @@ -814,121 +714,42 @@ mod tests { 0xf5, 0x23, 0xb3, 0xef, ], }; - assert_eq!(REQUESTS_IN_PARALLEL, 1); assert_eq!(CONTRACT_ABI, contract_abi_expected); assert_eq!(TRANSACTION_LITERAL, transaction_literal_expected); assert_eq!(TRANSFER_METHOD_ID, [0xa9, 0x05, 0x9c, 0xbb]); - assert_eq!(GWEI, U256([1_000_000_000u64, 0, 0, 0])); - assert_eq!( - BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED, - "To avoid being delinquency-banned, you \ - should restart the Node with a value for blockchain-service-url" - ) - } - - struct TestServer { - port: u16, - rx: Receiver>>, - } - - impl Drop for TestServer { - fn drop(&mut self) { - self.stop(); - } + assert_eq!(REQUESTS_IN_PARALLEL, 1); } - impl TestServer { - fn start(port: u16, bodies: Vec>) -> Self { - std::env::set_var("SIMPLESERVER_THREADS", "1"); - let (tx, rx) = unbounded(); - let _ = thread::spawn(move || { - let bodies_arc = Arc::new(Mutex::new(bodies)); - Server::new(move |req, mut rsp| { - if req.headers().get("X-Quit").is_some() { - panic!("Server stop requested"); - } - tx.send(req).unwrap(); - let body = bodies_arc.lock().unwrap().remove(0); - Ok(rsp.body(body)?) - }) - .listen(&Ipv4Addr::LOCALHOST.to_string(), &format!("{}", port)); - }); - let deadline = Instant::now().add(Duration::from_secs(5)); - loop { - thread::sleep(Duration::from_millis(10)); - match TcpStream::connect(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port)) { - Ok(_) => break, - Err(e) => eprintln!("No: {:?}", e), - } - if Instant::now().gt(&deadline) { - panic!("TestServer still not started after 5sec"); - } - } - TestServer { port, rx } - } - - fn requests_so_far(&self) -> Vec>> { - let mut requests = vec![]; - while let Ok(request) = self.rx.try_recv() { - requests.push(request); - } - return requests; - } + #[test] + fn blockchain_interface_web3_can_return_contract() { + all_chains().iter().for_each(|chain| { + let subject = BlockchainInterfaceWeb3::new( + TestTransport::default(), + make_fake_event_loop_handle(), + *chain, + ); - fn stop(&mut self) { - let mut stream = match TcpStream::connect(SocketAddr::new( - IpAddr::V4(Ipv4Addr::LOCALHOST), - self.port, - )) { - Ok(s) => s, - Err(_) => return, - }; - stream - .write(b"DELETE /irrelevant.htm HTTP/1.1\r\nX-Quit: Yes") - .unwrap(); - } + assert_eq!(subject.contract_address(), chain.rec().contract) + }) } #[test] - fn blockchain_interface_web3_handles_no_retrieved_transactions() { - let to = "0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc"; - let port = find_free_port(); - let test_server = TestServer::start( - port, - vec![br#"{"jsonrpc":"2.0","id":3,"result":[]}"#.to_vec()], - ); - - let (event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), - REQUESTS_IN_PARALLEL, - ) - .unwrap(); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); - - let result = subject - .retrieve_transactions( - 42, - &Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(), + fn blockchain_interface_web3_provides_plain_rp_calls_correctly() { + let subject_factory = |port: u16, _chain: Chain| { + let chain = Chain::PolyMainnet; + let (event_loop_handle, transport) = Http::with_max_parallel( + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), + REQUESTS_IN_PARALLEL, ) .unwrap(); + Box::new(BlockchainInterfaceWeb3::new( + transport, + event_loop_handle, + chain, + )) as Box + }; - let requests = test_server.requests_so_far(); - let bodies: Vec = requests - .into_iter() - .map(|request| serde_json::from_slice(&request.body()).unwrap()) - .collect(); - assert_eq!( - format!("\"0x000000000000000000000000{}\"", &to[2..]), - bodies[0]["params"][0]["topics"][2].to_string(), - ); - assert_eq!( - result, - RetrievedBlockchainTransactions { - new_start_block: 42 + 1, - transactions: vec![] - } - ) + test_blockchain_interface_is_connected_and_functioning(subject_factory) } #[test] @@ -937,7 +758,7 @@ mod tests { let port = find_free_port(); #[rustfmt::skip] let test_server = TestServer::start (port, vec![ - br#"{ + br#"[{"jsonrpc":"2.0","id":2,"result":"0x400"},{ "jsonrpc":"2.0", "id":3, "result":[ @@ -972,49 +793,56 @@ mod tests { "transactionIndex":"0x0" } ] - }"#.to_vec(), + }]"#.to_vec(), ]); - let (event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), REQUESTS_IN_PARALLEL, ) .unwrap(); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); + let chain = TEST_DEFAULT_CHAIN; + let subject = BlockchainInterfaceWeb3::new(transport, event_loop_handle, chain); + let end_block_nbr = 1024u64; let result = subject .retrieve_transactions( - 42, - &Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(), + BlockNumber::Number(42u64.into()), + BlockNumber::Number(end_block_nbr.into()), + &Wallet::from_str(&to).unwrap(), ) .unwrap(); let requests = test_server.requests_so_far(); - let bodies: Vec = requests + let bodies: Vec = requests .into_iter() .map(|request| serde_json::from_slice(&request.body()).unwrap()) + .map(|b: Value| serde_json::to_string(&b).unwrap()) .collect(); - assert_eq!( - format!("\"0x000000000000000000000000{}\"", &to[2..]), - bodies[0]["params"][0]["topics"][2].to_string(), - ); + let expected_body_prefix = r#"[{"id":0,"jsonrpc":"2.0","method":"eth_blockNumber","params":[]},{"id":1,"jsonrpc":"2.0","method":"eth_getLogs","params":[{"address":"0x384dec25e03f94931767ce4c3556168468ba24c3","fromBlock":"0x2a","toBlock":"0x400","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",null,"0x000000000000000000000000"#; + let expected_body_suffix = r#""]}]}]"#; + let expected_body = format!( + "{}{}{}", + expected_body_prefix, + &to[2..], + expected_body_suffix + ); + assert_eq!(bodies, vec!(expected_body)); assert_eq!( result, RetrievedBlockchainTransactions { - new_start_block: 0x4be663 + 1, + new_start_block: 0x4be664, transactions: vec![ BlockchainTransaction { block_number: 0x4be663, from: Wallet::from_str("0x3ab28ecedea6cdb6feed398e93ae8c7b316b1182") .unwrap(), - wei_amount: 4_503_599_627_370_496, + wei_amount: 4_503_599_627_370_496u128, }, BlockchainTransaction { block_number: 0x4be662, from: Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc") .unwrap(), - wei_amount: 4_503_599_627_370_496, + wei_amount: 4_503_599_627_370_496u128, }, ] } @@ -1022,10 +850,13 @@ mod tests { } #[test] - #[should_panic(expected = "No address for an uninitialized wallet!")] - fn blockchain_interface_web3_retrieve_transactions_returns_an_error_if_the_to_address_is_invalid( - ) { - let port = 8545; + fn blockchain_interface_web3_handles_no_retrieved_transactions() { + let to = "0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc"; + let port = find_free_port(); + let test_server = TestServer::start( + port, + vec![br#"[{"jsonrpc":"2.0","id":2,"result":"0x400"},{"jsonrpc":"2.0","id":3,"result":[]}]"#.to_vec()], + ); let (event_loop_handle, transport) = Http::with_max_parallel( &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), REQUESTS_IN_PARALLEL, @@ -1033,9 +864,58 @@ mod tests { .unwrap(); let subject = BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); + let end_block_nbr = 1024u64; let result = subject - .retrieve_transactions(42, &Wallet::new("0x3f69f9efd4f2592fd70beecd9dce71c472fc")); + .retrieve_transactions( + BlockNumber::Number(42u64.into()), + BlockNumber::Number(end_block_nbr.into()), + &Wallet::from_str(&to).unwrap(), + ) + .unwrap(); + + let requests = test_server.requests_so_far(); + let bodies: Vec = requests + .into_iter() + .map(|request| serde_json::from_slice(&request.body()).unwrap()) + .map(|b: Value| serde_json::to_string(&b).unwrap()) + .collect(); + let expected_body_prefix = r#"[{"id":0,"jsonrpc":"2.0","method":"eth_blockNumber","params":[]},{"id":1,"jsonrpc":"2.0","method":"eth_getLogs","params":[{"address":"0x384dec25e03f94931767ce4c3556168468ba24c3","fromBlock":"0x2a","toBlock":"0x400","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",null,"0x000000000000000000000000"#; + let expected_body_suffix = r#""]}]}]"#; + let expected_body = format!( + "{}{}{}", + expected_body_prefix, + &to[2..], + expected_body_suffix + ); + assert_eq!(bodies, vec!(expected_body)); + assert_eq!( + result, + RetrievedBlockchainTransactions { + new_start_block: 1 + end_block_nbr, + transactions: vec![] + } + ); + } + + #[test] + #[should_panic(expected = "No address for an uninitialized wallet!")] + fn blockchain_interface_web3_retrieve_transactions_returns_an_error_if_the_to_address_is_invalid( + ) { + let port = find_free_port(); + let (event_loop_handle, transport) = Http::with_max_parallel( + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), + REQUESTS_IN_PARALLEL, + ) + .unwrap(); + let chain = TEST_DEFAULT_CHAIN; + let subject = BlockchainInterfaceWeb3::new(transport, event_loop_handle, chain); + + let result = subject.retrieve_transactions( + BlockNumber::Number(42u64.into()), + BlockNumber::Latest, + &Wallet::new("0x3f69f9efd4f2592fd70beecd9dce71c472fc"), + ); assert_eq!( result.expect_err("Expected an Err, got Ok"), @@ -1048,18 +928,19 @@ mod tests { ) { let port = find_free_port(); let _test_server = TestServer::start (port, vec![ - br#"{"jsonrpc":"2.0","id":3,"result":[{"address":"0xcd6c588e005032dd882cd43bf53a32129be81302","blockHash":"0x1a24b9169cbaec3f6effa1f600b70c7ab9e8e86db44062b49132a4415d26732a","blockNumber":"0x4be663","data":"0x0000000000000000000000000000000000000000000000056bc75e2d63100000","logIndex":"0x0","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"],"transactionHash":"0x955cec6ac4f832911ab894ce16aa22c3003f46deff3f7165b32700d2f5ff0681","transactionIndex":"0x0"}]}"#.to_vec() + br#"[{"jsonrpc":"2.0","id":2,"result":"0x400"},{"jsonrpc":"2.0","id":3,"result":[{"address":"0xcd6c588e005032dd882cd43bf53a32129be81302","blockHash":"0x1a24b9169cbaec3f6effa1f600b70c7ab9e8e86db44062b49132a4415d26732a","blockNumber":"0x4be663","data":"0x0000000000000000000000000000000000000000000000056bc75e2d63100000","logIndex":"0x0","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"],"transactionHash":"0x955cec6ac4f832911ab894ce16aa22c3003f46deff3f7165b32700d2f5ff0681","transactionIndex":"0x0"}]}]"#.to_vec() ]); let (event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), REQUESTS_IN_PARALLEL, ) .unwrap(); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); + let chain = TEST_DEFAULT_CHAIN; + let subject = BlockchainInterfaceWeb3::new(transport, event_loop_handle, chain); let result = subject.retrieve_transactions( - 42, + BlockNumber::Number(42u64.into()), + BlockNumber::Latest, &Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(), ); @@ -1074,20 +955,19 @@ mod tests { ) { let port = find_free_port(); let _test_server = TestServer::start(port, vec![ - br#"{"jsonrpc":"2.0","id":3,"result":[{"address":"0xcd6c588e005032dd882cd43bf53a32129be81302","blockHash":"0x1a24b9169cbaec3f6effa1f600b70c7ab9e8e86db44062b49132a4415d26732a","blockNumber":"0x4be663","data":"0x0000000000000000000000000000000000000000000000056bc75e2d6310000001","logIndex":"0x0","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000003f69f9efd4f2592fd70be8c32ecd9dce71c472fc","0x000000000000000000000000adc1853c7859369639eb414b6342b36288fe6092"],"transactionHash":"0x955cec6ac4f832911ab894ce16aa22c3003f46deff3f7165b32700d2f5ff0681","transactionIndex":"0x0"}]}"#.to_vec() + br#"[{"jsonrpc":"2.0","id":2,"result":"0x400"},{"jsonrpc":"2.0","id":3,"result":[{"address":"0xcd6c588e005032dd882cd43bf53a32129be81302","blockHash":"0x1a24b9169cbaec3f6effa1f600b70c7ab9e8e86db44062b49132a4415d26732a","blockNumber":"0x4be663","data":"0x0000000000000000000000000000000000000000000000056bc75e2d6310000001","logIndex":"0x0","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000003f69f9efd4f2592fd70be8c32ecd9dce71c472fc","0x000000000000000000000000adc1853c7859369639eb414b6342b36288fe6092"],"transactionHash":"0x955cec6ac4f832911ab894ce16aa22c3003f46deff3f7165b32700d2f5ff0681","transactionIndex":"0x0"}]}]"#.to_vec() ]); - let (event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), REQUESTS_IN_PARALLEL, ) .unwrap(); - - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); + let chain = TEST_DEFAULT_CHAIN; + let subject = BlockchainInterfaceWeb3::new(transport, event_loop_handle, chain); let result = subject.retrieve_transactions( - 42, + BlockNumber::Number(42u64.into()), + BlockNumber::Latest, &Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(), ); @@ -1099,201 +979,238 @@ mod tests { ) { let port = find_free_port(); let _test_server = TestServer::start (port, vec![ - br#"{"jsonrpc":"2.0","id":3,"result":[{"address":"0xcd6c588e005032dd882cd43bf53a32129be81302","blockHash":"0x1a24b9169cbaec3f6effa1f600b70c7ab9e8e86db44062b49132a4415d26732a","data":"0x0000000000000000000000000000000000000000000000000010000000000000","logIndex":"0x0","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000003f69f9efd4f2592fd70be8c32ecd9dce71c472fc","0x000000000000000000000000adc1853c7859369639eb414b6342b36288fe6092"],"transactionHash":"0x955cec6ac4f832911ab894ce16aa22c3003f46deff3f7165b32700d2f5ff0681","transactionIndex":"0x0"}]}"#.to_vec() + br#"[{"jsonrpc":"2.0","id":1,"result":"0x400"},{"jsonrpc":"2.0","id":2,"result":[{"address":"0xcd6c588e005032dd882cd43bf53a32129be81302","blockHash":"0x1a24b9169cbaec3f6effa1f600b70c7ab9e8e86db44062b49132a4415d26732a","data":"0x0000000000000000000000000000000000000000000000000010000000000000","logIndex":"0x0","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000003f69f9efd4f2592fd70be8c32ecd9dce71c472fc","0x000000000000000000000000adc1853c7859369639eb414b6342b36288fe6092"],"transactionHash":"0x955cec6ac4f832911ab894ce16aa22c3003f46deff3f7165b32700d2f5ff0681","transactionIndex":"0x0"}]}]"#.to_vec() ]); - + init_test_logging(); let (event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), REQUESTS_IN_PARALLEL, ) .unwrap(); + let end_block_nbr = 1024u64; let subject = BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); let result = subject.retrieve_transactions( - 42, + BlockNumber::Number(42u64.into()), + BlockNumber::Number(end_block_nbr.into()), &Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(), ); assert_eq!( result, Ok(RetrievedBlockchainTransactions { - new_start_block: 43, + new_start_block: 1 + end_block_nbr, transactions: vec![] }) ); + let test_log_handler = TestLogHandler::new(); + test_log_handler.exists_log_containing( + "WARN: BlockchainInterface: Retrieving transactions: logs: 1, transactions: 0", + ); } #[test] - fn blockchain_interface_web3_can_retrieve_eth_balance_of_a_wallet() { + fn blockchain_interface_non_clandestine_retrieve_transactions_uses_block_number_latest_as_fallback_start_block_plus_one( + ) { let port = find_free_port(); - let _test_server = TestServer::start( - port, - vec![br#"{"jsonrpc":"2.0","id":0,"result":"0xFFFF"}"#.to_vec()], - ); - + let _test_server = TestServer::start (port, vec![ + br#"[{"jsonrpc":"2.0","id":1,"result":"error"},{"jsonrpc":"2.0","id":2,"result":[{"address":"0xcd6c588e005032dd882cd43bf53a32129be81302","blockHash":"0x1a24b9169cbaec3f6effa1f600b70c7ab9e8e86db44062b49132a4415d26732a","data":"0x0000000000000000000000000000000000000000000000000010000000000000","logIndex":"0x0","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000003f69f9efd4f2592fd70be8c32ecd9dce71c472fc","0x000000000000000000000000adc1853c7859369639eb414b6342b36288fe6092"],"transactionHash":"0x955cec6ac4f832911ab894ce16aa22c3003f46deff3f7165b32700d2f5ff0681","transactionIndex":"0x0"}]}]"#.to_vec() + ]); let (event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), REQUESTS_IN_PARALLEL, ) .unwrap(); + let chain = TEST_DEFAULT_CHAIN; + let subject = BlockchainInterfaceWeb3::new(transport, event_loop_handle, chain); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); + let start_block = BlockNumber::Number(42u64.into()); + let result = subject.retrieve_transactions( + start_block, + BlockNumber::Latest, + &Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(), + ); - let result = subject - .get_transaction_fee_balance( - &Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(), - ) - .unwrap(); + let expected_fallback_start_block = + if let BlockNumber::Number(start_block_nbr) = start_block { + start_block_nbr.as_u64() + 1u64 + } else { + panic!("start_block of Latest, Earliest, and Pending are not supported!") + }; - assert_eq!(result, U256::from(65_535)); + assert_eq!( + result, + Ok(RetrievedBlockchainTransactions { + new_start_block: 1 + expected_fallback_start_block, + transactions: vec![] + }) + ); } #[test] - #[should_panic(expected = "No address for an uninitialized wallet!")] - fn blockchain_interface_web3_returns_an_error_when_requesting_eth_balance_of_an_invalid_wallet() - { - let port = 8545; - let (event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), - REQUESTS_IN_PARALLEL, - ) - .unwrap(); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); + fn blockchain_interface_web3_can_build_blockchain_agent() { + let get_transaction_fee_balance_params_arc = Arc::new(Mutex::new(vec![])); + let get_masq_balance_params_arc = Arc::new(Mutex::new(vec![])); + let get_transactions_id_params_arc = Arc::new(Mutex::new(vec![])); + let chain = Chain::PolyMainnet; + let wallet = make_wallet("abc"); + let persistent_config = PersistentConfigurationMock::new().gas_price_result(Ok(50)); + let mut subject = BlockchainInterfaceWeb3::new( + TestTransport::default(), + make_fake_event_loop_handle(), + chain, + ); + let transaction_fee_balance = U256::from(123_456_789); + let masq_balance = U256::from(444_444_444); + let transaction_id = U256::from(23); + let lower_blockchain_interface = LowBlockchainIntMock::default() + .get_transaction_fee_balance_params(&get_transaction_fee_balance_params_arc) + .get_transaction_fee_balance_result(Ok(transaction_fee_balance)) + .get_masq_balance_params(&get_masq_balance_params_arc) + .get_masq_balance_result(Ok(masq_balance)) + .get_transaction_id_params(&get_transactions_id_params_arc) + .get_transaction_id_result(Ok(transaction_id)); + subject.lower_interface = Box::new(lower_blockchain_interface); - let result = subject.get_transaction_fee_balance(&Wallet::new( - "0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fQ", - )); + let result = subject + .build_blockchain_agent(&wallet, &persistent_config) + .unwrap(); - assert_eq!(result, Err(BlockchainError::InvalidAddress)); + let get_transaction_fee_balance_params = + get_transaction_fee_balance_params_arc.lock().unwrap(); + assert_eq!(*get_transaction_fee_balance_params, vec![wallet.clone()]); + let get_masq_balance_params = get_masq_balance_params_arc.lock().unwrap(); + assert_eq!(*get_masq_balance_params, vec![wallet.clone()]); + let get_transaction_id_params = get_transactions_id_params_arc.lock().unwrap(); + assert_eq!(*get_transaction_id_params, vec![wallet.clone()]); + assert_eq!(result.consuming_wallet(), &wallet); + assert_eq!(result.pending_transaction_id(), transaction_id); + assert_eq!( + result.consuming_wallet_balances(), + ConsumingWalletBalances { + transaction_fee_balance_in_minor_units: transaction_fee_balance, + masq_token_balance_in_minor_units: masq_balance + } + ); + assert_eq!(result.agreed_fee_per_computation_unit(), 50); + let expected_fee_estimation = (3 + * (BlockchainInterfaceWeb3::::web3_gas_limit_const_part(chain) + + WEB3_MAXIMAL_GAS_LIMIT_MARGIN) + * 50) as u128; + assert_eq!( + result.estimated_transaction_fee_total(3), + expected_fee_estimation + ) } #[test] - fn blockchain_interface_web3_returns_an_error_for_unintelligible_response_to_requesting_eth_balance( - ) { - let port = find_free_port(); - let _test_server = TestServer::start( - port, - vec![br#"{"jsonrpc":"2.0","id":0,"result":"0xFFFQ"}"#.to_vec()], + fn build_of_the_blockchain_agent_fails_on_fetching_gas_price() { + let chain = Chain::PolyMumbai; + let wallet = make_wallet("abc"); + let persistent_config = PersistentConfigurationMock::new().gas_price_result(Err( + PersistentConfigError::UninterpretableValue("booga".to_string()), + )); + let subject = BlockchainInterfaceWeb3::new( + TestTransport::default(), + make_fake_event_loop_handle(), + chain, ); - let (event_loop_handle, transport) = Http::new(&format!( - "http://{}:{}", - &Ipv4Addr::LOCALHOST.to_string(), - port - )) - .unwrap(); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); + let result = subject.build_blockchain_agent(&wallet, &persistent_config); - let result = subject.get_transaction_fee_balance( - &Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(), + let err = match result { + Err(e) => e, + _ => panic!("we expected Err() but got Ok()"), + }; + let expected_err = BlockchainAgentBuildError::GasPrice( + PersistentConfigError::UninterpretableValue("booga".to_string()), ); + assert_eq!(err, expected_err) + } - match result { - Err(BlockchainError::QueryFailed(msg)) if msg.contains("invalid hex character: Q") => { - () - } - x => panic!("Expected complaint about hex character, but got {:?}", x), + fn build_of_the_blockchain_agent_fails_on_blockchain_interface_error( + lower_blockchain_interface: LowBlockchainIntMock, + expected_err_factory: F, + ) where + F: FnOnce(&Wallet) -> BlockchainAgentBuildError, + { + let chain = Chain::EthMainnet; + let wallet = make_wallet("bcd"); + let persistent_config = PersistentConfigurationMock::new().gas_price_result(Ok(30)); + let mut subject = BlockchainInterfaceWeb3::new( + TestTransport::default(), + make_fake_event_loop_handle(), + chain, + ); + subject.lower_interface = Box::new(lower_blockchain_interface); + + let result = subject.build_blockchain_agent(&wallet, &persistent_config); + + let err = match result { + Err(e) => e, + _ => panic!("we expected Err() but got Ok()"), }; + let expected_err = expected_err_factory(&wallet); + assert_eq!(err, expected_err) } #[test] - fn blockchain_interface_web3_returns_error_for_unintelligible_response_to_gas_balance() { - let act = |subject: &BlockchainInterfaceWeb3, wallet: &Wallet| { - subject.get_transaction_fee_balance(wallet) + fn build_of_the_blockchain_agent_fails_on_transaction_fee_balance() { + let lower_interface = LowBlockchainIntMock::default() + .get_transaction_fee_balance_result(Err(BlockchainError::InvalidAddress)); + let expected_err_factory = |wallet: &Wallet| { + BlockchainAgentBuildError::TransactionFeeBalance( + wallet.clone(), + BlockchainError::InvalidAddress, + ) }; - assert_error_during_requesting_balance(act, "invalid hex character"); + build_of_the_blockchain_agent_fails_on_blockchain_interface_error( + lower_interface, + expected_err_factory, + ) } #[test] - fn blockchain_interface_web3_can_retrieve_token_balance_of_a_wallet() { - let port = find_free_port(); - let _test_server = TestServer::start (port, vec![ - br#"{"jsonrpc":"2.0","id":0,"result":"0x000000000000000000000000000000000000000000000000000000000000FFFF"}"#.to_vec() - ]); - - let (event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), - REQUESTS_IN_PARALLEL, - ) - .unwrap(); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); - - let result = subject - .get_token_balance( - &Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(), + fn build_of_the_blockchain_agent_fails_on_masq_balance() { + let transaction_fee_balance = U256::from(123_456_789); + let lower_interface = LowBlockchainIntMock::default() + .get_transaction_fee_balance_result(Ok(transaction_fee_balance)) + .get_masq_balance_result(Err(BlockchainError::InvalidResponse)); + let expected_err_factory = |wallet: &Wallet| { + BlockchainAgentBuildError::ServiceFeeBalance( + wallet.clone(), + BlockchainError::InvalidResponse, ) - .unwrap(); - - assert_eq!(result, U256::from(65_535)); - } + }; - #[test] - #[should_panic(expected = "No address for an uninitialized wallet!")] - fn blockchain_interface_web3_returns_an_error_when_requesting_token_balance_of_an_invalid_wallet( - ) { - let port = 8545; - let (event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), - REQUESTS_IN_PARALLEL, + build_of_the_blockchain_agent_fails_on_blockchain_interface_error( + lower_interface, + expected_err_factory, ) - .unwrap(); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); - - let result = - subject.get_token_balance(&Wallet::new("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fQ")); - - assert_eq!(result, Err(BlockchainError::InvalidAddress)); } #[test] - fn blockchain_interface_web3_returns_error_for_unintelligible_response_to_token_balance() { - let act = |subject: &BlockchainInterfaceWeb3, wallet: &Wallet| { - subject.get_token_balance(wallet) + fn build_of_the_blockchain_agent_fails_on_transaction_id() { + let transaction_fee_balance = U256::from(123_456_789); + let masq_balance = U256::from(500_000_000); + let lower_interface = LowBlockchainIntMock::default() + .get_transaction_fee_balance_result(Ok(transaction_fee_balance)) + .get_masq_balance_result(Ok(masq_balance)) + .get_transaction_id_result(Err(BlockchainError::InvalidResponse)); + let expected_err_factory = |wallet: &Wallet| { + BlockchainAgentBuildError::TransactionID( + wallet.clone(), + BlockchainError::InvalidResponse, + ) }; - assert_error_during_requesting_balance(act, "Invalid hex"); - } - - fn assert_error_during_requesting_balance(act: F, expected_err_msg_fragment: &str) - where - F: FnOnce(&BlockchainInterfaceWeb3, &Wallet) -> ResultForBalance, - { - let port = find_free_port(); - let _test_server = TestServer::start (port, vec![ - br#"{"jsonrpc":"2.0","id":0,"result":"0x000000000000000000000000000000000000000000000000000000000000FFFQ"}"#.to_vec() - ]); - - let (event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), - REQUESTS_IN_PARALLEL, - ) - .unwrap(); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); - - let result = act( - &subject, - &Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(), + build_of_the_blockchain_agent_fails_on_blockchain_interface_error( + lower_interface, + expected_err_factory, ); - - let err_msg = match result { - Err(BlockchainError::QueryFailed(msg)) => msg, - x => panic!("Expected BlockchainError::QueryFailed, but got {:?}", x), - }; - assert!( - err_msg.contains(expected_err_msg_fragment), - "Expected this fragment {} in this err msg: {}", - expected_err_msg_fragment, - err_msg - ) } #[test] @@ -1306,7 +1223,7 @@ mod tests { //Any eventual rpc errors brought back are processed as well... let expected_batch_responses = vec![ Ok(json!("...unnecessarily important hash...")), - Err(web3::Error::Rpc(Error { + Err(web3::Error::Rpc(RPCError { code: ErrorCode::ServerError(114), message: "server being busy".to_string(), data: None, @@ -1320,13 +1237,10 @@ mod tests { let actor_addr = accountant.start(); let fingerprint_recipient = recipient!(actor_addr, PendingPayableFingerprintSeeds); let logger = Logger::new("sending_batch_payments"); - let mut subject = BlockchainInterfaceWeb3::new( - transport.clone(), - make_fake_event_loop_handle(), - TEST_DEFAULT_CHAIN, - ); + let chain = TEST_DEFAULT_CHAIN; + let mut subject = + BlockchainInterfaceWeb3::new(transport.clone(), make_fake_event_loop_handle(), chain); subject.logger = logger; - let gas_price = 120; let amount_1 = gwei_to_wei(900_000_000_u64); let account_1 = make_payable_account_with_wallet_and_balance_and_timestamp_opt( make_wallet("w123"), @@ -1345,19 +1259,13 @@ mod tests { amount_3, None, ); - let pending_nonce = U256::from(6); let accounts_to_process = vec![account_1, account_2, account_3]; let consuming_wallet = make_paying_wallet(b"gdasgsa"); + let agent = make_initialized_agent(120, consuming_wallet, U256::from(6)); let test_timestamp_before = SystemTime::now(); let result = subject - .send_payables_within_batch( - &consuming_wallet, - gas_price, - pending_nonce, - &fingerprint_recipient, - &accounts_to_process, - ) + .send_batch_of_payables(agent, &fingerprint_recipient, &accounts_to_process) .unwrap(); let test_timestamp_after = SystemTime::now(); @@ -1405,10 +1313,10 @@ mod tests { ); let check_expected_successful_request = |expected_hash: H256, idx: usize| { let pending_payable = match &result[idx]{ - Correct(pp) => pp, - Failed(RpcPayableFailure{ rpc_error, recipient_wallet: recipient, hash }) => panic!( - "we expected correct pending payable but got one with rpc_error: {:?} and hash: {} for recipient: {}", - rpc_error, hash, recipient + Ok(pp) => pp, + Err(RpcPayablesFailure { rpc_error, recipient_wallet: recipient, hash }) => panic!( + "we expected correct pending payable but got one with rpc_error: {:?} and hash: {} for recipient: {}", + rpc_error, hash, recipient ), }; let hash = pending_payable.hash; @@ -1422,11 +1330,11 @@ mod tests { //failing request let pending_payable_fallible_2 = &result[1]; let (rpc_error, recipient_2, hash_2) = match pending_payable_fallible_2 { - Correct(pp) => panic!( + Ok(pp) => panic!( "we expected failing pending payable but got a good one: {:?}", pp ), - Failed(RpcPayableFailure { + Err(RpcPayablesFailure { rpc_error, recipient_wallet: recipient, hash, @@ -1434,7 +1342,7 @@ mod tests { }; assert_eq!( rpc_error, - &web3::Error::Rpc(Error { + &web3::Error::Rpc(RPCError { code: ErrorCode::ServerError(114), message: "server being busy".to_string(), data: None @@ -1501,7 +1409,7 @@ mod tests { } #[test] - fn web3_interface_send_payables_within_batch_components_are_used_together_properly() { + fn send_payables_within_batch_components_are_used_together_properly() { let sign_transaction_params_arc = Arc::new(Mutex::new(vec![])); let append_transaction_to_batch_params_arc = Arc::new(Mutex::new(vec![])); let new_payable_fingerprint_params_arc = Arc::new(Mutex::new(vec![])); @@ -1512,13 +1420,13 @@ mod tests { let initiate_fingerprints_recipient = accountant.start().recipient(); let consuming_wallet_secret = b"consuming_wallet_0123456789abcde"; let secret_key = - (&Bip32ECKeyProvider::from_raw_secret(consuming_wallet_secret).unwrap()).into(); + (&Bip32EncryptionKeyProvider::from_raw_secret(consuming_wallet_secret).unwrap()).into(); let batch_wide_timestamp_expected = SystemTime::now(); let transport = TestTransport::default().initiate_reference_counter(&reference_counter_arc); let chain = Chain::EthMainnet; let mut subject = BlockchainInterfaceWeb3::new(transport, make_fake_event_loop_handle(), chain); - let first_tx_parameters = TransactionParameters { + let first_transaction_params_expected = TransactionParameters { nonce: Some(U256::from(4)), to: Some(subject.contract_address()), gas: U256::from(56_552), @@ -1534,10 +1442,10 @@ mod tests { let first_signed_transaction = subject .web3 .accounts() - .sign_transaction(first_tx_parameters.clone(), &secret_key) + .sign_transaction(first_transaction_params_expected.clone(), &secret_key) .wait() .unwrap(); - let second_tx_parameters = TransactionParameters { + let second_transaction_params_expected = TransactionParameters { nonce: Some(U256::from(5)), to: Some(subject.contract_address()), gas: U256::from(56_552), @@ -1553,12 +1461,11 @@ mod tests { let second_signed_transaction = subject .web3 .accounts() - .sign_transaction(second_tx_parameters.clone(), &secret_key) + .sign_transaction(second_transaction_params_expected.clone(), &secret_key) .wait() .unwrap(); let first_hash = first_signed_transaction.transaction_hash; let second_hash = second_signed_transaction.transaction_hash; - let pending_nonce = U256::from(4); //technically, the JSON values in the correct responses don't matter, we only check for errors if any came back let rpc_responses = vec![ Ok(Value::String((&first_hash.to_string()[2..]).to_string())), @@ -1575,7 +1482,6 @@ mod tests { .submit_batch_result(Ok(rpc_responses)); subject.batch_payable_tools = Box::new(batch_payables_tools); let consuming_wallet = make_paying_wallet(consuming_wallet_secret); - let gas_price = 123; let first_payment_amount = 333_222_111_000; let first_creditor_wallet = make_wallet("creditor321"); let first_account = make_payable_account_with_wallet_and_balance_and_timestamp_opt( @@ -1590,11 +1496,10 @@ mod tests { second_payment_amount, None, ); + let agent = make_initialized_agent(123, consuming_wallet, U256::from(4)); - let result = subject.send_payables_within_batch( - &consuming_wallet, - gas_price, - pending_nonce, + let result = subject.send_batch_of_payables( + agent, &initiate_fingerprints_recipient, &vec![first_account, second_account], ); @@ -1610,13 +1515,16 @@ mod tests { assert_eq!( result, Ok(vec![ - Correct(first_resulting_pending_payable), - Correct(second_resulting_pending_payable) + Ok(first_resulting_pending_payable), + Ok(second_resulting_pending_payable) ]) ); let mut sign_transaction_params = sign_transaction_params_arc.lock().unwrap(); - let (first_transaction_params, web3, secret) = sign_transaction_params.remove(0); - assert_eq!(first_transaction_params, first_tx_parameters); + let (first_transaction_params_actual, web3, secret) = sign_transaction_params.remove(0); + assert_eq!( + first_transaction_params_actual, + first_transaction_params_expected + ); let check_web3_origin = |web3: &Web3>| { let ref_count_before_clone = Arc::strong_count(&reference_counter_arc); let _new_ref = web3.clone(); @@ -1626,16 +1534,21 @@ mod tests { check_web3_origin(&web3); assert_eq!( secret, - (&Bip32ECKeyProvider::from_raw_secret(&consuming_wallet_secret.keccak256()).unwrap()) + (&Bip32EncryptionKeyProvider::from_raw_secret(&consuming_wallet_secret.keccak256()) + .unwrap()) .into() ); - let (second_transaction_params, web3_from_st_call, secret) = + let (second_transaction_params_actual, web3_from_st_call, secret) = sign_transaction_params.remove(0); - assert_eq!(second_transaction_params, second_tx_parameters); + assert_eq!( + second_transaction_params_actual, + second_transaction_params_expected + ); check_web3_origin(&web3_from_st_call); assert_eq!( secret, - (&Bip32ECKeyProvider::from_raw_secret(&consuming_wallet_secret.keccak256()).unwrap()) + (&Bip32EncryptionKeyProvider::from_raw_secret(&consuming_wallet_secret.keccak256()) + .unwrap()) .into() ); assert!(sign_transaction_params.is_empty()); @@ -1672,9 +1585,8 @@ mod tests { assert_eq!(submit_batch_params.len(), 1); check_web3_origin(&web3_from_sb_call); assert!(accountant_recording_arc.lock().unwrap().is_empty()); - let system = System::new( - "web3_interface_send_payables_in_batch_components_are_used_together_properly", - ); + let system = + System::new("send_payables_within_batch_components_are_used_together_properly"); let probe_message = PendingPayableFingerprintSeeds { batch_wide_timestamp: SystemTime::now(), hashes_and_balances: vec![], @@ -1687,70 +1599,47 @@ mod tests { } #[test] - fn web3_interface_base_gas_limit_is_properly_set() { - assert_eq!( - BlockchainInterfaceWeb3::::base_gas_limit(Chain::PolyMainnet), - 70_000 - ); - assert_eq!( - BlockchainInterfaceWeb3::::base_gas_limit(Chain::PolyMumbai), - 70_000 - ); + fn web3_gas_limit_const_part_returns_reasonable_values() { + type Subject = BlockchainInterfaceWeb3; assert_eq!( - BlockchainInterfaceWeb3::::base_gas_limit(Chain::EthMainnet), + Subject::web3_gas_limit_const_part(Chain::EthMainnet), 55_000 ); assert_eq!( - BlockchainInterfaceWeb3::::base_gas_limit(Chain::EthRopsten), + Subject::web3_gas_limit_const_part(Chain::EthRopsten), 55_000 ); assert_eq!( - BlockchainInterfaceWeb3::::base_gas_limit(Chain::Dev), - 55_000 + Subject::web3_gas_limit_const_part(Chain::PolyMainnet), + 70_000 ); - } - - #[test] - fn web3_interface_gas_limit_for_polygon_mainnet_starts_on_70000_as_the_base() { - let transport = TestTransport::default(); - let subject = BlockchainInterfaceWeb3::new( - transport, - make_fake_event_loop_handle(), - Chain::PolyMainnet, + assert_eq!( + Subject::web3_gas_limit_const_part(Chain::PolyMumbai), + 70_000 ); - - assert_gas_limit_is_between(subject, 70000, u64::MAX) + assert_eq!(Subject::web3_gas_limit_const_part(Chain::Dev), 55_000); } #[test] - fn web3_interface_gas_limit_for_dev_lies_within_limits() { - let transport = TestTransport::default(); - let subject = - BlockchainInterfaceWeb3::new(transport, make_fake_event_loop_handle(), Chain::Dev); - - assert_gas_limit_is_between(subject, 55000, 65000) + fn gas_limit_for_polygon_mainnet_lies_within_limits_for_raw_transaction() { + test_gas_limit_is_between_limits(Chain::PolyMainnet); } #[test] - fn web3_interface_gas_limit_for_eth_mainnet_lies_within_limits() { - let transport = TestTransport::default(); - let subject = BlockchainInterfaceWeb3::new( - transport, - make_fake_event_loop_handle(), - Chain::EthMainnet, - ); - - assert_gas_limit_is_between(subject, 55000, 65000) + fn gas_limit_for_eth_mainnet_lies_within_limits_for_raw_transaction() { + test_gas_limit_is_between_limits(Chain::EthMainnet) } - fn assert_gas_limit_is_between( - mut subject: BlockchainInterfaceWeb3, - not_under_this_value: u64, - not_above_this_value: u64, - ) { + fn test_gas_limit_is_between_limits(chain: Chain) { let sign_transaction_params_arc = Arc::new(Mutex::new(vec![])); + let transport = TestTransport::default(); + let mut subject = + BlockchainInterfaceWeb3::new(transport, make_fake_event_loop_handle(), chain); + let not_under_this_value = + BlockchainInterfaceWeb3::::web3_gas_limit_const_part(chain); + let not_above_this_value = not_under_this_value + WEB3_MAXIMAL_GAS_LIMIT_MARGIN; let consuming_wallet_secret_raw_bytes = b"my-wallet"; - let batch_payable_tools = BatchPayableToolsMock::::default() + let batch_payable_tools = BatchPayableToolsMock::::default() .sign_transaction_params(&sign_transaction_params_arc) .sign_transaction_result(Ok(make_default_signed_transaction())); subject.batch_payable_tools = Box::new(batch_payable_tools); @@ -1769,44 +1658,47 @@ mod tests { let mut sign_transaction_params = sign_transaction_params_arc.lock().unwrap(); let (transaction_params, _, secret) = sign_transaction_params.remove(0); assert!(sign_transaction_params.is_empty()); - assert!(transaction_params.gas >= U256::from(not_under_this_value)); - assert!(transaction_params.gas <= U256::from(not_above_this_value)); + assert!( + transaction_params.gas >= U256::from(not_under_this_value), + "actual gas limit {} isn't above or equal {}", + transaction_params.gas, + not_under_this_value + ); + assert!( + transaction_params.gas <= U256::from(not_above_this_value), + "actual gas limit {} isn't below or equal {}", + transaction_params.gas, + not_above_this_value + ); assert_eq!( secret, - (&Bip32ECKeyProvider::from_raw_secret(&consuming_wallet_secret_raw_bytes.keccak256()) - .unwrap()) + (&Bip32EncryptionKeyProvider::from_raw_secret( + &consuming_wallet_secret_raw_bytes.keccak256() + ) + .unwrap()) .into() ); } #[test] - fn signing_error_ends_iteration_over_accounts_after_detecting_first_error_which_is_then_propagated_all_way_up_and_out( - ) { + fn signing_error_terminates_iteration_over_accounts_and_propagates_it_all_way_up_and_out() { let transport = TestTransport::default(); + let chain = Chain::PolyMumbai; let batch_payable_tools = BatchPayableToolsMock::::default() .sign_transaction_result(Err(Web3Error::Signing( secp256k1secrets::Error::InvalidSecretKey, ))) //we return after meeting the first result .sign_transaction_result(Err(Web3Error::Internal)); - let mut subject = BlockchainInterfaceWeb3::new( - transport, - make_fake_event_loop_handle(), - Chain::PolyMumbai, - ); + let mut subject = + BlockchainInterfaceWeb3::new(transport, make_fake_event_loop_handle(), chain); subject.batch_payable_tools = Box::new(batch_payable_tools); let recipient = Recorder::new().start().recipient(); let consuming_wallet = make_paying_wallet(&b"consume, you greedy fool!"[..]); - let nonce = U256::from(123); let accounts = vec![make_payable_account(5555), make_payable_account(6666)]; + let agent = make_initialized_agent(123, consuming_wallet, U256::from(4)); - let result = subject.send_payables_within_batch( - &consuming_wallet, - 111, - nonce, - &recipient, - &accounts, - ); + let result = subject.send_batch_of_payables(agent, &recipient, &accounts); assert_eq!( result, @@ -1819,16 +1711,12 @@ mod tests { } #[test] - fn send_payables_within_batch_fails_on_badly_prepared_consuming_wallet_without_secret() { + fn send_batch_of_payables_fails_on_badly_prepared_consuming_wallet_without_secret() { let transport = TestTransport::default(); let incomplete_consuming_wallet = Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(); - let subject = BlockchainInterfaceWeb3::new( - transport, - make_fake_event_loop_handle(), - TEST_DEFAULT_CHAIN, - ); - + let chain = TEST_DEFAULT_CHAIN; + let subject = BlockchainInterfaceWeb3::new(transport, make_fake_event_loop_handle(), chain); let system = System::new("test"); let (accountant, _, accountant_recording_arc) = make_recorder(); let recipient = accountant.start().recipient(); @@ -1837,16 +1725,9 @@ mod tests { 9000, None, ); - let gas_price = 123; - let nonce = U256::from(1); + let agent = make_initialized_agent(123, incomplete_consuming_wallet, U256::from(1)); - let result = subject.send_payables_within_batch( - &incomplete_consuming_wallet, - gas_price, - nonce, - &recipient, - &vec![account], - ); + let result = subject.send_batch_of_payables(agent, &recipient, &vec![account]); System::current().stop(); system.run(); @@ -1858,7 +1739,7 @@ mod tests { } #[test] - fn send_payables_within_batch_fails_on_sending() { + fn send_batch_of_payables_fails_on_sending() { let transport = TestTransport::default(); let hash = make_tx_hash(123); let mut signed_transaction = make_default_signed_transaction(); @@ -1868,11 +1749,9 @@ mod tests { .batch_wide_timestamp_result(SystemTime::now()) .submit_batch_result(Err(Web3Error::Transport("Transaction crashed".to_string()))); let consuming_wallet_secret_raw_bytes = b"okay-wallet"; - let mut subject = BlockchainInterfaceWeb3::new( - transport, - make_fake_event_loop_handle(), - Chain::PolyMumbai, - ); + let chain = Chain::PolyMumbai; + let mut subject = + BlockchainInterfaceWeb3::new(transport, make_fake_event_loop_handle(), chain); subject.batch_payable_tools = Box::new(batch_payable_tools); let unimportant_recipient = Recorder::new().start().recipient(); let account = make_payable_account_with_wallet_and_balance_and_timestamp_opt( @@ -1881,16 +1760,9 @@ mod tests { None, ); let consuming_wallet = make_paying_wallet(consuming_wallet_secret_raw_bytes); - let gas_price = 123; - let nonce = U256::from(1); + let agent = make_initialized_agent(120, consuming_wallet, U256::from(6)); - let result = subject.send_payables_within_batch( - &consuming_wallet, - gas_price, - nonce, - &unimportant_recipient, - &vec![account], - ); + let result = subject.send_batch_of_payables(agent, &unimportant_recipient, &vec![account]); assert_eq!( result, @@ -1909,11 +1781,9 @@ mod tests { secp256k1secrets::Error::InvalidSecretKey, ))); let consuming_wallet_secret_raw_bytes = b"okay-wallet"; - let mut subject = BlockchainInterfaceWeb3::new( - transport, - make_fake_event_loop_handle(), - Chain::PolyMumbai, - ); + let chain = Chain::PolyMumbai; + let mut subject = + BlockchainInterfaceWeb3::new(transport, make_fake_event_loop_handle(), chain); subject.batch_payable_tools = Box::new(batch_payable_tools); let recipient = make_wallet("unlucky man"); let consuming_wallet = make_paying_wallet(consuming_wallet_secret_raw_bytes); @@ -1931,8 +1801,12 @@ mod tests { ); } + const TEST_PAYMENT_AMOUNT: u128 = 1_000_000_000_000; + const TEST_GAS_PRICE_ETH: u64 = 110; + const TEST_GAS_PRICE_POLYGON: u64 = 50; + fn test_consuming_wallet_with_secret() -> Wallet { - let key_pair = Bip32ECKeyProvider::from_raw_secret( + let key_pair = Bip32EncryptionKeyProvider::from_raw_secret( &decode_hex("97923d8fd8de4a00f912bfb77ef483141dec551bd73ea59343ef5c4aac965d04") .unwrap(), ) @@ -1947,10 +1821,6 @@ mod tests { Wallet::from(address) } - const TEST_PAYMENT_AMOUNT: u128 = 1_000_000_000_000; - const TEST_GAS_PRICE_ETH: u64 = 110; - const TEST_GAS_PRICE_POLYGON: u64 = 50; - fn assert_that_signed_transactions_agrees_with_template( chain: Chain, nonce: u64, @@ -1961,10 +1831,9 @@ mod tests { let consuming_wallet = test_consuming_wallet_with_secret(); let recipient_wallet = test_recipient_wallet(); let nonce_correct_type = U256::from(nonce); - let gas_price = match chain.rec().chain_family { - ChainFamily::Eth => TEST_GAS_PRICE_ETH, - ChainFamily::Polygon => TEST_GAS_PRICE_POLYGON, - _ => panic!("isn't our interest in this test"), + let gas_price = match chain { + Chain::EthMainnet | Chain::EthRopsten | Chain::Dev => TEST_GAS_PRICE_ETH, + Chain::PolyMainnet | Chain::PolyMumbai => TEST_GAS_PRICE_POLYGON, }; let payable_account = make_payable_account_with_wallet_and_balance_and_timestamp_opt( recipient_wallet, @@ -2055,7 +1924,7 @@ mod tests { //an adapted test from old times when we had our own signing method //I don't have data for the new chains so I omit them in this kind of tests #[test] - fn signs_various_transaction_for_eth_mainnet() { + fn signs_various_transactions_for_eth_mainnet() { let signatures = &[ &[ 248, 108, 9, 133, 4, 168, 23, 200, 0, 130, 82, 8, 148, 53, 53, 53, 53, 53, 53, 53, @@ -2127,7 +1996,7 @@ mod tests { private_key: H256, } - fn assert_signature(chain: Chain, slice_of_sclices: &[&[u8]]) { + fn assert_signature(chain: Chain, slice_of_slices: &[&[u8]]) { let first_part_tx_1 = r#"[{"nonce": "0x9", "gasPrice": "0x4a817c800", "gasLimit": "0x5208", "to": "0x3535353535353535353535353535353535353535", "value": "0xde0b6b3a7640000", "data": []}, {"private_key": "0x4646464646464646464646464646464646464646464646464646464646464646", "signed": "#; let first_part_tx_2 = r#"[{"nonce": "0x0", "gasPrice": "0xd55698372431", "gasLimit": "0x1e8480", "to": "0xF0109fC8DF283027b6285cc889F5aA624EaC1F55", "value": "0x3b9aca00", "data": []}, {"private_key": "0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318", "signed": "#; let first_part_tx_3 = r#"[{"nonce": "0x00", "gasPrice": "0x09184e72a000", "gasLimit": "0x2710", "to": null, "value": "0x00", "data": [127,116,101,115,116,50,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,87]}, {"private_key": "0xe331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109", "signed": "#; @@ -2139,7 +2008,7 @@ mod tests { "[{}]", vec![first_part_tx_1, first_part_tx_2, first_part_tx_3] .iter() - .zip(slice_of_sclices.iter()) + .zip(slice_of_slices.iter()) .zip(0usize..2) .fold(String::new(), |so_far, actual| [ so_far, @@ -2176,13 +2045,14 @@ mod tests { .zip(constant_parts) { let secret = Wallet::from( - Bip32ECKeyProvider::from_raw_secret(&signed.private_key.0.as_ref()).unwrap(), + Bip32EncryptionKeyProvider::from_raw_secret(&signed.private_key.0.as_ref()) + .unwrap(), ) .prepare_secp256k1_secret() .unwrap(); let tx_params = from_raw_transaction_to_transaction_parameters(tx, chain); let sign = subject - .web3() + .web3 .accounts() .sign_transaction(tx_params, &secret) .wait() @@ -2208,50 +2078,6 @@ mod tests { } } - #[test] - fn blockchain_interface_web3_can_fetch_nonce() { - let prepare_params_arc = Arc::new(Mutex::new(vec![])); - let send_params_arc = Arc::new(Mutex::new(vec![])); - let transport = TestTransport::default() - .prepare_params(&prepare_params_arc) - .send_params(&send_params_arc) - .send_result(json!( - "0x0000000000000000000000000000000000000000000000000000000000000001" - )); - let subject = BlockchainInterfaceWeb3::new( - transport.clone(), - make_fake_event_loop_handle(), - TEST_DEFAULT_CHAIN, - ); - - let result = subject.get_transaction_count(&make_paying_wallet(b"gdasgsa")); - - assert_eq!(result, Ok(U256::from(1))); - let mut prepare_params = prepare_params_arc.lock().unwrap(); - let (method_name, actual_arguments) = prepare_params.remove(0); - assert!(prepare_params.is_empty()); - let actual_arguments: Vec = actual_arguments - .into_iter() - .map(|arg| serde_json::to_string(&arg).unwrap()) - .collect(); - assert_eq!(method_name, "eth_getTransactionCount".to_string()); - assert_eq!( - actual_arguments, - vec![ - String::from(r#""0x5c361ba8d82fcf0e5538b2a823e9d457a2296725""#), - String::from(r#""pending""#), - ] - ); - let send_params = send_params_arc.lock().unwrap(); - let rpc_call_params = vec![ - Value::String(String::from("0x5c361ba8d82fcf0e5538b2a823e9d457a2296725")), - Value::String(String::from("pending")), - ]; - let expected_request = - web3::helpers::build_request(1, "eth_getTransactionCount", rpc_call_params); - assert_eq!(*send_params, vec![(1, expected_request)]) - } - #[test] fn blockchain_interface_web3_can_fetch_transaction_receipt() { let port = find_free_port(); @@ -2260,12 +2086,12 @@ mod tests { .to_vec() ]); let (event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), REQUESTS_IN_PARALLEL, ) .unwrap(); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); + let chain = TEST_DEFAULT_CHAIN; + let subject = BlockchainInterfaceWeb3::new(transport, event_loop_handle, chain); let tx_hash = H256::from_str("a128f9ca1e705cc20a936a24a7fa1df73bad6e0aaf58e8e6ffcc154a7cff6e0e") .unwrap(); @@ -2292,146 +2118,28 @@ mod tests { fn get_transaction_receipt_handles_errors() { let port = find_free_port(); let (event_loop_handle, transport) = Http::with_max_parallel( - &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port), + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), REQUESTS_IN_PARALLEL, ) .unwrap(); - let subject = - BlockchainInterfaceWeb3::new(transport, event_loop_handle, TEST_DEFAULT_CHAIN); + let chain = TEST_DEFAULT_CHAIN; + let subject = BlockchainInterfaceWeb3::new(transport, event_loop_handle, chain); let tx_hash = make_tx_hash(4564546); - let result = subject.get_transaction_receipt(tx_hash); - - match result { - Err(BlockchainError::QueryFailed(err_message)) => assert!( - err_message.contains("Transport error: Error(Connect, Os"), - "we got this error msg: {}", - err_message - ), - Err(e) => panic!("we expected a different error than: {}", e), - Ok(x) => panic!("we expected an error, but got: {:?}", x), + let actual_error = subject.get_transaction_receipt(tx_hash).unwrap_err(); + let error_message = if let BlockchainError::QueryFailed(em) = actual_error { + em + } else { + panic!("Expected BlockchainError::QueryFailed(msg)"); }; - } - - #[test] - fn to_wei_converts_units_properly_for_max_value() { - let converted_wei = to_wei(u64::MAX); - - assert_eq!( - converted_wei, - U256::from_dec_str(format!("{}000000000", u64::MAX).as_str()).unwrap() - ); - } - - #[test] - fn to_wei_converts_units_properly_for_one() { - let converted_wei = to_wei(1); - - assert_eq!(converted_wei, U256::from_dec_str("1000000000").unwrap()); - } - - #[test] - fn constant_gwei_matches_calculated_value() { - let value = U256::from(1_000_000_000); - assert_eq!(value.0[0], 1_000_000_000); - assert_eq!(value.0[1], 0); - assert_eq!(value.0[2], 0); - assert_eq!(value.0[3], 0); - - let gwei = U256([1_000_000_000u64, 0, 0, 0]); - assert_eq!(value, gwei); - assert_eq!(gwei, GWEI); - assert_eq!(value, GWEI); - } - - #[test] - fn hash_the_smartcontract_transfer_function_signature() { - assert_eq!( - TRANSFER_METHOD_ID, - "transfer(address,uint256)".keccak256()[0..4] - ); - } - - #[test] - fn blockchain_error_implements_display() { - let original_errors = [ - BlockchainError::InvalidUrl, - BlockchainError::InvalidAddress, - BlockchainError::InvalidResponse, - BlockchainError::QueryFailed( - "Don't query so often, it gives me a headache".to_string(), - ), - BlockchainError::UninitializedBlockchainInterface, - ]; - - let actual_error_msgs = original_errors - .iter() - .map(|err| err.to_string()) - .collect::>(); - - assert_eq!( - original_errors.len(), - BlockchainError::VARIANT_COUNT, - "you forgot to add all variants in this test" + assert_string_contains( + error_message.as_str(), + "Transport error: Error(Connect, Os { code: ", ); - assert_eq!( - actual_error_msgs, - slice_of_strs_to_vec_of_strings(&[ - "Blockchain error: Invalid url", - "Blockchain error: Invalid address", - "Blockchain error: Invalid response", - "Blockchain error: Query failed: Don't query so often, it gives me a headache", - &format!("Blockchain error: {}", BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED) - ]) - ); - } - - #[test] - fn payable_payment_error_implements_display() { - let original_errors = [ - PayableTransactionError::MissingConsumingWallet, - PayableTransactionError::GasPriceQueryFailed( - "Gas halves shut, no drop left".to_string(), - ), - PayableTransactionError::TransactionCount(BlockchainError::InvalidResponse), - PayableTransactionError::UnusableWallet( - "This is a LEATHER wallet, not LEDGER wallet, stupid.".to_string(), - ), - PayableTransactionError::Signing( - "You cannot sign with just three crosses here, clever boy".to_string(), - ), - PayableTransactionError::Sending { - msg: "Sending to cosmos belongs elsewhere".to_string(), - hashes: vec![make_tx_hash(0x6f), make_tx_hash(0xde)], - }, - PayableTransactionError::UninitializedBlockchainInterface, - ]; - - let actual_error_msgs = original_errors - .iter() - .map(|err| err.to_string()) - .collect::>(); - - assert_eq!( - original_errors.len(), - PayableTransactionError::VARIANT_COUNT, - "you forgot to add all variants in this test" + assert_string_contains( + error_message.as_str(), + ", kind: ConnectionRefused, message: ", ); - assert_eq!( - actual_error_msgs, - slice_of_strs_to_vec_of_strings(&[ - "Missing consuming wallet to pay payable from", - "Unsuccessful gas price query: \"Gas halves shut, no drop left\"", - "Transaction count fetching failed for: Blockchain error: Invalid response", - "Unusable wallet for signing payable transactions: \"This is a LEATHER wallet, not \ - LEDGER wallet, stupid.\"", - "Signing phase: \"You cannot sign with just three crosses here, clever boy\"", - "Sending phase: \"Sending to cosmos belongs elsewhere\". Signed and hashed \ - transactions: 0x000000000000000000000000000000000000000000000000000000000000006f, \ - 0x00000000000000000000000000000000000000000000000000000000000000de", - BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED - ]) - ) } #[test] @@ -2465,7 +2173,7 @@ mod tests { ]; let responses = vec![ Ok(Value::String(String::from("blah"))), - Err(web3::Error::Rpc(Error { + Err(web3::Error::Rpc(RPCError { code: ErrorCode::ParseError, message: "I guess we've got a problem".to_string(), data: None, @@ -2481,12 +2189,12 @@ mod tests { assert_eq!( result, vec![ - Correct(PendingPayable { + Ok(PendingPayable { recipient_wallet: make_wallet("4567"), hash: make_tx_hash(444) }), - Failed(RpcPayableFailure { - rpc_error: web3::Error::Rpc(Error { + Err(RpcPayablesFailure { + rpc_error: web3::Error::Rpc(RPCError { code: ErrorCode::ParseError, message: "I guess we've got a problem".to_string(), data: None, @@ -2498,19 +2206,24 @@ mod tests { ) } - #[test] - fn blockchain_interface_null_error_is_implemented_for_blockchain_error() { - assert_eq!( - BlockchainError::error(), - BlockchainError::UninitializedBlockchainInterface + fn make_initialized_agent( + gas_price_gwei: u64, + consuming_wallet: Wallet, + nonce: U256, + ) -> Box { + Box::new( + BlockchainAgentMock::default() + .consuming_wallet_result(consuming_wallet) + .agreed_fee_per_computation_unit_result(gas_price_gwei) + .pending_transaction_id_result(nonce), ) } #[test] - fn blockchain_interface_null_error_is_implemented_for_payable_transaction_error() { + fn hash_the_smart_contract_transfer_function_signature() { assert_eq!( - PayableTransactionError::error(), - PayableTransactionError::UninitializedBlockchainInterface - ) + "transfer(address,uint256)".keccak256()[0..4], + TRANSFER_METHOD_ID, + ); } } diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/test_utils.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/test_utils.rs new file mode 100644 index 000000000..55f9fbce7 --- /dev/null +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/test_utils.rs @@ -0,0 +1,172 @@ +// Copyright (c) 2023, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +#![cfg(test)] + +use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; +use crate::blockchain::blockchain_interface::blockchain_interface_web3::batch_payable_tools::BatchPayableTools; +use actix::Recipient; +use jsonrpc_core as rpc; +use std::cell::RefCell; +use std::sync::{Arc, Mutex}; +use std::time::SystemTime; +use web3::transports::Batch; +use web3::types::{Bytes, SignedTransaction, TransactionParameters, H256}; +use web3::{BatchTransport, Error as Web3Error, Web3}; + +#[derive(Default)] +pub struct BatchPayableToolsMock { + sign_transaction_params: Arc< + Mutex< + Vec<( + TransactionParameters, + Web3>, + secp256k1secrets::key::SecretKey, + )>, + >, + >, + sign_transaction_results: RefCell>>, + append_transaction_to_batch_params: Arc>)>>>, + //append_transaction_to_batch returns just the unit type + //batch_wide_timestamp doesn't have params + batch_wide_timestamp_results: RefCell>, + send_new_payable_fingerprints_seeds_params: Arc< + Mutex< + Vec<( + SystemTime, + Recipient, + Vec<(H256, u128)>, + )>, + >, + >, + //new_payable_fingerprints returns just the unit type + submit_batch_params: Arc>>>>, + submit_batch_results: + RefCell>, Web3Error>>>, +} + +impl BatchPayableTools for BatchPayableToolsMock { + fn sign_transaction( + &self, + transaction_params: TransactionParameters, + web3: &Web3>, + key: &secp256k1secrets::key::SecretKey, + ) -> Result { + self.sign_transaction_params.lock().unwrap().push(( + transaction_params.clone(), + web3.clone(), + key.clone(), + )); + self.sign_transaction_results.borrow_mut().remove(0) + } + + fn append_transaction_to_batch(&self, signed_transaction: Bytes, web3: &Web3>) { + self.append_transaction_to_batch_params + .lock() + .unwrap() + .push((signed_transaction, web3.clone())); + } + + fn batch_wide_timestamp(&self) -> SystemTime { + self.batch_wide_timestamp_results.borrow_mut().remove(0) + } + + fn send_new_payable_fingerprints_seeds( + &self, + batch_wide_timestamp: SystemTime, + pp_fingerprint_sub: &Recipient, + hashes_and_balances: &[(H256, u128)], + ) { + self.send_new_payable_fingerprints_seeds_params + .lock() + .unwrap() + .push(( + batch_wide_timestamp, + (*pp_fingerprint_sub).clone(), + hashes_and_balances.to_vec(), + )); + } + + fn submit_batch( + &self, + web3: &Web3>, + ) -> Result>, Web3Error> { + self.submit_batch_params.lock().unwrap().push(web3.clone()); + self.submit_batch_results.borrow_mut().remove(0) + } +} + +impl BatchPayableToolsMock { + pub fn sign_transaction_params( + mut self, + params: &Arc< + Mutex< + Vec<( + TransactionParameters, + Web3>, + secp256k1secrets::key::SecretKey, + )>, + >, + >, + ) -> Self { + self.sign_transaction_params = params.clone(); + self + } + + pub fn sign_transaction_result(self, result: Result) -> Self { + self.sign_transaction_results.borrow_mut().push(result); + self + } + + pub fn batch_wide_timestamp_result(self, result: SystemTime) -> Self { + self.batch_wide_timestamp_results.borrow_mut().push(result); + self + } + + pub fn send_new_payable_fingerprint_credentials_params( + mut self, + params: &Arc< + Mutex< + Vec<( + SystemTime, + Recipient, + Vec<(H256, u128)>, + )>, + >, + >, + ) -> Self { + self.send_new_payable_fingerprints_seeds_params = params.clone(); + self + } + + pub fn append_transaction_to_batch_params( + mut self, + params: &Arc>)>>>, + ) -> Self { + self.append_transaction_to_batch_params = params.clone(); + self + } + + pub fn submit_batch_params(mut self, params: &Arc>>>>) -> Self { + self.submit_batch_params = params.clone(); + self + } + + pub fn submit_batch_result( + self, + result: Result>, Web3Error>, + ) -> Self { + self.submit_batch_results.borrow_mut().push(result); + self + } +} + +pub fn make_default_signed_transaction() -> SignedTransaction { + SignedTransaction { + message_hash: Default::default(), + v: 0, + r: Default::default(), + s: Default::default(), + raw_transaction: Default::default(), + transaction_hash: Default::default(), + } +} diff --git a/node/src/blockchain/blockchain_interface/data_structures/errors.rs b/node/src/blockchain/blockchain_interface/data_structures/errors.rs new file mode 100644 index 000000000..e4649c744 --- /dev/null +++ b/node/src/blockchain/blockchain_interface/data_structures/errors.rs @@ -0,0 +1,262 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::accountant::comma_joined_stringifiable; +use crate::db_config::persistent_configuration::PersistentConfigError; +use crate::sub_lib::wallet::Wallet; +use itertools::Either; +use std::fmt; +use std::fmt::{Display, Formatter}; +use variant_count::VariantCount; +use web3::types::{TransactionReceipt, H256}; + +const BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED: &str = "Uninitialized blockchain interface. To avoid \ +being delinquency-banned, you should restart the Node with a value for blockchain-service-url"; + +#[derive(Clone, Debug, PartialEq, Eq, VariantCount)] +pub enum BlockchainError { + InvalidUrl, + InvalidAddress, + InvalidResponse, + QueryFailed(String), + UninitializedBlockchainInterface, +} + +impl Display for BlockchainError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let err_spec = match self { + Self::InvalidUrl => Either::Left("Invalid url"), + Self::InvalidAddress => Either::Left("Invalid address"), + Self::InvalidResponse => Either::Left("Invalid response"), + Self::QueryFailed(msg) => Either::Right(format!("Query failed: {}", msg)), + Self::UninitializedBlockchainInterface => { + Either::Left(BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED) + } + }; + write!(f, "Blockchain error: {}", err_spec) + } +} + +pub type BlockchainResult = Result; +pub type ResultForReceipt = BlockchainResult>; + +#[derive(Clone, Debug, PartialEq, Eq, VariantCount)] +pub enum PayableTransactionError { + MissingConsumingWallet, + GasPriceQueryFailed(String), + TransactionID(BlockchainError), + UnusableWallet(String), + Signing(String), + Sending { msg: String, hashes: Vec }, + UninitializedBlockchainInterface, +} + +impl Display for PayableTransactionError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::MissingConsumingWallet => { + write!(f, "Missing consuming wallet to pay payable from") + } + Self::GasPriceQueryFailed(msg) => { + write!(f, "Unsuccessful gas price query: \"{}\"", msg) + } + Self::TransactionID(blockchain_err) => { + write!(f, "Transaction id fetching failed: {}", blockchain_err) + } + Self::UnusableWallet(msg) => write!( + f, + "Unusable wallet for signing payable transactions: \"{}\"", + msg + ), + Self::Signing(msg) => write!(f, "Signing phase: \"{}\"", msg), + Self::Sending { msg, hashes } => write!( + f, + "Sending phase: \"{}\". Signed and hashed transactions: {}", + msg, + comma_joined_stringifiable(hashes, |hash| format!("{:?}", hash)) + ), + Self::UninitializedBlockchainInterface => { + write!(f, "{}", BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED) + } + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, VariantCount)] +pub enum BlockchainAgentBuildError { + GasPrice(PersistentConfigError), + TransactionFeeBalance(Wallet, BlockchainError), + ServiceFeeBalance(Wallet, BlockchainError), + TransactionID(Wallet, BlockchainError), + UninitializedBlockchainInterface, +} + +impl Display for BlockchainAgentBuildError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let preformatted_or_complete = match self { + Self::GasPrice(persistent_config_e) => Either::Left(format!( + "gas price from the database: {:?}", + persistent_config_e + )), + Self::TransactionFeeBalance(wallet, blockchain_e) => Either::Left(format!( + "transaction fee balance for our earning wallet {} due to: {}", + wallet, blockchain_e + )), + Self::ServiceFeeBalance(wallet, blockchain_e) => Either::Left(format!( + "masq balance for our earning wallet {} due to {}", + wallet, blockchain_e + )), + Self::TransactionID(wallet, blockchain_e) => Either::Left(format!( + "transaction id for our earning wallet {} due to {}", + wallet, blockchain_e + )), + Self::UninitializedBlockchainInterface => { + Either::Right(BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED.to_string()) + } + }; + + match preformatted_or_complete { + Either::Left(ending) => write!( + f, + "Blockchain agent construction failed at fetching {}", + ending + ), + Either::Right(msg) => write!(f, "{}", msg), + } + } +} + +#[cfg(test)] +mod tests { + use crate::blockchain::blockchain_interface::data_structures::errors::BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED; + use crate::blockchain::blockchain_interface::{ + BlockchainAgentBuildError, BlockchainError, PayableTransactionError, + }; + use crate::blockchain::test_utils::make_tx_hash; + use crate::db_config::persistent_configuration::PersistentConfigError; + use crate::test_utils::make_wallet; + use masq_lib::utils::{slice_of_strs_to_vec_of_strings, to_string}; + + #[test] + fn constants_have_correct_values() { + assert_eq!( + BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED, + "Uninitialized blockchain interface. To avoid being delinquency-banned, you should \ + restart the Node with a value for blockchain-service-url" + ) + } + + #[test] + fn blockchain_error_implements_display() { + let original_errors = [ + BlockchainError::InvalidUrl, + BlockchainError::InvalidAddress, + BlockchainError::InvalidResponse, + BlockchainError::QueryFailed( + "Don't query so often, it gives me a headache".to_string(), + ), + BlockchainError::UninitializedBlockchainInterface, + ]; + + let actual_error_msgs = original_errors.iter().map(to_string).collect::>(); + + assert_eq!( + original_errors.len(), + BlockchainError::VARIANT_COUNT, + "you forgot to add all variants in this test" + ); + assert_eq!( + actual_error_msgs, + slice_of_strs_to_vec_of_strings(&[ + "Blockchain error: Invalid url", + "Blockchain error: Invalid address", + "Blockchain error: Invalid response", + "Blockchain error: Query failed: Don't query so often, it gives me a headache", + &format!("Blockchain error: {}", BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED) + ]) + ); + } + + #[test] + fn payable_payment_error_implements_display() { + let original_errors = [ + PayableTransactionError::MissingConsumingWallet, + PayableTransactionError::GasPriceQueryFailed( + "Gas halves shut, no drop left".to_string(), + ), + PayableTransactionError::TransactionID(BlockchainError::InvalidResponse), + PayableTransactionError::UnusableWallet( + "This is a LEATHER wallet, not LEDGER wallet, stupid.".to_string(), + ), + PayableTransactionError::Signing( + "You cannot sign with just three crosses here, clever boy".to_string(), + ), + PayableTransactionError::Sending { + msg: "Sending to cosmos belongs elsewhere".to_string(), + hashes: vec![make_tx_hash(0x6f), make_tx_hash(0xde)], + }, + PayableTransactionError::UninitializedBlockchainInterface, + ]; + + let actual_error_msgs = original_errors.iter().map(to_string).collect::>(); + + assert_eq!( + original_errors.len(), + PayableTransactionError::VARIANT_COUNT, + "you forgot to add all variants in this test" + ); + assert_eq!( + actual_error_msgs, + slice_of_strs_to_vec_of_strings(&[ + "Missing consuming wallet to pay payable from", + "Unsuccessful gas price query: \"Gas halves shut, no drop left\"", + "Transaction id fetching failed: Blockchain error: Invalid response", + "Unusable wallet for signing payable transactions: \"This is a LEATHER wallet, not \ + LEDGER wallet, stupid.\"", + "Signing phase: \"You cannot sign with just three crosses here, clever boy\"", + "Sending phase: \"Sending to cosmos belongs elsewhere\". Signed and hashed \ + transactions: 0x000000000000000000000000000000000000000000000000000000000000006f, \ + 0x00000000000000000000000000000000000000000000000000000000000000de", + BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED + ]) + ) + } + + #[test] + fn blockchain_agent_build_error_implements_display() { + let wallet = make_wallet("abc"); + let original_errors = [ + BlockchainAgentBuildError::GasPrice(PersistentConfigError::NotPresent), + BlockchainAgentBuildError::TransactionFeeBalance( + wallet.clone(), + BlockchainError::InvalidResponse, + ), + BlockchainAgentBuildError::ServiceFeeBalance( + wallet.clone(), + BlockchainError::InvalidAddress, + ), + BlockchainAgentBuildError::TransactionID(wallet.clone(), BlockchainError::InvalidUrl), + BlockchainAgentBuildError::UninitializedBlockchainInterface, + ]; + + let actual_error_msgs = original_errors.iter().map(to_string).collect::>(); + + assert_eq!( + original_errors.len(), + BlockchainAgentBuildError::VARIANT_COUNT, + "you forgot to add all variants in this test" + ); + assert_eq!( + actual_error_msgs, + slice_of_strs_to_vec_of_strings(&[ + "Blockchain agent construction failed at fetching gas price from the database: NotPresent", + "Blockchain agent construction failed at fetching transaction fee balance for our earning \ + wallet 0x0000000000000000000000000000000000616263 due to: Blockchain error: Invalid response", + "Blockchain agent construction failed at fetching masq balance for our earning wallet \ + 0x0000000000000000000000000000000000616263 due to Blockchain error: Invalid address", + "Blockchain agent construction failed at fetching transaction id for our earning wallet \ + 0x0000000000000000000000000000000000616263 due to Blockchain error: Invalid url", + BLOCKCHAIN_SERVICE_URL_NOT_SPECIFIED + ]) + ) + } +} diff --git a/node/src/blockchain/blockchain_interface/data_structures/mod.rs b/node/src/blockchain/blockchain_interface/data_structures/mod.rs new file mode 100644 index 000000000..d1d785aae --- /dev/null +++ b/node/src/blockchain/blockchain_interface/data_structures/mod.rs @@ -0,0 +1,29 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +pub mod errors; +use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; +use crate::sub_lib::wallet::Wallet; +use web3::types::H256; +use web3::Error; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BlockchainTransaction { + pub block_number: u64, + pub from: Wallet, + pub wei_amount: u128, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RetrievedBlockchainTransactions { + pub new_start_block: u64, + pub transactions: Vec, +} + +pub type ProcessedPayableFallible = Result; + +#[derive(Debug, PartialEq, Clone)] +pub struct RpcPayablesFailure { + pub rpc_error: Error, + pub recipient_wallet: Wallet, + pub hash: H256, +} diff --git a/node/src/blockchain/blockchain_interface/lower_level_interface.rs b/node/src/blockchain/blockchain_interface/lower_level_interface.rs new file mode 100644 index 000000000..7300a7ef8 --- /dev/null +++ b/node/src/blockchain/blockchain_interface/lower_level_interface.rs @@ -0,0 +1,21 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::blockchain::blockchain_interface::data_structures::errors::BlockchainResult; +use crate::sub_lib::wallet::Wallet; +use ethereum_types::U64; +use web3::types::U256; + +pub trait LowBlockchainInt { + fn get_transaction_fee_balance(&self, wallet: &Wallet) -> ResultForBalance; + + fn get_service_fee_balance(&self, wallet: &Wallet) -> ResultForBalance; + + fn get_block_number(&self) -> LatestBlockNumber; + + fn get_transaction_id(&self, wallet: &Wallet) -> ResultForNonce; +} + +pub type ResultForBalance = BlockchainResult; +pub type ResultForBothBalances = BlockchainResult<(web3::types::U256, web3::types::U256)>; +pub type ResultForNonce = BlockchainResult; +pub type LatestBlockNumber = BlockchainResult; diff --git a/node/src/blockchain/blockchain_interface/mod.rs b/node/src/blockchain/blockchain_interface/mod.rs new file mode 100644 index 000000000..250f0d2f8 --- /dev/null +++ b/node/src/blockchain/blockchain_interface/mod.rs @@ -0,0 +1,52 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +pub mod blockchain_interface_null; +pub mod blockchain_interface_web3; +pub mod data_structures; +pub mod lower_level_interface; +pub mod test_utils; + +use crate::accountant::db_access_objects::payable_dao::PayableAccount; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; +use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; +use crate::blockchain::blockchain_interface::data_structures::errors::{ + BlockchainAgentBuildError, BlockchainError, PayableTransactionError, ResultForReceipt, +}; +use crate::blockchain::blockchain_interface::data_structures::{ + ProcessedPayableFallible, RetrievedBlockchainTransactions, +}; +use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; +use crate::db_config::persistent_configuration::PersistentConfiguration; +use crate::sub_lib::wallet::Wallet; +use actix::Recipient; +use web3::types::{Address, BlockNumber, H256}; + +pub trait BlockchainInterface { + fn contract_address(&self) -> Address; + + fn retrieve_transactions( + &self, + start_block: BlockNumber, + end_block: BlockNumber, + recipient: &Wallet, + ) -> Result; + + fn build_blockchain_agent( + &self, + consuming_wallet: &Wallet, + persistent_config: &dyn PersistentConfiguration, + ) -> Result, BlockchainAgentBuildError>; + + fn send_batch_of_payables( + &self, + agent: Box, + new_fingerprints_recipient: &Recipient, + accounts: &[PayableAccount], + ) -> Result, PayableTransactionError>; + + fn get_transaction_receipt(&self, hash: H256) -> ResultForReceipt; + + fn lower_interface(&self) -> &dyn LowBlockchainInt; + + as_any_in_trait!(); +} diff --git a/node/src/blockchain/blockchain_interface/test_utils.rs b/node/src/blockchain/blockchain_interface/test_utils.rs new file mode 100644 index 000000000..6817e74ab --- /dev/null +++ b/node/src/blockchain/blockchain_interface/test_utils.rs @@ -0,0 +1,131 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +#![cfg(test)] + +use crate::blockchain::blockchain_interface::lower_level_interface::{ + LatestBlockNumber, LowBlockchainInt, ResultForBalance, ResultForNonce, +}; + +use crate::blockchain::blockchain_interface::BlockchainInterface; +use crate::sub_lib::wallet::Wallet; +use crate::test_utils::http_test_server::TestServer; +use crate::test_utils::make_wallet; +use masq_lib::blockchains::chains::Chain; +use masq_lib::utils::find_free_port; +use serde_json::Value; +use std::cell::RefCell; +use std::sync::{Arc, Mutex}; + +#[derive(Default)] +pub struct LowBlockchainIntMock { + get_transaction_fee_balance_params: Arc>>, + get_transaction_fee_balance_results: RefCell>, + get_masq_balance_params: Arc>>, + get_masq_balance_results: RefCell>, + get_block_number_results: RefCell>, + get_transaction_id_params: Arc>>, + get_transaction_id_results: RefCell>, +} + +impl LowBlockchainInt for LowBlockchainIntMock { + fn get_transaction_fee_balance(&self, address: &Wallet) -> ResultForBalance { + self.get_transaction_fee_balance_params + .lock() + .unwrap() + .push(address.clone()); + self.get_transaction_fee_balance_results + .borrow_mut() + .remove(0) + } + + fn get_service_fee_balance(&self, address: &Wallet) -> ResultForBalance { + self.get_masq_balance_params + .lock() + .unwrap() + .push(address.clone()); + self.get_masq_balance_results.borrow_mut().remove(0) + } + + fn get_block_number(&self) -> LatestBlockNumber { + self.get_block_number_results.borrow_mut().remove(0) + } + + fn get_transaction_id(&self, address: &Wallet) -> ResultForNonce { + self.get_transaction_id_params + .lock() + .unwrap() + .push(address.clone()); + self.get_transaction_id_results.borrow_mut().remove(0) + } +} + +impl LowBlockchainIntMock { + pub fn get_transaction_fee_balance_params(mut self, params: &Arc>>) -> Self { + self.get_transaction_fee_balance_params = params.clone(); + self + } + + pub fn get_transaction_fee_balance_result(self, result: ResultForBalance) -> Self { + self.get_transaction_fee_balance_results + .borrow_mut() + .push(result); + self + } + + pub fn get_masq_balance_params(mut self, params: &Arc>>) -> Self { + self.get_masq_balance_params = params.clone(); + self + } + + pub fn get_masq_balance_result(self, result: ResultForBalance) -> Self { + self.get_masq_balance_results.borrow_mut().push(result); + self + } + + pub fn get_block_number_result(self, result: LatestBlockNumber) -> Self { + self.get_block_number_results.borrow_mut().push(result); + self + } + + pub fn get_transaction_id_params(mut self, params: &Arc>>) -> Self { + self.get_transaction_id_params = params.clone(); + self + } + + pub fn get_transaction_id_result(self, result: ResultForNonce) -> Self { + self.get_transaction_id_results.borrow_mut().push(result); + self + } +} + +pub fn test_blockchain_interface_is_connected_and_functioning(subject_factory: F) +where + F: Fn(u16, Chain) -> Box, +{ + let port = find_free_port(); + let test_server = TestServer::start( + port, + vec![br#"{"jsonrpc":"2.0","id":0,"result":someGarbage}"#.to_vec()], + ); + let wallet = make_wallet("123"); + let chain = Chain::PolyMainnet; + let subject = subject_factory(port, chain); + + // no assertion for the result, we anticipate an error from a badly formatted response from the server; + // yet enough to prove we have a proper connection + let _ = subject.lower_interface().get_service_fee_balance(&wallet); + + let requests = test_server.requests_so_far(); + let bodies: Vec = requests + .into_iter() + .map(|request| serde_json::from_slice(&request.body()).unwrap()) + .collect(); + assert_eq!( + bodies[0]["params"][0]["data"].to_string()[35..75], + wallet.to_string()[2..] + ); + assert_eq!( + bodies[0]["params"][0]["to"], + format!("{:?}", chain.rec().contract) + ); +} diff --git a/node/src/blockchain/blockchain_interface_initializer.rs b/node/src/blockchain/blockchain_interface_initializer.rs new file mode 100644 index 000000000..7448420a3 --- /dev/null +++ b/node/src/blockchain/blockchain_interface_initializer.rs @@ -0,0 +1,75 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::blockchain::blockchain_interface::blockchain_interface_web3::{ + BlockchainInterfaceWeb3, REQUESTS_IN_PARALLEL, +}; +use crate::blockchain::blockchain_interface::BlockchainInterface; +use masq_lib::blockchains::chains::Chain; +use web3::transports::Http; + +pub(in crate::blockchain) struct BlockchainInterfaceInitializer {} + +impl BlockchainInterfaceInitializer { + // TODO when we have multiple chains of fundamentally different architectures and are able to switch them, + // this should probably be replaced by a HashMap of distinct interfaces for each chain + pub fn initialize_interface( + &self, + blockchain_service_url: &str, + chain: Chain, + ) -> Box { + self.initialize_web3_interface(blockchain_service_url, chain) + } + + fn initialize_web3_interface( + &self, + blockchain_service_url: &str, + chain: Chain, + ) -> Box { + match Http::with_max_parallel(blockchain_service_url, REQUESTS_IN_PARALLEL) { + Ok((event_loop_handle, transport)) => Box::new(BlockchainInterfaceWeb3::new( + transport, + event_loop_handle, + chain, + )), + Err(e) => panic!( + "Invalid blockchain service URL \"{}\". Error: {:?}. Chain: {}", + blockchain_service_url, + e, + chain.rec().literal_identifier + ), + } + } +} + +#[cfg(test)] +mod tests { + use crate::blockchain::blockchain_interface_initializer::BlockchainInterfaceInitializer; + use masq_lib::blockchains::chains::Chain; + + use std::net::Ipv4Addr; + + use crate::blockchain::blockchain_interface::test_utils::test_blockchain_interface_is_connected_and_functioning; + + use masq_lib::constants::DEFAULT_CHAIN; + + #[test] + fn initialize_web3_interface_works() { + let subject_factory = |port: u16, chain: Chain| { + let subject = BlockchainInterfaceInitializer {}; + let server_url = &format!("http://{}:{}", &Ipv4Addr::LOCALHOST.to_string(), port); + subject.initialize_web3_interface(server_url, chain) + }; + + test_blockchain_interface_is_connected_and_functioning(subject_factory) + } + + #[test] + #[should_panic(expected = "Invalid blockchain service URL \"http://λ:8545\". \ + Error: Transport(\"InvalidUri(InvalidUriChar)\"). Chain: polygon-mainnet")] + fn invalid_blockchain_url_for_produces_panic_for_web3_interface() { + let blockchain_service_url = "http://λ:8545"; + let subject = BlockchainInterfaceInitializer {}; + + subject.initialize_web3_interface(blockchain_service_url, DEFAULT_CHAIN); + } +} diff --git a/node/src/blockchain/mod.rs b/node/src/blockchain/mod.rs index 7375b60e9..937380fd1 100644 --- a/node/src/blockchain/mod.rs +++ b/node/src/blockchain/mod.rs @@ -1,9 +1,10 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -pub mod batch_payable_tools; + pub mod bip32; pub mod bip39; pub mod blockchain_bridge; pub mod blockchain_interface; +pub mod blockchain_interface_initializer; pub mod payer; pub mod signature; diff --git a/node/src/blockchain/payer.rs b/node/src/blockchain/payer.rs index 3979e054e..f794ee403 100644 --- a/node/src/blockchain/payer.rs +++ b/node/src/blockchain/payer.rs @@ -1,5 +1,5 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::database_access_objects::banned_dao::BAN_CACHE; +use crate::accountant::db_access_objects::banned_dao::BAN_CACHE; use crate::blockchain::signature::SerializableSignature; use crate::sub_lib::wallet::Wallet; use ethsign::Signature; diff --git a/node/src/blockchain/test_utils.rs b/node/src/blockchain/test_utils.rs index 23bc6a2f9..b1a6eae30 100644 --- a/node/src/blockchain/test_utils.rs +++ b/node/src/blockchain/test_utils.rs @@ -2,33 +2,39 @@ #![cfg(test)] +use crate::accountant::db_access_objects::payable_dao::PayableAccount; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; -use crate::blockchain::blockchain_interface::{ - BlockchainError, BlockchainInterface, BlockchainResult, PayableTransactionError, - ProcessedPayableFallible, ResultForBalance, ResultForNonce, ResultForReceipt, - REQUESTS_IN_PARALLEL, +use crate::blockchain::blockchain_interface::blockchain_interface_web3::REQUESTS_IN_PARALLEL; +use crate::blockchain::blockchain_interface::data_structures::errors::{ + BlockchainAgentBuildError, BlockchainError, PayableTransactionError, ResultForReceipt, }; +use crate::blockchain::blockchain_interface::data_structures::{ + ProcessedPayableFallible, RetrievedBlockchainTransactions, +}; +use crate::blockchain::blockchain_interface::lower_level_interface::LowBlockchainInt; +use crate::blockchain::blockchain_interface::test_utils::LowBlockchainIntMock; +use crate::blockchain::blockchain_interface::BlockchainInterface; +use crate::db_config::persistent_configuration::PersistentConfiguration; +use crate::set_arbitrary_id_stamp_in_mock_impl; use crate::sub_lib::wallet::Wallet; +use crate::test_utils::unshared_test_utils::arbitrary_id_stamp::ArbitraryIdStamp; use actix::Recipient; use bip39::{Language, Mnemonic, Seed}; use ethereum_types::{BigEndianHash, H256}; use jsonrpc_core as rpc; use lazy_static::lazy_static; +use masq_lib::blockchains::chains::Chain; +use masq_lib::utils::to_string; use std::cell::RefCell; use std::collections::VecDeque; use std::fmt::Debug; use std::sync::{Arc, Mutex}; -use std::time::SystemTime; - -use crate::accountant::database_access_objects::payable_dao::PayableAccount; -use crate::blockchain::batch_payable_tools::BatchPayableTools; -use web3::transports::{Batch, EventLoopHandle, Http}; -use web3::types::{Address, Bytes, SignedTransaction, TransactionParameters, U256}; -use web3::{BatchTransport, Error as Web3Error, Web3}; +use web3::transports::{EventLoopHandle, Http}; +use web3::types::{Address, BlockNumber, U256}; +use web3::{BatchTransport, Error as Web3Error}; use web3::{RequestId, Transport}; -use crate::blockchain::blockchain_interface::RetrievedBlockchainTransactions; - lazy_static! { static ref BIG_MEANINGLESS_PHRASE: Vec<&'static str> = vec![ "parent", "prevent", "vehicle", "tooth", "crazy", "cruel", "update", "mango", "female", @@ -38,10 +44,7 @@ lazy_static! { } pub fn make_meaningless_phrase_words() -> Vec { - BIG_MEANINGLESS_PHRASE - .iter() - .map(|word| word.to_string()) - .collect() + BIG_MEANINGLESS_PHRASE.iter().map(to_string).collect() } pub fn make_meaningless_phrase() -> String { @@ -55,97 +58,72 @@ pub fn make_meaningless_seed() -> Seed { #[derive(Default)] pub struct BlockchainInterfaceMock { - retrieve_transactions_parameters: Arc>>, + retrieve_transactions_parameters: Arc>>, retrieve_transactions_results: RefCell>>, - send_payables_within_batch_params: Arc< + build_blockchain_agent_params: Arc>>, + build_blockchain_agent_results: + RefCell, BlockchainAgentBuildError>>>, + send_batch_of_payables_params: Arc< Mutex< Vec<( - Wallet, - u64, - U256, + ArbitraryIdStamp, Recipient, Vec, )>, >, >, - send_payables_within_batch_results: + send_batch_of_payables_results: RefCell, PayableTransactionError>>>, - get_transaction_fee_balance_params: Arc>>, - get_transaction_fee_balance_results: RefCell>, - get_token_balance_params: Arc>>, - get_token_balance_results: RefCell>, get_transaction_receipt_params: Arc>>, get_transaction_receipt_results: RefCell>, - contract_address_results: RefCell>, - get_transaction_count_parameters: Arc>>, - get_transaction_count_results: RefCell>>, + arbitrary_id_stamp_opt: Option, + lower_interface_result: Option>, } impl BlockchainInterface for BlockchainInterfaceMock { fn contract_address(&self) -> Address { - self.contract_address_results.borrow_mut().remove(0) + unimplemented!("not needed so far") } fn retrieve_transactions( &self, - start_block: u64, + start_block: BlockNumber, + end_block: BlockNumber, recipient: &Wallet, ) -> Result { - self.retrieve_transactions_parameters - .lock() - .unwrap() - .push((start_block, recipient.clone())); + self.retrieve_transactions_parameters.lock().unwrap().push(( + start_block, + end_block, + recipient.clone(), + )); self.retrieve_transactions_results.borrow_mut().remove(0) } - fn send_payables_within_batch( + fn build_blockchain_agent( &self, consuming_wallet: &Wallet, - gas_price: u64, - last_nonce: U256, + persistent_config: &dyn PersistentConfiguration, + ) -> Result, BlockchainAgentBuildError> { + self.build_blockchain_agent_params.lock().unwrap().push(( + consuming_wallet.clone(), + persistent_config.arbitrary_id_stamp(), + )); + self.build_blockchain_agent_results.borrow_mut().remove(0) + } + + fn send_batch_of_payables( + &self, + agent: Box, new_fingerprints_recipient: &Recipient, accounts: &[PayableAccount], ) -> Result, PayableTransactionError> { - self.send_payables_within_batch_params - .lock() - .unwrap() - .push(( - consuming_wallet.clone(), - gas_price, - last_nonce, - new_fingerprints_recipient.clone(), - accounts.to_vec(), - )); - self.send_payables_within_batch_results - .borrow_mut() - .remove(0) - } - - fn get_transaction_fee_balance(&self, address: &Wallet) -> ResultForBalance { - self.get_transaction_fee_balance_params - .lock() - .unwrap() - .push(address.clone()); - self.get_transaction_fee_balance_results - .borrow_mut() - .remove(0) - } - - fn get_token_balance(&self, address: &Wallet) -> ResultForBalance { - self.get_token_balance_params - .lock() - .unwrap() - .push(address.clone()); - self.get_token_balance_results.borrow_mut().remove(0) - } - - fn get_transaction_count(&self, wallet: &Wallet) -> ResultForNonce { - self.get_transaction_count_parameters - .lock() - .unwrap() - .push(wallet.clone()); - self.get_transaction_count_results.borrow_mut().remove(0) + self.send_batch_of_payables_params.lock().unwrap().push(( + agent.arbitrary_id_stamp(), + new_fingerprints_recipient.clone(), + accounts.to_vec(), + )); + self.send_batch_of_payables_results.borrow_mut().remove(0) } fn get_transaction_receipt(&self, hash: H256) -> ResultForReceipt { @@ -155,10 +133,17 @@ impl BlockchainInterface for BlockchainInterfaceMock { .push(hash); self.get_transaction_receipt_results.borrow_mut().remove(0) } + + fn lower_interface(&self) -> &dyn LowBlockchainInt { + self.lower_interface_result.as_ref().unwrap().as_ref() + } } impl BlockchainInterfaceMock { - pub fn retrieve_transactions_params(mut self, params: &Arc>>) -> Self { + pub fn retrieve_transactions_params( + mut self, + params: &Arc>>, + ) -> Self { self.retrieve_transactions_parameters = params.clone(); self } @@ -171,71 +156,50 @@ impl BlockchainInterfaceMock { self } - pub fn send_payables_within_batch_params( + pub fn build_blockchain_agent_params( + mut self, + params: &Arc>>, + ) -> Self { + self.build_blockchain_agent_params = params.clone(); + self + } + + pub fn build_blockchain_agent_result( + self, + result: Result, BlockchainAgentBuildError>, + ) -> Self { + self.build_blockchain_agent_results + .borrow_mut() + .push(result); + self + } + + pub fn send_batch_of_payables_params( mut self, params: &Arc< Mutex< Vec<( - Wallet, - u64, - U256, + ArbitraryIdStamp, Recipient, Vec, )>, >, >, ) -> Self { - self.send_payables_within_batch_params = params.clone(); + self.send_batch_of_payables_params = params.clone(); self } - pub fn send_payables_within_batch_result( + pub fn send_batch_of_payables_result( self, result: Result, PayableTransactionError>, ) -> Self { - self.send_payables_within_batch_results - .borrow_mut() - .push(result); - self - } - - pub fn get_transaction_fee_balance_params(mut self, params: &Arc>>) -> Self { - self.get_transaction_fee_balance_params = params.clone(); - self - } - - pub fn get_transaction_fee_balance_result(self, result: ResultForBalance) -> Self { - self.get_transaction_fee_balance_results + self.send_batch_of_payables_results .borrow_mut() .push(result); self } - pub fn get_token_balance_params(mut self, params: &Arc>>) -> Self { - self.get_token_balance_params = params.clone(); - self - } - - pub fn get_token_balance_result(self, result: ResultForBalance) -> Self { - self.get_token_balance_results.borrow_mut().push(result); - self - } - - pub fn contract_address_result(self, address: Address) -> Self { - self.contract_address_results.borrow_mut().push(address); - self - } - - pub fn get_transaction_count_params(mut self, params: &Arc>>) -> Self { - self.get_transaction_count_parameters = params.clone(); - self - } - - pub fn get_transaction_count_result(self, result: BlockchainResult) -> Self { - self.get_transaction_count_results.borrow_mut().push(result); - self - } - pub fn get_transaction_receipt_params(mut self, params: &Arc>>) -> Self { self.get_transaction_receipt_params = params.clone(); self @@ -247,6 +211,16 @@ impl BlockchainInterfaceMock { .push(result); self } + + pub fn lower_interface_results( + mut self, + aggregated_results: Box, + ) -> Self { + self.lower_interface_result = Some(aggregated_results); + self + } + + set_arbitrary_id_stamp_in_mock_impl!(); } #[derive(Debug, Default, Clone)] @@ -349,174 +323,15 @@ pub fn make_fake_event_loop_handle() -> EventLoopHandle { .0 } -#[derive(Default)] -pub struct BatchPayableToolsFactoryMock { - make_results: RefCell>>>, -} - -impl BatchPayableToolsFactoryMock { - pub fn make_result(self, result: Box>) -> Self { - self.make_results.borrow_mut().push(result); - self - } -} - -#[derive(Default)] -pub struct BatchPayableToolsMock { - sign_transaction_params: Arc< - Mutex< - Vec<( - TransactionParameters, - Web3>, - secp256k1secrets::key::SecretKey, - )>, - >, - >, - sign_transaction_results: RefCell>>, - append_transaction_to_batch_params: Arc>)>>>, - //append_transaction_to_batch returns just the unit type - //batch_wide_timestamp doesn't have params - batch_wide_timestamp_results: RefCell>, - send_new_payable_fingerprints_seeds_params: Arc< - Mutex< - Vec<( - SystemTime, - Recipient, - Vec<(H256, u128)>, - )>, - >, - >, - //new_payable_fingerprints returns just the unit type - submit_batch_params: Arc>>>>, - submit_batch_results: - RefCell>, Web3Error>>>, -} - -impl BatchPayableTools for BatchPayableToolsMock { - fn sign_transaction( - &self, - transaction_params: TransactionParameters, - web3: &Web3>, - key: &secp256k1secrets::key::SecretKey, - ) -> Result { - self.sign_transaction_params.lock().unwrap().push(( - transaction_params.clone(), - web3.clone(), - key.clone(), - )); - self.sign_transaction_results.borrow_mut().remove(0) - } - - fn append_transaction_to_batch(&self, signed_transaction: Bytes, web3: &Web3>) { - self.append_transaction_to_batch_params - .lock() - .unwrap() - .push((signed_transaction, web3.clone())); - } - - fn batch_wide_timestamp(&self) -> SystemTime { - self.batch_wide_timestamp_results.borrow_mut().remove(0) - } - - fn send_new_payable_fingerprints_seeds( - &self, - batch_wide_timestamp: SystemTime, - pp_fingerprint_sub: &Recipient, - hashes_and_balances: &[(H256, u128)], - ) { - self.send_new_payable_fingerprints_seeds_params - .lock() - .unwrap() - .push(( - batch_wide_timestamp, - (*pp_fingerprint_sub).clone(), - hashes_and_balances.to_vec(), - )); - } - - fn submit_batch( - &self, - web3: &Web3>, - ) -> Result>, Web3Error> { - self.submit_batch_params.lock().unwrap().push(web3.clone()); - self.submit_batch_results.borrow_mut().remove(0) - } -} - -impl BatchPayableToolsMock { - pub fn sign_transaction_params( - mut self, - params: &Arc< - Mutex< - Vec<( - TransactionParameters, - Web3>, - secp256k1secrets::key::SecretKey, - )>, - >, - >, - ) -> Self { - self.sign_transaction_params = params.clone(); - self - } - pub fn sign_transaction_result(self, result: Result) -> Self { - self.sign_transaction_results.borrow_mut().push(result); - self - } - - pub fn batch_wide_timestamp_result(self, result: SystemTime) -> Self { - self.batch_wide_timestamp_results.borrow_mut().push(result); - self - } - - pub fn send_new_payable_fingerprint_credentials_params( - mut self, - params: &Arc< - Mutex< - Vec<( - SystemTime, - Recipient, - Vec<(H256, u128)>, - )>, - >, - >, - ) -> Self { - self.send_new_payable_fingerprints_seeds_params = params.clone(); - self - } - - pub fn append_transaction_to_batch_params( - mut self, - params: &Arc>)>>>, - ) -> Self { - self.append_transaction_to_batch_params = params.clone(); - self - } - - pub fn submit_batch_params(mut self, params: &Arc>>>>) -> Self { - self.submit_batch_params = params.clone(); - self - } - pub fn submit_batch_result( - self, - result: Result>, Web3Error>, - ) -> Self { - self.submit_batch_results.borrow_mut().push(result); - self - } -} - -pub fn make_default_signed_transaction() -> SignedTransaction { - SignedTransaction { - message_hash: Default::default(), - v: 0, - r: Default::default(), - s: Default::default(), - raw_transaction: Default::default(), - transaction_hash: Default::default(), - } -} - pub fn make_tx_hash(base: u32) -> H256 { H256::from_uint(&U256::from(base)) } + +pub fn all_chains() -> [Chain; 4] { + [ + Chain::EthMainnet, + Chain::PolyMainnet, + Chain::PolyMumbai, + Chain::Dev, + ] +} diff --git a/node/src/bootstrapper.rs b/node/src/bootstrapper.rs index d92a2e40a..5d40c2379 100644 --- a/node/src/bootstrapper.rs +++ b/node/src/bootstrapper.rs @@ -724,9 +724,8 @@ mod tests { use crate::discriminator::Discriminator; use crate::discriminator::UnmaskedChunk; use crate::listener_handler::{ListenerHandler, ListenerHandlerFactory}; - use crate::node_test_utils::make_stream_handler_pool_subs_from; - use crate::node_test_utils::TestLogOwner; use crate::node_test_utils::{extract_log, DirsWrapperMock, IdWrapperMock}; + use crate::node_test_utils::{make_stream_handler_pool_subs_from_recorder, TestLogOwner}; use crate::server_initializer::test_utils::LoggerInitializerWrapperMock; use crate::server_initializer::LoggerInitializerWrapper; use crate::stream_handler_pool::StreamHandlerPoolSubs; @@ -753,8 +752,8 @@ mod tests { }; use crate::test_utils::{assert_contains, rate_pack}; use crate::test_utils::{main_cryptde, make_wallet}; - use actix::Recipient; use actix::System; + use actix::{Actor, Recipient}; use crossbeam_channel::unbounded; use futures::Future; use lazy_static::lazy_static; @@ -767,7 +766,7 @@ mod tests { use masq_lib::test_utils::fake_stream_holder::FakeStreamHolder; use masq_lib::test_utils::logging::{init_test_logging, TestLog, TestLogHandler}; use masq_lib::test_utils::utils::{ensure_node_home_directory_exists, TEST_DEFAULT_CHAIN}; - use masq_lib::utils::find_free_port; + use masq_lib::utils::{find_free_port, to_string}; use std::cell::RefCell; use std::collections::HashMap; use std::io; @@ -934,9 +933,9 @@ mod tests { sudo_user: Option<&str>, ) -> EnvironmentWrapperMock { EnvironmentWrapperMock { - sudo_uid: sudo_uid.map(|s| s.to_string()), - sudo_gid: sudo_gid.map(|s| s.to_string()), - sudo_user: sudo_user.map(|s| s.to_string()), + sudo_uid: sudo_uid.map(to_string), + sudo_gid: sudo_gid.map(to_string), + sudo_user: sudo_user.map(to_string), } } } @@ -2229,7 +2228,9 @@ mod tests { StreamHandlerPoolCluster { recording: Some(recording), awaiter: Some(awaiter), - subs: make_stream_handler_pool_subs_from(Some(stream_handler_pool)), + subs: make_stream_handler_pool_subs_from_recorder( + &stream_handler_pool.start(), + ), } }; diff --git a/node/src/daemon/daemon_initializer.rs b/node/src/daemon/daemon_initializer.rs index beda18164..61519c047 100644 --- a/node/src/daemon/daemon_initializer.rs +++ b/node/src/daemon/daemon_initializer.rs @@ -20,8 +20,6 @@ use masq_lib::shared_schema::ConfiguratorError; use std::collections::HashMap; use masq_lib::utils::ExpectValue; -#[cfg(test)] -use std::any::Any; use std::path::PathBuf; use std::str::FromStr; @@ -85,7 +83,7 @@ impl DaemonInitializer for DaemonInitializerReal { self.split(system, receiver); Ok(()) } - implement_as_any!(); + as_any_in_trait_impl!(); } pub trait Rerunner { diff --git a/node/src/daemon/setup_reporter.rs b/node/src/daemon/setup_reporter.rs index 4816a8810..e83a522bf 100644 --- a/node/src/daemon/setup_reporter.rs +++ b/node/src/daemon/setup_reporter.rs @@ -37,7 +37,7 @@ use masq_lib::multi_config::{ CommandLineVcl, ConfigFileVcl, EnvironmentVcl, MultiConfig, VirtualCommandLine, }; use masq_lib::shared_schema::{shared_app, ConfiguratorError}; -use masq_lib::utils::{add_chain_specific_directory, ExpectValue}; +use masq_lib::utils::{add_chain_specific_directory, to_string, ExpectValue}; use std::collections::HashMap; use std::fmt::Display; use std::net::{IpAddr, Ipv4Addr}; @@ -786,10 +786,7 @@ impl ValueRetriever for DnsServers { if ip_addrs.iter().any(|ip_addr| ip_addr.is_loopback()) { return None; } - let dns_servers = ip_addrs - .into_iter() - .map(|ip_addr| ip_addr.to_string()) - .join(","); + let dns_servers = ip_addrs.into_iter().map(to_string).join(","); Some((dns_servers, Default)) } Err(e) => { diff --git a/node/src/database/config_dumper.rs b/node/src/database/config_dumper.rs index fb4715a94..3b2ed50c3 100644 --- a/node/src/database/config_dumper.rs +++ b/node/src/database/config_dumper.rs @@ -27,8 +27,6 @@ use masq_lib::shared_schema::ConfiguratorError; use rustc_hex::ToHex; use serde_json::json; use serde_json::{Map, Value}; -#[cfg(test)] -use std::any::Any; use std::path::{Path, PathBuf}; pub struct DumpConfigRunnerReal { @@ -52,7 +50,7 @@ impl DumpConfigRunner for DumpConfigRunnerReal { Ok(()) } - implement_as_any!(); + as_any_in_trait_impl!(); } fn write_string(streams: &mut StdStreams, json: String) { diff --git a/node/src/database/db_initializer.rs b/node/src/database/db_initializer.rs index e961a631c..2fad52508 100644 --- a/node/src/database/db_initializer.rs +++ b/node/src/database/db_initializer.rs @@ -261,6 +261,7 @@ impl DbInitializerReal { false, "scan intervals", ); + Self::set_config_value(conn, "max_block_count", None, false, "maximum block count"); } fn create_pending_payable_table(&self, conn: &Connection) { @@ -764,7 +765,7 @@ mod tests { #[test] fn constants_have_correct_values() { assert_eq!(DATABASE_FILE, "node-data.db"); - assert_eq!(CURRENT_SCHEMA_VERSION, 8); + assert_eq!(CURRENT_SCHEMA_VERSION, 9); } #[test] @@ -1039,6 +1040,7 @@ mod tests { false, ); verify(&mut config_vec, "mapping_protocol", None, false); + verify(&mut config_vec, "max_block_count", None, false); verify(&mut config_vec, "min_hops", Some("3"), false); verify( &mut config_vec, diff --git a/node/src/database/db_migrations/db_migrator.rs b/node/src/database/db_migrations/db_migrator.rs index 0866a7d0d..4dba9ce97 100644 --- a/node/src/database/db_migrations/db_migrator.rs +++ b/node/src/database/db_migrations/db_migrator.rs @@ -10,6 +10,7 @@ use crate::database::db_migrations::migrations::migration_4_to_5::Migrate_4_to_5 use crate::database::db_migrations::migrations::migration_5_to_6::Migrate_5_to_6; use crate::database::db_migrations::migrations::migration_6_to_7::Migrate_6_to_7; use crate::database::db_migrations::migrations::migration_7_to_8::Migrate_7_to_8; +use crate::database::db_migrations::migrations::migration_8_to_9::Migrate_8_to_9; use crate::database::db_migrations::migrator_utils::{ DBMigDeclarator, DBMigrationUtilities, DBMigrationUtilitiesReal, DBMigratorInnerConfiguration, }; @@ -77,6 +78,7 @@ impl DbMigratorReal { &Migrate_5_to_6, &Migrate_6_to_7, &Migrate_7_to_8, + &Migrate_8_to_9, ] } diff --git a/node/src/database/db_migrations/migrations/migration_3_to_4.rs b/node/src/database/db_migrations/migrations/migration_3_to_4.rs index 2853bb94d..e06bd50d0 100644 --- a/node/src/database/db_migrations/migrations/migration_3_to_4.rs +++ b/node/src/database/db_migrations/migrations/migration_3_to_4.rs @@ -125,7 +125,7 @@ mod tests { }; use bip39::{Language, Mnemonic, MnemonicType, Seed}; use masq_lib::test_utils::utils::ensure_node_home_directory_exists; - use masq_lib::utils::derivation_path; + use masq_lib::utils::{derivation_path, to_string}; use rand::Rng; use rusqlite::ToSql; use std::panic::{catch_unwind, AssertUnwindSafe}; @@ -294,9 +294,9 @@ mod tests { seed_encrypted_opt: Option<&str>, ) -> String { let mig_declarator = &DBMigDeclaratorMock::default(); - let consuming_path_opt = consuming_path_opt.map(|str| str.to_string()); - let example_encrypted_opt = example_encrypted_opt.map(|str| str.to_string()); - let seed_encrypted_opt = seed_encrypted_opt.map(|str| str.to_string()); + let consuming_path_opt = consuming_path_opt.map(to_string); + let example_encrypted_opt = example_encrypted_opt.map(to_string); + let seed_encrypted_opt = seed_encrypted_opt.map(to_string); let panic = catch_unwind(AssertUnwindSafe(|| { Migrate_3_to_4::maybe_exchange_seed_for_private_key( consuming_path_opt, diff --git a/node/src/database/db_migrations/migrations/migration_4_to_5.rs b/node/src/database/db_migrations/migrations/migration_4_to_5.rs index 208a1018f..7b3af68bd 100644 --- a/node/src/database/db_migrations/migrations/migration_4_to_5.rs +++ b/node/src/database/db_migrations/migrations/migration_4_to_5.rs @@ -1,6 +1,6 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::database_access_objects::dao_utils::VigilantRusqliteFlatten; +use crate::accountant::db_access_objects::utils::VigilantRusqliteFlatten; use crate::database::db_migrations::db_migrator::DatabaseMigration; use crate::database::db_migrations::migrator_utils::DBMigDeclarator; @@ -76,7 +76,7 @@ impl DatabaseMigration for Migrate_4_to_5 { #[cfg(test)] mod tests { - use crate::accountant::database_access_objects::dao_utils::{from_time_t, to_time_t}; + use crate::accountant::db_access_objects::utils::{from_time_t, to_time_t}; use crate::database::connection_wrapper::{ConnectionWrapper, ConnectionWrapperReal}; use crate::database::db_initializer::{ DbInitializationConfig, DbInitializer, DbInitializerReal, ExternalData, DATABASE_FILE, diff --git a/node/src/database/db_migrations/migrations/migration_6_to_7.rs b/node/src/database/db_migrations/migrations/migration_6_to_7.rs index 3a1606281..12f14ecfc 100644 --- a/node/src/database/db_migrations/migrations/migration_6_to_7.rs +++ b/node/src/database/db_migrations/migrations/migration_6_to_7.rs @@ -1,7 +1,7 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::big_int_processing::big_int_divider::BigIntDivider; -use crate::accountant::database_access_objects::dao_utils::VigilantRusqliteFlatten; +use crate::accountant::db_access_objects::utils::VigilantRusqliteFlatten; +use crate::accountant::db_big_integer::big_int_divider::BigIntDivider; use crate::accountant::gwei_to_wei; use crate::database::db_migrations::db_migrator::DatabaseMigration; use crate::database::db_migrations::migrator_utils::{ 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 new file mode 100644 index 000000000..c5928edb6 --- /dev/null +++ b/node/src/database/db_migrations/migrations/migration_8_to_9.rs @@ -0,0 +1,70 @@ +use crate::database::db_migrations::db_migrator::DatabaseMigration; +use crate::database::db_migrations::migrator_utils::DBMigDeclarator; + +#[allow(non_camel_case_types)] +pub struct Migrate_8_to_9; + +impl DatabaseMigration for Migrate_8_to_9 { + fn migrate<'a>( + &self, + declaration_utils: Box, + ) -> rusqlite::Result<()> { + declaration_utils.execute_upon_transaction(&[ + &"INSERT INTO config (name, value, encrypted) VALUES ('max_block_count', null, 0)", + ]) + } + + fn old_version(&self) -> usize { + 8 + } +} + +#[cfg(test)] +mod tests { + use crate::database::db_initializer::{ + DbInitializationConfig, DbInitializer, DbInitializerReal, DATABASE_FILE, + }; + use crate::test_utils::database_utils::{ + bring_db_0_back_to_life_and_return_connection, make_external_data, retrieve_config_row, + }; + 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_8_to_9_is_properly_set() { + init_test_logging(); + let dir_path = ensure_node_home_directory_exists( + "db_migrations", + "migration_from_8_to_9_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 result = subject.initialize_to_version( + &dir_path, + 9, + DbInitializationConfig::create_or_migrate(make_external_data()), + ); + 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); + assert_eq!(mp_encrypted, false); + assert_eq!(cs_value, Some("9".to_string())); + assert_eq!(cs_encrypted, false); + TestLogHandler::new().assert_logs_contain_in_order(vec![ + "DbMigrator: Database successfully migrated from version 0 to 1", + "DbMigrator: Database successfully migrated from version 1 to 2", + "DbMigrator: Database successfully migrated from version 2 to 3", + "DbMigrator: Database successfully migrated from version 3 to 4", + "DbMigrator: Database successfully migrated from version 4 to 5", + "DbMigrator: Database successfully migrated from version 5 to 6", + "DbMigrator: Database successfully migrated from version 6 to 7", + "DbMigrator: Database successfully migrated from version 7 to 8", + "DbMigrator: Database successfully migrated from version 8 to 9", + ]); + } +} diff --git a/node/src/database/db_migrations/migrations/mod.rs b/node/src/database/db_migrations/migrations/mod.rs index bc540b4ae..68b10ca9b 100644 --- a/node/src/database/db_migrations/migrations/mod.rs +++ b/node/src/database/db_migrations/migrations/mod.rs @@ -8,3 +8,4 @@ pub mod migration_4_to_5; pub mod migration_5_to_6; pub mod migration_6_to_7; pub mod migration_7_to_8; +pub mod migration_8_to_9; diff --git a/node/src/database/db_migrations/migrator_utils.rs b/node/src/database/db_migrations/migrator_utils.rs index e9bab401c..422273b06 100644 --- a/node/src/database/db_migrations/migrator_utils.rs +++ b/node/src/database/db_migrations/migrator_utils.rs @@ -5,7 +5,7 @@ use crate::database::db_initializer::ExternalData; use crate::database::db_migrations::db_migrator::{DatabaseMigration, DbMigratorReal}; use masq_lib::constants::CURRENT_SCHEMA_VERSION; use masq_lib::logger::Logger; -use masq_lib::utils::ExpectValue; +use masq_lib::utils::{to_string, ExpectValue}; use rusqlite::{params_from_iter, Error, ToSql, Transaction}; use std::fmt::{Display, Formatter}; @@ -71,7 +71,7 @@ impl<'a> DBMigrationUtilities for DBMigrationUtilitiesReal<'a> { .take() .expectv("owned root transaction") .commit() - .map_err(|e| e.to_string()) + .map_err(to_string) } fn make_mig_declarator<'b>( diff --git a/node/src/database/db_migrations/test_utils.rs b/node/src/database/db_migrations/test_utils.rs index d85fbc41b..11c68c5a7 100644 --- a/node/src/database/db_migrations/test_utils.rs +++ b/node/src/database/db_migrations/test_utils.rs @@ -4,6 +4,7 @@ use crate::database::db_initializer::ExternalData; use crate::database::db_migrations::migrator_utils::{DBMigDeclarator, StatementObject}; use masq_lib::logger::Logger; +use masq_lib::utils::to_string; use rusqlite::Transaction; use std::cell::RefCell; use std::sync::{Arc, Mutex}; @@ -53,7 +54,7 @@ impl DBMigDeclarator for DBMigDeclaratorMock { self.execute_upon_transaction_params.lock().unwrap().push( sql_statements .iter() - .map(|stm_obj| stm_obj.to_string()) + .map(to_string) .collect::>(), ); self.execute_upon_transaction_results.borrow_mut().remove(0) diff --git a/node/src/db_config/config_dao.rs b/node/src/db_config/config_dao.rs index e19aacda6..7d386f151 100644 --- a/node/src/db_config/config_dao.rs +++ b/node/src/db_config/config_dao.rs @@ -1,6 +1,7 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::database_access_objects::dao_utils::DaoFactoryReal; +use crate::accountant::db_access_objects::utils::DaoFactoryReal; use crate::database::connection_wrapper::ConnectionWrapper; +use masq_lib::utils::to_string; use rusqlite::types::ToSql; use rusqlite::{Row, Rows, Statement}; @@ -22,7 +23,7 @@ impl ConfigDaoRecord { pub fn new(name: &str, value: Option<&str>, encrypted: bool) -> Self { Self { name: name.to_string(), - value_opt: value.map(|x| x.to_string()), + value_opt: value.map(to_string), encrypted, } } @@ -266,4 +267,16 @@ mod tests { let result = subject.get("schema_version").unwrap(); assert_eq!(result, ConfigDaoRecord::new("schema_version", None, false)); } + + #[test] + fn test_handle_update_execution() { + let result = handle_update_execution(Err(rusqlite::Error::ExecuteReturnedResults)); + + assert_eq!( + result, + Err(ConfigDaoError::DatabaseError( + "Execute returned results - did you mean to call query?".to_string() + )) + ) + } } diff --git a/node/src/db_config/config_dao_null.rs b/node/src/db_config/config_dao_null.rs index e3550da14..2cc00e527 100644 --- a/node/src/db_config/config_dao_null.rs +++ b/node/src/db_config/config_dao_null.rs @@ -130,6 +130,7 @@ impl Default for ConfigDaoNull { "scan_intervals".to_string(), (Some(DEFAULT_SCAN_INTERVALS.to_string()), false), ); + data.insert("max_block_count".to_string(), (None, false)); Self { data } } } @@ -273,6 +274,7 @@ mod tests { "schema_version", Some(format!("{}", CURRENT_SCHEMA_VERSION).as_str()), ), + ("max_block_count", None), ] .into_iter() .map(|(k, v_opt)| (k.to_string(), v_opt.map(|v| v.to_string()))) diff --git a/node/src/db_config/mocks.rs b/node/src/db_config/mocks.rs index 60b092c7d..a3083807c 100644 --- a/node/src/db_config/mocks.rs +++ b/node/src/db_config/mocks.rs @@ -1,6 +1,7 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::db_config::config_dao::{ConfigDao, ConfigDaoError, ConfigDaoRecord}; +use masq_lib::utils::to_string; use std::cell::RefCell; use std::sync::{Arc, Mutex}; @@ -27,7 +28,7 @@ impl ConfigDao for ConfigDaoMock { self.set_params .lock() .unwrap() - .push((name.to_string(), value.map(|x| x.to_string()))); + .push((name.to_string(), value.map(to_string))); self.set_results.borrow_mut().remove(0) } } diff --git a/node/src/db_config/persistent_configuration.rs b/node/src/db_config/persistent_configuration.rs index 3420ec688..8842b896a 100644 --- a/node/src/db_config/persistent_configuration.rs +++ b/node/src/db_config/persistent_configuration.rs @@ -1,8 +1,7 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -#[cfg(test)] use crate::arbitrary_id_stamp_in_trait; -use crate::blockchain::bip32::Bip32ECKeyProvider; +use crate::blockchain::bip32::Bip32EncryptionKeyProvider; use crate::blockchain::bip39::{Bip39, Bip39Error}; use crate::database::connection_wrapper::ConnectionWrapper; use crate::db_config::config_dao::{ConfigDao, ConfigDaoError, ConfigDaoReal, ConfigDaoRecord}; @@ -15,12 +14,10 @@ use crate::sub_lib::accountant::{PaymentThresholds, ScanIntervals}; use crate::sub_lib::cryptde::PlainData; use crate::sub_lib::neighborhood::{Hops, NodeDescriptor, RatePack}; use crate::sub_lib::wallet::Wallet; -#[cfg(test)] -use crate::test_utils::unshared_test_utils::arbitrary_id_stamp::ArbitraryIdStamp; use masq_lib::constants::{HIGHEST_USABLE_PORT, LOWEST_USABLE_INSECURE_PORT}; use masq_lib::shared_schema::{ConfiguratorError, ParamError}; -use masq_lib::utils::AutomapProtocol; use masq_lib::utils::NeighborhoodModeLight; +use masq_lib::utils::{to_string, AutomapProtocol}; use rustc_hex::{FromHex, ToHex}; use std::fmt::Display; use std::net::{Ipv4Addr, SocketAddrV4, TcpListener}; @@ -136,6 +133,8 @@ pub trait PersistentConfiguration { ) -> Result<(), PersistentConfigError>; fn start_block(&self) -> Result; fn set_start_block(&mut self, value: u64) -> Result<(), PersistentConfigError>; + fn max_block_count(&self) -> Result, PersistentConfigError>; + fn set_max_block_count(&mut self, value: Option) -> Result<(), PersistentConfigError>; fn set_wallet_info( &mut self, consuming_wallet_private_key: &str, @@ -149,7 +148,6 @@ pub trait PersistentConfiguration { fn scan_intervals(&self) -> Result; fn set_scan_intervals(&mut self, intervals: String) -> Result<(), PersistentConfigError>; - #[cfg(test)] arbitrary_id_stamp_in_trait!(); } @@ -221,13 +219,15 @@ impl PersistentConfiguration for PersistentConfigurationReal { "Database corruption {:?}: consuming private key is not hex, but '{}'", e, key ), - Ok(bytes) => match Bip32ECKeyProvider::from_raw_secret(bytes.as_slice()) { - Err(e) => panic!( - "Database corruption {:?}: consuming private key is invalid", - e - ), - Ok(pair) => Wallet::from(pair), - }, + Ok(bytes) => { + match Bip32EncryptionKeyProvider::from_raw_secret(bytes.as_slice()) { + Err(e) => panic!( + "Database corruption {:?}: consuming private key is invalid", + e + ), + Ok(pair) => Wallet::from(pair), + } + } }) }) } @@ -332,9 +332,7 @@ impl PersistentConfiguration for PersistentConfigurationReal { &mut self, value: Option, ) -> Result<(), PersistentConfigError> { - Ok(self - .dao - .set("mapping_protocol", value.map(|v| v.to_string()))?) + Ok(self.dao.set("mapping_protocol", value.map(to_string))?) } fn min_hops(&self) -> Result { @@ -411,6 +409,14 @@ impl PersistentConfiguration for PersistentConfigurationReal { self.simple_set_method("start_block", value) } + fn max_block_count(&self) -> Result, PersistentConfigError> { + Ok(decode_u64(self.get("max_block_count")?)?) + } + + fn set_max_block_count(&mut self, value: Option) -> Result<(), PersistentConfigError> { + Ok(self.dao.set("max_block_count", encode_u64(value)?)?) + } + fn set_wallet_info( &mut self, consuming_wallet_private_key: &str, @@ -1058,7 +1064,7 @@ mod tests { let consuming_private_key = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"; let consuming_wallet = Wallet::from( - Bip32ECKeyProvider::from_raw_secret( + Bip32EncryptionKeyProvider::from_raw_secret( consuming_private_key .from_hex::>() .unwrap() @@ -1191,7 +1197,7 @@ mod tests { let earning_private_key = ExtendedPrivKey::derive(seed_bytes.as_slice(), derivation_path.as_str()).unwrap(); let earning_key_pair = - Bip32ECKeyProvider::from_raw_secret(&earning_private_key.secret()).unwrap(); + Bip32EncryptionKeyProvider::from_raw_secret(&earning_private_key.secret()).unwrap(); let earning_wallet = Wallet::from(earning_key_pair); let earning_wallet_address = earning_wallet.to_string(); ( @@ -1933,6 +1939,39 @@ mod tests { ); } + #[test] + fn max_block_count_set_method_works_with_some() { + let set_params_arc = Arc::new(Mutex::new(Vec::new())); + let config_dao = ConfigDaoMock::new() + .set_params(&set_params_arc) + .set_result(Ok(())); + let mut subject = PersistentConfigurationReal::new(Box::new(config_dao)); + + let result = subject.set_max_block_count(Some(100_000u64)); + + assert!(result.is_ok()); + let set_params = set_params_arc.lock().unwrap(); + assert_eq!( + *set_params, + vec![("max_block_count".to_string(), Some(100_000u64.to_string()))] + ); + } + + #[test] + fn max_block_count_set_method_works_with_none() { + let set_params_arc = Arc::new(Mutex::new(Vec::new())); + let config_dao = ConfigDaoMock::new() + .set_params(&set_params_arc) + .set_result(Ok(())); + let mut subject = PersistentConfigurationReal::new(Box::new(config_dao)); + + let result = subject.set_max_block_count(None); + + assert!(result.is_ok()); + let set_params = set_params_arc.lock().unwrap(); + assert_eq!(*set_params, vec![("max_block_count".to_string(), None)]); + } + #[test] #[should_panic( expected = "ever-supplied value missing: payment_thresholds; database is corrupt!" diff --git a/node/src/dispatcher.rs b/node/src/dispatcher.rs index 583677035..415f12612 100644 --- a/node/src/dispatcher.rs +++ b/node/src/dispatcher.rs @@ -211,7 +211,7 @@ mod tests { use super::*; use crate::actor_system_factory::{ActorFactory, ActorFactoryReal}; use crate::bootstrapper::BootstrapperConfig; - use crate::node_test_utils::make_stream_handler_pool_subs_from; + use crate::node_test_utils::make_stream_handler_pool_subs_from_recorder; use crate::stream_messages::NonClandestineAttributes; use crate::sub_lib::cryptde::CryptDE; use crate::sub_lib::dispatcher::Endpoint; @@ -426,7 +426,7 @@ mod tests { let mut peer_actors = peer_actors_builder().build(); peer_actors.dispatcher = Dispatcher::make_subs_from(&subject_addr); let stream_handler_pool_subs = - make_stream_handler_pool_subs_from(Some(stream_handler_pool)); + make_stream_handler_pool_subs_from_recorder(&stream_handler_pool.start()); subject_addr .try_send(PoolBindMessage { dispatcher_subs: peer_actors.dispatcher.clone(), @@ -440,14 +440,11 @@ mod tests { System::current().stop_with_code(0); system.run(); - awaiter.await_message_count(1); let recording = recording_arc.lock().unwrap(); - let message = recording.get_record::(0); let actual_endpoint = message.endpoint.clone(); let actual_data = message.data.clone(); - assert_eq!(actual_endpoint, Endpoint::Socket(socket_addr)); assert_eq!(actual_data, data); assert_eq!(recording.len(), 1); diff --git a/node/src/hopper/routing_service.rs b/node/src/hopper/routing_service.rs index 6335975a7..3ebaaab65 100644 --- a/node/src/hopper/routing_service.rs +++ b/node/src/hopper/routing_service.rs @@ -500,7 +500,7 @@ impl RoutingService { #[cfg(test)] mod tests { use super::*; - use crate::accountant::database_access_objects::banned_dao::BAN_CACHE; + use crate::accountant::db_access_objects::banned_dao::BAN_CACHE; use crate::bootstrapper::Bootstrapper; use crate::neighborhood::gossip::{GossipBuilder, Gossip_0v1}; use crate::node_test_utils::check_timestamp; @@ -521,9 +521,9 @@ mod tests { use crate::test_utils::recorder::{make_recorder, peer_actors_builder}; use crate::test_utils::{ alias_cryptde, main_cryptde, make_cryptde_pair, make_meaningless_message_type, - make_meaningless_stream_key, make_paying_wallet, make_request_payload, - make_response_payload, rate_pack_routing, rate_pack_routing_byte, route_from_proxy_client, - route_to_proxy_client, route_to_proxy_server, + make_paying_wallet, make_request_payload, make_response_payload, rate_pack_routing, + rate_pack_routing_byte, route_from_proxy_client, route_to_proxy_client, + route_to_proxy_server, }; use actix::System; use masq_lib::test_utils::environment_guard::EnvironmentGuard; @@ -537,7 +537,7 @@ mod tests { fn dns_resolution_failures_are_reported_to_the_proxy_server() { let cryptdes = make_cryptde_pair(); let route = route_to_proxy_server(&cryptdes.main.public_key(), cryptdes.main); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let dns_resolve_failure = DnsResolveFailure_0v1::new(stream_key); let lcp = LiveCoresPackage::new( route, @@ -873,7 +873,7 @@ mod tests { let alias_cryptde = alias_cryptde(); let (proxy_server, _, proxy_server_recording_arc) = make_recorder(); let route = route_to_proxy_server(&main_cryptde.public_key(), main_cryptde); - let payload = make_response_payload(0, alias_cryptde); + let payload = make_response_payload(0); let lcp = LiveCoresPackage::new( route, encodex::( @@ -1903,10 +1903,7 @@ mod tests { &MessageType::ClientRequest(VersionedData::new( &crate::sub_lib::migrations::client_request_payload::MIGRATIONS, &ClientRequestPayload_0v1 { - stream_key: StreamKey::new( - PublicKey::new(b"1234"), - SocketAddr::from_str("1.2.3.4:1234").unwrap(), - ), + stream_key: StreamKey::make_meaningless_stream_key(), sequenced_packet: SequencedPacket::new(vec![1, 2, 3, 4], 1234, false), target_hostname: Some("hostname".to_string()), target_port: 1234, @@ -1932,10 +1929,7 @@ mod tests { &MessageType::DnsResolveFailed(VersionedData::new( &crate::sub_lib::migrations::dns_resolve_failure::MIGRATIONS, &DnsResolveFailure_0v1 { - stream_key: StreamKey::new( - PublicKey::new(b"1234"), - SocketAddr::from_str("1.2.3.4:1234").unwrap(), - ), + stream_key: StreamKey::make_meaningless_stream_key(), }, )), ) @@ -1975,10 +1969,7 @@ mod tests { &MessageType::ClientResponse(VersionedData::new( &crate::sub_lib::migrations::client_request_payload::MIGRATIONS, &ClientResponsePayload_0v1 { - stream_key: StreamKey::new( - PublicKey::new(b"1234"), - SocketAddr::from_str("1.2.3.4:1234").unwrap(), - ), + stream_key: StreamKey::make_meaningless_stream_key(), sequenced_packet: SequencedPacket::new(vec![1, 2, 3, 4], 1234, false), }, )), diff --git a/node/src/neighborhood/gossip_acceptor.rs b/node/src/neighborhood/gossip_acceptor.rs index 3700a448b..2548a7593 100644 --- a/node/src/neighborhood/gossip_acceptor.rs +++ b/node/src/neighborhood/gossip_acceptor.rs @@ -937,8 +937,7 @@ impl GossipHandler for StandardGossipHandler { let initial_neighborship_status = StandardGossipHandler::check_full_neighbor(database, gossip_source.ip()); - let patch = - self.compute_patch(&agrs, database.root(), neighborhood_metadata.min_hops as u8); + let patch = self.compute_patch(&agrs, database.root(), neighborhood_metadata.db_patch_size); let filtered_agrs = self.filter_agrs_by_patch(agrs, patch); let mut db_changed = self.identify_and_add_non_introductory_new_nodes( @@ -986,7 +985,7 @@ impl StandardGossipHandler { &self, agrs: &[AccessibleGossipRecord], root_node: &NodeRecord, - min_hops: u8, + db_patch_size: u8, ) -> HashSet { let agrs_by_key = agrs .iter() @@ -998,7 +997,7 @@ impl StandardGossipHandler { &mut patch, root_node.public_key(), &agrs_by_key, - min_hops, + db_patch_size, root_node, ); @@ -1331,12 +1330,12 @@ mod tests { use crate::neighborhood::gossip_producer::GossipProducerReal; use crate::neighborhood::node_record::NodeRecord; use crate::sub_lib::cryptde_null::CryptDENull; - use crate::sub_lib::neighborhood::{ConnectionProgressEvent, ConnectionProgressMessage, Hops}; + use crate::sub_lib::neighborhood::{ConnectionProgressEvent, ConnectionProgressMessage}; use crate::sub_lib::utils::time_t_timestamp; use crate::test_utils::neighborhood_test_utils::{ db_from_node, gossip_about_nodes_from_database, linearly_connect_nodes, make_meaningless_db, make_node_record, make_node_record_f, make_node_records, - public_keys_from_node_records, MIN_HOPS_FOR_TEST, + public_keys_from_node_records, DB_PATCH_SIZE_FOR_TEST, }; use crate::test_utils::unshared_test_utils::make_cpm_recipient; use crate::test_utils::{assert_contains, main_cryptde, vec_to_set}; @@ -1366,7 +1365,7 @@ mod tests { NeighborhoodMetadata { connection_progress_peers: vec![], cpm_recipient: make_cpm_recipient().0, - min_hops: MIN_HOPS_FOR_TEST, + db_patch_size: DB_PATCH_SIZE_FOR_TEST, } } @@ -2382,7 +2381,7 @@ mod tests { .build(); let agrs: Vec = gossip.try_into().unwrap(); - let result = subject.compute_patch(&agrs, node_a_db.root(), MIN_HOPS_FOR_TEST as u8); + let result = subject.compute_patch(&agrs, node_a_db.root(), DB_PATCH_SIZE_FOR_TEST); let expected_hashset = vec![ node_a.public_key().clone(), @@ -2434,7 +2433,7 @@ mod tests { .build(); let agrs: Vec = gossip.try_into().unwrap(); - let patch = subject.compute_patch(&agrs, node_a_db.root(), MIN_HOPS_FOR_TEST as u8); + let patch = subject.compute_patch(&agrs, node_a_db.root(), DB_PATCH_SIZE_FOR_TEST); let expected_hashset = vec![ node_a.public_key().clone(), @@ -2483,7 +2482,7 @@ mod tests { .build(); let agrs: Vec = gossip.try_into().unwrap(); - let patch = subject.compute_patch(&agrs, node_a_db.root(), MIN_HOPS_FOR_TEST as u8); + let patch = subject.compute_patch(&agrs, node_a_db.root(), DB_PATCH_SIZE_FOR_TEST); let expected_hashset = vec![ node_a.public_key().clone(), @@ -2565,17 +2564,17 @@ mod tests { assert_eq!(result, GossipAcceptanceResult::Ignored); } - fn assert_compute_patch(min_hops: Hops) { + fn assert_compute_patch(db_patch_size: u8) { let subject = StandardGossipHandler::new(Logger::new("assert_compute_patch")); // one node to finish hops and another node that's outside the patch - let nodes_count = min_hops as usize + 2; + let nodes_count = db_patch_size as usize + 2; let nodes = make_node_records(nodes_count as u16); let db = linearly_connect_nodes(&nodes); // gossip is intended for the first node (also root), thereby it's excluded let gossip = gossip_about_nodes_from_database(&db, &nodes[1..]); let agrs: Vec = gossip.try_into().unwrap(); - let result = subject.compute_patch(&agrs, db.root(), min_hops as u8); + let result = subject.compute_patch(&agrs, db.root(), db_patch_size); // last node is excluded because it is outside the patch let expected_nodes = &nodes[0..nodes_count - 1]; @@ -2584,13 +2583,11 @@ mod tests { } #[test] - fn patch_can_be_calculated_for_different_hops() { - assert_compute_patch(Hops::OneHop); - assert_compute_patch(Hops::TwoHops); - assert_compute_patch(Hops::ThreeHops); - assert_compute_patch(Hops::FourHops); - assert_compute_patch(Hops::FiveHops); - assert_compute_patch(Hops::SixHops); + fn patch_can_be_calculated_for_realistic_sizes() { + assert_compute_patch(3); + assert_compute_patch(4); + assert_compute_patch(5); + assert_compute_patch(6); } #[test] diff --git a/node/src/neighborhood/mod.rs b/node/src/neighborhood/mod.rs index 5a68bd485..4a0ba99ba 100644 --- a/node/src/neighborhood/mod.rs +++ b/node/src/neighborhood/mod.rs @@ -46,11 +46,11 @@ use crate::sub_lib::cryptde::{CryptDE, CryptData, PlainData}; use crate::sub_lib::dispatcher::{Component, StreamShutdownMsg}; use crate::sub_lib::hopper::{ExpiredCoresPackage, NoLookupIncipientCoresPackage}; use crate::sub_lib::hopper::{IncipientCoresPackage, MessageType}; -use crate::sub_lib::neighborhood::NodeRecordMetadataMessage; -use crate::sub_lib::neighborhood::RemoveNeighborMessage; -use crate::sub_lib::neighborhood::RouteQueryMessage; use crate::sub_lib::neighborhood::RouteQueryResponse; +use crate::sub_lib::neighborhood::UpdateNodeRecordMetadataMessage; use crate::sub_lib::neighborhood::{AskAboutDebutGossipMessage, NodeDescriptor}; +use crate::sub_lib::neighborhood::{ConfigurationChange, RemoveNeighborMessage}; +use crate::sub_lib::neighborhood::{ConfigurationChangeMessage, RouteQueryMessage}; use crate::sub_lib::neighborhood::{ConnectionProgressEvent, ExpectedServices}; use crate::sub_lib::neighborhood::{ConnectionProgressMessage, ExpectedService}; use crate::sub_lib::neighborhood::{DispatcherNodeQueryMessage, GossipFailure_0v1}; @@ -61,7 +61,6 @@ use crate::sub_lib::node_addr::NodeAddr; use crate::sub_lib::peer_actors::{BindMessage, NewPublicIp, StartMessage}; use crate::sub_lib::route::Route; use crate::sub_lib::route::RouteSegment; -use crate::sub_lib::set_consuming_wallet_message::SetConsumingWalletMessage; use crate::sub_lib::stream_handler_pool::DispatcherNodeQueryResponse; use crate::sub_lib::utils::{ db_connection_launch_panic, handle_ui_crash_request, NODE_MAILBOX_CAPACITY, @@ -95,6 +94,7 @@ pub struct Neighborhood { consuming_wallet_opt: Option, mode: NeighborhoodModeLight, min_hops: Hops, + db_patch_size: u8, next_return_route_id: u32, overall_connection_status: OverallConnectionStatus, chain: Chain, @@ -138,13 +138,35 @@ impl Handler for Neighborhood { } } -//TODO comes across as basically dead code -// I think the idea was to supply the wallet if wallets hadn't been generated until recently during the ongoing Node's run -impl Handler for Neighborhood { +impl Handler for Neighborhood { type Result = (); - fn handle(&mut self, msg: SetConsumingWalletMessage, _ctx: &mut Self::Context) -> Self::Result { - self.consuming_wallet_opt = Some(msg.wallet); + fn handle( + &mut self, + msg: ConfigurationChangeMessage, + _ctx: &mut Self::Context, + ) -> Self::Result { + match msg.change { + ConfigurationChange::UpdateConsumingWallet(new_wallet) => { + self.consuming_wallet_opt = Some(new_wallet) + } + ConfigurationChange::UpdateMinHops(new_min_hops) => { + self.set_min_hops_and_patch_size(new_min_hops); + if self.overall_connection_status.can_make_routes() { + let node_to_ui_recipient = self + .node_to_ui_recipient_opt + .as_ref() + .expect("UI gateway is dead"); + self.overall_connection_status + .update_ocs_stage_and_send_message_to_ui( + OverallConnectionStage::ConnectedToNeighbor, + node_to_ui_recipient, + &self.logger, + ); + } + self.search_for_a_new_route(); + } + } } } @@ -314,10 +336,14 @@ impl Handler for Neighborhood { } } -impl Handler for Neighborhood { +impl Handler for Neighborhood { type Result = (); - fn handle(&mut self, msg: NodeRecordMetadataMessage, _ctx: &mut Self::Context) -> Self::Result { + fn handle( + &mut self, + msg: UpdateNodeRecordMetadataMessage, + _ctx: &mut Self::Context, + ) -> Self::Result { match msg.metadata_change { NRMetadataChange::AddUnreachableHost { hostname } => { let public_key = msg.public_key; @@ -338,6 +364,15 @@ impl Handler for Neighborhood { } } +// GH-728 +impl Handler for Neighborhood { + type Result = (); + + fn handle(&mut self, msg: NewPasswordMessage, _ctx: &mut Self::Context) -> Self::Result { + self.handle_new_password(msg.new_password); + } +} + impl Handler for Neighborhood { type Result = (); @@ -361,14 +396,6 @@ impl Handler for Neighborhood { } } -impl Handler for Neighborhood { - type Result = (); - - fn handle(&mut self, msg: NewPasswordMessage, _ctx: &mut Self::Context) -> Self::Result { - self.handle_new_password(msg.new_password); - } -} - #[derive(Debug, PartialEq, Eq, Clone)] pub struct AccessibleGossipRecord { pub signed_gossip: PlainData, @@ -411,6 +438,7 @@ impl Neighborhood { pub fn new(cryptde: &'static dyn CryptDE, config: &BootstrapperConfig) -> Self { let neighborhood_config = &config.neighborhood_config; let min_hops = neighborhood_config.min_hops; + let db_patch_size = Neighborhood::calculate_db_patch_size(min_hops); let neighborhood_mode = &neighborhood_config.mode; let mode: NeighborhoodModeLight = neighborhood_mode.into(); let neighbor_configs = neighborhood_mode.neighbor_configs(); @@ -455,6 +483,7 @@ impl Neighborhood { consuming_wallet_opt: config.consuming_wallet_opt.clone(), mode, min_hops, + db_patch_size, next_return_route_id: 0, overall_connection_status, chain: config.blockchain_bridge_config.chain, @@ -473,17 +502,19 @@ impl Neighborhood { start: addr.clone().recipient::(), new_public_ip: addr.clone().recipient::(), route_query: addr.clone().recipient::(), - update_node_record_metadata: addr.clone().recipient::(), + update_node_record_metadata: addr + .clone() + .recipient::(), from_hopper: addr.clone().recipient::>(), gossip_failure: addr .clone() .recipient::>(), dispatcher_node_query: addr.clone().recipient::(), remove_neighbor: addr.clone().recipient::(), + configuration_change_msg_sub: addr.clone().recipient::(), stream_shutdown_sub: addr.clone().recipient::(), - set_consuming_wallet_sub: addr.clone().recipient::(), from_ui_message_sub: addr.clone().recipient::(), - new_password_sub: addr.clone().recipient::(), + new_password_sub: addr.clone().recipient::(), // GH-728 connection_progress_sub: addr.clone().recipient::(), } } @@ -748,7 +779,7 @@ impl Neighborhood { let neighborhood_metadata = NeighborhoodMetadata { connection_progress_peers: self.overall_connection_status.get_peer_addrs(), cpm_recipient, - min_hops: self.min_hops, + db_patch_size: self.db_patch_size, }; let acceptance_result = self.gossip_acceptor.handle( &mut self.neighborhood_database, @@ -828,9 +859,16 @@ impl Neighborhood { } fn check_connectedness(&mut self) { - if self.overall_connection_status.can_make_routes() { - return; + if !self.overall_connection_status.can_make_routes() { + self.search_for_a_new_route(); } + } + + fn search_for_a_new_route(&mut self) { + debug!( + self.logger, + "Searching for a {}-hop route...", self.min_hops + ); let msg = RouteQueryMessage { target_key_opt: None, target_component: Component::ProxyClient, @@ -1542,6 +1580,24 @@ impl Neighborhood { ); } + fn calculate_db_patch_size(min_hops: Hops) -> u8 { + let db_patch_size = if min_hops <= DEFAULT_MIN_HOPS { + DEFAULT_MIN_HOPS + } else { + min_hops + }; + + db_patch_size as u8 + } + + fn set_min_hops_and_patch_size(&mut self, new_min_hops: Hops) { + let (prev_min_hops, prev_db_patch_size) = (self.min_hops, self.db_patch_size); + self.min_hops = new_min_hops; + self.db_patch_size = Neighborhood::calculate_db_patch_size(new_min_hops); + debug!(self.logger, "The value of min_hops ({}-hop -> {}-hop) and db_patch_size ({} -> {}) has been changed", prev_min_hops, self.min_hops, prev_db_patch_size, self.db_patch_size); + } + + // GH-728 fn handle_new_password(&mut self, new_password: String) { self.db_password_opt = Some(new_password); } @@ -1618,7 +1674,8 @@ mod tests { use crate::sub_lib::hop::LiveHop; use crate::sub_lib::hopper::MessageType; use crate::sub_lib::neighborhood::{ - AskAboutDebutGossipMessage, ExpectedServices, NeighborhoodMode, + AskAboutDebutGossipMessage, ConfigurationChange, ConfigurationChangeMessage, + ExpectedServices, NeighborhoodMode, }; use crate::sub_lib::neighborhood::{NeighborhoodConfig, DEFAULT_RATE_PACK}; use crate::sub_lib::neighborhood::{NeighborhoodMetadata, RatePack}; @@ -1678,7 +1735,7 @@ mod tests { } #[test] - fn min_hops_is_set_inside_neighborhood() { + fn min_hops_and_db_patch_size_are_set_inside_neighborhood() { let min_hops = Hops::SixHops; let mode = NeighborhoodMode::Standard( NodeAddr::new(&make_ip(1), &[1234, 2345]), @@ -1697,7 +1754,9 @@ mod tests { ), ); - assert_eq!(subject.min_hops, Hops::SixHops); + let expected_db_patch_size = Neighborhood::calculate_db_patch_size(min_hops); + assert_eq!(subject.min_hops, min_hops); + assert_eq!(subject.db_patch_size, expected_db_patch_size); } #[test] @@ -2932,13 +2991,13 @@ mod tests { } #[test] - fn can_update_consuming_wallet() { + fn can_update_consuming_wallet_with_configuration_change_msg() { let cryptde = main_cryptde(); let system = System::new("can_update_consuming_wallet"); let (o, r, e, mut subject) = make_o_r_e_subject(); subject.min_hops = Hops::TwoHops; let addr: Addr = subject.start(); - let set_wallet_sub = addr.clone().recipient::(); + let configuration_change_msg_sub = addr.clone().recipient::(); let route_sub = addr.recipient::(); let expected_new_wallet = make_paying_wallet(b"new consuming wallet"); let expected_before_route = Route::round_trip( @@ -2962,9 +3021,11 @@ mod tests { let route_request_1 = route_sub.send(RouteQueryMessage::data_indefinite_route_request(None, 1000)); - let _ = set_wallet_sub.try_send(SetConsumingWalletMessage { - wallet: expected_new_wallet, - }); + configuration_change_msg_sub + .try_send(ConfigurationChangeMessage { + change: ConfigurationChange::UpdateConsumingWallet(expected_new_wallet), + }) + .unwrap(); let route_request_2 = route_sub.send(RouteQueryMessage::data_indefinite_route_request(None, 2000)); @@ -2978,6 +3039,145 @@ mod tests { assert_eq!(route_2, expected_after_route); } + #[test] + fn can_calculate_db_patch_size_from_min_hops() { + assert_eq!(Neighborhood::calculate_db_patch_size(Hops::OneHop), 3); + assert_eq!(Neighborhood::calculate_db_patch_size(Hops::TwoHops), 3); + assert_eq!(Neighborhood::calculate_db_patch_size(Hops::ThreeHops), 3); + assert_eq!(Neighborhood::calculate_db_patch_size(Hops::FourHops), 4); + assert_eq!(Neighborhood::calculate_db_patch_size(Hops::FiveHops), 5); + assert_eq!(Neighborhood::calculate_db_patch_size(Hops::SixHops), 6); + } + + #[test] + fn can_set_min_hops_and_db_patch_size() { + init_test_logging(); + let test_name = "can_set_min_hops_and_db_patch_size"; + let initial_min_hops = Hops::TwoHops; + let new_min_hops = Hops::FourHops; + let mut subject = make_standard_subject(); + subject.logger = Logger::new(test_name); + subject.min_hops = initial_min_hops; + + subject.set_min_hops_and_patch_size(new_min_hops); + + let expected_db_patch_size = Neighborhood::calculate_db_patch_size(new_min_hops); + assert_eq!(subject.min_hops, new_min_hops); + assert_eq!(subject.db_patch_size, expected_db_patch_size); + TestLogHandler::new().exists_log_containing(&format!( + "DEBUG: {test_name}: The value of min_hops (2-hop -> 4-hop) and db_patch_size (3 -> 4) has been changed" + )); + } + + #[test] + fn min_hops_can_be_changed_during_runtime_using_configuration_change_msg() { + init_test_logging(); + let test_name = "min_hops_can_be_changed_during_runtime_using_configuration_change_msg"; + let new_min_hops = Hops::FourHops; + let system = System::new(test_name); + let (ui_gateway, _, ui_gateway_recording) = make_recorder(); + let mut subject = make_standard_subject(); + subject.min_hops = Hops::TwoHops; + subject.logger = Logger::new(test_name); + subject.overall_connection_status.stage = OverallConnectionStage::RouteFound; + let subject_addr = subject.start(); + let peer_actors = peer_actors_builder().ui_gateway(ui_gateway).build(); + subject_addr.try_send(BindMessage { peer_actors }).unwrap(); + + subject_addr + .try_send(ConfigurationChangeMessage { + change: ConfigurationChange::UpdateMinHops(new_min_hops), + }) + .unwrap(); + + subject_addr + .try_send(AssertionsMessage { + assertions: Box::new(move |neighborhood: &mut Neighborhood| { + let expected_db_patch_size = + Neighborhood::calculate_db_patch_size(new_min_hops); + assert_eq!(neighborhood.min_hops, new_min_hops); + assert_eq!(neighborhood.db_patch_size, expected_db_patch_size); + assert_eq!( + neighborhood.overall_connection_status.stage, + OverallConnectionStage::ConnectedToNeighbor + ); + }), + }) + .unwrap(); + System::current().stop(); + system.run(); + let recording = ui_gateway_recording.lock().unwrap(); + let message_opt = recording.get_record_opt::(0); + assert_eq!( + message_opt, + Some(&NodeToUiMessage { + target: MessageTarget::AllClients, + body: UiConnectionChangeBroadcast { + stage: UiConnectionStage::ConnectedToNeighbor + } + .tmb(0), + }) + ); + TestLogHandler::new().assert_logs_contain_in_order(vec![ + &format!( + "DEBUG: {test_name}: The stage of OverallConnectionStatus has been changed \ + from RouteFound to ConnectedToNeighbor. A message to the UI was also sent." + ), + &format!("DEBUG: {test_name}: Searching for a 4-hop route..."), + ]); + } + + #[test] + fn ocs_stage_is_not_changed_in_case_routes_can_not_be_found_before_min_hops_change() { + init_test_logging(); + let test_name = + "ocs_stage_is_not_regressed_in_case_routes_can_not_be_found_before_min_hops_change"; + let new_min_hops = Hops::FourHops; + let system = System::new(test_name); + let (ui_gateway, _, ui_gateway_recording) = make_recorder(); + let mut subject = make_standard_subject(); + subject.min_hops = Hops::TwoHops; + subject.logger = Logger::new(test_name); + subject.overall_connection_status.stage = OverallConnectionStage::NotConnected; + let subject_addr = subject.start(); + let peer_actors = peer_actors_builder().ui_gateway(ui_gateway).build(); + subject_addr.try_send(BindMessage { peer_actors }).unwrap(); + + subject_addr + .try_send(ConfigurationChangeMessage { + change: ConfigurationChange::UpdateMinHops(new_min_hops), + }) + .unwrap(); + + subject_addr + .try_send(AssertionsMessage { + assertions: Box::new(move |neighborhood: &mut Neighborhood| { + let expected_db_patch_size = + Neighborhood::calculate_db_patch_size(new_min_hops); + assert_eq!(neighborhood.min_hops, new_min_hops); + assert_eq!(neighborhood.db_patch_size, expected_db_patch_size); + assert_eq!( + neighborhood.overall_connection_status.stage, + OverallConnectionStage::NotConnected + ); + }), + }) + .unwrap(); + System::current().stop(); + system.run(); + let recording = ui_gateway_recording.lock().unwrap(); + let message_opt = recording.get_record_opt::(0); + assert_eq!(message_opt, None); + let tlh = TestLogHandler::new(); + tlh.exists_no_log_containing(&format!( + "DEBUG: {test_name}: The stage of OverallConnectionStatus has been changed \ + from RouteFound to ConnectedToNeighbor. A message to the UI was also sent." + )); + tlh.exists_log_containing(&format!( + "DEBUG: {test_name}: Searching for a 4-hop route..." + )); + } + #[test] fn compose_route_query_response_returns_an_error_when_route_segment_keys_is_empty() { let mut subject = make_standard_subject(); @@ -3784,6 +3984,7 @@ mod tests { let mut subject = Neighborhood::new(main_cryptde(), &bootstrap_config); subject.node_to_ui_recipient_opt = Some(node_to_ui_recipient); subject.gossip_acceptor = Box::new(gossip_acceptor); + subject.db_patch_size = 6; let mut peer_2_db = db_from_node(&peer_2); peer_2_db.add_node(peer_1.clone()).unwrap(); peer_2_db.add_arbitrary_full_neighbor(peer_2.public_key(), peer_1.public_key()); @@ -3795,6 +3996,8 @@ mod tests { subject.handle_agrs(agrs, peer_2_socket_addr, make_cpm_recipient().0); + let (_, _, _, neighborhood_metadata) = handle_params_arc.lock().unwrap().remove(0); + assert_eq!(neighborhood_metadata.db_patch_size, 6); TestLogHandler::new() .exists_log_containing(&format!("Gossip from {} ignored", peer_2_socket_addr)); } @@ -3858,10 +4061,10 @@ mod tests { } #[test] - fn neighborhood_logs_when_three_hops_route_can_not_be_made() { + fn neighborhood_logs_when_min_hops_route_can_not_be_made() { init_test_logging(); - let test_name = "neighborhood_logs_when_three_hops_route_can_not_be_made"; - let mut subject: Neighborhood = make_neighborhood_with_linearly_connected_nodes(3); + let test_name = "neighborhood_logs_when_min_hops_route_can_not_be_made"; + let mut subject: Neighborhood = make_neighborhood_with_linearly_connected_nodes(5); let (ui_gateway, _, ui_gateway_arc) = make_recorder(); let (accountant, _, _) = make_recorder(); let node_to_ui_recipient = ui_gateway.start().recipient::(); @@ -3869,6 +4072,7 @@ mod tests { subject.logger = Logger::new(test_name); subject.node_to_ui_recipient_opt = Some(node_to_ui_recipient); subject.connected_signal_opt = Some(connected_signal); + subject.min_hops = Hops::FiveHops; let system = System::new(test_name); subject.handle_gossip_agrs( @@ -3882,9 +4086,12 @@ mod tests { let ui_recording = ui_gateway_arc.lock().unwrap(); assert_eq!(ui_recording.len(), 0); assert_eq!(subject.overall_connection_status.can_make_routes(), false); - TestLogHandler::new().exists_log_containing(&format!( - "DEBUG: {}: The connectivity check still can't find a good route.", - test_name + let tlh = TestLogHandler::new(); + tlh.exists_log_containing(&format!( + "DEBUG: {test_name}: The connectivity check still can't find a good route.", + )); + tlh.exists_no_log_containing(&format!( + "DEBUG: {test_name}: The connectivity check has found a 5-hop route." )); } @@ -5359,7 +5566,7 @@ mod tests { let addr = subject.start(); let system = System::new("test"); - let _ = addr.try_send(NodeRecordMetadataMessage { + let _ = addr.try_send(UpdateNodeRecordMetadataMessage { public_key: public_key.clone(), metadata_change: NRMetadataChange::AddUnreachableHost { hostname: unreachable_host.clone(), @@ -5677,6 +5884,7 @@ mod tests { let peer_actors = peer_actors_builder().build(); subject_addr.try_send(BindMessage { peer_actors }).unwrap(); + // GH-728 subject_addr .try_send(NewPasswordMessage { new_password: "borkety-bork".to_string(), diff --git a/node/src/neighborhood/neighborhood_database.rs b/node/src/neighborhood/neighborhood_database.rs index 79a4d1d1f..ceabe133a 100644 --- a/node/src/neighborhood/neighborhood_database.rs +++ b/node/src/neighborhood/neighborhood_database.rs @@ -161,7 +161,10 @@ impl NeighborhoodDatabase { Err(NodeRecordError::SelfNeighborAttempt(key)) => { Err(NeighborhoodDatabaseError::SelfNeighborAttempt(key)) } - Ok(_) => Ok(true), + Ok(_) => { + node.metadata.last_update = time_t_timestamp(); + Ok(true) + } }, None => Err(NodeKeyNotFound(node_key.clone())), } @@ -662,10 +665,15 @@ mod tests { &CryptDENull::from(this_node.public_key(), TEST_DEFAULT_CHAIN), ); subject.add_node(other_node.clone()).unwrap(); + subject.root_mut().metadata.last_update = time_t_timestamp() - 2; + let before = time_t_timestamp(); let result = subject.add_half_neighbor(other_node.public_key()); + let after = time_t_timestamp(); assert_eq!(Ok(true), result, "add_arbitrary_neighbor done goofed"); + assert!(before <= subject.root().metadata.last_update); + assert!(subject.root().metadata.last_update <= after); } #[test] diff --git a/node/src/neighborhood/overall_connection_status.rs b/node/src/neighborhood/overall_connection_status.rs index 04358f076..18e5b2a13 100644 --- a/node/src/neighborhood/overall_connection_status.rs +++ b/node/src/neighborhood/overall_connection_status.rs @@ -266,9 +266,8 @@ impl OverallConnectionStatus { node_to_ui_recipient: &Recipient, logger: &Logger, ) { - // TODO: Modify this fn when you're implementing the regressing transitions let prev_stage = self.stage; - if new_stage as usize > prev_stage as usize { + if new_stage != prev_stage { self.stage = new_stage; OverallConnectionStatus::send_message_to_ui(self.stage.into(), node_to_ui_recipient); debug!( @@ -909,9 +908,9 @@ mod tests { } #[test] - fn doesn_t_send_message_to_the_ui_in_case_stage_hasn_t_updated() { + fn does_not_send_message_to_the_ui_in_case_the_stage_has_not_updated() { init_test_logging(); - let test_name = "doesn_t_send_message_to_the_ui_in_case_stage_hasn_t_updated"; + let test_name = "does_not_send_message_to_the_ui_in_case_the_stage_has_not_updated"; let initial_stage = OverallConnectionStage::ConnectedToNeighbor; let new_stage = initial_stage; @@ -928,21 +927,29 @@ mod tests { } #[test] - fn doesn_t_send_a_message_to_ui_in_case_connection_drops_from_three_hops_to_connected_to_neighbor( - ) { + fn sends_a_message_to_ui_in_case_connection_drops_from_three_hops_to_connected_to_neighbor() { init_test_logging(); - let test_name = "doesn_t_send_a_message_to_ui_in_case_connection_drops_from_three_hops_to_connected_to_neighbor"; + let test_name = "sends_a_message_to_ui_in_case_connection_drops_from_three_hops_to_connected_to_neighbor"; let initial_stage = OverallConnectionStage::RouteFound; let new_stage = OverallConnectionStage::ConnectedToNeighbor; let (stage, message_opt) = assert_stage_and_node_to_ui_message(initial_stage, new_stage, test_name); - assert_eq!(stage, initial_stage); - assert_eq!(message_opt, None); + assert_eq!(stage, new_stage); + assert_eq!( + message_opt, + Some(NodeToUiMessage { + target: MessageTarget::AllClients, + body: UiConnectionChangeBroadcast { + stage: new_stage.into() + } + .tmb(0) + }) + ); TestLogHandler::new().exists_log_containing(&format!( - "TRACE: {}: There was an attempt to update the stage of OverallConnectionStatus \ - from {:?} to {:?}. The request has been discarded.", + "DEBUG: {}: The stage of OverallConnectionStatus has been changed \ + from {:?} to {:?}. A message to the UI was also sent.", test_name, initial_stage, new_stage )); } diff --git a/node/src/node_configurator/configurator.rs b/node/src/node_configurator/configurator.rs index 7a9da5f62..037736a2c 100644 --- a/node/src/node_configurator/configurator.rs +++ b/node/src/node_configurator/configurator.rs @@ -1,6 +1,7 @@ // Copyright (c) 2019-2021, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use std::path::PathBuf; +use std::str::FromStr; use actix::{Actor, Context, Handler, Recipient}; @@ -17,7 +18,7 @@ use masq_lib::ui_gateway::{ MessageBody, MessagePath, MessageTarget, NodeFromUiMessage, NodeToUiMessage, }; -use crate::blockchain::bip32::Bip32ECKeyProvider; +use crate::blockchain::bip32::Bip32EncryptionKeyProvider; use crate::blockchain::bip39::Bip39; use crate::database::db_initializer::DbInitializationConfig; use crate::database::db_initializer::{DbInitializer, DbInitializerReal}; @@ -26,6 +27,8 @@ use crate::db_config::persistent_configuration::{ PersistentConfigError, PersistentConfiguration, PersistentConfigurationReal, }; use crate::sub_lib::configurator::NewPasswordMessage; +use crate::sub_lib::neighborhood::ConfigurationChange::UpdateMinHops; +use crate::sub_lib::neighborhood::{ConfigurationChangeMessage, Hops}; use crate::sub_lib::peer_actors::BindMessage; use crate::sub_lib::utils::{db_connection_launch_panic, handle_ui_crash_request}; use crate::sub_lib::wallet::Wallet; @@ -37,7 +40,7 @@ use masq_lib::constants::{ UNKNOWN_ERROR, UNRECOGNIZED_MNEMONIC_LANGUAGE_ERROR, UNRECOGNIZED_PARAMETER, }; use masq_lib::logger::Logger; -use masq_lib::utils::derivation_path; +use masq_lib::utils::{derivation_path, to_string}; use rustc_hex::{FromHex, ToHex}; use tiny_hderive::bip32::ExtendedPrivKey; @@ -45,8 +48,9 @@ pub const CRASH_KEY: &str = "CONFIGURATOR"; pub struct Configurator { persistent_config: Box, - node_to_ui_sub: Option>, - new_password_subs: Option>>, + new_password_subs: Option>>, // GH-728 + node_to_ui_sub_opt: Option>, + configuration_change_msg_sub_opt: Option>, crashable: bool, logger: Logger, } @@ -59,8 +63,10 @@ impl Handler for Configurator { type Result = (); fn handle(&mut self, msg: BindMessage, _ctx: &mut Self::Context) -> Self::Result { - self.node_to_ui_sub = Some(msg.peer_actors.ui_gateway.node_to_ui_message_sub.clone()); - self.new_password_subs = Some(vec![msg.peer_actors.neighborhood.new_password_sub]) + self.node_to_ui_sub_opt = Some(msg.peer_actors.ui_gateway.node_to_ui_message_sub.clone()); + self.new_password_subs = Some(vec![msg.peer_actors.neighborhood.new_password_sub]); // GH-728 + self.configuration_change_msg_sub_opt = + Some(msg.peer_actors.neighborhood.configuration_change_msg_sub); } } @@ -107,8 +113,9 @@ impl Configurator { Box::new(PersistentConfigurationReal::new(Box::new(config_dao))); Configurator { persistent_config, - node_to_ui_sub: None, - new_password_subs: None, + new_password_subs: None, // GH-728 + node_to_ui_sub_opt: None, + configuration_change_msg_sub_opt: None, crashable, logger: Logger::new("Configurator"), } @@ -431,11 +438,7 @@ impl Configurator { let mnemonic = Bip39::mnemonic(mnemonic_type, language); let mnemonic_passphrase = Self::make_passphrase(passphrase_opt); let seed = Bip39::seed(&mnemonic, &mnemonic_passphrase); - let phrase_words: Vec = mnemonic - .into_phrase() - .split(' ') - .map(|w| w.to_string()) - .collect(); + let phrase_words: Vec = mnemonic.into_phrase().split(' ').map(to_string).collect(); Ok((seed, phrase_words)) } @@ -478,7 +481,7 @@ impl Configurator { } fn generate_wallet(seed: &Seed, derivation_path: &str) -> Result { - match Bip32ECKeyProvider::try_from((seed.as_bytes(), derivation_path)) { + match Bip32EncryptionKeyProvider::try_from((seed.as_bytes(), derivation_path)) { Err(e) => Err(( DERIVATION_PATH_ERROR, format!("Bad derivation-path syntax: {}: {}", e, derivation_path), @@ -547,12 +550,19 @@ impl Configurator { "earningWalletAddressOpt", )?; let start_block = Self::value_required(persistent_config.start_block(), "startBlock")?; + let max_block_count_opt = match persistent_config.max_block_count() { + Ok(value) => value, + Err(e) => panic!( + "Database corruption: Could not read max block count: {:?}", + e + ), + }; let neighborhood_mode = Self::value_required(persistent_config.neighborhood_mode(), "neighborhoodMode")? .to_string(); let port_mapping_protocol_opt = Self::value_not_required(persistent_config.mapping_protocol(), "portMappingProtocol")? - .map(|p| p.to_string()); + .map(to_string); let (consuming_wallet_private_key_opt, consuming_wallet_address_opt, past_neighbors) = match good_password_opt { Some(password) => { @@ -563,7 +573,7 @@ impl Configurator { Ok(bytes) => bytes, Err(e) => panic! ("Database corruption: consuming wallet private key '{}' cannot be converted from hexadecimal: {:?}", private_key_hex, e), }; - let key_pair = match Bip32ECKeyProvider::from_raw_secret(private_key_bytes.as_slice()) { + let key_pair = match Bip32EncryptionKeyProvider::from_raw_secret(private_key_bytes.as_slice()) { Ok(pair) => pair, Err(e) => panic!("Database corruption: consuming wallet private key '{}' is invalid: {:?}", private_key_hex, e), }; @@ -616,6 +626,7 @@ impl Configurator { clandestine_port, chain_name, gas_price, + max_block_count_opt, neighborhood_mode, consuming_wallet_private_key_opt, consuming_wallet_address_opt, @@ -690,33 +701,57 @@ impl Configurator { msg: UiSetConfigurationRequest, context_id: u64, ) -> MessageBody { + let configuration_change_msg_sub_opt = self.configuration_change_msg_sub_opt.clone(); + let logger = &self.logger; + debug!( + logger, + "A request from UI received: {:?} from context id: {}", msg, context_id + ); match Self::unfriendly_handle_set_configuration( msg, context_id, &mut self.persistent_config, + configuration_change_msg_sub_opt, + logger, ) { Ok(message_body) => message_body, - Err((code, msg)) => MessageBody { - opcode: "setConfiguration".to_string(), - path: MessagePath::Conversation(context_id), - payload: Err((code, msg)), - }, + Err((code, msg)) => { + error!( + logger, + "{}", + format!("The UiSetConfigurationRequest failed with an error {code}: {msg}") + ); + MessageBody { + opcode: "setConfiguration".to_string(), + path: MessagePath::Conversation(context_id), + payload: Err((code, msg)), + } + } } } fn unfriendly_handle_set_configuration( msg: UiSetConfigurationRequest, context_id: u64, - persist_config: &mut Box, + persistent_config: &mut Box, + configuration_change_msg_sub_opt: Option>, + logger: &Logger, ) -> Result { let password: Option = None; //prepared for an upgrade with parameters requiring the password match password { None => { if "gas-price" == &msg.name { - Self::set_gas_price(msg.value, persist_config)?; + Self::set_gas_price(msg.value, persistent_config)?; } else if "start-block" == &msg.name { - Self::set_start_block(msg.value, persist_config)?; + Self::set_start_block(msg.value, persistent_config)?; + } else if "min-hops" == &msg.name { + Self::set_min_hops( + msg.value, + persistent_config, + configuration_change_msg_sub_opt, + logger, + )?; } else { return Err(( UNRECOGNIZED_PARAMETER, @@ -746,6 +781,38 @@ impl Configurator { } } + fn set_min_hops( + string_number: String, + config: &mut Box, + configuration_change_msg_sub_opt: Option>, + logger: &Logger, + ) -> Result<(), (u64, String)> { + let min_hops = match Hops::from_str(&string_number) { + Ok(min_hops) => min_hops, + Err(e) => { + return Err((NON_PARSABLE_VALUE, format!("min hops: {:?}", e))); + } + }; + match config.set_min_hops(min_hops) { + Ok(_) => { + debug!( + logger, + "The value of min-hops has been changed to {}-hop inside the database", + min_hops + ); + configuration_change_msg_sub_opt + .as_ref() + .expect("Configurator is unbound") + .try_send(ConfigurationChangeMessage { + change: UpdateMinHops(min_hops), + }) + .expect("Neighborhood is dead"); + Ok(()) + } + Err(e) => Err((CONFIGURATOR_WRITE_ERROR, format!("min hops: {:?}", e))), + } + } + fn set_start_block( string_number: String, config: &mut Box, @@ -762,7 +829,7 @@ impl Configurator { fn send_to_ui_gateway(&self, target: MessageTarget, body: MessageBody) { let msg = NodeToUiMessage { target, body }; - self.node_to_ui_sub + self.node_to_ui_sub_opt .as_ref() .expect("Configurator is unbound") .try_send(msg) @@ -770,6 +837,7 @@ impl Configurator { } fn send_password_changes(&self, new_password: String) { + // GH-728 let msg = NewPasswordMessage { new_password }; self.new_password_subs .as_ref() @@ -827,14 +895,15 @@ mod tests { use masq_lib::test_utils::logging::{init_test_logging, TestLogHandler}; use super::*; - use crate::blockchain::bip32::Bip32ECKeyProvider; + use crate::blockchain::bip32::Bip32EncryptionKeyProvider; use crate::blockchain::bip39::Bip39; use crate::blockchain::test_utils::make_meaningless_phrase_words; use crate::database::db_initializer::{DbInitializer, DbInitializerReal}; use crate::sub_lib::accountant::{PaymentThresholds, ScanIntervals}; + use crate::sub_lib::configurator::NewPasswordMessage; use crate::sub_lib::cryptde::PublicKey as PK; use crate::sub_lib::cryptde::{CryptDE, PlainData}; - use crate::sub_lib::neighborhood::{NodeDescriptor, RatePack}; + use crate::sub_lib::neighborhood::{ConfigurationChange, NodeDescriptor, RatePack}; use crate::sub_lib::node_addr::NodeAddr; use crate::sub_lib::wallet::Wallet; use crate::test_utils::unshared_test_utils::{ @@ -865,11 +934,14 @@ mod tests { ))); let (recorder, _, _) = make_recorder(); let recorder_addr = recorder.start(); + let (neighborhood, _, _) = make_recorder(); + let neighborhood_addr = neighborhood.start(); let mut subject = Configurator::new(data_dir, false); - subject.node_to_ui_sub = Some(recorder_addr.recipient()); - subject.new_password_subs = Some(vec![]); + subject.node_to_ui_sub_opt = Some(recorder_addr.recipient()); + subject.configuration_change_msg_sub_opt = Some(neighborhood_addr.recipient()); + subject.new_password_subs = Some(vec![]); // GH-728 let _ = subject.handle_change_password( UiChangePasswordRequest { old_password_opt: None, @@ -1033,6 +1105,7 @@ mod tests { } ); let neighborhood_recording = neighborhood_recording_arc.lock().unwrap(); + // GH-728 assert_eq!( neighborhood_recording.get_record::(0), &NewPasswordMessage { @@ -1299,7 +1372,7 @@ mod tests { ExtendedPrivKey::derive(seed.as_slice(), derivation_path(0, 4).as_str()).unwrap(); let consuming_private_key = consuming_epk.secret().to_hex::().to_uppercase(); let consuming_wallet = Wallet::from( - Bip32ECKeyProvider::try_from((seed.as_slice(), derivation_path(0, 4).as_str())) + Bip32EncryptionKeyProvider::try_from((seed.as_slice(), derivation_path(0, 4).as_str())) .unwrap(), ); assert_eq!( @@ -1315,7 +1388,7 @@ mod tests { .unwrap() ); let earning_wallet = Wallet::from( - Bip32ECKeyProvider::try_from((seed.as_slice(), derivation_path(0, 5).as_str())) + Bip32EncryptionKeyProvider::try_from((seed.as_slice(), derivation_path(0, 5).as_str())) .unwrap(), ); assert_eq!( @@ -1693,7 +1766,7 @@ mod tests { ) .unwrap(); let earning_keypair = - Bip32ECKeyProvider::from_raw_secret(&earning_private_key.secret()).unwrap(); + Bip32EncryptionKeyProvider::from_raw_secret(&earning_private_key.secret()).unwrap(); let earning_wallet = Wallet::from(earning_keypair); let set_wallet_info_params = set_wallet_info_params_arc.lock().unwrap(); assert_eq!( @@ -1934,24 +2007,28 @@ mod tests { #[test] fn handle_set_configuration_works() { + init_test_logging(); + let test_name = "handle_set_configuration_works"; let set_start_block_params_arc = Arc::new(Mutex::new(vec![])); let (ui_gateway, _, ui_gateway_recording_arc) = make_recorder(); let persistent_config = PersistentConfigurationMock::new() .set_start_block_params(&set_start_block_params_arc) .set_start_block_result(Ok(())); - let subject = make_subject(Some(persistent_config)); + let mut subject = make_subject(Some(persistent_config)); + subject.logger = Logger::new(test_name); let subject_addr = subject.start(); let peer_actors = peer_actors_builder().ui_gateway(ui_gateway).build(); subject_addr.try_send(BindMessage { peer_actors }).unwrap(); + let msg = UiSetConfigurationRequest { + name: "start-block".to_string(), + value: "166666".to_string(), + }; + let context_id = 4444; subject_addr .try_send(NodeFromUiMessage { client_id: 1234, - body: UiSetConfigurationRequest { - name: "start-block".to_string(), - value: "166666".to_string(), - } - .tmb(4444), + body: msg.clone().tmb(context_id), }) .unwrap(); @@ -1964,6 +2041,10 @@ mod tests { assert_eq!(context_id, 4444); let check_start_block_params = set_start_block_params_arc.lock().unwrap(); assert_eq!(*check_start_block_params, vec![166666]); + TestLogHandler::new().exists_log_containing(&format!( + "DEBUG: {}: A request from UI received: {:?} from context id: {}", + test_name, msg, context_id + )); } #[test] @@ -2101,6 +2182,135 @@ mod tests { ); } + #[test] + fn handle_set_configuration_works_for_min_hops() { + init_test_logging(); + let test_name = "handle_set_configuration_works_for_min_hops"; + let new_min_hops = Hops::SixHops; + let set_min_hops_params_arc = Arc::new(Mutex::new(vec![])); + let persistent_config = PersistentConfigurationMock::new() + .set_min_hops_params(&set_min_hops_params_arc) + .set_min_hops_result(Ok(())); + let system = System::new("handle_set_configuration_works_for_min_hops"); + let (neighborhood, _, neighborhood_recording_arc) = make_recorder(); + let neighborhood_addr = neighborhood.start(); + let mut subject = make_subject(Some(persistent_config)); + subject.logger = Logger::new(test_name); + subject.configuration_change_msg_sub_opt = + Some(neighborhood_addr.recipient::()); + + let result = subject.handle_set_configuration( + UiSetConfigurationRequest { + name: "min-hops".to_string(), + value: new_min_hops.to_string(), + }, + 4000, + ); + + System::current().stop(); + system.run(); + let neighborhood_recording = neighborhood_recording_arc.lock().unwrap(); + let message_to_neighborhood = + neighborhood_recording.get_record::(0); + let set_min_hops_params = set_min_hops_params_arc.lock().unwrap(); + let min_hops_in_db = set_min_hops_params.get(0).unwrap(); + assert_eq!( + result, + MessageBody { + opcode: "setConfiguration".to_string(), + path: MessagePath::Conversation(4000), + payload: Ok(r#"{}"#.to_string()) + } + ); + assert_eq!( + message_to_neighborhood, + &ConfigurationChangeMessage { + change: ConfigurationChange::UpdateMinHops(new_min_hops) + } + ); + assert_eq!(*min_hops_in_db, new_min_hops); + TestLogHandler::new().exists_log_containing(&format!( + "DEBUG: {test_name}: The value of min-hops has been changed to {new_min_hops}-hop inside the database" + )); + } + + #[test] + fn handle_set_configuration_throws_err_for_invalid_min_hops() { + init_test_logging(); + let test_name = "handle_set_configuration_throws_err_for_invalid_min_hops"; + let mut subject = make_subject(None); + subject.logger = Logger::new(test_name); + + let result = subject.handle_set_configuration( + UiSetConfigurationRequest { + name: "min-hops".to_string(), + value: "600".to_string(), + }, + 4000, + ); + + assert_eq!( + result, + MessageBody { + opcode: "setConfiguration".to_string(), + path: MessagePath::Conversation(4000), + payload: Err(( + NON_PARSABLE_VALUE, + "min hops: \"Invalid value for min hops provided\"".to_string() + )) + } + ); + TestLogHandler::new().exists_log_containing(&format!( + "ERROR: {test_name}: The UiSetConfigurationRequest failed with an error \ + 281474976710668: min hops: \"Invalid value for min hops provided\"" + )); + } + + #[test] + fn handle_set_configuration_handles_failure_on_min_hops_database_issue() { + init_test_logging(); + let test_name = "handle_set_configuration_handles_failure_on_min_hops_database_issue"; + let persistent_config = PersistentConfigurationMock::new() + .set_min_hops_result(Err(PersistentConfigError::TransactionError)); + let system = + System::new("handle_set_configuration_handles_failure_on_min_hops_database_issue"); + let (neighborhood, _, neighborhood_recording_arc) = make_recorder(); + let configuration_change_msg_sub = neighborhood + .start() + .recipient::(); + let mut subject = make_subject(Some(persistent_config)); + subject.configuration_change_msg_sub_opt = Some(configuration_change_msg_sub); + subject.logger = Logger::new(test_name); + + let result = subject.handle_set_configuration( + UiSetConfigurationRequest { + name: "min-hops".to_string(), + value: "4".to_string(), + }, + 4000, + ); + + System::current().stop(); + system.run(); + let recording = neighborhood_recording_arc.lock().unwrap(); + assert!(recording.is_empty()); + assert_eq!( + result, + MessageBody { + opcode: "setConfiguration".to_string(), + path: MessagePath::Conversation(4000), + payload: Err(( + CONFIGURATOR_WRITE_ERROR, + "min hops: TransactionError".to_string() + )) + } + ); + TestLogHandler::new().exists_log_containing(&format!( + "ERROR: {test_name}: The UiSetConfigurationRequest failed with an error \ + 281474976710658: min hops: TransactionError" + )); + } + #[test] fn handle_set_configuration_complains_about_unexpected_parameter() { let persistent_config = PersistentConfigurationMock::new(); @@ -2211,6 +2421,7 @@ mod tests { .gas_price_result(Ok(2345)) .consuming_wallet_private_key_result(Ok(Some(consuming_wallet_private_key))) .mapping_protocol_result(Ok(Some(AutomapProtocol::Igdp))) + .max_block_count_result(Ok(Some(100000))) .neighborhood_mode_result(Ok(NeighborhoodModeLight::Standard)) .past_neighbors_result(Ok(Some(vec![node_descriptor.clone()]))) .earning_wallet_address_result(Ok(Some(earning_wallet_address.clone()))) @@ -2236,6 +2447,7 @@ mod tests { clandestine_port: 1234, chain_name: "ropsten".to_string(), gas_price: 2345, + max_block_count_opt: Some(100000), neighborhood_mode: String::from("standard"), consuming_wallet_private_key_opt: None, consuming_wallet_address_opt: None, @@ -2311,7 +2523,7 @@ mod tests { "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF".to_string(); let consuming_wallet_address = format!( "{:?}", - Bip32ECKeyProvider::from_raw_secret( + Bip32EncryptionKeyProvider::from_raw_secret( consuming_wallet_private_key .from_hex::>() .unwrap() @@ -2339,6 +2551,7 @@ mod tests { .consuming_wallet_private_key_params(&consuming_wallet_private_key_params_arc) .consuming_wallet_private_key_result(Ok(Some(consuming_wallet_private_key.clone()))) .mapping_protocol_result(Ok(Some(AutomapProtocol::Igdp))) + .max_block_count_result(Ok(None)) .neighborhood_mode_result(Ok(NeighborhoodModeLight::ConsumeOnly)) .past_neighbors_params(&past_neighbors_params_arc) .past_neighbors_result(Ok(Some(vec![node_descriptor.clone()]))) @@ -2366,6 +2579,7 @@ mod tests { clandestine_port: 1234, chain_name: "ropsten".to_string(), gas_price: 2345, + max_block_count_opt: None, neighborhood_mode: String::from("consume-only"), consuming_wallet_private_key_opt: Some(consuming_wallet_private_key), consuming_wallet_address_opt: Some(consuming_wallet_address), @@ -2404,6 +2618,118 @@ mod tests { assert_eq!(*past_neighbors_params, vec!["password".to_string()]) } + #[test] + fn configuration_handles_retrieving_all_possible_none_values() { + let persistent_config = PersistentConfigurationMock::new() + .blockchain_service_url_result(Ok(None)) + .current_schema_version_result("3") + .clandestine_port_result(Ok(1234)) + .chain_name_result("ropsten".to_string()) + .gas_price_result(Ok(2345)) + .earning_wallet_address_result(Ok(None)) + .start_block_result(Ok(3456)) + .max_block_count_result(Ok(None)) + .neighborhood_mode_result(Ok(NeighborhoodModeLight::ZeroHop)) + .mapping_protocol_result(Ok(None)) + .consuming_wallet_private_key_result(Ok(None)) + .past_neighbors_result(Ok(None)) + .rate_pack_result(Ok(RatePack { + routing_byte_rate: 0, + routing_service_rate: 0, + exit_byte_rate: 0, + exit_service_rate: 0, + })) + .scan_intervals_result(Ok(ScanIntervals { + pending_payable_scan_interval: Default::default(), + payable_scan_interval: Default::default(), + receivable_scan_interval: Default::default(), + })) + .payment_thresholds_result(Ok(PaymentThresholds { + debt_threshold_gwei: 0, + maturity_threshold_sec: 0, + payment_grace_period_sec: 0, + permanent_debt_allowed_gwei: 0, + threshold_interval_sec: 0, + unban_below_gwei: 0, + })); + let mut subject = make_subject(Some(persistent_config)); + + let (configuration, context_id) = + UiConfigurationResponse::fmb(subject.handle_configuration( + UiConfigurationRequest { + db_password_opt: None, + }, + 4321, + )) + .unwrap(); + + assert_eq!(context_id, 4321); + assert_eq!( + configuration, + UiConfigurationResponse { + blockchain_service_url_opt: None, + current_schema_version: "3".to_string(), + clandestine_port: 1234, + chain_name: "ropsten".to_string(), + gas_price: 2345, + max_block_count_opt: None, + neighborhood_mode: String::from("zero-hop"), + consuming_wallet_private_key_opt: None, + consuming_wallet_address_opt: None, + earning_wallet_address_opt: None, + port_mapping_protocol_opt: None, + past_neighbors: vec![], + payment_thresholds: UiPaymentThresholds { + threshold_interval_sec: 0, + debt_threshold_gwei: 0, + maturity_threshold_sec: 0, + payment_grace_period_sec: 0, + permanent_debt_allowed_gwei: 0, + unban_below_gwei: 0 + }, + rate_pack: UiRatePack { + routing_byte_rate: 0, + routing_service_rate: 0, + exit_byte_rate: 0, + exit_service_rate: 0 + }, + start_block: 3456, + scan_intervals: UiScanIntervals { + pending_payable_sec: 0, + payable_sec: 0, + receivable_sec: 0 + } + } + ); + } + + #[test] + #[should_panic( + expected = "Database corruption: Could not read max block count: DatabaseError(\"Corruption\")" + )] + fn configuration_panic_on_error_retrieving_max_block_count() { + let persistent_config = PersistentConfigurationMock::new() + .check_password_result(Ok(true)) + .blockchain_service_url_result(Ok(None)) + .current_schema_version_result("3") + .clandestine_port_result(Ok(1234)) + .chain_name_result("ropsten".to_string()) + .gas_price_result(Ok(2345)) + .earning_wallet_address_result(Ok(Some("4a5e43b54c6C56Ebf7".to_string()))) + .start_block_result(Ok(3456)) + .max_block_count_result(Err(PersistentConfigError::DatabaseError( + "Corruption".to_string(), + ))); + let mut subject = make_subject(Some(persistent_config)); + + let _result = subject.handle_configuration( + UiConfigurationRequest { + db_password_opt: Some("password".to_string()), + }, + 4321, + ); + } + #[test] fn configuration_handles_check_password_error() { let persistent_config = PersistentConfigurationMock::new() @@ -2441,6 +2767,7 @@ mod tests { "0x0123456789012345678901234567890123456789".to_string(), ))) .start_block_result(Ok(3456)) + .max_block_count_result(Ok(Some(100000))) .neighborhood_mode_result(Ok(NeighborhoodModeLight::ConsumeOnly)) .mapping_protocol_result(Ok(Some(AutomapProtocol::Igdp))) .consuming_wallet_private_key_result(cwpk); @@ -2547,8 +2874,9 @@ mod tests { fn from(persistent_config: Box) -> Self { Configurator { persistent_config, - node_to_ui_sub: None, - new_password_subs: None, + new_password_subs: None, // GH-728 + node_to_ui_sub_opt: None, + configuration_change_msg_sub_opt: None, crashable: false, logger: Logger::new("Configurator"), } diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 89d817012..3d1a93027 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -357,7 +357,7 @@ fn configure_database( #[cfg(test)] mod tests { use super::*; - use crate::blockchain::bip32::Bip32ECKeyProvider; + use crate::blockchain::bip32::Bip32EncryptionKeyProvider; use crate::bootstrapper::{BootstrapperConfig, RealUser}; use crate::database::db_initializer::{DbInitializer, DbInitializerReal}; use crate::db_config::config_dao::ConfigDaoReal; @@ -610,7 +610,8 @@ mod tests { let consuming_private_key_bytes: Vec = consuming_private_key.from_hex().unwrap(); let consuming_keypair = - Bip32ECKeyProvider::from_raw_secret(consuming_private_key_bytes.as_ref()).unwrap(); + Bip32EncryptionKeyProvider::from_raw_secret(consuming_private_key_bytes.as_ref()) + .unwrap(); assert_eq!( bootstrapper_config.consuming_wallet_opt, Some(Wallet::from(consuming_keypair)), @@ -1582,6 +1583,7 @@ mod tests { let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); running_test(); + let _guard = EnvironmentGuard::new(); let home_dir = Path::new("/home/cooga"); let home_dir_poly_main = home_dir.join(".local").join("MASQ").join("polygon-mainnet"); let home_dir_poly_mumbai = home_dir.join(".local").join("MASQ").join("polygon-mumbai"); diff --git a/node/src/node_configurator/unprivileged_parse_args_configuration.rs b/node/src/node_configurator/unprivileged_parse_args_configuration.rs index 2d86fb1ad..4238bd8d5 100644 --- a/node/src/node_configurator/unprivileged_parse_args_configuration.rs +++ b/node/src/node_configurator/unprivileged_parse_args_configuration.rs @@ -1,7 +1,7 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. use crate::accountant::DEFAULT_PENDING_TOO_LONG_SEC; -use crate::blockchain::bip32::Bip32ECKeyProvider; +use crate::blockchain::bip32::Bip32EncryptionKeyProvider; use crate::bootstrapper::BootstrapperConfig; use crate::db_config::persistent_configuration::{PersistentConfigError, PersistentConfiguration}; use crate::sub_lib::accountant::{PaymentThresholds, ScanIntervals, DEFAULT_EARNING_WALLET}; @@ -20,7 +20,7 @@ use masq_lib::constants::{DEFAULT_CHAIN, MASQ_URL_PREFIX}; use masq_lib::logger::Logger; use masq_lib::multi_config::MultiConfig; use masq_lib::shared_schema::{ConfiguratorError, ParamError}; -use masq_lib::utils::{AutomapProtocol, ExpectValue}; +use masq_lib::utils::{to_string, AutomapProtocol, ExpectValue}; use rustc_hex::FromHex; use std::net::{IpAddr, Ipv4Addr}; use std::str::FromStr; @@ -173,8 +173,8 @@ pub fn get_wallets( consuming_private_key ) }); - let key_pair = - Bip32ECKeyProvider::from_raw_secret(key_bytes.as_slice()).unwrap_or_else(|_| { + let key_pair = Bip32EncryptionKeyProvider::from_raw_secret(key_bytes.as_slice()) + .unwrap_or_else(|_| { panic!( "Wallet corruption: consuming wallet private key in invalid format: {:?}", key_bytes @@ -343,10 +343,8 @@ fn convert_ci_configs( match value_m!(multi_config, "neighbors", String) { None => Ok(None), Some(joined_configs) => { - let separate_configs: Vec = joined_configs - .split(',') - .map(|s| s.to_string()) - .collect_vec(); + let separate_configs: Vec = + joined_configs.split(',').map(to_string).collect_vec(); if separate_configs.is_empty() { Ok(None) } else { @@ -623,9 +621,9 @@ fn is_user_specified(multi_config: &MultiConfig, parameter: &str) -> bool { #[cfg(test)] mod tests { use super::*; - use crate::accountant::database_access_objects::dao_utils::ThresholdUtils; + use crate::accountant::db_access_objects::utils::ThresholdUtils; use crate::apps::app_node; - use crate::blockchain::bip32::Bip32ECKeyProvider; + use crate::blockchain::bip32::Bip32EncryptionKeyProvider; use crate::database::db_initializer::DbInitializationConfig; use crate::database::db_initializer::{DbInitializer, DbInitializerReal}; use crate::db_config::config_dao::{ConfigDao, ConfigDaoReal}; @@ -1558,7 +1556,8 @@ mod tests { assert_eq!( config.consuming_wallet_opt, Some(Wallet::from( - Bip32ECKeyProvider::from_raw_secret(consuming_private_key.as_slice()).unwrap() + Bip32EncryptionKeyProvider::from_raw_secret(consuming_private_key.as_slice()) + .unwrap() )), ); assert_eq!( @@ -2633,8 +2632,7 @@ mod tests { rate_pack_opt: Option, min_hops_opt: Option, ) -> PersistentConfigurationMock { - let consuming_wallet_private_key_opt = - consuming_wallet_private_key_opt.map(|x| x.to_string()); + let consuming_wallet_private_key_opt = consuming_wallet_private_key_opt.map(to_string); let earning_wallet_opt = match earning_wallet_address_opt { None => None, Some(address) => Some(Wallet::from_str(address).unwrap()), @@ -2653,9 +2651,7 @@ mod tests { let min_hops = min_hops_opt.unwrap_or(MIN_HOPS_FOR_TEST); PersistentConfigurationMock::new() .consuming_wallet_private_key_result(Ok(consuming_wallet_private_key_opt)) - .earning_wallet_address_result( - Ok(earning_wallet_address_opt.map(|ewa| ewa.to_string())), - ) + .earning_wallet_address_result(Ok(earning_wallet_address_opt.map(to_string))) .earning_wallet_result(Ok(earning_wallet_opt)) .gas_price_result(Ok(gas_price)) .past_neighbors_result(past_neighbors_result) diff --git a/node/src/node_test_utils.rs b/node/src/node_test_utils.rs index d5fbf01b0..f4c1a8d29 100644 --- a/node/src/node_test_utils.rs +++ b/node/src/node_test_utils.rs @@ -289,17 +289,6 @@ pub fn start_recorder_refcell_opt(recorder: &RefCell>) -> Addr< recorder.borrow_mut().take().unwrap().start() } -pub fn make_stream_handler_pool_subs_from( - stream_handler_pool_opt: Option, -) -> StreamHandlerPoolSubs { - let recorder = match stream_handler_pool_opt { - Some(recorder) => recorder, - None => Recorder::new(), - }; - let addr = recorder.start(); - make_stream_handler_pool_subs_from_recorder(&addr) -} - pub fn make_stream_handler_pool_subs_from_recorder(addr: &Addr) -> StreamHandlerPoolSubs { StreamHandlerPoolSubs { add_sub: recipient!(addr, AddStreamMsg), diff --git a/node/src/proxy_client/mod.rs b/node/src/proxy_client/mod.rs index 20f287b2d..d05cb505b 100644 --- a/node/src/proxy_client/mod.rs +++ b/node/src/proxy_client/mod.rs @@ -601,7 +601,7 @@ mod tests { #[should_panic(expected = "StreamHandlerPool unbound")] fn panics_if_unbound() { let request = ClientRequestPayload_0v1 { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), sequenced_packet: SequencedPacket { data: b"HEAD http://www.nyan.cat/ HTTP/1.1\r\n\r\n".to_vec(), sequence_number: 0, @@ -641,7 +641,7 @@ mod tests { fn logs_nonexistent_stream_key_during_dns_resolution_failure() { init_test_logging(); let cryptde = main_cryptde(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let stream_key_inner = stream_key.clone(); thread::spawn(move || { let system = System::new("logs_nonexistent_stream_key_during_dns_resolution_failure"); @@ -677,7 +677,7 @@ mod tests { init_test_logging(); let cryptde = main_cryptde(); let (hopper, hopper_awaiter, hopper_recording_arc) = make_recorder(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let return_route = make_meaningless_route(); let originator_key = make_meaningless_public_key(); let stream_key_inner = stream_key.clone(); @@ -744,7 +744,7 @@ mod tests { fn data_from_hopper_is_relayed_to_stream_handler_pool() { let cryptde = main_cryptde(); let request = ClientRequestPayload_0v1 { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), sequenced_packet: SequencedPacket { data: b"inbound data".to_vec(), sequence_number: 0, @@ -804,7 +804,7 @@ mod tests { init_test_logging(); let cryptde = main_cryptde(); let request = ClientRequestPayload_0v1 { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), sequenced_packet: SequencedPacket { data: b"inbound data".to_vec(), sequence_number: 0, @@ -861,7 +861,7 @@ mod tests { let main_cryptde = main_cryptde(); let alias_cryptde = alias_cryptde(); let request = ClientRequestPayload_0v1 { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), sequenced_packet: SequencedPacket { data: b"inbound data".to_vec(), sequence_number: 0, @@ -926,11 +926,12 @@ mod tests { #[test] fn inbound_server_data_is_translated_to_cores_packages() { init_test_logging(); + let test_name = "inbound_server_data_is_translated_to_cores_packages"; let (hopper, _, hopper_recording_arc) = make_recorder(); let (accountant, _, accountant_recording_arc) = make_recorder(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningful_stream_key(test_name); let data: &[u8] = b"An honest politician is one who, when he is bought, will stay bought."; - let system = System::new("inbound_server_data_is_translated_to_cores_packages"); + let system = System::new(test_name); let route = make_meaningless_route(); let mut subject = ProxyClient::new(ProxyClientConfig { cryptde: main_cryptde(), @@ -948,6 +949,7 @@ mod tests { paying_wallet: Some(make_wallet("paying")), }, ); + subject.logger = Logger::new(test_name); let subject_addr: Addr = subject.start(); let peer_actors = peer_actors_builder() .hopper(hopper) @@ -1068,17 +1070,18 @@ mod tests { ); assert_eq!(accountant_recording.len(), 2); let tlh = TestLogHandler::new(); - tlh.exists_log_containing(format!("ERROR: ProxyClient: Received InboundServerData from 1.2.3.4:5678: stream +dKB2Lsh3ET2TS/J/cexaanFQz4, sequence 1236, length {}; but no such known stream - ignoring", data.len()).as_str()); - tlh.exists_log_containing(format!("ERROR: ProxyClient: Received InboundServerData (last_data) from 1.2.3.4:5678: stream +dKB2Lsh3ET2TS/J/cexaanFQz4, sequence 1237, length {}; but no such known stream - ignoring", data.len()).as_str()); + tlh.exists_log_containing(format!("ERROR: {test_name}: Received InboundServerData from 1.2.3.4:5678: stream MBqy2yoLFeyqzyArXNTwzbNG16c, sequence 1236, length {}; but no such known stream - ignoring", data.len()).as_str()); + tlh.exists_log_containing(format!("ERROR: {test_name}: Received InboundServerData (last_data) from 1.2.3.4:5678: stream MBqy2yoLFeyqzyArXNTwzbNG16c, sequence 1237, length {}; but no such known stream - ignoring", data.len()).as_str()); } #[test] fn inbound_server_data_without_paying_wallet_does_not_report_exit_service() { init_test_logging(); let (accountant, _, accountant_recording_arc) = make_recorder(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let data: &[u8] = b"An honest politician is one who, when he is bought, will stay bought."; - let system = System::new("inbound_server_data_is_translated_to_cores_packages"); + let system = + System::new("inbound_server_data_without_paying_wallet_does_not_report_exit_service"); let mut subject = ProxyClient::new(ProxyClientConfig { cryptde: main_cryptde(), dns_servers: vec![SocketAddr::from_str("8.7.6.5:4321").unwrap()], @@ -1128,9 +1131,9 @@ mod tests { init_test_logging(); let (hopper, _, hopper_recording_arc) = make_recorder(); let (accountant, _, accountant_recording_arc) = make_recorder(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let data: &[u8] = b"An honest politician is one who, when he is bought, will stay bought."; - let system = System::new("inbound_server_data_is_translated_to_cores_packages"); + let system = System::new("error_creating_incipient_cores_package_is_logged_and_dropped"); let mut subject = ProxyClient::new(ProxyClientConfig { cryptde: main_cryptde(), dns_servers: vec![SocketAddr::from_str("8.7.6.5:4321").unwrap()], @@ -1178,7 +1181,7 @@ mod tests { let cryptde = main_cryptde(); let (hopper, _, hopper_recording_arc) = make_recorder(); let (accountant, _, accountant_recording_arc) = make_recorder(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let data: &[u8] = b"An honest politician is one who, when he is bought, will stay bought."; let system = System::new("new_return_route_overwrites_existing_return_route"); let mut subject = ProxyClient::new(ProxyClientConfig { diff --git a/node/src/proxy_client/stream_establisher.rs b/node/src/proxy_client/stream_establisher.rs index f6659e7d4..f25d7a9fe 100644 --- a/node/src/proxy_client/stream_establisher.rs +++ b/node/src/proxy_client/stream_establisher.rs @@ -128,7 +128,6 @@ mod tests { use super::*; use crate::sub_lib::proxy_server::ProxyProtocol; use crate::test_utils::main_cryptde; - use crate::test_utils::make_meaningless_stream_key; use crate::test_utils::recorder::make_recorder; use crate::test_utils::recorder::peer_actors_builder; use crate::test_utils::stream_connector_mock::StreamConnectorMock; @@ -179,7 +178,7 @@ mod tests { }; subject.spawn_stream_reader( &ClientRequestPayload_0v1 { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), sequenced_packet: SequencedPacket { data: vec![], sequence_number: 0, @@ -212,7 +211,7 @@ mod tests { assert_eq!( ibsd, InboundServerData { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), last_data: false, sequence_number: 0, source: SocketAddr::from_str("1.2.3.4:5678").unwrap(), diff --git a/node/src/proxy_client/stream_handler_pool.rs b/node/src/proxy_client/stream_handler_pool.rs index 20d7d6c9f..7f81f1b22 100644 --- a/node/src/proxy_client/stream_handler_pool.rs +++ b/node/src/proxy_client/stream_handler_pool.rs @@ -509,7 +509,6 @@ mod tests { use crate::test_utils::channel_wrapper_mocks::SenderWrapperMock; use crate::test_utils::main_cryptde; use crate::test_utils::make_meaningless_route; - use crate::test_utils::make_meaningless_stream_key; use crate::test_utils::make_wallet; use crate::test_utils::recorder::make_recorder; use crate::test_utils::recorder::peer_actors_builder; @@ -563,7 +562,7 @@ mod tests { #[test] fn dns_resolution_failure_sends_a_message_to_proxy_client() { let (proxy_client, proxy_client_awaiter, proxy_client_recording) = make_recorder(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); thread::spawn(move || { let system = System::new("dns_resolution_failure_sends_a_message_to_proxy_client"); let peer_actors = peer_actors_builder().proxy_client(proxy_client).build(); @@ -624,7 +623,7 @@ mod tests { #[test] fn non_terminal_payload_can_be_sent_over_existing_connection() { let cryptde = main_cryptde(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let client_request_payload = ClientRequestPayload_0v1 { stream_key: stream_key.clone(), sequenced_packet: SequencedPacket { @@ -688,7 +687,7 @@ mod tests { let originator_key = PublicKey::new(&b"men's souls"[..]); thread::spawn(move || { let client_request_payload = ClientRequestPayload_0v1 { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), sequenced_packet: SequencedPacket { data: b"These are the times".to_vec(), sequence_number: 0, @@ -737,7 +736,7 @@ mod tests { assert_eq!( proxy_client_recording.get_record::(0), &InboundServerData { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), last_data: true, sequence_number: 0, source: SocketAddr::from_str("2.3.4.5:80").unwrap(), @@ -757,7 +756,7 @@ mod tests { thread::spawn(move || { let peer_actors = peer_actors_builder().proxy_client(proxy_client).build(); let client_request_payload = ClientRequestPayload_0v1 { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), sequenced_packet: SequencedPacket { data: b"These are the times".to_vec(), sequence_number: 0, @@ -846,7 +845,7 @@ mod tests { assert_eq!( proxy_client_recording.get_record::(0), &InboundServerData { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), last_data: false, sequence_number: 0, source: SocketAddr::from_str("3.4.5.6:80").unwrap(), @@ -866,7 +865,7 @@ mod tests { thread::spawn(move || { let peer_actors = peer_actors_builder().proxy_client(proxy_client).build(); let client_request_payload = ClientRequestPayload_0v1 { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), sequenced_packet: SequencedPacket { data: b"These are the times".to_vec(), sequence_number: 0, @@ -955,7 +954,7 @@ mod tests { assert_eq!( proxy_client_recording.get_record::(0), &InboundServerData { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), last_data: false, sequence_number: 0, source: SocketAddr::from_str("3.4.5.6:80").unwrap(), @@ -967,13 +966,16 @@ mod tests { #[test] fn missing_hostname_for_nonexistent_stream_generates_log_and_termination_message() { init_test_logging(); + let test_name = + "missing_hostname_for_nonexistent_stream_generates_log_and_termination_message"; let cryptde = main_cryptde(); let (proxy_client, proxy_client_awaiter, proxy_client_recording_arc) = make_recorder(); let originator_key = PublicKey::new(&b"men's souls"[..]); + let stream_key = StreamKey::make_meaningful_stream_key(test_name); thread::spawn(move || { let peer_actors = peer_actors_builder().proxy_client(proxy_client).build(); let client_request_payload = ClientRequestPayload_0v1 { - stream_key: make_meaningless_stream_key(), + stream_key: stream_key.clone(), sequenced_packet: SequencedPacket { data: b"These are the times".to_vec(), sequence_number: 0, @@ -1010,7 +1012,7 @@ mod tests { assert_eq!( proxy_client_recording.get_record::(0), &InboundServerData { - stream_key: make_meaningless_stream_key(), + stream_key: stream_key.clone(), last_data: true, sequence_number: 0, source: error_socket_addr(), @@ -1020,7 +1022,7 @@ mod tests { TestLogHandler::new().exists_log_containing( format!( "ERROR: ProxyClient: Cannot open new stream with key {:?}: no hostname supplied", - make_meaningless_stream_key() + stream_key ) .as_str(), ); @@ -1042,7 +1044,7 @@ mod tests { .accountant(accountant) .build(); let client_request_payload = ClientRequestPayload_0v1 { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), sequenced_packet: SequencedPacket { data: b"These are the times".to_vec(), sequence_number: 0, @@ -1133,7 +1135,7 @@ mod tests { assert_eq!( proxy_client_recording.get_record::(0), &InboundServerData { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), last_data: false, sequence_number: 0, source: SocketAddr::from_str("3.4.5.6:80").unwrap(), @@ -1148,7 +1150,7 @@ mod tests { #[test] fn failing_to_make_a_connection_sends_an_error_response() { let cryptde = main_cryptde(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let lookup_ip_parameters = Arc::new(Mutex::new(vec![])); let (proxy_client, proxy_client_awaiter, proxy_client_recording_arc) = make_recorder(); let originator_key = PublicKey::new(&b"men's souls"[..]); @@ -1233,7 +1235,7 @@ mod tests { #[test] fn trying_to_write_to_disconnected_stream_writer_sends_an_error_response() { let cryptde = main_cryptde(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let lookup_ip_parameters = Arc::new(Mutex::new(vec![])); let write_parameters = Arc::new(Mutex::new(vec![])); let (proxy_client, proxy_client_awaiter, proxy_client_recording_arc) = make_recorder(); @@ -1353,7 +1355,7 @@ mod tests { fn bad_dns_lookup_produces_log_and_sends_error_response() { init_test_logging(); let cryptde = main_cryptde(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let (proxy_client, proxy_client_awaiter, proxy_client_recording_arc) = make_recorder(); let originator_key = PublicKey::new(&b"men's souls"[..]); thread::spawn(move || { @@ -1414,7 +1416,7 @@ mod tests { fn error_from_tx_to_writer_removes_stream() { init_test_logging(); let cryptde = main_cryptde(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let (proxy_client, _, _) = make_recorder(); let (hopper, _, _) = make_recorder(); let (accountant, _, _) = make_recorder(); @@ -1479,16 +1481,18 @@ mod tests { fn process_package_does_not_create_new_connection_for_zero_length_data_with_unfamiliar_stream_key( ) { init_test_logging(); + let test_name = "process_package_does_not_create_new_connection_for_zero_length_data_with_unfamiliar_stream_key"; let cryptde = main_cryptde(); let (hopper, _, hopper_recording_arc) = make_recorder(); let (accountant, _, accountant_recording_arc) = make_recorder(); + let stream_key = StreamKey::make_meaningful_stream_key(test_name); thread::spawn(move || { let peer_actors = peer_actors_builder() .hopper(hopper) .accountant(accountant) .build(); let client_request_payload = ClientRequestPayload_0v1 { - stream_key: make_meaningless_stream_key(), + stream_key: stream_key.clone(), sequenced_packet: SequencedPacket { data: vec![], sequence_number: 0, @@ -1528,7 +1532,7 @@ mod tests { tlh.await_log_containing( &format!( "Empty request payload received for nonexistent stream {:?} - ignoring", - make_meaningless_stream_key() + stream_key )[..], 2000, ); @@ -1553,7 +1557,7 @@ mod tests { ); let (stream_killer_tx, stream_killer_rx) = unbounded(); subject.stream_killer_rx = stream_killer_rx; - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let peer_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); { let mut inner = subject.inner.lock().unwrap(); @@ -1596,7 +1600,7 @@ mod tests { ); let (stream_killer_tx, stream_killer_rx) = unbounded(); subject.stream_killer_rx = stream_killer_rx; - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); stream_killer_tx.send((stream_key, 47)).unwrap(); subject.clean_up_dead_streams(); diff --git a/node/src/proxy_client/stream_reader.rs b/node/src/proxy_client/stream_reader.rs index 1fab8e395..3f3d82477 100644 --- a/node/src/proxy_client/stream_reader.rs +++ b/node/src/proxy_client/stream_reader.rs @@ -117,7 +117,6 @@ impl StreamReader { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::make_meaningless_stream_key; use crate::test_utils::recorder::make_recorder; use crate::test_utils::recorder::peer_actors_builder; use crate::test_utils::tokio_wrapper_mocks::ReadHalfWrapperMock; @@ -172,7 +171,7 @@ mod tests { let proxy_client_sub = rx.recv().unwrap(); let (stream_killer, stream_killer_params) = unbounded(); let mut subject = StreamReader { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), proxy_client_sub, stream, stream_killer, @@ -188,7 +187,7 @@ mod tests { assert_eq!( proxy_client_recording.get_record::(0), &InboundServerData { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), last_data: false, sequence_number: 0, source: SocketAddr::from_str("8.7.4.3:50").unwrap(), @@ -198,7 +197,7 @@ mod tests { assert_eq!( proxy_client_recording.get_record::(1), &InboundServerData { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), last_data: false, sequence_number: 1, source: SocketAddr::from_str("8.7.4.3:50").unwrap(), @@ -208,7 +207,7 @@ mod tests { assert_eq!( proxy_client_recording.get_record::(2), &InboundServerData { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), last_data: false, sequence_number: 2, source: SocketAddr::from_str("8.7.4.3:50").unwrap(), @@ -216,7 +215,10 @@ mod tests { }, ); let stream_killer_parameters = stream_killer_params.try_recv().unwrap(); - assert_eq!(stream_killer_parameters, (make_meaningless_stream_key(), 3)); + assert_eq!( + stream_killer_parameters, + (StreamKey::make_meaningless_stream_key(), 3) + ); } #[test] @@ -252,7 +254,7 @@ mod tests { let proxy_client_sub = rx.recv().unwrap(); let (stream_killer, stream_killer_params) = unbounded(); let mut subject = StreamReader { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), proxy_client_sub, stream: Box::new(stream), stream_killer, @@ -269,7 +271,7 @@ mod tests { assert_eq!( proxy_client_recording.get_record::(0), &InboundServerData { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), last_data: false, sequence_number: 0, source: SocketAddr::from_str("5.7.9.0:95").unwrap(), @@ -279,7 +281,7 @@ mod tests { assert_eq!( proxy_client_recording.get_record::(1), &InboundServerData { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), last_data: false, sequence_number: 1, source: SocketAddr::from_str("5.7.9.0:95").unwrap(), @@ -289,7 +291,7 @@ mod tests { assert_eq!( proxy_client_recording.get_record::(2), &InboundServerData { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), last_data: false, sequence_number: 2, source: SocketAddr::from_str("5.7.9.0:95").unwrap(), @@ -300,14 +302,17 @@ mod tests { let kill_stream_msg = stream_killer_params .try_recv() .expect("stream was not killed"); - assert_eq!(kill_stream_msg, (make_meaningless_stream_key(), 3)); + assert_eq!( + kill_stream_msg, + (StreamKey::make_meaningless_stream_key(), 3) + ); assert!(stream_killer_params.try_recv().is_err()); } #[test] fn receiving_0_bytes_kills_stream() { init_test_logging(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let (stream_killer, kill_stream_params) = unbounded(); let mut stream = ReadHalfWrapperMock::new(); stream.poll_read_results = vec![(vec![], Ok(Async::Ready(0)))]; @@ -342,7 +347,7 @@ mod tests { fn non_dead_stream_read_errors_log_but_do_not_shut_down() { init_test_logging(); let (proxy_client, proxy_client_awaiter, proxy_client_recording_arc) = make_recorder(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let (stream_killer, _) = unbounded(); let mut stream = ReadHalfWrapperMock::new(); stream.poll_read_results = vec![ @@ -387,7 +392,7 @@ mod tests { assert_eq!( proxy_client_recording.get_record::(0), &InboundServerData { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), last_data: false, sequence_number: 0, source: SocketAddr::from_str("6.5.4.1:8325").unwrap(), diff --git a/node/src/proxy_client/stream_writer.rs b/node/src/proxy_client/stream_writer.rs index 355443e40..c9842741a 100644 --- a/node/src/proxy_client/stream_writer.rs +++ b/node/src/proxy_client/stream_writer.rs @@ -155,7 +155,6 @@ impl StreamWriter { mod tests { use super::*; use crate::test_utils::channel_wrapper_mocks::ReceiverWrapperMock; - use crate::test_utils::make_meaningless_stream_key; use crate::test_utils::tokio_wrapper_mocks::WriteHalfWrapperMock; use masq_lib::test_utils::logging::init_test_logging; use masq_lib::test_utils::logging::TestLogHandler; @@ -171,7 +170,7 @@ mod tests { let packet_b: Vec = vec![2, 4, 10, 8, 6, 3]; let packet_c: Vec = vec![1, 0, 1, 2]; - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let mut rx_to_write = Box::new(ReceiverWrapperMock::new()); rx_to_write.poll_results = vec![ @@ -266,7 +265,7 @@ mod tests { #[test] fn stream_writer_returns_not_ready_when_the_stream_is_not_ready() { - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let mut rx_to_write = Box::new(ReceiverWrapperMock::new()); rx_to_write.poll_results = vec![ Ok(Async::Ready(Some(SequencedPacket { @@ -297,7 +296,7 @@ mod tests { fn stream_writer_returns_not_ready_when_the_channel_is_not_ready() { let mut rx_to_write = Box::new(ReceiverWrapperMock::new()); rx_to_write.poll_results = vec![Ok(Async::NotReady)]; - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let writer = WriteHalfWrapperMock::new().poll_write_result(Ok(Async::Ready(5))); let mut subject = StreamWriter::new( @@ -316,7 +315,7 @@ mod tests { fn stream_writer_logs_error_and_continues_when_it_gets_a_non_dead_stream_error() { init_test_logging(); let text_data = b"These are the times"; - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let mut rx_to_write = Box::new(ReceiverWrapperMock::new()); rx_to_write.poll_results = vec![ @@ -349,7 +348,7 @@ mod tests { #[test] fn stream_writer_attempts_to_write_until_successful_before_reading_new_messages_from_channel() { - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let first_data = &b"These are the times"[..]; let second_data = &b"These are the other times"[..]; @@ -391,7 +390,7 @@ mod tests { #[test] fn stream_writer_exits_if_channel_is_closed() { - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let mut rx_to_write = Box::new(ReceiverWrapperMock::new()); rx_to_write.poll_results = vec![ Ok(Async::Ready(Some(SequencedPacket { @@ -420,7 +419,7 @@ mod tests { rx_to_write.poll_results = vec![Err(())]; let writer = WriteHalfWrapperMock::new(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let peer_addr = SocketAddr::from_str("4.2.3.4:5678").unwrap(); let mut subject = StreamWriter::new(Box::new(writer), peer_addr, rx_to_write, stream_key); @@ -432,7 +431,7 @@ mod tests { fn dead_stream_error_generates_log_and_returns_err() { init_test_logging(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let mut rx_to_write = Box::new(ReceiverWrapperMock::new()); rx_to_write.poll_results = vec![ Ok(Async::Ready(Some(SequencedPacket { @@ -465,7 +464,7 @@ mod tests { #[test] fn stream_writer_reattempts_writing_packets_that_were_prevented_by_not_ready() { - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let mut rx = Box::new(ReceiverWrapperMock::new()); rx.poll_results = vec![ Ok(Async::Ready(Some(SequencedPacket { @@ -498,7 +497,7 @@ mod tests { #[test] fn stream_writer_resubmits_partial_packet_when_written_len_is_less_than_packet_len() { - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let mut rx = Box::new(ReceiverWrapperMock::new()); rx.poll_results = vec![ Ok(Async::Ready(Some(SequencedPacket::new( @@ -542,7 +541,7 @@ mod tests { init_test_logging(); let packet_a: Vec = vec![1, 3, 5, 9, 7]; - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let mut rx_to_write = Box::new(ReceiverWrapperMock::new()); rx_to_write.poll_results = vec![ @@ -594,7 +593,7 @@ mod tests { init_test_logging(); let packet_a: Vec = vec![1, 3, 5, 9, 7]; - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let mut rx_to_write = Box::new(ReceiverWrapperMock::new()); rx_to_write.poll_results = vec![ @@ -651,7 +650,7 @@ mod tests { init_test_logging(); let packet_a: Vec = vec![1, 3, 5, 9, 7]; - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let mut rx_to_write = Box::new(ReceiverWrapperMock::new()); rx_to_write.poll_results = vec![ diff --git a/node/src/proxy_server/client_request_payload_factory.rs b/node/src/proxy_server/client_request_payload_factory.rs index 1f8e45689..006f37a13 100644 --- a/node/src/proxy_server/client_request_payload_factory.rs +++ b/node/src/proxy_server/client_request_payload_factory.rs @@ -75,7 +75,7 @@ impl ClientRequestPayloadFactoryReal { mod tests { use super::*; use crate::sub_lib::proxy_server::ProxyProtocol; - use crate::test_utils::{main_cryptde, make_meaningless_stream_key}; + use crate::test_utils::main_cryptde; use masq_lib::constants::HTTP_PORT; use masq_lib::test_utils::logging::init_test_logging; use masq_lib::test_utils::logging::TestLogHandler; @@ -96,15 +96,16 @@ mod tests { data: data.clone().into(), }; let cryptde = main_cryptde(); + let stream_key = StreamKey::make_meaningless_stream_key(); let logger = Logger::new("test"); let subject = Box::new(ClientRequestPayloadFactoryReal::new()); - let result = subject.make(&ibcd, make_meaningless_stream_key(), cryptde, &logger); + let result = subject.make(&ibcd, stream_key, cryptde, &logger); assert_eq!( result, Some(ClientRequestPayload_0v1 { - stream_key: make_meaningless_stream_key(), + stream_key, sequenced_packet: SequencedPacket { data: data.into(), sequence_number: 1, @@ -120,6 +121,7 @@ mod tests { #[test] fn handles_http_with_no_port() { + let test_name = "handles_http_with_no_port"; let data = PlainData::new(&b"GET http://borkoed.com/fleebs.html HTTP/1.1\r\n\r\n"[..]); let ibcd = InboundClientData { timestamp: SystemTime::now(), @@ -131,15 +133,16 @@ mod tests { data: data.clone().into(), }; let cryptde = main_cryptde(); - let logger = Logger::new("test"); + let logger = Logger::new(test_name); + let stream_key = StreamKey::make_meaningful_stream_key(test_name); let subject = Box::new(ClientRequestPayloadFactoryReal::new()); - let result = subject.make(&ibcd, make_meaningless_stream_key(), cryptde, &logger); + let result = subject.make(&ibcd, stream_key, cryptde, &logger); assert_eq!( result, Some(ClientRequestPayload_0v1 { - stream_key: make_meaningless_stream_key(), + stream_key, sequenced_packet: SequencedPacket { data: data.into(), sequence_number: 1, @@ -184,16 +187,17 @@ mod tests { is_clandestine: false, data: data.clone().into(), }; + let stream_key = StreamKey::make_meaningless_stream_key(); let cryptde = main_cryptde(); let logger = Logger::new("test"); let subject = Box::new(ClientRequestPayloadFactoryReal::new()); - let result = subject.make(&ibcd, make_meaningless_stream_key(), cryptde, &logger); + let result = subject.make(&ibcd, stream_key, cryptde, &logger); assert_eq!( result, Some(ClientRequestPayload_0v1 { - stream_key: make_meaningless_stream_key(), + stream_key, sequenced_packet: SequencedPacket { data: data.into(), sequence_number: 0, @@ -209,6 +213,7 @@ mod tests { #[test] fn handles_tls_without_hostname() { + let test_name = "handles_tls_without_hostname"; let data = PlainData::new(&[ 0x16, // content_type: Handshake 0x00, 0x00, 0x00, 0x00, // version, length: don't care @@ -233,15 +238,16 @@ mod tests { data: data.clone().into(), }; let cryptde = main_cryptde(); - let logger = Logger::new("test"); + let logger = Logger::new(test_name); + let stream_key = StreamKey::make_meaningful_stream_key(test_name); let subject = Box::new(ClientRequestPayloadFactoryReal::new()); - let result = subject.make(&ibcd, make_meaningless_stream_key(), cryptde, &logger); + let result = subject.make(&ibcd, stream_key, cryptde, &logger); assert_eq!( result, Some(ClientRequestPayload_0v1 { - stream_key: make_meaningless_stream_key(), + stream_key, sequenced_packet: SequencedPacket { data: data.into(), sequence_number: 0, @@ -258,6 +264,7 @@ mod tests { #[test] fn makes_no_payload_if_origin_port_is_not_specified() { init_test_logging(); + let test_name = "makes_no_payload_if_origin_port_is_not_specified"; let ibcd = InboundClientData { timestamp: SystemTime::now(), peer_addr: SocketAddr::from_str("1.2.3.4:5678").unwrap(), @@ -268,20 +275,22 @@ mod tests { data: vec![0x10, 0x11, 0x12], }; let cryptde = main_cryptde(); - let logger = Logger::new("test"); + let logger = Logger::new(test_name); + let stream_key = StreamKey::make_meaningful_stream_key(test_name); let subject = Box::new(ClientRequestPayloadFactoryReal::new()); - let result = subject.make(&ibcd, make_meaningless_stream_key(), cryptde, &logger); + let result = subject.make(&ibcd, stream_key, cryptde, &logger); assert_eq!(result, None); TestLogHandler::new().exists_log_containing( - "ERROR: test: No origin port specified with 3-byte non-clandestine packet: [16, 17, 18]", + &format!("ERROR: {test_name}: No origin port specified with 3-byte non-clandestine packet: [16, 17, 18]"), ); } #[test] fn makes_no_payload_if_origin_port_is_unknown() { init_test_logging(); + let test_name = "makes_no_payload_if_origin_port_is_unknown"; let ibcd = InboundClientData { timestamp: SystemTime::now(), peer_addr: SocketAddr::from_str("1.2.3.4:5678").unwrap(), @@ -292,13 +301,14 @@ mod tests { data: vec![0x10, 0x11, 0x12], }; let cryptde = main_cryptde(); - let logger = Logger::new("test"); + let logger = Logger::new(test_name); + let stream_key = StreamKey::make_meaningful_stream_key(test_name); let subject = Box::new(ClientRequestPayloadFactoryReal::new()); - let result = subject.make(&ibcd, make_meaningless_stream_key(), cryptde, &logger); + let result = subject.make(&ibcd, stream_key, cryptde, &logger); assert_eq!(result, None); - TestLogHandler::new ().exists_log_containing ("ERROR: test: No protocol associated with origin port 1234 for 3-byte non-clandestine packet: [16, 17, 18]"); + TestLogHandler::new().exists_log_containing(&format!("ERROR: {test_name}: No protocol associated with origin port 1234 for 3-byte non-clandestine packet: [16, 17, 18]")); } #[test] @@ -317,7 +327,12 @@ mod tests { let subject = Box::new(ClientRequestPayloadFactoryReal::new()); let result = subject - .make(&ibcd, make_meaningless_stream_key(), cryptde, &logger) + .make( + &ibcd, + StreamKey::make_meaningless_stream_key(), + cryptde, + &logger, + ) .unwrap(); assert_eq!(result.sequenced_packet.sequence_number, 1); @@ -326,6 +341,7 @@ mod tests { #[test] fn makes_no_payload_if_sequence_number_is_unknown() { init_test_logging(); + let test_name = "makes_no_payload_if_sequence_number_is_unknown"; let ibcd = InboundClientData { timestamp: SystemTime::now(), peer_addr: SocketAddr::from_str("1.2.3.4:80").unwrap(), @@ -336,14 +352,15 @@ mod tests { data: vec![1, 3, 5, 7], }; let cryptde = main_cryptde(); - let logger = Logger::new("test"); + let logger = Logger::new(test_name); + let stream_key = StreamKey::make_meaningful_stream_key(test_name); let subject = Box::new(ClientRequestPayloadFactoryReal::new()); - let result = subject.make(&ibcd, make_meaningless_stream_key(), cryptde, &logger); + let result = subject.make(&ibcd, stream_key, cryptde, &logger); assert_eq!(result, None); - TestLogHandler::new().exists_log_containing( - "ERROR: test: internal error: got IBCD with no sequence number and 4 bytes", - ); + TestLogHandler::new().exists_log_containing(&format!( + "ERROR: {test_name}: internal error: got IBCD with no sequence number and 4 bytes" + )); } } diff --git a/node/src/proxy_server/mod.rs b/node/src/proxy_server/mod.rs index 3f2ed1357..bd8f62d4a 100644 --- a/node/src/proxy_server/mod.rs +++ b/node/src/proxy_server/mod.rs @@ -24,7 +24,7 @@ use crate::sub_lib::dispatcher::InboundClientData; use crate::sub_lib::dispatcher::{Endpoint, StreamShutdownMsg}; use crate::sub_lib::hopper::{ExpiredCoresPackage, IncipientCoresPackage}; use crate::sub_lib::neighborhood::RouteQueryResponse; -use crate::sub_lib::neighborhood::{ExpectedService, NodeRecordMetadataMessage}; +use crate::sub_lib::neighborhood::{ExpectedService, UpdateNodeRecordMetadataMessage}; use crate::sub_lib::neighborhood::{ExpectedServices, RatePack}; use crate::sub_lib::neighborhood::{NRMetadataChange, RouteQueryMessage}; use crate::sub_lib::peer_actors::BindMessage; @@ -33,7 +33,6 @@ use crate::sub_lib::proxy_server::ClientRequestPayload_0v1; use crate::sub_lib::proxy_server::ProxyServerSubs; use crate::sub_lib::proxy_server::{AddReturnRouteMessage, AddRouteMessage}; use crate::sub_lib::route::Route; -use crate::sub_lib::set_consuming_wallet_message::SetConsumingWalletMessage; use crate::sub_lib::stream_handler_pool::TransmitDataMsg; use crate::sub_lib::stream_key::StreamKey; use crate::sub_lib::ttl_hashmap::TtlHashMap; @@ -61,7 +60,7 @@ struct ProxyServerOutSubs { hopper: Recipient, accountant: Recipient, route_source: Recipient, - update_node_record_metadata: Recipient, + update_node_record_metadata: Recipient, add_return_route: Recipient, add_route: Recipient, stream_shutdown_sub: Recipient, @@ -108,21 +107,6 @@ impl Handler for ProxyServer { } } -//TODO comes across as basically dead code -// I think the idea was to supply the wallet if wallets hadn't been generated until recently, without the need to kill the Node -// I also found out that there is a test for this, but it changes nothing on it's normally unused -impl Handler for ProxyServer { - type Result = (); - - fn handle( - &mut self, - _msg: SetConsumingWalletMessage, - _ctx: &mut Self::Context, - ) -> Self::Result { - self.consuming_wallet_balance = Some(0); - } -} - impl Handler for ProxyServer { type Result = (); @@ -247,16 +231,16 @@ impl ProxyServer { add_return_route: recipient!(addr, AddReturnRouteMessage), add_route: recipient!(addr, AddRouteMessage), stream_shutdown_sub: recipient!(addr, StreamShutdownMsg), - set_consuming_wallet_sub: recipient!(addr, SetConsumingWalletMessage), node_from_ui: recipient!(addr, NodeFromUiMessage), } } fn handle_dns_resolve_failure(&mut self, msg: &ExpiredCoresPackage) { - let return_route_info = match self.get_return_route_info(&msg.remaining_route) { - Some(rri) => rri, - None => return, // TODO: Eventually we'll have to do something better here, but we'll probably need some heuristics. - }; + let return_route_info = + match self.get_return_route_info(&msg.remaining_route, "dns resolve failure") { + Some(rri) => rri, + None => return, // TODO: Eventually we'll have to do something better here, but we'll probably need some heuristics. + }; let exit_public_key = { // ugly, ugly let self_public_key = self.main_cryptde.public_key(); @@ -283,7 +267,7 @@ impl ProxyServer { .as_ref() .expect("Neighborhood unbound in ProxyServer") .update_node_record_metadata - .try_send(NodeRecordMetadataMessage { + .try_send(UpdateNodeRecordMetadataMessage { public_key: exit_public_key.clone(), metadata_change: NRMetadataChange::AddUnreachableHost { hostname: server_name, @@ -342,10 +326,11 @@ impl ProxyServer { "Relaying ClientResponsePayload (stream key {}, sequence {}, length {}) from Hopper to Dispatcher for client", response.stream_key, response.sequenced_packet.sequence_number, response.sequenced_packet.data.len() ); - let return_route_info = match self.get_return_route_info(&msg.remaining_route) { - Some(rri) => rri, - None => return, - }; + let return_route_info = + match self.get_return_route_info(&msg.remaining_route, "client response") { + Some(rri) => rri, + None => return, + }; self.report_response_services_consumed( &return_route_info, response.sequenced_packet.data.len(), @@ -399,7 +384,7 @@ impl ProxyServer { let http_data = HttpProtocolPack {}.find_host(&msg.data.clone().into()); match http_data { Some(ref host) if host.port == Some(443) => { - let stream_key = self.make_stream_key(msg); + let stream_key = self.find_or_generate_stream_key(msg); self.tunneled_hosts.insert(stream_key, host.name.clone()); self.subs .as_ref() @@ -481,7 +466,7 @@ impl ProxyServer { } } - fn make_stream_key(&mut self, ibcd: &InboundClientData) -> StreamKey { + fn find_or_generate_stream_key(&mut self, ibcd: &InboundClientData) -> StreamKey { match self.keys_and_addrs.b_to_a(&ibcd.peer_addr) { Some(stream_key) => { debug!( @@ -493,9 +478,7 @@ impl ProxyServer { stream_key } None => { - let stream_key = self - .stream_key_factory - .make(self.main_cryptde.public_key(), ibcd.peer_addr); + let stream_key = self.stream_key_factory.make(); self.keys_and_addrs.insert(stream_key, ibcd.peer_addr); debug!( self.logger, @@ -755,7 +738,11 @@ impl ProxyServer { } } - fn get_return_route_info(&self, remaining_route: &Route) -> Option> { + fn get_return_route_info( + &self, + remaining_route: &Route, + source: &str, + ) -> Option> { let mut mut_remaining_route = remaining_route.clone(); mut_remaining_route .shift(self.main_cryptde) @@ -770,7 +757,7 @@ impl ProxyServer { match self.route_ids_to_return_routes.get(&return_route_id) { Some(rri) => Some(rri), None => { - error!(self.logger, "Can't report services consumed: received response with bogus return-route ID {}. Ignoring", return_route_id); + error!(self.logger, "Can't report services consumed: received response with bogus return-route ID {} for {}. Ignoring", return_route_id, source); None } } @@ -884,7 +871,7 @@ impl IBCDHelper for IBCDHelperReal { .expect("Dispatcher is dead"); return Err("Browser request rejected due to missing consuming wallet".to_string()); } - let stream_key = proxy.make_stream_key(&msg); + let stream_key = proxy.find_or_generate_stream_key(&msg); let timestamp = msg.timestamp; let payload = match proxy.make_payload(msg, &stream_key) { Ok(payload) => payload, @@ -1008,15 +995,14 @@ enum ExitServiceSearch { } trait StreamKeyFactory: Send { - fn make(&self, public_key: &PublicKey, peer_addr: SocketAddr) -> StreamKey; + fn make(&self) -> StreamKey; } struct StreamKeyFactoryReal {} impl StreamKeyFactory for StreamKeyFactoryReal { - fn make(&self, public_key: &PublicKey, peer_addr: SocketAddr) -> StreamKey { - // TODO: Replace this implementation - StreamKey::new(public_key.clone(), peer_addr) + fn make(&self) -> StreamKey { + StreamKey::new() } } @@ -1045,6 +1031,7 @@ mod tests { use crate::sub_lib::ttl_hashmap::TtlHashMap; use crate::sub_lib::versioned_data::VersionedData; use crate::test_utils::make_paying_wallet; + use crate::test_utils::make_request_payload; use crate::test_utils::make_wallet; use crate::test_utils::recorder::make_recorder; use crate::test_utils::recorder::peer_actors_builder; @@ -1053,7 +1040,6 @@ mod tests { use crate::test_utils::zero_hop_route_response; use crate::test_utils::{alias_cryptde, rate_pack}; use crate::test_utils::{main_cryptde, make_meaningless_route}; - use crate::test_utils::{make_meaningless_stream_key, make_request_payload}; use actix::System; use crossbeam_channel::unbounded; use masq_lib::constants::{HTTP_PORT, TLS_PORT}; @@ -1083,7 +1069,7 @@ mod tests { hopper: recipient!(addr, IncipientCoresPackage), accountant: recipient!(addr, ReportServicesConsumedMessage), route_source: recipient!(addr, RouteQueryMessage), - update_node_record_metadata: recipient!(addr, NodeRecordMetadataMessage), + update_node_record_metadata: recipient!(addr, UpdateNodeRecordMetadataMessage), add_return_route: recipient!(addr, AddReturnRouteMessage), add_route: recipient!(addr, AddRouteMessage), stream_shutdown_sub: recipient!(addr, StreamShutdownMsg), @@ -1091,16 +1077,13 @@ mod tests { } struct StreamKeyFactoryMock { - make_parameters: Arc>>, + make_parameters: Arc>>, make_results: RefCell>, } impl StreamKeyFactory for StreamKeyFactoryMock { - fn make(&self, key: &PublicKey, peer_addr: SocketAddr) -> StreamKey { - self.make_parameters - .lock() - .unwrap() - .push((key.clone(), peer_addr)); + fn make(&self) -> StreamKey { + self.make_parameters.lock().unwrap().push(()); self.make_results.borrow_mut().remove(0) } } @@ -1113,10 +1096,7 @@ mod tests { } } - fn make_parameters( - mut self, - params: &Arc>>, - ) -> StreamKeyFactoryMock { + fn make_parameters(mut self, params: &Arc>>) -> StreamKeyFactoryMock { self.make_parameters = params.clone(); self } @@ -1212,7 +1192,7 @@ mod tests { })); let (proxy_server_mock, _, proxy_server_recording_arc) = make_recorder(); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let expected_data = http_request.to_vec(); let msg_from_dispatcher = InboundClientData { timestamp: SystemTime::now(), @@ -1278,10 +1258,7 @@ mod tests { let record = recording.get_record::(0); assert_eq!(record, &expected_pkg); let mut make_parameters = make_parameters_arc_a.lock().unwrap(); - assert_eq!( - make_parameters.remove(0), - (main_cryptde.public_key().clone(), socket_addr) - ); + assert_eq!(make_parameters.remove(0), ()); let recording = neighborhood_recording_arc.lock().unwrap(); let record = recording.get_record::(0); assert_eq!( @@ -1311,7 +1288,7 @@ mod tests { let route = Route { hops: vec![] }; let (dispatcher_mock, _, dispatcher_recording_arc) = make_recorder(); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let request_data = http_request.to_vec(); let msg_from_dispatcher = InboundClientData { timestamp: SystemTime::now(), @@ -1393,10 +1370,7 @@ mod tests { let dispatcher_record = dispatcher_recording.get_record::(0); assert_eq!(dispatcher_record, &expected_tdm); let mut make_parameters = make_parameters_arc.lock().unwrap(); - assert_eq!( - make_parameters.remove(0), - (main_cryptde.public_key().clone(), socket_addr) - ); + assert_eq!(make_parameters.remove(0), ()); let hopper_recording = hopper_recording_arc.lock().unwrap(); let hopper_record = hopper_recording.get_record::(0); @@ -1427,7 +1401,7 @@ mod tests { false, ); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); subject .keys_and_addrs .insert(stream_key.clone(), socket_addr.clone()); @@ -1504,7 +1478,7 @@ mod tests { )); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let request_data = http_request.to_vec(); let msg_from_dispatcher = InboundClientData { timestamp: SystemTime::now(), @@ -1576,7 +1550,7 @@ mod tests { )); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let request_data = http_request.to_vec(); let msg_from_dispatcher = InboundClientData { timestamp: SystemTime::now(), @@ -1644,7 +1618,7 @@ mod tests { let (neighborhood, _, neighborhood_log_arc) = make_recorder(); let (dispatcher, _, dispatcher_log_arc) = make_recorder(); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let expected_data = http_request.to_vec(); let msg_from_dispatcher = InboundClientData { timestamp: SystemTime::now(), @@ -1704,7 +1678,7 @@ mod tests { let (neighborhood, _, neighborhood_log_arc) = make_recorder(); let (dispatcher, _, dispatcher_log_arc) = make_recorder(); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let expected_data = tls_request.to_vec(); let msg_from_dispatcher = InboundClientData { timestamp: SystemTime::now(), @@ -1763,7 +1737,7 @@ mod tests { let expected_data = b"GET /index.html HTTP/1.1\r\nHost: nowhere.com\r\n\r\n".to_vec(); let expected_data_inner = expected_data.clone(); let expected_route = zero_hop_route_response(main_cryptde.public_key(), main_cryptde); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let (hopper, hopper_awaiter, hopper_log_arc) = make_recorder(); let neighborhood = Recorder::new().route_query_response(Some(expected_route.clone())); let neighborhood_log_arc = neighborhood.get_recording(); @@ -1843,7 +1817,7 @@ mod tests { let expected_data = b"Fake TLS request".to_vec(); let expected_data_inner = expected_data.clone(); let expected_route = zero_hop_route_response(main_cryptde.public_key(), main_cryptde); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let (hopper, hopper_awaiter, hopper_log_arc) = make_recorder(); let neighborhood = Recorder::new().route_query_response(Some(expected_route.clone())); let neighborhood_log_arc = neighborhood.get_recording(); @@ -1933,7 +1907,7 @@ mod tests { ), })); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let expected_data = http_request.to_vec(); let msg_from_dispatcher = InboundClientData { timestamp: SystemTime::now(), @@ -1996,86 +1970,6 @@ mod tests { assert_eq!(record, &expected_pkg); } - #[test] - fn proxy_server_applies_late_wallet_information() { - let main_cryptde = main_cryptde(); - let alias_cryptde = alias_cryptde(); - let http_request = b"GET /index.html HTTP/1.1\r\nHost: nowhere.com\r\n\r\n"; - let hopper_mock = Recorder::new(); - let hopper_log_arc = hopper_mock.get_recording(); - let hopper_awaiter = hopper_mock.get_awaiter(); - let destination_key = PublicKey::from(&b"our destination"[..]); - let route_query_response = RouteQueryResponse { - route: Route { hops: vec![] }, - expected_services: ExpectedServices::RoundTrip( - vec![make_exit_service_from_key(destination_key.clone())], - vec![], - 1234, - ), - }; - let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); - let stream_key = make_meaningless_stream_key(); - let expected_data = http_request.to_vec(); - let msg_from_dispatcher = InboundClientData { - timestamp: SystemTime::now(), - peer_addr: socket_addr.clone(), - reception_port: Some(HTTP_PORT), - sequence_number: Some(0), - last_data: true, - is_clandestine: false, - data: expected_data.clone(), - }; - let expected_http_request = PlainData::new(http_request); - let route = route_query_response.route.clone(); - let expected_payload = ClientRequestPayload_0v1 { - stream_key: stream_key.clone(), - sequenced_packet: SequencedPacket { - data: expected_http_request.into(), - sequence_number: 0, - last_data: true, - }, - target_hostname: Some(String::from("nowhere.com")), - target_port: HTTP_PORT, - protocol: ProxyProtocol::HTTP, - originator_public_key: alias_cryptde.public_key().clone(), - }; - let expected_pkg = IncipientCoresPackage::new( - main_cryptde, - route, - expected_payload.into(), - &destination_key, - ) - .unwrap(); - thread::spawn(move || { - let stream_key_factory = StreamKeyFactoryMock::new(); // can't make any stream keys; shouldn't have to - let system = System::new("proxy_server_applies_late_wallet_information"); - let mut subject = ProxyServer::new(main_cryptde, alias_cryptde, true, None, false); - subject.stream_key_factory = Box::new(stream_key_factory); - subject.keys_and_addrs.insert(stream_key, socket_addr); - subject - .stream_key_routes - .insert(stream_key, route_query_response); - let subject_addr: Addr = subject.start(); - let mut peer_actors = peer_actors_builder().hopper(hopper_mock).build(); - peer_actors.proxy_server = ProxyServer::make_subs_from(&subject_addr); - subject_addr.try_send(BindMessage { peer_actors }).unwrap(); - - subject_addr - .try_send(SetConsumingWalletMessage { - wallet: make_wallet("Consuming wallet"), - }) - .unwrap(); - - subject_addr.try_send(msg_from_dispatcher).unwrap(); - system.run(); - }); - - hopper_awaiter.await_message_count(1); - let recording = hopper_log_arc.lock().unwrap(); - let record = recording.get_record::(0); - assert_eq!(record, &expected_pkg); - } - #[test] fn proxy_server_receives_http_request_from_dispatcher_then_sends_multihop_cores_package_to_hopper( ) { @@ -2133,7 +2027,7 @@ mod tests { ), })); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let expected_data = http_request.to_vec(); let msg_from_dispatcher = InboundClientData { timestamp: SystemTime::now(), @@ -2213,7 +2107,7 @@ mod tests { let neighborhood_mock = neighborhood_mock.route_query_response(route_query_response.clone()); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let expected_data = http_request.to_vec(); let msg_from_dispatcher = InboundClientData { timestamp: SystemTime::now(), @@ -2279,7 +2173,7 @@ mod tests { }; let (hopper_mock, hopper_awaiter, hopper_recording_arc) = make_recorder(); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let expected_data = http_request.to_vec(); let msg_from_dispatcher = InboundClientData { timestamp: SystemTime::now(), @@ -2399,7 +2293,7 @@ mod tests { ), }; let source_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let expected_data = http_request.to_vec(); let system = System::new("proxy_server_sends_message_to_accountant_for_all_services_consumed"); @@ -2486,7 +2380,7 @@ mod tests { ), }; let source_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let expected_data = http_request.to_vec(); let system = System::new("proxy_server_sends_message_to_accountant_for_routing_service_consumed"); @@ -2565,7 +2459,7 @@ mod tests { let neighborhood_mock = neighborhood_mock.route_query_response(Some(route_query_response.clone())); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let expected_data = http_request.to_vec(); let msg_from_dispatcher = InboundClientData { timestamp: SystemTime::now(), @@ -2739,7 +2633,7 @@ mod tests { ]), }; let payload = ClientRequestPayload_0v1 { - stream_key: make_meaningless_stream_key(), + stream_key: StreamKey::make_meaningless_stream_key(), sequenced_packet: SequencedPacket { data: vec![], sequence_number: 0, @@ -2916,7 +2810,7 @@ mod tests { 1234, ), })); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); let expected_data = tls_request.to_vec(); let msg_from_dispatcher = InboundClientData { @@ -3002,7 +2896,7 @@ mod tests { 1234, ), })); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); let expected_data = tls_request.to_vec(); let msg_from_dispatcher = InboundClientData { @@ -3068,6 +2962,7 @@ mod tests { #[test] fn proxy_server_receives_tls_packet_other_than_handshake_from_dispatcher_then_sends_cores_package_to_hopper( ) { + let test_name = "proxy_server_receives_tls_packet_other_than_handshake_from_dispatcher_then_sends_cores_package_to_hopper"; let tls_request = &[ 0xFF, // content_type: don't care, just not Handshake 0x00, 0x00, 0x00, 0x00, // version, length: don't care @@ -3086,12 +2981,12 @@ mod tests { 1234, ), })); - let source_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); - let stream_key = StreamKey::new(main_cryptde.public_key().clone(), source_addr); + let client_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); + let stream_key = StreamKey::make_meaningful_stream_key(test_name); let expected_data = tls_request.to_vec(); let msg_from_dispatcher = InboundClientData { timestamp: SystemTime::now(), - peer_addr: source_addr.clone(), + peer_addr: client_addr, reception_port: Some(TLS_PORT), sequence_number: Some(0), last_data: true, @@ -3120,14 +3015,15 @@ mod tests { ) .unwrap(); thread::spawn(move || { - let subject = ProxyServer::new( + let mut subject = ProxyServer::new( main_cryptde, alias_cryptde, true, Some(STANDARD_CONSUMING_WALLET_BALANCE), false, ); - let system = System::new("proxy_server_receives_tls_client_hello_from_dispatcher_then_sends_cores_package_to_hopper"); + subject.keys_and_addrs.insert(stream_key, client_addr); + let system = System::new(test_name); let subject_addr: Addr = subject.start(); let mut peer_actors = peer_actors_builder() .hopper(hopper_mock) @@ -3236,7 +3132,7 @@ mod tests { false, ); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); subject .keys_and_addrs .insert(stream_key.clone(), socket_addr.clone()); @@ -3296,7 +3192,7 @@ mod tests { ); subject.subs = Some(make_proxy_server_out_subs()); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); subject .keys_and_addrs @@ -3357,7 +3253,7 @@ mod tests { false, ); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let irrelevant_public_key = PublicKey::from(&b"irrelevant"[..]); subject .keys_and_addrs @@ -3564,7 +3460,7 @@ mod tests { Some(STANDARD_CONSUMING_WALLET_BALANCE), false, ); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let irrelevant_public_key = PublicKey::from(&b"irrelevant"[..]); // subject.keys_and_addrs contains no browser stream let incoming_route_d_wallet = make_wallet("D Earning"); @@ -3665,7 +3561,7 @@ mod tests { false, ); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); subject .keys_and_addrs @@ -3732,7 +3628,7 @@ mod tests { false, ); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let irrelevant_public_key = PublicKey::from(&b"irrelevant"[..]); subject .keys_and_addrs @@ -3837,7 +3733,7 @@ mod tests { Some(STANDARD_CONSUMING_WALLET_BALANCE), false, ); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); subject .keys_and_addrs @@ -3878,10 +3774,10 @@ mod tests { System::current().stop(); system.run(); let neighborhood_recording = neighborhood_log_arc.lock().unwrap(); - let record = neighborhood_recording.get_record::(0); + let record = neighborhood_recording.get_record::(0); assert_eq!( record, - &NodeRecordMetadataMessage { + &UpdateNodeRecordMetadataMessage { public_key: exit_public_key, metadata_change: NRMetadataChange::AddUnreachableHost { hostname: "server.com".to_string() @@ -3903,7 +3799,7 @@ mod tests { Some(STANDARD_CONSUMING_WALLET_BALANCE), false, ); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); subject .keys_and_addrs @@ -3942,7 +3838,8 @@ mod tests { System::current().stop(); system.run(); let neighborhood_recording = neighborhood_recording_arc.lock().unwrap(); - let record_opt = neighborhood_recording.get_record_opt::(0); + let record_opt = + neighborhood_recording.get_record_opt::(0); assert_eq!(record_opt, None); } @@ -3959,7 +3856,7 @@ mod tests { Some(STANDARD_CONSUMING_WALLET_BALANCE), false, ); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let return_route_id = 1234; let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); subject @@ -4029,7 +3926,7 @@ mod tests { false, ); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let return_route_id = 1234; let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); @@ -4115,7 +4012,7 @@ mod tests { peer_actors.neighborhood.update_node_record_metadata; subject.subs.as_mut().unwrap().dispatcher = peer_actors.dispatcher.from_dispatcher_client; - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); subject .keys_and_addrs @@ -4163,7 +4060,7 @@ mod tests { let system = System::new("panics_if_dispatcher_is_unbound"); let cryptde = main_cryptde(); let socket_addr = SocketAddr::from_str("1.2.3.4:5678").unwrap(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let mut subject = ProxyServer::new( cryptde, alias_cryptde(), @@ -4254,7 +4151,7 @@ mod tests { Some(STANDARD_CONSUMING_WALLET_BALANCE), false, ); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); subject .keys_and_addrs .insert(stream_key, SocketAddr::from_str("1.2.3.4:5678").unwrap()); @@ -4285,7 +4182,7 @@ mod tests { System::current().stop(); system.run(); - TestLogHandler::new().exists_log_containing("ERROR: ProxyServer: Can't report services consumed: received response with bogus return-route ID 1234. Ignoring"); + TestLogHandler::new().exists_log_containing("ERROR: ProxyServer: Can't report services consumed: received response with bogus return-route ID 1234 for client response. Ignoring"); assert_eq!(dispatcher_recording_arc.lock().unwrap().len(), 0); assert_eq!(accountant_recording_arc.lock().unwrap().len(), 0); } @@ -4305,7 +4202,7 @@ mod tests { Some(STANDARD_CONSUMING_WALLET_BALANCE), false, ); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); subject .keys_and_addrs .insert(stream_key, SocketAddr::from_str("1.2.3.4:5678").unwrap()); @@ -4349,7 +4246,7 @@ mod tests { fn return_route_ids_expire_when_instructed() { init_test_logging(); let cryptde = main_cryptde(); - let stream_key = make_meaningless_stream_key(); + let stream_key = StreamKey::make_meaningless_stream_key(); let (tx, rx) = unbounded(); thread::spawn(move || { @@ -4404,15 +4301,14 @@ mod tests { ); subject_addr.try_send(expired_cores_package).unwrap(); - TestLogHandler::new().await_log_containing("ERROR: ProxyServer: Can't report services consumed: received response with bogus return-route ID 1234. Ignoring", 1000); + TestLogHandler::new().await_log_containing("ERROR: ProxyServer: Can't report services consumed: received response with bogus return-route ID 1234 for client response. Ignoring", 1000); } #[test] fn handle_stream_shutdown_msg_handles_unknown_peer_addr() { let mut subject = ProxyServer::new(main_cryptde(), alias_cryptde(), true, None, false); let unaffected_socket_addr = SocketAddr::from_str("2.3.4.5:6789").unwrap(); - let unaffected_stream_key = - StreamKey::new(main_cryptde().public_key().clone(), unaffected_socket_addr); + let unaffected_stream_key = StreamKey::make_meaningful_stream_key("unaffected"); subject .keys_and_addrs .insert(unaffected_stream_key, unaffected_socket_addr); @@ -4458,11 +4354,9 @@ mod tests { false, ); let unaffected_socket_addr = SocketAddr::from_str("2.3.4.5:6789").unwrap(); - let unaffected_stream_key = - StreamKey::new(main_cryptde().public_key().clone(), unaffected_socket_addr); + let unaffected_stream_key = StreamKey::make_meaningful_stream_key("unaffected"); let affected_socket_addr = SocketAddr::from_str("3.4.5.6:7890").unwrap(); - let affected_stream_key = - StreamKey::new(main_cryptde().public_key().clone(), affected_socket_addr); + let affected_stream_key = StreamKey::make_meaningful_stream_key("affected"); let affected_cryptde = CryptDENull::from(&PublicKey::new(b"affected"), TEST_DEFAULT_CHAIN); subject .keys_and_addrs @@ -4581,11 +4475,9 @@ mod tests { false, ); let unaffected_socket_addr = SocketAddr::from_str("2.3.4.5:6789").unwrap(); - let unaffected_stream_key = - StreamKey::new(main_cryptde().public_key().clone(), unaffected_socket_addr); + let unaffected_stream_key = StreamKey::make_meaningful_stream_key("unaffected"); let affected_socket_addr = SocketAddr::from_str("3.4.5.6:7890").unwrap(); - let affected_stream_key = - StreamKey::new(main_cryptde().public_key().clone(), affected_socket_addr); + let affected_stream_key = StreamKey::make_meaningful_stream_key("affected"); let affected_cryptde = CryptDENull::from(&PublicKey::new(b"affected"), TEST_DEFAULT_CHAIN); subject .keys_and_addrs @@ -4691,11 +4583,9 @@ mod tests { fn handle_stream_shutdown_msg_does_not_report_to_counterpart_when_unnecessary() { let mut subject = ProxyServer::new(main_cryptde(), alias_cryptde(), true, None, false); let unaffected_socket_addr = SocketAddr::from_str("2.3.4.5:6789").unwrap(); - let unaffected_stream_key = - StreamKey::new(main_cryptde().public_key().clone(), unaffected_socket_addr); + let unaffected_stream_key = StreamKey::make_meaningful_stream_key("unaffected"); let affected_socket_addr = SocketAddr::from_str("3.4.5.6:7890").unwrap(); - let affected_stream_key = - StreamKey::new(main_cryptde().public_key().clone(), affected_socket_addr); + let affected_stream_key = StreamKey::make_meaningful_stream_key("affected"); subject .keys_and_addrs .insert(unaffected_stream_key, unaffected_socket_addr); @@ -4757,7 +4647,7 @@ mod tests { .handle_normal_client_data_result(Err("Our help is not welcome".to_string())); subject.inbound_client_data_helper_opt = Some(Box::new(helper)); let socket_addr = SocketAddr::from_str("3.4.5.6:7777").unwrap(); - let stream_key = StreamKey::new(main_cryptde().public_key().clone(), socket_addr); + let stream_key = StreamKey::make_meaningful_stream_key("All Things Must Pass"); subject.keys_and_addrs.insert(stream_key, socket_addr); let msg = StreamShutdownMsg { peer_addr: socket_addr, @@ -4782,7 +4672,7 @@ mod tests { .handle_normal_client_data_result(Ok(())); subject.inbound_client_data_helper_opt = Some(Box::new(icd_helper)); let socket_addr = SocketAddr::from_str("3.4.5.6:7890").unwrap(); - let stream_key = StreamKey::new(main_cryptde().public_key().clone(), socket_addr); + let stream_key = StreamKey::make_meaningful_stream_key("All Things Must Pass"); subject.keys_and_addrs.insert(stream_key, socket_addr); subject.stream_key_routes.insert( stream_key, diff --git a/node/src/run_modes_factories.rs b/node/src/run_modes_factories.rs index c168b195d..b3f3d3f08 100644 --- a/node/src/run_modes_factories.rs +++ b/node/src/run_modes_factories.rs @@ -15,8 +15,6 @@ use crate::server_initializer::{ use masq_lib::command::StdStreams; use masq_lib::shared_schema::ConfiguratorError; use masq_lib::utils::ExpectValue; -#[cfg(test)] -use std::any::Any; use std::cell::RefCell; pub type RunModeResult = Result<(), ConfiguratorError>; @@ -68,17 +66,17 @@ pub trait DaemonInitializerFactory { pub trait DumpConfigRunner { fn go(&self, streams: &mut StdStreams, args: &[String]) -> RunModeResult; - declare_as_any!(); + as_any_in_trait!(); } pub trait ServerInitializer: futures::Future { fn go(&mut self, streams: &mut StdStreams, args: &[String]) -> RunModeResult; - declare_as_any!(); + as_any_in_trait!(); } pub trait DaemonInitializer { fn go(&mut self, streams: &mut StdStreams, args: &[String]) -> RunModeResult; - declare_as_any!(); + as_any_in_trait!(); } impl DumpConfigRunnerFactory for DumpConfigRunnerFactoryReal { diff --git a/node/src/server_initializer.rs b/node/src/server_initializer.rs index c2f921c15..1764f5638 100644 --- a/node/src/server_initializer.rs +++ b/node/src/server_initializer.rs @@ -71,7 +71,7 @@ impl ServerInitializer for ServerInitializerReal { .initialize_as_unprivileged(&multi_config, streams), ) } - implement_as_any!(); + as_any_in_trait_impl!(); } impl Future for ServerInitializerReal { diff --git a/node/src/stream_messages.rs b/node/src/stream_messages.rs index 4b838ecdc..34dccbf92 100644 --- a/node/src/stream_messages.rs +++ b/node/src/stream_messages.rs @@ -77,27 +77,20 @@ impl Debug for PoolBindMessage { #[cfg(test)] mod tests { use super::*; - use crate::node_test_utils::make_stream_handler_pool_subs_from; - use crate::test_utils::recorder::peer_actors_builder; - use actix::System; - - impl PartialEq for AddStreamMsg { - fn eq(&self, _other: &Self) -> bool { - // We need to implement PartialEq so that AddStreamMsg can be received by the Recorder; - // but AddStreamMsg breaks the rules for an actor message by containing references to - // outside resources (namely, an I/O stream) and therefore cannot have a real implementation - // of PartialEq. So here we break the rules again to patch up the problems created by - // the first breach of the rules. Don't move this into the production tree; it only needs - // to be here for the Recorder, and the Recorder is only in the test tree. - intentionally_blank!() - } - } + use crate::node_test_utils::make_stream_handler_pool_subs_from_recorder; + use crate::test_utils::recorder::{ + make_dispatcher_subs_from_recorder, make_recorder, peer_actors_builder, + }; + use actix::{Actor, System}; #[test] fn pool_bind_message_is_debug() { let _system = System::new("test"); - let dispatcher_subs = peer_actors_builder().build().dispatcher; - let stream_handler_pool_subs = make_stream_handler_pool_subs_from(None); + let (dispatcher, _, _) = make_recorder(); + let dispatcher_subs = make_dispatcher_subs_from_recorder(&dispatcher.start()); + let (stream_handler_pool, _, _) = make_recorder(); + let stream_handler_pool_subs = + make_stream_handler_pool_subs_from_recorder(&stream_handler_pool.start()); let neighborhood_subs = peer_actors_builder().build().neighborhood; let subject = PoolBindMessage { dispatcher_subs, diff --git a/node/src/stream_reader.rs b/node/src/stream_reader.rs index 6f297f806..7b42aad17 100644 --- a/node/src/stream_reader.rs +++ b/node/src/stream_reader.rs @@ -213,11 +213,11 @@ mod tests { use crate::json_discriminator_factory::JsonDiscriminatorFactory; use crate::json_masquerader::JsonMasquerader; use crate::masquerader::Masquerader; - use crate::node_test_utils::{check_timestamp, make_stream_handler_pool_subs_from}; + use crate::node_test_utils::{check_timestamp, make_stream_handler_pool_subs_from_recorder}; use crate::stream_handler_pool::StreamHandlerPoolSubs; use crate::stream_messages::RemovedStreamType::NonClandestine; use crate::sub_lib::dispatcher::DispatcherSubs; - use crate::test_utils::recorder::make_dispatcher_subs_from; + use crate::test_utils::recorder::make_dispatcher_subs_from_recorder; use crate::test_utils::recorder::make_recorder; use crate::test_utils::recorder::Recorder; use crate::test_utils::recorder::Recording; @@ -238,13 +238,16 @@ mod tests { fn stream_handler_pool_stuff() -> (Arc>, StreamHandlerPoolSubs) { let (shp, _, recording) = make_recorder(); - (recording, make_stream_handler_pool_subs_from(Some(shp))) + ( + recording, + make_stream_handler_pool_subs_from_recorder(&shp.start()), + ) } fn dispatcher_stuff() -> (Arc>, DispatcherSubs) { let (dispatcher, _, recording) = make_recorder(); let addr: Addr = dispatcher.start(); - (recording, make_dispatcher_subs_from(&addr)) + (recording, make_dispatcher_subs_from_recorder(&addr)) } #[test] diff --git a/node/src/sub_lib/accountant.rs b/node/src/sub_lib/accountant.rs index dc0f12c7e..15ceb0613 100644 --- a/node/src/sub_lib/accountant.rs +++ b/node/src/sub_lib/accountant.rs @@ -1,12 +1,14 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::database_access_objects::banned_dao::BannedDaoFactory; -use crate::accountant::database_access_objects::payable_dao::PayableDaoFactory; -use crate::accountant::database_access_objects::pending_payable_dao::PendingPayableDaoFactory; -use crate::accountant::database_access_objects::receivable_dao::ReceivableDaoFactory; +use crate::accountant::db_access_objects::banned_dao::BannedDaoFactory; +use crate::accountant::db_access_objects::payable_dao::PayableDaoFactory; +use crate::accountant::db_access_objects::pending_payable_dao::PendingPayableDaoFactory; +use crate::accountant::db_access_objects::receivable_dao::ReceivableDaoFactory; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::BlockchainAgentWithContextMessage; use crate::accountant::{ - checked_conversion, Accountant, ConsumingWalletBalancesAndQualifiedPayables, ReceivedPayments, - ReportTransactionReceipts, ScanError, SentPayables, + checked_conversion, Accountant, ReceivedPayments, ReportTransactionReceipts, ScanError, + SentPayables, }; +use crate::actor_system_factory::SubsFactory; use crate::blockchain::blockchain_bridge::PendingPayableFingerprintSeeds; use crate::sub_lib::peer_actors::{BindMessage, StartMessage}; use crate::sub_lib::wallet::Wallet; @@ -14,8 +16,6 @@ use actix::Recipient; use actix::{Addr, Message}; use lazy_static::lazy_static; use masq_lib::ui_gateway::NodeFromUiMessage; -#[cfg(test)] -use std::any::Any; use std::fmt::{Debug, Formatter}; use std::str::FromStr; use std::sync::atomic::{AtomicU32, Ordering}; @@ -76,8 +76,8 @@ pub struct DaoFactories { #[derive(PartialEq, Eq, Debug, Clone, Copy)] pub struct ScanIntervals { - pub pending_payable_scan_interval: Duration, pub payable_scan_interval: Duration, + pub pending_payable_scan_interval: Duration, pub receivable_scan_interval: Duration, } @@ -94,8 +94,7 @@ pub struct AccountantSubs { pub report_routing_service_provided: Recipient, pub report_exit_service_provided: Recipient, pub report_services_consumed: Recipient, - pub report_consuming_wallet_balances_and_qualified_payables: - Recipient, + pub report_payable_payments_setup: Recipient, pub report_inbound_payments: Recipient, pub init_pending_payable_fingerprints: Recipient, pub report_transaction_receipts: Recipient, @@ -110,13 +109,9 @@ impl Debug for AccountantSubs { } } -pub trait AccountantSubsFactory { - fn make(&self, addr: &Addr) -> AccountantSubs; -} - pub struct AccountantSubsFactoryReal {} -impl AccountantSubsFactory for AccountantSubsFactoryReal { +impl SubsFactory for AccountantSubsFactoryReal { fn make(&self, addr: &Addr) -> AccountantSubs { Accountant::make_subs_from(addr) } @@ -179,7 +174,7 @@ pub enum SignConversionError { pub trait MessageIdGenerator { fn id(&self) -> u32; - declare_as_any!(); + as_any_in_trait!(); } #[derive(Default)] @@ -189,7 +184,7 @@ impl MessageIdGenerator for MessageIdGeneratorReal { fn id(&self) -> u32 { MSG_ID_INCREMENTER.fetch_add(1, Ordering::Relaxed) } - implement_as_any!(); + as_any_in_trait_impl!(); } #[cfg(test)] @@ -197,10 +192,9 @@ mod tests { use crate::accountant::test_utils::AccountantBuilder; use crate::accountant::{checked_conversion, Accountant}; use crate::sub_lib::accountant::{ - AccountantSubsFactory, AccountantSubsFactoryReal, MessageIdGenerator, - MessageIdGeneratorReal, PaymentThresholds, ScanIntervals, DEFAULT_EARNING_WALLET, - DEFAULT_PAYMENT_THRESHOLDS, DEFAULT_SCAN_INTERVALS, MSG_ID_INCREMENTER, - TEMPORARY_CONSUMING_WALLET, + AccountantSubsFactoryReal, MessageIdGenerator, MessageIdGeneratorReal, PaymentThresholds, + ScanIntervals, SubsFactory, DEFAULT_EARNING_WALLET, DEFAULT_PAYMENT_THRESHOLDS, + DEFAULT_SCAN_INTERVALS, MSG_ID_INCREMENTER, TEMPORARY_CONSUMING_WALLET, }; use crate::sub_lib::wallet::Wallet; use crate::test_utils::recorder::{make_accountant_subs_from_recorder, Recorder}; diff --git a/node/src/sub_lib/blockchain_bridge.rs b/node/src/sub_lib/blockchain_bridge.rs index 513d23835..ff006fdaa 100644 --- a/node/src/sub_lib/blockchain_bridge.rs +++ b/node/src/sub_lib/blockchain_bridge.rs @@ -1,6 +1,8 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::accountant::database_access_objects::payable_dao::PayableAccount; +use crate::accountant::db_access_objects::payable_dao::PayableAccount; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::blockchain_agent::BlockchainAgent; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::QualifiedPayablesMessage; use crate::accountant::{RequestTransactionReceipts, ResponseSkeleton, SkeletonOptHolder}; use crate::blockchain::blockchain_bridge::RetrieveTransactions; use crate::sub_lib::peer_actors::BindMessage; @@ -16,14 +18,16 @@ use web3::types::U256; pub struct BlockchainBridgeConfig { pub blockchain_service_url_opt: Option, pub chain: Chain, + // TODO: totally ignored during the setup of the BlockchainBridge actor! + // Use it in the body or delete this field pub gas_price: u64, } #[derive(Clone, PartialEq, Eq)] pub struct BlockchainBridgeSubs { pub bind: Recipient, - pub report_accounts_payable: Recipient, - pub request_balances_to_pay_payables: Recipient, + pub outbound_payments_instructions: Recipient, + pub qualified_payables: Recipient, pub retrieve_transactions: Recipient, pub ui_sub: Recipient, pub request_transaction_receipts: Recipient, @@ -35,47 +39,81 @@ impl Debug for BlockchainBridgeSubs { } } -#[derive(Clone, PartialEq, Eq, Debug, Message)] -pub struct RequestBalancesToPayPayables { - pub accounts: Vec, +#[derive(Message)] +pub struct OutboundPaymentsInstructions { + pub affordable_accounts: Vec, + pub agent: Box, pub response_skeleton_opt: Option, } -impl SkeletonOptHolder for RequestBalancesToPayPayables { - fn skeleton_opt(&self) -> Option { - self.response_skeleton_opt +impl OutboundPaymentsInstructions { + pub fn new( + affordable_accounts: Vec, + agent: Box, + response_skeleton_opt: Option, + ) -> Self { + Self { + affordable_accounts, + agent, + response_skeleton_opt, + } } } -#[derive(Clone, PartialEq, Eq, Debug, Message)] -pub struct ReportAccountsPayable { - pub accounts: Vec, - pub response_skeleton_opt: Option, -} - -impl SkeletonOptHolder for ReportAccountsPayable { +impl SkeletonOptHolder for OutboundPaymentsInstructions { fn skeleton_opt(&self) -> Option { self.response_skeleton_opt } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ConsumingWalletBalances { - pub gas_currency: U256, - pub masq_tokens: U256, + pub transaction_fee_balance_in_minor_units: U256, + pub masq_token_balance_in_minor_units: U256, +} + +impl ConsumingWalletBalances { + pub fn new(transaction_fee: U256, masq_token: U256) -> Self { + Self { + transaction_fee_balance_in_minor_units: transaction_fee, + masq_token_balance_in_minor_units: masq_token, + } + } } #[cfg(test)] mod tests { - use crate::test_utils::recorder::{make_blockchain_bridge_subs_from, Recorder}; + use crate::actor_system_factory::SubsFactory; + use crate::blockchain::blockchain_bridge::{BlockchainBridge, BlockchainBridgeSubsFactoryReal}; + use crate::blockchain::test_utils::BlockchainInterfaceMock; + use crate::test_utils::persistent_configuration_mock::PersistentConfigurationMock; + use crate::test_utils::recorder::{make_blockchain_bridge_subs_from_recorder, Recorder}; use actix::Actor; #[test] fn blockchain_bridge_subs_debug() { let recorder = Recorder::new().start(); - let subject = make_blockchain_bridge_subs_from(&recorder); + let subject = make_blockchain_bridge_subs_from_recorder(&recorder); assert_eq!(format!("{:?}", subject), "BlockchainBridgeSubs"); } + + #[test] + fn blockchain_bridge_subs_factory_produces_proper_subs() { + let subject = BlockchainBridgeSubsFactoryReal {}; + let blockchain_interface = BlockchainInterfaceMock::default(); + let persistent_config = PersistentConfigurationMock::new(); + let accountant = BlockchainBridge::new( + Box::new(blockchain_interface), + Box::new(persistent_config), + false, + None, + ); + let addr = accountant.start(); + + let subs = subject.make(&addr); + + assert_eq!(subs, BlockchainBridge::make_subs_from(&addr)) + } } diff --git a/node/src/sub_lib/combined_parameters.rs b/node/src/sub_lib/combined_parameters.rs index dfe188a81..f70f60f0f 100644 --- a/node/src/sub_lib/combined_parameters.rs +++ b/node/src/sub_lib/combined_parameters.rs @@ -5,7 +5,7 @@ use crate::sub_lib::combined_parameters::CombinedParamsDataTypes::U64; use crate::sub_lib::combined_parameters::InitializationState::{Initialized, Uninitialized}; use crate::sub_lib::neighborhood::RatePack; use masq_lib::constants::COMBINED_PARAMETERS_DELIMITER; -use masq_lib::utils::ExpectValue; +use masq_lib::utils::{to_string, ExpectValue}; use paste::paste; use std::any::Any; use std::collections::HashMap; @@ -55,9 +55,9 @@ impl CombinedParamsValueRetriever { fn parse(str_value: &str) -> Result where T: std::str::FromStr, - ::Err: ToString, + ::Err: Display, { - str_value.parse::().map_err(|e| e.to_string()) + str_value.parse::().map_err(to_string) } match data_type { CombinedParamsDataTypes::U64 => { diff --git a/node/src/sub_lib/configurator.rs b/node/src/sub_lib/configurator.rs index 43f5c3319..3f92a7535 100644 --- a/node/src/sub_lib/configurator.rs +++ b/node/src/sub_lib/configurator.rs @@ -6,6 +6,7 @@ use masq_lib::ui_gateway::NodeFromUiMessage; use std::fmt; use std::fmt::{Debug, Formatter}; +// GH-728 #[derive(Debug, actix::Message, Clone, PartialEq, Eq)] pub struct NewPasswordMessage { pub new_password: String, diff --git a/node/src/sub_lib/migrations/client_request_payload.rs b/node/src/sub_lib/migrations/client_request_payload.rs index b48adc6f7..2bd9993f5 100644 --- a/node/src/sub_lib/migrations/client_request_payload.rs +++ b/node/src/sub_lib/migrations/client_request_payload.rs @@ -124,8 +124,6 @@ mod tests { use crate::sub_lib::cryptde::PublicKey; use masq_lib::data_version::DataVersion; use serde_derive::{Deserialize, Serialize}; - use std::net::SocketAddr; - use std::str::FromStr; #[test] fn can_migrate_from_the_future() { @@ -141,10 +139,7 @@ mod tests { pub yet_another_field: u64, } let expected_crp = ClientRequestPayload_0v1 { - stream_key: StreamKey::new( - PublicKey::new(&[1, 2, 3, 4]), - SocketAddr::from_str("1.2.3.4:1234").unwrap(), - ), + stream_key: StreamKey::make_meaningful_stream_key("All Things Must Pass"), sequenced_packet: SequencedPacket::new(vec![4, 3, 2, 1], 4321, false), target_hostname: Some("target.hostname.com".to_string()), target_port: 1234, diff --git a/node/src/sub_lib/migrations/client_response_payload.rs b/node/src/sub_lib/migrations/client_response_payload.rs index 2a65ab96e..84578b2da 100644 --- a/node/src/sub_lib/migrations/client_response_payload.rs +++ b/node/src/sub_lib/migrations/client_response_payload.rs @@ -95,11 +95,8 @@ impl TryFrom<&Value> for ClientResponsePayload_0v1 { #[cfg(test)] mod tests { use super::*; - use crate::sub_lib::cryptde::PublicKey; use masq_lib::data_version::DataVersion; use serde_derive::{Deserialize, Serialize}; - use std::net::SocketAddr; - use std::str::FromStr; #[test] fn can_migrate_from_the_future() { @@ -111,10 +108,7 @@ mod tests { pub yet_another_field: u64, } let expected_crp = ClientResponsePayload_0v1 { - stream_key: StreamKey::new( - PublicKey::new(&[1, 2, 3, 4]), - SocketAddr::from_str("1.2.3.4:1234").unwrap(), - ), + stream_key: StreamKey::make_meaningful_stream_key("All Things Must Pass"), sequenced_packet: SequencedPacket::new(vec![4, 3, 2, 1], 4321, false), }; let future_crp = ExampleFutureCRP { diff --git a/node/src/sub_lib/migrations/dns_resolve_failure.rs b/node/src/sub_lib/migrations/dns_resolve_failure.rs index 60ca0a637..5736f687a 100644 --- a/node/src/sub_lib/migrations/dns_resolve_failure.rs +++ b/node/src/sub_lib/migrations/dns_resolve_failure.rs @@ -83,11 +83,8 @@ impl TryFrom<&Value> for DnsResolveFailure_0v1 { #[cfg(test)] mod tests { use super::*; - use crate::sub_lib::cryptde::PublicKey; use masq_lib::data_version::DataVersion; use serde_derive::{Deserialize, Serialize}; - use std::net::SocketAddr; - use std::str::FromStr; #[test] fn can_migrate_from_the_future() { @@ -98,10 +95,7 @@ mod tests { pub yet_another_field: u64, } let expected_crp = DnsResolveFailure_0v1 { - stream_key: StreamKey::new( - PublicKey::new(&[1, 2, 3, 4]), - SocketAddr::from_str("1.2.3.4:1234").unwrap(), - ), + stream_key: StreamKey::make_meaningful_stream_key("All Things Must Pass"), }; let future_crp = ExampleFutureDRF { stream_key: expected_crp.stream_key.clone(), diff --git a/node/src/sub_lib/mod.rs b/node/src/sub_lib/mod.rs index 2bbc553ab..51360357b 100644 --- a/node/src/sub_lib/mod.rs +++ b/node/src/sub_lib/mod.rs @@ -34,7 +34,6 @@ pub mod proxy_server; pub mod route; pub mod sequence_buffer; pub mod sequencer; -pub mod set_consuming_wallet_message; pub mod socket_server; pub mod stream_connector; pub mod stream_handler_pool; diff --git a/node/src/sub_lib/neighborhood.rs b/node/src/sub_lib/neighborhood.rs index f70ca1857..64c30c2fe 100644 --- a/node/src/sub_lib/neighborhood.rs +++ b/node/src/sub_lib/neighborhood.rs @@ -12,7 +12,6 @@ use crate::sub_lib::hopper::ExpiredCoresPackage; use crate::sub_lib::node_addr::NodeAddr; use crate::sub_lib::peer_actors::{BindMessage, NewPublicIp, StartMessage}; use crate::sub_lib::route::Route; -use crate::sub_lib::set_consuming_wallet_message::SetConsumingWalletMessage; use crate::sub_lib::stream_handler_pool::DispatcherNodeQueryResponse; use crate::sub_lib::stream_handler_pool::TransmitDataMsg; use crate::sub_lib::utils::{NotifyLaterHandle, NotifyLaterHandleReal}; @@ -370,7 +369,7 @@ impl Display for DescriptorParsingError<'_> { } } -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq)] pub enum Hops { OneHop = 1, TwoHops = 2, @@ -418,15 +417,15 @@ pub struct NeighborhoodSubs { pub start: Recipient, pub new_public_ip: Recipient, pub route_query: Recipient, - pub update_node_record_metadata: Recipient, + pub update_node_record_metadata: Recipient, pub from_hopper: Recipient>, pub gossip_failure: Recipient>, pub dispatcher_node_query: Recipient, pub remove_neighbor: Recipient, + pub configuration_change_msg_sub: Recipient, pub stream_shutdown_sub: Recipient, - pub set_consuming_wallet_sub: Recipient, pub from_ui_message_sub: Recipient, - pub new_password_sub: Recipient, + pub new_password_sub: Recipient, // GH-728 pub connection_progress_sub: Recipient, } @@ -545,7 +544,7 @@ pub struct AskAboutDebutGossipMessage { } #[derive(Clone, Debug, Message, PartialEq, Eq)] -pub struct NodeRecordMetadataMessage { +pub struct UpdateNodeRecordMetadataMessage { pub public_key: PublicKey, pub metadata_change: NRMetadataChange, } @@ -555,6 +554,17 @@ pub enum NRMetadataChange { AddUnreachableHost { hostname: String }, } +#[derive(Clone, Debug, Message, PartialEq, Eq)] +pub struct ConfigurationChangeMessage { + pub change: ConfigurationChange, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ConfigurationChange { + UpdateConsumingWallet(Wallet), + UpdateMinHops(Hops), +} + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[allow(non_camel_case_types)] pub enum GossipFailure_0v1 { @@ -582,7 +592,7 @@ impl fmt::Display for GossipFailure_0v1 { pub struct NeighborhoodMetadata { pub connection_progress_peers: Vec, pub cpm_recipient: Recipient, - pub min_hops: Hops, + pub db_patch_size: u8, } pub struct NeighborhoodTools { @@ -654,15 +664,15 @@ mod tests { start: recipient!(recorder, StartMessage), new_public_ip: recipient!(recorder, NewPublicIp), route_query: recipient!(recorder, RouteQueryMessage), - update_node_record_metadata: recipient!(recorder, NodeRecordMetadataMessage), + update_node_record_metadata: recipient!(recorder, UpdateNodeRecordMetadataMessage), from_hopper: recipient!(recorder, ExpiredCoresPackage), gossip_failure: recipient!(recorder, ExpiredCoresPackage), dispatcher_node_query: recipient!(recorder, DispatcherNodeQueryMessage), remove_neighbor: recipient!(recorder, RemoveNeighborMessage), + configuration_change_msg_sub: recipient!(recorder, ConfigurationChangeMessage), stream_shutdown_sub: recipient!(recorder, StreamShutdownMsg), - set_consuming_wallet_sub: recipient!(recorder, SetConsumingWalletMessage), from_ui_message_sub: recipient!(recorder, NodeFromUiMessage), - new_password_sub: recipient!(recorder, NewPasswordMessage), + new_password_sub: recipient!(recorder, NewPasswordMessage), // GH-728 connection_progress_sub: recipient!(recorder, ConnectionProgressMessage), }; diff --git a/node/src/sub_lib/proxy_client.rs b/node/src/sub_lib/proxy_client.rs index 9428c1ed4..36fc31220 100644 --- a/node/src/sub_lib/proxy_client.rs +++ b/node/src/sub_lib/proxy_client.rs @@ -107,13 +107,12 @@ pub struct InboundServerData { mod tests { use super::*; use crate::sub_lib::peer_actors::BindMessage; - use crate::test_utils::make_meaningless_stream_key; use crate::test_utils::recorder::Recorder; use actix::Actor; #[test] fn make_terminating_payload_makes_terminating_payload() { - let stream_key: StreamKey = make_meaningless_stream_key(); + let stream_key: StreamKey = StreamKey::make_meaningless_stream_key(); let payload = ClientResponsePayload_0v1::make_terminating_payload(stream_key); diff --git a/node/src/sub_lib/proxy_server.rs b/node/src/sub_lib/proxy_server.rs index 5d3b242ae..8706d2943 100644 --- a/node/src/sub_lib/proxy_server.rs +++ b/node/src/sub_lib/proxy_server.rs @@ -8,7 +8,6 @@ use crate::sub_lib::neighborhood::{ExpectedService, RouteQueryResponse}; use crate::sub_lib::peer_actors::BindMessage; use crate::sub_lib::proxy_client::{ClientResponsePayload_0v1, DnsResolveFailure_0v1}; use crate::sub_lib::sequence_buffer::SequencedPacket; -use crate::sub_lib::set_consuming_wallet_message::SetConsumingWalletMessage; use crate::sub_lib::stream_key::StreamKey; use crate::sub_lib::versioned_data::VersionedData; use actix::Message; @@ -79,7 +78,6 @@ pub struct ProxyServerSubs { pub add_return_route: Recipient, pub add_route: Recipient, pub stream_shutdown_sub: Recipient, - pub set_consuming_wallet_sub: Recipient, pub node_from_ui: Recipient, } @@ -111,7 +109,6 @@ mod tests { add_return_route: recipient!(recorder, AddReturnRouteMessage), add_route: recipient!(recorder, AddRouteMessage), stream_shutdown_sub: recipient!(recorder, StreamShutdownMsg), - set_consuming_wallet_sub: recipient!(recorder, SetConsumingWalletMessage), node_from_ui: recipient!(recorder, NodeFromUiMessage), }; diff --git a/node/src/sub_lib/set_consuming_wallet_message.rs b/node/src/sub_lib/set_consuming_wallet_message.rs deleted file mode 100644 index 186f4be77..000000000 --- a/node/src/sub_lib/set_consuming_wallet_message.rs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. - -use crate::sub_lib::wallet::Wallet; -use actix::Message; - -#[derive(Clone, PartialEq, Eq, Debug, Message)] -pub struct SetConsumingWalletMessage { - pub wallet: Wallet, -} diff --git a/node/src/sub_lib/stream_key.rs b/node/src/sub_lib/stream_key.rs index b6d4f7e03..f50f67b5d 100644 --- a/node/src/sub_lib/stream_key.rs +++ b/node/src/sub_lib/stream_key.rs @@ -1,14 +1,12 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::sub_lib::cryptde::PublicKey; use serde::de::Visitor; use serde::Deserialize; use serde::Deserializer; use serde::Serialize; use serde::Serializer; use std::fmt; -use std::net::IpAddr; -use std::net::SocketAddr; +use uuid::Uuid; #[derive(Hash, PartialEq, Eq, Clone, Copy)] pub struct StreamKey { @@ -74,18 +72,32 @@ impl<'a> Visitor<'a> for StreamKeyVisitor { } } +impl Default for StreamKey { + fn default() -> Self { + Self::new() + } +} + impl StreamKey { - pub fn new(public_key: PublicKey, peer_addr: SocketAddr) -> StreamKey { + pub fn new() -> StreamKey { let mut hash = sha1::Sha1::new(); - match peer_addr.ip() { - IpAddr::V4(ipv4) => hash.update(&ipv4.octets()), - IpAddr::V6(_ipv6) => unimplemented!(), + let uuid = Uuid::new_v4(); + let uuid_bytes: &[u8] = uuid.as_bytes(); + hash.update(uuid_bytes); + StreamKey { + hash: hash.digest().bytes(), + } + } + + pub fn make_meaningless_stream_key() -> StreamKey { + StreamKey { + hash: [0; sha1::DIGEST_LENGTH], } - hash.update(&[ - (peer_addr.port() >> 8) as u8, - (peer_addr.port() & 0xFF) as u8, - ]); - hash.update(public_key.as_slice()); + } + + pub fn make_meaningful_stream_key(phrase: &str) -> StreamKey { + let mut hash = sha1::Sha1::new(); + hash.update(phrase.as_bytes()); StreamKey { hash: hash.digest().bytes(), } @@ -97,82 +109,42 @@ type HashType = [u8; sha1::DIGEST_LENGTH]; #[cfg(test)] mod tests { use super::*; - use std::str::FromStr; - - #[test] - fn matching_keys_and_matching_addrs_make_matching_stream_keys() { - let key = PublicKey::new(&b"These are the times"[..]); - let addr = SocketAddr::from_str("2.3.4.5:6789").unwrap(); - - let one = StreamKey::new(key.clone(), addr); - let another = StreamKey::new(key, addr); - - assert_eq!(one, another); - } - - #[test] - fn matching_keys_and_mismatched_addrs_make_mismatched_stream_keys() { - let key = PublicKey::new(&b"These are the times"[..]); - let one_addr = SocketAddr::from_str("2.3.4.5:6789").unwrap(); - let another_addr = SocketAddr::from_str("3.4.5.6:6789").unwrap(); - - let one = StreamKey::new(key.clone(), one_addr); - let another = StreamKey::new(key, another_addr); - - assert_ne!(one, another); - } + use std::collections::HashSet; #[test] - fn matching_keys_and_mismatched_port_numbers_make_mismatched_stream_keys() { - let key = PublicKey::new(&b"These are the times"[..]); - let one_addr = SocketAddr::from_str("3.4.5.6:6789").unwrap(); - let another_addr = SocketAddr::from_str("3.4.5.6:7890").unwrap(); - - let one = StreamKey::new(key.clone(), one_addr); - let another = StreamKey::new(key, another_addr); + fn stream_keys_are_unique() { + let mut stream_keys_set = HashSet::new(); - assert_ne!(one, another); - } + for i in 1..=1_000 { + let stream_key = StreamKey::default(); + let is_unique = stream_keys_set.insert(stream_key); - #[test] - fn mismatched_keys_and_matching_addrs_make_mismatched_stream_keys() { - let one_key = PublicKey::new(&b"These are the times"[..]); - let another_key = PublicKey::new(&b"that try men's souls"[..]); - let addr = SocketAddr::from_str("2.3.4.5:6789").unwrap(); - - let one = StreamKey::new(one_key.clone(), addr); - let another = StreamKey::new(another_key, addr); - - assert_ne!(one, another); + assert!(is_unique, "{}", &format!("Stream key {i} is not unique")); + } } #[test] fn debug_implementation() { - let key = PublicKey::new(&b"These are the times"[..]); - let addr = SocketAddr::from_str("2.3.4.5:6789").unwrap(); - let subject = StreamKey::new(key, addr); + let subject = StreamKey::make_meaningful_stream_key("These are the times"); let result = format!("{:?}", subject); - assert_eq!(result, String::from("X4SEhZulE9WrmSolWqKFErYBVgI")); + assert_eq!(result, "HNksM7Mqjxr34GiUscSNeixMFzg".to_string()); } #[test] fn display_implementation() { - let key = PublicKey::new(&b"These are the times"[..]); - let addr = SocketAddr::from_str("2.3.4.5:6789").unwrap(); - let subject = StreamKey::new(key, addr); + let subject = StreamKey::make_meaningful_stream_key("These are the times"); let result = format!("{}", subject); - assert_eq!(result, String::from("X4SEhZulE9WrmSolWqKFErYBVgI")); + assert_eq!(result, "HNksM7Mqjxr34GiUscSNeixMFzg".to_string()); } #[test] fn serialization_and_deserialization_can_talk() { - let subject = StreamKey::new( - PublicKey::new(&b"booga"[..]), - SocketAddr::from_str("1.2.3.4:5678").unwrap(), + let subject = StreamKey::make_meaningful_stream_key( + "Chancellor on brink of second bailout for banks", ); let serial = serde_cbor::ser::to_vec(&subject).unwrap(); diff --git a/node/src/sub_lib/utils.rs b/node/src/sub_lib/utils.rs index cad371e09..4b493cb5d 100644 --- a/node/src/sub_lib/utils.rs +++ b/node/src/sub_lib/utils.rs @@ -9,8 +9,6 @@ use masq_lib::multi_config::{MultiConfig, VirtualCommandLine}; use masq_lib::shared_schema::ConfiguratorError; use masq_lib::ui_gateway::NodeFromUiMessage; use masq_lib::utils::type_name_of; -#[cfg(test)] -use std::any::Any; use std::io::ErrorKind; use std::marker::PhantomData; use std::path::Path; @@ -151,7 +149,7 @@ where interval: Duration, ctx: &mut Context, ) -> Box; - declare_as_any!(); + as_any_in_trait!(); } #[derive(Default)] @@ -181,7 +179,7 @@ where let handle = ctx.notify_later(msg, interval); Box::new(NLSpawnHandleHolderReal::new(handle)) } - implement_as_any!(); + as_any_in_trait_impl!(); } pub trait NotifyHandle @@ -189,7 +187,7 @@ where A: Actor>, { fn notify<'a>(&'a self, msg: M, ctx: &'a mut Context); - declare_as_any!(); + as_any_in_trait!(); } impl Default for Box> @@ -236,7 +234,7 @@ where fn notify<'a>(&'a self, msg: M, ctx: &'a mut Context) { ctx.notify(msg) } - implement_as_any!(); + as_any_in_trait_impl!(); } pub fn db_connection_launch_panic(err: InitializationError, data_directory: &Path) -> ! { diff --git a/node/src/sub_lib/wallet.rs b/node/src/sub_lib/wallet.rs index 59898ee44..feb0667ec 100644 --- a/node/src/sub_lib/wallet.rs +++ b/node/src/sub_lib/wallet.rs @@ -1,5 +1,5 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use crate::blockchain::bip32::Bip32ECKeyProvider; +use crate::blockchain::bip32::Bip32EncryptionKeyProvider; use crate::blockchain::payer::Payer; use crate::sub_lib::cryptde; use crate::sub_lib::cryptde::PublicKey as CryptdePublicKey; @@ -38,7 +38,7 @@ impl Display for WalletError { #[derive(Debug)] pub enum WalletKind { Address(Address), - SecretKey(Bip32ECKeyProvider), + SecretKey(Bip32EncryptionKeyProvider), PublicKey(PublicKey), Uninitialized, } @@ -48,7 +48,7 @@ impl Clone for WalletKind { match self { WalletKind::Address(address) => WalletKind::Address(H160(address.0)), WalletKind::SecretKey(keypair) => WalletKind::SecretKey( - Bip32ECKeyProvider::from_raw_secret(keypair.clone_secret().as_ref()) + Bip32EncryptionKeyProvider::from_raw_secret(keypair.clone_secret().as_ref()) .expect("failed to clone once checked secret"), ), WalletKind::PublicKey(public) => WalletKind::PublicKey( @@ -132,6 +132,12 @@ impl Wallet { } } + pub fn null() -> Self { + Wallet { + kind: WalletKind::Uninitialized, + } + } + pub fn string_address_from_keypair(&self) -> String { format!("{:#x}", self.address()) } @@ -233,8 +239,8 @@ impl From for Wallet { } } -impl From for Wallet { - fn from(keypair: Bip32ECKeyProvider) -> Self { +impl From for Wallet { + fn from(keypair: Bip32EncryptionKeyProvider) -> Self { Self { kind: WalletKind::SecretKey(keypair), } @@ -265,10 +271,10 @@ impl FromSql for Wallet { } } -impl TryInto for Wallet { +impl TryInto for Wallet { type Error = String; - fn try_into(self) -> Result { + fn try_into(self) -> Result { match self.kind { WalletKind::SecretKey(keypair) => Ok(keypair), _ => Err("Wallet contains no secret key: can't convert to Bip32KeyPair".to_string()), @@ -493,8 +499,11 @@ mod tests { let derivation_path = derivation_path(0, 5); let expected_seed = make_meaningless_seed(); let wallet = Wallet::from( - Bip32ECKeyProvider::try_from((expected_seed.as_bytes(), derivation_path.as_str())) - .unwrap(), + Bip32EncryptionKeyProvider::try_from(( + expected_seed.as_bytes(), + derivation_path.as_str(), + )) + .unwrap(), ); let result = wallet.string_address_from_keypair(); @@ -502,6 +511,18 @@ mod tests { assert_eq!(result, "0x28330c4b886fc83bd6e3409a9eae776c19403c2e") } + #[test] + fn null_wallet() { + let result = Wallet::null(); + + assert_eq!( + result, + Wallet { + kind: WalletKind::Uninitialized + } + ) + } + #[test] fn serialization_roundtrips_wallet_by_address_with_cbor_successfully() { let expected_wallet = make_wallet("A valid eth address!"); @@ -529,7 +550,7 @@ mod tests { ) .unwrap(); let seed = Seed::new(&mnemonic, "Test123!"); - let keypair = Bip32ECKeyProvider::try_from(( + let keypair = Bip32EncryptionKeyProvider::try_from(( seed.as_ref(), DEFAULT_CONSUMING_DERIVATION_PATH.as_str(), )) @@ -552,7 +573,7 @@ mod tests { ) .unwrap(); let seed = Seed::new(&mnemonic, "Test123!"); - let keypair = Bip32ECKeyProvider::try_from(( + let keypair = Bip32EncryptionKeyProvider::try_from(( seed.as_ref(), DEFAULT_CONSUMING_DERIVATION_PATH.as_str(), )) @@ -647,15 +668,17 @@ mod tests { #[test] fn can_convert_to_keypair_if_came_from_keypair() { let secret_key_text = "0000000000000000000000003f69f9efd4f2592fd70be8c32ecd9dce71c472fc"; - let keypair = - Bip32ECKeyProvider::from_raw_secret(&secret_key_text.from_hex::>().unwrap()) - .unwrap(); - let expected_keypair = - Bip32ECKeyProvider::from_raw_secret(&secret_key_text.from_hex::>().unwrap()) - .unwrap(); + let keypair = Bip32EncryptionKeyProvider::from_raw_secret( + &secret_key_text.from_hex::>().unwrap(), + ) + .unwrap(); + let expected_keypair = Bip32EncryptionKeyProvider::from_raw_secret( + &secret_key_text.from_hex::>().unwrap(), + ) + .unwrap(); let subject = Wallet::from(keypair); - let result: Bip32ECKeyProvider = subject.try_into().unwrap(); + let result: Bip32EncryptionKeyProvider = subject.try_into().unwrap(); assert_eq!(result, expected_keypair); } @@ -664,7 +687,7 @@ mod tests { fn cant_convert_to_keypair_if_didnt_come_from_keypair() { let subject = Wallet::from_str("0x3f69f9efd4f2592fd70be8c32ecd9dce71c472fc").unwrap(); - let result: Result = subject.try_into(); + let result: Result = subject.try_into(); assert_eq!( result, @@ -698,14 +721,14 @@ mod tests { } } - fn keypair_a() -> Bip32ECKeyProvider { + fn keypair_a() -> Bip32EncryptionKeyProvider { let numbers = (0u8..32u8).collect::>(); - Bip32ECKeyProvider::from_raw_secret(&numbers).unwrap() + Bip32EncryptionKeyProvider::from_raw_secret(&numbers).unwrap() } - fn keypair_b() -> Bip32ECKeyProvider { + fn keypair_b() -> Bip32EncryptionKeyProvider { let numbers = (1u8..33u8).collect::>(); - Bip32ECKeyProvider::from_raw_secret(&numbers).unwrap() + Bip32EncryptionKeyProvider::from_raw_secret(&numbers).unwrap() } fn hash(wallet: &Wallet) -> u64 { diff --git a/node/src/test_utils/actor_system_factory.rs b/node/src/test_utils/actor_system_factory.rs new file mode 100644 index 000000000..ba0baeccc --- /dev/null +++ b/node/src/test_utils/actor_system_factory.rs @@ -0,0 +1,16 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +use crate::accountant::db_access_objects::banned_dao::BannedCacheLoader; +use crate::database::connection_wrapper::ConnectionWrapper; +use std::sync::{Arc, Mutex}; + +#[derive(Default)] +pub struct BannedCacheLoaderMock { + pub load_params: Arc>>>, +} + +impl BannedCacheLoader for BannedCacheLoaderMock { + fn load(&self, conn: Box) { + self.load_params.lock().unwrap().push(conn); + } +} diff --git a/node/src/test_utils/database_utils.rs b/node/src/test_utils/database_utils.rs index 9672eefd4..ac64b0681 100644 --- a/node/src/test_utils/database_utils.rs +++ b/node/src/test_utils/database_utils.rs @@ -2,14 +2,14 @@ #![cfg(test)] -use crate::accountant::database_access_objects::dao_utils::VigilantRusqliteFlatten; +use crate::accountant::db_access_objects::utils::VigilantRusqliteFlatten; use crate::database::connection_wrapper::ConnectionWrapper; use crate::database::db_initializer::ExternalData; use crate::database::db_migrations::db_migrator::DbMigrator; use masq_lib::logger::Logger; use masq_lib::test_utils::utils::TEST_DEFAULT_CHAIN; -use masq_lib::utils::NeighborhoodModeLight; +use masq_lib::utils::{to_string, NeighborhoodModeLight}; use rusqlite::{Connection, Error}; use std::cell::RefCell; use std::env::current_dir; @@ -207,7 +207,7 @@ fn prepare_expected_vectors_of_words_including_sorting( .map(|slice_of_strs| { let mut one_line = slice_of_strs .into_iter() - .map(|word| word.to_string()) + .map(to_string) .collect::>(); one_line.sort(); one_line @@ -233,7 +233,7 @@ fn parse_sql_to_pieces(sql: &str) -> SQLLinesChoppedIntoWords { let mut vec_of_words = sql_line .split(|char: char| char.is_whitespace()) .filter(|chunk| !chunk.is_empty()) - .map(|chunk| chunk.to_string()) + .map(to_string) .collect::>(); vec_of_words.sort(); vec_of_words diff --git a/node/src/test_utils/http_test_server.rs b/node/src/test_utils/http_test_server.rs new file mode 100644 index 000000000..56e0daddf --- /dev/null +++ b/node/src/test_utils/http_test_server.rs @@ -0,0 +1,73 @@ +// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. + +#![cfg(test)] + +use crossbeam_channel::{unbounded, Receiver}; +use simple_server::{Request, Server}; +use std::io::Write; +use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream}; +use std::ops::Add; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::{Duration, Instant}; + +pub struct TestServer { + port: u16, + rx: Receiver>>, +} + +impl Drop for TestServer { + fn drop(&mut self) { + self.stop(); + } +} + +impl TestServer { + pub fn start(port: u16, bodies: Vec>) -> Self { + std::env::set_var("SIMPLESERVER_THREADS", "1"); + let (tx, rx) = unbounded(); + let _ = thread::spawn(move || { + let bodies_arc = Arc::new(Mutex::new(bodies)); + Server::new(move |req, mut rsp| { + if req.headers().get("X-Quit").is_some() { + panic!("Server stop requested"); + } + tx.send(req).unwrap(); + let body = bodies_arc.lock().unwrap().remove(0); + Ok(rsp.body(body)?) + }) + .listen(&Ipv4Addr::LOCALHOST.to_string(), &format!("{}", port)); + }); + let deadline = Instant::now().add(Duration::from_secs(5)); + loop { + thread::sleep(Duration::from_millis(10)); + match TcpStream::connect(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port)) { + Ok(_) => break, + Err(e) => eprintln!("No: {:?}", e), + } + if Instant::now().gt(&deadline) { + panic!("TestServer still not started after 5sec"); + } + } + TestServer { port, rx } + } + + pub fn requests_so_far(&self) -> Vec>> { + let mut requests = vec![]; + while let Ok(request) = self.rx.try_recv() { + requests.push(request); + } + return requests; + } + + fn stop(&mut self) { + let mut stream = + match TcpStream::connect(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), self.port)) { + Ok(s) => s, + Err(_) => return, + }; + stream + .write(b"DELETE /irrelevant.htm HTTP/1.1\r\nX-Quit: Yes") + .unwrap(); + } +} diff --git a/node/src/test_utils/mod.rs b/node/src/test_utils/mod.rs index 3dfd8839e..60a9b7741 100644 --- a/node/src/test_utils/mod.rs +++ b/node/src/test_utils/mod.rs @@ -2,10 +2,12 @@ #[macro_use] pub mod channel_wrapper_mocks; +pub mod actor_system_factory; pub mod automap_mocks; pub mod data_hunk; pub mod data_hunk_framer; pub mod database_utils; +pub mod http_test_server; pub mod little_tcp_server; pub mod logfile_name_guard; pub mod neighborhood_test_utils; @@ -15,7 +17,8 @@ pub mod recorder_stop_conditions; pub mod stream_connector_mock; pub mod tcp_wrapper_mocks; pub mod tokio_wrapper_mocks; -use crate::blockchain::bip32::Bip32ECKeyProvider; + +use crate::blockchain::bip32::Bip32EncryptionKeyProvider; use crate::blockchain::payer::Payer; use crate::bootstrapper::CryptDEPair; use crate::sub_lib::cryptde::CryptDE; @@ -53,7 +56,6 @@ use std::hash::Hash; use std::io::ErrorKind; use std::io::Read; use std::iter::repeat; -use std::net::SocketAddr; use std::net::{Shutdown, TcpStream}; use std::str::FromStr; use std::sync::{Arc, Mutex}; @@ -173,15 +175,8 @@ impl Waiter { } } -pub fn make_meaningless_stream_key() -> StreamKey { - StreamKey::new( - PublicKey::new(&[]), - SocketAddr::from_str("4.3.2.1:8765").unwrap(), - ) -} - pub fn make_meaningless_message_type() -> MessageType { - DnsResolveFailure_0v1::new(make_meaningless_stream_key()).into() + DnsResolveFailure_0v1::new(StreamKey::make_meaningless_stream_key()).into() } pub fn make_one_way_route_to_proxy_client(public_keys: Vec<&PublicKey>) -> Route { @@ -282,10 +277,7 @@ pub fn make_garbage_data(bytes: usize) -> Vec { pub fn make_request_payload(bytes: usize, cryptde: &dyn CryptDE) -> ClientRequestPayload_0v1 { ClientRequestPayload_0v1 { - stream_key: StreamKey::new( - cryptde.public_key().clone(), - SocketAddr::from_str("1.2.3.4:5678").unwrap(), - ), + stream_key: StreamKey::make_meaningful_stream_key("request"), sequenced_packet: SequencedPacket::new(make_garbage_data(bytes), 0, true), target_hostname: Some("example.com".to_string()), target_port: HTTP_PORT, @@ -294,12 +286,9 @@ pub fn make_request_payload(bytes: usize, cryptde: &dyn CryptDE) -> ClientReques } } -pub fn make_response_payload(bytes: usize, cryptde: &dyn CryptDE) -> ClientResponsePayload_0v1 { +pub fn make_response_payload(bytes: usize) -> ClientResponsePayload_0v1 { ClientResponsePayload_0v1 { - stream_key: StreamKey::new( - cryptde.public_key().clone(), - SocketAddr::from_str("1.2.3.4:5678").unwrap(), - ), + stream_key: StreamKey::make_meaningful_stream_key("response"), sequenced_packet: SequencedPacket { data: make_garbage_data(bytes), sequence_number: 0, @@ -511,7 +500,8 @@ pub fn make_payer(secret: &[u8], public_key: &PublicKey) -> Payer { pub fn make_paying_wallet(secret: &[u8]) -> Wallet { let digest = secret.keccak256(); Wallet::from( - Bip32ECKeyProvider::from_raw_secret(&digest).expect("Invalid Secret for Bip32ECKeyPair"), + Bip32EncryptionKeyProvider::from_raw_secret(&digest) + .expect("Invalid Secret for Bip32ECKeyPair"), ) } @@ -539,6 +529,14 @@ pub struct TestRawTransaction { pub data: Vec, } +#[macro_export] +macro_rules! arbitrary_id_stamp_in_trait { + () => { + #[cfg(test)] + $crate::arbitrary_id_stamp_in_trait_internal___!(); + }; +} + #[cfg(test)] pub mod unshared_test_utils { use crate::accountant::DEFAULT_PENDING_TOO_LONG_SEC; @@ -888,7 +886,7 @@ pub mod unshared_test_utils { self } - pub fn permit_to_send_out(mut self) -> Self { + pub fn capture_msg_and_let_it_fly_on(mut self) -> Self { self.send_message_out = true; self } @@ -968,6 +966,7 @@ pub mod unshared_test_utils { pub mod arbitrary_id_stamp { use super::*; + use crate::arbitrary_id_stamp_in_trait; //The issues we are to solve might look as follows: @@ -999,27 +998,31 @@ pub mod unshared_test_utils { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ArbitraryIdStamp { - id: usize, + id_opt: Option, } impl ArbitraryIdStamp { pub fn new() -> Self { + let mut access = ARBITRARY_ID_STAMP_SEQUENCER.lock().unwrap(); + access.0 += 1; ArbitraryIdStamp { - id: { - let mut access = ARBITRARY_ID_STAMP_SEQUENCER.lock().unwrap(); - access.0 += 1; - access.0 - }, + id_opt: Some(access.0), } } + + pub fn null() -> Self { + ArbitraryIdStamp { id_opt: None } + } } // To be added together with other methods in your trait + // DO NOT USE ME DIRECTLY, USE arbitrary_id_stamp_in_trait INSTEAD! #[macro_export] - macro_rules! arbitrary_id_stamp_in_trait { + macro_rules! arbitrary_id_stamp_in_trait_internal___ { () => { - #[cfg(test)] - fn arbitrary_id_stamp(&self) -> ArbitraryIdStamp { + fn arbitrary_id_stamp( + &self, + ) -> crate::test_utils::unshared_test_utils::arbitrary_id_stamp::ArbitraryIdStamp { // No necessity to implement this method for all impls, // basically you want to do that just for the mock version @@ -1042,7 +1045,23 @@ pub mod unshared_test_utils { macro_rules! arbitrary_id_stamp_in_trait_impl { () => { fn arbitrary_id_stamp(&self) -> ArbitraryIdStamp { - *self.arbitrary_id_stamp_opt.as_ref().unwrap() + match self.arbitrary_id_stamp_opt { + Some(id) => id, + // In some implementations of mocks that have methods demanding args, the best we can do in order to + // capture and examine these args in assertions is to receive the ArbitraryIdStamp of the given + // argument. + // If such strategy is once decided for, transfers of this id will have to happen in all the tests + // relying on this mock, while also calling the intended method. So even in cases where we certainly + // are not really interested in checking that id, if we ignored that, the call of this method would + // blow up because the field that stores it is likely optional, with the value defaulted to None. + // + // As prevention of confusion from putting a requirement on devs to set the id stamp even though + // they're not planning to use it, we have a null type of that stamp to be there at most cases. + // As a result, we don't risk a direct punishment (for the None value being the problem) but also + // we'll set the assertion on fire if it doesn't match the expected id in tests where we suddenly + // do care + None => ArbitraryIdStamp::null(), + } } }; } @@ -1176,6 +1195,28 @@ pub mod unshared_test_utils { } } } + + pub struct SubsFactoryTestAddrLeaker + where + A: actix::Actor, + { + pub address_leaker: Sender>, + } + + impl SubsFactoryTestAddrLeaker + where + A: actix::Actor, + { + pub fn send_leaker_msg_and_return_meaningless_subs( + &self, + addr: &Addr, + make_subs_from_recorder_fn: fn(&Addr) -> S, + ) -> S { + self.address_leaker.try_send(addr.clone()).unwrap(); + let meaningless_addr = Recorder::new().start(); + make_subs_from_recorder_fn(&meaningless_addr) + } + } } #[cfg(test)] diff --git a/node/src/test_utils/neighborhood_test_utils.rs b/node/src/test_utils/neighborhood_test_utils.rs index d9899c3f1..25dfeeadb 100644 --- a/node/src/test_utils/neighborhood_test_utils.rs +++ b/node/src/test_utils/neighborhood_test_utils.rs @@ -19,6 +19,7 @@ use std::net::IpAddr; use std::net::Ipv4Addr; pub const MIN_HOPS_FOR_TEST: Hops = DEFAULT_MIN_HOPS; +pub const DB_PATCH_SIZE_FOR_TEST: u8 = DEFAULT_MIN_HOPS as u8; impl From<(&NeighborhoodDatabase, &PublicKey, bool)> for AccessibleGossipRecord { fn from( diff --git a/node/src/test_utils/persistent_configuration_mock.rs b/node/src/test_utils/persistent_configuration_mock.rs index 1f8fceadc..e93ce308c 100644 --- a/node/src/test_utils/persistent_configuration_mock.rs +++ b/node/src/test_utils/persistent_configuration_mock.rs @@ -12,6 +12,7 @@ use masq_lib::utils::AutomapProtocol; use masq_lib::utils::NeighborhoodModeLight; use std::cell::RefCell; use std::sync::{Arc, Mutex}; +use std::u64; #[allow(clippy::type_complexity)] #[derive(Clone, Default)] @@ -59,6 +60,10 @@ pub struct PersistentConfigurationMock { start_block_results: RefCell>>, set_start_block_params: Arc>>, set_start_block_results: RefCell>>, + max_block_count_params: Arc>>, + max_block_count_results: RefCell, PersistentConfigError>>>, + set_max_block_count_params: Arc>>>, + set_max_block_count_results: RefCell>>, payment_thresholds_results: RefCell>>, set_payment_thresholds_params: Arc>>, set_payment_thresholds_results: RefCell>>, @@ -232,6 +237,16 @@ impl PersistentConfiguration for PersistentConfigurationMock { Self::result_from(&self.set_start_block_results) } + fn max_block_count(&self) -> Result, PersistentConfigError> { + self.max_block_count_params.lock().unwrap().push(()); + Self::result_from(&self.max_block_count_results) + } + + fn set_max_block_count(&mut self, value: Option) -> Result<(), PersistentConfigError> { + self.set_max_block_count_params.lock().unwrap().push(value); + Self::result_from(&self.set_max_block_count_results) + } + fn set_wallet_info( &mut self, consuming_wallet_private_key: &str, @@ -312,7 +327,7 @@ impl PersistentConfigurationMock { self } - pub fn current_schema_version_result(self, result: &str) -> PersistentConfigurationMock { + pub fn current_schema_version_result(self, result: &str) -> Self { self.current_schema_version_results .borrow_mut() .push(result.to_string()); @@ -333,55 +348,37 @@ impl PersistentConfigurationMock { pub fn change_password_params( mut self, params: &Arc, String)>>>, - ) -> PersistentConfigurationMock { + ) -> Self { self.change_password_params = params.clone(); self } - pub fn change_password_result( - self, - result: Result<(), PersistentConfigError>, - ) -> PersistentConfigurationMock { + pub fn change_password_result(self, result: Result<(), PersistentConfigError>) -> Self { self.change_password_results.borrow_mut().push(result); self } - pub fn check_password_params( - mut self, - params: &Arc>>>, - ) -> PersistentConfigurationMock { + pub fn check_password_params(mut self, params: &Arc>>>) -> Self { self.check_password_params = params.clone(); self } - pub fn check_password_result( - self, - result: Result, - ) -> PersistentConfigurationMock { + pub fn check_password_result(self, result: Result) -> Self { self.check_password_results.borrow_mut().push(result); self } - pub fn clandestine_port_result( - self, - result: Result, - ) -> PersistentConfigurationMock { + pub fn clandestine_port_result(self, result: Result) -> Self { self.clandestine_port_results.borrow_mut().push(result); self } - pub fn set_clandestine_port_params( - mut self, - params: &Arc>>, - ) -> PersistentConfigurationMock { + pub fn set_clandestine_port_params(mut self, params: &Arc>>) -> Self { self.set_clandestine_port_params = params.clone(); self } - pub fn set_clandestine_port_result( - self, - result: Result<(), PersistentConfigError>, - ) -> PersistentConfigurationMock { + pub fn set_clandestine_port_result(self, result: Result<(), PersistentConfigError>) -> Self { self.set_clandestine_port_results.borrow_mut().push(result); self } @@ -410,7 +407,7 @@ impl PersistentConfigurationMock { pub fn neighborhood_mode_result( self, result: Result, - ) -> PersistentConfigurationMock { + ) -> Self { self.neighborhood_mode_results.borrow_mut().push(result); self } @@ -418,23 +415,17 @@ impl PersistentConfigurationMock { pub fn set_neighborhood_mode_params( mut self, params: &Arc>>, - ) -> PersistentConfigurationMock { + ) -> Self { self.set_neighborhood_mode_params = params.clone(); self } - pub fn set_neighborhood_mode_result( - self, - result: Result<(), PersistentConfigError>, - ) -> PersistentConfigurationMock { + pub fn set_neighborhood_mode_result(self, result: Result<(), PersistentConfigError>) -> Self { self.set_neighborhood_mode_results.borrow_mut().push(result); self } - pub fn consuming_wallet_params( - mut self, - params: &Arc>>, - ) -> PersistentConfigurationMock { + pub fn consuming_wallet_params(mut self, params: &Arc>>) -> Self { self.consuming_wallet_params = params.clone(); self } @@ -442,15 +433,12 @@ impl PersistentConfigurationMock { pub fn consuming_wallet_result( self, result: Result, PersistentConfigError>, - ) -> PersistentConfigurationMock { + ) -> Self { self.consuming_wallet_results.borrow_mut().push(result); self } - pub fn consuming_wallet_private_key_params( - mut self, - params: &Arc>>, - ) -> PersistentConfigurationMock { + pub fn consuming_wallet_private_key_params(mut self, params: &Arc>>) -> Self { self.consuming_wallet_private_key_params = params.clone(); self } @@ -458,7 +446,7 @@ impl PersistentConfigurationMock { pub fn consuming_wallet_private_key_result( self, result: Result, PersistentConfigError>, - ) -> PersistentConfigurationMock { + ) -> Self { self.consuming_wallet_private_key_results .borrow_mut() .push(result); @@ -469,7 +457,7 @@ impl PersistentConfigurationMock { pub fn set_wallet_info_params( mut self, params: &Arc>>, - ) -> PersistentConfigurationMock { + ) -> Self { self.set_wallet_info_params = params.clone(); self } @@ -484,10 +472,7 @@ impl PersistentConfigurationMock { self } - pub fn set_gas_price_params( - mut self, - params: &Arc>>, - ) -> PersistentConfigurationMock { + pub fn set_gas_price_params(mut self, params: &Arc>>) -> Self { self.set_gas_price_params = params.clone(); self } @@ -497,10 +482,7 @@ impl PersistentConfigurationMock { self } - pub fn past_neighbors_params( - mut self, - params: &Arc>>, - ) -> PersistentConfigurationMock { + pub fn past_neighbors_params(mut self, params: &Arc>>) -> Self { self.past_neighbors_params = params.clone(); self } @@ -508,7 +490,7 @@ impl PersistentConfigurationMock { pub fn past_neighbors_result( self, result: Result>, PersistentConfigError>, - ) -> PersistentConfigurationMock { + ) -> Self { self.past_neighbors_results.borrow_mut().push(result); self } @@ -517,15 +499,12 @@ impl PersistentConfigurationMock { pub fn set_past_neighbors_params( mut self, params: &Arc>, String)>>>, - ) -> PersistentConfigurationMock { + ) -> Self { self.set_past_neighbors_params = params.clone(); self } - pub fn set_past_neighbors_result( - self, - result: Result<(), PersistentConfigError>, - ) -> PersistentConfigurationMock { + pub fn set_past_neighbors_result(self, result: Result<(), PersistentConfigError>) -> Self { self.set_past_neighbors_results.borrow_mut().push(result); self } @@ -533,7 +512,7 @@ impl PersistentConfigurationMock { pub fn earning_wallet_result( self, result: Result, PersistentConfigError>, - ) -> PersistentConfigurationMock { + ) -> Self { self.earning_wallet_results.borrow_mut().push(result); self } @@ -541,7 +520,7 @@ impl PersistentConfigurationMock { pub fn earning_wallet_address_result( self, result: Result, PersistentConfigError>, - ) -> PersistentConfigurationMock { + ) -> Self { self.earning_wallet_address_results .borrow_mut() .push(result); @@ -558,10 +537,7 @@ impl PersistentConfigurationMock { self } - pub fn set_start_block_params( - mut self, - params: &Arc>>, - ) -> PersistentConfigurationMock { + pub fn set_start_block_params(mut self, params: &Arc>>) -> Self { self.set_start_block_params = params.clone(); self } @@ -571,6 +547,29 @@ impl PersistentConfigurationMock { self } + pub fn max_block_count_params(mut self, params: &Arc>>) -> Self { + self.max_block_count_params = params.clone(); + self + } + + pub fn max_block_count_result( + self, + result: Result, PersistentConfigError>, + ) -> Self { + self.max_block_count_results.borrow_mut().push(result); + self + } + + pub fn set_max_block_count_params(mut self, params: &Arc>>>) -> Self { + self.set_max_block_count_params = params.clone(); + self + } + + pub fn set_max_block_count_result(self, result: Result<(), PersistentConfigError>) -> Self { + self.set_max_block_count_results.borrow_mut().push(result); + self + } + pub fn payment_thresholds_result( self, result: Result, diff --git a/node/src/test_utils/recorder.rs b/node/src/test_utils/recorder.rs index e63844706..22566cbdc 100644 --- a/node/src/test_utils/recorder.rs +++ b/node/src/test_utils/recorder.rs @@ -1,6 +1,9 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. #![cfg(test)] -use crate::accountant::{ConsumingWalletBalancesAndQualifiedPayables, ReportTransactionReceipts}; + +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::BlockchainAgentWithContextMessage; +use crate::accountant::scanners::mid_scan_msg_handling::payable_scanner::msgs::QualifiedPayablesMessage; +use crate::accountant::ReportTransactionReceipts; use crate::accountant::{ ReceivedPayments, RequestTransactionReceipts, ScanError, ScanForPayables, ScanForPendingPayables, ScanForReceivables, SentPayables, @@ -15,22 +18,23 @@ use crate::sub_lib::accountant::AccountantSubs; use crate::sub_lib::accountant::ReportExitServiceProvidedMessage; use crate::sub_lib::accountant::ReportRoutingServiceProvidedMessage; use crate::sub_lib::accountant::ReportServicesConsumedMessage; -use crate::sub_lib::blockchain_bridge::ReportAccountsPayable; -use crate::sub_lib::blockchain_bridge::{BlockchainBridgeSubs, RequestBalancesToPayPayables}; -use crate::sub_lib::configurator::{ConfiguratorSubs, NewPasswordMessage}; +use crate::sub_lib::blockchain_bridge::BlockchainBridgeSubs; +use crate::sub_lib::blockchain_bridge::OutboundPaymentsInstructions; +use crate::sub_lib::configurator::NewPasswordMessage; use crate::sub_lib::dispatcher::InboundClientData; use crate::sub_lib::dispatcher::{DispatcherSubs, StreamShutdownMsg}; use crate::sub_lib::hopper::IncipientCoresPackage; use crate::sub_lib::hopper::{ExpiredCoresPackage, NoLookupIncipientCoresPackage}; use crate::sub_lib::hopper::{HopperSubs, MessageType}; -use crate::sub_lib::neighborhood::ConnectionProgressMessage; use crate::sub_lib::neighborhood::NeighborhoodSubs; +use crate::sub_lib::neighborhood::{ConfigurationChangeMessage, ConnectionProgressMessage}; +use crate::sub_lib::configurator::ConfiguratorSubs; use crate::sub_lib::neighborhood::NodeQueryResponseMetadata; -use crate::sub_lib::neighborhood::NodeRecordMetadataMessage; use crate::sub_lib::neighborhood::RemoveNeighborMessage; use crate::sub_lib::neighborhood::RouteQueryMessage; use crate::sub_lib::neighborhood::RouteQueryResponse; +use crate::sub_lib::neighborhood::UpdateNodeRecordMetadataMessage; use crate::sub_lib::neighborhood::{DispatcherNodeQueryMessage, GossipFailure_0v1}; use crate::sub_lib::peer_actors::PeerActors; use crate::sub_lib::peer_actors::{BindMessage, NewPublicIp, StartMessage}; @@ -40,12 +44,13 @@ use crate::sub_lib::proxy_server::ProxyServerSubs; use crate::sub_lib::proxy_server::{ AddReturnRouteMessage, AddRouteMessage, ClientRequestPayload_0v1, }; -use crate::sub_lib::set_consuming_wallet_message::SetConsumingWalletMessage; use crate::sub_lib::stream_handler_pool::DispatcherNodeQueryResponse; use crate::sub_lib::stream_handler_pool::TransmitDataMsg; use crate::sub_lib::ui_gateway::UiGatewaySubs; use crate::sub_lib::utils::MessageScheduler; -use crate::test_utils::recorder_stop_conditions::StopConditions; +use crate::test_utils::recorder_stop_conditions::{ + ForcedMatchable, PretendedMatchableWrapper, StopConditions, +}; use crate::test_utils::to_millis; use crate::test_utils::unshared_test_utils::system_killer_actor::SystemKillerActor; use actix::Addr; @@ -55,7 +60,7 @@ use actix::MessageResult; use actix::System; use actix::{Actor, Message}; use masq_lib::ui_gateway::{NodeFromUiMessage, NodeToUiMessage}; -use std::any::Any; +use std::any::{Any, TypeId}; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; @@ -82,66 +87,91 @@ impl Actor for Recorder { type Context = Context; } -macro_rules! recorder_message_handler { - ($message_type: ty) => { +macro_rules! message_handler_common { + ($message_type: ty, $handling_fn: ident) => { impl Handler<$message_type> for Recorder { type Result = (); fn handle(&mut self, msg: $message_type, _ctx: &mut Self::Context) { - self.handle_msg(msg) + self.$handling_fn(msg) + } + } + }; +} + +macro_rules! matchable { + ($message_type: ty) => { + impl ForcedMatchable<$message_type> for $message_type { + fn correct_msg_type_id(&self) -> TypeId { + TypeId::of::<$message_type>() } } }; } -recorder_message_handler!(AddReturnRouteMessage); -recorder_message_handler!(AddRouteMessage); -recorder_message_handler!(AddStreamMsg); -recorder_message_handler!(BindMessage); -recorder_message_handler!(CrashNotification); -recorder_message_handler!(DaemonBindMessage); -recorder_message_handler!(DispatcherNodeQueryMessage); -recorder_message_handler!(DispatcherNodeQueryResponse); -recorder_message_handler!(DnsResolveFailure_0v1); -recorder_message_handler!(ExpiredCoresPackage); -recorder_message_handler!(ExpiredCoresPackage); -recorder_message_handler!(ExpiredCoresPackage); -recorder_message_handler!(ExpiredCoresPackage); -recorder_message_handler!(ExpiredCoresPackage); -recorder_message_handler!(ExpiredCoresPackage); -recorder_message_handler!(InboundClientData); -recorder_message_handler!(InboundServerData); -recorder_message_handler!(IncipientCoresPackage); -recorder_message_handler!(NewPasswordMessage); -recorder_message_handler!(NewPublicIp); -recorder_message_handler!(NodeFromUiMessage); -recorder_message_handler!(NodeToUiMessage); -recorder_message_handler!(NodeRecordMetadataMessage); -recorder_message_handler!(NoLookupIncipientCoresPackage); -recorder_message_handler!(PoolBindMessage); -recorder_message_handler!(ReceivedPayments); -recorder_message_handler!(RemoveNeighborMessage); -recorder_message_handler!(RemoveStreamMsg); -recorder_message_handler!(ReportServicesConsumedMessage); -recorder_message_handler!(ReportExitServiceProvidedMessage); -recorder_message_handler!(ReportRoutingServiceProvidedMessage); -recorder_message_handler!(ScanError); -recorder_message_handler!(ConsumingWalletBalancesAndQualifiedPayables); -recorder_message_handler!(SentPayables); -recorder_message_handler!(SetConsumingWalletMessage); -recorder_message_handler!(RequestBalancesToPayPayables); -recorder_message_handler!(StartMessage); -recorder_message_handler!(StreamShutdownMsg); -recorder_message_handler!(TransmitDataMsg); -recorder_message_handler!(PendingPayableFingerprintSeeds); -recorder_message_handler!(RetrieveTransactions); -recorder_message_handler!(RequestTransactionReceipts); -recorder_message_handler!(ReportTransactionReceipts); -recorder_message_handler!(ReportAccountsPayable); -recorder_message_handler!(ScanForReceivables); -recorder_message_handler!(ScanForPayables); -recorder_message_handler!(ConnectionProgressMessage); -recorder_message_handler!(ScanForPendingPayables); +// t, m, p (type, match, predicate) represents a list of the possible system stop conditions + +macro_rules! recorder_message_handler_t_m_p { + ($message_type: ty) => { + message_handler_common!($message_type, handle_msg_t_m_p); + matchable!($message_type); + }; +} + +macro_rules! recorder_message_handler_t_p { + ($message_type: ty) => { + message_handler_common!($message_type, handle_msg_t_p); + }; +} + +recorder_message_handler_t_m_p!(AddReturnRouteMessage); +recorder_message_handler_t_m_p!(AddRouteMessage); +recorder_message_handler_t_p!(AddStreamMsg); +recorder_message_handler_t_m_p!(BindMessage); +recorder_message_handler_t_p!(BlockchainAgentWithContextMessage); +recorder_message_handler_t_m_p!(ConfigurationChangeMessage); +recorder_message_handler_t_m_p!(ConnectionProgressMessage); +recorder_message_handler_t_m_p!(CrashNotification); +recorder_message_handler_t_m_p!(DaemonBindMessage); +recorder_message_handler_t_m_p!(DispatcherNodeQueryMessage); +recorder_message_handler_t_m_p!(DispatcherNodeQueryResponse); +recorder_message_handler_t_m_p!(DnsResolveFailure_0v1); +recorder_message_handler_t_m_p!(ExpiredCoresPackage); +recorder_message_handler_t_m_p!(ExpiredCoresPackage); +recorder_message_handler_t_m_p!(ExpiredCoresPackage); +recorder_message_handler_t_m_p!(ExpiredCoresPackage); +recorder_message_handler_t_m_p!(ExpiredCoresPackage); +recorder_message_handler_t_m_p!(ExpiredCoresPackage); +recorder_message_handler_t_m_p!(InboundClientData); +recorder_message_handler_t_m_p!(InboundServerData); +recorder_message_handler_t_m_p!(IncipientCoresPackage); +recorder_message_handler_t_m_p!(NewPasswordMessage); // GH-728 +recorder_message_handler_t_m_p!(NewPublicIp); +recorder_message_handler_t_m_p!(NodeFromUiMessage); +recorder_message_handler_t_m_p!(NodeToUiMessage); +recorder_message_handler_t_m_p!(NoLookupIncipientCoresPackage); +recorder_message_handler_t_p!(OutboundPaymentsInstructions); +recorder_message_handler_t_m_p!(PendingPayableFingerprintSeeds); +recorder_message_handler_t_m_p!(PoolBindMessage); +recorder_message_handler_t_m_p!(QualifiedPayablesMessage); +recorder_message_handler_t_m_p!(ReceivedPayments); +recorder_message_handler_t_m_p!(RemoveNeighborMessage); +recorder_message_handler_t_m_p!(RemoveStreamMsg); +recorder_message_handler_t_m_p!(ReportExitServiceProvidedMessage); +recorder_message_handler_t_m_p!(ReportRoutingServiceProvidedMessage); +recorder_message_handler_t_m_p!(ReportServicesConsumedMessage); +recorder_message_handler_t_m_p!(ReportTransactionReceipts); +recorder_message_handler_t_m_p!(RequestTransactionReceipts); +recorder_message_handler_t_m_p!(RetrieveTransactions); +recorder_message_handler_t_m_p!(ScanError); +recorder_message_handler_t_m_p!(ScanForPayables); +recorder_message_handler_t_m_p!(ScanForPendingPayables); +recorder_message_handler_t_m_p!(ScanForReceivables); +recorder_message_handler_t_m_p!(SentPayables); +recorder_message_handler_t_m_p!(StartMessage); +recorder_message_handler_t_m_p!(StreamShutdownMsg); +recorder_message_handler_t_m_p!(TransmitDataMsg); +recorder_message_handler_t_m_p!(UpdateNodeRecordMetadataMessage); impl Handler> for Recorder where @@ -150,7 +180,17 @@ where type Result = (); fn handle(&mut self, msg: MessageScheduler, _ctx: &mut Self::Context) { - self.handle_msg(msg) + self.handle_msg_t_m_p(msg) + } +} + +impl ForcedMatchable for MessageScheduler +where + OuterM: PartialEq + 'static, + InnerM: PartialEq + Send + Message, +{ + fn correct_msg_type_id(&self) -> TypeId { + TypeId::of::() } } @@ -231,9 +271,12 @@ impl Recorder { system_killer.start(); } - fn handle_msg(&mut self, msg: T) { + fn handle_msg_t_m_p(&mut self, msg: M) + where + M: 'static + ForcedMatchable + Send, + { let kill_system = if let Some(stop_conditions) = &mut self.stop_conditions_opt { - stop_conditions.resolve_stop_conditions::(&msg) + stop_conditions.resolve_stop_conditions::(&msg) } else { false }; @@ -244,6 +287,14 @@ impl Recorder { System::current().stop() } } + + //for messages that cannot implement PartialEq + fn handle_msg_t_p(&mut self, msg: M) + where + M: 'static + Send, + { + self.handle_msg_t_m_p(PretendedMatchableWrapper(msg)) + } } impl Recording { @@ -279,7 +330,7 @@ impl Recording { self.get_record_inner_body(index).ok() } - fn get_record_inner_body(&self, index: usize) -> Result<&T, String> { + fn get_record_inner_body(&self, index: usize) -> Result<&T, String> { let item_box = match self.messages.get(index) { Some(item_box) => item_box, None => { @@ -290,14 +341,20 @@ impl Recording { )) } }; - let item_opt = item_box.downcast_ref::(); - - match item_opt { + match item_box.downcast_ref::() { Some(item) => Ok(item), - None => Err(format!( - "Message {:?} could not be downcast to the expected type", - item_box - )), + None => { + // double-checking for an uncommon, yet possible other type of an actor message, which doesn't implement PartialEq + let item_opt = item_box.downcast_ref::>(); + + match item_opt { + Some(item) => Ok(&item.0), + None => Err(format!( + "Message {:?} could not be downcast to the expected type", + item_box + )), + } + } } } } @@ -335,7 +392,7 @@ pub fn make_recorder() -> (Recorder, RecordAwaiter, Arc>) { (recorder, awaiter, recording) } -pub fn make_proxy_server_subs_from(addr: &Addr) -> ProxyServerSubs { +pub fn make_proxy_server_subs_from_recorder(addr: &Addr) -> ProxyServerSubs { ProxyServerSubs { bind: recipient!(addr, BindMessage), from_dispatcher: recipient!(addr, InboundClientData), @@ -344,12 +401,11 @@ pub fn make_proxy_server_subs_from(addr: &Addr) -> ProxyServerSubs { add_return_route: recipient!(addr, AddReturnRouteMessage), add_route: recipient!(addr, AddRouteMessage), stream_shutdown_sub: recipient!(addr, StreamShutdownMsg), - set_consuming_wallet_sub: recipient!(addr, SetConsumingWalletMessage), node_from_ui: recipient!(addr, NodeFromUiMessage), } } -pub fn make_dispatcher_subs_from(addr: &Addr) -> DispatcherSubs { +pub fn make_dispatcher_subs_from_recorder(addr: &Addr) -> DispatcherSubs { DispatcherSubs { ibcd_sub: recipient!(addr, InboundClientData), bind: recipient!(addr, BindMessage), @@ -360,7 +416,7 @@ pub fn make_dispatcher_subs_from(addr: &Addr) -> DispatcherSubs { } } -pub fn make_hopper_subs_from(addr: &Addr) -> HopperSubs { +pub fn make_hopper_subs_from_recorder(addr: &Addr) -> HopperSubs { HopperSubs { bind: recipient!(addr, BindMessage), from_hopper_client: recipient!(addr, IncipientCoresPackage), @@ -370,7 +426,7 @@ pub fn make_hopper_subs_from(addr: &Addr) -> HopperSubs { } } -pub fn make_proxy_client_subs_from(addr: &Addr) -> ProxyClientSubs { +pub fn make_proxy_client_subs_from_recorder(addr: &Addr) -> ProxyClientSubs { ProxyClientSubs { bind: recipient!(addr, BindMessage), from_hopper: recipient!(addr, ExpiredCoresPackage), @@ -380,21 +436,21 @@ pub fn make_proxy_client_subs_from(addr: &Addr) -> ProxyClientSubs { } } -pub fn make_neighborhood_subs_from(addr: &Addr) -> NeighborhoodSubs { +pub fn make_neighborhood_subs_from_recorder(addr: &Addr) -> NeighborhoodSubs { NeighborhoodSubs { bind: recipient!(addr, BindMessage), start: recipient!(addr, StartMessage), new_public_ip: recipient!(addr, NewPublicIp), route_query: recipient!(addr, RouteQueryMessage), - update_node_record_metadata: recipient!(addr, NodeRecordMetadataMessage), + update_node_record_metadata: recipient!(addr, UpdateNodeRecordMetadataMessage), from_hopper: recipient!(addr, ExpiredCoresPackage), gossip_failure: recipient!(addr, ExpiredCoresPackage), dispatcher_node_query: recipient!(addr, DispatcherNodeQueryMessage), remove_neighbor: recipient!(addr, RemoveNeighborMessage), + configuration_change_msg_sub: recipient!(addr, ConfigurationChangeMessage), stream_shutdown_sub: recipient!(addr, StreamShutdownMsg), - set_consuming_wallet_sub: recipient!(addr, SetConsumingWalletMessage), from_ui_message_sub: recipient!(addr, NodeFromUiMessage), - new_password_sub: recipient!(addr, NewPasswordMessage), + new_password_sub: recipient!(addr, NewPasswordMessage), // GH-728 connection_progress_sub: recipient!(addr, ConnectionProgressMessage), } } @@ -406,10 +462,7 @@ pub fn make_accountant_subs_from_recorder(addr: &Addr) -> AccountantSu report_routing_service_provided: recipient!(addr, ReportRoutingServiceProvidedMessage), report_exit_service_provided: recipient!(addr, ReportExitServiceProvidedMessage), report_services_consumed: recipient!(addr, ReportServicesConsumedMessage), - report_consuming_wallet_balances_and_qualified_payables: recipient!( - addr, - ConsumingWalletBalancesAndQualifiedPayables - ), + report_payable_payments_setup: recipient!(addr, BlockchainAgentWithContextMessage), report_inbound_payments: recipient!(addr, ReceivedPayments), init_pending_payable_fingerprints: recipient!(addr, PendingPayableFingerprintSeeds), report_transaction_receipts: recipient!(addr, ReportTransactionReceipts), @@ -427,18 +480,18 @@ pub fn make_ui_gateway_subs_from_recorder(addr: &Addr) -> UiGatewaySub } } -pub fn make_blockchain_bridge_subs_from(addr: &Addr) -> BlockchainBridgeSubs { +pub fn make_blockchain_bridge_subs_from_recorder(addr: &Addr) -> BlockchainBridgeSubs { BlockchainBridgeSubs { bind: recipient!(addr, BindMessage), - report_accounts_payable: recipient!(addr, ReportAccountsPayable), - request_balances_to_pay_payables: recipient!(addr, RequestBalancesToPayPayables), + outbound_payments_instructions: recipient!(addr, OutboundPaymentsInstructions), + qualified_payables: recipient!(addr, QualifiedPayablesMessage), retrieve_transactions: recipient!(addr, RetrieveTransactions), ui_sub: recipient!(addr, NodeFromUiMessage), request_transaction_receipts: recipient!(addr, RequestTransactionReceipts), } } -pub fn make_configurator_subs_from(addr: &Addr) -> ConfiguratorSubs { +pub fn make_configurator_subs_from_recorder(addr: &Addr) -> ConfiguratorSubs { ConfiguratorSubs { bind: recipient!(addr, BindMessage), node_from_ui_sub: recipient!(addr, NodeFromUiMessage), @@ -535,15 +588,15 @@ impl PeerActorsBuilder { let configurator_addr = self.configurator.start(); PeerActors { - proxy_server: make_proxy_server_subs_from(&proxy_server_addr), - dispatcher: make_dispatcher_subs_from(&dispatcher_addr), - hopper: make_hopper_subs_from(&hopper_addr), - proxy_client_opt: Some(make_proxy_client_subs_from(&proxy_client_addr)), - neighborhood: make_neighborhood_subs_from(&neighborhood_addr), + proxy_server: make_proxy_server_subs_from_recorder(&proxy_server_addr), + dispatcher: make_dispatcher_subs_from_recorder(&dispatcher_addr), + hopper: make_hopper_subs_from_recorder(&hopper_addr), + proxy_client_opt: Some(make_proxy_client_subs_from_recorder(&proxy_client_addr)), + neighborhood: make_neighborhood_subs_from_recorder(&neighborhood_addr), accountant: make_accountant_subs_from_recorder(&accountant_addr), ui_gateway: make_ui_gateway_subs_from_recorder(&ui_gateway_addr), - blockchain_bridge: make_blockchain_bridge_subs_from(&blockchain_bridge_addr), - configurator: make_configurator_subs_from(&configurator_addr), + blockchain_bridge: make_blockchain_bridge_subs_from_recorder(&blockchain_bridge_addr), + configurator: make_configurator_subs_from_recorder(&configurator_addr), } } } @@ -553,13 +606,14 @@ mod tests { use super::*; use actix::Message; use actix::System; + use std::any::TypeId; #[derive(Debug, PartialEq, Eq, Message)] struct FirstMessageType { string: String, } - recorder_message_handler!(FirstMessageType); + recorder_message_handler_t_m_p!(FirstMessageType); #[derive(Debug, PartialEq, Eq, Message)] struct SecondMessageType { @@ -567,7 +621,7 @@ mod tests { flag: bool, } - recorder_message_handler!(SecondMessageType); + recorder_message_handler_t_m_p!(SecondMessageType); #[test] fn recorder_records_different_messages() { @@ -608,4 +662,20 @@ mod tests { ); assert_eq!(recording.len(), 2); } + + struct ExampleMsgA; + + struct ExampleMsgB; + + #[test] + fn different_messages_in_pretending_matchable_have_different_type_ids() { + assert_eq!( + TypeId::of::>(), + TypeId::of::>() + ); + assert_ne!( + TypeId::of::>(), + TypeId::of::>() + ) + } } diff --git a/node/src/test_utils/recorder_stop_conditions.rs b/node/src/test_utils/recorder_stop_conditions.rs index c9a18cbcb..b3dca287d 100644 --- a/node/src/test_utils/recorder_stop_conditions.rs +++ b/node/src/test_utils/recorder_stop_conditions.rs @@ -24,14 +24,17 @@ pub type BoxedMsgExpected = Box; pub type RefMsgExpected<'a> = &'a (dyn Any + Send); impl StopConditions { - pub fn resolve_stop_conditions(&mut self, msg: &T) -> bool { + pub fn resolve_stop_conditions + Send + 'static>( + &mut self, + msg: &T, + ) -> bool { match self { StopConditions::Any(conditions) => Self::resolve_any::(conditions, msg), StopConditions::All(conditions) => Self::resolve_all::(conditions, msg), } } - fn resolve_any( + fn resolve_any + Send + 'static>( conditions: &Vec, msg: &T, ) -> bool { @@ -40,7 +43,7 @@ impl StopConditions { .any(|condition| condition.resolve_condition::(msg)) } - fn resolve_all( + fn resolve_all + Send + 'static>( conditions: &mut Vec, msg: &T, ) -> bool { @@ -49,7 +52,7 @@ impl StopConditions { conditions.is_empty() } - fn indexes_of_matched_conditions( + fn indexes_of_matched_conditions + Send + 'static>( conditions: &[StopCondition], msg: &T, ) -> Vec { @@ -82,9 +85,9 @@ impl StopConditions { } impl StopCondition { - fn resolve_condition(&self, msg: &T) -> bool { + fn resolve_condition + Send + 'static>(&self, msg: &T) -> bool { match self { - StopCondition::StopOnType(type_id) => Self::matches_stop_on_type::(*type_id), + StopCondition::StopOnType(type_id) => Self::matches_stop_on_type::(msg, *type_id), StopCondition::StopOnMatch { exemplar } => { Self::matches_stop_on_match::(exemplar, msg) } @@ -94,12 +97,12 @@ impl StopCondition { } } - fn matches_stop_on_type(expected_type_id: TypeId) -> bool { - let msg_type_id = TypeId::of::(); - msg_type_id == expected_type_id + fn matches_stop_on_type>(msg: &T, expected_type_id: TypeId) -> bool { + let correct_msg_type_id = msg.correct_msg_type_id(); + correct_msg_type_id == expected_type_id } - fn matches_stop_on_match( + fn matches_stop_on_match + 'static + Send>( exemplar: &BoxedMsgExpected, msg: &T, ) -> bool { @@ -117,6 +120,33 @@ impl StopCondition { } } +pub trait ForcedMatchable: PartialEq + Send { + fn correct_msg_type_id(&self) -> TypeId; +} + +pub struct PretendedMatchableWrapper(pub M); + +impl ForcedMatchable for PretendedMatchableWrapper +where + OuterM: PartialEq, + InnerM: Send, +{ + fn correct_msg_type_id(&self) -> TypeId { + TypeId::of::() + } +} + +impl PartialEq for PretendedMatchableWrapper { + fn eq(&self, _other: &Self) -> bool { + panic!( + r#"You requested StopCondition::StopOnMatch for message + that does not implement PartialEq. Consider two other + options: matching the type simply by its TypeId or using + a predicate."# + ) + } +} + #[macro_export] macro_rules! match_every_type_id{ ($($single_message: ident),+) => { diff --git a/node/tests/financials_test.rs b/node/tests/financials_test.rs index 981c81ebb..0874cb39b 100644 --- a/node/tests/financials_test.rs +++ b/node/tests/financials_test.rs @@ -10,11 +10,9 @@ use masq_lib::messages::{ use masq_lib::test_utils::ui_connection::UiConnection; use masq_lib::test_utils::utils::ensure_node_home_directory_exists; use masq_lib::utils::find_free_port; -use node_lib::accountant::database_access_objects::dao_utils::{from_time_t, to_time_t}; -use node_lib::accountant::database_access_objects::payable_dao::{PayableDao, PayableDaoReal}; -use node_lib::accountant::database_access_objects::receivable_dao::{ - ReceivableDao, ReceivableDaoReal, -}; +use node_lib::accountant::db_access_objects::payable_dao::{PayableDao, PayableDaoReal}; +use node_lib::accountant::db_access_objects::receivable_dao::{ReceivableDao, ReceivableDaoReal}; +use node_lib::accountant::db_access_objects::utils::{from_time_t, to_time_t}; use node_lib::accountant::gwei_to_wei; use node_lib::test_utils::make_wallet; use std::time::SystemTime; From 18775134732af93e151a160c18ba5ebf674006a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Mon, 8 Jan 2024 14:08:48 +0100 Subject: [PATCH 45/52] fix ENV and CLAP Guards in tests --- node/src/node_configurator/node_configurator_standard.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 3d1a93027..bd22cc1d6 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -532,9 +532,9 @@ mod tests { #[test] fn server_initializer_collected_params_can_read_parameters_from_config_file() { - running_test(); let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); + running_test(); let home_dir = ensure_node_home_directory_exists( "node_configurator_standard", "server_initializer_collected_params_can_read_parameters_from_config_file", @@ -849,9 +849,9 @@ mod tests { #[test] fn server_initializer_collected_params_handle_dot_config_file_path_and_reads_arguments_from_cf() { - running_test(); let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); + running_test(); let home_dir = ensure_node_home_directory_exists( "node_configurator_standard", "server_initializer_collected_params_handle_dot_config_file_path_and_reads_arguments_from_cf", @@ -899,9 +899,9 @@ mod tests { #[test] fn server_initializer_collected_params_handles_only_path_in_config_file_param() { - running_test(); let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); + running_test(); let home_dir = ensure_node_home_directory_exists( "node_configurator_standard", "server_initializer_collected_params_handles_only_path_in_config_file_param", @@ -943,9 +943,9 @@ mod tests { #[test] fn server_initializer_collected_params_rewrite_config_files_parameters_from_command_line() { - running_test(); let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); + running_test(); let home_dir = ensure_node_home_directory_exists( "node_configurator_standard", "server_initializer_collected_params_rewrite_config_files_parameters_from_command_line", @@ -1583,7 +1583,6 @@ mod tests { let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); running_test(); - let _guard = EnvironmentGuard::new(); let home_dir = Path::new("/home/cooga"); let home_dir_poly_main = home_dir.join(".local").join("MASQ").join("polygon-mainnet"); let home_dir_poly_mumbai = home_dir.join(".local").join("MASQ").join("polygon-mumbai"); From 29a1c3d5868350d7a757021a251e7bc3506a93cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Mon, 8 Jan 2024 15:34:06 +0100 Subject: [PATCH 46/52] fix Windows panic message for server_initializer_collected_params_handles_only_path_in_config_file_param --- .../node_configurator/node_configurator_standard.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index bd22cc1d6..cf0d0e7e5 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -926,6 +926,17 @@ mod tests { let result = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()).unwrap_err(); + #[cfg(target_os = "windows")] + let result_path = format!( + "Couldn't open configuration file \"{}\". Are you sure it exists?", + current_dir() + .expect("expected current dir") + .as_path() + .join(home_dir.as_path()) + .to_str() + .unwrap() + ); + #[cfg(not(target_os = "windows"))] let result_path = format!( "The permissions on configuration file \"{}\" make it unreadable.", current_dir() From 4bae1dd9c396416695b3c1d7b9e62eecf807ef71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Mon, 8 Jan 2024 15:37:08 +0100 Subject: [PATCH 47/52] formatting --- node/src/node_configurator/node_configurator_standard.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index cf0d0e7e5..f2b197791 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -927,7 +927,7 @@ mod tests { server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()).unwrap_err(); #[cfg(target_os = "windows")] - let result_path = format!( + let result_path = format!( "Couldn't open configuration file \"{}\". Are you sure it exists?", current_dir() .expect("expected current dir") From 4a75406a865e55e80cf2732b245b0b6438eb4a40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Mon, 8 Jan 2024 17:06:15 +0100 Subject: [PATCH 48/52] removing debugging println from accountant --- node/src/accountant/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index f4da3b3d2..f8764690f 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -4481,7 +4481,6 @@ mod tests { let factory = Accountant::dao_factory(data_dir); factory.make(); }; - println!("make_dao_factory_uses_panic_on_migration {:?}", &data_dir); assert_on_initialization_with_panic_on_migration(&data_dir, &act); } } From ee900f9e36fe3c6a18c3e53c94e411efa9657b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Thu, 18 Jan 2024 21:20:03 +0100 Subject: [PATCH 49/52] fix CLI UI issue with config file --- node/src/daemon/setup_reporter.rs | 53 +++++++++++++++++++ node/src/node_configurator/mod.rs | 29 +++++----- .../node_configurator_standard.rs | 3 +- 3 files changed, 67 insertions(+), 18 deletions(-) diff --git a/node/src/daemon/setup_reporter.rs b/node/src/daemon/setup_reporter.rs index e83a522bf..b8473b05d 100644 --- a/node/src/daemon/setup_reporter.rs +++ b/node/src/daemon/setup_reporter.rs @@ -1216,6 +1216,7 @@ mod tests { }; use crate::test_utils::{assert_string_contains, rate_pack}; use core::option::Option; + use dirs::home_dir; use masq_lib::blockchains::chains::Chain as Blockchain; use masq_lib::blockchains::chains::Chain::PolyMumbai; use masq_lib::constants::{DEFAULT_CHAIN, DEFAULT_GAS_PRICE}; @@ -2031,6 +2032,58 @@ mod tests { assert_eq!(actual_data_directory, expected_data_directory); } + #[test] + fn get_modified_setup_tilde_in_config_file_path() { + let _guard = EnvironmentGuard::new(); + let base_dir = ensure_node_home_directory_exists( + "setup_reporter", + "get_modified_setup_tilde_in_data_directory", + ); + let data_dir = base_dir.join("data_dir"); + std::fs::create_dir_all(home_dir().expect("expect home dir").join("masqhome")).unwrap(); + let mut config_file = File::create( + home_dir() + .expect("expect home dir") + .join("masqhome") + .join("config.toml"), + ) + .unwrap(); + config_file + .write_all(b"blockchain-service-url = \"https://www.mainnet.com\"\n") + .unwrap(); + let existing_setup = setup_cluster_from(vec![ + ("neighborhood-mode", "zero-hop", Set), + ("chain", DEFAULT_CHAIN.rec().literal_identifier, Default), + ( + "data-directory", + &data_dir.to_string_lossy().to_string(), + Default, + ), + ]); + let incoming_setup = vec![ + ("data-directory", "~/masqhome"), + ("config-file", "~/masqhome/config.toml"), + ] + .into_iter() + .map(|(name, value)| UiSetupRequestValue::new(name, value)) + .collect_vec(); + + let expected_config_file_data = "https://www.mainnet.com"; + let dirs_wrapper = Box::new( + DirsWrapperMock::new() + .data_dir_result(Some(data_dir)) + .home_dir_result(Some(base_dir)), + ); + let subject = SetupReporterReal::new(dirs_wrapper); + + let result = subject + .get_modified_setup(existing_setup, incoming_setup) + .unwrap(); + + let actual_config_file_data = result.get("blockchain-service-url").unwrap().value.as_str(); + assert_eq!(actual_config_file_data, expected_config_file_data); + } + #[test] fn get_modified_setup_user_specified_data_directory_depends_on_new_chain_on_success() { let _guard = EnvironmentGuard::new(); diff --git a/node/src/node_configurator/mod.rs b/node/src/node_configurator/mod.rs index ce002c7b6..a45a2969a 100644 --- a/node/src/node_configurator/mod.rs +++ b/node/src/node_configurator/mod.rs @@ -119,21 +119,18 @@ fn get_data_directory_from_mc( } } -fn replace_tilde(config_path: PathBuf, dirs_wrapper: &dyn DirsWrapper) -> PathBuf { +fn replace_tilde(config_path: PathBuf) -> PathBuf { match config_path.starts_with("~") { - true => { - let home_dir_from_wrapper = dirs_wrapper.home_dir(); - PathBuf::from( - config_path.display().to_string().replacen( - '~', - home_dir_from_wrapper - .expect("expected users home_dir") - .to_str() - .expect("expected str home_dir"), - 1, - ), - ) - } + true => PathBuf::from( + config_path.display().to_string().replacen( + '~', + home_dir() + .expect("expected users home_dir") + .to_str() + .expect("expected str home_dir"), + 1, + ), + ), false => config_path, } } @@ -169,13 +166,12 @@ fn get_config_file_from_mc( multi_config: &MultiConfig, data_directory: &Path, data_directory_def: bool, - dirs_wrapper: &dyn DirsWrapper, ) -> FieldPair { let mut panic: bool = false; let config_file = value_m!(multi_config, "config-file", PathBuf); match config_file { Some(config_path) => { - let config_path = replace_tilde(config_path, dirs_wrapper); + let config_path = replace_tilde(config_path); let config_path = replace_dots(config_path); let config_path = replace_relative_path(config_path, data_directory_def, data_directory, &mut panic); @@ -220,7 +216,6 @@ fn config_file_data_dir_real_user_chain_from_mc( &multi_config, &initialization_data.data_directory.item, initialization_data.data_directory.user_specified, - dirs_wrapper, ); initialization_data } diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index f2b197791..639c83556 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -376,6 +376,7 @@ mod tests { make_pre_populated_mocked_directory_wrapper, make_simplified_multi_config, }; use crate::test_utils::{assert_string_contains, main_cryptde, ArgsBuilder}; + use dirs::home_dir; use masq_lib::blockchains::chains::Chain; use masq_lib::constants::DEFAULT_CHAIN; use masq_lib::multi_config::VirtualCommandLine; @@ -1075,7 +1076,7 @@ mod tests { running_test(); let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); - let home_dir = ensure_node_home_directory_exists( "node_configurator_standard","server_initializer_collected_params_handle_tilde_in_path_config_file_from_commandline_and_real_user_from_config_file"); + let home_dir = home_dir().expect("expectexd home dir"); let home_dir = canonicalize(home_dir).unwrap(); let data_dir = &home_dir.join("masqhome"); let _create_data_dir = create_dir_all(data_dir); From 3b829b761f07508719994c401fc3952df8f2afc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Mon, 22 Jan 2024 16:40:34 +0100 Subject: [PATCH 50/52] remove canonicalize() from test uses home_dir() from dirs crate. Produces malfuncioning behaviour --- node/src/node_configurator/node_configurator_standard.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 639c83556..4fa11f22c 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -1077,7 +1077,6 @@ mod tests { let _guard = EnvironmentGuard::new(); let _clap_guard = ClapGuard::new(); let home_dir = home_dir().expect("expectexd home dir"); - let home_dir = canonicalize(home_dir).unwrap(); let data_dir = &home_dir.join("masqhome"); let _create_data_dir = create_dir_all(data_dir); let config_file_relative = File::create(data_dir.join("config.toml")).unwrap(); From cb58d9728dc8a53dc6f1a9d1d26b70c01886f240 Mon Sep 17 00:00:00 2001 From: Dan Wiebe Date: Wed, 24 Jan 2024 07:50:36 -0500 Subject: [PATCH 51/52] Reformatted some code, added a comment --- node/src/daemon/setup_reporter.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/node/src/daemon/setup_reporter.rs b/node/src/daemon/setup_reporter.rs index b8473b05d..0bb1628c1 100644 --- a/node/src/daemon/setup_reporter.rs +++ b/node/src/daemon/setup_reporter.rs @@ -46,10 +46,13 @@ use std::str::FromStr; const CONSOLE_DIAGNOSTICS: bool = false; -const ARG_PAIRS_SENSITIVE_TO_SETUP_ERRS: &[ErrorSensitiveArgPair] = &[ErrorSensitiveArgPair { - blanked_arg: "chain", - linked_arg: "data-directory", -}]; +const ARG_PAIRS_SENSITIVE_TO_SETUP_ERRS: &[ErrorSensitiveArgPair] = &[ + // If we have chain A and data directory X, and then an incoming_setup arrives with chain blanked out and data + // directory Y, we'll preserve the blank chain, but resurrect data directory X. (I'm not sure this is correct; + // perhaps if we're going to take advantage of a default chain, we should also use the default chain's data + // directory. --Dan) + ErrorSensitiveArgPair { blanked_arg: "chain", linked_arg: "data-directory" } +]; pub type SetupCluster = HashMap; @@ -110,6 +113,7 @@ impl SetupReporter for SetupReporterReal { eprintln_setup("DEFAULTS", &default_setup); eprintln_setup("EXISTING", &existing_setup); eprintln_setup("BLANKED-OUT FORMER VALUES", &blanked_out_former_values); + eprintln_setup("PREVENTION TO ERR INDUCED SETUP IMPAIRMENTS", &prevention_to_err_induced_setup_impairments); eprintln_setup("INCOMING", &incoming_setup); eprintln_setup("ALL BUT CONFIGURED", &all_but_configured); let mut error_so_far = ConfiguratorError::new(vec![]); From 7110f4fe26e71700174e72c3c111487611d1f5ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojte=CC=8Cch=20Parka=CC=81n?= Date: Thu, 25 Jan 2024 11:54:23 +0100 Subject: [PATCH 52/52] formatting, fix order of Vcls in setup_reporter, add assertion to node_configurator_standard --- node/src/daemon/setup_reporter.rs | 23 +++++++++++++------ .../node_configurator_standard.rs | 15 ++++++++++++ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/node/src/daemon/setup_reporter.rs b/node/src/daemon/setup_reporter.rs index 0bb1628c1..870bb3273 100644 --- a/node/src/daemon/setup_reporter.rs +++ b/node/src/daemon/setup_reporter.rs @@ -51,7 +51,10 @@ const ARG_PAIRS_SENSITIVE_TO_SETUP_ERRS: &[ErrorSensitiveArgPair] = &[ // directory Y, we'll preserve the blank chain, but resurrect data directory X. (I'm not sure this is correct; // perhaps if we're going to take advantage of a default chain, we should also use the default chain's data // directory. --Dan) - ErrorSensitiveArgPair { blanked_arg: "chain", linked_arg: "data-directory" } + ErrorSensitiveArgPair { + blanked_arg: "chain", + linked_arg: "data-directory", + }, ]; pub type SetupCluster = HashMap; @@ -113,7 +116,10 @@ impl SetupReporter for SetupReporterReal { eprintln_setup("DEFAULTS", &default_setup); eprintln_setup("EXISTING", &existing_setup); eprintln_setup("BLANKED-OUT FORMER VALUES", &blanked_out_former_values); - eprintln_setup("PREVENTION TO ERR INDUCED SETUP IMPAIRMENTS", &prevention_to_err_induced_setup_impairments); + eprintln_setup( + "PREVENTION TO ERR INDUCED SETUP IMPAIRMENTS", + &prevention_to_err_induced_setup_impairments, + ); eprintln_setup("INCOMING", &incoming_setup); eprintln_setup("ALL BUT CONFIGURED", &all_but_configured); let mut error_so_far = ConfiguratorError::new(vec![]); @@ -491,9 +497,6 @@ impl SetupReporterReal { if let Some(command_line) = command_line_opt.clone() { vcls.push(Box::new(CommandLineVcl::new(command_line))); } - if environment { - vcls.push(Box::new(EnvironmentVcl::new(&app))); - } if config_file { let command_line = match command_line_opt { Some(command_line) => command_line, @@ -510,6 +513,9 @@ impl SetupReporterReal { }; vcls.push(Box::new(config_file_vcl)); } + if environment { + vcls.push(Box::new(EnvironmentVcl::new(&app))); + } make_new_multi_config(&app, vcls) } @@ -1702,7 +1708,10 @@ mod tests { .write_all(b"clandestine-port = \"7788\"\n") .unwrap(); config_file.write_all(b"consuming-private-key = \"00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF\"\n").unwrap(); - config_file.write_all(b"crash-point = \"None\"\n").unwrap(); + config_file.write_all(b"crash-point = \"Error\"\n").unwrap(); + config_file + .write_all(b"db-password = \"mainnetPassword\"\n") + .unwrap(); config_file .write_all(b"dns-servers = \"5.6.7.8\"\n") .unwrap(); @@ -1716,7 +1725,7 @@ mod tests { .unwrap(); config_file.write_all(b"min-hops = \"6\"\n").unwrap(); config_file - .write_all(b"neighborhood-mode = \"zero-hop\"\n") + .write_all(b"neighborhood-mode = \"standard\"\n") .unwrap(); config_file.write_all(b"scans = \"off\"\n").unwrap(); config_file.write_all(b"rate-pack = \"2|2|2|2\"\n").unwrap(); diff --git a/node/src/node_configurator/node_configurator_standard.rs b/node/src/node_configurator/node_configurator_standard.rs index 4fa11f22c..bb930a9d1 100644 --- a/node/src/node_configurator/node_configurator_standard.rs +++ b/node/src/node_configurator/node_configurator_standard.rs @@ -1215,6 +1215,8 @@ mod tests { let env_vec_array = vec![ ("MASQ_CONFIG_FILE", "booga.toml"), + ("MASQ_CLANDESTINE_PORT", "8888"), + ("MASQ_DNS_SERVERS", "1.2.3.4"), ("MASQ_DATA_DIRECTORY", "/nonexistent/directory/home"), #[cfg(not(target_os = "windows"))] ("MASQ_REAL_USER", "9999:9999:booga"), @@ -1228,12 +1230,25 @@ mod tests { .data_dir_result(Some(data_dir.to_path_buf())); let args = ArgsBuilder::new() .param("--data-directory", current_directory.join(Path::new("generated/test/node_configurator_standard/server_initializer_collected_params_combine_vcls_properly/home")).to_string_lossy().to_string().as_str()) + .param("--clandestine-port", "1111") .param("--real-user", "1001:1001:cooga"); let args_vec: Vec = args.into(); let params = server_initializer_collected_params(&dir_wrapper, args_vec.as_slice()); let multiconfig = params.as_ref().unwrap(); + assert_eq!( + value_m!(multiconfig, "clandestine-port", String).unwrap(), + "1111".to_string() + ); + assert_eq!( + value_m!(multiconfig, "dns-servers", String).unwrap(), + "1.2.3.4".to_string() + ); + assert_eq!( + value_m!(multiconfig, "ip", String).unwrap(), + "6.6.6.6".to_string() + ); #[cfg(not(target_os = "windows"))] { assert_eq!(